The linux pcips2 driver is responsible for driving this device. Signed-off-by: Liav Albani <liav...@gmail.com> --- hw/i386/Kconfig | 1 + hw/input/Kconfig | 5 + hw/input/meson.build | 2 + hw/input/ps2pci.c | 282 ++++++++++++++++++++++++++++++++++++++ include/hw/input/ps2pci.h | 52 +++++++ 5 files changed, 342 insertions(+) create mode 100644 hw/input/ps2pci.c create mode 100644 include/hw/input/ps2pci.h
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index d40802d..dad6188 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -36,6 +36,7 @@ config PC select I8259 select I8254 select PCKBD + select PS2PCI select PCSPK select I8257 select MC146818RTC diff --git a/hw/input/Kconfig b/hw/input/Kconfig index 55865bb..04c97e8 100644 --- a/hw/input/Kconfig +++ b/hw/input/Kconfig @@ -17,6 +17,11 @@ config PL050 bool select PS2 +config PS2PCI + bool + depends on PCI + select PS2 + config PS2 bool diff --git a/hw/input/meson.build b/hw/input/meson.build index 8deb011..4fa1462 100644 --- a/hw/input/meson.build +++ b/hw/input/meson.build @@ -16,3 +16,5 @@ softmmu_ss.add(when: 'CONFIG_VHOST_USER_INPUT', if_true: files('vhost-user-input softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_keypad.c')) softmmu_ss.add(when: 'CONFIG_TSC210X', if_true: files('tsc210x.c')) softmmu_ss.add(when: 'CONFIG_LASIPS2', if_true: files('lasips2.c')) + +softmmu_ss.add(when: 'CONFIG_PS2PCI', if_true: files('ps2pci.c')) diff --git a/hw/input/ps2pci.c b/hw/input/ps2pci.c new file mode 100644 index 0000000..4340af1 --- /dev/null +++ b/hw/input/ps2pci.c @@ -0,0 +1,282 @@ +/* + * QEMU PCI PS/2 adapter. + * + * 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 "qemu/module.h" +#include "qemu/units.h" +#include "hw/pci/pci_device.h" +#include "hw/input/ps2pci.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/irq.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "qemu/log.h" + +static const VMStateDescription vmstate_ps2_pci = { + .name = "ps2-pci", + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, PS2PCIState), + VMSTATE_END_OF_LIST() + } +}; + +#define PS2_CTRL (0) +#define PS2_STATUS (1) +#define PS2_DATA (2) + +#define PS2_CTRL_CLK (1<<0) +#define PS2_CTRL_DAT (1<<1) +#define PS2_CTRL_TXIRQ (1<<2) +#define PS2_CTRL_ENABLE (1<<3) +#define PS2_CTRL_RXIRQ (1<<4) + +#define PS2_STAT_CLK (1<<0) +#define PS2_STAT_DAT (1<<1) +#define PS2_STAT_PARITY (1<<2) +#define PS2_STAT_RXFULL (1<<5) +#define PS2_STAT_TXBUSY (1<<6) +#define PS2_STAT_TXEMPTY (1<<7) + +static void ps2_pci_update_irq(PS2PCIState *s) +{ + int level = (s->pending && (s->cr & PS2_CTRL_RXIRQ) != 0) + || (s->cr & PS2_CTRL_TXIRQ) != 0; + + qemu_set_irq(s->irq, level); +} + +static void ps2_pci_set_irq(void *opaque, int n, int level) +{ + PS2PCIState *s = (PS2PCIState *)opaque; + + s->pending = level; + ps2_pci_update_irq(s); +} + +static uint64_t ps2_pci_io_read(void *opaque, hwaddr offset, + unsigned size) +{ + PS2PCIState *s = (PS2PCIState*)opaque; + switch (offset) { + case PS2_CTRL: /* CTRL */ + return s->cr; + case PS2_STATUS: /* STATUS */ + { + uint32_t stat = 0; + if (s->pending) + stat = PS2_STAT_RXFULL; + else + stat = PS2_STAT_TXEMPTY; + uint8_t val = 0; + val = s->last; + val = val ^ (val >> 4); + val = val ^ (val >> 2); + val = (val ^ (val >> 1)) & 1; + if (val) { + stat |= PS2_STAT_PARITY; + } + return stat; + } + case PS2_DATA: /* DATA */ + if (s->pending && s->cr & PS2_CTRL_ENABLE) { + s->last = ps2_read_data(s->ps2dev); + if (ps2_queue_empty(s->ps2dev)) + s->pending = 0; + } else { + return 0; + } + return s->last; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ps2_pci_io_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void ps2_pci_io_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PS2PCIState *s = (PS2PCIState *)opaque; + switch (offset) { + case PS2_CTRL: /* CTRL */ + s->cr = value; + break; + case PS2_STATUS: /* STATUS */ + break; + case PS2_DATA: /* DATA */ + if (s->cr & PS2_CTRL_ENABLE) { + if (s->is_mouse) { + ps2_write_mouse(PS2_MOUSE_DEVICE(s->ps2dev), value); + } else { + ps2_write_keyboard(PS2_KBD_DEVICE(s->ps2dev), value); + } + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ps2_pci_io_write: Bad offset %x\n", (int)offset); + } +} + +static const MemoryRegionOps ps2_pci_io_ops = { + .read = ps2_pci_io_read, + .write = ps2_pci_io_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void ps2_pci_realize_common(PCIDevice *dev, Error **errp) +{ + PS2PCIState *s = PS2_PCI(dev); + Object *obj = OBJECT(dev); + int ret; + + uint8_t *pci_conf = dev->config; + pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */ + + s->irq = pci_allocate_irq(&s->parent_obj); + memory_region_init_io(&s->io, obj, &ps2_pci_io_ops, s, + "ps2-pci-io", 16); + pci_set_byte(&s->parent_obj.config[PCI_REVISION_ID], 0); + pci_register_bar(&s->parent_obj, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io); + + if (pci_bus_is_express(pci_get_bus(dev))) { + ret = pcie_endpoint_cap_init(dev, 0x80); + assert(ret > 0); + } else { + dev->cap_present &= ~QEMU_PCI_CAP_EXPRESS; + } + qdev_connect_gpio_out(DEVICE(s->ps2dev), PS2_DEVICE_IRQ, + qdev_get_gpio_in_named((DeviceState*)dev, "ps2-input-irq", 0)); +} + +static void ps2_pci_keyboard_realize(PCIDevice *dev, Error **errp) +{ + PS2PCIKbdState *s = PS2_PCI_KBD_DEVICE(dev); + PS2PCIState *ps = PS2_PCI(dev); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->kbd), errp)) { + return; + } + + ps->ps2dev = PS2_DEVICE(&s->kbd); + ps2_pci_realize_common(dev, errp); +} + +static void ps2_pci_mouse_realize(PCIDevice *dev, Error **errp) +{ + PS2PCIMouseState *s = PS2_PCI_MOUSE_DEVICE(dev); + PS2PCIState *ps = PS2_PCI(dev); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->mouse), errp)) { + return; + } + + ps->ps2dev = PS2_DEVICE(&s->mouse); + ps2_pci_realize_common(dev, errp); +} + +static void ps2_pci_kbd_init(Object *obj) +{ + PCIDevice *dev = PCI_DEVICE(obj); + PS2PCIKbdState *s = PS2_PCI_KBD_DEVICE(obj); + PS2PCIState *ps = PS2_PCI(obj); + + ps->is_mouse = false; + object_initialize_child(obj, "kbd", &s->kbd, TYPE_PS2_KBD_DEVICE); + + dev->cap_present |= QEMU_PCI_CAP_EXPRESS; + + qdev_init_gpio_out(DEVICE(obj), &ps->irq, 1); + qdev_init_gpio_in_named(DEVICE(obj), ps2_pci_set_irq, + "ps2-input-irq", 1); +} + +static void ps2_pci_mouse_init(Object *obj) +{ + PCIDevice *dev = PCI_DEVICE(obj); + PS2PCIMouseState *s = PS2_PCI_MOUSE_DEVICE(obj); + PS2PCIState *ps = PS2_PCI(obj); + + ps->is_mouse = true; + object_initialize_child(obj, "mouse", &s->mouse, TYPE_PS2_MOUSE_DEVICE); + + dev->cap_present |= QEMU_PCI_CAP_EXPRESS; + + qdev_init_gpio_out(DEVICE(obj), &ps->irq, 1); + qdev_init_gpio_in_named(DEVICE(obj), ps2_pci_set_irq, + "ps2-input-irq", 1); +} + +static void ps2_pci_keyboard_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->class_id = PCI_CLASS_INPUT_KEYBOARD; + k->vendor_id = 0x14f2; + k->device_id = 0x0123; + + k->realize = ps2_pci_keyboard_realize; + k->exit = NULL; + dc->vmsd = &vmstate_ps2_pci; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static void ps2_pci_mouse_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->class_id = PCI_CLASS_INPUT_MOUSE; + k->vendor_id = 0x14f2; + k->device_id = 0x0124; + + k->realize = ps2_pci_mouse_realize; + k->exit = NULL; + dc->vmsd = &vmstate_ps2_pci; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo ps2_pci_keyboard_type_info = { + .name = TYPE_PS2_PCI_KBD_DEVICE, + .parent = TYPE_PS2_PCI, + .instance_size = sizeof(PS2PCIKbdState), + .instance_init = ps2_pci_kbd_init, + .class_init = ps2_pci_keyboard_class_init, +}; + +static const TypeInfo ps2_pci_mouse_type_info = { + .name = TYPE_PS2_PCI_MOUSE_DEVICE, + .parent = TYPE_PS2_PCI, + .instance_size = sizeof(PS2PCIMouseState), + .instance_init = ps2_pci_mouse_init, + .class_init = ps2_pci_mouse_class_init, +}; + +static const TypeInfo ps2_pci_type_info = { + .name = TYPE_PS2_PCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PS2PCIState), + .abstract = true, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_PCIE_DEVICE }, + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void ps2_pci_register_types(void) +{ + type_register_static(&ps2_pci_keyboard_type_info); + type_register_static(&ps2_pci_mouse_type_info); + type_register_static(&ps2_pci_type_info); +} + +type_init(ps2_pci_register_types) diff --git a/include/hw/input/ps2pci.h b/include/hw/input/ps2pci.h new file mode 100644 index 0000000..68a8c40 --- /dev/null +++ b/include/hw/input/ps2pci.h @@ -0,0 +1,52 @@ +/* + * QEMU PCI PS/2 adapter. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef HW_PS2_PCI_H +#define HW_PS2_PCI_H + +#include "hw/input/ps2.h" + +#define TYPE_PS2_PCI "ps2-pci" + +OBJECT_DECLARE_SIMPLE_TYPE(PS2PCIState, PS2_PCI) + +struct PS2PCIState { + /*< private >*/ + PCIDevice parent_obj; + /*< public >*/ + + MemoryRegion io; + + PS2State *ps2dev; + uint32_t cr; + uint32_t clk; + uint32_t last; + int pending; + qemu_irq irq; + bool is_mouse; +}; + +#define TYPE_PS2_PCI_KBD_DEVICE "ps2-pci-keyboard" +OBJECT_DECLARE_SIMPLE_TYPE(PS2PCIKbdState, PS2_PCI_KBD_DEVICE) + +struct PS2PCIKbdState { + PS2PCIState parent_obj; + + PS2KbdState kbd; +}; + +#define TYPE_PS2_PCI_MOUSE_DEVICE "ps2-pci-mouse" +OBJECT_DECLARE_SIMPLE_TYPE(PS2PCIMouseState, PS2_PCI_MOUSE_DEVICE) + +struct PS2PCIMouseState { + PS2PCIState parent_obj; + + PS2MouseState mouse; +}; + + +#endif /* HW_PS2_PCI_H */ -- 2.40.1