mirror of
https://gitee.com/Vancouver2017/luban-lite-t3e-pro.git
synced 2025-12-14 10:28:54 +00:00
1391 lines
52 KiB
Python
Executable File
1391 lines
52 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding:utf-8 -*-
|
|
# SPDX-License-Identifier: GPL-2.0+
|
|
#
|
|
# Copyright (C) 2021 ArtInChip Technology Co., Ltd
|
|
# Dehuang Wu <dehuang.wu@artinchip.com>
|
|
|
|
import os, sys, subprocess, math, re, zlib, json, struct, argparse
|
|
from collections import namedtuple
|
|
from collections import OrderedDict
|
|
from Cryptodome.PublicKey import RSA
|
|
from Cryptodome.Hash import MD5
|
|
from Cryptodome.Hash import SHA256
|
|
from Cryptodome.Cipher import AES
|
|
from Cryptodome.Signature import PKCS1_v1_5
|
|
|
|
DATA_ALIGNED_SIZE = 2048
|
|
META_ALIGNED_SIZE = 512
|
|
VERBOSE = False
|
|
|
|
def parse_image_cfg(cfgfile):
|
|
""" Load image configuration file
|
|
Args:
|
|
cfgfile: Configuration file name
|
|
"""
|
|
with open(cfgfile, "r") as f:
|
|
lines = f.readlines()
|
|
jsonstr = ""
|
|
for line in lines:
|
|
sline = line.strip()
|
|
if sline.startswith("//"):
|
|
continue
|
|
slash_start = sline.find("//")
|
|
if slash_start > 0:
|
|
jsonstr += sline[0:slash_start]
|
|
else:
|
|
jsonstr += sline
|
|
# Use OrderedDict is important, we need to iterate FWC in order.
|
|
jsonstr = jsonstr.replace(",}", "}").replace(",]", "]")
|
|
cfg = json.loads(jsonstr, object_pairs_hook=OrderedDict)
|
|
return cfg
|
|
|
|
def get_file_path(path, alternate_dir):
|
|
if os.path.exists(path):
|
|
return path
|
|
if os.path.exists(alternate_dir + path):
|
|
return alternate_dir + path
|
|
return None
|
|
|
|
def aic_boot_get_resource_file_size(cfg, keydir, datadir):
|
|
""" Get size of all resource files
|
|
"""
|
|
files = {}
|
|
filepath = ""
|
|
if "resource" in cfg:
|
|
if "private" in cfg["resource"]:
|
|
filepath = get_file_path(cfg["resource"]["private"], keydir)
|
|
if filepath == None:
|
|
filepath = get_file_path(cfg["resource"]["private"], datadir)
|
|
if filepath == None:
|
|
print("Error, {} is not found.".format(cfg["resource"]["private"]))
|
|
sys.exit(1)
|
|
statinfo = os.stat(filepath)
|
|
files["resource/private"] = statinfo.st_size
|
|
files["round(resource/private)"] = round_up(statinfo.st_size, 4)
|
|
|
|
if "pubkey" in cfg["resource"]:
|
|
filepath = get_file_path(cfg["resource"]["pubkey"], keydir)
|
|
if filepath == None:
|
|
filepath = get_file_path(cfg["resource"]["pubkey"], datadir)
|
|
if filepath == None:
|
|
print("Error, {} is not found.".format(cfg["resource"]["pubkey"]))
|
|
sys.exit(1)
|
|
statinfo = os.stat(filepath)
|
|
files["resource/pubkey"] = statinfo.st_size
|
|
files["round(resource/pubkey)"] = round_up(statinfo.st_size, 4)
|
|
if "pbp" in cfg["resource"]:
|
|
filepath = get_file_path(cfg["resource"]["pbp"], datadir)
|
|
if filepath == None:
|
|
print("Error, {} is not found.".format(cfg["resource"]["pbp"]))
|
|
sys.exit(1)
|
|
statinfo = os.stat(filepath)
|
|
files["resource/pbp"] = statinfo.st_size
|
|
files["round(resource/pbp)"] = round_up(statinfo.st_size, 16)
|
|
if "encryption" in cfg:
|
|
if "iv" in cfg["encryption"]:
|
|
filepath = get_file_path(cfg["encryption"]["iv"], keydir)
|
|
if filepath == None:
|
|
filepath = get_file_path(cfg["encryption"]["iv"], datadir)
|
|
if filepath == None:
|
|
print("Error, {} is not found.".format(cfg["encryption"]["iv"]))
|
|
sys.exit(1)
|
|
statinfo = os.stat(filepath)
|
|
files["encryption/iv"] = statinfo.st_size
|
|
files["round(encryption/iv)"] = round_up(statinfo.st_size, 4)
|
|
if "loader" in cfg:
|
|
if "file" in cfg["loader"]:
|
|
filepath = get_file_path(cfg["loader"]["file"], datadir)
|
|
if filepath != None:
|
|
statinfo = os.stat(filepath)
|
|
if statinfo.st_size > (4 * 1024 * 1024):
|
|
print("Loader size is too large")
|
|
sys.exit(1)
|
|
files["loader/file"] = statinfo.st_size
|
|
files["round(loader/file)"] = round_up(statinfo.st_size, 256)
|
|
else:
|
|
print("File {} is not exist".format(cfg["loader"]["file"]))
|
|
sys.exit(1)
|
|
return files
|
|
|
|
def aic_boot_calc_image_length(filesizes, sign):
|
|
""" Calculate the boot image's total length
|
|
"""
|
|
total_siz = filesizes["resource_start"]
|
|
if "resource/pubkey" in filesizes:
|
|
total_siz = total_siz + filesizes["round(resource/pubkey)"]
|
|
if "encryption/iv" in filesizes:
|
|
total_siz = total_siz + filesizes["round(encryption/iv)"]
|
|
if "resource/private" in filesizes:
|
|
total_siz = total_siz + filesizes["round(resource/private)"]
|
|
if "resource/pbp" in filesizes:
|
|
total_siz = total_siz + filesizes["round(resource/pbp)"]
|
|
total_siz = round_up(total_siz, 256)
|
|
if sign:
|
|
# Add the length of signature
|
|
total_siz = total_siz + 256
|
|
else:
|
|
# Add the length of md5
|
|
total_siz = total_siz + 16
|
|
return total_siz
|
|
|
|
def aic_boot_calc_image_length_for_ext(filesizes, sign):
|
|
""" Calculate the boot image's total length
|
|
"""
|
|
total_siz = filesizes["resource_start"]
|
|
if "resource/pubkey" in filesizes:
|
|
total_siz = total_siz + filesizes["round(resource/pubkey)"]
|
|
if "resource/private" in filesizes:
|
|
total_siz = total_siz + filesizes["round(resource/private)"]
|
|
total_siz = round_up(total_siz, 256)
|
|
if sign:
|
|
# Add the length of signature
|
|
total_siz = total_siz + 256
|
|
else:
|
|
# Add the length of md5
|
|
total_siz = total_siz + 16
|
|
return total_siz
|
|
|
|
def check_loader_run_in_dram(cfg):
|
|
if "loader" not in cfg:
|
|
return False
|
|
if "run in dram" in cfg["loader"]:
|
|
if cfg["loader"]["run in dram"].upper() == "FALSE":
|
|
return False
|
|
return True
|
|
|
|
def aic_boot_get_loader_bytes(cfg, filesizes):
|
|
""" Read the loader's binaray data, and perform encryption if it is needed.
|
|
"""
|
|
|
|
loader_size = 0
|
|
header_size = 256
|
|
rawbytes = bytearray(0)
|
|
if check_loader_run_in_dram(cfg):
|
|
# No loader in first aicimg
|
|
# Record the information to generate header and resource bytes
|
|
filesizes["resource_start"] = header_size + loader_size
|
|
return rawbytes
|
|
|
|
if "round(loader/file)" in filesizes:
|
|
loader_size = filesizes["round(loader/file)"]
|
|
try:
|
|
fpath = get_file_path(cfg["loader"]["file"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
rawbytes = f.read(loader_size)
|
|
except IOError:
|
|
print("Failed to open loader file: {}".format(fpath))
|
|
sys.exit(1)
|
|
|
|
if len(rawbytes) == 0:
|
|
print("Read loader data failed.")
|
|
sys.exit(1)
|
|
if len(rawbytes) < loader_size:
|
|
rawbytes = rawbytes + bytearray(loader_size - len(rawbytes))
|
|
|
|
# Record the information to generate header and resource bytes
|
|
filesizes["resource_start"] = header_size + loader_size
|
|
|
|
if "encryption" in cfg and loader_size > 0:
|
|
# Only encrypt loader content, if loader not exist, don't do it
|
|
try:
|
|
fpath = get_file_path(cfg["encryption"]["key"], cfg["keydir"])
|
|
if fpath == None:
|
|
fpath = get_file_path(cfg["encryption"]["key"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
keydata = f.read(16)
|
|
except IOError:
|
|
print('Failed to open aes key file')
|
|
sys.exit(1)
|
|
try:
|
|
fpath = get_file_path(cfg["encryption"]["iv"], cfg["keydir"])
|
|
if fpath == None:
|
|
fpath = get_file_path(cfg["encryption"]["iv"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
ivdata = f.read(16)
|
|
except IOError:
|
|
print('Failed to open iv file')
|
|
sys.exit(1)
|
|
cipher = AES.new(keydata, AES.MODE_CBC, ivdata)
|
|
enc_bytes = cipher.encrypt(rawbytes)
|
|
return enc_bytes
|
|
else:
|
|
return rawbytes
|
|
|
|
def aic_boot_get_loader_for_ext(cfg, filesizes):
|
|
""" Read the loader's binaray data, and perform encryption if it is needed.
|
|
"""
|
|
|
|
loader_size = 0
|
|
rawbytes = bytearray(0)
|
|
if "round(loader/file)" in filesizes:
|
|
loader_size = filesizes["round(loader/file)"]
|
|
try:
|
|
fpath = get_file_path(cfg["loader"]["file"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
rawbytes = f.read(loader_size)
|
|
except IOError:
|
|
print("Failed to open loader file: {}".format(fpath))
|
|
sys.exit(1)
|
|
|
|
if len(rawbytes) == 0:
|
|
print("Read loader data failed.")
|
|
sys.exit(1)
|
|
if len(rawbytes) < loader_size:
|
|
rawbytes = rawbytes + bytearray(loader_size - len(rawbytes))
|
|
|
|
header_size = 256
|
|
# Record the information to generate header and resource bytes
|
|
filesizes["resource_start"] = header_size + loader_size
|
|
|
|
return rawbytes
|
|
|
|
def aic_boot_get_resource_bytes(cfg, filesizes):
|
|
""" Pack all resource data into boot image's resource section
|
|
"""
|
|
resbytes = bytearray(0)
|
|
if "resource/private" in filesizes:
|
|
priv_size = filesizes["round(resource/private)"]
|
|
try:
|
|
fpath = get_file_path(cfg["resource"]["private"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
privdata = f.read(priv_size)
|
|
except IOError:
|
|
print('Failed to open private file')
|
|
sys.exit(1)
|
|
resbytes = resbytes + privdata + bytearray(priv_size - len(privdata))
|
|
if "resource/pubkey" in filesizes:
|
|
pubkey_size = filesizes["round(resource/pubkey)"]
|
|
try:
|
|
fpath = get_file_path(cfg["resource"]["pubkey"], cfg["keydir"])
|
|
if fpath == None:
|
|
fpath = get_file_path(cfg["resource"]["pubkey"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
pkdata = f.read(pubkey_size)
|
|
except IOError:
|
|
print('Failed to open pubkey file')
|
|
sys.exit(1)
|
|
# Add padding to make it alignment
|
|
resbytes = resbytes + pkdata + bytearray(pubkey_size - len(pkdata))
|
|
if "encryption/iv" in filesizes:
|
|
iv_size = filesizes["round(encryption/iv)"]
|
|
try:
|
|
fpath = get_file_path(cfg["encryption"]["iv"], cfg["keydir"])
|
|
if fpath == None:
|
|
fpath = get_file_path(cfg["encryption"]["iv"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
ivdata = f.read(iv_size)
|
|
except IOError:
|
|
print('Failed to open iv file')
|
|
sys.exit(1)
|
|
resbytes = resbytes + ivdata + bytearray(iv_size - len(ivdata))
|
|
if "resource/pbp" in filesizes:
|
|
pbp_size = filesizes["round(resource/pbp)"]
|
|
try:
|
|
fpath = get_file_path(cfg["resource"]["pbp"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
pbp_data = f.read(pbp_size)
|
|
except IOError:
|
|
print('Failed to open pbp file')
|
|
sys.exit(1)
|
|
resbytes = resbytes + pbp_data + bytearray(pbp_size - len(pbp_data))
|
|
if len(resbytes) > 0:
|
|
res_size = round_up(len(resbytes), 256)
|
|
if len(resbytes) != res_size:
|
|
resbytes = resbytes + bytearray(res_size - len(resbytes))
|
|
return resbytes
|
|
|
|
def aic_boot_get_resource_for_ext(cfg, filesizes):
|
|
""" Pack all resource data into boot image's resource section
|
|
"""
|
|
resbytes = bytearray(0)
|
|
if "resource/private" in filesizes:
|
|
priv_size = filesizes["round(resource/private)"]
|
|
try:
|
|
fpath = get_file_path(cfg["resource"]["private"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
privdata = f.read(priv_size)
|
|
except IOError:
|
|
print('Failed to open private file')
|
|
sys.exit(1)
|
|
resbytes = resbytes + privdata + bytearray(priv_size - len(privdata))
|
|
if "resource/pubkey" in filesizes:
|
|
pubkey_size = filesizes["round(resource/pubkey)"]
|
|
try:
|
|
fpath = get_file_path(cfg["resource"]["pubkey"], cfg["keydir"])
|
|
if fpath == None:
|
|
fpath = get_file_path(cfg["resource"]["pubkey"], cfg["datadir"])
|
|
with open(fpath, "rb") as f:
|
|
pkdata = f.read(pubkey_size)
|
|
except IOError:
|
|
print('Failed to open pubkey file')
|
|
sys.exit(1)
|
|
# Add padding to make it alignment
|
|
resbytes = resbytes + pkdata + bytearray(pubkey_size - len(pkdata))
|
|
if len(resbytes) > 0:
|
|
res_size = round_up(len(resbytes), 256)
|
|
if len(resbytes) != res_size:
|
|
resbytes = resbytes + bytearray(res_size - len(resbytes))
|
|
return resbytes
|
|
|
|
def aic_boot_checksum(bootimg):
|
|
length = len(bootimg)
|
|
offset = 0
|
|
total = 0
|
|
while offset < length:
|
|
val = int.from_bytes(bootimg[offset: offset + 4], byteorder='little', signed=False)
|
|
total = total + val
|
|
offset = offset + 4
|
|
return (~total) & 0xFFFFFFFF
|
|
|
|
def aic_boot_add_header(h, n):
|
|
return h + n.to_bytes(4, byteorder='little', signed=False)
|
|
|
|
def aic_boot_gen_header_bytes(cfg, filesizes):
|
|
""" Generate header bytes
|
|
"""
|
|
# Prepare header information
|
|
magic = "AIC "
|
|
checksum = 0
|
|
header_ver = int("0x00010001", 16)
|
|
if "head_ver" in cfg:
|
|
header_ver = int(cfg["head_ver"], 16)
|
|
|
|
img_len = aic_boot_calc_image_length(filesizes, "signature" in cfg)
|
|
fw_ver = 0
|
|
if "anti-rollback counter" in cfg:
|
|
fw_ver = cfg["anti-rollback counter"]
|
|
|
|
loader_length = 0
|
|
if "loader/file" in filesizes:
|
|
loader_length = filesizes["loader/file"]
|
|
|
|
loader_ext_offset = 0
|
|
if check_loader_run_in_dram(cfg):
|
|
loader_length = 0
|
|
loader_ext_offset = img_len
|
|
# ensure ext loader start position is aligned to 512
|
|
loader_ext_offset = round_up(img_len, META_ALIGNED_SIZE)
|
|
|
|
load_address = 0
|
|
entry_point = 0
|
|
if "loader" in cfg:
|
|
load_address = int(cfg["loader"]["load address"], 16)
|
|
entry_point = int(cfg["loader"]["entry point"], 16)
|
|
sign_algo = 0
|
|
sign_offset = 0
|
|
sign_length = 0
|
|
sign_key_offset = 0
|
|
sign_key_length = 0
|
|
next_res_offset = filesizes["resource_start"]
|
|
priv_data_offset = 0
|
|
priv_data_length = 0
|
|
if "resource" in cfg and "private" in cfg["resource"]:
|
|
priv_data_offset = next_res_offset
|
|
priv_data_length = filesizes["resource/private"]
|
|
next_res_offset = priv_data_offset + filesizes["round(resource/private)"]
|
|
if "signature" in cfg and cfg["signature"]["algo"] == "rsa,2048":
|
|
sign_algo = 1
|
|
sign_length = 256
|
|
sign_offset = img_len - sign_length
|
|
else:
|
|
# Append md5 result to the end
|
|
sign_algo = 0
|
|
sign_length = 16
|
|
sign_offset = img_len - sign_length
|
|
|
|
if "resource" in cfg and "pubkey" in cfg["resource"]:
|
|
sign_key_offset = next_res_offset
|
|
# Set the length value equal to real size
|
|
sign_key_length = filesizes["resource/pubkey"]
|
|
# Calculate offset use the size after alignment
|
|
next_res_offset = sign_key_offset + filesizes["round(resource/pubkey)"]
|
|
enc_algo = 0
|
|
iv_data_offset = 0
|
|
iv_data_length = 0
|
|
if "encryption" in cfg and cfg["encryption"]["algo"] == "aes-128-cbc":
|
|
enc_algo = 1
|
|
iv_data_offset = next_res_offset
|
|
iv_data_length = 16
|
|
next_res_offset = iv_data_offset + filesizes["round(encryption/iv)"]
|
|
pbp_data_offset = 0
|
|
pbp_data_length = 0
|
|
if "resource" in cfg and "pbp" in cfg["resource"]:
|
|
pbp_data_offset = next_res_offset
|
|
pbp_data_length = filesizes["resource/pbp"]
|
|
next_res_offset = pbp_data_offset + filesizes["round(resource/pbp)"]
|
|
# Generate header bytes
|
|
header_bytes = magic.encode(encoding="utf-8")
|
|
header_bytes = aic_boot_add_header(header_bytes, checksum)
|
|
header_bytes = aic_boot_add_header(header_bytes, header_ver)
|
|
header_bytes = aic_boot_add_header(header_bytes, img_len)
|
|
header_bytes = aic_boot_add_header(header_bytes, fw_ver)
|
|
header_bytes = aic_boot_add_header(header_bytes, loader_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, load_address)
|
|
header_bytes = aic_boot_add_header(header_bytes, entry_point)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_algo)
|
|
header_bytes = aic_boot_add_header(header_bytes, enc_algo)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_key_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_key_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, iv_data_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, iv_data_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, priv_data_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, priv_data_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, pbp_data_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, pbp_data_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, loader_ext_offset)
|
|
header_bytes = header_bytes + bytearray(256 - len(header_bytes))
|
|
return header_bytes
|
|
|
|
def aic_boot_gen_header_for_ext(cfg, filesizes):
|
|
""" Generate header bytes
|
|
"""
|
|
# Prepare header information
|
|
magic = "AIC "
|
|
checksum = 0
|
|
header_ver = int("0x00010001", 16)
|
|
if "head_ver" in cfg:
|
|
header_ver = int(cfg["head_ver"], 16)
|
|
|
|
img_len = aic_boot_calc_image_length_for_ext(filesizes, "signature" in cfg)
|
|
fw_ver = 0
|
|
|
|
loader_length = 0
|
|
if "loader/file" in filesizes:
|
|
loader_length = filesizes["loader/file"]
|
|
|
|
loader_ext_offset = 0
|
|
|
|
load_address = 0
|
|
entry_point = 0
|
|
if "loader" in cfg:
|
|
load_address = int(cfg["loader"]["load address"], 16)
|
|
entry_point = int(cfg["loader"]["entry point"], 16)
|
|
sign_algo = 0
|
|
sign_offset = 0
|
|
sign_length = 0
|
|
sign_key_offset = 0
|
|
sign_key_length = 0
|
|
next_res_offset = filesizes["resource_start"]
|
|
priv_data_offset = 0
|
|
priv_data_length = 0
|
|
if "resource" in cfg and "private" in cfg["resource"]:
|
|
priv_data_offset = next_res_offset
|
|
priv_data_length = filesizes["resource/private"]
|
|
next_res_offset = priv_data_offset + filesizes["round(resource/private)"]
|
|
if "signature" in cfg and cfg["signature"]["algo"] == "rsa,2048":
|
|
sign_algo = 1
|
|
sign_length = 256
|
|
sign_offset = img_len - sign_length
|
|
else:
|
|
# Append md5 result to the end
|
|
sign_algo = 0
|
|
sign_length = 16
|
|
sign_offset = img_len - sign_length
|
|
|
|
if "resource" in cfg and "pubkey" in cfg["resource"]:
|
|
sign_key_offset = next_res_offset
|
|
# Set the length value equal to real size
|
|
sign_key_length = filesizes["resource/pubkey"]
|
|
# Calculate offset use the size after alignment
|
|
next_res_offset = sign_key_offset + filesizes["round(resource/pubkey)"]
|
|
enc_algo = 0
|
|
iv_data_offset = 0
|
|
iv_data_length = 0
|
|
pbp_data_offset = 0
|
|
pbp_data_length = 0
|
|
# Generate header bytes
|
|
header_bytes = magic.encode(encoding="utf-8")
|
|
header_bytes = aic_boot_add_header(header_bytes, checksum)
|
|
header_bytes = aic_boot_add_header(header_bytes, header_ver)
|
|
header_bytes = aic_boot_add_header(header_bytes, img_len)
|
|
header_bytes = aic_boot_add_header(header_bytes, fw_ver)
|
|
header_bytes = aic_boot_add_header(header_bytes, loader_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, load_address)
|
|
header_bytes = aic_boot_add_header(header_bytes, entry_point)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_algo)
|
|
header_bytes = aic_boot_add_header(header_bytes, enc_algo)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_key_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, sign_key_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, iv_data_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, iv_data_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, priv_data_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, priv_data_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, pbp_data_offset)
|
|
header_bytes = aic_boot_add_header(header_bytes, pbp_data_length)
|
|
header_bytes = aic_boot_add_header(header_bytes, loader_ext_offset)
|
|
header_bytes = header_bytes + bytearray(256 - len(header_bytes))
|
|
return header_bytes
|
|
|
|
def aic_boot_gen_signature_bytes(cfg, bootimg):
|
|
""" Generate RSASSA-PKCS1-v1.5 Signature with SHA-256
|
|
"""
|
|
if "privkey" not in cfg["signature"]:
|
|
print("RSA Private key is not exist.")
|
|
sys.exit(1)
|
|
try:
|
|
fpath = get_file_path(cfg["signature"]["privkey"], cfg["keydir"])
|
|
if fpath == None:
|
|
fpath = get_file_path(cfg["signature"]["privkey"], cfg["datadir"])
|
|
with open(fpath, 'rb') as frsa:
|
|
rsakey = RSA.importKey(frsa.read())
|
|
except IOError:
|
|
print("Failed to open file: " + cfg["signature"]["privkey"])
|
|
sys.exit(1)
|
|
# Check if it is private key
|
|
if rsakey.has_private() == False:
|
|
print("Should to use RSA private key to sign")
|
|
sys.exit(1)
|
|
keysize = max(1, math.ceil(rsakey.n.bit_length() / 8))
|
|
if keysize != 256:
|
|
print("Only RSA 2048 is supported, please input RSA 2048 Private Key.")
|
|
sys.exit(1)
|
|
# Calculate SHA-256 hash
|
|
sha256 = SHA256.new()
|
|
sha256.update(bootimg)
|
|
# Encrypt the hash, and using RSASSA-PKCS1-V1.5 Padding
|
|
signer = PKCS1_v1_5.new(rsakey)
|
|
sign_bytes = signer.sign(sha256)
|
|
return sign_bytes
|
|
|
|
def aic_boot_gen_img_md5_bytes(cfg, bootimg):
|
|
""" Calculate MD5 of image to make brom verify image faster
|
|
"""
|
|
# Calculate MD5 hash
|
|
md5 = MD5.new()
|
|
md5.update(bootimg)
|
|
md5_bytes = md5.digest()
|
|
return md5_bytes
|
|
|
|
def aic_boot_check_params(cfg):
|
|
if "encryption" in cfg and cfg["encryption"]["algo"] != "aes-128-cbc":
|
|
print("Only support aes-128-cbc encryption")
|
|
return False
|
|
if "signature" in cfg and cfg["signature"]["algo"] != "rsa,2048":
|
|
print("Only support rsa,2048 signature")
|
|
return False
|
|
# if "loader" not in cfg or "load address" not in cfg["loader"]:
|
|
# print("load address is not set")
|
|
# return False
|
|
# if "loader" not in cfg or "entry point" not in cfg["loader"]:
|
|
# print("entry point is not set")
|
|
# return False
|
|
return True
|
|
|
|
def aic_boot_create_image(cfg, keydir, datadir):
|
|
""" Create AIC format Boot Image for Boot ROM
|
|
"""
|
|
if aic_boot_check_params(cfg) == False:
|
|
sys.exit(1)
|
|
filesizes = aic_boot_get_resource_file_size(cfg, keydir, datadir)
|
|
|
|
loader_bytes = aic_boot_get_loader_bytes(cfg, filesizes)
|
|
resource_bytes = bytearray(0)
|
|
if "resource" in cfg or "encryption" in cfg:
|
|
resource_bytes = aic_boot_get_resource_bytes(cfg, filesizes)
|
|
header_bytes = aic_boot_gen_header_bytes(cfg, filesizes)
|
|
bootimg = header_bytes + loader_bytes + resource_bytes
|
|
|
|
head_ver = int("0x00010001", 16)
|
|
if "head_ver" in cfg:
|
|
head_ver = int(cfg["head_ver"], 16)
|
|
if "signature" in cfg:
|
|
signature_bytes = aic_boot_gen_signature_bytes(cfg, bootimg)
|
|
bootimg = bootimg + signature_bytes
|
|
return bootimg
|
|
|
|
# Secure boot is not enabled, always add md5 result to the end
|
|
md5_bytes = aic_boot_gen_img_md5_bytes(cfg, bootimg[8:])
|
|
bootimg = bootimg + md5_bytes
|
|
# Calculate checksum.
|
|
# When MD5 is disabled, checksum will be checked by BROM.
|
|
cs = aic_boot_checksum(bootimg)
|
|
cs_bytes = cs.to_bytes(4, byteorder='little', signed=False)
|
|
bootimg = bootimg[0:4] + cs_bytes + bootimg[8:]
|
|
# Verify the checksum value
|
|
cs = aic_boot_checksum(bootimg)
|
|
if cs != 0:
|
|
print("Checksum is error: {}".format(cs))
|
|
sys.exit(1)
|
|
return bootimg
|
|
|
|
def aic_boot_create_ext_image(cfg, keydir, datadir):
|
|
""" Create AIC format Boot Image for Boot ROM
|
|
"""
|
|
|
|
filesizes = aic_boot_get_resource_file_size(cfg, keydir, datadir)
|
|
loader_bytes = aic_boot_get_loader_for_ext(cfg, filesizes)
|
|
resource_bytes = bytearray(0)
|
|
if "resource" in cfg:
|
|
resource_bytes = aic_boot_get_resource_for_ext(cfg, filesizes)
|
|
header_bytes = aic_boot_gen_header_for_ext(cfg, filesizes)
|
|
bootimg = header_bytes + loader_bytes + resource_bytes
|
|
|
|
head_ver = int("0x00010001", 16)
|
|
if "head_ver" in cfg:
|
|
head_ver = int(cfg["head_ver"], 16)
|
|
if "signature" in cfg:
|
|
signature_bytes = aic_boot_gen_signature_bytes(cfg, bootimg)
|
|
bootimg = bootimg + signature_bytes
|
|
return bootimg
|
|
|
|
# Secure boot is not enabled, always add md5 result to the end
|
|
md5_bytes = aic_boot_gen_img_md5_bytes(cfg, bootimg[8:])
|
|
bootimg = bootimg + md5_bytes
|
|
# Calculate checksum.
|
|
# When MD5 is disabled, checksum will be checked by BROM.
|
|
cs = aic_boot_checksum(bootimg)
|
|
cs_bytes = cs.to_bytes(4, byteorder='little', signed=False)
|
|
bootimg = bootimg[0:4] + cs_bytes + bootimg[8:]
|
|
# Verify the checksum value
|
|
cs = aic_boot_checksum(bootimg)
|
|
if cs != 0:
|
|
print("Checksum is error: {}".format(cs))
|
|
sys.exit(1)
|
|
return bootimg
|
|
|
|
def itb_create_image(itsname, itbname, keydir, dtbname):
|
|
# If the key exists, generate image signature information and write it to the itb file.
|
|
# If the key exists, write the public key to the dtb file.
|
|
if keydir != None and dtbname != None:
|
|
cmd = ["mkimage", "-f", itsname, "-k", keydir, "-K", dtbname, "-r", itbname]
|
|
else:
|
|
cmd = ["mkimage", "-f", itsname, itbname]
|
|
|
|
ret = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
if ret.returncode != 0:
|
|
sys.exit(1)
|
|
|
|
def img_gen_fw_file_name(cfg):
|
|
# Image file name format:
|
|
# <platform>_<product>_v<version>_c<anti-rollback counter>.img
|
|
img_file_name = cfg["image"]["info"]["platform"];
|
|
img_file_name += "_"
|
|
img_file_name += cfg["image"]["info"]["product"];
|
|
img_file_name += "_v"
|
|
img_file_name += cfg["image"]["info"]["version"];
|
|
if "anti-rollback" in cfg["image"]["info"]:
|
|
img_file_name += "_c"
|
|
img_file_name += cfg["image"]["info"]["anti-rollback"];
|
|
img_file_name += ".img"
|
|
return img_file_name.replace(" ", "_")
|
|
|
|
def calc_crc32(fname, size):
|
|
"""Calculate crc32 for a file
|
|
Args:
|
|
fname: file path
|
|
"""
|
|
hash = 0
|
|
step = 16 * 1024
|
|
if size > 0:
|
|
step = size
|
|
|
|
if os.path.exists(fname) == False:
|
|
return 0
|
|
|
|
with open(fname, 'rb') as fp:
|
|
while True:
|
|
s = fp.read(step)
|
|
if not s:
|
|
break
|
|
hash = zlib.crc32(s, hash)
|
|
if size > 0:
|
|
# only need to calc first 'size' byte
|
|
break
|
|
return hash & 0xffffffff
|
|
|
|
def str_to_nbytes(s, n):
|
|
""" String to n bytes
|
|
"""
|
|
ba = bytearray(s, encoding="utf-8")
|
|
nzero = n - len(ba)
|
|
if nzero > 0:
|
|
ba.extend([0] * nzero)
|
|
return bytes(ba)
|
|
|
|
def int_to_uint32_bytes(n):
|
|
""" Int value to uint32 bytes
|
|
"""
|
|
return n.to_bytes(4, byteorder='little', signed=False)
|
|
|
|
"""
|
|
struct artinchip_fw_hdr{
|
|
char magic[8];
|
|
char platform[64];
|
|
char product[64];
|
|
char version[64];
|
|
char media_type[64];
|
|
u32 media_dev_id;
|
|
u8 nand_array_org[64];/* NAND Array Organization */
|
|
u32 meta_offset; /* Meta Area start offset */
|
|
u32 meta_size; /* Meta Area size */
|
|
u32 file_offset; /* File data Area start offset */
|
|
u32 file_size; /* File data Area size */
|
|
};
|
|
"""
|
|
def img_write_fw_header(imgfile, cfg, meta_area_size, file_area_size):
|
|
""" Generate Firmware image's header data
|
|
Args:
|
|
cfg: Dict from JSON
|
|
meta_area_size: size of meta data area
|
|
file_area_size: size of file data area
|
|
"""
|
|
array_org_len = 64
|
|
nand_array_org = ""
|
|
if "array_organization" in cfg["image"]["info"]["media"]:
|
|
array_orgval = cfg["image"]["info"]["media"]["array_organization"]
|
|
if not isinstance(array_orgval, list):
|
|
print("Error, nand array organization should be a list.")
|
|
return -1
|
|
param_str = ""
|
|
for item in array_orgval:
|
|
param_str += "P={},B={};".format(item["page"].upper(), item["block"].upper())
|
|
param_str = param_str[0:-1]
|
|
nand_array_org = param_str
|
|
dev_id = 0
|
|
if "device_id" in cfg["image"]["info"]["media"]:
|
|
val = cfg["image"]["info"]["media"]["device_id"]
|
|
if isinstance(val, str):
|
|
dev_id = int(val)
|
|
else:
|
|
dev_id = val
|
|
|
|
magic = "AIC.FW"
|
|
platform = str(cfg["image"]["info"]["platform"])
|
|
product = str(cfg["image"]["info"]["product"])
|
|
version = str(cfg["image"]["info"]["version"])
|
|
media_type = str(cfg["image"]["info"]["media"]["type"])
|
|
media_dev_id = dev_id
|
|
meta_offset = DATA_ALIGNED_SIZE
|
|
meta_size = meta_area_size
|
|
file_offset = DATA_ALIGNED_SIZE + meta_area_size
|
|
file_size = file_area_size
|
|
|
|
buff = str_to_nbytes("AIC.FW", 8)
|
|
buff = buff + str_to_nbytes(platform, 64)
|
|
buff = buff + str_to_nbytes(product, 64)
|
|
buff = buff + str_to_nbytes(version, 64)
|
|
buff = buff + str_to_nbytes(media_type, 64)
|
|
buff = buff + int_to_uint32_bytes(media_dev_id)
|
|
buff = buff + str_to_nbytes(nand_array_org, 64)
|
|
buff = buff + int_to_uint32_bytes(meta_offset)
|
|
buff = buff + int_to_uint32_bytes(meta_size)
|
|
buff = buff + int_to_uint32_bytes(file_offset)
|
|
buff = buff + int_to_uint32_bytes(file_size)
|
|
imgfile.seek(0, 0)
|
|
imgfile.write(buff)
|
|
imgfile.flush()
|
|
if VERBOSE:
|
|
print("\tImage header is generated.")
|
|
return 0
|
|
|
|
"""
|
|
struct artinchip_fwc_meta {
|
|
char magic[8];
|
|
char name[64];
|
|
char partition[64];
|
|
u32 offset;
|
|
u32 size;
|
|
u32 crc;
|
|
u32 ram;
|
|
char attr[64]
|
|
};
|
|
"""
|
|
def img_gen_fwc_meta(name, part, offset, size, crc, ram, attr):
|
|
""" Generate Firmware component's meta data
|
|
Args:
|
|
cfg: Dict from JSON
|
|
datadir: working directory for image data
|
|
"""
|
|
buff = str_to_nbytes("META", 8)
|
|
buff = buff + str_to_nbytes(name, 64)
|
|
buff = buff + str_to_nbytes(part, 64)
|
|
buff = buff + int_to_uint32_bytes(offset)
|
|
buff = buff + int_to_uint32_bytes(size)
|
|
buff = buff + int_to_uint32_bytes(crc)
|
|
buff = buff + int_to_uint32_bytes(ram)
|
|
buff = buff + str_to_nbytes(attr, 64)
|
|
|
|
if VERBOSE:
|
|
print("\t\tMeta for {:<25} offset {:<10} size {} ({})".format(name,
|
|
hex(offset), hex(size), size))
|
|
return buff
|
|
|
|
def check_partition_exist(table, partval):
|
|
if isinstance(partval, list):
|
|
for item in partval:
|
|
if item.find(":") > 0: # UBI Volume
|
|
parts = item.split(":")
|
|
part = parts[0]
|
|
vol = parts[1]
|
|
if part not in table:
|
|
print("{} not in table {}".format(part, table))
|
|
return False
|
|
if vol not in table[part]["ubi"]:
|
|
print("{} not in ubi {}".format(vol, table[part]["ubi"]))
|
|
return False
|
|
else:
|
|
if item not in table:
|
|
print("{} not in table {}".format(partval, table))
|
|
return False
|
|
else:
|
|
if partval not in table:
|
|
print("{} not in table {}".format(partval, table))
|
|
return False
|
|
return True
|
|
|
|
def img_write_fwc_meta_section(imgfile, cfg, sect, meta_off, file_off, datadir):
|
|
fwcset = cfg["image"][sect]
|
|
media_type = cfg["image"]["info"]["media"]["type"]
|
|
|
|
if media_type not in cfg:
|
|
print("Cannot find partitions for {}".format(media_type))
|
|
return (-1, -1)
|
|
partitions = cfg[media_type]["partitions"]
|
|
for fwc in fwcset:
|
|
file_size = fwcset[fwc]["filesize"]
|
|
if file_size <= 0:
|
|
continue
|
|
imgfile.seek(meta_off, 0)
|
|
path = str(datadir + fwcset[fwc]["file"])
|
|
crc = calc_crc32(path, 0)
|
|
if "ram" in fwcset[fwc]:
|
|
ram = int(fwcset[fwc]["ram"], 16)
|
|
else:
|
|
ram = 0xFFFFFFFF
|
|
attrval = fwcset[fwc]["attr"]
|
|
if isinstance(attrval, list):
|
|
attr = str(";".join(attrval))
|
|
else:
|
|
attr = str(attrval)
|
|
attr = attr.replace(' ', '')
|
|
name = str("image." + sect + "." + fwc)
|
|
|
|
if "part" in fwcset[fwc]:
|
|
partval = fwcset[fwc]["part"]
|
|
if check_partition_exist(partitions, partval) == False:
|
|
print("Partition {} not exist".format(partval))
|
|
return (-1, -1)
|
|
if isinstance(partval, list):
|
|
part = str(";".join(partval))
|
|
else:
|
|
part = str(partval)
|
|
else:
|
|
part = ""
|
|
meta = img_gen_fwc_meta(name, part, file_off, file_size, crc, ram, attr)
|
|
imgfile.write(meta)
|
|
fwcset[fwc]["meta_off"] = meta_off
|
|
fwcset[fwc]["file_off"] = file_off
|
|
# Update for next item
|
|
meta_off += META_ALIGNED_SIZE
|
|
file_size = round_up(file_size, DATA_ALIGNED_SIZE)
|
|
file_off += file_size
|
|
return (meta_off, file_off)
|
|
|
|
def img_write_fwc_meta_to_imgfile(imgfile, cfg, meta_start, file_start, datadir):
|
|
""" Generate and write FW component's meta data
|
|
Args:
|
|
imgfile: Image file handle
|
|
cfg: Dict from JSON
|
|
meta_start: meta data area start offset
|
|
file_start: file data area start offset
|
|
datadir: working directory
|
|
"""
|
|
meta_offset = meta_start
|
|
file_offset = file_start
|
|
if VERBOSE:
|
|
print("\tMeta data for image components:")
|
|
# 1, FWC of updater
|
|
meta_offset, file_offset = img_write_fwc_meta_section(imgfile, cfg, "updater",
|
|
meta_offset, file_offset,
|
|
datadir)
|
|
if meta_offset < 0:
|
|
return -1
|
|
# 2, Image Info(The same with image header)
|
|
imgfile.seek(meta_offset, 0)
|
|
img_fn = datadir + img_gen_fw_file_name(cfg);
|
|
crc = calc_crc32(img_fn, DATA_ALIGNED_SIZE)
|
|
info_offset = 0 # Image info is the image header, start from 0
|
|
info_size = DATA_ALIGNED_SIZE
|
|
part = ""
|
|
name = "image.info"
|
|
ram = 0xFFFFFFFF
|
|
attr = "required"
|
|
meta = img_gen_fwc_meta(name, part, info_offset, info_size, crc, ram, attr)
|
|
imgfile.write(meta)
|
|
# Only meta offset increase
|
|
meta_offset += META_ALIGNED_SIZE
|
|
# 3, FWC of target
|
|
meta_offset, file_offset = img_write_fwc_meta_section(imgfile, cfg, "target",
|
|
meta_offset, file_offset,
|
|
datadir)
|
|
if meta_offset < 0:
|
|
return -1
|
|
imgfile.flush()
|
|
return 0
|
|
|
|
def img_write_fwc_file_to_imgfile(imgfile, cfg, file_start, datadir):
|
|
""" Write FW component's file data
|
|
Args:
|
|
imgfile: Image file handle
|
|
cfg: Dict from JSON
|
|
file_start: file data area start offset
|
|
datadir: working directory
|
|
"""
|
|
file_offset = file_start
|
|
if VERBOSE:
|
|
print("\tPacking file data:")
|
|
for section in ["updater", "target"]:
|
|
fwcset = cfg["image"][section]
|
|
for fwc in fwcset:
|
|
path = get_file_path(fwcset[fwc]["file"], datadir)
|
|
if path == None:
|
|
continue
|
|
if VERBOSE:
|
|
print("\t\t" + os.path.split(path)[1])
|
|
# Read fwc file content, and write to image file
|
|
imgfile.seek(file_offset, 0)
|
|
step = 16 * 1024
|
|
with open(path, "rb") as fwcfile:
|
|
while True:
|
|
bindata = fwcfile.read(step)
|
|
if not bindata:
|
|
break
|
|
imgfile.write(bindata)
|
|
# Update for next file
|
|
filesize = fwcset[fwc]["filesize"]
|
|
filesize = round_up(filesize, DATA_ALIGNED_SIZE)
|
|
file_offset += filesize
|
|
imgfile.flush()
|
|
return 0
|
|
|
|
def img_get_fwc_file_size(cfg, datadir):
|
|
""" Scan directory and get Firmware component's file size, update to cfg
|
|
Args:
|
|
cfg: Dict from JSON
|
|
datadir: working directory for image data
|
|
"""
|
|
for section in ["updater", "target"]:
|
|
fwcset = cfg["image"][section]
|
|
for fwc in fwcset:
|
|
path = get_file_path(fwcset[fwc]["file"], datadir)
|
|
if path == None:
|
|
attr = fwcset[fwc]["attr"]
|
|
if "required" in attr:
|
|
print("Error, file {} is not exist".format(fwcset[fwc]["file"]))
|
|
return -1
|
|
else:
|
|
# FWC file is not exist, but it is not necessary
|
|
fwcset[fwc]["filesize"] = 0
|
|
continue
|
|
statinfo = os.stat(path)
|
|
fwcset[fwc]["filesize"] = statinfo.st_size
|
|
return 0
|
|
|
|
def round_up(x, y):
|
|
return int((x + y - 1) / y) * y
|
|
|
|
def aic_create_parts_for_env(cfg):
|
|
mtd = ""
|
|
ubi = ""
|
|
gpt = ""
|
|
|
|
part_str = ""
|
|
media_type = cfg["image"]["info"]["media"]["type"]
|
|
if media_type == "spi-nand" or media_type == "spi-nor":
|
|
partitions = cfg[media_type]["partitions"]
|
|
mtd = "spi{}.0:".format(cfg["image"]["info"]["media"]["device_id"])
|
|
if len(partitions) == 0:
|
|
print("Partition table is empty")
|
|
sys.exit(1)
|
|
for part in partitions:
|
|
itemstr = ""
|
|
if "size" not in partitions[part]:
|
|
print("No size value for partition: {}".format(part))
|
|
itemstr += partitions[part]["size"]
|
|
if "offset" in partitions[part]:
|
|
itemstr += "@{}".format(partitions[part]["offset"])
|
|
itemstr += "({})".format(part)
|
|
mtd += itemstr + ","
|
|
if "ubi" in partitions[part]:
|
|
volumes = partitions[part]["ubi"]
|
|
if len(volumes) == 0:
|
|
print("Volume of {} is empty".format(part))
|
|
sys.exit(1)
|
|
ubi += "{}:".format(part)
|
|
for vol in volumes:
|
|
itemstr = ""
|
|
if "size" not in volumes[vol]:
|
|
print("No size value for ubi volume: {}".format(vol))
|
|
itemstr += volumes[vol]["size"]
|
|
if "offset" in volumes[vol]:
|
|
itemstr += "@{}".format(volumes[vol]["offset"])
|
|
itemstr += "({})".format(vol)
|
|
ubi += itemstr + ","
|
|
ubi = ubi[0:-1] + ";"
|
|
mtd = mtd[0:-1]
|
|
part_str = "MTD={}\n".format(mtd)
|
|
if len(ubi) > 0:
|
|
ubi = ubi[0:-1]
|
|
part_str += "UBI={}\n".format(ubi)
|
|
elif media_type == "mmc":
|
|
partitions = cfg[media_type]["partitions"]
|
|
if len(partitions) == 0:
|
|
print("Partition table is empty")
|
|
sys.exit(1)
|
|
for part in partitions:
|
|
itemstr = ""
|
|
if "size" not in partitions[part]:
|
|
print("No size value for partition: {}".format(part))
|
|
itemstr += partitions[part]["size"]
|
|
if "offset" in partitions[part]:
|
|
itemstr += "@{}".format(partitions[part]["offset"])
|
|
itemstr += "({})".format(part)
|
|
gpt += itemstr + ","
|
|
gpt = gpt[0:-1]
|
|
part_str = "GPT={}\nparts_mmc={}\n".format(gpt, gpt)
|
|
# parts_mmc will be deleted later, keep it just for old version AiBurn tool
|
|
else:
|
|
print("Not supported media type: {}".format(media_type))
|
|
sys.exit(1)
|
|
|
|
return part_str
|
|
|
|
def uboot_env_create_image(srcfile, outfile, size, part_str):
|
|
tmpfile = srcfile + ".part.tmp"
|
|
fs = open(srcfile, "r+")
|
|
envstr = fs.readlines()
|
|
fs.close()
|
|
fp = open(tmpfile, "w+")
|
|
fp.write(part_str)
|
|
fp.writelines(envstr)
|
|
fp.close()
|
|
|
|
cmd = ["mkenvimage", "-s", str(size), "-o", outfile, tmpfile]
|
|
ret = subprocess.run(cmd, subprocess.PIPE)
|
|
if ret.returncode != 0:
|
|
sys.exit(1)
|
|
|
|
def firmware_component_preproc(cfg, datadir, keydir):
|
|
""" Perform firmware component pre-process
|
|
Args:
|
|
cfg: Dict from JSON
|
|
datadir: working directory for image data
|
|
keydir: key material directory for image data
|
|
"""
|
|
|
|
preproc_cfg = cfg["temporary"]
|
|
if "itb" in preproc_cfg:
|
|
# Need to generate FIT image
|
|
imgnames = preproc_cfg["itb"].keys()
|
|
for itbname in imgnames:
|
|
itsname = preproc_cfg["itb"][itbname]["its"]
|
|
outfile = datadir + itbname
|
|
dtbfile = None
|
|
keypath = None
|
|
|
|
if VERBOSE:
|
|
print("\tCreating {} ...".format(outfile))
|
|
srcfile = get_file_path(itsname, datadir)
|
|
if srcfile == None:
|
|
print("File {} is not exist".format(itsname))
|
|
sys.exit(1)
|
|
if "dtb" in preproc_cfg["itb"][itbname].keys():
|
|
dtbname = preproc_cfg["itb"][itbname]["dtb"]
|
|
dtbfile = get_file_path(dtbname, datadir)
|
|
if dtbfile == None:
|
|
print("File {} is not exist".format(dtbname))
|
|
sys.exit(1)
|
|
if "keydir" in preproc_cfg["itb"][itbname].keys():
|
|
keydir = preproc_cfg["itb"][itbname]["keydir"]
|
|
keypath = get_file_path(keydir, datadir)
|
|
if keypath == None:
|
|
print("File {} is not exist".format(keydir))
|
|
|
|
itb_create_image(srcfile, outfile, keypath, dtbfile)
|
|
|
|
# Generate a spl image with spl dtb file
|
|
if "bin" in preproc_cfg["itb"][itbname].keys():
|
|
srcbin = preproc_cfg["itb"][itbname]["bin"]["src"]
|
|
dstbin = preproc_cfg["itb"][itbname]["bin"]["dst"]
|
|
srcfile = get_file_path(srcbin, datadir)
|
|
if srcfile == None:
|
|
print("File {} is not exist".format(srcbin))
|
|
sys.exit(1)
|
|
dstfile = get_file_path(dstbin, datadir)
|
|
if dstfile == None:
|
|
print("File {} is not exist".format(dstbin))
|
|
sys.exit(1)
|
|
cmd = ["cat {} {} > {}".format(srcfile, dtbfile, dstfile)]
|
|
print (cmd)
|
|
ret = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE)
|
|
if ret.returncode != 0:
|
|
sys.exit(1)
|
|
if "uboot_env" in preproc_cfg:
|
|
# Need to generate uboot env bin
|
|
imgnames = preproc_cfg["uboot_env"].keys()
|
|
part_str = aic_create_parts_for_env(cfg)
|
|
for binname in imgnames:
|
|
envfile = preproc_cfg["uboot_env"][binname]["file"]
|
|
envsize = preproc_cfg["uboot_env"][binname]["size"]
|
|
outfile = datadir + binname
|
|
if VERBOSE:
|
|
print("\tCreating {} ...".format(outfile))
|
|
srcfile = get_file_path(envfile, datadir)
|
|
if srcfile == None:
|
|
print("File {} is not exist".format(envfile))
|
|
sys.exit(1)
|
|
uboot_env_create_image(srcfile, outfile, envsize, part_str)
|
|
if "aicboot" in preproc_cfg:
|
|
# Need to generate aicboot image
|
|
imgnames = preproc_cfg["aicboot"].keys()
|
|
for name in imgnames:
|
|
imgcfg = preproc_cfg["aicboot"][name]
|
|
imgcfg["keydir"] = keydir
|
|
imgcfg["datadir"] = datadir
|
|
outname = datadir + name
|
|
if VERBOSE:
|
|
print("\tCreating {} ...".format(outname))
|
|
imgbytes = aic_boot_create_image(imgcfg, keydir, datadir)
|
|
|
|
if check_loader_run_in_dram(imgcfg):
|
|
extimgbytes = aic_boot_create_ext_image(imgcfg, keydir, datadir)
|
|
padlen = round_up(len(imgbytes), META_ALIGNED_SIZE) - len(imgbytes)
|
|
if padlen > 0:
|
|
imgbytes += bytearray(padlen)
|
|
imgbytes += extimgbytes
|
|
# For Debug
|
|
# with open(outname + ".ext", "wb") as f:
|
|
# f.write(extimgbytes)
|
|
|
|
with open(outname, "wb") as f:
|
|
f.write(imgbytes)
|
|
|
|
|
|
def generate_bootcfg(bcfgfile, cfg):
|
|
comments = ["# Boot configuration file\n",
|
|
"# Used in SD Card FAT32 boot and USB Disk upgrade.\n",
|
|
"# Format:\n",
|
|
"# protection=part1 name,part2 name,part3 name\n",
|
|
"# Protects partitions from being overwritten when they are upgraded.\n"
|
|
"# boot0=size@offset\n",
|
|
"# boot0 size and location offset in 'image' file, boot rom read it.\n"
|
|
"# boot0=example.bin\n",
|
|
"# boot0 image is file example.bin, boot rom read it.\n"
|
|
"# boot1=size@offset\n",
|
|
"# boot1 size and location offset in 'image' file, boot0 read it.\n"
|
|
"# boot1=example.bin\n",
|
|
"# boot1 image is file example.bin, boot0 read it.\n"
|
|
"# image=example.img\n",
|
|
"# Packed image file is example.img, boot1 use it.\n",
|
|
"\n\n",
|
|
]
|
|
bcfgfile.writelines(comments)
|
|
|
|
fwcset = cfg["image"]["updater"]
|
|
fwckeys = cfg["image"]["updater"].keys()
|
|
if "spl" in fwckeys:
|
|
fwcname = "spl"
|
|
linestr = "# {}\n".format(fwcset[fwcname]["file"])
|
|
bcfgfile.write(linestr)
|
|
linestr = "boot0={}@{}\n".format(hex(fwcset[fwcname]["filesize"]),
|
|
hex(fwcset[fwcname]["file_off"]))
|
|
bcfgfile.write(linestr)
|
|
|
|
if "uboot" in fwckeys:
|
|
fwcname = "uboot"
|
|
linestr = "# {}\n".format(fwcset[fwcname]["file"])
|
|
bcfgfile.write(linestr)
|
|
linestr = "boot1={}@{}\n".format(hex(fwcset[fwcname]["filesize"]),
|
|
hex(fwcset[fwcname]["file_off"]))
|
|
bcfgfile.write(linestr)
|
|
|
|
if "env" in fwckeys:
|
|
fwcname = "env"
|
|
linestr = "# {}\n".format(fwcset[fwcname]["file"])
|
|
bcfgfile.write(linestr)
|
|
linestr = "env={}@{}\n".format(hex(fwcset[fwcname]["filesize"]),
|
|
hex(fwcset[fwcname]["file_off"]))
|
|
bcfgfile.write(linestr)
|
|
|
|
imgfn = img_gen_fw_file_name(cfg)
|
|
linestr = "image={}\n".format(imgfn)
|
|
bcfgfile.write(linestr)
|
|
bcfgfile.flush()
|
|
|
|
def get_spinand_image_list(cfg, datadir):
|
|
imglist = []
|
|
orglist = cfg["image"]["info"]["media"]["array_organization"]
|
|
for item in orglist:
|
|
paramstr = "_page_{}_block_{}".format(item["page"], item["block"])
|
|
paramstr = paramstr.lower()
|
|
status_ok = True
|
|
for fwcname in cfg["image"]["target"]:
|
|
if "ubi" not in cfg["image"]["target"][fwcname]["attr"]:
|
|
# Not UBI partition
|
|
continue
|
|
# UBI component
|
|
filepath = cfg["image"]["target"][fwcname]["file"]
|
|
if filepath.find("*") <= 0:
|
|
# No need to check
|
|
continue
|
|
filepath = filepath.replace("*", paramstr)
|
|
filepath = get_file_path(filepath, datadir)
|
|
if filepath == None and "optional" not in cfg["image"]["target"][fwcname]["attr"]:
|
|
# FWC file not exist
|
|
status_ok = False
|
|
print("{} is not found".format(cfg["image"]["target"][fwcname]["file"]))
|
|
break
|
|
backup = cfg["image"]["target"][fwcname]["file"]
|
|
# Backup the original file path string, because it will be modified
|
|
# when generating image
|
|
cfg["image"]["target"][fwcname]["file.backup"] = backup
|
|
if status_ok:
|
|
imglist.append(paramstr)
|
|
backup = cfg["image"]["info"]["product"]
|
|
cfg["image"]["info"]["product.backup"] = backup
|
|
backup = cfg["image"]["bootcfg"]
|
|
cfg["image"]["bootcfg.backup"] = backup
|
|
return imglist
|
|
|
|
def fixup_spinand_ubi_fwc_name(cfg, paramstr):
|
|
for fwcname in cfg["image"]["target"]:
|
|
if "ubi" not in cfg["image"]["target"][fwcname]["attr"]:
|
|
# Not UBI partition
|
|
continue
|
|
# UBI component
|
|
filepath = cfg["image"]["target"][fwcname]["file.backup"]
|
|
if filepath.find("*") <= 0:
|
|
# No need to fixup
|
|
continue
|
|
cfg["image"]["target"][fwcname]["file"] = filepath.replace("*", paramstr)
|
|
# fixup others
|
|
backup = cfg["image"]["info"]["product.backup"]
|
|
cfg["image"]["info"]["product"] = backup + paramstr
|
|
backup = cfg["image"]["bootcfg.backup"]
|
|
cfg["image"]["bootcfg"] = "{}({})".format(backup, paramstr[1:])
|
|
|
|
def build_firmware_image(cfg, datadir):
|
|
""" Build firmware image
|
|
Args:
|
|
cfg: Dict from JSON
|
|
datadir: working directory for image data
|
|
"""
|
|
# Step1: Get all FWC file's size
|
|
ret = img_get_fwc_file_size(cfg, datadir)
|
|
if ret != 0:
|
|
return ret
|
|
|
|
# Step2: Calculate Meta Area's size, one FWC use DATA_ALIGNED_SIZE bytes
|
|
meta_area_size = 0
|
|
for s in ["updater", "target"]:
|
|
fwcset = cfg["image"][s]
|
|
for fwc in fwcset:
|
|
if fwcset[fwc]["filesize"] > 0:
|
|
meta_area_size += META_ALIGNED_SIZE
|
|
# Image header is also one FWC, it need one FWC Meta
|
|
meta_area_size += META_ALIGNED_SIZE
|
|
|
|
# Step3: Calculate the size of FWC File Data Area
|
|
file_area_size = 0
|
|
for s in ["updater", "target"]:
|
|
if s in cfg["image"] == False:
|
|
return -1
|
|
for fwc in cfg["image"][s]:
|
|
if "filesize" in cfg["image"][s][fwc] == False:
|
|
return -1
|
|
filesize = cfg["image"][s][fwc]["filesize"]
|
|
if filesize > 0:
|
|
filesize = round_up(filesize, DATA_ALIGNED_SIZE)
|
|
file_area_size += filesize
|
|
|
|
# Step4: Create empty image file
|
|
img_fn = datadir + img_gen_fw_file_name(cfg)
|
|
img_total_size = DATA_ALIGNED_SIZE # Header
|
|
img_total_size += meta_area_size
|
|
img_total_size += file_area_size
|
|
with open(img_fn, 'wb') as imgfile:
|
|
imgfile.truncate(img_total_size)
|
|
# Step5: Write header
|
|
ret = img_write_fw_header(imgfile, cfg, meta_area_size, file_area_size)
|
|
if ret != 0:
|
|
return ret
|
|
# Step6: Write FW Component meta to image
|
|
meta_start = DATA_ALIGNED_SIZE
|
|
file_start = meta_start + meta_area_size
|
|
ret = img_write_fwc_meta_to_imgfile(imgfile, cfg, meta_start,
|
|
file_start, datadir)
|
|
if ret != 0:
|
|
return ret
|
|
# Step7: Write FW Component file data to image
|
|
ret = img_write_fwc_file_to_imgfile(imgfile, cfg, file_start, datadir)
|
|
if ret != 0:
|
|
return ret
|
|
imgfile.flush()
|
|
|
|
abspath = "{}".format(img_fn)
|
|
(img_path, img_name) = os.path.split(abspath)
|
|
if VERBOSE:
|
|
print("\tImage file is generated: {}/{}\n\n".format(img_path, img_name))
|
|
|
|
bootcfg_fn = datadir + cfg["image"]["bootcfg"]
|
|
with open(bootcfg_fn, 'w') as bcfgfile:
|
|
generate_bootcfg(bcfgfile, cfg)
|
|
bcfgfile.flush()
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-c", "--config", type=str,
|
|
help="image configuration file name")
|
|
parser.add_argument("-d", "--datadir", type=str,
|
|
help="input image data directory")
|
|
parser.add_argument("-k", "--keydir", type=str,
|
|
help="key material directory")
|
|
parser.add_argument("-v", "--verbose", action='store_true',
|
|
help="show detail information")
|
|
args = parser.parse_args()
|
|
if args.config == None:
|
|
print('Error, option --config is required.')
|
|
sys.exit(1)
|
|
# If user not specified data directory, use current directory as default
|
|
if args.datadir == None:
|
|
args.datadir = './'
|
|
if args.datadir.endswith('/') == False:
|
|
args.datadir = args.datadir + '/'
|
|
if args.keydir == None:
|
|
args.keydir = args.datadir
|
|
if args.keydir.endswith('/') == False:
|
|
args.keydir = args.keydir + '/'
|
|
if args.verbose:
|
|
VERBOSE = True
|
|
|
|
cfg = parse_image_cfg(args.config)
|
|
# Pre-process here, e.g: signature, encryption, ...
|
|
if "temporary" in cfg:
|
|
firmware_component_preproc(cfg, args.datadir, args.keydir)
|
|
|
|
cfg["image"]["bootcfg"] = "bootcfg.txt"
|
|
# Finally build the firmware image
|
|
imglist = []
|
|
if cfg["image"]["info"]["media"]["type"] == "spi-nand":
|
|
imglist = get_spinand_image_list(cfg, args.datadir)
|
|
if len(imglist) > 0:
|
|
# SPI-NAND UBI case
|
|
for item in imglist:
|
|
# fixup file path
|
|
fixup_spinand_ubi_fwc_name(cfg, item)
|
|
ret = build_firmware_image(cfg, args.datadir)
|
|
if ret != 0:
|
|
sys.exit(1)
|
|
else:
|
|
# Just create image, no need to fixup anything
|
|
ret = build_firmware_image(cfg, args.datadir)
|
|
if ret != 0:
|
|
sys.exit(1)
|
|
|