mirror of https://github.com/ipxe/ipxe.git
[rndis] Add generic RNDIS device abstraction
RNDIS provides an abstraction of a network device on top of a generic packet transmission mechanism. Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/33/head
parent
c86b22221d
commit
1d2b7c91f7
|
@ -227,6 +227,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||
#define ERRFILE_ping ( ERRFILE_NET | 0x003a0000 )
|
||||
#define ERRFILE_dhcpv6 ( ERRFILE_NET | 0x003b0000 )
|
||||
#define ERRFILE_nfs_uri ( ERRFILE_NET | 0x003c0000 )
|
||||
#define ERRFILE_rndis ( ERRFILE_NET | 0x003d0000 )
|
||||
|
||||
#define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 )
|
||||
#define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 )
|
||||
|
|
|
@ -36,13 +36,12 @@ struct device;
|
|||
|
||||
/** Maximum length of a link-layer header
|
||||
*
|
||||
* The longest currently-supported link-layer header is for 802.11: a
|
||||
* 24-byte frame header plus an 8-byte 802.3 LLC/SNAP header, plus a
|
||||
* possible 4-byte VLAN header. (The IPoIB link-layer pseudo-header
|
||||
* doesn't actually include link-layer addresses; see ipoib.c for
|
||||
* details.)
|
||||
* The longest currently-supported link-layer header is for RNDIS: an
|
||||
* 8-byte RNDIS header, a 32-byte RNDIS packet message header, a
|
||||
* 14-byte Ethernet header and a possible 4-byte VLAN header. Round
|
||||
* up to 64 bytes.
|
||||
*/
|
||||
#define MAX_LL_HEADER_LEN 36
|
||||
#define MAX_LL_HEADER_LEN 64
|
||||
|
||||
/** Maximum length of a network-layer address */
|
||||
#define MAX_NET_ADDR_LEN 16
|
||||
|
|
|
@ -0,0 +1,348 @@
|
|||
#ifndef _IPXE_RNDIS_H
|
||||
#define _IPXE_RNDIS_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* Remote Network Driver Interface Specification
|
||||
*
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ipxe/netdevice.h>
|
||||
#include <ipxe/iobuf.h>
|
||||
|
||||
/** Maximum time to wait for a transaction to complete
|
||||
*
|
||||
* This is a policy decision.
|
||||
*/
|
||||
#define RNDIS_MAX_WAIT_MS 1000
|
||||
|
||||
/** RNDIS message header */
|
||||
struct rndis_header {
|
||||
/** Message type */
|
||||
uint32_t type;
|
||||
/** Message length */
|
||||
uint32_t len;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS initialise message */
|
||||
#define RNDIS_INITIALIZE_MSG 0x00000002UL
|
||||
|
||||
/** RNDIS initialise message */
|
||||
struct rndis_initialize_message {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
/** Major version */
|
||||
uint32_t major;
|
||||
/** Minor version */
|
||||
uint32_t minor;
|
||||
/** Maximum transfer size */
|
||||
uint32_t mtu;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS initialise completion */
|
||||
#define RNDIS_INITIALISE_CMPLT 0x80000002UL
|
||||
|
||||
/** RNDIS initialise completion */
|
||||
struct rndis_initialise_completion {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
/** Major version */
|
||||
uint32_t major;
|
||||
/** Minor version */
|
||||
uint32_t minor;
|
||||
/** Device flags */
|
||||
uint32_t flags;
|
||||
/** Medium */
|
||||
uint32_t medium;
|
||||
/** Maximum packets per transfer */
|
||||
uint32_t max_pkts;
|
||||
/** Maximum transfer size */
|
||||
uint32_t mtu;
|
||||
/** Packet alignment factor */
|
||||
uint32_t align;
|
||||
/** Reserved */
|
||||
uint32_t reserved;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS halt message */
|
||||
#define RNIS_HALT_MSG 0x00000003UL
|
||||
|
||||
/** RNDIS halt message */
|
||||
struct rndis_halt_message {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS query OID message */
|
||||
#define RNDIS_QUERY_MSG 0x00000004UL
|
||||
|
||||
/** RNDIS set OID message */
|
||||
#define RNDIS_SET_MSG 0x00000005UL
|
||||
|
||||
/** RNDIS query or set OID message */
|
||||
struct rndis_oid_message {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
/** Object ID */
|
||||
uint32_t oid;
|
||||
/** Information buffer length */
|
||||
uint32_t len;
|
||||
/** Information buffer offset */
|
||||
uint32_t offset;
|
||||
/** Reserved */
|
||||
uint32_t reserved;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS query OID completion */
|
||||
#define RNDIS_QUERY_CMPLT 0x80000004UL
|
||||
|
||||
/** RNDIS query OID completion */
|
||||
struct rndis_query_completion {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
/** Information buffer length */
|
||||
uint32_t len;
|
||||
/** Information buffer offset */
|
||||
uint32_t offset;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS set OID completion */
|
||||
#define RNDIS_SET_CMPLT 0x80000005UL
|
||||
|
||||
/** RNDIS set OID completion */
|
||||
struct rndis_set_completion {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS reset message */
|
||||
#define RNDIS_RESET_MSG 0x00000006UL
|
||||
|
||||
/** RNDIS reset message */
|
||||
struct rndis_reset_message {
|
||||
/** Reserved */
|
||||
uint32_t reserved;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS reset completion */
|
||||
#define RNDIS_RESET_CMPLT 0x80000006UL
|
||||
|
||||
/** RNDIS reset completion */
|
||||
struct rndis_reset_completion {
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
/** Addressing reset */
|
||||
uint32_t addr;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS indicate status message */
|
||||
#define RNDIS_INDICATE_STATUS_MSG 0x00000007UL
|
||||
|
||||
/** RNDIS diagnostic information */
|
||||
struct rndis_diagnostic_info {
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
/** Error offset */
|
||||
uint32_t offset;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS indicate status message */
|
||||
struct rndis_indicate_status_message {
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
/** Status buffer length */
|
||||
uint32_t len;
|
||||
/** Status buffer offset */
|
||||
uint32_t offset;
|
||||
/** Diagnostic information (optional) */
|
||||
struct rndis_diagnostic_info diag[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS status codes */
|
||||
enum rndis_status {
|
||||
/** Device is connected to a network medium */
|
||||
RNDIS_STATUS_MEDIA_CONNECT = 0x4001000bUL,
|
||||
/** Device is disconnected from the medium */
|
||||
RNDIS_STATUS_MEDIA_DISCONNECT = 0x4001000cUL,
|
||||
};
|
||||
|
||||
/** RNDIS keepalive message */
|
||||
#define RNDIS_KEEPALIVE_MSG 0x00000008UL
|
||||
|
||||
/** RNDIS keepalive message */
|
||||
struct rndis_keepalive_message {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS keepalive completion */
|
||||
#define RNDIS_KEEPALIVE_CMPLT 0x80000008UL
|
||||
|
||||
/** RNDIS keepalive completion */
|
||||
struct rndis_keepalive_completion {
|
||||
/** Request ID */
|
||||
uint32_t id;
|
||||
/** Status */
|
||||
uint32_t status;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS packet message */
|
||||
#define RNDIS_PACKET_MSG 0x00000001UL
|
||||
|
||||
/** RNDIS packet field */
|
||||
struct rndis_packet_field {
|
||||
/** Offset */
|
||||
uint32_t offset;
|
||||
/** Length */
|
||||
uint32_t len;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS packet message */
|
||||
struct rndis_packet_message {
|
||||
/** Data */
|
||||
struct rndis_packet_field data;
|
||||
/** Out-of-band data records */
|
||||
struct rndis_packet_field oob;
|
||||
/** Number of out-of-band data records */
|
||||
uint32_t oob_count;
|
||||
/** Per-packet information record */
|
||||
struct rndis_packet_field ppi;
|
||||
/** Reserved */
|
||||
uint32_t reserved;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** RNDIS packet record */
|
||||
struct rndis_packet_record {
|
||||
/** Length */
|
||||
uint32_t len;
|
||||
/** Type */
|
||||
uint32_t type;
|
||||
/** Offset */
|
||||
uint32_t offset;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** OID for packet filter */
|
||||
#define RNDIS_OID_GEN_CURRENT_PACKET_FILTER 0x0001010eUL
|
||||
|
||||
/** Packet filter bits */
|
||||
enum rndis_packet_filter {
|
||||
/** Unicast packets */
|
||||
RNDIS_FILTER_UNICAST = 0x00000001UL,
|
||||
/** Multicast packets */
|
||||
RNDIS_FILTER_MULTICAST = 0x00000002UL,
|
||||
/** All multicast packets */
|
||||
RNDIS_FILTER_ALL_MULTICAST = 0x00000004UL,
|
||||
/** Broadcast packets */
|
||||
RNDIS_FILTER_BROADCAST = 0x00000008UL,
|
||||
/** All packets */
|
||||
RNDIS_FILTER_PROMISCUOUS = 0x00000020UL
|
||||
};
|
||||
|
||||
/** OID for media status */
|
||||
#define RNDIS_OID_GEN_MEDIA_CONNECT_STATUS 0x00010114UL
|
||||
|
||||
/** OID for permanent MAC address */
|
||||
#define RNDIS_OID_802_3_PERMANENT_ADDRESS 0x01010101UL
|
||||
|
||||
/** OID for current MAC address */
|
||||
#define RNDIS_OID_802_3_CURRENT_ADDRESS 0x01010102UL
|
||||
|
||||
struct rndis_device;
|
||||
|
||||
/** RNDIS device operations */
|
||||
struct rndis_operations {
|
||||
/**
|
||||
* Open RNDIS device
|
||||
*
|
||||
* @v rndis RNDIS device
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int ( * open ) ( struct rndis_device *rndis );
|
||||
/**
|
||||
* Close RNDIS device
|
||||
*
|
||||
* @v rndis RNDIS device
|
||||
*/
|
||||
void ( * close ) ( struct rndis_device *rndis );
|
||||
/**
|
||||
* Transmit packet
|
||||
*
|
||||
* @v rndis RNDIS device
|
||||
* @v iobuf I/O buffer
|
||||
* @ret rc Return status code
|
||||
*
|
||||
* If this method returns success then the RNDIS device must
|
||||
* eventually report completion via rndis_tx_complete().
|
||||
*/
|
||||
int ( * transmit ) ( struct rndis_device *rndis,
|
||||
struct io_buffer *iobuf );
|
||||
/**
|
||||
* Poll for completed and received packets
|
||||
*
|
||||
* @v rndis RNDIS device
|
||||
*/
|
||||
void ( * poll ) ( struct rndis_device *rndis );
|
||||
};
|
||||
|
||||
/** An RNDIS device */
|
||||
struct rndis_device {
|
||||
/** Network device */
|
||||
struct net_device *netdev;
|
||||
/** Device name */
|
||||
const char *name;
|
||||
/** RNDIS operations */
|
||||
struct rndis_operations *op;
|
||||
/** Driver private data */
|
||||
void *priv;
|
||||
|
||||
/** Request ID for current blocking request */
|
||||
unsigned int wait_id;
|
||||
/** Return status code for current blocking request */
|
||||
int wait_rc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise an RNDIS device
|
||||
*
|
||||
* @v rndis RNDIS device
|
||||
* @v op RNDIS device operations
|
||||
*/
|
||||
static inline void rndis_init ( struct rndis_device *rndis,
|
||||
struct rndis_operations *op ) {
|
||||
|
||||
rndis->op = op;
|
||||
}
|
||||
|
||||
extern void rndis_tx_complete_err ( struct rndis_device *rndis,
|
||||
struct io_buffer *iobuf, int rc );
|
||||
extern int rndis_tx_defer ( struct rndis_device *rndis,
|
||||
struct io_buffer *iobuf );
|
||||
extern void rndis_rx ( struct rndis_device *rndis, struct io_buffer *iobuf );
|
||||
|
||||
extern struct rndis_device * alloc_rndis ( size_t priv_len );
|
||||
extern int register_rndis ( struct rndis_device *rndis );
|
||||
extern void unregister_rndis ( struct rndis_device *rndis );
|
||||
extern void free_rndis ( struct rndis_device *rndis );
|
||||
|
||||
/**
|
||||
* Complete message transmission
|
||||
*
|
||||
* @v rndis RNDIS device
|
||||
* @v iobuf I/O buffer
|
||||
*/
|
||||
static inline void rndis_tx_complete ( struct rndis_device *rndis,
|
||||
struct io_buffer *iobuf ) {
|
||||
|
||||
rndis_tx_complete_err ( rndis, iobuf, 0 );
|
||||
}
|
||||
|
||||
#endif /* _IPXE_RNDIS_H */
|
|
@ -0,0 +1,850 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
/** @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 ) {
|
||||
unsigned int i;
|
||||
int rc;
|
||||
|
||||
/* Transmit query */
|
||||
if ( ( rc = rndis_tx_oid ( rndis, oid, data, len ) ) != 0 )
|
||||
return rc;
|
||||
|
||||
/* Record query ID */
|
||||
rndis->wait_id = oid;
|
||||
|
||||
/* 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 OID %#08x\n",
|
||||
rndis->name, oid );
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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_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, or NULL if allocation failed
|
||||
*/
|
||||
void rndis_rx ( struct rndis_device *rndis, struct io_buffer *iobuf ) {
|
||||
struct net_device *netdev = rndis->netdev;
|
||||
struct rndis_header *header;
|
||||
struct io_buffer *msg;
|
||||
size_t len;
|
||||
unsigned int type;
|
||||
int rc;
|
||||
|
||||
/* Record dropped packet if I/O buffer is missing */
|
||||
if ( ! iobuf ) {
|
||||
DBGC2 ( rndis, "RNDIS %s received dropped packet\n",
|
||||
rndis->name );
|
||||
rc = -ENOBUFS;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
/* Split packet into messages */
|
||||
while ( iobuf ) {
|
||||
|
||||
/* 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 check header */
|
||||
type = le32_to_cpu ( header->type );
|
||||
len = le32_to_cpu ( header->len );
|
||||
if ( len > iob_len ( iobuf ) ) {
|
||||
DBGC ( rndis, "RNDIS %s received underlength packet:\n",
|
||||
rndis->name );
|
||||
DBGC_HDA ( rndis, 0, iobuf->data, iob_len ( iobuf ) );
|
||||
rc = -EINVAL;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
/* Split buffer if required */
|
||||
if ( len < iob_len ( iobuf ) ) {
|
||||
msg = iob_split ( iobuf, len );
|
||||
if ( ! msg ) {
|
||||
rc = -ENOMEM;
|
||||
goto drop;
|
||||
}
|
||||
} else {
|
||||
msg = iobuf;
|
||||
iobuf = NULL;
|
||||
}
|
||||
|
||||
/* Strip header */
|
||||
iob_pull ( msg, sizeof ( *header ) );
|
||||
|
||||
/* Handle message */
|
||||
rndis_rx_message ( rndis, iob_disown ( msg ), type );
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
drop:
|
||||
/* Record error */
|
||||
netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
uint32_t filter;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Set receive filter */
|
||||
filter = cpu_to_le32 ( RNDIS_FILTER_UNICAST |
|
||||
RNDIS_FILTER_MULTICAST |
|
||||
RNDIS_FILTER_ALL_MULTICAST |
|
||||
RNDIS_FILTER_BROADCAST |
|
||||
RNDIS_FILTER_PROMISCUOUS );
|
||||
if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_CURRENT_PACKET_FILTER,
|
||||
&filter, sizeof ( filter ) ) ) != 0 ) {
|
||||
DBGC ( rndis, "RNDIS %s could not set receive filter: %s\n",
|
||||
rndis->name, strerror ( rc ) );
|
||||
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->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;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Close RNDIS device */
|
||||
rndis->op->close ( rndis );
|
||||
|
||||
return 0;
|
||||
|
||||
err_query_link:
|
||||
err_query_current:
|
||||
err_query_permanent:
|
||||
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 );
|
||||
}
|
Loading…
Reference in New Issue