developer | 23f9f0f | 2023-06-15 13:06:25 +0800 | [diff] [blame] | 1 | diff --git a/scripts/make-squashfs-hashed.sh b/scripts/make-squashfs-hashed.sh |
| 2 | new file mode 100755 |
| 3 | index 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} |
| 30 | diff --git a/scripts/prepare-dm-verity-uboot-script.sh b/scripts/prepare-dm-verity-uboot-script.sh |
| 31 | new file mode 100755 |
| 32 | index 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} |
| 90 | diff --git a/tools/ar-tool/Makefile b/tools/ar-tool/Makefile |
| 91 | new file mode 100644 |
| 92 | index 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)) |
| 132 | diff --git a/tools/ar-tool/src/Makefile b/tools/ar-tool/src/Makefile |
| 133 | new file mode 100644 |
| 134 | index 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} |
| 158 | diff --git a/tools/ar-tool/src/ar-tool.py b/tools/ar-tool/src/ar-tool.py |
| 159 | new file mode 100755 |
| 160 | index 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 | + |