Skip to content

Commit 48d22f6

Browse files
committed
src/snagrecover/firmware/rk_fw.py: Add support for sending rockchip bin files
Add support for reading and sending rockchip binary files, made with boot_merger. Signed-off-by: Arnaud Patard <[email protected]>
1 parent 221e186 commit 48d22f6

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed

src/snagrecover/firmware/rk_fw.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
#!/usr/bin/python3
2+
# Copyright 2025 Collabora Ltd.
3+
#
4+
# SPDX-License-Identifier: GPL-2.0+
5+
#
6+
# Author: Arnaud Patard <[email protected]>
7+
#
8+
# Notes:
9+
# to unpack / descramble encrypted parts, the rc4 key is inside u-boot's code.
10+
# Some information used here are coming from rkdeveloptool, which is GPL-2.0+
11+
12+
import logging
13+
import struct
14+
import time
15+
from cryptography.hazmat.primitives.ciphers import Cipher
16+
from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4
17+
from crccheck import crc
18+
19+
logger = logging.getLogger("snagrecover")
20+
from snagrecover.protocols import rockchip
21+
22+
BOOTTAG = b"BOOT"
23+
LDRTAG = b"LDR "
24+
TAG_LIST = [ BOOTTAG, LDRTAG ]
25+
BOOTENTRYSIZE = 57
26+
BOOTHEADERENTRYSIZE = 6
27+
BOOTHEADERSIZE = 102
28+
BOOTHEADERTIMESIZE = 7
29+
RC4_KEY = bytearray([124, 78, 3, 4, 85, 5, 9, 7, 45, 44, 123, 56, 23, 13, 23, 17])
30+
31+
class BootEntryError(Exception):
32+
pass
33+
34+
class BootEntry():
35+
def __init__(self, entry):
36+
if len(entry) != BOOTENTRYSIZE:
37+
raise BootEntryError(f"Invalid boot entry size {len(entry)}")
38+
self.entry = entry
39+
40+
def unpack(self):
41+
(self.size, self.type) = struct.unpack("<BI", self.entry[:5])
42+
self.name = self.entry[5:45].decode('utf-16le')
43+
(self.data_offset, self.data_size, self.data_delay) = struct.unpack("<III", self.entry[45:57])
44+
45+
def __str__(self):
46+
return f"Entry {self.name} (type: {self.type}, size: {self.size}, data offset: {self.data_offset}, data size: {self.data_size}, delay: {self.data_delay})"
47+
48+
49+
class BootHeaderEntryError(Exception):
50+
pass
51+
52+
class BootHeaderEntry():
53+
def __init__(self, name, entry):
54+
self.name = name
55+
if len(entry) != BOOTHEADERENTRYSIZE:
56+
raise BootHeaderEntryError(f"Invalid boot header entry size {len(entry)}")
57+
self.entry = entry
58+
59+
def unpack(self):
60+
(self.count, self.offset, self.size) = struct.unpack("<BIB", self.entry)
61+
if self.size != BOOTENTRYSIZE:
62+
raise BootHeaderError("Invalid boot entry size in the header definition")
63+
64+
def __str__(self):
65+
return f"Header entry {self.name}: ({self.count}, {self.offset})"
66+
67+
class BootHeaderError(Exception):
68+
pass
69+
70+
class BootHeader():
71+
def __init__(self, header):
72+
if len(header) != BOOTHEADERSIZE:
73+
raise BootHeaderError(f"Invalid boot header size {len(header)}")
74+
self.header = header
75+
76+
def unpack(self):
77+
offset = 0
78+
self.tag = self.header[offset:offset+4]
79+
offset += 4
80+
(self.size, version, self.merge_version) = struct.unpack("<HII", self.header[offset:offset+10])
81+
if self.size != BOOTHEADERSIZE:
82+
raise BootHeaderError("Invalid header size in the header definition")
83+
# not sure how to exactly parse version/merge_version
84+
self.maj_ver = version >> 8
85+
self.min_ver = version & 0xff
86+
offset += 10
87+
self.releasetime = self.header[offset:offset+BOOTHEADERTIMESIZE]
88+
offset += BOOTHEADERTIMESIZE
89+
self.chip = self.header[offset:offset+4][::-1]
90+
offset += 4
91+
self.entry471 = BootHeaderEntry("471", self.header[offset:offset+BOOTHEADERENTRYSIZE])
92+
self.entry471.unpack()
93+
offset += BOOTHEADERENTRYSIZE
94+
self.entry472 = BootHeaderEntry("472", self.header[offset:offset+BOOTHEADERENTRYSIZE])
95+
self.entry472.unpack()
96+
offset += BOOTHEADERENTRYSIZE
97+
self.loader = BootHeaderEntry("loader", self.header[offset:offset+BOOTHEADERENTRYSIZE])
98+
self.loader.unpack()
99+
offset += BOOTHEADERENTRYSIZE
100+
# rc4 = 1: disable rc4 on 471/472. Not sure about rc4 on loader.
101+
(self.sign, self.rc4) = struct.unpack("<BB", self.header[offset:offset+2])
102+
if self.rc4:
103+
self.rc4 = False
104+
else:
105+
self.rc4 = True
106+
if self.sign == 'S':
107+
self.sign = True
108+
else:
109+
self.sign = False
110+
111+
def __str__(self):
112+
return f"{self.tag},{self.maj_ver}.{self.min_ver},{self.merge_version},{self.chip},{self.entry471},{self.entry472},{self.loader},sign:{self.sign},enc:{self.rc4}"
113+
114+
class LoaderFileError(Exception):
115+
pass
116+
117+
class RkCrc32(crc.Crc32Base):
118+
"""CRC-32/ROCKCHIP
119+
"""
120+
_names = ('CRC-32/ROCKCHIP')
121+
_width = 32
122+
_poly = 0x04c10db7
123+
_initvalue = 0x00000000
124+
_reflect_input = False
125+
_reflect_output = False
126+
_xor_output = 0
127+
128+
class LoaderFile():
129+
def __init__(self, blob):
130+
self.blob = blob
131+
buf = self.blob[0:BOOTHEADERSIZE]
132+
offset = BOOTHEADERSIZE
133+
self.header = BootHeader(buf)
134+
self.header.unpack()
135+
if self.header.tag not in TAG_LIST:
136+
raise LoaderFileError(f"Invalid tag {self.header.tag}")
137+
138+
offset = self.header.entry471.offset
139+
self.entry471 = []
140+
for _i in range(self.header.entry471.count):
141+
self.entry471.append(BootEntry(self.blob[offset:offset+BOOTENTRYSIZE]))
142+
offset += BOOTENTRYSIZE
143+
144+
offset = self.header.entry472.offset
145+
self.entry472 = []
146+
for _i in range(self.header.entry472.count):
147+
self.entry472.append(BootEntry(self.blob[offset:offset+BOOTENTRYSIZE]))
148+
offset += BOOTENTRYSIZE
149+
150+
offset = self.header.loader.offset
151+
self.loader = []
152+
for _i in range(self.header.loader.count):
153+
self.loader.append(BootEntry(self.blob[offset:offset+BOOTENTRYSIZE]))
154+
offset += BOOTENTRYSIZE
155+
crc32 = self.blob[-4:]
156+
calc_crc32 = RkCrc32.calc(self.blob[:-4])
157+
(self.crc32,) = struct.unpack("<I", crc32)
158+
assert self.crc32 == calc_crc32
159+
160+
def entry_data(self, name, idx = 0):
161+
entry = None
162+
if name == "471":
163+
entry = self.entry471
164+
elif name == "472":
165+
entry = self.entry472
166+
elif name == "loader":
167+
entry = self.loader
168+
else:
169+
raise LoaderFileError(f"Invalid name {name}")
170+
171+
if idx > len(entry):
172+
raise LoaderFileError(f"Invalid index {idx}. Only has {len(entry)} entries.")
173+
e = entry[idx]
174+
e.unpack()
175+
logger.debug(f"{e}")
176+
return (self.blob[e.data_offset:e.data_offset+e.data_size], e.data_delay)
177+
178+
def __str__(self):
179+
return f"{self.header} crc: {self.crc32:02x}"
180+
181+
def rc4_encrypt(fw_blob):
182+
183+
# Round to 4096 block size
184+
blob_len = len(fw_blob)
185+
padded_len = (blob_len+4095)//4096 * 4096
186+
fw_blob = bytearray(fw_blob)
187+
fw_blob += bytearray([0]*(padded_len - blob_len))
188+
a = ARC4(RC4_KEY)
189+
c = Cipher(a, mode=None)
190+
d = c.encryptor()
191+
obuf = bytearray()
192+
for i in range(padded_len):
193+
obuf += d.update(fw_blob[i*512:(i+1)*512])
194+
return obuf
195+
196+
def rockchip_run(dev, fw_name, fw_blob):
197+
rom = rockchip.RochipBootRom(dev)
198+
199+
if fw_name == 'code471':
200+
logger.info("Downloading code471...")
201+
blob = rc4_encrypt(fw_blob)
202+
rom.write_blob(blob, 0x471)
203+
elif fw_name == 'code472':
204+
logger.info("Downloading code472...")
205+
blob = rc4_encrypt(fw_blob)
206+
rom.write_blob(blob, 0x472)
207+
else:
208+
fw = LoaderFile(fw_blob)
209+
logger.info(f"{fw}")
210+
for i in range(fw.header.entry471.count):
211+
logger.info(f"Downloading entry 471 {i}...")
212+
(data, delay) = fw.entry_data("471", i)
213+
rom.write_blob(data, 0x471)
214+
logger.info(f"Sleeping {delay}ms")
215+
time.sleep(delay / 1000)
216+
logger.info("Done")
217+
for i in range(fw.header.entry472.count):
218+
logger.info(f"Downloading entry 472 {i}...")
219+
(data, delay) = fw.entry_data("472", i)
220+
rom.write_blob(data, 0x472)
221+
logger.info(f"Sleeping {delay}ms")
222+
time.sleep(delay / 1000)
223+
logger.info("Done")

0 commit comments

Comments
 (0)