These changes are the raw update to qemu-2.6.
[kvmfornfv.git] / qemu / roms / ipxe / src / drivers / net / netvsc.c
diff --git a/qemu/roms/ipxe/src/drivers/net/netvsc.c b/qemu/roms/ipxe/src/drivers/net/netvsc.c
new file mode 100644 (file)
index 0000000..d269cd6
--- /dev/null
@@ -0,0 +1,848 @@
+/*
+ * 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;
+
+       /* 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;
+
+       /* 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;
+}
+
+/**
+ * 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,
+       .remove = netvsc_remove,
+};