0001
0002
0003
0004
0005
0006 #include "iwl-drv.h"
0007 #include "pnvm.h"
0008 #include "iwl-prph.h"
0009 #include "iwl-io.h"
0010
0011 #include "fw/uefi.h"
0012 #include "fw/api/alive.h"
0013 #include <linux/efi.h>
0014 #include "fw/runtime.h"
0015
0016 #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \
0017 0xb2, 0xec, 0xf5, 0xa3, \
0018 0x59, 0x4f, 0x4a, 0xea)
0019
0020 void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len)
0021 {
0022 void *data;
0023 unsigned long package_size;
0024 efi_status_t status;
0025
0026 *len = 0;
0027
0028 if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
0029 return ERR_PTR(-ENODEV);
0030
0031
0032
0033
0034
0035
0036 package_size = IWL_HARDCODED_PNVM_SIZE;
0037
0038 data = kmalloc(package_size, GFP_KERNEL);
0039 if (!data)
0040 return ERR_PTR(-ENOMEM);
0041
0042 status = efi.get_variable(IWL_UEFI_OEM_PNVM_NAME, &IWL_EFI_VAR_GUID,
0043 NULL, &package_size, data);
0044 if (status != EFI_SUCCESS) {
0045 IWL_DEBUG_FW(trans,
0046 "PNVM UEFI variable not found 0x%lx (len %lu)\n",
0047 status, package_size);
0048 kfree(data);
0049 return ERR_PTR(-ENOENT);
0050 }
0051
0052 IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size);
0053 *len = package_size;
0054
0055 return data;
0056 }
0057
0058 static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans,
0059 const u8 *data, size_t len)
0060 {
0061 const struct iwl_ucode_tlv *tlv;
0062 u8 *reduce_power_data = NULL, *tmp;
0063 u32 size = 0;
0064
0065 IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n");
0066
0067 while (len >= sizeof(*tlv)) {
0068 u32 tlv_len, tlv_type;
0069
0070 len -= sizeof(*tlv);
0071 tlv = (const void *)data;
0072
0073 tlv_len = le32_to_cpu(tlv->length);
0074 tlv_type = le32_to_cpu(tlv->type);
0075
0076 if (len < tlv_len) {
0077 IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
0078 len, tlv_len);
0079 kfree(reduce_power_data);
0080 reduce_power_data = ERR_PTR(-EINVAL);
0081 goto out;
0082 }
0083
0084 data += sizeof(*tlv);
0085
0086 switch (tlv_type) {
0087 case IWL_UCODE_TLV_MEM_DESC: {
0088 IWL_DEBUG_FW(trans,
0089 "Got IWL_UCODE_TLV_MEM_DESC len %d\n",
0090 tlv_len);
0091
0092 IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len);
0093
0094 tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL);
0095 if (!tmp) {
0096 IWL_DEBUG_FW(trans,
0097 "Couldn't allocate (more) reduce_power_data\n");
0098
0099 kfree(reduce_power_data);
0100 reduce_power_data = ERR_PTR(-ENOMEM);
0101 goto out;
0102 }
0103
0104 reduce_power_data = tmp;
0105
0106 memcpy(reduce_power_data + size, data, tlv_len);
0107
0108 size += tlv_len;
0109
0110 break;
0111 }
0112 case IWL_UCODE_TLV_PNVM_SKU:
0113 IWL_DEBUG_FW(trans,
0114 "New REDUCE_POWER section started, stop parsing.\n");
0115 goto done;
0116 default:
0117 IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
0118 tlv_type, tlv_len);
0119 break;
0120 }
0121
0122 len -= ALIGN(tlv_len, 4);
0123 data += ALIGN(tlv_len, 4);
0124 }
0125
0126 done:
0127 if (!size) {
0128 IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n");
0129
0130
0131
0132 kfree(reduce_power_data);
0133 reduce_power_data = ERR_PTR(-ENOENT);
0134 goto out;
0135 }
0136
0137 IWL_INFO(trans, "loaded REDUCE_POWER\n");
0138
0139 out:
0140 return reduce_power_data;
0141 }
0142
0143 static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans,
0144 const u8 *data, size_t len)
0145 {
0146 const struct iwl_ucode_tlv *tlv;
0147 void *sec_data;
0148
0149 IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n");
0150
0151 while (len >= sizeof(*tlv)) {
0152 u32 tlv_len, tlv_type;
0153
0154 len -= sizeof(*tlv);
0155 tlv = (const void *)data;
0156
0157 tlv_len = le32_to_cpu(tlv->length);
0158 tlv_type = le32_to_cpu(tlv->type);
0159
0160 if (len < tlv_len) {
0161 IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
0162 len, tlv_len);
0163 return ERR_PTR(-EINVAL);
0164 }
0165
0166 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
0167 const struct iwl_sku_id *sku_id =
0168 (const void *)(data + sizeof(*tlv));
0169
0170 IWL_DEBUG_FW(trans,
0171 "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
0172 tlv_len);
0173 IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
0174 le32_to_cpu(sku_id->data[0]),
0175 le32_to_cpu(sku_id->data[1]),
0176 le32_to_cpu(sku_id->data[2]));
0177
0178 data += sizeof(*tlv) + ALIGN(tlv_len, 4);
0179 len -= ALIGN(tlv_len, 4);
0180
0181 if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) &&
0182 trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) &&
0183 trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) {
0184 sec_data = iwl_uefi_reduce_power_section(trans,
0185 data,
0186 len);
0187 if (!IS_ERR(sec_data))
0188 return sec_data;
0189 } else {
0190 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
0191 }
0192 } else {
0193 data += sizeof(*tlv) + ALIGN(tlv_len, 4);
0194 len -= ALIGN(tlv_len, 4);
0195 }
0196 }
0197
0198 return ERR_PTR(-ENOENT);
0199 }
0200
0201 void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len)
0202 {
0203 struct pnvm_sku_package *package;
0204 void *data = NULL;
0205 unsigned long package_size;
0206 efi_status_t status;
0207
0208 *len = 0;
0209
0210 if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
0211 return ERR_PTR(-ENODEV);
0212
0213
0214
0215
0216
0217
0218 package_size = IWL_HARDCODED_REDUCE_POWER_SIZE;
0219
0220 package = kmalloc(package_size, GFP_KERNEL);
0221 if (!package)
0222 return ERR_PTR(-ENOMEM);
0223
0224 status = efi.get_variable(IWL_UEFI_REDUCED_POWER_NAME, &IWL_EFI_VAR_GUID,
0225 NULL, &package_size, data);
0226 if (status != EFI_SUCCESS) {
0227 IWL_DEBUG_FW(trans,
0228 "Reduced Power UEFI variable not found 0x%lx (len %lu)\n",
0229 status, package_size);
0230 kfree(package);
0231 return ERR_PTR(-ENOENT);
0232 }
0233
0234 IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n",
0235 package_size);
0236 *len = package_size;
0237
0238 IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n",
0239 package->rev, package->total_size, package->n_skus);
0240
0241 data = iwl_uefi_reduce_power_parse(trans, package->data,
0242 *len - sizeof(*package));
0243
0244 kfree(package);
0245
0246 return data;
0247 }
0248
0249 #ifdef CONFIG_ACPI
0250 static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data,
0251 struct iwl_fw_runtime *fwrt)
0252 {
0253 int i, j;
0254
0255 if (sgom_data->revision != 1)
0256 return -EINVAL;
0257
0258 memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map,
0259 sizeof(fwrt->sgom_table.offset_map));
0260
0261 for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) {
0262 for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) {
0263
0264
0265
0266 u8 value = fwrt->sgom_table.offset_map[i][j];
0267 u8 low = value & 0xF;
0268 u8 high = (value & 0xF0) >> 4;
0269
0270 if (high > fwrt->geo_num_profiles)
0271 high = 0;
0272 if (low > fwrt->geo_num_profiles)
0273 low = 0;
0274 fwrt->sgom_table.offset_map[i][j] = (high << 4) | low;
0275 }
0276 }
0277
0278 fwrt->sgom_enabled = true;
0279 return 0;
0280 }
0281
0282 void iwl_uefi_get_sgom_table(struct iwl_trans *trans,
0283 struct iwl_fw_runtime *fwrt)
0284 {
0285 struct uefi_cnv_wlan_sgom_data *data;
0286 unsigned long package_size;
0287 efi_status_t status;
0288 int ret;
0289
0290 if (!fwrt->geo_enabled ||
0291 !efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
0292 return;
0293
0294
0295
0296
0297
0298 package_size = IWL_HARDCODED_SGOM_SIZE;
0299
0300 data = kmalloc(package_size, GFP_KERNEL);
0301 if (!data)
0302 return;
0303
0304 status = efi.get_variable(IWL_UEFI_SGOM_NAME, &IWL_EFI_VAR_GUID,
0305 NULL, &package_size, data);
0306 if (status != EFI_SUCCESS) {
0307 IWL_DEBUG_FW(trans,
0308 "SGOM UEFI variable not found 0x%lx\n", status);
0309 goto out_free;
0310 }
0311
0312 IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n",
0313 package_size);
0314
0315 ret = iwl_uefi_sgom_parse(data, fwrt);
0316 if (ret < 0)
0317 IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n");
0318
0319 out_free:
0320 kfree(data);
0321
0322 }
0323 IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table);
0324 #endif