From 6038bee87814abb9b88c762d7e6a9e5b566acb0b Mon Sep 17 00:00:00 2001 Message-Id: <6038bee87814abb9b88c762d7e6a9e5b566acb0b.1344844502.git.minovotn@redhat.com> In-Reply-To: <0c740d356f8d30310bcff2b06cfefad0e72769e2.1344844502.git.minovotn@redhat.com> References: <0c740d356f8d30310bcff2b06cfefad0e72769e2.1344844502.git.minovotn@redhat.com> From: Alex Williamson Date: Tue, 7 Aug 2012 19:41:21 +0200 Subject: [PATCH 2/5] acpi_piix4: Fix PCI hotplug race RH-Author: Alex Williamson Message-id: <20120807194121.6021.12306.stgit@bling.home> Patchwork-id: 40606 O-Subject: [RHEL6.4 qemu-kvm PATCH 2/5] acpi_piix4: Fix PCI hotplug race Bugzilla: 807391 RH-Acked-by: Michael S. Tsirkin RH-Acked-by: Igor Mammedov RH-Acked-by: Don Dutile Bugzilla: 807391 Upstream commit: 7faa8075d898ae56d2c533c530569bb25ab86eaf As Michael Tsirkin demonstrated, current PCI hotplug is vulnerable to a few races. The first is a race with other hotplug operations because we clear the up & down registers at each event. If a new event comes before the last is processed, up/down is cleared and the event is lost. To fix this for the down register, we create a life cycle for the event request that starts with the hot unplug request in piix4_device_hotplug() and ends when the device is ejected. This allows us to mask and clear individual bits, preserving them against races. For the up register, we have no clear end point for when the event is finished. We could modify the BIOS to acknowledge the bit and clear it, but this creates BIOS compatibiliy issues without offering a complete solution. Instead we note that gratuitous ACPI device checks are not harmful, which allows us to issue a device check for every slot. We know which slots are present and we know which slots are hotpluggable, so we can easily reduce this to a more manageable set for the guest. The other race Michael noted was that an unplug request followed by reset may also lose the eject notification, which may also result in the eject request being lost which a subsequent add or remove. Once we're in reset, the device is unused and we can flush the queue of device removals ourselves. Previously if a device_del was issued to a guest without ACPI PCI hotplug support, it was necessary to shutdown the guest to recover the device. With this, a guest reboot is sufficient. Signed-off-by: Alex Williamson Signed-off-by: Michael S. Tsirkin --- hw/acpi.c | 88 +++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 29 deletions(-) Signed-off-by: Michal Novotny --- hw/acpi.c | 88 ++++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/hw/acpi.c b/hw/acpi.c index 31f4a61..54d4d91 100644 --- a/hw/acpi.c +++ b/hw/acpi.c @@ -51,7 +51,7 @@ struct gpe_regs { }; struct pci_status { - uint32_t up; + uint32_t up; /* deprecated, maintained for migration compatibility */ uint32_t down; }; @@ -83,6 +83,7 @@ typedef struct PIIX4PMState { struct gpe_regs gpe; struct pci_status pci0_status; uint32_t pci0_hotplug_enable; + uint32_t pci0_slot_device_present; } PIIX4PMState; #define RSM_STS (1 << 15) @@ -497,6 +498,17 @@ static void pm_write_config(PCIDevice *d, pm_io_space_update((PIIX4PMState *)d); } +static void vmstate_pci_status_pre_save(void *opaque) +{ + struct pci_status *pci0_status = opaque; + PIIX4PMState *s = container_of(pci0_status, PIIX4PMState, pci0_status); + + /* We no longer track up, so build a safe value for migrating + * to a version that still does... of course these might get lost + * by an old buggy implementation, but we try. */ + pci0_status->up = s->pci0_slot_device_present & s->pci0_hotplug_enable; +} + static int vmstate_acpi_post_load(void *opaque, int version_id) { PIIX4PMState *s = opaque; @@ -522,6 +534,7 @@ static const VMStateDescription vmstate_pci_status = { .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, + .pre_save = vmstate_pci_status_pre_save, .fields = (VMStateField []) { VMSTATE_UINT32(up, struct pci_status), VMSTATE_UINT32(down, struct pci_status), @@ -551,13 +564,39 @@ static const VMStateDescription vmstate_acpi = { } }; +static void acpi_piix_eject_slot(PIIX4PMState *s, unsigned slots) +{ + DeviceState *qdev, *next; + BusState *bus = qdev_get_parent_bus(&s->dev.qdev); + int slot = ffs(slots) - 1; + + /* Mark request as complete */ + s->pci0_status.down &= ~(1U << slot); + + QTAILQ_FOREACH_SAFE(qdev, &bus->children, sibling, next) { + PCIDevice *dev = DO_UPCAST(PCIDevice, qdev, qdev); + PCIDeviceInfo *info = container_of(qdev->info, PCIDeviceInfo, qdev); + if (PCI_SLOT(dev->devfn) == slot && !info->no_hotplug) { + s->pci0_slot_device_present &= ~(1U << slot); + qdev_free(qdev); + } + } + +} + static void piix4_update_hotplug(PIIX4PMState *s) { PCIDevice *dev = &s->dev; BusState *bus = qdev_get_parent_bus(&dev->qdev); DeviceState *qdev, *next; + /* Execute any pending removes during reset */ + while (s->pci0_status.down) { + acpi_piix_eject_slot(s, s->pci0_status.down); + } + s->pci0_hotplug_enable = ~0; + s->pci0_slot_device_present = 0; QTAILQ_FOREACH_SAFE(qdev, &bus->children, sibling, next) { PCIDeviceInfo *info = container_of(qdev->info, PCIDeviceInfo, qdev); @@ -565,8 +604,10 @@ static void piix4_update_hotplug(PIIX4PMState *s) int slot = PCI_SLOT(pdev->devfn); if (info->no_hotplug) { - s->pci0_hotplug_enable &= ~(1 << slot); + s->pci0_hotplug_enable &= ~(1U << slot); } + + s->pci0_slot_device_present |= (1U << slot); } } @@ -798,7 +839,11 @@ static void gpe_writeb(void *opaque, uint32_t addr, uint32_t val) static uint32_t pci_up_read(void *opaque, uint32_t addr) { PIIX4PMState *s = opaque; - uint32_t val = s->pci0_status.up; + 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 = s->pci0_slot_device_present & s->pci0_hotplug_enable; #if defined(DEBUG) printf("pci_up_read %x\n", val); @@ -827,20 +872,7 @@ static uint32_t pciej_read(void *opaque, uint32_t addr) static void pciej_write(void *opaque, uint32_t addr, uint32_t val) { - BusState *bus = opaque; - DeviceState *qdev, *next; - PCIDevice *dev; - PCIDeviceInfo *info; - int slot = ffs(val) - 1; - - QTAILQ_FOREACH_SAFE(qdev, &bus->children, sibling, next) { - dev = DO_UPCAST(PCIDevice, qdev, qdev); - info = container_of(qdev->info, PCIDeviceInfo, qdev); - if (PCI_SLOT(dev->devfn) == slot && !info->no_hotplug) { - qdev_free(qdev); - } - } - + acpi_piix_eject_slot(opaque, val); #if defined(DEBUG) printf("pciej write %x <== %d\n", addr, val); @@ -881,8 +913,8 @@ void piix4_acpi_system_hot_add_init(PCIBus *bus, const char *cpu_model) register_ioport_read(PCI_UP_BASE, 4, 4, pci_up_read, pm_state); register_ioport_read(PCI_DOWN_BASE, 4, 4, pci_down_read, pm_state); - register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, bus); - register_ioport_read(PCI_EJ_BASE, 4, 4, pciej_read, bus); + register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, pm_state); + register_ioport_read(PCI_EJ_BASE, 4, 4, pciej_read, pm_state); register_ioport_write(PCI_RMV_BASE, 4, 4, pcirmv_write, pm_state); register_ioport_read(PCI_RMV_BASE, 4, 4, pcirmv_read, pm_state); @@ -950,28 +982,26 @@ void qemu_system_cpu_hot_add(int cpu, int state, Monitor *mon) } #endif -static void enable_device(struct pci_status *p, struct gpe_regs *g, int slot) +static void enable_device(PIIX4PMState *s, int slot) { - g->sts |= PIIX4_PCI_HOTPLUG_STATUS; - p->up |= (1 << slot); + s->gpe.sts |= PIIX4_PCI_HOTPLUG_STATUS; + s->pci0_slot_device_present |= (1U << slot); } -static void disable_device(struct pci_status *p, struct gpe_regs *g, int slot) +static void disable_device(PIIX4PMState *s, int slot) { - g->sts |= PIIX4_PCI_HOTPLUG_STATUS; - p->down |= (1 << slot); + s->gpe.sts |= PIIX4_PCI_HOTPLUG_STATUS; + s->pci0_status.down |= (1U << slot); } static int piix4_device_hotplug(PCIDevice *dev, int state) { int slot = PCI_SLOT(dev->devfn); - pm_state->pci0_status.up = 0; - pm_state->pci0_status.down = 0; if (state) - enable_device(&pm_state->pci0_status, &pm_state->gpe, slot); + enable_device(pm_state, slot); else - disable_device(&pm_state->pci0_status, &pm_state->gpe, slot); + disable_device(pm_state, slot); pm_update_sci(pm_state); -- 1.7.11.2