diff --git a/MAINTAINERS b/MAINTAINERS index 9012bf476d..6be9c5725b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1355,6 +1355,10 @@ F: include/hw/pci-host/dino.h F: pc-bios/hppa-firmware.img F: roms/seabios-hppa/ F: tests/functional/hppa/ +F: hw/isa/pc87560.c +F: hw/ide/pc87560-ide.c +F: hw/usb/pc87560-ohci.c +F: include/hw/isa/pc87560.h Hexagon Machines ---------------- diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index 98931481b2..96133f09ba 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -394,7 +394,7 @@ static TranslateFn *machine_HP_common_init_cpus(MachineState *machine) * Last creation step: Add NICs, graphics & load firmware */ static void machine_HP_common_init_tail(MachineState *machine, PCIBus *pci_bus, - TranslateFn *translate) + TranslateFn *translate, bool create_diva) { const char *kernel_filename = machine->kernel_filename; MachineClass *mc = MACHINE_GET_CLASS(machine); @@ -426,7 +426,7 @@ static void machine_HP_common_init_tail(MachineState *machine, PCIBus *pci_bus, pci_init_nic_devices(pci_bus, mc->default_nic); } - if (pci_bus && hppa_is_pa20(&cpu[0]->env)) { + if (pci_bus && hppa_is_pa20(&cpu[0]->env) && create_diva) { /* BMC board: HP Diva GSP PCI card */ pci_dev = pci_new_multifunction(PCI_DEVFN(2, 0), "diva-gsp"); if (!lasi_dev) { @@ -442,8 +442,9 @@ static void machine_HP_common_init_tail(MachineState *machine, PCIBus *pci_bus, /* create USB OHCI controller for USB keyboard & mouse on Astro machines */ if (!lasi_dev && machine->enable_graphics && defaults_enabled()) { USBBus *usb_bus; - - pci_create_simple(pci_bus, -1, "pci-ohci"); + if (create_diva) { + pci_create_simple(pci_bus, -1, "pci-ohci"); + } usb_bus = USB_BUS(object_resolve_type_unambiguous(TYPE_USB_BUS, &error_abort)); usb_create_simple(usb_bus, "usb-kbd"); @@ -647,7 +648,7 @@ static void machine_HP_715_init(MachineState *machine) } /* Add NICs, graphics & load firmware */ - machine_HP_common_init_tail(machine, NULL, translate); + machine_HP_common_init_tail(machine, NULL, translate, false); } /* @@ -726,7 +727,7 @@ static void machine_HP_B160L_init(MachineState *machine) } /* Add NICs, graphics & load firmware */ - machine_HP_common_init_tail(machine, pci_bus, translate); + machine_HP_common_init_tail(machine, pci_bus, translate, false); } static AstroState *astro_init(void) @@ -771,6 +772,32 @@ static void machine_HP_C3700_init(MachineState *machine) pci_bus = PCI_BUS(qdev_get_child_bus(DEVICE(astro->elroy[0]), "pci")); assert(pci_bus); + /* The C3700 has a SuperIO chip, while the A400 hasn't. */ + MachineClass *mc = MACHINE_GET_CLASS(machine); + bool is_C3700 = strcmp(mc->name, "C3700") == 0; + if (is_C3700) { + PCIDevice *ide_pdev = pci_create_simple(pci_bus, PCI_DEVFN(2, 0), + "pc87560-ide"); + PCIDevice *usb_pdev = pci_create_simple(pci_bus, PCI_DEVFN(2, 2), + "pc87560-ohci"); + PCIDevice *sio_pdev = pci_new(PCI_DEVFN(2, 1), "pc87560-superio"); + if (serial_hd(0)) { + qdev_prop_set_chr(DEVICE(sio_pdev), "serial0", serial_hd(0)); + } + if (serial_hd(1)) { + qdev_prop_set_chr(DEVICE(sio_pdev), "serial1", serial_hd(1)); + } + pci_realize_and_unref(sio_pdev, pci_bus, &error_fatal); + + qdev_connect_gpio_out(DEVICE(ide_pdev), 0, + qdev_get_gpio_in_named(DEVICE(sio_pdev), "pic-irq", 7)); + qdev_connect_gpio_out(DEVICE(usb_pdev), 0, + qdev_get_gpio_in_named(DEVICE(sio_pdev), "pic-irq", 1)); + + qdev_connect_gpio_out_named(DEVICE(sio_pdev), "pic-out", 0, + pci_allocate_irq(usb_pdev)); + } + /* SCSI disk setup. */ if (drive_get_max_bus(IF_SCSI) >= 0) { DeviceState *dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a")); @@ -778,7 +805,7 @@ static void machine_HP_C3700_init(MachineState *machine) } /* Add NICs, graphics & load firmware */ - machine_HP_common_init_tail(machine, pci_bus, translate); + machine_HP_common_init_tail(machine, pci_bus, translate, !is_C3700); } /* diff --git a/hw/ide/meson.build b/hw/ide/meson.build index ddd7066040..be82ee8974 100644 --- a/hw/ide/meson.build +++ b/hw/ide/meson.build @@ -14,3 +14,4 @@ system_ss.add(when: 'CONFIG_IDE_PCI', if_true: files('pci.c')) system_ss.add(when: 'CONFIG_IDE_PIIX', if_true: files('piix.c', 'ioport.c')) system_ss.add(when: 'CONFIG_IDE_SII3112', if_true: files('sii3112.c')) system_ss.add(when: 'CONFIG_IDE_VIA', if_true: files('via.c')) +system_ss.add(when: 'CONFIG_PC87560', if_true: files('pc87560-ide.c')) diff --git a/hw/ide/pc87560-ide.c b/hw/ide/pc87560-ide.c new file mode 100644 index 0000000000..879b5f1234 --- /dev/null +++ b/hw/ide/pc87560-ide.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NS PC87560 SuperI/O — IDE emulation + * + * Copyright (c) 2026 Abizer abizerlokhandwalastd10@gmail.com + */ + +#include "qemu/osdep.h" +#include "hw/pci/pci.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "hw/isa/isa.h" +#include "hw/core/irq.h" +#include "hw/ide/pci.h" +#include "ide-internal.h" +#include "trace.h" + +#define PC87415_IDE_CHANNELS 2 + +/* PCI configuration space */ +#define PC87415_CTRL 0x40 +#define PC87415_WRITE_BUFFER_STATUS 0x43 + +/* CTRL register, byte 1 (PCI config offset 0x41) */ +#define PC87415_CTRL_CH1_INT_MASK BIT(0) +#define PC87415_CTRL_CH2_INT_MASK BIT(1) + +/* PCI programming interface */ +#define PC87415_PIF_CH1_PROGRAMMABLE BIT(1) +#define PC87415_PIF_CH2_PROGRAMMABLE BIT(3) +#define PC87415_PIF_MASTER_IDE BIT(7) + +#define PC87415_PIF_DEFAULT \ + (PC87415_PIF_CH1_PROGRAMMABLE | \ + PC87415_PIF_CH2_PROGRAMMABLE | \ + PC87415_PIF_MASTER_IDE) + +/* Per-device timing registers */ +#define PC87415_CH1_DEV1_READ_TIMING 0x44 +#define PC87415_CH1_DEV1_WRITE_TIMING 0x45 +#define PC87415_CH1_DEV2_READ_TIMING 0x48 +#define PC87415_CH1_DEV2_WRITE_TIMING 0x49 +#define PC87415_CH2_DEV1_READ_TIMING 0x4c +#define PC87415_CH2_DEV1_WRITE_TIMING 0x4d +#define PC87415_CH2_DEV2_READ_TIMING 0x50 +#define PC87415_CH2_DEV2_WRITE_TIMING 0x51 + +#define PC87415_CMD_TIMING 0x54 +#define PC87415_SECTOR_SIZE 0x55 + +/* PCI BARs */ +#define PC87415_BAR_CH1_DATA 0 +#define PC87415_BAR_CH1_CONTROL 1 +#define PC87415_BAR_CH2_DATA 2 +#define PC87415_BAR_CH2_CONTROL 3 +#define PC87415_BAR_BMDMA 4 + +/* Bus-master IDE registers */ +#define PC87415_BMDMA_CMD 0x00 +#define PC87415_BMDMA_STATUS 0x02 +#define PC87415_BMDMA_PRD 0x04 +#define PC87415_BMDMA_CHANNEL_SIZE 0x08 +#define PC87415_BMDMA_BAR_SIZE 0x10 + +/* + * Bus-master command register: + * bit 0 = start/stop + * bit 3 = read/write + */ +#define PC87415_BMDMA_CMD_START BIT(0) +#define PC87415_BMDMA_CMD_WRITE BIT(3) +#define PC87415_BMDMA_CMD_MASK \ + (PC87415_BMDMA_CMD_START | PC87415_BMDMA_CMD_WRITE) + +/* + * Bus-master status register: + * bit 1 = error + * bit 2 = interrupt + */ +#define PC87415_BMDMA_STATUS_ERROR BIT(1) +#define PC87415_BMDMA_STATUS_INT BIT(2) + +static qemu_irq pc87560_ide_irq_out; +static void pc87560_update_irq(PCIDevice *pd); + +static uint64_t bmdma_read(void *opaque, hwaddr addr, unsigned size) +{ + BMDMAState *bm = opaque; + uint32_t val; + + if (size != 1) { + return ((uint64_t)1 << (size * 8)) - 1; + } + + switch (addr & 3) { + case 0: + val = bm->cmd; + break; + case 2: + val = bm->status; + break; + default: + val = 0xff; + break; + } + return val; +} + +static void bmdma_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + BMDMAState *bm = opaque; + PCIDevice *pd = PCI_DEVICE(bm->bus->qbus.parent); + + if (size != 1) { + return; + } + + switch (addr & 3) { + case 0: { + /* + * NS87415 erratum: writing the command register also clears + * the error and interrupt status bits. + */ + uint8_t status_clear = val & (PC87415_BMDMA_STATUS_INT | + PC87415_BMDMA_STATUS_ERROR); + uint8_t real_cmd = val & PC87415_BMDMA_CMD_MASK; + /* only START(0) and WRITE(3) are real cmd bits */ + + bm->status &= ~status_clear; + + if (real_cmd != (bm->cmd & 0x09)) { + bmdma_cmd_writeb(bm, real_cmd); + } + + pc87560_update_irq(pd); + break; + } + case 2: + bmdma_status_writeb(bm, val); + pc87560_update_irq(pd); + break; + default: + break; + } +} + +static void pc87560_update_irq(PCIDevice *pd) +{ + PCIIDEState *d = PCI_IDE(pd); + uint8_t ctrl1 = pd->config[PC87415_CTRL + 1]; + int level = 0; + + if (!(ctrl1 & PC87415_CTRL_CH1_INT_MASK)) { + level |= !!(d->bmdma[0].status & BM_STATUS_INT); + } + + if (!(ctrl1 & PC87415_CTRL_CH2_INT_MASK)) { + level |= !!(d->bmdma[1].status & BM_STATUS_INT); + } + + qemu_set_irq(pc87560_ide_irq_out, level); +} + +static const MemoryRegionOps pc87560_bmdma_ops = { + .read = bmdma_read, + .write = bmdma_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void bmdma_setup_bar(PCIIDEState *d) +{ + unsigned int i; + + memory_region_init(&d->bmdma_bar, OBJECT(d), "pc87415-bmdma", + PC87415_BMDMA_BAR_SIZE); + + for (i = 0; i < PC87415_IDE_CHANNELS; i++) { + BMDMAState *bm = &d->bmdma[i]; + + memory_region_init_io(&bm->extra_io, OBJECT(d), + &pc87560_bmdma_ops, bm, + "pc87415-bmdma-cmd-status", + 4); + memory_region_add_subregion(&d->bmdma_bar, + i * PC87415_BMDMA_CHANNEL_SIZE, + &bm->extra_io); + + memory_region_init_io(&bm->addr_ioport, OBJECT(d), + &bmdma_addr_ioport_ops, bm, + "pc87415-bmdma-prd", + 4); + memory_region_add_subregion(&d->bmdma_bar, + i * PC87415_BMDMA_CHANNEL_SIZE + + PC87415_BMDMA_PRD, + &bm->addr_ioport); + } +} + + +static void pc87560_set_irq(void *opaque, int channel, int level) +{ + PCIIDEState *d = opaque; + PCIDevice *pd = PCI_DEVICE(d); + + if (level) { + d->bmdma[channel].status |= BM_STATUS_INT; + } + pc87560_update_irq(pd); +} + + +static void pc87560_reset(Object *dev, ResetType type) +{ + PCIIDEState *d = PCI_IDE(dev); + unsigned int i; + + for (i = 0; i < 2; i++) { + ide_bus_reset(&d->bus[i], IDE_RESET_HARDWARE); + } +} + + +static void pc87560_pci_config_write(PCIDevice *d, uint32_t addr, uint32_t val, + int len) +{ + pci_default_write_config(d, addr, val, len); + + if (ranges_overlap(addr, len, PC87415_CTRL + 1, 1)) { + pc87560_update_irq(d); + } +} + +static void pci_pc87560_ide_realize(PCIDevice *dev, Error **errp) +{ + + PCIIDEState *d = PCI_IDE(dev); + DeviceState *ds = DEVICE(dev); + uint8_t *pci_conf = dev->config; + int i; + + dev->cap_present |= QEMU_PCI_CAP_MULTIFUNCTION; + pci_conf[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION; + + pci_conf[PCI_CLASS_PROG] = 0x8a; + dev->wmask[PCI_CLASS_PROG] = 0xff; + + pci_conf[PCI_INTERRUPT_PIN] = 0x01; + qdev_init_gpio_out(ds, &pc87560_ide_irq_out, 1); + + memset(&dev->wmask[PC87415_CTRL], 0xff, 3); + + dev->wmask[PC87415_CH1_DEV1_READ_TIMING] = 0xff; + dev->wmask[PC87415_CH1_DEV1_WRITE_TIMING] = 0xff; + + dev->wmask[PC87415_CH1_DEV2_READ_TIMING] = 0xff; + dev->wmask[PC87415_CH1_DEV2_WRITE_TIMING] = 0xff; + + dev->wmask[PC87415_CH2_DEV1_READ_TIMING] = 0xff; + dev->wmask[PC87415_CH2_DEV1_WRITE_TIMING] = 0xff; + + dev->wmask[PC87415_CH2_DEV2_READ_TIMING] = 0xff; + dev->wmask[PC87415_CH2_DEV2_WRITE_TIMING] = 0xff; + + dev->wmask[PC87415_CMD_TIMING] = 0xff; + dev->wmask[PC87415_SECTOR_SIZE] = 0xff; + + pci_conf[PC87415_CTRL + 0] = 0x00; + pci_conf[PC87415_CTRL + 1] = 0x00; + pci_conf[PC87415_CTRL + 2] = 0x00; + + pci_conf[PC87415_CH1_DEV1_READ_TIMING] = 0x85; + pci_conf[PC87415_CH1_DEV1_WRITE_TIMING] = 0x85; + pci_conf[PC87415_CH1_DEV2_READ_TIMING] = 0x85; + pci_conf[PC87415_CH1_DEV2_WRITE_TIMING] = 0x85; + pci_conf[PC87415_CH2_DEV1_READ_TIMING] = 0x85; + pci_conf[PC87415_CH2_DEV1_WRITE_TIMING] = 0x85; + pci_conf[PC87415_CH2_DEV2_READ_TIMING] = 0x85; + pci_conf[PC87415_CH2_DEV2_WRITE_TIMING] = 0x85; + + pci_conf[PC87415_CMD_TIMING] = 0xB7; + pci_conf[PC87415_SECTOR_SIZE] = 0xEE; + + memory_region_init_io(&d->data_bar[0], OBJECT(d), &pci_ide_data_le_ops, + &d->bus[0], "pc87560-data0", 8); + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->data_bar[0]); + + memory_region_init_io(&d->cmd_bar[0], OBJECT(d), &pci_ide_cmd_le_ops, + &d->bus[0], "pc87560-cmd0", 4); + pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd_bar[0]); + + memory_region_init_io(&d->data_bar[1], OBJECT(d), &pci_ide_data_le_ops, + &d->bus[1], "pc87560-data1", 8); + pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_IO, &d->data_bar[1]); + + memory_region_init_io(&d->cmd_bar[1], OBJECT(d), &pci_ide_cmd_le_ops, + &d->bus[1], "pc87560-cmd1", 4); + pci_register_bar(dev, 3, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd_bar[1]); + + bmdma_setup_bar(d); + pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar); + + qdev_init_gpio_in(ds, pc87560_set_irq, 2); + for (i = 0; i < 2; i++) { + ide_bus_init(&d->bus[i], sizeof(d->bus[i]), ds, i, 2); + ide_bus_init_output_irq(&d->bus[i], qdev_get_gpio_in(ds, i)); + + bmdma_init(&d->bus[i], &d->bmdma[i], d); + d->bmdma[i].bus = &d->bus[i]; + ide_bus_register_restart_cb(&d->bus[i]); + } +} + +static void pci_pc87560_ide_exitfn(PCIDevice *dev) +{ + PCIIDEState *d = PCI_IDE(dev); + unsigned int i; + + for (i = 0; i < 2; ++i) { + memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].extra_io); + memory_region_del_subregion(&d->bmdma_bar, &d->bmdma[i].addr_ioport); + } +} + + +static void pc87560_ide_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = pc87560_reset; + dc->vmsd = &vmstate_ide_pci; + dc->user_creatable = false; + k->realize = pci_pc87560_ide_realize; + k->exit = pci_pc87560_ide_exitfn; + k->vendor_id = PCI_VENDOR_ID_NS; + k->device_id = PCI_DEVICE_ID_NS_87415; + k->revision = 0x02; + k->subsystem_vendor_id = PCI_VENDOR_ID_HP; + k->subsystem_id = 0x10A7; + k->class_id = PCI_CLASS_STORAGE_IDE; + k->config_write = pc87560_pci_config_write; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); +} + + +static const TypeInfo pc87560_ide_info = { + .name = "pc87560-ide", + .parent = TYPE_PCI_IDE, + .class_init = pc87560_ide_class_init, +}; + +static void pc87560_ide_register_types(void) +{ + type_register_static(&pc87560_ide_info); +} + +type_init(pc87560_ide_register_types) diff --git a/hw/ide/trace-events b/hw/ide/trace-events index 3ab5e7bd1d..c44eec6c58 100644 --- a/hw/ide/trace-events +++ b/hw/ide/trace-events @@ -27,6 +27,10 @@ ide_dma_cb(void *s, int64_t sector_num, int n, const char *dma) "IDEState %p; se bmdma_read_cmd646(uint64_t addr, uint32_t val) "bmdma: readb 0x%"PRIx64" : 0x%02x" bmdma_write_cmd646(uint64_t addr, uint64_t val) "bmdma: writeb 0x%"PRIx64" : 0x%02"PRIx64 +# hw/ide/pc87560.c +bmdma_read_pc87560(uint64_t addr, uint32_t val) "bmdma: readb 0x%"PRIx64" : 0x%02x" +bmdma_write_pc87560(uint64_t addr, uint64_t val) "bmdma: writeb 0x%"PRIx64" : 0x%02"PRIx64 + # pci.c bmdma_reset(void) "" bmdma_cmd_writeb(uint32_t val) "val: 0x%08x" diff --git a/hw/isa/Kconfig b/hw/isa/Kconfig index 73c6470805..23d327e88c 100644 --- a/hw/isa/Kconfig +++ b/hw/isa/Kconfig @@ -67,6 +67,17 @@ config SMC37C669 bool select ISA_SUPERIO +config PC87560 + bool + default y if PCI_DEVICES + depends on PCI + depends on HPPA + select ISA_BUS + select FDC_ISA + select SERIAL_ISA + select IDE_PCI + select USB_OHCI + config LPC_ICH9 bool # For historical reasons, SuperIO devices are created in the board diff --git a/hw/isa/meson.build b/hw/isa/meson.build index 3219282217..1a150e7069 100644 --- a/hw/isa/meson.build +++ b/hw/isa/meson.build @@ -8,4 +8,5 @@ system_ss.add(when: 'CONFIG_PIIX', if_true: files('piix.c')) system_ss.add(when: 'CONFIG_SMC37C669', if_true: files('smc37c669-superio.c')) system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686.c')) +specific_ss.add(when: 'CONFIG_PC87560', if_true: files('pc87560.c')) specific_ss.add(when: 'CONFIG_LPC_ICH9', if_true: files('lpc_ich9.c')) diff --git a/hw/isa/pc87560.c b/hw/isa/pc87560.c new file mode 100644 index 0000000000..c13c54deb2 --- /dev/null +++ b/hw/isa/pc87560.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NS PC87560 SuperI/O — ISA emulation + * + * Copyright (c) 2026 Abizer abizerlokhandwalastd10@gmail.com + */ +#include "qemu/osdep.h" +#include "hw/pci/pci.h" +#include "hw/pci/pci_device.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "hw/char/serial.h" +#include "hw/char/parallel.h" +#include "hw/block/fdc-internal.h" +#include "hw/isa/pc87560.h" + +static uint64_t pc87560_pp_read(void *opaque, hwaddr addr, unsigned size) +{ + ParallelState *s = opaque; + switch (addr) { + case 0: return s->datar; + case 1: return s->status; + case 2: return s->control; + default: + qemu_log_mask(LOG_UNIMP, + "pc87560-pp: read offset 0x%" HWADDR_PRIx "\n", addr); + return 0xFF; + } +} + +static int pic_highest_priority(int priority_base, uint8_t mask) +{ + int i; + for (i = 1; i <= 8; i++) { + int irq = (priority_base + i) & 7; + if (mask & (1 << irq)) { + return irq; + } + } + return -1; +} + +static void pic_update_irq(PC87560SuperioState *s) +{ + int base = s->pic.priority_base; + int first_irr = pic_highest_priority(base, s->pic.irr & ~s->pic.imr); + int first_isr = pic_highest_priority(base, s->pic.isr); + int pending = 0; + + if (first_irr != -1) { + if (first_isr == -1) { + pending = 1; + } else if (first_irr != first_isr) { + int irr_lvl = (first_irr - s->pic.priority_base - 1) & 7; + int isr_lvl = (first_isr - s->pic.priority_base - 1) & 7; + if (irr_lvl < isr_lvl) { + pending = 1; + } + } + } + + if (pending != s->pic.irq_level) { + s->pic.irq_level = pending; + qemu_set_irq(s->parent_irq, pending); + } +} + +static uint64_t pic1_read(void *opaque, hwaddr addr, unsigned size) +{ + PC87560SuperioState *s = opaque; + if (addr == 0) { + if (s->pic.poll_mode) { + s->pic.poll_mode = false; + uint8_t pending_mask = s->pic.irr & ~s->pic.imr; + int irq = pic_highest_priority(s->pic.priority_base, pending_mask); + if (irq == -1) { + pic_update_irq(s); + return 0x00; + } + s->pic.isr |= (1 << irq); + pic_update_irq(s); + return 0x80 | irq; + } + return s->pic.read_isr ? s->pic.isr : s->pic.irr; + } + return s->pic.imr; +} + +static void pic1_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + PC87560SuperioState *s = opaque; + uint8_t v = (uint8_t)val; + + if (addr == 1) { + if (s->pic.init_phase > 0) { + if (s->pic.init_phase == 3) { + s->pic.init_phase = 0; + } else { + s->pic.init_phase++; + } + } else { + s->pic.imr = v; + pic_update_irq(s); + } + return; + } + + if (v & 0x10) { + s->pic.init_phase = 1; + s->pic.imr = 0xFF; + s->pic.irr = 0; + s->pic.isr = 0; + s->pic.poll_mode = false; + s->pic.priority_base = 7; + } else if (v & 0x08) { + if (v & 0x04) { + s->pic.poll_mode = true; + } + if (v & 0x02) { + s->pic.read_isr = (v & 0x01); + } + } else { + uint8_t cmd = (v >> 5) & 0x07; + uint8_t irq = v & 0x07; + switch (cmd) { + case 1: + case 5: { + int serviced = pic_highest_priority(s->pic.priority_base, + s->pic.isr); + if (serviced != -1) { + s->pic.isr &= ~(1 << serviced); + if (cmd == 5) { + s->pic.priority_base = serviced; + } + } + break; + } + case 3: + case 7: + s->pic.isr &= ~(1 << irq); + if (cmd == 7) { + s->pic.priority_base = irq; + } + break; + case 6: + s->pic.priority_base = irq; + break; + } + pic_update_irq(s); + } +} + +static const MemoryRegionOps pc87560_pic1_ops = { + .read = pic1_read, + .write = pic1_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 1, + .valid.max_access_size = 1, +}; + +static void pc87560_pic_irq_in(void *opaque, int irq_n, int level) +{ + PC87560SuperioState *s = opaque; + if (level) { + s->pic.irr |= (1 << irq_n); + } else { + s->pic.irr &= ~(1 << irq_n); + } + pic_update_irq(s); +} + + +static void pc87560_pp_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + ParallelState *s = opaque; + switch (addr) { + case 0: + s->dataw = (uint8_t)val; + if (qemu_chr_fe_backend_connected(&s->chr)) { + unsigned char ch = (uint8_t)val; + qemu_chr_fe_write_all(&s->chr, &ch, 1); + } + break; + case 2: + s->control = (uint8_t)(val & 0x1F); + if (!(s->control & 0x10)) { + qemu_irq_lower(s->irq); + } + break; + default: + qemu_log_mask(LOG_UNIMP, + "pc87560-pp: write offset 0x%" HWADDR_PRIx "\n", addr); + } +} +static const MemoryRegionOps pc87560_pp_ops = { + .read = pc87560_pp_read, + .write = pc87560_pp_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 1, + .valid.max_access_size = 1, +}; + +/* + * Some features on the chip are disabled by default. If a guest + * reads or writes one of those registers, respond via this stub + * instead of faulting. + */ +static uint64_t pc87560_stub_read(void *opaque, hwaddr addr, unsigned size) +{ + qemu_log_mask(LOG_UNIMP, + "pc87560-superio: stub read 0x%" HWADDR_PRIx "\n", addr); + return 0xFF; +} +static void pc87560_stub_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + qemu_log_mask(LOG_UNIMP, + "pc87560-superio: stub write 0x%" HWADDR_PRIx + " = 0x%" PRIx64 "\n", addr, val); +} + +static const MemoryRegionOps pc87560_stub_ops = { + .read = pc87560_stub_read, + .write = pc87560_stub_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + + +static uint64_t pc87560_fdc_read(void *opaque, hwaddr addr, unsigned size) +{ + return fdctrl_read(opaque, (uint32_t)addr); +} + +static void pc87560_fdc_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + fdctrl_write(opaque, (uint32_t)addr, (uint32_t)val); +} + +static const MemoryRegionOps pc87560_fdc_ops = { + .read = pc87560_fdc_read, + .write = pc87560_fdc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 1, + .valid.max_access_size = 1, +}; + +static void pc87560_map_region(PC87560SuperioState *s, MemoryRegion *mr, + bool *mapped, hwaddr base) +{ + MemoryRegion *io = pci_address_space_io(PCI_DEVICE(s)); + if (*mapped) { + memory_region_del_subregion(io, mr); + *mapped = false; + } + if (base) { + memory_region_add_subregion(io, base, mr); + *mapped = true; + } +} + +static void pc87560_apply_funcen(PC87560SuperioState *s) +{ + PCIDevice *pci = PCI_DEVICE(s); + uint16_t en = pci_get_word(pci->config + REG_FUNCEN); + + pc87560_map_region(s, &s->fdc_io, &s->fdc_mapped, (en & FUNCEN_FDC) + ? (pci_get_long(pci->config + REG_FDCBAR) & ~(hwaddr)0x7) : 0); + + pc87560_map_region(s, &s->sp1_io, &s->sp1_mapped, (en & FUNCEN_SP1) + ? (pci_get_long(pci->config + REG_SP1BAR) & ~(hwaddr)0x7) : 0); + + pc87560_map_region(s, &s->sp2_io, &s->sp2_mapped, (en & FUNCEN_SP2) + ? (pci_get_long(pci->config + REG_SP2BAR) & ~(hwaddr)0x7) : 0); + + pc87560_map_region(s, &s->pp_io, &s->pp_mapped, (en & FUNCEN_PP) + ? (pci_get_long(pci->config + REG_PPBAR) & ~(hwaddr)0x7) : 0); + + pc87560_map_region(s, &s->kbc_io, &s->kbc_mapped, (en & FUNCEN_KBC) + ? (pci_get_long(pci->config + REG_KBCBAR) & ~(hwaddr)0x7) : 0); +} + +static void pc87560_superio_config_write(PCIDevice *pci, + uint32_t addr, uint32_t val, int len) +{ + PC87560SuperioState *s = PC87560_Superio(pci); + uint16_t funcen; + + pci_default_write_config(pci, addr, val, len); + funcen = pci_get_word(pci->config + REG_FUNCEN); + + if (ranges_overlap(addr, len, REG_FUNCEN, 2)) { + pc87560_apply_funcen(s); + } + + if (ranges_overlap(addr, len, REG_FDCBAR, 4)) { + if (funcen & FUNCEN_FDC) { + pc87560_map_region(s, &s->fdc_io, &s->fdc_mapped, + pci_get_long(pci->config + REG_FDCBAR) & ~(hwaddr)0x7); + } + } + + if (ranges_overlap(addr, len, REG_SP1BAR, 4)) { + if (funcen & FUNCEN_SP1) { + pc87560_map_region(s, &s->sp1_io, &s->sp1_mapped, + pci_get_long(pci->config + REG_SP1BAR) & ~(hwaddr)0x7); + } + } + + if (ranges_overlap(addr, len, REG_SP2BAR, 4)) { + if (funcen & FUNCEN_SP2) { + pc87560_map_region(s, &s->sp2_io, &s->sp2_mapped, + pci_get_long(pci->config + REG_SP2BAR) & ~(hwaddr)0x7); + } + } + + if (ranges_overlap(addr, len, REG_PPBAR, 4)) { + if (funcen & FUNCEN_PP) { + pc87560_map_region(s, &s->pp_io, &s->pp_mapped, + pci_get_long(pci->config + REG_PPBAR) & ~(hwaddr)0x7); + } + } + + if (ranges_overlap(addr, len, REG_KBCBAR, 4)) { + if (funcen & FUNCEN_KBC) { + pc87560_map_region(s, &s->kbc_io, &s->kbc_mapped, + pci_get_long(pci->config + REG_KBCBAR) & ~(hwaddr)0x7); + } + } + + if (ranges_overlap(addr, len, REG_ACPIBAR, 4)) { + uint32_t ab = pci_get_long(pci->config + REG_ACPIBAR); + pc87560_map_region(s, &s->acpi_io, &s->acpi_mapped, + (ab & 1) ? (ab & ~(hwaddr)0x1F) : 0); + } + + if (ranges_overlap(addr, len, REG_PMBAR, 4)) { + uint32_t pb = pci_get_long(pci->config + REG_PMBAR); + pc87560_map_region(s, &s->pm_io, &s->pm_mapped, + (pb & 1) ? (pb & ~(hwaddr)0xFF) : 0); + } + + if (ranges_overlap(addr, len, REG_RSVD_CFG, 1)) { + if ((pci->config[REG_RSVD_CFG] & 0x0F) != 0x01) { + qemu_log_mask(LOG_GUEST_ERROR, + "pc87560-superio: offset 0x7E bits[3:0] must be 0x01\n"); + } + } +} + +static void pc87560_superio_realize(PCIDevice *pci, Error **errp) +{ + PC87560SuperioState *s = PC87560_Superio(pci); + MemoryRegion *io = pci_address_space_io(pci); + + pci_set_word(pci->config + REG_FUNCEN, FUNCEN_DEFAULT); + pci->config[REG_RSVD_CFG] = 0x01; + pci->config[REG_DMA_ROUTE1] = 0x67; + pci->config[REG_PPDID] = 0x10; + + pci_set_long(pci->config + REG_KBCBAR, 0x00000060); /* not usable */ + pci_set_long(pci->config + REG_ACPIBAR, 0x00004001); + pci_set_long(pci->config + REG_PMBAR, 0xFFFFFF01); + pci_set_long(pci->config + REG_FDCBAR, 0x000003F0); /* not usable */ + pci_set_long(pci->config + REG_SP1BAR, 0x000003F8); + pci_set_long(pci->config + REG_SP2BAR, 0x000002F8); + pci_set_long(pci->config + REG_PPBAR, 0x00000378); + + + pci_set_long(pci->wmask + REG_KBCBAR, 0xFFFFFFF8); /* not usable */ + pci_set_long(pci->wmask + REG_ACPIBAR, 0xFFFFFFE1); + pci_set_long(pci->wmask + REG_PMBAR, 0xFFFFFF01); + pci_set_long(pci->wmask + REG_FDCBAR, 0xFFFFFFF8); /* not usable */ + pci_set_long(pci->wmask + REG_SP1BAR, 0xFFFFFFF8); + pci_set_long(pci->wmask + REG_SP2BAR, 0xFFFFFFF8); + pci_set_long(pci->wmask + REG_PPBAR, 0xFFFFFFF8); + + pci->config[PCI_INTERRUPT_PIN] = 0x01; + + qdev_init_gpio_out_named(DEVICE(pci), &s->parent_irq, "pic-out", 1); + qdev_init_gpio_in_named(DEVICE(pci), pc87560_pic_irq_in, "pic-irq", 8); + + memory_region_init_io(&s->pic1_io, OBJECT(s), &pc87560_pic1_ops, + s, "pc87560-pic1", 2); + memory_region_add_subregion(io, IC_PIC1, &s->pic1_io); + + s->serial[0].irq = qemu_allocate_irq(pc87560_pic_irq_in, s, 3); + if (!qdev_realize(DEVICE(&s->serial[0]), NULL, errp)) { + return; + } + memory_region_init_io(&s->sp1_io, OBJECT(s), &serial_io_ops, + &s->serial[0], "pc87560-sp1", 8); + memory_region_add_subregion(io, 0x3F8, &s->sp1_io); + s->sp1_mapped = true; + + s->serial[1].irq = qemu_allocate_irq(pc87560_pic_irq_in, s, 4); + if (!qdev_realize(DEVICE(&s->serial[1]), NULL, errp)) { + return; + } + memory_region_init_io(&s->sp2_io, OBJECT(s), &serial_io_ops, + &s->serial[1], "pc87560-sp2", 8); + memory_region_add_subregion(io, 0x2F8, &s->sp2_io); + s->sp2_mapped = true; + + s->pp.irq = qemu_allocate_irq(pc87560_pic_irq_in, s, 5); + memory_region_init_io(&s->pp_io, OBJECT(s), &pc87560_pp_ops, + &s->pp, "pc87560-pp", 8); + memory_region_add_subregion(io, 0x378, &s->pp_io); + s->pp_mapped = true; + s->pic.irq_level = -1; + s->pic.priority_base = 7; + s->fdc.irq = qemu_allocate_irq(pc87560_pic_irq_in, s, 6); + s->fdc.dma_chann = -1; + s->fdc.dma = NULL; + s->fdc.fallback = FLOPPY_DRIVE_TYPE_144; + fdctrl_realize_common(DEVICE(pci), &s->fdc, errp); + if (*errp) { + return; + } + memory_region_init_io(&s->fdc_io, OBJECT(s), &pc87560_fdc_ops, + &s->fdc, "pc87560-fdc", 8); + memory_region_add_subregion(io, 0x3F0, &s->fdc_io); + s->fdc_mapped = true; + + memory_region_init_io(&s->kbc_io, OBJECT(s), &pc87560_stub_ops, + s, "pc87560-kbc", 8); + memory_region_add_subregion(io, 0x60, &s->kbc_io); + s->kbc_mapped = true; + + memory_region_init_io(&s->acpi_io, OBJECT(s), &pc87560_stub_ops, + s, "pc87560-acpi", 32); + memory_region_add_subregion(io, 0x4000, &s->acpi_io); + s->acpi_mapped = true; + + memory_region_init_io(&s->pm_io, OBJECT(s), &pc87560_stub_ops, + s, "pc87560-pm", 256); +} + +static void pc87560_superio_instance_init(Object *obj) +{ + PC87560SuperioState *s = PC87560_Superio(obj); + + object_initialize_child(obj, "serial[*]", &s->serial[0], TYPE_SERIAL); + object_initialize_child(obj, "serial[*]", &s->serial[1], TYPE_SERIAL); +} + +static const VMStateDescription vmstate_pc87560_superio = { + .name = "pc87560-superio", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, PC87560SuperioState), + VMSTATE_STRUCT_ARRAY(serial, PC87560SuperioState, 2, 0, + vmstate_serial, SerialState), + VMSTATE_STRUCT(fdc, PC87560SuperioState, 0, vmstate_fdc, FDCtrl), + VMSTATE_END_OF_LIST() + }, +}; + +static const Property pc87560_superio_properties[] = { + DEFINE_PROP_CHR("serial0", PC87560SuperioState, serial[0].chr), + DEFINE_PROP_CHR("serial1", PC87560SuperioState, serial[1].chr), + DEFINE_PROP_CHR("parallel", PC87560SuperioState, pp.chr), +}; + +static void pc87560_superio_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + PCIDeviceClass *k = PCI_DEVICE_CLASS(oc); + + k->realize = pc87560_superio_realize; + k->config_write = pc87560_superio_config_write; + k->vendor_id = PCI_VENDOR_ID_NS; + k->device_id = PCI_DEVICE_ID_NS_87560_LIO; + k->class_id = 0x0680; + k->revision = 0x01; + k->subsystem_vendor_id = PCI_VENDOR_ID_HP; + k->subsystem_id = 0x10A7; + + dc->desc = "NS PC87560 SuperI/O Function 1 (I/O Peripherals)"; + dc->vmsd = &vmstate_pc87560_superio; + dc->user_creatable = false; + device_class_set_props(dc, pc87560_superio_properties); + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); +} + +static const TypeInfo pc87560_superio_info = { + .name = TYPE_PC87560_Superio, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PC87560SuperioState), + .instance_init = pc87560_superio_instance_init, + .class_init = pc87560_superio_class_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void pc87560_superio_register_types(void) +{ + type_register_static(&pc87560_superio_info); +} + +type_init(pc87560_superio_register_types) diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c index 23990115ab..5e5fe41f47 100644 --- a/hw/usb/hcd-ohci.c +++ b/hw/usb/hcd-ohci.c @@ -74,6 +74,7 @@ struct ohci_hcca { /* Flags in the head field of an Endpoint Descriptor. */ #define OHCI_ED_H 1 #define OHCI_ED_C 2 +#define OHCI_ED_HEAD_RSVD (1 << 2 | 1 << 3) /* Bitfields for the first word of a Transfer Descriptor. */ #define OHCI_TD_R (1 << 18) @@ -371,6 +372,8 @@ void ohci_hard_reset(OHCIState *ohci) ohci_soft_reset(ohci); ohci->ctl = 0; ohci_roothub_reset(ohci); + ohci->big_endian = false; + ohci->consistency_check = false; } /* Get an array of dwords from main memory */ @@ -867,6 +870,18 @@ static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len) } } +static void ohci_bswap_buf(uint8_t *buf, size_t len) +{ + uint32_t w; + while (len >= sizeof(w)) { + memcpy(&w, buf, sizeof(w)); + w = bswap32(w); + memcpy(buf, &w, sizeof(w)); + buf += sizeof(w); + len -= sizeof(w); + } +} + /* * Service a transport descriptor. * Returns nonzero to terminate processing of this endpoint. @@ -975,6 +990,9 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) DMA_DIRECTION_TO_DEVICE)) { ohci_die(ohci); } + if (ohci->big_endian) { + ohci_bswap_buf(ohci->usb_buf, pktlen); + } } } } @@ -1024,6 +1042,9 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) if (ret >= 0) { if (dir == OHCI_TD_DIR_IN) { + if (ohci->big_endian) { + ohci_bswap_buf(ohci->usb_buf, ret); + } if (ohci_copy_td(ohci, &td, ohci->usb_buf, ret, DMA_DIRECTION_FROM_DEVICE)) { ohci_die(ohci); @@ -1108,6 +1129,13 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) ohci->done_count = i; } exit_no_retire: + if (ohci->media_error) { + int cc = OHCI_BM(td.flags, TD_CC); + /* only these 2 errors will ever actually be possible in qemu */ + if (cc == OHCI_CC_DEVICENOTRESPONDING || cc == OHCI_CC_UNDEXPETEDPID) { + ohci->media_error(ohci); + } + } if (ohci_put_td(ohci, addr, &td)) { ohci_die(ohci); return 1; @@ -1136,6 +1164,13 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head) ohci_die(ohci); return 0; } + if (ohci->consistency_check && (ed.head & OHCI_ED_HEAD_RSVD)) { + if (ohci->descriptor_error) { + ohci->descriptor_error(ohci); + } + ohci_die(ohci); + return 0; + } next_ed = ed.next & OHCI_DPTR_MASK; diff --git a/hw/usb/hcd-ohci.h b/hw/usb/hcd-ohci.h index 243cd1f46a..d079f8d24e 100644 --- a/hw/usb/hcd-ohci.h +++ b/hw/usb/hcd-ohci.h @@ -93,6 +93,11 @@ struct OHCIState { bool async_complete; void (*ohci_die)(OHCIState *ohci); + void (*media_error)(OHCIState *ohci); + void (*descriptor_error)(OHCIState *ohci); + + bool big_endian; + bool consistency_check; }; #define TYPE_SYSBUS_OHCI "sysbus-ohci" diff --git a/hw/usb/meson.build b/hw/usb/meson.build index d4ba60a91c..429dcd6a24 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -45,6 +45,7 @@ system_ss.add(when: 'CONFIG_USB_STORAGE_UAS', if_true: files('dev-uas.c')) system_ss.add(when: 'CONFIG_USB_AUDIO', if_true: files('dev-audio.c')) system_ss.add(when: 'CONFIG_USB_SERIAL', if_true: files('dev-serial.c')) system_ss.add(when: 'CONFIG_USB_NETWORK', if_true: files('dev-network.c')) +system_ss.add(when: 'CONFIG_PC87560', if_true: files('pc87560-ohci.c')) if host_os != 'windows' system_ss.add(when: 'CONFIG_USB_STORAGE_MTP', if_true: files('dev-mtp.c')) endif diff --git a/hw/usb/pc87560-ohci.c b/hw/usb/pc87560-ohci.c new file mode 100644 index 0000000000..cc00f6e936 --- /dev/null +++ b/hw/usb/pc87560-ohci.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NS PC87560 SuperI/O — USB OHCI emulation + * + * Copyright (c) 2026 Abizer abizerlokhandwalastd10@gmail.com + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/timer.h" +#include "hw/usb/usb.h" +#include "migration/vmstate.h" +#include "hw/pci/pci_device.h" +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "trace.h" +#include "hcd-ohci.h" +#include "qom/object.h" + +#define TYPE_NSC_PCI_OHCI "pc87560-ohci" +OBJECT_DECLARE_SIMPLE_TYPE(OHCIPCIState, NSC_PCI_OHCI) + +struct OHCIPCIState { + + PCIDevice parent_obj; + OHCIState state; + MemoryRegion nsc_bar; + char *masterbus; + uint32_t num_ports; + uint32_t firstport; + qemu_irq irq_out[1]; + + /* NSC (NUSSBAR) vendor-specific register state */ + uint32_t nsc_control; + uint32_t nsc_status; +}; + +#define NSC_CONTROL 0x00 +#define NSC_COMMAND_STATUS 0x04 +#define NSC_PORT_RX_STATUS 0x08 +#define NSC_ROOT_HUB_STATUS 0x34 +#define NSC_CTL_BIG_ENDIAN (1U << 0) +#define NSC_CTL_CCE (1U << 1) /* Consistency Check Enable */ +#define NSC_CTL_WORDCOUNT (0x3fU << 2) /* bits 7:2 */ +#define NSC_CTL_MODE (0x3U << 8) /* bits 9:8 */ +#define NSC_CTL_LIMIT_CONTROL (1U << 16) +#define NSC_CTL_RISE_TIME (0xffU << 24) /* bits 31:24 */ +#define NSC_CTL_WRMASK (NSC_CTL_BIG_ENDIAN | NSC_CTL_CCE | NSC_CTL_WORDCOUNT |\ + NSC_CTL_MODE | NSC_CTL_LIMIT_CONTROL | \ + NSC_CTL_RISE_TIME) +#define NSC_CONTROL_RESET 0x90000014U +#define NSC_CS_MEDIA_ERROR 0x00000fffU /* bits 11:0, 12-bit counter */ +#define NSC_CS_MEDIA_ERR_OVFL (1U << 16) +#define NSC_CS_UE_PCI (1U << 17) +#define NSC_CS_UE_DESCRIPTOR (1U << 18) +#define NSC_CS_UE_INTERNAL (1U << 19) +#define NSC_CS_SCHEDULE_ERROR (1U << 20) +#define NSC_CS_PHY_OVERRUN (1U << 21) +#define NSC_CS_W1C_MASK (NSC_CS_MEDIA_ERR_OVFL | NSC_CS_UE_PCI |\ + NSC_CS_UE_DESCRIPTOR | NSC_CS_UE_INTERNAL |\ + NSC_CS_SCHEDULE_ERROR | NSC_CS_PHY_OVERRUN) +#define NSC_OHCI_INTR_SO (1U << 0) +#define NSC_RHPORT_CCS (1U << 0) /* CurrentConnectStatus */ +#define NSC_RHPORT_PES (1U << 1) /* PortEnableStatus */ +#define NSC_RHPORT_PSS (1U << 2) /* PortSuspendStatus */ +#define NSC_RHPORT_PRS (1U << 4) /* PortResetStatus */ +#define NSC_RHPORT_PPS (1U << 8) /* PortPowerStatus */ + +static uint32_t nsc_hub_port_state(OHCIState *s, int port) +{ + uint32_t ctrl = s->rhport[port].ctrl; + + if (!(ctrl & NSC_RHPORT_PPS)) { + return 0x8; /* POWER OFF */ + } + if (!(ctrl & NSC_RHPORT_CCS)) { + return 0xa; /* DISCONNECTED */ + } + if (ctrl & NSC_RHPORT_PRS) { + return 0x2; /* RESET */ + } + if (ctrl & NSC_RHPORT_PSS) { + return 0xc; /* SUSPENDED */ + } + if (ctrl & NSC_RHPORT_PES) { + return 0xe; /* ENABLED, Idle */ + } + return 0xb; /* DISABLED */ +} + +static uint32_t nsc_root_hub_status(OHCIPCIState *ohci) +{ + OHCIState *s = &ohci->state; + uint32_t val = 0; + + if (s->num_ports >= 1) { + val |= nsc_hub_port_state(s, 0); /* Port 1 Status[3:0] */ + } + if (s->num_ports >= 2) { + val |= nsc_hub_port_state(s, 1) << 8; /* Port 2 Status[3:0] */ + } + return val; +} + +static void nsc_ohci_media_error(OHCIState *ohci) +{ + OHCIPCIState *dev = container_of(ohci, OHCIPCIState, state); + uint32_t count = dev->nsc_status & NSC_CS_MEDIA_ERROR; + + if (count == NSC_CS_MEDIA_ERROR) { + dev->nsc_status |= NSC_CS_MEDIA_ERR_OVFL; + } else { + dev->nsc_status = (dev->nsc_status & ~NSC_CS_MEDIA_ERROR) | (count + 1); + } +} + +static void nsc_ohci_descriptor_error(OHCIState *ohci) +{ + OHCIPCIState *dev = container_of(ohci, OHCIPCIState, state); + dev->nsc_status |= NSC_CS_UE_DESCRIPTOR; +} + +static uint64_t nsc_usb_read(void *opaque, hwaddr addr, unsigned size) +{ + OHCIPCIState *ohci = opaque; + uint32_t val; + + switch (addr & ~3) { + case NSC_CONTROL: + val = ohci->nsc_control; + break; + case NSC_COMMAND_STATUS: + val = ohci->nsc_status; + if (ohci->state.intr_status & NSC_OHCI_INTR_SO) { + val |= NSC_CS_SCHEDULE_ERROR; + } + break; + case NSC_PORT_RX_STATUS: + val = 0; + break; + case NSC_ROOT_HUB_STATUS: + val = nsc_root_hub_status(ohci); + break; + default: + val = 0; + break; + } + + val >>= (addr & 3) * 8; + if (size < 4) { + val &= (1U << (size * 8)) - 1; + } + return val; +} + +static void pc87560_usb_irq_relay(void *opaque, int n, int level) +{ + OHCIPCIState *ohci = opaque; + qemu_set_irq(ohci->irq_out[0], level); +} + +static void nsc_usb_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + OHCIPCIState *ohci = opaque; + uint32_t shift = (addr & 3) * 8; + uint32_t lane = (size < 4) ? (((1U << (size * 8)) - 1) << shift) + : 0xffffffffU; + uint32_t wval = ((uint32_t)val << shift) & lane; + + switch (addr & ~3) { + case NSC_CONTROL: + ohci->nsc_control = (ohci->nsc_control & ~lane) | + (wval & NSC_CTL_WRMASK); + ohci->state.big_endian = (ohci->nsc_control & NSC_CTL_BIG_ENDIAN) != 0; + ohci->state.consistency_check = (ohci->nsc_control & NSC_CTL_CCE) != 0; + break; + + case NSC_COMMAND_STATUS: + if (wval & NSC_CS_MEDIA_ERROR) { + ohci->nsc_status &= ~NSC_CS_MEDIA_ERROR; + } + ohci->nsc_status &= ~(wval & NSC_CS_W1C_MASK); + if (wval & NSC_CS_SCHEDULE_ERROR) { + ohci->state.intr_status &= ~NSC_OHCI_INTR_SO; + } + break; + + case NSC_PORT_RX_STATUS: + case NSC_ROOT_HUB_STATUS: + break; + + default: + break; + } +} + +static const MemoryRegionOps nsc_usb_ops = { + .read = nsc_usb_read, + .write = nsc_usb_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void ohci_pci_die(struct OHCIState *ohci) +{ + OHCIPCIState *dev = container_of(ohci, OHCIPCIState, state); + + ohci_sysbus_die(ohci); + + dev->nsc_status |= NSC_CS_UE_INTERNAL; + + pci_set_word(dev->parent_obj.config + PCI_STATUS, + PCI_STATUS_DETECTED_PARITY); +} + +static uint32_t pc87560_ohci_config_read(PCIDevice *pci, uint32_t addr, int len) +{ + uint32_t val = pci_default_read_config(pci, addr, len); + return val; +} + +static void pc87560_ohci_config_write(PCIDevice *pci, uint32_t addr, + uint32_t val, int len) +{ + pci_default_write_config(pci, addr, val, len); +} + +static void usb_ohci_realize_pci(PCIDevice *dev, Error **errp) +{ + Error *err = NULL; + OHCIPCIState *ohci = NSC_PCI_OHCI(dev); + + dev->config[PCI_CLASS_PROG] = 0x10; + dev->wmask[PCI_CLASS_PROG] = 0xff; + dev->config[PCI_INTERRUPT_PIN] = 0x01; + + qdev_init_gpio_out(DEVICE(dev), ohci->irq_out, 1); + ohci->state.irq = qemu_allocate_irq(pc87560_usb_irq_relay, ohci, 0); + + usb_ohci_init(&ohci->state, DEVICE(dev), ohci->num_ports, 0, + ohci->masterbus, ohci->firstport, + pci_get_address_space(dev), ohci_pci_die, &err); + if (err) { + error_propagate(errp, err); + return; + } + ohci->state.media_error = nsc_ohci_media_error; + ohci->state.descriptor_error = nsc_ohci_descriptor_error; + + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &ohci->state.mem); + + memory_region_init_io(&ohci->nsc_bar, OBJECT(dev), &nsc_usb_ops, ohci, + "nsc-usb", 0x1000); + pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &ohci->nsc_bar); +} + +static void usb_ohci_exit(PCIDevice *dev) +{ + OHCIPCIState *ohci = NSC_PCI_OHCI(dev); + OHCIState *s = &ohci->state; + + trace_usb_ohci_exit(s->name); + ohci_bus_stop(s); + + if (s->async_td) { + usb_cancel_packet(&s->usb_packet); + s->async_td = 0; + } + ohci_stop_endpoints(s); + + if (!ohci->masterbus) { + usb_bus_release(&s->bus); + } + + timer_free(s->eof_timer); +} + +static void usb_ohci_reset_pci(DeviceState *d) +{ + PCIDevice *dev = PCI_DEVICE(d); + OHCIPCIState *ohci = NSC_PCI_OHCI(dev); + OHCIState *s = &ohci->state; + + ohci_hard_reset(s); + + ohci->nsc_control = NSC_CONTROL_RESET; + ohci->nsc_status = 0; +} + +static const Property ohci_pci_properties[] = { + DEFINE_PROP_STRING("masterbus", OHCIPCIState, masterbus), + DEFINE_PROP_UINT32("num-ports", OHCIPCIState, num_ports, 3), + DEFINE_PROP_UINT32("firstport", OHCIPCIState, firstport, 0), +}; + +static const VMStateDescription vmstate_ohci = { + .name = "pc87560-ohci", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, OHCIPCIState), + VMSTATE_STRUCT(state, OHCIPCIState, 1, vmstate_ohci_state, OHCIState), + VMSTATE_UINT32(nsc_control, OHCIPCIState), + VMSTATE_UINT32(nsc_status, OHCIPCIState), + VMSTATE_END_OF_LIST() + } +}; + +static void ohci_pci_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = usb_ohci_realize_pci; + k->exit = usb_ohci_exit; + k->config_read = pc87560_ohci_config_read; + k->config_write = pc87560_ohci_config_write; + k->vendor_id = PCI_VENDOR_ID_NS; + k->device_id = PCI_DEVICE_ID_NS_87560_USB; + k->revision = 0x01; + k->class_id = PCI_CLASS_SERIAL_USB; + k->subsystem_vendor_id = PCI_VENDOR_ID_HP; + k->subsystem_id = 0x10A7; + + set_bit(DEVICE_CATEGORY_USB, dc->categories); + dc->desc = "National Semiconductor USB Controller"; + device_class_set_props(dc, ohci_pci_properties); + dc->hotpluggable = false; + dc->user_creatable = false; + dc->vmsd = &vmstate_ohci; + device_class_set_legacy_reset(dc, usb_ohci_reset_pci); +} + +static const TypeInfo ohci_pci_info = { + .name = TYPE_NSC_PCI_OHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(OHCIPCIState), + .class_init = ohci_pci_class_init, + .interfaces = (const InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void ohci_pci_register_types(void) +{ + type_register_static(&ohci_pci_info); +} + +type_init(ohci_pci_register_types) diff --git a/include/hw/isa/pc87560.h b/include/hw/isa/pc87560.h new file mode 100644 index 0000000000..72e317cc7b --- /dev/null +++ b/include/hw/isa/pc87560.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef HW_ISA_PC87560_H +#define HW_ISA_PC87560_H + +#define TYPE_PC87560_Superio "pc87560-superio" +OBJECT_DECLARE_SIMPLE_TYPE(PC87560SuperioState, PC87560_Superio) + +#define REG_FXBAR0 0x40 +#define REG_FXBAR1 0x44 +#define REG_FXBAR2 0x48 +#define REG_DDMA_BAR 0x4C +#define REG_FIR_BAR 0x50 +#define REG_SYSCFG 0x58 +#define REG_FUNCEN 0x5A +#define REG_SIOCFG 0x5C +#define REG_SPECFEN 0x5D +#define REG_PPDID 0x5E +#define REG_PPMODE 0x5F +#define REG_DMA_ROUTE4 0x61 +#define REG_ROMCFG 0x62 +#define REG_DMA_ROUTE1 0x63 +#define REG_DMA_ROUTE2 0x64 +#define REG_DMA_ROUTE3 0x65 +#define REG_DMA_CHAN 0x66 +#define REG_IRQ_TRIG 0x67 +#define REG_IRQ_ROUTE1 0x69 +#define REG_ARBCTL 0x72 +#define REG_FX_MASK2 0x74 +#define REG_FX_TIMING 0x76 +#define REG_FXCTL 0x78 +#define REG_GPIO_CFG 0x79 +#define REG_GPIO_DIR 0x7A +#define REG_SIRQ_CTL 0x7B +#define REG_SIRQ_EN 0x7C +#define REG_RSVD_CFG 0x7E +#define REG_AUDIOCS_EN 0x7F +#define REG_FXMEM_CTL1 0x80 +#define REG_KBCBAR 0x84 +#define REG_ACPIBAR 0x88 +#define REG_PMBAR 0x8C +#define REG_FDCBAR 0x90 +#define REG_SP1BAR 0x94 +#define REG_SP2BAR 0x98 +#define REG_PPBAR 0x9C +#define REG_TEST_CTL 0xFF + +#define FUNCEN_FDC (1 << 0) +#define FUNCEN_SP1 (1 << 1) +#define FUNCEN_SP2 (1 << 3) +#define FUNCEN_PP (1 << 5) +#define FUNCEN_KBC (1 << 13) + +#define FUNCEN_DEFAULT 0xFFBF + +#define IC_PIC1 0x20 + +typedef struct { + uint8_t imr; /* interrupt mask register (OCW1) */ + uint8_t irr; /* interrupt request register */ + uint8_t isr; /* in-service register */ + bool poll_mode; /* OCW3 POLL bit set */ + bool read_isr; /* OCW3: true=ISR, false=IRR */ + int init_phase;/* 0=normal, 1-3=ICW2/3/4 expected */ + int priority_base; + int irq_level; +} Pic8259; + +struct PC87560SuperioState { + PCIDevice parent_obj; + + qemu_irq parent_irq; + + SerialState serial[2]; + ParallelState pp; + FDCtrl fdc; + + MemoryRegion fdc_io; + MemoryRegion sp1_io; + MemoryRegion sp2_io; + MemoryRegion pp_io; + MemoryRegion kbc_io; + MemoryRegion acpi_io; + MemoryRegion pm_io; + MemoryRegion pic1_io; + + bool fdc_mapped; + bool sp1_mapped; + bool sp2_mapped; + bool pp_mapped; + bool kbc_mapped; + bool acpi_mapped; + bool pm_mapped; + Pic8259 pic; +}; + +#endif diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h index 16034aaa2c..477609ed62 100644 --- a/include/hw/pci/pci_ids.h +++ b/include/hw/pci/pci_ids.h @@ -168,6 +168,11 @@ #define PCI_DEVICE_ID_LSI_SAS1078 0x0060 #define PCI_DEVICE_ID_LSI_SAS0079 0x0079 +#define PCI_VENDOR_ID_NS 0x100b +#define PCI_DEVICE_ID_NS_87415 0x0002 +#define PCI_DEVICE_ID_NS_87560_LIO 0x000e +#define PCI_DEVICE_ID_NS_87560_USB 0x0012 + #define PCI_VENDOR_ID_DEC 0x1011 #define PCI_DEVICE_ID_DEC_21143 0x0019