Commit e0d9727c authored by Luca Coelho's avatar Luca Coelho

iwlwifi: pcie: fix SPLC structure parsing

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: bcb079a1 ("iwlwifi: pcie: retrieve and parse ACPI power limitations")
Reported-by: default avatarChris Rorvick <chris@rorvick.com>
Tested-by: default avatarPaul Bolle <pebolle@tiscali.nl>
Tested-by: default avatarChris Rorvick <chris@rorvick.com>
Signed-off-by: default avatarLuca Coelho <luciano.coelho@intel.com>
parent 3a732c65
...@@ -541,48 +541,64 @@ static const struct pci_device_id iwl_hw_card_ids[] = { ...@@ -541,48 +541,64 @@ static const struct pci_device_id iwl_hw_card_ids[] = {
MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids); MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids);
#ifdef CONFIG_ACPI #ifdef CONFIG_ACPI
#define SPL_METHOD "SPLC" #define ACPI_SPLC_METHOD "SPLC"
#define SPL_DOMAINTYPE_MODULE BIT(0) #define ACPI_SPLC_DOMAIN_WIFI (0x07)
#define SPL_DOMAINTYPE_WIFI BIT(1)
#define SPL_DOMAINTYPE_WIGIG BIT(2)
#define SPL_DOMAINTYPE_RFEM BIT(3)
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; union acpi_object *data_pkg, *dflt_pwr_limit;
int i;
if (splx->type != ACPI_TYPE_PACKAGE ||
splx->package.count != 2 || /* We need at least two elements, one for the revision and one
splx->package.elements[0].type != ACPI_TYPE_INTEGER || * for the data itself. Also check that the revision is
splx->package.elements[0].integer.value != 0) { * supported (currently only revision 0).
IWL_ERR(trans, "Unsupported splx structure\n"); */
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; return 0;
} }
limits = &splx->package.elements[1]; /* loop through all the packages to find the one for WiFi */
if (limits->type != ACPI_TYPE_PACKAGE || for (i = 1; i < splc->package.count; i++) {
limits->package.count < 2 || union acpi_object *domain;
limits->package.elements[0].type != ACPI_TYPE_INTEGER ||
limits->package.elements[1].type != ACPI_TYPE_INTEGER) { data_pkg = &splc->package.elements[i];
IWL_ERR(trans, "Invalid limits element\n");
return 0; /* 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]; if (!data_pkg) {
power_limit = &limits->package.elements[1]; IWL_DEBUG_INFO(trans,
if (!(domain_type->integer.value & SPL_DOMAINTYPE_WIFI)) { "No element for the WiFi domain returned by the SPLC method.\n");
IWL_DEBUG_INFO(trans, "WiFi power is not limited\n");
return 0; 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) static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev)
{ {
acpi_handle pxsx_handle; acpi_handle pxsx_handle;
acpi_handle handle; acpi_handle handle;
struct acpi_buffer splx = {ACPI_ALLOCATE_BUFFER, NULL}; struct acpi_buffer splc = {ACPI_ALLOCATE_BUFFER, NULL};
acpi_status status; acpi_status status;
pxsx_handle = ACPI_HANDLE(&pdev->dev); pxsx_handle = ACPI_HANDLE(&pdev->dev);
...@@ -593,23 +609,24 @@ static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev) ...@@ -593,23 +609,24 @@ static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev)
} }
/* Get the method's handle */ /* 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)) { if (ACPI_FAILURE(status)) {
IWL_DEBUG_INFO(trans, "SPL method not found\n"); IWL_DEBUG_INFO(trans, "SPLC method not found\n");
return; return;
} }
/* Call SPLC with no arguments */ /* 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)) { if (ACPI_FAILURE(status)) {
IWL_ERR(trans, "SPLC invocation failed (0x%x)\n", status); IWL_ERR(trans, "SPLC invocation failed (0x%x)\n", status);
return; 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", IWL_DEBUG_INFO(trans, "Default power limit set to %lld\n",
trans->dflt_pwr_limit); trans->dflt_pwr_limit);
kfree(splx.pointer); kfree(splc.pointer);
} }
#else /* CONFIG_ACPI */ #else /* CONFIG_ACPI */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment