1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 /* 3 * Copyright(c) 2021 Intel Corporation 4 */ 5 6 #include "iwl-drv.h" 7 #include "pnvm.h" 8 #include "iwl-prph.h" 9 #include "iwl-io.h" 10 11 #include "fw/uefi.h" 12 #include "fw/api/alive.h" 13 #include <linux/efi.h> 14 #include "fw/runtime.h" 15 16 #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \ 17 0xb2, 0xec, 0xf5, 0xa3, \ 18 0x59, 0x4f, 0x4a, 0xea) 19 20 void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) 21 { 22 void *data; 23 unsigned long package_size; 24 efi_status_t status; 25 26 *len = 0; 27 28 if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 29 return ERR_PTR(-ENODEV); 30 31 /* 32 * TODO: we hardcode a maximum length here, because reading 33 * from the UEFI is not working. To implement this properly, 34 * we have to call efivar_entry_size(). 35 */ 36 package_size = IWL_HARDCODED_PNVM_SIZE; 37 38 data = kmalloc(package_size, GFP_KERNEL); 39 if (!data) 40 return ERR_PTR(-ENOMEM); 41 42 status = efi.get_variable(IWL_UEFI_OEM_PNVM_NAME, &IWL_EFI_VAR_GUID, 43 NULL, &package_size, data); 44 if (status != EFI_SUCCESS) { 45 IWL_DEBUG_FW(trans, 46 "PNVM UEFI variable not found 0x%lx (len %lu)\n", 47 status, package_size); 48 kfree(data); 49 return ERR_PTR(-ENOENT); 50 } 51 52 IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size); 53 *len = package_size; 54 55 return data; 56 } 57 58 static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans, 59 const u8 *data, size_t len) 60 { 61 const struct iwl_ucode_tlv *tlv; 62 u8 *reduce_power_data = NULL, *tmp; 63 u32 size = 0; 64 65 IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); 66 67 while (len >= sizeof(*tlv)) { 68 u32 tlv_len, tlv_type; 69 70 len -= sizeof(*tlv); 71 tlv = (const void *)data; 72 73 tlv_len = le32_to_cpu(tlv->length); 74 tlv_type = le32_to_cpu(tlv->type); 75 76 if (len < tlv_len) { 77 IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 78 len, tlv_len); 79 kfree(reduce_power_data); 80 reduce_power_data = ERR_PTR(-EINVAL); 81 goto out; 82 } 83 84 data += sizeof(*tlv); 85 86 switch (tlv_type) { 87 case IWL_UCODE_TLV_MEM_DESC: { 88 IWL_DEBUG_FW(trans, 89 "Got IWL_UCODE_TLV_MEM_DESC len %d\n", 90 tlv_len); 91 92 IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); 93 94 tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL); 95 if (!tmp) { 96 IWL_DEBUG_FW(trans, 97 "Couldn't allocate (more) reduce_power_data\n"); 98 99 kfree(reduce_power_data); 100 reduce_power_data = ERR_PTR(-ENOMEM); 101 goto out; 102 } 103 104 reduce_power_data = tmp; 105 106 memcpy(reduce_power_data + size, data, tlv_len); 107 108 size += tlv_len; 109 110 break; 111 } 112 case IWL_UCODE_TLV_PNVM_SKU: 113 IWL_DEBUG_FW(trans, 114 "New REDUCE_POWER section started, stop parsing.\n"); 115 goto done; 116 default: 117 IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", 118 tlv_type, tlv_len); 119 break; 120 } 121 122 len -= ALIGN(tlv_len, 4); 123 data += ALIGN(tlv_len, 4); 124 } 125 126 done: 127 if (!size) { 128 IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); 129 /* Better safe than sorry, but 'reduce_power_data' should 130 * always be NULL if !size. 131 */ 132 kfree(reduce_power_data); 133 reduce_power_data = ERR_PTR(-ENOENT); 134 goto out; 135 } 136 137 IWL_INFO(trans, "loaded REDUCE_POWER\n"); 138 139 out: 140 return reduce_power_data; 141 } 142 143 static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans, 144 const u8 *data, size_t len) 145 { 146 const struct iwl_ucode_tlv *tlv; 147 void *sec_data; 148 149 IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); 150 151 while (len >= sizeof(*tlv)) { 152 u32 tlv_len, tlv_type; 153 154 len -= sizeof(*tlv); 155 tlv = (const void *)data; 156 157 tlv_len = le32_to_cpu(tlv->length); 158 tlv_type = le32_to_cpu(tlv->type); 159 160 if (len < tlv_len) { 161 IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 162 len, tlv_len); 163 return ERR_PTR(-EINVAL); 164 } 165 166 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { 167 const struct iwl_sku_id *sku_id = 168 (const void *)(data + sizeof(*tlv)); 169 170 IWL_DEBUG_FW(trans, 171 "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", 172 tlv_len); 173 IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", 174 le32_to_cpu(sku_id->data[0]), 175 le32_to_cpu(sku_id->data[1]), 176 le32_to_cpu(sku_id->data[2])); 177 178 data += sizeof(*tlv) + ALIGN(tlv_len, 4); 179 len -= ALIGN(tlv_len, 4); 180 181 if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && 182 trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && 183 trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { 184 sec_data = iwl_uefi_reduce_power_section(trans, 185 data, 186 len); 187 if (!IS_ERR(sec_data)) 188 return sec_data; 189 } else { 190 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); 191 } 192 } else { 193 data += sizeof(*tlv) + ALIGN(tlv_len, 4); 194 len -= ALIGN(tlv_len, 4); 195 } 196 } 197 198 return ERR_PTR(-ENOENT); 199 } 200 201 void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) 202 { 203 struct pnvm_sku_package *package; 204 void *data = NULL; 205 unsigned long package_size; 206 efi_status_t status; 207 208 *len = 0; 209 210 if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 211 return ERR_PTR(-ENODEV); 212 213 /* 214 * TODO: we hardcode a maximum length here, because reading 215 * from the UEFI is not working. To implement this properly, 216 * we have to call efivar_entry_size(). 217 */ 218 package_size = IWL_HARDCODED_REDUCE_POWER_SIZE; 219 220 package = kmalloc(package_size, GFP_KERNEL); 221 if (!package) 222 return ERR_PTR(-ENOMEM); 223 224 status = efi.get_variable(IWL_UEFI_REDUCED_POWER_NAME, &IWL_EFI_VAR_GUID, 225 NULL, &package_size, data); 226 if (status != EFI_SUCCESS) { 227 IWL_DEBUG_FW(trans, 228 "Reduced Power UEFI variable not found 0x%lx (len %lu)\n", 229 status, package_size); 230 kfree(package); 231 return ERR_PTR(-ENOENT); 232 } 233 234 IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", 235 package_size); 236 *len = package_size; 237 238 IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", 239 package->rev, package->total_size, package->n_skus); 240 241 data = iwl_uefi_reduce_power_parse(trans, package->data, 242 *len - sizeof(*package)); 243 244 kfree(package); 245 246 return data; 247 } 248 249 #ifdef CONFIG_ACPI 250 static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data, 251 struct iwl_fw_runtime *fwrt) 252 { 253 int i, j; 254 255 if (sgom_data->revision != 1) 256 return -EINVAL; 257 258 memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map, 259 sizeof(fwrt->sgom_table.offset_map)); 260 261 for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) { 262 for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) { 263 /* since each byte is composed of to values, */ 264 /* one for each letter, */ 265 /* extract and check each of them separately */ 266 u8 value = fwrt->sgom_table.offset_map[i][j]; 267 u8 low = value & 0xF; 268 u8 high = (value & 0xF0) >> 4; 269 270 if (high > fwrt->geo_num_profiles) 271 high = 0; 272 if (low > fwrt->geo_num_profiles) 273 low = 0; 274 fwrt->sgom_table.offset_map[i][j] = (high << 4) | low; 275 } 276 } 277 278 fwrt->sgom_enabled = true; 279 return 0; 280 } 281 282 void iwl_uefi_get_sgom_table(struct iwl_trans *trans, 283 struct iwl_fw_runtime *fwrt) 284 { 285 struct uefi_cnv_wlan_sgom_data *data; 286 unsigned long package_size; 287 efi_status_t status; 288 int ret; 289 290 if (!fwrt->geo_enabled || 291 !efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 292 return; 293 294 /* TODO: we hardcode a maximum length here, because reading 295 * from the UEFI is not working. To implement this properly, 296 * we have to call efivar_entry_size(). 297 */ 298 package_size = IWL_HARDCODED_SGOM_SIZE; 299 300 data = kmalloc(package_size, GFP_KERNEL); 301 if (!data) 302 return; 303 304 status = efi.get_variable(IWL_UEFI_SGOM_NAME, &IWL_EFI_VAR_GUID, 305 NULL, &package_size, data); 306 if (status != EFI_SUCCESS) { 307 IWL_DEBUG_FW(trans, 308 "SGOM UEFI variable not found 0x%lx\n", status); 309 goto out_free; 310 } 311 312 IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n", 313 package_size); 314 315 ret = iwl_uefi_sgom_parse(data, fwrt); 316 if (ret < 0) 317 IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n"); 318 319 out_free: 320 kfree(data); 321 322 } 323 IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table); 324 #endif /* CONFIG_ACPI */ 325