-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This attempts to implement a "virtual I/O" layer which should allow common drivers to be efficiently used across most virtual I/O mechanisms. It will no-doubt need further enhancement. The virtio drivers add buffers to virtio queues; as the buffers are consumed the driver "interrupt" callbacks are invoked. There is also a generic implementation of config space which drivers can query to get setup information from the host. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Cc: Dor Laor <dor.laor@qumranet.com> Cc: Arnd Bergmann <arnd@arndb.de>
- Loading branch information
1 parent
47436aa
commit ec3d41c
Showing
10 changed files
with
419 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Virtio always gets selected by whoever wants it. | ||
config VIRTIO | ||
bool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
obj-$(CONFIG_VIRTIO) += virtio.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* Configuration space parsing helpers for virtio. | ||
* | ||
* The configuration is [type][len][... len bytes ...] fields. | ||
* | ||
* Copyright 2007 Rusty Russell, IBM Corporation. | ||
* GPL v2 or later. | ||
*/ | ||
#include <linux/err.h> | ||
#include <linux/virtio.h> | ||
#include <linux/virtio_config.h> | ||
#include <linux/bug.h> | ||
#include <asm/system.h> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
#include <linux/virtio.h> | ||
#include <linux/spinlock.h> | ||
#include <linux/virtio_config.h> | ||
|
||
static ssize_t device_show(struct device *_d, | ||
struct device_attribute *attr, char *buf) | ||
{ | ||
struct virtio_device *dev = container_of(_d,struct virtio_device,dev); | ||
return sprintf(buf, "%hu", dev->id.device); | ||
} | ||
static ssize_t vendor_show(struct device *_d, | ||
struct device_attribute *attr, char *buf) | ||
{ | ||
struct virtio_device *dev = container_of(_d,struct virtio_device,dev); | ||
return sprintf(buf, "%hu", dev->id.vendor); | ||
} | ||
static ssize_t status_show(struct device *_d, | ||
struct device_attribute *attr, char *buf) | ||
{ | ||
struct virtio_device *dev = container_of(_d,struct virtio_device,dev); | ||
return sprintf(buf, "0x%08x", dev->config->get_status(dev)); | ||
} | ||
static struct device_attribute virtio_dev_attrs[] = { | ||
__ATTR_RO(device), | ||
__ATTR_RO(vendor), | ||
__ATTR_RO(status), | ||
__ATTR_NULL | ||
}; | ||
|
||
static inline int virtio_id_match(const struct virtio_device *dev, | ||
const struct virtio_device_id *id) | ||
{ | ||
if (id->device != dev->id.device) | ||
return 0; | ||
|
||
return id->vendor == VIRTIO_DEV_ANY_ID || id->vendor != dev->id.vendor; | ||
} | ||
|
||
/* This looks through all the IDs a driver claims to support. If any of them | ||
* match, we return 1 and the kernel will call virtio_dev_probe(). */ | ||
static int virtio_dev_match(struct device *_dv, struct device_driver *_dr) | ||
{ | ||
unsigned int i; | ||
struct virtio_device *dev = container_of(_dv,struct virtio_device,dev); | ||
const struct virtio_device_id *ids; | ||
|
||
ids = container_of(_dr, struct virtio_driver, driver)->id_table; | ||
for (i = 0; ids[i].device; i++) | ||
if (virtio_id_match(dev, &ids[i])) | ||
return 1; | ||
return 0; | ||
} | ||
|
||
static struct bus_type virtio_bus = { | ||
.name = "virtio", | ||
.match = virtio_dev_match, | ||
.dev_attrs = virtio_dev_attrs, | ||
}; | ||
|
||
static void add_status(struct virtio_device *dev, unsigned status) | ||
{ | ||
dev->config->set_status(dev, dev->config->get_status(dev) | status); | ||
} | ||
|
||
static int virtio_dev_probe(struct device *_d) | ||
{ | ||
int err; | ||
struct virtio_device *dev = container_of(_d,struct virtio_device,dev); | ||
struct virtio_driver *drv = container_of(dev->dev.driver, | ||
struct virtio_driver, driver); | ||
|
||
add_status(dev, VIRTIO_CONFIG_S_DRIVER); | ||
err = drv->probe(dev); | ||
if (err) | ||
add_status(dev, VIRTIO_CONFIG_S_FAILED); | ||
else | ||
add_status(dev, VIRTIO_CONFIG_S_DRIVER_OK); | ||
return err; | ||
} | ||
|
||
int register_virtio_driver(struct virtio_driver *driver) | ||
{ | ||
driver->driver.bus = &virtio_bus; | ||
driver->driver.probe = virtio_dev_probe; | ||
return driver_register(&driver->driver); | ||
} | ||
EXPORT_SYMBOL_GPL(register_virtio_driver); | ||
|
||
void unregister_virtio_driver(struct virtio_driver *driver) | ||
{ | ||
driver_unregister(&driver->driver); | ||
} | ||
EXPORT_SYMBOL_GPL(unregister_virtio_driver); | ||
|
||
int register_virtio_device(struct virtio_device *dev) | ||
{ | ||
int err; | ||
|
||
dev->dev.bus = &virtio_bus; | ||
sprintf(dev->dev.bus_id, "%u", dev->index); | ||
|
||
/* Acknowledge that we've seen the device. */ | ||
add_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE); | ||
|
||
/* device_register() causes the bus infrastructure to look for a | ||
* matching driver. */ | ||
err = device_register(&dev->dev); | ||
if (err) | ||
add_status(dev, VIRTIO_CONFIG_S_FAILED); | ||
return err; | ||
} | ||
EXPORT_SYMBOL_GPL(register_virtio_device); | ||
|
||
void unregister_virtio_device(struct virtio_device *dev) | ||
{ | ||
device_unregister(&dev->dev); | ||
} | ||
EXPORT_SYMBOL_GPL(unregister_virtio_device); | ||
|
||
int __virtio_config_val(struct virtio_device *vdev, | ||
u8 type, void *val, size_t size) | ||
{ | ||
void *token; | ||
unsigned int len; | ||
|
||
token = vdev->config->find(vdev, type, &len); | ||
if (!token) | ||
return -ENOENT; | ||
|
||
if (len != size) | ||
return -EIO; | ||
|
||
vdev->config->get(vdev, token, val, size); | ||
return 0; | ||
} | ||
EXPORT_SYMBOL_GPL(__virtio_config_val); | ||
|
||
int virtio_use_bit(struct virtio_device *vdev, | ||
void *token, unsigned int len, unsigned int bitnum) | ||
{ | ||
unsigned long bits[16]; | ||
|
||
/* This makes it convenient to pass-through find() results. */ | ||
if (!token) | ||
return 0; | ||
|
||
/* bit not in range of this bitfield? */ | ||
if (bitnum * 8 >= len / 2) | ||
return 0; | ||
|
||
/* Giant feature bitfields are silly. */ | ||
BUG_ON(len > sizeof(bits)); | ||
vdev->config->get(vdev, token, bits, len); | ||
|
||
if (!test_bit(bitnum, bits)) | ||
return 0; | ||
|
||
/* Set acknowledge bit, and write it back. */ | ||
set_bit(bitnum + len * 8 / 2, bits); | ||
vdev->config->set(vdev, token, bits, len); | ||
return 1; | ||
} | ||
EXPORT_SYMBOL_GPL(virtio_use_bit); | ||
|
||
static int virtio_init(void) | ||
{ | ||
if (bus_register(&virtio_bus) != 0) | ||
panic("virtio bus registration failed"); | ||
return 0; | ||
} | ||
core_initcall(virtio_init); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
#ifndef _LINUX_VIRTIO_H | ||
#define _LINUX_VIRTIO_H | ||
/* Everything a virtio driver needs to work with any particular virtio | ||
* implementation. */ | ||
#include <linux/types.h> | ||
#include <linux/scatterlist.h> | ||
#include <linux/spinlock.h> | ||
#include <linux/device.h> | ||
#include <linux/mod_devicetable.h> | ||
|
||
/** | ||
* virtqueue - a queue to register buffers for sending or receiving. | ||
* @callback: the function to call when buffers are consumed (can be NULL). | ||
* If this returns false, callbacks are suppressed until vq_ops->restart | ||
* is called. | ||
* @vdev: the virtio device this queue was created for. | ||
* @vq_ops: the operations for this virtqueue (see below). | ||
* @priv: a pointer for the virtqueue implementation to use. | ||
*/ | ||
struct virtqueue | ||
{ | ||
bool (*callback)(struct virtqueue *vq); | ||
struct virtio_device *vdev; | ||
struct virtqueue_ops *vq_ops; | ||
void *priv; | ||
}; | ||
|
||
/** | ||
* virtqueue_ops - operations for virtqueue abstraction layer | ||
* @add_buf: expose buffer to other end | ||
* vq: the struct virtqueue we're talking about. | ||
* sg: the description of the buffer(s). | ||
* out_num: the number of sg readable by other side | ||
* in_num: the number of sg which are writable (after readable ones) | ||
* data: the token identifying the buffer. | ||
* Returns 0 or an error. | ||
* @kick: update after add_buf | ||
* vq: the struct virtqueue | ||
* After one or more add_buf calls, invoke this to kick the other side. | ||
* @get_buf: get the next used buffer | ||
* vq: the struct virtqueue we're talking about. | ||
* len: the length written into the buffer | ||
* Returns NULL or the "data" token handed to add_buf. | ||
* @restart: restart callbacks after callback returned false. | ||
* vq: the struct virtqueue we're talking about. | ||
* This returns "false" (and doesn't re-enable) if there are pending | ||
* buffers in the queue, to avoid a race. | ||
* @shutdown: "unadd" all buffers. | ||
* vq: the struct virtqueue we're talking about. | ||
* Remove everything from the queue. | ||
* | ||
* Locking rules are straightforward: the driver is responsible for | ||
* locking. No two operations may be invoked simultaneously. | ||
* | ||
* All operations can be called in any context. | ||
*/ | ||
struct virtqueue_ops { | ||
int (*add_buf)(struct virtqueue *vq, | ||
struct scatterlist sg[], | ||
unsigned int out_num, | ||
unsigned int in_num, | ||
void *data); | ||
|
||
void (*kick)(struct virtqueue *vq); | ||
|
||
void *(*get_buf)(struct virtqueue *vq, unsigned int *len); | ||
|
||
bool (*restart)(struct virtqueue *vq); | ||
|
||
void (*shutdown)(struct virtqueue *vq); | ||
}; | ||
|
||
/** | ||
* virtio_device - representation of a device using virtio | ||
* @index: unique position on the virtio bus | ||
* @dev: underlying device. | ||
* @id: the device type identification (used to match it with a driver). | ||
* @config: the configuration ops for this device. | ||
* @priv: private pointer for the driver's use. | ||
*/ | ||
struct virtio_device | ||
{ | ||
int index; | ||
struct device dev; | ||
struct virtio_device_id id; | ||
struct virtio_config_ops *config; | ||
void *priv; | ||
}; | ||
|
||
int register_virtio_device(struct virtio_device *dev); | ||
void unregister_virtio_device(struct virtio_device *dev); | ||
|
||
/** | ||
* virtio_driver - operations for a virtio I/O driver | ||
* @driver: underlying device driver (populate name and owner). | ||
* @id_table: the ids serviced by this driver. | ||
* @probe: the function to call when a device is found. Returns a token for | ||
* remove, or PTR_ERR(). | ||
* @remove: the function when a device is removed. | ||
*/ | ||
struct virtio_driver { | ||
struct device_driver driver; | ||
const struct virtio_device_id *id_table; | ||
int (*probe)(struct virtio_device *dev); | ||
void (*remove)(struct virtio_device *dev); | ||
}; | ||
|
||
int register_virtio_driver(struct virtio_driver *drv); | ||
void unregister_virtio_driver(struct virtio_driver *drv); | ||
#endif /* _LINUX_VIRTIO_H */ |
Oops, something went wrong.