diff --git a/.github/workflows/kernel.yml b/.github/workflows/kernel.yml new file mode 100644 index 000000000..adcae1034 --- /dev/null +++ b/.github/workflows/kernel.yml @@ -0,0 +1,28 @@ +name: KBuild + +on: + workflow_dispatch: + +jobs: + kb: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Clean + uses: rokibhasansagar/slimhub_actions@main + - name: Install tools + run: | + sudo apt-get update -y &>/dev/null || sudo apt-get update -y &>/dev/null || true + sudo apt-get upgrade -y &>/dev/null || sudo apt-get upgrade -y &>/dev/null || true + sudo apt-get install brotli zip zstd tar lz4 cpio xz-utils -y || sudo apt-get install brotli zip zstd tar lz4 cpio xz-utils -y + - name: Build + run: | + curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash - + bash build.sh + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: Kernels_a53x + path: kernel_build/ExynosUnbound* diff --git a/arch/arm64/configs/a53x_defconfig b/arch/arm64/configs/a53x_defconfig index 00d3b2923..8cd58f68f 100755 --- a/arch/arm64/configs/a53x_defconfig +++ b/arch/arm64/configs/a53x_defconfig @@ -813,9 +813,9 @@ CONFIG_HAVE_GCC_PLUGINS=y CONFIG_RT_MUTEXES=y CONFIG_BASE_SMALL=0 CONFIG_MODULES=y -CONFIG_MODULE_FORCE_LOAD=y +# CONFIG_MODULE_FORCE_LOAD is not set CONFIG_MODULE_UNLOAD=y -CONFIG_MODULE_FORCE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set CONFIG_MODVERSIONS=y CONFIG_ASM_MODVERSIONS=y # CONFIG_MODULE_SRCVERSION_ALL is not set diff --git a/kernel_build/bin/python2 b/kernel_build/bin/python2 new file mode 100755 index 000000000..fb21b9f5b Binary files /dev/null and b/kernel_build/bin/python2 differ diff --git a/kernel_build/boot/ramdisk b/kernel_build/boot/ramdisk new file mode 100755 index 000000000..b2197da8c Binary files /dev/null and b/kernel_build/boot/ramdisk differ diff --git a/kernel_build/build.sh b/kernel_build/build.sh new file mode 100755 index 000000000..c150e6597 --- /dev/null +++ b/kernel_build/build.sh @@ -0,0 +1,186 @@ +#!/bin/bash + +XY_VERSION="R3.0" + +set -e + +if [ -z "$1" ]; then + echo "Please exec from root directory" + exit 1 +fi +cd "$1" + +if [ "$(uname -m)" != "x86_64" ]; then + echo "This script requires an x86_64 (64-bit) machine." + exit 1 +fi + +export PATH="$(pwd)/kernel_build/bin:$PATH" + +# Configs +OUTDIR="$(pwd)/out" +MODULES_OUTDIR="$(pwd)/modules_out" +TMPDIR="$(pwd)/kernel_build/tmp" + +IN_PLATFORM="$(pwd)/kernel_build/vboot_platform" +IN_DLKM="$(pwd)/kernel_build/vboot_dlkm" +IN_DTB="$OUTDIR/arch/arm64/boot/dts/exynos/s5e8825.dtb" + +PLATFORM_RAMDISK_DIR="$TMPDIR/ramdisk_platform" +DLKM_RAMDISK_DIR="$TMPDIR/ramdisk_dlkm" +PREBUILT_RAMDISK="$(pwd)/kernel_build/boot/ramdisk" +MODULES_DIR="$DLKM_RAMDISK_DIR/lib/modules" + +MKBOOTIMG="$(pwd)/kernel_build/mkbootimg/mkbootimg.py" +MKDTBOIMG="$(pwd)/kernel_build/dtb/mkdtboimg.py" + +OUT_KERNELZIP="$(pwd)/kernel_build/ExynosUnbound-${XY_VERSION}_a53x.zip" +OUT_KERNELTAR="$(pwd)/kernel_build/ExynosUnbound-${XY_VERSION}_a53x.tar" +OUT_KERNEL="$OUTDIR/arch/arm64/boot/Image" +OUT_BOOTIMG="$(pwd)/kernel_build/zip/boot.img" +OUT_VENDORBOOTIMG="$(pwd)/kernel_build/zip/vendor_boot.img" +OUT_DTBIMAGE="$TMPDIR/dtb.img" + +# Kernel-side +BUILD_ARGS="LOCALVERSION=-XyUnbound-${XY_VERSION} KBUILD_BUILD_USER=Gabriel260BR KBUILD_BUILD_HOST=ExynosUnbound" + +kfinish() { + rm -rf "$TMPDIR" + rm -rf "$OUTDIR" + rm -rf "$MODULES_OUTDIR" +} + +kfinish + +DIR="$(readlink -f .)" +PARENT_DIR="$(readlink -f ${DIR}/..)" + +export CROSS_COMPILE="$PARENT_DIR/clang-r416183b/bin/aarch64-linux-gnu-" +export CC="$PARENT_DIR/clang-r416183b/bin/clang" + +export PLATFORM_VERSION=12 +export ANDROID_MAJOR_VERSION=s +export PATH="$PARENT_DIR/build-tools/path/linux-x86:$PARENT_DIR/clang-r416183b/bin:$PATH" +export TARGET_SOC=s5e8825 +export LLVM=1 LLVM_IAS=1 +export ARCH=arm64 + +if [ ! -d "$PARENT_DIR/clang-r416183b" ]; then + git clone https://github.com/crdroidandroid/android_prebuilts_clang_host_linux-x86_clang-r416183b "$PARENT_DIR/clang-r416183b" --depth=1 +fi + +if [ ! -d "$PARENT_DIR/build-tools" ]; then + git clone https://android.googlesource.com/platform/prebuilts/build-tools "$PARENT_DIR/build-tools" --depth=1 +fi + +make -j$(nproc --all) -C $(pwd) O=out $BUILD_ARGS a53x_defconfig >/dev/null +make -j$(nproc --all) -C $(pwd) O=out $BUILD_ARGS dtbs >/dev/null +make -j$(nproc --all) -C $(pwd) O=out $BUILD_ARGS >/dev/null +make -j$(nproc --all) -C $(pwd) O=out INSTALL_MOD_STRIP="--strip-debug --keep-section=.ARM.attributes" INSTALL_MOD_PATH="$MODULES_OUTDIR" modules_install >/dev/null + +rm -rf "$TMPDIR" +rm -f "$OUT_BOOTIMG" +rm -f "$OUT_VENDORBOOTIMG" +mkdir "$TMPDIR" +mkdir -p "$MODULES_DIR/0.0" +mkdir "$PLATFORM_RAMDISK_DIR" + +cp -rf "$IN_PLATFORM"/* "$PLATFORM_RAMDISK_DIR/" +mkdir "$PLATFORM_RAMDISK_DIR/first_stage_ramdisk" +cp -f "$PLATFORM_RAMDISK_DIR/fstab.s5e8825" "$PLATFORM_RAMDISK_DIR/first_stage_ramdisk/fstab.s5e8825" + +if ! find "$MODULES_OUTDIR/lib/modules" -mindepth 1 -type d | read; then + echo "Unknown error!" + exit 1 +fi + +missing_modules="" + +for module in $(cat "$IN_DLKM/modules.load"); do + i=$(find "$MODULES_OUTDIR/lib/modules" -name $module); + if [ -f "$i" ]; then + cp -f "$i" "$MODULES_DIR/0.0/$module" + else + missing_modules="$missing_modules $module" + fi +done + +if [ "$missing_modules" != "" ]; then + echo "ERROR: the following modules were not found: $missing_modules" + exit 1 +fi + +depmod 0.0 -b "$DLKM_RAMDISK_DIR" +sed -i 's/\([^ ]\+\)/\/lib\/modules\/\1/g' "$MODULES_DIR/0.0/modules.dep" +cd "$MODULES_DIR/0.0" +for i in $(find . -name "modules.*" -type f); do + if [ $(basename "$i") != "modules.dep" ] && [ $(basename "$i") != "modules.softdep" ] && [ $(basename "$i") != "modules.alias" ]; then + rm -f "$i" + fi +done +cd "$DIR" + +cp -f "$IN_DLKM/modules.load" "$MODULES_DIR/0.0/modules.load" +mv "$MODULES_DIR/0.0"/* "$MODULES_DIR/" +rm -rf "$MODULES_DIR/0.0" + +echo "Building dtb image..." +python2 "$MKDTBOIMG" create "$OUT_DTBIMAGE" --custom0=0x00000000 --custom1=0xff000000 --version=0 --page_size=2048 "$IN_DTB" || exit 1 + +echo "Building boot image..." + +$MKBOOTIMG --header_version 4 \ + --kernel "$OUT_KERNEL" \ + --output "$OUT_BOOTIMG" \ + --ramdisk "$PREBUILT_RAMDISK" \ + --os_version 12.0.0 \ + --os_patch_level 2024-01 || exit 1 + +echo "Done!" +echo "Building vendor_boot image..." + +cd "$DLKM_RAMDISK_DIR" +find . | cpio --quiet -o -H newc -R root:root | lz4 -9cl > ../ramdisk_dlkm.lz4 +cd ../ramdisk_platform +find . | cpio --quiet -o -H newc -R root:root | lz4 -9cl > ../ramdisk_platform.lz4 +cd .. +echo "buildtime_bootconfig=enable" > bootconfig + +$MKBOOTIMG --header_version 4 \ + --vendor_boot "$OUT_VENDORBOOTIMG" \ + --vendor_bootconfig "$(pwd)/bootconfig" \ + --dtb "$OUT_DTBIMAGE" \ + --vendor_ramdisk "$(pwd)/ramdisk_platform.lz4" \ + --ramdisk_type dlkm \ + --ramdisk_name dlkm \ + --vendor_ramdisk_fragment "$(pwd)/ramdisk_dlkm.lz4" \ + --os_version 12.0.0 \ + --os_patch_level 2024-01 || exit 1 + +cd "$DIR" + +echo "Done!" + +echo "Building zip..." +cd "$(pwd)/kernel_build/zip" +rm -f "$OUT_KERNELZIP" +brotli --quality=11 -c boot.img > boot.br +brotli --quality=11 -c vendor_boot.img > vendor_boot.br +zip -r9 -q "$OUT_KERNELZIP" META-INF boot.br vendor_boot.br +rm -f boot.br vendor_boot.br +cd "$DIR" +echo "Done! Output: $OUT_KERNELZIP" + +echo "Building tar..." +cd "$(pwd)/kernel_build" +rm -f "$OUT_KERNELTAR" +lz4 -c -12 -B6 --content-size "$OUT_BOOTIMG" > boot.img.lz4 +lz4 -c -12 -B6 --content-size "$OUT_VENDORBOOTIMG" > vendor_boot.img.lz4 +tar -cf "$OUT_KERNELTAR" boot.img.lz4 vendor_boot.img.lz4 +cd "$DIR" +rm -f boot.img.lz4 vendor_boot.img.lz4 +echo "Done! Output: $OUT_KERNELTAR" + +echo "Cleaning..." +rm -f "${OUT_VENDORBOOTIMG}" "${OUT_BOOTIMG}" +kfinish diff --git a/kernel_build/dtb/mkdtboimg.py b/kernel_build/dtb/mkdtboimg.py new file mode 100755 index 000000000..9ea2780d8 --- /dev/null +++ b/kernel_build/dtb/mkdtboimg.py @@ -0,0 +1,886 @@ +#! /usr/bin/env python2 +# Copyright 2017, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function +"""Tool for packing multiple DTB/DTBO files into a single image""" +import argparse +import fnmatch +import os +import struct +import zlib +from array import array +from collections import namedtuple +from sys import stdout +class CompressionFormat(object): + """Enum representing DT compression format for a DT entry. + """ + NO_COMPRESSION = 0x00 + ZLIB_COMPRESSION = 0x01 + GZIP_COMPRESSION = 0x02 +class DtEntry(object): + """Provides individual DT image file arguments to be added to a DTBO. + Attributes: + REQUIRED_KEYS_V0: 'keys' needed to be present in the dictionary passed to instantiate + an object of this class when a DTBO header of version 0 is used. + REQUIRED_KEYS_V1: 'keys' needed to be present in the dictionary passed to instantiate + an object of this class when a DTBO header of version 1 is used. + COMPRESSION_FORMAT_MASK: Mask to retrieve compression info for DT entry from flags field + when a DTBO header of version 1 is used. + """ + COMPRESSION_FORMAT_MASK = 0x0f + REQUIRED_KEYS_V0 = ('dt_file', 'dt_size', 'dt_offset', 'id', 'rev', + 'custom0', 'custom1', 'custom2', 'custom3') + REQUIRED_KEYS_V1 = ('dt_file', 'dt_size', 'dt_offset', 'id', 'rev', + 'flags', 'custom0', 'custom1', 'custom2') + @staticmethod + def __get_number_or_prop(arg): + """Converts string to integer or reads the property from DT image. + Args: + arg: String containing the argument provided on the command line. + Returns: + An integer property read from DT file or argument string + converted to integer + """ + if not arg or arg[0] == '+' or arg[0] == '-': + raise ValueError('Invalid argument passed to DTImage') + if arg[0] == '/': + # TODO(b/XXX): Use pylibfdt to get property value from DT + raise ValueError('Invalid argument passed to DTImage') + else: + base = 10 + if arg.startswith('0x') or arg.startswith('0X'): + base = 16 + elif arg.startswith('0'): + base = 8 + return int(arg, base) + def __init__(self, **kwargs): + """Constructor for DtEntry object. + Initializes attributes from dictionary object that contains + values keyed with names equivalent to the class's attributes. + Args: + kwargs: Dictionary object containing values to instantiate + class members with. Expected keys in dictionary are from + the tuple (_REQUIRED_KEYS) + """ + self.__version = kwargs['version'] + required_keys = None + if self.__version == 0: + required_keys = self.REQUIRED_KEYS_V0 + elif self.__version == 1: + required_keys = self.REQUIRED_KEYS_V1 + missing_keys = set(required_keys) - set(kwargs) + if missing_keys: + raise ValueError('Missing keys in DtEntry constructor: %r' % + sorted(missing_keys)) + self.__dt_file = kwargs['dt_file'] + self.__dt_offset = kwargs['dt_offset'] + self.__dt_size = kwargs['dt_size'] + self.__id = self.__get_number_or_prop(kwargs['id']) + self.__rev = self.__get_number_or_prop(kwargs['rev']) + if self.__version == 1: + self.__flags = self.__get_number_or_prop(kwargs['flags']) + self.__custom0 = self.__get_number_or_prop(kwargs['custom0']) + self.__custom1 = self.__get_number_or_prop(kwargs['custom1']) + self.__custom2 = self.__get_number_or_prop(kwargs['custom2']) + if self.__version == 0: + self.__custom3 = self.__get_number_or_prop(kwargs['custom3']) + def __str__(self): + sb = [] + sb.append('{key:>20} = {value:d}'.format(key='dt_size', + value=self.__dt_size)) + sb.append('{key:>20} = {value:d}'.format(key='dt_offset', + value=self.__dt_offset)) + sb.append('{key:>20} = {value:08x}'.format(key='id', + value=self.__id)) + sb.append('{key:>20} = {value:08x}'.format(key='rev', + value=self.__rev)) + if self.__version == 1: + sb.append('{key:>20} = {value:08x}'.format(key='flags', + value=self.__flags)) + sb.append('{key:>20} = {value:08x}'.format(key='custom[0]', + value=self.__custom0)) + sb.append('{key:>20} = {value:08x}'.format(key='custom[1]', + value=self.__custom1)) + sb.append('{key:>20} = {value:08x}'.format(key='custom[2]', + value=self.__custom2)) + if self.__version == 0: + sb.append('{key:>20} = {value:08x}'.format(key='custom[3]', + value=self.__custom3)) + return '\n'.join(sb) + def compression_info(self): + """CompressionFormat: compression format for DT image file. + Args: + version: Version of DTBO header, compression is only + supported from version 1. + """ + if self.__version == 0: + return CompressionFormat.NO_COMPRESSION + return self.flags & self.COMPRESSION_FORMAT_MASK + @property + def dt_file(self): + """file: File handle to the DT image file.""" + return self.__dt_file + @property + def size(self): + """int: size in bytes of the DT image file.""" + return self.__dt_size + @size.setter + def size(self, value): + self.__dt_size = value + @property + def dt_offset(self): + """int: offset in DTBO file for this DT image.""" + return self.__dt_offset + @dt_offset.setter + def dt_offset(self, value): + self.__dt_offset = value + @property + def image_id(self): + """int: DT entry _id for this DT image.""" + return self.__id + @property + def rev(self): + """int: DT entry _rev for this DT image.""" + return self.__rev + @property + def flags(self): + """int: DT entry _flags for this DT image.""" + return self.__flags + @property + def custom0(self): + """int: DT entry _custom0 for this DT image.""" + return self.__custom0 + @property + def custom1(self): + """int: DT entry _custom1 for this DT image.""" + return self.__custom1 + @property + def custom2(self): + """int: DT entry custom2 for this DT image.""" + return self.__custom2 + @property + def custom3(self): + """int: DT entry custom3 for this DT image.""" + return self.__custom3 +class Dtbo(object): + """ + Provides parser, reader, writer for dumping and creating Device Tree Blob + Overlay (DTBO) images. + Attributes: + _DTBO_MAGIC: Device tree table header magic. + _ACPIO_MAGIC: Advanced Configuration and Power Interface table header + magic. + _DT_TABLE_HEADER_SIZE: Size of Device tree table header. + _DT_TABLE_HEADER_INTS: Number of integers in DT table header. + _DT_ENTRY_HEADER_SIZE: Size of Device tree entry header within a DTBO. + _DT_ENTRY_HEADER_INTS: Number of integers in DT entry header. + _GZIP_COMPRESSION_WBITS: Argument 'wbits' for gzip compression + _ZLIB_DECOMPRESSION_WBITS: Argument 'wbits' for zlib/gzip compression + """ + _DTBO_MAGIC = 0xd7b7ab1e + _ACPIO_MAGIC = 0x41435049 + _DT_TABLE_HEADER_SIZE = struct.calcsize('>8I') + _DT_TABLE_HEADER_INTS = 8 + _DT_ENTRY_HEADER_SIZE = struct.calcsize('>8I') + _DT_ENTRY_HEADER_INTS = 8 + _GZIP_COMPRESSION_WBITS = 31 + _ZLIB_DECOMPRESSION_WBITS = 47 + def _update_dt_table_header(self): + """Converts header entries into binary data for DTBO header. + Packs the current Device tree table header attribute values in + metadata buffer. + """ + struct.pack_into('>8I', self.__metadata, 0, self.magic, + self.total_size, self.header_size, + self.dt_entry_size, self.dt_entry_count, + self.dt_entries_offset, self.page_size, + self.version) + def _update_dt_entry_header(self, dt_entry, metadata_offset): + """Converts each DT entry header entry into binary data for DTBO file. + Packs the current device tree table entry attribute into + metadata buffer as device tree entry header. + Args: + dt_entry: DtEntry object for the header to be packed. + metadata_offset: Offset into metadata buffer to begin writing. + dtbo_offset: Offset where the DT image file for this dt_entry can + be found in the resulting DTBO image. + """ + if self.version == 0: + struct.pack_into('>8I', self.__metadata, metadata_offset, dt_entry.size, + dt_entry.dt_offset, dt_entry.image_id, dt_entry.rev, + dt_entry.custom0, dt_entry.custom1, dt_entry.custom2, + dt_entry.custom3) + elif self.version == 1: + struct.pack_into('>8I', self.__metadata, metadata_offset, dt_entry.size, + dt_entry.dt_offset, dt_entry.image_id, dt_entry.rev, + dt_entry.flags, dt_entry.custom0, dt_entry.custom1, + dt_entry.custom2) + def _update_metadata(self): + """Updates the DTBO metadata. + Initialize the internal metadata buffer and fill it with all Device + Tree table entries and update the DTBO header. + """ + self.__metadata = array('b', b' ' * self.__metadata_size) + metadata_offset = self.header_size + for dt_entry in self.__dt_entries: + self._update_dt_entry_header(dt_entry, metadata_offset) + metadata_offset += self.dt_entry_size + self._update_dt_table_header() + def _read_dtbo_header(self, buf): + """Reads DTBO file header into metadata buffer. + Unpack and read the DTBO table header from given buffer. The + buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE. + Args: + buf: Bytebuffer read directly from the file of size + _DT_TABLE_HEADER_SIZE. + """ + (self.magic, self.total_size, self.header_size, + self.dt_entry_size, self.dt_entry_count, self.dt_entries_offset, + self.page_size, self.version) = struct.unpack_from('>8I', buf, 0) + # verify the header + if self.magic != self._DTBO_MAGIC and self.magic != self._ACPIO_MAGIC: + raise ValueError('Invalid magic number 0x%x in DTBO/ACPIO file' % + (self.magic)) + if self.header_size != self._DT_TABLE_HEADER_SIZE: + raise ValueError('Invalid header size (%d) in DTBO/ACPIO file' % + (self.header_size)) + if self.dt_entry_size != self._DT_ENTRY_HEADER_SIZE: + raise ValueError('Invalid DT entry header size (%d) in DTBO/ACPIO file' % + (self.dt_entry_size)) + def _read_dt_entries_from_metadata(self): + """Reads individual DT entry headers from metadata buffer. + Unpack and read the DTBO DT entry headers from the internal buffer. + The buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE + + (_DT_ENTRY_HEADER_SIZE * dt_entry_count). The method raises exception + if DT entries have already been set for this object. + """ + if self.__dt_entries: + raise ValueError('DTBO DT entries can be added only once') + offset = self.dt_entries_offset // 4 + params = {} + params['version'] = self.version + params['dt_file'] = None + for i in range(0, self.dt_entry_count): + dt_table_entry = self.__metadata[offset:offset + self._DT_ENTRY_HEADER_INTS] + params['dt_size'] = dt_table_entry[0] + params['dt_offset'] = dt_table_entry[1] + for j in range(2, self._DT_ENTRY_HEADER_INTS): + required_keys = None + if self.version == 0: + required_keys = DtEntry.REQUIRED_KEYS_V0 + elif self.version == 1: + required_keys = DtEntry.REQUIRED_KEYS_V1 + params[required_keys[j + 1]] = str(dt_table_entry[j]) + dt_entry = DtEntry(**params) + self.__dt_entries.append(dt_entry) + offset += self._DT_ENTRY_HEADER_INTS + def _read_dtbo_image(self): + """Parse the input file and instantiate this object.""" + # First check if we have enough to read the header + file_size = os.fstat(self.__file.fileno()).st_size + if file_size < self._DT_TABLE_HEADER_SIZE: + raise ValueError('Invalid DTBO file') + self.__file.seek(0) + buf = self.__file.read(self._DT_TABLE_HEADER_SIZE) + self._read_dtbo_header(buf) + self.__metadata_size = (self.header_size + + self.dt_entry_count * self.dt_entry_size) + if file_size < self.__metadata_size: + raise ValueError('Invalid or truncated DTBO file of size %d expected %d' % + file_size, self.__metadata_size) + num_ints = (self._DT_TABLE_HEADER_INTS + + self.dt_entry_count * self._DT_ENTRY_HEADER_INTS) + if self.dt_entries_offset > self._DT_TABLE_HEADER_SIZE: + num_ints += (self.dt_entries_offset - self._DT_TABLE_HEADER_SIZE) / 4 + format_str = '>' + str(num_ints) + 'I' + self.__file.seek(0) + self.__metadata = struct.unpack(format_str, + self.__file.read(self.__metadata_size)) + self._read_dt_entries_from_metadata() + def _find_dt_entry_with_same_file(self, dt_entry): + """Finds DT Entry that has identical backing DT file. + Args: + dt_entry: DtEntry object whose 'dtfile' we find for existence in the + current 'dt_entries'. + Returns: + If a match by file path is found, the corresponding DtEntry object + from internal list is returned. If not, 'None' is returned. + """ + dt_entry_path = os.path.realpath(dt_entry.dt_file.name) + for entry in self.__dt_entries: + entry_path = os.path.realpath(entry.dt_file.name) + if entry_path == dt_entry_path: + return entry + return None + def __init__(self, file_handle, dt_type='dtb', page_size=None, version=0): + """Constructor for Dtbo Object + Args: + file_handle: The Dtbo File handle corresponding to this object. + The file handle can be used to write to (in case of 'create') + or read from (in case of 'dump') + """ + self.__file = file_handle + self.__dt_entries = [] + self.__metadata = None + self.__metadata_size = 0 + # if page_size is given, assume the object is being instantiated to + # create a DTBO file + if page_size: + if dt_type == 'acpi': + self.magic = self._ACPIO_MAGIC + else: + self.magic = self._DTBO_MAGIC + self.total_size = self._DT_TABLE_HEADER_SIZE + self.header_size = self._DT_TABLE_HEADER_SIZE + self.dt_entry_size = self._DT_ENTRY_HEADER_SIZE + self.dt_entry_count = 0 + self.dt_entries_offset = self._DT_TABLE_HEADER_SIZE + self.page_size = page_size + self.version = version + self.__metadata_size = self._DT_TABLE_HEADER_SIZE + else: + self._read_dtbo_image() + def __str__(self): + sb = [] + sb.append('dt_table_header:') + _keys = ('magic', 'total_size', 'header_size', 'dt_entry_size', + 'dt_entry_count', 'dt_entries_offset', 'page_size', 'version') + for key in _keys: + if key == 'magic': + sb.append('{key:>20} = {value:08x}'.format(key=key, + value=self.__dict__[key])) + else: + sb.append('{key:>20} = {value:d}'.format(key=key, + value=self.__dict__[key])) + count = 0 + for dt_entry in self.__dt_entries: + sb.append('dt_table_entry[{0:d}]:'.format(count)) + sb.append(str(dt_entry)) + count = count + 1 + return '\n'.join(sb) + @property + def dt_entries(self): + """Returns a list of DtEntry objects found in DTBO file.""" + return self.__dt_entries + def compress_dt_entry(self, compression_format, dt_entry_file): + """Compresses a DT entry. + Args: + compression_format: Compression format for DT Entry + dt_entry_file: File handle to read DT entry from. + Returns: + Compressed DT entry and its length. + Raises: + ValueError if unrecognized compression format is found. + """ + compress_zlib = zlib.compressobj() # zlib + compress_gzip = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, + zlib.DEFLATED, self._GZIP_COMPRESSION_WBITS) # gzip + compression_obj_dict = { + CompressionFormat.NO_COMPRESSION: None, + CompressionFormat.ZLIB_COMPRESSION: compress_zlib, + CompressionFormat.GZIP_COMPRESSION: compress_gzip, + } + if compression_format not in compression_obj_dict: + ValueError("Bad compression format %d" % compression_format) + if compression_format is CompressionFormat.NO_COMPRESSION: + dt_entry = dt_entry_file.read() + else: + compression_object = compression_obj_dict[compression_format] + dt_entry_file.seek(0) + dt_entry = compression_object.compress(dt_entry_file.read()) + dt_entry += compression_object.flush() + return dt_entry, len(dt_entry) + def add_dt_entries(self, dt_entries): + """Adds DT image files to the DTBO object. + Adds a list of Dtentry Objects to the DTBO image. The changes are not + committed to the output file until commit() is called. + Args: + dt_entries: List of DtEntry object to be added. + Returns: + A buffer containing all DT entries. + Raises: + ValueError: if the list of DT entries is empty or if a list of DT entries + has already been added to the DTBO. + """ + if not dt_entries: + raise ValueError('Attempted to add empty list of DT entries') + if self.__dt_entries: + raise ValueError('DTBO DT entries can be added only once') + dt_entry_count = len(dt_entries) + dt_offset = (self.header_size + + dt_entry_count * self.dt_entry_size) + dt_entry_buf = b"" + for dt_entry in dt_entries: + if not isinstance(dt_entry, DtEntry): + raise ValueError('Adding invalid DT entry object to DTBO') + entry = self._find_dt_entry_with_same_file(dt_entry) + dt_entry_compression_info = dt_entry.compression_info() + if entry and (entry.compression_info() == dt_entry_compression_info): + dt_entry.dt_offset = entry.dt_offset + dt_entry.size = entry.size + else: + dt_entry.dt_offset = dt_offset + compressed_entry, dt_entry.size = self.compress_dt_entry(dt_entry_compression_info, + dt_entry.dt_file) + dt_entry_buf += compressed_entry + dt_offset += dt_entry.size + self.total_size += dt_entry.size + self.__dt_entries.append(dt_entry) + self.dt_entry_count += 1 + self.__metadata_size += self.dt_entry_size + self.total_size += self.dt_entry_size + return dt_entry_buf + def extract_dt_file(self, idx, fout, decompress): + """Extract DT Image files embedded in the DTBO file. + Extracts Device Tree blob image file at given index into a file handle. + Args: + idx: Index of the DT entry in the DTBO file. + fout: File handle where the DTB at index idx to be extracted into. + decompress: If a DT entry is compressed, decompress it before writing + it to the file handle. + Raises: + ValueError: if invalid DT entry index or compression format is detected. + """ + if idx > self.dt_entry_count: + raise ValueError('Invalid index %d of DtEntry' % idx) + size = self.dt_entries[idx].size + offset = self.dt_entries[idx].dt_offset + self.__file.seek(offset, 0) + fout.seek(0) + compression_format = self.dt_entries[idx].compression_info() + if decompress and compression_format: + if (compression_format == CompressionFormat.ZLIB_COMPRESSION or + compression_format == CompressionFormat.GZIP_COMPRESSION): + fout.write(zlib.decompress(self.__file.read(size), self._ZLIB_DECOMPRESSION_WBITS)) + else: + raise ValueError("Unknown compression format detected") + else: + fout.write(self.__file.read(size)) + def commit(self, dt_entry_buf): + """Write out staged changes to the DTBO object to create a DTBO file. + Writes a fully instantiated Dtbo Object into the output file using the + file handle present in '_file'. No checks are performed on the object + except for existence of output file handle on the object before writing + out the file. + Args: + dt_entry_buf: Buffer containing all DT entries. + """ + if not self.__file: + raise ValueError('No file given to write to.') + if not self.__dt_entries: + raise ValueError('No DT image files to embed into DTBO image given.') + self._update_metadata() + self.__file.seek(0) + self.__file.write(self.__metadata) + self.__file.write(dt_entry_buf) + self.__file.flush() +def parse_dt_entry(global_args, arglist): + """Parse arguments for single DT entry file. + Parses command line arguments for single DT image file while + creating a Device tree blob overlay (DTBO). + Args: + global_args: Dtbo object containing global default values + for DtEntry attributes. + arglist: Command line argument list for this DtEntry. + Returns: + A Namespace object containing all values to instantiate DtEntry object. + """ + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('dt_file', nargs='?', + type=argparse.FileType('rb'), + default=None) + parser.add_argument('--id', type=str, dest='id', action='store', + default=global_args.global_id) + parser.add_argument('--rev', type=str, dest='rev', + action='store', default=global_args.global_rev) + parser.add_argument('--flags', type=str, dest='flags', + action='store', + default=global_args.global_flags) + parser.add_argument('--custom0', type=str, dest='custom0', + action='store', + default=global_args.global_custom0) + parser.add_argument('--custom1', type=str, dest='custom1', + action='store', + default=global_args.global_custom1) + parser.add_argument('--custom2', type=str, dest='custom2', + action='store', + default=global_args.global_custom2) + parser.add_argument('--custom3', type=str, dest='custom3', + action='store', + default=global_args.global_custom3) + return parser.parse_args(arglist) +def parse_dt_entries(global_args, arg_list): + """Parse all DT entries from command line. + Parse all DT image files and their corresponding attribute from + command line + Args: + global_args: Argument containing default global values for _id, + _rev and customX. + arg_list: The remainder of the command line after global options + DTBO creation have been parsed. + Returns: + A List of DtEntry objects created after parsing the command line + given in argument. + """ + dt_entries = [] + img_file_idx = [] + idx = 0 + # find all positional arguments (i.e. DT image file paths) + for arg in arg_list: + if not arg.startswith("--"): + img_file_idx.append(idx) + idx = idx + 1 + if not img_file_idx: + raise ValueError('Input DT images must be provided') + total_images = len(img_file_idx) + for idx in range(total_images): + start_idx = img_file_idx[idx] + if idx == total_images - 1: + argv = arg_list[start_idx:] + else: + end_idx = img_file_idx[idx + 1] + argv = arg_list[start_idx:end_idx] + args = parse_dt_entry(global_args, argv) + params = vars(args) + params['version'] = global_args.version + params['dt_offset'] = 0 + params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size + dt_entries.append(DtEntry(**params)) + return dt_entries +def parse_config_option(line, is_global, dt_keys, global_key_types): + """Parses a single line from the configuration file. + Args: + line: String containing the key=value line from the file. + is_global: Boolean indicating if we should parse global or DT entry + specific option. + dt_keys: Tuple containing all valid DT entry and global option strings + in configuration file. + global_key_types: A dict of global options and their corresponding types. It + contains all exclusive valid global option strings in configuration + file that are not repeated in dt entry options. + Returns: + Returns a tuple for parsed key and value for the option. Also, checks + the key to make sure its valid. + """ + if line.find('=') == -1: + raise ValueError('Invalid line (%s) in configuration file' % line) + key, value = (x.strip() for x in line.split('=')) + if is_global and key in global_key_types: + if global_key_types[key] is int: + value = int(value) + elif key not in dt_keys: + raise ValueError('Invalid option (%s) in configuration file' % key) + return key, value +def parse_config_file(fin, dt_keys, global_key_types): + """Parses the configuration file for creating DTBO image. + Args: + fin: File handle for configuration file + is_global: Boolean indicating if we should parse global or DT entry + specific option. + dt_keys: Tuple containing all valid DT entry and global option strings + in configuration file. + global_key_types: A dict of global options and their corresponding types. It + contains all exclusive valid global option strings in configuration + file that are not repeated in dt entry options. + Returns: + global_args, dt_args: Tuple of a dictionary with global arguments + and a list of dictionaries for all DT entry specific arguments the + following format. + global_args: + {'id' : , 'rev' : ...} + dt_args: + [{'filename' : 'dt_file_name', 'id' : , + 'rev' : ...}, + {'filename' : 'dt_file_name2', 'id' : , + 'rev' : ...}, ... + ] + """ + # set all global defaults + global_args = dict((k, '0') for k in dt_keys) + global_args['dt_type'] = 'dtb' + global_args['page_size'] = 2048 + global_args['version'] = 0 + dt_args = [] + found_dt_entry = False + count = -1 + for line in fin: + line = line.rstrip() + if line.lstrip().startswith('#'): + continue + comment_idx = line.find('#') + line = line if comment_idx == -1 else line[0:comment_idx] + if not line or line.isspace(): + continue + if line.startswith((' ', '\t')) and not found_dt_entry: + # This is a global argument + key, value = parse_config_option(line, True, dt_keys, global_key_types) + global_args[key] = value + elif line.find('=') != -1: + key, value = parse_config_option(line, False, dt_keys, global_key_types) + dt_args[-1][key] = value + else: + found_dt_entry = True + count += 1 + dt_args.append({}) + dt_args[-1]['filename'] = line.strip() + return global_args, dt_args +def parse_create_args(arg_list): + """Parse command line arguments for 'create' sub-command. + Args: + arg_list: All command line arguments except the outfile file name. + Returns: + The list of remainder of the command line arguments after parsing + for 'create'. + """ + image_arg_index = 0 + for arg in arg_list: + if not arg.startswith("--"): + break + image_arg_index = image_arg_index + 1 + argv = arg_list[0:image_arg_index] + remainder = arg_list[image_arg_index:] + parser = argparse.ArgumentParser(prog='create', add_help=False) + parser.add_argument('--dt_type', type=str, dest='dt_type', + action='store', default='dtb') + parser.add_argument('--page_size', type=int, dest='page_size', + action='store', default=2048) + parser.add_argument('--version', type=int, dest='version', + action='store', default=0) + parser.add_argument('--id', type=str, dest='global_id', + action='store', default='0') + parser.add_argument('--rev', type=str, dest='global_rev', + action='store', default='0') + parser.add_argument('--flags', type=str, dest='global_flags', + action='store', default='0') + parser.add_argument('--custom0', type=str, dest='global_custom0', + action='store', default='0') + parser.add_argument('--custom1', type=str, dest='global_custom1', + action='store', default='0') + parser.add_argument('--custom2', type=str, dest='global_custom2', + action='store', default='0') + parser.add_argument('--custom3', type=str, dest='global_custom3', + action='store', default='0') + args = parser.parse_args(argv) + return args, remainder +def parse_dump_cmd_args(arglist): + """Parse command line arguments for 'dump' sub-command. + Args: + arglist: List of all command line arguments including the outfile + file name if exists. + Returns: + A namespace object of parsed arguments. + """ + parser = argparse.ArgumentParser(prog='dump') + parser.add_argument('--output', '-o', nargs='?', + type=argparse.FileType('w'), + dest='outfile', + default=stdout) + parser.add_argument('--dtb', '-b', nargs='?', type=str, + dest='dtfilename') + parser.add_argument('--decompress', action='store_true', dest='decompress') + return parser.parse_args(arglist) +def parse_config_create_cmd_args(arglist): + """Parse command line arguments for 'cfg_create subcommand. + Args: + arglist: A list of all command line arguments including the + mandatory input configuration file name. + Returns: + A Namespace object of parsed arguments. + """ + parser = argparse.ArgumentParser(prog='cfg_create') + parser.add_argument('conf_file', nargs='?', + type=argparse.FileType('r'), + default=None) + cwd = os.getcwd() + parser.add_argument('--dtb-dir', '-d', nargs='?', type=str, + dest='dtbdir', default=cwd) + return parser.parse_args(arglist) +def create_dtbo_image(fout, argv): + """Create Device Tree Blob Overlay image using provided arguments. + Args: + fout: Output file handle to write to. + argv: list of command line arguments. + """ + global_args, remainder = parse_create_args(argv) + if not remainder: + raise ValueError('List of dtimages to add to DTBO not provided') + dt_entries = parse_dt_entries(global_args, remainder) + dtbo = Dtbo(fout, global_args.dt_type, global_args.page_size, global_args.version) + dt_entry_buf = dtbo.add_dt_entries(dt_entries) + dtbo.commit(dt_entry_buf) + fout.close() +def dump_dtbo_image(fin, argv): + """Dump DTBO file. + Dump Device Tree Blob Overlay metadata as output and the device + tree image files embedded in the DTBO image into file(s) provided + as arguments + Args: + fin: Input DTBO image files. + argv: list of command line arguments. + """ + dtbo = Dtbo(fin) + args = parse_dump_cmd_args(argv) + if args.dtfilename: + num_entries = len(dtbo.dt_entries) + for idx in range(0, num_entries): + with open(args.dtfilename + '.{:d}'.format(idx), 'wb') as fout: + dtbo.extract_dt_file(idx, fout, args.decompress) + args.outfile.write(str(dtbo) + '\n') + args.outfile.close() +def create_dtbo_image_from_config(fout, argv): + """Create DTBO file from a configuration file. + Args: + fout: Output file handle to write to. + argv: list of command line arguments. + """ + args = parse_config_create_cmd_args(argv) + if not args.conf_file: + raise ValueError('Configuration file must be provided') + _DT_KEYS = ('id', 'rev', 'flags', 'custom0', 'custom1', 'custom2', 'custom3') + _GLOBAL_KEY_TYPES = {'dt_type': str, 'page_size': int, 'version': int} + global_args, dt_args = parse_config_file(args.conf_file, + _DT_KEYS, _GLOBAL_KEY_TYPES) + version = global_args['version'] + params = {} + params['version'] = version + dt_entries = [] + for dt_arg in dt_args: + filepath = dt_arg['filename'] + if not os.path.isabs(filepath): + for root, dirnames, filenames in os.walk(args.dtbdir): + for filename in fnmatch.filter(filenames, os.path.basename(filepath)): + filepath = os.path.join(root, filename) + params['dt_file'] = open(filepath, 'rb') + params['dt_offset'] = 0 + params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size + for key in _DT_KEYS: + if key not in dt_arg: + params[key] = global_args[key] + else: + params[key] = dt_arg[key] + dt_entries.append(DtEntry(**params)) + # Create and write DTBO file + dtbo = Dtbo(fout, global_args['dt_type'], global_args['page_size'], version) + dt_entry_buf = dtbo.add_dt_entries(dt_entries) + dtbo.commit(dt_entry_buf) + fout.close() +def print_default_usage(progname): + """Prints program's default help string. + Args: + progname: This program's name. + """ + sb = [] + sb.append(' ' + progname + ' help all') + sb.append(' ' + progname + ' help \n') + sb.append(' commands:') + sb.append(' help, dump, create, cfg_create') + print('\n'.join(sb)) +def print_dump_usage(progname): + """Prints usage for 'dump' sub-command. + Args: + progname: This program's name. + """ + sb = [] + sb.append(' ' + progname + ' dump (