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