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" 12*9dad325fSLuca Coelho #include "fw/api/alive.h" 1384c3c995SLuca Coelho #include <linux/efi.h> 1484c3c995SLuca Coelho 1584c3c995SLuca Coelho #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \ 1684c3c995SLuca Coelho 0xb2, 0xec, 0xf5, 0xa3, \ 1784c3c995SLuca Coelho 0x59, 0x4f, 0x4a, 0xea) 1884c3c995SLuca Coelho 1984c3c995SLuca Coelho void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) 2084c3c995SLuca Coelho { 2184c3c995SLuca Coelho struct efivar_entry *pnvm_efivar; 2284c3c995SLuca Coelho void *data; 2384c3c995SLuca Coelho unsigned long package_size; 2484c3c995SLuca Coelho int err; 2584c3c995SLuca Coelho 26*9dad325fSLuca Coelho *len = 0; 27*9dad325fSLuca Coelho 2884c3c995SLuca Coelho pnvm_efivar = kzalloc(sizeof(*pnvm_efivar), GFP_KERNEL); 2984c3c995SLuca Coelho if (!pnvm_efivar) 3084c3c995SLuca Coelho return ERR_PTR(-ENOMEM); 3184c3c995SLuca Coelho 3284c3c995SLuca Coelho memcpy(&pnvm_efivar->var.VariableName, IWL_UEFI_OEM_PNVM_NAME, 3384c3c995SLuca Coelho sizeof(IWL_UEFI_OEM_PNVM_NAME)); 3484c3c995SLuca Coelho pnvm_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 3584c3c995SLuca Coelho 3684c3c995SLuca Coelho /* 3784c3c995SLuca Coelho * TODO: we hardcode a maximum length here, because reading 3884c3c995SLuca Coelho * from the UEFI is not working. To implement this properly, 3984c3c995SLuca Coelho * we have to call efivar_entry_size(). 4084c3c995SLuca Coelho */ 4184c3c995SLuca Coelho package_size = IWL_HARDCODED_PNVM_SIZE; 4284c3c995SLuca Coelho 4384c3c995SLuca Coelho data = kmalloc(package_size, GFP_KERNEL); 4484c3c995SLuca Coelho if (!data) { 4584c3c995SLuca Coelho data = ERR_PTR(-ENOMEM); 4684c3c995SLuca Coelho goto out; 4784c3c995SLuca Coelho } 4884c3c995SLuca Coelho 4984c3c995SLuca Coelho err = efivar_entry_get(pnvm_efivar, NULL, &package_size, data); 5084c3c995SLuca Coelho if (err) { 5184c3c995SLuca Coelho IWL_DEBUG_FW(trans, 5284c3c995SLuca Coelho "PNVM UEFI variable not found %d (len %zd)\n", 5384c3c995SLuca Coelho err, package_size); 5484c3c995SLuca Coelho kfree(data); 5584c3c995SLuca Coelho data = ERR_PTR(err); 5684c3c995SLuca Coelho goto out; 5784c3c995SLuca Coelho } 5884c3c995SLuca Coelho 5984c3c995SLuca Coelho IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %zd\n", package_size); 6084c3c995SLuca Coelho *len = package_size; 6184c3c995SLuca Coelho 6284c3c995SLuca Coelho out: 6384c3c995SLuca Coelho kfree(pnvm_efivar); 6484c3c995SLuca Coelho 6584c3c995SLuca Coelho return data; 6684c3c995SLuca Coelho } 67*9dad325fSLuca Coelho 68*9dad325fSLuca Coelho static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans, 69*9dad325fSLuca Coelho const u8 *data, size_t len) 70*9dad325fSLuca Coelho { 71*9dad325fSLuca Coelho struct iwl_ucode_tlv *tlv; 72*9dad325fSLuca Coelho u8 *reduce_power_data = NULL, *tmp; 73*9dad325fSLuca Coelho u32 size = 0; 74*9dad325fSLuca Coelho 75*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); 76*9dad325fSLuca Coelho 77*9dad325fSLuca Coelho while (len >= sizeof(*tlv)) { 78*9dad325fSLuca Coelho u32 tlv_len, tlv_type; 79*9dad325fSLuca Coelho 80*9dad325fSLuca Coelho len -= sizeof(*tlv); 81*9dad325fSLuca Coelho tlv = (void *)data; 82*9dad325fSLuca Coelho 83*9dad325fSLuca Coelho tlv_len = le32_to_cpu(tlv->length); 84*9dad325fSLuca Coelho tlv_type = le32_to_cpu(tlv->type); 85*9dad325fSLuca Coelho 86*9dad325fSLuca Coelho if (len < tlv_len) { 87*9dad325fSLuca Coelho IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 88*9dad325fSLuca Coelho len, tlv_len); 89*9dad325fSLuca Coelho reduce_power_data = ERR_PTR(-EINVAL); 90*9dad325fSLuca Coelho goto out; 91*9dad325fSLuca Coelho } 92*9dad325fSLuca Coelho 93*9dad325fSLuca Coelho data += sizeof(*tlv); 94*9dad325fSLuca Coelho 95*9dad325fSLuca Coelho switch (tlv_type) { 96*9dad325fSLuca Coelho case IWL_UCODE_TLV_MEM_DESC: { 97*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, 98*9dad325fSLuca Coelho "Got IWL_UCODE_TLV_MEM_DESC len %d\n", 99*9dad325fSLuca Coelho tlv_len); 100*9dad325fSLuca Coelho 101*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); 102*9dad325fSLuca Coelho 103*9dad325fSLuca Coelho tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL); 104*9dad325fSLuca Coelho if (!tmp) { 105*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, 106*9dad325fSLuca Coelho "Couldn't allocate (more) reduce_power_data\n"); 107*9dad325fSLuca Coelho 108*9dad325fSLuca Coelho reduce_power_data = ERR_PTR(-ENOMEM); 109*9dad325fSLuca Coelho goto out; 110*9dad325fSLuca Coelho } 111*9dad325fSLuca Coelho 112*9dad325fSLuca Coelho reduce_power_data = tmp; 113*9dad325fSLuca Coelho 114*9dad325fSLuca Coelho memcpy(reduce_power_data + size, data, tlv_len); 115*9dad325fSLuca Coelho 116*9dad325fSLuca Coelho size += tlv_len; 117*9dad325fSLuca Coelho 118*9dad325fSLuca Coelho break; 119*9dad325fSLuca Coelho } 120*9dad325fSLuca Coelho case IWL_UCODE_TLV_PNVM_SKU: 121*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, 122*9dad325fSLuca Coelho "New REDUCE_POWER section started, stop parsing.\n"); 123*9dad325fSLuca Coelho goto done; 124*9dad325fSLuca Coelho default: 125*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", 126*9dad325fSLuca Coelho tlv_type, tlv_len); 127*9dad325fSLuca Coelho break; 128*9dad325fSLuca Coelho } 129*9dad325fSLuca Coelho 130*9dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 131*9dad325fSLuca Coelho data += ALIGN(tlv_len, 4); 132*9dad325fSLuca Coelho } 133*9dad325fSLuca Coelho 134*9dad325fSLuca Coelho done: 135*9dad325fSLuca Coelho if (!size) { 136*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); 137*9dad325fSLuca Coelho reduce_power_data = ERR_PTR(-ENOENT); 138*9dad325fSLuca Coelho goto out; 139*9dad325fSLuca Coelho } 140*9dad325fSLuca Coelho 141*9dad325fSLuca Coelho IWL_INFO(trans, "loaded REDUCE_POWER\n"); 142*9dad325fSLuca Coelho 143*9dad325fSLuca Coelho out: 144*9dad325fSLuca Coelho return reduce_power_data; 145*9dad325fSLuca Coelho } 146*9dad325fSLuca Coelho 147*9dad325fSLuca Coelho static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans, 148*9dad325fSLuca Coelho const u8 *data, size_t len) 149*9dad325fSLuca Coelho { 150*9dad325fSLuca Coelho struct iwl_ucode_tlv *tlv; 151*9dad325fSLuca Coelho void *sec_data; 152*9dad325fSLuca Coelho 153*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); 154*9dad325fSLuca Coelho 155*9dad325fSLuca Coelho while (len >= sizeof(*tlv)) { 156*9dad325fSLuca Coelho u32 tlv_len, tlv_type; 157*9dad325fSLuca Coelho 158*9dad325fSLuca Coelho len -= sizeof(*tlv); 159*9dad325fSLuca Coelho tlv = (void *)data; 160*9dad325fSLuca Coelho 161*9dad325fSLuca Coelho tlv_len = le32_to_cpu(tlv->length); 162*9dad325fSLuca Coelho tlv_type = le32_to_cpu(tlv->type); 163*9dad325fSLuca Coelho 164*9dad325fSLuca Coelho if (len < tlv_len) { 165*9dad325fSLuca Coelho IWL_ERR(trans, "invalid TLV len: %zd/%u\n", 166*9dad325fSLuca Coelho len, tlv_len); 167*9dad325fSLuca Coelho return ERR_PTR(-EINVAL); 168*9dad325fSLuca Coelho } 169*9dad325fSLuca Coelho 170*9dad325fSLuca Coelho if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { 171*9dad325fSLuca Coelho struct iwl_sku_id *sku_id = 172*9dad325fSLuca Coelho (void *)(data + sizeof(*tlv)); 173*9dad325fSLuca Coelho 174*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, 175*9dad325fSLuca Coelho "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", 176*9dad325fSLuca Coelho tlv_len); 177*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", 178*9dad325fSLuca Coelho le32_to_cpu(sku_id->data[0]), 179*9dad325fSLuca Coelho le32_to_cpu(sku_id->data[1]), 180*9dad325fSLuca Coelho le32_to_cpu(sku_id->data[2])); 181*9dad325fSLuca Coelho 182*9dad325fSLuca Coelho data += sizeof(*tlv) + ALIGN(tlv_len, 4); 183*9dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 184*9dad325fSLuca Coelho 185*9dad325fSLuca Coelho if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && 186*9dad325fSLuca Coelho trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && 187*9dad325fSLuca Coelho trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { 188*9dad325fSLuca Coelho sec_data = iwl_uefi_reduce_power_section(trans, 189*9dad325fSLuca Coelho data, 190*9dad325fSLuca Coelho len); 191*9dad325fSLuca Coelho if (!IS_ERR(sec_data)) 192*9dad325fSLuca Coelho return sec_data; 193*9dad325fSLuca Coelho } else { 194*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); 195*9dad325fSLuca Coelho } 196*9dad325fSLuca Coelho } else { 197*9dad325fSLuca Coelho data += sizeof(*tlv) + ALIGN(tlv_len, 4); 198*9dad325fSLuca Coelho len -= ALIGN(tlv_len, 4); 199*9dad325fSLuca Coelho } 200*9dad325fSLuca Coelho } 201*9dad325fSLuca Coelho 202*9dad325fSLuca Coelho return ERR_PTR(-ENOENT); 203*9dad325fSLuca Coelho } 204*9dad325fSLuca Coelho 205*9dad325fSLuca Coelho void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) 206*9dad325fSLuca Coelho { 207*9dad325fSLuca Coelho struct efivar_entry *reduce_power_efivar; 208*9dad325fSLuca Coelho struct pnvm_sku_package *package; 209*9dad325fSLuca Coelho void *data = NULL; 210*9dad325fSLuca Coelho unsigned long package_size; 211*9dad325fSLuca Coelho int err; 212*9dad325fSLuca Coelho 213*9dad325fSLuca Coelho *len = 0; 214*9dad325fSLuca Coelho 215*9dad325fSLuca Coelho reduce_power_efivar = kzalloc(sizeof(*reduce_power_efivar), GFP_KERNEL); 216*9dad325fSLuca Coelho if (!reduce_power_efivar) 217*9dad325fSLuca Coelho return ERR_PTR(-ENOMEM); 218*9dad325fSLuca Coelho 219*9dad325fSLuca Coelho memcpy(&reduce_power_efivar->var.VariableName, IWL_UEFI_REDUCED_POWER_NAME, 220*9dad325fSLuca Coelho sizeof(IWL_UEFI_REDUCED_POWER_NAME)); 221*9dad325fSLuca Coelho reduce_power_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; 222*9dad325fSLuca Coelho 223*9dad325fSLuca Coelho /* 224*9dad325fSLuca Coelho * TODO: we hardcode a maximum length here, because reading 225*9dad325fSLuca Coelho * from the UEFI is not working. To implement this properly, 226*9dad325fSLuca Coelho * we have to call efivar_entry_size(). 227*9dad325fSLuca Coelho */ 228*9dad325fSLuca Coelho package_size = IWL_HARDCODED_REDUCE_POWER_SIZE; 229*9dad325fSLuca Coelho 230*9dad325fSLuca Coelho package = kmalloc(package_size, GFP_KERNEL); 231*9dad325fSLuca Coelho if (!package) { 232*9dad325fSLuca Coelho package = ERR_PTR(-ENOMEM); 233*9dad325fSLuca Coelho goto out; 234*9dad325fSLuca Coelho } 235*9dad325fSLuca Coelho 236*9dad325fSLuca Coelho err = efivar_entry_get(reduce_power_efivar, NULL, &package_size, package); 237*9dad325fSLuca Coelho if (err) { 238*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, 239*9dad325fSLuca Coelho "Reduced Power UEFI variable not found %d (len %lu)\n", 240*9dad325fSLuca Coelho err, package_size); 241*9dad325fSLuca Coelho kfree(package); 242*9dad325fSLuca Coelho data = ERR_PTR(err); 243*9dad325fSLuca Coelho goto out; 244*9dad325fSLuca Coelho } 245*9dad325fSLuca Coelho 246*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", 247*9dad325fSLuca Coelho package_size); 248*9dad325fSLuca Coelho *len = package_size; 249*9dad325fSLuca Coelho 250*9dad325fSLuca Coelho IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", 251*9dad325fSLuca Coelho package->rev, package->total_size, package->n_skus); 252*9dad325fSLuca Coelho 253*9dad325fSLuca Coelho data = iwl_uefi_reduce_power_parse(trans, package->data, 254*9dad325fSLuca Coelho *len - sizeof(*package)); 255*9dad325fSLuca Coelho 256*9dad325fSLuca Coelho kfree(package); 257*9dad325fSLuca Coelho 258*9dad325fSLuca Coelho out: 259*9dad325fSLuca Coelho kfree(reduce_power_efivar); 260*9dad325fSLuca Coelho 261*9dad325fSLuca Coelho return data; 262*9dad325fSLuca Coelho } 263