
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
1020 lines
29 KiB
C
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;
|
|
}
|
|
}
|