add Tuya coeff-key OpenBK7252 build to release (#2152)

* add Tuya BK7252U back

* Update .releaserc.yaml

* rbl

* Probe BK7252 Tuya OTA readback CRC

* Fix BK7252 Tuya OTA partition table

* Restore BK7252 Tuya OTA artefact

* Probe BK7252 OTA flash readback CRC

* patch RT bootloader

* remove releaser

* use standard partitions

* Update Makefile

* Create bk7252_partition_2M.json
This commit is contained in:
divadiow
2026-07-03 01:58:22 +01:00
committed by GitHub
parent 8395dcacc4
commit 3c5f4a2d99
5 changed files with 291 additions and 1 deletions

View File

@ -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

View File

@ -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

View File

@ -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"
}
]
}

View File

@ -0,0 +1,131 @@
#include <stdint.h>
#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);
}

View File

@ -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()