/* The device-independent network driver framework. */ #include #include #include #include #include #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 * 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); } }