簡介:
前幾節分析了virtio機制和現有的balloon設備實現,至此我們已經知道了virtio是什么、怎么使用的,本節我們就自己實現一個virtio純虛設備。
功能:
- QEMU模擬的設備啟動一個定時器,每5秒發送一次中斷通知GUEST
- GUEST對應的驅動接收到中斷后講自身變量自增,然后通過vring發送給QEMU
- QEMU收到GUEST發送過來的消息后打印出接收到的數值
一: 設備創建
1. 添加virtio id,
用於guest內部的設備和驅動match,需要和linux內核中定義一致。
文件: include/standard-headers/linux/virtio_ids.h
#define VIRTIO_ID_TEST 21 /* virtio test */
2. 添加device id
vendor-id和device-id用於區分PCI設備,注意不要超過0x104f
文件: include/hw/pci/pci.h
#define PCI_DEVICE_ID_VIRTIO_TEST 0x1013
3. 添加virtio-test設備配置空間定義的頭文件
定義於GUEST協商配置的feature和config結構體,需要與linux中定義一致,config在本示例中並未使用,結構拷貝自balloon
文件: include/standard-headers/linux/virtio_test.h
#ifndef _LINUX_VIRTIO_TEST_H #define _LINUX_VIRTIO_TEST_H #include "standard-headers/linux/types.h" #include "standard-headers/linux/virtio_types.h" #include "standard-headers/linux/virtio_ids.h" #include "standard-headers/linux/virtio_config.h" #define VIRTIO_TEST_F_CAN_PRINT 0 struct virtio_test_config { /* Number of pages host wants Guest to give up. */ uint32_t num_pages; /* Number of pages we've actually got in balloon. */ uint32_t actual; /* Event host wants Guest to do */ uint32_t event; }; struct virtio_test_stat { __virtio16 tag; __virtio64 val; } QEMU_PACKED; #endif
4. 添加virtio-test設備模擬代碼
此代碼包括了對vring的操作和簡介中的功能主體實現,與驅動交互的代碼邏輯都在這里。
文件:hw/virtio/virtio-test.c
#include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/iov.h" #include "qemu/timer.h" #include "qemu-common.h" #include "hw/virtio/virtio.h" #include "hw/virtio/virtio-test.h" #include "sysemu/kvm.h" #include "sysemu/hax.h" #include "exec/address-spaces.h" #include "qapi/error.h" #include "qapi/qapi-events-misc.h" #include "qapi/visitor.h" #include "qemu/error-report.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/virtio-access.h" #include "migration/migration.h" static void virtio_test_handle_output(VirtIODevice *vdev, VirtQueue *vq) { VirtIOTest *s = VIRTIO_TEST(vdev); VirtQueueElement *elem; MemoryRegionSection section; for (;;) { size_t offset = 0; uint32_t pfn; elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); if (!elem) { return; } while (iov_to_buf(elem->out_sg, elem->out_num, offset, &pfn, 4) == 4) { int p = virtio_ldl_p(vdev, &pfn); offset += 4; qemu_log("=========get virtio num:%d\n", p); } virtqueue_push(vq, elem, offset); virtio_notify(vdev, vq); g_free(elem); } } static void virtio_test_get_config(VirtIODevice *vdev, uint8_t *config_data) { VirtIOTest *dev = VIRTIO_TEST(vdev); struct virtio_test_config config; config.actual = cpu_to_le32(dev->actual); config.event = cpu_to_le32(dev->event); memcpy(config_data, &config, sizeof(struct virtio_test_config)); } static void virtio_test_set_config(VirtIODevice *vdev, const uint8_t *config_data) { VirtIOTest *dev = VIRTIO_TEST(vdev); struct virtio_test_config config; memcpy(&config, config_data, sizeof(struct virtio_test_config)); dev->actual = le32_to_cpu(config.actual); dev->event = le32_to_cpu(config.event); } static uint64_t virtio_test_get_features(VirtIODevice *vdev, uint64_t f, Error **errp) { VirtIOTest *dev = VIRTIO_TEST(vdev); f |= dev->host_features; virtio_add_feature(&f, VIRTIO_TEST_F_CAN_PRINT); return f; } static int virtio_test_post_load_device(void *opaque, int version_id) { VirtIOTest *s = VIRTIO_TEST(opaque); return 0; } static const VMStateDescription vmstate_virtio_test_device = { .name = "virtio-test-device", .version_id = 1, .minimum_version_id = 1, .post_load = virtio_test_post_load_device, .fields = (VMStateField[]) { VMSTATE_UINT32(actual, VirtIOTest), VMSTATE_END_OF_LIST() }, }; static void test_stats_change_timer(VirtIOTest *s, int64_t secs) { timer_mod(s->stats_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + secs * 1000); } static void test_stats_poll_cb(void *opaque) { VirtIOTest *s = opaque; VirtIODevice *vdev = VIRTIO_DEVICE(s); qemu_log("==============set config:%d\n", s->set_config++); virtio_notify_config(vdev); test_stats_change_timer(s, 1); } static void virtio_test_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIOTest *s = VIRTIO_TEST(dev); int ret; virtio_init(vdev, "virtio-test", VIRTIO_ID_TEST, sizeof(struct virtio_test_config)); s->ivq = virtio_add_queue(vdev, 128, virtio_test_handle_output); /* create a new timer */ g_assert(s->stats_timer == NULL); s->stats_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_stats_poll_cb, s); test_stats_change_timer(s, 30); } static void virtio_test_device_unrealize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIOTest *s = VIRTIO_TEST(dev); virtio_cleanup(vdev); } static void virtio_test_device_reset(VirtIODevice *vdev) { VirtIOTest *s = VIRTIO_TEST(vdev); } static void virtio_test_set_status(VirtIODevice *vdev, uint8_t status) { VirtIOTest *s = VIRTIO_TEST(vdev); return; } static void virtio_test_instance_init(Object *obj) { VirtIOTest *s = VIRTIO_TEST(obj); return; } static const VMStateDescription vmstate_virtio_test = { .name = "virtio-test", .minimum_version_id = 1, .version_id = 1, .fields = (VMStateField[]) { VMSTATE_VIRTIO_DEVICE, VMSTATE_END_OF_LIST() }, }; static Property virtio_test_properties[] = { DEFINE_PROP_END_OF_LIST(), }; static void virtio_test_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); dc->props = virtio_test_properties; dc->vmsd = &vmstate_virtio_test; set_bit(DEVICE_CATEGORY_MISC, dc->categories); vdc->realize = virtio_test_device_realize; vdc->unrealize = virtio_test_device_unrealize; vdc->reset = virtio_test_device_reset; vdc->get_config = virtio_test_get_config; vdc->set_config = virtio_test_set_config; vdc->get_features = virtio_test_get_features; vdc->set_status = virtio_test_set_status; vdc->vmsd = &vmstate_virtio_test_device; } static const TypeInfo virtio_test_info = { .name = TYPE_VIRTIO_TEST, .parent = TYPE_VIRTIO_DEVICE, .instance_size = sizeof(VirtIOTest), .instance_init = virtio_test_instance_init, .class_init = virtio_test_class_init, }; static void virtio_register_types(void) { type_register_static(&virtio_test_info); } type_init(virtio_register_types)
文件: include/hw/virtio/virtio-test.h
#ifndef QEMU_VIRTIO_TEST_H #define QEMU_VIRTIO_TEST_H #include "standard-headers/linux/virtio_test.h" #include "hw/virtio/virtio.h" #include "hw/pci/pci.h" #define TYPE_VIRTIO_TEST "virtio-test-device" #define VIRTIO_TEST(obj) \ OBJECT_CHECK(VirtIOTest, (obj), TYPE_VIRTIO_TEST) typedef struct VirtIOTest { VirtIODevice parent_obj; VirtQueue *ivq; uint32_t set_config; uint32_t actual; VirtQueueElement *stats_vq_elem; size_t stats_vq_offset; QEMUTimer *stats_timer; uint32_t host_features; uint32_t event; } VirtIOTest; #endif
5. virtio-test-pci設備的實現
virtio-test設備屬於virtio設備掛接在virtio總線上,但是virtio屬於PCI設備。真正的設備發現和配置操作都依賴於PCI協議,因此將virtio-test設備包含於virtio-test-pci中,提供給外層的感知是這是一個pci設備,遵循PCI協議的規范。
頭文件: hw/virtio/virtio-pci.h
#include "hw/virtio/virtio-gpu.h" #include "hw/virtio/virtio-crypto.h" #include "hw/virtio/vhost-user-scsi.h" +#include "hw/virtio/virtio-test.h" #if defined(CONFIG_VHOST_USER) && defined(CONFIG_LINUX) #include "hw/virtio/vhost-user-blk.h" #endif typedef struct VirtIOGPUPCI VirtIOGPUPCI; typedef struct VHostVSockPCI VHostVSockPCI; typedef struct VirtIOCryptoPCI VirtIOCryptoPCI; typedef struct VirtIOWifiPCI VirtIOWifiPCI; +typedef struct VirtIOTestPCI VirtIOTestPCI;+/* + * virtio-test-pci: This extends VirtioPCIProxy. + */ +#define TYPE_VIRTIO_TEST_PCI "virtio-test-pci" +#define VIRTIO_TEST_PCI(obj) \ + OBJECT_CHECK(VirtIOTestPCI, (obj), TYPE_VIRTIO_TEST_PCI) + +struct VirtIOTestPCI { + VirtIOPCIProxy parent_obj; + VirtIOTest vdev; +};
文件: hw/virtio/virtio-pci.c
/* virtio-test-pci */ static Property virtio_test_pci_properties[] = { DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0), DEFINE_PROP_END_OF_LIST(), }; static void virtio_test_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) { VirtIOTestPCI *dev = VIRTIO_TEST_PCI(vpci_dev); DeviceState *vdev = DEVICE(&dev->vdev); if (vpci_dev->class_code != PCI_CLASS_OTHERS && vpci_dev->class_code != PCI_CLASS_MEMORY_RAM) { /* qemu < 1.1 */ vpci_dev->class_code = PCI_CLASS_OTHERS; } qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus)); object_property_set_bool(OBJECT(vdev), true, "realized", errp); } static void virtio_test_pci_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); k->realize = virtio_test_pci_realize; set_bit(DEVICE_CATEGORY_MISC, dc->categories); dc->props = virtio_test_pci_properties; pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_TEST; pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; pcidev_k->class_id = PCI_CLASS_OTHERS; } static void virtio_test_pci_instance_init(Object *obj) { VirtIOTestPCI *dev = VIRTIO_TEST_PCI(obj); virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), TYPE_VIRTIO_TEST); } static const TypeInfo virtio_test_pci_info = { .name = TYPE_VIRTIO_TEST_PCI, .parent = TYPE_VIRTIO_PCI, .instance_size = sizeof(VirtIOTestPCI), .instance_init = virtio_test_pci_instance_init, .class_init = virtio_test_pci_class_init, }; @@ -2739,6 +2789,7 @@ static void virtio_pci_register_types(void) type_register_static(&virtio_scsi_pci_info); type_register_static(&virtio_balloon_pci_info); + type_register_static(&virtio_test_pci_info); type_register_static(&virtio_serial_pci_info); type_register_static(&virtio_net_pci_info);
6. 使設備生效
- 上述代碼沒有添加將virtio-test.c加入編譯工程的代碼,需要在對應CMake工程中將C文件加入,設置include目錄(-I)的地方不要漏掉
- 完成后編譯生成可執行文件
- 執行啟動命令時加入對應參數: -qemu -device virtio-test-pci
- 在hmp界面輸入info qtree可以看到設備已經創建

-
進入guest找到 /sys/buc/pci/devices目錄,這里的第19就是我們新建的設備

二: GUEST內實現驅動
1. 添加virtio id
需要和設備定義的virtio id一致,用於設備和驅動的match
文件:include/uapi/linux/virtio_ids.h
#define VIRTIO_ID_TEST 21 /* virtio test */
2. 添加virtio-test驅動配置空間結構定義頭文件
文件內容和QEMU定義相同,用於驅動和設備協商配置和feature
文件:include/uapi/linux/virtio_test.h
#ifndef _LINUX_VIRTIO_TEST_H #define _LINUX_VIRTIO_TEST_H #include <linux/types.h> #include <linux/virtio_types.h> #include <linux/virtio_ids.h> #include <linux/virtio_config.h> /* The feature bitmap for virtio balloon */ #define VIRTIO_TEST_F_CAN_PRINT 0 struct virtio_test_config { /* Number of pages host wants Guest to give up. */ __u32 num_pages; /* Number of pages we've actually got in balloon. */ __u32 actual; }; struct virtio_test_stat { __virtio16 tag; __virtio64 val; } __attribute__((packed)); #endif /* _LINUX_VIRTIO_TEST_H */
3. 添加virtio-test驅動實現
文件: drivers/virtio/virtio_test.c
#include <linux/virtio.h> #include <linux/virtio_test.h> #include <linux/swap.h> #include <linux/workqueue.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/oom.h> #include <linux/wait.h> #include <linux/mm.h> #include <linux/mount.h> #include <linux/magic.h> struct virtio_test { struct virtio_device *vdev; struct virtqueue *print_vq; struct work_struct print_val_work; bool stop_update; atomic_t stop_once; /* Waiting for host to ack the pages we released. */ wait_queue_head_t acked; __virtio32 num[256]; }; static struct virtio_device_id id_table[] = { { VIRTIO_ID_TEST, VIRTIO_DEV_ANY_ID }, { 0 }, }; static struct virtio_test *vb_dev; static void test_ack(struct virtqueue *vq) { struct virtio_test *vb = vq->vdev->priv; printk("virttest get ack\n"); unsigned int len; virtqueue_get_buf(vq, &len); } static int init_vqs(struct virtio_test *vb) { struct virtqueue *vqs[1]; vq_callback_t *callbacks[] = { test_ack }; static const char * const names[] = { "print"}; int err, nvqs; nvqs = virtio_has_feature(vb->vdev, VIRTIO_TEST_F_CAN_PRINT) ? 1 : 0; err = virtio_find_vqs(vb->vdev, nvqs, vqs, callbacks, names, NULL); if (err) return err; vb->print_vq = vqs[0]; return 0; } static void remove_common(struct virtio_test *vb) { /* Now we reset the device so we can clean up the queues. */ vb->vdev->config->reset(vb->vdev); vb->vdev->config->del_vqs(vb->vdev); } static void virttest_remove(struct virtio_device *vdev) { struct virtio_test *vb = vdev->priv; remove_common(vb); cancel_work_sync(&vb->print_val_work); kfree(vb); vb_dev = NULL; } static int virttest_validate(struct virtio_device *vdev) { return 0; } static void print_val_func(struct work_struct *work) { struct virtio_test *vb; struct scatterlist sg; vb = container_of(work, struct virtio_test, print_val_work); printk("virttest get config change\n"); struct virtqueue *vq = vb->print_vq; vb->num[0]++; sg_init_one(&sg, &vb->num[0], sizeof(vb->num[0])); /* We should always be able to add one buffer to an empty queue. */ virtqueue_add_outbuf(vq, &sg, 1, vb, GFP_KERNEL); virtqueue_kick(vq); } static void virttest_changed(struct virtio_device *vdev) { struct virtio_test *vb = vdev->priv; printk("virttest virttest_changed\n"); if (!vb->stop_update) { //atomic_set(&vb->stop_once, 0); queue_work(system_freezable_wq, &vb->print_val_work); } } static int virttest_probe(struct virtio_device *vdev) { struct virtio_test *vb; int err; printk("******create virttest\n"); if (!vdev->config->get) { return -EINVAL; } vdev->priv = vb = kmalloc(sizeof(*vb), GFP_KERNEL); if (!vb) { err = -ENOMEM; goto out; } vb->num[0] = 0; vb->vdev = vdev; INIT_WORK(&vb->print_val_work, print_val_func); vb->stop_update = false; init_waitqueue_head(&vb->acked); err = init_vqs(vb); if (err) goto out_free_vb; virtio_device_ready(vdev); atomic_set(&vb->stop_once, 0); vb_dev = vb; return 0; out_free_vb: kfree(vb); out: return err; } static unsigned int features[] = { VIRTIO_TEST_F_CAN_PRINT, }; static struct virtio_driver virtio_test_driver = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .validate = virttest_validate, .probe = virttest_probe, .remove = virttest_remove, .config_changed = virttest_changed, }; module_virtio_driver(virtio_test_driver); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_DESCRIPTION("Virtio test driver"); MODULE_LICENSE("GPL");
4. 新驅動編譯進內核
為了簡便我們沒有定義KConfig中的宏,直接將模塊編譯進生成的內核文件
當然這里也可以將virtio_test.o賦值給obj-m,編譯成模塊,啟動后通過insmod進行加載virtio_test.ko
文件: drivers/virtio/Makefile
obj-y += virtio_test.o
三: 最終效果
啟動后在qemu測交互打印,每次set config將會使guest內部變量自增,並通過vring發送給qemu,qemu進行打印。

