1024 lines
26 KiB
C
1024 lines
26 KiB
C
/****************************************************************************
|
|
* drivers/net/slip.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/* Reference: RFC 1055 */
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/wdog.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/net/ip.h>
|
|
#include <nuttx/net/netdev.h>
|
|
|
|
#ifdef CONFIG_NET_SLIP
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* NOTE: Slip requires UART hardware handshake. If hardware handshake is
|
|
* not available with your UART, then you might try the 'slattach' option
|
|
* -L which enable "3-wire operation." That allows operation without the
|
|
* hardware handshake (but with the possibility of data overrun).
|
|
*/
|
|
|
|
/* Configuration ************************************************************/
|
|
|
|
/* The Linux slip module hard-codes its MTU size to 296 (40 bytes for the
|
|
* IP+TCP headers plus 256 bytes of data). So you might as well set
|
|
* CONFIG_NET_SLIP_PKTSIZE to 296 as well.
|
|
*
|
|
* There may be an issue with this setting, however. I see that Linux uses
|
|
* a MTU of 296 and window of 256, but actually only sends 168 bytes of data:
|
|
* 40 + 128. I believe that is to allow for the 2x worst cast packet
|
|
* expansion. Ideally we would like to advertise the 256 MSS, but restrict
|
|
* transfers to 128 bytes (possibly by modifying the tcp_mss() macro).
|
|
*/
|
|
|
|
#if CONFIG_NET_SLIP_PKTSIZE < 296
|
|
# error "CONFIG_NET_SLIP_PKTSIZE >= 296 is required"
|
|
#endif
|
|
|
|
/* Work queue support is required. */
|
|
|
|
#if !defined(CONFIG_SCHED_WORKQUEUE)
|
|
# error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
|
|
#else
|
|
|
|
/* The low priority work queue is preferred. If it is not enabled, LPWORK
|
|
* will be the same as HPWORK.
|
|
*
|
|
* NOTE: However, the network should NEVER run on the high priority work
|
|
* queue! That queue is intended only to service short back end interrupt
|
|
* processing that never suspends. Suspending the high priority work queue
|
|
* may bring the system to its knees!
|
|
*/
|
|
|
|
#define SLIPWORK LPWORK
|
|
|
|
/* CONFIG_NET_SLIP_NINTERFACES determines the number of
|
|
* physical interfaces that will be supported.
|
|
*/
|
|
|
|
#ifndef CONFIG_NET_SLIP_NINTERFACES
|
|
# define CONFIG_NET_SLIP_NINTERFACES 1
|
|
#endif
|
|
|
|
/* SLIP special character codes ********************************************/
|
|
|
|
#define SLIP_END 0300 /* Indicates end of packet */
|
|
#define SLIP_ESC 0333 /* Indicates byte stuffing */
|
|
#define SLIP_ESC_END 0334 /* ESC ESC_END means SLIP_END data byte */
|
|
#define SLIP_ESC_ESC 0335 /* ESC ESC_ESC means ESC data byte */
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The slip_driver_s encapsulates all state information for a single hardware
|
|
* interface
|
|
*/
|
|
|
|
struct slip_driver_s
|
|
{
|
|
bool bifup; /* true:ifup false:ifdown */
|
|
struct work_s irqwork; /* For deferring interrupt work */
|
|
struct work_s pollwork; /* For deferring poll work to the work queue */
|
|
struct file tty; /* TTY file */
|
|
struct pollfd pollfd; /* Polling TTY for read- or writeable */
|
|
|
|
uint8_t rxbuf[2 * CONFIG_NET_SLIP_PKTSIZE + 2];
|
|
size_t rxlen;
|
|
|
|
uint8_t txbuf[2 * CONFIG_NET_SLIP_PKTSIZE + 2];
|
|
size_t txlen;
|
|
size_t txsent;
|
|
|
|
/* This holds the information visible to the NuttX network */
|
|
|
|
struct net_driver_s dev; /* Interface understood by the network */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* Driver state structure */
|
|
|
|
static struct slip_driver_s g_slip[CONFIG_NET_SLIP_NINTERFACES];
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Common TX logic */
|
|
|
|
static void slip_transmit(FAR struct slip_driver_s *self);
|
|
static int slip_txpoll(FAR struct net_driver_s *dev);
|
|
|
|
/* Interrupt handling */
|
|
|
|
static void slip_reply(FAR struct slip_driver_s *self);
|
|
static void slip_receive(FAR struct slip_driver_s *self);
|
|
static void slip_txdone(FAR struct slip_driver_s *self);
|
|
|
|
static void slip_interrupt_work(FAR void *arg);
|
|
static void slip_pollfd_cb(FAR struct pollfd *pollfd);
|
|
static void slip_set_pollfd_events(FAR struct slip_driver_s *self,
|
|
short events);
|
|
|
|
/* NuttX callback functions */
|
|
|
|
static int slip_ifup(FAR struct net_driver_s *dev);
|
|
static int slip_ifdown(FAR struct net_driver_s *dev);
|
|
|
|
static void slip_txavail_work(FAR void *arg);
|
|
static int slip_txavail(FAR struct net_driver_s *dev);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: slip_pollfd_cb
|
|
*
|
|
* Description:
|
|
* TTY reports to be read- or writable
|
|
*
|
|
* Input Parameters:
|
|
* pollfd - Information about the event that happened.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_pollfd_cb(FAR struct pollfd *pollfd)
|
|
{
|
|
FAR struct slip_driver_s *self = (FAR struct slip_driver_s *)pollfd->arg;
|
|
|
|
DEBUGASSERT(self != NULL);
|
|
|
|
/* Schedule to perform the processing on the worker thread. */
|
|
|
|
if (work_available(&self->irqwork))
|
|
{
|
|
work_queue(SLIPWORK, &self->irqwork, slip_interrupt_work, self, 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_set_pollfd_events
|
|
*
|
|
* Description:
|
|
* Setup TTY to report poll events (such as POLLIN and POLLOUT)
|
|
*
|
|
* Input Parameters:
|
|
* self - The SLIP interface to register for poll events
|
|
* events - The poll events to request reporting for
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_set_pollfd_events(FAR struct slip_driver_s *self,
|
|
short events)
|
|
{
|
|
int ret;
|
|
|
|
/* Teardown any potentially pending poll, if applicable */
|
|
|
|
if (self->pollfd.events != 0)
|
|
{
|
|
ret = file_poll(&self->tty, &self->pollfd, false);
|
|
|
|
if (ret != OK)
|
|
{
|
|
nerr("file_poll(false) failed: %d\n", ret);
|
|
}
|
|
}
|
|
|
|
memset(&self->pollfd, 0, sizeof(self->pollfd));
|
|
|
|
/* Setup requested poll, if applicable */
|
|
|
|
if (events != 0)
|
|
{
|
|
self->pollfd.arg = self;
|
|
self->pollfd.cb = slip_pollfd_cb;
|
|
self->pollfd.events = events;
|
|
self->pollfd.revents = 0;
|
|
self->pollfd.priv = NULL;
|
|
|
|
ret = file_poll(&self->tty, &self->pollfd, true);
|
|
|
|
if (ret != OK)
|
|
{
|
|
nerr("file_poll(true) failed: %d\n", ret);
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_transmit
|
|
*
|
|
* Description:
|
|
* Start hardware transmission. Called either from the txdone interrupt
|
|
* handling or from watchdog based polling.
|
|
*
|
|
* Input Parameters:
|
|
* self - Reference to the driver state structure
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_transmit(FAR struct slip_driver_s *self)
|
|
{
|
|
ssize_t ssz;
|
|
uint8_t *p;
|
|
|
|
DEBUGASSERT(self->dev.d_len > 0);
|
|
|
|
/* Verify that the hardware is ready to send another packet. If we get
|
|
* here, then we are committed to sending a packet; Higher level logic
|
|
* must have assured that there is no transmission in progress.
|
|
*/
|
|
|
|
if (self->txlen > 0)
|
|
{
|
|
int i;
|
|
|
|
/* Transmission of previous packet is still pending. This might happen
|
|
* in the 'slip_receive' -> 'slip_reply' -> 'slip_transmit' case. Try
|
|
* to forward pending packet into UART's transmit buffer. Timeout on
|
|
* packet if not forwarded within a second.
|
|
*/
|
|
|
|
for (i = 0; (i < 10) && (self->txsent != self->txlen); )
|
|
{
|
|
ssz = file_write(&self->tty,
|
|
&self->txbuf[self->txsent],
|
|
self->txlen - self->txsent);
|
|
if (ssz <= 0)
|
|
{
|
|
nxsig_usleep(10000);
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
self->txsent += ssz;
|
|
}
|
|
|
|
if (self->txsent == self->txlen)
|
|
{
|
|
slip_txdone(self);
|
|
}
|
|
else
|
|
{
|
|
NETDEV_TXTIMEOUTS(&self->dev);
|
|
}
|
|
}
|
|
|
|
self->txlen = 0;
|
|
self->txsent = 0;
|
|
|
|
/* Send an initial END character to flush out any data that may have
|
|
* accumulated in the receiver due to line noise
|
|
*/
|
|
|
|
self->txbuf[self->txlen++] = SLIP_END;
|
|
|
|
/* Now copy the I/O buffer into self->txbuf */
|
|
|
|
for (unsigned int bytesread = 0; bytesread < self->dev.d_len; )
|
|
{
|
|
unsigned int chunk_sz = sizeof(self->txbuf) - self->txlen;
|
|
int copied;
|
|
|
|
if (self->dev.d_len - bytesread < chunk_sz)
|
|
{
|
|
chunk_sz = self->dev.d_len - bytesread;
|
|
}
|
|
|
|
copied = iob_copyout(&self->txbuf[self->txlen],
|
|
self->dev.d_iob,
|
|
chunk_sz,
|
|
bytesread);
|
|
if (copied <= 0)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
bytesread += (unsigned int)copied;
|
|
self->txlen += (size_t)copied;
|
|
}
|
|
|
|
/* SLIP encode self->txbuf. First escape the ESC bytes. */
|
|
|
|
for (p = memchr(&self->txbuf[1], SLIP_ESC, self->txlen - 1);
|
|
p != NULL;
|
|
p = memchr(p, SLIP_ESC, self->txlen - 1))
|
|
{
|
|
ssize_t postfix_len = self->txlen - (p - self->txbuf) - 1;
|
|
|
|
if (self->txlen >= sizeof(self->txbuf))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if (postfix_len > 0)
|
|
{
|
|
memmove(p + 2, p + 1, postfix_len);
|
|
}
|
|
|
|
p++;
|
|
*p = SLIP_ESC_ESC;
|
|
self->txlen++;
|
|
}
|
|
|
|
/* SLIP encode self->txbuf. Then escape the END bytes. */
|
|
|
|
for (p = memchr(&self->txbuf[1], SLIP_END, self->txlen - 1);
|
|
p != NULL;
|
|
p = memchr(p, SLIP_END, self->txlen - 1))
|
|
{
|
|
ssize_t postfix_len = self->txlen - (p - self->txbuf) - 1;
|
|
|
|
if (self->txlen >= sizeof(self->txbuf))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if (postfix_len > 0)
|
|
{
|
|
memmove(p + 2, p + 1, postfix_len);
|
|
}
|
|
|
|
*p = SLIP_ESC;
|
|
p++;
|
|
*p = SLIP_ESC_END;
|
|
self->txlen++;
|
|
}
|
|
|
|
/* Append the END token */
|
|
|
|
if (self->txlen >= sizeof(self->txbuf))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
self->txbuf[self->txlen++] = SLIP_END;
|
|
|
|
/* Increment statistics */
|
|
|
|
NETDEV_TXPACKETS(&self->dev);
|
|
|
|
/* Try to send packet */
|
|
|
|
ssz = file_write(&self->tty, self->txbuf, self->txlen);
|
|
|
|
if (ssz > 0)
|
|
{
|
|
self->txsent = (size_t)ssz;
|
|
}
|
|
else
|
|
{
|
|
self->txsent = 0;
|
|
}
|
|
|
|
if (self->txsent == self->txlen)
|
|
{
|
|
/* Complete packet went out at first try. */
|
|
|
|
slip_txdone(self);
|
|
}
|
|
|
|
return;
|
|
|
|
error:
|
|
|
|
/* Drop the packet and reset the receiver logic. */
|
|
|
|
self->txlen = 0;
|
|
self->txsent = 0;
|
|
|
|
NETDEV_TXERRORS(&self->dev);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_txpoll
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if the network has any outgoing
|
|
* packets ready to send. This is a callback from devif_poll().
|
|
* devif_poll() may be called:
|
|
*
|
|
* 1. When the preceding TX packet send is complete,
|
|
* 2. When the preceding TX packet send timesout and the interface is reset
|
|
* 3. During normal TX polling
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int slip_txpoll(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct slip_driver_s *self =
|
|
(FAR struct slip_driver_s *)dev->d_private;
|
|
|
|
/* Send the packet */
|
|
|
|
slip_transmit(self);
|
|
|
|
/* If zero is returned, the polling will continue until all connections
|
|
* have been examined. We return -EBUSY if there is still transmission
|
|
* data pending in the TTY's buffer.
|
|
*/
|
|
|
|
return (self->txlen > 0) ? -EBUSY : OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_reply
|
|
*
|
|
* Description:
|
|
* After a packet has been received and dispatched to the network, it
|
|
* may return return with an outgoing packet. This function checks for
|
|
* that case and performs the transmission if necessary.
|
|
*
|
|
* Input Parameters:
|
|
* self - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_reply(struct slip_driver_s *self)
|
|
{
|
|
/* If the packet dispatch resulted in data that should be sent out on the
|
|
* network, the field d_len will set to a value > 0.
|
|
*/
|
|
|
|
if (self->dev.d_len > 0)
|
|
{
|
|
/* And send the packet */
|
|
|
|
slip_transmit(self);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_receive
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating the availability of a new RX packet
|
|
*
|
|
* Input Parameters:
|
|
* self - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_receive(FAR struct slip_driver_s *self)
|
|
{
|
|
FAR struct net_driver_s *dev = &self->dev;
|
|
FAR struct iob_s *iob;
|
|
FAR uint8_t *p;
|
|
FAR uint8_t *pend;
|
|
size_t remaining;
|
|
size_t copied;
|
|
int ret;
|
|
|
|
/* Drop potential prefix SLIP_ENDs */
|
|
|
|
while ((self->rxbuf[0] == SLIP_END) && (self->rxlen > 0))
|
|
{
|
|
self->rxlen--;
|
|
memmove(&self->rxbuf[0], &self->rxbuf[1], self->rxlen);
|
|
}
|
|
|
|
/* Find end of packet */
|
|
|
|
pend = memchr(self->rxbuf, SLIP_END, self->rxlen);
|
|
|
|
if (pend == NULL)
|
|
{
|
|
/* No complete packet present. Let's wait for more bytes to arrive. */
|
|
|
|
if (self->rxlen == sizeof(self->rxbuf))
|
|
{
|
|
/* Purge receive buffer overflow due to overflow. */
|
|
|
|
NETDEV_RXERRORS(&self->dev);
|
|
self->rxlen = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
p = self->rxbuf;
|
|
remaining = pend - p;
|
|
copied = 0;
|
|
iob = iob_alloc(false);
|
|
iob_reserve(iob, CONFIG_NET_LL_GUARDSIZE);
|
|
|
|
while (remaining)
|
|
{
|
|
uint8_t *pesc = memchr(p, SLIP_ESC, remaining);
|
|
|
|
if (pesc != NULL)
|
|
{
|
|
unsigned int prefix_len = (unsigned int)(pesc - p);
|
|
|
|
if (prefix_len > 0)
|
|
{
|
|
ret = iob_copyin(iob, p, prefix_len, copied, false);
|
|
|
|
DEBUGASSERT(ret >= 0);
|
|
DEBUGASSERT((unsigned int)ret == prefix_len);
|
|
|
|
copied += prefix_len;
|
|
remaining -= prefix_len;
|
|
}
|
|
|
|
p = pesc + 1;
|
|
remaining--;
|
|
|
|
switch (*p)
|
|
{
|
|
case SLIP_ESC_END:
|
|
*p = SLIP_END;
|
|
break;
|
|
|
|
case SLIP_ESC_ESC:
|
|
*p = SLIP_ESC;
|
|
break;
|
|
|
|
default:
|
|
|
|
/* SLIP protocol error */
|
|
|
|
goto error;
|
|
}
|
|
|
|
ret = iob_copyin(iob, p, 1, copied, false);
|
|
DEBUGASSERT(ret == 1);
|
|
p++;
|
|
copied++;
|
|
remaining--;
|
|
}
|
|
else
|
|
{
|
|
ret = iob_copyin(iob, p, remaining, copied, false);
|
|
DEBUGASSERT(ret >= 0);
|
|
DEBUGASSERT((unsigned int)ret == remaining);
|
|
p += remaining;
|
|
copied += remaining;
|
|
remaining = 0;
|
|
}
|
|
}
|
|
|
|
/* Move remaining bytes in rxbuf to the front */
|
|
|
|
DEBUGASSERT((pend - self->rxbuf) <= self->rxlen);
|
|
self->rxlen -= (pend - self->rxbuf);
|
|
memmove(self->rxbuf, pend, self->rxlen);
|
|
|
|
/* Handle the IP input. */
|
|
|
|
netdev_iob_replace(&self->dev, iob);
|
|
iob = NULL;
|
|
|
|
NETDEV_RXPACKETS(&self->dev);
|
|
|
|
/* All packets are assumed to be IP packets (we don't have a choice..
|
|
* there is no Ethernet header containing the EtherType). So pass the
|
|
* received packet on for IP processing -- but only if it is big
|
|
* enough to hold an IP header.
|
|
*/
|
|
|
|
if ((IPv4BUF->vhl & IP_VERSION_MASK) == IPv4_VERSION)
|
|
{
|
|
NETDEV_RXIPV4(&self->dev);
|
|
|
|
ipv4_input(&self->dev);
|
|
|
|
slip_reply(self);
|
|
}
|
|
else
|
|
{
|
|
NETDEV_RXDROPPED(&self->dev);
|
|
}
|
|
|
|
return;
|
|
|
|
error:
|
|
|
|
NETDEV_RXERRORS(&self->dev);
|
|
|
|
if (iob)
|
|
{
|
|
iob_free_chain(iob);
|
|
iob = NULL;
|
|
}
|
|
|
|
/* Move remaining bytes in rxbuf to the front */
|
|
|
|
DEBUGASSERT((pend - self->rxbuf) <= self->rxlen);
|
|
self->rxlen -= (pend - self->rxbuf);
|
|
memmove(self->rxbuf, pend, self->rxlen);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_txdone
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating that the last TX packet(s) is done
|
|
*
|
|
* Input Parameters:
|
|
* self - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_txdone(FAR struct slip_driver_s *self)
|
|
{
|
|
/* Update statistics */
|
|
|
|
self->txlen = 0;
|
|
self->txsent = 0;
|
|
|
|
NETDEV_TXDONE(&self->dev);
|
|
|
|
/* Poll the network for new TX data */
|
|
|
|
if (work_available(&self->pollwork))
|
|
{
|
|
work_queue(SLIPWORK, &self->pollwork, slip_txavail_work, self, 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_interrupt_work
|
|
*
|
|
* Description:
|
|
* Perform interrupt related work from the worker thread
|
|
*
|
|
* Input Parameters:
|
|
* arg - The argument passed when work_queue() was called.
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
* Runs on a worker thread.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_interrupt_work(FAR void *arg)
|
|
{
|
|
FAR struct slip_driver_s *self = (FAR struct slip_driver_s *)arg;
|
|
ssize_t ssz;
|
|
|
|
if (!self->bifup)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Lock the network and serialize driver operations if necessary.
|
|
* NOTE: Serialization is only required in the case where the driver work
|
|
* is performed on an LP worker thread and where more than one LP worker
|
|
* thread has been configured.
|
|
*/
|
|
|
|
net_lock();
|
|
|
|
/* Process pending Ethernet interrupts */
|
|
|
|
/* Get and clear interrupt status bits */
|
|
|
|
/* Handle interrupts according to status bit settings */
|
|
|
|
if (self->rxlen < sizeof(self->rxbuf))
|
|
{
|
|
ssz = file_read(&self->tty,
|
|
&self->rxbuf[self->rxlen],
|
|
sizeof(self->rxbuf) - self->rxlen);
|
|
if (ssz > 0)
|
|
{
|
|
self->rxlen += (size_t)ssz;
|
|
}
|
|
}
|
|
|
|
if (self->txsent < self->txlen)
|
|
{
|
|
ssz = file_write(&self->tty,
|
|
&self->txbuf[self->txsent],
|
|
self->txlen - self->txsent);
|
|
if (ssz > 0)
|
|
{
|
|
self->txsent += (size_t)ssz;
|
|
}
|
|
}
|
|
|
|
/* Check if we received an incoming packet, if so, call slip_receive() */
|
|
|
|
if (self->rxlen == sizeof(self->rxbuf) ||
|
|
memchr(self->rxbuf, SLIP_END, self->rxlen))
|
|
{
|
|
slip_receive(self);
|
|
}
|
|
|
|
/* Check if a packet transmission just completed. If so, call skel_txdone.
|
|
* This may disable further Tx interrupts if there are no pending
|
|
* transmissions.
|
|
*/
|
|
|
|
if (self->txlen > 0 && self->txsent == self->txlen)
|
|
{
|
|
slip_txdone(self);
|
|
}
|
|
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_ifup
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Bring up the Ethernet interface when an IP address is
|
|
* provided
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int slip_ifup(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct slip_driver_s *self =
|
|
(FAR struct slip_driver_s *)dev->d_private;
|
|
|
|
ninfo("Bringing up: %u.%u.%u.%u\n",
|
|
ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr),
|
|
ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr));
|
|
|
|
/* Enable POLLIN and POLLOUT events on the TTY */
|
|
|
|
slip_set_pollfd_events(self, POLLIN | POLLOUT);
|
|
|
|
/* Mark the device "up" */
|
|
|
|
self->bifup = true;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_ifdown
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Stop the interface.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int slip_ifdown(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct slip_driver_s *self =
|
|
(FAR struct slip_driver_s *)dev->d_private;
|
|
|
|
/* Disable the Ethernet interrupt */
|
|
|
|
slip_set_pollfd_events(self, 0);
|
|
|
|
/* Mark the device "down" */
|
|
|
|
self->bifup = false;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_txavail_work
|
|
*
|
|
* Description:
|
|
* Perform an out-of-cycle poll on the worker thread.
|
|
*
|
|
* Input Parameters:
|
|
* arg - Reference to the NuttX driver state structure (cast to void*)
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Runs on a work queue thread.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void slip_txavail_work(FAR void *arg)
|
|
{
|
|
FAR struct slip_driver_s *self = (FAR struct slip_driver_s *)arg;
|
|
|
|
/* Lock the network and serialize driver operations if necessary.
|
|
* NOTE: Serialization is only required in the case where the driver work
|
|
* is performed on an LP worker thread and where more than one LP worker
|
|
* thread has been configured.
|
|
*/
|
|
|
|
net_lock();
|
|
|
|
/* Ignore the notification if the interface is not yet up */
|
|
|
|
if (self->bifup)
|
|
{
|
|
/* Check if there is room in the hardware to hold another packet. */
|
|
|
|
if (self->txlen == 0)
|
|
{
|
|
/* If so, then poll the network for new XMIT data */
|
|
|
|
self->dev.d_buf = NULL;
|
|
|
|
devif_poll(&self->dev, slip_txpoll);
|
|
}
|
|
}
|
|
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: slip_txavail
|
|
*
|
|
* Description:
|
|
* Driver callback invoked when new TX data is available. This is a
|
|
* stimulus perform an out-of-cycle poll and, thereby, reduce the TX
|
|
* latency.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The network is locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int slip_txavail(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct slip_driver_s *self =
|
|
(FAR struct slip_driver_s *)dev->d_private;
|
|
|
|
/* Is our single work structure available? It may not be if there are
|
|
* pending interrupt actions and we will have to ignore the Tx
|
|
* availability action.
|
|
*/
|
|
|
|
if (work_available(&self->pollwork))
|
|
{
|
|
/* Schedule to serialize the poll on the worker thread. */
|
|
|
|
work_queue(SLIPWORK, &self->pollwork, slip_txavail_work, self, 0);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: slip_initialize
|
|
*
|
|
* Description:
|
|
* Instantiate a SLIP network interface.
|
|
*
|
|
* Input Parameters:
|
|
* intf - In the case where there are multiple SLIP interfaces, this
|
|
* value identifies which is to be initialized. The number of
|
|
* possible SLIP interfaces is determined by
|
|
* devname - This is the path to the serial device that will support SLIP.
|
|
* For example, this might be "/dev/ttyS1"
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int slip_initialize(int intf, FAR const char *devname)
|
|
{
|
|
FAR struct slip_driver_s *self;
|
|
int ret;
|
|
|
|
/* Get the interface structure associated with this interface number. */
|
|
|
|
DEBUGASSERT(intf < CONFIG_NET_SLIP_NINTERFACES);
|
|
self = &g_slip[intf];
|
|
|
|
/* Initialize the driver structure */
|
|
|
|
memset(self, 0, sizeof(struct slip_driver_s));
|
|
self->dev.d_ifup = slip_ifup; /* I/F up (new IP address) callback */
|
|
self->dev.d_ifdown = slip_ifdown; /* I/F down callback */
|
|
self->dev.d_txavail = slip_txavail; /* New TX data callback */
|
|
self->dev.d_private = self; /* Used to recover SLIP I/F instance */
|
|
|
|
ret = file_open(&self->tty, devname, O_RDWR | O_NONBLOCK | O_CLOEXEC);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: Failed to open %s: %d\n", devname, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Put the interface in the down state. This usually amounts to resetting
|
|
* the device and/or calling slip_ifdown().
|
|
*/
|
|
|
|
slip_set_pollfd_events(self, 0);
|
|
self->bifup = false;
|
|
|
|
/* Register the device with the OS so that socket IOCTLs can be performed */
|
|
|
|
netdev_register(&self->dev, NET_LL_SLIP);
|
|
|
|
return OK;
|
|
}
|
|
|
|
#endif /* !defined(CONFIG_SCHED_WORKQUEUE) */
|
|
|
|
#endif /* CONFIG_NET_SLIP */
|