Skip to content

Commit 8fcddbc

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 8fcddbc

File tree

1 file changed

+239
-0
lines changed

1 file changed

+239
-0
lines changed

src/snagrecover/firmware/rk_fw.py

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

0 commit comments

Comments
 (0)