blob: 04b653d37f60818afac83cbbc09948f4e1210a93 [file] [log] [blame] [edit]
#!/usr/bin/env python3
#
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: GPL-2.0-only
#
"""
Generate a C header file to standard output with a preprocessor symbol
definition of the physical load address in hexadecimal of the ELF-loader in
`payload_filename`. The address is calculated using the description of memory
in `platform_filename` and the CPIO archive members embedded in the payload
file, including the loadable segments of the ELF objects and a possible DTB
(device tree binary) file. The ELF-loader is placed in the first (lowest)
sufficiently-large memory region.
THIS IS NOT A STABLE API. Use as a script, not a module.
"""
import argparse
import io
import os.path
import re
import sys
import libarchive
import elf_sift
import platform_sift
do_debug = False
program_name = 'shoehorn'
def write(message: str):
"""
Write diagnostic `message` to standard error.
"""
sys.stderr.write('{}: {}\n'.format(program_name, message))
def debug(message: str):
"""
Emit debugging diagnostic `message`.
"""
if do_debug:
write('debug: {}'.format(message))
def debug_marker_set(mark: int, obj: str):
debug('setting marker to 0x{:x} ({})'.format(mark, obj))
def die(message: str, status: int = 3):
"""
Emit fatal diagnostic `message` and exit with `status` (3 if not specified).
"""
write('fatal error: {}'.format(message))
sys.exit(status)
def notice(message: str):
"""
Emit notification diagnostic `message`.
"""
write('notice: {}'.format(message))
def warn(message: str):
"""
Emit warning diagnostic `message`.
"""
write('warning: {}'.format(message))
def get_bytes(entry: libarchive.entry.ArchiveEntry) -> io.BytesIO:
"""
Return an io.BytesIO object with the contents of the given archive entry.
"""
bytes = bytearray()
bytes.extend([byte for block in entry.get_blocks() for byte in block])
return io.BytesIO(bytes)
def get_cpio(payload_filename: str) -> io.BytesIO:
"""
Return an io.BytesIO object containing the CPIO part of `payload_filename`.
The payload file is a CPIO archive with an object file header (e.g., an ELF
prologue) prepended. The embedded CPIO archive file is expected to be of
the format the `file` command calls an "ASCII cpio archive (SVR4 with no
CRC)".
We assume that the CPIO "magic number" is not a valid sequence inside the
object file header of `payload_filename`.
"""
cpio_magic = b'070701'
with open(payload_filename, 'rb') as payload:
match = re.search(cpio_magic, payload.read())
if match:
debug('found CPIO identifying sequence {} at offset 0x{:x} in {}'
.format(cpio_magic, match.start(), payload_filename))
payload.seek(match.start())
cpio_bytes = payload.read()
else:
warn('did not find the CPIO identifying sequence {} expected in {}'
.format(cpio_magic, header_size, payload_filename))
cpio_bytes = None
return cpio_bytes
def main() -> int:
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""
Generate a C header file to standard output with a preprocessor symbol
definition of the physical load address in hexadecimal of the ELF-loader in
`payload_filename`. The address is calculated using the description of memory
in `platform_filename` and the CPIO archive members embedded in the payload
file, including the loadable segments of the ELF objects and a possible DTB
(device tree binary) file. The ELF-loader is placed in the first (lowest)
sufficiently-large memory region.
""")
parser.add_argument('--load-rootservers-high', dest='load_rootservers_high',
default=False, action='store_true',
help='assume ELF-loader will put rootservers at top of'
' memory')
parser.add_argument('platform_filename', nargs=1, type=str,
help='YAML description of platform parameters (e.g.,'
' platform_gen.yaml)')
parser.add_argument('payload_filename', nargs=1, type=str,
help='ELF-loader image file (e.g., archive.o)')
# Set up some simpler names for argument data and derived information.
args = parser.parse_args()
image = args.payload_filename[0]
image_size = os.path.getsize(image)
do_load_rootservers_high = args.load_rootservers_high
platform = platform_sift.load_data(args.platform_filename[0])
rootservers = []
is_dtb_present = False
is_good_fit = False
with libarchive.memory_reader(get_cpio(image)) as archive:
for entry in archive:
name = entry.name
debug('encountered CPIO entry name: {}'.format(name))
if name == 'kernel.elf':
kernel_elf = get_bytes(entry)
elif name == 'kernel.dtb':
# The ELF-loader loads the entire DTB into memory.
is_dtb_present = True
dtb_size = entry.size
elif name.endswith('.bin'):
# Skip checksum entries.
notice('skipping checkum entry "{}"'.format(name))
else:
rootservers.append(get_bytes(entry))
# Enumerate the regions as we encounter them for diagnostic purposes.
region_counter = -1
last_region = len(platform['memory'])
for region in platform['memory']:
region_counter += 1
marker = region['start']
debug_marker_set(marker, 'region {} start'.format(region_counter))
# Note: We assume that the kernel is loaded at the start of memory
# because that is what elfloader-tool/src/arch-arm/linker.lds ensures
# will happen. This assumption may change in the future!
kernel_start = region['start']
kernel_size = elf_sift.get_memory_usage(kernel_elf, align=True)
kernel_end = elf_sift.get_aligned_size(kernel_start + kernel_size)
marker = kernel_end
debug_marker_set(marker, 'kernel_end')
if is_dtb_present:
dtb_start = marker
dtb_end = elf_sift.get_aligned_size(dtb_start + dtb_size)
marker = dtb_end
debug_marker_set(marker, 'dtb_end')
if do_load_rootservers_high and (region_counter == last_region):
warn('"--load-rootservers-high" specified but placing'
' ELF-loader in last (or only) region ({} of {}); overlap'
' may not be detected by this tool'
.format(region_counter, last_region))
# Deal with the 1..(# of CPUs - 1) possible user payloads, if we're not
# loading them in high memory, discontiguously with the kernel.
#
# TODO: Handle this differently (skipping, or checking to see if we had
# to push the kernel so high that it whacks the user payloads--but the
# ELF-loader itself should detect that case). At present the case of
# multiple rootservers is difficult to debug because it is not
# implemented on the archive-construction side; see JIRA SELFOUR-2368.
if not do_load_rootservers_high:
for elf in rootservers:
marker += elf_sift.get_memory_usage(elf, align=True)
debug_marker_set(marker, 'end of rootserver')
image_start_address = marker
if (image_start_address + image_size) <= region['end']:
is_good_fit = True
break
if not is_good_fit:
die('ELF-loader image "{image}" of size 0x{size:x} does not fit within'
' any memory region described in "{yaml}"'
.format(image=image, size=image_size, yaml=platform), status=1)
sys.stdout.write('#define IMAGE_START_ADDR 0x{load:x}\n'
.format(load=image_start_address))
return 0
if __name__ == '__main__':
sys.exit(main())