184c3c995SLuca Coelho // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 284c3c995SLuca Coelho /* 3380bf72dSAlon Giladi * Copyright(c) 2021-2023 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 20*8ae3e231SGregory Greenman static void *iwl_uefi_get_variable(efi_char16_t *name, efi_guid_t *guid, 21*8ae3e231SGregory Greenman unsigned long *data_size) 2284c3c995SLuca Coelho { 230c4bad7fSArd Biesheuvel efi_status_t status; 24*8ae3e231SGregory Greenman void *data; 2584c3c995SLuca Coelho 26*8ae3e231SGregory Greenman if (!data_size) 27*8ae3e231SGregory Greenman return ERR_PTR(-EINVAL); 289dad325fSLuca Coelho 290c4bad7fSArd Biesheuvel if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 300c4bad7fSArd Biesheuvel return ERR_PTR(-ENODEV); 3184c3c995SLuca Coelho 32*8ae3e231SGregory Greenman /* first call with NULL data to get the exact entry size */ 33*8ae3e231SGregory Greenman *data_size = 0; 34*8ae3e231SGregory Greenman status = efi.get_variable(name, guid, NULL, data_size, NULL); 35*8ae3e231SGregory Greenman if (status != EFI_BUFFER_TOO_SMALL || !*data_size) 36*8ae3e231SGregory Greenman return ERR_PTR(-EIO); 3784c3c995SLuca Coelho 38*8ae3e231SGregory Greenman data = kmalloc(*data_size, GFP_KERNEL); 390c4bad7fSArd Biesheuvel if (!data) 400c4bad7fSArd Biesheuvel return ERR_PTR(-ENOMEM); 4184c3c995SLuca Coelho 42*8ae3e231SGregory Greenman status = efi.get_variable(name, guid, NULL, data_size, data); 430c4bad7fSArd Biesheuvel if (status != EFI_SUCCESS) { 4484c3c995SLuca Coelho kfree(data); 450c4bad7fSArd Biesheuvel return ERR_PTR(-ENOENT); 4684c3c995SLuca Coelho } 4784c3c995SLuca Coelho 48*8ae3e231SGregory Greenman return data; 49*8ae3e231SGregory Greenman } 50*8ae3e231SGregory Greenman 51*8ae3e231SGregory Greenman void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) 52*8ae3e231SGregory Greenman { 53*8ae3e231SGregory Greenman unsigned long package_size; 54*8ae3e231SGregory Greenman void *data; 55*8ae3e231SGregory Greenman 56*8ae3e231SGregory Greenman *len = 0; 57*8ae3e231SGregory Greenman 58*8ae3e231SGregory Greenman data = iwl_uefi_get_variable(IWL_UEFI_OEM_PNVM_NAME, &IWL_EFI_VAR_GUID, 59*8ae3e231SGregory Greenman &package_size); 60*8ae3e231SGregory Greenman if (IS_ERR(data)) { 61*8ae3e231SGregory Greenman IWL_DEBUG_FW(trans, 62*8ae3e231SGregory Greenman "PNVM UEFI variable not found 0x%lx (len %lu)\n", 63*8ae3e231SGregory Greenman PTR_ERR(data), package_size); 64*8ae3e231SGregory Greenman return data; 65*8ae3e231SGregory Greenman } 66*8ae3e231SGregory Greenman 671476ff21SLinus Torvalds IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size); 6884c3c995SLuca Coelho *len = package_size; 6984c3c995SLuca Coelho 7084c3c995SLuca Coelho return data; 7184c3c995SLuca Coelho } 729dad325fSLuca Coelho 73ea3571f4SAlon Giladi static int iwl_uefi_reduce_power_section(struct iwl_trans *trans, 74ea3571f4SAlon Giladi const u8 *data, size_t len, 75ea3571f4SAlon Giladi struct iwl_pnvm_image *pnvm_data) 769dad325fSLuca Coelho { 7786e8e657SJohannes Berg const struct iwl_ucode_tlv *tlv; 789dad325fSLuca Coelho 799dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); 80ea3571f4SAlon Giladi memset(pnvm_data, 0, sizeof(*pnvm_data)); 819dad325fSLuca Coelho 829dad325fSLuca Coelho while (len >= sizeof(*tlv)) { 839dad325fSLuca Coelho u32 tlv_len, tlv_type; 849dad325fSLuca Coelho 859dad325fSLuca Coelho len -= sizeof(*tlv); 8686e8e657SJohannes Berg tlv = (const void *)data; 879dad325fSLuca Coelho 889dad325fSLuca Coelho tlv_len = le32_to_cpu(tlv->length); 899dad325fSLuca Coelho tlv_type = le32_to_cpu(tlv->type); 909dad325fSLuca Coelho 919dad325fSLuca Coelho if (len < tlv_len) { 929dad325fSLuca Coelho IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 939dad325fSLuca Coelho len, tlv_len); 94ea3571f4SAlon Giladi return -EINVAL; 959dad325fSLuca Coelho } 969dad325fSLuca Coelho 979dad325fSLuca Coelho data += sizeof(*tlv); 989dad325fSLuca Coelho 999dad325fSLuca Coelho switch (tlv_type) { 1009dad325fSLuca Coelho case IWL_UCODE_TLV_MEM_DESC: { 1019dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1029dad325fSLuca Coelho "Got IWL_UCODE_TLV_MEM_DESC len %d\n", 1039dad325fSLuca Coelho tlv_len); 1049dad325fSLuca Coelho 105ea3571f4SAlon Giladi if (pnvm_data->n_chunks == IPC_DRAM_MAP_ENTRY_NUM_MAX) { 1069dad325fSLuca Coelho IWL_DEBUG_FW(trans, 107ea3571f4SAlon Giladi "too many payloads to allocate in DRAM.\n"); 108ea3571f4SAlon Giladi return -EINVAL; 1099dad325fSLuca Coelho } 1109dad325fSLuca Coelho 111ea3571f4SAlon Giladi IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); 1129dad325fSLuca Coelho 113ea3571f4SAlon Giladi pnvm_data->chunks[pnvm_data->n_chunks].data = data; 114ea3571f4SAlon Giladi pnvm_data->chunks[pnvm_data->n_chunks].len = tlv_len; 115ea3571f4SAlon Giladi pnvm_data->n_chunks++; 1169dad325fSLuca Coelho 1179dad325fSLuca Coelho break; 1189dad325fSLuca Coelho } 1199dad325fSLuca Coelho case IWL_UCODE_TLV_PNVM_SKU: 1209dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1219dad325fSLuca Coelho "New REDUCE_POWER section started, stop parsing.\n"); 1229dad325fSLuca Coelho goto done; 1239dad325fSLuca Coelho default: 1249dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", 1259dad325fSLuca Coelho tlv_type, tlv_len); 1269dad325fSLuca Coelho break; 1279dad325fSLuca Coelho } 1289dad325fSLuca Coelho 1299dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 1309dad325fSLuca Coelho data += ALIGN(tlv_len, 4); 1319dad325fSLuca Coelho } 1329dad325fSLuca Coelho 1339dad325fSLuca Coelho done: 134ea3571f4SAlon Giladi if (!pnvm_data->n_chunks) { 1359dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); 136ea3571f4SAlon Giladi return -ENOENT; 137ea3571f4SAlon Giladi } 138ea3571f4SAlon Giladi return 0; 1399dad325fSLuca Coelho } 1409dad325fSLuca Coelho 141380bf72dSAlon Giladi int iwl_uefi_reduce_power_parse(struct iwl_trans *trans, 142ea3571f4SAlon Giladi const u8 *data, size_t len, 143ea3571f4SAlon Giladi struct iwl_pnvm_image *pnvm_data) 1449dad325fSLuca Coelho { 14586e8e657SJohannes Berg const struct iwl_ucode_tlv *tlv; 1469dad325fSLuca Coelho 1479dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); 1489dad325fSLuca Coelho 1499dad325fSLuca Coelho while (len >= sizeof(*tlv)) { 1509dad325fSLuca Coelho u32 tlv_len, tlv_type; 1519dad325fSLuca Coelho 1529dad325fSLuca Coelho len -= sizeof(*tlv); 15386e8e657SJohannes Berg tlv = (const void *)data; 1549dad325fSLuca Coelho 1559dad325fSLuca Coelho tlv_len = le32_to_cpu(tlv->length); 1569dad325fSLuca Coelho tlv_type = le32_to_cpu(tlv->type); 1579dad325fSLuca Coelho 1589dad325fSLuca Coelho if (len < tlv_len) { 1599dad325fSLuca Coelho IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 1609dad325fSLuca Coelho len, tlv_len); 161ea3571f4SAlon Giladi return -EINVAL; 1629dad325fSLuca Coelho } 1639dad325fSLuca Coelho 1649dad325fSLuca Coelho if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { 16586e8e657SJohannes Berg const struct iwl_sku_id *sku_id = 16686e8e657SJohannes Berg (const void *)(data + sizeof(*tlv)); 1679dad325fSLuca Coelho 1689dad325fSLuca Coelho IWL_DEBUG_FW(trans, 1699dad325fSLuca Coelho "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", 1709dad325fSLuca Coelho tlv_len); 1719dad325fSLuca Coelho IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", 1729dad325fSLuca Coelho le32_to_cpu(sku_id->data[0]), 1739dad325fSLuca Coelho le32_to_cpu(sku_id->data[1]), 1749dad325fSLuca Coelho le32_to_cpu(sku_id->data[2])); 1759dad325fSLuca Coelho 1769dad325fSLuca Coelho data += sizeof(*tlv) + ALIGN(tlv_len, 4); 1779dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 1789dad325fSLuca Coelho 1799dad325fSLuca Coelho if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && 1809dad325fSLuca Coelho trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && 1819dad325fSLuca Coelho trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { 182ea3571f4SAlon Giladi int ret = iwl_uefi_reduce_power_section(trans, 183ea3571f4SAlon Giladi data, len, 184ea3571f4SAlon Giladi pnvm_data); 185ea3571f4SAlon Giladi if (!ret) 186ea3571f4SAlon Giladi return 0; 1879dad325fSLuca Coelho } else { 1889dad325fSLuca Coelho IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); 1899dad325fSLuca Coelho } 1909dad325fSLuca Coelho } else { 1919dad325fSLuca Coelho data += sizeof(*tlv) + ALIGN(tlv_len, 4); 1929dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 1939dad325fSLuca Coelho } 1949dad325fSLuca Coelho } 1959dad325fSLuca Coelho 196ea3571f4SAlon Giladi return -ENOENT; 1979dad325fSLuca Coelho } 1989dad325fSLuca Coelho 199380bf72dSAlon Giladi u8 *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) 2009dad325fSLuca Coelho { 2019dad325fSLuca Coelho struct pnvm_sku_package *package; 2029dad325fSLuca Coelho unsigned long package_size; 203380bf72dSAlon Giladi u8 *data; 2049dad325fSLuca Coelho 205*8ae3e231SGregory Greenman package = iwl_uefi_get_variable(IWL_UEFI_REDUCED_POWER_NAME, 206*8ae3e231SGregory Greenman &IWL_EFI_VAR_GUID, &package_size); 2079dad325fSLuca Coelho 208*8ae3e231SGregory Greenman if (IS_ERR(package)) { 2099dad325fSLuca Coelho IWL_DEBUG_FW(trans, 2100c4bad7fSArd Biesheuvel "Reduced Power UEFI variable not found 0x%lx (len %lu)\n", 211*8ae3e231SGregory Greenman PTR_ERR(package), package_size); 212*8ae3e231SGregory Greenman return ERR_CAST(package); 213*8ae3e231SGregory Greenman } 214*8ae3e231SGregory Greenman 215*8ae3e231SGregory Greenman if (package_size < sizeof(*package)) { 216*8ae3e231SGregory Greenman IWL_DEBUG_FW(trans, 217*8ae3e231SGregory Greenman "Invalid Reduced Power UEFI variable len (%lu)\n", 218*8ae3e231SGregory Greenman package_size); 2199dad325fSLuca Coelho kfree(package); 220*8ae3e231SGregory Greenman return ERR_PTR(-EINVAL); 2219dad325fSLuca Coelho } 2229dad325fSLuca Coelho 2239dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", 2249dad325fSLuca Coelho package_size); 2259dad325fSLuca Coelho 2269dad325fSLuca Coelho IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", 2279dad325fSLuca Coelho package->rev, package->total_size, package->n_skus); 2289dad325fSLuca Coelho 229380bf72dSAlon Giladi *len = package_size - sizeof(*package); 230380bf72dSAlon Giladi data = kmemdup(package->data, *len, GFP_KERNEL); 231*8ae3e231SGregory Greenman if (!data) { 232*8ae3e231SGregory Greenman kfree(package); 233380bf72dSAlon Giladi return ERR_PTR(-ENOMEM); 234*8ae3e231SGregory Greenman } 235*8ae3e231SGregory Greenman 2369dad325fSLuca Coelho kfree(package); 2379dad325fSLuca Coelho 238380bf72dSAlon Giladi return data; 2399dad325fSLuca Coelho } 240c593d2faSAyala Barazani 24109b4c35dSAyala Barazani static int iwl_uefi_step_parse(struct uefi_cnv_common_step_data *common_step_data, 24209b4c35dSAyala Barazani struct iwl_trans *trans) 24309b4c35dSAyala Barazani { 24409b4c35dSAyala Barazani if (common_step_data->revision != 1) 24509b4c35dSAyala Barazani return -EINVAL; 24609b4c35dSAyala Barazani 24709b4c35dSAyala Barazani trans->mbx_addr_0_step = (u32)common_step_data->revision | 24809b4c35dSAyala Barazani (u32)common_step_data->cnvi_eq_channel << 8 | 24909b4c35dSAyala Barazani (u32)common_step_data->cnvr_eq_channel << 16 | 25009b4c35dSAyala Barazani (u32)common_step_data->radio1 << 24; 25109b4c35dSAyala Barazani trans->mbx_addr_1_step = (u32)common_step_data->radio2; 25209b4c35dSAyala Barazani return 0; 25309b4c35dSAyala Barazani } 25409b4c35dSAyala Barazani 25509b4c35dSAyala Barazani void iwl_uefi_get_step_table(struct iwl_trans *trans) 25609b4c35dSAyala Barazani { 25709b4c35dSAyala Barazani struct uefi_cnv_common_step_data *data; 25809b4c35dSAyala Barazani unsigned long package_size; 25909b4c35dSAyala Barazani int ret; 26009b4c35dSAyala Barazani 26109b4c35dSAyala Barazani if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) 26209b4c35dSAyala Barazani return; 26309b4c35dSAyala Barazani 264*8ae3e231SGregory Greenman data = iwl_uefi_get_variable(IWL_UEFI_STEP_NAME, &IWL_EFI_VAR_GUID, 265*8ae3e231SGregory Greenman &package_size); 26609b4c35dSAyala Barazani 267*8ae3e231SGregory Greenman if (IS_ERR(data)) { 26809b4c35dSAyala Barazani IWL_DEBUG_FW(trans, 269*8ae3e231SGregory Greenman "STEP UEFI variable not found 0x%lx\n", 270*8ae3e231SGregory Greenman PTR_ERR(data)); 271*8ae3e231SGregory Greenman return; 272*8ae3e231SGregory Greenman } 273*8ae3e231SGregory Greenman 274*8ae3e231SGregory Greenman if (package_size < sizeof(*data)) { 275*8ae3e231SGregory Greenman IWL_DEBUG_FW(trans, 276*8ae3e231SGregory Greenman "Invalid STEP table UEFI variable len (%lu)\n", 277*8ae3e231SGregory Greenman package_size); 278*8ae3e231SGregory Greenman kfree(data); 279*8ae3e231SGregory Greenman return; 28009b4c35dSAyala Barazani } 28109b4c35dSAyala Barazani 28209b4c35dSAyala Barazani IWL_DEBUG_FW(trans, "Read STEP from UEFI with size %lu\n", 28309b4c35dSAyala Barazani package_size); 28409b4c35dSAyala Barazani 28509b4c35dSAyala Barazani ret = iwl_uefi_step_parse(data, trans); 28609b4c35dSAyala Barazani if (ret < 0) 28709b4c35dSAyala Barazani IWL_DEBUG_FW(trans, "Cannot read STEP tables. rev is invalid\n"); 28809b4c35dSAyala Barazani 28909b4c35dSAyala Barazani kfree(data); 29009b4c35dSAyala Barazani } 29109b4c35dSAyala Barazani IWL_EXPORT_SYMBOL(iwl_uefi_get_step_table); 29209b4c35dSAyala Barazani 293c593d2faSAyala Barazani #ifdef CONFIG_ACPI 294c593d2faSAyala Barazani static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data, 295c593d2faSAyala Barazani struct iwl_fw_runtime *fwrt) 296c593d2faSAyala Barazani { 297c593d2faSAyala Barazani int i, j; 298c593d2faSAyala Barazani 299c593d2faSAyala Barazani if (sgom_data->revision != 1) 300c593d2faSAyala Barazani return -EINVAL; 301c593d2faSAyala Barazani 302c593d2faSAyala Barazani memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map, 303c593d2faSAyala Barazani sizeof(fwrt->sgom_table.offset_map)); 304c593d2faSAyala Barazani 305c593d2faSAyala Barazani for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) { 306c593d2faSAyala Barazani for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) { 307c593d2faSAyala Barazani /* since each byte is composed of to values, */ 308c593d2faSAyala Barazani /* one for each letter, */ 309c593d2faSAyala Barazani /* extract and check each of them separately */ 310c593d2faSAyala Barazani u8 value = fwrt->sgom_table.offset_map[i][j]; 311c593d2faSAyala Barazani u8 low = value & 0xF; 312c593d2faSAyala Barazani u8 high = (value & 0xF0) >> 4; 313c593d2faSAyala Barazani 314c593d2faSAyala Barazani if (high > fwrt->geo_num_profiles) 315c593d2faSAyala Barazani high = 0; 316c593d2faSAyala Barazani if (low > fwrt->geo_num_profiles) 317c593d2faSAyala Barazani low = 0; 318c593d2faSAyala Barazani fwrt->sgom_table.offset_map[i][j] = (high << 4) | low; 319c593d2faSAyala Barazani } 320c593d2faSAyala Barazani } 321c593d2faSAyala Barazani 322c593d2faSAyala Barazani fwrt->sgom_enabled = true; 323c593d2faSAyala Barazani return 0; 324c593d2faSAyala Barazani } 325c593d2faSAyala Barazani 326c593d2faSAyala Barazani void iwl_uefi_get_sgom_table(struct iwl_trans *trans, 327c593d2faSAyala Barazani struct iwl_fw_runtime *fwrt) 328c593d2faSAyala Barazani { 329c593d2faSAyala Barazani struct uefi_cnv_wlan_sgom_data *data; 330c593d2faSAyala Barazani unsigned long package_size; 3310c4bad7fSArd Biesheuvel int ret; 332c593d2faSAyala Barazani 333*8ae3e231SGregory Greenman if (!fwrt->geo_enabled) 334c593d2faSAyala Barazani return; 335c593d2faSAyala Barazani 336*8ae3e231SGregory Greenman data = iwl_uefi_get_variable(IWL_UEFI_SGOM_NAME, &IWL_EFI_VAR_GUID, 337*8ae3e231SGregory Greenman &package_size); 338*8ae3e231SGregory Greenman if (IS_ERR(data)) { 339c593d2faSAyala Barazani IWL_DEBUG_FW(trans, 340*8ae3e231SGregory Greenman "SGOM UEFI variable not found 0x%lx\n", 341*8ae3e231SGregory Greenman PTR_ERR(data)); 342*8ae3e231SGregory Greenman return; 343*8ae3e231SGregory Greenman } 344*8ae3e231SGregory Greenman 345*8ae3e231SGregory Greenman if (package_size < sizeof(*data)) { 346*8ae3e231SGregory Greenman IWL_DEBUG_FW(trans, 347*8ae3e231SGregory Greenman "Invalid SGOM table UEFI variable len (%lu)\n", 348*8ae3e231SGregory Greenman package_size); 349*8ae3e231SGregory Greenman kfree(data); 350*8ae3e231SGregory Greenman return; 351c593d2faSAyala Barazani } 352c593d2faSAyala Barazani 353c593d2faSAyala Barazani IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n", 354c593d2faSAyala Barazani package_size); 355c593d2faSAyala Barazani 356c593d2faSAyala Barazani ret = iwl_uefi_sgom_parse(data, fwrt); 357c593d2faSAyala Barazani if (ret < 0) 358c593d2faSAyala Barazani IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n"); 359c593d2faSAyala Barazani 360c593d2faSAyala Barazani kfree(data); 361c593d2faSAyala Barazani } 362c593d2faSAyala Barazani IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table); 363c593d2faSAyala Barazani #endif /* CONFIG_ACPI */ 364