-/* This file contains device independent network device driver interface.
- *
- * Changes:
- * Apr 01, 2010 Created (Cristiano Giuffrida)
- *
- * The file contains the following entry points:
- *
- * netdriver_announce: called by a network driver to announce it is up
- * netdriver_receive: receive() interface for network drivers
- */
+/* 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>
-static int conf_expected = TRUE;
-
-/*===========================================================================*
- * netdriver_announce *
- *===========================================================================*/
-void netdriver_announce()
-{
-/* Announce we are up after a fresh start or restart. */
- int r;
- char key[DS_MAX_KEYLEN];
- char label[DS_MAX_KEYLEN];
- const char *driver_prefix = "drv.net.";
-
- /* Publish a driver up event. */
- r = ds_retrieve_label_name(label, sef_self());
- if (r != OK) {
- panic("driver_announce: unable to get own label: %d\n", r);
- }
- snprintf(key, DS_MAX_KEYLEN, "%s%s", driver_prefix, label);
- r = ds_publish_u32(key, DS_DRIVER_UP, DSF_OVERWRITE);
- if (r != OK) {
- panic("driver_announce: unable to publish driver up event: %d\n", r);
- }
-
- conf_expected = TRUE;
-}
-
-/*===========================================================================*
- * netdriver_receive *
- *===========================================================================*/
-int netdriver_receive(src, m_ptr, status_ptr)
-endpoint_t src;
-message *m_ptr;
-int *status_ptr;
-{
-/* receive() interface for drivers. */
- int r;
-
- while (TRUE) {
- /* Wait for a request. */
- r = sef_receive_status(src, m_ptr, status_ptr);
- if (r != OK) {
- return r;
- }
+#include "netdriver.h"
- /* Let non-datalink requests through regardless. */
- if (!IS_DL_RQ(m_ptr->m_type)) {
- return r;
+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;
}
- /* See if only DL_CONF is to be expected. */
- if(conf_expected) {
- if(m_ptr->m_type == DL_CONF) {
- conf_expected = FALSE;
+ 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;
}
- else {
- continue;
+ 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 write)
+{
+ struct netdriver_data *data;
+ unsigned int i;
+ int r;
+
+ /* Copy in the I/O vector. */
+ data = (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 ||
+ (!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 (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 stat;
+ int r;
+
+ memset(&stat, 0, sizeof(stat));
+
+ if (ndp->ndr_stat != NULL)
+ ndp->ndr_stat(&stat);
+
+ if ((r = sys_safecopyto(m_ptr->m_source,
+ m_ptr->m_net_netdrv_dl_getstat_s.grant, 0, (vir_bytes)&stat,
+ sizeof(stat))) != 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 /*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 /*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;
- }
+/*
+ * 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();
+}
- return OK;
+/*
+ * 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_init_restart(do_init); /* TODO: revisit this */
+ 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);
+ }
+}
--- /dev/null
+/*
+ * Port-based I/O routines. These are in a separate module because most
+ * drivers will not use them, and system services are statically linked.
+ */
+#include <minix/drivers.h>
+#include <minix/netdriver.h>
+#include <assert.h>
+
+#include "netdriver.h"
+
+/*
+ * Port-based I/O byte sequence copy routine.
+ */
+static void
+netdriver_portb(struct netdriver_data * data, size_t off, long port,
+ size_t size, int portin)
+{
+ size_t chunk;
+ unsigned int i;
+ int r, req;
+
+ off = netdriver_prepare_copy(data, off, size, &i);
+
+ req = portin ? DIO_SAFE_INPUT_BYTE : DIO_SAFE_OUTPUT_BYTE;
+
+ while (size > 0) {
+ chunk = data->iovec[i].iov_size - off;
+ if (chunk > size)
+ chunk = size;
+ assert(chunk > 0);
+
+ if ((r = sys_sdevio(req, port, data->endpt,
+ (void *)data->iovec[i].iov_grant, chunk, off)) != OK)
+ panic("netdriver: port I/O failed: %d", r);
+
+ i++;
+ off = 0;
+ size -= chunk;
+ }
+}
+
+/*
+ * Transfer bytes from hardware to a destination buffer using port-based I/O.
+ */
+void
+netdriver_portinb(struct netdriver_data * data, size_t off, long port,
+ size_t size)
+{
+
+ return netdriver_portb(data, off, port, size, TRUE /*portin*/);
+}
+
+/*
+ * Transfer bytes from a source buffer to hardware using port-based I/O.
+ */
+void
+netdriver_portoutb(struct netdriver_data * data, size_t off, long port,
+ size_t size)
+{
+
+ return netdriver_portb(data, off, port, size, FALSE /*portin*/);
+}
+
+/*
+ * Transfer words from hardware to a destination buffer using port-based I/O.
+ */
+void
+netdriver_portinw(struct netdriver_data * data, size_t off, long port,
+ size_t size)
+{
+ uint8_t buf[2];
+ uint32_t value;
+ size_t chunk;
+ unsigned int i;
+ int r, odd_byte;
+
+ off = netdriver_prepare_copy(data, off, size, &i);
+
+ odd_byte = 0;
+ while (size > 0) {
+ chunk = data->iovec[i].iov_size - off;
+ if (chunk > size)
+ chunk = size;
+ assert(chunk > 0);
+
+ if (odd_byte) {
+ if ((r = sys_safecopyto(data->endpt,
+ data->iovec[i].iov_grant, off, (vir_bytes)&buf[1],
+ 1)) != OK)
+ panic("netdriver: unable to copy data: %d", r);
+
+ off++;
+ size--;
+ chunk--;
+ }
+
+ odd_byte = chunk & 1;
+ chunk -= odd_byte;
+
+ if (chunk > 0) {
+ if ((r = sys_safe_insw(port, data->endpt,
+ data->iovec[i].iov_grant, off, chunk)) != OK)
+ panic("netdriver: port input failed: %d", r);
+
+ off += chunk;
+ size -= chunk;
+ }
+
+ if (odd_byte) {
+ if ((r = sys_inw(port, &value)) != OK)
+ panic("netdriver: port input failed: %d", r);
+ *(uint16_t *)buf = (uint16_t)value;
+
+ if ((r = sys_safecopyto(data->endpt,
+ data->iovec[i].iov_grant, off, (vir_bytes)&buf[0],
+ 1)) != OK)
+ panic("netdriver: unable to copy data: %d", r);
+
+ size--;
+ }
+
+ i++;
+ off = 0;
+ }
+}
+
+/*
+ * Transfer words from a source buffer to hardware using port-based I/O.
+ */
+void
+netdriver_portoutw(struct netdriver_data * data, size_t off, long port,
+ size_t size)
+{
+ uint8_t buf[2];
+ size_t chunk;
+ unsigned int i;
+ int r, odd_byte;
+
+ off = netdriver_prepare_copy(data, off, size, &i);
+
+ odd_byte = 0;
+ while (size > 0) {
+ chunk = data->iovec[i].iov_size - off;
+ if (chunk > size)
+ chunk = size;
+ assert(chunk > 0);
+
+ if (odd_byte) {
+ if ((r = sys_safecopyfrom(data->endpt,
+ data->iovec[i].iov_grant, off, (vir_bytes)&buf[1],
+ 1)) != OK)
+ panic("netdriver: unable to copy data: %d", r);
+
+ if ((r = sys_outw(port, *(uint16_t *)buf)) != OK)
+ panic("netdriver: port output failed: %d", r);
+
+ off++;
+ size--;
+ chunk--;
+ }
+
+ odd_byte = chunk & 1;
+ chunk -= odd_byte;
+
+ if (chunk > 0) {
+ if ((r = sys_safe_outsw(port, data->endpt,
+ data->iovec[i].iov_grant, off, chunk)) != OK)
+ panic("netdriver: port output failed: %d", r);
+
+ off += chunk;
+ size -= chunk;
+ }
+
+ if (odd_byte) {
+ if ((r = sys_safecopyfrom(data->endpt,
+ data->iovec[i].iov_grant, off, (vir_bytes)&buf[0],
+ 1)) != OK)
+ panic("netdriver: unable to copy data: %d", r);
+
+ size--;
+ }
+
+ i++;
+ off = 0;
+ }
+
+ if (odd_byte) {
+ buf[1] = 0;
+
+ if ((r = sys_outw(port, *(uint16_t *)buf)) != OK)
+ panic("netdriver: port output failed: %d", r);
+ }
+}