| /* |
| * HP Diva GSP controller |
| * |
| * The Diva PCI boards are Remote Management cards for PA-RISC machines. |
| * They come with built-in 16550A multi UARTs for serial consoles |
| * and a mailbox-like memory area for hardware auto-reboot functionality. |
| * GSP stands for "Guardian Service Processor". Later products were marketed |
| * "Management Processor" (MP). |
| * |
| * Diva cards are multifunctional cards. The first part, the aux port, |
| * is on physical machines not useable but we still try to mimic it here. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * |
| * Copyright (c) 2025 Helge Deller <deller@gmx.de> |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/units.h" |
| #include "hw/char/serial.h" |
| #include "hw/irq.h" |
| #include "hw/pci/pci_device.h" |
| #include "hw/qdev-properties.h" |
| #include "hw/qdev-properties-system.h" |
| #include "migration/vmstate.h" |
| |
| #define PCI_DEVICE_ID_HP_DIVA 0x1048 |
| /* various DIVA GSP cards: */ |
| #define PCI_DEVICE_ID_HP_DIVA_TOSCA1 0x1049 |
| #define PCI_DEVICE_ID_HP_DIVA_TOSCA2 0x104A |
| #define PCI_DEVICE_ID_HP_DIVA_MAESTRO 0x104B |
| #define PCI_DEVICE_ID_HP_REO_IOC 0x10f1 |
| #define PCI_DEVICE_ID_HP_DIVA_HALFDOME 0x1223 |
| #define PCI_DEVICE_ID_HP_DIVA_KEYSTONE 0x1226 |
| #define PCI_DEVICE_ID_HP_DIVA_POWERBAR 0x1227 |
| #define PCI_DEVICE_ID_HP_DIVA_EVEREST 0x1282 |
| #define PCI_DEVICE_ID_HP_DIVA_AUX 0x1290 |
| #define PCI_DEVICE_ID_HP_DIVA_RMP3 0x1301 |
| #define PCI_DEVICE_ID_HP_DIVA_HURRICANE 0x132a |
| |
| |
| #define PCI_SERIAL_MAX_PORTS 4 |
| |
| typedef struct PCIDivaSerialState { |
| PCIDevice dev; |
| MemoryRegion membar; /* for serial ports */ |
| MemoryRegion mailboxbar; /* for hardware mailbox */ |
| uint32_t subvendor; |
| uint32_t ports; |
| char *name[PCI_SERIAL_MAX_PORTS]; |
| SerialState state[PCI_SERIAL_MAX_PORTS]; |
| uint32_t level[PCI_SERIAL_MAX_PORTS]; |
| qemu_irq *irqs; |
| bool disable; |
| } PCIDivaSerialState; |
| |
| static void diva_pci_exit(PCIDevice *dev) |
| { |
| PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev); |
| SerialState *s; |
| int i; |
| |
| for (i = 0; i < pci->ports; i++) { |
| s = pci->state + i; |
| qdev_unrealize(DEVICE(s)); |
| memory_region_del_subregion(&pci->membar, &s->io); |
| g_free(pci->name[i]); |
| } |
| qemu_free_irqs(pci->irqs, pci->ports); |
| } |
| |
| static void multi_serial_irq_mux(void *opaque, int n, int level) |
| { |
| PCIDivaSerialState *pci = opaque; |
| int i, pending = 0; |
| |
| pci->level[n] = level; |
| for (i = 0; i < pci->ports; i++) { |
| if (pci->level[i]) { |
| pending = 1; |
| } |
| } |
| pci_set_irq(&pci->dev, pending); |
| } |
| |
| struct diva_info { |
| unsigned int nports:4; /* number of serial ports */ |
| unsigned int omask:12; /* offset mask: BIT(1) -> offset 8 */ |
| }; |
| |
| static struct diva_info diva_get_diva_info(PCIDeviceClass *pc) |
| { |
| switch (pc->subsystem_id) { |
| case PCI_DEVICE_ID_HP_DIVA_POWERBAR: |
| case PCI_DEVICE_ID_HP_DIVA_HURRICANE: |
| return (struct diva_info) { .nports = 1, |
| .omask = BIT(0) }; |
| case PCI_DEVICE_ID_HP_DIVA_TOSCA2: |
| return (struct diva_info) { .nports = 2, |
| .omask = BIT(0) | BIT(1) }; |
| case PCI_DEVICE_ID_HP_DIVA_TOSCA1: |
| case PCI_DEVICE_ID_HP_DIVA_HALFDOME: |
| case PCI_DEVICE_ID_HP_DIVA_KEYSTONE: |
| return (struct diva_info) { .nports = 3, |
| .omask = BIT(0) | BIT(1) | BIT(2) }; |
| case PCI_DEVICE_ID_HP_DIVA_EVEREST: /* e.g. in rp3410 */ |
| return (struct diva_info) { .nports = 3, |
| .omask = BIT(0) | BIT(2) | BIT(7) }; |
| case PCI_DEVICE_ID_HP_DIVA_MAESTRO: |
| return (struct diva_info) { .nports = 4, |
| .omask = BIT(0) | BIT(1) | BIT(2) | BIT(7) }; |
| } |
| g_assert_not_reached(); |
| } |
| |
| |
| static void diva_pci_realize(PCIDevice *dev, Error **errp) |
| { |
| PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); |
| PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev); |
| SerialState *s; |
| struct diva_info di = diva_get_diva_info(pc); |
| size_t i, offset = 0; |
| size_t portmask = di.omask; |
| |
| pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */ |
| pci->dev.config[PCI_INTERRUPT_PIN] = 1; |
| memory_region_init(&pci->membar, OBJECT(pci), "serial_ports", 4096); |
| pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->membar); |
| pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, di.nports); |
| |
| for (i = 0; i < di.nports; i++) { |
| s = pci->state + i; |
| if (!qdev_realize(DEVICE(s), NULL, errp)) { |
| diva_pci_exit(dev); |
| return; |
| } |
| s->irq = pci->irqs[i]; |
| pci->name[i] = g_strdup_printf("uart #%zu", i + 1); |
| memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s, |
| pci->name[i], 8); |
| |
| /* calculate offset of given port based on bitmask */ |
| while ((portmask & BIT(0)) == 0) { |
| offset += 8; |
| portmask >>= 1; |
| } |
| memory_region_add_subregion(&pci->membar, offset, &s->io); |
| offset += 8; |
| portmask >>= 1; |
| pci->ports++; |
| } |
| |
| /* mailbox bar */ |
| memory_region_init(&pci->mailboxbar, OBJECT(pci), "mailbox", 128 * KiB); |
| pci_register_bar(&pci->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY | |
| PCI_BASE_ADDRESS_MEM_PREFETCH, &pci->mailboxbar); |
| } |
| |
| static const VMStateDescription vmstate_pci_diva = { |
| .name = "pci-diva-serial", |
| .version_id = 1, |
| .minimum_version_id = 1, |
| .fields = (const VMStateField[]) { |
| VMSTATE_PCI_DEVICE(dev, PCIDivaSerialState), |
| VMSTATE_STRUCT_ARRAY(state, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS, |
| 0, vmstate_serial, SerialState), |
| VMSTATE_UINT32_ARRAY(level, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS), |
| VMSTATE_BOOL(disable, PCIDivaSerialState), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static const Property diva_serial_properties[] = { |
| DEFINE_PROP_BOOL("disable", PCIDivaSerialState, disable, false), |
| DEFINE_PROP_CHR("chardev1", PCIDivaSerialState, state[0].chr), |
| DEFINE_PROP_CHR("chardev2", PCIDivaSerialState, state[1].chr), |
| DEFINE_PROP_CHR("chardev3", PCIDivaSerialState, state[2].chr), |
| DEFINE_PROP_CHR("chardev4", PCIDivaSerialState, state[3].chr), |
| DEFINE_PROP_UINT32("subvendor", PCIDivaSerialState, subvendor, |
| PCI_DEVICE_ID_HP_DIVA_TOSCA1), |
| }; |
| |
| static void diva_serial_class_initfn(ObjectClass *klass, const void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); |
| pc->realize = diva_pci_realize; |
| pc->exit = diva_pci_exit; |
| pc->vendor_id = PCI_VENDOR_ID_HP; |
| pc->device_id = PCI_DEVICE_ID_HP_DIVA; |
| pc->subsystem_vendor_id = PCI_VENDOR_ID_HP; |
| pc->subsystem_id = PCI_DEVICE_ID_HP_DIVA_TOSCA1; |
| pc->revision = 3; |
| pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL; |
| dc->vmsd = &vmstate_pci_diva; |
| device_class_set_props(dc, diva_serial_properties); |
| set_bit(DEVICE_CATEGORY_INPUT, dc->categories); |
| } |
| |
| static void diva_serial_init(Object *o) |
| { |
| PCIDevice *dev = PCI_DEVICE(o); |
| PCIDivaSerialState *pms = DO_UPCAST(PCIDivaSerialState, dev, dev); |
| struct diva_info di = diva_get_diva_info(PCI_DEVICE_GET_CLASS(dev)); |
| size_t i; |
| |
| for (i = 0; i < di.nports; i++) { |
| object_initialize_child(o, "serial[*]", &pms->state[i], TYPE_SERIAL); |
| } |
| } |
| |
| |
| /* Diva-aux is the driver for portion 0 of the multifunction PCI device */ |
| |
| struct DivaAuxState { |
| PCIDevice dev; |
| MemoryRegion mem; |
| qemu_irq irq; |
| }; |
| |
| #define TYPE_DIVA_AUX "diva-aux" |
| OBJECT_DECLARE_SIMPLE_TYPE(DivaAuxState, DIVA_AUX) |
| |
| static void diva_aux_realize(PCIDevice *dev, Error **errp) |
| { |
| DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev); |
| |
| pci->dev.config[PCI_CLASS_PROG] = 0x02; |
| pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; |
| pci->irq = pci_allocate_irq(&pci->dev); |
| |
| memory_region_init(&pci->mem, OBJECT(pci), "mem", 16); |
| pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->mem); |
| } |
| |
| static void diva_aux_exit(PCIDevice *dev) |
| { |
| DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev); |
| qemu_free_irq(pci->irq); |
| } |
| |
| static void diva_aux_class_initfn(ObjectClass *klass, const void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); |
| pc->realize = diva_aux_realize; |
| pc->exit = diva_aux_exit; |
| pc->vendor_id = PCI_VENDOR_ID_HP; |
| pc->device_id = PCI_DEVICE_ID_HP_DIVA_AUX; |
| pc->subsystem_vendor_id = PCI_VENDOR_ID_HP; |
| pc->subsystem_id = 0x1291; |
| pc->revision = 1; |
| pc->class_id = PCI_CLASS_COMMUNICATION_MULTISERIAL; |
| set_bit(DEVICE_CATEGORY_MISC, dc->categories); |
| dc->user_creatable = false; |
| } |
| |
| static void diva_aux_init(Object *o) |
| { |
| } |
| |
| static const TypeInfo diva_aux_info = { |
| .name = TYPE_DIVA_AUX, |
| .parent = TYPE_PCI_DEVICE, |
| .instance_size = sizeof(DivaAuxState), |
| .instance_init = diva_aux_init, |
| .class_init = diva_aux_class_initfn, |
| .interfaces = (const InterfaceInfo[]) { |
| { INTERFACE_CONVENTIONAL_PCI_DEVICE }, |
| { }, |
| }, |
| }; |
| |
| |
| |
| static const TypeInfo diva_serial_pci_info = { |
| .name = "diva-gsp", |
| .parent = TYPE_PCI_DEVICE, |
| .instance_size = sizeof(PCIDivaSerialState), |
| .instance_init = diva_serial_init, |
| .class_init = diva_serial_class_initfn, |
| .interfaces = (const InterfaceInfo[]) { |
| { INTERFACE_CONVENTIONAL_PCI_DEVICE }, |
| { }, |
| }, |
| }; |
| |
| static void diva_pci_register_type(void) |
| { |
| type_register_static(&diva_serial_pci_info); |
| type_register_static(&diva_aux_info); |
| } |
| |
| type_init(diva_pci_register_type) |