iwlwifi: pcie: fix SPLC structure parsing
authorLuca Coelho <luciano.coelho@intel.com>
Thu, 13 Oct 2016 07:07:07 +0000 (10:07 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 26 Nov 2016 08:54:53 +0000 (09:54 +0100)
commit e0d9727c111a5917a1184c71c1a8e6f78c7fc41d upstream.

The SPLC data parsing is too restrictive and was not trying find the
correct element for WiFi.  This causes problems with some BIOSes where
the SPLC method exists, but doesn't have a WiFi entry on the first
element of the list.  The domain type values are also incorrect
according to the specification.

Fix this by complying with the actual specification.

Additionally, replace all occurrences of SPLX to SPLC, since SPLX is
only a structure internal to the ACPI tables, and may not even exist.

Fixes: bcb079a14d75 ("iwlwifi: pcie: retrieve and parse ACPI power limitations")
Reported-by: Chris Rorvick <chris@rorvick.com>
Tested-by: Paul Bolle <pebolle@tiscali.nl>
Tested-by: Chris Rorvick <chris@rorvick.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/net/wireless/iwlwifi/pcie/drv.c

index d58c094f2f044a67f7a4bb2d0fab0b3784ad7131..f7e6a09926dd4a939c87af63fb2cf08cbbc0baf8 100644 (file)
@@ -475,48 +475,64 @@ static const struct pci_device_id iwl_hw_card_ids[] = {
 MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids);
 
 #ifdef CONFIG_ACPI
-#define SPL_METHOD             "SPLC"
-#define SPL_DOMAINTYPE_MODULE  BIT(0)
-#define SPL_DOMAINTYPE_WIFI    BIT(1)
-#define SPL_DOMAINTYPE_WIGIG   BIT(2)
-#define SPL_DOMAINTYPE_RFEM    BIT(3)
+#define ACPI_SPLC_METHOD       "SPLC"
+#define ACPI_SPLC_DOMAIN_WIFI  (0x07)
 
-static u64 splx_get_pwr_limit(struct iwl_trans *trans, union acpi_object *splx)
+static u64 splc_get_pwr_limit(struct iwl_trans *trans, union acpi_object *splc)
 {
-       union acpi_object *limits, *domain_type, *power_limit;
-
-       if (splx->type != ACPI_TYPE_PACKAGE ||
-           splx->package.count != 2 ||
-           splx->package.elements[0].type != ACPI_TYPE_INTEGER ||
-           splx->package.elements[0].integer.value != 0) {
-               IWL_ERR(trans, "Unsupported splx structure\n");
+       union acpi_object *data_pkg, *dflt_pwr_limit;
+       int i;
+
+       /* We need at least two elements, one for the revision and one
+        * for the data itself.  Also check that the revision is
+        * supported (currently only revision 0).
+       */
+       if (splc->type != ACPI_TYPE_PACKAGE ||
+           splc->package.count < 2 ||
+           splc->package.elements[0].type != ACPI_TYPE_INTEGER ||
+           splc->package.elements[0].integer.value != 0) {
+               IWL_DEBUG_INFO(trans,
+                              "Unsupported structure returned by the SPLC method.  Ignoring.\n");
                return 0;
        }
 
-       limits = &splx->package.elements[1];
-       if (limits->type != ACPI_TYPE_PACKAGE ||
-           limits->package.count < 2 ||
-           limits->package.elements[0].type != ACPI_TYPE_INTEGER ||
-           limits->package.elements[1].type != ACPI_TYPE_INTEGER) {
-               IWL_ERR(trans, "Invalid limits element\n");
-               return 0;
+       /* loop through all the packages to find the one for WiFi */
+       for (i = 1; i < splc->package.count; i++) {
+               union acpi_object *domain;
+
+               data_pkg = &splc->package.elements[i];
+
+               /* Skip anything that is not a package with the right
+                * amount of elements (i.e. at least 2 integers).
+                */
+               if (data_pkg->type != ACPI_TYPE_PACKAGE ||
+                   data_pkg->package.count < 2 ||
+                   data_pkg->package.elements[0].type != ACPI_TYPE_INTEGER ||
+                   data_pkg->package.elements[1].type != ACPI_TYPE_INTEGER)
+                       continue;
+
+               domain = &data_pkg->package.elements[0];
+               if (domain->integer.value == ACPI_SPLC_DOMAIN_WIFI)
+                       break;
+
+               data_pkg = NULL;
        }
 
-       domain_type = &limits->package.elements[0];
-       power_limit = &limits->package.elements[1];
-       if (!(domain_type->integer.value & SPL_DOMAINTYPE_WIFI)) {
-               IWL_DEBUG_INFO(trans, "WiFi power is not limited\n");
+       if (!data_pkg) {
+               IWL_DEBUG_INFO(trans,
+                              "No element for the WiFi domain returned by the SPLC method.\n");
                return 0;
        }
 
-       return power_limit->integer.value;
+       dflt_pwr_limit = &data_pkg->package.elements[1];
+       return dflt_pwr_limit->integer.value;
 }
 
 static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev)
 {
        acpi_handle pxsx_handle;
        acpi_handle handle;
-       struct acpi_buffer splx = {ACPI_ALLOCATE_BUFFER, NULL};
+       struct acpi_buffer splc = {ACPI_ALLOCATE_BUFFER, NULL};
        acpi_status status;
 
        pxsx_handle = ACPI_HANDLE(&pdev->dev);
@@ -527,23 +543,24 @@ static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev)
        }
 
        /* Get the method's handle */
-       status = acpi_get_handle(pxsx_handle, (acpi_string)SPL_METHOD, &handle);
+       status = acpi_get_handle(pxsx_handle, (acpi_string)ACPI_SPLC_METHOD,
+                                &handle);
        if (ACPI_FAILURE(status)) {
-               IWL_DEBUG_INFO(trans, "SPL method not found\n");
+               IWL_DEBUG_INFO(trans, "SPLC method not found\n");
                return;
        }
 
        /* Call SPLC with no arguments */
-       status = acpi_evaluate_object(handle, NULL, NULL, &splx);
+       status = acpi_evaluate_object(handle, NULL, NULL, &splc);
        if (ACPI_FAILURE(status)) {
                IWL_ERR(trans, "SPLC invocation failed (0x%x)\n", status);
                return;
        }
 
-       trans->dflt_pwr_limit = splx_get_pwr_limit(trans, splx.pointer);
+       trans->dflt_pwr_limit = splc_get_pwr_limit(trans, splc.pointer);
        IWL_DEBUG_INFO(trans, "Default power limit set to %lld\n",
                       trans->dflt_pwr_limit);
-       kfree(splx.pointer);
+       kfree(splc.pointer);
 }
 
 #else /* CONFIG_ACPI */