diff options
-rw-r--r-- | commands/boot.c | 168 | ||||
-rw-r--r-- | common/Kconfig | 14 | ||||
-rw-r--r-- | common/Makefile | 1 | ||||
-rw-r--r-- | common/blspec.c | 516 | ||||
-rw-r--r-- | include/blspec.h | 92 |
5 files changed, 768 insertions, 23 deletions
diff --git a/commands/boot.c b/commands/boot.c index 33d1177861..78508056e6 100644 --- a/commands/boot.c +++ b/commands/boot.c @@ -15,51 +15,155 @@ #include <globalvar.h> #include <magicvar.h> #include <command.h> +#include <readkey.h> #include <common.h> #include <getopt.h> +#include <blspec.h> #include <libgen.h> #include <malloc.h> #include <boot.h> +#include <menu.h> #include <fs.h> +#include <complete.h> #include <linux/stat.h> +static int boot_script(char *path); + static int verbose; static int dryrun; -static void bootsources_list(void) +static void bootsource_action(struct menu *m, struct menu_entry *me) +{ + struct blspec_entry *be = container_of(me, struct blspec_entry, me); + int ret; + + if (be->scriptpath) { + ret = boot_script(be->scriptpath); + } else { + if (IS_ENABLED(CONFIG_BLSPEC)) + ret = blspec_boot(be, 0, 0); + else + ret = -ENOSYS; + } + + if (ret) + printf("Booting failed with: %s\n", strerror(-ret)); + + printf("Press any key to continue\n"); + + read_key(); +} + +static int bootsources_menu_env_entries(struct blspec *blspec) { + const char *path = "/env/boot", *title; DIR *dir; struct dirent *d; - const char *path = "/env/boot"; + struct blspec_entry *be; + char *cmd; dir = opendir(path); - if (!dir) { - printf("cannot open %s: %s\n", path, strerror(-errno)); - return; - } - - printf("Bootsources: "); + if (!dir) + return -errno; while ((d = readdir(dir))) { + if (*d->d_name == '.') continue; - printf("%s ", d->d_name); - } + be = blspec_entry_alloc(blspec); + be->me.type = MENU_ENTRY_NORMAL; + be->scriptpath = asprintf("/env/boot/%s", d->d_name); + + cmd = asprintf(". %s menu", be->scriptpath); + setenv("title", ""); + run_command(cmd, 0); + free(cmd); + title = getenv("title"); - printf("\n"); + if (title) + be->me.display = xstrdup(title); + else + be->me.display = xstrdup(d->d_name); + } closedir(dir); + + return 0; } -static const char *getenv_or_null(const char *var) +static struct blspec *bootentries_collect(void) { - const char *val = getenv(var); + struct blspec *blspec; + + blspec = blspec_alloc(); + blspec->menu->display = asprintf("boot"); + bootsources_menu_env_entries(blspec); + if (IS_ENABLED(CONFIG_BLSPEC)) + blspec_scan_devices(blspec); + return blspec; +} - if (val && *val) - return val; - return NULL; +static void bootsources_menu(void) +{ + struct blspec *blspec = NULL; + struct blspec_entry *entry; + struct menu_entry *back_entry; + + if (!IS_ENABLED(CONFIG_MENU)) { + printf("no menu support available\n"); + return; + } + + blspec = bootentries_collect(); + + blspec_for_each_entry(blspec, entry) { + entry->me.action = bootsource_action; + menu_add_entry(blspec->menu, &entry->me); + } + + back_entry = xzalloc(sizeof(*back_entry)); + back_entry->display = "back"; + back_entry->type = MENU_ENTRY_NORMAL; + back_entry->non_re_ent = 1; + menu_add_entry(blspec->menu, back_entry); + + menu_show(blspec->menu); + + free(back_entry); + + blspec_free(blspec); +} + +static void bootsources_list(void) +{ + struct blspec *blspec; + struct blspec_entry *entry; + + blspec = bootentries_collect(); + + printf("\nBootscripts:\n\n"); + printf("%-40s %-20s\n", "name", "title"); + printf("%-40s %-20s\n", "----", "-----"); + + blspec_for_each_entry(blspec, entry) { + if (entry->scriptpath) + printf("%-40s %s\n", basename(entry->scriptpath), entry->me.display); + } + + if (!IS_ENABLED(CONFIG_BLSPEC)) + return; + + printf("\nBootloader spec entries:\n\n"); + printf("%-20s %-20s %s\n", "device", "hwdevice", "title"); + printf("%-20s %-20s %s\n", "------", "--------", "-----"); + + blspec_for_each_entry(blspec, entry) + if (!entry->scriptpath) + printf("%s\n", entry->me.display); + + blspec_free(blspec); } /* @@ -67,8 +171,11 @@ static const char *getenv_or_null(const char *var) */ static int boot_script(char *path) { - struct bootm_data data = {}; int ret; + struct bootm_data data = { + .os_address = UIMAGE_SOME_ADDRESS, + .initrd_address = UIMAGE_SOME_ADDRESS, + }; printf("booting %s...\n", basename(path)); @@ -83,11 +190,11 @@ static int boot_script(char *path) data.initrd_address = UIMAGE_INVALID_ADDRESS; data.os_address = UIMAGE_SOME_ADDRESS; - data.oftree_file = getenv_or_null("global.bootm.oftree"); - data.os_file = getenv_or_null("global.bootm.image"); + data.oftree_file = getenv_nonempty("global.bootm.oftree"); + data.os_file = getenv_nonempty("global.bootm.image"); getenv_ul("global.bootm.image.loadaddr", &data.os_address); getenv_ul("global.bootm.initrd.loadaddr", &data.initrd_address); - data.initrd_file = getenv_or_null("global.bootm.initrd"); + data.initrd_file = getenv_nonempty("global.bootm.initrd"); data.verbose = verbose; data.dryrun = dryrun; @@ -118,7 +225,13 @@ static int boot(const char *name) ret = stat(path, &s); if (ret) { - pr_err("%s: %s\n", path, strerror(-ret)); + if (!IS_ENABLED(CONFIG_BLSPEC)) { + pr_err("%s: %s\n", path, strerror(-ret)); + goto out; + } + + ret = blspec_boot_hwdevice(name, verbose, dryrun); + pr_err("%s: %s\n", name, strerror(-ret)); goto out; } @@ -173,12 +286,12 @@ static int do_boot(int argc, char *argv[]) { const char *sources = NULL; char *source, *freep; - int opt, ret = 0, do_list = 0; + int opt, ret = 0, do_list = 0, do_menu = 0; verbose = 0; dryrun = 0; - while ((opt = getopt(argc, argv, "vld")) > 0) { + while ((opt = getopt(argc, argv, "vldm")) > 0) { switch (opt) { case 'v': verbose++; @@ -189,6 +302,9 @@ static int do_boot(int argc, char *argv[]) case 'd': dryrun = 1; break; + case 'm': + do_menu = 1; + break; } } @@ -197,6 +313,11 @@ static int do_boot(int argc, char *argv[]) return 0; } + if (do_menu) { + bootsources_menu(); + return 0; + } + if (optind < argc) { while (optind < argc) { source = argv[optind]; @@ -247,6 +368,7 @@ BAREBOX_CMD_HELP_SHORT("\nOptions:\n") BAREBOX_CMD_HELP_OPT ("-v","Increase verbosity\n") BAREBOX_CMD_HELP_OPT ("-d","Dryrun. See what happens but do no actually boot\n") BAREBOX_CMD_HELP_OPT ("-l","List available boot sources\n") +BAREBOX_CMD_HELP_OPT ("-m","Show a menu with boot options\n") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(boot) diff --git a/common/Kconfig b/common/Kconfig index 13419dc5bd..dd60ec9a26 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -437,6 +437,20 @@ config TIMESTAMP commands like bootm or iminfo. This option is automatically enabled when you select CFG_CMD_DATE . +config BLSPEC + depends on BLOCK + select OFTREE + select FLEXIBLE_BOOTARGS + bool + prompt "Support bootloader spec" + help + Enable this to let barebox support the Freedesktop bootloader spec, + see: http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/ + The bootloader spec is a standard interface between the bootloader + and the kernel. It allows the bootloader to discover boot options + on a device and it allows the Operating System to install / update + kernels. + choice prompt "console support" default CONSOLE_FULL diff --git a/common/Makefile b/common/Makefile index 9a9e3fe7d9..75d924e90f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_PARTITION_DISK) += partitions.o partitions/ obj-$(CONFIG_CMD_LOADS) += s_record.o obj-$(CONFIG_OFTREE) += oftree.o +obj-$(CONFIG_BLSPEC) += blspec.o obj-y += memory.o obj-$(CONFIG_DDR_SPD) += ddr_spd.o obj-y += memory_display.o diff --git a/common/blspec.c b/common/blspec.c new file mode 100644 index 0000000000..f306adac0c --- /dev/null +++ b/common/blspec.c @@ -0,0 +1,516 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <environment.h> +#include <globalvar.h> +#include <readkey.h> +#include <common.h> +#include <driver.h> +#include <blspec.h> +#include <malloc.h> +#include <block.h> +#include <fcntl.h> +#include <libbb.h> +#include <init.h> +#include <boot.h> +#include <fs.h> +#include <of.h> +#include <linux/stat.h> +#include <linux/err.h> + +/* + * blspec_entry_var_set - set a variable to a value + */ +int blspec_entry_var_set(struct blspec_entry *entry, const char *name, + const char *val) +{ + return of_set_property(entry->node, name, val, + val ? strlen(val) + 1 : 0, 1); +} + +/* + * blspec_entry_var_get - get the value of a variable + */ +const char *blspec_entry_var_get(struct blspec_entry *entry, const char *name) +{ + const char *str; + int ret; + + ret = of_property_read_string(entry->node, name, &str); + + return ret ? NULL : str; +} + +/* + * blspec_entry_open - open an entry given a path + */ +static struct blspec_entry *blspec_entry_open(struct blspec *blspec, + const char *abspath) +{ + struct blspec_entry *entry; + char *end, *line, *next; + char *buf; + + pr_debug("%s: %s\n", __func__, abspath); + + buf = read_file(abspath, NULL); + if (!buf) + return ERR_PTR(-errno); + + entry = blspec_entry_alloc(blspec); + + next = buf; + + while (*next) { + char *name, *val; + + line = next; + + next = strchr(line, '\n'); + if (next) { + *next = 0; + next++; + } + + name = line; + end = name; + + while (*end && (*end != ' ' && *end != '\t')) + end++; + + if (!*end) { + blspec_entry_var_set(entry, name, NULL); + continue; + } + + *end = 0; + + end++; + + while (*end == ' ' || *end == '\t') + end++; + + if (!*end) { + blspec_entry_var_set(entry, name, NULL); + continue; + } + + val = end; + + blspec_entry_var_set(entry, name, val); + } + + free(buf); + + return entry; +} + +/* + * blspec_have_entry - check if we already have an entry with + * a certain path + */ +static int blspec_have_entry(struct blspec *blspec, const char *path) +{ + struct blspec_entry *e; + + list_for_each_entry(e, &blspec->entries, list) { + if (e->configpath && !strcmp(e->configpath, path)) + return 1; + } + + return 0; +} + +/* + * blspec_scan_directory - scan over a directory + * + * Given a root path collects all blspec entries found under /blspec/entries/. + * + * returns 0 if at least one entry could be successfully loaded, negative + * error value otherwise. + */ +static int blspec_scan_directory(struct blspec *blspec, const char *root, + struct cdev *cdev) +{ + struct blspec_entry *entry; + DIR *dir; + struct dirent *d; + char *abspath; + int ret, found = 0; + const char *dirname = "loader/entries"; + char *entry_default = NULL, *entry_once = NULL; + + pr_debug("%s: %s %s\n", __func__, root, dirname); + + entry_default = read_file_line("%s/default", root); + entry_once = read_file_line("%s/once", root); + + abspath = asprintf("%s/%s", root, dirname); + + dir = opendir(abspath); + if (!dir) { + pr_debug("%s: %s: %s\n", __func__, abspath, strerror(errno)); + ret = -errno; + goto err_out; + } + + while ((d = readdir(dir))) { + char *configname; + struct stat s; + char *dot; + char *devname = NULL, *hwdevname = NULL; + + if (*d->d_name == '.') + continue; + + configname = asprintf("%s/%s", abspath, d->d_name); + + dot = strrchr(configname, '.'); + if (!dot) { + free(configname); + continue; + } + + if (strcmp(dot, ".conf")) { + free(configname); + continue; + } + + ret = stat(configname, &s); + if (ret) { + free(configname); + continue; + } + + if (!S_ISREG(s.st_mode)) { + free(configname); + continue; + } + + if (blspec_have_entry(blspec, configname)) { + free(configname); + continue; + } + + entry = blspec_entry_open(blspec, configname); + if (IS_ERR(entry)) { + free(configname); + continue; + } + + found = 1; + + entry->rootpath = xstrdup(root); + entry->configpath = configname; + entry->cdev = cdev; + + if (entry_default && !strcmp(d->d_name, entry_default)) + entry->boot_default = true; + if (entry_once && !strcmp(d->d_name, entry_once)) + entry->boot_once = true; + + devname = xstrdup(dev_name(entry->cdev->dev)); + if (entry->cdev->dev->parent) + hwdevname = xstrdup(dev_name(entry->cdev->dev->parent)); + + entry->me.display = asprintf("%-20s %-20s %s", devname, hwdevname, + blspec_entry_var_get(entry, "title")); + free(devname); + free(hwdevname); + + entry->me.type = MENU_ENTRY_NORMAL; + } + + ret = found ? 0 : -ENOENT; + + closedir(dir); +err_out: + free(abspath); + free(entry_default); + free(entry_once); + + return ret; +} + +/* + * blspec_scan_cdev - scan over a cdev + * + * Given a cdev this function mounts the filesystem and collects all blspec + * entries found under /blspec/entries/. + * + * returns 0 if at least one entry could be successfully loaded, negative + * error value otherwise. + */ +static int blspec_scan_cdev(struct blspec *blspec, struct cdev *cdev) +{ + int ret; + void *buf = xzalloc(512); + enum filetype type; + const char *rootpath; + + pr_debug("%s: %s\n", __func__, cdev->name); + + ret = cdev_read(cdev, buf, 512, 0, 0); + if (ret < 0) { + free(buf); + return ret; + } + + type = file_detect_partition_table(buf, 512); + free(buf); + + if (type == filetype_mbr || type == filetype_gpt) + return -EINVAL; + + rootpath = cdev_mount_default(cdev); + if (IS_ERR(rootpath)) + return PTR_ERR(rootpath); + + ret = blspec_scan_directory(blspec, rootpath, cdev); + + return ret; +} + +/* + * blspec_scan_devices - scan all devices for child cdevs + * + * Iterate over all devices and collect child their cdevs. + */ +void blspec_scan_devices(struct blspec *blspec) +{ + struct device_d *dev; + struct block_device *bdev; + + for_each_device(dev) + device_detect(dev); + + for_each_block_device(bdev) { + struct cdev *cdev = &bdev->cdev; + + list_for_each_entry(cdev, &bdev->dev->cdevs, devices_list) + blspec_scan_cdev(blspec, cdev); + } +} + +/* + * blspec_scan_device - scan a device for child cdevs + * + * Given a device this functions scans over all child cdevs looking + * for blspec entries. + */ +int blspec_scan_device(struct blspec *blspec, struct device_d *dev) +{ + struct device_d *child; + struct cdev *cdev; + int ret; + + pr_debug("%s: %s\n", __func__, dev_name(dev)); + + list_for_each_entry(cdev, &dev->cdevs, devices_list) { + /* + * If the OS is installed on a disk with MBR disk label, and a + * partition with the MBR type id of 0xEA already exists it + * should be used as $BOOT + */ + if (cdev->dos_partition_type == 0xea) { + blspec_scan_cdev(blspec, cdev); + return 0; + } + + /* + * If the OS is installed on a disk with GPT disk label, and a + * partition with the GPT type GUID of + * bc13c2ff-59e6-4262-a352-b275fd6f7172 already exists, it + * should be used as $BOOT. + * + * Not yet implemented + */ + } + + /* Try child devices */ + device_for_each_child(dev, child) { + ret = blspec_scan_device(blspec, child); + if (!ret) + return 0; + } + + /* + * As a last resort try all cdevs (Not only the ones explicitly stated + * by the bootblspec spec). + */ + list_for_each_entry(cdev, &dev->cdevs, devices_list) { + ret = blspec_scan_cdev(blspec, cdev); + if (!ret) + return 0; + } + + return -ENODEV; +} + +/* + * blspec_scan_hwdevice - scan a hardware device for child cdevs + * + * Given a name of a hardware device this functions scans over all child + * cdevs looking for blspec entries. + */ +int blspec_scan_hwdevice(struct blspec *blspec, const char *devname) +{ + struct device_d *dev; + + pr_debug("%s: %s\n", __func__, devname); + + dev = get_device_by_name(devname); + if (!dev) + return -ENODEV; + + device_detect(dev); + + blspec_scan_device(blspec, dev); + + return 0; +} + +/* + * blspec_boot - boot an entry + * + * This boots an entry. On success this function does not return. + * In case of an error the error code is returned. This function may + * return 0 in case of a succesful dry run. + */ +int blspec_boot(struct blspec_entry *entry, int verbose, int dryrun) +{ + int ret; + const char *abspath, *devicetree, *options, *initrd, *linuximage; + struct bootm_data data = { + .initrd_address = UIMAGE_INVALID_ADDRESS, + .os_address = UIMAGE_SOME_ADDRESS, + .verbose = verbose, + .dryrun = dryrun, + }; + + globalvar_set_match("linux.bootargs.dyn.", ""); + globalvar_set_match("bootm.", ""); + + devicetree = blspec_entry_var_get(entry, "devicetree"); + initrd = blspec_entry_var_get(entry, "initrd"); + options = blspec_entry_var_get(entry, "options"); + linuximage = blspec_entry_var_get(entry, "linux"); + + if (entry->rootpath) + abspath = entry->rootpath; + else + abspath = ""; + + data.os_file = asprintf("%s/%s", abspath, linuximage); + + if (devicetree) { + if (!strcmp(devicetree, "none")) { + struct device_node *node = of_get_root_node(); + if (node) + of_delete_node(node); + } else { + data.oftree_file = asprintf("%s/%s", abspath, + devicetree); + } + } + + if (initrd) + data.initrd_file = asprintf("%s/%s", abspath, initrd); + + globalvar_add_simple("linux.bootargs.blspec", options); + + pr_info("booting %s from %s\n", blspec_entry_var_get(entry, "title"), + dev_name(entry->cdev->dev)); + + if (entry->boot_once) { + char *s = asprintf("%s/once", abspath); + + ret = unlink(s); + if (ret) + pr_err("unable to unlink 'once': %s\n", strerror(-ret)); + else + pr_info("removed 'once'\n"); + + free(s); + } + + ret = bootm_boot(&data); + if (ret) + pr_err("Booting failed\n"); + + free((char *)data.oftree_file); + free((char *)data.initrd_file); + free((char *)data.os_file); + + return ret; +} + +/* + * blspec_entry_default - find the entry to load. + * + * return in the order of precendence: + * - The entry specified in the 'once' file + * - The entry specified in the 'default' file + * - The first entry + */ +struct blspec_entry *blspec_entry_default(struct blspec *l) +{ + struct blspec_entry *entry_once = NULL; + struct blspec_entry *entry_default = NULL; + struct blspec_entry *entry_first = NULL; + struct blspec_entry *e; + + list_for_each_entry(e, &l->entries, list) { + if (!entry_first) + entry_first = e; + if (e->boot_once) + entry_once = e; + if (e->boot_default) + entry_default = e; + } + + if (entry_once) + return entry_once; + if (entry_default) + return entry_default; + return entry_first; +} + +/* + * blspec_boot_hwdevice - scan hardware device for blspec entries and + * start the best one. + */ +int blspec_boot_hwdevice(const char *devname, int verbose, int dryrun) +{ + struct blspec *blspec; + struct blspec_entry *e; + int ret; + + blspec = blspec_alloc(); + + ret = blspec_scan_hwdevice(blspec, devname); + if (ret) + return ret; + + e = blspec_entry_default(blspec); + if (!e) { + printf("Nothing found on %s\n", devname); + ret = -ENOENT; + goto out; + } + + ret = blspec_boot(e, verbose, dryrun); +out: + blspec_free(blspec); + + return ret; +} diff --git a/include/blspec.h b/include/blspec.h new file mode 100644 index 0000000000..8422e5b598 --- /dev/null +++ b/include/blspec.h @@ -0,0 +1,92 @@ +#ifndef __LOADER_H__ +#define __LOADER_H__ + +#include <linux/list.h> +#include <menu.h> + +struct blspec { + struct list_head entries; + struct menu *menu; +}; + +struct blspec_entry { + struct list_head list; + struct device_node *node; + struct cdev *cdev; + char *rootpath; + char *configpath; + bool boot_default; + bool boot_once; + + struct menu_entry me; + + char *scriptpath; +}; + +int blspec_entry_var_set(struct blspec_entry *entry, const char *name, + const char *val); +const char *blspec_entry_var_get(struct blspec_entry *entry, const char *name); + +int blspec_entry_save(struct blspec_entry *entry, const char *path); + +int blspec_boot(struct blspec_entry *entry, int verbose, int dryrun); + +int blspec_boot_hwdevice(const char *devname, int verbose, int dryrun); + +void blspec_scan_devices(struct blspec *blspec); + +struct blspec_entry *blspec_entry_default(struct blspec *l); +int blspec_scan_hwdevice(struct blspec *blspec, const char *devname); + +#define blspec_for_each_entry(blspec, entry) \ + list_for_each_entry(entry, &blspec->entries, list) + +static inline struct blspec_entry *blspec_entry_alloc(struct blspec *blspec) +{ + struct blspec_entry *entry; + + entry = xzalloc(sizeof(*entry)); + + entry->node = of_new_node(NULL, NULL); + + list_add_tail(&entry->list, &blspec->entries); + + return entry; +} + +static inline void blspec_entry_free(struct blspec_entry *entry) +{ + list_del(&entry->list); + of_delete_node(entry->node); + free(entry->me.display); + free(entry->scriptpath); + free(entry->configpath); + free(entry->rootpath); + free(entry); +} + +static inline struct blspec *blspec_alloc(void) +{ + struct blspec *blspec; + + blspec = xzalloc(sizeof(*blspec)); + INIT_LIST_HEAD(&blspec->entries); + + if (IS_ENABLED(CONFIG_MENU)) + blspec->menu = menu_alloc(); + + return blspec; +} + +static inline void blspec_free(struct blspec *blspec) +{ + struct blspec_entry *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &blspec->entries, list) + blspec_entry_free(entry); + free(blspec->menu->display); + free(blspec->menu); + free(blspec); +} + +#endif /* __LOADER_H__ */ |