/* * Copyright (C) 2014 Michael Brown . * * 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 #include #include #include #include #include #include #include #include /** * 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; } /** * Describe RNDIS device * * @v rndis RNDIS device * @ret rc Return status code */ static int rndis_describe ( struct rndis_device *rndis ) { struct net_device *netdev = rndis->netdev; int rc; /* Assign device name (for debugging) */ rndis->name = netdev->dev->name; /* 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: return rc; } /** * 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; /* Describe RNDIS device */ if ( ( rc = rndis_describe ( rndis ) ) != 0 ) goto err_describe; /* 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; } return 0; unregister_netdev ( netdev ); err_register: err_describe: 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 ); }