Browse Source
The Diva GSP ("Guardian Service Processor") PCI boards are Remote
Management cards for PA-RISC machines. They come with built-in 16550A
UARTs for serial consoles and modem functionalities, as well as a
mailbox-like memory area for hardware auto-reboot functionality.
Latest generation HP PA-RISC server machines use those Diva cards
for console output.
Signed-off-by: Helge Deller <deller@gmx.de>
pull/281/head
4 changed files with 302 additions and 0 deletions
@ -0,0 +1,297 @@ |
|||
/*
|
|||
* 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; |
|||
uint8_t prog_if; |
|||
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] = pci->prog_if; |
|||
pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; |
|||
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_UINT8("prog_if", PCIDivaSerialState, prog_if, 0x02), |
|||
DEFINE_PROP_UINT32("subvendor", PCIDivaSerialState, subvendor, |
|||
PCI_DEVICE_ID_HP_DIVA_TOSCA1), |
|||
}; |
|||
|
|||
static void diva_serial_class_initfn(ObjectClass *klass, 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, 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 = (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 = (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) |
|||
Loading…
Reference in new issue