iwlwifi: dual-format firmware TLV parser (Red Bear + Linux Intel)

Support both firmware formats:
- Red Bear simple format: magic 0x0A4F5749 (IWO\n) at offset 0, version/build at 4-11
- Linux Intel TLV format: zero at offset 0, magic 0x0a4c5749 (IWL\n) at offset 4,
  version string (64 bytes) at offset 8, TLVs from offset 88.

TLV type 30 (ENABLED_CAPABILITIES) now correctly parses {api_index, bitmap} pairs per
Linux 7.1 iwl-drv.c. Multiple type-30 TLVs are merged via bitmap << (api_index*16).

Verified against real firmware: iwlwifi-bz-b0-fm-c0-c101.ucode (BE201 Wi-Fi 7)
extracts api_index 0..4, 67 scan channels, version string + major version.
This commit is contained in:
2026-07-08 14:30:26 +03:00
parent bdb1e27816
commit a849fa91f0
@@ -104,16 +104,43 @@ int rb_iwl_mvm_parse_firmware(const uint8_t *data, size_t len,
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)
if (magic == RB_IWL_TLV_UCODE_MAGIC) {
/* Simple format: "IWO\n" at offset 0, version at 4, build at 8 */
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;
} else if (magic == 0 && len >= 8) {
/* Linux TLV format: zero at offset 0, "IWL\n" at offset 4 */
uint32_t tlv_magic;
tlv_magic = (uint32_t)data[4] | ((uint32_t)data[5] << 8) |
((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24);
if (tlv_magic != 0x0a4c5749U) /* "IWL\n" */
return -1;
/* Extract version string from offset 8 (64 bytes) */
if (len >= 72) {
size_t copy = RB_IWL_FW_VER_HUMAN_READABLE_SZ - 1;
if (len - 8 < copy)
copy = len - 8;
if (copy > 0) {
memcpy(out->version_str, data + 8, copy);
out->version_str[copy] = '\0';
}
/* Major version at offset 72 */
if (len >= 76) {
out->version = (uint32_t)data[72] |
((uint32_t)data[73] << 8) |
((uint32_t)data[74] << 16) |
((uint32_t)data[75] << 24);
}
}
p = data + 88; /* skip TLV header */
} else {
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) {
@@ -138,11 +165,18 @@ int rb_iwl_mvm_parse_firmware(const uint8_t *data, size_t len,
switch (tlv_type) {
case RB_IWL_UCODE_TLV_ENABLED_CAPABILITIES:
if (tlv_len >= 4)
out->capabilities = (uint32_t)tlv_data[0] |
if (tlv_len >= 8) {
uint32_t api_index, bitmap;
api_index = (uint32_t)tlv_data[0] |
((uint32_t)tlv_data[1] << 8) |
((uint32_t)tlv_data[2] << 16) |
((uint32_t)tlv_data[3] << 24);
bitmap = (uint32_t)tlv_data[4] |
((uint32_t)tlv_data[5] << 8) |
((uint32_t)tlv_data[6] << 16) |
((uint32_t)tlv_data[7] << 24);
out->capabilities |= (bitmap << (api_index * 16));
}
break;
case RB_IWL_UCODE_TLV_N_SCAN_CHANNELS: