summaryrefslogblamecommitdiffstats
path: root/src/drivers/net/netvsc.c
blob: 5be52fb8ec909ac787ab6c13b8c85fe22082337d (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                                      



                                                                    

   
                                       












































































































































































































































                                                                                








                                                                      






                                                                          


                                                                         
                          
         






























                                                                          
                                           



                                                                       
                                                        


                                                                 

                                
















                                                                             
                                          





                                                                            




                                                             




                 



                                                             































































































                                                                             
                                                   

                               

                                          





















                                                            







                                                                     



















































































































































































                                                                                

                 



















                                                                    




























                                                                            



                                                                         

















                                                                              


                                                                            


                 

                                                     





                                                          

                                                      










                                                         

                                                     





                                                                            


                                                      


























































                                                                               




























                                                                     



















                                                                  
                              

                                
/*
 * 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
 * @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,
			      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;
	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;
	}

	/* 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;
	}

	/* 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:
	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,
};