/* * QEMU Xbox PCI buses implementation * * Copyright (c) 2012 espes * Copyright (c) 2018-2021 Matt Borgerson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include "qemu/osdep.h" #include "hw/hw.h" #include "hw/i386/pc.h" #include "hw/pci/pci.h" #include "hw/pci/pci_host.h" #include "hw/isa/isa.h" #include "hw/sysbus.h" #include "qapi/error.h" #include "qemu/range.h" #include "hw/xen/xen.h" #include "hw/pci-host/pam.h" #include "sysemu/sysemu.h" #include "hw/i386/ioapic.h" #include "qapi/visitor.h" #include "qemu/error-report.h" #include "hw/loader.h" #include "qemu/config-file.h" #include "hw/pci/pci_bus.h" #include "hw/pci/pci_bridge.h" #include "exec/address-spaces.h" #include "qemu-common.h" #include "qemu/option.h" #include "hw/xbox/acpi_xbox.h" #include "hw/xbox/amd_smbus.h" #include "hw/xbox/xbox_pci.h" #include "hw/irq.h" #include "migration/vmstate.h" /* * xbox chipset based on nForce 420, which was based on AMD-760 * * http://support.amd.com/us/ChipsetMotherboard_TechDocs/24494.pdf * http://support.amd.com/us/ChipsetMotherboard_TechDocs/24416.pdf * http://support.amd.com/us/ChipsetMotherboard_TechDocs/24467.pdf * * http://support.amd.com/us/ChipsetMotherboard_TechDocs/24462.pdf * * - 'NV2A' combination northbridge/gpu * - 'MCPX' combination southbridge/apu */ //#define DEBUG #ifdef DEBUG # define XBOXPCI_DPRINTF(format, ...) printf(format, ## __VA_ARGS__) #else # define XBOXPCI_DPRINTF(format, ...) do { } while (0) #endif #define XBOX_NUM_INT_IRQS 8 #define XBOX_NUM_PIRQS 4 #define XBOX_NUM_PIC_IRQS 16 #define XBOX_LPC_ACPI_IRQ_ROUT 0x64 #define XBOX_LPC_PIRQ_ROUT 0x68 #define XBOX_LPC_INT_IRQ_ROUT 0x6C static void xbox_lpc_set_irq(void *opaque, int pirq, int level) { XBOX_LPCState *lpc = opaque; assert(pirq >= 0); assert(pirq < XBOX_NUM_INT_IRQS + XBOX_NUM_PIRQS); int pic_irq = 0; if (pirq < XBOX_NUM_INT_IRQS) { /* devices on the internal bus */ uint32_t routing = pci_get_long(lpc->dev.config + XBOX_LPC_INT_IRQ_ROUT); pic_irq = (routing >> (pirq * 4)) & 0xF; if (pic_irq == 0) { return; } } else { /* pirqs */ pirq -= XBOX_NUM_INT_IRQS; pic_irq = lpc->dev.config[XBOX_LPC_PIRQ_ROUT + pirq]; } if (pic_irq >= XBOX_NUM_PIC_IRQS) { return; } qemu_set_irq(lpc->pic[pic_irq], level); } static int xbox_lpc_map_irq(PCIDevice *pci_dev, int intx) { int slot = PCI_SLOT(pci_dev->devfn); switch (slot) { /* devices on the internal bus */ case 2: return 0; /* usb0 */ case 3: return 1; /* usb1 */ case 4: return 2; /* nic */ case 5: return 3; /* apu */ case 6: return 4; /* aci */ case 9: return 6; /* ide */ case 30: /* agp bridge -> PIRQC? */ return XBOX_NUM_INT_IRQS + 2; default: /* don't actually know how this should work */ assert(false); return XBOX_NUM_INT_IRQS + ((slot + intx) & 3); } } static void xbox_lpc_set_acpi_irq(void *opaque, int irq_num, int level) { XBOX_LPCState *lpc = opaque; assert(irq_num == 0 || irq_num == 1); uint32_t routing = pci_get_long(lpc->dev.config + XBOX_LPC_ACPI_IRQ_ROUT); int irq = (routing >> (irq_num * 8)) & 0xff; if (irq == 0 || irq >= XBOX_NUM_PIC_IRQS) { return; } qemu_set_irq(lpc->pic[irq], level); } void xbox_pci_init(qemu_irq *pic, MemoryRegion *address_space_mem, MemoryRegion *address_space_io, MemoryRegion *pci_memory, MemoryRegion *ram_memory, MemoryRegion *rom_memory, PCIBus **out_host_bus, ISABus **out_isa_bus, I2CBus **out_smbus, PCIBus **out_agp_bus) { DeviceState *host; PCIHostState *host_state; PCIBus *host_bus; PCIDevice *bridge; XBOX_PCIState *bridge_state; /* pci host bus */ host = qdev_new("xbox-pcihost"); host_state = PCI_HOST_BRIDGE(host); host_bus = pci_root_bus_new(host, NULL, pci_memory, address_space_io, 0, TYPE_PCI_BUS); host_state->bus = host_bus; sysbus_realize_and_unref(SYS_BUS_DEVICE(host), &error_fatal); bridge = pci_create_simple_multifunction(host_bus, PCI_DEVFN(0, 0), true, "xbox-pci"); bridge_state = XBOX_PCI_DEVICE(bridge); bridge_state->ram_memory = ram_memory; bridge_state->pci_address_space = pci_memory; bridge_state->system_memory = address_space_mem; /* PCI hole */ /* TODO: move to xbox-pci init */ memory_region_init_alias(&bridge_state->pci_hole, OBJECT(bridge), "pci-hole", bridge_state->pci_address_space, memory_region_size(ram_memory), 0x100000000ULL - memory_region_size(ram_memory)); memory_region_add_subregion(bridge_state->system_memory, memory_region_size(ram_memory), &bridge_state->pci_hole); /* lpc bridge */ PCIDevice *lpc = pci_create_simple_multifunction(host_bus, PCI_DEVFN(1, 0), true, "xbox-lpc"); XBOX_LPCState *lpc_state = XBOX_LPC_DEVICE(lpc); lpc_state->pic = pic; lpc_state->rom_memory = rom_memory; pci_bus_irqs(host_bus, xbox_lpc_set_irq, xbox_lpc_map_irq, lpc_state, XBOX_NUM_INT_IRQS + XBOX_NUM_PIRQS); qemu_irq *acpi_irq = qemu_allocate_irqs(xbox_lpc_set_acpi_irq, lpc_state, 2); xbox_pm_init(lpc, &lpc_state->pm, acpi_irq[0]); //xbox_lpc_reset(&s->dev.qdev); /* smbus */ PCIDevice *smbus = pci_create_simple_multifunction(host_bus, PCI_DEVFN(1, 1), true, "xbox-smbus"); XBOX_SMBState *smbus_state = XBOX_SMBUS_DEVICE(smbus); amd756_smbus_init(&smbus->qdev, &smbus_state->smb, acpi_irq[1]); /* AGP bus */ PCIDevice *agp = pci_create_simple(host_bus, PCI_DEVFN(30, 0), "xbox-agp"); //qdev = &br->dev.qdev; //qdev_init_nofail(qdev); PCIBus *agp_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(agp)); *out_host_bus = host_bus; *out_isa_bus = lpc_state->isa_bus; *out_smbus = smbus_state->smb.smbus; *out_agp_bus = agp_bus; } #define XBOX_SMBUS_BASE_BAR 1 static void xbox_smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val, unsigned size) { XBOX_SMBState *s = opaque; uint64_t offset = addr - s->dev.io_regions[XBOX_SMBUS_BASE_BAR].addr; amd756_smb_ioport_writeb(&s->smb, offset, val); } static uint64_t xbox_smb_ioport_readb(void *opaque, hwaddr addr, unsigned size) { XBOX_SMBState *s = opaque; uint64_t offset = addr - s->dev.io_regions[XBOX_SMBUS_BASE_BAR].addr; return amd756_smb_ioport_readb(&s->smb, offset); } static const MemoryRegionOps xbox_smbus_ops = { .read = xbox_smb_ioport_readb, .write = xbox_smb_ioport_writeb, .endianness = DEVICE_LITTLE_ENDIAN, .impl = { .min_access_size = 1, .max_access_size = 1, }, }; static void xbox_smbus_realize(PCIDevice *dev, Error **errp) { XBOX_SMBState *s = XBOX_SMBUS_DEVICE(dev); memory_region_init_io(&s->smb_bar, OBJECT(dev), &xbox_smbus_ops, s, "xbox-smbus-bar", 32); pci_register_bar(dev, XBOX_SMBUS_BASE_BAR, PCI_BASE_ADDRESS_SPACE_IO, &s->smb_bar); } static const VMStateDescription vmstate_xbox_smbus = { .name = "xbox-smbus", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_PCI_DEVICE(dev, XBOX_SMBState), // FIXME VMSTATE_END_OF_LIST() }, }; static void xbox_smbus_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); k->realize = xbox_smbus_realize; k->vendor_id = PCI_VENDOR_ID_NVIDIA; k->device_id = PCI_DEVICE_ID_NVIDIA_NFORCE_SMBUS; k->revision = 177; k->class_id = PCI_CLASS_SERIAL_SMBUS; dc->desc = "nForce PCI System Management"; dc->user_creatable = false; dc->vmsd = &vmstate_xbox_smbus; } static const TypeInfo xbox_smbus_info = { .name = "xbox-smbus", .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(XBOX_SMBState), .class_init = xbox_smbus_class_init, .interfaces = (InterfaceInfo[]) { { INTERFACE_CONVENTIONAL_PCI_DEVICE }, { }, }, }; static void xbox_lpc_realize(PCIDevice *dev, Error **errp) { XBOX_LPCState *d = XBOX_LPC_DEVICE(dev); ISABus *isa_bus; isa_bus = isa_bus_new(DEVICE(d), get_system_memory(), pci_address_space_io(dev), errp); if (isa_bus == NULL) { return; } d->isa_bus = isa_bus; } static void xbox_lpc_enable_mcpx_rom(PCIDevice *dev, bool enable) { XBOX_LPCState *s = XBOX_LPC_DEVICE(dev); MemoryRegion *subregion; QTAILQ_FOREACH(subregion, &s->rom_memory->subregions, subregions_link) { if (subregion->name != NULL && strcmp(subregion->name, "xbox.mcpx") == 0) { memory_region_set_enabled(subregion, enable); break; } } } static void xbox_lpc_reset(DeviceState *dev) { XBOXPCI_DPRINTF("ACTIVATING BOOT ROM\n"); xbox_lpc_enable_mcpx_rom(PCI_DEVICE(dev), true); } static void xbox_lpc_config_write(PCIDevice *dev, uint32_t addr, uint32_t val, int len) { pci_default_write_config(dev, addr, val, len); if ((addr == 0x80) && (val & 2)) { XBOXPCI_DPRINTF("DEACTIVATING BOOT ROM\n"); xbox_lpc_enable_mcpx_rom(dev, false); } XBOXPCI_DPRINTF("%s: %x %x %d\n", __func__, addr, val, len); } #if 0 /* Xbox 1.1 uses a config register instead of a bar to set the pm base address */ #define XBOX_LPC_PMBASE 0x84 #define XBOX_LPC_PMBASE_ADDRESS_MASK 0xff00 #define XBOX_LPC_PMBASE_DEFAULT 0x1 static void xbox_lpc_pmbase_update(XBOX_LPCState *s) { uint32_t pm_io_base = pci_get_long(s->dev.config + XBOX_LPC_PMBASE); pm_io_base &= XBOX_LPC_PMBASE_ADDRESS_MASK; xbox_pm_iospace_update(&s->pm, pm_io_base); } static void xbox_lpc_reset(DeviceState *dev) { PCIDevice *d = PCI_DEVICE(dev); XBOX_LPCState *s = XBOX_LPC_DEVICE(d); pci_set_long(s->dev.config + XBOX_LPC_PMBASE, XBOX_LPC_PMBASE_DEFAULT); xbox_lpc_pmbase_update(s); } static void xbox_lpc_config_write(PCIDevice *dev, uint32_t addr, uint32_t val, int len) { XBOX_LPCState *s = XBOX_LPC_DEVICE(dev); pci_default_write_config(dev, addr, val, len); if (ranges_overlap(addr, len, XBOX_LPC_PMBASE, 2)) { xbox_lpc_pmbase_update(s); } } static int xbox_lpc_post_load(void *opaque, int version_id) { XBOX_LPCState *s = opaque; xbox_lpc_pmbase_update(s); return 0; } static const VMStateDescription vmstate_xbox_lpc = { .name = "XBOX LPC", .version_id = 1, .post_load = xbox_lpc_post_load, }; #endif static void xbox_send_gpe(AcpiDeviceIf *adev, AcpiEventStatusBits ev) { XBOX_LPCState *s = XBOX_LPC_DEVICE(adev); acpi_send_gpe_event(&s->pm.acpi_regs, s->pm.irq, ev); } static const VMStateDescription vmstate_xbox_lpc = { .name = "xbox-lpc", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_PCI_DEVICE(dev, XBOX_LPCState), VMSTATE_STRUCT(pm, XBOX_LPCState, 0, vmstate_xbox_pm, XBOX_PMRegs), VMSTATE_END_OF_LIST() }, }; static void xbox_lpc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(klass); dc->hotpluggable = false; k->realize = xbox_lpc_realize; k->config_write = xbox_lpc_config_write; k->vendor_id = PCI_VENDOR_ID_NVIDIA; k->device_id = PCI_DEVICE_ID_NVIDIA_NFORCE_LPC; /* FIXME - correct revision for this is 0xB2 (178) for retail 1.0 Xbox, causes known USB bug */ k->revision = 212; k->class_id = PCI_CLASS_BRIDGE_ISA; dc->desc = "nForce LPC Bridge"; dc->user_creatable = false; dc->reset = xbox_lpc_reset; dc->vmsd = &vmstate_xbox_lpc; adevc->send_event = xbox_send_gpe; } static const TypeInfo xbox_lpc_info = { .name = "xbox-lpc", .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(XBOX_LPCState), .class_init = xbox_lpc_class_init, .interfaces = (InterfaceInfo[]) { { TYPE_ACPI_DEVICE_IF }, { INTERFACE_CONVENTIONAL_PCI_DEVICE }, { }, }, }; static void xbox_agp_realize(PCIDevice *d, Error **errp) { pci_set_word(d->config + PCI_PREF_MEMORY_BASE, PCI_PREF_RANGE_TYPE_32); pci_set_word(d->config + PCI_PREF_MEMORY_LIMIT, PCI_PREF_RANGE_TYPE_32); pci_bridge_initfn(d, TYPE_PCI_BUS); } static void xbox_agp_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); k->realize = xbox_agp_realize; k->exit = pci_bridge_exitfn; k->config_write = pci_bridge_write_config; k->is_bridge = 1; k->vendor_id = PCI_VENDOR_ID_NVIDIA; k->device_id = PCI_DEVICE_ID_NVIDIA_NFORCE_AGP; k->revision = 161; dc->desc = "nForce AGP to PCI Bridge"; dc->vmsd = &vmstate_pci_device; dc->reset = pci_bridge_reset; } static const TypeInfo xbox_agp_info = { .name = "xbox-agp", .parent = TYPE_PCI_BRIDGE, .instance_size = sizeof(PCIBridge), .class_init = xbox_agp_class_init, .interfaces = (InterfaceInfo[]) { { INTERFACE_CONVENTIONAL_PCI_DEVICE }, { }, }, }; static void xbox_pci_realize(PCIDevice *d, Error **errp) { //XBOX_PCIState *s = DO_UPCAST(XBOX_PCIState, dev, dev); } static const VMStateDescription pci_bridge_dev_vmstate = { .name = "xbox-pci", .fields = (VMStateField[]) { VMSTATE_PCI_DEVICE(parent_obj, PCIBridge), VMSTATE_END_OF_LIST() } }; static void xbox_pci_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); dc->hotpluggable = false; k->realize = xbox_pci_realize; //k->config_write = xbox_pci_write_config; k->vendor_id = PCI_VENDOR_ID_NVIDIA; k->device_id = PCI_DEVICE_ID_NVIDIA_XBOX_PCHB; k->revision = 161; k->class_id = PCI_CLASS_BRIDGE_HOST; dc->desc = "Xbox PCI Host"; dc->user_creatable = false; dc->vmsd = &pci_bridge_dev_vmstate; } static const TypeInfo xbox_pci_info = { .name = "xbox-pci", .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(XBOX_PCIState), .class_init = xbox_pci_class_init, .interfaces = (InterfaceInfo[]) { { INTERFACE_CONVENTIONAL_PCI_DEVICE }, { }, }, }; #define CONFIG_ADDR 0xcf8 #define CONFIG_DATA 0xcfc static void xbox_pcihost_realize(DeviceState *dev, Error **errp) { PCIHostState *s = PCI_HOST_BRIDGE(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); memory_region_init_io(&s->conf_mem, OBJECT(dev), &pci_host_conf_le_ops, s, "pci-conf-idx", 4); sysbus_add_io(sbd, CONFIG_ADDR, &s->conf_mem); sysbus_init_ioports(&s->busdev, CONFIG_ADDR, 4); memory_region_init_io(&s->data_mem, OBJECT(dev), &pci_host_data_le_ops, s, "pci-conf-data", 4); sysbus_add_io(sbd, CONFIG_DATA, &s->data_mem); sysbus_init_ioports(&s->busdev, CONFIG_DATA, 4); } static void xbox_pcihost_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = xbox_pcihost_realize; dc->user_creatable = false; } static const TypeInfo xbox_pcihost_info = { .name = "xbox-pcihost", .parent = TYPE_PCI_HOST_BRIDGE, .instance_size = sizeof(PCIHostState), .class_init = xbox_pcihost_class_init, }; static void xboxpci_register_types(void) { type_register(&xbox_pcihost_info); type_register(&xbox_pci_info); type_register(&xbox_agp_info); type_register(&xbox_lpc_info); type_register(&xbox_smbus_info); } type_init(xboxpci_register_types)