From e04dafbe0f66420446c0c9d5830677b6f8377a17 Mon Sep 17 00:00:00 2001 From: Nikita Travkin Date: Sun, 6 Aug 2023 21:55:15 +0500 Subject: [PATCH] lk2nd: boot: add extlinux support --- app/aboot/aboot.c | 11 + lk2nd/boot/boot.c | 59 +++ lk2nd/boot/boot.h | 17 + lk2nd/boot/extlinux.c | 409 +++++++++++++++++++++ lk2nd/boot/rules.mk | 12 + lk2nd/boot/util.c | 90 +++++ lk2nd/device/device.c | 6 + lk2nd/device/device.h | 3 + lk2nd/device/dts/msm8916/msm8916-qrd-9.dts | 1 + lk2nd/device/dts/msm8916/samsung.dts | 4 + lk2nd/include/lk2nd/boot.h | 8 + lk2nd/include/lk2nd/device.h | 3 + lk2nd/project/base.mk | 1 + 13 files changed, 624 insertions(+) create mode 100644 lk2nd/boot/boot.c create mode 100644 lk2nd/boot/boot.h create mode 100644 lk2nd/boot/extlinux.c create mode 100644 lk2nd/boot/rules.mk create mode 100644 lk2nd/boot/util.c create mode 100644 lk2nd/include/lk2nd/boot.h diff --git a/app/aboot/aboot.c b/app/aboot/aboot.c index 53f823ad70..05ebb3b5c8 100644 --- a/app/aboot/aboot.c +++ b/app/aboot/aboot.c @@ -102,6 +102,12 @@ #if WITH_LK2ND_DEVICE #include #endif +#if WITH_LK2ND_DISPLAY_MENU +#include +#endif +#if WITH_LK2ND_BOOT +#include +#endif extern bool target_use_signed_kernel(void); extern void platform_uninit(void); @@ -5630,6 +5636,11 @@ void aboot_init(const struct app_descriptor *app) normal_boot: if (!boot_into_fastboot) { +#if WITH_LK2ND_BOOT + if (!boot_into_recovery) + lk2nd_boot(); +#endif + if (target_is_emmc_boot()) { if(!IS_ENABLED(ABOOT_STANDALONE) && emmc_recovery_init()) diff --git a/lk2nd/boot/boot.c b/lk2nd/boot/boot.c new file mode 100644 index 0000000000..c324cef8fb --- /dev/null +++ b/lk2nd/boot/boot.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright (c) 2023 Nikita Travkin */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "boot.h" + +/** + * lk2nd_scan_devices() - Scan filesystems and try to boot + */ +static void lk2nd_scan_devices(void) +{ + struct bdev_struct *bdevs = bio_get_bdevs(); + char mountpoint[16]; + bdev_t *bdev; + int ret; + + dprintf(INFO, "boot: Trying to boot...\n"); + + list_for_every_entry(&bdevs->list, bdev, bdev_t, node) { + + /* Skip top level block devices. */ + if (!bdev->is_subdev) + continue; + + snprintf(mountpoint, sizeof(mountpoint), "/%s", bdev->name); + ret = fs_mount(mountpoint, "ext2", bdev->name); + if (ret < 0) + continue; + + /* TODO: THIS MUST BE DEBUG ONLY!!! */ + dprintf(INFO, "Scanning %s ...\n", bdev->name); + dprintf(INFO, "%s\n", mountpoint); + print_file_tree(mountpoint, " "); + + lk2nd_try_extlinux(mountpoint); + } +} + +/** + * lk2nd_boot() - Try to boot the OS. + * + * This method is supposed to be called from aboot. + * If appropriate OS is found, it will be booted, and this + * method will never return. + */ +void lk2nd_boot(void) +{ + lk2nd_boot_dump_devices(); + dprintf(INFO, "boot: Trying to boot from the file system\n"); + lk2nd_scan_devices(); +} diff --git a/lk2nd/boot/boot.h b/lk2nd/boot/boot.h new file mode 100644 index 0000000000..62382a509a --- /dev/null +++ b/lk2nd/boot/boot.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef LK2ND_BOOT_BOOT_H +#define LK2ND_BOOT_BOOT_H + +#include +#include + +#include + +/* util.c */ +void lk2nd_boot_dump_devices(void); +void print_file_tree(char *root, char *prefix); + +/* extlinux.c */ +void lk2nd_try_extlinux(const char *mountpoint); + +#endif /* LK2ND_BOOT_BOOT_H */ diff --git a/lk2nd/boot/extlinux.c b/lk2nd/boot/extlinux.c new file mode 100644 index 0000000000..3e6819ccf6 --- /dev/null +++ b/lk2nd/boot/extlinux.c @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright (c) 2023 Nikita Travkin */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "boot.h" + +struct label { + char *kernel; + char *initramfs; + char *dtb; + char *dtbdir; + char *cmdline; +}; + +enum token { + CMD_KERNEL, + CMD_APPEND, + CMD_INITRD, + CMD_FDT, + CMD_FDTDIR, + CMD_UNKNOWN, +}; + +static const struct { + char *command; + enum token token; +} token_map[] = { + {"kernel", CMD_KERNEL}, + {"fdtdir", CMD_FDTDIR}, + {"fdt", CMD_FDT}, + {"initrd", CMD_INITRD}, + {"append", CMD_APPEND}, +}; + +static enum token cmd_to_tok(char *command) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(token_map); i++) + if (!strcmp(command, token_map[i].command)) + return token_map[i].token; + + return CMD_UNKNOWN; +} + +#define EOF -1 + +/** + * parse_char() - Get one char from the file. + * @data: File contents + * @size: remaining size of data + * + * Update pointers and return the next character. + * + * Returns: char value or EOF. + */ +static int parse_char(char **data, size_t *size) +{ + char c = **data; + + if (*size == 0) + return EOF; + + (*size)--; + (*data)++; + + return c; +} + +/** + * parse_line() - Read one command from the file. + * @data: File contents + * @size: remaining size of data + * @command: returns pointer to the command string + * @value: returns pointer to the value string + * + * Function scans one line from the data blob, ignoring comments and + * whitespace; returns pointers to the start of the command and it's + * value after replacing whitespace and newline after them with \0. + * @data and @size will be updated to remove the parsed line(s). + * + * Returns: 0 on success, negative value on error or EOF. + */ +static int parse_command(char **data, size_t *size, char **command, char **value) +{ + int c; + + /* Step 1: Ignore leading comments and whitespace. */ + + while (*size != 0 && (**data == '#' || **data == ' ' || **data == '\t' || **data == '\n')) { + /* Skip leading whitespace. */ + while (*size != 0 && (**data == ' ' || **data == '\t' || **data == '\n')) { + c = parse_char(data, size); + if (c == EOF) + return -1; + } + + if (*size != 0 && **data == '#') { + do { + c = parse_char(data, size); + if (c == EOF) + return -1; + } while (c != '\n'); + } + } + + if (*size == 0) + return -1; + + /* Step 2: Read the command. */ + *command = *data; + while (*size != 0 && **data != ' ' && **data != '\t' && **data != '\n') { + c = parse_char(data, size); + if (c == EOF) + return -1; + } + + if (*size != 0 && (**data == ' ' || **data == '\t')) { + **data = '\0'; + (*data)++; + (*size)--; + } + + if (*size == 0 || **data == '\n') + return -1; + + /* Step 3: Read the value. */ + + /* Skip whitespace. */ + while (*size != 0 && (**data == ' ' || **data == '\t' || **data == '\n')) { + c = parse_char(data, size); + if (c == EOF || c == '\n') + return -1; + } + + *value = *data; + while (*size != 0 && **data != '\n') + c = parse_char(data, size); + + /* The last command may not have a newline. */ + if (*size == 0 || **data == '\n') { + **data = '\0'; + (*data)++; + (*size)--; + } + + return 0; +} + +/** + * parse_conf() - Extract default label from extlinux.conf + * @data: File contents + * @size: Length of the file + * @label: structure to write strings to + * + * Find the default label in the file and extract strings from it. + * This function may destroy the file by changing some newlines to nulls + * as it may be implemented by pointing into the data buffer to return + * the configuration strings. + * + * NOTE: The data buffer must be one byte longer than the actual data. + * + * Returns: 0 on success or negative error on parse failure. + */ +static int parse_conf(char *data, size_t size, struct label *label) +{ + char *command = NULL, *value = NULL; + + while (parse_command(&data, &size, &command, &value) == 0) { + dprintf(INFO, "(cmd) %s \t-> %s\n", command, value); // TODO: spew + + switch (cmd_to_tok(command)) { + case CMD_KERNEL: + label->kernel = value; + break; + case CMD_INITRD: + label->initramfs = value; + break; + case CMD_APPEND: + label->cmdline = value; + break; + case CMD_FDT: + label->dtb = value; + break; + case CMD_FDTDIR: + label->dtbdir = value; + break; + default: + } + } + + return 0; +} + +static bool fs_file_exists(const char *file) +{ + struct filehandle *fileh; + int ret; + + if (!file) + return false; + + ret = fs_open_file(file, &fileh); + if (ret < 0) + return false; + + fs_close_file(fileh); + return true; +} + +/** + * expand_conf() - Sanity check and rewrite the parsed config. + * + * This function checks if all the values in the config are sane, + * all mentioned files exists. It then appends the paths with the + * root directory and rewrites the dtb field based on dtbdir if + * possible. This funtion allocates new strings for all values. + * + * Returns: True if the config seems bootable, false otherwise. + */ +static bool expand_conf(struct label *label, const char *root) +{ + char path[128]; + int i = 0; + + /* Cant boot without any kernel. */ + if (!label->kernel) { + dprintf(INFO, "Kernel is not specified\n"); + return false; + } + + snprintf(path, sizeof(path), "%s/%s", root, label->kernel); + label->kernel = strndup(path, sizeof(path)); + + if (!fs_file_exists(label->kernel)) { + dprintf(INFO, "Kernel %s does not exist\n", label->kernel); + return false; + } + + /* lk2nd needs to patch the dtb to boot. */ + else if (!label->dtbdir && !label->dtb) { + dprintf(INFO, "Neither fdt nor fdtdir is specified\n"); + return false; + } + + if (label->dtbdir) { + if (!lk2nd_dev.dtbfiles) { + dprintf(INFO, "The dtb-files for this device is not set\n"); + return false; + } + + while (lk2nd_dev.dtbfiles[i]) { + snprintf(path, sizeof(path), "%s/%s/%s", root, label->dtbdir, lk2nd_dev.dtbfiles[i]); + dprintf(INFO, "Check: %s\n", path); + if (fs_file_exists(path)) { + label->dtb = strndup(path, sizeof(path)); + break; + } + i++; + } + } + else if (!fs_file_exists(label->dtb)) { + dprintf(INFO, "FDT %s does not exist\n", label->dtb); + return false; + } + + if (label->initramfs) { + snprintf(path, sizeof(path), "%s/%s", root, label->initramfs); + label->initramfs = strndup(path, sizeof(path)); + + if (!fs_file_exists(label->initramfs)) { + dprintf(INFO, "Initramfs %s does not exist\n", label->initramfs); + return false; + } + } + + if (label->cmdline) + label->cmdline = strdup(label->cmdline); + else + label->cmdline = strdup(""); + + return true; +} + +extern void boot_linux(void *kernel, unsigned *tags, + const char *cmdline, unsigned machtype, + void *ramdisk, unsigned ramdisk_size, + enum boot_type boot_type); + +/** + * lk2nd_boot_label() - Load all files from the label and boot. + */ +static void lk2nd_boot_label(struct label *label) +{ + int scratch_size = target_get_max_flash_size(); + void *scratch = target_get_scratch_address(); + unsigned int kernel_size, ramdisk_size = 0; + int ret; + + ret = fs_load_file(label->kernel, scratch, scratch_size); + if (ret < 0) { + dprintf(INFO, "Failed to load the kernel: %d\n", ret); + return; + } + + kernel_size = ret; + + if (is_gzip_package(scratch, kernel_size)) { + dprintf(INFO, "Decompressing the kernel...\n"); + ret = decompress(scratch, kernel_size, + (void *)ABOOT_FORCE_KERNEL64_ADDR, (ABOOT_FORCE_TAGS_ADDR - ABOOT_FORCE_KERNEL64_ADDR), + NULL, &kernel_size); + if (ret) { + dprintf(INFO, "Failed to decompress the kernel: %d\n", ret); + return; + } + } + else { + dprintf(INFO, "Copying uncompressed kernel...\n"); + memcpy((void *)ABOOT_FORCE_KERNEL64_ADDR, scratch, kernel_size); + } + + ret = fs_load_file(label->dtb, (void *)ABOOT_FORCE_TAGS_ADDR, (ABOOT_FORCE_RAMDISK_ADDR - ABOOT_FORCE_TAGS_ADDR)); + if (ret < 0) { + dprintf(INFO, "Failed to load the dtb: %d\n", ret); + return; + } + + if (label->initramfs) { + ret = fs_load_file(label->initramfs, (void *)ABOOT_FORCE_RAMDISK_ADDR, scratch_size); + if (ret < 0) { + dprintf(INFO, "Failed to load the initramfs: %d\n", ret); + return; + } + ramdisk_size = ret; + } + + // FIXME: those addresses are kinda sad. + boot_linux((void *)ABOOT_FORCE_KERNEL64_ADDR, + (void *)ABOOT_FORCE_TAGS_ADDR, + label->cmdline, + board_machtype(), + (void *)ABOOT_FORCE_RAMDISK_ADDR, ramdisk_size, + 0); +} + +/** + * lk2nd_try_extlinux() - Try to boot with extlinux + * + * Check if /extlinux/extlinux.conf exists and try to + * boot it if so. + */ +void lk2nd_try_extlinux(const char *root) +{ + struct filehandle *fileh; + struct file_stat stat; + struct label label = {0}; + char path[32]; + char *data; + int ret; + + snprintf(path, sizeof(path), "%s/extlinux/extlinux.conf", root); + ret = fs_open_file(path, &fileh); + if (ret < 0) { + dprintf(INFO, "No extlinux config in %s: %d\n", root, ret); // TODO spew + return; + } + + fs_stat_file(fileh, &stat); + data = malloc(stat.size + 1); + fs_read_file(fileh, data, 0, stat.size); + fs_close_file(fileh); + + ret = parse_conf(data, stat.size, &label); + if (ret < 0) + goto error; + + if (!expand_conf(&label, root)) + goto error; + + free(data); + + // TODO: drop/spew? + dprintf(INFO, "Parsed %s\n", path); + dprintf(INFO, "kernel = %s\n", label.kernel); + dprintf(INFO, "dtb = %s\n", label.dtb); + dprintf(INFO, "dtbdir = %s\n", label.dtbdir); + dprintf(INFO, "initramfs = %s\n", label.initramfs); + dprintf(INFO, "cmdline = %s\n", label.cmdline); + + lk2nd_boot_label(&label); + + return; + +error: + dprintf(INFO, "Failed to parse extlinux.conf\n"); + free(data); +} diff --git a/lk2nd/boot/rules.mk b/lk2nd/boot/rules.mk new file mode 100644 index 0000000000..4b8159ba27 --- /dev/null +++ b/lk2nd/boot/rules.mk @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULES += \ + lib/fs \ + lib/bio \ + lib/partition \ + +OBJS += \ + $(LOCAL_DIR)/boot.o \ + $(LOCAL_DIR)/util.o \ + $(LOCAL_DIR)/extlinux.o \ diff --git a/lk2nd/boot/util.c b/lk2nd/boot/util.c new file mode 100644 index 0000000000..790da2c133 --- /dev/null +++ b/lk2nd/boot/util.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright (c) 2022 Nikita Travkin */ + +#include +#include +#include +#include +#include + +#include + +#include "boot.h" + +/** + * dump_devices() - Log a table with all block devices. + */ +void lk2nd_boot_dump_devices() +{ + struct bdev_struct *bdevs = bio_get_bdevs(); + bdev_t *entry; + + dprintf(INFO, "block devices:\n"); + dprintf(INFO, " | dev | label | size | Sub |\n"); + list_for_every_entry(&bdevs->list, entry, bdev_t, node) { + dprintf(INFO, " | %-10s | %-10s | %6lld %s | %-3s |\n", + entry->name, + (entry->label ? entry->label : ""), + entry->size / (entry->size > 1024 * 1024 ? 1024*1024 : 1024), + (entry->size > 1024 * 1024 ? "MiB" : "KiB"), + (entry->is_subdev ? "Yes" : "") + ); + } +} + +/** + * print_file_tree(char *root, char *prefix) - Pretty print the fs tree. + * @root: path to recursively print. + * @prefix: Prefix for the tree, user should put "" here. + */ +void print_file_tree(char *root, char *prefix) +{ + struct filehandle *fileh; + struct dirhandle *dirh; + struct dirent dirent, next; + struct file_stat stat; + char path[129], pref[128]; + int ret, tmp; + + ret = fs_open_dir(root, &dirh); + if (ret < 0) { + dprintf(INFO, "fs_open_dir ret = %d\n", ret); + return; + } + + tmp = fs_read_dir(dirh, &dirent); + + while (tmp >= 0) { + if (!strcmp(dirent.name, ".") || !strcmp(dirent.name, "..") || *dirent.name == '\0') { + tmp = fs_read_dir(dirh, &dirent); + continue; + } + + snprintf(path, sizeof(path), "%s/%s", root, dirent.name); + + ret = fs_open_file(path, &fileh); + if (ret < 0) { + dprintf(INFO, "fs_open_file ret = %d\n", ret); + tmp = fs_read_dir(dirh, &dirent); + continue; + } + ret = fs_stat_file(fileh, &stat); + ret = fs_close_file(fileh); + + tmp = fs_read_dir(dirh, &next); + + dprintf(INFO, "%s%s-- %s%s [%lld %s]\n", prefix, (tmp < 0 ? "`" : "|"), + dirent.name, (stat.is_dir ? "/" : ""), + stat.size / (stat.size > 1024 * 1024 ? 1024*1024 : 1024), + (stat.size > 1024 * 1024 ? "MiB" : "KiB")); + + strcat(path, "/"); + if (stat.is_dir) { + strcpy(pref, prefix); + strcat(pref, (tmp < 0 ? " " : "| ")); + print_file_tree(path, pref); + } + dirent = next; + } + fs_close_dir(dirh); +} diff --git a/lk2nd/device/device.c b/lk2nd/device/device.c index 95790dc904..48311809b8 100644 --- a/lk2nd/device/device.c +++ b/lk2nd/device/device.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -108,6 +109,11 @@ static void parse_dtb(const void *dtb) else dprintf(CRITICAL, "Failed to read 'model': %d\n", len); + lk2nd_dev.dtbfiles = lkfdt_stringlist_get_all(dtb, node, "lk2nd,dtb-files", &len); + lk2nd_dev.dtbcount = len; + if (len < 0) + dprintf(CRITICAL, "Failed to read 'lk2nd,dtb-files': %d\n", len); + dprintf(INFO, "Detected device: %s (compatible: %s)\n", lk2nd_dev.model, lk2nd_dev.compatible); diff --git a/lk2nd/device/device.h b/lk2nd/device/device.h index 6407eca508..adacd45c45 100644 --- a/lk2nd/device/device.h +++ b/lk2nd/device/device.h @@ -20,6 +20,9 @@ struct lk2nd_device { const char *model; const char *battery; + const char **dtbfiles; + int dtbcount; + struct lk2nd_panel panel; #if WITH_LK2ND_DEVICE_2ND diff --git a/lk2nd/device/dts/msm8916/msm8916-qrd-9.dts b/lk2nd/device/dts/msm8916/msm8916-qrd-9.dts index 5311726aef..ef65169d4a 100644 --- a/lk2nd/device/dts/msm8916/msm8916-qrd-9.dts +++ b/lk2nd/device/dts/msm8916/msm8916-qrd-9.dts @@ -48,6 +48,7 @@ model = "Wileyfox Swift (Longcheer L8150)"; compatible = "wileyfox,crackling", "longcheer,l8150"; lk2nd,match-bootloader = "crackling-*"; + lk2nd,dtb-files = "qcom-msm8916-longcheer-l8150.dtb", "qcom/msm8916-longcheer-l8150.dtb"; panel { compatible = "longcheer,l8150-panel", "lk2nd,panel"; diff --git a/lk2nd/device/dts/msm8916/samsung.dts b/lk2nd/device/dts/msm8916/samsung.dts index 7b15e59d29..dc6918b96c 100644 --- a/lk2nd/device/dts/msm8916/samsung.dts +++ b/lk2nd/device/dts/msm8916/samsung.dts @@ -34,6 +34,8 @@ compatible = "samsung,gt58lte", "samsung,gt58"; lk2nd,match-bootloader = "T355*"; + lk2nd,dtb-files = "qcom/msm8916-samsung-gt58.dtb"; + qcom,msm-id = ; qcom,board-id = <0xCE08FF01 1>; @@ -51,6 +53,8 @@ compatible = "samsung,gt510lte", "samsung,gt510"; lk2nd,match-bootloader = "T555*"; + lk2nd,dtb-files = "qcom/msm8916-samsung-gt510.dtb"; + qcom,msm-id = ; qcom,board-id = <0xCE08FF01 7>; diff --git a/lk2nd/include/lk2nd/boot.h b/lk2nd/include/lk2nd/boot.h new file mode 100644 index 0000000000..ef3e935ff2 --- /dev/null +++ b/lk2nd/include/lk2nd/boot.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef LK2ND_BOOT_H +#define LK2ND_BOOT_H + +void lk2nd_boot(void); + +#endif /* LK2ND_BOOT_H */ + diff --git a/lk2nd/include/lk2nd/device.h b/lk2nd/include/lk2nd/device.h index e529021f20..6a22fbfb77 100644 --- a/lk2nd/include/lk2nd/device.h +++ b/lk2nd/include/lk2nd/device.h @@ -4,6 +4,9 @@ #include +// HACK XXX +#include "../../device/device.h" + unsigned char *lk2nd_device_update_cmdline(const char *cmdline, enum boot_type boot_type); bool lk2nd_device2nd_have_atags(void) __PURE; diff --git a/lk2nd/project/base.mk b/lk2nd/project/base.mk index 9b09ee2ad9..73f93910e8 100644 --- a/lk2nd/project/base.mk +++ b/lk2nd/project/base.mk @@ -16,6 +16,7 @@ GPL ?= 1 MODULES += \ lk2nd \ + lk2nd/boot \ lk2nd/fastboot \ lk2nd/fastboot/debug \ lk2nd/hw/bdev \