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 15 #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \ 16 0xb2, 0xec, 0xf5, 0xa3, \ 17 0x59, 0x4f, 0x4a, 0xea) 18 19 void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) 20 { 21 struct efivar_entry *pnvm_efivar; 22 void *data; 23 unsigned long package_size; 24 int err; 25 26 *len = 0; 27 28 pnvm_efivar = kzalloc(sizeof(*pnvm_efivar), GFP_KERNEL); 29 if (!pnvm_efivar) 30 return ERR_PTR(-ENOMEM); 31 32 memcpy(&pnvm_efivar->var.VariableName, IWL_UEFI_OEM_PNVM_NAME, 33 sizeof(IWL_UEFI_OEM_PNVM_NAME)); 34 pnvm_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 35 36 /* 37 * TODO: we hardcode a maximum length here, because reading 38 * from the UEFI is not working. To implement this properly, 39 * we have to call efivar_entry_size(). 40 */ 41 package_size = IWL_HARDCODED_PNVM_SIZE; 42 43 data = kmalloc(package_size, GFP_KERNEL); 44 if (!data) { 45 data = ERR_PTR(-ENOMEM); 46 goto out; 47 } 48 49 err = efivar_entry_get(pnvm_efivar, NULL, &package_size, data); 50 if (err) { 51 IWL_DEBUG_FW(trans, 52 "PNVM UEFI variable not found %d (len %lu)\n", 53 err, package_size); 54 kfree(data); 55 data = ERR_PTR(err); 56 goto out; 57 } 58 59 IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size); 60 *len = package_size; 61 62 out: 63 kfree(pnvm_efivar); 64 65 return data; 66 } 67 68 static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans, 69 const u8 *data, size_t len) 70 { 71 struct iwl_ucode_tlv *tlv; 72 u8 *reduce_power_data = NULL, *tmp; 73 u32 size = 0; 74 75 IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); 76 77 while (len >= sizeof(*tlv)) { 78 u32 tlv_len, tlv_type; 79 80 len -= sizeof(*tlv); 81 tlv = (void *)data; 82 83 tlv_len = le32_to_cpu(tlv->length); 84 tlv_type = le32_to_cpu(tlv->type); 85 86 if (len < tlv_len) { 87 IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 88 len, tlv_len); 89 kfree(reduce_power_data); 90 reduce_power_data = ERR_PTR(-EINVAL); 91 goto out; 92 } 93 94 data += sizeof(*tlv); 95 96 switch (tlv_type) { 97 case IWL_UCODE_TLV_MEM_DESC: { 98 IWL_DEBUG_FW(trans, 99 "Got IWL_UCODE_TLV_MEM_DESC len %d\n", 100 tlv_len); 101 102 IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); 103 104 tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL); 105 if (!tmp) { 106 IWL_DEBUG_FW(trans, 107 "Couldn't allocate (more) reduce_power_data\n"); 108 109 kfree(reduce_power_data); 110 reduce_power_data = ERR_PTR(-ENOMEM); 111 goto out; 112 } 113 114 reduce_power_data = tmp; 115 116 memcpy(reduce_power_data + size, data, tlv_len); 117 118 size += tlv_len; 119 120 break; 121 } 122 case IWL_UCODE_TLV_PNVM_SKU: 123 IWL_DEBUG_FW(trans, 124 "New REDUCE_POWER section started, stop parsing.\n"); 125 goto done; 126 default: 127 IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", 128 tlv_type, tlv_len); 129 break; 130 } 131 132 len -= ALIGN(tlv_len, 4); 133 data += ALIGN(tlv_len, 4); 134 } 135 136 done: 137 if (!size) { 138 IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); 139 /* Better safe than sorry, but 'reduce_power_data' should 140 * always be NULL if !size. 141 */ 142 kfree(reduce_power_data); 143 reduce_power_data = ERR_PTR(-ENOENT); 144 goto out; 145 } 146 147 IWL_INFO(trans, "loaded REDUCE_POWER\n"); 148 149 out: 150 return reduce_power_data; 151 } 152 153 static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans, 154 const u8 *data, size_t len) 155 { 156 struct iwl_ucode_tlv *tlv; 157 void *sec_data; 158 159 IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); 160 161 while (len >= sizeof(*tlv)) { 162 u32 tlv_len, tlv_type; 163 164 len -= sizeof(*tlv); 165 tlv = (void *)data; 166 167 tlv_len = le32_to_cpu(tlv->length); 168 tlv_type = le32_to_cpu(tlv->type); 169 170 if (len < tlv_len) { 171 IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 172 len, tlv_len); 173 return ERR_PTR(-EINVAL); 174 } 175 176 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { 177 struct iwl_sku_id *sku_id = 178 (void *)(data + sizeof(*tlv)); 179 180 IWL_DEBUG_FW(trans, 181 "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", 182 tlv_len); 183 IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", 184 le32_to_cpu(sku_id->data[0]), 185 le32_to_cpu(sku_id->data[1]), 186 le32_to_cpu(sku_id->data[2])); 187 188 data += sizeof(*tlv) + ALIGN(tlv_len, 4); 189 len -= ALIGN(tlv_len, 4); 190 191 if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && 192 trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && 193 trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { 194 sec_data = iwl_uefi_reduce_power_section(trans, 195 data, 196 len); 197 if (!IS_ERR(sec_data)) 198 return sec_data; 199 } else { 200 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); 201 } 202 } else { 203 data += sizeof(*tlv) + ALIGN(tlv_len, 4); 204 len -= ALIGN(tlv_len, 4); 205 } 206 } 207 208 return ERR_PTR(-ENOENT); 209 } 210 211 void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) 212 { 213 struct efivar_entry *reduce_power_efivar; 214 struct pnvm_sku_package *package; 215 void *data = NULL; 216 unsigned long package_size; 217 int err; 218 219 *len = 0; 220 221 reduce_power_efivar = kzalloc(sizeof(*reduce_power_efivar), GFP_KERNEL); 222 if (!reduce_power_efivar) 223 return ERR_PTR(-ENOMEM); 224 225 memcpy(&reduce_power_efivar->var.VariableName, IWL_UEFI_REDUCED_POWER_NAME, 226 sizeof(IWL_UEFI_REDUCED_POWER_NAME)); 227 reduce_power_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 228 229 /* 230 * TODO: we hardcode a maximum length here, because reading 231 * from the UEFI is not working. To implement this properly, 232 * we have to call efivar_entry_size(). 233 */ 234 package_size = IWL_HARDCODED_REDUCE_POWER_SIZE; 235 236 package = kmalloc(package_size, GFP_KERNEL); 237 if (!package) { 238 package = ERR_PTR(-ENOMEM); 239 goto out; 240 } 241 242 err = efivar_entry_get(reduce_power_efivar, NULL, &package_size, package); 243 if (err) { 244 IWL_DEBUG_FW(trans, 245 "Reduced Power UEFI variable not found %d (len %lu)\n", 246 err, package_size); 247 kfree(package); 248 data = ERR_PTR(err); 249 goto out; 250 } 251 252 IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", 253 package_size); 254 *len = package_size; 255 256 IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", 257 package->rev, package->total_size, package->n_skus); 258 259 data = iwl_uefi_reduce_power_parse(trans, package->data, 260 *len - sizeof(*package)); 261 262 kfree(package); 263 264 out: 265 kfree(reduce_power_efivar); 266 267 return data; 268 } 269