Files
2026-10-23 01:17:30 -05:00

209 lines
6.9 KiB
Python

import os
import hashlib
import base64
import opendbc
import subprocess
import json
from pathlib import Path
PREFIX = "arm-none-eabi-"
BUILDER = "DEV"
common_flags = []
if os.getenv("RELEASE"):
BUILD_TYPE = "RELEASE"
cert_fn = os.getenv("CERT")
assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable'
assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path'
else:
BUILD_TYPE = "DEBUG"
cert_fn = File("./board/crypto/certs/debug").srcnode().relpath
common_flags += ["-DALLOW_DEBUG"]
def objcopy(source, target, env, for_signature):
return '$OBJCOPY -O binary %s %s' % (source[0], target[0])
def get_version(builder, build_type):
try:
git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip()
source_root = Path(subprocess.check_output(["git", "rev-parse", "--show-toplevel"], encoding='utf8').strip())
except subprocess.CalledProcessError:
return f"{builder}-unknown-{build_type}"
manifest = source_root / "upstream-sync.json"
if manifest.is_file():
dependency = next(d for d in json.loads(manifest.read_text())["dependencies"] if d["path"] == "panda")
# The source commit identifies local firmware changes; P identifies its upstream base.
git += f"-P{dependency['commit'][:8]}"
return f"{builder}-{git}-{build_type}"
def get_key_header(name):
public_fn = File(f'./board/crypto/certs/{name}.pub').srcnode().get_path()
with open(public_fn, "rb") as f:
key = base64.b64decode(f.read().split()[1])
values = []
for _ in range(3):
length = int.from_bytes(key[:4], "big")
values.append(key[4:4 + length])
key = key[4 + length:]
_, e, n = values
e, n = int.from_bytes(e, "big"), int.from_bytes(n, "big")
assert n.bit_length() == 1024
rr = pow(2**1024, 2, n)
n0inv = 2**32 - pow(n, -1, 2**32)
r = [
f"RSAPublicKey {name}_rsa_key = {{",
f" .len = 0x20,",
f" .n0inv = {n0inv}U,",
f" .n = {to_c_uint32(n)},",
f" .rr = {to_c_uint32(rr)},",
f" .exponent = {e},",
f"}};",
]
return r
def to_c_uint32(x):
nums = []
for _ in range(0x20):
nums.append(x % (2**32))
x //= (2**32)
return "{" + 'U,'.join(map(str, nums)) + "U}"
def build_project(project_name, project, main, extra_flags):
project_dir = Dir(f'./board/obj/{project_name}/')
flags = project["FLAGS"] + extra_flags + common_flags + [
"-Wall",
"-Wextra",
"-Wstrict-prototypes",
"-Werror",
"-mlittle-endian",
"-mthumb",
"-nostdlib",
"-fno-builtin",
"-std=gnu11",
"-fmax-errors=1",
f"-T{File(project['LINKER_SCRIPT']).srcnode().relpath}",
"-fsingle-precision-constant",
"-Os",
"-g",
]
env = Environment(
ENV=os.environ,
CC=PREFIX + 'gcc',
AS=PREFIX + 'gcc',
OBJCOPY=PREFIX + 'objcopy',
OBJDUMP=PREFIX + 'objdump',
OBJPREFIX=project_dir,
CFLAGS=flags,
ASFLAGS=flags,
LINKFLAGS=flags,
CPPPATH=[Dir("./"), project["INCLUDE_PATH"], opendbc.INCLUDE_PATH],
ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES",
BUILDERS={
'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf')
},
tools=["default", "compilation_db"],
)
startup = env.Object(project["STARTUP_FILE"])
# Build bootstub
bs_env = env.Clone()
bs_env.Append(CFLAGS="-DBOOTSTUB", ASFLAGS="-DBOOTSTUB", LINKFLAGS="-DBOOTSTUB")
bs_elf = bs_env.Program(f"{project_dir}/bootstub.elf", [
startup,
"./board/crypto/rsa.c",
"./board/crypto/sha.c",
"./board/bootstub.c",
])
bs_bin = bs_env.Objcopy(f"./board/obj/bootstub.{project_name}.bin", bs_elf)
# Bootstubs are data images; objcopy must not inherit the ELF executable mode.
bs_env.AddPostAction(bs_bin, Chmod("$TARGET", 0o644))
# Build + sign main (aka app)
main_elf = env.Program(f"{project_dir}/main.elf", [
startup,
main
], LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags)
main_bin = env.Objcopy(f"{project_dir}/main.bin", main_elf)
sign_py = File(f"./board/crypto/sign.py").srcnode().relpath
env.Command(f"./board/obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
base_project_f4 = {
"STARTUP_FILE": "./board/stm32f4/startup_stm32f413xx.s",
"LINKER_SCRIPT": "./board/stm32f4/stm32f4_flash.ld",
"APP_START_ADDRESS": "0x8004000",
"INCLUDE_PATH": "./board/stm32f4/inc",
"FLAGS": ["-mcpu=cortex-m4", "-mhard-float", "-DSTM32F4", "-DSTM32F413xx", "-mfpu=fpv4-sp-d16"],
}
base_project_h7 = {
"STARTUP_FILE": "./board/stm32h7/startup_stm32h7x5xx.s",
"INCLUDE_PATH": "./board/stm32h7/inc",
"LINKER_SCRIPT": "./board/stm32h7/stm32h7x5_flash.ld",
"APP_START_ADDRESS": "0x8020000",
"FLAGS": [
"-mcpu=cortex-m7",
"-mhard-float",
"-DSTM32H7",
"-DSTM32H725xx",
"-Iboard/stm32h7/inc",
"-mfpu=fpv5-d16",
],
}
# Common autogenerated includes
version = get_version(BUILDER, BUILD_TYPE)
gitversion_header = ("#pragma once\n\n" +
f'extern const uint8_t gitversion[{len(version)+1}];\n' +
f'const uint8_t gitversion[{len(version)+1}] = "{version}";\n')
certs = [get_key_header(n) for n in ["debug", "release"]]
cert_header = "#pragma once\n\n" + "".join("\n".join(cert) + "\n" for cert in certs)
def write_generated(target, source, env):
Path(target[0].abspath).write_text(str(source[0]))
return 0
generation_env = Environment(tools=[])
for name, payload in (("gitversion.h", gitversion_header), ("version", version), ("cert.h", cert_header)):
generation_env.Command(File(f"./board/obj/{name}"), Value(payload), write_generated)
# Packet version defines: SHA hash of the struct header files
def version_hash(path):
with open(path, "rb") as f:
# Normalize line endings on Windows
return int.from_bytes(hashlib.sha256(f.read().replace(b'\r', b'')).digest()[:4], 'little')
hh, ch = version_hash("board/health.h"), version_hash(os.path.join(opendbc.INCLUDE_PATH, "opendbc/safety/can.h"))
common_flags += [f"-DHEALTH_PACKET_VERSION=0x{hh:08X}U", f"-DCAN_PACKET_VERSION_HASH=0x{ch:08X}U"]
# panda fw
build_project("panda", base_project_f4, "./board/main.c", [])
build_project("panda_h7", base_project_h7, "./board/main.c", [])
for variant, flags in (
("remote", ["-DPANDA_GM_REMOTE_START_C9"]),
("hkg_remote", ["-DPANDA_HKG_REMOTE_START"]),
("tesla_wake", ["-DPANDA_TESLA_WAKE_ON_CAN"]),
("can_ignition_only", ["-DPANDA_IGNORE_IGNITION_LINE"]),
("remote_can_ignition_only", ["-DPANDA_GM_REMOTE_START_C9", "-DPANDA_IGNORE_IGNITION_LINE"]),
("hkg_remote_can_ignition_only", ["-DPANDA_HKG_REMOTE_START", "-DPANDA_IGNORE_IGNITION_LINE"]),
("tesla_wake_can_ignition_only", ["-DPANDA_TESLA_WAKE_ON_CAN", "-DPANDA_IGNORE_IGNITION_LINE"]),
):
build_project(f"panda_{variant}", base_project_f4, "./board/main.c", flags)
build_project(f"panda_h7_{variant}", base_project_h7, "./board/main.c", flags)
# body fw
build_project("body_h7", base_project_h7, "./board/body/main.c", ["-DPANDA_BODY"])
# test files
SConscript('tests/libpanda/SConscript')