David van Moolenbroek ef8d499e2d Add lwip: a new lwIP-based TCP/IP service
This commit adds a new TCP/IP service to MINIX 3.  As its core, the
service uses the lwIP TCP/IP stack for maintenance reasons.  The
service aims to be compatible with NetBSD userland, including its
low-level network management utilities.  It also aims to support
modern features such as IPv6.  In summary, the new LWIP service has
support for the following main features:

- TCP, UDP, RAW sockets with mostly standard BSD API semantics;
- IPv6 support: host mode (complete) and router mode (partial);
- most of the standard BSD API socket options (SO_);
- all of the standard BSD API message flags (MSG_);
- the most used protocol-specific socket and control options;
- a default loopback interface and the ability to create one more;
- configuration-free ethernet interfaces and driver tracking;
- queuing and multiple concurrent requests to each ethernet driver;
- standard ioctl(2)-based BSD interface management;
- radix tree backed, destination-based routing;
- routing sockets for standard BSD route reporting and management;
- multicast traffic and multicast group membership tracking;
- Berkeley Packet Filter (BPF) devices;
- standard and custom sysctl(7) nodes for many internals;
- a slab allocation based, hybrid static/dynamic memory pool model.

Many of its modules come with fairly elaborate comments that cover
many aspects of what is going on.  The service is primarily a socket
driver built on top of the libsockdriver library, but for BPF devices
it is at the same time also a character driver.

Change-Id: Ib0c02736234b21143915e5fcc0fda8fe408f046f
2017-04-30 13:16:03 +00:00

1020 lines
29 KiB
C

/* LWIP service - ndev.c - network driver communication module */
/*
* There is almost a one-to-one mapping between network device driver (ndev)
* objects and ethernet interface (ethif) objects, with as major difference
* that there may be an ndev object but not an ethif object for a driver that
* is known to exist but has not yet replied to our initialization request:
* without the information from the initialization request, there is no point
* creating an ethif object just yet, while we do need to track the driver
* process. TODO: it would be nice if unanswered init requests timed out and
* caused the removal of the ndev object after a while.
*
* Beyond that, this module aims to abstract away the low-level details of
* communication, memory grants, and driver restarts. Driver restarts are not
* fully transparent to the ethif module because it needs to reinitialize
* driver state only it knows about after a restart. Drivers that are in the
* process of restarting and therefore not operational are said to be disabled.
*
* From this module's point of view, a network driver is one of two states:
* initializing, where it has yet to respond to our initialization request, and
* active, where it is expected to accept and respond to all other requests.
* This module does not keep track of higher-level states and rules however;
* that is left to the ethif layer on one side, and the network driver itself
* on the other side. One important example is the interface being up or down:
* the ndev layer will happily forward send and receive requests when the
* interface is down, but these requests will be (resp.) dropped and rejected
* by the network driver in that state, and will not be generated by the ethif
* layer when the layer is down. Imposing barriers between configure and send
* requests is also left to the other parties.
*
* In this module, each active network driver has a send queue and a receive
* queue. The send queue is shared for packet send requests and configuration
* change requests. The receive queue is used for packet receive requests
* only. Each queue has a maximum depth, which is the minimum of a value
* provided by the network driver during initialization and local restrictions.
* These local restrictions are different for the two queue types: the receive
* queue is always bounded to a hardcoded value, while the send queue has a
* guaranteed minimum depth but may use up to the driver's maximum using spare
* entries. For both, a minimum depth is always available, since it is not
* possible to cancel individual send or receive requests after they have been
* sent to a particular driver. This does mean that we necessarily waste a
* large number of request structures in the common case.
*
* The general API model does not support the notion of blocking calls. While
* it would make sense to retrieve e.g. error statistics from the driver only
* when requested by userland, implementing this without threads would be
* seriously complicated, because such requests can have many origins (ioctl,
* PF_ROUTE message, sysctl). Instead, we rely on drivers updating us with the
* latest information on everything at all times, so that we can hand over a
* cached copy of (e.g.) those error statistics right away. We provide a means
* for drivers to perform rate limiting of such status updates (to prevent
* overflowing asynsend queues), by replying to these status messages. That
* means that there is a request-response combo going in the opposite direction
* of the regular messages.
*
* TODO: in the future we will want to obtain the list of supported media modes
* (IFM_) from drivers, so that userland can view the list. Given the above
* model, the easiest way would be to obtain a copy of the full list, limited
* to a configured number of entries, at driver initialization time. This
* would require that the initialization request also involve a memory grant.
*
* If necessary, it would not be too much work to split off this module into
* its own libndev library. For now, there is no point in doing this and the
* tighter coupling allows us to optimize just a little but (see pbuf usage).
*/
#include "lwip.h"
#include "ndev.h"
#include "ethif.h"
#define LABEL_MAX 16 /* FIXME: this should be in a system header */
#define NDEV_SENDQ 2 /* minimum guaranteed send queue depth */
#define NDEV_RECVQ 2 /* guaranteed receive queue depth */
#define NREQ_SPARES 8 /* spare send queue (request) objects */
#define NR_NREQ ((NDEV_SENDQ + NDEV_RECVQ) * NR_NDEV + NREQ_SPARES)
static SIMPLEQ_HEAD(, ndev_req) nreq_freelist;
static struct ndev_req {
SIMPLEQ_ENTRY(ndev_req) nreq_next; /* next request in queue */
int nreq_type; /* type of request message */
cp_grant_id_t nreq_grant[NDEV_IOV_MAX]; /* grants for request */
} nreq_array[NR_NREQ];
static unsigned int nreq_spares; /* number of free spare objects */
struct ndev_queue {
uint32_t nq_head; /* ID of oldest pending request */
uint8_t nq_count; /* current nr of pending requests */
uint8_t nq_max; /* maximum nr of pending requests */
SIMPLEQ_HEAD(, ndev_req) nq_req; /* queue of pending requests */
};
static struct ndev {
endpoint_t ndev_endpt; /* driver endpoint */
char ndev_label[LABEL_MAX]; /* driver label */
struct ethif *ndev_ethif; /* ethif object, or NULL if init'ing */
struct ndev_queue ndev_sendq; /* packet send and configure queue */
struct ndev_queue ndev_recvq; /* packet receive queue */
} ndev_array[NR_NDEV];
static ndev_id_t ndev_max; /* highest driver count ever seen */
/*
* This macro checks whether the network driver is active rather than
* initializing. See above for more information.
*/
#define NDEV_ACTIVE(ndev) ((ndev)->ndev_sendq.nq_max > 0)
static int ndev_pending; /* number of initializing drivers */
/* The CTL_MINIX MINIX_LWIP "drivers" subtree. Dynamically numbered. */
static struct rmib_node minix_lwip_drivers_table[] = {
RMIB_INTPTR(RMIB_RO, &ndev_pending, "pending",
"Number of drivers currently initializing"),
};
static struct rmib_node minix_lwip_drivers_node =
RMIB_NODE(RMIB_RO, minix_lwip_drivers_table, "drivers",
"Network driver information");
/*
* Initialize the network driver communication module.
*/
void
ndev_init(void)
{
unsigned int slot;
int r;
/* Initialize local variables. */
ndev_max = 0;
SIMPLEQ_INIT(&nreq_freelist);
for (slot = 0; slot < __arraycount(nreq_array); slot++)
SIMPLEQ_INSERT_TAIL(&nreq_freelist, &nreq_array[slot],
nreq_next);
nreq_spares = NREQ_SPARES;
/*
* Preallocate the total number of grants that we could possibly need
* concurrently. Even though it is extremely unlikely that we will
* ever need that many grants in practice, the alternative is runtime
* dynamic memory (re)allocation which is something we prefer to avoid
* altogether. At time of writing, we end up preallocating 320 grants
* using up a total of a bit under 9KB of memory.
*/
cpf_prealloc(NR_NREQ * NDEV_IOV_MAX);
/*
* Not needed, just for ultimate safety: start off all queues with
* wildly different request sequence numbers, to minimize the chance
* that any two replies will ever be confused.
*/
for (slot = 0; slot < __arraycount(ndev_array); slot++) {
ndev_array[slot].ndev_sendq.nq_head = slot << 21;
ndev_array[slot].ndev_recvq.nq_head = (slot * 2 + 1) << 20;
}
/* Subscribe to Data Store (DS) events from network drivers. */
if ((r = ds_subscribe("drv\\.net\\..*",
DSF_INITIAL | DSF_OVERWRITE)) != OK)
panic("unable to subscribe to driver events: %d", r);
/*
* Keep track of how many drivers are in "pending" state, which means
* that they have not yet replied to our initialization request.
*/
ndev_pending = 0;
/* Register the minix.lwip.drivers subtree. */
mibtree_register_lwip(&minix_lwip_drivers_node);
}
/*
* Initialize a queue for first use.
*/
static void
ndev_queue_init(struct ndev_queue * nq)
{
/*
* Only ever increase sequence numbers, to minimize the chance that
* two (e.g. from different driver instances) happen to be the same.
*/
nq->nq_head++;
nq->nq_count = 0;
nq->nq_max = 0;
SIMPLEQ_INIT(&nq->nq_req);
}
/*
* Advance the given request queue, freeing up the request at the head of the
* queue including any grants in use for it.
*/
static void
ndev_queue_advance(struct ndev_queue * nq)
{
struct ndev_req * nreq;
cp_grant_id_t grant;
unsigned int i;
nreq = SIMPLEQ_FIRST(&nq->nq_req);
for (i = 0; i < __arraycount(nreq->nreq_grant); i++) {
grant = nreq->nreq_grant[i];
if (!GRANT_VALID(grant))
break;
/* TODO: make the safecopies code stop using errno. */
if (cpf_revoke(grant) != 0)
panic("unable to revoke grant: %d", -errno);
}
if (nreq->nreq_type != NDEV_RECV && nq->nq_count > NDEV_SENDQ) {
nreq_spares++;
assert(nreq_spares <= NREQ_SPARES);
}
SIMPLEQ_REMOVE_HEAD(&nq->nq_req, nreq_next);
SIMPLEQ_INSERT_HEAD(&nreq_freelist, nreq, nreq_next);
nq->nq_head++;
nq->nq_count--;
}
/*
* Clear any outstanding requests from the given queue and reset it to a
* pre-initialization state.
*/
static void
ndev_queue_reset(struct ndev_queue * nq)
{
while (nq->nq_count > 0) {
assert(!SIMPLEQ_EMPTY(&nq->nq_req));
ndev_queue_advance(nq);
}
nq->nq_max = 0;
}
/*
* Obtain a request object for use in a new request. Return the request
* object, with its request type field set to 'type', and with the request
* sequence ID returned in 'seq'. Return NULL if no request objects are
* available for the given request type. If the caller does send off the
* request, a call to ndev_queue_add() must follow immediately after. If the
* caller fails to send off the request for other reasons, it need not do
* anything: this function does not perform any actions that need to be undone.
*/
static struct ndev_req *
ndev_queue_get(struct ndev_queue * nq, int type, uint32_t * seq)
{
struct ndev_req *nreq;
/* Has the hard queue depth limit been reached? */
if (nq->nq_count == nq->nq_max)
return NULL;
/*
* For send requests, we may use request objects from a shared "spares"
* pool, if available.
*/
if (type != NDEV_RECV && nq->nq_count >= NDEV_SENDQ &&
nreq_spares == 0)
return NULL;
assert(!SIMPLEQ_EMPTY(&nreq_freelist));
nreq = SIMPLEQ_FIRST(&nreq_freelist);
nreq->nreq_type = type;
*seq = nq->nq_head + nq->nq_count;
return nreq;
}
/*
* Add a successfully sent request to the given queue. The request must have
* been obtained using ndev_queue_get() directly before the call to this
* function. This function never fails.
*/
static void
ndev_queue_add(struct ndev_queue * nq, struct ndev_req * nreq)
{
if (nreq->nreq_type != NDEV_RECV && nq->nq_count >= NDEV_SENDQ) {
assert(nreq_spares > 0);
nreq_spares--;
}
SIMPLEQ_REMOVE_HEAD(&nreq_freelist, nreq_next);
SIMPLEQ_INSERT_TAIL(&nq->nq_req, nreq, nreq_next);
nq->nq_count++;
}
/*
* Remove the head of the given request queue, but only if it matches the given
* request type and sequence ID. Return TRUE if the head was indeed removed,
* or FALSE if the head of the request queue (if any) did not match the given
* type and/or sequence ID.
*/
static int
ndev_queue_remove(struct ndev_queue * nq, int type, uint32_t seq)
{
struct ndev_req *nreq;
if (nq->nq_count < 1 || nq->nq_head != seq)
return FALSE;
assert(!SIMPLEQ_EMPTY(&nq->nq_req));
nreq = SIMPLEQ_FIRST(&nq->nq_req);
if (nreq->nreq_type != type)
return FALSE;
ndev_queue_advance(nq);
return TRUE;
}
/*
* Send an initialization request to a driver. If this is a new driver, the
* ethif module does not get to know about the driver until it answers to this
* request, as the ethif module needs much of what the reply contains. On the
* other hand, if this is a restarted driver, it will stay disabled until the
* init reply comes in.
*/
static void
ndev_send_init(struct ndev * ndev)
{
message m;
int r;
memset(&m, 0, sizeof(m));
m.m_type = NDEV_INIT;
m.m_ndev_netdriver_init.id = ndev->ndev_sendq.nq_head;
if ((r = asynsend3(ndev->ndev_endpt, &m, AMF_NOREPLY)) != OK)
panic("asynsend to driver failed: %d", r);
}
/*
* A network device driver has been started or restarted.
*/
static void
ndev_up(const char * label, endpoint_t endpt)
{
static int reported = FALSE;
struct ndev *ndev;
ndev_id_t slot;
/*
* First see if we already had an entry for this driver. If so, it has
* been restarted, and we need to report it as not running to ethif.
*/
ndev = NULL;
for (slot = 0; slot < ndev_max; slot++) {
if (ndev_array[slot].ndev_endpt == NONE) {
if (ndev == NULL)
ndev = &ndev_array[slot];
continue;
}
if (!strcmp(ndev_array[slot].ndev_label, label)) {
/* Cancel any ongoing requests. */
ndev_queue_reset(&ndev_array[slot].ndev_sendq);
ndev_queue_reset(&ndev_array[slot].ndev_recvq);
if (ndev_array[slot].ndev_ethif != NULL) {
ethif_disable(ndev_array[slot].ndev_ethif);
ndev_pending++;
}
ndev_array[slot].ndev_endpt = endpt;
/* Attempt to resume communication. */
ndev_send_init(&ndev_array[slot]);
return;
}
}
if (ndev == NULL) {
/*
* If there is no free slot for this driver in our table, we
* necessarily have to ignore the driver altogether. We report
* such cases once, so that the user can recompile if desired.
*/
if (ndev_max == __arraycount(ndev_array)) {
if (!reported) {
printf("LWIP: not enough ndev slots!\n");
reported = TRUE;
}
return;
}
ndev = &ndev_array[ndev_max++];
}
/* Initialize the slot. */
ndev->ndev_endpt = endpt;
strlcpy(ndev->ndev_label, label, sizeof(ndev->ndev_label));
ndev->ndev_ethif = NULL;
ndev_queue_init(&ndev->ndev_sendq);
ndev_queue_init(&ndev->ndev_recvq);
ndev_send_init(ndev);
ndev_pending++;
}
/*
* A network device driver has been terminated.
*/
static void
ndev_down(struct ndev * ndev)
{
/* Cancel any ongoing requests. */
ndev_queue_reset(&ndev->ndev_sendq);
ndev_queue_reset(&ndev->ndev_recvq);
/*
* If this ndev object had a corresponding ethif object, tell the ethif
* layer that the device is really gone now.
*/
if (ndev->ndev_ethif != NULL)
ethif_remove(ndev->ndev_ethif);
else
ndev_pending--;
/* Remove the driver from our own administration. */
ndev->ndev_endpt = NONE;
while (ndev_max > 0 && ndev_array[ndev_max - 1].ndev_endpt == NONE)
ndev_max--;
}
/*
* The DS service has notified us of changes to our subscriptions. That means
* that network drivers may have been started, restarted, and/or shut down.
* Find out what has changed, and act accordingly.
*/
void
ndev_check(void)
{
static const char *prefix = "drv.net.";
char key[DS_MAX_KEYLEN], *label;
size_t prefixlen;
endpoint_t endpt;
uint32_t val;
ndev_id_t slot;
int r;
prefixlen = strlen(prefix);
/* Check whether any drivers have been (re)started. */
while ((r = ds_check(key, NULL, &endpt)) == OK) {
if (strncmp(key, prefix, prefixlen) != 0 || endpt == NONE)
continue;
if (ds_retrieve_u32(key, &val) != OK || val != DS_DRIVER_UP)
continue;
label = &key[prefixlen];
if (label[0] == '\0' || memchr(label, '\0', LABEL_MAX) == NULL)
continue;
ndev_up(label, endpt);
}
if (r != ENOENT)
printf("LWIP: DS check failed (%d)\n", r);
/*
* Check whether the drivers we currently know about are still up. The
* ones that are not are really gone. It is no problem that we recheck
* any drivers that have just been reported by ds_check() above.
* However, we cannot check the same key: while the driver is being
* restarted, its driver status is already gone from DS. Instead, see
* if there is still an entry for its label, as that entry remains in
* existence during the restart. The associated endpoint may still
* change however, so do not check that part: in such cases we will get
* a driver-up announcement later anyway.
*/
for (slot = 0; slot < ndev_max; slot++) {
if (ndev_array[slot].ndev_endpt == NONE)
continue;
if (ds_retrieve_label_endpt(ndev_array[slot].ndev_label,
&endpt) != OK)
ndev_down(&ndev_array[slot]);
}
}
/*
* A network device driver has sent a reply to our initialization request.
*/
static void
ndev_init_reply(struct ndev * ndev, const message * m_ptr)
{
struct ndev_hwaddr hwaddr;
uint8_t hwaddr_len, max_send, max_recv;
const char *name;
int enabled;
/*
* Make sure that we were waiting for a reply to an initialization
* request, and that this is the reply to that request.
*/
if (NDEV_ACTIVE(ndev) ||
m_ptr->m_netdriver_ndev_init_reply.id != ndev->ndev_sendq.nq_head)
return;
/*
* Do just enough sanity checking on the data to pass it up to the
* ethif layer, which will check the rest (e.g., name duplicates).
*/
if (memchr(m_ptr->m_netdriver_ndev_init_reply.name, '\0',
sizeof(m_ptr->m_netdriver_ndev_init_reply.name)) == NULL ||
m_ptr->m_netdriver_ndev_init_reply.name[0] == '\0') {
printf("LWIP: driver %d provided invalid name\n",
m_ptr->m_source);
ndev_down(ndev);
return;
}
hwaddr_len = m_ptr->m_netdriver_ndev_init_reply.hwaddr_len;
if (hwaddr_len < 1 || hwaddr_len > __arraycount(hwaddr.nhwa_addr)) {
printf("LWIP: driver %d provided invalid HW-addr length\n",
m_ptr->m_source);
ndev_down(ndev);
return;
}
if ((max_send = m_ptr->m_netdriver_ndev_init_reply.max_send) < 1 ||
(max_recv = m_ptr->m_netdriver_ndev_init_reply.max_recv) < 1) {
printf("LWIP: driver %d provided invalid queue maximum\n",
m_ptr->m_source);
ndev_down(ndev);
return;
}
/*
* If the driver is new, allocate a new ethif object for it. On
* success, or if the driver was restarted, (re)enable the interface.
* Both calls may fail, in which case we should forget about the
* driver. It may continue to send us messages, which we should then
* discard.
*/
name = m_ptr->m_netdriver_ndev_init_reply.name;
if (ndev->ndev_ethif == NULL) {
ndev->ndev_ethif = ethif_add((ndev_id_t)(ndev - ndev_array),
name, m_ptr->m_netdriver_ndev_init_reply.caps);
name = NULL;
}
if (ndev->ndev_ethif != NULL) {
/*
* Set the maximum numbers of pending requests (for each
* direction) first, because enabling the interface may cause
* the ethif layer to start sending requests immediately.
*/
ndev->ndev_sendq.nq_max = max_send;
ndev->ndev_sendq.nq_head++;
/*
* Limit the maximum number of concurrently pending receive
* requests to our configured maximum. For send requests, we
* use a more dynamic approach with spare request objects.
*/
if (max_recv > NDEV_RECVQ)
max_recv = NDEV_RECVQ;
ndev->ndev_recvq.nq_max = max_recv;
ndev->ndev_recvq.nq_head++;
memset(&hwaddr, 0, sizeof(hwaddr));
memcpy(hwaddr.nhwa_addr,
m_ptr->m_netdriver_ndev_init_reply.hwaddr, hwaddr_len);
/*
* Provide a NULL pointer for the name if we have only just
* added the interface at all. The callee may use this to
* determine whether the driver is new or has been restarted.
*/
enabled = ethif_enable(ndev->ndev_ethif, name, &hwaddr,
m_ptr->m_netdriver_ndev_init_reply.hwaddr_len,
m_ptr->m_netdriver_ndev_init_reply.caps,
m_ptr->m_netdriver_ndev_init_reply.link,
m_ptr->m_netdriver_ndev_init_reply.media);
} else
enabled = FALSE;
/*
* If we did not manage to enable the interface, remove it again,
* possibly also from the ethif layer.
*/
if (!enabled)
ndev_down(ndev);
else
ndev_pending--;
}
/*
* Request that a network device driver change its configuration. This
* function allows for configuration of various different driver and device
* aspects: the I/O mode (and multicast receipt list), the enabled (sub)set of
* capabilities, the driver-specific flags, and the hardware address. Each of
* these settings may be changed by setting the corresponding NDEV_SET_ flag in
* the 'set' field of the given configuration structure. It is explicitly
* allowed to generate a request with no NDEV_SET_ flags; such a request will
* be sent to the driver and ultimately generate a response. Return OK if the
* configuration request was sent to the driver, EBUSY if no (more) requests
* can be sent to the driver right now, or ENOMEM on grant allocation failure.
*/
int
ndev_conf(ndev_id_t id, const struct ndev_conf * nconf)
{
struct ndev *ndev;
struct ndev_req *nreq;
uint32_t seq;
message m;
cp_grant_id_t grant;
int r;
assert(id < __arraycount(ndev_array));
ndev = &ndev_array[id];
assert(ndev->ndev_endpt != NONE);
assert(NDEV_ACTIVE(ndev));
if ((nreq = ndev_queue_get(&ndev->ndev_sendq, NDEV_CONF,
&seq)) == NULL)
return EBUSY;
memset(&m, 0, sizeof(m));
m.m_type = NDEV_CONF;
m.m_ndev_netdriver_conf.id = seq;
m.m_ndev_netdriver_conf.set = nconf->nconf_set;
grant = GRANT_INVALID;
if (nconf->nconf_set & NDEV_SET_MODE) {
m.m_ndev_netdriver_conf.mode = nconf->nconf_mode;
if (nconf->nconf_mode & NDEV_MODE_MCAST_LIST) {
assert(nconf->nconf_mclist != NULL);
assert(nconf->nconf_mccount != 0);
grant = cpf_grant_direct(ndev->ndev_endpt,
(vir_bytes)nconf->nconf_mclist,
sizeof(nconf->nconf_mclist[0]) *
nconf->nconf_mccount, CPF_READ);
if (!GRANT_VALID(grant))
return ENOMEM;
m.m_ndev_netdriver_conf.mcast_count =
nconf->nconf_mccount;
}
}
m.m_ndev_netdriver_conf.mcast_grant = grant;
if (nconf->nconf_set & NDEV_SET_CAPS)
m.m_ndev_netdriver_conf.caps = nconf->nconf_caps;
if (nconf->nconf_set & NDEV_SET_FLAGS)
m.m_ndev_netdriver_conf.flags = nconf->nconf_flags;
if (nconf->nconf_set & NDEV_SET_MEDIA)
m.m_ndev_netdriver_conf.media = nconf->nconf_media;
if (nconf->nconf_set & NDEV_SET_HWADDR)
memcpy(m.m_ndev_netdriver_conf.hwaddr,
nconf->nconf_hwaddr.nhwa_addr,
__arraycount(m.m_ndev_netdriver_conf.hwaddr));
if ((r = asynsend3(ndev->ndev_endpt, &m, AMF_NOREPLY)) != OK)
panic("asynsend to driver failed: %d", r);
nreq->nreq_grant[0] = grant; /* may also be invalid */
nreq->nreq_grant[1] = GRANT_INVALID;
ndev_queue_add(&ndev->ndev_sendq, nreq);
return OK;
}
/*
* The network device driver has sent a reply to a configuration request.
*/
static void
ndev_conf_reply(struct ndev * ndev, const message * m_ptr)
{
/*
* Was this the request we were waiting for? If so, remove it from the
* send queue. Otherwise, ignore this reply message.
*/
if (!NDEV_ACTIVE(ndev) || !ndev_queue_remove(&ndev->ndev_sendq,
NDEV_CONF, m_ptr->m_netdriver_ndev_reply.id))
return;
/* Tell the ethif layer about the updated configuration. */
assert(ndev->ndev_ethif != NULL);
ethif_configured(ndev->ndev_ethif,
m_ptr->m_netdriver_ndev_reply.result);
}
/*
* Construct a packet send or receive request and send it off to a network
* driver. The given pbuf chain may be part of a queue. Return OK if the
* request was successfully sent, or ENOMEM on grant allocation failure.
*/
static int
ndev_transfer(struct ndev * ndev, const struct pbuf * pbuf, int do_send,
uint32_t seq, struct ndev_req * nreq)
{
cp_grant_id_t grant;
message m;
unsigned int i;
size_t left;
int r;
memset(&m, 0, sizeof(m));
m.m_type = (do_send) ? NDEV_SEND : NDEV_RECV;
m.m_ndev_netdriver_transfer.id = seq;
left = pbuf->tot_len;
for (i = 0; left > 0; i++) {
assert(i < NDEV_IOV_MAX);
grant = cpf_grant_direct(ndev->ndev_endpt,
(vir_bytes)pbuf->payload, pbuf->len,
(do_send) ? CPF_READ : CPF_WRITE);
if (!GRANT_VALID(grant)) {
while (i-- > 0)
(void)cpf_revoke(nreq->nreq_grant[i]);
return ENOMEM;
}
m.m_ndev_netdriver_transfer.grant[i] = grant;
m.m_ndev_netdriver_transfer.len[i] = pbuf->len;
nreq->nreq_grant[i] = grant;
assert(left >= pbuf->len);
left -= pbuf->len;
pbuf = pbuf->next;
}
m.m_ndev_netdriver_transfer.count = i;
/*
* Unless the array is full, an invalid grant marks the end of the list
* of invalid grants.
*/
if (i < __arraycount(nreq->nreq_grant))
nreq->nreq_grant[i] = GRANT_INVALID;
if ((r = asynsend3(ndev->ndev_endpt, &m, AMF_NOREPLY)) != OK)
panic("asynsend to driver failed: %d", r);
return OK;
}
/*
* Send a packet to the given network driver. Return OK if the packet is sent
* off to the driver, EBUSY if no (more) packets can be sent to the driver at
* this time, or ENOMEM on grant allocation failure.
*
* The use of 'pbuf' in this interface is a bit ugly, but it saves us from
* having to go through an intermediate representation (e.g. an iovec array)
* for the data being sent. The same applies to ndev_receive().
*/
int
ndev_send(ndev_id_t id, const struct pbuf * pbuf)
{
struct ndev *ndev;
struct ndev_req *nreq;
uint32_t seq;
int r;
assert(id < __arraycount(ndev_array));
ndev = &ndev_array[id];
assert(ndev->ndev_endpt != NONE);
assert(NDEV_ACTIVE(ndev));
if ((nreq = ndev_queue_get(&ndev->ndev_sendq, NDEV_SEND,
&seq)) == NULL)
return EBUSY;
if ((r = ndev_transfer(ndev, pbuf, TRUE /*do_send*/, seq, nreq)) != OK)
return r;
ndev_queue_add(&ndev->ndev_sendq, nreq);
return OK;
}
/*
* The network device driver has sent a reply to a send request.
*/
static void
ndev_send_reply(struct ndev * ndev, const message * m_ptr)
{
/*
* Was this the request we were waiting for? If so, remove it from the
* send queue. Otherwise, ignore this reply message.
*/
if (!NDEV_ACTIVE(ndev) || !ndev_queue_remove(&ndev->ndev_sendq,
NDEV_SEND, m_ptr->m_netdriver_ndev_reply.id))
return;
/* Tell the ethif layer about the result of the transmission. */
assert(ndev->ndev_ethif != NULL);
ethif_sent(ndev->ndev_ethif,
m_ptr->m_netdriver_ndev_reply.result);
}
/*
* Return TRUE if a new receive request can be spawned for a particular network
* driver, or FALSE if its queue of receive requests is full. This call exists
* merely to avoid needless buffer allocatin in the case that ndev_recv() is
* going to return EBUSY anyway.
*/
int
ndev_can_recv(ndev_id_t id)
{
struct ndev *ndev;
assert(id < __arraycount(ndev_array));
ndev = &ndev_array[id];
assert(ndev->ndev_endpt != NONE);
assert(NDEV_ACTIVE(ndev));
return (ndev->ndev_recvq.nq_count < ndev->ndev_recvq.nq_max);
}
/*
* Start the process of receiving a packet from a network driver. The packet
* will be stored in the given pbuf chain upon completion. Return OK if the
* receive request is sent to the driver, EBUSY if the maximum number of
* concurrent receive requests has been reached for this driver, or ENOMEM on
* grant allocation failure.
*/
int
ndev_recv(ndev_id_t id, struct pbuf * pbuf)
{
struct ndev *ndev;
struct ndev_req *nreq;
uint32_t seq;
int r;
assert(id < __arraycount(ndev_array));
ndev = &ndev_array[id];
assert(ndev->ndev_endpt != NONE);
assert(NDEV_ACTIVE(ndev));
if ((nreq = ndev_queue_get(&ndev->ndev_recvq, NDEV_RECV,
&seq)) == NULL)
return EBUSY;
if ((r = ndev_transfer(ndev, pbuf, FALSE /*do_send*/, seq,
nreq)) != OK)
return r;
ndev_queue_add(&ndev->ndev_recvq, nreq);
return OK;
}
/*
* The network device driver has sent a reply to a receive request.
*/
static void
ndev_recv_reply(struct ndev * ndev, const message * m_ptr)
{
/*
* Was this the request we were waiting for? If so, remove it from the
* receive queue. Otherwise, ignore this reply message.
*/
if (!NDEV_ACTIVE(ndev) || !ndev_queue_remove(&ndev->ndev_recvq,
NDEV_RECV, m_ptr->m_netdriver_ndev_reply.id))
return;
/* Tell the ethif layer about the result of the receipt. */
assert(ndev->ndev_ethif != NULL);
ethif_received(ndev->ndev_ethif,
m_ptr->m_netdriver_ndev_reply.result);
}
/*
* A network device driver sent a status report to us. Process it and send a
* reply.
*/
static void
ndev_status(struct ndev * ndev, const message * m_ptr)
{
message m;
int r;
if (!NDEV_ACTIVE(ndev))
return;
/* Tell the ethif layer about the status update. */
assert(ndev->ndev_ethif != NULL);
ethif_status(ndev->ndev_ethif, m_ptr->m_netdriver_ndev_status.link,
m_ptr->m_netdriver_ndev_status.media,
m_ptr->m_netdriver_ndev_status.oerror,
m_ptr->m_netdriver_ndev_status.coll,
m_ptr->m_netdriver_ndev_status.ierror,
m_ptr->m_netdriver_ndev_status.iqdrop);
/*
* Send a reply, so that the driver knows it can send a new status
* update without risking asynsend queue overflows. The ID of these
* messages is chosen by the driver and and we simply echo it.
*/
memset(&m, 0, sizeof(m));
m.m_type = NDEV_STATUS_REPLY;
m.m_ndev_netdriver_status_reply.id = m_ptr->m_netdriver_ndev_status.id;
if ((r = asynsend(m_ptr->m_source, &m)) != OK)
panic("asynsend to driver failed: %d", r);
}
/*
* Process a network driver reply message.
*/
void
ndev_process(const message * m_ptr, int ipc_status)
{
struct ndev *ndev;
endpoint_t endpt;
ndev_id_t slot;
/* Find the slot of the driver that sent the message, if any. */
endpt = m_ptr->m_source;
for (slot = 0, ndev = ndev_array; slot < ndev_max; slot++, ndev++)
if (ndev->ndev_endpt == endpt)
break;
/*
* If we cannot find a slot for the driver, drop the message. We may
* be ignoring the driver because it misbehaved or we are out of slots.
*/
if (slot == ndev_max)
return;
/*
* Process the reply message. For future compatibility, ignore any
* unrecognized message types.
*/
switch (m_ptr->m_type) {
case NDEV_INIT_REPLY:
ndev_init_reply(ndev, m_ptr);
break;
case NDEV_CONF_REPLY:
ndev_conf_reply(ndev, m_ptr);
break;
case NDEV_SEND_REPLY:
ndev_send_reply(ndev, m_ptr);
break;
case NDEV_RECV_REPLY:
ndev_recv_reply(ndev, m_ptr);
break;
case NDEV_STATUS:
ndev_status(ndev, m_ptr);
break;
}
}