2010-07-02 20:15:47 +02:00
|
|
|
/*
|
|
|
|
* (c) Copyright 2010 Stefan Hajnoczi <stefanha@gmail.com>
|
|
|
|
*
|
|
|
|
* based on the Etherboot virtio-net driver
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
2010-07-02 20:15:47 +02:00
|
|
|
* (c) Copyright 2008 Bull S.A.S.
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
|
|
|
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
|
|
|
*
|
|
|
|
* some parts from Linux Virtio PCI driver
|
|
|
|
*
|
|
|
|
* Copyright IBM Corp. 2007
|
|
|
|
* Authors: Anthony Liguori <aliguori@us.ibm.com>
|
|
|
|
*
|
|
|
|
* some parts from Linux Virtio Ring
|
|
|
|
*
|
|
|
|
* Copyright Rusty Russell IBM Corporation 2007
|
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
|
|
* See the COPYING file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
FILE_LICENCE ( GPL2_OR_LATER );
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ipxe/list.h>
|
|
|
|
#include <ipxe/iobuf.h>
|
|
|
|
#include <ipxe/netdevice.h>
|
|
|
|
#include <ipxe/pci.h>
|
|
|
|
#include <ipxe/if_ether.h>
|
|
|
|
#include <ipxe/ethernet.h>
|
|
|
|
#include <ipxe/virtio-ring.h>
|
|
|
|
#include <ipxe/virtio-pci.h>
|
2008-08-01 17:55:49 +02:00
|
|
|
#include "virtio-net.h"
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/*
|
|
|
|
* Virtio network device driver
|
|
|
|
*
|
|
|
|
* Specification:
|
|
|
|
* http://ozlabs.org/~rusty/virtio-spec/
|
|
|
|
*
|
|
|
|
* The virtio network device is supported by Linux virtualization software
|
|
|
|
* including QEMU/KVM and lguest. This driver supports the virtio over PCI
|
|
|
|
* transport; virtual machines have one virtio-net PCI adapter per NIC.
|
|
|
|
*
|
|
|
|
* Virtio-net is different from hardware NICs because virtio devices
|
|
|
|
* communicate with the hypervisor via virtqueues, not traditional descriptor
|
|
|
|
* rings. Virtqueues are unordered queues, they support add_buf() and
|
|
|
|
* get_buf() operations. To transmit a packet, the driver has to add the
|
|
|
|
* packet buffer onto the virtqueue. To receive a packet, the driver must
|
|
|
|
* first add an empty buffer to the virtqueue and then get the filled packet
|
|
|
|
* buffer on completion.
|
|
|
|
*
|
|
|
|
* Virtqueues are an abstraction that is commonly implemented using the vring
|
|
|
|
* descriptor ring layout. The vring is the actual shared memory structure
|
|
|
|
* that allows the virtual machine to communicate buffers with the hypervisor.
|
|
|
|
* Because the vring layout is optimized for flexibility and performance rather
|
|
|
|
* than space, it is heavy-weight and allocated like traditional descriptor
|
|
|
|
* rings in the open() function of the driver and not in probe().
|
|
|
|
*
|
|
|
|
* There is no true interrupt enable/disable. Virtqueues have callback
|
|
|
|
* enable/disable flags but these are only hints. The hypervisor may still
|
|
|
|
* raise an interrupt. Nevertheless, this driver disables callbacks in the
|
|
|
|
* hopes of avoiding interrupts.
|
|
|
|
*/
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Driver types are declared here so virtio-net.h can be easily synced with its
|
|
|
|
* Linux source.
|
|
|
|
*/
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Virtqueue indicies */
|
|
|
|
enum {
|
|
|
|
RX_INDEX = 0,
|
|
|
|
TX_INDEX,
|
|
|
|
QUEUE_NB
|
2008-08-01 17:55:49 +02:00
|
|
|
};
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
enum {
|
|
|
|
/** Max number of pending rx packets */
|
|
|
|
NUM_RX_BUF = 8,
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Max Ethernet frame length, including FCS and VLAN tag */
|
|
|
|
RX_BUF_SIZE = 1522,
|
|
|
|
};
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
struct virtnet_nic {
|
|
|
|
/** Base pio register address */
|
|
|
|
unsigned long ioaddr;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** RX/TX virtqueues */
|
|
|
|
struct vring_virtqueue *virtqueue;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** RX packets handed to the NIC waiting to be filled in */
|
|
|
|
struct list_head rx_iobufs;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Pending rx packet count */
|
|
|
|
unsigned int rx_num_iobufs;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Virtio net packet header, we only need one */
|
|
|
|
struct virtio_net_hdr empty_header;
|
2008-08-01 17:55:49 +02:00
|
|
|
};
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Add an iobuf to a virtqueue
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
2010-07-02 20:15:47 +02:00
|
|
|
* @v netdev Network device
|
|
|
|
* @v vq_idx Virtqueue index (RX_INDEX or TX_INDEX)
|
|
|
|
* @v iobuf I/O buffer
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
2010-07-02 20:15:47 +02:00
|
|
|
* The virtqueue is kicked after the iobuf has been added.
|
2008-08-01 17:55:49 +02:00
|
|
|
*/
|
2010-07-02 20:15:47 +02:00
|
|
|
static void virtnet_enqueue_iob ( struct net_device *netdev,
|
|
|
|
int vq_idx, struct io_buffer *iobuf ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
struct vring_virtqueue *vq = &virtnet->virtqueue[vq_idx];
|
|
|
|
unsigned int out = ( vq_idx == TX_INDEX ) ? 2 : 0;
|
|
|
|
unsigned int in = ( vq_idx == TX_INDEX ) ? 0 : 2;
|
|
|
|
struct vring_list list[] = {
|
|
|
|
{
|
|
|
|
/* Share a single zeroed virtio net header between all
|
|
|
|
* rx and tx packets. This works because this driver
|
|
|
|
* does not use any advanced features so none of the
|
|
|
|
* header fields get used.
|
|
|
|
*/
|
|
|
|
.addr = ( char* ) &virtnet->empty_header,
|
|
|
|
.length = sizeof ( virtnet->empty_header ),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.addr = ( char* ) iobuf->data,
|
|
|
|
.length = iob_len ( iobuf ),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
DBGC ( virtnet, "VIRTIO-NET %p enqueuing iobuf %p on vq %d\n",
|
|
|
|
virtnet, iobuf, vq_idx );
|
|
|
|
|
|
|
|
vring_add_buf ( vq, list, out, in, iobuf, 0 );
|
|
|
|
vring_kick ( virtnet->ioaddr, vq, 1 );
|
2008-08-01 17:55:49 +02:00
|
|
|
}
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Try to keep rx virtqueue filled with iobufs
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
2010-07-02 20:15:47 +02:00
|
|
|
* @v netdev Network device
|
2008-08-01 17:55:49 +02:00
|
|
|
*/
|
2010-07-02 20:15:47 +02:00
|
|
|
static void virtnet_refill_rx_virtqueue ( struct net_device *netdev ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
while ( virtnet->rx_num_iobufs < NUM_RX_BUF ) {
|
|
|
|
struct io_buffer *iobuf;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Try to allocate a buffer, stop for now if out of memory */
|
|
|
|
iobuf = alloc_iob ( RX_BUF_SIZE );
|
|
|
|
if ( ! iobuf )
|
|
|
|
break;
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Keep track of iobuf so close() can free it */
|
|
|
|
list_add ( &iobuf->list, &virtnet->rx_iobufs );
|
2008-11-19 17:28:26 +01:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Mark packet length until we know the actual size */
|
|
|
|
iob_put ( iobuf, RX_BUF_SIZE );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
virtnet_enqueue_iob ( netdev, RX_INDEX, iobuf );
|
|
|
|
virtnet->rx_num_iobufs++;
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
}
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Open network device
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
2010-07-02 20:15:47 +02:00
|
|
|
* @v netdev Network device
|
|
|
|
* @ret rc Return status code
|
2008-08-01 17:55:49 +02:00
|
|
|
*/
|
2010-07-02 20:15:47 +02:00
|
|
|
static int virtnet_open ( struct net_device *netdev ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
unsigned long ioaddr = virtnet->ioaddr;
|
|
|
|
u32 features;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Reset for sanity */
|
|
|
|
vp_reset ( ioaddr );
|
|
|
|
|
|
|
|
/* Allocate virtqueues */
|
|
|
|
virtnet->virtqueue = zalloc ( QUEUE_NB *
|
|
|
|
sizeof ( *virtnet->virtqueue ) );
|
|
|
|
if ( ! virtnet->virtqueue )
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* Initialize rx/tx virtqueues */
|
|
|
|
for ( i = 0; i < QUEUE_NB; i++ ) {
|
|
|
|
if ( vp_find_vq ( ioaddr, i, &virtnet->virtqueue[i] ) == -1 ) {
|
|
|
|
DBGC ( virtnet, "VIRTIO-NET %p cannot register queue %d\n",
|
|
|
|
virtnet, i );
|
|
|
|
free ( virtnet->virtqueue );
|
|
|
|
virtnet->virtqueue = NULL;
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize rx packets */
|
|
|
|
INIT_LIST_HEAD ( &virtnet->rx_iobufs );
|
|
|
|
virtnet->rx_num_iobufs = 0;
|
|
|
|
virtnet_refill_rx_virtqueue ( netdev );
|
|
|
|
|
|
|
|
/* Disable interrupts before starting */
|
|
|
|
netdev_irq ( netdev, 0 );
|
|
|
|
|
|
|
|
/* Driver is ready */
|
|
|
|
features = vp_get_features ( ioaddr );
|
|
|
|
vp_set_features ( ioaddr, features & ( 1 << VIRTIO_NET_F_MAC ) );
|
|
|
|
vp_set_status ( ioaddr, VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK );
|
|
|
|
return 0;
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Close network device
|
|
|
|
*
|
|
|
|
* @v netdev Network device
|
|
|
|
*/
|
|
|
|
static void virtnet_close ( struct net_device *netdev ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
struct io_buffer *iobuf;
|
|
|
|
struct io_buffer *next_iobuf;
|
|
|
|
|
|
|
|
vp_reset ( virtnet->ioaddr );
|
|
|
|
|
|
|
|
/* Virtqueues can be freed now that NIC is reset */
|
|
|
|
free ( virtnet->virtqueue );
|
|
|
|
virtnet->virtqueue = NULL;
|
|
|
|
|
|
|
|
/* Free rx iobufs */
|
|
|
|
list_for_each_entry_safe ( iobuf, next_iobuf, &virtnet->rx_iobufs, list ) {
|
|
|
|
free_iob ( iobuf );
|
|
|
|
}
|
|
|
|
INIT_LIST_HEAD ( &virtnet->rx_iobufs );
|
|
|
|
virtnet->rx_num_iobufs = 0;
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Transmit packet
|
|
|
|
*
|
|
|
|
* @v netdev Network device
|
|
|
|
* @v iobuf I/O buffer
|
|
|
|
* @ret rc Return status code
|
|
|
|
*/
|
|
|
|
static int virtnet_transmit ( struct net_device *netdev,
|
|
|
|
struct io_buffer *iobuf ) {
|
|
|
|
virtnet_enqueue_iob ( netdev, TX_INDEX, iobuf );
|
|
|
|
return 0;
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Complete packet transmission
|
|
|
|
*
|
|
|
|
* @v netdev Network device
|
|
|
|
*/
|
|
|
|
static void virtnet_process_tx_packets ( struct net_device *netdev ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
struct vring_virtqueue *tx_vq = &virtnet->virtqueue[TX_INDEX];
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
while ( vring_more_used ( tx_vq ) ) {
|
|
|
|
struct io_buffer *iobuf = vring_get_buf ( tx_vq, NULL );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
DBGC ( virtnet, "VIRTIO-NET %p tx complete iobuf %p\n",
|
|
|
|
virtnet, iobuf );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
netdev_tx_complete ( netdev, iobuf );
|
|
|
|
}
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Complete packet reception
|
|
|
|
*
|
|
|
|
* @v netdev Network device
|
|
|
|
*/
|
|
|
|
static void virtnet_process_rx_packets ( struct net_device *netdev ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
struct vring_virtqueue *rx_vq = &virtnet->virtqueue[RX_INDEX];
|
2008-11-19 17:28:26 +01:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
while ( vring_more_used ( rx_vq ) ) {
|
|
|
|
unsigned int len;
|
|
|
|
struct io_buffer *iobuf = vring_get_buf ( rx_vq, &len );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Release ownership of iobuf */
|
|
|
|
list_del ( &iobuf->list );
|
|
|
|
virtnet->rx_num_iobufs--;
|
2008-10-08 22:02:33 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Update iobuf length */
|
|
|
|
iob_unput ( iobuf, RX_BUF_SIZE );
|
|
|
|
iob_put ( iobuf, len - sizeof ( struct virtio_net_hdr ) );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
DBGC ( virtnet, "VIRTIO-NET %p rx complete iobuf %p len %zd\n",
|
|
|
|
virtnet, iobuf, iob_len ( iobuf ) );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Pass completed packet to the network stack */
|
|
|
|
netdev_rx ( netdev, iobuf );
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
virtnet_refill_rx_virtqueue ( netdev );
|
2008-08-01 17:55:49 +02:00
|
|
|
}
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Poll for completed and received packets
|
|
|
|
*
|
|
|
|
* @v netdev Network device
|
|
|
|
*/
|
|
|
|
static void virtnet_poll ( struct net_device *netdev ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
|
|
|
|
/* Acknowledge interrupt. This is necessary for UNDI operation and
|
|
|
|
* interrupts that are raised despite VRING_AVAIL_F_NO_INTERRUPT being
|
|
|
|
* set (that flag is just a hint and the hypervisor not not have to
|
|
|
|
* honor it).
|
|
|
|
*/
|
|
|
|
vp_get_isr ( virtnet->ioaddr );
|
|
|
|
|
|
|
|
virtnet_process_tx_packets ( netdev );
|
|
|
|
virtnet_process_rx_packets ( netdev );
|
2008-08-01 17:55:49 +02:00
|
|
|
}
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** Enable or disable interrupts
|
|
|
|
*
|
|
|
|
* @v netdev Network device
|
|
|
|
* @v enable Interrupts should be enabled
|
|
|
|
*/
|
|
|
|
static void virtnet_irq ( struct net_device *netdev, int enable ) {
|
|
|
|
struct virtnet_nic *virtnet = netdev->priv;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for ( i = 0; i < QUEUE_NB; i++ ) {
|
|
|
|
if ( enable )
|
|
|
|
vring_enable_cb ( &virtnet->virtqueue[i] );
|
|
|
|
else
|
|
|
|
vring_disable_cb ( &virtnet->virtqueue[i] );
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
}
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/** virtio-net device operations */
|
|
|
|
static struct net_device_operations virtnet_operations = {
|
|
|
|
.open = virtnet_open,
|
|
|
|
.close = virtnet_close,
|
2008-08-01 17:55:49 +02:00
|
|
|
.transmit = virtnet_transmit,
|
2010-07-02 20:15:47 +02:00
|
|
|
.poll = virtnet_poll,
|
2008-08-01 17:55:49 +02:00
|
|
|
.irq = virtnet_irq,
|
|
|
|
};
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/**
|
|
|
|
* Probe PCI device
|
2008-08-01 17:55:49 +02:00
|
|
|
*
|
2010-07-02 20:15:47 +02:00
|
|
|
* @v pci PCI device
|
|
|
|
* @v id PCI ID
|
|
|
|
* @ret rc Return status code
|
2008-08-01 17:55:49 +02:00
|
|
|
*/
|
2011-02-12 02:11:57 +01:00
|
|
|
static int virtnet_probe ( struct pci_device *pci ) {
|
2010-07-02 20:15:47 +02:00
|
|
|
unsigned long ioaddr = pci->ioaddr;
|
|
|
|
struct net_device *netdev;
|
|
|
|
struct virtnet_nic *virtnet;
|
|
|
|
u32 features;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* Allocate and hook up net device */
|
|
|
|
netdev = alloc_etherdev ( sizeof ( *virtnet ) );
|
|
|
|
if ( ! netdev )
|
|
|
|
return -ENOMEM;
|
|
|
|
netdev_init ( netdev, &virtnet_operations );
|
|
|
|
virtnet = netdev->priv;
|
|
|
|
virtnet->ioaddr = ioaddr;
|
|
|
|
pci_set_drvdata ( pci, netdev );
|
|
|
|
netdev->dev = &pci->dev;
|
|
|
|
|
|
|
|
DBGC ( virtnet, "VIRTIO-NET %p busaddr=%s ioaddr=%#lx irq=%d\n",
|
|
|
|
virtnet, pci->dev.name, ioaddr, pci->irq );
|
|
|
|
|
|
|
|
/* Enable PCI bus master and reset NIC */
|
|
|
|
adjust_pci_device ( pci );
|
|
|
|
vp_reset ( ioaddr );
|
|
|
|
|
|
|
|
/* Load MAC address */
|
|
|
|
features = vp_get_features ( ioaddr );
|
|
|
|
if ( features & ( 1 << VIRTIO_NET_F_MAC ) ) {
|
|
|
|
vp_get ( ioaddr, offsetof ( struct virtio_net_config, mac ),
|
|
|
|
netdev->hw_addr, ETH_ALEN );
|
|
|
|
DBGC ( virtnet, "VIRTIO-NET %p mac=%s\n", virtnet,
|
|
|
|
eth_ntoa ( netdev->hw_addr ) );
|
|
|
|
}
|
|
|
|
|
2010-09-05 03:03:31 +02:00
|
|
|
/* Register network device */
|
|
|
|
if ( ( rc = register_netdev ( netdev ) ) != 0 )
|
|
|
|
goto err_register_netdev;
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/* Mark link as up, control virtqueue is not used */
|
|
|
|
netdev_link_up ( netdev );
|
|
|
|
|
2010-09-05 03:03:31 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
unregister_netdev ( netdev );
|
|
|
|
err_register_netdev:
|
|
|
|
vp_reset ( ioaddr );
|
|
|
|
netdev_nullify ( netdev );
|
|
|
|
netdev_put ( netdev );
|
2010-07-02 20:15:47 +02:00
|
|
|
return rc;
|
|
|
|
}
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
/**
|
|
|
|
* Remove device
|
|
|
|
*
|
|
|
|
* @v pci PCI device
|
|
|
|
*/
|
|
|
|
static void virtnet_remove ( struct pci_device *pci ) {
|
|
|
|
struct net_device *netdev = pci_get_drvdata ( pci );
|
2008-08-01 17:55:49 +02:00
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
unregister_netdev ( netdev );
|
|
|
|
netdev_nullify ( netdev );
|
|
|
|
netdev_put ( netdev );
|
2008-08-01 17:55:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct pci_device_id virtnet_nics[] = {
|
2010-07-02 20:15:47 +02:00
|
|
|
PCI_ROM(0x1af4, 0x1000, "virtio-net", "Virtio Network Interface", 0),
|
2008-08-01 17:55:49 +02:00
|
|
|
};
|
|
|
|
|
2010-07-02 20:15:47 +02:00
|
|
|
struct pci_driver virtnet_driver __pci_driver = {
|
|
|
|
.ids = virtnet_nics,
|
|
|
|
.id_count = ( sizeof ( virtnet_nics ) / sizeof ( virtnet_nics[0] ) ),
|
|
|
|
.probe = virtnet_probe,
|
|
|
|
.remove = virtnet_remove,
|
|
|
|
};
|