| /* |
| * QTest testcase for VirtIO NIC |
| * |
| * Copyright (c) 2014 SUSE LINUX Products GmbH |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| * See the COPYING file in the top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "libqtest.h" |
| #include "qemu-common.h" |
| #include "qemu/sockets.h" |
| #include "qemu/iov.h" |
| #include "libqos/pci-pc.h" |
| #include "libqos/virtio.h" |
| #include "libqos/virtio-pci.h" |
| #include "libqos/malloc.h" |
| #include "libqos/malloc-pc.h" |
| #include "libqos/malloc-generic.h" |
| #include "qemu/bswap.h" |
| #include "hw/virtio/virtio-net.h" |
| #include "standard-headers/linux/virtio_ids.h" |
| #include "standard-headers/linux/virtio_ring.h" |
| |
| #define PCI_SLOT_HP 0x06 |
| #define PCI_SLOT 0x04 |
| #define PCI_FN 0x00 |
| |
| #define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000) |
| #define VNET_HDR_SIZE sizeof(struct virtio_net_hdr_mrg_rxbuf) |
| |
| static void test_end(void) |
| { |
| qtest_end(); |
| } |
| |
| #ifndef _WIN32 |
| |
| static QVirtioPCIDevice *virtio_net_pci_init(QPCIBus *bus, int slot) |
| { |
| QVirtioPCIDevice *dev; |
| |
| dev = qvirtio_pci_device_find(bus, VIRTIO_ID_NET); |
| g_assert(dev != NULL); |
| g_assert_cmphex(dev->vdev.device_type, ==, VIRTIO_ID_NET); |
| |
| qvirtio_pci_device_enable(dev); |
| qvirtio_reset(&qvirtio_pci, &dev->vdev); |
| qvirtio_set_acknowledge(&qvirtio_pci, &dev->vdev); |
| qvirtio_set_driver(&qvirtio_pci, &dev->vdev); |
| |
| return dev; |
| } |
| |
| static QPCIBus *pci_test_start(int socket) |
| { |
| char *cmdline; |
| |
| cmdline = g_strdup_printf("-netdev socket,fd=%d,id=hs0 -device " |
| "virtio-net-pci,netdev=hs0", socket); |
| qtest_start(cmdline); |
| g_free(cmdline); |
| |
| return qpci_init_pc(NULL); |
| } |
| |
| static void driver_init(const QVirtioBus *bus, QVirtioDevice *dev) |
| { |
| uint32_t features; |
| |
| features = qvirtio_get_features(bus, dev); |
| features = features & ~(QVIRTIO_F_BAD_FEATURE | |
| (1u << VIRTIO_RING_F_INDIRECT_DESC) | |
| (1u << VIRTIO_RING_F_EVENT_IDX)); |
| qvirtio_set_features(bus, dev, features); |
| |
| qvirtio_set_driver_ok(bus, dev); |
| } |
| |
| static void rx_test(const QVirtioBus *bus, QVirtioDevice *dev, |
| QGuestAllocator *alloc, QVirtQueue *vq, |
| int socket) |
| { |
| uint64_t req_addr; |
| uint32_t free_head; |
| char test[] = "TEST"; |
| char buffer[64]; |
| int len = htonl(sizeof(test)); |
| struct iovec iov[] = { |
| { |
| .iov_base = &len, |
| .iov_len = sizeof(len), |
| }, { |
| .iov_base = test, |
| .iov_len = sizeof(test), |
| }, |
| }; |
| int ret; |
| |
| req_addr = guest_alloc(alloc, 64); |
| |
| free_head = qvirtqueue_add(vq, req_addr, 64, true, false); |
| qvirtqueue_kick(bus, dev, vq, free_head); |
| |
| ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test)); |
| g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len)); |
| |
| qvirtio_wait_queue_isr(bus, dev, vq, QVIRTIO_NET_TIMEOUT_US); |
| memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test)); |
| g_assert_cmpstr(buffer, ==, "TEST"); |
| |
| guest_free(alloc, req_addr); |
| } |
| |
| static void tx_test(const QVirtioBus *bus, QVirtioDevice *dev, |
| QGuestAllocator *alloc, QVirtQueue *vq, |
| int socket) |
| { |
| uint64_t req_addr; |
| uint32_t free_head; |
| uint32_t len; |
| char buffer[64]; |
| int ret; |
| |
| req_addr = guest_alloc(alloc, 64); |
| memwrite(req_addr + VNET_HDR_SIZE, "TEST", 4); |
| |
| free_head = qvirtqueue_add(vq, req_addr, 64, false, false); |
| qvirtqueue_kick(bus, dev, vq, free_head); |
| |
| qvirtio_wait_queue_isr(bus, dev, vq, QVIRTIO_NET_TIMEOUT_US); |
| guest_free(alloc, req_addr); |
| |
| ret = qemu_recv(socket, &len, sizeof(len), 0); |
| g_assert_cmpint(ret, ==, sizeof(len)); |
| len = ntohl(len); |
| |
| ret = qemu_recv(socket, buffer, len, 0); |
| g_assert_cmpstr(buffer, ==, "TEST"); |
| } |
| |
| static void rx_stop_cont_test(const QVirtioBus *bus, QVirtioDevice *dev, |
| QGuestAllocator *alloc, QVirtQueue *vq, |
| int socket) |
| { |
| uint64_t req_addr; |
| uint32_t free_head; |
| char test[] = "TEST"; |
| char buffer[64]; |
| int len = htonl(sizeof(test)); |
| QDict *rsp; |
| struct iovec iov[] = { |
| { |
| .iov_base = &len, |
| .iov_len = sizeof(len), |
| }, { |
| .iov_base = test, |
| .iov_len = sizeof(test), |
| }, |
| }; |
| int ret; |
| |
| req_addr = guest_alloc(alloc, 64); |
| |
| free_head = qvirtqueue_add(vq, req_addr, 64, true, false); |
| qvirtqueue_kick(bus, dev, vq, free_head); |
| |
| rsp = qmp("{ 'execute' : 'stop'}"); |
| QDECREF(rsp); |
| |
| ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test)); |
| g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len)); |
| |
| /* We could check the status, but this command is more importantly to |
| * ensure the packet data gets queued in QEMU, before we do 'cont'. |
| */ |
| rsp = qmp("{ 'execute' : 'query-status'}"); |
| QDECREF(rsp); |
| rsp = qmp("{ 'execute' : 'cont'}"); |
| QDECREF(rsp); |
| |
| qvirtio_wait_queue_isr(bus, dev, vq, QVIRTIO_NET_TIMEOUT_US); |
| memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test)); |
| g_assert_cmpstr(buffer, ==, "TEST"); |
| |
| guest_free(alloc, req_addr); |
| } |
| |
| static void send_recv_test(const QVirtioBus *bus, QVirtioDevice *dev, |
| QGuestAllocator *alloc, QVirtQueue *rvq, |
| QVirtQueue *tvq, int socket) |
| { |
| rx_test(bus, dev, alloc, rvq, socket); |
| tx_test(bus, dev, alloc, tvq, socket); |
| } |
| |
| static void stop_cont_test(const QVirtioBus *bus, QVirtioDevice *dev, |
| QGuestAllocator *alloc, QVirtQueue *rvq, |
| QVirtQueue *tvq, int socket) |
| { |
| rx_stop_cont_test(bus, dev, alloc, rvq, socket); |
| } |
| |
| static void pci_basic(gconstpointer data) |
| { |
| QVirtioPCIDevice *dev; |
| QPCIBus *bus; |
| QVirtQueuePCI *tx, *rx; |
| QGuestAllocator *alloc; |
| void (*func) (const QVirtioBus *bus, |
| QVirtioDevice *dev, |
| QGuestAllocator *alloc, |
| QVirtQueue *rvq, |
| QVirtQueue *tvq, |
| int socket) = data; |
| int sv[2], ret; |
| |
| ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv); |
| g_assert_cmpint(ret, !=, -1); |
| |
| bus = pci_test_start(sv[1]); |
| dev = virtio_net_pci_init(bus, PCI_SLOT); |
| |
| alloc = pc_alloc_init(); |
| rx = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, |
| alloc, 0); |
| tx = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, |
| alloc, 1); |
| |
| driver_init(&qvirtio_pci, &dev->vdev); |
| func(&qvirtio_pci, &dev->vdev, alloc, &rx->vq, &tx->vq, sv[0]); |
| |
| /* End test */ |
| close(sv[0]); |
| qvirtqueue_cleanup(&qvirtio_pci, &tx->vq, alloc); |
| qvirtqueue_cleanup(&qvirtio_pci, &rx->vq, alloc); |
| pc_alloc_uninit(alloc); |
| qvirtio_pci_device_disable(dev); |
| g_free(dev->pdev); |
| g_free(dev); |
| qpci_free_pc(bus); |
| test_end(); |
| } |
| #endif |
| |
| static void hotplug(void) |
| { |
| qtest_start("-device virtio-net-pci"); |
| |
| qpci_plug_device_test("virtio-net-pci", "net1", PCI_SLOT_HP, NULL); |
| qpci_unplug_acpi_device_test("net1", PCI_SLOT_HP); |
| |
| test_end(); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| g_test_init(&argc, &argv, NULL); |
| #ifndef _WIN32 |
| qtest_add_data_func("/virtio/net/pci/basic", send_recv_test, pci_basic); |
| qtest_add_data_func("/virtio/net/pci/rx_stop_cont", |
| stop_cont_test, pci_basic); |
| #endif |
| qtest_add_func("/virtio/net/pci/hotplug", hotplug); |
| |
| return g_test_run(); |
| } |