diff --git a/minix/include/minix/netdriver.h b/minix/include/minix/netdriver.h index 58ce47564..693855838 100644 --- a/minix/include/minix/netdriver.h +++ b/minix/include/minix/netdriver.h @@ -5,9 +5,63 @@ #include #include +#include -/* Functions defined by netdriver.c: */ -void netdriver_announce(void); -int netdriver_receive(endpoint_t src, message *m_ptr, int *status_ptr); +/* The flags that make up the requested receive mode. */ +#define NDEV_NOMODE DL_NOMODE /* targeted packets only */ +#define NDEV_PROMISC DL_PROMISC_REQ /* promiscuous mode */ +#define NDEV_MULTI DL_MULTI_REQ /* receive multicast packets */ +#define NDEV_BROAD DL_BROAD_REQ /* receive broadcast packets */ + +/* + * For now, only ethernet-type network drivers are supported, and thus, we use + * some ethernet-specific data structures. + */ +#include +#include + +/* Opaque data structure for copying in and out actual packet data. */ +struct netdriver_data; + +/* Function call table for network drivers. */ +struct netdriver { + int (*ndr_init)(unsigned int instance, ether_addr_t *addr); + void (*ndr_stop)(void); + void (*ndr_mode)(unsigned int mode); + ssize_t (*ndr_recv)(struct netdriver_data *data, size_t max); + int (*ndr_send)(struct netdriver_data *data, size_t size); + void (*ndr_stat)(eth_stat_t *stat); + void (*ndr_intr)(unsigned int mask); + void (*ndr_alarm)(clock_t stamp); + void (*ndr_other)(const message *m_ptr, int ipc_status); +}; + +/* Functions defined by libnetdriver. */ +void netdriver_task(const struct netdriver *ndp); + +void netdriver_announce(void); /* legacy; deprecated */ +int netdriver_init(const struct netdriver *ndp); +void netdriver_process(const struct netdriver * __restrict ndp, + const message * __restrict m_ptr, int ipc_status); +void netdriver_terminate(void); + +void netdriver_recv(void); +void netdriver_send(void); + +void netdriver_copyin(struct netdriver_data * __restrict data, size_t off, + void * __restrict ptr, size_t size); +void netdriver_copyout(struct netdriver_data * __restrict data, size_t off, + const void * __restrict ptr, size_t size); + +void netdriver_portinb(struct netdriver_data *data, size_t off, long port, + size_t size); +void netdriver_portoutb(struct netdriver_data *data, size_t off, long port, + size_t size); +void netdriver_portinw(struct netdriver_data *data, size_t off, long port, + size_t size); +void netdriver_portoutw(struct netdriver_data *data, size_t off, long port, + size_t size); + +#define netdriver_receive sef_receive_status /* legacy; deprecated */ #endif /* _MINIX_NETDRIVER_H */ diff --git a/minix/lib/libnetdriver/Makefile b/minix/lib/libnetdriver/Makefile index 2c670a4ca..a0abb86fd 100644 --- a/minix/lib/libnetdriver/Makefile +++ b/minix/lib/libnetdriver/Makefile @@ -6,6 +6,6 @@ CPPFLAGS+= -D_MINIX_SYSTEM LIB= netdriver -SRCS= netdriver.c +SRCS= netdriver.c portio.c .include diff --git a/minix/lib/libnetdriver/netdriver.c b/minix/lib/libnetdriver/netdriver.c index 51adcf2a5..bdea11df6 100644 --- a/minix/lib/libnetdriver/netdriver.c +++ b/minix/lib/libnetdriver/netdriver.c @@ -1,82 +1,620 @@ -/* 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 #include #include #include +#include -static int conf_expected = TRUE; +#include "netdriver.h" -/*===========================================================================* - * netdriver_announce * - *===========================================================================*/ -void netdriver_announce() +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) { -/* 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."; + const char *driver_prefix = "drv.net."; + char label[DS_MAX_KEYLEN]; + char key[DS_MAX_KEYLEN]; + int r; - /* 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); - } + /* Publish a driver up event. */ + if ((r = ds_retrieve_label_name(label, sef_self())) != OK) + panic("netdriver: unable to get own label: %d", r); - conf_expected = TRUE; + 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); } -/*===========================================================================* - * netdriver_receive * - *===========================================================================*/ -int netdriver_receive(src, m_ptr, status_ptr) -endpoint_t src; -message *m_ptr; -int *status_ptr; +/* + * 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) { -/* receive() interface for drivers. */ - int r; + unsigned int i; - while (TRUE) { - /* Wait for a request. */ - r = sef_receive_status(src, m_ptr, status_ptr); - if (r != OK) { - return r; + 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; } - /* Let non-datalink requests through regardless. */ - if (!IS_DL_RQ(m_ptr->m_type)) { - return r; - } + assert(i < data->count); - /* See if only DL_CONF is to be expected. */ - if(conf_expected) { - if(m_ptr->m_type == DL_CONF) { - conf_expected = FALSE; - } - else { - continue; - } - } - - break; - } - - return OK; + *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 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 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_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); + } +} diff --git a/minix/lib/libnetdriver/netdriver.h b/minix/lib/libnetdriver/netdriver.h new file mode 100644 index 000000000..24b9c5cc0 --- /dev/null +++ b/minix/lib/libnetdriver/netdriver.h @@ -0,0 +1,15 @@ +#ifndef _MINIX_LIB_NETDRIVER_NETDRIVER_H +#define _MINIX_LIB_NETDRIVER_NETDRIVER_H + +/* Data (I/O) structure. */ +struct netdriver_data { + endpoint_t endpt; + size_t size; + unsigned int count; + iovec_s_t iovec[NR_IOREQS]; +}; + +size_t netdriver_prepare_copy(struct netdriver_data *data, size_t offp, + size_t size, unsigned int * indexp); + +#endif /* !_MINIX_LIB_NETDRIVER_NETDRIVER_H */ diff --git a/minix/lib/libnetdriver/portio.c b/minix/lib/libnetdriver/portio.c new file mode 100644 index 000000000..432166f29 --- /dev/null +++ b/minix/lib/libnetdriver/portio.c @@ -0,0 +1,193 @@ +/* + * 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 +#include +#include + +#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); + } +}