diff mbox

[23/25] q35: add acpi-based pci hotplug.

Message ID 9550dd02daa9aa41ae141db6a8743159264ff017.1347561356.git.jbaron@redhat.com
State New
Headers show

Commit Message

Jason Baron Sept. 13, 2012, 8:12 p.m. UTC
Add piix style acpi hotplug to q35.

Signed-off-by: Jason Baron <jbaron@redhat.com>
---
 hw/acpi_ich9.c |  173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 hw/acpi_ich9.h |   10 +++
 2 files changed, 182 insertions(+), 1 deletions(-)

Comments

Blue Swirl Sept. 14, 2012, 6:56 p.m. UTC | #1
On Thu, Sep 13, 2012 at 8:12 PM, Jason Baron <jbaron@redhat.com> wrote:
> Add piix style acpi hotplug to q35.
>
> Signed-off-by: Jason Baron <jbaron@redhat.com>
> ---
>  hw/acpi_ich9.c |  173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  hw/acpi_ich9.h |   10 +++
>  2 files changed, 182 insertions(+), 1 deletions(-)
>
> diff --git a/hw/acpi_ich9.c b/hw/acpi_ich9.c
> index 570ce0c..ba463a0 100644
> --- a/hw/acpi_ich9.c
> +++ b/hw/acpi_ich9.c
> @@ -41,6 +41,13 @@ do { printf("%s "fmt, __func__, ## __VA_ARGS__); } while (0)
>  #define ICH9_DEBUG(fmt, ...)    do { } while (0)
>  #endif
>
> +#define PCI_UP_BASE 0xae00
> +#define PCI_DOWN_BASE 0xae04
> +#define PCI_EJ_BASE 0xae08
> +#define PCI_RMV_BASE 0xae0c
> +#define ICH9_PCI_HOTPLUG_STATUS 2
> +
> +
>  static void pm_ioport_write_fallback(void *opaque, uint32_t addr, int len,
>                                       uint32_t val);
>  static uint32_t pm_ioport_read_fallback(void *opaque, uint32_t addr, int len);
> @@ -55,7 +62,10 @@ static void pm_update_sci(ICH9_LPCPmRegs *pm)
>                    (ACPI_BITMASK_RT_CLOCK_ENABLE |
>                     ACPI_BITMASK_POWER_BUTTON_ENABLE |
>                     ACPI_BITMASK_GLOBAL_LOCK_ENABLE |
> -                   ACPI_BITMASK_TIMER_ENABLE)) != 0);
> +                   ACPI_BITMASK_TIMER_ENABLE)) != 0) ||
> +         (((pm->acpi_regs.gpe.sts[0] & pm->acpi_regs.gpe.en[0])
> +          & ICH9_PCI_HOTPLUG_STATUS) != 0);
> +
>      qemu_set_irq(pm->irq, sci_level);
>
>      /* schedule a timer interruption if needed */
> @@ -77,6 +87,7 @@ static void pm_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
>      switch (addr & ICH9_PMIO_MASK) {
>      case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + ICH9_PMIO_GPE0_LEN - 1):
>          acpi_gpe_ioport_writeb(&pm->acpi_regs, addr, val);
> +        pm_update_sci(pm);
>          break;
>      default:
>          break;
> @@ -283,6 +294,65 @@ const VMStateDescription vmstate_ich9_pm = {
>      }
>  };
>
> +static void acpi_ich9_eject_slot(ICH9_LPCPmRegs *opaque, unsigned slots)
> +{
> +    BusChild *kid, *next;
> +    ICH9_LPCPmRegs *pm = opaque;
> +    ICH9_LPCState *lpc = container_of(pm, ICH9_LPCState, pm);
> +    PCIDevice *s = PCI_DEVICE(lpc);
> +    BusState *bus = qdev_get_parent_bus(&s->qdev);
> +    int slot = ffs(slots) - 1;
> +    bool slot_free = true;
> +
> +    /* Mark request as complete */
> +    pm->pci0_status.down &= ~(1U << slot);
> +
> +    QTAILQ_FOREACH_SAFE(kid, &bus->children, sibling, next) {
> +        DeviceState *qdev = kid->child;
> +        PCIDevice *dev = PCI_DEVICE(qdev);
> +        PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
> +        if (PCI_SLOT(dev->devfn) == slot) {
> +            if (pc->no_hotplug) {
> +                slot_free = false;
> +            } else {
> +                qdev_free(qdev);
> +            }
> +        }
> +    }
> +    if (slot_free) {
> +        pm->pci0_slot_device_present &= ~(1U << slot);
> +    }
> +}
> +
> +static void acpi_ich9_update_hotplug(ICH9_LPCPmRegs *pm)
> +{
> +    ICH9_LPCState *lpc = container_of(pm, ICH9_LPCState, pm);
> +    PCIDevice *dev = PCI_DEVICE(lpc);
> +    BusState *bus = qdev_get_parent_bus(&dev->qdev);
> +    BusChild *kid, *next;
> +
> +    /* Execute any pending removes during reset */
> +    while (pm->pci0_status.down) {
> +        acpi_ich9_eject_slot(pm, pm->pci0_status.down);
> +    }
> +
> +    pm->pci0_hotplug_enable = ~0;
> +    pm->pci0_slot_device_present = 0;
> +
> +    QTAILQ_FOREACH_SAFE(kid, &bus->children, sibling, next) {
> +        DeviceState *qdev = kid->child;
> +        PCIDevice *pdev = PCI_DEVICE(qdev);
> +        PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pdev);
> +        int slot = PCI_SLOT(pdev->devfn);
> +
> +        if (pc->no_hotplug) {
> +            pm->pci0_hotplug_enable &= ~(1U << slot);
> +        }
> +
> +        pm->pci0_slot_device_present |= (1U << slot);
> +    }
> +}
> +
>  static void pm_reset(void *opaque)
>  {
>      ICH9_LPCPmRegs *pm = opaque;
> @@ -300,6 +370,7 @@ static void pm_reset(void *opaque)
>      }
>
>      pm_update_sci(pm);
> +    acpi_ich9_update_hotplug(pm);
>  }
>
>  static void pm_powerdown(void *opaque, int irq, int power_failing)
> @@ -309,6 +380,104 @@ static void pm_powerdown(void *opaque, int irq, int power_failing)
>      acpi_pm1_evt_power_down(&pm->acpi_regs);
>  }
>
> +static uint32_t pci_up_read(void *opaque, uint32_t addr)
> +{
> +    ICH9_LPCPmRegs *pm = opaque;
> +    uint32_t val;
> +
> +    /* Manufacture an "up" value to cause a device check on any hotplug
> +     * slot with a device.  Extra device checks are harmless. */
> +    val = pm->pci0_slot_device_present & pm->pci0_hotplug_enable;
> +
> +    ICH9_DEBUG("pci_up_read %x\n", val);
> +    return val;
> +}
> +
> +static uint32_t pci_down_read(void *opaque, uint32_t addr)
> +{
> +    ICH9_LPCPmRegs *pm = opaque;
> +    uint32_t val = pm->pci0_status.down;
> +
> +    ICH9_DEBUG("pci_down_read %x\n", val);
> +    return val;
> +}
> +
> +static uint32_t pci_features_read(void *opaque, uint32_t addr)
> +{
> +    /* No feature defined yet */
> +    ICH9_DEBUG("pci_features_read %x\n", 0);
> +    return 0;
> +}
> +
> +static void pciej_write(void *opaque, uint32_t addr, uint32_t val)
> +{
> +    acpi_ich9_eject_slot(opaque, val);
> +
> +    ICH9_DEBUG("pciej write %x <== %d\n", addr, val);
> +}
> +
> +static uint32_t pcirmv_read(void *opaque, uint32_t addr)
> +{
> +    ICH9_LPCPmRegs *pm = opaque;
> +
> +    return pm->pci0_hotplug_enable;
> +}
> +
> +static void enable_device(ICH9_LPCPmRegs *pm, int slot)
> +{
> +    pm->acpi_regs.gpe.sts[0] |= ICH9_PCI_HOTPLUG_STATUS;
> +    pm->pci0_slot_device_present |= (1U << slot);
> +}
> +
> +static void disable_device(ICH9_LPCPmRegs *pm, int slot)
> +{
> +    pm->acpi_regs.gpe.sts[0] |= ICH9_PCI_HOTPLUG_STATUS;
> +    pm->pci0_status.down |= (1U << slot);
> +}
> +
> +static int ich9_device_hotplug(DeviceState *qdev, PCIDevice *dev,
> +                PCIHotplugState state)
> +{
> +    int slot = PCI_SLOT(dev->devfn);
> +    ICH9_LPCState *lpc = DO_UPCAST(ICH9_LPCState, d,
> +                                PCI_DEVICE(qdev));
> +    ICH9_LPCPmRegs *pm = &lpc->pm;
> +
> +    /* Don't send event when device is enabled during qemu machine creation:
> +     * it is present on boot, no hotplug event is necessary. We do send an
> +     * event when the device is disabled later. */
> +    if (state == PCI_COLDPLUG_ENABLED) {
> +        pm->pci0_slot_device_present |= (1U << slot);
> +        return 0;
> +    }
> +
> +    if (state == PCI_HOTPLUG_ENABLED) {
> +        enable_device(pm, slot);
> +    } else {
> +        disable_device(pm, slot);
> +    }
> +
> +    pm_update_sci(pm);
> +
> +    return 0;
> +}
> +
> +static void ich9_acpi_system_hot_add_init(ICH9_LPCPmRegs *s)
> +{
> +    ICH9_LPCState *lpc = container_of(s, ICH9_LPCState, pm);
> +    PCIDevice *pdev = PCI_DEVICE(lpc);
> +
> +    register_ioport_read(PCI_UP_BASE, 4, 4, pci_up_read, s);

Please convert these to MemoryRegionPortio.

> +    register_ioport_read(PCI_DOWN_BASE, 4, 4, pci_down_read, s);
> +
> +    register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, s);
> +    register_ioport_read(PCI_EJ_BASE, 4, 4,  pci_features_read, s);
> +
> +    register_ioport_read(PCI_RMV_BASE, 4, 4,  pcirmv_read, s);
> +
> +    pci_bus_hotplug(pdev->bus, ich9_device_hotplug, &pdev->qdev);
> +}
> +
>  void ich9_pm_init(ICH9_LPCPmRegs *pm, qemu_irq sci_irq, qemu_irq cmos_s3)
>  {
>      acpi_pm_tmr_init(&pm->acpi_regs, ich9_pm_update_sci_fn);
> @@ -318,4 +487,6 @@ void ich9_pm_init(ICH9_LPCPmRegs *pm, qemu_irq sci_irq, qemu_irq cmos_s3)
>      pm->irq = sci_irq;
>      qemu_register_reset(pm_reset, pm);
>      qemu_system_powerdown = *qemu_allocate_irqs(pm_powerdown, pm, 1);
> +
> +    ich9_acpi_system_hot_add_init(pm);
>  }
> diff --git a/hw/acpi_ich9.h b/hw/acpi_ich9.h
> index 9ff4c42..434e221 100644
> --- a/hw/acpi_ich9.h
> +++ b/hw/acpi_ich9.h
> @@ -23,6 +23,11 @@
>
>  #include "acpi.h"
>
> +struct pci_status {

PCIStatus, typedef missing

> +    uint32_t up; /* deprecated, maintained for migration compatibility */
> +    uint32_t down;
> +};
> +
>  typedef struct ICH9_LPCPmRegs {
>      /*
>       * In ich9 spec says that pm1_cnt register is 32bit width and
> @@ -36,6 +41,11 @@ typedef struct ICH9_LPCPmRegs {
>      qemu_irq irq;      /* SCI */
>
>      uint32_t pm_io_base;
> +
> +    /* for pci hotplug */
> +    struct pci_status pci0_status;
> +    uint32_t pci0_hotplug_enable;
> +    uint32_t pci0_slot_device_present;
>  } ICH9_LPCPmRegs;
>
>  void ich9_pm_init(ICH9_LPCPmRegs *pm,
> --
> 1.7.1
>
>
diff mbox

Patch

diff --git a/hw/acpi_ich9.c b/hw/acpi_ich9.c
index 570ce0c..ba463a0 100644
--- a/hw/acpi_ich9.c
+++ b/hw/acpi_ich9.c
@@ -41,6 +41,13 @@  do { printf("%s "fmt, __func__, ## __VA_ARGS__); } while (0)
 #define ICH9_DEBUG(fmt, ...)    do { } while (0)
 #endif
 
+#define PCI_UP_BASE 0xae00
+#define PCI_DOWN_BASE 0xae04
+#define PCI_EJ_BASE 0xae08
+#define PCI_RMV_BASE 0xae0c
+#define ICH9_PCI_HOTPLUG_STATUS 2
+
+
 static void pm_ioport_write_fallback(void *opaque, uint32_t addr, int len,
                                      uint32_t val);
 static uint32_t pm_ioport_read_fallback(void *opaque, uint32_t addr, int len);
@@ -55,7 +62,10 @@  static void pm_update_sci(ICH9_LPCPmRegs *pm)
                   (ACPI_BITMASK_RT_CLOCK_ENABLE |
                    ACPI_BITMASK_POWER_BUTTON_ENABLE |
                    ACPI_BITMASK_GLOBAL_LOCK_ENABLE |
-                   ACPI_BITMASK_TIMER_ENABLE)) != 0);
+                   ACPI_BITMASK_TIMER_ENABLE)) != 0) ||
+         (((pm->acpi_regs.gpe.sts[0] & pm->acpi_regs.gpe.en[0])
+          & ICH9_PCI_HOTPLUG_STATUS) != 0);
+
     qemu_set_irq(pm->irq, sci_level);
 
     /* schedule a timer interruption if needed */
@@ -77,6 +87,7 @@  static void pm_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
     switch (addr & ICH9_PMIO_MASK) {
     case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + ICH9_PMIO_GPE0_LEN - 1):
         acpi_gpe_ioport_writeb(&pm->acpi_regs, addr, val);
+        pm_update_sci(pm);
         break;
     default:
         break;
@@ -283,6 +294,65 @@  const VMStateDescription vmstate_ich9_pm = {
     }
 };
 
+static void acpi_ich9_eject_slot(ICH9_LPCPmRegs *opaque, unsigned slots)
+{
+    BusChild *kid, *next;
+    ICH9_LPCPmRegs *pm = opaque;
+    ICH9_LPCState *lpc = container_of(pm, ICH9_LPCState, pm);
+    PCIDevice *s = PCI_DEVICE(lpc);
+    BusState *bus = qdev_get_parent_bus(&s->qdev);
+    int slot = ffs(slots) - 1;
+    bool slot_free = true;
+
+    /* Mark request as complete */
+    pm->pci0_status.down &= ~(1U << slot);
+
+    QTAILQ_FOREACH_SAFE(kid, &bus->children, sibling, next) {
+        DeviceState *qdev = kid->child;
+        PCIDevice *dev = PCI_DEVICE(qdev);
+        PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+        if (PCI_SLOT(dev->devfn) == slot) {
+            if (pc->no_hotplug) {
+                slot_free = false;
+            } else {
+                qdev_free(qdev);
+            }
+        }
+    }
+    if (slot_free) {
+        pm->pci0_slot_device_present &= ~(1U << slot);
+    }
+}
+
+static void acpi_ich9_update_hotplug(ICH9_LPCPmRegs *pm)
+{
+    ICH9_LPCState *lpc = container_of(pm, ICH9_LPCState, pm);
+    PCIDevice *dev = PCI_DEVICE(lpc);
+    BusState *bus = qdev_get_parent_bus(&dev->qdev);
+    BusChild *kid, *next;
+
+    /* Execute any pending removes during reset */
+    while (pm->pci0_status.down) {
+        acpi_ich9_eject_slot(pm, pm->pci0_status.down);
+    }
+
+    pm->pci0_hotplug_enable = ~0;
+    pm->pci0_slot_device_present = 0;
+
+    QTAILQ_FOREACH_SAFE(kid, &bus->children, sibling, next) {
+        DeviceState *qdev = kid->child;
+        PCIDevice *pdev = PCI_DEVICE(qdev);
+        PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pdev);
+        int slot = PCI_SLOT(pdev->devfn);
+
+        if (pc->no_hotplug) {
+            pm->pci0_hotplug_enable &= ~(1U << slot);
+        }
+
+        pm->pci0_slot_device_present |= (1U << slot);
+    }
+}
+
 static void pm_reset(void *opaque)
 {
     ICH9_LPCPmRegs *pm = opaque;
@@ -300,6 +370,7 @@  static void pm_reset(void *opaque)
     }
 
     pm_update_sci(pm);
+    acpi_ich9_update_hotplug(pm);
 }
 
 static void pm_powerdown(void *opaque, int irq, int power_failing)
@@ -309,6 +380,104 @@  static void pm_powerdown(void *opaque, int irq, int power_failing)
     acpi_pm1_evt_power_down(&pm->acpi_regs);
 }
 
+static uint32_t pci_up_read(void *opaque, uint32_t addr)
+{
+    ICH9_LPCPmRegs *pm = opaque;
+    uint32_t val;
+
+    /* Manufacture an "up" value to cause a device check on any hotplug
+     * slot with a device.  Extra device checks are harmless. */
+    val = pm->pci0_slot_device_present & pm->pci0_hotplug_enable;
+
+    ICH9_DEBUG("pci_up_read %x\n", val);
+    return val;
+}
+
+static uint32_t pci_down_read(void *opaque, uint32_t addr)
+{
+    ICH9_LPCPmRegs *pm = opaque;
+    uint32_t val = pm->pci0_status.down;
+
+    ICH9_DEBUG("pci_down_read %x\n", val);
+    return val;
+}
+
+static uint32_t pci_features_read(void *opaque, uint32_t addr)
+{
+    /* No feature defined yet */
+    ICH9_DEBUG("pci_features_read %x\n", 0);
+    return 0;
+}
+
+static void pciej_write(void *opaque, uint32_t addr, uint32_t val)
+{
+    acpi_ich9_eject_slot(opaque, val);
+
+    ICH9_DEBUG("pciej write %x <== %d\n", addr, val);
+}
+
+static uint32_t pcirmv_read(void *opaque, uint32_t addr)
+{
+    ICH9_LPCPmRegs *pm = opaque;
+
+    return pm->pci0_hotplug_enable;
+}
+
+static void enable_device(ICH9_LPCPmRegs *pm, int slot)
+{
+    pm->acpi_regs.gpe.sts[0] |= ICH9_PCI_HOTPLUG_STATUS;
+    pm->pci0_slot_device_present |= (1U << slot);
+}
+
+static void disable_device(ICH9_LPCPmRegs *pm, int slot)
+{
+    pm->acpi_regs.gpe.sts[0] |= ICH9_PCI_HOTPLUG_STATUS;
+    pm->pci0_status.down |= (1U << slot);
+}
+
+static int ich9_device_hotplug(DeviceState *qdev, PCIDevice *dev,
+                PCIHotplugState state)
+{
+    int slot = PCI_SLOT(dev->devfn);
+    ICH9_LPCState *lpc = DO_UPCAST(ICH9_LPCState, d,
+                                PCI_DEVICE(qdev));
+    ICH9_LPCPmRegs *pm = &lpc->pm;
+
+    /* Don't send event when device is enabled during qemu machine creation:
+     * it is present on boot, no hotplug event is necessary. We do send an
+     * event when the device is disabled later. */
+    if (state == PCI_COLDPLUG_ENABLED) {
+        pm->pci0_slot_device_present |= (1U << slot);
+        return 0;
+    }
+
+    if (state == PCI_HOTPLUG_ENABLED) {
+        enable_device(pm, slot);
+    } else {
+        disable_device(pm, slot);
+    }
+
+    pm_update_sci(pm);
+
+    return 0;
+}
+
+static void ich9_acpi_system_hot_add_init(ICH9_LPCPmRegs *s)
+{
+    ICH9_LPCState *lpc = container_of(s, ICH9_LPCState, pm);
+    PCIDevice *pdev = PCI_DEVICE(lpc);
+
+    register_ioport_read(PCI_UP_BASE, 4, 4, pci_up_read, s);
+    register_ioport_read(PCI_DOWN_BASE, 4, 4, pci_down_read, s);
+
+    register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, s);
+    register_ioport_read(PCI_EJ_BASE, 4, 4,  pci_features_read, s);
+
+    register_ioport_read(PCI_RMV_BASE, 4, 4,  pcirmv_read, s);
+
+    pci_bus_hotplug(pdev->bus, ich9_device_hotplug, &pdev->qdev);
+}
+
 void ich9_pm_init(ICH9_LPCPmRegs *pm, qemu_irq sci_irq, qemu_irq cmos_s3)
 {
     acpi_pm_tmr_init(&pm->acpi_regs, ich9_pm_update_sci_fn);
@@ -318,4 +487,6 @@  void ich9_pm_init(ICH9_LPCPmRegs *pm, qemu_irq sci_irq, qemu_irq cmos_s3)
     pm->irq = sci_irq;
     qemu_register_reset(pm_reset, pm);
     qemu_system_powerdown = *qemu_allocate_irqs(pm_powerdown, pm, 1);
+
+    ich9_acpi_system_hot_add_init(pm);
 }
diff --git a/hw/acpi_ich9.h b/hw/acpi_ich9.h
index 9ff4c42..434e221 100644
--- a/hw/acpi_ich9.h
+++ b/hw/acpi_ich9.h
@@ -23,6 +23,11 @@ 
 
 #include "acpi.h"
 
+struct pci_status {
+    uint32_t up; /* deprecated, maintained for migration compatibility */
+    uint32_t down;
+};
+
 typedef struct ICH9_LPCPmRegs {
     /*
      * In ich9 spec says that pm1_cnt register is 32bit width and
@@ -36,6 +41,11 @@  typedef struct ICH9_LPCPmRegs {
     qemu_irq irq;      /* SCI */
 
     uint32_t pm_io_base;
+
+    /* for pci hotplug */
+    struct pci_status pci0_status;
+    uint32_t pci0_hotplug_enable;
+    uint32_t pci0_slot_device_present;
 } ICH9_LPCPmRegs;
 
 void ich9_pm_init(ICH9_LPCPmRegs *pm,