summaryrefslogtreecommitdiffstats
path: root/qemu/roms/ipxe/src/net/rndis.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/roms/ipxe/src/net/rndis.c')
-rw-r--r--qemu/roms/ipxe/src/net/rndis.c1052
1 files changed, 1052 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/net/rndis.c b/qemu/roms/ipxe/src/net/rndis.c
new file mode 100644
index 000000000..8c4fe8b30
--- /dev/null
+++ b/qemu/roms/ipxe/src/net/rndis.c
@@ -0,0 +1,1052 @@
+/*
+ * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** @file
+ *
+ * Remote Network Driver Interface Specification
+ *
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/device.h>
+#include <ipxe/rndis.h>
+
+/**
+ * Allocate I/O buffer
+ *
+ * @v len Length
+ * @ret iobuf I/O buffer, or NULL
+ */
+static struct io_buffer * rndis_alloc_iob ( size_t len ) {
+ struct rndis_header *header;
+ struct io_buffer *iobuf;
+
+ /* Allocate I/O buffer and reserve space */
+ iobuf = alloc_iob ( sizeof ( *header ) + len );
+ if ( iobuf )
+ iob_reserve ( iobuf, sizeof ( *header ) );
+
+ return iobuf;
+}
+
+/**
+ * Wait for completion
+ *
+ * @v rndis RNDIS device
+ * @v wait_id Request ID
+ * @ret rc Return status code
+ */
+static int rndis_wait ( struct rndis_device *rndis, unsigned int wait_id ) {
+ unsigned int i;
+
+ /* Record query ID */
+ rndis->wait_id = wait_id;
+
+ /* Wait for operation to complete */
+ for ( i = 0 ; i < RNDIS_MAX_WAIT_MS ; i++ ) {
+
+ /* Check for completion */
+ if ( ! rndis->wait_id )
+ return rndis->wait_rc;
+
+ /* Poll RNDIS device */
+ rndis->op->poll ( rndis );
+
+ /* Delay for 1ms */
+ mdelay ( 1 );
+ }
+
+ DBGC ( rndis, "RNDIS %s timed out waiting for ID %#08x\n",
+ rndis->name, wait_id );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Transmit message
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ * @v type Message type
+ * @ret rc Return status code
+ */
+static int rndis_tx_message ( struct rndis_device *rndis,
+ struct io_buffer *iobuf, unsigned int type ) {
+ struct rndis_header *header;
+ int rc;
+
+ /* Prepend RNDIS header */
+ header = iob_push ( iobuf, sizeof ( *header ) );
+ header->type = cpu_to_le32 ( type );
+ header->len = cpu_to_le32 ( iob_len ( iobuf ) );
+
+ /* Transmit message */
+ if ( ( rc = rndis->op->transmit ( rndis, iobuf ) ) != 0 ) {
+ DBGC ( rndis, "RNDIS %s could not transmit: %s\n",
+ rndis->name, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Complete message transmission
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ * @v rc Packet status code
+ */
+void rndis_tx_complete_err ( struct rndis_device *rndis,
+ struct io_buffer *iobuf, int rc ) {
+ struct net_device *netdev = rndis->netdev;
+ struct rndis_header *header;
+ size_t len = iob_len ( iobuf );
+
+ /* Sanity check */
+ if ( len < sizeof ( *header ) ) {
+ DBGC ( rndis, "RNDIS %s completed underlength transmission:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ netdev_tx_err ( netdev, NULL, -EINVAL );
+ return;
+ }
+ header = iobuf->data;
+
+ /* Complete buffer */
+ if ( header->type == cpu_to_le32 ( RNDIS_PACKET_MSG ) ) {
+ netdev_tx_complete_err ( netdev, iobuf, rc );
+ } else {
+ free_iob ( iobuf );
+ }
+}
+
+/**
+ * Transmit data packet
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int rndis_tx_data ( struct rndis_device *rndis,
+ struct io_buffer *iobuf ) {
+ struct rndis_packet_message *msg;
+ size_t len = iob_len ( iobuf );
+ int rc;
+
+ /* Prepend packet message header */
+ msg = iob_push ( iobuf, sizeof ( *msg ) );
+ memset ( msg, 0, sizeof ( *msg ) );
+ msg->data.offset = cpu_to_le32 ( sizeof ( *msg ) );
+ msg->data.len = cpu_to_le32 ( len );
+
+ /* Transmit message */
+ if ( ( rc = rndis_tx_message ( rndis, iobuf, RNDIS_PACKET_MSG ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Defer transmitted packet
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ *
+ * As with netdev_tx_defer(), the caller must ensure that space in the
+ * transmit descriptor ring is freed up before calling
+ * rndis_tx_complete().
+ *
+ * Unlike netdev_tx_defer(), this call may fail.
+ */
+int rndis_tx_defer ( struct rndis_device *rndis, struct io_buffer *iobuf ) {
+ struct net_device *netdev = rndis->netdev;
+ struct rndis_header *header;
+ struct rndis_packet_message *msg;
+
+ /* Fail unless this was a packet message. Only packet
+ * messages correspond to I/O buffers in the network device's
+ * TX queue; other messages cannot be deferred in this way.
+ */
+ assert ( iob_len ( iobuf ) >= sizeof ( *header ) );
+ header = iobuf->data;
+ if ( header->type != cpu_to_le32 ( RNDIS_PACKET_MSG ) )
+ return -ENOTSUP;
+
+ /* Strip RNDIS header and packet message header, to return
+ * this packet to the state in which we received it.
+ */
+ iob_pull ( iobuf, ( sizeof ( *header ) + sizeof ( *msg ) ) );
+
+ /* Defer packet */
+ netdev_tx_defer ( netdev, iobuf );
+
+ return 0;
+}
+
+/**
+ * Receive data packet
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ */
+static void rndis_rx_data ( struct rndis_device *rndis,
+ struct io_buffer *iobuf ) {
+ struct net_device *netdev = rndis->netdev;
+ struct rndis_packet_message *msg;
+ size_t len = iob_len ( iobuf );
+ size_t data_offset;
+ size_t data_len;
+ int rc;
+
+ /* Sanity check */
+ if ( len < sizeof ( *msg ) ) {
+ DBGC ( rndis, "RNDIS %s received underlength data packet:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_len;
+ }
+ msg = iobuf->data;
+
+ /* Locate and sanity check data buffer */
+ data_offset = le32_to_cpu ( msg->data.offset );
+ data_len = le32_to_cpu ( msg->data.len );
+ if ( ( data_offset > len ) || ( data_len > ( len - data_offset ) ) ) {
+ DBGC ( rndis, "RNDIS %s data packet data exceeds packet:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_data;
+ }
+
+ /* Strip non-data portions */
+ iob_pull ( iobuf, data_offset );
+ iob_unput ( iobuf, ( iob_len ( iobuf ) - data_len ) );
+
+ /* Hand off to network stack */
+ netdev_rx ( netdev, iob_disown ( iobuf ) );
+
+ return;
+
+ err_data:
+ err_len:
+ /* Report error to network stack */
+ netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
+}
+
+/**
+ * Transmit initialisation message
+ *
+ * @v rndis RNDIS device
+ * @v id Request ID
+ * @ret rc Return status code
+ */
+static int rndis_tx_initialise ( struct rndis_device *rndis, unsigned int id ) {
+ struct io_buffer *iobuf;
+ struct rndis_initialise_message *msg;
+ int rc;
+
+ /* Allocate I/O buffer */
+ iobuf = rndis_alloc_iob ( sizeof ( *msg ) );
+ if ( ! iobuf ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Construct message */
+ msg = iob_put ( iobuf, sizeof ( *msg ) );
+ memset ( msg, 0, sizeof ( *msg ) );
+ msg->id = id; /* Non-endian */
+ msg->major = cpu_to_le32 ( RNDIS_VERSION_MAJOR );
+ msg->minor = cpu_to_le32 ( RNDIS_VERSION_MINOR );
+ msg->mtu = cpu_to_le32 ( RNDIS_MTU );
+
+ /* Transmit message */
+ if ( ( rc = rndis_tx_message ( rndis, iobuf,
+ RNDIS_INITIALISE_MSG ) ) != 0 )
+ goto err_tx;
+
+ return 0;
+
+ err_tx:
+ free_iob ( iobuf );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Receive initialisation completion
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ */
+static void rndis_rx_initialise ( struct rndis_device *rndis,
+ struct io_buffer *iobuf ) {
+ struct rndis_initialise_completion *cmplt;
+ size_t len = iob_len ( iobuf );
+ unsigned int id;
+ int rc;
+
+ /* Sanity check */
+ if ( len < sizeof ( *cmplt ) ) {
+ DBGC ( rndis, "RNDIS %s received underlength initialisation "
+ "completion:\n", rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_len;
+ }
+ cmplt = iobuf->data;
+
+ /* Extract request ID */
+ id = cmplt->id; /* Non-endian */
+
+ /* Check status */
+ if ( cmplt->status ) {
+ DBGC ( rndis, "RNDIS %s received initialisation completion "
+ "failure %#08x\n", rndis->name,
+ le32_to_cpu ( cmplt->status ) );
+ rc = -EIO;
+ goto err_status;
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_status:
+ /* Record completion result if applicable */
+ if ( id == rndis->wait_id ) {
+ rndis->wait_id = 0;
+ rndis->wait_rc = rc;
+ }
+ err_len:
+ free_iob ( iobuf );
+}
+
+/**
+ * Initialise RNDIS
+ *
+ * @v rndis RNDIS device
+ * @ret rc Return status code
+ */
+static int rndis_initialise ( struct rndis_device *rndis ) {
+ int rc;
+
+ /* Transmit initialisation message */
+ if ( ( rc = rndis_tx_initialise ( rndis, RNDIS_INIT_ID ) ) != 0 )
+ return rc;
+
+ /* Wait for response */
+ if ( ( rc = rndis_wait ( rndis, RNDIS_INIT_ID ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Transmit halt message
+ *
+ * @v rndis RNDIS device
+ * @ret rc Return status code
+ */
+static int rndis_tx_halt ( struct rndis_device *rndis ) {
+ struct io_buffer *iobuf;
+ struct rndis_halt_message *msg;
+ int rc;
+
+ /* Allocate I/O buffer */
+ iobuf = rndis_alloc_iob ( sizeof ( *msg ) );
+ if ( ! iobuf ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Construct message */
+ msg = iob_put ( iobuf, sizeof ( *msg ) );
+ memset ( msg, 0, sizeof ( *msg ) );
+
+ /* Transmit message */
+ if ( ( rc = rndis_tx_message ( rndis, iobuf, RNDIS_HALT_MSG ) ) != 0 )
+ goto err_tx;
+
+ return 0;
+
+ err_tx:
+ free_iob ( iobuf );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Halt RNDIS
+ *
+ * @v rndis RNDIS device
+ * @ret rc Return status code
+ */
+static int rndis_halt ( struct rndis_device *rndis ) {
+ int rc;
+
+ /* Transmit halt message */
+ if ( ( rc = rndis_tx_halt ( rndis ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Transmit OID message
+ *
+ * @v rndis RNDIS device
+ * @v oid Object ID
+ * @v data New OID value (or NULL to query current value)
+ * @v len Length of new OID value
+ * @ret rc Return status code
+ */
+static int rndis_tx_oid ( struct rndis_device *rndis, unsigned int oid,
+ const void *data, size_t len ) {
+ struct io_buffer *iobuf;
+ struct rndis_oid_message *msg;
+ unsigned int type;
+ int rc;
+
+ /* Allocate I/O buffer */
+ iobuf = rndis_alloc_iob ( sizeof ( *msg ) + len );
+ if ( ! iobuf ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Construct message. We use the OID as the request ID. */
+ msg = iob_put ( iobuf, sizeof ( *msg ) );
+ memset ( msg, 0, sizeof ( *msg ) );
+ msg->id = oid; /* Non-endian */
+ msg->oid = cpu_to_le32 ( oid );
+ msg->offset = cpu_to_le32 ( sizeof ( *msg ) );
+ msg->len = cpu_to_le32 ( len );
+ memcpy ( iob_put ( iobuf, len ), data, len );
+
+ /* Transmit message */
+ type = ( data ? RNDIS_SET_MSG : RNDIS_QUERY_MSG );
+ if ( ( rc = rndis_tx_message ( rndis, iobuf, type ) ) != 0 )
+ goto err_tx;
+
+ return 0;
+
+ err_tx:
+ free_iob ( iobuf );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Receive query OID completion
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ */
+static void rndis_rx_query_oid ( struct rndis_device *rndis,
+ struct io_buffer *iobuf ) {
+ struct net_device *netdev = rndis->netdev;
+ struct rndis_query_completion *cmplt;
+ size_t len = iob_len ( iobuf );
+ size_t info_offset;
+ size_t info_len;
+ unsigned int id;
+ void *info;
+ uint32_t *link_status;
+ int rc;
+
+ /* Sanity check */
+ if ( len < sizeof ( *cmplt ) ) {
+ DBGC ( rndis, "RNDIS %s received underlength query "
+ "completion:\n", rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_len;
+ }
+ cmplt = iobuf->data;
+
+ /* Extract request ID */
+ id = cmplt->id; /* Non-endian */
+
+ /* Check status */
+ if ( cmplt->status ) {
+ DBGC ( rndis, "RNDIS %s received query completion failure "
+ "%#08x\n", rndis->name, le32_to_cpu ( cmplt->status ) );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EIO;
+ goto err_status;
+ }
+
+ /* Locate and sanity check information buffer */
+ info_offset = le32_to_cpu ( cmplt->offset );
+ info_len = le32_to_cpu ( cmplt->len );
+ if ( ( info_offset > len ) || ( info_len > ( len - info_offset ) ) ) {
+ DBGC ( rndis, "RNDIS %s query completion information exceeds "
+ "packet:\n", rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_info;
+ }
+ info = ( ( ( void * ) cmplt ) + info_offset );
+
+ /* Handle OID */
+ switch ( id ) {
+
+ case RNDIS_OID_802_3_PERMANENT_ADDRESS:
+ if ( info_len > sizeof ( netdev->hw_addr ) )
+ info_len = sizeof ( netdev->hw_addr );
+ memcpy ( netdev->hw_addr, info, info_len );
+ break;
+
+ case RNDIS_OID_802_3_CURRENT_ADDRESS:
+ if ( info_len > sizeof ( netdev->ll_addr ) )
+ info_len = sizeof ( netdev->ll_addr );
+ memcpy ( netdev->ll_addr, info, info_len );
+ break;
+
+ case RNDIS_OID_GEN_MEDIA_CONNECT_STATUS:
+ if ( info_len != sizeof ( *link_status ) ) {
+ DBGC ( rndis, "RNDIS %s invalid link status:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EPROTO;
+ goto err_link_status;
+ }
+ link_status = info;
+ if ( *link_status == 0 ) {
+ DBGC ( rndis, "RNDIS %s link is up\n", rndis->name );
+ netdev_link_up ( netdev );
+ } else {
+ DBGC ( rndis, "RNDIS %s link is down: %#08x\n",
+ rndis->name, le32_to_cpu ( *link_status ) );
+ netdev_link_down ( netdev );
+ }
+ break;
+
+ default:
+ DBGC ( rndis, "RNDIS %s unexpected query completion ID %#08x\n",
+ rndis->name, id );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EPROTO;
+ goto err_id;
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_id:
+ err_link_status:
+ err_info:
+ err_status:
+ /* Record completion result if applicable */
+ if ( id == rndis->wait_id ) {
+ rndis->wait_id = 0;
+ rndis->wait_rc = rc;
+ }
+ err_len:
+ /* Free I/O buffer */
+ free_iob ( iobuf );
+}
+
+/**
+ * Receive set OID completion
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ */
+static void rndis_rx_set_oid ( struct rndis_device *rndis,
+ struct io_buffer *iobuf ) {
+ struct rndis_set_completion *cmplt;
+ size_t len = iob_len ( iobuf );
+ unsigned int id;
+ int rc;
+
+ /* Sanity check */
+ if ( len < sizeof ( *cmplt ) ) {
+ DBGC ( rndis, "RNDIS %s received underlength set completion:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_len;
+ }
+ cmplt = iobuf->data;
+
+ /* Extract request ID */
+ id = cmplt->id; /* Non-endian */
+
+ /* Check status */
+ if ( cmplt->status ) {
+ DBGC ( rndis, "RNDIS %s received set completion failure "
+ "%#08x\n", rndis->name, le32_to_cpu ( cmplt->status ) );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EIO;
+ goto err_status;
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_status:
+ /* Record completion result if applicable */
+ if ( id == rndis->wait_id ) {
+ rndis->wait_id = 0;
+ rndis->wait_rc = rc;
+ }
+ err_len:
+ /* Free I/O buffer */
+ free_iob ( iobuf );
+}
+
+/**
+ * Query or set OID
+ *
+ * @v rndis RNDIS device
+ * @v oid Object ID
+ * @v data New OID value (or NULL to query current value)
+ * @v len Length of new OID value
+ * @ret rc Return status code
+ */
+static int rndis_oid ( struct rndis_device *rndis, unsigned int oid,
+ const void *data, size_t len ) {
+ int rc;
+
+ /* Transmit query */
+ if ( ( rc = rndis_tx_oid ( rndis, oid, data, len ) ) != 0 )
+ return rc;
+
+ /* Wait for response */
+ if ( ( rc = rndis_wait ( rndis, oid ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Receive indicate status message
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ */
+static void rndis_rx_status ( struct rndis_device *rndis,
+ struct io_buffer *iobuf ) {
+ struct net_device *netdev = rndis->netdev;
+ struct rndis_indicate_status_message *msg;
+ size_t len = iob_len ( iobuf );
+ unsigned int status;
+ int rc;
+
+ /* Sanity check */
+ if ( len < sizeof ( *msg ) ) {
+ DBGC ( rndis, "RNDIS %s received underlength status message:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -EINVAL;
+ goto err_len;
+ }
+ msg = iobuf->data;
+
+ /* Extract status */
+ status = le32_to_cpu ( msg->status );
+
+ /* Handle status */
+ switch ( msg->status ) {
+
+ case RNDIS_STATUS_MEDIA_CONNECT:
+ DBGC ( rndis, "RNDIS %s link is up\n", rndis->name );
+ netdev_link_up ( netdev );
+ break;
+
+ case RNDIS_STATUS_MEDIA_DISCONNECT:
+ DBGC ( rndis, "RNDIS %s link is down\n", rndis->name );
+ netdev_link_down ( netdev );
+ break;
+
+ case RNDIS_STATUS_WTF_WORLD:
+ /* Ignore */
+ break;
+
+ default:
+ DBGC ( rndis, "RNDIS %s unexpected status %#08x:\n",
+ rndis->name, status );
+ DBGC_HDA ( rndis, 0, iobuf->data, len );
+ rc = -ENOTSUP;
+ goto err_status;
+ }
+
+ /* Free I/O buffer */
+ free_iob ( iobuf );
+
+ return;
+
+ err_status:
+ err_len:
+ /* Report error via network device statistics */
+ netdev_rx_err ( netdev, iobuf, rc );
+}
+
+/**
+ * Receive RNDIS message
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ * @v type Message type
+ */
+static void rndis_rx_message ( struct rndis_device *rndis,
+ struct io_buffer *iobuf, unsigned int type ) {
+ struct net_device *netdev = rndis->netdev;
+ int rc;
+
+ /* Handle packet */
+ switch ( type ) {
+
+ case RNDIS_PACKET_MSG:
+ rndis_rx_data ( rndis, iob_disown ( iobuf ) );
+ break;
+
+ case RNDIS_INITIALISE_CMPLT:
+ rndis_rx_initialise ( rndis, iob_disown ( iobuf ) );
+ break;
+
+ case RNDIS_QUERY_CMPLT:
+ rndis_rx_query_oid ( rndis, iob_disown ( iobuf ) );
+ break;
+
+ case RNDIS_SET_CMPLT:
+ rndis_rx_set_oid ( rndis, iob_disown ( iobuf ) );
+ break;
+
+ case RNDIS_INDICATE_STATUS_MSG:
+ rndis_rx_status ( rndis, iob_disown ( iobuf ) );
+ break;
+
+ default:
+ DBGC ( rndis, "RNDIS %s received unexpected type %#08x\n",
+ rndis->name, type );
+ DBGC_HDA ( rndis, 0, iobuf->data, iob_len ( iobuf ) );
+ rc = -EPROTO;
+ goto err_type;
+ }
+
+ return;
+
+ err_type:
+ /* Report error via network device statistics */
+ netdev_rx_err ( netdev, iobuf, rc );
+}
+
+/**
+ * Receive packet from underlying transport layer
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ */
+void rndis_rx ( struct rndis_device *rndis, struct io_buffer *iobuf ) {
+ struct net_device *netdev = rndis->netdev;
+ struct rndis_header *header;
+ unsigned int type;
+ int rc;
+
+ /* Sanity check */
+ if ( iob_len ( iobuf ) < sizeof ( *header ) ) {
+ DBGC ( rndis, "RNDIS %s received underlength packet:\n",
+ rndis->name );
+ DBGC_HDA ( rndis, 0, iobuf->data, iob_len ( iobuf ) );
+ rc = -EINVAL;
+ goto drop;
+ }
+ header = iobuf->data;
+
+ /* Parse and strip header */
+ type = le32_to_cpu ( header->type );
+ iob_pull ( iobuf, sizeof ( *header ) );
+
+ /* Handle message */
+ rndis_rx_message ( rndis, iob_disown ( iobuf ), type );
+
+ return;
+
+ drop:
+ /* Record error */
+ netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
+}
+
+/**
+ * Discard packet from underlying transport layer
+ *
+ * @v rndis RNDIS device
+ * @v iobuf I/O buffer
+ * @v rc Packet status code
+ */
+void rndis_rx_err ( struct rndis_device *rndis, struct io_buffer *iobuf,
+ int rc ) {
+ struct net_device *netdev = rndis->netdev;
+
+ /* Record error */
+ netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
+}
+
+/**
+ * Set receive filter
+ *
+ * @v rndis RNDIS device
+ * @v filter Receive filter
+ * @ret rc Return status code
+ */
+static int rndis_filter ( struct rndis_device *rndis, unsigned int filter ) {
+ uint32_t value = cpu_to_le32 ( filter );
+ int rc;
+
+ /* Set receive filter */
+ if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_CURRENT_PACKET_FILTER,
+ &value, sizeof ( value ) ) ) != 0 ) {
+ DBGC ( rndis, "RNDIS %s could not set receive filter to %#08x: "
+ "%s\n", rndis->name, filter, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Open network device
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int rndis_open ( struct net_device *netdev ) {
+ struct rndis_device *rndis = netdev->priv;
+ int rc;
+
+ /* Open RNDIS device */
+ if ( ( rc = rndis->op->open ( rndis ) ) != 0 ) {
+ DBGC ( rndis, "RNDIS %s could not open: %s\n",
+ rndis->name, strerror ( rc ) );
+ goto err_open;
+ }
+
+ /* Initialise RNDIS */
+ if ( ( rc = rndis_initialise ( rndis ) ) != 0 )
+ goto err_initialise;
+
+ /* Set receive filter */
+ if ( ( rc = rndis_filter ( rndis, ( RNDIS_FILTER_UNICAST |
+ RNDIS_FILTER_MULTICAST |
+ RNDIS_FILTER_ALL_MULTICAST |
+ RNDIS_FILTER_BROADCAST |
+ RNDIS_FILTER_PROMISCUOUS ) ) ) != 0)
+ goto err_set_filter;
+
+ /* Update link status */
+ if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_MEDIA_CONNECT_STATUS,
+ NULL, 0 ) ) != 0 )
+ goto err_query_link;
+
+ return 0;
+
+ err_query_link:
+ err_set_filter:
+ rndis_halt ( rndis );
+ err_initialise:
+ rndis->op->close ( rndis );
+ err_open:
+ return rc;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev Network device
+ */
+static void rndis_close ( struct net_device *netdev ) {
+ struct rndis_device *rndis = netdev->priv;
+
+ /* Clear receive filter */
+ rndis_filter ( rndis, 0 );
+
+ /* Halt RNDIS device */
+ rndis_halt ( rndis );
+
+ /* Close RNDIS device */
+ rndis->op->close ( rndis );
+}
+
+/**
+ * Transmit packet
+ *
+ * @v netdev Network device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int rndis_transmit ( struct net_device *netdev,
+ struct io_buffer *iobuf ) {
+ struct rndis_device *rndis = netdev->priv;
+
+ /* Transmit data packet */
+ return rndis_tx_data ( rndis, iobuf );
+}
+
+/**
+ * Poll for completed and received packets
+ *
+ * @v netdev Network device
+ */
+static void rndis_poll ( struct net_device *netdev ) {
+ struct rndis_device *rndis = netdev->priv;
+
+ /* Poll RNDIS device */
+ rndis->op->poll ( rndis );
+}
+
+/** Network device operations */
+static struct net_device_operations rndis_operations = {
+ .open = rndis_open,
+ .close = rndis_close,
+ .transmit = rndis_transmit,
+ .poll = rndis_poll,
+};
+
+/**
+ * Allocate RNDIS device
+ *
+ * @v priv_len Length of private data
+ * @ret rndis RNDIS device, or NULL on allocation failure
+ */
+struct rndis_device * alloc_rndis ( size_t priv_len ) {
+ struct net_device *netdev;
+ struct rndis_device *rndis;
+
+ /* Allocate and initialise structure */
+ netdev = alloc_etherdev ( sizeof ( *rndis ) + priv_len );
+ if ( ! netdev )
+ return NULL;
+ netdev_init ( netdev, &rndis_operations );
+ rndis = netdev->priv;
+ rndis->netdev = netdev;
+ rndis->priv = ( ( ( void * ) rndis ) + sizeof ( *rndis ) );
+
+ return rndis;
+}
+
+/**
+ * Register RNDIS device
+ *
+ * @v rndis RNDIS device
+ * @ret rc Return status code
+ *
+ * Note that this routine will open and use the RNDIS device in order
+ * to query the MAC address. The device must be immediately ready for
+ * use prior to registration.
+ */
+int register_rndis ( struct rndis_device *rndis ) {
+ struct net_device *netdev = rndis->netdev;
+ int rc;
+
+ /* Assign device name (for debugging) */
+ rndis->name = netdev->dev->name;
+
+ /* Register network device */
+ if ( ( rc = register_netdev ( netdev ) ) != 0 ) {
+ DBGC ( rndis, "RNDIS %s could not register: %s\n",
+ rndis->name, strerror ( rc ) );
+ goto err_register;
+ }
+
+ /* Open RNDIS device to read MAC addresses */
+ if ( ( rc = rndis->op->open ( rndis ) ) != 0 ) {
+ DBGC ( rndis, "RNDIS %s could not open: %s\n",
+ rndis->name, strerror ( rc ) );
+ goto err_open;
+ }
+
+ /* Initialise RNDIS */
+ if ( ( rc = rndis_initialise ( rndis ) ) != 0 )
+ goto err_initialise;
+
+ /* Query permanent MAC address */
+ if ( ( rc = rndis_oid ( rndis, RNDIS_OID_802_3_PERMANENT_ADDRESS,
+ NULL, 0 ) ) != 0 )
+ goto err_query_permanent;
+
+ /* Query current MAC address */
+ if ( ( rc = rndis_oid ( rndis, RNDIS_OID_802_3_CURRENT_ADDRESS,
+ NULL, 0 ) ) != 0 )
+ goto err_query_current;
+
+ /* Get link status */
+ if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_MEDIA_CONNECT_STATUS,
+ NULL, 0 ) ) != 0 )
+ goto err_query_link;
+
+ /* Halt RNDIS device */
+ rndis_halt ( rndis );
+
+ /* Close RNDIS device */
+ rndis->op->close ( rndis );
+
+ return 0;
+
+ err_query_link:
+ err_query_current:
+ err_query_permanent:
+ rndis_halt ( rndis );
+ err_initialise:
+ rndis->op->close ( rndis );
+ err_open:
+ unregister_netdev ( netdev );
+ err_register:
+ return rc;
+}
+
+/**
+ * Unregister RNDIS device
+ *
+ * @v rndis RNDIS device
+ */
+void unregister_rndis ( struct rndis_device *rndis ) {
+ struct net_device *netdev = rndis->netdev;
+
+ /* Unregister network device */
+ unregister_netdev ( netdev );
+}
+
+/**
+ * Free RNDIS device
+ *
+ * @v rndis RNDIS device
+ */
+void free_rndis ( struct rndis_device *rndis ) {
+ struct net_device *netdev = rndis->netdev;
+
+ /* Free network device */
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+}