Skip to content

Commit 8ceea38

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 bf62819 commit 8ceea38

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed

src/snagrecover/firmware/rk_fw.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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+
from dataclasses import dataclass
19+
20+
logger = logging.getLogger("snagrecover")
21+
from snagrecover.protocols import rockchip
22+
from snagrecover.utils import BinFileHeader
23+
24+
# List generated with a grep on rkbin repository
25+
NEWIDB_LIST = [ "rk3506", "rk3506b", "rk3528", "rk3562", "rk3566", "rk3568", "rk3576", "rk3583", "rk3588", "rv1103b", "rv1106" ]
26+
27+
BOOTTAG = b"BOOT"
28+
LDRTAG = b"LDR "
29+
TAG_LIST = [ BOOTTAG, LDRTAG ]
30+
BOOTENTRYSIZE = 57
31+
BOOTHEADERENTRYSIZE = 6
32+
BOOTHEADERSIZE = 102
33+
BOOTHEADERTIMESIZE = 7
34+
RC4_KEY = bytearray([124, 78, 3, 4, 85, 5, 9, 7, 45, 44, 123, 56, 23, 13, 23, 17])
35+
36+
@dataclass
37+
class BootEntry(BinFileHeader):
38+
size: int
39+
type: int
40+
name: bytes
41+
data_offset: int
42+
data_size: int
43+
data_delay: int
44+
45+
fmt = "<BI40sIII"
46+
class_size = BOOTENTRYSIZE
47+
48+
def __str__(self):
49+
name = self.name.decode('utf-16le')
50+
return f"Entry {name} (type: {self.type}, size: {self.size}, data offset: {self.data_offset}, data size: {self.data_size}, delay: {self.data_delay})"
51+
52+
53+
@dataclass
54+
class BootHeaderEntry(BinFileHeader):
55+
count: int
56+
offset: int
57+
size: int
58+
59+
fmt = "<BIB"
60+
class_size = BOOTHEADERENTRYSIZE
61+
62+
63+
@dataclass
64+
class BootReleaseTime(BinFileHeader):
65+
year: int
66+
month: int
67+
day: int
68+
hour: int
69+
minute: int
70+
second: int
71+
72+
fmt = "<HBBBBB"
73+
class_size = BOOTHEADERTIMESIZE
74+
75+
def __str__(self):
76+
return f"{self.year}/{self.month}/{self.day} {self.hour}:{self.minute}:{self.second}"
77+
78+
class LoaderFileError(Exception):
79+
def __init__(self, message):
80+
self.message = message
81+
super().__init__(self.message)
82+
83+
def __str__(self):
84+
return f"File format error: {self.message}"
85+
86+
@dataclass
87+
class BootHeader(BinFileHeader):
88+
tag: bytes
89+
size: int
90+
version: int
91+
merge_version: int
92+
releasetime: BootReleaseTime
93+
chip: bytes
94+
entry471: BootHeaderEntry
95+
entry472: BootHeaderEntry
96+
loader: BootHeaderEntry
97+
sign: int
98+
# 1 : disable rc4
99+
rc4: int
100+
reserved: bytes
101+
102+
fmt = f"<4sHII{BOOTHEADERTIMESIZE}s4s{BOOTHEADERENTRYSIZE}s{BOOTHEADERENTRYSIZE}s{BOOTHEADERENTRYSIZE}sBB57s"
103+
class_size = BOOTHEADERSIZE
104+
105+
def __post_init__(self):
106+
if self.tag not in TAG_LIST:
107+
raise LoaderFileError(f"Invalid tag {self.header.tag}")
108+
# not sure how to exactly parse version/merge_version
109+
self.maj_ver = self.version >> 8
110+
self.min_ver = self.version & 0xff
111+
self.releasetime = BootReleaseTime.read(self.releasetime, 0)
112+
# the code should possible check that the soc_model in cfg is matching
113+
# this information but a mapping is needed.
114+
self.chip = self.chip[::-1]
115+
self.entry471 = BootHeaderEntry.read(self.entry471, 0)
116+
self.entry472 = BootHeaderEntry.read(self.entry472, 0)
117+
self.loader = BootHeaderEntry.read(self.loader, 0)
118+
if self.rc4:
119+
self.rc4 = False
120+
else:
121+
self.rc4 = True
122+
if self.sign == 'S':
123+
self.sign = True
124+
else:
125+
self.sign = False
126+
127+
def __str__(self):
128+
return f"{self.tag}, {self.size} ,{self.maj_ver}.{self.min_ver}, 0x{self.merge_version:0x}, {self.releasetime}, {self.chip}, {self.entry471}, {self.entry472}, {self.loader}, sign: {self.sign}, enc: {self.rc4}"
129+
130+
class RkCrc32(crc.Crc32Base):
131+
"""CRC-32/ROCKCHIP
132+
"""
133+
_names = ('CRC-32/ROCKCHIP')
134+
_width = 32
135+
_poly = 0x04c10db7
136+
_initvalue = 0x00000000
137+
_reflect_input = False
138+
_reflect_output = False
139+
_xor_output = 0
140+
141+
class LoaderFile():
142+
def __init__(self, blob):
143+
self.blob = blob
144+
offset = BOOTHEADERSIZE
145+
self.header = BootHeader.read(self.blob, 0)
146+
147+
offset = self.header.entry471.offset
148+
self.entry471 = []
149+
for _i in range(self.header.entry471.count):
150+
entry = BootEntry.read(self.blob, offset)
151+
self.entry471.append(entry)
152+
offset += BOOTENTRYSIZE
153+
154+
offset = self.header.entry472.offset
155+
self.entry472 = []
156+
for _i in range(self.header.entry472.count):
157+
entry = BootEntry.read(self.blob, offset)
158+
self.entry472.append(entry)
159+
offset += BOOTENTRYSIZE
160+
161+
offset = self.header.loader.offset
162+
self.loader = []
163+
for _i in range(self.header.loader.count):
164+
entry = BootEntry.read(self.blob, offset)
165+
self.loader.append(entry)
166+
offset += BOOTENTRYSIZE
167+
crc32 = self.blob[-4:]
168+
calc_crc32 = RkCrc32.calc(self.blob[:-4])
169+
(self.crc32,) = struct.unpack("<I", crc32)
170+
assert self.crc32 == calc_crc32
171+
172+
def entry_data(self, name, idx = 0):
173+
entry = None
174+
if name == "471":
175+
entry = self.entry471
176+
elif name == "472":
177+
entry = self.entry472
178+
elif name == "loader":
179+
entry = self.loader
180+
else:
181+
raise LoaderFileError(f"Invalid name {name}")
182+
183+
if idx > len(entry):
184+
raise LoaderFileError(f"Invalid index {idx}. Only has {len(entry)} entries.")
185+
e = entry[idx]
186+
logger.debug(f"{e}")
187+
return (self.blob[e.data_offset:e.data_offset+e.data_size], e.data_delay)
188+
189+
def __str__(self):
190+
return f"{self.header} crc: {self.crc32:02x}"
191+
192+
def rc4_encrypt(fw_blob):
193+
194+
# Round to 4096 block size
195+
blob_len = len(fw_blob)
196+
padded_len = (blob_len+4095)//4096 * 4096
197+
fw_blob = bytearray(fw_blob)
198+
fw_blob += bytearray([0]*(padded_len - blob_len))
199+
a = ARC4(RC4_KEY)
200+
c = Cipher(a, mode=None)
201+
d = c.encryptor()
202+
obuf = bytearray()
203+
for i in range(padded_len):
204+
obuf += d.update(fw_blob[i*512:(i+1)*512])
205+
return obuf
206+
207+
def rockchip_run(dev, fw_name, fw_blob):
208+
rom = rockchip.RochipBootRom(dev)
209+
210+
if fw_name == 'code471':
211+
logger.info("Downloading code471...")
212+
blob = rc4_encrypt(fw_blob)
213+
rom.write_blob(blob, 0x471)
214+
elif fw_name == 'code472':
215+
logger.info("Downloading code472...")
216+
blob = rc4_encrypt(fw_blob)
217+
rom.write_blob(blob, 0x472)
218+
else:
219+
fw = LoaderFile(fw_blob)
220+
logger.info(f"{fw}")
221+
for i in range(fw.header.entry471.count):
222+
logger.info(f"Downloading entry 471 {i}...")
223+
(data, delay) = fw.entry_data("471", i)
224+
rom.write_blob(data, 0x471)
225+
logger.info(f"Sleeping {delay}ms")
226+
time.sleep(delay / 1000)
227+
logger.info("Done")
228+
for i in range(fw.header.entry472.count):
229+
logger.info(f"Downloading entry 472 {i}...")
230+
(data, delay) = fw.entry_data("472", i)
231+
rom.write_blob(data, 0x472)
232+
logger.info(f"Sleeping {delay}ms")
233+
time.sleep(delay / 1000)
234+
logger.info("Done")

0 commit comments

Comments
 (0)