// SPDX-License-Identifier: GPL-2.0+ /* * Bootmethod for extlinux boot from a block device * * Copyright 2021 Google LLC * Written by Simon Glass */ #define LOG_CATEGORY UCLASS_BOOTSTD #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int extlinux_get_state_desc(struct udevice *dev, char *buf, int maxsize) { if (IS_ENABLED(CONFIG_SANDBOX)) { int len; len = snprintf(buf, maxsize, "OK"); return len + 1 < maxsize ? 0 : -ENOSPC; } return 0; } static int extlinux_getfile(struct pxe_context *ctx, const char *file_path, ulong *addrp, ulong align, enum bootflow_img_t type, ulong *sizep) { struct extlinux_info *info = ctx->userdata; int ret; /* Allow up to 1GB */ *sizep = 1 << 30; ret = bootmeth_read_file(info->dev, info->bflow, file_path, addrp, align, type, sizep); if (ret) return log_msg_ret("read", ret); return 0; } static int extlinux_check(struct udevice *dev, struct bootflow_iter *iter) { int ret; /* This only works on block devices */ ret = bootflow_iter_check_blk(iter); if (ret) return log_msg_ret("blk", ret); return 0; } /** * extlinux_check_luks() - Check for LUKS encryption on other partitions * * This scans all partitions on the same device to check for LUKS encryption. * If found, it marks this bootflow as encrypted since it likely boots from * an encrypted root partition. * * @bflow: Bootflow to potentially mark as encrypted * Return: 0 on success, -ve on error */ static int extlinux_check_luks(struct bootflow *bflow) { struct blk_desc *desc; struct disk_partition info; int ret, part; if (!IS_ENABLED(CONFIG_BLK_LUKS) || !bflow->blk) return 0; desc = dev_get_uclass_plat(bflow->blk); if (!desc || !desc->bdev) return 0; /* * Check all partitions on this device for LUKS encryption. * Typically partition 1 has the bootloader files and partition 2 * has the encrypted root filesystem. Check up to 10 partitions. */ for (part = 1; part <= 10; part++) { ret = part_get_info(desc, part, &info); if (ret) continue; /* Partition doesn't exist */ ret = luks_detect(desc->bdev, &info); if (!ret) { int luks_ver = luks_get_version(desc->bdev, &info); log_debug("LUKS partition %d detected (v%d), marking bootflow as encrypted\n", part, luks_ver); bflow->flags |= BOOTFLOWF_ENCRYPTED; bflow->luks_version = luks_ver; return 0; } } return 0; } /** * extlinux_fill_info() - Decode the extlinux file to find out its info * * @bflow: Bootflow to process * @return 0 if OK, -ve on error */ static int extlinux_fill_info(struct bootflow *bflow) { struct membuf mb; char line[200]; char *data; int len; log_debug("parsing bflow file size %x\n", bflow->size); membuf_init(&mb, bflow->buf, bflow->size); membuf_putraw(&mb, bflow->size, true, &data); while (len = membuf_readline(&mb, line, sizeof(line) - 1, 0, true), len) { char *tok, *p = line; const char *name = NULL; if (*p == '#') continue; while (*p == ' ' || *p == '\t') p++; tok = strsep(&p, " "); if (p) { if (!strcmp("label", tok)) { name = p; if (bflow->os_name) break; /* just find the first */ } else if (!strcmp("menu", tok)) { tok = strsep(&p, " "); if (!strcmp("label", tok)) { name = p; } } if (name) { free(bflow->os_name); bflow->os_name = strdup(name); if (!bflow->os_name) return log_msg_ret("os", -ENOMEM); } } } return 0; } static int extlinux_read_bootflow(struct udevice *dev, struct bootflow *bflow) { struct blk_desc *desc; const char *const *prefixes; struct udevice *bootstd; const char *prefix; loff_t size; int ret, i; log_debug("starting part %d\n", bflow->part); ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); if (ret) { log_debug("no bootstd\n"); return log_msg_ret("std", ret); } /* If a block device, we require a partition table */ if (bflow->blk && !bflow->part) { log_debug("no partition table\n"); return -ENOENT; } prefixes = bootstd_get_prefixes(bootstd); i = 0; desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL; do { prefix = prefixes ? prefixes[i] : NULL; log_debug("try prefix %s\n", prefix); ret = bootmeth_try_file(bflow, desc, prefix, EXTLINUX_FNAME); } while (ret && prefixes && prefixes[++i]); if (ret) { log_debug("no file found\n"); return log_msg_ret("try", ret); } size = bflow->size; ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN, BFI_EXTLINUX_CFG); if (ret) return log_msg_ret("read", ret); ret = extlinux_fill_info(bflow); if (ret) return log_msg_ret("inf", ret); ret = extlinux_check_luks(bflow); if (ret) return log_msg_ret("luks", ret); return 0; } static int extlinux_local_boot(struct udevice *dev, struct bootflow *bflow) { return extlinux_boot(dev, bflow, extlinux_getfile, true, bflow->fname, false); } #if CONFIG_IS_ENABLED(BOOTSTD_FULL) static int extlinux_local_read_all(struct udevice *dev, struct bootflow *bflow) { return extlinux_read_all(dev, bflow, extlinux_getfile, true, bflow->fname); } #endif static int extlinux_bootmeth_bind(struct udevice *dev) { struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? "Extlinux boot from a block device" : "extlinux"; return 0; } static struct bootmeth_ops extlinux_bootmeth_ops = { .get_state_desc = extlinux_get_state_desc, .check = extlinux_check, .read_bootflow = extlinux_read_bootflow, .read_file = bootmeth_common_read_file, .boot = extlinux_local_boot, .set_property = extlinux_set_property, #if CONFIG_IS_ENABLED(BOOTSTD_FULL) .read_all = extlinux_local_read_all, #endif }; static const struct udevice_id extlinux_bootmeth_ids[] = { { .compatible = "u-boot,extlinux" }, { } }; /* Put a number before 'extlinux' to provide a default ordering */ U_BOOT_DRIVER(bootmeth_1extlinux) = { .name = "bootmeth_extlinux", .id = UCLASS_BOOTMETH, .of_match = extlinux_bootmeth_ids, .ops = &extlinux_bootmeth_ops, .bind = extlinux_bootmeth_bind, .plat_auto = sizeof(struct extlinux_plat) };