incubator-nuttx/drivers/net/slip.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 */