iwlwifi: firmware TLV parser — capabilities, version, scan channels

Added rb_iwl_mvm_parse_firmware() — walks Intel firmware TLV sections cross-referenced from Linux 7.1 fw/file.h (struct iwl_ucode_tlv).

Extracts:
- IWL_UCODE_TLV_ENABLED_CAPABILITIES (type 30) — capability bitmap
- IWL_UCODE_TLV_N_SCAN_CHANNELS (type 31) — firmware channel limit
- IWL_UCODE_TLV_FW_VERSION (type 36) — human-readable version string

Wired into rb_iwlwifi_linux_prepare() — capabilities and version logged at
info level during firmware load. TLV entries are 4-byte aligned per Intel
firmware specification.
This commit is contained in:
2026-07-08 13:59:39 +03:00
parent ecad753f5f
commit 069178df95
3 changed files with 142 additions and 4 deletions
@@ -83,3 +83,91 @@ int rb_iwl_mvm_rate_to_mcs(int signal)
if (signal > -80) return 3;
return 1;
}
/* ------------------------------------------------------------------ */
/* TLV type IDs — Linux 7.1 fw/file.h enum iwl_ucode_tlv_type */
/* ------------------------------------------------------------------ */
#define RB_IWL_UCODE_TLV_ENABLED_CAPABILITIES 30
#define RB_IWL_UCODE_TLV_N_SCAN_CHANNELS 31
#define RB_IWL_UCODE_TLV_FW_VERSION 36
int rb_iwl_mvm_parse_firmware(const uint8_t *data, size_t len,
struct rb_iwl_fw_metadata *out)
{
const uint8_t *p, *end;
uint32_t magic;
if (!data || !out || len < RB_IWL_TLV_HDR_SIZE)
return -1;
memset(out, 0, sizeof(*out));
magic = (uint32_t)data[0] | ((uint32_t)data[1] << 8) |
((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
if (magic != RB_IWL_TLV_UCODE_MAGIC)
return -1;
out->version = (uint32_t)data[4] | ((uint32_t)data[5] << 8) |
((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24);
out->build = (uint32_t)data[8] | ((uint32_t)data[9] << 8) |
((uint32_t)data[10] << 16) | ((uint32_t)data[11] << 24);
out->api = (uint8_t)((out->version >> 8) & 0xFFU);
p = data + RB_IWL_TLV_HDR_SIZE;
end = data + len;
while (p + 8 <= end) {
uint32_t tlv_type, tlv_len;
const uint8_t *tlv_data;
tlv_type = (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
tlv_len = (uint32_t)p[4] | ((uint32_t)p[5] << 8) |
((uint32_t)p[6] << 16) | ((uint32_t)p[7] << 24);
p += 8;
if (p + tlv_len > end)
break;
tlv_data = p;
p += tlv_len;
/* TLV entries are 4-byte aligned per Intel firmware spec */
if (tlv_len & 3)
p += 4 - (tlv_len & 3);
switch (tlv_type) {
case RB_IWL_UCODE_TLV_ENABLED_CAPABILITIES:
if (tlv_len >= 4)
out->capabilities = (uint32_t)tlv_data[0] |
((uint32_t)tlv_data[1] << 8) |
((uint32_t)tlv_data[2] << 16) |
((uint32_t)tlv_data[3] << 24);
break;
case RB_IWL_UCODE_TLV_N_SCAN_CHANNELS:
if (tlv_len >= 4)
out->n_scan_channels = (uint32_t)tlv_data[0] |
((uint32_t)tlv_data[1] << 8) |
((uint32_t)tlv_data[2] << 16) |
((uint32_t)tlv_data[3] << 24);
break;
case RB_IWL_UCODE_TLV_FW_VERSION:
if (tlv_len > 0) {
size_t copy = tlv_len;
if (copy >= RB_IWL_FW_VER_HUMAN_READABLE_SZ)
copy = RB_IWL_FW_VER_HUMAN_READABLE_SZ - 1;
memcpy(out->version_str, tlv_data, copy);
out->version_str[copy] = '\0';
}
break;
default:
break;
}
}
out->parsed = 1;
return 0;
}
@@ -165,4 +165,42 @@ void rb_iwl_mvm_extract_signal(const uint8_t *desc_data,
*/
int rb_iwl_mvm_rate_to_mcs(int signal);
/* ------------------------------------------------------------------ */
/* Firmware TLV parser — Linux 7.1 fw/file.h. */
/* ------------------------------------------------------------------ */
#define RB_IWL_TLV_UCODE_MAGIC 0x0A4F5749 /* "IWO\x0a" — Red Bear firmware */
#define RB_IWL_TLV_HDR_SIZE 12 /* magic(4) + version(4) + build(4) */
#define RB_IWL_FW_VER_HUMAN_READABLE_SZ 64
/* Linux 7.1 iwl_ucode_tlv — type/length/data format */
struct rb_iwl_ucode_tlv {
uint32_t type;
uint32_t length;
uint8_t data[];
};
/* Extracted firmware metadata */
struct rb_iwl_fw_metadata {
uint32_t version;
uint32_t build;
uint8_t api;
char version_str[RB_IWL_FW_VER_HUMAN_READABLE_SZ];
uint32_t capabilities; /* from IWL_UCODE_TLV_ENABLED_CAPABILITIES */
uint32_t n_scan_channels; /* from IWL_UCODE_TLV_N_SCAN_CHANNELS */
int parsed; /* 1 if TLV parsing succeeded */
};
/*
* rb_iwl_mvm_parse_firmware — walk TLV sections in the firmware blob
* and extract metadata (capabilities, version string, scan channels).
*
* Cross-referenced with Linux 7.1 iwl_req_fw_callback() in iwl-drv.c
* and iwl_parse_tlv_firmware().
*
* Returns: 0 on success, negative on error.
*/
int rb_iwl_mvm_parse_firmware(const uint8_t *data, size_t len,
struct rb_iwl_fw_metadata *out);
#endif /* RB_IWLWIFI_MVM_H */
@@ -31,10 +31,12 @@
// Known gaps vs Linux 7.1 iwlwifi (drivers/net/wireless/intel/iwlwifi/):
// - Mini-MVM present (linux_mvm.c) but not a full port of iwl-mvm.c (~5200 lines).
// Provides RX descriptor parsing, signal extraction, notification dispatch.
// Provides RX descriptor parsing, signal extraction, notification dispatch,
// and firmware TLV metadata (capabilities, version string).
// Missing: Minstrel rate adaptation, thermal management, WoWLAN, debug hooks.
// - No firmware TLV/NVM parser — only magic number check.
// See Linux 7.1 iwl-nvm-parse.c.
// - Firmware TLV parser present — extracts ENABLED_CAPABILITIES, N_SCAN_CHANNELS,
// and FW_VERSION from the Intel firmware blob.
// Missing: full NVM section parsing (MAC address, calibration, regulatory).
// - No power management (PS mode, WoWLAN, thermal throttling).
// - PCI device ID table: 37 entries. Linux 7.1 has ~500+ across all families.
// - Scan uses active+dwell schedule but no UMAC scan engine integration.
@@ -272,6 +274,7 @@ struct iwl_trans_pcie {
struct ieee80211_bss_conf bss_conf;
struct rb_iwl_fw_blob_info fw_info;
struct rb_iwl_fw_blob_info pnvm_info;
struct rb_iwl_fw_metadata fw_meta;
struct rb_iwl_key keys[4];
char fw_name_storage[RB_IWL_MAX_FW_NAME];
char pnvm_name_storage[RB_IWL_MAX_FW_NAME];
@@ -974,8 +977,17 @@ static int rb_iwlwifi_do_prepare(struct iwl_trans_pcie *trans, const char *ucode
return rc;
rc = rb_iwlwifi_parse_fw_blob(fw, &trans->fw_info);
if (!rc)
if (!rc) {
rb_iwlwifi_update_fw_info(&trans->fw_info, fw);
if (rb_iwl_mvm_parse_firmware(fw->data, fw->size, &trans->fw_meta) == 0) {
pr_info("prepare: firmware %s v%d.%d (build %u), caps=0x%x\n",
trans->fw_meta.version_str[0] ? trans->fw_meta.version_str : "(unknown)",
(trans->fw_meta.version >> 16) & 0xFF,
(trans->fw_meta.version >> 8) & 0xFF,
trans->fw_meta.build,
trans->fw_meta.capabilities);
}
}
release_firmware(fw);
if (rc)
return rc;