david/ipxe
david
/
ipxe
Archived
1
0
Fork 0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/drivers/net/netvsc.c

896 lines
24 KiB
C
Raw Normal View History

/*
* 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
*
* Hyper-V network virtual service client
*
* The network virtual service client (NetVSC) connects to the network
* virtual service provider (NetVSP) via the Hyper-V virtual machine
* bus (VMBus). It provides a transport layer for RNDIS packets.
*/
#include <errno.h>
#include <unistd.h>
#include <byteswap.h>
#include <ipxe/umalloc.h>
#include <ipxe/rndis.h>
#include <ipxe/vmbus.h>
#include "netvsc.h"
/**
* Send control message and wait for completion
*
* @v netvsc NetVSC device
* @v xrid Relative transaction ID
* @v data Data
* @v len Length of data
* @ret rc Return status code
*/
static int netvsc_control ( struct netvsc_device *netvsc, unsigned int xrid,
const void *data, size_t len ) {
uint64_t xid = ( NETVSC_BASE_XID + xrid );
unsigned int i;
int rc;
/* Send control message */
if ( ( rc = vmbus_send_control ( netvsc->vmdev, xid, data, len ) ) !=0){
DBGC ( netvsc, "NETVSC %s could not send control message: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
/* Record transaction ID */
netvsc->wait_xrid = xrid;
/* Wait for operation to complete */
for ( i = 0 ; i < NETVSC_MAX_WAIT_MS ; i++ ) {
/* Check for completion */
if ( ! netvsc->wait_xrid )
return netvsc->wait_rc;
/* Poll VMBus device */
vmbus_poll ( netvsc->vmdev );
/* Delay for 1ms */
mdelay ( 1 );
}
DBGC ( netvsc, "NETVSC %s timed out waiting for XRID %d\n",
netvsc->name, xrid );
vmbus_dump_channel ( netvsc->vmdev );
return -ETIMEDOUT;
}
/**
* Handle generic completion
*
* @v netvsc NetVSC device
* @v data Data
* @v len Length of data
* @ret rc Return status code
*/
static int netvsc_completed ( struct netvsc_device *netvsc __unused,
const void *data __unused, size_t len __unused ) {
return 0;
}
/**
* Initialise communication
*
* @v netvsc NetVSC device
* @ret rc Return status code
*/
static int netvsc_initialise ( struct netvsc_device *netvsc ) {
struct netvsc_init_message msg;
int rc;
/* Construct message */
memset ( &msg, 0, sizeof ( msg ) );
msg.header.type = cpu_to_le32 ( NETVSC_INIT_MSG );
msg.min = cpu_to_le32 ( NETVSC_VERSION_1 );
msg.max = cpu_to_le32 ( NETVSC_VERSION_1 );
/* Send message and wait for completion */
if ( ( rc = netvsc_control ( netvsc, NETVSC_INIT_XRID, &msg,
sizeof ( msg ) ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not initialise: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Handle initialisation completion
*
* @v netvsc NetVSC device
* @v data Data
* @v len Length of data
* @ret rc Return status code
*/
static int
netvsc_initialised ( struct netvsc_device *netvsc, const void *data,
size_t len ) {
const struct netvsc_init_completion *cmplt = data;
/* Check completion */
if ( len < sizeof ( *cmplt ) ) {
DBGC ( netvsc, "NETVSC %s underlength initialisation "
"completion (%zd bytes)\n", netvsc->name, len );
return -EINVAL;
}
if ( cmplt->header.type != cpu_to_le32 ( NETVSC_INIT_CMPLT ) ) {
DBGC ( netvsc, "NETVSC %s unexpected initialisation completion "
"type %d\n", netvsc->name,
le32_to_cpu ( cmplt->header.type ) );
return -EPROTO;
}
if ( cmplt->status != cpu_to_le32 ( NETVSC_OK ) ) {
DBGC ( netvsc, "NETVSC %s initialisation failure status %d\n",
netvsc->name, le32_to_cpu ( cmplt->status ) );
return -EPROTO;
}
return 0;
}
/**
* Set NDIS version
*
* @v netvsc NetVSC device
* @ret rc Return status code
*/
static int netvsc_ndis_version ( struct netvsc_device *netvsc ) {
struct netvsc_ndis_version_message msg;
int rc;
/* Construct message */
memset ( &msg, 0, sizeof ( msg ) );
msg.header.type = cpu_to_le32 ( NETVSC_NDIS_VERSION_MSG );
msg.major = cpu_to_le32 ( NETVSC_NDIS_MAJOR );
msg.minor = cpu_to_le32 ( NETVSC_NDIS_MINOR );
/* Send message and wait for completion */
if ( ( rc = netvsc_control ( netvsc, NETVSC_NDIS_VERSION_XRID,
&msg, sizeof ( msg ) ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not set NDIS version: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Establish data buffer
*
* @v netvsc NetVSC device
* @v buffer Data buffer
* @ret rc Return status code
*/
static int netvsc_establish_buffer ( struct netvsc_device *netvsc,
struct netvsc_buffer *buffer ) {
struct netvsc_establish_buffer_message msg;
int rc;
/* Construct message */
memset ( &msg, 0, sizeof ( msg ) );
msg.header.type = cpu_to_le32 ( buffer->establish_type );
msg.gpadl = cpu_to_le32 ( buffer->gpadl );
msg.pageset = buffer->pages.pageset; /* Already protocol-endian */
/* Send message and wait for completion */
if ( ( rc = netvsc_control ( netvsc, buffer->establish_xrid, &msg,
sizeof ( msg ) ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not establish buffer: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Handle establish receive data buffer completion
*
* @v netvsc NetVSC device
* @v data Data
* @v len Length of data
* @ret rc Return status code
*/
static int netvsc_rx_established_buffer ( struct netvsc_device *netvsc,
const void *data, size_t len ) {
const struct netvsc_rx_establish_buffer_completion *cmplt = data;
/* Check completion */
if ( len < sizeof ( *cmplt ) ) {
DBGC ( netvsc, "NETVSC %s underlength buffer completion (%zd "
"bytes)\n", netvsc->name, len );
return -EINVAL;
}
if ( cmplt->header.type != cpu_to_le32 ( NETVSC_RX_ESTABLISH_CMPLT ) ) {
DBGC ( netvsc, "NETVSC %s unexpected buffer completion type "
"%d\n", netvsc->name, le32_to_cpu ( cmplt->header.type));
return -EPROTO;
}
if ( cmplt->status != cpu_to_le32 ( NETVSC_OK ) ) {
DBGC ( netvsc, "NETVSC %s buffer failure status %d\n",
netvsc->name, le32_to_cpu ( cmplt->status ) );
return -EPROTO;
}
return 0;
}
/**
* Revoke data buffer
*
* @v netvsc NetVSC device
* @v buffer Data buffer
* @ret rc Return status code
*/
static int netvsc_revoke_buffer ( struct netvsc_device *netvsc,
struct netvsc_buffer *buffer ) {
struct netvsc_revoke_buffer_message msg;
int rc;
/* If the buffer's GPADL is obsolete (i.e. was created before
* the most recent Hyper-V reset), then we will never receive
* a response to the revoke message. Since the GPADL is
* already destroyed as far as the hypervisor is concerned, no
* further action is required.
*/
if ( netvsc_is_obsolete ( netvsc ) )
return 0;
/* Construct message */
memset ( &msg, 0, sizeof ( msg ) );
msg.header.type = cpu_to_le32 ( buffer->revoke_type );
msg.pageset = buffer->pages.pageset; /* Already protocol-endian */
/* Send message and wait for completion */
if ( ( rc = netvsc_control ( netvsc, buffer->revoke_xrid,
&msg, sizeof ( msg ) ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not revoke buffer: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Handle received control packet
*
* @v vmdev VMBus device
* @v xid Transaction ID
* @v data Data
* @v len Length of data
* @ret rc Return status code
*/
static int netvsc_recv_control ( struct vmbus_device *vmdev, uint64_t xid,
const void *data, size_t len ) {
struct rndis_device *rndis = vmbus_get_drvdata ( vmdev );
struct netvsc_device *netvsc = rndis->priv;
DBGC ( netvsc, "NETVSC %s received unsupported control packet "
"(%08llx):\n", netvsc->name, xid );
DBGC_HDA ( netvsc, 0, data, len );
return -ENOTSUP;
}
/**
* Handle received data packet
*
* @v vmdev VMBus device
* @v xid Transaction ID
* @v data Data
* @v len Length of data
[hyperv] Assume that VMBus xfer page ranges correspond to RNDIS messages The (undocumented) VMBus protocol seems to allow for transfer page-based packets where the data payload is split into an arbitrary set of ranges within the transfer page set. The RNDIS protocol includes a length field within the header of each message, and it is known from observation that multiple RNDIS messages can be concatenated into a single VMBus message. iPXE currently assumes that the transfer page range boundaries are entirely arbitrary, and uses the RNDIS header length to determine the RNDIS message boundaries. Windows Server 2012 R2 generates an RNDIS_INDICATE_STATUS_MSG for an undocumented and unknown status code (0x40020006) with a malformed RNDIS header length: the length does not cover the StatusBuffer portion of the message. This causes iPXE to report a malformed RNDIS message and to discard any further RNDIS messages within the same VMBus message. The Linux Hyper-V driver assumes that the transfer page range boundaries correspond to RNDIS message boundaries, and so does not notice the malformed length field in the RNDIS header. Match the behaviour of the Linux Hyper-V driver: assume that the transfer page range boundaries correspond to the RNDIS message boundaries and ignore the RNDIS header length. This avoids triggering the "malformed packet" error and also avoids unnecessary data copying: since we now have one I/O buffer per RNDIS message, there is no longer any need to use iob_split(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-12-20 22:01:27 +01:00
* @v list List of I/O buffers
* @ret rc Return status code
*/
static int netvsc_recv_data ( struct vmbus_device *vmdev, uint64_t xid,
const void *data, size_t len,
[hyperv] Assume that VMBus xfer page ranges correspond to RNDIS messages The (undocumented) VMBus protocol seems to allow for transfer page-based packets where the data payload is split into an arbitrary set of ranges within the transfer page set. The RNDIS protocol includes a length field within the header of each message, and it is known from observation that multiple RNDIS messages can be concatenated into a single VMBus message. iPXE currently assumes that the transfer page range boundaries are entirely arbitrary, and uses the RNDIS header length to determine the RNDIS message boundaries. Windows Server 2012 R2 generates an RNDIS_INDICATE_STATUS_MSG for an undocumented and unknown status code (0x40020006) with a malformed RNDIS header length: the length does not cover the StatusBuffer portion of the message. This causes iPXE to report a malformed RNDIS message and to discard any further RNDIS messages within the same VMBus message. The Linux Hyper-V driver assumes that the transfer page range boundaries correspond to RNDIS message boundaries, and so does not notice the malformed length field in the RNDIS header. Match the behaviour of the Linux Hyper-V driver: assume that the transfer page range boundaries correspond to the RNDIS message boundaries and ignore the RNDIS header length. This avoids triggering the "malformed packet" error and also avoids unnecessary data copying: since we now have one I/O buffer per RNDIS message, there is no longer any need to use iob_split(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-12-20 22:01:27 +01:00
struct list_head *list ) {
struct rndis_device *rndis = vmbus_get_drvdata ( vmdev );
struct netvsc_device *netvsc = rndis->priv;
const struct netvsc_rndis_message *msg = data;
[hyperv] Assume that VMBus xfer page ranges correspond to RNDIS messages The (undocumented) VMBus protocol seems to allow for transfer page-based packets where the data payload is split into an arbitrary set of ranges within the transfer page set. The RNDIS protocol includes a length field within the header of each message, and it is known from observation that multiple RNDIS messages can be concatenated into a single VMBus message. iPXE currently assumes that the transfer page range boundaries are entirely arbitrary, and uses the RNDIS header length to determine the RNDIS message boundaries. Windows Server 2012 R2 generates an RNDIS_INDICATE_STATUS_MSG for an undocumented and unknown status code (0x40020006) with a malformed RNDIS header length: the length does not cover the StatusBuffer portion of the message. This causes iPXE to report a malformed RNDIS message and to discard any further RNDIS messages within the same VMBus message. The Linux Hyper-V driver assumes that the transfer page range boundaries correspond to RNDIS message boundaries, and so does not notice the malformed length field in the RNDIS header. Match the behaviour of the Linux Hyper-V driver: assume that the transfer page range boundaries correspond to the RNDIS message boundaries and ignore the RNDIS header length. This avoids triggering the "malformed packet" error and also avoids unnecessary data copying: since we now have one I/O buffer per RNDIS message, there is no longer any need to use iob_split(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-12-20 22:01:27 +01:00
struct io_buffer *iobuf;
struct io_buffer *tmp;
int rc;
/* Sanity check */
if ( len < sizeof ( *msg ) ) {
DBGC ( netvsc, "NETVSC %s received underlength RNDIS packet "
"(%zd bytes)\n", netvsc->name, len );
rc = -EINVAL;
goto err_sanity;
}
if ( msg->header.type != cpu_to_le32 ( NETVSC_RNDIS_MSG ) ) {
DBGC ( netvsc, "NETVSC %s received unexpected RNDIS packet "
"type %d\n", netvsc->name,
le32_to_cpu ( msg->header.type ) );
rc = -EINVAL;
goto err_sanity;
}
[hyperv] Assume that VMBus xfer page ranges correspond to RNDIS messages The (undocumented) VMBus protocol seems to allow for transfer page-based packets where the data payload is split into an arbitrary set of ranges within the transfer page set. The RNDIS protocol includes a length field within the header of each message, and it is known from observation that multiple RNDIS messages can be concatenated into a single VMBus message. iPXE currently assumes that the transfer page range boundaries are entirely arbitrary, and uses the RNDIS header length to determine the RNDIS message boundaries. Windows Server 2012 R2 generates an RNDIS_INDICATE_STATUS_MSG for an undocumented and unknown status code (0x40020006) with a malformed RNDIS header length: the length does not cover the StatusBuffer portion of the message. This causes iPXE to report a malformed RNDIS message and to discard any further RNDIS messages within the same VMBus message. The Linux Hyper-V driver assumes that the transfer page range boundaries correspond to RNDIS message boundaries, and so does not notice the malformed length field in the RNDIS header. Match the behaviour of the Linux Hyper-V driver: assume that the transfer page range boundaries correspond to the RNDIS message boundaries and ignore the RNDIS header length. This avoids triggering the "malformed packet" error and also avoids unnecessary data copying: since we now have one I/O buffer per RNDIS message, there is no longer any need to use iob_split(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-12-20 22:01:27 +01:00
/* Send completion back to host */
if ( ( rc = vmbus_send_completion ( vmdev, xid, NULL, 0 ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not send completion: %s\n",
netvsc->name, strerror ( rc ) );
goto err_completion;
}
[hyperv] Assume that VMBus xfer page ranges correspond to RNDIS messages The (undocumented) VMBus protocol seems to allow for transfer page-based packets where the data payload is split into an arbitrary set of ranges within the transfer page set. The RNDIS protocol includes a length field within the header of each message, and it is known from observation that multiple RNDIS messages can be concatenated into a single VMBus message. iPXE currently assumes that the transfer page range boundaries are entirely arbitrary, and uses the RNDIS header length to determine the RNDIS message boundaries. Windows Server 2012 R2 generates an RNDIS_INDICATE_STATUS_MSG for an undocumented and unknown status code (0x40020006) with a malformed RNDIS header length: the length does not cover the StatusBuffer portion of the message. This causes iPXE to report a malformed RNDIS message and to discard any further RNDIS messages within the same VMBus message. The Linux Hyper-V driver assumes that the transfer page range boundaries correspond to RNDIS message boundaries, and so does not notice the malformed length field in the RNDIS header. Match the behaviour of the Linux Hyper-V driver: assume that the transfer page range boundaries correspond to the RNDIS message boundaries and ignore the RNDIS header length. This avoids triggering the "malformed packet" error and also avoids unnecessary data copying: since we now have one I/O buffer per RNDIS message, there is no longer any need to use iob_split(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-12-20 22:01:27 +01:00
/* Hand off to RNDIS */
list_for_each_entry_safe ( iobuf, tmp, list, list ) {
list_del ( &iobuf->list );
rndis_rx ( rndis, iob_disown ( iobuf ) );
}
return 0;
err_completion:
err_sanity:
[hyperv] Assume that VMBus xfer page ranges correspond to RNDIS messages The (undocumented) VMBus protocol seems to allow for transfer page-based packets where the data payload is split into an arbitrary set of ranges within the transfer page set. The RNDIS protocol includes a length field within the header of each message, and it is known from observation that multiple RNDIS messages can be concatenated into a single VMBus message. iPXE currently assumes that the transfer page range boundaries are entirely arbitrary, and uses the RNDIS header length to determine the RNDIS message boundaries. Windows Server 2012 R2 generates an RNDIS_INDICATE_STATUS_MSG for an undocumented and unknown status code (0x40020006) with a malformed RNDIS header length: the length does not cover the StatusBuffer portion of the message. This causes iPXE to report a malformed RNDIS message and to discard any further RNDIS messages within the same VMBus message. The Linux Hyper-V driver assumes that the transfer page range boundaries correspond to RNDIS message boundaries, and so does not notice the malformed length field in the RNDIS header. Match the behaviour of the Linux Hyper-V driver: assume that the transfer page range boundaries correspond to the RNDIS message boundaries and ignore the RNDIS header length. This avoids triggering the "malformed packet" error and also avoids unnecessary data copying: since we now have one I/O buffer per RNDIS message, there is no longer any need to use iob_split(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-12-20 22:01:27 +01:00
list_for_each_entry_safe ( iobuf, tmp, list, list ) {
list_del ( &iobuf->list );
free_iob ( iobuf );
}
return rc;
}
/**
* Handle received completion packet
*
* @v vmdev VMBus device
* @v xid Transaction ID
* @v data Data
* @v len Length of data
* @ret rc Return status code
*/
static int netvsc_recv_completion ( struct vmbus_device *vmdev, uint64_t xid,
const void *data, size_t len ) {
struct rndis_device *rndis = vmbus_get_drvdata ( vmdev );
struct netvsc_device *netvsc = rndis->priv;
struct io_buffer *iobuf;
int ( * completion ) ( struct netvsc_device *netvsc,
const void *data, size_t len );
unsigned int xrid = ( xid - NETVSC_BASE_XID );
unsigned int tx_id;
int rc;
/* Handle transmit completion, if applicable */
tx_id = ( xrid - NETVSC_TX_BASE_XRID );
if ( ( tx_id < NETVSC_TX_NUM_DESC ) &&
( ( iobuf = netvsc->tx.iobufs[tx_id] ) != NULL ) ) {
/* Free buffer ID */
netvsc->tx.iobufs[tx_id] = NULL;
netvsc->tx.ids[ ( netvsc->tx.id_cons++ ) &
( netvsc->tx.count - 1 ) ] = tx_id;
/* Hand back to RNDIS */
rndis_tx_complete ( rndis, iobuf );
return 0;
}
/* Otherwise determine completion handler */
if ( xrid == NETVSC_INIT_XRID ) {
completion = netvsc_initialised;
} else if ( xrid == NETVSC_RX_ESTABLISH_XRID ) {
completion = netvsc_rx_established_buffer;
} else if ( ( netvsc->wait_xrid != 0 ) &&
( xrid == netvsc->wait_xrid ) ) {
completion = netvsc_completed;
} else {
DBGC ( netvsc, "NETVSC %s received unexpected completion "
"(%08llx)\n", netvsc->name, xid );
return -EPIPE;
}
/* Hand off to completion handler */
rc = completion ( netvsc, data, len );
/* Record completion handler result if applicable */
if ( xrid == netvsc->wait_xrid ) {
netvsc->wait_xrid = 0;
netvsc->wait_rc = rc;
}
return rc;
}
/**
* Handle received cancellation packet
*
* @v vmdev VMBus device
* @v xid Transaction ID
* @ret rc Return status code
*/
static int netvsc_recv_cancellation ( struct vmbus_device *vmdev,
uint64_t xid ) {
struct rndis_device *rndis = vmbus_get_drvdata ( vmdev );
struct netvsc_device *netvsc = rndis->priv;
DBGC ( netvsc, "NETVSC %s received unsupported cancellation packet "
"(%08llx):\n", netvsc->name, xid );
return -ENOTSUP;
}
/** VMBus channel operations */
static struct vmbus_channel_operations netvsc_channel_operations = {
.recv_control = netvsc_recv_control,
.recv_data = netvsc_recv_data,
.recv_completion = netvsc_recv_completion,
.recv_cancellation = netvsc_recv_cancellation,
};
/**
* Poll for completed and received packets
*
* @v rndis RNDIS device
*/
static void netvsc_poll ( struct rndis_device *rndis ) {
struct netvsc_device *netvsc = rndis->priv;
struct vmbus_device *vmdev = netvsc->vmdev;
/* Poll VMBus device */
while ( vmbus_has_data ( vmdev ) )
vmbus_poll ( vmdev );
}
/**
* 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().
*/
static int netvsc_transmit ( struct rndis_device *rndis,
struct io_buffer *iobuf ) {
struct netvsc_device *netvsc = rndis->priv;
struct rndis_header *header = iobuf->data;
struct netvsc_rndis_message msg;
unsigned int tx_id;
unsigned int xrid;
uint64_t xid;
int rc;
/* If the device is obsolete (i.e. was opened before the most
* recent Hyper-V reset), then we will never receive transmit
* completions. Fail transmissions immediately to minimise
* the delay in closing and reopening the device.
*/
if ( netvsc_is_obsolete ( netvsc ) )
return -EPIPE;
/* Sanity check */
assert ( iob_len ( iobuf ) >= sizeof ( *header ) );
assert ( iob_len ( iobuf ) == le32_to_cpu ( header->len ) );
/* Check that we have space in the transmit ring */
if ( netvsc_ring_is_full ( &netvsc->tx ) )
return rndis_tx_defer ( rndis, iobuf );
/* Allocate buffer ID and calculate transaction ID */
tx_id = netvsc->tx.ids[ netvsc->tx.id_prod & ( netvsc->tx.count - 1 ) ];
assert ( netvsc->tx.iobufs[tx_id] == NULL );
xrid = ( NETVSC_TX_BASE_XRID + tx_id );
xid = ( NETVSC_BASE_XID + xrid );
/* Construct message */
memset ( &msg, 0, sizeof ( msg ) );
msg.header.type = cpu_to_le32 ( NETVSC_RNDIS_MSG );
msg.channel = ( ( header->type == cpu_to_le32 ( RNDIS_PACKET_MSG ) ) ?
NETVSC_RNDIS_DATA : NETVSC_RNDIS_CONTROL );
msg.buffer = cpu_to_le32 ( NETVSC_RNDIS_NO_BUFFER );
/* Send message */
if ( ( rc = vmbus_send_data ( netvsc->vmdev, xid, &msg, sizeof ( msg ),
iobuf ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not send RNDIS message: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
/* Store I/O buffer and consume buffer ID */
netvsc->tx.iobufs[tx_id] = iobuf;
netvsc->tx.id_prod++;
return 0;
}
/**
* Cancel transmission
*
* @v netvsc NetVSC device
* @v iobuf I/O buffer
* @v tx_id Transmission ID
*/
static void netvsc_cancel_transmit ( struct netvsc_device *netvsc,
struct io_buffer *iobuf,
unsigned int tx_id ) {
unsigned int xrid;
uint64_t xid;
/* Send cancellation */
xrid = ( NETVSC_TX_BASE_XRID + tx_id );
xid = ( NETVSC_BASE_XID + xrid );
DBGC ( netvsc, "NETVSC %s cancelling transmission %#x\n",
netvsc->name, tx_id );
vmbus_send_cancellation ( netvsc->vmdev, xid );
/* Report back to RNDIS */
rndis_tx_complete_err ( netvsc->rndis, iobuf, -ECANCELED );
}
/**
* Create descriptor ring
*
* @v netvsc NetVSC device
* @v ring Descriptor ring
* @ret rc Return status code
*/
static int netvsc_create_ring ( struct netvsc_device *netvsc __unused,
struct netvsc_ring *ring ) {
unsigned int i;
/* Initialise buffer ID ring */
for ( i = 0 ; i < ring->count ; i++ ) {
ring->ids[i] = i;
assert ( ring->iobufs[i] == NULL );
}
ring->id_prod = 0;
ring->id_cons = 0;
return 0;
}
/**
* Destroy descriptor ring
*
* @v netvsc NetVSC device
* @v ring Descriptor ring
* @v discard Method used to discard outstanding buffer, or NULL
*/
static void netvsc_destroy_ring ( struct netvsc_device *netvsc,
struct netvsc_ring *ring,
void ( * discard ) ( struct netvsc_device *,
struct io_buffer *,
unsigned int ) ) {
struct io_buffer *iobuf;
unsigned int i;
/* Flush any outstanding buffers */
for ( i = 0 ; i < ring->count ; i++ ) {
iobuf = ring->iobufs[i];
if ( ! iobuf )
continue;
ring->iobufs[i] = NULL;
ring->ids[ ( ring->id_cons++ ) & ( ring->count - 1 ) ] = i;
if ( discard )
discard ( netvsc, iobuf, i );
}
/* Sanity check */
assert ( netvsc_ring_is_empty ( ring ) );
}
/**
* Copy data from data buffer
*
* @v pages Transfer page set
* @v data Data buffer
* @v offset Offset within page set
* @v len Length within page set
* @ret rc Return status code
*/
static int netvsc_buffer_copy ( struct vmbus_xfer_pages *pages, void *data,
size_t offset, size_t len ) {
struct netvsc_buffer *buffer =
container_of ( pages, struct netvsc_buffer, pages );
/* Sanity check */
if ( ( offset > buffer->len ) || ( len > ( buffer->len - offset ) ) )
return -ERANGE;
/* Copy data from buffer */
copy_from_user ( data, buffer->data, offset, len );
return 0;
}
/** Transfer page set operations */
static struct vmbus_xfer_pages_operations netvsc_xfer_pages_operations = {
.copy = netvsc_buffer_copy,
};
/**
* Create data buffer
*
* @v netvsc NetVSC device
* @v buffer Data buffer
* @ret rc Return status code
*/
static int netvsc_create_buffer ( struct netvsc_device *netvsc,
struct netvsc_buffer *buffer ) {
struct vmbus_device *vmdev = netvsc->vmdev;
int gpadl;
int rc;
/* Allocate receive buffer */
buffer->data = umalloc ( buffer->len );
if ( ! buffer->data ) {
DBGC ( netvsc, "NETVSC %s could not allocate %zd-byte buffer\n",
netvsc->name, buffer->len );
rc = -ENOMEM;
goto err_alloc;
}
/* Establish GPA descriptor list */
gpadl = vmbus_establish_gpadl ( vmdev, buffer->data, buffer->len );
if ( gpadl < 0 ) {
rc = gpadl;
DBGC ( netvsc, "NETVSC %s could not establish GPADL: %s\n",
netvsc->name, strerror ( rc ) );
goto err_establish_gpadl;
}
buffer->gpadl = gpadl;
/* Register transfer page set */
if ( ( rc = vmbus_register_pages ( vmdev, &buffer->pages ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not register transfer pages: "
"%s\n", netvsc->name, strerror ( rc ) );
goto err_register_pages;
}
return 0;
vmbus_unregister_pages ( vmdev, &buffer->pages );
err_register_pages:
vmbus_gpadl_teardown ( vmdev, gpadl );
err_establish_gpadl:
ufree ( buffer->data );
err_alloc:
return rc;
}
/**
* Destroy data buffer
*
* @v netvsc NetVSC device
* @v buffer Data buffer
*/
static void netvsc_destroy_buffer ( struct netvsc_device *netvsc,
struct netvsc_buffer *buffer ) {
struct vmbus_device *vmdev = netvsc->vmdev;
int rc;
/* Unregister transfer pages */
vmbus_unregister_pages ( vmdev, &buffer->pages );
/* Tear down GPA descriptor list */
if ( ( rc = vmbus_gpadl_teardown ( vmdev, buffer->gpadl ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not tear down GPADL: %s\n",
netvsc->name, strerror ( rc ) );
/* Death is imminent. The host may well continue to
* write to the data buffer. The best we can do is
* leak memory for now and hope that the host doesn't
* write to this region after we load an OS.
*/
return;
}
/* Free buffer */
ufree ( buffer->data );
}
/**
* Open device
*
* @v rndis RNDIS device
* @ret rc Return status code
*/
static int netvsc_open ( struct rndis_device *rndis ) {
struct netvsc_device *netvsc = rndis->priv;
int rc;
/* Initialise receive buffer */
if ( ( rc = netvsc_create_buffer ( netvsc, &netvsc->rx ) ) != 0 )
goto err_create_rx;
/* Open channel */
if ( ( rc = vmbus_open ( netvsc->vmdev, &netvsc_channel_operations,
PAGE_SIZE, PAGE_SIZE, NETVSC_MTU ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not open VMBus: %s\n",
netvsc->name, strerror ( rc ) );
goto err_vmbus_open;
}
/* Initialise communication with NetVSP */
if ( ( rc = netvsc_initialise ( netvsc ) ) != 0 )
goto err_initialise;
if ( ( rc = netvsc_ndis_version ( netvsc ) ) != 0 )
goto err_ndis_version;
/* Initialise transmit ring */
if ( ( rc = netvsc_create_ring ( netvsc, &netvsc->tx ) ) != 0 )
goto err_create_tx;
/* Establish receive buffer */
if ( ( rc = netvsc_establish_buffer ( netvsc, &netvsc->rx ) ) != 0 )
goto err_establish_rx;
return 0;
netvsc_revoke_buffer ( netvsc, &netvsc->rx );
err_establish_rx:
netvsc_destroy_ring ( netvsc, &netvsc->tx, NULL );
err_create_tx:
err_ndis_version:
err_initialise:
vmbus_close ( netvsc->vmdev );
err_vmbus_open:
netvsc_destroy_buffer ( netvsc, &netvsc->rx );
err_create_rx:
return rc;
}
/**
* Close device
*
* @v rndis RNDIS device
*/
static void netvsc_close ( struct rndis_device *rndis ) {
struct netvsc_device *netvsc = rndis->priv;
/* Revoke receive buffer */
netvsc_revoke_buffer ( netvsc, &netvsc->rx );
/* Destroy transmit ring */
netvsc_destroy_ring ( netvsc, &netvsc->tx, netvsc_cancel_transmit );
/* Close channel */
vmbus_close ( netvsc->vmdev );
/* Destroy receive buffer */
netvsc_destroy_buffer ( netvsc, &netvsc->rx );
}
/** RNDIS operations */
static struct rndis_operations netvsc_operations = {
.open = netvsc_open,
.close = netvsc_close,
.transmit = netvsc_transmit,
.poll = netvsc_poll,
};
/**
* Probe device
*
* @v vmdev VMBus device
* @ret rc Return status code
*/
static int netvsc_probe ( struct vmbus_device *vmdev ) {
struct netvsc_device *netvsc;
struct rndis_device *rndis;
int rc;
/* Allocate and initialise structure */
rndis = alloc_rndis ( sizeof ( *netvsc ) );
if ( ! rndis ) {
rc = -ENOMEM;
goto err_alloc;
}
rndis_init ( rndis, &netvsc_operations );
rndis->netdev->dev = &vmdev->dev;
netvsc = rndis->priv;
netvsc->vmdev = vmdev;
netvsc->rndis = rndis;
netvsc->name = vmdev->dev.name;
netvsc_init_ring ( &netvsc->tx, NETVSC_TX_NUM_DESC,
netvsc->tx_iobufs, netvsc->tx_ids );
netvsc_init_buffer ( &netvsc->rx, NETVSC_RX_BUF_PAGESET,
&netvsc_xfer_pages_operations,
NETVSC_RX_ESTABLISH_MSG, NETVSC_RX_ESTABLISH_XRID,
NETVSC_RX_REVOKE_MSG, NETVSC_RX_REVOKE_XRID,
NETVSC_RX_BUF_LEN );
vmbus_set_drvdata ( vmdev, rndis );
/* Register RNDIS device */
if ( ( rc = register_rndis ( rndis ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not register: %s\n",
netvsc->name, strerror ( rc ) );
goto err_register;
}
return 0;
unregister_rndis ( rndis );
err_register:
free_rndis ( rndis );
err_alloc:
return rc;
}
/**
* Reset device
*
* @v vmdev VMBus device
* @ret rc Return status code
*/
static int netvsc_reset ( struct vmbus_device *vmdev ) {
struct rndis_device *rndis = vmbus_get_drvdata ( vmdev );
struct netvsc_device *netvsc = rndis->priv;
struct net_device *netdev = rndis->netdev;
int rc;
/* A closed device holds no NetVSC (or RNDIS) state, so there
* is nothing to reset.
*/
if ( ! netdev_is_open ( netdev ) )
return 0;
/* Close and reopen device to reset any stale state */
netdev_close ( netdev );
if ( ( rc = netdev_open ( netdev ) ) != 0 ) {
DBGC ( netvsc, "NETVSC %s could not reopen: %s\n",
netvsc->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Remove device
*
* @v vmdev VMBus device
*/
static void netvsc_remove ( struct vmbus_device *vmdev ) {
struct rndis_device *rndis = vmbus_get_drvdata ( vmdev );
/* Unregister RNDIS device */
unregister_rndis ( rndis );
/* Free RNDIS device */
free_rndis ( rndis );
}
/** NetVSC driver */
struct vmbus_driver netvsc_driver __vmbus_driver = {
.name = "netvsc",
.type = VMBUS_TYPE ( 0xf8615163, 0xdf3e, 0x46c5, 0x913f,
0xf2, 0xd2, 0xf9, 0x65, 0xed, 0x0e ),
.probe = netvsc_probe,
.reset = netvsc_reset,
.remove = netvsc_remove,
};