diff options
Diffstat (limited to 'drivers/pci/hotplug/acpiphp_glue.c')
-rw-r--r-- | drivers/pci/hotplug/acpiphp_glue.c | 252 |
1 files changed, 204 insertions, 48 deletions
diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index d370f999782e..83e8e4412de5 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -26,7 +26,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * - * Send feedback to <t-kochi@bq.jp.nec.com> + * Send feedback to <kristen.c.accardi@intel.com> * */ @@ -53,12 +53,15 @@ #include "acpiphp.h" static LIST_HEAD(bridge_list); +static LIST_HEAD(ioapic_list); +static DEFINE_SPINLOCK(ioapic_list_lock); #define MY_NAME "acpiphp_glue" static void handle_hotplug_event_bridge (acpi_handle, u32, void *); static void acpiphp_sanitize_bus(struct pci_bus *bus); static void acpiphp_set_hpp_values(acpi_handle handle, struct pci_bus *bus); +static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context); /* @@ -116,6 +119,59 @@ is_ejectable_slot(acpi_handle handle, u32 lvl, void *context, void **rv) } } +/* callback routine to check for the existance of a pci dock device */ +static acpi_status +is_pci_dock_device(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *count = (int *)context; + + if (is_dock_device(handle)) { + (*count)++; + return AE_CTRL_TERMINATE; + } else { + return AE_OK; + } +} + + + + +/* + * the _DCK method can do funny things... and sometimes not + * hah-hah funny. + * + * TBD - figure out a way to only call fixups for + * systems that require them. + */ +static int post_dock_fixups(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct acpiphp_func *func = container_of(nb, struct acpiphp_func, nb); + struct pci_bus *bus = func->slot->bridge->pci_bus; + u32 buses; + + if (!bus->self) + return NOTIFY_OK; + + /* fixup bad _DCK function that rewrites + * secondary bridge on slot + */ + pci_read_config_dword(bus->self, + PCI_PRIMARY_BUS, + &buses); + + if (((buses >> 8) & 0xff) != bus->secondary) { + buses = (buses & 0xff000000) + | ((unsigned int)(bus->primary) << 0) + | ((unsigned int)(bus->secondary) << 8) + | ((unsigned int)(bus->subordinate) << 16); + pci_write_config_dword(bus->self, PCI_PRIMARY_BUS, buses); + } + return NOTIFY_OK; +} + + + /* callback routine to register each ACPI PCI slot object */ static acpi_status @@ -124,7 +180,6 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) struct acpiphp_bridge *bridge = (struct acpiphp_bridge *)context; struct acpiphp_slot *slot; struct acpiphp_func *newfunc; - struct dependent_device *dd; acpi_handle tmp; acpi_status status = AE_OK; unsigned long adr, sun; @@ -137,7 +192,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) status = acpi_get_handle(handle, "_EJ0", &tmp); - if (ACPI_FAILURE(status) && !(is_dependent_device(handle))) + if (ACPI_FAILURE(status) && !(is_dock_device(handle))) return AE_OK; device = (adr >> 16) & 0xffff; @@ -162,22 +217,17 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) if (ACPI_SUCCESS(acpi_get_handle(handle, "_PS3", &tmp))) newfunc->flags |= FUNC_HAS_PS3; - if (ACPI_SUCCESS(acpi_get_handle(handle, "_DCK", &tmp))) { + if (ACPI_SUCCESS(acpi_get_handle(handle, "_DCK", &tmp))) newfunc->flags |= FUNC_HAS_DCK; - /* add to devices dependent on dock station, - * because this may actually be the dock bridge - */ - dd = alloc_dependent_device(handle); - if (!dd) - err("Can't allocate memory for " - "new dependent device!\n"); - else - add_dependent_device(dd); - } status = acpi_evaluate_integer(handle, "_SUN", NULL, &sun); - if (ACPI_FAILURE(status)) - sun = -1; + if (ACPI_FAILURE(status)) { + /* + * use the count of the number of slots we've found + * for the number of the slot + */ + sun = bridge->nr_slots+1; + } /* search for objects that share the same slot */ for (slot = bridge->slots; slot; slot = slot->next) @@ -225,20 +275,23 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) slot->flags |= (SLOT_ENABLED | SLOT_POWEREDON); } - /* if this is a device dependent on a dock station, - * associate the acpiphp_func to the dependent_device - * struct. - */ - if ((dd = get_dependent_device(handle))) { - newfunc->flags |= FUNC_IS_DD; - /* - * we don't want any devices which is dependent - * on the dock to have it's _EJ0 method executed. - * because we need to run _DCK first. + if (is_dock_device(handle)) { + /* we don't want to call this device's _EJ0 + * because we want the dock notify handler + * to call it after it calls _DCK */ newfunc->flags &= ~FUNC_HAS_EJ0; - dd->func = newfunc; - add_pci_dependent_device(dd); + if (register_hotplug_dock_device(handle, + handle_hotplug_event_func, newfunc)) + dbg("failed to register dock device\n"); + + /* we need to be notified when dock events happen + * outside of the hotplug operation, since we may + * need to do fixups before we can hotplug. + */ + newfunc->nb.notifier_call = post_dock_fixups; + if (register_dock_notifier(&newfunc->nb)) + dbg("failed to register a dock notifier"); } /* install notify handler */ @@ -277,6 +330,15 @@ static int detect_ejectable_slots(acpi_handle *bridge_handle) status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge_handle, (u32)1, is_ejectable_slot, (void *)&count, NULL); + /* + * we also need to add this bridge if there is a dock bridge or + * other pci device on a dock station (removable) + */ + if (!count) + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge_handle, + (u32)1, is_pci_dock_device, (void *)&count, + NULL); + return count; } @@ -487,8 +549,7 @@ find_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv) goto out; /* check if this bridge has ejectable slots */ - if ((detect_ejectable_slots(handle) > 0) || - (detect_dependent_devices(handle) > 0)) { + if ((detect_ejectable_slots(handle) > 0)) { dbg("found PCI-to-PCI bridge at PCI %s\n", pci_name(dev)); add_p2p_bridge(handle, dev); } @@ -605,6 +666,10 @@ static void cleanup_bridge(struct acpiphp_bridge *bridge) list_for_each_safe (list, tmp, &slot->funcs) { struct acpiphp_func *func; func = list_entry(list, struct acpiphp_func, sibling); + if (is_dock_device(func->handle)) { + unregister_hotplug_dock_device(func->handle); + unregister_dock_notifier(&func->nb); + } if (!(func->flags & FUNC_HAS_DCK)) { status = acpi_remove_notify_handler(func->handle, ACPI_SYSTEM_NOTIFY, @@ -734,6 +799,7 @@ ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv) struct pci_dev *pdev; u32 gsi_base; u64 phys_addr; + struct acpiphp_ioapic *ioapic; /* Evaluate _STA if present */ status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); @@ -748,41 +814,107 @@ ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv) if (get_gsi_base(handle, &gsi_base)) return AE_OK; + ioapic = kmalloc(sizeof(*ioapic), GFP_KERNEL); + if (!ioapic) + return AE_NO_MEMORY; + pdev = get_apic_pci_info(handle); if (!pdev) - return AE_OK; + goto exit_kfree; - if (pci_enable_device(pdev)) { - pci_dev_put(pdev); - return AE_OK; - } + if (pci_enable_device(pdev)) + goto exit_pci_dev_put; pci_set_master(pdev); - if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)")) { - pci_disable_device(pdev); - pci_dev_put(pdev); - return AE_OK; - } + if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)")) + goto exit_pci_disable_device; phys_addr = pci_resource_start(pdev, 0); - if (acpi_register_ioapic(handle, phys_addr, gsi_base)) { - pci_release_region(pdev, 0); - pci_disable_device(pdev); - pci_dev_put(pdev); + if (acpi_register_ioapic(handle, phys_addr, gsi_base)) + goto exit_pci_release_region; + + ioapic->gsi_base = gsi_base; + ioapic->dev = pdev; + spin_lock(&ioapic_list_lock); + list_add_tail(&ioapic->list, &ioapic_list); + spin_unlock(&ioapic_list_lock); + + return AE_OK; + + exit_pci_release_region: + pci_release_region(pdev, 0); + exit_pci_disable_device: + pci_disable_device(pdev); + exit_pci_dev_put: + pci_dev_put(pdev); + exit_kfree: + kfree(ioapic); + + return AE_OK; +} + +static acpi_status +ioapic_remove(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + acpi_status status; + unsigned long sta; + acpi_handle tmp; + u32 gsi_base; + struct acpiphp_ioapic *pos, *n, *ioapic = NULL; + + /* Evaluate _STA if present */ + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && sta != ACPI_STA_ALL) + return AE_CTRL_DEPTH; + + /* Scan only PCI bus scope */ + status = acpi_get_handle(handle, "_HID", &tmp); + if (ACPI_SUCCESS(status)) + return AE_CTRL_DEPTH; + + if (get_gsi_base(handle, &gsi_base)) return AE_OK; + + acpi_unregister_ioapic(handle, gsi_base); + + spin_lock(&ioapic_list_lock); + list_for_each_entry_safe(pos, n, &ioapic_list, list) { + if (pos->gsi_base != gsi_base) + continue; + ioapic = pos; + list_del(&ioapic->list); + break; } + spin_unlock(&ioapic_list_lock); + + if (!ioapic) + return AE_OK; + + pci_release_region(ioapic->dev, 0); + pci_disable_device(ioapic->dev); + pci_dev_put(ioapic->dev); + kfree(ioapic); return AE_OK; } static int acpiphp_configure_ioapics(acpi_handle handle) { + ioapic_add(handle, 0, NULL, NULL); acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, ACPI_UINT32_MAX, ioapic_add, NULL, NULL); return 0; } +static int acpiphp_unconfigure_ioapics(acpi_handle handle) +{ + ioapic_remove(handle, 0, NULL, NULL); + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, + ACPI_UINT32_MAX, ioapic_remove, NULL, NULL); + return 0; +} + static int power_on_slot(struct acpiphp_slot *slot) { acpi_status status; @@ -934,7 +1066,7 @@ acpiphp_bus_add_out: * @handle: handle to acpi namespace * */ -int acpiphp_bus_trim(acpi_handle handle) +static int acpiphp_bus_trim(acpi_handle handle) { struct acpi_device *device; int retval; @@ -1011,10 +1143,11 @@ static int enable_device(struct acpiphp_slot *slot) pci_bus_assign_resources(bus); acpiphp_sanitize_bus(bus); + acpiphp_set_hpp_values(slot->bridge->handle, bus); + list_for_each_entry(func, &slot->funcs, sibling) + acpiphp_configure_ioapics(func->handle); pci_enable_bridges(bus); pci_bus_add_devices(bus); - acpiphp_set_hpp_values(slot->bridge->handle, bus); - acpiphp_configure_ioapics(slot->bridge->handle); /* associate pci_dev to our representation */ list_for_each (l, &slot->funcs) { @@ -1040,6 +1173,16 @@ static int enable_device(struct acpiphp_slot *slot) return retval; } +static void disable_bridges(struct pci_bus *bus) +{ + struct pci_dev *dev; + list_for_each_entry(dev, &bus->devices, bus_list) { + if (dev->subordinate) { + disable_bridges(dev->subordinate); + pci_disable_device(dev); + } + } +} /** * disable_device - disable a slot @@ -1064,6 +1207,19 @@ static int disable_device(struct acpiphp_slot *slot) func->bridge = NULL; } + if (func->pci_dev) { + pci_stop_bus_device(func->pci_dev); + if (func->pci_dev->subordinate) { + disable_bridges(func->pci_dev->subordinate); + pci_disable_device(func->pci_dev); + } + } + } + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + acpiphp_unconfigure_ioapics(func->handle); acpiphp_bus_trim(func->handle); /* try to remove anyway. * acpiphp_bus_add might have been failed */ @@ -1440,7 +1596,7 @@ static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *cont * handles ACPI event notification on slots * */ -void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context) +static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context) { struct acpiphp_func *func; char objname[64]; |