in wireless/intel/iwlwifi/iwl-drv.c [652:1292]
static int iwl_parse_tlv_firmware(struct iwl_drv *drv,
const struct firmware *ucode_raw,
struct iwl_firmware_pieces *pieces,
struct iwl_ucode_capabilities *capa,
bool *usniffer_images)
{
struct iwl_tlv_ucode_header *ucode = (void *)ucode_raw->data;
const struct iwl_ucode_tlv *tlv;
size_t len = ucode_raw->size;
const u8 *data;
u32 tlv_len;
u32 usniffer_img;
enum iwl_ucode_tlv_type tlv_type;
const u8 *tlv_data;
char buildstr[25];
u32 build, paging_mem_size;
int num_of_cpus;
bool usniffer_req = false;
if (len < sizeof(*ucode)) {
IWL_ERR(drv, "uCode has invalid length: %zd\n", len);
return -EINVAL;
}
if (ucode->magic != cpu_to_le32(IWL_TLV_UCODE_MAGIC)) {
IWL_ERR(drv, "invalid uCode magic: 0X%x\n",
le32_to_cpu(ucode->magic));
return -EINVAL;
}
drv->fw.ucode_ver = le32_to_cpu(ucode->ver);
memcpy(drv->fw.human_readable, ucode->human_readable,
sizeof(drv->fw.human_readable));
build = le32_to_cpu(ucode->build);
if (build)
sprintf(buildstr, " build %u", build);
else
buildstr[0] = '\0';
snprintf(drv->fw.fw_version,
sizeof(drv->fw.fw_version),
"%u.%u.%u.%u%s %s",
IWL_UCODE_MAJOR(drv->fw.ucode_ver),
IWL_UCODE_MINOR(drv->fw.ucode_ver),
IWL_UCODE_API(drv->fw.ucode_ver),
IWL_UCODE_SERIAL(drv->fw.ucode_ver),
buildstr, iwl_reduced_fw_name(drv));
data = ucode->data;
len -= sizeof(*ucode);
while (len >= sizeof(*tlv)) {
len -= sizeof(*tlv);
tlv = (void *)data;
tlv_len = le32_to_cpu(tlv->length);
tlv_type = le32_to_cpu(tlv->type);
tlv_data = tlv->data;
if (len < tlv_len) {
IWL_ERR(drv, "invalid TLV len: %zd/%u\n",
len, tlv_len);
return -EINVAL;
}
len -= ALIGN(tlv_len, 4);
data += sizeof(*tlv) + ALIGN(tlv_len, 4);
switch (tlv_type) {
case IWL_UCODE_TLV_INST:
set_sec_data(pieces, IWL_UCODE_REGULAR,
IWL_UCODE_SECTION_INST, tlv_data);
set_sec_size(pieces, IWL_UCODE_REGULAR,
IWL_UCODE_SECTION_INST, tlv_len);
set_sec_offset(pieces, IWL_UCODE_REGULAR,
IWL_UCODE_SECTION_INST,
IWLAGN_RTC_INST_LOWER_BOUND);
break;
case IWL_UCODE_TLV_DATA:
set_sec_data(pieces, IWL_UCODE_REGULAR,
IWL_UCODE_SECTION_DATA, tlv_data);
set_sec_size(pieces, IWL_UCODE_REGULAR,
IWL_UCODE_SECTION_DATA, tlv_len);
set_sec_offset(pieces, IWL_UCODE_REGULAR,
IWL_UCODE_SECTION_DATA,
IWLAGN_RTC_DATA_LOWER_BOUND);
break;
case IWL_UCODE_TLV_INIT:
set_sec_data(pieces, IWL_UCODE_INIT,
IWL_UCODE_SECTION_INST, tlv_data);
set_sec_size(pieces, IWL_UCODE_INIT,
IWL_UCODE_SECTION_INST, tlv_len);
set_sec_offset(pieces, IWL_UCODE_INIT,
IWL_UCODE_SECTION_INST,
IWLAGN_RTC_INST_LOWER_BOUND);
break;
case IWL_UCODE_TLV_INIT_DATA:
set_sec_data(pieces, IWL_UCODE_INIT,
IWL_UCODE_SECTION_DATA, tlv_data);
set_sec_size(pieces, IWL_UCODE_INIT,
IWL_UCODE_SECTION_DATA, tlv_len);
set_sec_offset(pieces, IWL_UCODE_INIT,
IWL_UCODE_SECTION_DATA,
IWLAGN_RTC_DATA_LOWER_BOUND);
break;
case IWL_UCODE_TLV_BOOT:
IWL_ERR(drv, "Found unexpected BOOT ucode\n");
break;
case IWL_UCODE_TLV_PROBE_MAX_LEN:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
capa->max_probe_length =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_PAN:
if (tlv_len)
goto invalid_tlv_len;
capa->flags |= IWL_UCODE_TLV_FLAGS_PAN;
break;
case IWL_UCODE_TLV_FLAGS:
/* must be at least one u32 */
if (tlv_len < sizeof(u32))
goto invalid_tlv_len;
/* and a proper number of u32s */
if (tlv_len % sizeof(u32))
goto invalid_tlv_len;
/*
* This driver only reads the first u32 as
* right now no more features are defined,
* if that changes then either the driver
* will not work with the new firmware, or
* it'll not take advantage of new features.
*/
capa->flags = le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_API_CHANGES_SET:
if (tlv_len != sizeof(struct iwl_ucode_api))
goto invalid_tlv_len;
iwl_set_ucode_api_flags(drv, tlv_data, capa);
break;
case IWL_UCODE_TLV_ENABLED_CAPABILITIES:
if (tlv_len != sizeof(struct iwl_ucode_capa))
goto invalid_tlv_len;
iwl_set_ucode_capabilities(drv, tlv_data, capa);
break;
case IWL_UCODE_TLV_INIT_EVTLOG_PTR:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
pieces->init_evtlog_ptr =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_INIT_EVTLOG_SIZE:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
pieces->init_evtlog_size =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_INIT_ERRLOG_PTR:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
pieces->init_errlog_ptr =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_RUNT_EVTLOG_PTR:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
pieces->inst_evtlog_ptr =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_RUNT_EVTLOG_SIZE:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
pieces->inst_evtlog_size =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_RUNT_ERRLOG_PTR:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
pieces->inst_errlog_ptr =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_ENHANCE_SENS_TBL:
if (tlv_len)
goto invalid_tlv_len;
drv->fw.enhance_sensitivity_table = true;
break;
case IWL_UCODE_TLV_WOWLAN_INST:
set_sec_data(pieces, IWL_UCODE_WOWLAN,
IWL_UCODE_SECTION_INST, tlv_data);
set_sec_size(pieces, IWL_UCODE_WOWLAN,
IWL_UCODE_SECTION_INST, tlv_len);
set_sec_offset(pieces, IWL_UCODE_WOWLAN,
IWL_UCODE_SECTION_INST,
IWLAGN_RTC_INST_LOWER_BOUND);
break;
case IWL_UCODE_TLV_WOWLAN_DATA:
set_sec_data(pieces, IWL_UCODE_WOWLAN,
IWL_UCODE_SECTION_DATA, tlv_data);
set_sec_size(pieces, IWL_UCODE_WOWLAN,
IWL_UCODE_SECTION_DATA, tlv_len);
set_sec_offset(pieces, IWL_UCODE_WOWLAN,
IWL_UCODE_SECTION_DATA,
IWLAGN_RTC_DATA_LOWER_BOUND);
break;
case IWL_UCODE_TLV_PHY_CALIBRATION_SIZE:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
capa->standard_phy_calibration_size =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_SEC_RT:
iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR,
tlv_len);
drv->fw.type = IWL_FW_MVM;
break;
case IWL_UCODE_TLV_SEC_INIT:
iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT,
tlv_len);
drv->fw.type = IWL_FW_MVM;
break;
case IWL_UCODE_TLV_SEC_WOWLAN:
iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN,
tlv_len);
drv->fw.type = IWL_FW_MVM;
break;
case IWL_UCODE_TLV_DEF_CALIB:
if (tlv_len != sizeof(struct iwl_tlv_calib_data))
goto invalid_tlv_len;
if (iwl_set_default_calib(drv, tlv_data))
goto tlv_error;
break;
case IWL_UCODE_TLV_PHY_SKU:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
drv->fw.phy_config = le32_to_cpup((__le32 *)tlv_data);
drv->fw.valid_tx_ant = (drv->fw.phy_config &
FW_PHY_CFG_TX_CHAIN) >>
FW_PHY_CFG_TX_CHAIN_POS;
drv->fw.valid_rx_ant = (drv->fw.phy_config &
FW_PHY_CFG_RX_CHAIN) >>
FW_PHY_CFG_RX_CHAIN_POS;
break;
case IWL_UCODE_TLV_SECURE_SEC_RT:
iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR,
tlv_len);
drv->fw.type = IWL_FW_MVM;
break;
case IWL_UCODE_TLV_SECURE_SEC_INIT:
iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT,
tlv_len);
drv->fw.type = IWL_FW_MVM;
break;
case IWL_UCODE_TLV_SECURE_SEC_WOWLAN:
iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN,
tlv_len);
drv->fw.type = IWL_FW_MVM;
break;
case IWL_UCODE_TLV_NUM_OF_CPU:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
num_of_cpus =
le32_to_cpup((__le32 *)tlv_data);
if (num_of_cpus == 2) {
drv->fw.img[IWL_UCODE_REGULAR].is_dual_cpus =
true;
drv->fw.img[IWL_UCODE_INIT].is_dual_cpus =
true;
drv->fw.img[IWL_UCODE_WOWLAN].is_dual_cpus =
true;
} else if ((num_of_cpus > 2) || (num_of_cpus < 1)) {
IWL_ERR(drv, "Driver support upto 2 CPUs\n");
return -EINVAL;
}
break;
case IWL_UCODE_TLV_CSCHEME:
if (iwl_store_cscheme(&drv->fw, tlv_data, tlv_len))
goto invalid_tlv_len;
break;
case IWL_UCODE_TLV_N_SCAN_CHANNELS:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
capa->n_scan_channels =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_FW_VERSION: {
__le32 *ptr = (void *)tlv_data;
u32 major, minor;
u8 local_comp;
if (tlv_len != sizeof(u32) * 3)
goto invalid_tlv_len;
major = le32_to_cpup(ptr++);
minor = le32_to_cpup(ptr++);
local_comp = le32_to_cpup(ptr);
if (major >= 35)
snprintf(drv->fw.fw_version,
sizeof(drv->fw.fw_version),
"%u.%08x.%u %s", major, minor,
local_comp, iwl_reduced_fw_name(drv));
else
snprintf(drv->fw.fw_version,
sizeof(drv->fw.fw_version),
"%u.%u.%u %s", major, minor,
local_comp, iwl_reduced_fw_name(drv));
break;
}
case IWL_UCODE_TLV_FW_DBG_DEST: {
struct iwl_fw_dbg_dest_tlv *dest = NULL;
struct iwl_fw_dbg_dest_tlv_v1 *dest_v1 = NULL;
u8 mon_mode;
pieces->dbg_dest_ver = (u8 *)tlv_data;
if (*pieces->dbg_dest_ver == 1) {
dest = (void *)tlv_data;
} else if (*pieces->dbg_dest_ver == 0) {
dest_v1 = (void *)tlv_data;
} else {
IWL_ERR(drv,
"The version is %d, and it is invalid\n",
*pieces->dbg_dest_ver);
break;
}
if (pieces->dbg_dest_tlv_init) {
IWL_ERR(drv,
"dbg destination ignored, already exists\n");
break;
}
pieces->dbg_dest_tlv_init = true;
if (dest_v1) {
pieces->dbg_dest_tlv_v1 = dest_v1;
mon_mode = dest_v1->monitor_mode;
} else {
pieces->dbg_dest_tlv = dest;
mon_mode = dest->monitor_mode;
}
IWL_INFO(drv, "Found debug destination: %s\n",
get_fw_dbg_mode_string(mon_mode));
drv->fw.dbg.n_dest_reg = (dest_v1) ?
tlv_len -
offsetof(struct iwl_fw_dbg_dest_tlv_v1,
reg_ops) :
tlv_len -
offsetof(struct iwl_fw_dbg_dest_tlv,
reg_ops);
drv->fw.dbg.n_dest_reg /=
sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]);
break;
}
case IWL_UCODE_TLV_FW_DBG_CONF: {
struct iwl_fw_dbg_conf_tlv *conf = (void *)tlv_data;
if (!pieces->dbg_dest_tlv_init) {
IWL_ERR(drv,
"Ignore dbg config %d - no destination configured\n",
conf->id);
break;
}
if (conf->id >= ARRAY_SIZE(drv->fw.dbg.conf_tlv)) {
IWL_ERR(drv,
"Skip unknown configuration: %d\n",
conf->id);
break;
}
if (pieces->dbg_conf_tlv[conf->id]) {
IWL_ERR(drv,
"Ignore duplicate dbg config %d\n",
conf->id);
break;
}
if (conf->usniffer)
usniffer_req = true;
IWL_INFO(drv, "Found debug configuration: %d\n",
conf->id);
pieces->dbg_conf_tlv[conf->id] = conf;
pieces->dbg_conf_tlv_len[conf->id] = tlv_len;
break;
}
case IWL_UCODE_TLV_FW_DBG_TRIGGER: {
struct iwl_fw_dbg_trigger_tlv *trigger =
(void *)tlv_data;
u32 trigger_id = le32_to_cpu(trigger->id);
if (trigger_id >= ARRAY_SIZE(drv->fw.dbg.trigger_tlv)) {
IWL_ERR(drv,
"Skip unknown trigger: %u\n",
trigger->id);
break;
}
if (pieces->dbg_trigger_tlv[trigger_id]) {
IWL_ERR(drv,
"Ignore duplicate dbg trigger %u\n",
trigger->id);
break;
}
IWL_INFO(drv, "Found debug trigger: %u\n", trigger->id);
pieces->dbg_trigger_tlv[trigger_id] = trigger;
pieces->dbg_trigger_tlv_len[trigger_id] = tlv_len;
break;
}
case IWL_UCODE_TLV_FW_DBG_DUMP_LST: {
if (tlv_len != sizeof(u32)) {
IWL_ERR(drv,
"dbg lst mask size incorrect, skip\n");
break;
}
drv->fw.dbg.dump_mask =
le32_to_cpup((__le32 *)tlv_data);
break;
}
case IWL_UCODE_TLV_SEC_RT_USNIFFER:
*usniffer_images = true;
iwl_store_ucode_sec(pieces, tlv_data,
IWL_UCODE_REGULAR_USNIFFER,
tlv_len);
break;
case IWL_UCODE_TLV_PAGING:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
paging_mem_size = le32_to_cpup((__le32 *)tlv_data);
IWL_DEBUG_FW(drv,
"Paging: paging enabled (size = %u bytes)\n",
paging_mem_size);
if (paging_mem_size > MAX_PAGING_IMAGE_SIZE) {
IWL_ERR(drv,
"Paging: driver supports up to %lu bytes for paging image\n",
MAX_PAGING_IMAGE_SIZE);
return -EINVAL;
}
if (paging_mem_size & (FW_PAGING_SIZE - 1)) {
IWL_ERR(drv,
"Paging: image isn't multiple %lu\n",
FW_PAGING_SIZE);
return -EINVAL;
}
drv->fw.img[IWL_UCODE_REGULAR].paging_mem_size =
paging_mem_size;
usniffer_img = IWL_UCODE_REGULAR_USNIFFER;
drv->fw.img[usniffer_img].paging_mem_size =
paging_mem_size;
break;
case IWL_UCODE_TLV_FW_GSCAN_CAPA:
/* ignored */
break;
case IWL_UCODE_TLV_FW_MEM_SEG: {
struct iwl_fw_dbg_mem_seg_tlv *dbg_mem =
(void *)tlv_data;
size_t size;
struct iwl_fw_dbg_mem_seg_tlv *n;
if (tlv_len != (sizeof(*dbg_mem)))
goto invalid_tlv_len;
IWL_DEBUG_INFO(drv, "Found debug memory segment: %u\n",
dbg_mem->data_type);
size = sizeof(*pieces->dbg_mem_tlv) *
(pieces->n_mem_tlv + 1);
n = krealloc(pieces->dbg_mem_tlv, size, GFP_KERNEL);
if (!n)
return -ENOMEM;
pieces->dbg_mem_tlv = n;
pieces->dbg_mem_tlv[pieces->n_mem_tlv] = *dbg_mem;
pieces->n_mem_tlv++;
break;
}
case IWL_UCODE_TLV_IML: {
drv->fw.iml_len = tlv_len;
drv->fw.iml = kmemdup(tlv_data, tlv_len, GFP_KERNEL);
if (!drv->fw.iml)
return -ENOMEM;
break;
}
case IWL_UCODE_TLV_FW_RECOVERY_INFO: {
struct {
__le32 buf_addr;
__le32 buf_size;
} *recov_info = (void *)tlv_data;
if (tlv_len != sizeof(*recov_info))
goto invalid_tlv_len;
capa->error_log_addr =
le32_to_cpu(recov_info->buf_addr);
capa->error_log_size =
le32_to_cpu(recov_info->buf_size);
}
break;
case IWL_UCODE_TLV_FW_FSEQ_VERSION: {
struct {
u8 version[32];
u8 sha1[20];
} *fseq_ver = (void *)tlv_data;
if (tlv_len != sizeof(*fseq_ver))
goto invalid_tlv_len;
IWL_INFO(drv, "TLV_FW_FSEQ_VERSION: %s\n",
fseq_ver->version);
}
break;
case IWL_UCODE_TLV_FW_NUM_STATIONS:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
if (le32_to_cpup((__le32 *)tlv_data) >
IWL_MVM_STATION_COUNT_MAX) {
IWL_ERR(drv,
"%d is an invalid number of station\n",
le32_to_cpup((__le32 *)tlv_data));
goto tlv_error;
}
capa->num_stations =
le32_to_cpup((__le32 *)tlv_data);
break;
case IWL_UCODE_TLV_UMAC_DEBUG_ADDRS: {
struct iwl_umac_debug_addrs *dbg_ptrs =
(void *)tlv_data;
if (tlv_len != sizeof(*dbg_ptrs))
goto invalid_tlv_len;
if (drv->trans->trans_cfg->device_family <
IWL_DEVICE_FAMILY_22000)
break;
drv->trans->dbg.umac_error_event_table =
le32_to_cpu(dbg_ptrs->error_info_addr) &
~FW_ADDR_CACHE_CONTROL;
drv->trans->dbg.error_event_table_tlv_status |=
IWL_ERROR_EVENT_TABLE_UMAC;
break;
}
case IWL_UCODE_TLV_LMAC_DEBUG_ADDRS: {
struct iwl_lmac_debug_addrs *dbg_ptrs =
(void *)tlv_data;
if (tlv_len != sizeof(*dbg_ptrs))
goto invalid_tlv_len;
if (drv->trans->trans_cfg->device_family <
IWL_DEVICE_FAMILY_22000)
break;
drv->trans->dbg.lmac_error_event_table[0] =
le32_to_cpu(dbg_ptrs->error_event_table_ptr) &
~FW_ADDR_CACHE_CONTROL;
drv->trans->dbg.error_event_table_tlv_status |=
IWL_ERROR_EVENT_TABLE_LMAC1;
break;
}
case IWL_UCODE_TLV_TYPE_REGIONS:
iwl_parse_dbg_tlv_assert_tables(drv, tlv);
fallthrough;
case IWL_UCODE_TLV_TYPE_DEBUG_INFO:
case IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION:
case IWL_UCODE_TLV_TYPE_HCMD:
case IWL_UCODE_TLV_TYPE_TRIGGERS:
case IWL_UCODE_TLV_TYPE_CONF_SET:
if (iwlwifi_mod_params.enable_ini)
iwl_dbg_tlv_alloc(drv->trans, tlv, false);
break;
case IWL_UCODE_TLV_CMD_VERSIONS:
if (tlv_len % sizeof(struct iwl_fw_cmd_version)) {
IWL_ERR(drv,
"Invalid length for command versions: %u\n",
tlv_len);
tlv_len /= sizeof(struct iwl_fw_cmd_version);
tlv_len *= sizeof(struct iwl_fw_cmd_version);
}
if (WARN_ON(capa->cmd_versions))
return -EINVAL;
capa->cmd_versions = kmemdup(tlv_data, tlv_len,
GFP_KERNEL);
if (!capa->cmd_versions)
return -ENOMEM;
capa->n_cmd_versions =
tlv_len / sizeof(struct iwl_fw_cmd_version);
break;
case IWL_UCODE_TLV_PHY_INTEGRATION_VERSION:
if (drv->fw.phy_integration_ver) {
IWL_ERR(drv,
"phy integration str ignored, already exists\n");
break;
}
drv->fw.phy_integration_ver =
kmemdup(tlv_data, tlv_len, GFP_KERNEL);
if (!drv->fw.phy_integration_ver)
return -ENOMEM;
drv->fw.phy_integration_ver_len = tlv_len;
break;
case IWL_UCODE_TLV_SEC_TABLE_ADDR:
case IWL_UCODE_TLV_D3_KEK_KCK_ADDR:
iwl_drv_set_dump_exclude(drv, tlv_type,
tlv_data, tlv_len);
break;
default:
IWL_DEBUG_INFO(drv, "unknown TLV: %d\n", tlv_type);
break;
}
}
if (!fw_has_capa(capa, IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED) &&
usniffer_req && !*usniffer_images) {
IWL_ERR(drv,
"user selected to work with usniffer but usniffer image isn't available in ucode package\n");
return -EINVAL;
}
if (len) {
IWL_ERR(drv, "invalid TLV after parsing: %zd\n", len);
iwl_print_hex_dump(drv, IWL_DL_FW, (u8 *)data, len);
return -EINVAL;
}
return 0;
invalid_tlv_len:
IWL_ERR(drv, "TLV %d has invalid size: %u\n", tlv_type, tlv_len);
tlv_error:
iwl_print_hex_dump(drv, IWL_DL_FW, tlv_data, tlv_len);
return -EINVAL;
}