Skip to content

Instantly share code, notes, and snippets.

@jernejsk
Created August 17, 2026 12:28
Show Gist options
  • Select an option

  • Save jernejsk/cc46114b58635d55e916bd4909aca231 to your computer and use it in GitHub Desktop.

Select an option

Save jernejsk/cc46114b58635d55e916bd4909aca231 to your computer and use it in GitHub Desktop.
uwe5622 binary config
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
"""Convert a vendor UWE5622 RF INI file to the driver's stable container."""
import argparse
import re
import struct
from pathlib import Path
SEC1_LEN = 328
SEC2_LEN = 1464
CONFIG_LEN = SEC1_LEN + SEC2_LEN
def layout():
fields = {
"Major": (0, 2, 1), "Minor": (2, 2, 1),
"Calib_Bypass": (4, 2, 1), "TxChain_Mask": (6, 1, 1),
"RxChain_Mask": (7, 1, 1), "DPD_LUT_idx": (8, 1, 8),
"TPC_Goal_Chain0": (16, 2, 8), "TPC_Goal_Chain1": (32, 2, 8),
"2G_Channel_Chain0": (112, 1, 14),
"2G_Channel_Chain1": (126, 1, 14),
"5G_Channel_Chain0": (140, 1, 25),
"5G_Channel_Chain1": (165, 1, 25),
"11b_Power": (192, 1, 4), "11ag_Power": (196, 1, 8),
"11n_Power": (204, 1, 17), "11ac_Power": (221, 1, 20),
"Green_WIFI_offset": (244, 1, 1),
"HT40_Power_offset": (245, 1, 1),
"VHT40_Power_offset": (246, 1, 1),
"VHT80_Power_offset": (247, 1, 1),
"SAR_Power_offset": (248, 1, 1),
"Mean_Power_offset": (249, 1, 1), "TPC_mode": (250, 1, 1),
"MAGIC_word": (251, 1, 1), "reg_domain1": (252, 4, 1),
"reg_domain2": (256, 4, 1), "BW20M": (260, 1, 39),
"BW40M": (299, 1, 21), "BW80M": (320, 1, 6),
"DFS_switch": (1576, 1, 1),
"power_save_switch": (1577, 1, 1),
"ex-Fem_and_ex-LNA_param_setup": (1578, 1, 1),
"rssi_report_diff": (1579, 1, 1),
"address": (1580, 4, 16), "value": (1644, 4, 16),
}
for chain in range(2):
base = 48 + chain * 32
for index in range(8):
fields[f"Chain{chain}_LUT_{index}"] = (base + index * 4, 1, 4)
channels = list(range(1, 15)) + [36, 40, 44, 48, 52, 56, 60, 64,
100, 104, 108, 112, 116, 120, 124,
128, 132, 136, 140, 144, 149, 153,
157, 161, 165]
for chain in range(2):
base = 328 + chain * 39 * 16
for index, channel in enumerate(channels):
fields[f"Chain{chain}_{channel}"] = (base + index * 16, 1, 16)
coex = [
"bt_performance_cfg0", "bt_performance_cfg1",
"wifi_performance_cfg0", "wifi_performance_cfg2",
"strategy_cfg0", "strategy_cfg1", "strategy_cfg2",
"compatibility_cfg0", "compatibility_cfg1", "ant_cfg0", "ant_cfg1",
"isolation_cfg0", "isolation_cfg1", "reserved_cfg0",
"reserved_cfg1", "reserved_cfg2", "reserved_cfg3", "reserved_cfg4",
"reserved_cfg5", "reserved_cfg6", "reserved_cfg7",
]
for index, name in enumerate(coex):
fields[name] = (1708 + index * 4, 4, 1)
return fields
def parse_ini(path):
values = {}
for number, raw in enumerate(path.read_text().splitlines(), 1):
line = raw.split("#", 1)[0].strip()
if not line or line.startswith("["):
continue
match = re.fullmatch(r"([^=]+?)\s*=\s*(.*)", line)
if not match:
raise ValueError(f"{path}:{number}: malformed line")
key = match.group(1).strip()
try:
values[key] = [int(item.strip(), 0)
for item in match.group(2).split(",")
if item.strip()]
except ValueError as error:
raise ValueError(f"{path}:{number}: invalid integer") from error
return values
def build(values):
fields = layout()
config = bytearray(CONFIG_LEN)
rf = bytes(value & 0xff for value in values.pop("rf_config", []))
if len(rf) > 1500:
raise ValueError("rf_config exceeds the firmware's 1500-byte limit")
for name in ("Major", "Minor"):
if len(values.get(name, [])) != 1:
raise ValueError(f"{name}: exactly one value is required")
major = values["Major"][0]
minor = values["Minor"][0]
if not 2 <= major <= 128:
raise ValueError("Major must be in the firmware-supported range 2..128")
if not 0 <= minor <= 128:
raise ValueError("Minor must be in the firmware-supported range 0..128")
values.setdefault("MAGIC_word", [0xaa])
if major > 2 and values["MAGIC_word"] != [0xaa]:
raise ValueError("MAGIC_word must be 0xaa when Major is greater than 2")
for key, items in values.items():
if key not in fields:
raise ValueError(f"unknown parameter {key!r}")
offset, width, count = fields[key]
if len(items) > count:
raise ValueError(f"{key}: got {len(items)} values, maximum is {count}")
mask = (1 << (width * 8)) - 1
for index, value in enumerate(items):
start = offset + index * width
config[start:start + width] = (value & mask).to_bytes(width, "little")
return (b"UWEI" + struct.pack("<HHHH", 1, SEC1_LEN, SEC2_LEN, len(rf)) +
config + rf)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
values = parse_ini(args.input)
output = build(values)
args.output.write_bytes(output)
print(f"wrote {len(output)} bytes to {args.output}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment