| 1 | /* |
| 2 | * HP Diva GSP controller |
| 3 | * |
| 4 | * The Diva PCI boards are Remote Management cards for PA-RISC machines. |
| 5 | * They come with built-in 16550A multi UARTs for serial consoles |
| 6 | * and a mailbox-like memory area for hardware auto-reboot functionality. |
| 7 | * GSP stands for "Guardian Service Processor". Later products were marketed |
| 8 | * "Management Processor" (MP). |
| 9 | * |
| 10 | * Diva cards are multifunctional cards. The first part, the aux port, |
| 11 | * is on physical machines not useable but we still try to mimic it here. |
| 12 | * |
| 13 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 14 | * |
| 15 | * Copyright (c) 2025 Helge Deller <deller@gmx.de> |
| 16 | */ |
| 17 | |
| 18 | #include "qemu/osdep.h" |
| 19 | #include "qemu/units.h" |
| 20 | #include "hw/char/serial.h" |
| 21 | #include "hw/core/irq.h" |
| 22 | #include "hw/pci/pci_device.h" |
| 23 | #include "hw/core/qdev-properties.h" |
| 24 | #include "hw/core/qdev-properties-system.h" |
| 25 | #include "migration/vmstate.h" |
| 26 | |
| 27 | #define PCI_DEVICE_ID_HP_DIVA 0x1048 |
| 28 | /* various DIVA GSP cards: */ |
| 29 | #define PCI_DEVICE_ID_HP_DIVA_TOSCA1 0x1049 |
| 30 | #define PCI_DEVICE_ID_HP_DIVA_TOSCA2 0x104A |
| 31 | #define PCI_DEVICE_ID_HP_DIVA_MAESTRO 0x104B |
| 32 | #define PCI_DEVICE_ID_HP_REO_IOC 0x10f1 |
| 33 | #define PCI_DEVICE_ID_HP_DIVA_HALFDOME 0x1223 |
| 34 | #define PCI_DEVICE_ID_HP_DIVA_KEYSTONE 0x1226 |
| 35 | #define PCI_DEVICE_ID_HP_DIVA_POWERBAR 0x1227 |
| 36 | #define PCI_DEVICE_ID_HP_DIVA_EVEREST 0x1282 |
| 37 | #define PCI_DEVICE_ID_HP_DIVA_AUX 0x1290 |
| 38 | #define PCI_DEVICE_ID_HP_DIVA_RMP3 0x1301 |
| 39 | #define PCI_DEVICE_ID_HP_DIVA_HURRICANE 0x132a |
| 40 | |
| 41 | |
| 42 | #define PCI_SERIAL_MAX_PORTS 4 |
| 43 | |
| 44 | typedef struct PCIDivaSerialState { |
| 45 | PCIDevice dev; |
| 46 | MemoryRegion membar; /* for serial ports */ |
| 47 | MemoryRegion mailboxbar; /* for hardware mailbox */ |
| 48 | uint32_t subvendor; |
| 49 | uint32_t ports; |
| 50 | char *name[PCI_SERIAL_MAX_PORTS]; |
| 51 | SerialState state[PCI_SERIAL_MAX_PORTS]; |
| 52 | uint32_t level[PCI_SERIAL_MAX_PORTS]; |
| 53 | qemu_irq *irqs; |
| 54 | } PCIDivaSerialState; |
| 55 | |
| 56 | static void diva_pci_exit(PCIDevice *dev) |
| 57 | { |
| 58 | PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev); |
| 59 | SerialState *s; |
| 60 | int i; |
| 61 | |
| 62 | for (i = 0; i < pci->ports; i++) { |
| 63 | s = pci->state + i; |
| 64 | memory_region_del_subregion(&pci->membar, &s->io); |
| 65 | qdev_unrealize(DEVICE(s)); |
| 66 | g_free(pci->name[i]); |
| 67 | } |
| 68 | qemu_free_irqs(pci->irqs, pci->ports); |
| 69 | } |
| 70 | |
| 71 | static void multi_serial_irq_mux(void *opaque, int n, int level) |
| 72 | { |
| 73 | PCIDivaSerialState *pci = opaque; |
| 74 | int i, pending = 0; |
| 75 | |
| 76 | pci->level[n] = level; |
| 77 | for (i = 0; i < pci->ports; i++) { |
| 78 | if (pci->level[i]) { |
| 79 | pending = 1; |
| 80 | } |
| 81 | } |
| 82 | pci_set_irq(&pci->dev, pending); |
| 83 | } |
| 84 | |
| 85 | struct diva_info { |
| 86 | unsigned int nports:4; /* number of serial ports */ |
| 87 | unsigned int omask:12; /* offset mask: BIT(1) -> offset 8 */ |
| 88 | }; |
| 89 | |
| 90 | static struct diva_info diva_get_diva_info(PCIDeviceClass *pc) |
| 91 | { |
| 92 | switch (pc->subsystem_id) { |
| 93 | case PCI_DEVICE_ID_HP_DIVA_POWERBAR: |
| 94 | case PCI_DEVICE_ID_HP_DIVA_HURRICANE: |
| 95 | return (struct diva_info) { .nports = 1, |
| 96 | .omask = BIT(0) }; |
| 97 | case PCI_DEVICE_ID_HP_DIVA_TOSCA2: |
| 98 | return (struct diva_info) { .nports = 2, |
| 99 | .omask = BIT(0) | BIT(1) }; |
| 100 | case PCI_DEVICE_ID_HP_DIVA_TOSCA1: |
| 101 | case PCI_DEVICE_ID_HP_DIVA_HALFDOME: |
| 102 | case PCI_DEVICE_ID_HP_DIVA_KEYSTONE: |
| 103 | return (struct diva_info) { .nports = 3, |
| 104 | .omask = BIT(0) | BIT(1) | BIT(2) }; |
| 105 | case PCI_DEVICE_ID_HP_DIVA_EVEREST: /* e.g. in rp3410 */ |
| 106 | return (struct diva_info) { .nports = 3, |
| 107 | .omask = BIT(0) | BIT(2) | BIT(7) }; |
| 108 | case PCI_DEVICE_ID_HP_DIVA_MAESTRO: |
| 109 | return (struct diva_info) { .nports = 4, |
| 110 | .omask = BIT(0) | BIT(1) | BIT(2) | BIT(7) }; |
| 111 | } |
| 112 | g_assert_not_reached(); |
| 113 | } |
| 114 | |
| 115 | |
| 116 | static void diva_pci_realize(PCIDevice *dev, Error **errp) |
| 117 | { |
| 118 | PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); |
| 119 | PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev); |
| 120 | SerialState *s; |
| 121 | struct diva_info di = diva_get_diva_info(pc); |
| 122 | size_t i, offset = 0; |
| 123 | size_t portmask = di.omask; |
| 124 | |
| 125 | pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */ |
| 126 | pci->dev.config[PCI_INTERRUPT_PIN] = 1; |
| 127 | memory_region_init(&pci->membar, OBJECT(pci), "serial_ports", 4096); |
| 128 | pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->membar); |
| 129 | pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, di.nports); |
| 130 | |
| 131 | for (i = 0; i < di.nports; i++) { |
| 132 | s = pci->state + i; |
| 133 | if (!qdev_realize(DEVICE(s), NULL, errp)) { |
| 134 | diva_pci_exit(dev); |
| 135 | return; |
| 136 | } |
| 137 | s->irq = pci->irqs[i]; |
| 138 | pci->name[i] = g_strdup_printf("uart #%zu", i + 1); |
| 139 | memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s, |
| 140 | pci->name[i], 8); |
| 141 | |
| 142 | /* calculate offset of given port based on bitmask */ |
| 143 | while ((portmask & BIT(0)) == 0) { |
| 144 | offset += 8; |
| 145 | portmask >>= 1; |
| 146 | } |
| 147 | memory_region_add_subregion(&pci->membar, offset, &s->io); |
| 148 | offset += 8; |
| 149 | portmask >>= 1; |
| 150 | pci->ports++; |
| 151 | } |
| 152 | |
| 153 | /* mailbox bar */ |
| 154 | memory_region_init(&pci->mailboxbar, OBJECT(pci), "mailbox", 128 * KiB); |
| 155 | pci_register_bar(&pci->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY | |
| 156 | PCI_BASE_ADDRESS_MEM_PREFETCH, &pci->mailboxbar); |
| 157 | } |
| 158 | |
| 159 | static const VMStateDescription vmstate_pci_diva = { |
| 160 | .name = "pci-diva-serial", |
| 161 | .version_id = 2, |
| 162 | .minimum_version_id = 2, |
| 163 | .fields = (const VMStateField[]) { |
| 164 | VMSTATE_PCI_DEVICE(dev, PCIDivaSerialState), |
| 165 | VMSTATE_STRUCT_ARRAY(state, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS, |
| 166 | 0, vmstate_serial, SerialState), |
| 167 | VMSTATE_UINT32_ARRAY(level, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS), |
| 168 | VMSTATE_END_OF_LIST() |
| 169 | } |
| 170 | }; |
| 171 | |
| 172 | static const Property diva_serial_properties[] = { |
| 173 | DEFINE_PROP_CHR("chardev1", PCIDivaSerialState, state[0].chr), |
| 174 | DEFINE_PROP_CHR("chardev2", PCIDivaSerialState, state[1].chr), |
| 175 | DEFINE_PROP_CHR("chardev3", PCIDivaSerialState, state[2].chr), |
| 176 | DEFINE_PROP_CHR("chardev4", PCIDivaSerialState, state[3].chr), |
| 177 | DEFINE_PROP_UINT32("subvendor", PCIDivaSerialState, subvendor, |
| 178 | PCI_DEVICE_ID_HP_DIVA_TOSCA1), |
| 179 | }; |
| 180 | |
| 181 | static void diva_serial_class_initfn(ObjectClass *klass, const void *data) |
| 182 | { |
| 183 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 184 | PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); |
| 185 | pc->realize = diva_pci_realize; |
| 186 | pc->exit = diva_pci_exit; |
| 187 | pc->vendor_id = PCI_VENDOR_ID_HP; |
| 188 | pc->device_id = PCI_DEVICE_ID_HP_DIVA; |
| 189 | pc->subsystem_vendor_id = PCI_VENDOR_ID_HP; |
| 190 | pc->subsystem_id = PCI_DEVICE_ID_HP_DIVA_TOSCA1; |
| 191 | pc->revision = 3; |
| 192 | pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL; |
| 193 | dc->vmsd = &vmstate_pci_diva; |
| 194 | device_class_set_props(dc, diva_serial_properties); |
| 195 | set_bit(DEVICE_CATEGORY_INPUT, dc->categories); |
| 196 | } |
| 197 | |
| 198 | static void diva_serial_init(Object *o) |
| 199 | { |
| 200 | PCIDevice *dev = PCI_DEVICE(o); |
| 201 | PCIDivaSerialState *pms = DO_UPCAST(PCIDivaSerialState, dev, dev); |
| 202 | struct diva_info di = diva_get_diva_info(PCI_DEVICE_GET_CLASS(dev)); |
| 203 | size_t i; |
| 204 | |
| 205 | for (i = 0; i < di.nports; i++) { |
| 206 | object_initialize_child(o, "serial[*]", &pms->state[i], TYPE_SERIAL); |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | |
| 211 | /* Diva-aux is the driver for portion 0 of the multifunction PCI device */ |
| 212 | |
| 213 | struct DivaAuxState { |
| 214 | PCIDevice dev; |
| 215 | MemoryRegion mem; |
| 216 | qemu_irq irq; |
| 217 | }; |
| 218 | |
| 219 | #define TYPE_DIVA_AUX "diva-aux" |
| 220 | OBJECT_DECLARE_SIMPLE_TYPE(DivaAuxState, DIVA_AUX) |
| 221 | |
| 222 | static void diva_aux_realize(PCIDevice *dev, Error **errp) |
| 223 | { |
| 224 | DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev); |
| 225 | |
| 226 | pci->dev.config[PCI_CLASS_PROG] = 0x02; |
| 227 | pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; |
| 228 | pci->irq = pci_allocate_irq(&pci->dev); |
| 229 | |
| 230 | memory_region_init(&pci->mem, OBJECT(pci), "mem", 16); |
| 231 | pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->mem); |
| 232 | } |
| 233 | |
| 234 | static void diva_aux_exit(PCIDevice *dev) |
| 235 | { |
| 236 | DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev); |
| 237 | qemu_free_irq(pci->irq); |
| 238 | } |
| 239 | |
| 240 | static void diva_aux_class_initfn(ObjectClass *klass, const void *data) |
| 241 | { |
| 242 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 243 | PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); |
| 244 | pc->realize = diva_aux_realize; |
| 245 | pc->exit = diva_aux_exit; |
| 246 | pc->vendor_id = PCI_VENDOR_ID_HP; |
| 247 | pc->device_id = PCI_DEVICE_ID_HP_DIVA_AUX; |
| 248 | pc->subsystem_vendor_id = PCI_VENDOR_ID_HP; |
| 249 | pc->subsystem_id = 0x1291; |
| 250 | pc->revision = 1; |
| 251 | pc->class_id = PCI_CLASS_COMMUNICATION_MULTISERIAL; |
| 252 | set_bit(DEVICE_CATEGORY_MISC, dc->categories); |
| 253 | dc->user_creatable = false; |
| 254 | } |
| 255 | |
| 256 | static void diva_aux_init(Object *o) |
| 257 | { |
| 258 | } |
| 259 | |
| 260 | static const TypeInfo diva_aux_info = { |
| 261 | .name = TYPE_DIVA_AUX, |
| 262 | .parent = TYPE_PCI_DEVICE, |
| 263 | .instance_size = sizeof(DivaAuxState), |
| 264 | .instance_init = diva_aux_init, |
| 265 | .class_init = diva_aux_class_initfn, |
| 266 | .interfaces = (const InterfaceInfo[]) { |
| 267 | { INTERFACE_CONVENTIONAL_PCI_DEVICE }, |
| 268 | { }, |
| 269 | }, |
| 270 | }; |
| 271 | |
| 272 | |
| 273 | |
| 274 | static const TypeInfo diva_serial_pci_info = { |
| 275 | .name = "diva-gsp", |
| 276 | .parent = TYPE_PCI_DEVICE, |
| 277 | .instance_size = sizeof(PCIDivaSerialState), |
| 278 | .instance_init = diva_serial_init, |
| 279 | .class_init = diva_serial_class_initfn, |
| 280 | .interfaces = (const InterfaceInfo[]) { |
| 281 | { INTERFACE_CONVENTIONAL_PCI_DEVICE }, |
| 282 | { }, |
| 283 | }, |
| 284 | }; |
| 285 | |
| 286 | static void diva_pci_register_type(void) |
| 287 | { |
| 288 | type_register_static(&diva_serial_pci_info); |
| 289 | type_register_static(&diva_aux_info); |
| 290 | } |
| 291 | |
| 292 | type_init(diva_pci_register_type) |