acpi-build: enable hotplug for PCI bridges

This enables support for device hotplug behind
pci bridges. Bridge devices themselves need
to be pre-configured on qemu command line.

Design:
    - at machine init time, assign "bsel" property to bridges with
      hotplug support
    - dynamically (At ACPI table read) generate ACPI code to handle
      hotplug events for each bridge with "bsel" property

Note: ACPI doesn't support adding or removing bridges by hotplug.
We detect and prevent removal of bridges by hotplug,
unless they were added by hotplug previously
(and so, are not described by ACPI).

Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
Michael S. Tsirkin 2013-10-14 18:01:29 +03:00
parent 9e047b9824
commit 99fd437dee
3 changed files with 298 additions and 95 deletions

View File

@ -40,6 +40,7 @@
/* Supported chipsets: */ /* Supported chipsets: */
#include "hw/acpi/piix4.h" #include "hw/acpi/piix4.h"
#include "hw/acpi/pcihp.h"
#include "hw/i386/ich9.h" #include "hw/i386/ich9.h"
#include "hw/pci/pci_bus.h" #include "hw/pci/pci_bus.h"
#include "hw/pci-host/q35.h" #include "hw/pci-host/q35.h"
@ -79,6 +80,12 @@ typedef struct AcpiMiscInfo {
uint16_t pvpanic_port; uint16_t pvpanic_port;
} AcpiMiscInfo; } AcpiMiscInfo;
typedef struct AcpiBuildPciBusHotplugState {
GArray *device_table;
GArray *notify_table;
struct AcpiBuildPciBusHotplugState *parent;
} AcpiBuildPciBusHotplugState;
static void acpi_get_dsdt(AcpiMiscInfo *info) static void acpi_get_dsdt(AcpiMiscInfo *info)
{ {
uint16_t *applesmc_sta; uint16_t *applesmc_sta;
@ -179,38 +186,6 @@ static void acpi_get_pm_info(AcpiPmInfo *pm)
NULL); NULL);
} }
static void acpi_get_hotplug_info(AcpiMiscInfo *misc)
{
int i;
PCIBus *bus = find_i440fx();
if (!bus) {
/* Only PIIX supports ACPI hotplug */
memset(misc->slot_hotplug_enable, 0, sizeof misc->slot_hotplug_enable);
return;
}
memset(misc->slot_hotplug_enable, 0xff,
DIV_ROUND_UP(PCI_SLOT_MAX, BITS_PER_BYTE));
for (i = 0; i < ARRAY_SIZE(bus->devices); ++i) {
PCIDeviceClass *pc;
PCIDevice *pdev = bus->devices[i];
if (!pdev) {
continue;
}
pc = PCI_DEVICE_GET_CLASS(pdev);
if (pc->no_hotplug) {
int slot = PCI_SLOT(i);
clear_bit(slot, misc->slot_hotplug_enable);
}
}
}
static void acpi_get_misc_info(AcpiMiscInfo *info) static void acpi_get_misc_info(AcpiMiscInfo *info)
{ {
info->has_hpet = hpet_find(); info->has_hpet = hpet_find();
@ -376,6 +351,12 @@ static void build_package(GArray *package, uint8_t op, unsigned min_bytes)
build_prepend_byte(package, op); build_prepend_byte(package, op);
} }
static void build_extop_package(GArray *package, uint8_t op)
{
build_package(package, op, 1);
build_prepend_byte(package, 0x5B); /* ExtOpPrefix */
}
static void build_append_value(GArray *table, uint32_t value, int size) static void build_append_value(GArray *table, uint32_t value, int size)
{ {
uint8_t prefix; uint8_t prefix;
@ -402,8 +383,44 @@ static void build_append_value(GArray *table, uint32_t value, int size)
} }
} }
static void build_append_notify_target(GArray *method, GArray *target_name, static void build_append_int(GArray *table, uint32_t value)
uint32_t value, int size) {
if (value == 0x00) {
build_append_byte(table, 0x00); /* ZeroOp */
} else if (value == 0x01) {
build_append_byte(table, 0x01); /* OneOp */
} else if (value <= 0xFF) {
build_append_value(table, value, 1);
} else if (value <= 0xFFFFF) {
build_append_value(table, value, 2);
} else {
build_append_value(table, value, 4);
}
}
static GArray *build_alloc_method(const char *name, uint8_t arg_count)
{
GArray *method = build_alloc_array();
build_append_nameseg(method, "%s", name);
build_append_byte(method, arg_count); /* MethodFlags: ArgCount */
return method;
}
static void build_append_and_cleanup_method(GArray *device, GArray *method)
{
uint8_t op = 0x14; /* MethodOp */
build_package(method, op, 0);
build_append_array(device, method);
build_free_array(method);
}
static void build_append_notify_target_ifequal(GArray *method,
GArray *target_name,
uint32_t value, int size)
{ {
GArray *notify = build_alloc_array(); GArray *notify = build_alloc_array();
uint8_t op = 0xA0; /* IfOp */ uint8_t op = 0xA0; /* IfOp */
@ -423,6 +440,7 @@ static void build_append_notify_target(GArray *method, GArray *target_name,
build_free_array(notify); build_free_array(notify);
} }
/* End here */
#define ACPI_PORT_SMI_CMD 0x00b2 /* TODO: this is APM_CNT_IOPORT */ #define ACPI_PORT_SMI_CMD 0x00b2 /* TODO: this is APM_CNT_IOPORT */
static inline void *acpi_data_push(GArray *table_data, unsigned size) static inline void *acpi_data_push(GArray *table_data, unsigned size)
@ -632,44 +650,236 @@ static inline char acpi_get_hex(uint32_t val)
#include "hw/i386/ssdt-pcihp.hex" #include "hw/i386/ssdt-pcihp.hex"
static void static void
build_append_notify(GArray *device, const char *name, build_append_notify_method(GArray *device, const char *name,
const char *format, int skip, int count) const char *format, int count)
{ {
int i; int i;
GArray *method = build_alloc_array(); GArray *method = build_alloc_method(name, 2);
uint8_t op = 0x14; /* MethodOp */
build_append_nameseg(method, "%s", name); for (i = 0; i < count; i++) {
build_append_byte(method, 0x02); /* MethodFlags: ArgCount */
for (i = skip; i < count; i++) {
GArray *target = build_alloc_array(); GArray *target = build_alloc_array();
build_append_nameseg(target, format, i); build_append_nameseg(target, format, i);
assert(i < 256); /* Fits in 1 byte */ assert(i < 256); /* Fits in 1 byte */
build_append_notify_target(method, target, i, 1); build_append_notify_target_ifequal(method, target, i, 1);
build_free_array(target); build_free_array(target);
} }
build_package(method, op, 2);
build_append_array(device, method); build_append_and_cleanup_method(device, method);
build_free_array(method);
} }
static void patch_pcihp(int slot, uint8_t *ssdt_ptr, uint32_t eject) static void patch_pcihp(int slot, uint8_t *ssdt_ptr)
{ {
ssdt_ptr[ACPI_PCIHP_OFFSET_HEX] = acpi_get_hex(slot >> 4); unsigned devfn = PCI_DEVFN(slot, 0);
ssdt_ptr[ACPI_PCIHP_OFFSET_HEX + 1] = acpi_get_hex(slot);
ssdt_ptr[ACPI_PCIHP_OFFSET_HEX] = acpi_get_hex(devfn >> 4);
ssdt_ptr[ACPI_PCIHP_OFFSET_HEX + 1] = acpi_get_hex(devfn);
ssdt_ptr[ACPI_PCIHP_OFFSET_ID] = slot; ssdt_ptr[ACPI_PCIHP_OFFSET_ID] = slot;
ssdt_ptr[ACPI_PCIHP_OFFSET_ADR + 2] = slot; ssdt_ptr[ACPI_PCIHP_OFFSET_ADR + 2] = slot;
}
/* Runtime patching of ACPI_EJ0: to disable hotplug for a slot, /* Assign BSEL property to all buses. In the future, this can be changed
* replace the method name: _EJ0 by ACPI_EJ0_. * to only assign to buses that support hotplug.
*/ */
/* Sanity check */ static void *acpi_set_bsel(PCIBus *bus, void *opaque)
assert(!memcmp(ssdt_ptr + ACPI_PCIHP_OFFSET_EJ0, "_EJ0", 4)); {
unsigned *bsel_alloc = opaque;
unsigned *bus_bsel;
if (!eject) { if (bus->qbus.allow_hotplug) {
memcpy(ssdt_ptr + ACPI_PCIHP_OFFSET_EJ0, "EJ0_", 4); bus_bsel = g_malloc(sizeof *bus_bsel);
*bus_bsel = (*bsel_alloc)++;
object_property_add_uint32_ptr(OBJECT(bus), ACPI_PCIHP_PROP_BSEL,
bus_bsel, NULL);
} }
return bsel_alloc;
}
static void acpi_set_pci_info(void)
{
PCIBus *bus = find_i440fx(); /* TODO: Q35 support */
unsigned bsel_alloc = 0;
if (bus) {
/* Scan all PCI buses. Set property to enable acpi based hotplug. */
pci_for_each_bus_depth_first(bus, acpi_set_bsel, NULL, &bsel_alloc);
}
}
static void build_pci_bus_state_init(AcpiBuildPciBusHotplugState *state,
AcpiBuildPciBusHotplugState *parent)
{
state->parent = parent;
state->device_table = build_alloc_array();
state->notify_table = build_alloc_array();
}
static void build_pci_bus_state_cleanup(AcpiBuildPciBusHotplugState *state)
{
build_free_array(state->device_table);
build_free_array(state->notify_table);
}
static void *build_pci_bus_begin(PCIBus *bus, void *parent_state)
{
AcpiBuildPciBusHotplugState *parent = parent_state;
AcpiBuildPciBusHotplugState *child = g_malloc(sizeof *child);
build_pci_bus_state_init(child, parent);
return child;
}
static void build_pci_bus_end(PCIBus *bus, void *bus_state)
{
AcpiBuildPciBusHotplugState *child = bus_state;
AcpiBuildPciBusHotplugState *parent = child->parent;
GArray *bus_table = build_alloc_array();
DECLARE_BITMAP(slot_hotplug_enable, PCI_SLOT_MAX);
uint8_t op;
int i;
QObject *bsel;
GArray *method;
bool bus_hotplug_support = false;
if (bus->parent_dev) {
op = 0x82; /* DeviceOp */
build_append_nameseg(bus_table, "S%.02X_",
bus->parent_dev->devfn);
build_append_byte(bus_table, 0x08); /* NameOp */
build_append_nameseg(bus_table, "_SUN");
build_append_value(bus_table, PCI_SLOT(bus->parent_dev->devfn), 1);
build_append_byte(bus_table, 0x08); /* NameOp */
build_append_nameseg(bus_table, "_ADR");
build_append_value(bus_table, (PCI_SLOT(bus->parent_dev->devfn) << 16) |
PCI_FUNC(bus->parent_dev->devfn), 4);
} else {
op = 0x10; /* ScopeOp */;
build_append_nameseg(bus_table, "PCI0");
}
bsel = object_property_get_qobject(OBJECT(bus), ACPI_PCIHP_PROP_BSEL, NULL);
if (bsel) {
build_append_byte(bus_table, 0x08); /* NameOp */
build_append_nameseg(bus_table, "BSEL");
build_append_int(bus_table, qint_get_int(qobject_to_qint(bsel)));
memset(slot_hotplug_enable, 0xff, sizeof slot_hotplug_enable);
for (i = 0; i < ARRAY_SIZE(bus->devices); ++i) {
PCIDeviceClass *pc;
PCIDevice *pdev = bus->devices[i];
if (!pdev) {
continue;
}
pc = PCI_DEVICE_GET_CLASS(pdev);
if (pc->no_hotplug || pc->is_bridge) {
int slot = PCI_SLOT(i);
clear_bit(slot, slot_hotplug_enable);
}
}
/* Append Device object for each slot which supports eject */
for (i = 0; i < PCI_SLOT_MAX; i++) {
bool can_eject = test_bit(i, slot_hotplug_enable);
if (can_eject) {
void *pcihp = acpi_data_push(bus_table,
ACPI_PCIHP_SIZEOF);
memcpy(pcihp, ACPI_PCIHP_AML, ACPI_PCIHP_SIZEOF);
patch_pcihp(i, pcihp);
bus_hotplug_support = true;
}
}
method = build_alloc_method("DVNT", 2);
for (i = 0; i < PCI_SLOT_MAX; i++) {
GArray *notify;
uint8_t op;
if (!test_bit(i, slot_hotplug_enable)) {
continue;
}
notify = build_alloc_array();
op = 0xA0; /* IfOp */
build_append_byte(notify, 0x7B); /* AndOp */
build_append_byte(notify, 0x68); /* Arg0Op */
build_append_int(notify, 0x1 << i);
build_append_byte(notify, 0x00); /* NullName */
build_append_byte(notify, 0x86); /* NotifyOp */
build_append_nameseg(notify, "S%.02X_", PCI_DEVFN(i, 0));
build_append_byte(notify, 0x69); /* Arg1Op */
/* Pack it up */
build_package(notify, op, 0);
build_append_array(method, notify);
build_free_array(notify);
}
build_append_and_cleanup_method(bus_table, method);
}
/* Append PCNT method to notify about events on local and child buses.
* Add unconditionally for root since DSDT expects it.
*/
if (bus_hotplug_support || child->notify_table->len || !bus->parent_dev) {
method = build_alloc_method("PCNT", 0);
/* If bus supports hotplug select it and notify about local events */
if (bsel) {
build_append_byte(method, 0x70); /* StoreOp */
build_append_int(method, qint_get_int(qobject_to_qint(bsel)));
build_append_nameseg(method, "BNUM");
build_append_nameseg(method, "DVNT");
build_append_nameseg(method, "PCIU");
build_append_int(method, 1); /* Device Check */
build_append_nameseg(method, "DVNT");
build_append_nameseg(method, "PCID");
build_append_int(method, 3); /* Eject Request */
}
/* Notify about child bus events in any case */
build_append_array(method, child->notify_table);
build_append_and_cleanup_method(bus_table, method);
/* Append description of child buses */
build_append_array(bus_table, child->device_table);
/* Pack it up */
if (bus->parent_dev) {
build_extop_package(bus_table, op);
} else {
build_package(bus_table, op, 0);
}
/* Append our bus description to parent table */
build_append_array(parent->device_table, bus_table);
/* Also tell parent how to notify us, invoking PCNT method.
* At the moment this is not needed for root as we have a single root.
*/
if (bus->parent_dev) {
build_append_byte(parent->notify_table, '^'); /* ParentPrefixChar */
build_append_byte(parent->notify_table, 0x2E); /* DualNamePrefix */
build_append_nameseg(parent->notify_table, "S%.02X_",
bus->parent_dev->devfn);
build_append_nameseg(parent->notify_table, "PCNT");
}
}
build_free_array(bus_table);
build_pci_bus_state_cleanup(child);
g_free(child);
} }
static void patch_pci_windows(PcPciInfo *pci, uint8_t *start, unsigned size) static void patch_pci_windows(PcPciInfo *pci, uint8_t *start, unsigned size)
@ -741,7 +951,7 @@ build_ssdt(GArray *table_data, GArray *linker,
* Method(NTFY, 2) {If (LEqual(Arg0, 0x00)) {Notify(CP00, Arg1)} ...} * Method(NTFY, 2) {If (LEqual(Arg0, 0x00)) {Notify(CP00, Arg1)} ...}
*/ */
/* Arg0 = Processor ID = APIC ID */ /* Arg0 = Processor ID = APIC ID */
build_append_notify(sb_scope, "NTFY", "CP%0.02X", 0, acpi_cpus); build_append_notify_method(sb_scope, "NTFY", "CP%0.02X", acpi_cpus);
/* build "Name(CPON, Package() { One, One, ..., Zero, Zero, ... })" */ /* build "Name(CPON, Package() { One, One, ..., Zero, Zero, ... })" */
build_append_byte(sb_scope, 0x08); /* NameOp */ build_append_byte(sb_scope, 0x08); /* NameOp */
@ -763,24 +973,19 @@ build_ssdt(GArray *table_data, GArray *linker,
} }
{ {
GArray *pci0 = build_alloc_array(); AcpiBuildPciBusHotplugState hotplug_state;
uint8_t op = 0x10; /* ScopeOp */; PCIBus *bus = find_i440fx(); /* TODO: Q35 support */
build_append_nameseg(pci0, "PCI0"); build_pci_bus_state_init(&hotplug_state, NULL);
/* build Device object for each slot */ if (bus) {
for (i = 1; i < PCI_SLOT_MAX; i++) { /* Scan all PCI buses. Generate tables to support hotplug. */
bool eject = test_bit(i, misc->slot_hotplug_enable); pci_for_each_bus_depth_first(bus, build_pci_bus_begin,
void *pcihp = acpi_data_push(pci0, ACPI_PCIHP_SIZEOF); build_pci_bus_end, &hotplug_state);
memcpy(pcihp, ACPI_PCIHP_AML, ACPI_PCIHP_SIZEOF);
patch_pcihp(i, pcihp, eject);
} }
build_append_notify(pci0, "PCNT", "S%0.02X_", 1, PCI_SLOT_MAX); build_append_array(sb_scope, hotplug_state.device_table);
build_package(pci0, op, 3); build_pci_bus_state_cleanup(&hotplug_state);
build_append_array(sb_scope, pci0);
build_free_array(pci0);
} }
build_package(sb_scope, op, 3); build_package(sb_scope, op, 3);
@ -1063,7 +1268,6 @@ void acpi_build(PcGuestInfo *guest_info, AcpiBuildTables *tables)
acpi_get_cpu_info(&cpu); acpi_get_cpu_info(&cpu);
acpi_get_pm_info(&pm); acpi_get_pm_info(&pm);
acpi_get_dsdt(&misc); acpi_get_dsdt(&misc);
acpi_get_hotplug_info(&misc);
acpi_get_misc_info(&misc); acpi_get_misc_info(&misc);
acpi_get_pci_info(&pci); acpi_get_pci_info(&pci);
@ -1208,6 +1412,8 @@ void acpi_setup(PcGuestInfo *guest_info)
build_state->guest_info = guest_info; build_state->guest_info = guest_info;
acpi_set_pci_info();
acpi_build_tables_init(&tables); acpi_build_tables_init(&tables);
acpi_build(build_state->guest_info, &tables); acpi_build(build_state->guest_info, &tables);

View File

@ -134,32 +134,28 @@ DefinitionBlock (
B0EJ, 32, B0EJ, 32,
} }
OperationRegion(BNMR, SystemIO, 0xae10, 0x04)
Field(BNMR, DWordAcc, NoLock, WriteAsZeros) {
BNUM, 32,
}
/* Lock to protect access to fields above. */
Mutex(BLCK, 0)
/* Methods called by bulk generated PCI devices below */ /* Methods called by bulk generated PCI devices below */
/* Methods called by hotplug devices */ /* Methods called by hotplug devices */
Method(PCEJ, 1, NotSerialized) { Method(PCEJ, 2, NotSerialized) {
// _EJ0 method - eject callback // _EJ0 method - eject callback
Store(ShiftLeft(1, Arg0), B0EJ) Acquire(BLCK, 0xFFFF)
Store(Arg0, BNUM)
Store(ShiftLeft(1, Arg1), B0EJ)
Release(BLCK)
Return (0x0) Return (0x0)
} }
/* Hotplug notification method supplied by SSDT */ /* Hotplug notification method supplied by SSDT */
External(\_SB.PCI0.PCNT, MethodObj) External(\_SB.PCI0.PCNT, MethodObj)
/* PCI hotplug notify method */
Method(PCNF, 0) {
// Local0 = iterator
Store(Zero, Local0)
While (LLess(Local0, 31)) {
Increment(Local0)
If (And(PCIU, ShiftLeft(1, Local0))) {
PCNT(Local0, 1)
}
If (And(PCID, ShiftLeft(1, Local0))) {
PCNT(Local0, 3)
}
}
}
} }
@ -308,7 +304,9 @@ DefinitionBlock (
} }
Method(_E01) { Method(_E01) {
// PCI hotplug event // PCI hotplug event
\_SB.PCI0.PCNF() Acquire(\_SB.PCI0.BLCK, 0xFFFF)
\_SB.PCI0.PCNT()
Release(\_SB.PCI0.BLCK)
} }
Method(_E02) { Method(_E02) {
// CPU hotplug event // CPU hotplug event

View File

@ -25,6 +25,7 @@ DefinitionBlock ("ssdt-pcihp.aml", "SSDT", 0x01, "BXPC", "BXSSDTPCIHP", 0x1)
/* Objects supplied by DSDT */ /* Objects supplied by DSDT */
External(\_SB.PCI0, DeviceObj) External(\_SB.PCI0, DeviceObj)
External(\_SB.PCI0.PCEJ, MethodObj) External(\_SB.PCI0.PCEJ, MethodObj)
External(BSEL, IntObj)
Scope(\_SB.PCI0) { Scope(\_SB.PCI0) {
@ -33,19 +34,17 @@ DefinitionBlock ("ssdt-pcihp.aml", "SSDT", 0x01, "BXPC", "BXSSDTPCIHP", 0x1)
ACPI_EXTRACT_DEVICE_END ssdt_pcihp_end ACPI_EXTRACT_DEVICE_END ssdt_pcihp_end
ACPI_EXTRACT_DEVICE_STRING ssdt_pcihp_name ACPI_EXTRACT_DEVICE_STRING ssdt_pcihp_name
// Method _EJ0 can be patched by BIOS to EJ0_ // Extract the offsets of the device name, address dword and the slot
// at runtime, if the slot is detected to not support hotplug. // name byte - we fill them in for each device.
// Extract the offset of the address dword and the
// _EJ0 name to allow this patching.
Device(SAA) { Device(SAA) {
ACPI_EXTRACT_NAME_BYTE_CONST ssdt_pcihp_id ACPI_EXTRACT_NAME_BYTE_CONST ssdt_pcihp_id
Name(_SUN, 0xAA) Name(_SUN, 0xAA)
ACPI_EXTRACT_NAME_DWORD_CONST ssdt_pcihp_adr ACPI_EXTRACT_NAME_DWORD_CONST ssdt_pcihp_adr
Name(_ADR, 0xAA0000) Name(_ADR, 0xAA0000)
ACPI_EXTRACT_METHOD_STRING ssdt_pcihp_ej0
Method(_EJ0, 1) { Method(_EJ0, 1) {
Return (PCEJ(_SUN)) PCEJ(BSEL, _SUN)
} }
} }
} }
} }