From c9ffa5a586a97da4d552f89b8f39eea79a63a612 Mon Sep 17 00:00:00 2001 From: Kenji Kaneshige Date: Wed, 17 Dec 2008 12:07:38 +0900 Subject: PCI: pciehp: add ACPI based slot detection There is a problem that some non hot-pluggable PCIe slots are detected as hot-pluggable by pciehp on some platforms. The immediate cause of this problem is that hot-plug capable bit in the Slot Capabilities register is set even for non hot-pluggable slots on those platforms. It seems a BIOS/hardware problem, but we need workaround about that. Some of those platforms define hot-pluggable PCIe slots on ACPI namespace properly, while hot-plug capable bit in the Slot Capabilities register is set improperly. So using ACPI namespace information in pciehp to detect PCIe hot-pluggable slots would be a workaround. This patch adds 'pciehp_detect_mode' module option. When 'acpi' is specified, pciehp uses ACPI namespace information to detect PCIe hot-pluggable slots. Signed-off-by: Kenji Kaneshige Signed-off-by: Jesse Barnes --- drivers/pci/hotplug/pciehp_acpi.c | 114 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 drivers/pci/hotplug/pciehp_acpi.c (limited to 'drivers/pci/hotplug/pciehp_acpi.c') diff --git a/drivers/pci/hotplug/pciehp_acpi.c b/drivers/pci/hotplug/pciehp_acpi.c new file mode 100644 index 00000000000..0cd49b72804 --- /dev/null +++ b/drivers/pci/hotplug/pciehp_acpi.c @@ -0,0 +1,114 @@ +/* + * ACPI related functions for PCI Express Hot Plug driver. + * + * Copyright (C) 2008 Kenji Kaneshige + * Copyright (C) 2008 Fujitsu Limited. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include "pciehp.h" + +#define PCIEHP_DETECT_PCIE (0) +#define PCIEHP_DETECT_ACPI (1) +#define PCIEHP_DETECT_DEFAULT PCIEHP_DETECT_PCIE + +static int slot_detection_mode; +static char *pciehp_detect_mode; +module_param(pciehp_detect_mode, charp, 0444); +MODULE_PARM_DESC(pciehp_detect_mode, + "Slot detection mode: pcie, acpi\n" + " pcie - Use PCIe based slot detection (default)\n" + " acpi - Use ACPI for slot detection\n"); + +static int is_ejectable(acpi_handle handle) +{ + acpi_status status; + acpi_handle tmp; + unsigned long long removable; + status = acpi_get_handle(handle, "_ADR", &tmp); + if (ACPI_FAILURE(status)) + return 0; + status = acpi_get_handle(handle, "_EJ0", &tmp); + if (ACPI_SUCCESS(status)) + return 1; + status = acpi_evaluate_integer(handle, "_RMV", NULL, &removable); + if (ACPI_SUCCESS(status) && removable) + return 1; + return 0; +} + +static acpi_status +check_hotplug(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + int *found = (int *)context; + if (is_ejectable(handle)) { + *found = 1; + return AE_CTRL_TERMINATE; + } + return AE_OK; +} + +static int pciehp_detect_acpi_slot(struct pci_bus *pbus) +{ + acpi_handle handle; + struct pci_dev *pdev = pbus->self; + int found = 0; + + if (!pdev){ + int seg = pci_domain_nr(pbus), busnr = pbus->number; + handle = acpi_get_pci_rootbridge_handle(seg, busnr); + } else + handle = DEVICE_ACPI_HANDLE(&(pdev->dev)); + + if (!handle) + return 0; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1, + check_hotplug, (void *)&found, NULL); + return found; +} + +int pciehp_acpi_slot_detection_check(struct pci_dev *dev) +{ + if (slot_detection_mode != PCIEHP_DETECT_ACPI) + return 0; + if (pciehp_detect_acpi_slot(dev->subordinate)) + return 0; + return -ENODEV; +} + +static int __init parse_detect_mode(void) +{ + if (!pciehp_detect_mode) + return PCIEHP_DETECT_DEFAULT; + if (!strcmp(pciehp_detect_mode, "pcie")) + return PCIEHP_DETECT_PCIE; + if (!strcmp(pciehp_detect_mode, "acpi")) + return PCIEHP_DETECT_ACPI; + warn("bad specifier '%s' for pciehp_detect_mode. Use default\n", + pciehp_detect_mode); + return PCIEHP_DETECT_DEFAULT; +} + +void __init pciehp_acpi_slot_detection_init(void) +{ + slot_detection_mode = parse_detect_mode(); +} -- cgit v1.2.3-18-g5258 From e046cbd6c05ee859244245d7beeac395cd0057b3 Mon Sep 17 00:00:00 2001 From: Kenji Kaneshige Date: Wed, 17 Dec 2008 12:08:15 +0900 Subject: PCI: pciehp: add auto option to pciehp_detect_mode ACPI based hot-pluggable PCIe slot detection logic was added to prevent the problem non hot-pluggable PCIe slot was detected as hot-pluggable. The slot detection logic can be selected through 'pciehp_detect_mode', but it would be better if it is selected automatically. This patch adds 'auto' option for 'pciehp_detect_mode'. When it is specified, pciehp judges which 'acpi' or 'pcie' should be used. It seems that the physical slot number is duplicated among some slots on most of the platforms with the above-mentioned problem. So 'auto' mode uses this information to judge which 'acpi' or 'pcie' should be used. That is, if duplicated physical slot numbers are detected, 'acpi' mode is used. This method is not perfect, but it's realistic. Signed-off-by: Kenji Kaneshige Signed-off-by: Jesse Barnes --- drivers/pci/hotplug/pciehp_acpi.c | 80 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) (limited to 'drivers/pci/hotplug/pciehp_acpi.c') diff --git a/drivers/pci/hotplug/pciehp_acpi.c b/drivers/pci/hotplug/pciehp_acpi.c index 0cd49b72804..88a5c57f2e5 100644 --- a/drivers/pci/hotplug/pciehp_acpi.c +++ b/drivers/pci/hotplug/pciehp_acpi.c @@ -28,15 +28,18 @@ #define PCIEHP_DETECT_PCIE (0) #define PCIEHP_DETECT_ACPI (1) -#define PCIEHP_DETECT_DEFAULT PCIEHP_DETECT_PCIE +#define PCIEHP_DETECT_AUTO (2) +#define PCIEHP_DETECT_DEFAULT PCIEHP_DETECT_AUTO static int slot_detection_mode; static char *pciehp_detect_mode; module_param(pciehp_detect_mode, charp, 0444); MODULE_PARM_DESC(pciehp_detect_mode, - "Slot detection mode: pcie, acpi\n" - " pcie - Use PCIe based slot detection (default)\n" - " acpi - Use ACPI for slot detection\n"); + "Slot detection mode: pcie, acpi, auto\n" + " pcie - Use PCIe based slot detection\n" + " acpi - Use ACPI for slot detection\n" + " auto(default) - Auto select mode. Use acpi option if duplicate\n" + " slot ids are found. Otherwise, use pcie option\n"); static int is_ejectable(acpi_handle handle) { @@ -103,12 +106,81 @@ static int __init parse_detect_mode(void) return PCIEHP_DETECT_PCIE; if (!strcmp(pciehp_detect_mode, "acpi")) return PCIEHP_DETECT_ACPI; + if (!strcmp(pciehp_detect_mode, "auto")) + return PCIEHP_DETECT_AUTO; warn("bad specifier '%s' for pciehp_detect_mode. Use default\n", pciehp_detect_mode); return PCIEHP_DETECT_DEFAULT; } +static struct pcie_port_service_id __initdata port_pci_ids[] = { + { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .port_type = PCIE_ANY_PORT, + .service_type = PCIE_PORT_SERVICE_HP, + .driver_data = 0, + }, { /* end: all zeroes */ } +}; + +static int __initdata dup_slot_id; +static int __initdata acpi_slot_detected; +static struct list_head __initdata dummy_slots = LIST_HEAD_INIT(dummy_slots); + +/* Dummy driver for dumplicate name detection */ +static int __init dummy_probe(struct pcie_device *dev, + const struct pcie_port_service_id *id) +{ + int pos; + u32 slot_cap; + struct slot *slot, *tmp; + struct pci_dev *pdev = dev->port; + if (!(slot = kzalloc(sizeof(*slot), GFP_KERNEL))) + return -ENOMEM; + /* Note: pciehp_detect_mode != PCIEHP_DETECT_ACPI here */ + if (pciehp_get_hp_hw_control_from_firmware(pdev)) + return -ENODEV; + if (!(pos = pci_find_capability(pdev, PCI_CAP_ID_EXP))) + return -ENODEV; + pci_read_config_dword(pdev, pos + PCI_EXP_SLTCAP, &slot_cap); + slot->number = slot_cap >> 19; + list_for_each_entry(tmp, &dummy_slots, slot_list) { + if (tmp->number == slot->number) + dup_slot_id++; + } + list_add_tail(&slot->slot_list, &dummy_slots); + if (!acpi_slot_detected && pciehp_detect_acpi_slot(pdev->subordinate)) + acpi_slot_detected = 1; + return -ENODEV; /* dummy driver always returns error */ +} + +static struct pcie_port_service_driver __initdata dummy_driver = { + .name = "pciehp_dummy", + .id_table = port_pci_ids, + .probe = dummy_probe, +}; + +static int __init select_detection_mode(void) +{ + struct slot *slot, *tmp; + pcie_port_service_register(&dummy_driver); + pcie_port_service_unregister(&dummy_driver); + list_for_each_entry_safe(slot, tmp, &dummy_slots, slot_list) { + list_del(&slot->slot_list); + kfree(slot); + } + if (acpi_slot_detected && dup_slot_id) + return PCIEHP_DETECT_ACPI; + return PCIEHP_DETECT_PCIE; +} + void __init pciehp_acpi_slot_detection_init(void) { slot_detection_mode = parse_detect_mode(); + if (slot_detection_mode != PCIEHP_DETECT_AUTO) + goto out; + slot_detection_mode = select_detection_mode(); +out: + if (slot_detection_mode == PCIEHP_DETECT_ACPI) + info("Using ACPI for slot detection.\n"); } -- cgit v1.2.3-18-g5258 From e8c331e963c58b83db24b7d0e39e8c07f687dbc6 Mon Sep 17 00:00:00 2001 From: Kenji Kaneshige Date: Wed, 17 Dec 2008 12:09:12 +0900 Subject: PCI hotplug: introduce functions for ACPI slot detection Some ACPI related PCI hotplug code can be shared among PCI hotplug drivers. This patch introduces the following functions in drivers/pci/hotplug/acpi_pcihp.c to share the code, and changes acpiphp and pciehp to use them. - int acpi_pci_detect_ejectable(struct pci_bus *pbus) This checks if the specified PCI bus has ejectable slots. - int acpi_pci_check_ejectable(struct pci_bus *pbus, acpi_handle handle) This checks if the specified handle is ejectable ACPI PCI slot. The 'pbus' parameter is needed to check if 'handle' is PCI related ACPI object. This patch also introduces the following inline function in include/linux/pci-acpi.h, which is useful to get ACPI handle of the PCI bridge from struct pci_bus of the bridge's secondary bus. - static inline acpi_handle acpi_pci_get_bridge_handle(struct pci_bus *pbus) This returns ACPI handle of the PCI bridge which generates PCI bus specified by 'pbus'. Signed-off-by: Kenji Kaneshige Signed-off-by: Jesse Barnes --- drivers/pci/hotplug/pciehp_acpi.c | 55 ++++----------------------------------- 1 file changed, 5 insertions(+), 50 deletions(-) (limited to 'drivers/pci/hotplug/pciehp_acpi.c') diff --git a/drivers/pci/hotplug/pciehp_acpi.c b/drivers/pci/hotplug/pciehp_acpi.c index 88a5c57f2e5..438d795f9fe 100644 --- a/drivers/pci/hotplug/pciehp_acpi.c +++ b/drivers/pci/hotplug/pciehp_acpi.c @@ -24,6 +24,8 @@ */ #include +#include +#include #include "pciehp.h" #define PCIEHP_DETECT_PCIE (0) @@ -41,59 +43,11 @@ MODULE_PARM_DESC(pciehp_detect_mode, " auto(default) - Auto select mode. Use acpi option if duplicate\n" " slot ids are found. Otherwise, use pcie option\n"); -static int is_ejectable(acpi_handle handle) -{ - acpi_status status; - acpi_handle tmp; - unsigned long long removable; - status = acpi_get_handle(handle, "_ADR", &tmp); - if (ACPI_FAILURE(status)) - return 0; - status = acpi_get_handle(handle, "_EJ0", &tmp); - if (ACPI_SUCCESS(status)) - return 1; - status = acpi_evaluate_integer(handle, "_RMV", NULL, &removable); - if (ACPI_SUCCESS(status) && removable) - return 1; - return 0; -} - -static acpi_status -check_hotplug(acpi_handle handle, u32 lvl, void *context, void **rv) -{ - int *found = (int *)context; - if (is_ejectable(handle)) { - *found = 1; - return AE_CTRL_TERMINATE; - } - return AE_OK; -} - -static int pciehp_detect_acpi_slot(struct pci_bus *pbus) -{ - acpi_handle handle; - struct pci_dev *pdev = pbus->self; - int found = 0; - - if (!pdev){ - int seg = pci_domain_nr(pbus), busnr = pbus->number; - handle = acpi_get_pci_rootbridge_handle(seg, busnr); - } else - handle = DEVICE_ACPI_HANDLE(&(pdev->dev)); - - if (!handle) - return 0; - - acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, (u32)1, - check_hotplug, (void *)&found, NULL); - return found; -} - int pciehp_acpi_slot_detection_check(struct pci_dev *dev) { if (slot_detection_mode != PCIEHP_DETECT_ACPI) return 0; - if (pciehp_detect_acpi_slot(dev->subordinate)) + if (acpi_pci_detect_ejectable(dev->subordinate)) return 0; return -ENODEV; } @@ -135,6 +89,7 @@ static int __init dummy_probe(struct pcie_device *dev, u32 slot_cap; struct slot *slot, *tmp; struct pci_dev *pdev = dev->port; + struct pci_bus *pbus = pdev->subordinate; if (!(slot = kzalloc(sizeof(*slot), GFP_KERNEL))) return -ENOMEM; /* Note: pciehp_detect_mode != PCIEHP_DETECT_ACPI here */ @@ -149,7 +104,7 @@ static int __init dummy_probe(struct pcie_device *dev, dup_slot_id++; } list_add_tail(&slot->slot_list, &dummy_slots); - if (!acpi_slot_detected && pciehp_detect_acpi_slot(pdev->subordinate)) + if (!acpi_slot_detected && acpi_pci_detect_ejectable(pbus)) acpi_slot_detected = 1; return -ENODEV; /* dummy driver always returns error */ } -- cgit v1.2.3-18-g5258