[PATCH] pciehp: clean-up how we request control of hotplug hardware
This patch further tweaks how we request control of hotplug
controller hardware from BIOS. We first search the ACPI namespace
corresponding to a specific hotplug controller looking for an
_OSC or OSHP method. On failure, we successively move to the
ACPI parent object, till we hit the highest level host bridge
in the hierarchy. This allows for different types of BIOS's
which place the _OSC/OSHP methods at various places in the acpi
namespace, while still not encroaching on the namespace of
some other root level host bridge.
This patch also introduces a new load time option (pciehp_force)
that allows us to bypass all _OSC/OSHP checking. Not supporting
these methods seems to be be the most common ACPI firmware problem
we've run into. This will still _not_ allow the pciehp driver to
work correctly if the BIOS really doesn't support pciehp (i.e. if
it doesn't generate a hotplug interrupt). Use this option with
caution. Some BIOS's may deliberately not build any _OSC/OSHP
methods to make sure it retains control the hotplug hardware.
Using the pciehp_force parameter for such systems can lead to
two separate entities trying to control the same hardware.
Signed-off-by: Rajesh Shah <rajesh.shah@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h
index e1c2cea..e71f783 100644
--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -40,6 +40,7 @@
extern int pciehp_poll_mode;
extern int pciehp_poll_time;
extern int pciehp_debug;
+extern int pciehp_force;
/*#define dbg(format, arg...) do { if (pciehp_debug) printk(KERN_DEBUG "%s: " format, MY_NAME , ## arg); } while (0)*/
#define dbg(format, arg...) do { if (pciehp_debug) printk("%s: " format, MY_NAME , ## arg); } while (0)
diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c
index 0bfc373..8df7048 100644
--- a/drivers/pci/hotplug/pciehp_core.c
+++ b/drivers/pci/hotplug/pciehp_core.c
@@ -39,6 +39,7 @@
int pciehp_debug;
int pciehp_poll_mode;
int pciehp_poll_time;
+int pciehp_force;
struct controller *pciehp_ctrl_list;
#define DRIVER_VERSION "0.4"
@@ -52,9 +53,11 @@
module_param(pciehp_debug, bool, 0644);
module_param(pciehp_poll_mode, bool, 0644);
module_param(pciehp_poll_time, int, 0644);
+module_param(pciehp_force, bool, 0644);
MODULE_PARM_DESC(pciehp_debug, "Debugging mode enabled or not");
MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not");
MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds");
+MODULE_PARM_DESC(pciehp_force, "Force pciehp, even if _OSC and OSHP are missing");
#define PCIE_MODULE_NAME "pciehp"
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 5c812b8..2d2539b 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -1417,9 +1417,14 @@
goto abort_free_ctlr;
}
- rc = get_hp_hw_control_from_firmware(ctrl->pci_dev);
- if (rc)
- goto abort_free_ctlr;
+ if (pciehp_force) {
+ dbg("Bypassing BIOS check for pciehp use on %s\n",
+ pci_name(ctrl->pci_dev));
+ } else {
+ rc = get_hp_hw_control_from_firmware(ctrl->pci_dev);
+ if (rc)
+ goto abort_free_ctlr;
+ }
/* Add this HPC instance into the HPC list */
spin_lock(&list_lock);
diff --git a/drivers/pci/hotplug/pciehprm_acpi.c b/drivers/pci/hotplug/pciehprm_acpi.c
index 5acdae3..4d013f8 100644
--- a/drivers/pci/hotplug/pciehprm_acpi.c
+++ b/drivers/pci/hotplug/pciehprm_acpi.c
@@ -132,7 +132,7 @@
/* run OSHP */
status = acpi_evaluate_object(handle, METHOD_NAME_OSHP, NULL, NULL);
if (ACPI_FAILURE(status)) {
- err("%s:%s OSHP fails=0x%x\n", __FUNCTION__, path_name,
+ dbg("%s:%s OSHP fails=0x%x\n", __FUNCTION__, path_name,
status);
} else {
dbg("%s:%s OSHP passes\n", __FUNCTION__, path_name);
@@ -140,32 +140,92 @@
return status;
}
+static int is_root_bridge(acpi_handle handle)
+{
+ acpi_status status;
+ struct acpi_device_info *info;
+ struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+ int i;
+
+ status = acpi_get_object_info(handle, &buffer);
+ if (ACPI_SUCCESS(status)) {
+ info = buffer.pointer;
+ if ((info->valid & ACPI_VALID_HID) &&
+ !strcmp(PCI_ROOT_HID_STRING,
+ info->hardware_id.value)) {
+ acpi_os_free(buffer.pointer);
+ return 1;
+ }
+ if (info->valid & ACPI_VALID_CID) {
+ for (i=0; i < info->compatibility_id.count; i++) {
+ if (!strcmp(PCI_ROOT_HID_STRING,
+ info->compatibility_id.id[i].value)) {
+ acpi_os_free(buffer.pointer);
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
int get_hp_hw_control_from_firmware(struct pci_dev *dev)
{
acpi_status status;
- acpi_handle handle = DEVICE_ACPI_HANDLE(&(dev->dev));
+ acpi_handle chandle, handle = DEVICE_ACPI_HANDLE(&(dev->dev));
+ struct pci_dev *pdev = dev;
+ u8 *path_name;
/*
* Per PCI firmware specification, we should run the ACPI _OSC
- * method to get control of hotplug hardware before using it
+ * method to get control of hotplug hardware before using it.
+ * If an _OSC is missing, we look for an OSHP to do the same thing.
+ * To handle different BIOS behavior, we look for _OSC and OSHP
+ * within the scope of the hotplug controller and its parents, upto
+ * the host bridge under which this controller exists.
*/
- status = pci_osc_control_set(handle,
- OSC_PCI_EXPRESS_NATIVE_HP_CONTROL);
-
- /* Fixme: fail native hotplug if _OSC does not exist for root ports */
- if (status == AE_NOT_FOUND) {
+ while (!handle) {
/*
- * Some older BIOS's don't support _OSC but support
- * OSHP to do the same thing
+ * This hotplug controller was not listed in the ACPI name
+ * space at all. Try to get acpi handle of parent pci bus.
*/
- status = acpi_run_oshp(handle);
- }
- if (ACPI_FAILURE(status)) {
- err("Cannot get control of hotplug hardware\n");
- return -1;
+ if (!pdev || !pdev->bus->parent)
+ break;
+ dbg("Could not find %s in acpi namespace, trying parent\n",
+ pci_name(pdev));
+ if (!pdev->bus->parent->self)
+ /* Parent must be a host bridge */
+ handle = acpi_get_pci_rootbridge_handle(
+ pci_domain_nr(pdev->bus->parent),
+ pdev->bus->parent->number);
+ else
+ handle = DEVICE_ACPI_HANDLE(
+ &(pdev->bus->parent->self->dev));
+ pdev = pdev->bus->parent->self;
}
- dbg("Sucess getting control of hotplug hardware\n");
- return 0;
+ while (handle) {
+ path_name = acpi_path_name(handle);
+ dbg("Trying to get hotplug control for %s \n", path_name);
+ status = pci_osc_control_set(handle,
+ OSC_PCI_EXPRESS_NATIVE_HP_CONTROL);
+ if (status == AE_NOT_FOUND)
+ status = acpi_run_oshp(handle);
+ if (ACPI_SUCCESS(status)) {
+ dbg("Gained control for hotplug HW for pci %s (%s)\n",
+ pci_name(dev), path_name);
+ return 0;
+ }
+ if (is_root_bridge(handle))
+ break;
+ chandle = handle;
+ status = acpi_get_parent(chandle, &handle);
+ if (ACPI_FAILURE(status))
+ break;
+ }
+
+ err("Cannot get control of hotplug hardware for pci %s\n",
+ pci_name(dev));
+ return -1;
}
void get_hp_params_from_firmware(struct pci_dev *dev,