184c3c995SLuca Coelho // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 284c3c995SLuca Coelho /* 384c3c995SLuca Coelho * Copyright(c) 2021 Intel Corporation 484c3c995SLuca Coelho */ 584c3c995SLuca Coelho 684c3c995SLuca Coelho #include "iwl-drv.h" 784c3c995SLuca Coelho #include "pnvm.h" 884c3c995SLuca Coelho #include "iwl-prph.h" 984c3c995SLuca Coelho #include "iwl-io.h" 1084c3c995SLuca Coelho 1184c3c995SLuca Coelho #include "fw/uefi.h" 129dad325fSLuca Coelho #include "fw/api/alive.h" 1384c3c995SLuca Coelho #include <linux/efi.h> 14c593d2faSAyala Barazani #include "fw/runtime.h" 1584c3c995SLuca Coelho 1684c3c995SLuca Coelho #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \ 1784c3c995SLuca Coelho 0xb2, 0xec, 0xf5, 0xa3, \ 1884c3c995SLuca Coelho 0x59, 0x4f, 0x4a, 0xea) 1984c3c995SLuca Coelho 2084c3c995SLuca Coelho void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) 2184c3c995SLuca Coelho { 2284c3c995SLuca Coelho struct efivar_entry *pnvm_efivar; 2384c3c995SLuca Coelho void *data; 2484c3c995SLuca Coelho unsigned long package_size; 2584c3c995SLuca Coelho int err; 2684c3c995SLuca Coelho 279dad325fSLuca Coelho *len = 0; 289dad325fSLuca Coelho 2984c3c995SLuca Coelho pnvm_efivar = kzalloc(sizeof(*pnvm_efivar), GFP_KERNEL); 3084c3c995SLuca Coelho if (!pnvm_efivar) 3184c3c995SLuca Coelho return ERR_PTR(-ENOMEM); 3284c3c995SLuca Coelho 3384c3c995SLuca Coelho memcpy(&pnvm_efivar->var.VariableName, IWL_UEFI_OEM_PNVM_NAME, 3484c3c995SLuca Coelho sizeof(IWL_UEFI_OEM_PNVM_NAME)); 3584c3c995SLuca Coelho pnvm_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 3684c3c995SLuca Coelho 3784c3c995SLuca Coelho /* 3884c3c995SLuca Coelho * TODO: we hardcode a maximum length here, because reading 3984c3c995SLuca Coelho * from the UEFI is not working. To implement this properly, 4084c3c995SLuca Coelho * we have to call efivar_entry_size(). 4184c3c995SLuca Coelho */ 4284c3c995SLuca Coelho package_size = IWL_HARDCODED_PNVM_SIZE; 4384c3c995SLuca Coelho 4484c3c995SLuca Coelho data = kmalloc(package_size, GFP_KERNEL); 4584c3c995SLuca Coelho if (!data) { 4684c3c995SLuca Coelho data = ERR_PTR(-ENOMEM); 4784c3c995SLuca Coelho goto out; 4884c3c995SLuca Coelho } 4984c3c995SLuca Coelho 5084c3c995SLuca Coelho err = efivar_entry_get(pnvm_efivar, NULL, &package_size, data); 5184c3c995SLuca Coelho if (err) { 5284c3c995SLuca Coelho IWL_DEBUG_FW(trans, 531476ff21SLinus Torvalds "PNVM UEFI variable not found %d (len %lu)\n", 5484c3c995SLuca Coelho err, package_size); 5584c3c995SLuca Coelho kfree(data); 5684c3c995SLuca Coelho data = ERR_PTR(err); 5784c3c995SLuca Coelho goto out; 5884c3c995SLuca Coelho } 5984c3c995SLuca Coelho 601476ff21SLinus Torvalds IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size); 6184c3c995SLuca Coelho *len = package_size; 6284c3c995SLuca Coelho 6384c3c995SLuca Coelho out: 6484c3c995SLuca Coelho kfree(pnvm_efivar); 6584c3c995SLuca Coelho 6684c3c995SLuca Coelho return data; 6784c3c995SLuca Coelho } 689dad325fSLuca Coelho 699dad325fSLuca Coelho static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans, 709dad325fSLuca Coelho const u8 *data, size_t len) 719dad325fSLuca Coelho { 72*86e8e657SJohannes Berg const struct iwl_ucode_tlv *tlv; 739dad325fSLuca Coelho u8 *reduce_power_data = NULL, *tmp; 749dad325fSLuca Coelho u32 size = 0; 759dad325fSLuca Coelho 769dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); 779dad325fSLuca Coelho 789dad325fSLuca Coelho while (len >= sizeof(*tlv)) { 799dad325fSLuca Coelho u32 tlv_len, tlv_type; 809dad325fSLuca Coelho 819dad325fSLuca Coelho len -= sizeof(*tlv); 82*86e8e657SJohannes Berg tlv = (const void *)data; 839dad325fSLuca Coelho 849dad325fSLuca Coelho tlv_len = le32_to_cpu(tlv->length); 859dad325fSLuca Coelho tlv_type = le32_to_cpu(tlv->type); 869dad325fSLuca Coelho 879dad325fSLuca Coelho if (len < tlv_len) { 889dad325fSLuca Coelho IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 899dad325fSLuca Coelho len, tlv_len); 90a571bc28SChristophe JAILLET kfree(reduce_power_data); 919dad325fSLuca Coelho reduce_power_data = ERR_PTR(-EINVAL); 929dad325fSLuca Coelho goto out; 939dad325fSLuca Coelho } 949dad325fSLuca Coelho 959dad325fSLuca Coelho data += sizeof(*tlv); 969dad325fSLuca Coelho 979dad325fSLuca Coelho switch (tlv_type) { 989dad325fSLuca Coelho case IWL_UCODE_TLV_MEM_DESC: { 999dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1009dad325fSLuca Coelho "Got IWL_UCODE_TLV_MEM_DESC len %d\n", 1019dad325fSLuca Coelho tlv_len); 1029dad325fSLuca Coelho 1039dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); 1049dad325fSLuca Coelho 1059dad325fSLuca Coelho tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL); 1069dad325fSLuca Coelho if (!tmp) { 1079dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1089dad325fSLuca Coelho "Couldn't allocate (more) reduce_power_data\n"); 1099dad325fSLuca Coelho 110a571bc28SChristophe JAILLET kfree(reduce_power_data); 1119dad325fSLuca Coelho reduce_power_data = ERR_PTR(-ENOMEM); 1129dad325fSLuca Coelho goto out; 1139dad325fSLuca Coelho } 1149dad325fSLuca Coelho 1159dad325fSLuca Coelho reduce_power_data = tmp; 1169dad325fSLuca Coelho 1179dad325fSLuca Coelho memcpy(reduce_power_data + size, data, tlv_len); 1189dad325fSLuca Coelho 1199dad325fSLuca Coelho size += tlv_len; 1209dad325fSLuca Coelho 1219dad325fSLuca Coelho break; 1229dad325fSLuca Coelho } 1239dad325fSLuca Coelho case IWL_UCODE_TLV_PNVM_SKU: 1249dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1259dad325fSLuca Coelho "New REDUCE_POWER section started, stop parsing.\n"); 1269dad325fSLuca Coelho goto done; 1279dad325fSLuca Coelho default: 1289dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", 1299dad325fSLuca Coelho tlv_type, tlv_len); 1309dad325fSLuca Coelho break; 1319dad325fSLuca Coelho } 1329dad325fSLuca Coelho 1339dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 1349dad325fSLuca Coelho data += ALIGN(tlv_len, 4); 1359dad325fSLuca Coelho } 1369dad325fSLuca Coelho 1379dad325fSLuca Coelho done: 1389dad325fSLuca Coelho if (!size) { 1399dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); 140a571bc28SChristophe JAILLET /* Better safe than sorry, but 'reduce_power_data' should 141a571bc28SChristophe JAILLET * always be NULL if !size. 142a571bc28SChristophe JAILLET */ 143a571bc28SChristophe JAILLET kfree(reduce_power_data); 1449dad325fSLuca Coelho reduce_power_data = ERR_PTR(-ENOENT); 1459dad325fSLuca Coelho goto out; 1469dad325fSLuca Coelho } 1479dad325fSLuca Coelho 1489dad325fSLuca Coelho IWL_INFO(trans, "loaded REDUCE_POWER\n"); 1499dad325fSLuca Coelho 1509dad325fSLuca Coelho out: 1519dad325fSLuca Coelho return reduce_power_data; 1529dad325fSLuca Coelho } 1539dad325fSLuca Coelho 1549dad325fSLuca Coelho static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans, 1559dad325fSLuca Coelho const u8 *data, size_t len) 1569dad325fSLuca Coelho { 157*86e8e657SJohannes Berg const struct iwl_ucode_tlv *tlv; 1589dad325fSLuca Coelho void *sec_data; 1599dad325fSLuca Coelho 1609dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); 1619dad325fSLuca Coelho 1629dad325fSLuca Coelho while (len >= sizeof(*tlv)) { 1639dad325fSLuca Coelho u32 tlv_len, tlv_type; 1649dad325fSLuca Coelho 1659dad325fSLuca Coelho len -= sizeof(*tlv); 166*86e8e657SJohannes Berg tlv = (const void *)data; 1679dad325fSLuca Coelho 1689dad325fSLuca Coelho tlv_len = le32_to_cpu(tlv->length); 1699dad325fSLuca Coelho tlv_type = le32_to_cpu(tlv->type); 1709dad325fSLuca Coelho 1719dad325fSLuca Coelho if (len < tlv_len) { 1729dad325fSLuca Coelho IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 1739dad325fSLuca Coelho len, tlv_len); 1749dad325fSLuca Coelho return ERR_PTR(-EINVAL); 1759dad325fSLuca Coelho } 1769dad325fSLuca Coelho 1779dad325fSLuca Coelho if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { 178*86e8e657SJohannes Berg const struct iwl_sku_id *sku_id = 179*86e8e657SJohannes Berg (const void *)(data + sizeof(*tlv)); 1809dad325fSLuca Coelho 1819dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1829dad325fSLuca Coelho "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", 1839dad325fSLuca Coelho tlv_len); 1849dad325fSLuca Coelho IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", 1859dad325fSLuca Coelho le32_to_cpu(sku_id->data[0]), 1869dad325fSLuca Coelho le32_to_cpu(sku_id->data[1]), 1879dad325fSLuca Coelho le32_to_cpu(sku_id->data[2])); 1889dad325fSLuca Coelho 1899dad325fSLuca Coelho data += sizeof(*tlv) + ALIGN(tlv_len, 4); 1909dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 1919dad325fSLuca Coelho 1929dad325fSLuca Coelho if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && 1939dad325fSLuca Coelho trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && 1949dad325fSLuca Coelho trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { 1959dad325fSLuca Coelho sec_data = iwl_uefi_reduce_power_section(trans, 1969dad325fSLuca Coelho data, 1979dad325fSLuca Coelho len); 1989dad325fSLuca Coelho if (!IS_ERR(sec_data)) 1999dad325fSLuca Coelho return sec_data; 2009dad325fSLuca Coelho } else { 2019dad325fSLuca Coelho IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); 2029dad325fSLuca Coelho } 2039dad325fSLuca Coelho } else { 2049dad325fSLuca Coelho data += sizeof(*tlv) + ALIGN(tlv_len, 4); 2059dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 2069dad325fSLuca Coelho } 2079dad325fSLuca Coelho } 2089dad325fSLuca Coelho 2099dad325fSLuca Coelho return ERR_PTR(-ENOENT); 2109dad325fSLuca Coelho } 2119dad325fSLuca Coelho 2129dad325fSLuca Coelho void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) 2139dad325fSLuca Coelho { 2149dad325fSLuca Coelho struct efivar_entry *reduce_power_efivar; 2159dad325fSLuca Coelho struct pnvm_sku_package *package; 2169dad325fSLuca Coelho void *data = NULL; 2179dad325fSLuca Coelho unsigned long package_size; 2189dad325fSLuca Coelho int err; 2199dad325fSLuca Coelho 2209dad325fSLuca Coelho *len = 0; 2219dad325fSLuca Coelho 2229dad325fSLuca Coelho reduce_power_efivar = kzalloc(sizeof(*reduce_power_efivar), GFP_KERNEL); 2239dad325fSLuca Coelho if (!reduce_power_efivar) 2249dad325fSLuca Coelho return ERR_PTR(-ENOMEM); 2259dad325fSLuca Coelho 2269dad325fSLuca Coelho memcpy(&reduce_power_efivar->var.VariableName, IWL_UEFI_REDUCED_POWER_NAME, 2279dad325fSLuca Coelho sizeof(IWL_UEFI_REDUCED_POWER_NAME)); 2289dad325fSLuca Coelho reduce_power_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 2299dad325fSLuca Coelho 2309dad325fSLuca Coelho /* 2319dad325fSLuca Coelho * TODO: we hardcode a maximum length here, because reading 2329dad325fSLuca Coelho * from the UEFI is not working. To implement this properly, 2339dad325fSLuca Coelho * we have to call efivar_entry_size(). 2349dad325fSLuca Coelho */ 2359dad325fSLuca Coelho package_size = IWL_HARDCODED_REDUCE_POWER_SIZE; 2369dad325fSLuca Coelho 2379dad325fSLuca Coelho package = kmalloc(package_size, GFP_KERNEL); 2389dad325fSLuca Coelho if (!package) { 2399dad325fSLuca Coelho package = ERR_PTR(-ENOMEM); 2409dad325fSLuca Coelho goto out; 2419dad325fSLuca Coelho } 2429dad325fSLuca Coelho 2439dad325fSLuca Coelho err = efivar_entry_get(reduce_power_efivar, NULL, &package_size, package); 2449dad325fSLuca Coelho if (err) { 2459dad325fSLuca Coelho IWL_DEBUG_FW(trans, 2469dad325fSLuca Coelho "Reduced Power UEFI variable not found %d (len %lu)\n", 2479dad325fSLuca Coelho err, package_size); 2489dad325fSLuca Coelho kfree(package); 2499dad325fSLuca Coelho data = ERR_PTR(err); 2509dad325fSLuca Coelho goto out; 2519dad325fSLuca Coelho } 2529dad325fSLuca Coelho 2539dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", 2549dad325fSLuca Coelho package_size); 2559dad325fSLuca Coelho *len = package_size; 2569dad325fSLuca Coelho 2579dad325fSLuca Coelho IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", 2589dad325fSLuca Coelho package->rev, package->total_size, package->n_skus); 2599dad325fSLuca Coelho 2609dad325fSLuca Coelho data = iwl_uefi_reduce_power_parse(trans, package->data, 2619dad325fSLuca Coelho *len - sizeof(*package)); 2629dad325fSLuca Coelho 2639dad325fSLuca Coelho kfree(package); 2649dad325fSLuca Coelho 2659dad325fSLuca Coelho out: 2669dad325fSLuca Coelho kfree(reduce_power_efivar); 2679dad325fSLuca Coelho 2689dad325fSLuca Coelho return data; 2699dad325fSLuca Coelho } 270c593d2faSAyala Barazani 271c593d2faSAyala Barazani #ifdef CONFIG_ACPI 272c593d2faSAyala Barazani static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data, 273c593d2faSAyala Barazani struct iwl_fw_runtime *fwrt) 274c593d2faSAyala Barazani { 275c593d2faSAyala Barazani int i, j; 276c593d2faSAyala Barazani 277c593d2faSAyala Barazani if (sgom_data->revision != 1) 278c593d2faSAyala Barazani return -EINVAL; 279c593d2faSAyala Barazani 280c593d2faSAyala Barazani memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map, 281c593d2faSAyala Barazani sizeof(fwrt->sgom_table.offset_map)); 282c593d2faSAyala Barazani 283c593d2faSAyala Barazani for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) { 284c593d2faSAyala Barazani for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) { 285c593d2faSAyala Barazani /* since each byte is composed of to values, */ 286c593d2faSAyala Barazani /* one for each letter, */ 287c593d2faSAyala Barazani /* extract and check each of them separately */ 288c593d2faSAyala Barazani u8 value = fwrt->sgom_table.offset_map[i][j]; 289c593d2faSAyala Barazani u8 low = value & 0xF; 290c593d2faSAyala Barazani u8 high = (value & 0xF0) >> 4; 291c593d2faSAyala Barazani 292c593d2faSAyala Barazani if (high > fwrt->geo_num_profiles) 293c593d2faSAyala Barazani high = 0; 294c593d2faSAyala Barazani if (low > fwrt->geo_num_profiles) 295c593d2faSAyala Barazani low = 0; 296c593d2faSAyala Barazani fwrt->sgom_table.offset_map[i][j] = (high << 4) | low; 297c593d2faSAyala Barazani } 298c593d2faSAyala Barazani } 299c593d2faSAyala Barazani 300c593d2faSAyala Barazani fwrt->sgom_enabled = true; 301c593d2faSAyala Barazani return 0; 302c593d2faSAyala Barazani } 303c593d2faSAyala Barazani 304c593d2faSAyala Barazani void iwl_uefi_get_sgom_table(struct iwl_trans *trans, 305c593d2faSAyala Barazani struct iwl_fw_runtime *fwrt) 306c593d2faSAyala Barazani { 307c593d2faSAyala Barazani struct efivar_entry *sgom_efivar; 308c593d2faSAyala Barazani struct uefi_cnv_wlan_sgom_data *data; 309c593d2faSAyala Barazani unsigned long package_size; 310c593d2faSAyala Barazani int err, ret; 311c593d2faSAyala Barazani 312c593d2faSAyala Barazani if (!fwrt->geo_enabled) 313c593d2faSAyala Barazani return; 314c593d2faSAyala Barazani 315c593d2faSAyala Barazani sgom_efivar = kzalloc(sizeof(*sgom_efivar), GFP_KERNEL); 316c593d2faSAyala Barazani if (!sgom_efivar) 317c593d2faSAyala Barazani return; 318c593d2faSAyala Barazani 319c593d2faSAyala Barazani memcpy(&sgom_efivar->var.VariableName, IWL_UEFI_SGOM_NAME, 320c593d2faSAyala Barazani sizeof(IWL_UEFI_SGOM_NAME)); 321c593d2faSAyala Barazani sgom_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 322c593d2faSAyala Barazani 323c593d2faSAyala Barazani /* TODO: we hardcode a maximum length here, because reading 324c593d2faSAyala Barazani * from the UEFI is not working. To implement this properly, 325c593d2faSAyala Barazani * we have to call efivar_entry_size(). 326c593d2faSAyala Barazani */ 327c593d2faSAyala Barazani package_size = IWL_HARDCODED_SGOM_SIZE; 328c593d2faSAyala Barazani 329c593d2faSAyala Barazani data = kmalloc(package_size, GFP_KERNEL); 330c593d2faSAyala Barazani if (!data) { 331c593d2faSAyala Barazani data = ERR_PTR(-ENOMEM); 332c593d2faSAyala Barazani goto out; 333c593d2faSAyala Barazani } 334c593d2faSAyala Barazani 335c593d2faSAyala Barazani err = efivar_entry_get(sgom_efivar, NULL, &package_size, data); 336c593d2faSAyala Barazani if (err) { 337c593d2faSAyala Barazani IWL_DEBUG_FW(trans, 338c593d2faSAyala Barazani "SGOM UEFI variable not found %d\n", err); 339c593d2faSAyala Barazani goto out_free; 340c593d2faSAyala Barazani } 341c593d2faSAyala Barazani 342c593d2faSAyala Barazani IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n", 343c593d2faSAyala Barazani package_size); 344c593d2faSAyala Barazani 345c593d2faSAyala Barazani ret = iwl_uefi_sgom_parse(data, fwrt); 346c593d2faSAyala Barazani if (ret < 0) 347c593d2faSAyala Barazani IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n"); 348c593d2faSAyala Barazani 349c593d2faSAyala Barazani out_free: 350c593d2faSAyala Barazani kfree(data); 351c593d2faSAyala Barazani 352c593d2faSAyala Barazani out: 353c593d2faSAyala Barazani kfree(sgom_efivar); 354c593d2faSAyala Barazani } 355c593d2faSAyala Barazani IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table); 356c593d2faSAyala Barazani #endif /* CONFIG_ACPI */ 357