| /* | |
| * 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_SECBOOT ( PERMITTED ); | |
| /** @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; | |
| } | |
| /** | |
| * 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 ); | |
| } |