minix/minix/lib/libnetdriver/netdriver.c
David van Moolenbroek 7c48de6cc4 Resolve more warnings
Change-Id: Ibc1b7f7cd45ad7295285e59c6ce55888266fece8
2015-09-23 12:04:58 +00:00

620 lines
14 KiB
C

/* The device-independent network driver framework. */
#include <minix/drivers.h>
#include <minix/endpoint.h>
#include <minix/netdriver.h>
#include <minix/ds.h>
#include <assert.h>
#include "netdriver.h"
static const struct netdriver *netdriver_table = NULL;
static int running;
static int conf_expected;
static endpoint_t pending_endpt;
static struct netdriver_data pending_recv, pending_send;
static int defer_reply;
static unsigned int pending_flags;
static size_t pending_size;
static ether_addr_t hw_addr;
/*
* Announce we are up after a fresh start or restart.
*/
void
netdriver_announce(void)
{
const char *driver_prefix = "drv.net.";
char label[DS_MAX_KEYLEN];
char key[DS_MAX_KEYLEN];
int r;
/* Publish a driver up event. */
if ((r = ds_retrieve_label_name(label, sef_self())) != OK)
panic("netdriver: unable to get own label: %d", r);
snprintf(key, sizeof(key), "%s%s", driver_prefix, label);
if ((r = ds_publish_u32(key, DS_DRIVER_UP, DSF_OVERWRITE)) != OK)
panic("netdriver: unable to publish driver up event: %d", r);
}
/*
* Prepare for copying. Given a flat offset, return the vector element index
* and an offset into that element. Panic if the request does not fall
* entirely within the vector.
*/
size_t
netdriver_prepare_copy(struct netdriver_data * data, size_t off, size_t size,
unsigned int * indexp)
{
unsigned int i;
assert(data->size > 0);
/*
* In theory we could truncate when copying out, but this creates a
* problem for port-based I/O, where the size of the transfer is
* typically specified in advance. We could do extra port-based I/O
* to discard the extra bytes, but the driver is better off doing such
* truncation itself. Thus, we disallow copying (in and out) beyond
* the given data vector altogether.
*/
if (off + size > data->size)
panic("netdriver: request to copy beyond data size");
/*
* Find the starting offset in the vector. If this turns out to be
* expensive, this can be adapted to store the last <element,offset>
* pair in the "data" structure (this is the reason it is not 'const').
*/
for (i = 0; i < data->count; i++) {
assert(data->iovec[i].iov_size > 0);
if (off >= data->iovec[i].iov_size)
off -= data->iovec[i].iov_size;
else
break;
}
assert(i < data->count);
*indexp = i;
return off;
}
/*
* Copy in or out packet data from/to a vector of grants.
*/
static void
netdriver_copy(struct netdriver_data * data, size_t off, vir_bytes addr,
size_t size, int copyin)
{
struct vscp_vec vec[SCPVEC_NR];
size_t chunk;
unsigned int i, v;
int r;
off = netdriver_prepare_copy(data, off, size, &i);
/* Generate a new vector with all the individual copies to make. */
for (v = 0; size > 0; v++) {
chunk = data->iovec[i].iov_size - off;
if (chunk > size)
chunk = size;
assert(chunk > 0);
/*
* We should be able to fit the entire I/O request in a single
* copy vector. If not, MINIX3 has been misconfigured.
*/
if (v >= SCPVEC_NR)
panic("netdriver: invalid vector size constant");
if (copyin) {
vec[v].v_from = data->endpt;
vec[v].v_to = SELF;
} else {
vec[v].v_from = SELF;
vec[v].v_to = data->endpt;
}
vec[v].v_gid = data->iovec[i].iov_grant;
vec[v].v_offset = off;
vec[v].v_addr = addr;
vec[v].v_bytes = chunk;
i++;
off = 0;
addr += chunk;
size -= chunk;
}
assert(v > 0 && v <= SCPVEC_NR);
/*
* If only one vector element was generated, use a direct copy. This
* saves the kernel from having to copy in the vector.
*/
if (v == 1) {
if (copyin)
r = sys_safecopyfrom(vec->v_from, vec->v_gid,
vec->v_offset, vec->v_addr, vec->v_bytes);
else
r = sys_safecopyto(vec->v_to, vec->v_gid,
vec->v_offset, vec->v_addr, vec->v_bytes);
} else
r = sys_vsafecopy(vec, v);
if (r != OK)
panic("netdriver: unable to copy data: %d", r);
}
/*
* Copy in packet data.
*/
void
netdriver_copyin(struct netdriver_data * __restrict data, size_t off,
void * __restrict ptr, size_t size)
{
netdriver_copy(data, off, (vir_bytes)ptr, size, TRUE /*copyin*/);
}
/*
* Copy out packet data.
*/
void
netdriver_copyout(struct netdriver_data * __restrict data, size_t off,
const void * __restrict ptr, size_t size)
{
netdriver_copy(data, off, (vir_bytes)ptr, size, FALSE /*copyin*/);
}
/*
* Send a reply to a request.
*/
static void
send_reply(endpoint_t endpt, message * m_ptr)
{
int r;
if ((r = ipc_send(endpt, m_ptr)) != OK)
panic("netdriver: unable to send to %d: %d", endpt, r);
}
/*
* Defer sending any replies to task requests until the next call to
* check_replies(). The purpose of this is aggregation of task replies to both
* send and receive requests into a single reply message, which saves on
* messages, in particular when processing interrupts.
*/
static void
defer_replies(void)
{
assert(netdriver_table != NULL);
assert(defer_reply == FALSE);
defer_reply = TRUE;
}
/*
* Check if we have to reply to earlier task (I/O) requests, and if so, send
* the reply. If deferred is FALSE and the call to this function was preceded
* by a call to defer_replies(), do not send a reply yet. If always_send is
* TRUE, send a reply even if no tasks have completed yet.
*/
static void
check_replies(int deferred, int always_send)
{
message m_reply;
if (defer_reply && !deferred)
return;
defer_reply = FALSE;
if (pending_flags == 0 && !always_send)
return;
assert(pending_endpt != NONE);
memset(&m_reply, 0, sizeof(m_reply));
m_reply.m_type = DL_TASK_REPLY;
m_reply.m_netdrv_net_dl_task.flags = pending_flags;
m_reply.m_netdrv_net_dl_task.count = pending_size;
send_reply(pending_endpt, &m_reply);
pending_flags = 0;
pending_size = 0;
}
/*
* Resume receiving packets. In particular, if a receive request was pending,
* call the driver's receive function. If the call is successful, schedule
* sending a reply to the requesting party.
*/
void
netdriver_recv(void)
{
ssize_t r;
if (pending_recv.size == 0)
return;
assert(netdriver_table != NULL);
/*
* For convenience of driver writers: if the receive function returns
* zero, simply call it again, to simplify discarding invalid packets.
*/
do {
r = netdriver_table->ndr_recv(&pending_recv,
pending_recv.size);
/*
* The default policy is: drop undersized packets, panic on
* oversized packets. The driver may implement any other
* policy (e.g., pad small packets, drop or truncate large
* packets), but it should at least test against the given
* 'max' value. The reason that truncation should be
* implemented in the driver rather than here, is explained in
* an earlier comment about truncating copy operations.
*/
if (r >= 0 && r < ETH_MIN_PACK_SIZE)
r = 0;
else if (r > (ssize_t)pending_recv.size)
panic("netdriver: oversized packet returned: %zd", r);
} while (r == 0);
if (r == SUSPEND)
return;
if (r < 0)
panic("netdriver: driver reported receive failure: %d", r);
assert(r >= ETH_MIN_PACK_SIZE && (size_t)r <= pending_recv.size);
pending_flags |= DL_PACK_RECV;
pending_size = r;
pending_recv.size = 0;
check_replies(FALSE /*deferred*/, FALSE /*always_send*/);
}
/*
* Resume sending packets. In particular, if a send request was pending, call
* the driver's send function. If the call is successful, schedule sending a
* reply to the requesting party. This function relies on being called
* between init_pending() and check_pending().
*/
void
netdriver_send(void)
{
int r;
if (pending_send.size == 0)
return;
assert(netdriver_table != NULL);
r = netdriver_table->ndr_send(&pending_send, pending_send.size);
if (r == SUSPEND)
return;
if (r < 0)
panic("netdriver: driver reported send failure: %d", r);
pending_flags |= DL_PACK_SEND;
pending_send.size = 0;
check_replies(FALSE /*deferred*/, FALSE /*always_send*/);
}
/*
* Process a request to receive or send a packet.
*/
static void
do_readwrite(const struct netdriver * __restrict ndp, endpoint_t endpt,
cp_grant_id_t grant, unsigned int count, int do_write)
{
struct netdriver_data *data;
unsigned int i;
int r;
/* Copy in the I/O vector. */
data = (do_write) ? &pending_send : &pending_recv;
if (data->size != 0)
panic("netdriver: multiple concurrent requests");
if (count == 0 || count > NR_IOREQS)
panic("netdriver: bad I/O vector count: %u", count);
data->endpt = endpt;
data->count = count;
if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes)data->iovec,
sizeof(data->iovec[0]) * count)) != OK)
panic("netdriver: unable to copy in I/O vector: %d", r);
for (i = 0; i < count; i++)
data->size += data->iovec[i].iov_size;
if (data->size < ETH_MIN_PACK_SIZE ||
(!do_write && data->size < ETH_MAX_PACK_SIZE_TAGGED))
panic("netdriver: invalid I/O vector size: %zu\n", data->size);
/* Save the endpoint to which we should reply. */
if (pending_endpt != NONE && pending_endpt != endpt)
panic("netdriver: multiple request sources");
pending_endpt = endpt;
/* Resume sending or receiving. */
defer_replies();
if (do_write)
netdriver_send();
else
netdriver_recv();
/* Always send a reply in this case, even if no flags are set. */
check_replies(TRUE /*deferred*/, TRUE /*always_send*/);
}
/*
* Process a request to configure the driver, by setting its mode and obtaining
* its ethernet hardware address. We already have the latter as a result of
* calling the ndr_init callback function.
*/
static void
do_conf(const struct netdriver * __restrict ndp,
const message * __restrict m_ptr)
{
message m_reply;
if (ndp->ndr_mode != NULL)
ndp->ndr_mode(m_ptr->m_net_netdrv_dl_conf.mode);
memset(&m_reply, 0, sizeof(m_reply));
m_reply.m_type = DL_CONF_REPLY;
m_reply.m_netdrv_net_dl_conf.stat = OK; /* legacy */
memcpy(&m_reply.m_netdrv_net_dl_conf.hw_addr, &hw_addr,
sizeof(m_reply.m_netdrv_net_dl_conf.hw_addr));
send_reply(m_ptr->m_source, &m_reply);
}
/*
* Process a request to obtain statistics from the driver.
*/
static void
do_getstat(const struct netdriver * __restrict ndp,
const message * __restrict m_ptr)
{
message m_reply;
eth_stat_t st;
int r;
memset(&st, 0, sizeof(st));
if (ndp->ndr_stat != NULL)
ndp->ndr_stat(&st);
if ((r = sys_safecopyto(m_ptr->m_source,
m_ptr->m_net_netdrv_dl_getstat_s.grant, 0, (vir_bytes)&st,
sizeof(st))) != OK)
panic("netdriver: unable to copy out statistics: %d", r);
memset(&m_reply, 0, sizeof(m_reply));
m_reply.m_type = DL_STAT_REPLY;
send_reply(m_ptr->m_source, &m_reply);
}
/*
* Process an incoming message, and send a reply.
*/
void
netdriver_process(const struct netdriver * __restrict ndp,
const message * __restrict m_ptr, int ipc_status)
{
netdriver_table = ndp;
/* Check for notifications first. */
if (is_ipc_notify(ipc_status)) {
defer_replies();
switch (_ENDPOINT_P(m_ptr->m_source)) {
case HARDWARE:
if (ndp->ndr_intr != NULL)
ndp->ndr_intr(m_ptr->m_notify.interrupts);
break;
case CLOCK:
if (ndp->ndr_alarm != NULL)
ndp->ndr_alarm(m_ptr->m_notify.timestamp);
break;
default:
if (ndp->ndr_other != NULL)
ndp->ndr_other(m_ptr, ipc_status);
}
/*
* Any of the above calls may end up invoking netdriver_send()
* and/or netdriver_recv(), which may in turn have deferred
* sending a reply to an earlier request. See if we have to
* send the reply now.
*/
check_replies(TRUE /*deferred*/, FALSE /*always_send*/);
}
/*
* Discard datalink requests preceding a first DL_CONF request, so that
* after a driver restart, any in-flight request is discarded. This is
* a rather blunt approach and must be revised if the protocol is ever
* made less inefficient (i.e. not strictly serialized). Note that for
* correct driver operation it is important that non-datalink requests,
* interrupts in particular, do not go through this check.
*/
if (IS_DL_RQ(m_ptr->m_type) && conf_expected) {
if (m_ptr->m_type != DL_CONF)
return; /* do not send a reply */
conf_expected = FALSE;
}
switch (m_ptr->m_type) {
case DL_CONF:
do_conf(ndp, m_ptr);
break;
case DL_GETSTAT_S:
do_getstat(ndp, m_ptr);
break;
case DL_READV_S:
do_readwrite(ndp, m_ptr->m_source,
m_ptr->m_net_netdrv_dl_readv_s.grant,
m_ptr->m_net_netdrv_dl_readv_s.count, FALSE /*do_write*/);
break;
case DL_WRITEV_S:
do_readwrite(ndp, m_ptr->m_source,
m_ptr->m_net_netdrv_dl_writev_s.grant,
m_ptr->m_net_netdrv_dl_writev_s.count, TRUE /*do_write*/);
break;
default:
defer_replies();
if (ndp->ndr_other != NULL)
ndp->ndr_other(m_ptr, ipc_status);
/* As above: see if we have to send a reply now. */
check_replies(TRUE /*deferred*/, FALSE /*always_send*/);
}
}
/*
* Perform initialization. Return OK or an error code.
*/
int
netdriver_init(const struct netdriver * ndp)
{
unsigned int instance;
long v;
int r;
/* Initialize global variables. */
pending_recv.size = 0;
pending_send.size = 0;
pending_endpt = NONE;
defer_reply = FALSE;
pending_flags = 0;
pending_size = 0;
conf_expected = TRUE;
/* Get the card instance number. */
v = 0;
(void)env_parse("instance", "d", 0, &v, 0, 255);
instance = (unsigned int)v;
/* Call the initialization routine. */
memset(&hw_addr, 0, sizeof(hw_addr));
if (ndp->ndr_init != NULL &&
(r = ndp->ndr_init(instance, &hw_addr)) != OK)
return r;
/* Announce we are up! */
netdriver_announce();
return OK;
}
/*
* SEF initialization function.
*/
static int
do_init(int __unused type, sef_init_info_t * __unused info)
{
const struct netdriver *ndp;
ndp = netdriver_table;
assert(ndp != NULL);
return netdriver_init(ndp);
}
/*
* Break out of the main loop after finishing the current request.
*/
void
netdriver_terminate(void)
{
if (netdriver_table != NULL && netdriver_table->ndr_stop != NULL)
netdriver_table->ndr_stop();
running = FALSE;
sef_cancel();
}
/*
* The process has received a signal. See if we have to terminate.
*/
static void
got_signal(int sig)
{
if (sig != SIGTERM)
return;
netdriver_terminate();
}
/*
* Main program of any network driver.
*/
void
netdriver_task(const struct netdriver * ndp)
{
message mess;
int r, ipc_status;
/* Perform SEF initialization. */
sef_setcb_init_fresh(do_init);
sef_setcb_signal_handler(got_signal);
netdriver_table = ndp;
sef_startup();
netdriver_table = NULL;
/* The main message loop. */
running = TRUE;
while (running) {
if ((r = sef_receive_status(ANY, &mess, &ipc_status)) != OK) {
if (r == EINTR)
continue; /* sef_cancel() was called */
panic("netdriver: sef_receive_status failed: %d", r);
}
netdriver_process(ndp, &mess, ipc_status);
}
}