*/
#include <linux/acpi.h>
+#include <linux/pci.h>
+#include <linux/pci_hotplug.h>
#include "pciehp.h"
#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");
-
-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;
-}
+ "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");
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;
}
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;
+ struct pci_bus *pbus = pdev->subordinate;
+ 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 && acpi_pci_detect_ejectable(pbus))
+ 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");
}