blob: 332ee6488b667571b6d5a5dbf0b8a3e0f020b36b [file] [log] [blame]
developer23f9f0f2023-06-15 13:06:25 +08001diff --git a/scripts/make-squashfs-hashed.sh b/scripts/make-squashfs-hashed.sh
2new file mode 100755
3index 0000000..a4b183e
4--- /dev/null
5+++ b/scripts/make-squashfs-hashed.sh
6@@ -0,0 +1,23 @@
7+#!/bin/bash
8+#
9+# 1. Using veritysetup to append hash image into squashfs
10+# 2. Parsing output of veritysetup to generate uboot script
11+#
12+SQUASHFS_FILE_PATH=$1
13+STAGING_DIR_HOST=$2
14+TOPDIR=$3
15+SUMMARY_FILE=$4
16+
17+FILE_SIZE=`stat -c "%s" ${SQUASHFS_FILE_PATH}`
18+BLOCK_SIZE=4096
19+
20+DATA_BLOCKS=$((${FILE_SIZE} / ${BLOCK_SIZE}))
21+[ $((${FILE_SIZE} % ${BLOCK_SIZE})) -ne 0 ] && DATA_BLOCKS=$((${DATA_BLOCKS} + 1))
22+
23+HASH_OFFSET=$((${DATA_BLOCKS} * ${BLOCK_SIZE}))
24+
25+${STAGING_DIR_HOST}/bin/veritysetup format \
26+ --data-blocks=${DATA_BLOCKS} \
27+ --hash-offset=${HASH_OFFSET} \
28+ ${SQUASHFS_FILE_PATH} ${SQUASHFS_FILE_PATH} \
29+ > ${SUMMARY_FILE}
30diff --git a/scripts/prepare-dm-verity-uboot-script.sh b/scripts/prepare-dm-verity-uboot-script.sh
31new file mode 100755
32index 0000000..a66b921
33--- /dev/null
34+++ b/scripts/prepare-dm-verity-uboot-script.sh
35@@ -0,0 +1,54 @@
36+#!/bin/bash
37+
38+ROOT_DEVICE=$1
39+EXTRA_ARGS=$2
40+
41+while read line; do
42+ key=$(echo ${line} | cut -f1 -d':')
43+ value=$(echo ${line} | cut -f2 -d':')
44+
45+ case "${key}" in
46+ "UUID")
47+ UUID=${value}
48+ ;;
49+ "Data blocks")
50+ DATA_BLOCKS=${value}
51+ ;;
52+ "Data block size")
53+ DATA_BLOCK_SIZE=${value}
54+ ;;
55+ "Hash block size")
56+ HASH_BLOCK_SIZE=${value}
57+ ;;
58+ "Hash algorithm")
59+ HASH_ALG=${value}
60+ ;;
61+ "Salt")
62+ SALT=${value}
63+ ;;
64+ "Root hash")
65+ ROOT_HASH=${value}
66+ ;;
67+ esac
68+done
69+
70+#
71+# dm-mod.create=<name>,<uuid>,<minor>,<flags>,
72+# <start_sector> <num_sectors> <target_type> <target_args>
73+# <target_type>=verity
74+# <target_args>=<version> <data_dev> <hash_dev> <data_block_size> <hash_block_size>
75+# <num_data_blocks> <hash_start_block> <algorithm> <root_hash> <salt>
76+#
77+# <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
78+# <minor> ::= The device minor number | ""
79+# <flags> ::= "ro" | "rw"
80+#
81+# More detail in field you can ref.
82+# Documentation/admin-guide/device-mapper/dm-init.rst
83+# Documentation/admin-guide/device-mapper/verity.rst
84+#
85+
86+BOOTARGS=$( printf '%s root=/dev/dm-0 dm-mod.create="dm-verity,,,ro,0 %s verity 1 %s %s %s %s %s %s %s %s %s"' \
87+ "${EXTRA_ARGS}" $((${DATA_BLOCKS} * 8)) ${ROOT_DEVICE} ${ROOT_DEVICE} ${DATA_BLOCK_SIZE} ${HASH_BLOCK_SIZE} ${DATA_BLOCKS} $((${DATA_BLOCKS} + 1)) ${HASH_ALG} ${ROOT_HASH} ${SALT} )
88+
89+echo setenv bootargs ${BOOTARGS}
90diff --git a/tools/ar-tool/Makefile b/tools/ar-tool/Makefile
91new file mode 100644
92index 0000000..2b22ac0
93--- /dev/null
94+++ b/tools/ar-tool/Makefile
95@@ -0,0 +1,36 @@
96+#
97+# Copyright (C) 2011-2012 OpenWrt.org
98+#
99+# This is free software, licensed under the GNU General Public License v2.
100+# See /LICENSE for more information.
101+#
102+
103+include $(TOPDIR)/rules.mk
104+
105+PKG_NAME:=ar-tool
106+PKG_VERSION:=1
107+
108+include $(INCLUDE_DIR)/host-build.mk
109+
110+define Host/Prepare
111+ mkdir -p $(HOST_BUILD_DIR)
112+ $(CP) ./src/* $(HOST_BUILD_DIR)/
113+endef
114+
115+define Host/Compile
116+ $(MAKE) -C $(HOST_BUILD_DIR)
117+endef
118+
119+define Host/Configure
120+endef
121+
122+define Host/Install
123+ $(CP) $(HOST_BUILD_DIR)/ar-tool $(STAGING_DIR_HOST)/bin/
124+endef
125+
126+define Host/Clean
127+ rm -f $(HOST_BUILD_DIR)/ar-tool
128+ rm -f $(STAGING_DIR_HOST)/bin/ar-tool
129+endef
130+
131+$(eval $(call HostBuild))
132diff --git a/tools/ar-tool/src/Makefile b/tools/ar-tool/src/Makefile
133new file mode 100644
134index 0000000..26ab3cf
135--- /dev/null
136+++ b/tools/ar-tool/src/Makefile
137@@ -0,0 +1,20 @@
138+#
139+# Copyright (C) 2019 MediaTek Inc.
140+#
141+# Author: Sam Shih <sam.shih@mediatek.com>
142+#
143+# SPDX-License-Identifier: BSD-3-Clause
144+# https://spdx.org/licenses
145+#
146+
147+TARGET := ar-tool
148+
149+.PHONY: all clean
150+
151+all: ${TARGET}
152+
153+%: %.py Makefile
154+ cp $< $@
155+
156+clean:
157+ rm ${TARGET}
158diff --git a/tools/ar-tool/src/ar-tool.py b/tools/ar-tool/src/ar-tool.py
159new file mode 100755
160index 0000000..e33510b
161--- /dev/null
162+++ b/tools/ar-tool/src/ar-tool.py
163@@ -0,0 +1,302 @@
164+#!/usr/bin/python
165+import os
166+import sys
167+from xml.dom import minidom
168+import pdb
169+import traceback
170+import re
171+
172+
173+class bl_ar_table_t:
174+
175+ def __init__(self, input_file):
176+ self.input_file = input_file
177+ self.ar_ver_list = []
178+
179+ def generate_ar_ver_code(self):
180+ code = ""
181+ code += "/* \n"
182+ code += " * This file is auto-generated by ar-tool\n"
183+ code += " * please do not modify this file manually\n"
184+ code += " */\n"
185+ code += "#include <plat/common/platform.h>\n"
186+ code += "const uint32_t bl_ar_ver = %d;\n" % self.ar_ver_list[-1]
187+ return code
188+
189+ def generate_ar_conf_code(self):
190+ code = ""
191+ code += "BL_AR_VER\t:=\t%d\n" % self.ar_ver_list[-1]
192+ return code
193+
194+ def check_and_set_ar_ver_list(self, ar_ver):
195+ if ((ar_ver not in self.ar_ver_list) and (ar_ver <= 64) and (ar_ver >= 0)):
196+ self.ar_ver_list.append(ar_ver)
197+ return True
198+ else:
199+ return False
200+
201+ def get_data_by_name_from_ar_entry(self, xml_node, entry_id, name, print_err=True):
202+ i = entry_id
203+ datalist = xml_node.getElementsByTagName(name)
204+ if not datalist:
205+ if print_err is True:
206+ print("XML parse fail in ar_entry[%d]:" % i)
207+ print(" Chilld node '%s' not exist" % name)
208+ return None
209+ data = None
210+ if len(datalist) != 1:
211+ if print_err is True:
212+ print("XML parse fail in ar_entry[%d]:" % i)
213+ print(" Duplicate '%s' node exist" % name)
214+ return None
215+ datanode = datalist[0].firstChild
216+ if not datanode:
217+ if print_err is True:
218+ print("XML parse fail in ar_entry[%d].%s:" % (i, name))
219+ print(" '%s' data not exist" % name)
220+ return None
221+ if datanode.nodeType != datanode.TEXT_NODE:
222+ if print_err is True:
223+ print("XML parse fail in ar_entry[%d].%s:" % (i, name))
224+ print(" '%s' data not exist" % name)
225+ return None
226+ return str(datanode.data)
227+
228+ def get_int_by_name_from_ar_entry(self, xml_node, entry_id, name, print_err=True):
229+ data = self.get_data_by_name_from_ar_entry(xml_node, entry_id, name, print_err)
230+ if data:
231+ data = data.strip()
232+ if not data.isdigit():
233+ if print_err is True:
234+ print("XML parse fail in ar_entry[%d].%s:" % (i, name))
235+ print(" '%s' must be an integer" % name)
236+ return None
237+ return data
238+ return None
239+
240+ def xml_debug_show(self, line, column):
241+ f = open(self.input_file, "r")
242+ if not f:
243+ sys.stderr.write("Unable to open file '%s'\n" % self.input_file)
244+ raise
245+ xml_data = f.read()
246+ xml_lines = xml_data.split("\n")
247+ f.close()
248+ print("input xml fail at line %d, column %d" % (line, column))
249+ if line < 2:
250+ show_lines = [xml_lines[line]]
251+ elif line+2 >= len(xml_lines):
252+ show_lines = [xml_lines[line]]
253+ else:
254+ show_lines = xml_lines[line-1:line+1]
255+ for line in show_lines:
256+ print(line)
257+
258+ def parse(self):
259+ data = None
260+ try:
261+ f = open(self.input_file, "r")
262+ if not f:
263+ raise
264+ f.close()
265+ except:
266+ sys.stderr.write("Unable to open file '%s'\n" % self.input_file)
267+ return 1
268+ try:
269+ xmldoc = minidom.parse(self.input_file)
270+ ar_entry_list = xmldoc.getElementsByTagName('bl_ar_entry')
271+
272+ for i in range(0, len(ar_entry_list)):
273+ ar_entry = ar_entry_list[i]
274+ data = self.get_int_by_name_from_ar_entry(ar_entry, i, "USED", False)
275+ if not data:
276+ continue
277+
278+ data = self.get_int_by_name_from_ar_entry(ar_entry, i, "BL_AR_VER")
279+ if not data:
280+ return 1
281+ if data:
282+ data = data.strip()
283+ if self.check_and_set_ar_ver_list(int(data)) is False:
284+ print("XML parse fail in bl_ar_entry[%d].BL_AR_VER:" % i)
285+ print(" 'BL_AR_VER' value duplicate or exceed range")
286+ return 1
287+ print("Get %d record in bl_ar_table" % len(self.ar_ver_list))
288+ except:
289+ sys.stderr.write("Unable to parse file '%s'\n" % self.input_file)
290+ crash_info = traceback.format_exc()
291+ m = re.search("ExpatError: mismatched tag: line (.+), column (.+)", crash_info)
292+ if m:
293+ line = int(m.group(1))
294+ column = int(m.group(2))
295+ self.xml_debug_show(line, column)
296+ print(m.group(0))
297+ else:
298+ print(crash_info)
299+ return 1
300+ return 0
301+
302+
303+class fw_ar_table_t:
304+
305+ def __init__(self, input_file):
306+ self.input_file = input_file
307+ self.ar_ver_list = []
308+
309+ def generate_ar_ver_code(self):
310+ code = ""
311+ code += "/* \n"
312+ code += " * This file is auto-generated by ar-tool\n"
313+ code += " * please do not modify this file manually\n"
314+ code += " */\n"
315+ code += "const uint32_t fw_ar_ver = %d;\n" % self.ar_ver_list[-1]
316+ return code
317+
318+ def generate_ar_conf_code(self):
319+ code = ""
320+ code += "FW_AR_VER\t:=\t%d\n" % self.ar_ver_list[-1]
321+ return code
322+
323+ def check_and_set_ar_ver_list(self, ar_ver):
324+ if ((ar_ver not in self.ar_ver_list) and (ar_ver <= 64) and (ar_ver >= 0)):
325+ self.ar_ver_list.append(ar_ver)
326+ return True
327+ else:
328+ return False
329+
330+ def get_data_by_name_from_ar_entry(self, xml_node, entry_id, name, print_err=True):
331+ i = entry_id
332+ datalist = xml_node.getElementsByTagName(name)
333+ if not datalist:
334+ if print_err is True:
335+ print("XML parse fail in ar_entry[%d]:" % i)
336+ print(" Chilld node '%s' not exist" % name)
337+ return None
338+ data = None
339+ if len(datalist) != 1:
340+ if print_err is True:
341+ print("XML parse fail in ar_entry[%d]:" % i)
342+ print(" Duplicate '%s' node exist" % name)
343+ return None
344+ datanode = datalist[0].firstChild
345+ if not datanode:
346+ if print_err is True:
347+ print("XML parse fail in ar_entry[%d].%s:" % (i, name))
348+ print(" '%s' data not exist" % name)
349+ return None
350+ if datanode.nodeType != datanode.TEXT_NODE:
351+ if print_err is True:
352+ print("XML parse fail in ar_entry[%d].%s:" % (i, name))
353+ print(" '%s' data not exist" % name)
354+ return None
355+ return str(datanode.data)
356+
357+ def get_int_by_name_from_ar_entry(self, xml_node, entry_id, name, print_err=True):
358+ data = self.get_data_by_name_from_ar_entry(xml_node, entry_id, name, print_err)
359+ if data:
360+ data = data.strip()
361+ if not data.isdigit():
362+ if print_err is True:
363+ print("XML parse fail in ar_entry[%d].%s:" % (i, name))
364+ print(" '%s' must be an integer" % name)
365+ return None
366+ return data
367+ return None
368+
369+ def xml_debug_show(self, line, column):
370+ f = open(self.input_file, "r")
371+ if not f:
372+ sys.stderr.write("Unable to open file '%s'\n" % self.input_file)
373+ raise
374+ xml_data = f.read()
375+ xml_lines = xml_data.split("\n")
376+ f.close()
377+ print("input xml fail at line %d, column %d" % (line, column))
378+ if line < 2:
379+ show_lines = [xml_lines[line]]
380+ elif line+2 >= len(xml_lines):
381+ show_lines = [xml_lines[line]]
382+ else:
383+ show_lines = xml_lines[line-1:line+1]
384+ for line in show_lines:
385+ print(line)
386+
387+ def parse(self):
388+ data = None
389+ try:
390+ f = open(self.input_file, "r")
391+ if not f:
392+ raise
393+ f.close()
394+ except:
395+ sys.stderr.write("Unable to open file '%s'\n" % self.input_file)
396+ return 1
397+ try:
398+ xmldoc = minidom.parse(self.input_file)
399+ ar_entry_list = xmldoc.getElementsByTagName('fw_ar_entry')
400+
401+ for i in range(0, len(ar_entry_list)):
402+ ar_entry = ar_entry_list[i]
403+ data = self.get_int_by_name_from_ar_entry(ar_entry, i, "USED", False)
404+ if not data:
405+ continue
406+
407+ data = self.get_int_by_name_from_ar_entry(ar_entry, i, "FW_AR_VER")
408+ if not data:
409+ return 1
410+ if data:
411+ data = data.strip()
412+ if self.check_and_set_ar_ver_list(int(data)) is False:
413+ print("XML parse fail in fw_ar_entry[%d].FW_AR_VER:" % i)
414+ print(" 'FW_AR_VER' value duplicate or exceed range")
415+ return 1
416+ print("Get %d record in fw_ar_table" % len(self.ar_ver_list))
417+ except:
418+ sys.stderr.write("Unable to parse file '%s'\n" % self.input_file)
419+ crash_info = traceback.format_exc()
420+ m = re.search("ExpatError: mismatched tag: line (.+), column (.+)", crash_info)
421+ if m:
422+ line = int(m.group(1))
423+ column = int(m.group(2))
424+ self.xml_debug_show(line, column)
425+ print(m.group(0))
426+ else:
427+ print(crash_info)
428+ return 1
429+ return 0
430+
431+
432+def main(argc, argv):
433+ if argc != 5:
434+ sys.stdout.write("ar-tool [bl_ar_table|fw_ar_table] [create_ar_ver|create_ar_conf] $(input_file) $(output_file)\n")
435+ return 1
436+ if argv[1] == "bl_ar_table":
437+ ar_table = bl_ar_table_t(argv[3])
438+ else:
439+ ar_table = fw_ar_table_t(argv[3])
440+ if ar_table.parse() != 0:
441+ return 1
442+ if argv[2] == "create_ar_ver":
443+ code = ar_table.generate_ar_ver_code()
444+ print("(%s) --> (%s)" % (argv[3], argv[4]))
445+ #print(code)
446+ f = open(argv[4], "w")
447+ f.write(code)
448+ f.close()
449+ return 0
450+ elif argv[2] == "create_ar_conf":
451+ code = ar_table.generate_ar_conf_code()
452+ print("(%s) --> (%s)" % (argv[3], argv[4]))
453+ #print(code)
454+ f = open(argv[4], "w")
455+ f.write(code)
456+ f.close()
457+ return 0
458+ else:
459+ print("Unknow option '%s'" % argv[1])
460+ return 1
461+
462+
463+if __name__ == '__main__':
464+ sys.exit(main(len(sys.argv), sys.argv))
465+