qemu: add test-irq device for uio pci generic driver development This device has two registers: 1 byte at offset 0x40: interrupt register - write 1 to assert interrupt - write 0 to de-assert interrupt 4 byte at offset 0x44: interrupt counter - increments by 1 each time interrupt is asserted The device implements Interrupt Disable and Interrupt Status bits as per PCI 2.3 spec. It is used to test uio_pci_generic linux kernel driver. The device is activated by passing the flag -device test-irq to qemu The patch uses the new qdev infrastructure and is on top of this tree git://git.et.redhat.com/qemu-kraxel.git qdev.v9 Signed-off-by: Michael S. Tsirkin diff --git a/Makefile.hw b/Makefile.hw index f7a9507..e393693 100644 --- a/Makefile.hw +++ b/Makefile.hw @@ -16,7 +16,7 @@ CPPFLAGS += -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE CPPFLAGS+=-I$(SRC_PATH)/fpu obj-y = -obj-y += virtio.o virtio-pci.o +obj-y += virtio.o virtio-pci.o test-irq.o obj-y += fw_cfg.o obj-y += watchdog.o obj-y += nand.o ecc.o diff --git a/Makefile.target b/Makefile.target index a593503..11e99f5 100644 --- a/Makefile.target +++ b/Makefile.target @@ -492,7 +492,7 @@ obj-y = vl.o osdep.o monitor.o pci.o loader.o isa_mmio.o machine.o \ gdbstub.o gdbstub-xml.o msix.o # virtio has to be here due to weird dependency between PCI and virtio-net. # need to fix this properly -obj-y += virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o +obj-y += virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o test-irq.o obj-$(CONFIG_KVM) += kvm.o kvm-all.o LIBS+=-lz diff --git a/hw/pci.h b/hw/pci.h index ab92092..355896f 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -99,6 +99,7 @@ typedef struct PCIIORegion { #define PCI_COMMAND_IO 0x1 /* Enable response in I/O space */ #define PCI_COMMAND_MEMORY 0x2 /* Enable response in Memory space */ #define PCI_COMMAND_MASTER 0x4 /* Enable bus master */ +#define PCI_COMMAND_INTX_DISABLE 0x400 /* INTx Emulation Disable */ #define PCI_STATUS 0x06 /* 16 bits */ #define PCI_REVISION_ID 0x08 /* 8 bits */ #define PCI_CLASS_PROG 0x09 /* Reg. Level Programming Interface */ diff --git a/hw/test-irq.c b/hw/test-irq.c new file mode 100644 index 0000000..fda156c --- /dev/null +++ b/hw/test-irq.c @@ -0,0 +1,116 @@ +/* + * Test IRQ PCI Device + * + * Author: Michael S. Tsirkin + * + * Copyright (c) 2009, Red Hat Inc, Michael S. Tsirkin (mst@redhat.com) + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include + +#include "pci.h" +#include "sysbus.h" + +#define PCI_STATUS_INTERRUPT 0x08 /* Interrupt status */ +#define PCI_DEVICE_ID_TEST_IRQ 0x2009 + +/* Status register: 1 bit to control interrupt */ +#define TEST_IRQ_STATUS 0x40 +#define TEST_IRQ_STATUS_MASK 0x1 + +/* Counter register: 4 byte counter for interrupt asserted */ +#define TEST_IRQ_COUNTER 0x44 + +struct test_irq_device { + PCIDevice pdev; + uint32_t counter; +}; + +/* test-irq device */ + +static void test_irq_reset(void *opaque) +{ + struct test_irq_device *dev = opaque; + dev->counter = 0; +} + +static void test_irq_set(struct test_irq_device *dev) +{ + ++dev->counter; + pci_set_long(dev->pdev.config + TEST_IRQ_COUNTER, dev->counter); + qemu_set_irq(dev->pdev.irq[0], 1); +} + +static void test_irq_clr(struct test_irq_device *dev) +{ + qemu_set_irq(dev->pdev.irq[0], 0); +} + +static void test_irq_write_config(PCIDevice *pdev, uint32_t address, + uint32_t val, int len) +{ + int is_disabled, was_disabled, was_requested, is_requested, + was_asserted, is_asserted; + uint16_t status; + struct test_irq_device *tdev; + tdev = container_of(pdev, struct test_irq_device, pdev); + + was_disabled = pci_get_word(pdev->config + PCI_COMMAND) & + PCI_COMMAND_INTX_DISABLE; + was_requested = pdev->config[TEST_IRQ_STATUS] & TEST_IRQ_STATUS_MASK; + + pci_default_write_config(pdev, address, val, len); + + is_disabled = pci_get_word(pdev->config + PCI_COMMAND) & + PCI_COMMAND_INTX_DISABLE; + is_requested = pdev->config[TEST_IRQ_STATUS] & TEST_IRQ_STATUS_MASK; + + status = pci_get_word(pdev->config + PCI_STATUS); + if (is_requested) + status |= PCI_STATUS_INTERRUPT; + else + status &= ~PCI_STATUS_INTERRUPT; + pci_set_word(pdev->config + PCI_STATUS, status); + + was_asserted = was_requested && !was_disabled; + is_asserted = is_requested && !is_disabled; + + if (!was_asserted && is_asserted) { + test_irq_set(tdev); + } else if (was_asserted && !is_asserted) { + test_irq_clr(tdev); + } +} + +static void test_irq_initpci(PCIDevice *pdev) +{ + struct test_irq_device *tdev; + uint8_t *command = pdev->wmask + PCI_COMMAND; + + pdev->wmask[TEST_IRQ_STATUS] = 0x1; + pci_set_word(command, PCI_COMMAND_INTX_DISABLE | pci_get_word(command)); + pci_config_set_vendor_id(pdev->config, PCI_VENDOR_ID_REDHAT_QUMRANET); + pci_config_set_device_id(pdev->config, PCI_DEVICE_ID_TEST_IRQ); + pdev->config[PCI_INTERRUPT_PIN] = 0x1; + pdev->config_write = test_irq_write_config; + + tdev = container_of(pdev, struct test_irq_device, pdev); + qemu_register_reset(test_irq_reset, tdev); +} + +static PCIDeviceInfo test_irq_info = { + .qdev.name = "test-irq", + .qdev.size = sizeof(struct test_irq_device), + .init = test_irq_initpci, +}; + +static void test_irq_register_device(void) +{ + pci_qdev_register(&test_irq_info); +} + +device_init(test_irq_register_device)