Created
August 17, 2026 12:28
-
-
Save jernejsk/cc46114b58635d55e916bd4909aca231 to your computer and use it in GitHub Desktop.
uwe5622 binary config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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