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 <m...@redhat.com>
---
 hw/i386/acpi-build.c   | 346 +++++++++++++++++++++++++++++++++++++++----------
 hw/i386/acpi-dsdt.dsl  |  34 +++--
 hw/i386/ssdt-pcihp.dsl |  11 +-
 3 files changed, 297 insertions(+), 94 deletions(-)

diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 6cfa044..4311814 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -39,6 +39,7 @@
 
 /* Supported chipsets: */
 #include "hw/acpi/piix4.h"
+#include "hw/acpi/pcihp.h"
 #include "hw/i386/ich9.h"
 #include "hw/pci/pci_bus.h"
 #include "hw/pci-host/q35.h"
@@ -78,6 +79,12 @@ typedef struct AcpiMiscInfo {
     uint16_t pvpanic_port;
 } AcpiMiscInfo;
 
+typedef struct AcpiBuildPciBusHotplugState {
+    GArray *device_table;
+    GArray *notify_table;
+    struct AcpiBuildPciBusHotplugState *parent;
+} AcpiBuildPciBusHotplugState;
+
 static void acpi_get_dsdt(AcpiMiscInfo *info)
 {
     Object *piix = piix4_pm_find();
@@ -171,38 +178,6 @@ static void acpi_get_pm_info(AcpiPmInfo *pm)
                                                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)
 {
     info->has_hpet = hpet_find();
@@ -366,6 +341,12 @@ static void build_package(GArray *package, uint8_t op, 
unsigned min_bytes)
     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)
 {
     uint8_t prefix;
@@ -392,8 +373,44 @@ static void build_append_value(GArray *table, uint32_t 
value, int size)
     }
 }
 
-static void build_append_notify_target(GArray *method, GArray *target_name,
-                                       uint32_t value, int size)
+static void build_append_int(GArray *table, uint32_t value)
+{
+    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, 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();
     uint8_t op = 0xA0; /* IfOp */
@@ -413,6 +430,7 @@ static void build_append_notify_target(GArray *method, 
GArray *target_name,
     build_free_array(notify);
 }
 
+/* End here */
 #define ACPI_PORT_SMI_CMD           0x00b2 /* TODO: this is APM_CNT_IOPORT */
 
 static inline void *acpi_data_push(GArray *table_data, unsigned size)
@@ -621,44 +639,234 @@ static inline char acpi_get_hex(uint32_t val)
 #include "hw/i386/ssdt-pcihp.hex"
 
 static void
-build_append_notify(GArray *device, const char *name,
-                    const char *format, int skip, int count)
+build_append_notify_method(GArray *device, const char *name,
+                           const char *format, int count)
 {
     int i;
-    GArray *method = build_alloc_array();
-    uint8_t op = 0x14; /* MethodOp */
+    GArray *method = build_alloc_method(name, 2);
 
-    build_append_nameseg(method, name);
-    build_append_byte(method, 0x02); /* MethodFlags: ArgCount */
-    for (i = skip; i < count; i++) {
+    for (i = 0; i < count; i++) {
         GArray *target = build_alloc_array();
         build_append_nameseg(target, format, i);
         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_package(method, op, 2);
 
-    build_append_array(device, method);
-    build_free_array(method);
+    build_append_and_cleanup_method(device, 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);
-    ssdt_ptr[ACPI_PCIHP_OFFSET_HEX + 1] = acpi_get_hex(slot);
+    unsigned devfn = PCI_DEVFN(slot, 0);
+
+    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_ADR + 2] = slot;
+}
+
+/* Assign BSEL property to all buses.  In the future, this can be changed
+ * to only assign to buses that support hotplug.
+ */
+static void *acpi_set_bsel(PCIBus *bus, void *opaque)
+{
+    unsigned *bsel_alloc = opaque;
+    unsigned *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);
 
-    /* Runtime patching of ACPI_EJ0: to disable hotplug for a slot,
-     * replace the method name: _EJ0 by ACPI_EJ0_.
+    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. Generate tables to support
+         * PCI hotplug. 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%0.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%0.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.
      */
-    /* Sanity check */
-    assert(!memcmp(ssdt_ptr + ACPI_PCIHP_OFFSET_EJ0, "_EJ0", 4));
+    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);
+        }
 
-    if (!eject) {
-        memcpy(ssdt_ptr + ACPI_PCIHP_OFFSET_EJ0, "EJ0_", 4);
+        /* 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%0.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)
@@ -730,7 +938,7 @@ build_ssdt(GArray *table_data, GArray *linker,
          *   Method(NTFY, 2) {If (LEqual(Arg0, 0x00)) {Notify(CP00, Arg1)} ...}
          */
         /* 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_append_byte(sb_scope, 0x08); /* NameOp */
@@ -752,24 +960,21 @@ build_ssdt(GArray *table_data, GArray *linker,
         }
 
         {
-            GArray *pci0 = build_alloc_array();
-            uint8_t op = 0x10; /* ScopeOp */;
+            AcpiBuildPciBusHotplugState hotplug_state;
+            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 */
-            for (i = 1; i < PCI_SLOT_MAX; i++) {
-                bool eject = test_bit(i, misc->slot_hotplug_enable);
-                void *pcihp = acpi_data_push(pci0, ACPI_PCIHP_SIZEOF);
-
-                memcpy(pcihp, ACPI_PCIHP_AML, ACPI_PCIHP_SIZEOF);
-                patch_pcihp(i, pcihp, eject);
+            if (bus) {
+                /* Scan all PCI buses. Generate tables to support
+                 * PCI hotplug. Set property to enable acpi based hotplug.
+                */
+                pci_for_each_bus_depth_first(bus, build_pci_bus_begin,
+                                             build_pci_bus_end, 
&hotplug_state);
             }
 
-            build_append_notify(pci0, "PCNT", "S%0.02X_", 1, PCI_SLOT_MAX);
-            build_package(pci0, op, 3);
-            build_append_array(sb_scope, pci0);
-            build_free_array(pci0);
+            build_append_array(sb_scope, hotplug_state.device_table);
+            build_pci_bus_state_cleanup(&hotplug_state);
         }
 
         build_package(sb_scope, op, 3);
@@ -1046,7 +1251,6 @@ void acpi_build(PcGuestInfo *guest_info, AcpiBuildTables 
*tables)
     acpi_get_cpu_info(&cpu);
     acpi_get_pm_info(&pm);
     acpi_get_dsdt(&misc);
-    acpi_get_hotplug_info(&misc);
     acpi_get_misc_info(&misc);
     acpi_get_pci_info(&pci);
 
@@ -1186,6 +1390,8 @@ void acpi_setup(PcGuestInfo *guest_info)
 
     build_state->guest_info = guest_info;
 
+    acpi_set_pci_info();
+
     acpi_build_tables_init(&tables);
     acpi_build(build_state->guest_info, &tables);
 
diff --git a/hw/i386/acpi-dsdt.dsl b/hw/i386/acpi-dsdt.dsl
index 90efce0..70b8329 100644
--- a/hw/i386/acpi-dsdt.dsl
+++ b/hw/i386/acpi-dsdt.dsl
@@ -133,32 +133,28 @@ DefinitionBlock (
             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 hotplug devices */
-        Method(PCEJ, 1, NotSerialized) {
+        Method(PCEJ, 2, NotSerialized) {
             // _EJ0 method - eject callback
-            Store(ShiftLeft(1, Arg0), B0EJ)
+            Acquire(BLCK, 0xFFFF)
+            Store(Arg0, BNUM)
+            Store(ShiftLeft(1, Arg1), B0EJ)
+            Release(BLCK)
             Return (0x0)
         }
 
         /* Hotplug notification method supplied by SSDT */
         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)
-                }
-            }
-        }
     }
 
 
@@ -307,7 +303,9 @@ DefinitionBlock (
         }
         Method(_E01) {
             // PCI hotplug event
-            \_SB.PCI0.PCNF()
+            Acquire(\_SB.PCI0.BLCK, 0xFFFF)
+            \_SB.PCI0.PCNT()
+            Release(\_SB.PCI0.BLCK)
         }
         Method(_E02) {
             // CPU hotplug event
diff --git a/hw/i386/ssdt-pcihp.dsl b/hw/i386/ssdt-pcihp.dsl
index d29a5b9..cc245c3 100644
--- a/hw/i386/ssdt-pcihp.dsl
+++ b/hw/i386/ssdt-pcihp.dsl
@@ -25,6 +25,7 @@ DefinitionBlock ("ssdt-pcihp.aml", "SSDT", 0x01, "BXPC", 
"BXSSDTPCIHP", 0x1)
     /* Objects supplied by DSDT */
     External(\_SB.PCI0, DeviceObj)
     External(\_SB.PCI0.PCEJ, MethodObj)
+    External(BSEL, IntObj)
 
     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_STRING ssdt_pcihp_name
 
-        // Method _EJ0 can be patched by BIOS to EJ0_
-        // at runtime, if the slot is detected to not support hotplug.
-        // Extract the offset of the address dword and the
-        // _EJ0 name to allow this patching.
+        // Extract the offsets of the device name, address dword and the slot
+        // name byte - we fill them in for each device.
         Device(SAA) {
             ACPI_EXTRACT_NAME_BYTE_CONST ssdt_pcihp_id
             Name(_SUN, 0xAA)
             ACPI_EXTRACT_NAME_DWORD_CONST ssdt_pcihp_adr
             Name(_ADR, 0xAA0000)
-            ACPI_EXTRACT_METHOD_STRING ssdt_pcihp_ej0
             Method(_EJ0, 1) {
-                Return (PCEJ(_SUN))
+                PCEJ(BSEL, _SUN)
             }
         }
+
     }
 }
-- 
MST


Reply via email to