diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 45c01d932..b8d7458b6 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -317,9 +317,11 @@ jobs: name: ${{ env.APP_NAME }}_${{ needs.refs.outputs.version }}_${{ matrix.platform }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }} path: | output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_UA_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.bin + output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_Tuya_UA_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.bin output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_UG_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.bin output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_UG_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.img output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_QIO_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.bin + output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_Tuya_QIO_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.bin output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/OpenBK7231M_QIO_${{ needs.refs.outputs.version }}.bin output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/OpenBK7231M_ALT_QIO_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.bin output/${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}/${{ matrix.platform }}_${{ needs.refs.outputs.version }}${{ matrix.variant != 'default' && '_' || '' }}${{ matrix.variant != 'default' && matrix.variant || '' }}.rbl diff --git a/Makefile b/Makefile index cf6ae64f0..ae87d133f 100644 --- a/Makefile +++ b/Makefile @@ -726,7 +726,23 @@ OpenBK7231U: prebuild_OpenBK7231U .PHONY: OpenBK7252 OpenBK7252: prebuild_OpenBK7252 - cd sdk/beken_freertos_sdk && ARM_GCC_TOOLCHAIN=$(PWD)/sdk/beken_freertos_sdk/toolchain/arm-none-eabi/bin/ OBK_VARIANT=$(OBK_VARIANT) sh build.sh bk7251 $(APP_VERSION) + cd sdk/beken_freertos_sdk && { \ + boot=./tools/beken_packager/bootloader_bk7251_uart2_v1.0.15_enc.bin; \ + raw=./tools/beken_packager/bootloader_bk7251_uart2_v1.0.15.bin; \ + patched=./tools/beken_packager/bootloader_bk7251_uart2_v1.0.15_tuya_ota_patch.bin; \ + patched_enc=./tools/beken_packager/bootloader_bk7251_uart2_v1.0.15_tuya_ota_patch_enc.bin; \ + bak=$$boot.bk7252-tuya-patch; \ + cp "$$boot" "$$bak" || exit $$?; \ + status=0; \ + python ../../platforms/BK723x/patch_bk7252_tuya_bootloader.py "$$raw" "$$patched" "$(PWD)/sdk/beken_freertos_sdk/toolchain/arm-none-eabi/bin" ../../platforms/BK723x/bk7252_tuya_ota_write_wrap.c && \ + ./tools/crc_binary/encrypt_n "$$patched" 510fb093 a3cbeadc 5993a17e c7adeb03 0 && \ + mv -f "$$patched_enc" "$$boot" && \ + ./tools/rt_partition_tool/rt_partition_tool_cli-x64 "$$boot" ../../platforms/BK723x/bk7252_partition_2M.json && \ + ARM_GCC_TOOLCHAIN=$(PWD)/sdk/beken_freertos_sdk/toolchain/arm-none-eabi/bin/ OBK_VARIANT=$(OBK_VARIANT) sh build.sh bk7251 $(APP_VERSION) || status=$$?; \ + rm -f "$$patched" "$$patched_enc"; \ + mv -f "$$bak" "$$boot"; \ + exit $$status; \ + } mkdir -p output/$(APP_VERSION) cp sdk/beken_freertos_sdk/out/bk7251.bin output/$(APP_VERSION)/OpenBK7252_${APP_VERSION}.bin cp sdk/beken_freertos_sdk/out/bk7251_QIO.bin output/$(APP_VERSION)/OpenBK7252_QIO_${APP_VERSION}.bin diff --git a/platforms/BK723x/bk7252_partition_2M.json b/platforms/BK723x/bk7252_partition_2M.json new file mode 100644 index 000000000..0a83e415e --- /dev/null +++ b/platforms/BK723x/bk7252_partition_2M.json @@ -0,0 +1,22 @@ +{ + "part_table": [ + { + "name": "download", + "flash_name": "beken_onchip", + "offset": "0x00132000", + "len": "696K" + }, + { + "name": "app", + "flash_name": "beken_onchip_crc", + "offset": "0x00010000", + "len": "1088K" + }, + { + "name": "bootloader", + "flash_name": "beken_onchip_crc", + "offset": "0x00000000", + "len": "64K" + } + ] +} diff --git a/platforms/BK723x/bk7252_tuya_ota_write_wrap.c b/platforms/BK723x/bk7252_tuya_ota_write_wrap.c new file mode 100644 index 000000000..efe99de95 --- /dev/null +++ b/platforms/BK723x/bk7252_tuya_ota_write_wrap.c @@ -0,0 +1,131 @@ +#include + +#define COEF0 0x510FB093u +#define COEF1 0xA3CBEADCu +#define COEF2 0x5993A17Eu +#define COEF3 0xC7ADEB03u + +typedef int (*fal_partition_write_fn)(void *part, uint32_t offset, const uint8_t *buf, uint32_t size); + +static uint32_t rol32(uint32_t v, unsigned int bits) +{ + return (v << bits) | (v >> (32u - bits)); +} + +static uint32_t pn15(uint32_t v, uint32_t disabled) +{ + uint32_t part1; + uint32_t x; + uint32_t part2; + + if (disabled) + return 0; + + v &= 0xffffu; + part1 = ((v << 9) + (v >> 7)) & 0xffffu; + x = v >> 5; + part2 = ((x << 12) + ((x & 0xfu) << 8) + ((x << 4) & 0xffu) + (x & 0xfu)) & 0x6371u; + return part1 ^ part2; +} + +static uint32_t pn16(uint32_t v, uint32_t disabled) +{ + uint32_t part1; + uint32_t seed; + uint32_t part2; + + if (disabled) + return 0; + + v &= 0x1ffffu; + part1 = (((v & 0x3ffu) << 7) + ((v >> 10) & 0x7fu)) & 0x1ffffu; + seed = (((v >> 1) & 1u) << 3) + (((v >> 5) & 1u) << 2) + (((v >> 9) & 1u) << 1) + ((v >> 13) & 1u); + part2 = ((((v >> 4) & 1u) << 16) + (seed << 12) + (seed << 8) + (seed << 4) + seed) & 0x13659u; + return part1 ^ part2; +} + +static uint32_t pn32(uint32_t v, uint32_t disabled) +{ + uint32_t seed; + uint32_t part2; + + if (disabled) + return 0; + + seed = (v >> 2) & 0xfu; + part2 = (seed << 28) + (seed << 24) + (seed << 20) + (seed << 16) + (seed << 12) + (seed << 8) + (seed << 4) + seed; + return rol32(v, 17) ^ (part2 & 0xE519A4F1u); +} + +static uint32_t enc_word(uint32_t addr, uint32_t data) +{ + uint32_t all_disabled; + uint32_t high_a; + uint32_t high_b; + uint32_t low_b; + uint32_t pn15_input; + uint32_t pn16_input; + uint32_t pn32_input; + uint32_t word_mode; + uint32_t rotate_mode; + uint32_t mask; + + all_disabled = (((COEF3 & 0xff000000u) == 0xff000000u) || ((COEF3 & 0xff000000u) == 0)); + word_mode = (COEF3 >> 5) & 3u; + rotate_mode = (COEF3 >> 11) & 3u; + + high_a = (((addr >> 24) << 8) + ((addr >> 16) & 0xffu)) & 0xffffu; + high_b = (((addr >> 16) << 8) + ((addr >> 24) & 0xffu)) & 0xffffu; + low_b = ((addr << 8) + ((addr >> 8) & 0xffu)) & 0xffffu; + + if (word_mode == 0) + pn15_input = high_a ^ (addr & 0xffffu); + else if (word_mode == 1) + pn15_input = high_a ^ low_b; + else if (word_mode == 2) + pn15_input = high_b ^ (addr & 0xffffu); + else + pn15_input = high_b ^ low_b; + + pn16_input = (addr >> ((COEF3 >> 8) & 3u)) & 0x1ffffu; + if (rotate_mode == 0) + pn32_input = addr; + else if (rotate_mode == 1) + pn32_input = rol32(addr, 24); + else if (rotate_mode == 2) + pn32_input = rol32(addr, 16); + else + pn32_input = rol32(addr, 8); + + mask = (pn15(((COEF1 >> 16) ^ pn15_input) & 0xffffu, all_disabled || (COEF3 & 1u)) & 0xffffu) << 16; + mask |= pn16(((((COEF1 >> 8) & 0xffu) << 9) + (((COEF3 >> 4) & 1u) << 8) + (COEF1 & 0xffu)) ^ pn16_input, + all_disabled || (COEF3 & 2u)) & 0xffffu; + mask ^= pn32(COEF0 ^ pn32_input, all_disabled || (COEF3 & 4u)); + if (!(all_disabled || (COEF3 & 8u))) + mask ^= COEF2; + + return data ^ mask; +} + +int tuya_fal_partition_write(void *part, uint32_t offset, uint8_t *buf, uint32_t size) +{ + uint32_t padded_size; + uint32_t addr; + uint32_t i; + + padded_size = (size + 31u) & ~31u; + for (i = size; i < padded_size; i++) + buf[i] = 0xffu; + + addr = *(const uint32_t *)((const uint8_t *)part + 0x34u) + offset; + for (i = 0; i < padded_size; i += 4) { + uint32_t word = (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) | ((uint32_t)buf[i + 2] << 16) | ((uint32_t)buf[i + 3] << 24); + word = enc_word(addr + i, word); + buf[i] = (uint8_t)word; + buf[i + 1] = (uint8_t)(word >> 8); + buf[i + 2] = (uint8_t)(word >> 16); + buf[i + 3] = (uint8_t)(word >> 24); + } + + return ((fal_partition_write_fn)0x7779u)(part, offset, buf, padded_size); +} diff --git a/platforms/BK723x/patch_bk7252_tuya_bootloader.py b/platforms/BK723x/patch_bk7252_tuya_bootloader.py new file mode 100644 index 000000000..e43dc4d04 --- /dev/null +++ b/platforms/BK723x/patch_bk7252_tuya_bootloader.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +import argparse +import hashlib +import pathlib +import subprocess +import tempfile + + +RAW_BOOTLOADER_SHA256 = "6342d53961e743dfe73c852b31c7083f08f740fc27444cb75ebcb22d4b1031f8" +WRAPPER_OFFSET = 0xD300 +ORIGINAL_WRITE = 0x7778 +CALL_SITES = (0x6774, 0x6816) + + +def thumb_bl(site, target): + offset = target - (site + 4) + if offset & 1: + raise ValueError("unaligned Thumb BL target") + if not (-(1 << 22) <= offset < (1 << 22)): + raise ValueError("Thumb BL target out of range") + + imm = offset >> 1 + hi = 0xF000 | ((imm >> 11) & 0x7FF) + lo = 0xF800 | (imm & 0x7FF) + return hi.to_bytes(2, "little") + lo.to_bytes(2, "little") + + +def tool_path(toolchain, name): + path = toolchain / name + if path.exists(): + return path + + exe_path = toolchain / (name + ".exe") + if exe_path.exists(): + return exe_path + + return path + + +def build_wrapper(toolchain, source): + gcc = tool_path(toolchain, "arm-none-eabi-gcc") + nm = tool_path(toolchain, "arm-none-eabi-nm") + objcopy = tool_path(toolchain, "arm-none-eabi-objcopy") + + with tempfile.TemporaryDirectory(prefix="bk7252_tuya_bl_") as tmp: + tmp_path = pathlib.Path(tmp) + obj = tmp_path / "wrap.o" + elf = tmp_path / "wrap.elf" + binary = tmp_path / "wrap.bin" + + subprocess.run([ + str(gcc), "-c", str(source), "-o", str(obj), + "-Os", "-mthumb", "-mcpu=arm9tdmi", + "-ffreestanding", "-fno-builtin", "-fno-pic", + "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", + ], check=True) + subprocess.run([ + str(gcc), "-nostdlib", "-Wl,-Ttext=0x%08x" % WRAPPER_OFFSET, + "-Wl,-e,tuya_fal_partition_write", + "-o", str(elf), str(obj), + ], check=True) + nm_output = subprocess.check_output([str(nm), "-n", str(elf)], text=True) + for line in nm_output.splitlines(): + parts = line.split() + if len(parts) >= 3 and parts[2] == "tuya_fal_partition_write": + entry = int(parts[0], 16) + if entry != WRAPPER_OFFSET: + raise RuntimeError("wrapper entry at 0x%04x, expected 0x%04x" % (entry, WRAPPER_OFFSET)) + break + else: + raise RuntimeError("wrapper entry symbol not found") + + subprocess.run([ + str(objcopy), "-O", "binary", "-j", ".text", str(elf), str(binary), + ], check=True) + + return binary.read_bytes() + + +def patch_bootloader(raw_path, output_path, wrapper): + raw = bytearray(raw_path.read_bytes()) + raw_sha = hashlib.sha256(raw).hexdigest() + if raw_sha != RAW_BOOTLOADER_SHA256: + raise RuntimeError("unexpected BK7252 bootloader SHA256: %s" % raw_sha) + + for site in CALL_SITES: + expected = thumb_bl(site, ORIGINAL_WRITE) + found = bytes(raw[site:site + 4]) + if found != expected: + raise RuntimeError("unexpected call at 0x%04x: got %s expected %s" % (site, found.hex(), expected.hex())) + raw[site:site + 4] = thumb_bl(site, WRAPPER_OFFSET) + + wrapper_end = WRAPPER_OFFSET + len(wrapper) + if len(raw) > WRAPPER_OFFSET: + raise RuntimeError("wrapper offset overlaps bootloader body") + if wrapper_end > 0x10000: + raise RuntimeError("patched bootloader exceeds 64K logical boot partition") + + raw.extend(b"\xff" * (WRAPPER_OFFSET - len(raw))) + raw[WRAPPER_OFFSET:wrapper_end] = wrapper + output_path.write_bytes(raw) + + print("Patched BK7252 Tuya bootloader: wrapper %d bytes at 0x%04x" % (len(wrapper), WRAPPER_OFFSET)) + + +def main(): + parser = argparse.ArgumentParser(description="Patch the BK7252 RT bootloader to coeff-encrypt app writes for Tuya OTA.") + parser.add_argument("raw_bootloader", type=pathlib.Path) + parser.add_argument("output_bootloader", type=pathlib.Path) + parser.add_argument("toolchain_bin", type=pathlib.Path) + parser.add_argument("wrapper_source", type=pathlib.Path) + args = parser.parse_args() + + wrapper = build_wrapper(args.toolchain_bin, args.wrapper_source) + patch_bootloader(args.raw_bootloader, args.output_bootloader, wrapper) + + +if __name__ == "__main__": + main()