diff --git a/Makefile b/Makefile index c5cc8276fa0..81f49b791e6 100644 --- a/Makefile +++ b/Makefile @@ -1692,6 +1692,8 @@ cmd_binman = $(srctree)/tools/binman/binman $(if $(BINMAN_DEBUG),-D) \ $(if $(KEYDIR),-a keydir=$(KEYDIR)) \ -a atf-bl1-path=${BL1} \ -a atf-bl31-path=${BL31} \ + -a linux-kernel-path=${LINUX_KERNEL} \ + -a linux-initrd-path=${LINUX_INITRD} \ -a tee-os-path=${TEE} \ -a ti-dm-path=${TI_DM} \ -a opensbi-path=${OPENSBI} \ diff --git a/arch/arm/dts/rk3576-flipper-one-rev-f0b0c1-u-boot.dtsi b/arch/arm/dts/rk3576-flipper-one-rev-f0b0c1-u-boot.dtsi new file mode 100644 index 00000000000..d1b0637983b --- /dev/null +++ b/arch/arm/dts/rk3576-flipper-one-rev-f0b0c1-u-boot.dtsi @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2026 Flipper FZCO + */ + +#include "rk3576-u-boot.dtsi" + +/ { + bootstd { + compatible = "u-boot,boot-std"; + + theme { + menu-pos-x = <8>; + menu-inset = <0>; + font-size = <8>; + }; + }; +}; + +/* + * The video driver needs its framebuffer reserved before relocation. + * Mark spi0 and the display node so they are bound during the pre-relocation + * DM scan and video_reserve() can carve out the framebuffer memory. + */ +&spi0 { + bootph-all; + + display@0 { + bootph-all; + }; +}; diff --git a/arch/arm/dts/rk3576-nanopi-m5-u-boot.dtsi b/arch/arm/dts/rk3576-nanopi-m5-u-boot.dtsi index 0767fbb8168..aa939f99f31 100644 --- a/arch/arm/dts/rk3576-nanopi-m5-u-boot.dtsi +++ b/arch/arm/dts/rk3576-nanopi-m5-u-boot.dtsi @@ -15,6 +15,9 @@ }; &sfc1 { + pinctrl-0 = <&fspi1m1_csn0 &fspi1m1_pins>; + pinctrl-names = "default"; + flash@0 { bootph-pre-ram; bootph-some-ram; diff --git a/arch/arm/dts/rk3576-u-boot.dtsi b/arch/arm/dts/rk3576-u-boot.dtsi index 8db9495c6d4..1b144c3d20e 100644 --- a/arch/arm/dts/rk3576-u-boot.dtsi +++ b/arch/arm/dts/rk3576-u-boot.dtsi @@ -124,7 +124,16 @@ }; &pinctrl { - bootph-all; + /* + * Keep pinctrl in SPL (bootph-pre-ram), but NOT in U-Boot proper's + * board_init_f: bootph-all would force a pre-relocation bind, and the + * driver's .bind = dm_scan_fdt_dev then walks every pin-group child + * against the flat DT with the D-cache off (~0.6s). With bootph-pre-ram + * the node binds in board_init_r instead (live tree + cache, ~13ms). + * Console/PMIC pins are already muxed by SPL, so pre-reloc pinctrl is + * not needed here. + */ + bootph-pre-ram; }; &pmu1_grf { diff --git a/arch/arm/dts/rockchip-u-boot.dtsi b/arch/arm/dts/rockchip-u-boot.dtsi index 4ba6a87e78a..5e23ff76175 100644 --- a/arch/arm/dts/rockchip-u-boot.dtsi +++ b/arch/arm/dts/rockchip-u-boot.dtsi @@ -35,6 +35,16 @@ #define HAS_FIT #endif +#ifdef CONFIG_ROCKCHIP_FALCON_IMAGE +/* Falcon-mode load addresses, relative to the SoC DRAM base. */ +#define FALCON_KERNEL_LOAD \ + (CFG_SYS_SDRAM_BASE + CONFIG_ROCKCHIP_FALCON_KERNEL_LOAD) +#define FALCON_FDT_LOAD \ + (CFG_SYS_SDRAM_BASE + CONFIG_ROCKCHIP_FALCON_FDT_LOAD) +#define FALCON_INITRD_LOAD \ + (CFG_SYS_SDRAM_BASE + CONFIG_ROCKCHIP_FALCON_INITRD_LOAD) +#endif + / { binman: binman { multiple-images; @@ -44,37 +54,13 @@ #ifdef CONFIG_SPL &binman { #ifdef HAS_FIT - fit_template: template-1 { - type = "fit"; #ifdef CONFIG_ARM64 - description = "FIT image for U-Boot with bl31 (TF-A)"; -#else - description = "FIT image with OP-TEE"; -#endif - #address-cells = <1>; - fit,fdt-list = "of-list"; - fit,external-offset = ; - fit,align = <512>; + /* + * Shared ARM Trusted Firmware (BL31) and optional OP-TEE split-elf + * images, reused by both the U-Boot and Falcon Linux FIT templates. + */ + fit_firmware_template: template-firmware { images { - u-boot { - description = "U-Boot"; - type = "standalone"; - os = "u-boot"; - arch = FIT_ARCH; - compression = FIT_UBOOT_COMP; - load = ; - entry = ; - u-boot-nodtb { - compress = FIT_UBOOT_COMP; - }; -#ifdef FIT_HASH_ALGO - hash { - algo = FIT_HASH_ALGO; - }; -#endif - }; - -#ifdef CONFIG_ARM64 @atf-SEQ { fit,operation = "split-elf"; description = "ARM Trusted Firmware"; @@ -114,7 +100,42 @@ }; #endif }; -#else /* !CONFIG_ARM64 */ + }; + }; +#endif /* CONFIG_ARM64 */ + + fit_template: template-1 { + type = "fit"; +#ifdef CONFIG_ARM64 + description = "FIT image for U-Boot with bl31 (TF-A)"; + insert-template = <&fit_firmware_template>; +#else + description = "FIT image with OP-TEE"; +#endif + #address-cells = <1>; + fit,fdt-list = "of-list"; + fit,external-offset = ; + fit,align = <512>; + images { + u-boot { + description = "U-Boot"; + type = "standalone"; + os = "u-boot"; + arch = FIT_ARCH; + compression = FIT_UBOOT_COMP; + load = ; + entry = ; + u-boot-nodtb { + compress = FIT_UBOOT_COMP; + }; +#ifdef FIT_HASH_ALGO + hash { + algo = FIT_HASH_ALGO; + }; +#endif + }; + +#ifndef CONFIG_ARM64 op-tee { description = "OP-TEE"; type = "tee"; @@ -132,7 +153,7 @@ }; #endif }; -#endif /* CONFIG_ARM64 */ +#endif /* !CONFIG_ARM64 */ @fdt-SEQ { description = "fdt-NAME"; @@ -161,6 +182,97 @@ }; }; }; + +#ifdef CONFIG_ROCKCHIP_FALCON_IMAGE + fit_falcon_template: template-falcon { + type = "fit"; + description = "FIT image for Falcon Linux with bl31 (TF-A)"; + insert-template = <&fit_firmware_template>; + #address-cells = <1>; + fit,fdt-list = "of-list"; + fit,external-offset = ; + fit,align = <512>; + + images { + kernel { + description = "Linux kernel"; + type = "kernel"; + arch = FIT_ARCH; + os = "linux"; + compression = FIT_UBOOT_COMP; + load = ; + entry = ; + + linux-kernel { + compress = FIT_UBOOT_COMP; + }; +#ifdef FIT_HASH_ALGO + hash { + algo = FIT_HASH_ALGO; + }; +#endif + }; + + ramdisk { + description = "ramdisk"; + type = "ramdisk"; + arch = FIT_ARCH; + compression = "none"; + load = ; + + linux-initrd { + optional; + }; +#ifdef FIT_HASH_ALGO + hash { + algo = FIT_HASH_ALGO; + }; +#endif + }; + + @fdt-SEQ { + description = "fdt-NAME"; + type = "flat_dt"; + arch = FIT_ARCH; + compression = "none"; + load = ; +#ifdef CONFIG_USE_BOOTARGS + fit,bootargs = CONFIG_BOOTARGS; +#endif + fit,initrd = "ramdisk"; +#ifdef FIT_HASH_ALGO + hash { + algo = FIT_HASH_ALGO; + }; +#endif + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "NAME.dtb"; + fit,firmware = "atf-1", "kernel", "ramdisk"; + fit,loadables; + fit,compatible; + fdt = "fdt-SEQ"; +#ifdef FIT_HASH_ALGO + hash { + algo = FIT_HASH_ALGO; + }; +#endif + }; + }; + }; + + falcon-fit { + filename = "u-boot-rockchip-falcon.itb"; + + fit { + insert-template = <&fit_falcon_template>; + }; + }; +#endif #endif /* HAS_FIT */ simple-bin { @@ -260,6 +372,36 @@ }; }; }; + +#ifdef CONFIG_ROCKCHIP_FALCON_IMAGE + simple-bin-usb472-falcon { + filename = "u-boot-rockchip-usb472-falcon.bin"; + pad-byte = <0x00>; + + u-boot-spl { + }; + + payload { + type = "section"; + align = ; + /* + * Must be named 'u-boot-any', unlike the non-Falcon + * image which can call this 'fit': SPL declares + * binman_sym(u_boot_any, image_pos) unconditionally, and + * binman resolves that symbol against an entry of that + * exact name or one called u-boot[-elf|-img|-nodtb]. + * Those only exist here by way of a U-Boot image inside + * the FIT, which a Falcon FIT does not carry, so without + * this name the symbol cannot be resolved and binman + * fails. The type property is what selects the etype. + */ + u-boot-any { + type = "fit"; + insert-template = <&fit_falcon_template>; + }; + }; + }; +#endif #endif /* CONFIG_ROCKCHIP_MASKROM_IMAGE */ }; #endif /* CONFIG_SPL */ diff --git a/arch/arm/include/asm/arch-rockchip/clock.h b/arch/arm/include/asm/arch-rockchip/clock.h index 95b08bfd046..f9bfdfb8a6a 100644 --- a/arch/arm/include/asm/arch-rockchip/clock.h +++ b/arch/arm/include/asm/arch-rockchip/clock.h @@ -104,7 +104,7 @@ struct rockchip_pll_rate_table { unsigned int m; unsigned int p; unsigned int s; - unsigned int k; + int k; }; enum rockchip_pll_type { diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig index 1a2e7847c9e..321afc0fbdf 100644 --- a/arch/arm/mach-rockchip/Kconfig +++ b/arch/arm/mach-rockchip/Kconfig @@ -758,6 +758,55 @@ config ROCKCHIP_MASKROM_IMAGE Rockchip SoCs support maskrom mode boot over USB. Enable this option to produce maskrom mode boot images containing U-Boot. +config ROCKCHIP_FALCON_IMAGE + bool "Build a Falcon mode image for Rockchip" + depends on SPL_OS_BOOT && ARM64 && SPL_ATF && SPL_FIT + help + Enable this option to produce a FIT image which SPL can use to boot + Linux directly via TF-A, bypassing U-Boot proper (Falcon mode). + + The image bundles TF-A (BL31), an optional OP-TEE, a Linux kernel + and an optional initramfs, and its device trees are pre-patched with + a /chosen node so that no further processing is needed before boot. + The kernel is mandatory and must be supplied at build time with + LINUX_KERNEL=/path/to/Image; pass LINUX_INITRD=/path/to/initramfs to + include an initramfs as well. + + This only controls image generation. Say N to build a Falcon-capable + SPL without bundling a kernel into the U-Boot image, for example when + the OS payload is assembled and written to storage separately. + +config ROCKCHIP_FALCON_KERNEL_LOAD + hex "Falcon mode kernel load offset" + depends on ROCKCHIP_FALCON_IMAGE + default 0x10000000 + help + Offset from the start of DRAM at which the Falcon mode kernel is + loaded and entered. + + All three Falcon mode load offsets have to be checked against the + memory layout of the SoC and of the firmware in use. None of the + three payloads may overlap each other, and none of them may overlap + the regions BL31 and OP-TEE are linked to run from - note that + binman splits their ELFs, so each of those occupies several regions + rather than one contiguous range. + +config ROCKCHIP_FALCON_FDT_LOAD + hex "Falcon mode devicetree load offset" + depends on ROCKCHIP_FALCON_IMAGE + default 0x1c000000 + help + Offset from the start of DRAM at which the Falcon mode devicetree is + loaded. The same constraints as for the kernel offset apply. + +config ROCKCHIP_FALCON_INITRD_LOAD + hex "Falcon mode initramfs load offset" + depends on ROCKCHIP_FALCON_IMAGE + default 0x1e000000 + help + Offset from the start of DRAM at which the Falcon mode initramfs is + loaded. The same constraints as for the kernel offset apply. + config LNX_KRNL_IMG_TEXT_OFFSET_BASE default TEXT_BASE diff --git a/arch/arm/mach-rockchip/rk3576/Kconfig b/arch/arm/mach-rockchip/rk3576/Kconfig index f347caf8904..e53e9a3bd85 100644 --- a/arch/arm/mach-rockchip/rk3576/Kconfig +++ b/arch/arm/mach-rockchip/rk3576/Kconfig @@ -1,5 +1,11 @@ if ROCKCHIP_RK3576 +config TARGET_FLIPPER_ONE_RK3576 + bool "Flipper One" + help + Flipper One is a handheld networking multitool for enthusiasts + using the Rockchip RK3576. + config TARGET_ROC_PC_RK3576 bool "Firefly ROC-RK3576-PC" help @@ -18,6 +24,7 @@ config SYS_SOC source board/firefly/roc-pc-rk3576/Kconfig config SYS_CONFIG_NAME + default "flipper-one-rk3576" if TARGET_FLIPPER_ONE_RK3576 default "rk3576_common" endif diff --git a/arch/arm/mach-rockchip/rk3576/MAINTAINERS b/arch/arm/mach-rockchip/rk3576/MAINTAINERS index 79cf9e97f70..a8dadca6486 100644 --- a/arch/arm/mach-rockchip/rk3576/MAINTAINERS +++ b/arch/arm/mach-rockchip/rk3576/MAINTAINERS @@ -28,6 +28,11 @@ S: Maintained F: arch/arm/dts/rk3576-rock-4d* F: configs/rock-4d-rk3576_defconfig +ROCKCHIP-EVB1-RK3576 +M: Alexey Charkov +S: Maintained +F: configs/evb-rk3576_defconfig + SIGE5-RK3576 M: Jonas Karlman S: Maintained diff --git a/arch/arm/mach-rockchip/rk3576/Makefile b/arch/arm/mach-rockchip/rk3576/Makefile index cbc58257deb..464def20c17 100644 --- a/arch/arm/mach-rockchip/rk3576/Makefile +++ b/arch/arm/mach-rockchip/rk3576/Makefile @@ -7,3 +7,13 @@ obj-y += rk3576.o obj-y += clk_rk3576.o obj-y += syscon_rk3576.o + +# BootROM "boost" stub: a freestanding bit of AArch64 code that pokes a fix-up +# value into SRAM so the ROM can boot from SD/eMMC. mkimage prepends it to the +# init image, so it must exist in the build dir. +quiet_cmd_rk3576boost = BOOST $@ + cmd_rk3576boost = $(CC) -nostdlib -ffreestanding -Os -c -o $@.o $< && \ + $(OBJCOPY) -O binary -j .text $@.o $@ +$(obj)/rk3576-boost.bin: $(src)/rk3576-boost.c FORCE + $(call if_changed,rk3576boost) +always-y += rk3576-boost.bin diff --git a/arch/arm/mach-rockchip/rk3576/rk3576-boost.c b/arch/arm/mach-rockchip/rk3576/rk3576-boost.c new file mode 100644 index 00000000000..10ba4ce72c9 --- /dev/null +++ b/arch/arm/mach-rockchip/rk3576/rk3576-boost.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright Contributors to the U-Boot project. + +/* + * Early initialization code to be executed before RAM training on RK3576 + * + * Anything in this file is to be compiled into a freestanding raw executable + * binary and prepended to the RKNS image by mkimage during build + */ + +#include + +#define SYS_SRAM_BASE 0x3ff80000 +#define OFFSET 0x03b0 + +int _start(void) +{ + uint32_t *sram = (void *)(SYS_SRAM_BASE + OFFSET); + uint64_t sctlr; + + /* set unknown value in sram to fix boot from sdmmc */ + *(sram) = 0x3ffff800; + + /* + * Enable the EL3 instruction cache before returning to the BootROM. + * + * The ROM runs with MMU and caches off. After a Maskrom USB download + * to DRAM (command 0x472) it verifies the *entire* image with a + * bit-serial CRC-16 executed from slow, non-cacheable on-chip ROM. + * With the I-cache off, every byte costs ~101 uncached ROM instruction + * fetches, so image verification can take noticeable time (~124 KB/s). + * Turning on the EL3 I-cache lets the tiny CRC loop run from L1I, + * measured ~2 MB/s (~16x speedup). The bit persists into the ROM's + * 0x472/CRC path. Harmless for the SD/storage path (small 2nd stage). + */ + asm volatile("mrs %0, sctlr_el3" : "=r"(sctlr)); + sctlr |= (1UL << 12); + asm volatile("msr sctlr_el3, %0; isb" :: "r"(sctlr) : "memory"); + + return 0; +} diff --git a/arch/arm/mach-rockchip/spl.c b/arch/arm/mach-rockchip/spl.c index e989c148079..54dec06737e 100644 --- a/arch/arm/mach-rockchip/spl.c +++ b/arch/arm/mach-rockchip/spl.c @@ -153,6 +153,35 @@ void spl_board_prepare_for_boot(void) cleanup_before_linux(); } +#if CONFIG_IS_ENABLED(OS_BOOT) +/** + * board_spl_start_uboot() - decides whether to boot the OS or U-Boot proper + * + * If Falcon mode (direct Linux boot via TF-A from SPL) is selected via config, + * try booting to Linux first, with automatic fallback to U-Boot if it fails to + * load. Boards that want to implement conditional U-Boot/Linux selection logic + * override this, for instance to look at a button. + * + * Returns + * 0 to boot the OS directly + * 1 to boot U-Boot proper + */ +__weak int board_spl_start_uboot(void) +{ + return 0; +} + +/* + * This overrides the weak default in common/spl/spl.c, so it must not itself + * be weak: two weak definitions of the same symbol would leave the choice + * between them up to archive link order. + */ +int spl_start_uboot(void) +{ + return board_spl_start_uboot(); +} +#endif + #if CONFIG_IS_ENABLED(RAM_DEVICE) && IS_ENABLED(CONFIG_SPL_LOAD_FIT) binman_sym_declare_optional(ulong, payload, image_pos); binman_sym_declare_optional(ulong, payload, size); diff --git a/board/rockchip/rockchip-falcon.config b/board/rockchip/rockchip-falcon.config new file mode 100644 index 00000000000..ac903f16f46 --- /dev/null +++ b/board/rockchip/rockchip-falcon.config @@ -0,0 +1,3 @@ +CONFIG_ROCKCHIP_FALCON_IMAGE=y +CONFIG_SPL_OS_BOOT=y +CONFIG_SYS_SPI_KERNEL_OFFS=0x0 diff --git a/boot/Kconfig b/boot/Kconfig index c67dc0ba493..c6f18aa2665 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -651,6 +651,23 @@ config BOOTMETH_EXTLINUX_PXE This provides a way to try out standard boot on an existing boot flow. +config BOOTMETH_BLS + bool "Bootdev support for Boot Loader Specification entries" + select PXE_UTILS + default y + help + Enables support for booting via Boot Loader Specification type #1 + entry files. The bootmeth scans each filesystem it finds for files + matching 'loader/entries/*.conf' (or '/boot/loader/entries/*.conf') + and boots the highest-sorting entry. + + The specification is here: + + https://uapi-group.org/specifications/specs/boot_loader_specification/ + + Type #2 BLS (drop-in directory of EFI binaries) is not handled here; + use BOOTMETH_EFI_BOOTMGR for that. + config BOOTMETH_EFILOADER bool "Bootdev support for EFI boot" depends on EFI_BINARY_EXEC diff --git a/boot/Makefile b/boot/Makefile index 7fb56e7ef37..0ce6fd1cd05 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_$(PHASE_)BOOTSTD_PROG) += prog_boot.o obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX) += bootmeth_extlinux.o obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX_PXE) += bootmeth_pxe.o +obj-$(CONFIG_$(PHASE_)BOOTMETH_BLS) += bootmeth_bls.o obj-$(CONFIG_$(PHASE_)BOOTMETH_EFILOADER) += bootmeth_efi.o obj-$(CONFIG_$(PHASE_)BOOTMETH_CROS) += bootm.o bootm_os.o bootmeth_cros.o obj-$(CONFIG_$(PHASE_)BOOTMETH_QFW) += bootmeth_qfw.o diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c index 55e1a6c4e02..2b8de758f28 100644 --- a/boot/bootdev-uclass.c +++ b/boot/bootdev-uclass.c @@ -110,7 +110,11 @@ int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, snprintf(partstr, sizeof(partstr), "part_%x", iter->part); else strcpy(partstr, "whole"); - snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); + if (iter->cur_subseq) + snprintf(name, sizeof(name), "%s.%s.%d", dev->name, partstr, + iter->cur_subseq); + else + snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); bflow->name = strdup(name); if (!bflow->name) return log_msg_ret("name", -ENOMEM); @@ -189,7 +193,7 @@ int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, } log_debug("method %s\n", bflow->method->name); - ret = bootmeth_read_bootflow(bflow->method, bflow); + ret = bootmeth_read_bootflow(bflow->method, bflow, iter->cur_subseq); if (ret) return log_msg_ret("method", ret); diff --git a/boot/bootflow.c b/boot/bootflow.c index d8a4a81a838..cd955657d37 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -25,6 +25,13 @@ static_assert(BOOTMETH_MAX_COUNT <= enum { BF_NO_MORE_PARTS = -ESHUTDOWN, BF_NO_MORE_DEVICES = -ENODEV, + /* + * Internal sentinel placed in iter->err when the last bootflow_check() + * succeeded. Tells iter_incr() to stay on the current + * (method, partition, bootdev) tuple and let the bootmeth produce the + * next sub-entry (cur_subseq + 1). Not exposed to bootmeths or callers. + */ + BF_TRY_NEXT_SEQ = -EAGAIN, }; static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = { @@ -300,6 +307,19 @@ static int iter_incr(struct bootflow_iter *iter) if (iter->err == BF_NO_MORE_DEVICES) return BF_NO_MORE_DEVICES; + /* + * The previous bootflow_check() succeeded and bumped cur_subseq. + * Stay on the same (method, part, bootdev) so the bootmeth can + * return its next sub-entry. + */ + if (iter->err == BF_TRY_NEXT_SEQ) { + iter->err = 0; + return 0; + } + + /* Leaving the current tuple - reset the sub-entry axis. */ + iter->cur_subseq = 0; + /* Get the next boothmethod */ for (iter->cur_method++; iter->cur_method < iter->num_methods; iter->cur_method++) { @@ -519,6 +539,13 @@ static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) if (!ret) { log_debug("Bootdev '%s' part %d method '%s': Found bootflow\n", dev->name, iter->part, iter->method->name); + /* + * Arrange for the next iter_incr() to retry the same tuple so + * the bootmeth can emit its next sub-entry. cur_subseq is + * advanced now so it reflects the index of the next call. + */ + iter->err = BF_TRY_NEXT_SEQ; + iter->cur_subseq++; return 0; } @@ -611,7 +638,17 @@ int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow) return 0; iter->err = ret; if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { - if (iter->flags & BOOTFLOWIF_ALL) + /* + * A failure while probing additional sub-entries + * (cur_subseq > 0) just means the current + * (method, part, bootdev) tuple has no more + * entries. Don't surface it as a bootflow (even + * in 'all' mode); free the partially-populated + * bflow and move on to the next method. + */ + if (iter->cur_subseq) + bootflow_free(bflow); + else if (iter->flags & BOOTFLOWIF_ALL) return log_msg_ret("all", ret); } } else { diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index 4a442e16850..e183a34fc7a 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -16,10 +16,12 @@ #include #include #include +#include #include #include #include #include "bootflow_internal.h" +#include "scene_internal.h" /** * struct menu_priv - information about the menu @@ -32,8 +34,111 @@ struct menu_priv { struct udevice *last_bootdev; }; -int bootflow_menu_new(struct expo **expp) +/* + * Caps on how much of the horizontal space inside the menu box each text + * column may occupy, expressed as a fraction of that space. The leftmost + * column (the bootdev label) is kept short so it never crowds out the rest, + * while the rightmost column (the OS/bootflow name) is allowed to be much + * wider but still bounded so it cannot run past the menu box. + */ +#define MENU_LABEL_MAX_NUM 1 /* 20% (1/5) for the label column */ +#define MENU_LABEL_MAX_DEN 5 +#define MENU_DESC_MAX_NUM 2 /* 2/3 for the description column */ +#define MENU_DESC_MAX_DEN 3 + +/** + * menu_trunc_strdup() - Duplicate a string, truncated to a maximum pixel width + * + * Copies @str into a newly allocated buffer, trimming it so that it occupies + * no more than @max_px pixels when rendered with a font whose nominal + * character width is @char_w. If the metrics are unknown (@char_w or @max_px + * not positive) the string is copied unchanged. + * + * @str: String to copy (must not be NULL) + * @max_px: Maximum width in pixels, or <= 0 for no limit + * @char_w: Nominal character width in pixels, or <= 0 if unknown + * Return: Newly allocated (possibly truncated) string, or NULL if out of memory + */ +static char *menu_trunc_strdup(const char *str, int max_px, int char_w) +{ + int max_chars; + char *out; + + if (char_w <= 0 || max_px <= 0) + return strdup(str); + + max_chars = max_px / char_w; + if (max_chars < 1) + max_chars = 1; + if (strlen(str) <= max_chars) + return strdup(str); + + out = malloc(max_chars + 1); + if (!out) + return NULL; + memcpy(out, str, max_chars); + out[max_chars] = '\0'; + + return out; +} + +/** + * menu_avail_width() - Work out the usable width inside the menu box + * + * Reads the geometry straight from the OBJ_BOX object: its bounding box gives + * the outer extent and its line width gives the border that eats into the + * interior on each side. This avoids duplicating the box placement maths from + * bootflow_menu_new(). + * + * @scn: Scene containing the menu box + * Return: Interior width of the menu box in pixels, or 0 if unknown + */ +static int menu_avail_width(struct scene *scn) { + struct scene_obj_box *box; + + box = scene_obj_find(scn, OBJ_BOX, SCENEOBJT_BOX); + if (!box) + return 0; + + return (box->obj.bbox.x1 - box->obj.bbox.x0) - 2 * box->width; +} + +/** + * menu_char_width() - Get the nominal character width of the menu font + * + * @exp: Expo containing the menu + * Return: Nominal character width in pixels, or 0 if unknown + */ +static int menu_char_width(struct expo *exp) +{ + struct vidconsole_priv *vc; + + if (!exp->cons) + return 0; + + vc = dev_get_uclass_priv(exp->cons); + + return vc->x_charsize; +} + +int bootflow_menu_new(struct expo **expp, int width, int height, int char_h) +{ + /* + * All positions are expressed as proportional fractions of the + * reference design (1366 x 768, the classic WXGA resolution used by + * the sandbox display). Use integer scaling so the layout fits any + * display resolution. + */ +#define SX(v) ((v) * width / 1366) +#define SY(v) ((v) * height / 768) + /* + * Use the actual console character height when provided; otherwise + * fall back to the SY-scaled reference value so the layout is + * self-consistent even without a live console (e.g. in unit tests). + */ + if (!char_h) + char_h = max(SY(16), 1); struct scene_obj_menu *menu; struct menu_priv *priv; struct scene *scn; @@ -57,53 +162,79 @@ int bootflow_menu_new(struct expo **expp) ret = scene_box(scn, "box", OBJ_BOX, 2, NULL); if (ret < 0) return log_msg_ret("bmb", ret); - ret |= scene_obj_set_bbox(scn, OBJ_BOX, 30, 90, 1366 - 30, 720); + ret |= scene_obj_set_bbox(scn, OBJ_BOX, SX(30), SY(90), + width - SX(30), height - max(SY(48), 2 * char_h)); ret = scene_menu(scn, "main", OBJ_MENU, &menu); - ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100); + ret |= scene_obj_set_pos(scn, OBJ_MENU, SX(100), SY(100)); ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE, "U-Boot - Boot Menu", NULL); - ret |= scene_obj_set_bbox(scn, OBJ_MENU_TITLE, 0, 32, - SCENEOB_DISPLAY_MAX, 30); + /* + * Ensure the title bbox is at least one character-cell tall so it is + * not clipped on small displays + */ + ret |= scene_obj_set_bbox(scn, OBJ_MENU_TITLE, 0, SY(32), + SCENEOB_DISPLAY_MAX, + max(SY(32) + SY(30), char_h)); ret |= scene_obj_set_halign(scn, OBJ_MENU_TITLE, SCENEOA_CENTRE); logo = video_get_u_boot_logo(); - if (logo) { + /* + * Only add the logo when the reserved right-side column is at least + * 64 px wide so that a clipped strip does not bleed over the menu + * box on small displays (e.g. 256x144 where the column is only 37 px). + */ + if (logo && SX(1366 - 1165) >= 64) { ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL); - ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1165, 100); + ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, + width - SX(1366 - 1165), SY(100)); + } else { + /* + * No logo/preview column is shown, so the right side of the box + * is empty. Let the menu justify its columns out to the box's + * inner right edge (less the border and a small gap) instead of + * bunching them in the reference column band on the left. + */ + struct scene_obj_box *box; + + box = scene_obj_find(scn, OBJ_BOX, SCENEOBJT_BOX); + if (box) + menu->fill_x1 = box->obj.bbox.x1 - box->width - 4; } ret |= scene_txt_str(scn, "prompt1a", OBJ_PROMPT1A, STR_PROMPT1A, - "Use the \x18 and \x19 keys to select which entry is highlighted.", + "Use the \x18 and \x19 keys to switch entries", NULL); ret |= scene_txt_str(scn, "prompt1b", OBJ_PROMPT1B, STR_PROMPT1B, - "Use the UP and DOWN keys to select which entry is highlighted.", + "Use the UP and DOWN keys to switch entries", NULL); ret |= scene_txt_str(scn, "prompt2", OBJ_PROMPT2, STR_PROMPT2, - "Press enter to boot the selected OS, 'e' to edit the commands " - "before booting or 'c' for a command-line. ESC to return to " - "previous menu", NULL); - ret |= scene_txt_str(scn, "autoboot", OBJ_AUTOBOOT, STR_AUTOBOOT, - "The highlighted entry will be executed automatically in %ds.", - NULL); - ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1A, 0, 590, - SCENEOB_DISPLAY_MAX, 30); - ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1B, 0, 620, - SCENEOB_DISPLAY_MAX, 30); - ret |= scene_obj_set_bbox(scn, OBJ_PROMPT2, 100, 650, - 1366 - 100, 700); - ret |= scene_obj_set_bbox(scn, OBJ_AUTOBOOT, 0, 720, - SCENEOB_DISPLAY_MAX, 750); + "Press Enter to boot, ESC to exit", NULL); + /* + * Prompt1A/B: up to 2 wrapped lines (navigation hint). + * Prompt2: up to 4 wrapped lines (boot instructions). + * All positions are anchored from the bottom so they remain visible + * on small displays; on large displays the SY() values dominate. + */ + ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1A, 0, + height - max(SY(48), 2 * char_h), + SCENEOB_DISPLAY_MAX, + height - max(SY(24), char_h)); + ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1B, 0, + height - max(SY(48), 2 * char_h), + SCENEOB_DISPLAY_MAX, + height - max(SY(24), char_h)); + ret |= scene_obj_set_bbox(scn, OBJ_PROMPT2, SX(100), + height - max(SY(24), char_h), + width - SX(100), height); ret |= scene_obj_set_halign(scn, OBJ_PROMPT1A, SCENEOA_CENTRE); ret |= scene_obj_set_halign(scn, OBJ_PROMPT1B, SCENEOA_CENTRE); ret |= scene_obj_set_halign(scn, OBJ_PROMPT2, SCENEOA_CENTRE); ret |= scene_obj_set_valign(scn, OBJ_PROMPT2, SCENEOA_CENTRE); - ret |= scene_obj_set_halign(scn, OBJ_AUTOBOOT, SCENEOA_CENTRE); use_font = IS_ENABLED(CONFIG_CONSOLE_TRUETYPE); scene_obj_set_hide(scn, OBJ_PROMPT1A, use_font); scene_obj_set_hide(scn, OBJ_PROMPT1B, !use_font); - scene_obj_set_hide(scn, OBJ_AUTOBOOT, use_font); ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">", NULL); @@ -115,6 +246,8 @@ int bootflow_menu_new(struct expo **expp) *expp = exp; +#undef SX +#undef SY return 0; } @@ -122,10 +255,12 @@ int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq, struct scene **scnp) { struct menu_priv *priv = exp->priv; - char str[2], *label, *key; + char str[2], *label, *key, *desc; + const char *desc_src; struct udevice *media; struct scene *scn; const char *name; + int avail, char_w; uint preview_id; uint scene_id; bool add_gap; @@ -148,13 +283,31 @@ int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq, name = "usb"; else name = media->name; - label = strdup(name); + /* + * Trim the label and description columns to a fraction of the space + * inside the menu box so that long bootdev or OS names cannot push the + * layout off the right edge on small displays. + */ + avail = menu_avail_width(scn); + char_w = menu_char_width(exp); + + label = menu_trunc_strdup(name, avail * MENU_LABEL_MAX_NUM / + MENU_LABEL_MAX_DEN, char_w); if (!label) { free(key); return log_msg_ret("nam", -ENOMEM); } + desc_src = bflow->os_name ? bflow->os_name : bflow->name; + desc = menu_trunc_strdup(desc_src, avail * MENU_DESC_MAX_NUM / + MENU_DESC_MAX_DEN, char_w); + if (!desc) { + free(label); + free(key); + return log_msg_ret("dsc", -ENOMEM); + } + add_gap = priv->last_bootdev != bflow->dev; /* disable this gap for now, since it looks a little ugly */ @@ -165,8 +318,7 @@ int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq, ret |= scene_txt_str(scn, "label", ITEM_LABEL + seq, STR_LABEL + seq, label, NULL); ret |= scene_txt_str(scn, "desc", ITEM_DESC + seq, STR_DESC + seq, - bflow->os_name ? bflow->os_name : - bflow->name, NULL); + desc, NULL); ret |= scene_txt_str(scn, "key", ITEM_KEY + seq, STR_KEY + seq, key, NULL); preview_id = 0; @@ -218,18 +370,34 @@ int bootflow_menu_add_all(struct expo *exp) int bootflow_menu_setup(struct bootstd_priv *std, bool text_mode, struct expo **expp) { + struct video_priv *vid_priv; struct udevice *dev; struct expo *exp; int ret; - ret = bootflow_menu_new(&exp); - if (ret) - return log_msg_ret("bmn", ret); - /* For now we only support a video console */ ret = uclass_first_device_err(UCLASS_VIDEO, &dev); if (ret) return log_msg_ret("vid", ret); + vid_priv = dev_get_uclass_priv(dev); + + { + struct udevice *cons; + struct vidconsole_priv *vc_priv = NULL; + int char_h = 0; + + if (!device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE, + &cons)) + vc_priv = dev_get_uclass_priv(cons); + if (vc_priv) + char_h = vc_priv->y_charsize; + + ret = bootflow_menu_new(&exp, vid_priv->xsize, vid_priv->ysize, + char_h); + } + if (ret) + return log_msg_ret("bmn", ret); + ret = expo_set_display(exp, dev); if (ret) return log_msg_ret("dis", ret); diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c index c841dd0d6d4..1fa5807bdf7 100644 --- a/boot/bootmeth-uclass.c +++ b/boot/bootmeth-uclass.c @@ -39,14 +39,15 @@ int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter) return ops->check(dev, iter); } -int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow) +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { const struct bootmeth_ops *ops = bootmeth_get_ops(dev); if (!ops->read_bootflow) return -ENOSYS; - return ops->read_bootflow(dev, bflow); + return ops->read_bootflow(dev, bflow, seq); } int bootmeth_set_bootflow(struct udevice *dev, struct bootflow *bflow, @@ -102,7 +103,7 @@ int bootmeth_get_bootflow(struct udevice *dev, struct bootflow *bflow) return -ENOSYS; bootflow_init(bflow, NULL, dev); - return ops->read_bootflow(dev, bflow); + return ops->read_bootflow(dev, bflow, 0); } int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global) diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c index ec255b072af..ed5933eb9d3 100644 --- a/boot/bootmeth_android.c +++ b/boot/bootmeth_android.c @@ -208,8 +208,11 @@ static int configure_bootloader_version(struct bootflow *bflow) PLAIN_VERSION, false); } -static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); struct disk_partition misc; struct android_priv *priv; diff --git a/boot/bootmeth_bls.c b/boot/bootmeth_bls.c new file mode 100644 index 00000000000..b658f2b37b4 --- /dev/null +++ b/boot/bootmeth_bls.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for the Boot Loader Specification (type #1 entry files) + * + * Reuses the pxelinux parser/boot path: each on-disk entry is read into a + * struct pxe_label via parse_label_keys() and booted via label_boot(). + * + * Spec: https://uapi-group.org/specifications/specs/boot_loader_specification/ + * + * Every *.conf file in /loader/entries/ is surfaced as its own + * bootflow via the bootstd iterator's sub-entry axis. The bootmeth is + * called repeatedly with seq = 0, 1, 2, ... for the same (partition, + * bootdev) tuple and returns -ENOENT once the entry list is exhausted. + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BLS_DIR "loader/entries" +#define BLS_SUFFIX ".conf" + +/** + * struct bls_priv - per-bootmeth-instance state + * + * Caches a single flat list of all *.conf entries discovered across every + * bootstd prefix on a given (bootdev, partition) tuple so that successive + * read_bootflow() calls answer from RAM instead of re-walking the + * filesystem. Sorted ascending by full path; read_bootflow() indexes from + * the end so the seq-th entry the iterator surfaces is the seq-th newest. + * + * Invalidated on seq == 0 (new scan) or when the (bootdev, partition) key + * changes. + * + * @paths: alist of ``char *`` full paths (strdup'd, owned), sorted + * ascending by full path. + * @cache_blk: Block device the cache was built against. + * @cache_part: Partition number the cache was built against. + */ +struct bls_priv { + struct alist paths; + struct udevice *cache_blk; + int cache_part; +}; + +static void bls_cache_drop(struct bls_priv *priv) +{ + char **arr = priv->paths.data; + uint i; + + for (i = 0; i < priv->paths.count; i++) + free(arr[i]); + alist_empty(&priv->paths); + priv->cache_blk = NULL; + priv->cache_part = 0; +} + +static int bls_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; +} + +static int bls_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, enum bootflow_img_t type, ulong *sizep) +{ + struct extlinux_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + + /* Allow up to 1GB */ + *sizep = 1 << 30; + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + type, sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +/** + * bls_load_all() - Enumerate every BLS *.conf across all bootstd prefixes + * + * Walks ``/loader/entries/`` for each prefix in @prefixes, collects + * every regular file with a ``.conf`` suffix as a strdup'd full path in + * @paths, then sorts the merged result ascending by full path (callers + * index from the end for the spec's descending order). + * + * The spec leaves ordering between prefixes unspecified; sorting full + * paths is a deterministic-and-cheap stand-in. + * + * The Boot Loader Specification says entries should be sorted by sort-key + * (descending), then version (descending), then filename (descending). For + * the time being only the filename criterion is implemented, which is + * sufficient for most distros that encode kernel version into the filename. + * + * TODO: implement proper spec-compliant ordering. That requires reading + * each candidate entry, parsing its 'sort-key' and 'version' fields (the + * latter compared with strverscmp()-style logic), and only falling back to + * filename order when those tie. + */ +static int bls_load_all(struct alist *paths, struct blk_desc *desc, + struct bootflow *bflow, + const char *const *prefixes) +{ + char dirpath[256]; + int ret; + int i; + + for (i = 0; prefixes && prefixes[i]; i++) { + struct fs_dir_stream *dirs; + struct fs_dirent *dent; + + /* fs_closedir() below resets the global fs_type. */ + ret = bootmeth_setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + snprintf(dirpath, sizeof(dirpath), "%s%s", + prefixes[i], BLS_DIR); + dirs = fs_opendir(dirpath); + if (!dirs) + continue; + + while ((dent = fs_readdir(dirs))) { + size_t len = strlen(dent->name); + char *full; + + if (dent->type != FS_DT_REG) + continue; + if (len <= strlen(BLS_SUFFIX)) + continue; + if (strcmp(dent->name + len - strlen(BLS_SUFFIX), + BLS_SUFFIX)) + continue; + + full = malloc(strlen(dirpath) + 1 + len + 1); + if (!full) { + fs_closedir(dirs); + return -ENOMEM; + } + sprintf(full, "%s/%s", dirpath, dent->name); + + if (!alist_add(paths, full)) { + free(full); + fs_closedir(dirs); + return -ENOMEM; + } + } + fs_closedir(dirs); + } + + if (paths->count) + qsort(paths->data, paths->count, paths->obj_size, + strcmp_compar); + + return 0; +} + +/* + * TODO: BLS entry filenames may carry a boot-counter suffix of the form + * '+TRIES_LEFT[-TRIES_DONE]' immediately before the .conf extension (see + * the spec section on "Boot counting"). When that is implemented, this + * bootmeth should: + * - parse and strip the suffix from the displayed entry name, + * - skip entries whose TRIES_LEFT has reached zero, + * - decrement TRIES_LEFT (renaming the file) on each boot attempt. + * For now the suffix is left intact in the entry name and ignored. + */ +static int bls_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) +{ + struct bls_priv *priv = dev_get_priv(dev); + struct blk_desc *desc; + const char *const *prefixes; + struct udevice *bootstd; + struct pxe_label *label = NULL; + struct pxe_menu scratch = {}; + const char *fpath; + const char *base; + char **slot; + char *body; + int ret; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) + return log_msg_ret("std", ret); + + /* We require a partitioned block device */ + if (!bflow->blk || !bflow->part) + return -ENOENT; + + desc = dev_get_uclass_plat(bflow->blk); + prefixes = bootstd_get_prefixes(bootstd); + + /* + * A fresh scan of this tuple always starts at seq == 0; rebuild then + * so callers see current on-disk state. seq > 0 only happens after a + * successful seq == 0 (the iterator only bumps cur_subseq on + * success). Still, check the block device and partition just in case + */ + if (!seq || priv->cache_blk != bflow->blk || + priv->cache_part != bflow->part) { + bls_cache_drop(priv); + ret = bls_load_all(&priv->paths, desc, bflow, prefixes); + if (ret) + return log_msg_ret("scan", ret); + priv->cache_blk = bflow->blk; + priv->cache_part = bflow->part; + } + + if (seq >= (int)priv->paths.count) + return -ENOENT; + + /* Sorted ascending by full path; index from the end for newest-first. */ + slot = alist_getw(&priv->paths, priv->paths.count - 1 - seq, char *); + fpath = *slot; + base = strrchr(fpath, '/'); + base = base ? base + 1 : fpath; + + /* + * bls_load_all() finished with fs_closedir(), which resets the + * global fs_type. Re-mount the partition so bootmeth_try_file()'s + * internal fs_size() call can find the right filesystem driver. + */ + ret = bootmeth_setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = bootmeth_try_file(bflow, desc, NULL, fpath); + if (ret) + return log_msg_ret("try", ret); + + ret = bootmeth_alloc_file(bflow, SZ_64K, 1, BFI_EXTLINUX_CFG); + if (ret) + return log_msg_ret("read", ret); + + label = label_create(); + if (!label) + return log_msg_ret("lbl", -ENOMEM); + + /* + * BLS files have no 'label NAME' header — derive the label name from + * the basename (without the .conf suffix) so messages are useful. + */ + label->name = strndup(base, strlen(base) - strlen(BLS_SUFFIX)); + if (!label->name) { + ret = -ENOMEM; + goto err; + } + + body = bflow->buf; + ret = parse_label_keys(&body, &scratch, label, true); + if (ret < 0) + goto err; + + /* + * scratch is only used to give parse_label_keys() somewhere safe to + * stash menu-level state (e.g. a stray 'menu default' line). BLS + * entry files don't contain such lines but defensively free anything + * that did get allocated. + */ + free(scratch.default_label); + + /* + * label->menu was populated either from a BLS 'title' line (the + * spec-mandated human-readable name) or from a stray 'menu label' + * the parser may have picked up. Fall back to the filename-derived + * label name when neither is present. + */ + bflow->os_name = strdup(label->menu ? label->menu : label->name); + if (!bflow->os_name) { + ret = -ENOMEM; + goto err; + } + + bflow->bootmeth_priv = label; + + return 0; + +err: + if (label) + label_destroy(label); + return log_msg_ret("bls", ret); +} + +static int bls_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct cmd_tbl cmdtp = {}; /* dummy */ + struct pxe_context ctx; + struct extlinux_info info; + struct pxe_label *label = bflow->bootmeth_priv; + int ret; + + if (!label) + return log_msg_ret("lbl", -ENOENT); + + info.dev = dev; + info.bflow = bflow; + + /* + * BLS paths are absolute relative to the filesystem root of the + * partition the entry lives on. allow_abs_path=true honours that; + * passing NULL as the bootfile keeps the prefix empty so absolute + * paths are not rebased. + */ + ret = pxe_setup_ctx(&ctx, &cmdtp, bls_getfile, &info, true, + NULL, false, false); + if (ret) + return log_msg_ret("ctx", -EINVAL); + + ret = label_boot(&ctx, label); + pxe_destroy_ctx(&ctx); + if (ret) + return log_msg_ret("boot", -EINVAL); + + return 0; +} + +static int bls_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "Boot Loader Specification" : "bls"; + + return 0; +} + +static int bls_bootmeth_probe(struct udevice *dev) +{ + struct bls_priv *priv = dev_get_priv(dev); + + alist_init_struct(&priv->paths, char *); + + return 0; +} + +static int bls_bootmeth_remove(struct udevice *dev) +{ + struct bls_priv *priv = dev_get_priv(dev); + + bls_cache_drop(priv); + alist_uninit(&priv->paths); + + return 0; +} + +static struct bootmeth_ops bls_bootmeth_ops = { + .check = bls_check, + .read_bootflow = bls_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = bls_boot, +}; + +static const struct udevice_id bls_bootmeth_ids[] = { + { .compatible = "u-boot,bls" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_2bls) = { + .name = "bootmeth_bls", + .id = UCLASS_BOOTMETH, + .of_match = bls_bootmeth_ids, + .ops = &bls_bootmeth_ops, + .bind = bls_bootmeth_bind, + .probe = bls_bootmeth_probe, + .remove = bls_bootmeth_remove, + .priv_auto = sizeof(struct bls_priv), +}; diff --git a/boot/bootmeth_cros.c b/boot/bootmeth_cros.c index c7b862e512a..19b591a9799 100644 --- a/boot/bootmeth_cros.c +++ b/boot/bootmeth_cros.c @@ -362,8 +362,11 @@ static int cros_read_kernel(struct bootflow *bflow) return 0; } -static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; const struct vb2_kernel_preamble *preamble; struct disk_partition info; struct vb2_keyblock *hdr; diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c index e187dc39912..bb1d6c847d8 100644 --- a/boot/bootmeth_efi.c +++ b/boot/bootmeth_efi.c @@ -302,8 +302,11 @@ static int distro_efi_read_bootflow_net(struct bootflow *bflow) return 0; } -static int distro_efi_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int distro_efi_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; int ret; log_debug("dev='%s', part=%d\n", bflow->dev->name, bflow->part); diff --git a/boot/bootmeth_efi_mgr.c b/boot/bootmeth_efi_mgr.c index 6e70c36ad99..f453ecd9e6b 100644 --- a/boot/bootmeth_efi_mgr.c +++ b/boot/bootmeth_efi_mgr.c @@ -45,8 +45,11 @@ static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter) return 0; } -static int efi_mgr_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int efi_mgr_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct efi_mgr_priv *priv = dev_get_priv(dev); efi_status_t ret; efi_uintn_t size; diff --git a/boot/bootmeth_extlinux.c b/boot/bootmeth_extlinux.c index 921d721a27b..b5ffa646ad1 100644 --- a/boot/bootmeth_extlinux.c +++ b/boot/bootmeth_extlinux.c @@ -132,8 +132,11 @@ static int extlinux_fill_info(struct bootflow *bflow) return 0; } -static int extlinux_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int extlinux_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct blk_desc *desc; const char *const *prefixes; struct udevice *bootstd; diff --git a/boot/bootmeth_pxe.c b/boot/bootmeth_pxe.c index faa8d729b15..38ad9bedccf 100644 --- a/boot/bootmeth_pxe.c +++ b/boot/bootmeth_pxe.c @@ -59,7 +59,7 @@ static int extlinux_pxe_check(struct udevice *dev, struct bootflow_iter *iter) } static int extlinux_pxe_read_bootflow(struct udevice *dev, - struct bootflow *bflow) + struct bootflow *bflow, int seq) { const char *addr_str; char fname[200]; @@ -69,6 +69,9 @@ static int extlinux_pxe_read_bootflow(struct udevice *dev, char *buf; int ret; + if (seq) + return -ENOENT; + addr_str = env_get("pxefile_addr_r"); if (!addr_str) return log_msg_ret("pxeb", -EPERM); diff --git a/boot/bootmeth_qfw.c b/boot/bootmeth_qfw.c index 028c2481583..2af15fba027 100644 --- a/boot/bootmeth_qfw.c +++ b/boot/bootmeth_qfw.c @@ -28,8 +28,11 @@ static int qfw_check(struct udevice *dev, struct bootflow_iter *iter) return -ENOTSUPP; } -static int qfw_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int qfw_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct udevice *qfw_dev = dev_get_parent(bflow->dev); ulong load, initrd; int ret; diff --git a/boot/bootmeth_rauc.c b/boot/bootmeth_rauc.c index d7b0c686ffe..f05d1c14a21 100644 --- a/boot/bootmeth_rauc.c +++ b/boot/bootmeth_rauc.c @@ -138,8 +138,11 @@ static int distro_rauc_scan_parts(struct bootflow *bflow) return -1; } -static int distro_rauc_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int distro_rauc_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct distro_rauc_priv *priv = NULL; int ret = 0; char *slot; diff --git a/boot/bootmeth_sandbox.c b/boot/bootmeth_sandbox.c index 92ba2e3f050..9ee05f5a755 100644 --- a/boot/bootmeth_sandbox.c +++ b/boot/bootmeth_sandbox.c @@ -18,8 +18,11 @@ static int sandbox_check(struct udevice *dev, struct bootflow_iter *iter) return 0; } -static int sandbox_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int sandbox_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; /* pretend we are ready */ bflow->state = BOOTFLOWST_READY; diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c index cd50977cc40..bbccd48dbd8 100644 --- a/boot/bootmeth_script.c +++ b/boot/bootmeth_script.c @@ -152,8 +152,11 @@ static int script_read_bootflow_net(struct bootflow *bflow) return 0; } -static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; const struct udevice *media = dev_get_parent(bflow->dev); struct udevice *bootstd; int ret; diff --git a/boot/expo.c b/boot/expo.c index 94413acd381..9c3086aaa43 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -288,6 +288,7 @@ int expo_apply_theme(struct expo *exp, ofnode node) ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y); ofnode_read_u32(node, "menu-title-margin-x", &theme->menu_title_margin_x); + ofnode_read_u32(node, "menu-pos-x", &theme->menu_pos_x); white_on_black = ofnode_read_bool(node, "white-on-black"); if (exp->display) video_set_white_on_black(exp->display, white_on_black); diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 8c1310dabeb..5e98c9df565 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -95,6 +95,7 @@ int format_mac_pxe(char *outbuf, size_t outbuf_len) * @ctx: PXE context * @file_path: File path to read (relative to the PXE file) * @file_addr: Address to load file to + * @type: Image type used to record the loaded file in the bootflow * @filesizep: If not NULL, returns the file size in bytes * Returns 1 for success, or < 0 on error */ @@ -162,6 +163,7 @@ int get_pxe_file(struct pxe_context *ctx, const char *file_path, * * @ctx: PXE context * @file: Filename to process (relative to pxelinux.cfg/) + * @pxefile_addr_r: Address to load the file to * Returns 1 for success, -ENAMETOOLONG if the resulting path is too long. * or other value < 0 on other error */ @@ -216,15 +218,7 @@ static int get_relfile_envaddr(struct pxe_context *ctx, const char *file_path, return get_relfile(ctx, file_path, file_addr, type, filesizep); } -/** - * label_create() - crate a new PXE label - * - * Allocates memory for and initializes a pxe_label. This uses malloc, so the - * result must be free()'d to reclaim the memory. - * - * Returns a pointer to the label, or NULL if out of memory - */ -static struct pxe_label *label_create(void) +struct pxe_label *label_create(void) { struct pxe_label *label; @@ -237,20 +231,7 @@ static struct pxe_label *label_create(void) return label; } -/** - * label_destroy() - free the memory used by a pxe_label - * - * This frees @label itself as well as memory used by its name, - * kernel, config, append, initrd, fdt, fdtdir and fdtoverlay members, if - * they're non-NULL. - * - * So - be sure to only use dynamically allocated memory for the members of - * the pxe_label struct, unless you want to clean it up first. These are - * currently only created by the pxe file parsing code. - * - * @label: Label to free - */ -static void label_destroy(struct pxe_label *label) +void label_destroy(struct pxe_label *label) { free(label->name); free(label->kernel_label); @@ -540,7 +521,7 @@ static void label_boot_extension(struct pxe_context *ctx, * Returns does not return on success, otherwise returns 0 if a localboot * label was processed, or 1 on error */ -static int label_boot(struct pxe_context *ctx, struct pxe_label *label) +int label_boot(struct pxe_context *ctx, struct pxe_label *label) { char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL }; char *zboot_argv[] = { "zboot", NULL, "0", NULL, NULL }; @@ -892,6 +873,7 @@ static const struct token keywords[] = { {"linux", T_LINUX}, {"localboot", T_LOCALBOOT}, {"append", T_APPEND}, + {"options", T_APPEND}, {"initrd", T_INITRD}, {"include", T_INCLUDE}, {"devicetree", T_FDT}, @@ -914,6 +896,13 @@ static const struct token keywords[] = { * Since pxe(linux) files don't have a token to identify the start of a * literal, we have to keep track of when we're in a state where a literal is * expected vs when we're in a state a keyword is expected. + * + * @L_NORMAL: Outside any specific lexical context; whitespace is skipped + * and the next non-blank token determines what is read + * @L_KEYWORD: A keyword is expected; the next word is matched against the + * keyword table and tagged with the matching token type + * @L_SLITERAL: A string literal is expected; characters are read up to the + * end of the line */ enum lex_state { L_NORMAL = 0, @@ -1018,6 +1007,9 @@ static void get_keyword(struct token *t) * * @p: Points to a pointer to the current position in the input being processed. * Updated to point at the first character after the current token + * @t: Token to populate with the type and value of what was read + * @state: Current lexer state, controlling whether a keyword or a string + * literal is expected next */ static void get_token(char **p, struct token *t, enum lex_state state) { @@ -1274,35 +1266,14 @@ static int parse_label_kernel(char **c, struct pxe_label *label) return 1; } -/* - * Parses a label and adds it to the list of labels for a menu. - * - * A label ends when we either get to the end of a file, or - * get some input we otherwise don't have a handler defined - * for. - * - */ -static int parse_label(char **c, struct pxe_menu *cfg) +int parse_label_keys(char **c, struct pxe_menu *cfg, struct pxe_label *label, + bool ignore_unknown) { struct token t; + char *s; int len; - char *s = *c; - struct pxe_label *label; int err; - label = label_create(); - if (!label) - return -ENOMEM; - - err = parse_sliteral(c, &label->name); - if (err < 0) { - printf("Expected label name: %.*s\n", (int)(*c - s), s); - label_destroy(label); - return -EINVAL; - } - - list_add_tail(&label->list, &cfg->labels); - while (1) { s = *c; get_token(c, &t, L_KEYWORD); @@ -1313,6 +1284,19 @@ static int parse_label(char **c, struct pxe_menu *cfg) err = parse_label_menu(c, cfg, label); break; + case T_TITLE: + /* + * Equivalent to 'menu label' inside a label body. + * Boot Loader Specification entries use a bare + * 'title' line for the human-readable name; honour + * it here so the existing parser handles BLS files + * natively. extlinux/pxelinux files conventionally + * use 'menu label' instead, so this is additive. + */ + if (!label->menu) + err = parse_sliteral(c, &label->menu); + break; + case T_KERNEL: case T_LINUX: err = parse_label_kernel(c, label); @@ -1369,7 +1353,31 @@ static int parse_label(char **c, struct pxe_menu *cfg) case T_EOL: break; + case T_EOF: + if (ignore_unknown) { + /* + * BLS-style callers parse a standalone label + * body, so there is no outer context to push + * T_EOF back into — stop cleanly here. + */ + return 1; + } + /* + * For pxelinux/extlinux, fall through so the default + * case pushes T_EOF back for the top-level parser. + */ + fallthrough; default: + if (ignore_unknown) { + /* + * Skip the rest of the line and keep going. + * Used for formats like the Boot Loader + * Specification, where the spec mandates that + * unknown keys must be silently ignored. + */ + eol_or_eof(c); + break; + } /* * put the token back! we don't want it - it's the end * of a label and whatever token this is, it's @@ -1384,6 +1392,36 @@ static int parse_label(char **c, struct pxe_menu *cfg) } } +/* + * Parses a label and adds it to the list of labels for a menu. + * + * A label ends when we either get to the end of a file, or + * get some input we otherwise don't have a handler defined + * for. + * + */ +static int parse_label(char **c, struct pxe_menu *cfg) +{ + char *s = *c; + struct pxe_label *label; + int err; + + label = label_create(); + if (!label) + return -ENOMEM; + + err = parse_sliteral(c, &label->name); + if (err < 0) { + printf("Expected label name: %.*s\n", (int)(*c - s), s); + label_destroy(label); + return -EINVAL; + } + + list_add_tail(&label->list, &cfg->labels); + + return parse_label_keys(c, cfg, label, false); +} + /* * This 16 comes from the limit pxelinux imposes on nested includes. * diff --git a/boot/scene.c b/boot/scene.c index fa8f540bfb0..f5a9af1be8d 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -756,6 +756,7 @@ int scene_arrange(struct scene *scn) ret = scene_calc_arrange(scn, &arr); if (ret < 0) return log_msg_ret("arr", ret); + arr.xsize = xsize; list_for_each_entry(obj, &scn->obj_head, sibling) { handle_alignment(obj->horiz, obj->vert, &obj->bbox, &obj->dims, @@ -1071,10 +1072,20 @@ int scene_apply_theme(struct scene *scn, struct expo_theme *theme) switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: - case SCENEOBJT_MENU: case SCENEOBJT_BOX: case SCENEOBJT_TEXTLINE: break; + case SCENEOBJT_MENU: + /* + * Move the menu to the themed x-position if one is + * given, keeping its y. The item columns are laid out + * relative to the menu, so this shifts the whole row. + */ + if (theme->menu_pos_x) + scene_obj_set_pos(scn, obj->id, + theme->menu_pos_x, + obj->bbox.y0); + break; case SCENEOBJT_TEXTEDIT: scene_txted_set_font(scn, obj->id, NULL, theme->font_size); diff --git a/boot/scene_menu.c b/boot/scene_menu.c index 23404172093..31d34780c08 100644 --- a/boot/scene_menu.c +++ b/boot/scene_menu.c @@ -77,6 +77,183 @@ struct scene_menitem *scene_menuitem_find_val(const struct scene_obj_menu *menu, return NULL; } +/* + * Reference column x-offsets (from the menu's left edge) for a 1366-pixel-wide + * reference design: the label sits at the menu inset, then the pointer, key and + * description columns. + */ +#define MENU_REF_WIDTH 1366 +#define MENU_REF_PTR_OFS 200 +#define MENU_REF_KEY_OFS 230 +#define MENU_REF_DESC_OFS 280 + +/* Minimum blank space kept between adjacent columns when compressed */ +#define MENU_COL_GAP 4 + +/** + * scene_menu_calc_cols() - Work out the x-offset of each menu column + * + * The reference design places the label, pointer, key and description columns + * at fixed offsets (menu_inset, 200, 230, 280) from the menu's left edge, + * scaled down proportionally to the actual display width. + * + * The blank space between columns is treated like an Android-style elastic + * spacer: it stretches up to the reference spacing when there is room and + * compresses (down to a minimum gap) when space is tight. Crucially the whole + * row is budgeted together against the available display width, so slack left + * between early columns is reclaimed to make room for a wide later column + * rather than letting that column run off-screen. Content is only clipped once + * even the fully-compressed row no longer fits. + * + * On a display at least as wide as the reference, every spacer reaches its + * reference value and the layout is pixel-identical to the reference design. + * + * scene_menu_calc_dims() equalises all item widths beforehand, so the first + * item's objects provide the per-column content widths. + * + * @scn: Scene containing the menu + * @menu: Menu to calculate columns for + * @label_ofsp: Returns the label column x-offset (may be NULL) + * @ptr_ofsp: Returns the pointer column x-offset (may be NULL) + * @key_ofsp: Returns the key column x-offset (may be NULL) + * @desc_ofsp: Returns the description column x-offset (may be NULL) + */ +static void scene_menu_calc_cols(struct scene *scn, struct scene_obj_menu *menu, + int *label_ofsp, int *ptr_ofsp, int *key_ofsp, + int *desc_ofsp) +{ + const struct expo_theme *theme = &scn->expo->theme; + struct expo *exp = scn->expo; + int label_w = 0, ptr_w = 0, key_w = 0, desc_w = 0; + int ref_ptr, ref_key, ref_desc; + int ref_sp_ptr, ref_sp_key, ref_sp_desc; + int flex_ptr, flex_key, flex_desc, total_flex; + int sp_ptr, sp_key, sp_desc, avail; + int label_ofs, ptr_ofs, key_ofs, desc_ofs; + int xsize = 0; + + if (exp->display) { + struct video_priv *vid = dev_get_uclass_priv(exp->display); + + xsize = vid->xsize; + } + + /* Scaled reference offsets (fall back to raw values if no display) */ + ref_ptr = xsize ? MENU_REF_PTR_OFS * xsize / MENU_REF_WIDTH : + MENU_REF_PTR_OFS; + ref_key = xsize ? MENU_REF_KEY_OFS * xsize / MENU_REF_WIDTH : + MENU_REF_KEY_OFS; + ref_desc = xsize ? MENU_REF_DESC_OFS * xsize / MENU_REF_WIDTH : + MENU_REF_DESC_OFS; + + if (!list_empty(&menu->item_head)) { + const struct scene_menitem *first; + struct scene_obj *obj; + + first = list_first_entry(&menu->item_head, + struct scene_menitem, sibling); + obj = scene_obj_find(scn, first->label_id, SCENEOBJT_NONE); + if (obj) + label_w = obj->bbox.x1 - obj->bbox.x0; + obj = scene_obj_find(scn, first->key_id, SCENEOBJT_NONE); + if (obj) + key_w = obj->bbox.x1 - obj->bbox.x0; + obj = scene_obj_find(scn, first->desc_id, SCENEOBJT_NONE); + if (obj) + desc_w = obj->bbox.x1 - obj->bbox.x0; + } + if (menu->pointer_id) + scene_obj_get_hw(scn, menu->pointer_id, &ptr_w); + + /* The label column starts at the menu inset (a fixed left indent) */ + label_ofs = theme->menu_inset; + + /* + * The blank space ('spacer') each column wants before it to land on + * its reference offset, and how much of each is stretch above the + * minimum gap (i.e. how much it can give up under pressure). + */ + ref_sp_ptr = ref_ptr - (label_ofs + label_w); + ref_sp_key = ref_key - (ref_ptr + ptr_w); + ref_sp_desc = ref_desc - (ref_key + key_w); + + flex_ptr = max(0, ref_sp_ptr - MENU_COL_GAP); + flex_key = max(0, ref_sp_key - MENU_COL_GAP); + flex_desc = max(0, ref_sp_desc - MENU_COL_GAP); + total_flex = flex_ptr + flex_key + flex_desc; + + /* Start every spacer fully compressed, then hand out the slack below */ + sp_ptr = MENU_COL_GAP; + sp_key = MENU_COL_GAP; + sp_desc = MENU_COL_GAP; + + if (menu->fill_x1) { + /* + * Justify mode: stretch the columns so the row fills out to + * menu->fill_x1 (an absolute x-coordinate). This lets the menu + * use the full available width instead of bunching on the left + * within the reference column band. The slack above the fully + * compressed layout is shared between the spacers - in + * proportion to their reference flex where there is any, else + * spread evenly. + */ + int min_right = label_ofs + label_w + ptr_w + key_w + desc_w + + 3 * MENU_COL_GAP; + int target = menu->fill_x1 - menu->obj.bbox.x0; + int extra = max(0, target - min_right); + + if (total_flex) { + sp_ptr += flex_ptr * extra / total_flex; + sp_key += flex_key * extra / total_flex; + sp_desc += flex_desc * extra / total_flex; + } else { + int third = extra / 3; + + sp_ptr += third; + sp_key += third; + sp_desc += extra - 2 * third; + } + } else { + /* + * Decide how much stretch we can afford. 'avail' is the space + * available above the fully-compressed layout (every spacer at + * MENU_COL_GAP): capped at total_flex it yields the exact + * reference layout, clamped at 0 it yields full compression. + * The whole row is budgeted at once, so slack between early + * columns is given up to make room for a wide later column + * instead of letting it run off-screen. With no display + * attached we assume ample width (reference layout). + */ + avail = total_flex; + if (xsize) { + int budget = xsize - menu->obj.bbox.x0; + int min_right = label_ofs + label_w + ptr_w + key_w + + desc_w + 3 * MENU_COL_GAP; + + avail = clamp(budget - min_right, 0, total_flex); + } + + if (total_flex) { + sp_ptr += flex_ptr * avail / total_flex; + sp_key += flex_key * avail / total_flex; + sp_desc += flex_desc * avail / total_flex; + } + } + + ptr_ofs = label_ofs + label_w + sp_ptr; + key_ofs = ptr_ofs + ptr_w + sp_key; + desc_ofs = key_ofs + key_w + sp_desc; + + if (label_ofsp) + *label_ofsp = label_ofs; + if (ptr_ofsp) + *ptr_ofsp = ptr_ofs; + if (key_ofsp) + *key_ofsp = key_ofs; + if (desc_ofsp) + *desc_ofsp = desc_ofs; +} + /** * update_pointers() - Update the pointer object and handle highlights * @@ -98,11 +275,21 @@ static int update_pointers(struct scene_obj_menu *menu, uint id, bool point) /* adjust the pointer object to point to the selected item */ if (menu->pointer_id && item && point) { struct scene_obj *label; + int ptr_ofs; label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE); + /* + * Place the pointer in its own column, just to the right of the + * label. scene_menu_calc_cols() guarantees this offset clears + * the label content, so the label text (rendered after the + * pointer) never overdraws it, even on small displays. + */ + scene_menu_calc_cols(scn, menu, NULL, &ptr_ofs, NULL, NULL); + ret = scene_obj_set_pos(scn, menu->pointer_id, - menu->obj.bbox.x0 + 200, label->bbox.y0); + menu->obj.bbox.x0 + ptr_ofs, + label->bbox.y0); if (ret < 0) return log_msg_ret("ptr", ret); } @@ -258,11 +445,14 @@ int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr, } /* - * Currently everything is hard-coded to particular columns so this - * won't work on small displays and looks strange if the font size is - * small. This can be updated once text measuring is supported in - * vidconsole + * Work out the label, pointer, key and description column offsets. + * These reproduce the reference layout on large displays and compress + * elastically on small ones (see scene_menu_calc_cols()). */ + int label_ofs, key_ofs, desc_ofs; + + scene_menu_calc_cols(scn, menu, &label_ofs, NULL, &key_ofs, &desc_ofs); + sel_id = menu->cur_item_id; list_for_each_entry(item, &menu->item_head, sibling) { bool selected; @@ -283,24 +473,26 @@ int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr, selected = sel_id == item->id; /* - * Put the label on the left, then leave a space for the - * pointer, then the key and the description + * Put the label on the left, then the pointer column (placed by + * update_pointers()), then the key and the description. */ ret = scene_obj_set_pos(scn, item->label_id, - x + theme->menu_inset, y); + x + label_ofs, y); if (ret < 0) return log_msg_ret("nam", ret); scene_obj_set_hide(scn, item->label_id, stack && !open && !selected); if (item->key_id) { - ret = scene_obj_set_pos(scn, item->key_id, x + 230, y); + ret = scene_obj_set_pos(scn, item->key_id, + x + key_ofs, y); if (ret < 0) return log_msg_ret("key", ret); } if (item->desc_id) { - ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y); + ret = scene_obj_set_pos(scn, item->desc_id, + x + desc_ofs, y); if (ret < 0) return log_msg_ret("des", ret); } diff --git a/boot/vbe_abrec.h b/boot/vbe_abrec.h index 63c73297351..db0fb752327 100644 --- a/boot/vbe_abrec.h +++ b/boot/vbe_abrec.h @@ -75,7 +75,8 @@ struct abrec_state { * @bflow: Place to put the created bootflow, on success * @return 0 if OK, -ve on error */ -int abrec_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow); +int abrec_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow, + int seq); /** * vbe_simple_read_state() - Read the VBE simple state information diff --git a/boot/vbe_abrec_fw.c b/boot/vbe_abrec_fw.c index d52bd9ddff0..f48f13f156b 100644 --- a/boot/vbe_abrec_fw.c +++ b/boot/vbe_abrec_fw.c @@ -60,8 +60,11 @@ static const char *const pick_names[] = {"A", "B", "Recovery"}; * @blow: Place to put the created bootflow, on success * @return 0 if OK, -ve on error */ -int abrec_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow) +int abrec_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct udevice *media = dev_get_parent(bflow->dev); struct udevice *meth = bflow->method; struct abrec_priv *priv = dev_get_priv(meth); @@ -225,7 +228,7 @@ static int abrec_load_from_image(struct spl_image_info *image, log_debug("bootdev %s\n", bdev->name); bootflow_init(&bflow, bdev, meth); - ret = bootmeth_read_bootflow(meth, &bflow); + ret = bootmeth_read_bootflow(meth, &bflow, 0); log_debug("\nfw ret=%d\n", ret); if (ret) return log_msg_ret("rd", ret); diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index c6766c532f2..5487d0d1478 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -81,13 +81,17 @@ static int vbe_simple_get_state_desc(struct udevice *dev, char *buf, return 0; } -static int vbe_simple_read_bootflow(struct udevice *dev, struct bootflow *bflow) +static int vbe_simple_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq) { int ret; + if (seq) + return -ENOENT; + if (CONFIG_IS_ENABLED(BOOTMETH_VBE_SIMPLE_FW)) { if (vbe_phase() == VBE_PHASE_FIRMWARE) { - ret = vbe_simple_read_bootflow_fw(dev, bflow); + ret = vbe_simple_read_bootflow_fw(dev, bflow, seq); if (ret) return log_msg_ret("fw", ret); return 0; diff --git a/boot/vbe_simple.h b/boot/vbe_simple.h index dc3f70052b0..3417e480cac 100644 --- a/boot/vbe_simple.h +++ b/boot/vbe_simple.h @@ -45,7 +45,8 @@ struct simple_state { * @blow: Place to put the created bootflow, on success * @return 0 if OK, -ve on error */ -int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow); +int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow, + int seq); /** * vbe_simple_read_state() - Read the VBE simple state information diff --git a/boot/vbe_simple_fw.c b/boot/vbe_simple_fw.c index cb5534fc731..9bc921dce8d 100644 --- a/boot/vbe_simple_fw.c +++ b/boot/vbe_simple_fw.c @@ -48,8 +48,11 @@ binman_sym_declare(ulong, vpl, size); * @blow: Place to put the created bootflow, on success * @return 0 if OK, -ve on error */ -int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow) +int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow, + int seq) { + if (seq) + return -ENOENT; struct udevice *media = dev_get_parent(bflow->dev); struct udevice *meth = bflow->method; struct simple_priv *priv = dev_get_priv(meth); @@ -113,7 +116,7 @@ static int simple_load_from_image(struct spl_image_info *image, log_debug("bootdev %s\n", bdev->name); bootflow_init(&bflow, bdev, meth); - ret = bootmeth_read_bootflow(meth, &bflow); + ret = bootmeth_read_bootflow(meth, &bflow, 0); log_debug("\nfw ret=%d\n", ret); if (ret) return log_msg_ret("rd", ret); diff --git a/cmd/bootflow.c b/cmd/bootflow.c index 081f64e12ce..5550587978f 100644 --- a/cmd/bootflow.c +++ b/cmd/bootflow.c @@ -17,6 +17,7 @@ #include #include #include +#include /** * report_bootflow_err() - Report where a bootflow failed @@ -137,6 +138,17 @@ __maybe_unused static int bootflow_handle_menu(struct bootstd_priv *std, std->cur_bootflow = bflow; *bflowp = bflow; } + + /* + * While rendering, the menu moves the vidconsole cursor to position its + * text columns, which also shifts the console's left margin (xstart). + * If we simply return, later console output is indented to the last + * menu column and the menu remains on screen. Clear the display and + * reset the cursor so the console continues cleanly from the top-left. + */ + if (!text_mode && exp->cons) + vidconsole_clear_and_reset(exp->cons); + expo_destroy(exp); if (ret) return ret; diff --git a/common/board_f.c b/common/board_f.c index 85b888d4bb8..7d1f80cd7fb 100644 --- a/common/board_f.c +++ b/common/board_f.c @@ -122,7 +122,7 @@ static int display_text_info(void) return 0; } -#ifdef CONFIG_SYSRESET +#ifdef CONFIG_SYSRESET_PRINT_RESETINFO static int print_resetinfo(void) { struct udevice *dev; @@ -963,7 +963,7 @@ static void initcall_run_f(void) INITCALL(display_options); /* say that we are here */ INITCALL(display_text_info); /* show debugging info if required */ INITCALL(checkcpu); -#if CONFIG_IS_ENABLED(SYSRESET) +#if CONFIG_IS_ENABLED(SYSRESET_PRINT_RESETINFO) INITCALL(print_resetinfo); #endif /* display cpu info (and speed) */ diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 0618f42c941..0e0c60eb979 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -1230,22 +1230,40 @@ config SPL_BOOTZ help Boot a linux zimage from memory in falcon boot. + This provides an entry path which is unused when the OS is entered + through TF-A, since SPL then only places the kernel in memory and + BL31 is what enters it. It remains selectable: SPL can still enter + a kernel itself on such a platform, dropping to EL2 without TF-A + resident, just without any firmware services being available to the + OS afterwards. + config SPL_BOOTI bool "Allow booting an Image style Linux kernel from SPL" depends on SPL_OS_BOOT && !SPL_OS_BOOT_SECURE - default y if ARM64 || RISCV + default y if (ARM64 || RISCV) && !SPL_ATF select SPL_LIB_BOOTI help Boot an uncompressed linux kernel image from memory in falcon boot. + This provides an entry path which is unused when the OS is entered + through TF-A, since SPL then only places the kernel in memory and + BL31 is what enters it, so it does not default to y in that case. It + remains selectable: SPL can still enter a kernel itself on such a + platform, dropping to EL2 without TF-A resident, just without any + firmware services being available to the OS afterwards. + config SPL_OS_BOOT_ARGS bool "Allow SPL to load args for kernel in falcon mode" depends on (SPL_OS_BOOT || SPL_LOAD_FIT_OPENSBI_OS_BOOT) && !SPL_OS_BOOT_SECURE - default y if !SPL_OS_BOOT_SECURE + default y if !SPL_OS_BOOT_SECURE && !SPL_ATF help This option enables the SPL to load an args file (usually the FDT) alongside the kernel image in falcon boot mode. + This is not needed when the OS is entered through TF-A, since the + device tree is then taken from the same FIT as the kernel, which is + why it does not default to y in that case. + config SPL_PAYLOAD_ARGS_ADDR hex "Address in memory to load 'args' file for Falcon Mode to" depends on SPL_OS_BOOT_ARGS @@ -1635,6 +1653,16 @@ config SPL_UFS_RAW_U_BOOT_DEVNUM SCSI device number, which might differ from the UFS LUN if you have multiple SCSI devices attached and recognized by the SPL. +config SPL_UFS_RAW_OS_DEVNUM + int "Falcon mode: SCSI device number of the UFS device to load OS from" + depends on SPL_UFS_SUPPORT && SPL_OS_BOOT + default SPL_UFS_RAW_U_BOOT_DEVNUM + help + UFS devices are usually configured with multiple LUNs, which present + themselves as sequentially numbered SCSI devices. This option controls + which of them SPL will attempt to load a Falcon OS payload from. + It allows putting Linux FITs on a different UFS LU than U-Boot. + config SPL_UFS_RAW_U_BOOT_SECTOR hex "Address on the UFS to load U-Boot from" depends on SPL_UFS_SUPPORT @@ -1643,6 +1671,19 @@ config SPL_UFS_RAW_U_BOOT_SECTOR Address on the block device to load U-Boot from. Units: UFS sectors (1 sector = 4096 bytes). +config SPL_UFS_RAW_OS_SECTOR + hex "Falcon mode: address on the UFS to load OS image from" + depends on SPL_UFS_SUPPORT && SPL_OS_BOOT + default SPL_UFS_RAW_U_BOOT_SECTOR + help + Address on the block device to load a Falcon OS FIT/image from. + Units: UFS sectors (1 sector = 4096 bytes). + + The default shares the region with U-Boot, which suits Falcon-only + boot: the OS image is written where U-Boot would have gone, so only + one region has to be reserved and there is no fallback. Point this + at a separate location to keep a working U-Boot to fall back to. + config SPL_WATCHDOG bool "Support watchdog drivers" imply SPL_WDT if !HW_WATCHDOG diff --git a/common/spl/spl.c b/common/spl/spl.c index 722b18c98ed..abbd56ca2fa 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -105,6 +105,24 @@ __weak int spl_start_uboot(void) return 1; } +/* + * Decide about Falcon mode boot exactly once per SPL run. Board + * implementations of spl_start_uboot() are not required to be idempotent - + * they may sample a button or read a character from the console - so asking + * twice can yield two different answers. That matters because both the loader + * and the code which finally enters the OS (e.g. spl_invoke_atf()) need the + * decision, and they must not disagree about it. + */ +bool spl_falcon_boot(void) +{ + static int falcon = -1; + + if (falcon < 0) + falcon = !spl_start_uboot(); + + return falcon; +} + /* * Weak default function for arch specific zImage check. Return zero * and fill start and end address if image is recognized. diff --git a/common/spl/spl_atf.c b/common/spl/spl_atf.c index 17acd3665df..0cec5d8d46f 100644 --- a/common/spl/spl_atf.c +++ b/common/spl/spl_atf.c @@ -15,6 +15,7 @@ #include #include #include +#include #include /* Holds all the structures we need for bl31 parameter passing */ @@ -87,8 +88,8 @@ struct bl31_params *bl2_plat_get_bl31_params_default(ulong bl32_entry, SET_PARAM_HEAD(bl33_ep_info, ATF_PARAM_EP, ATF_VERSION_1, ATF_EP_NON_SECURE); - /* BL33 expects to receive the primary CPU MPID (through x0) */ - bl33_ep_info->args.arg0 = 0xffff & read_mpidr(); + /* Pass the FDT address in x0, per the TF-A BL33 / Linux boot protocol. */ + bl33_ep_info->args.arg0 = fdt_addr; bl33_ep_info->pc = bl33_entry; bl33_ep_info->spsr = SPSR_64(MODE_EL2, MODE_SP_ELX, DISABLE_ALL_EXECPTIONS); @@ -162,8 +163,8 @@ struct bl_params *bl2_plat_get_bl31_params_v2_default(ulong bl32_entry, SET_PARAM_HEAD(bl_params_node->ep_info, ATF_PARAM_EP, ATF_VERSION_2, ATF_EP_NON_SECURE); - /* BL33 expects to receive the primary CPU MPID (through x0) */ - bl_params_node->ep_info->args.arg0 = 0xffff & read_mpidr(); + /* Pass the FDT address in x0, per the TF-A BL33 / Linux boot protocol. */ + bl_params_node->ep_info->args.arg0 = fdt_addr; bl_params_node->ep_info->pc = bl33_entry; bl_params_node->ep_info->spsr = SPSR_64(MODE_EL2, MODE_SP_ELX, DISABLE_ALL_EXECPTIONS); @@ -189,7 +190,8 @@ static inline void raw_write_daif(unsigned int daif) typedef void __noreturn (*atf_entry_t)(struct bl31_params *params, void *plat_params); static void __noreturn bl31_entry(ulong bl31_entry, ulong bl32_entry, - ulong bl33_entry, ulong fdt_addr) + ulong bl33_entry, ulong fdt_addr, + ulong plat_param) { atf_entry_t atf_entry = (atf_entry_t)bl31_entry; void *bl31_params; @@ -206,7 +208,7 @@ static void __noreturn bl31_entry(ulong bl31_entry, ulong bl32_entry, if (!CONFIG_IS_ENABLED(SYS_DCACHE_OFF)) dcache_disable(); - atf_entry(bl31_params, (void *)fdt_addr); + atf_entry(bl31_params, (void *)plat_param); } static int spl_fit_images_find(void *blob, int os) @@ -267,10 +269,11 @@ ulong spl_fit_images_get_entry(void *blob, int node) void __noreturn spl_invoke_atf(struct spl_image_info *spl_image) { - ulong bl32_entry = 0; - ulong bl33_entry = CONFIG_TEXT_BASE; - void *blob = spl_image->fdt_addr; + bool falcon = CONFIG_IS_ENABLED(OS_BOOT) && spl_falcon_boot(); + void *blob = spl_image_fdt_addr(spl_image); + ulong bl33_entry = CONFIG_TEXT_BASE; ulong platform_param = (ulong)blob; + ulong bl32_entry = 0; int node; /* @@ -283,15 +286,29 @@ void __noreturn spl_invoke_atf(struct spl_image_info *spl_image) bl32_entry = spl_fit_images_get_entry(blob, node); /* - * Find the U-Boot binary (in /fit-images) load addreess or - * entry point (if different) and pass it as the BL3-3 entry - * point. - * This will need to be extended to support Falcon mode. + * Find BL33 entry point. In Falcon mode, prefer Linux when requested. + * Fall back to U-Boot if Linux cannot be resolved, unless secure Falcon + * mode is in effect, in which case an unsigned fallback is not allowed. */ + node = -FDT_ERR_NOTFOUND; + if (falcon) + node = spl_fit_images_find(blob, IH_OS_LINUX); - node = spl_fit_images_find(blob, IH_OS_U_BOOT); - if (node >= 0) + if (node < 0) { + if (falcon && CONFIG_IS_ENABLED(OS_BOOT_SECURE)) + panic("SPL: TF-A: no Linux BL33 image for secure Falcon boot"); + node = spl_fit_images_find(blob, IH_OS_U_BOOT); + } + + if (node >= 0) { bl33_entry = spl_fit_images_get_entry(blob, node); + } else if (falcon) { + /* + * Falcon mode was requested, so U-Boot proper was never loaded + * and CONFIG_TEXT_BASE holds nothing we could enter. + */ + panic("SPL: TF-A: no BL33 image to boot"); + } /* * If ATF_NO_PLATFORM_PARAM is set, we override the platform @@ -307,5 +324,5 @@ void __noreturn spl_invoke_atf(struct spl_image_info *spl_image) * using similar logic. */ bl31_entry(spl_image->entry_point, bl32_entry, - bl33_entry, platform_param); + bl33_entry, (ulong)blob, platform_param); } diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c index 18bff7b8d4a..0dbc0ba8153 100644 --- a/common/spl/spl_fit.c +++ b/common/spl/spl_fit.c @@ -193,6 +193,33 @@ static int get_aligned_image_size(struct spl_load_info *info, int data_size, return ALIGN(data_size, spl_get_bl_len(info)); } +/** + * fit_fill_image_info(): describe a loaded image to the caller + * @fit: points to the FIT image + * @node: offset of the DT node describing the image + * @image_info: filled in with where the image ended up and how big it is; + * ignored if NULL + * @load_addr: address the image was loaded to + * @size: number of bytes loaded, which may be zero + */ +static void fit_fill_image_info(const void *fit, int node, + struct spl_image_info *image_info, + ulong load_addr, ulong size) +{ + ulong entry_point; + + if (!image_info) + return; + + image_info->load_addr = load_addr; + image_info->size = size; + + if (!fit_image_get_entry(fit, node, &entry_point)) + image_info->entry_point = entry_point; + else + image_info->entry_point = FDT_ERROR; +} + /** * load_simple_fit(): load the image described in a certain FIT node * @info: points to information about the device to load data from @@ -291,6 +318,7 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset, if (!len) { log_warning("%s: Skip load '%s': image size is 0!\n", __func__, fit_get_name(fit, node, NULL)); + fit_fill_image_info(fit, node, image_info, load_addr, 0); return 0; } @@ -385,17 +413,7 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset, memmove(load_ptr, src, length); } - if (image_info) { - ulong entry_point; - - image_info->load_addr = load_addr; - image_info->size = length; - - if (!fit_image_get_entry(fit, node, &entry_point)) - image_info->entry_point = entry_point; - else - image_info->entry_point = FDT_ERROR; - } + fit_fill_image_info(fit, node, image_info, load_addr, length); log_debug("- done loading\n"); upl_add_image(fit, node, load_addr, length); diff --git a/common/spl/spl_ufs.c b/common/spl/spl_ufs.c index cef1843f40f..d35b5e7ad33 100644 --- a/common/spl/spl_ufs.c +++ b/common/spl/spl_ufs.c @@ -20,13 +20,38 @@ static ulong spl_ufs_load_read(struct spl_load_info *load, ulong off, ulong size return blk_dread(bd, sector, count, buf) << bd->log2blksz; } +static int spl_ufs_load_image_raw_os(struct spl_image_info *spl_image, + struct spl_boot_device *bootdev, + struct spl_load_info *load, + struct blk_desc *bd) +{ + ulong sector = config_opt_enabled(CONFIG_SPL_OS_BOOT, + CONFIG_SPL_UFS_RAW_OS_SECTOR, + CONFIG_SPL_UFS_RAW_U_BOOT_SECTOR); + int err; + + err = spl_load(spl_image, bootdev, load, 0, + sector << bd->log2blksz); + if (err) + return err; + + if (spl_image->os != IH_OS_LINUX && spl_image->os != IH_OS_TEE && + spl_image->os != IH_OS_ARM_TRUSTED_FIRMWARE) { + puts("Expected OS image is not found\n"); + return -ENOENT; + } + + return 0; +} + static int spl_ufs_load_image(struct spl_image_info *spl_image, struct spl_boot_device *bootdev) { unsigned long sector = CONFIG_SPL_UFS_RAW_U_BOOT_SECTOR; + struct spl_load_info load, os_load; + struct blk_desc *bd, *os_bd; int devnum = CONFIG_SPL_UFS_RAW_U_BOOT_DEVNUM; - struct spl_load_info load; - struct blk_desc *bd; + int os_devnum; int err; /* try to recognize storage devices immediately */ @@ -36,6 +61,35 @@ static int spl_ufs_load_image(struct spl_image_info *spl_image, return -ENODEV; spl_load_init(&load, spl_ufs_load_read, bd, bd->blksz); + if (CONFIG_IS_ENABLED(OS_BOOT) && spl_falcon_boot()) { + os_devnum = config_opt_enabled(CONFIG_SPL_OS_BOOT, + CONFIG_SPL_UFS_RAW_OS_DEVNUM, + CONFIG_SPL_UFS_RAW_U_BOOT_DEVNUM); + if (os_devnum == devnum) { + os_bd = bd; + os_load = load; + } else { + os_bd = blk_get_devnum_by_uclass_id(UCLASS_SCSI, os_devnum); + if (!os_bd) { + puts("spl_ufs_load_image: UFS OS device not found\n"); + if (CONFIG_IS_ENABLED(OS_BOOT_SECURE)) + return -ENODEV; + goto load_uboot; + } + spl_load_init(&os_load, spl_ufs_load_read, os_bd, os_bd->blksz); + } + + err = spl_ufs_load_image_raw_os(spl_image, bootdev, &os_load, os_bd); + if (!err) + return 0; + + puts("spl_ufs_load_image: Failed to load falcon payload\n"); + log_debug("(error=%d)\n", err); + if (CONFIG_IS_ENABLED(OS_BOOT_SECURE)) + return err; + } + +load_uboot: err = spl_load(spl_image, bootdev, &load, 0, sector << bd->log2blksz); if (err) { puts("spl_ufs_load_image: ufs block read error\n"); diff --git a/configs/evb-rk3576_defconfig b/configs/evb-rk3576_defconfig new file mode 100644 index 00000000000..681acf43570 --- /dev/null +++ b/configs/evb-rk3576_defconfig @@ -0,0 +1,75 @@ +CONFIG_ARM=y +CONFIG_SKIP_LOWLEVEL_INIT=y +CONFIG_COUNTER_FREQUENCY=24000000 +CONFIG_ARCH_ROCKCHIP=y +CONFIG_SPL_GPIO=y +CONFIG_DEFAULT_DEVICE_TREE="rockchip/rk3576-evb1-v10" +CONFIG_ROCKCHIP_RK3576=y +CONFIG_SYS_LOAD_ADDR=0x40c00800 +CONFIG_DEBUG_UART_BASE=0x2AD40000 +CONFIG_DEBUG_UART_CLOCK=24000000 +CONFIG_PCI=y +CONFIG_DEBUG_UART=y +CONFIG_DEFAULT_FDT_FILE="rockchip/rk3576-evb1-v10.dtb" +# CONFIG_DISPLAY_CPUINFO is not set +CONFIG_SPL_MAX_SIZE=0x40000 +# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set +CONFIG_SPL_DM_RESET=y +CONFIG_SPL_UFS_SUPPORT=y +CONFIG_CMD_MEMINFO=y +CONFIG_CMD_MEMINFO_MAP=y +CONFIG_CMD_ADC=y +CONFIG_CMD_GPIO=y +CONFIG_CMD_GPT=y +CONFIG_CMD_I2C=y +CONFIG_CMD_MISC=y +CONFIG_CMD_MMC=y +CONFIG_CMD_PCI=y +CONFIG_CMD_UFS=y +CONFIG_CMD_USB=y +CONFIG_CMD_ROCKUSB=y +CONFIG_CMD_USB_MASS_STORAGE=y +# CONFIG_CMD_SETEXPR is not set +CONFIG_CMD_RNG=y +CONFIG_CMD_REGULATOR=y +# CONFIG_SPL_DOS_PARTITION is not set +CONFIG_OF_SPL_REMOVE_PROPS="clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents" +CONFIG_BUTTON=y +CONFIG_BUTTON_GPIO=y +# CONFIG_USB_FUNCTION_FASTBOOT is not set +CONFIG_ROCKCHIP_GPIO=y +CONFIG_SYS_I2C_ROCKCHIP=y +CONFIG_LED=y +CONFIG_LED_GPIO=y +CONFIG_SUPPORT_EMMC_RPMB=y +CONFIG_MMC_DW=y +CONFIG_MMC_DW_ROCKCHIP=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_SDMA=y +CONFIG_MMC_SDHCI_ROCKCHIP=y +CONFIG_PHY_REALTEK=y +CONFIG_DWC_ETH_QOS=y +CONFIG_DWC_ETH_QOS_ROCKCHIP=y +CONFIG_NVME_PCI=y +CONFIG_PCIE_DW_ROCKCHIP=y +CONFIG_PHY_ROCKCHIP_INNO_USB2=y +CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY=y +CONFIG_PHY_ROCKCHIP_USBDP=y +CONFIG_DM_PMIC=y +CONFIG_PMIC_RK8XX=y +CONFIG_REGULATOR_RK8XX=y +CONFIG_SCSI=y +CONFIG_BAUDRATE=1500000 +CONFIG_DEBUG_UART_SHIFT=2 +CONFIG_SYS_NS16550_MEM32=y +CONFIG_SYSRESET_PSCI=y +CONFIG_USB=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_GENERIC=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DOWNLOAD=y +CONFIG_USB_FUNCTION_ROCKUSB=y +CONFIG_UFS=y +CONFIG_UFS_ROCKCHIP=y +CONFIG_ERRNO_STR=y diff --git a/configs/flipper-one-rk3576_defconfig b/configs/flipper-one-rk3576_defconfig new file mode 100644 index 00000000000..956cd64ba93 --- /dev/null +++ b/configs/flipper-one-rk3576_defconfig @@ -0,0 +1,91 @@ +CONFIG_ARM=y +CONFIG_SKIP_LOWLEVEL_INIT=y +CONFIG_COUNTER_FREQUENCY=24000000 +CONFIG_ARCH_ROCKCHIP=y +CONFIG_SPL_GPIO=y +CONFIG_DEFAULT_DEVICE_TREE="rockchip/rk3576-flipper-one-rev-f0b0c1" +CONFIG_ROCKCHIP_RK3576=y +CONFIG_TARGET_FLIPPER_ONE_RK3576=y +CONFIG_SYS_LOAD_ADDR=0x48000000 +CONFIG_DEBUG_UART_BASE=0x2AD40000 +CONFIG_DEBUG_UART_CLOCK=24000000 +CONFIG_PCI=y +CONFIG_DEBUG_UART=y +CONFIG_BOOTDELAY=0 +CONFIG_AUTOBOOT_KEYED=y +CONFIG_AUTOBOOT_PROMPT="Autoboot in %d seconds, to stop use 's' key\n" +CONFIG_AUTOBOOT_STOP_STR="s" +CONFIG_BOOTCOMMAND="bootflow scan -l; if bootflow menu; then cls; bootflow boot; else run bootcmd; fi" +CONFIG_DEFAULT_FDT_FILE="rockchip/rk3576-flipper-one-rev-f0b0c1.dtb" +# CONFIG_DISPLAY_CPUINFO is not set +CONFIG_CYCLIC_MAX_CPU_TIME_US=50000 +CONFIG_SPL_MAX_SIZE=0x40000 +# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set +CONFIG_SPL_DM_RESET=y +CONFIG_SPL_UFS_SUPPORT=y +CONFIG_CMD_TCPM=y +CONFIG_CMD_MEMINFO=y +CONFIG_CMD_MEMINFO_MAP=y +CONFIG_CMD_ADC=y +CONFIG_CMD_GPIO=y +CONFIG_CMD_GPT=y +CONFIG_CMD_I2C=y +CONFIG_CMD_MISC=y +CONFIG_CMD_MMC=y +CONFIG_CMD_UFS=y +CONFIG_CMD_USB=y +CONFIG_CMD_ROCKUSB=y +CONFIG_CMD_USB_MASS_STORAGE=y +# CONFIG_CMD_SETEXPR is not set +CONFIG_CMD_RNG=y +CONFIG_CMD_REGULATOR=y +# CONFIG_SPL_DOS_PARTITION is not set +CONFIG_OF_SPL_REMOVE_PROPS="clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents" +CONFIG_BUTTON=y +CONFIG_BUTTON_GPIO=y +# CONFIG_USB_FUNCTION_FASTBOOT is not set +CONFIG_ROCKCHIP_GPIO=y +CONFIG_DM_PCA953X=y +CONFIG_SYS_I2C_ROCKCHIP=y +CONFIG_DM_KEYBOARD=y +CONFIG_FLIPPER_ONE_BTNS=y +CONFIG_SUPPORT_EMMC_RPMB=y +CONFIG_MMC_DW=y +CONFIG_MMC_DW_ROCKCHIP=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_SDMA=y +CONFIG_MMC_SDHCI_ROCKCHIP=y +CONFIG_PHY_ROCKCHIP_INNO_USB2=y +CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY=y +CONFIG_PHY_ROCKCHIP_USBDP=y +CONFIG_DM_PMIC=y +CONFIG_PMIC_RK8XX=y +CONFIG_REGULATOR_RK8XX=y +CONFIG_SCSI=y +CONFIG_BAUDRATE=1500000 +CONFIG_DEBUG_UART_SHIFT=2 +CONFIG_SYS_NS16550_MEM32=y +CONFIG_ROCKCHIP_SPI=y +CONFIG_SYSRESET_PSCI=y +CONFIG_USB=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_GENERIC=y +CONFIG_TYPEC_TCPM=y +CONFIG_TYPEC_FUSB302=y +CONFIG_USB_ONBOARD_HUB=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DOWNLOAD=y +CONFIG_USB_FUNCTION_ROCKUSB=y +CONFIG_UFS=y +CONFIG_UFS_ROCKCHIP=y +CONFIG_VIDEO=y +CONFIG_VIDEO_FONT_6X8=y +# CONFIG_VIDEO_FONT_8X16 is not set +# CONFIG_BACKLIGHT is not set +# CONFIG_VIDEO_BPP16 is not set +# CONFIG_VIDEO_BPP32 is not set +# CONFIG_SYS_WHITE_ON_BLACK is not set +# CONFIG_PANEL is not set +CONFIG_VIDEO_FLIPPER_ONE=y +CONFIG_ERRNO_STR=y diff --git a/configs/omni3576-rk3576_defconfig b/configs/omni3576-rk3576_defconfig index 13ef3112f1b..d1df3b8aca7 100644 --- a/configs/omni3576-rk3576_defconfig +++ b/configs/omni3576-rk3576_defconfig @@ -41,6 +41,9 @@ CONFIG_MMC_DW_ROCKCHIP=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_SDMA=y CONFIG_MMC_SDHCI_ROCKCHIP=y +CONFIG_PHY_REALTEK=y +CONFIG_DWC_ETH_QOS=y +CONFIG_DWC_ETH_QOS_ROCKCHIP=y CONFIG_NVME_PCI=y CONFIG_PCIE_DW_ROCKCHIP=y CONFIG_PHY_ROCKCHIP_INNO_USB2=y diff --git a/disk/part_efi.c b/disk/part_efi.c index d8b17ec2e91..062389c3af0 100644 --- a/disk/part_efi.c +++ b/disk/part_efi.c @@ -388,10 +388,23 @@ static int set_protective_mbr(struct blk_desc *desc) return 0; } +/** + * gpt_pte_blocks() - number of blocks taken up by a partition entry array + * + * @desc: block device descriptor + * @gpt_h: GPT header describing the array + * Return: number of blocks the array occupies on @desc + */ +static u32 gpt_pte_blocks(struct blk_desc *desc, const gpt_header *gpt_h) +{ + return DIV_ROUND_UP(le32_to_cpu(gpt_h->num_partition_entries) * + le32_to_cpu(gpt_h->sizeof_partition_entry), + desc->blksz); +} + int write_gpt_table(struct blk_desc *desc, gpt_header *gpt_h, gpt_entry *gpt_e) { - const int pte_blk_cnt = BLOCK_CNT((gpt_h->num_partition_entries - * sizeof(gpt_entry)), desc); + const int pte_blk_cnt = gpt_pte_blocks(desc, gpt_h); u32 calc_crc32; debug("max lba: %x\n", (u32)desc->lba); @@ -449,13 +462,11 @@ int gpt_fill_pte(struct blk_desc *desc, char *str_type_guid; unsigned char *bin_type_guid; #endif - size_t hdr_start = gpt_h->my_lba; + size_t hdr_start = le64_to_cpu(gpt_h->my_lba); size_t hdr_end = hdr_start + 1; - size_t pte_start = gpt_h->partition_entry_lba; - size_t pte_end = pte_start + - gpt_h->num_partition_entries * gpt_h->sizeof_partition_entry / - desc->blksz; + size_t pte_start = le64_to_cpu(gpt_h->partition_entry_lba); + size_t pte_end = pte_start + gpt_pte_blocks(desc, gpt_h); for (i = 0; i < parts; i++) { /* partition starting lba */ @@ -482,7 +493,9 @@ int gpt_fill_pte(struct blk_desc *desc, gpt_e[i].starting_lba = cpu_to_le64(start); if (offset > (last_usable_lba + 1)) { - log_debug("Partitions layout exceeds disk size\n"); + log_debug("Partitions layout exceeds disk size: " + LBAFU " > " LBAFU "\n", + offset, last_usable_lba + 1); return -E2BIG; } /* partition ending lba */ @@ -604,18 +617,29 @@ static uint32_t partition_entries_offset(struct blk_desc *desc) int gpt_fill_header(struct blk_desc *desc, gpt_header *gpt_h, char *str_guid, int parts_count) { + u32 pte_sectors; + gpt_h->signature = cpu_to_le64(GPT_HEADER_SIGNATURE_UBOOT); gpt_h->revision = cpu_to_le32(GPT_HEADER_REVISION_V1); gpt_h->header_size = cpu_to_le32(sizeof(gpt_header)); + gpt_h->num_partition_entries = cpu_to_le32(GPT_ENTRY_NUMBERS); + gpt_h->sizeof_partition_entry = cpu_to_le32(sizeof(gpt_entry)); + + /* + * Number of blocks occupied by the partition entry array. For the + * default of 128 entries that is 32 blocks on 512-byte sectors, but + * only 4 blocks on 4096-byte sectors. + */ + pte_sectors = gpt_pte_blocks(desc, gpt_h); + gpt_h->my_lba = cpu_to_le64(1); gpt_h->alternate_lba = cpu_to_le64(desc->lba - 1); - gpt_h->last_usable_lba = cpu_to_le64(desc->lba - 34); + /* Reserve space for backup GPT header (1) + backup partition entries */ + gpt_h->last_usable_lba = cpu_to_le64(desc->lba - pte_sectors - 2); gpt_h->partition_entry_lba = cpu_to_le64(partition_entries_offset(desc)); gpt_h->first_usable_lba = - cpu_to_le64(le64_to_cpu(gpt_h->partition_entry_lba) + 32); - gpt_h->num_partition_entries = cpu_to_le32(GPT_ENTRY_NUMBERS); - gpt_h->sizeof_partition_entry = cpu_to_le32(sizeof(gpt_entry)); + cpu_to_le64(le64_to_cpu(gpt_h->partition_entry_lba) + pte_sectors); gpt_h->header_crc32 = 0; gpt_h->partition_entry_array_crc32 = 0; @@ -747,8 +771,7 @@ static void restore_primary_gpt_header(gpt_header *gpt_h, struct blk_desc *desc) static int write_one_gpt_table(struct blk_desc *desc, gpt_header *gpt_h, gpt_entry *gpt_e) { - const int pte_blk_cnt = BLOCK_CNT((gpt_h->num_partition_entries - * sizeof(gpt_entry)), desc); + const int pte_blk_cnt = gpt_pte_blocks(desc, gpt_h); lbaint_t start; int ret = 0; @@ -828,7 +851,7 @@ int gpt_verify_partitions(struct blk_desc *desc, gpt_e = *gpt_pte; for (i = 0; i < parts; i++) { - if (i == gpt_head->num_partition_entries) { + if (i == le32_to_cpu(gpt_head->num_partition_entries)) { pr_err("More partitions than allowed!\n"); return -1; } @@ -854,7 +877,7 @@ int gpt_verify_partitions(struct blk_desc *desc, (unsigned long long)gpt_part_size, (unsigned long long)partitions[i].size); - if (le64_to_cpu(gpt_part_size) != partitions[i].size) { + if (gpt_part_size != partitions[i].size) { /* We do not check the extend partition size */ if ((i == parts - 1) && (partitions[i].size == 0)) continue; diff --git a/disk/part_mac.c b/disk/part_mac.c index dd3ce0be832..f0afab65d75 100644 --- a/disk/part_mac.c +++ b/disk/part_mac.c @@ -37,8 +37,8 @@ static int part_mac_read_pdb(struct blk_desc *desc, int part, */ static int part_test_mac(struct blk_desc *desc) { - ALLOC_CACHE_ALIGN_BUFFER(mac_driver_desc_t, ddesc, 1); - ALLOC_CACHE_ALIGN_BUFFER(mac_partition_t, mpart, 1); + ALLOC_CACHE_ALIGN_BUFFER_PAD(mac_driver_desc_t, ddesc, 1, desc->blksz); + ALLOC_CACHE_ALIGN_BUFFER_PAD(mac_partition_t, mpart, 1, desc->blksz); ulong i, n; if (part_mac_read_ddb(desc, ddesc)) { @@ -64,8 +64,8 @@ static int part_test_mac(struct blk_desc *desc) static void part_print_mac(struct blk_desc *desc) { ulong i, n; - ALLOC_CACHE_ALIGN_BUFFER(mac_driver_desc_t, ddesc, 1); - ALLOC_CACHE_ALIGN_BUFFER(mac_partition_t, mpart, 1); + ALLOC_CACHE_ALIGN_BUFFER_PAD(mac_driver_desc_t, ddesc, 1, desc->blksz); + ALLOC_CACHE_ALIGN_BUFFER_PAD(mac_partition_t, mpart, 1, desc->blksz); ldiv_t mb, gb; if (part_mac_read_ddb(desc, ddesc)) { @@ -208,8 +208,8 @@ static int part_mac_read_pdb(struct blk_desc *desc, int part, static int part_get_info_mac(struct blk_desc *desc, int part, struct disk_partition *info) { - ALLOC_CACHE_ALIGN_BUFFER(mac_driver_desc_t, ddesc, 1); - ALLOC_CACHE_ALIGN_BUFFER(mac_partition_t, mpart, 1); + ALLOC_CACHE_ALIGN_BUFFER_PAD(mac_driver_desc_t, ddesc, 1, desc->blksz); + ALLOC_CACHE_ALIGN_BUFFER_PAD(mac_partition_t, mpart, 1, desc->blksz); if (part_mac_read_ddb(desc, ddesc)) return -1; diff --git a/doc/board/rockchip/rockchip.rst b/doc/board/rockchip/rockchip.rst index de2df3046e2..453206103e0 100644 --- a/doc/board/rockchip/rockchip.rst +++ b/doc/board/rockchip/rockchip.rst @@ -150,6 +150,7 @@ List of mainline supported Rockchip boards: - Generic RK3576 (generic-rk3576) - Luckfox Omni3576 (omni3576-rk3576) - Radxa ROCK 4D (rock-4d-rk3576) + - Rockchip EVB1 RK3576 V10 (evb-rk3576) * rk3588 - ArmSoM Sige7 (sige7-rk3588) @@ -325,6 +326,15 @@ To build rk3588 boards: make evb-rk3588_defconfig make CROSS_COMPILE=aarch64-linux-gnu- +Falcon mode +""""""""""" + +ARM64 Rockchip SoCs can also boot Linux straight from SPL through TF-A, +without U-Boot proper running at all. Enabling CONFIG_ROCKCHIP_FALCON_IMAGE +makes binman build a ``u-boot-rockchip-falcon.itb`` holding TF-A, the kernel +and an optional initramfs, which are passed in with LINUX_KERNEL and +LINUX_INITRD. See :doc:`../../develop/falcon` for the details. + Flashing -------- diff --git a/doc/develop/falcon.rst b/doc/develop/falcon.rst index 3fc23f65c45..f29aaee06a1 100644 --- a/doc/develop/falcon.rst +++ b/doc/develop/falcon.rst @@ -95,11 +95,138 @@ Function that a board must implement ------------------------------------ void spl_board_prepare_for_linux(void) - optional, called from SPL before starting the kernel + optional, called from SPL before starting the kernel. Not called when the + kernel is entered through TF-A, see `Falcon Mode with TF-A`_; use + spl_board_prepare_for_boot() instead, which is called on every path. spl_start_uboot() required, returns "0" if SPL should start the kernel, "1" if U-Boot - must be started. + must be started. It may be called from more than one place in a single + SPL run, so prefer spl_falcon_boot() when querying the result - that + caches the answer, which matters for implementations that are not + idempotent, such as those sampling a button. + +Falcon Mode with TF-A +--------------------- + +On ARM64 SoCs which need ARM Trusted Firmware resident to provide firmware +services to the OS, the kernel cannot usefully be entered directly from SPL. +Instead SPL loads a FIT holding BL31 as its ``firmware`` image and the kernel +as a ``loadable``, hands control to BL31, and BL31 enters the kernel as BL33. + +SPL picks BL33 in spl_invoke_atf(), which looks for an image with +``os = "linux"`` in the ``/fit-images`` node it appended to the device tree +and falls back to ``os = "u-boot"``. The kernel is entered at EL2 with the +device tree address in x0, as the arm64 boot protocol requires. + +Nothing generates a FIT holding both an OS and a U-Boot payload, so the two +are separate images, and it is the loader which decides where to read from. +In Falcon mode it tries the OS image first and falls back to the U-Boot one +if that cannot be loaded, unless secure Falcon mode is in effect, in which +case there is no fallback. For UFS the two are addressed by +CONFIG_SPL_UFS_RAW_OS_DEVNUM and CONFIG_SPL_UFS_RAW_OS_SECTOR against +CONFIG_SPL_UFS_RAW_U_BOOT_DEVNUM and CONFIG_SPL_UFS_RAW_U_BOOT_SECTOR, so +they can live on different logical units, at different offsets, or both. +spl_invoke_atf() then hands over to whichever of the two actually loaded, +which is what makes that fallback work. + +Pointing both at one location is also possible, and is what the defaults do: +only one region then has to be reserved, at the cost of having nothing left +to fall back to. + +What the kernel image may be +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The payload is an ordinary FIT image node with ``type = "kernel"`` and +``os = "linux"``. SPL copies it to its ``load`` address, and BL31 enters it +at ``entry``, or at ``load`` if no entry point is given. + +Neither booti_setup() nor bootz_setup() runs in this flow - SPL does not +enter the kernel itself, it only places it in memory for BL31 - so there is +no inspection of the arm64 Image header and no relocation to the text offset +the kernel asks for. The addresses in the FIT have to be directly usable, +which for an arm64 ``Image`` means 2 MiB aligned, and clear of the device +tree, of the initramfs and of every region BL31 and OP-TEE are linked to run +from - each of those is split into several regions when it is supplied as an +ELF, rather than occupying one contiguous range. + +The image is loaded verbatim unless CONFIG_SPL_GZIP or CONFIG_SPL_LZMA is +enabled, in which case the FIT ``compression`` property may be set to +``gzip`` or ``lzma`` and SPL decompresses while loading. Two limits apply +then. The uncompressed image has to fit in CONFIG_SYS_BOOTM_LEN. And the +compressed one is first staged at CONFIG_SYS_LOAD_ADDR, so that address has +to clear both the region being decompressed to and, when the FIT is itself +already in memory, the FIT: booting from RAM reads the image out of DRAM at +CONFIG_SPL_LOAD_FIT_ADDRESS, and a staging buffer landing inside it makes +the copy overwrite its own source, which shows up as:: + + ## Checking hash(es) for Image kernel ... sha256 error! + +The device trees in the FIT +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each device tree in the FIT can be pre-patched at build time with a +``/chosen`` node holding the kernel command line and the initramfs location, +so that nothing has to fix them up before boot. See the ``fit,bootargs`` and +``fit,initrd`` properties of binman's :ref:`etype_fit`. + +Differences from the classic flow +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* The device tree comes from the same FIT as the kernel, so no separate + 'args' file is involved and CONFIG_SPL_OS_BOOT_ARGS is not needed. + +* CONFIG_SPL_BOOTI provides an entry path which is unused here. + +* Because U-Boot proper is never loaded in this flow, SPL panics rather than + jumping to CONFIG_TEXT_BASE if neither a kernel nor a U-Boot image can be + resolved from the FIT. + +Neither of the first two defaults to y when CONFIG_SPL_ATF is enabled, so +nothing has to be turned off by hand. + +Example: Rockchip RK3576 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Set the following in the board defconfig:: + + CONFIG_SPL_OS_BOOT=y + CONFIG_ROCKCHIP_FALCON_IMAGE=y + +On a board whose SPL can load from SPI flash, CONFIG_SYS_SPI_KERNEL_OFFS +becomes visible and has no default, so give it a value suiting the flash +layout even if the kernel is loaded from elsewhere. The load addresses of +the kernel, device tree and initramfs default to offsets from the start of +DRAM which clear BL31 and OP-TEE, and can be adjusted with +CONFIG_ROCKCHIP_FALCON_KERNEL_LOAD and friends. + +Enabling CONFIG_SPL_LZMA or CONFIG_SPL_GZIP additionally compresses the +kernel in the image, trading some decompression time for a smaller one - for +a 30 MiB arm64 Image, roughly 35% of the original with LZMA and 39% with +gzip. Note that this compresses U-Boot itself as well, since both use the +same setting. + +The kernel and, optionally, an initramfs are passed in at build time:: + + $ make BL31=.../bl31.elf \ + ROCKCHIP_TPL=.../rk3576_ddr_lp4_2112MHz_lp5_2736MHz_v1.13.bin \ + LINUX_KERNEL=.../Image \ + LINUX_INITRD=.../ramdisk.cpio.zst + +This produces ``u-boot-rockchip-falcon.itb``, to be written at +CONFIG_SPL_UFS_RAW_OS_SECTOR (or the equivalent for the boot medium in use). +Its default shares the region with U-Boot, which suits Falcon-only boot; +point it elsewhere to keep a U-Boot image to fall back to. + +With CONFIG_ROCKCHIP_MASKROM_IMAGE enabled, an equivalent maskrom payload +``u-boot-rockchip-usb472-falcon.bin`` is built as well, which can be loaded +straight into RAM:: + + rockusb download-sram u-boot-rockchip-usb471.bin + rockusb download-ddr u-boot-rockchip-usb472-falcon.bin + +Rockchip attempts Falcon Mode boot whenever it is enabled. Boards which need +a runtime choice override board_spl_start_uboot(). Environment variables --------------------- diff --git a/drivers/clk/rockchip/clk_pll.c b/drivers/clk/rockchip/clk_pll.c index 9dec40b1fe8..9d529242be3 100644 --- a/drivers/clk/rockchip/clk_pll.c +++ b/drivers/clk/rockchip/clk_pll.c @@ -11,6 +11,7 @@ #include #include #include +#include static struct rockchip_pll_rate_table rockchip_auto_table; @@ -166,25 +167,26 @@ rockchip_pll_clk_set_by_auto(ulong fin_hz, return rate_table; } -static u32 -rockchip_rk3588_pll_k_get(u32 m, u32 p, u32 s, u64 fin_hz, u64 fvco) +static s16 +rockchip_rk3588_pll_k_get(u32 *m, u32 p, u32 s, u64 fin_hz, u64 fvco) { - u64 fref, fout, ffrac; - u32 k = 0; + u64 fref, ffrac; + int k; fref = fin_hz / p; - ffrac = fvco - (m * fref); - fout = ffrac * 65536; - k = fout / fref; + ffrac = fvco - (*m) * fref; + k = ffrac * 65536 / fref; if (k > 32767) { - fref = fin_hz / p; - ffrac = ((m + 1) * fref) - fvco; - fout = ffrac * 65536; - k = ((fout * 10 / fref) + 7) / 10; - if (k > 32767) - k = 0; - else - k = ~k + 1; + /* + * The requested rate is closer to the next integer multiplier + * m, so pick it and use a negative fractional coefficient k + */ + *m += 1; + ffrac = (*m) * fref - fvco; + /* + * Round up to avoid overshooting requested rate for negative k + */ + k = -(int)DIV64_U64_ROUND_UP(ffrac * 65536, fref); } return k; } @@ -193,8 +195,9 @@ static struct rockchip_pll_rate_table * rockchip_rk3588_pll_frac_by_auto(unsigned long fin_hz, unsigned long fout_hz) { struct rockchip_pll_rate_table *rate_table = &rockchip_auto_table; - u32 p, m, s, k; + u32 p, m, s; u64 fvco; + s16 k; for (s = 0; s <= 6; s++) { fvco = (u64)fout_hz << s; @@ -204,7 +207,10 @@ rockchip_rk3588_pll_frac_by_auto(unsigned long fin_hz, unsigned long fout_hz) for (m = 64; m <= 1023; m++) { if ((fvco >= m * fin_hz / p) && (fvco < (m + 1) * fin_hz / p)) { - k = rockchip_rk3588_pll_k_get(m, p, s, + u32 m_tmp = m; + + k = rockchip_rk3588_pll_k_get(&m_tmp, + p, s, fin_hz, fvco); if (!k) @@ -212,10 +218,7 @@ rockchip_rk3588_pll_frac_by_auto(unsigned long fin_hz, unsigned long fout_hz) rate_table->p = p; rate_table->s = s; rate_table->k = k; - if (k > 32767) - rate_table->m = m + 1; - else - rate_table->m = m; + rate_table->m = m_tmp; return rate_table; } } @@ -556,9 +559,10 @@ static int rk3588_pll_set_rate(struct rockchip_pll_clock *pll, static ulong rk3588_pll_get_rate(struct rockchip_pll_clock *pll, void __iomem *base, ulong pll_id) { - u32 m, p, s, k; + u32 m, p, s; u32 con = 0, shift, mode; - u64 rate, postdiv; + u64 rate; + s16 k; con = readl(base + pll->mode_offset); shift = pll->mode_shift; @@ -585,25 +589,7 @@ static ulong rk3588_pll_get_rate(struct rockchip_pll_clock *pll, rate = OSC_HZ / p; rate *= m; - if (k & BIT(15)) { - /* fractional mode */ - u64 frac_rate64; - - k = (~(k - 1)) & RK3588_PLLCON2_K_MASK; - frac_rate64 = OSC_HZ * k; - postdiv = p; - postdiv *= 65536; - do_div(frac_rate64, postdiv); - rate -= frac_rate64; - } else { - /* fractional mode */ - u64 frac_rate64 = OSC_HZ * k; - - postdiv = p; - postdiv *= 65536; - do_div(frac_rate64, postdiv); - rate += frac_rate64; - } + rate += div_s64((s64)OSC_HZ * k, p * 65536); rate = rate >> s; return rate; case RKCLK_PLL_MODE_DEEP: diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig index 90212fcf9ef..fb063b2c6ab 100644 --- a/drivers/fastboot/Kconfig +++ b/drivers/fastboot/Kconfig @@ -26,6 +26,18 @@ config USB_FUNCTION_FASTBOOT help This enables the USB part of the fastboot gadget. +config USB_FUNCTION_FASTBOOT_EP_BUFFER_SIZE + hex "USB fastboot endpoint buffer size" + depends on USB_FUNCTION_FASTBOOT + default 0x1000 + help + The buffer size used for USB endpoint transfers in fastboot. + This must always be an integral multiple of the maxpacket size + (64 or 512 or 1024), otherwise transfers may fail on certain + controllers like DWC3 that expect bulk OUT requests to be + divisible by maxpacket size. + The default value is 0x1000 (4096 bytes). + config UDP_FUNCTION_FASTBOOT depends on NET_LEGACY select FASTBOOT diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index df8fbf1551d..2bfcde35b23 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -46,6 +46,14 @@ config APPLE_SPI_KEYB laptops based on Apple SoCs. These keyboards use an Apple-specific HID-over-SPI protocol. +config FLIPPER_ONE_BTNS + bool "Enable Flipper One MCU buttons as keyboard" + depends on DM_KEYBOARD && DM_I2C + help + Enable support for the physical buttons on the Flipper One device, + accessed via the onboard MCU over I2C. Exposes the D-pad, center, + back and app switch buttons as keyboard input for menu navigation. + config BUTTON_KEYBOARD bool "Buttons as keyboard" depends on DM_KEYBOARD && DM_GPIO diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 1303fcdb0b7..10e61def69f 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_$(PHASE_)CROS_EC_KEYB) += cros_ec_keyb.o obj-$(CONFIG_$(PHASE_)OF_CONTROL) += key_matrix.o obj-$(CONFIG_$(PHASE_)DM_KEYBOARD) += input.o keyboard-uclass.o +obj-$(CONFIG_FLIPPER_ONE_BTNS) += flipper_one_btns.o obj-$(CONFIG_BUTTON_KEYBOARD) += button_kbd.o obj-$(CONFIG_CPCAP_POWER_BUTTON) += cpcap_pwrbutton.o diff --git a/drivers/input/flipper_one_btns.c b/drivers/input/flipper_one_btns.c new file mode 100644 index 00000000000..eebc2d3ffdd --- /dev/null +++ b/drivers/input/flipper_one_btns.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Flipper One MCU buttons keyboard driver + * Copyright (C) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * MCU register addresses are 16-bit, sent big-endian over I2C (per i2c_set_chip_offset_len(2)). + * Register values are 16-bit little-endian. + */ +#define FOMCU_REG_INTSTS_INPUT 0x0100 +#define FOMCU_INTSTS_INPUT_BTN BIT(0) + +#define FOMCU_REG_INPUT_BTNS 0x0200 + +/* Button bits in FOMCU_REG_INPUT_BTNS */ +#define FO_BTN_VIEW BIT(0) +#define FO_BTN_ESCAPE BIT(1) +#define FO_BTN_POWER BIT(2) +#define FO_BTN_EDIT BIT(3) +#define FO_BTN_RUN BIT(4) +#define FO_BTN_APPSELECT BIT(5) +#define FO_BTN_BACK BIT(6) +#define FO_BTN_DOWN BIT(7) +#define FO_BTN_RIGHT BIT(8) +#define FO_BTN_CENTER BIT(9) +#define FO_BTN_LEFT BIT(10) +#define FO_BTN_UP BIT(11) +#define FO_BTN_PTT BIT(12) + +/* Mask of all bit positions that have a non-RESERVED keycode mapping */ +#define FO_BTN_MAPPED_MASK (FO_BTN_APPSELECT | FO_BTN_BACK | \ + FO_BTN_DOWN | FO_BTN_RIGHT | \ + FO_BTN_CENTER | FO_BTN_LEFT | FO_BTN_UP) + +/* + * Keycode table indexed by button bit position in FOMCU_REG_INPUT_BTNS. + * KEY_RESERVED (0) means the button is not mapped. + * + * The input layer converts KEY_UP/DOWN/LEFT/RIGHT to ANSI escape sequences + * which cli_ch_process folds back to control characters recognised by + * bootmenu_conv_key(). KEY_ENTER -> '\r' -> BKEY_SELECT, + * KEY_ESC -> '\e' -> BKEY_QUIT. + */ +static const int fo_btn_keycodes[] = { + [0] = KEY_RESERVED, /* VIEW */ + [1] = KEY_RESERVED, /* ESCAPE */ + [2] = KEY_RESERVED, /* POWER */ + [3] = KEY_RESERVED, /* EDIT */ + [4] = KEY_RESERVED, /* RUN */ + [5] = KEY_TAB, /* APPSELECT */ + [6] = KEY_ESC, /* BACK */ + [7] = KEY_DOWN, /* DOWN */ + [8] = KEY_RIGHT, /* RIGHT */ + [9] = KEY_ENTER, /* CENTER */ + [10] = KEY_LEFT, /* LEFT */ + [11] = KEY_UP, /* UP */ + [12] = KEY_RESERVED, /* PTT */ +}; + +struct fo_keyb_priv { + unsigned long last_btns; +}; + +static int fo_read_reg(struct udevice *dev, u32 reg, u16 *val) +{ + __le16 buf; + int ret; + + ret = dm_i2c_read(dev, reg, (u8 *)&buf, sizeof(buf)); + if (ret) + return ret; + + *val = le16_to_cpu(buf); + return 0; +} + +/* + * Called by the input layer when its fifo is empty. Reads the interrupt + * status (clearing it in the process) and, if a button event is signalled, + * refreshes last_btns from the hardware. Then passes the full currently-held + * keycode set to input_send_keycodes(), which handles repeat timing and + * ASCII/ANSI conversion. + */ +static int fo_read_keys(struct input_config *input) +{ + struct udevice *dev = input->dev; + struct fo_keyb_priv *priv = dev_get_priv(dev); + int keycodes[ARRAY_SIZE(fo_btn_keycodes)]; + unsigned long i; + int n = 0; + u16 reg; + + /* + * Read interrupt status to detect changes. The read also clears the + * pending bits, preventing spurious events when the next OS sets up + * its own interrupt handler. + */ + if (!fo_read_reg(dev, FOMCU_REG_INTSTS_INPUT, ®) && + (reg & FOMCU_INTSTS_INPUT_BTN)) { + if (!fo_read_reg(dev, FOMCU_REG_INPUT_BTNS, ®)) + priv->last_btns = reg & FO_BTN_MAPPED_MASK; + } + + for_each_set_bit(i, &priv->last_btns, ARRAY_SIZE(fo_btn_keycodes)) + keycodes[n++] = fo_btn_keycodes[i]; + + input_send_keycodes(input, keycodes, n); + return 1; +} + +static int fo_keyb_start(struct udevice *dev) +{ + struct fo_keyb_priv *priv = dev_get_priv(dev); + u16 reg; + int ret; + + /* + * Drain any interrupt status accumulated before the driver started so + * that the first real poll starts from a clean baseline. + */ + fo_read_reg(dev, FOMCU_REG_INTSTS_INPUT, ®); + + /* + * Snapshot the current button state. Without this, buttons already + * held at startup would look like fresh presses on the first poll. + */ + ret = fo_read_reg(dev, FOMCU_REG_INPUT_BTNS, ®); + if (!ret) + priv->last_btns = reg & FO_BTN_MAPPED_MASK; + + return 0; +} + +static int fo_keyb_probe(struct udevice *dev) +{ + struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev); + struct stdio_dev *sdev = &uc_priv->sdev; + struct input_config *input = &uc_priv->input; + int ret; + + /* MCU register addresses are 16-bit wide */ + ret = i2c_set_chip_offset_len(dev, 2); + if (ret) + return ret; + + input_add_tables(input, false); + input_set_delays(input, 250, 50); + input->dev = dev; + input->read_keys = fo_read_keys; + + strcpy(sdev->name, "flipper-one-btns"); + return input_stdio_register(sdev); +} + +static const struct keyboard_ops fo_keyb_ops = { + .start = fo_keyb_start, +}; + +static const struct udevice_id fo_keyb_ids[] = { + { .compatible = "flipper,one-mcu" }, + { } +}; + +U_BOOT_DRIVER(flipper_one_keyb) = { + .name = "flipper_one_btns", + .id = UCLASS_KEYBOARD, + .of_match = fo_keyb_ids, + .probe = fo_keyb_probe, + .ops = &fo_keyb_ops, + .priv_auto = sizeof(struct fo_keyb_priv), +}; diff --git a/drivers/misc/qfw.c b/drivers/misc/qfw.c index 8a11637ca7f..4ef26da452e 100644 --- a/drivers/misc/qfw.c +++ b/drivers/misc/qfw.c @@ -118,7 +118,7 @@ static int qfw_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, return log_msg_ret("max", -ESHUTDOWN); log_debug("reading bootflow with method: %s\n", bflow->method->name); - ret = bootmeth_read_bootflow(bflow->method, bflow); + ret = bootmeth_read_bootflow(bflow->method, bflow, 0); if (ret) return log_msg_ret("method", ret); diff --git a/drivers/sysreset/Kconfig b/drivers/sysreset/Kconfig index 16ef434a8d9..a046dd02e66 100644 --- a/drivers/sysreset/Kconfig +++ b/drivers/sysreset/Kconfig @@ -43,6 +43,20 @@ config VPL_SYSRESET if SYSRESET +config SYSRESET_PRINT_RESETINFO + bool "Print reset information during boot" + default y if SANDBOX + default y if SYSRESET_MPC83XX + default y if SYSRESET_PSCI && ARCH_IMX9 + default y if DM_PMIC_PCA9450 + help + Print information about the cause of the last reset during early + boot, as reported by the get_status() method of the sysreset + drivers. This requires probing all sysreset devices in + board_init_f(), which costs boot time, so only enable it if at + least one of the sysreset drivers in use actually implements + get_status(). + config SYSRESET_CMD_RESET bool "sysreset implementation of the reset command" default y diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c index 2356b3bc0aa..84b983a5dae 100644 --- a/drivers/usb/dwc3/dwc3-generic.c +++ b/drivers/usb/dwc3/dwc3-generic.c @@ -597,14 +597,12 @@ static int dwc3_glue_reset_init(struct udevice *dev, else if (ret) return ret; - if (device_is_compatible(dev, "qcom,dwc3")) { - reset_assert_bulk(&glue->resets); - /* We should wait at least 6 sleep clock cycles, that's - * (6 / 32764) * 1000000 ~= 200us. But some platforms - * have slower sleep clocks so we'll play it safe. - */ - udelay(500); - } + reset_assert_bulk(&glue->resets); + /* We should wait at least 6 sleep clock cycles, that's + * (6 / 32764) * 1000000 ~= 200us. But some platforms + * have slower sleep clocks so we'll play it safe. + */ + udelay(500); ret = reset_deassert_bulk(&glue->resets); if (ret) { reset_release_bulk(&glue->resets); @@ -645,16 +643,6 @@ int dwc3_glue_probe(struct udevice *dev) int ret; struct phy phy; - ret = generic_phy_get_by_name(dev, "usb3-phy", &phy); - if (!ret) { - ret = generic_phy_init(&phy); - if (ret) - return ret; - } else if (ret != -ENOENT && ret != -ENODATA) { - debug("could not get phy (err %d)\n", ret); - return ret; - } - glue->regs = dev_read_addr_size_index(dev, 0, &glue->size); ret = dwc3_glue_clk_init(dev, glue); @@ -671,6 +659,16 @@ int dwc3_glue_probe(struct udevice *dev) return ret; } + ret = generic_phy_get_by_name(dev, "usb3-phy", &phy); + if (!ret) { + ret = generic_phy_init(&phy); + if (ret) + return ret; + } else if (ret != -ENOENT && ret != -ENODATA) { + debug("could not get phy (err %d)\n", ret); + return ret; + } + device_find_first_child(dev, &child); if (!child) return 0; diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c index 8df0e3f331d..8350651baf9 100644 --- a/drivers/usb/gadget/f_fastboot.c +++ b/drivers/usb/gadget/f_fastboot.c @@ -31,7 +31,7 @@ #define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_1_1 (0x0040) #define TX_ENDPOINT_MAXIMUM_PACKET_SIZE (0x0040) -#define EP_BUFFER_SIZE 4096 +#define EP_BUFFER_SIZE (CONFIG_USB_FUNCTION_FASTBOOT_EP_BUFFER_SIZE) /* * EP_BUFFER_SIZE must always be an integral multiple of maxpacket size * (64 or 512 or 1024), else we break on certain controllers like DWC3 diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 15000e21840..7dc148d743f 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -367,6 +367,16 @@ config VIDEO_EFI the graphics device is configured by the EFI BIOS already. This can in principle be used with any platform that has an EFI BIOS. +config VIDEO_FLIPPER_ONE + bool "Enable video support for Flipper One display" + depends on DM_SPI && DM_GPIO && VIDEO_BPP8 + help + Enable support for the Flipper One 256x144 8-bit grayscale SPI display. + The display uses SPI mode 3 (CPOL=1, CPHA=1) at up to 24 MHz and expects + a single continuous SPI write transaction per frame. Each frame consists + of 144 rows of 258 bytes: 256 grayscale pixels followed by 2 footer bytes + which are ignored by the display controller. + config VIDEO_VESA bool "Enable VESA video driver support" help diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 082b8967982..2ad6063b96d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_VIDEO_COREBOOT) += coreboot.o obj-$(CONFIG_VIDEO_DW_HDMI) += dw_hdmi.o obj-$(CONFIG_VIDEO_DW_MIPI_DSI) += dw_mipi_dsi.o obj-$(CONFIG_VIDEO_EFI) += efi.o +obj-$(CONFIG_VIDEO_FLIPPER_ONE) += flipper-one-display.o obj-y += imx/ obj-$(CONFIG_VIDEO_IVYBRIDGE_IGD) += ivybridge_igd.o obj-$(CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804) += anx9804.o diff --git a/drivers/video/flipper-one-display.c b/drivers/video/flipper-one-display.c new file mode 100644 index 00000000000..bbd6c976f87 --- /dev/null +++ b/drivers/video/flipper-one-display.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Video driver for the Flipper One 256x144 grayscale SPI display. + * + * The display accepts one SPI write transaction per frame. Each row + * consists of 256 bytes of 8-bit grayscale pixel data (left justified) + * followed by 2 footer bytes, giving a frame size of 258 * 144 = 37152 + * bytes, VIDEO_BPP8 format (8-bit grayscale) + * + * SPI wiring: mode 3 (CPOL=1, CPHA=1), up to 24 MHz, no MISO. + * + * Copyright (c) 2026 Flipper FZCO + */ + +#include +#include +#include +#include +#include +#include + +/* Physical display dimensions */ +#define DISPLAY_WIDTH 256 +#define DISPLAY_HEIGHT 144 + +/* + * Wire frame layout: 256 pixels + 2 footer bytes per row. + * The footer bytes are ignored by the display. + */ +#define ROW_BYTES 258 +#define FRAME_BYTES (ROW_BYTES * DISPLAY_HEIGHT) + +/** + * struct flipper_one_priv - Driver private data + * @last_spi_sync_ms: Absolute timestamp (ms) of the most recent completed SPI + * frame transfer, used to enforce a per-driver rate limit. + * The video uclass rate-limiting (VIDEO_SYNC_MS) runs + * *after* calling ops->video_sync, so without this guard + * every vidconsole character write and every cyclic tick + * would trigger a full 37 KiB SPI transfer. + * @active_gpio: Optional GPIO asserted (active-low: driven low) for the + * duration of each SPI frame transfer to signal the display MCU + * that a new frame is incoming. + */ +struct flipper_one_priv { + ulong last_spi_sync_ms; + struct gpio_desc active_gpio; +}; + +/** + * flipper_one_sync() - Push the internal framebuffer to the display + */ +static int flipper_one_sync(struct udevice *vid) +{ + struct flipper_one_priv *priv = dev_get_priv(vid); + struct video_priv *uc_priv = dev_get_uclass_priv(vid); + int ret; + + /* + * The video uclass calls ops->video_sync() unconditionally — the + * VIDEO_SYNC_MS rate-limit check only runs *after* this callback + * returns. Guard the expensive SPI transfer here so it fires at most + * once per VIDEO_SYNC_MS milliseconds regardless of how often + * vidconsole_putc() or the cyclic mechanism calls video_sync(). + */ + if (get_timer(priv->last_spi_sync_ms) < CONFIG_VIDEO_SYNC_MS) + return 0; + + ret = dm_spi_claim_bus(vid); + if (ret) { + dev_err(vid, "failed to claim SPI bus: %d\n", ret); + return ret; + } + + dm_gpio_set_value(&priv->active_gpio, 1); + + /* + * Send the entire frame in one transaction so that chip-select + * remains active throughout (SPI_XFER_BEGIN asserts CS before the + * first bit; SPI_XFER_END deasserts it after the last bit). + */ + ret = dm_spi_xfer(vid, FRAME_BYTES * 8, uc_priv->fb, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + if (ret) + dev_err(vid, "failed to send frame: %d\n", ret); + + dm_gpio_set_value(&priv->active_gpio, 0); + + dm_spi_release_bus(vid); + + priv->last_spi_sync_ms = get_timer(0); + + return ret; +} + +static int flipper_one_probe(struct udevice *dev) +{ + struct flipper_one_priv *priv = dev_get_priv(dev); + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + int ret; + + uc_priv->xsize = DISPLAY_WIDTH; + uc_priv->ysize = DISPLAY_HEIGHT; + uc_priv->bpix = VIDEO_BPP8; + uc_priv->rot = 0; + uc_priv->line_length = ROW_BYTES; + + /* + * The active-gpios line is asserted (logical 1 = GPIO driven to its + * active state, which is physically low for GPIO_ACTIVE_LOW) for the + * duration of each SPI frame transfer. The display MCU uses this + * signal to detect frame boundaries. The GPIO is optional in + * device-tree but required for the display to accept frames. + */ + ret = gpio_request_by_name(dev, "active-gpios", 0, &priv->active_gpio, + GPIOD_IS_OUT); + if (ret && ret != -ENOENT) { + dev_err(dev, "failed to request active-gpios: %d\n", ret); + return ret; + } + + return 0; +} + +static int flipper_one_bind(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + plat->size = FRAME_BYTES; + + return 0; +} + +static const struct video_ops flipper_one_ops = { + .video_sync = flipper_one_sync, +}; + +static const struct udevice_id flipper_one_ids[] = { + { .compatible = "flipper,one-display" }, + { } +}; + +U_BOOT_DRIVER(flipper_one_display) = { + .name = "flipper_one_display", + .id = UCLASS_VIDEO, + .of_match = flipper_one_ids, + .ops = &flipper_one_ops, + .bind = flipper_one_bind, + .probe = flipper_one_probe, + .plat_auto = sizeof(struct video_uc_plat), + .priv_auto = sizeof(struct flipper_one_priv), +}; diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index f1b2d61bd8f..327f94a90d9 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -642,8 +642,81 @@ int vidconsole_measure(struct udevice *dev, const char *name, uint size, bbox->valid = true; bbox->x0 = 0; bbox->y0 = 0; - bbox->x1 = priv->x_charsize * strlen(text); - bbox->y1 = priv->y_charsize; + + /* + * Fallback for bitmap consoles that do not implement ops->measure. + * When a pixel limit is provided and the caller wants line records, + * do a simple word-wrapping pass so that multi-line text objects + * (e.g. the Expo help prompts) render correctly. + */ + if (limit > 0 && lines) { + const int cw = priv->x_charsize; + const int ch = priv->y_charsize; + const char *s = text; + const char *line_start = text; + const char *last_space = NULL; + int x = 0, last_space_x = 0, max_x = 0; + + alist_empty(lines); + bbox->y1 = 0; + + for (;;) { + bool at_end = !*s; + bool overflow = !at_end && (x + cw > limit); + + if (*s == ' ') { + last_space = s; + last_space_x = x; + } + + if (at_end || overflow) { + struct vidconsole_mline mline; + const char *wrap_at; + int line_w; + + if (overflow && last_space && + last_space > line_start) { + /* word-wrap at last space */ + wrap_at = last_space; + line_w = last_space_x; + } else { + wrap_at = s; + line_w = x; + } + + mline.start = line_start - text; + mline.len = wrap_at - line_start; + mline.bbox.x0 = 0; + mline.bbox.y0 = bbox->y1; + mline.bbox.x1 = line_w; + mline.bbox.y1 = bbox->y1 + ch; + mline.bbox.valid = true; + if (!alist_add(lines, mline)) + return -ENOMEM; + + max_x = max(max_x, line_w); + bbox->y1 += ch; + + if (at_end) + break; + + /* resume after the wrapped space */ + line_start = wrap_at + 1; + last_space = NULL; + x = 0; + s = line_start; + continue; + } + + x += cw; + s++; + } + + bbox->x1 = max_x; + } else { + bbox->x1 = priv->x_charsize * strlen(text); + bbox->y1 = priv->y_charsize; + } return 0; } diff --git a/dts/upstream/src/arm64/rockchip/rk3576-flipper-one-rev-f0b0c1.dts b/dts/upstream/src/arm64/rockchip/rk3576-flipper-one-rev-f0b0c1.dts new file mode 100644 index 00000000000..54dc14fce58 --- /dev/null +++ b/dts/upstream/src/arm64/rockchip/rk3576-flipper-one-rev-f0b0c1.dts @@ -0,0 +1,1763 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2026 Flipper FZCO + */ + +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include "rk3576.dtsi" + +/ { + model = "Flipper One rev. F0B0C1"; + compatible = "flipper,one-rev-f0b0c1", "rockchip,rk3576"; + + aliases { + ethernet0 = &gmac0; + ethernet1 = &gmac1; + }; + + battery: battery { + compatible = "simple-battery"; + charge-full-design-microamp-hours = <3100000>; + constant-charge-current-max-microamp = <3100000>; + constant-charge-voltage-max-microvolt = <8650000>; + device-chemistry = "lithium-ion-polymer"; + operating-range-celsius = <0 60>; + voltage-min-design-microvolt = <6000000>; + }; + + chosen: chosen { + stdout-path = "serial0:1500000n8"; + }; + + hdmi-con { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&led_cd2>, <&led_cd3>; + pinctrl-names = "default"; + + led-cd2 { + color = ; + function = LED_FUNCTION_HEARTBEAT; + gpios = <&gpio0 RK_PD2 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led-cd3 { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "default-on"; + }; + }; + + vcc8v4_sys: regulator-vcc8v4-sys { + compatible = "regulator-fixed"; + regulator-name = "vcc8v4_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <8400000>; + regulator-max-microvolt = <8400000>; + /* Powered by the charger's SYS output */ + }; + + vcc3v3_control: regulator-vcc3v3-control { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_control"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc8v4_sys>; + }; + + gpio_vcc3v3: regulator-vcc3v3-extgpio { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "gpio_vcc3v3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpios = <&gpio_expander 0xe GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc3v3_control>; + }; + + vcc3v3_m2: regulator-vcc3v3-m2 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_m2"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + enable-active-high; + gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&m2_pwr_en>; + pinctrl-names = "default"; + startup-delay-us = <5000>; + vin-supply = <&vcc8v4_sys>; + }; + + vcc3v3_rtc_s5: regulator-vcc3v3-rtc-s5 { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_rtc_s5"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* Powered by the system battery */ + }; + + vcc5v0_device_s0: regulator-vcc5v0-device-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_device"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + /* + * Controlled by the GPIO expander pin P13, but turning it off + * makes the expander I2C bus stuck and unrecoverable due to + * the USB MUX misbehavior on the same bus + */ + vin-supply = <&vcc8v4_sys>; + }; + + gpio_vcc5v0: regulator-vcc5v0-extgpio { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "gpio_vcc5v0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpios = <&gpio_expander 0xd GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_device_s0>; + }; + + vusb_typea_up: regulator-vcc5v0-vusb-typea-up { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "vusb_typea_up"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpios = <&gpio_expander 0xa GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_device_s0>; + }; + + vcc5v0_sys_s5: regulator-vcc5v0-sys-s5 { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys_s5"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + /* + * Controlled by the GPIO expander pin P14, but we + * can't let this pin to ever change state, as it makes + * the system lose power + */ + vin-supply = <&vcc8v4_sys>; + }; + + vcc_2v0_pldo_s3: regulator-vcc-2v0-pldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_2v0_pldo_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + /* PMIC_EXT_EN_OUT */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v1_nldo_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + /* PMIC_EXT_EN_OUT */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vdd2l_0v9_ddr_s3: regulator-vdd2l-0v9-ddr-s3 { + compatible = "regulator-fixed"; + regulator-name = "vdd2l_0v9_ddr_s3"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + /* VCC_3V3_S3 */ + vin-supply = <&vcc5v0_sys_s5>; + }; + + vdd1v1_hub: regulator-vdd-1v1-hub { + compatible = "regulator-fixed"; + enable-active-high; + regulator-name = "vdd1v1_hub"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + gpios = <&gpio_expander 0x9 GPIO_ACTIVE_HIGH>; + vin-supply = <&vcc5v0_sys_s5>; + }; + + vcc_3v3_s0: regulator-vcc-3v3-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_3v3_s3>; + }; + + vcc_1v8_s0: regulator-vcc-1v8-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v8_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_1v8_s3>; + }; + + vccio_1v8_s0: regulator-vccio-1v8-s0 { + compatible = "regulator-fixed"; + regulator-name = "vccio_1v8_s0"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* VCCA_1V8_S0 */ + vin-supply = <&vcc_1v8_s3>; + }; + + vcc1v8_ufs_vccq2_s0: regulator-vcc1v8-ufs-vccq2-s0 { + compatible = "regulator-fixed"; + regulator-name = "vcc1v8_ufs_vccq2_s0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vcc_1v8_s3>; + }; + + rfkill-onboard-wifi { + compatible = "rfkill-gpio"; + default-blocked; + radio-type = "wlan"; + label = "Onboard WiFi+BT"; + pinctrl-0 = <&wifi_pmu_en>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>; + }; + + rfkill-m2-wdisable1 { + compatible = "rfkill-gpio"; + radio-type = "wwan"; + label = "M.2 WWAN"; + pinctrl-0 = <&m2b_w_disable1>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_HIGH>; + }; + + rfkill-m2-wdisable2 { + compatible = "rfkill-gpio"; + radio-type = "wwan"; + label = "M.2 WWAN (WDISABLE2)"; + pinctrl-0 = <&m2b_w_disable2>; + pinctrl-names = "default"; + shutdown-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_HIGH>; + }; + + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "On-board Analog NAU8822"; + simple-audio-card,bitclock-master = <&masterdai>; + simple-audio-card,format = "i2s"; + simple-audio-card,frame-master = <&masterdai>; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,pin-switches = "Headphones", "Speaker", + "Headset Microphone", "Internal Microphone"; + simple-audio-card,routing = + "Headphones", "LHP", + "Headphones", "RHP", + "Speaker", "LSPK", + "Speaker", "RSPK", + "Line Out", "AUXOUT1", + "Line Out", "AUXOUT2", + "LMICP", "Internal Microphone", + "LMICN", "Internal Microphone", + "RMICP", "Headset Microphone"; + simple-audio-card,widgets = + "Headphones", "Headphones", + "Line Out", "Line Out", + "Speaker", "Speaker", + "Microphone", "Internal Microphone", + "Microphone", "Headset Microphone"; + + simple-audio-card,cpu { + sound-dai = <&sai2>; + }; + + masterdai: simple-audio-card,codec { + sound-dai = <&nau8822>; + system-clock-frequency = <12288000>; + }; + }; + + typea_up_con: usb-a-connector { + compatible = "usb-a-connector"; + data-role = "host"; + label = "USB-A (up)"; + power-role = "source"; + vbus-supply = <&vusb_typea_up>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typea_up_con_hs: endpoint { + remote-endpoint = <&hub_2_0_ds2>; + }; + }; + + port@1 { + reg = <1>; + typea_up_con_ss: endpoint { + remote-endpoint = <&hub_3_0_ds2>; + }; + }; + }; + }; + + typec_up_con: usb-c-connector { + compatible = "usb-c-connector"; + data-role = "host"; + label = "USB-C (up)"; + power-role = "source"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec_up_con_hs: endpoint { + remote-endpoint = <&hub_2_0_ds3>; + }; + }; + + port@1 { + reg = <1>; + typec_up_con_ss: endpoint { + remote-endpoint = <&usb_mux_in>; + }; + }; + }; + }; +}; + +&cpu_l0 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l1 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l2 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l3 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_b0 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b1 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b2 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&cpu_b3 { + cpu-supply = <&vdd_cpu_big_s0>; +}; + +&combphy0_ps { + status = "okay"; +}; + +&combphy1_psu { + status = "okay"; +}; + +&gmac0 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy0>; + phy-mode = "rgmii-id"; + phy-supply = <&vccio_1v8_s0>; + pinctrl-names = "default"; + pinctrl-0 = <ð0m0_miim + ð0m0_tx_bus2 + ð0m0_rx_bus2 + ð0m0_rgmii_clk + ð0m0_rgmii_bus + ðm0_clk0_25m_out>; + status = "okay"; +}; + +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy1>; + phy-mode = "rgmii-id"; + phy-supply = <&vccio_1v8_s0>; + pinctrl-names = "default"; + pinctrl-0 = <ð1m0_miim + ð1m0_tx_bus2 + ð1m0_rx_bus2 + ð1m0_rgmii_clk + ð1m0_rgmii_bus + ðm0_clk1_25m_out>; + status = "okay"; +}; + +&gpio2 { + m2-poweroff-hog { + gpios = ; + output-low; + line-name = "M.2 Full Card Power Off Disable"; + gpio-hog; + }; +}; + +&gpio3 { + m2-reset-hog { + gpios = ; + output-low; + line-name = "M.2 Reset Disable"; + gpio-hog; + }; +}; + +&gpu { + mali-supply = <&vdd_gpu_s0>; + status = "okay"; +}; + +&hdmi { + frl-enable-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_LOW>; + status = "okay"; +}; + +&hdmi_in { + hdmi_in_vp0: endpoint { + remote-endpoint = <&vp0_out_hdmi>; + }; +}; + +&hdmi_out { + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; +}; + +&hdmi_sound { + status = "okay"; +}; + +&hdptxphy { + status = "okay"; +}; + +&i2c0 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c0m1_xfer &i2c0_sda_pullup>; + pinctrl-names = "default"; + status = "okay"; + + embedded-controller@69 { + compatible = "flipper,one-mcu"; + reg = <0x69>; + interrupt-parent = <&gpio2>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&audio_headset_int &cpu_int>; + wakeup-source; + }; +}; + +&i2c1 { + status = "okay"; + + rk806: pmic@23 { + compatible = "rockchip,rk806"; + reg = <0x23>; + interrupt-parent = <&gpio0>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>, + <&rk806_dvs2_null>, <&rk806_dvs3_null>; + system-power-controller; + + vcc1-supply = <&vcc5v0_sys_s5>; + vcc2-supply = <&vcc5v0_sys_s5>; + vcc3-supply = <&vcc5v0_sys_s5>; + vcc4-supply = <&vcc5v0_sys_s5>; + vcc5-supply = <&vcc5v0_sys_s5>; + vcc6-supply = <&vcc5v0_sys_s5>; + vcc7-supply = <&vcc5v0_sys_s5>; + vcc8-supply = <&vcc5v0_sys_s5>; + vcc9-supply = <&vcc5v0_sys_s5>; + vcc10-supply = <&vcc5v0_sys_s5>; + vcc11-supply = <&vcc_2v0_pldo_s3>; + vcc12-supply = <&vcc5v0_sys_s5>; + vcc13-supply = <&vcc_1v1_nldo_s3>; + vcc14-supply = <&vcc_1v1_nldo_s3>; + vcca-supply = <&vcc5v0_sys_s5>; + + rk806_dvs1_null: dvs1-null-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun0"; + }; + + rk806_dvs2_null: dvs2-null-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun0"; + }; + + rk806_dvs3_null: dvs3-null-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun0"; + }; + + rk806_dvs1_slp: dvs1-slp-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun1"; + }; + + rk806_dvs1_pwrdn: dvs1-pwrdn-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun2"; + }; + + rk806_dvs1_rst: dvs1-rst-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun3"; + }; + + rk806_dvs2_slp: dvs2-slp-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun1"; + }; + + rk806_dvs2_pwrdn: dvs2-pwrdn-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun2"; + }; + + rk806_dvs2_rst: dvs2-rst-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun3"; + }; + + rk806_dvs2_dvs: dvs2-dvs-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun4"; + }; + + rk806_dvs2_gpio: dvs2-gpio-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun5"; + }; + + rk806_dvs3_slp: dvs3-slp-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun1"; + }; + + rk806_dvs3_pwrdn: dvs3-pwrdn-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun2"; + }; + + rk806_dvs3_rst: dvs3-rst-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun3"; + }; + + rk806_dvs3_dvs: dvs3-dvs-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun4"; + }; + + rk806_dvs3_gpio: dvs3-gpio-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun5"; + }; + + regulators { + vdd_cpu_big_s0: dcdc-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_cpu_big_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_npu_s0: dcdc-reg2 { + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_npu_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_lit_s0: dcdc-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_cpu_lit_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vcc_3v3_s3: dcdc-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vcc_3v3_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vdd_gpu_s0: dcdc-reg5 { + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <900000>; + regulator-ramp-delay = <12500>; + regulator-name = "vdd_gpu_s0"; + regulator-enable-ramp-delay = <400>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + vddq_ddr_s0: dcdc-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vddq_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_logic_s0: dcdc-reg7 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <800000>; + regulator-name = "vdd_logic_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s3: dcdc-reg8 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcc_1v8_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd2_ddr_s3: dcdc-reg9 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vdd2_ddr_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vdd_ddr_s0: dcdc-reg10 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <1200000>; + regulator-name = "vdd_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca_1v8_s0: pldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca_1v8_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca1v8_pldo2_s0: pldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pldo2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_1v2_s0: pldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-name = "vdda_1v2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca_3v3_s0: pldo-reg4 { + regulator-enable-ramp-delay = <500>; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-ramp-delay = <12500>; + regulator-name = "vcca_3v3_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vccio_3v3_sd_s0: pldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vccio_3v3_sd_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcca1v8_pldo6_s3: pldo-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pldo6_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd_0v75_s3: nldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdd_0v75_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdda_ddr_pll_s0: nldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdda_ddr_pll_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda0v75_hdmi_s0: nldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <837500>; + regulator-max-microvolt = <837500>; + regulator-name = "vdda0v75_hdmi_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_0v85_s0: nldo-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdda_0v85_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdda_0v75_s0: nldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdda_0v75_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + }; +}; + +&i2c2 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c2m0_xfer_dr5>; + pinctrl-names = "default"; + status = "okay"; + + gpio_expander: gpio@20 { + compatible = "ti,tca6416"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&gpio2>; + interrupts = ; + pinctrl-0 = <&expander_int>; + pinctrl-names = "default"; + vcc-supply = <&vcc3v3_control>; + + usb2-switch-hog { + gpios = <8 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Main Type-C USB 2.0 pins switch to CPU"; + gpio-hog; + }; + + vcc5v0-device-s0-hog { + gpios = <0xb GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Peripheral power supply"; + gpio-hog; + }; + + vcc5v0-sys-s5-hog { + gpios = <0xc GPIO_ACTIVE_HIGH>; + output-high; + line-name = "Main power supply to the system PMIC"; + gpio-hog; + }; + }; + + usbc0: typec-portc@22 { + compatible = "fcs,fusb302"; + reg = <0x22>; + interrupt-parent = <&gpio_expander>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + vbus-supply = <&vusb_typec>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + op-sink-microwatt = <10000000>; + /* fusb302 supports PD Rev 2.0 Ver 1.2 */ + pd-revision = /bits/ 8 <0x2 0x0 0x1 0x2>; + power-role = "dual"; + self-powered; + sink-pdos = ; + source-pdos = ; + try-power-role = "sink"; + + altmodes { + displayport { + svid = /bits/ 16 <0xff01>; + vdo = <0xffffffff>; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usbc0_hs: endpoint { + remote-endpoint = <&usb_drd0_hs_ep>; + }; + }; + port@1 { + reg = <1>; + usbc0_ss: endpoint { + remote-endpoint = <&usbdp_phy_ss_out>; + }; + }; + port@2 { + reg = <2>; + usbc0_sbu: endpoint { + remote-endpoint = <&usbdp_phy0_dp_out>; + }; + }; + }; + }; + }; + + ina4_1: power-sensor@40 { + compatible = "ti,ina4230"; + reg = <0x40>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdd_0v75_s3"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@1 { + reg = <1>; + label = "vcc_3v3_control"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <4000000>; + }; + + input@2 { + reg = <2>; + label = "vdd0v85_ddr_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@3 { + reg = <3>; + label = "vcc_3v3_s3"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + }; + + ina4_2: power-sensor@41 { + compatible = "ti,ina4230"; + reg = <0x41>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vddq0v51_ddr_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@1 { + reg = <1>; + label = "vdd0v75_npu_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + + input@2 { + reg = <2>; + label = "vdd0v75_gpu_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + + input@3 { + reg = <3>; + label = "vdd0v75_logic_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + }; + + ina4_3: power-sensor@43 { + compatible = "ti,ina4230"; + reg = <0x43>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdd0v75_cpu_big_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <6500000>; + }; + + input@1 { + reg = <1>; + label = "vdda_1v2_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <2>; + label = "vcca_1v8_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@3 { + reg = <3>; + label = "vdd0v75_cpu_lit_s0"; + shunt-resistor-micro-ohms = <10000>; + ti,maximum-expected-current-microamp = <5000000>; + }; + }; + + ina4_4: power-sensor@44 { + compatible = "ti,ina4230"; + reg = <0x44>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vdda_0v75_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@1 { + reg = <1>; + label = "vdda_0v85_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@2 { + reg = <2>; + label = "vdda0v75_hdmi_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@3 { + reg = <3>; + label = "vdda0v85_ddr_pll_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + }; + + ina_sys: power-sensor@45 { + compatible = "ti,ina219"; + reg = <0x45>; + #io-channel-cells = <1>; + label = "vcc8v4_sys"; + shunt-resistor = <4000>; + vs-supply = <&vcc3v3_control>; + }; + + ina4_5: power-sensor@46 { + compatible = "ti,ina4230"; + reg = <0x46>; + vs-supply = <&vcc3v3_control>; + #address-cells = <1>; + #size-cells = <0>; + + input@0 { + reg = <0>; + label = "vcca_3v3_s0"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <500000>; + }; + + input@1 { + reg = <1>; + label = "vccio_3v3_sd_s0"; + shunt-resistor-micro-ohms = <50000>; + ti,maximum-expected-current-microamp = <300000>; + }; + + input@2 { + reg = <2>; + label = "vdd2_1v05_ddr_s3"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <2500000>; + }; + + input@3 { + reg = <3>; + label = "vcc_1v8_s3"; + shunt-resistor-micro-ohms = <20000>; + ti,maximum-expected-current-microamp = <3000000>; + }; + }; + + usbmux: usb-mux@47 { + compatible = "ti,hd3ss3220"; + reg = <0x47>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-0 = <&usb_mux_int>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_mux_in: endpoint { + remote-endpoint = <&typec_up_con_ss>; + }; + }; + + port@1 { + reg = <1>; + usb_mux_out: endpoint { + remote-endpoint = <&hub_3_0_ds3>; + }; + }; + }; + }; + + gauge: fuel-gauge@55 { + compatible = "ti,bq28z610"; /* really bq28z620: tbc if we need a new compatible */ + reg = <0x55>; + monitored-battery = <&battery>; + }; + + bq25792: charger@6b { + compatible = "ti,bq25792"; + reg = <0x6b>; + input-current-limit-microamp = <3300000>; + interrupt-parent = <&gpio_expander>; + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + monitored-battery = <&battery>; + power-supplies = <&usbc0>; + + regulators { + vusb_typec: vbus { + regulator-max-microamp = <3320000>; + regulator-max-microvolt = <22000000>; + regulator-min-microamp = <0>; + regulator-min-microvolt = <2800000>; + regulator-name = "vusb_typec"; + }; + }; + }; +}; + +&i2c5 { + pinctrl-0 = <&i2c5m3_xfer>; + pinctrl-names = "default"; + status = "okay"; + + /* MIPI CSI camera + * PDN GPIO: GPIO3 RK_PC6 <&cam_pdn> + * CLK: CAM_CLK2_OUT_M0 <&cam_clk2m0_clk2> + */ +}; + +&i2c6 { + pinctrl-0 = <&i2c6m3_xfer>; + pinctrl-names = "default"; + status = "okay"; + + nau8822: audio-codec@1a { + compatible = "nuvoton,nau8822"; + reg = <0x1a>; + + /* all supplies: VCCA_3V3_S0 */ + assigned-clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; + assigned-clock-rates = <12288000>; + clock-names = "mclk"; + clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; + pinctrl-names = "default"; + pinctrl-0 = <&sai2m0_mclk>; + vdda-supply = <&vcca_3v3_s0>; + vddb-supply = <&vcca_3v3_s0>; + vddc-supply = <&vcca_3v3_s0>; + vddspk-supply = <&vcca_3v3_s0>; + #sound-dai-cells = <0>; + nuvoton,spk-btl; + }; +}; + +&i2c8 { + pinctrl-0 = <&i2c8m2_xfer>; + pinctrl-names = "default"; + status = "okay"; + + hym8563: rtc@51 { + compatible = "haoyu,hym8563"; + reg = <0x51>; + clock-output-names = "hym8563"; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rtc_int &clk_32k_pins>; + wakeup-source; + #clock-cells = <0>; + }; +}; + +&mdio0 { + rgmii_phy0: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c916"; + reg = <0x1>; + clocks = <&cru REFCLKO25M_GMAC0_OUT>; + assigned-clocks = <&cru REFCLKO25M_GMAC0_OUT>; + assigned-clock-rates = <25000000>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rgmii_phy0_int &rgmii_phy0_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>; + wakeup-source; + realtek,aldps-enable; + }; +}; + +&mdio1 { + rgmii_phy1: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c916"; + reg = <0x1>; + clocks = <&cru REFCLKO25M_GMAC1_OUT>; + assigned-clocks = <&cru REFCLKO25M_GMAC1_OUT>; + assigned-clock-rates = <25000000>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rgmii_phy1_int &rgmii_phy1_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio1 RK_PB4 GPIO_ACTIVE_LOW>; + wakeup-source; + realtek,aldps-enable; + }; +}; + +&pcie0 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0m1_pins &pcie0_rst>; + reset-gpios = <&gpio1 RK_PC3 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_m2>; + status = "okay"; +}; + +&pinctrl { + camera { + cam_pdn: cam-pdn { + rockchip,pins = <3 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + display { + spi0m0_wo_miso: spi0m0-wo-miso { + rockchip,pins = + /* spi0m0_csn0 */ + <0 RK_PC6 11 &pcfg_pull_none>, + /* spi0_clk_m0 */ + <0 RK_PC7 11 &pcfg_pull_none>, + /* spi0_mosi_m0 */ + <0 RK_PD0 11 &pcfg_pull_none>; + }; + + spi0m0_gpiomiso: spi0m0-gpiomiso { + rockchip,pins = <0 RK_PD1 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + gpio-expander { + expander_int: expander-int { + rockchip,pins = <2 RK_PA7 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + hdmi { + hdmi_enable_frl: hdmi-enable-frl { + rockchip,pins = <0 RK_PC3 RK_FUNC_GPIO &pcfg_pull_down>; + }; + }; + + hym8563 { + rtc_int: rtc-int { + rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + i2c0 { + i2c0_sda_pullup: i2c0-sda-pullup { + rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + i2c2 { + i2c2m0_xfer_dr5: i2c2m0-xfer-dr5 { + rockchip,pins = + /* i2c2_scl_m0 */ + <0 RK_PB7 9 &pcfg_pull_none_drv_level_5_smt>, + /* i2c2_sda_m0 */ + <0 RK_PC0 9 &pcfg_pull_none_drv_level_5_smt>; + }; + }; + + leds { + led_cd2: led-cd2 { + rockchip,pins = <0 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + led_cd3: led-cd3 { + rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + m2 { + m2_pwr_en: m2-pwren { + rockchip,pins = <0 RK_PB1 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + m2b_w_disable1: m2-rfkill-wdisable1 { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + m2b_w_disable2: m2-rfkill-wdisable2 { + rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + + mcu { + audio_headset_int: audio-headset-int { + rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + cpu_int: cpu-int { + rockchip,pins = <2 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + network { + rgmii_phy0_int: rgmii-phy0-int { + rockchip,pins = <1 RK_PC1 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + rgmii_phy0_rst: rgmii-phy0-rst { + rockchip,pins = <1 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + rgmii_phy1_int: rgmii-phy1-int { + rockchip,pins = <1 RK_PC2 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + rgmii_phy1_rst: rgmii-phy1-rst { + rockchip,pins = <1 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + pcie0 { + pcie0_rst: pcie0-rst { + rockchip,pins = <1 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + usb { + hub_reset: usb-hub-reset { + rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + usb_mux_int: usb-mux-int { + rockchip,pins = <1 RK_PC0 RK_FUNC_GPIO &pcfg_pull_up>; + }; + + usbc0_sbu1: usbc0-sbu1 { + rockchip,pins = <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + usbc0_sbu2: usbc0-sbu2 { + rockchip,pins = <4 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + wifibt { + wifi_pmu_en: wifi-pmu-en { + rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_down>; + }; + + wifi_wgpio0: bt-wake-host { + rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + wifi_wgpio1: wifi-wake-host { + rockchip,pins = <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&sai0 { + /* Serial audio for M.2 WWAN */ + pinctrl-names = "default"; + pinctrl-0 = <&sai0m2_lrck + &sai0m2_sclk + &sai0m2_sdi0 + &sai0m2_sdo0>; + status = "okay"; +}; + +&sai2 { + status = "okay"; +}; + +&sai6 { + status = "okay"; +}; + +&sdmmc { + bus-width = <4>; + cap-sd-highspeed; + cd-gpios = <&gpio0 RK_PA7 GPIO_ACTIVE_LOW>; + disable-wp; + no-sdio; + no-mmc; + sd-uhs-sdr104; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vccio_3v3_sd_s0>; + status = "okay"; +}; + +&saradc { + vref-supply = <&vcca_1v8_s0>; + status = "okay"; +}; + +&spi0 { + pinctrl-0 = <&spi0m0_wo_miso &spi0m0_gpiomiso>; + status = "okay"; + + display@0 { + compatible = "flipper,one-display"; + reg = <0>; + active-gpios = <&gpio0 RK_PD1 GPIO_ACTIVE_LOW>; + spi-cpha; + spi-cpol; + spi-max-frequency = <20000000>; + spi-rx-bus-width = <0>; + }; +}; + +&u2phy0 { + status = "okay"; +}; + +&u2phy0_otg { + status = "okay"; +}; + +&u2phy1 { + status = "okay"; +}; + +&u2phy1_otg { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart4 { + pinctrl-0 = <&uart4m1_xfer>; + pinctrl-names = "default"; + status = "okay"; + + /* MCU interconnect */ +}; + +&ufshc { + vcc-supply = <&vcc_3v3_s3>; + vccq2-supply = <&vcc1v8_ufs_vccq2_s0>; + status = "okay"; +}; + +&usbdp_phy { + mode-switch; + orientation-switch; + pinctrl-names = "default"; + pinctrl-0 = <&usbc0_sbu1 &usbc0_sbu2>; + sbu1-dc-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>; + sbu2-dc-gpios = <&gpio4 RK_PC5 GPIO_ACTIVE_HIGH>; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + + port@0 { + reg = <0>; + + usbdp_phy_ss_out: endpoint { + remote-endpoint = <&usbc0_ss>; + }; + }; + + port@1 { + reg = <1>; + + usbdp_phy_ss_in: endpoint { + remote-endpoint = <&usb_drd0_ss_ep>; + }; + }; + + port@2 { + reg = <2>; + + usbdp_phy_dp_in: endpoint { + }; + }; + + port@3 { + reg = <3>; + + usbdp_phy0_dp_out: endpoint { + remote-endpoint = <&usbc0_sbu>; + }; + }; + }; +}; + +&usb_drd0_dwc3 { + usb-role-switch; + dr_mode = "otg"; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd0_hs_ep: endpoint { + remote-endpoint = <&usbc0_hs>; + }; + }; + + port@1 { + reg = <1>; + usb_drd0_ss_ep: endpoint { + remote-endpoint = <&usbdp_phy_ss_in>; + }; + }; + }; +}; + +&usb_drd1_dwc3 { + #address-cells = <1>; + #size-cells = <0>; + dr_mode = "host"; + pinctrl-0 = <&hub_reset>; + pinctrl-names = "default"; + status = "okay"; + + /* 2.0 hub */ + hub_2_0: hub@1 { + compatible = "usb3431,6241"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_LOW>; + vdd1v1-supply = <&vdd1v1_hub>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + /* ext USB port 2.54mm */ + }; + + port@2 { + reg = <2>; + hub_2_0_ds2: endpoint { + remote-endpoint = <&typea_up_con_hs>; + }; + }; + + port@3 { + reg = <3>; + hub_2_0_ds3: endpoint { + remote-endpoint = <&typec_up_con_hs>; + }; + }; + + port@4 { + reg = <4>; + /* M.2 */ + }; + }; + }; + + /* 3.0 hub */ + hub_3_0: hub@2 { + compatible = "usb3431,6341"; + reg = <2>; + #address-cells = <1>; + #size-cells = <0>; + peer-hub = <&hub_2_0>; + vdd1v1-supply = <&vdd1v1_hub>; + + device@1 { + /* WiFi-BT module connection */ + compatible = "usbe8d,7961"; + reg = <1>; + #address-cells = <2>; + #size-cells = <0>; + + interface@0 { + /* BT part */ + compatible = "usbife8d,7961.config1.0"; + reg = <0 1>; + interrupt-parent = <&gpio1>; + interrupts = ; + interrupt-names = "wakeup"; + pinctrl-0 = <&wifi_wgpio0>; + pinctrl-names = "default"; + }; + + interface@0,3 { + /* WiFi part */ + compatible = "usbife8d,7961.config1.3"; + reg = <0 3>; + interrupt-parent = <&gpio1>; + interrupts = ; + pinctrl-0 = <&wifi_wgpio1>; + pinctrl-names = "default"; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + hub_3_0_ds2: endpoint { + remote-endpoint = <&typea_up_con_ss>; + }; + }; + + port@3 { + reg = <3>; + hub_3_0_ds3: endpoint { + remote-endpoint = <&usb_mux_out>; + }; + }; + + port@4 { + reg = <4>; + /* M.2 */ + }; + }; + }; +}; + +&vop { + status = "okay"; +}; + +&vop_mmu { + status = "okay"; +}; + +&vp0 { + vp0_out_hdmi: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi_in_vp0>; + }; +}; diff --git a/dts/upstream/src/arm64/rockchip/rk3576-luckfox-core3576.dtsi b/dts/upstream/src/arm64/rockchip/rk3576-luckfox-core3576.dtsi index 749f0a54b47..acfbb297851 100644 --- a/dts/upstream/src/arm64/rockchip/rk3576-luckfox-core3576.dtsi +++ b/dts/upstream/src/arm64/rockchip/rk3576-luckfox-core3576.dtsi @@ -225,6 +225,21 @@ cpu-supply = <&vdd_cpu_lit_s0>; }; +&gmac0 { + clock_in_out = "output"; + phy-mode = "rgmii-rxid"; + phy-handle = <&rgmii_phy0>; + pinctrl-names = "default"; + pinctrl-0 = <ð0m0_miim + ð0m0_tx_bus2 + ð0m0_rx_bus2 + ð0m0_rgmii_clk + ð0m0_rgmii_bus + ðm0_clk0_25m_out>; + tx_delay = <0x20>; + status = "okay"; +}; + &gpu { mali-supply = <&vdd_gpu_s0>; status = "okay"; @@ -645,6 +660,19 @@ }; }; +&mdio0 { + rgmii_phy0: ethernet-phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x0>; + clocks = <&cru REFCLKO25M_GMAC0_OUT>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac0_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PB3 GPIO_ACTIVE_LOW>; + }; +}; + &pcie0 { pinctrl-names = "default"; pinctrl-0 = <&pcie_reset>; @@ -654,6 +682,12 @@ }; &pinctrl { + gmac { + gmac0_rst: ethphy0-rst { + rockchip,pins = <2 RK_PB3 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + hdmi { hdmi_con_en: hdmi-con-en { rockchip,pins = <4 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>; @@ -712,8 +746,6 @@ no-sd; no-sdio; non-removable; - vmmc-supply = <&vcc_3v3_s3>; - vqmmc-supply = <&vccio_sd_s0>; status = "okay"; }; diff --git a/dts/upstream/src/arm64/rockchip/rk3576-luckfox-omni3576.dts b/dts/upstream/src/arm64/rockchip/rk3576-luckfox-omni3576.dts index 6c75959adfe..5ab0c7f3b54 100644 --- a/dts/upstream/src/arm64/rockchip/rk3576-luckfox-omni3576.dts +++ b/dts/upstream/src/arm64/rockchip/rk3576-luckfox-omni3576.dts @@ -30,7 +30,41 @@ }; }; +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy1>; + phy-mode = "rgmii-rxid"; + pinctrl-names = "default"; + pinctrl-0 = <ð1m0_miim + ð1m0_tx_bus2 + ð1m0_rx_bus2 + ð1m0_rgmii_clk + ð1m0_rgmii_bus + ðm0_clk1_25m_out>; + tx_delay = <0x20>; + status = "okay"; +}; + +&mdio1 { + rgmii_phy1: ethernet-phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x0>; + clocks = <&cru REFCLKO25M_GMAC1_OUT>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac1_rst>; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_LOW>; + }; +}; + &pinctrl { + gmac { + gmac1_rst: ethphy1-rst { + rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + leds { led_green_pin: led-green-pin { rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_none>; diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index e663dda12e8..f5f6d638ffd 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -88,12 +88,43 @@ static unsigned int btrfs_dirent_type_to_fs_type(u8 dirent_type) } } +/* + * Read the size stored in an inode item. A missing item is -ENOENT and + * leaves *size untouched. + */ +static int btrfs_get_inode_size(struct btrfs_root *root, u64 ino, u64 *size) +{ + struct btrfs_inode_item *ii; + struct btrfs_path path; + struct btrfs_key key; + int ret; + + key.objectid = ino; + key.type = BTRFS_INODE_ITEM_KEY; + key.offset = 0; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; + if (ret > 0) + ret = -ENOENT; + if (!ret) { + ii = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_inode_item); + *size = btrfs_inode_size(path.nodes[0], ii); + } + btrfs_release_path(&path); + return ret; +} + int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) { struct btrfs_dir_stream *dirs = container_of(fs_dirs, struct btrfs_dir_stream, parent); struct btrfs_fs_info *fs_info = current_fs_info; struct fs_dirent *dent = &dirs->dirent; struct btrfs_root *root; + struct btrfs_key location; struct btrfs_key key; u8 type; int ret; @@ -110,13 +141,29 @@ int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) memset(dent, 0, sizeof(*dent)); ret = btrfs_next_dir_entry(root, dirs->ino, &dirs->offset, dent->name, - sizeof(dent->name), &type); + sizeof(dent->name), &type, &location); if (ret < 0) return ret; if (ret > 0) return -ENOENT; dent->type = btrfs_dirent_type_to_fs_type(type); + + /* + * A subvolume entry points at a root item rather than an inode, and + * has no size of its own. Everything else carries one, and the fs + * layer prints it, so look it up. + */ + if (location.type == BTRFS_INODE_ITEM_KEY) { + u64 size; + + ret = btrfs_get_inode_size(root, location.objectid, &size); + if (ret < 0 && ret != -ENOENT) + return ret; + if (!ret) + dent->size = size; + } + *dentp = dent; return 0; } @@ -151,10 +198,8 @@ int btrfs_exists(const char *file) int btrfs_size(const char *file, loff_t *size) { struct btrfs_fs_info *fs_info = current_fs_info; - struct btrfs_inode_item *ii; struct btrfs_root *root; - struct btrfs_path path; - struct btrfs_key key; + u64 isize; u64 ino; u8 type; int ret; @@ -169,27 +214,13 @@ int btrfs_size(const char *file, loff_t *size) printf("Not a regular file: %s\n", file); return -ENOENT; } - btrfs_init_path(&path); - key.objectid = ino; - key.type = BTRFS_INODE_ITEM_KEY; - key.offset = 0; - - ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); - if (ret < 0) { - printf("Cannot lookup ino %llu\n", ino); + ret = btrfs_get_inode_size(root, ino, &isize); + if (ret) { + printf("Cannot read size of ino %llu\n", ino); return ret; } - if (ret > 0) { - printf("Ino %llu does not exist\n", ino); - ret = -ENOENT; - goto out; - } - ii = btrfs_item_ptr(path.nodes[0], path.slots[0], - struct btrfs_inode_item); - *size = btrfs_inode_size(path.nodes[0], ii); -out: - btrfs_release_path(&path); - return ret; + *size = isize; + return 0; } int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len, diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c index 8e932adc425..48c50e556b1 100644 --- a/fs/btrfs/ctree.c +++ b/fs/btrfs/ctree.c @@ -425,8 +425,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, level = btrfs_header_level(b); p->nodes[level] = b; ret = check_block(fs_info, p, level); - if (ret) - return -1; + if (ret) { + ret = -1; + goto err; + } ret = btrfs_bin_search(b, key, &slot); if (level != 0) { if (ret && slot > 0) @@ -461,8 +463,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, break; b = read_node_slot(fs_info, b, slot); - if (!extent_buffer_uptodate(b)) - return -EIO; + if (!extent_buffer_uptodate(b)) { + ret = -EIO; + goto err; + } } else { p->slots[level] = slot; /* @@ -479,6 +483,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, } } return 1; + +err: + btrfs_release_path(p); + return ret; } /* diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index 3fa9a8c9c02..cd3fd669f9a 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -1221,7 +1221,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, const char *name, int name_len, int mod); int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, - char *namebuf, int namebuf_len, u8 *ftype); + char *namebuf, int namebuf_len, u8 *ftype, + struct btrfs_key *location); /* inode.c */ int btrfs_lookup_path(struct btrfs_root *root, u64 ino, const char *filename, struct btrfs_root **root_ret, u64 *ino_ret, diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c index c7b87d60d98..6edda34818b 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -126,12 +126,16 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, * @namebuf: caller buffer that receives the NUL-terminated name * @namebuf_len: size of @namebuf in bytes * @ftype: receives the BTRFS_FT_* type of the entry + * @location: receives the key the entry points at, so the caller can + * reach the inode item without searching for the name + * again * * Return: 0 if an entry was returned, 1 when the directory is exhausted, * -ve on error. */ int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, - char *namebuf, int namebuf_len, u8 *ftype) + char *namebuf, int namebuf_len, u8 *ftype, + struct btrfs_key *location) { struct btrfs_path path; struct btrfs_key key; @@ -180,6 +184,7 @@ int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset, (unsigned long)(di + 1), name_len); namebuf[name_len] = '\0'; *ftype = btrfs_dir_type(path.nodes[0], di); + btrfs_dir_item_key_to_cpu(path.nodes[0], di, location); ret = 0; out: diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 3998ffc2c81..53f1059e1ef 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -362,9 +362,11 @@ int btrfs_read_extent_inline(struct btrfs_path *path, struct btrfs_file_extent_item *fi, char *dest) { struct extent_buffer *leaf = path->nodes[0]; + struct btrfs_fs_info *fs_info = leaf->fs_info; int slot = path->slots[0]; char *cbuf = NULL; char *dbuf = NULL; + u32 dbuf_size; u32 csize; u32 dsize; int ret; @@ -380,8 +382,17 @@ int btrfs_read_extent_inline(struct btrfs_path *path, /* Compressed extent, prepare the compressed and data buffer */ dsize = btrfs_file_extent_ram_bytes(leaf, fi); + /* + * The kernel compresses an inline extent as a whole block, zero-filling + * the tail past EOF, so the stream can decompress to more than + * ram_bytes. zstd's one-shot API rejects a destination that cannot + * hold the entire frame, so give the decompressor a full block and copy + * only ram_bytes back out. An inline extent never spans more than one + * block, which bounds the allocation. + */ + dbuf_size = max_t(u32, dsize, fs_info->sectorsize); cbuf = malloc(csize); - dbuf = malloc(dsize); + dbuf = malloc(dbuf_size); if (!cbuf || !dbuf) { ret = -ENOMEM; goto out; @@ -389,7 +400,7 @@ int btrfs_read_extent_inline(struct btrfs_path *path, read_extent_buffer(leaf, cbuf, btrfs_file_extent_inline_start(fi), csize); ret = btrfs_decompress(btrfs_file_extent_compression(leaf, fi), - cbuf, csize, dbuf, dsize); + cbuf, csize, dbuf, dbuf_size); if (ret < 0) { ret = -EIO; goto out; diff --git a/fs/sandbox/host_bootdev.c b/fs/sandbox/host_bootdev.c index 3f74972a9f8..e204c4b5314 100644 --- a/fs/sandbox/host_bootdev.c +++ b/fs/sandbox/host_bootdev.c @@ -31,7 +31,7 @@ static int host_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, bflow->state = BOOTFLOWST_MEDIA; bflow->fs_type = FS_TYPE_SANDBOX; - ret = bootmeth_read_bootflow(bflow->method, bflow); + ret = bootmeth_read_bootflow(bflow->method, bflow, 0); if (ret) return log_msg_ret("method", ret); diff --git a/include/bootflow.h b/include/bootflow.h index 1cfdda403ac..9a2d4aa6bdb 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -255,6 +255,10 @@ enum { * @cur_label: Current label being processed * @num_methods: Number of bootmeth devices in @method_order * @cur_method: Current method number, an index into @method_order + * @cur_subseq: Current sub-entry index within (method, part, bootdev). 0 for + * the first/only entry. Bumped by one each time a bootflow is + * successfully returned for the current tuple, and reset to 0 when the + * iterator advances to a new method, partition or bootdev. * @first_glob_method: Index of first global method within @method_order[], if * any, else -1 * @cur_prio: Current priority being scanned @@ -286,6 +290,7 @@ struct bootflow_iter { int cur_label; int num_methods; int cur_method; + int cur_subseq; int first_glob_method; enum bootdev_prio_t cur_prio; struct udevice **method_order; @@ -509,9 +514,14 @@ int bootflow_iter_check_system(const struct bootflow_iter *iter); * bootflows to it. * * @expp: Returns the expo created + * @width: Display width in pixels + * @height: Display height in pixels + * @char_h: Console character height in pixels; used to size text bboxes + * correctly regardless of the selected font. Pass 0 to let the + * function use a sensible default (the SY-scaled reference value). * Returns 0 on success, -ve on error */ -int bootflow_menu_new(struct expo **expp); +int bootflow_menu_new(struct expo **expp, int width, int height, int char_h); /** * bootflow_menu_add_all() - Add all bootflows to a menu diff --git a/include/bootmeth.h b/include/bootmeth.h index 2a492dfd73a..78d08e8e88d 100644 --- a/include/bootmeth.h +++ b/include/bootmeth.h @@ -91,9 +91,14 @@ struct bootmeth_ops { * @dev: Bootmethod device to use * @bflow: On entry, provides dev, hwpart, part and method. * Returns updated bootflow if found + * @seq: Sub-entry index within (method, part, bootdev). 0 is the + * first/only entry. Bootmeths that produce a single bootflow per + * tuple should return -ENOENT for any non-zero @seq. Bootmeths + * that can produce multiple entries should return them in order + * starting at 0, and -ENOENT when @seq is past the last entry. * Return: 0 if OK, -ve on error */ - int (*read_bootflow)(struct udevice *dev, struct bootflow *bflow); + int (*read_bootflow)(struct udevice *dev, struct bootflow *bflow, int seq); /** * set_bootflow() - set the bootflow for a device @@ -220,9 +225,12 @@ int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter); * @dev: Bootmethod device to check * @bflow: On entry, provides dev, hwpart, part and method. * Returns updated bootflow if found + * @seq: Sub-entry index within (method, part, bootdev). See the op + * documentation for semantics. * Return: 0 if OK, -ve on error */ -int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow); +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow, + int seq); /** * bootmeth_set_bootflow() - set the bootflow for a device diff --git a/include/configs/flipper-one-rk3576.h b/include/configs/flipper-one-rk3576.h new file mode 100644 index 00000000000..f0c817bb10e --- /dev/null +++ b/include/configs/flipper-one-rk3576.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Configuration for the Flipper One (RK3576) + * + * Copyright (c) 2026 Flipper FZCO + */ + +#ifndef __CONFIG_FLIPPER_ONE_RK3576_H +#define __CONFIG_FLIPPER_ONE_RK3576_H + +#define ROCKCHIP_DEVICE_SETTINGS \ + "stdin=serial,flipper-one-btns\0" \ + "stdout=serial,vidconsole\0" \ + "stderr=serial,vidconsole\0" + +#define BOOT_TARGETS "mmc0 scsi" + +#include "rk3576_common.h" + +#endif /* __CONFIG_FLIPPER_ONE_RK3576_H */ diff --git a/include/expo.h b/include/expo.h index 4dee479e9a0..03435a36c9a 100644 --- a/include/expo.h +++ b/include/expo.h @@ -83,12 +83,17 @@ struct expo_action { * @menuitem_gap_y: Gap between menu items in pixels * @menu_title_margin_x: Gap between right side of menu title and left size of * menu label + * @menu_pos_x: Absolute x-position (pixels) for the menu's left edge, or 0 to + * keep the position chosen when the menu was created. Lets a board with a + * small or unusually-proportioned display move the menu (and thus its + * item columns) without changing the default layout for everyone else. */ struct expo_theme { u32 font_size; u32 menu_inset; u32 menuitem_gap_y; u32 menu_title_margin_x; + u32 menu_pos_x; }; /** @@ -391,6 +396,10 @@ struct scene_obj_txt { * @title_id: ID of the title text, or 0 if none * @cur_item_id: ID of the current menu item, or 0 if none * @pointer_id: ID of the object pointing to the current selection + * @fill_x1: If non-zero, the absolute x-coordinate that the item columns + * should be justified (stretched) out to, so the row fills the available + * width instead of bunching on the left. 0 keeps the natural reference + * layout. * @item_head: List of items in the menu */ struct scene_obj_menu { @@ -398,6 +407,7 @@ struct scene_obj_menu { uint title_id; uint cur_item_id; uint pointer_id; + int fill_x1; struct list_head item_head; }; @@ -493,9 +503,11 @@ struct scene_obj_txtedit { * struct expo_arrange_info - Information used when arranging a scene * * @label_width: Maximum width of labels in scene + * @xsize: Display width in pixels, 0 if no display */ struct expo_arrange_info { int label_width; + int xsize; }; /** diff --git a/include/fastboot.h b/include/fastboot.h index b106d617749..48682d2e22d 100644 --- a/include/fastboot.h +++ b/include/fastboot.h @@ -26,9 +26,15 @@ */ #define FASTBOOT_MULTIRESPONSE_START ("MORE") -/* The 64 defined bytes plus \0 */ -#define FASTBOOT_COMMAND_LEN (64 + 1) -#define FASTBOOT_RESPONSE_LEN (64 + 1) +/* + * Fastboot protocol buffer sizes aligned with Android fastboot client: + * https://android.googlesource.com/platform/system/core/+/refs/heads/main/fastboot/constants.h + * + * FB_COMMAND_SZ 4096 - max command host sends + * FB_RESPONSE_SZ 256 - max response host expects + */ +#define FASTBOOT_COMMAND_LEN (4096 + 1) +#define FASTBOOT_RESPONSE_LEN (256 + 1) /** * All known commands to fastboot diff --git a/include/linux/math64.h b/include/linux/math64.h index 70a7ee3ff1d..78c184eb85b 100644 --- a/include/linux/math64.h +++ b/include/linux/math64.h @@ -1,8 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_MATH64_H #define _LINUX_MATH64_H #include #include +#include #include #if BITS_PER_LONG == 64 @@ -12,6 +14,11 @@ /** * div_u64_rem - unsigned 64bit divide with 32bit divisor with remainder + * @dividend: unsigned 64bit dividend + * @divisor: unsigned 32bit divisor + * @remainder: pointer to unsigned 32bit remainder + * + * Return: sets ``*remainder``, then returns dividend / divisor * * This is commonly provided by 32bit archs to provide an optimized 64bit * divide. @@ -24,6 +31,11 @@ static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder) /** * div_s64_rem - signed 64bit divide with 32bit divisor with remainder + * @dividend: signed 64bit dividend + * @divisor: signed 32bit divisor + * @remainder: pointer to signed 32bit remainder + * + * Return: sets ``*remainder``, then returns dividend / divisor */ static inline s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder) { @@ -33,6 +45,11 @@ static inline s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder) /** * div64_u64_rem - unsigned 64bit divide with 64bit divisor and remainder + * @dividend: unsigned 64bit dividend + * @divisor: unsigned 64bit divisor + * @remainder: pointer to unsigned 64bit remainder + * + * Return: sets ``*remainder``, then returns dividend / divisor */ static inline u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder) { @@ -42,17 +59,22 @@ static inline u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder) /** * div64_u64 - unsigned 64bit divide with 64bit divisor + * @dividend: unsigned 64bit dividend + * @divisor: unsigned 64bit divisor + * + * Return: dividend / divisor */ static inline u64 div64_u64(u64 dividend, u64 divisor) { return dividend / divisor; } -#define DIV64_U64_ROUND_UP(ll, d) \ - ({ u64 _tmp = (d); div64_u64((ll) + _tmp - 1, _tmp); }) - /** * div64_s64 - signed 64bit divide with 64bit divisor + * @dividend: signed 64bit dividend + * @divisor: signed 64bit divisor + * + * Return: dividend / divisor */ static inline s64 div64_s64(s64 dividend, s64 divisor) { @@ -92,10 +114,14 @@ extern s64 div64_s64(s64 dividend, s64 divisor); /** * div_u64 - unsigned 64bit divide with 32bit divisor + * @dividend: unsigned 64bit dividend + * @divisor: unsigned 32bit divisor * * This is the most common 64bit divide and should be used if possible, * as many 32bit archs can optimize this variant better than a full 64bit * divide. + * + * Return: dividend / divisor */ #ifndef div_u64 static inline u64 div_u64(u64 dividend, u32 divisor) @@ -107,6 +133,10 @@ static inline u64 div_u64(u64 dividend, u32 divisor) /** * div_s64 - signed 64bit divide with 32bit divisor + * @dividend: signed 64bit dividend + * @divisor: signed 32bit divisor + * + * Return: dividend / divisor */ #ifndef div_s64 static inline s64 div_s64(s64 dividend, s32 divisor) @@ -118,25 +148,6 @@ static inline s64 div_s64(s64 dividend, s32 divisor) u32 iter_div_u64_rem(u64 dividend, u32 divisor, u64 *remainder); -static __always_inline u32 -__iter_div_u64_rem(u64 dividend, u32 divisor, u64 *remainder) -{ - u32 ret = 0; - - while (dividend >= divisor) { - /* The following asm() prevents the compiler from - optimising this loop into a modulo operation. */ - asm("" : "+rm"(dividend)); - - dividend -= divisor; - ret++; - } - - *remainder = dividend; - - return ret; -} - #ifndef mul_u32_u32 /* * Many a GCC version messes this up and generates a 64x64 mult :-( @@ -147,17 +158,28 @@ static inline u64 mul_u32_u32(u32 a, u32 b) } #endif +#ifndef add_u64_u32 +/* + * Many a GCC version also messes this up. + * Zero extending b and then spilling everything to stack. + */ +static inline u64 add_u64_u32(u64 a, u32 b) +{ + return a + b; +} +#endif + #if defined(CONFIG_ARCH_SUPPORTS_INT128) && defined(__SIZEOF_INT128__) #ifndef mul_u64_u32_shr -static inline u64 mul_u64_u32_shr(u64 a, u32 mul, unsigned int shift) +static __always_inline u64 mul_u64_u32_shr(u64 a, u32 mul, unsigned int shift) { return (u64)(((unsigned __int128)a * mul) >> shift); } #endif /* mul_u64_u32_shr */ #ifndef mul_u64_u64_shr -static inline u64 mul_u64_u64_shr(u64 a, u64 mul, unsigned int shift) +static __always_inline u64 mul_u64_u64_shr(u64 a, u64 mul, unsigned int shift) { return (u64)(((unsigned __int128)a * mul) >> shift); } @@ -166,18 +188,14 @@ static inline u64 mul_u64_u64_shr(u64 a, u64 mul, unsigned int shift) #else #ifndef mul_u64_u32_shr -static inline u64 mul_u64_u32_shr(u64 a, u32 mul, unsigned int shift) +static __always_inline u64 mul_u64_u32_shr(u64 a, u32 mul, unsigned int shift) { - u32 ah, al; + u32 ah = a >> 32, al = a; u64 ret; - al = a; - ah = a >> 32; - ret = mul_u32_u32(al, mul) >> shift; if (ah) ret += mul_u32_u32(ah, mul) << (32 - shift); - return ret; } #endif /* mul_u64_u32_shr */ @@ -228,6 +246,27 @@ static inline u64 mul_u64_u64_shr(u64 a, u64 b, unsigned int shift) #endif +#ifndef mul_s64_u64_shr +static inline u64 mul_s64_u64_shr(s64 a, u64 b, unsigned int shift) +{ + u64 ret; + + /* + * Extract the sign before the multiplication and put it back + * afterwards if needed. + * + * Note: Linux uses abs() here, which U-Boot's abs() cannot do: + * it evaluates its argument as int for anything that is not a long. + */ + ret = mul_u64_u64_shr(abs64(a), b, shift); + + if (a < 0) + ret = -((s64) ret); + + return ret; +} +#endif /* mul_s64_u64_shr */ + #ifndef mul_u64_u32_div static inline u64 mul_u64_u32_div(u64 a, u32 mul, u32 divisor) { @@ -257,6 +296,108 @@ static inline u64 mul_u64_u32_div(u64 a, u32 mul, u32 divisor) } #endif /* mul_u64_u32_div */ +/* + * Not ported from Linux: mul_u64_add_u64_div_u64() and the + * mul_u64_u64_div_u64() / mul_u64_u64_div_u64_roundup() macros built on it. + * They need ~110 lines of lib/math/div64.c brought along, plus a u128 type + * and OPTIMIZER_HIDE_VAR(), and U-Boot has no users for them yet. Port the + * lot when the first one turns up. + */ + +/** + * DIV64_U64_ROUND_UP - unsigned 64bit divide with 64bit divisor rounded up + * @ll: unsigned 64bit dividend + * @d: unsigned 64bit divisor + * + * Divide unsigned 64bit dividend by unsigned 64bit divisor + * and round up. + * + * Return: dividend / divisor rounded up + */ +#define DIV64_U64_ROUND_UP(ll, d) \ + ({ u64 _tmp = (d); div64_u64((ll) + _tmp - 1, _tmp); }) + +/** + * DIV_U64_ROUND_UP - unsigned 64bit divide with 32bit divisor rounded up + * @ll: unsigned 64bit dividend + * @d: unsigned 32bit divisor + * + * Divide unsigned 64bit dividend by unsigned 32bit divisor + * and round up. + * + * Return: dividend / divisor rounded up + */ +#define DIV_U64_ROUND_UP(ll, d) \ + ({ u32 _tmp = (d); div_u64((ll) + _tmp - 1, _tmp); }) + +/** + * DIV64_U64_ROUND_CLOSEST - unsigned 64bit divide with 64bit divisor rounded to nearest integer + * @dividend: unsigned 64bit dividend + * @divisor: unsigned 64bit divisor + * + * Divide unsigned 64bit dividend by unsigned 64bit divisor + * and round to closest integer. + * + * Return: dividend / divisor rounded to nearest integer + */ +#define DIV64_U64_ROUND_CLOSEST(dividend, divisor) \ + ({ u64 _tmp = (divisor); div64_u64((dividend) + _tmp / 2, _tmp); }) + +/** + * DIV_U64_ROUND_CLOSEST - unsigned 64bit divide with 32bit divisor rounded to nearest integer + * @dividend: unsigned 64bit dividend + * @divisor: unsigned 32bit divisor + * + * Divide unsigned 64bit dividend by unsigned 32bit divisor + * and round to closest integer. + * + * Return: dividend / divisor rounded to nearest integer + */ +#define DIV_U64_ROUND_CLOSEST(dividend, divisor) \ + ({ u32 _tmp = (divisor); div_u64((u64)(dividend) + _tmp / 2, _tmp); }) + +/** + * DIV_S64_ROUND_CLOSEST - signed 64bit divide with 32bit divisor rounded to nearest integer + * @dividend: signed 64bit dividend + * @divisor: signed 32bit divisor + * + * Divide signed 64bit dividend by signed 32bit divisor + * and round to closest integer. + * + * Return: dividend / divisor rounded to nearest integer + */ +#define DIV_S64_ROUND_CLOSEST(dividend, divisor)( \ +{ \ + s64 __x = (dividend); \ + s32 __d = (divisor); \ + ((__x > 0) == (__d > 0)) ? \ + div_s64((__x + (__d / 2)), __d) : \ + div_s64((__x - (__d / 2)), __d); \ +} \ +) + +/** + * roundup_u64 - Round up a 64bit value to the next specified 32bit multiple + * @x: the value to up + * @y: 32bit multiple to round up to + * + * Rounds @x to the next multiple of @y. For 32bit @x values, see roundup and + * the faster round_up() for powers of 2. + * + * Return: rounded up value. + */ +static inline u64 roundup_u64(u64 x, u32 y) +{ + return DIV_U64_ROUND_UP(x, y) * y; +} + +/* + * U-Boot addition, not present in Linux's : Linux keeps + * abs_diff() in , which U-Boot does not have. Its other + * contents live in here, but abs_diff() was added to this + * header instead, so leave it be rather than move it around. + */ + /** * abs_diff - return absolute value of the difference between the arguments * @a: the first argument diff --git a/include/pxe_utils.h b/include/pxe_utils.h index 0378f2889f7..653e1a7d866 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -202,6 +202,68 @@ void handle_pxe_menu(struct pxe_context *ctx, struct pxe_menu *cfg); */ struct pxe_menu *parse_pxefile(struct pxe_context *ctx, ulong menucfg); +/** + * label_create() - Allocate and zero-initialise a struct pxe_label + * + * The result must be freed with label_destroy(). + * + * Return: pointer to the new label, or NULL if out of memory + */ +struct pxe_label *label_create(void); + +/** + * label_destroy() - Free a struct pxe_label and its string members + * + * Frees @label as well as memory used by its name, kernel, config, append, + * initrd, fdt, fdtdir and fdtoverlays members, if they are non-NULL. All + * such members must therefore reference malloc()'d memory. + * + * @label: Label to free + */ +void label_destroy(struct pxe_label *label); + +/** + * parse_label_keys() - Parse the body of a label + * + * Walks the sequence of key/value lines that follow a 'label NAME' header, + * populating @label. Stops at end-of-file or at a token that does not + * belong inside a label (which is pushed back so the caller can handle it). + * + * This is exported so that callers handling formats which lack a 'label' + * header (e.g. Boot Loader Specification type #2 entry files) can populate + * a pre-created label directly from a file body. + * + * @c: Pointer to the cursor into the file being parsed; updated on return + * @cfg: Menu the label belongs to (used for 'menu default' bookkeeping) + * @label: Label to populate; must already be allocated and (when called for + * a file that has a 'label' header) attached to @cfg->labels + * @ignore_unknown: If true, silently skip unknown keys (and consume the + * rest of their lines) instead of stopping. This matches the Boot + * Loader Specification's requirement that unknown keys be ignored. + * If false (extlinux/pxelinux behaviour), an unknown token is pushed + * back so the caller can treat it as the start of the next label. + * Return: 1 on success, < 0 on error + */ +int parse_label_keys(char **c, struct pxe_menu *cfg, struct pxe_label *label, + bool ignore_unknown); + +/** + * label_boot() - Boot according to the contents of a single pxe_label + * + * On success this function does not return; on failure it returns to + * let the caller try a different label or surface an error. + * + * The kernel is staged at $kernel_addr_r, an optional initrd at + * $ramdisk_addr_r, and an optional FDT at $fdt_addr_r. If the label + * has an 'append' string it overwrites $bootargs. + * + * @ctx: PXE context + * @label: Label to boot + * Return: does not return on success; 0 if a 'localboot' label was + * processed; 1 on error + */ +int label_boot(struct pxe_context *ctx, struct pxe_label *label); + /** * format_mac_pxe() - Convert a MAC address to PXE format * diff --git a/include/spl.h b/include/spl.h index 5078d7525ab..8da82e87aa3 100644 --- a/include/spl.h +++ b/include/spl.h @@ -757,6 +757,17 @@ void __noreturn jump_to_image_optee(struct spl_image_info *spl_image); */ int spl_start_uboot(void); +/** + * spl_falcon_boot() - Check whether Falcon mode boot was requested + * + * Wrapper around spl_start_uboot() which calls it at most once and caches the + * answer, so that everything taking part in a single SPL run agrees on it. + * Prefer this over calling spl_start_uboot() directly. + * + * Return: true if SPL should start the OS, false if U-Boot must be started + */ +bool spl_falcon_boot(void); + /** * spl_display_print() - Display a board-specific message in SPL * diff --git a/include/spl_load.h b/include/spl_load.h index 525e0c9e86c..31ed5651f7b 100644 --- a/include/spl_load.h +++ b/include/spl_load.h @@ -63,6 +63,7 @@ static inline int _spl_load(struct spl_image_info *spl_image, return spl_load_imx_container(spl_image, info, offset); if (IS_ENABLED(CONFIG_SPL_LZMA) && + CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT) && image_get_magic(header) == IH_MAGIC && image_get_comp(header) == IH_COMP_LZMA) { spl_image->flags |= SPL_COPY_PAYLOAD_ONLY; diff --git a/lib/div64.c b/lib/div64.c index 779d7521f69..e3553c3956e 100644 --- a/lib/div64.c +++ b/lib/div64.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2003 Bernardo Innocenti * @@ -27,9 +28,9 @@ #ifndef __div64_32 /* - * Don't instrument this function as it may be called from tracing code, since - * it needs to read the timer and this often requires calling do_div(), which - * calls this function. + * U-Boot addition, not present upstream: don't instrument this function as it + * may be called from tracing code, since it needs to read the timer and this + * often requires calling do_div(), which calls this function. */ uint32_t __attribute__((weak, no_instrument_function)) __div64_32(u64 *n, u32 base) @@ -87,7 +88,7 @@ s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder) EXPORT_SYMBOL(div_s64_rem); #endif -/** +/* * div64_u64_rem - unsigned 64bit divide with 64bit divisor and remainder * @dividend: 64bit dividend * @divisor: 64bit divisor @@ -109,7 +110,7 @@ u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder) quot = div_u64_rem(dividend, divisor, &rem32); *remainder = rem32; } else { - int n = 1 + fls(high); + int n = fls(high); quot = div_u64(dividend >> n, divisor >> n); if (quot != 0) @@ -127,7 +128,7 @@ u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder) EXPORT_SYMBOL(div64_u64_rem); #endif -/** +/* * div64_u64 - unsigned 64bit divide with 64bit divisor * @dividend: 64bit dividend * @divisor: 64bit divisor @@ -147,7 +148,7 @@ u64 div64_u64(u64 dividend, u64 divisor) if (high == 0) { quot = div_u64(dividend, divisor); } else { - int n = 1 + fls(high); + int n = fls(high); quot = div_u64(dividend >> n, divisor >> n); if (quot != 0) @@ -161,17 +162,17 @@ u64 div64_u64(u64 dividend, u64 divisor) EXPORT_SYMBOL(div64_u64); #endif -/** - * div64_s64 - signed 64bit divide with 64bit divisor - * @dividend: 64bit dividend - * @divisor: 64bit divisor - */ #ifndef div64_s64 s64 div64_s64(s64 dividend, s64 divisor) { s64 quot, t; - quot = div64_u64(abs(dividend), abs(divisor)); + /* + * Unlike Linux, U-Boot's abs() is not 64-bit safe: it evaluates its + * argument as int when sizeof(x) != sizeof(long), so both operands + * need abs64() here. + */ + quot = div64_u64(abs64(dividend), abs64(divisor)); t = (dividend ^ divisor) >> 63; return (quot ^ t) - t; @@ -185,8 +186,25 @@ EXPORT_SYMBOL(div64_s64); * Iterative div/mod for use when dividend is not expected to be much * bigger than divisor. */ +#ifndef iter_div_u64_rem u32 iter_div_u64_rem(u64 dividend, u32 divisor, u64 *remainder) { - return __iter_div_u64_rem(dividend, divisor, remainder); + u32 ret = 0; + + while (dividend >= divisor) { + /* + * The following asm() prevents the compiler from + * optimising this loop into a modulo operation. + */ + asm("" : "+rm"(dividend)); + + dividend -= divisor; + ret++; + } + + *remainder = dividend; + + return ret; } EXPORT_SYMBOL(iter_div_u64_rem); +#endif diff --git a/lib/zstd/zstd.c b/lib/zstd/zstd.c index 14bde369068..77823162dd7 100644 --- a/lib/zstd/zstd.c +++ b/lib/zstd/zstd.c @@ -8,14 +8,40 @@ #include #include #include +#include #include #include +/** + * zstd_frame_starts_at() - does a zstd frame header begin here? + * + * @src: buffer to inspect + * @size: bytes available at @src + * Return: true if @src begins with a zstd or skippable frame magic + */ +static bool zstd_frame_starts_at(const void *src, size_t size) +{ + u32 magic; + + if (size < sizeof(magic)) + return false; + + magic = get_unaligned_le32(src); + + return magic == ZSTD_MAGICNUMBER || + (magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START; +} + int zstd_decompress(struct abuf *in, struct abuf *out) { - zstd_dctx *ctx; - size_t wsize, len; + const u8 *in_pos = abuf_data(in); + size_t in_left = abuf_size(in); + u8 *out_pos = abuf_data(out); + size_t out_left = abuf_size(out); + size_t wsize, total = 0; + unsigned int frames = 0; void *workspace; + zstd_dctx *ctx; int ret; wsize = zstd_dctx_workspace_bound(); @@ -34,27 +60,51 @@ int zstd_decompress(struct abuf *in, struct abuf *out) } /* - * Find out how large the frame actually is, there may be junk at - * the end of the frame that zstd_decompress_dctx() can't handle. + * The payload may be several frames concatenated - pzstd and multiple + * appended writes both produce those, and zstd_decompress_dctx() only + * ever decodes one. Keep decoding until the output fills up or the + * input stops looking like a frame; a non-frame tail is the junk the + * size probe below has always been here to tolerate. */ - len = zstd_find_frame_compressed_size(abuf_data(in), abuf_size(in)); - if (zstd_is_error(len)) { - log_err("%s: failed to detect compressed size: %d\n", __func__, - zstd_get_error_code(len)); - ret = -EINVAL; - goto do_free; + while (out_left && zstd_frame_starts_at(in_pos, in_left)) { + size_t csize, dsize; + + /* + * Find out how large the frame actually is, there may be junk at + * the end of the frame that zstd_decompress_dctx() can't handle. + */ + csize = zstd_find_frame_compressed_size(in_pos, in_left); + if (zstd_is_error(csize)) { + log_err("%s: failed to detect compressed size: %d\n", + __func__, zstd_get_error_code(csize)); + ret = -EINVAL; + goto do_free; + } + + dsize = zstd_decompress_dctx(ctx, out_pos, out_left, in_pos, + csize); + if (zstd_is_error(dsize)) { + log_err("%s: failed to decompress: %d\n", __func__, + zstd_get_error_code(dsize)); + ret = -EINVAL; + goto do_free; + } + + in_pos += csize; + in_left -= csize; + out_pos += dsize; + out_left -= dsize; + total += dsize; + frames++; } - len = zstd_decompress_dctx(ctx, abuf_data(out), abuf_size(out), - abuf_data(in), len); - if (zstd_is_error(len)) { - log_err("%s: failed to decompress: %d\n", __func__, - zstd_get_error_code(len)); + if (!frames) { + log_err("%s: no zstd frame found\n", __func__); ret = -EINVAL; goto do_free; } - ret = len; + ret = total; do_free: free(workspace); return ret; diff --git a/net/eth_bootdev.c b/net/eth_bootdev.c index b0fca6e8313..2b6870a222e 100644 --- a/net/eth_bootdev.c +++ b/net/eth_bootdev.c @@ -47,7 +47,7 @@ static int eth_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, /* See distro_pxe_read_bootflow() for the standard impl of this */ log_debug("dhcp complete - reading bootflow with method '%s'\n", bflow->method->name); - ret = bootmeth_read_bootflow(bflow->method, bflow); + ret = bootmeth_read_bootflow(bflow->method, bflow, 0); log_debug("reading bootflow returned %d\n", ret); if (ret) return log_msg_ret("method", ret); diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index 1cc137c9700..35393476404 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -940,7 +940,7 @@ static int bootflow_menu_theme(struct unit_test_state *uts) ut_assertok(scan_mmc4_bootdev(uts)); - ut_assertok(bootflow_menu_new(&exp)); + ut_assertok(bootflow_menu_new(&exp, 1366, 720, 0)); ut_assertok(bootflow_menu_add_all(exp)); node = ofnode_path("/bootstd/theme"); ut_assert(ofnode_valid(node)); diff --git a/test/dm/part.c b/test/dm/part.c index ad37d7f406f..4b48322fb80 100644 --- a/test/dm/part.c +++ b/test/dm/part.c @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include #include @@ -248,3 +250,101 @@ static int dm_test_part_get_info_by_uuid(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_part_get_info_by_uuid, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +/* + * Check that the GPT layout adapts to the block size of the device. Neither + * gpt_fill_header() nor gpt_fill_pte() does any block I/O, so a synthetic + * descriptor is enough here; partition_entries_offset() reads the device tree + * '/config' node, which is available without scanning for devices. + */ +static int dm_test_part_gpt_blksz(struct unit_test_state *uts) +{ + static const struct { + unsigned long blksz; + lbaint_t lba; + } cases[] = { + { 512, 0x2000 }, /* 4MB, the traditional 34-block layout */ + { 1024, 0x1000 }, + { 2048, 0x800 }, + { 4096, 0x400 }, /* 4K native media, e.g. UFS */ + { 8192, 0x200 }, + { 16384, 0x100 }, /* the array fits in a single block... */ + { 32768, 0x40 }, /* ...on a device smaller than 34 blocks */ + }; + + char str_disk_guid[UUID_STR_LEN + 1] = + "8d60b397-1bb6-4d33-80ee-b1587d24c2f8"; + struct disk_partition part; + struct blk_desc desc; + gpt_header gpt_h; + gpt_entry *gpt_e; + int i; + + gpt_e = calloc(GPT_ENTRY_NUMBERS, sizeof(gpt_entry)); + ut_assertnonnull(gpt_e); + + for (i = 0; i < ARRAY_SIZE(cases); i++) { + u64 entry_lba, first_lba, last_lba; + u32 pte_blks; + + memset(&desc, '\0', sizeof(desc)); + memset(&gpt_h, '\0', sizeof(gpt_h)); + desc.blksz = cases[i].blksz; + desc.log2blksz = LOG2(desc.blksz); + desc.lba = cases[i].lba; + + ut_assertok(gpt_fill_header(&desc, &gpt_h, str_disk_guid, 1)); + + /* + * Size the array with BLOCK_CNT() rather than the + * DIV_ROUND_UP() that gpt_pte_blocks() uses, so that this + * checks the layout instead of restating the implementation. + */ + pte_blks = BLOCK_CNT(le32_to_cpu(gpt_h.num_partition_entries) * + le32_to_cpu(gpt_h.sizeof_partition_entry), + (&desc)); + entry_lba = le64_to_cpu(gpt_h.partition_entry_lba); + first_lba = le64_to_cpu(gpt_h.first_usable_lba); + last_lba = le64_to_cpu(gpt_h.last_usable_lba); + + ut_asserteq_64(1, le64_to_cpu(gpt_h.my_lba)); + ut_asserteq_64(desc.lba - 1, le64_to_cpu(gpt_h.alternate_lba)); + + /* the primary array ends where the usable area begins */ + ut_assert(entry_lba >= 2); + ut_asserteq_64(entry_lba + pte_blks, first_lba); + ut_assert(first_lba < last_lba); + + /* + * write_gpt_table() puts the backup array at + * last_usable_lba + 1, so it has to end exactly where the + * backup header begins + */ + ut_asserteq_64(le64_to_cpu(gpt_h.alternate_lba), + last_lba + 1 + pte_blks); + + /* a partition with no start and no size fills the disk */ + memset(&part, '\0', sizeof(part)); + disk_partition_set_uuid(&part, str_disk_guid); + ut_assertok(gpt_fill_pte(&desc, &gpt_h, gpt_e, &part, 1)); + ut_asserteq_64(first_lba, le64_to_cpu(gpt_e[0].starting_lba)); + ut_asserteq_64(last_lba, le64_to_cpu(gpt_e[0].ending_lba)); + + /* a partition overlapping the entry array is rejected */ + part.start = entry_lba; + part.size = 1; + ut_asserteq(-ENOSPC, + gpt_fill_pte(&desc, &gpt_h, gpt_e, &part, 1)); + + /* so is one running past the end of the usable area */ + part.start = first_lba; + part.size = last_lba - first_lba + 2; + ut_asserteq(-E2BIG, + gpt_fill_pte(&desc, &gpt_h, gpt_e, &part, 1)); + } + + free(gpt_e); + + return 0; +} +DM_TEST(dm_test_part_gpt_blksz, 0); diff --git a/test/lib/compression.c b/test/lib/compression.c index 31b6e5b1eb4..042aa9488a7 100644 --- a/test/lib/compression.c +++ b/test/lib/compression.c @@ -138,6 +138,32 @@ static const char zstd_compressed[] = "\x01\xe4\xf4\x6e\xfa"; static const unsigned long zstd_compressed_size = sizeof(zstd_compressed) - 1; +/* + * The same text as two concatenated zstd frames, as produced by pzstd or by + * appending one compressed write to another: + * head -c 166 /tmp/plain.txt | zstd -19 -c > /tmp/multi.zst + * tail -c +167 /tmp/plain.txt | zstd -19 -c >> /tmp/multi.zst + * Decoding only the first frame silently truncates the output. + */ +static const char zstd_multiframe_compressed[] = + "\x28\xb5\x2f\xfd\x24\xa6\x5d\x02\x00\x62\x85\x10\x11\xa0\xed\x78" + "\xb8\x5e\xdd\x2c\x5a\xdd\xd2\x8d\xfa\xb7\xbc\xdf\x33\x23\x40\x70" + "\x6b\xf6\xd8\xe7\xe6\x1e\x3b\xc9\xcb\x59\x1e\x98\x9d\x55\xaf\xd7" + "\xb6\xec\x0c\xf7\x0c\x39\x11\x20\x24\x65\xf9\xe3\x2e\xbb\x9c\x57" + "\xd9\x18\x19\x3e\xe6\xcc\xeb\x4a\xa8\xd3\xf7\x6c\x3f\x02\x01\x00" + "\xe8\x85\xaa\x32\xf1\xa3\x63\x2c\x28\xb5\x2f\xfd\x24\xb8\x0d\x04" + "\x00\x92\x4a\x1d\x16\x90\x59\x07\xc0\x7e\x24\x98\x76\x1b\xca\x6f" + "\xc1\x8e\x3b\xf5\x2f\x9a\xf2\x28\xdf\x2f\x1d\x47\xd0\x23\xa9\x7a" + "\x76\x76\xf9\xfa\x70\x3e\x4a\x66\x41\xe7\x9e\xaf\x64\x7a\x61\xb7" + "\x56\x98\x7f\x2f\x9d\x90\x4a\xf1\x04\x4c\x46\xa6\xa5\x03\xd7\x2c" + "\xa7\x55\xd1\xab\xf9\xe5\x1d\x2c\x1c\xe5\x7a\x5e\x85\xea\xf0\xd3" + "\xba\x29\x9f\x19\x69\x4a\xf5\xf0\x31\x87\x4e\x53\xbe\xb6\xab\x13" + "\x4a\x98\x5f\x78\x81\x66\xac\xc4\x45\x1f\x3e\xad\x03\xf8\x85\x2e" + "\xa9\x99\x96\x3e\x7c\x34\xe1\x83\x08\x02\x00\x18\x1b\x65\x12\x15" + "\x41\x0a\x09\xd1\xe4\x57"; +static const unsigned long zstd_multiframe_compressed_size = + sizeof(zstd_multiframe_compressed) - 1; + #define TEST_BUFFER_SIZE 512 typedef int (*mutate_func)(struct unit_test_state *uts, void *, unsigned long, @@ -505,6 +531,29 @@ static int compression_test_zstd(struct unit_test_state *uts) } LIB_TEST(compression_test_zstd, 0); +/* + * A payload of several concatenated frames must decode in full, not just up to + * the end of the first frame. + */ +static int compression_test_zstd_multiframe(struct unit_test_state *uts) +{ + struct abuf in_buf, out_buf; + char out[TEST_BUFFER_SIZE]; + int ret; + + memset(out, 0, sizeof(out)); + abuf_init_set(&in_buf, (void *)zstd_multiframe_compressed, + zstd_multiframe_compressed_size); + abuf_init_set(&out_buf, out, sizeof(out)); + + ret = zstd_decompress(&in_buf, &out_buf); + ut_asserteq(strlen(plain), ret); + ut_asserteq_mem(plain, out, strlen(plain)); + + return 0; +} +LIB_TEST(compression_test_zstd_multiframe, 0); + static int compress_using_none(struct unit_test_state *uts, void *in, unsigned long in_size, void *out, unsigned long out_max, diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index e6d8792ac1f..823fa40a929 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -6,6 +6,7 @@ import os import pytest +import struct import utils """ @@ -45,6 +46,28 @@ def parse_gpt_parts(disk_str): return parts +# Layout of gpt_header from include/part_efi.h. It is __packed and +# little-endian, and struct.calcsize() of this is 92, its header_size. +GPT_HEADER_FMT = '<8sIIIIQQQQ16sQIII' +GPT_HEADER_FIELDS = ( + 'signature', 'revision', 'header_size', 'header_crc32', 'reserved1', + 'my_lba', 'alternate_lba', 'first_usable_lba', 'last_usable_lba', + 'disk_guid', 'partition_entry_lba', 'num_partition_entries', + 'sizeof_partition_entry', 'partition_entry_array_crc32') + +def parse_gpt_header(blob): + """Unpack a raw GPT header. + + Args: + blob: Bytes starting at the first byte of the header + + Returns: + A dict mapping each gpt_header field name to its value + """ + size = struct.calcsize(GPT_HEADER_FMT) + return dict(zip(GPT_HEADER_FIELDS, + struct.unpack(GPT_HEADER_FMT, blob[:size]))) + class GptTestDiskImage(object): """Disk Image used by the GPT tests.""" @@ -93,6 +116,36 @@ def __init__(self, ubman): cmd = ('cp', persistent, self.path) utils.run_and_log(ubman, cmd) +class BlankDiskImage(object): + """Zero-filled disk image with no partition table on it.""" + + def __init__(self, ubman, size=4 * 1024 * 1024): + """Initialize a new BlankDiskImage object. + + Args: + ubman: A U-Boot console. + size: Image size in bytes. Must be a multiple of the block size + the image is later bound with. + + Returns: + Nothing. + """ + + self.size = size + self.path = os.path.join(ubman.config.result_dir, + 'test_gpt_blank_disk_image.bin') + ubman.log.action('Generating ' + self.path) + with open(self.path, 'wb') as fd: + fd.truncate(size) + +@pytest.fixture(scope='function') +def blank_disk_image(ubman): + """pytest fixture to provide an empty BlankDiskImage object to tests. + This is function-scoped because it uses ubman, which is also + function-scoped.""" + + return BlankDiskImage(ubman) + @pytest.fixture(scope='function') def state_disk_image(ubman): """pytest fixture to provide a GptTestDiskImage object to tests. @@ -330,6 +383,68 @@ def test_gpt_write(state_disk_image, ubman): output = ubman.run_command('gpt guid host 0') assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output +@pytest.mark.boardspec('sandbox') +@pytest.mark.buildconfigspec('cmd_gpt') +@pytest.mark.buildconfigspec('cmd_part') +def test_gpt_write_4k_sectors(blank_disk_image, ubman): + """Test the GPT layout written to a device with 4096-byte sectors. + + The partition entry array takes up fewer blocks on a device with large + sectors, and the backup array has to end immediately before the backup + header in the last block whatever the block size is. + """ + + blksz = 4096 + entry_sz = 128 + nblocks = blank_disk_image.size // blksz + entries = int(ubman.config.buildconfig.get( + 'config_efi_partition_entries_numbers', 128)) + pte_blocks = -(-entries * entry_sz // blksz) + + first_usable = 2 + pte_blocks + last_usable = nblocks - pte_blocks - 2 + + ubman.run_command(f'host bind 0 {blank_disk_image.path} {blksz}') + + output = ubman.run_command('gpt write host 0 "name=all,size=0"') + assert 'Writing GPT: success!' in output + output = ubman.run_command('gpt verify host 0') + assert 'Verify GPT: success!' in output + + output = ubman.run_command('part list host 0') + assert f'0x{first_usable:08x}\t0x{last_usable:08x}\t"all"' in output + + with open(blank_disk_image.path, 'rb') as fd: + data = fd.read() + + # The primary header sits in block 1 and describes the whole layout + prim = parse_gpt_header(data[blksz:]) + assert prim['signature'] == b'EFI PART' + assert prim['my_lba'] == 1 + assert prim['alternate_lba'] == nblocks - 1 + assert prim['partition_entry_lba'] == 2 + assert prim['first_usable_lba'] == first_usable + assert prim['last_usable_lba'] == last_usable + assert prim['num_partition_entries'] == entries + assert prim['sizeof_partition_entry'] == entry_sz + + # The backup header sits in the very last block... + bkp = parse_gpt_header(data[(nblocks - 1) * blksz:]) + assert bkp['signature'] == b'EFI PART' + assert bkp['my_lba'] == nblocks - 1 + assert bkp['alternate_lba'] == 1 + assert bkp['first_usable_lba'] == first_usable + assert bkp['last_usable_lba'] == last_usable + + # ...and its entry array must end immediately before it + assert bkp['partition_entry_lba'] == last_usable + 1 + assert bkp['partition_entry_lba'] + pte_blocks == nblocks - 1 + + # The backup array holds the same entries as the primary one + pte_len = entries * entry_sz + bkp_off = bkp['partition_entry_lba'] * blksz + assert data[bkp_off:bkp_off + pte_len] == data[2 * blksz:2 * blksz + pte_len] + @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('cmd_gpt') @pytest.mark.buildconfigspec('cmd_part') @@ -338,6 +453,7 @@ def test_gpt_write(state_disk_image, ubman): def test_gpt_write_part_type(state_disk_image, ubman): """Test the gpt command with part type uuid.""" + ubman.run_command('host bind 0 ' + state_disk_image.path) output = ubman.run_command('gpt write host 0 "name=part1,type=data,size=1M;name=part2,size=512K,type=system;name=part3,size=65536,type=u-boot-env;name=part4,size=65536,type=375a56f7-d6c9-4e81-b5f0-09d41ca89efe;name=part5,size=-,type=linux"') assert 'Writing GPT: success!' in output output = ubman.run_command('part list host 0') diff --git a/tools/binman/bintool.py b/tools/binman/bintool.py index 9c76c8881a4..4cf8d5b165b 100644 --- a/tools/binman/bintool.py +++ b/tools/binman/bintool.py @@ -12,6 +12,7 @@ import collections import glob import importlib +import inspect import multiprocessing import os import shutil @@ -463,9 +464,10 @@ def WriteDocs(modules, test_missing=None): if test_missing == name: docs = None if docs: - lines = docs.splitlines() + # See the note in Entry.WriteDocs() about cleandoc() + lines = inspect.cleandoc(docs).splitlines() first_line = lines[0] - rest = [line[4:] for line in lines[1:]] + rest = lines[1:] hdr = 'Bintool: %s: %s' % (name, first_line) print(hdr) print('-' * len(hdr)) diff --git a/tools/binman/entry.py b/tools/binman/entry.py index ce7ef28e94b..8164be34705 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -6,6 +6,7 @@ from collections import namedtuple import importlib +import inspect import os import pathlib import sys @@ -636,6 +637,14 @@ def GetData(self, required=True): self.Detail('GetData: size %s' % to_hex_size(self.data)) return self.data + def GetNode(self): + """Get the devicetree node which describes this entry + + Returns: + Node: Node for this entry + """ + return self._node + def GetPaddedData(self, data=None): """Get the data for an entry including any padding @@ -858,9 +867,13 @@ def WriteDocs(modules, test_missing=None): if test_missing == name: docs = None if docs: - lines = docs.splitlines() + # Use cleandoc() rather than removing a fixed four characters + # of indent: since Python 3.13 the compiler already strips the + # common indent from docstrings, so doing it again here would + # eat the first four characters of every line + lines = inspect.cleandoc(docs).splitlines() first_line = lines[0] - rest = [line[4:] for line in lines[1:]] + rest = lines[1:] hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line) # Create a reference for use by rST docs diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py index 32caa03a7e1..750ff5b47ed 100644 --- a/tools/binman/etype/fit.py +++ b/tools/binman/etype/fit.py @@ -304,6 +304,40 @@ class Entry_fit(Entry_section): See :ref:`fdtgrep_filter` for more information. + Patching /chosen into the generated device trees + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + When the OS is booted directly from this FIT (e.g. Falcon mode) U-Boot + proper never runs, so its usual runtime /chosen fixups do not happen. Two + optional properties on the `@fdt-SEQ` node make binman patch each generated + device tree at build time: + + fit,bootargs + A string written to /chosen/bootargs of every generated device tree + (any existing value is overwritten). Typically set to `CONFIG_BOOTARGS` + via the preprocessed devicetree source. + + fit,initrd + The image name of a loadable (e.g. "ramdisk") to use as the initramfs. + Its `load` address and packed size are written to + /chosen/linux,initrd-start and /chosen/linux,initrd-end (at the width of + the device tree's root #address-cells) and reserved in the FDT + memory-reservation map, mirroring fdt_initrd(). If the referenced image + is absent or empty (e.g. an optional initramfs that was not supplied) + the initrd properties are omitted. + + For example:: + + images { + @fdt-SEQ { + description = "fdt-NAME"; + type = "flat_dt"; + compression = "none"; + fit,bootargs = "console=ttyS2,1500000 root=/dev/mmcblk0p2"; + fit,initrd = "ramdisk"; + }; + }; + Generating nodes from an ELF file (split-elf) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -749,6 +783,66 @@ def _run_fdtgrep(self, infile, phase, outfile): return self.fdtgrep.create_for_phase(infile, phase, outfile, self._remove_props) + def _patch_fdt_chosen(self, data, bootargs, initrd_name): + """Patch /chosen (bootargs and initrd) into an embedded FDT + + This writes a kernel command line and/or initramfs location into the + /chosen node of a device tree that binman is about to embed in the FIT. + It is used for Falcon-mode images, where U-Boot proper never runs to + perform these fixups itself. + + Args: + data (bytes): Device-tree contents to patch + bootargs (str or None): Command line to store in /chosen/bootargs, + or None to leave the command line unchanged + initrd_name (str or None): Image name of the loadable to use as the + initramfs. Its 'load' address and packed size are written to + /chosen/linux,initrd-start and /chosen/linux,initrd-end (at the + width of the DTB's root #address-cells) and reserved in the FDT + memory-reservation map, mirroring fdt_initrd(). Ignored when the + referenced image is absent or empty, or when None. + + Returns: + bytes: The patched device-tree contents + + Raises: + ValueError: the referenced image has data but no 'load' address + """ + fdt = libfdt.Fdt(bytearray(data)) + fdt.resize(fdt.totalsize() + 1024 + (len(bootargs) if bootargs else 0)) + + chosen = fdt.path_offset('/chosen', libfdt.QUIET_NOTFOUND) + if chosen == -libfdt.FDT_ERR_NOTFOUND: + chosen = fdt.add_subnode(0, 'chosen') + + if bootargs: + fdt.setprop_str(chosen, 'bootargs', bootargs) + + if initrd_name: + entry = self._priv_entries.get(initrd_name) + initrd = entry.GetData(required=False) if entry else None + size = len(initrd) if initrd else 0 + if size: + start = fdt_util.GetInt(entry.GetNode(), 'load') + if start is None: + self.Raise(f"fit,initrd image '{initrd_name}' has no " + "'load' address") + end = start + size + # Use the device tree's root #address-cells for the width, as + # fdt_initrd() does (4 bytes per cell) + cells = 4 * libfdt.check_err( + libfdt.fdt_address_cells(fdt._fdt, 0)) + fdt.setprop(chosen, 'linux,initrd-start', + start.to_bytes(cells, 'big')) + fdt.setprop(chosen, 'linux,initrd-end', + end.to_bytes(cells, 'big')) + # Reserve the initramfs region so the kernel does not reuse it + libfdt.check_err( + libfdt.fdt_add_mem_rsv(fdt._fdt, start, size)) + + fdt.pack() + return bytes(fdt.as_bytearray()[:fdt.totalsize()]) + def _build_input(self): """Finish the FIT by adding the 'data' properties to it @@ -857,6 +951,8 @@ def _gen_fdt_nodes(node, depth, in_images): else: fname = tools.get_input_filename(fdt_fname + '.dtb') fdt_phase = None + bootargs = None + initrd_name = None with fsw.add_node(node_name): for pname, prop in node.props.items(): if pname == 'fit,firmware': @@ -876,6 +972,10 @@ def _gen_fdt_nodes(node, depth, in_images): fsw.property('compatible', prop.bytes) elif pname == 'fit,fdt-phase': fdt_phase = fdt_util.GetString(node, pname) + elif pname == 'fit,bootargs': + bootargs = fdt_util.GetString(node, pname) + elif pname == 'fit,initrd': + initrd_name = fdt_util.GetString(node, pname) elif pname.startswith('fit,'): self._raise_subnode( node, f"Unknown directive '{pname}'") @@ -896,6 +996,9 @@ def _gen_fdt_nodes(node, depth, in_images): data = tools.read_file(phase_fname) else: data = tools.read_file(fname) + if bootargs is not None or initrd_name is not None: + data = self._patch_fdt_chosen(data, bootargs, + initrd_name) fsw.property('data', data) for subnode in node.subnodes: diff --git a/tools/binman/etype/linux_initrd.py b/tools/binman/etype/linux_initrd.py new file mode 100644 index 00000000000..9603bc279c2 --- /dev/null +++ b/tools/binman/etype/linux_initrd.py @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2026 Flipper FZCO +# +# Entry-type module for a Linux initramfs/initrd binary blob + +from binman.etype.blob_named_by_arg import Entry_blob_named_by_arg + + +class Entry_linux_initrd(Entry_blob_named_by_arg): + """Linux initramfs (initrd) image blob + + Properties / Entry arguments: + - linux-initrd-path: Filename of file to read into entry. This is + typically a compressed cpio archive (e.g. initramfs.cpio.gz). + + This entry allows binman FIT templates to consume an initramfs provided via + a make variable, similar to how the kernel is passed to linux-kernel. It is + optional: when no path is supplied the entry is simply dropped. + """ + + def __init__(self, section, etype, node): + super().__init__(section, etype, node, 'linux-initrd', required=False) + self.external = True diff --git a/tools/binman/etype/linux_kernel.py b/tools/binman/etype/linux_kernel.py new file mode 100644 index 00000000000..74bef0ed824 --- /dev/null +++ b/tools/binman/etype/linux_kernel.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2026 Flipper FZCO +# +# Entry-type module for Linux kernel binary blob + +from binman.etype.blob_named_by_arg import Entry_blob_named_by_arg + + +class Entry_linux_kernel(Entry_blob_named_by_arg): + """Linux kernel image blob + + Properties / Entry arguments: + - linux-kernel-path: Filename of file to read into entry. This is + typically an uncompressed ARM64 Image. + + This entry allows binman FIT templates to consume a kernel provided via + make variable, similar to how BL31 is passed to atf-bl31. + """ + + def __init__(self, section, etype, node): + super().__init__(section, etype, node, 'linux-kernel') + self.external = True diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 9553eb6b736..064f7f6daec 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -1805,7 +1805,15 @@ def testEntryDocs(self): """Test for creation of entry documentation""" with terminal.capture() as (stdout, stderr): control.WriteEntryDocs(control.GetEntryModules()) - self.assertTrue(len(stdout.getvalue()) > 0) + out = stdout.getvalue() + self.assertTrue(len(out) > 0) + + # The body of each docstring must come out dedented but otherwise + # intact. Check a heading which several etypes use, since truncating + # the indent by too much would silently eat the start of every line. + self.assertIn('\nProperties / Entry arguments:\n', out) + self.assertIn('\nEntry: atf-bl31: ARM Trusted Firmware (ATF) BL31 blob\n', + out) def testEntryDocsMissing(self): """Test handling of missing entry documentation""" @@ -4397,6 +4405,113 @@ def testFitFdtNotInList(self): self.assertIn("default-dt entry argument 'test-fdt3' not found in fdt list: test-fdt1, test-fdt2", str(e.exception)) + def testFitChosen(self): + """Test patching /chosen (bootargs + initrd) into generated FIT FDTs""" + kernel = b'\x01\x02\x03\x04 kernel' + initrd = b'initramfs-contents' + self._MakeInputFile('test-kernel.bin', kernel) + self._MakeInputFile('test-initrd.bin', initrd) + entry_args = { + 'of-list': 'model1 model2', + 'default-dt': 'model2', + 'linux-kernel-path': 'test-kernel.bin', + 'linux-initrd-path': 'test-initrd.bin', + } + testdir, _ = self.SetupAlternateDts() + data = self._DoReadFileDtb( + 'fit/fit_chosen.dts', use_real_dtb=True, update_dtb=True, + entry_args=entry_args, extra_indirs=[testdir])[0] + fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)] + fit = fdt.Fdt.FromData(fit_data) + fit.Scan() + + # ramdisk is loaded at 0x1000 (see fit_chosen.dts) + rd_start = 0x1000 + rd_end = rd_start + len(initrd) + for seq in (1, 2): + fnode = fit.GetNode('/images/fdt-%d' % seq) + self.assertIsNotNone(fnode) + dtb = fdt.Fdt.FromData(fnode.props['data'].bytes) + dtb.Scan() + chosen = dtb.GetNode('/chosen') + self.assertIsNotNone(chosen) + self.assertEqual(b'console=ttyS0 root=/dev/ram\0', + chosen.props['bootargs'].bytes) + start = int.from_bytes(chosen.props['linux,initrd-start'].bytes, + 'big') + end = int.from_bytes(chosen.props['linux,initrd-end'].bytes, 'big') + self.assertEqual(rd_start, start) + self.assertEqual(rd_end, end) + # The initramfs region must be reserved in the FDT + fdt_obj = dtb.GetFdtObj() + self.assertEqual(1, fdt_obj.num_mem_rsv()) + _, addr, size = fdt_obj.get_mem_rsv(0) + self.assertEqual(rd_start, addr) + self.assertEqual(len(initrd), size) + + def testFitChosenNoInitrd(self): + """Test a Falcon-mode FIT where the optional initramfs is not supplied""" + self._MakeInputFile('test-kernel.bin', b'\x01\x02\x03\x04 kernel') + entry_args = { + 'of-list': 'model1 model2', + 'default-dt': 'model2', + 'linux-kernel-path': 'test-kernel.bin', + 'linux-initrd-path': '', + } + testdir, _ = self.SetupAlternateDts() + with terminal.capture() as (stdout, stderr): + data = self._DoReadFileDtb( + 'fit/fit_chosen_no_initrd.dts', use_real_dtb=True, + update_dtb=True, entry_args=entry_args, + extra_indirs=[testdir], allow_fake_blobs=False)[0] + self.assertRegex( + stderr.getvalue(), + "Image '.*' is missing optional external blobs but is still functional: linux-initrd") + + fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)] + fit = fdt.Fdt.FromData(fit_data) + fit.Scan() + + for seq in (1, 2): + # The node is still emitted and still listed as a loadable, as is + # already the case for an optional OP-TEE which was not supplied + # (see testFitFirmwareLoadables), but it carries no data + cnode = fit.GetNode('/configurations/config-%d' % seq) + self.assertEqual(b'kernel\0', cnode.props['firmware'].bytes) + self.assertEqual(['ramdisk'], + fdt_util.GetStringList(cnode, 'loadables')) + self.assertEqual(b'', fit.GetNode('/images/ramdisk') + .props['data'].bytes) + + # bootargs is still patched in, but the initrd properties are not + fnode = fit.GetNode('/images/fdt-%d' % seq) + dtb = fdt.Fdt.FromData(fnode.props['data'].bytes) + dtb.Scan() + chosen = dtb.GetNode('/chosen') + self.assertEqual(b'console=ttyS0\0', chosen.props['bootargs'].bytes) + self.assertNotIn('linux,initrd-start', chosen.props) + self.assertNotIn('linux,initrd-end', chosen.props) + self.assertEqual(0, dtb.GetFdtObj().num_mem_rsv()) + + def testFitChosenNoLoad(self): + """Test a fit,initrd image which is missing its load address""" + self._MakeInputFile('test-kernel.bin', b'\x01\x02\x03\x04 kernel') + self._MakeInputFile('test-initrd.bin', b'initramfs-contents') + entry_args = { + 'of-list': 'model1 model2', + 'default-dt': 'model2', + 'linux-kernel-path': 'test-kernel.bin', + 'linux-initrd-path': 'test-initrd.bin', + } + testdir, _ = self.SetupAlternateDts() + with self.assertRaises(ValueError) as e: + self._DoReadFileDtb( + 'fit/fit_chosen_no_load.dts', use_real_dtb=True, + update_dtb=True, entry_args=entry_args, + extra_indirs=[testdir]) + self.assertIn("fit,initrd image 'ramdisk' has no 'load' address", + str(e.exception)) + def testFitExtblobMissingHelp(self): """Test display of help messages when an external blob is missing""" control.missing_blob_help = control._ReadMissingBlobHelp() @@ -5491,7 +5606,14 @@ def testBintoolDocs(self): """Test for creation of bintool documentation""" with terminal.capture() as (stdout, stderr): control.write_bintool_docs(control.bintool.Bintool.get_tool_list()) - self.assertTrue(len(stdout.getvalue()) > 0) + out = stdout.getvalue() + self.assertTrue(len(out) > 0) + + # As in testEntryDocs(), check that the body is dedented but intact + self.assertIn('\nBintool: mkimage: Image generation for U-Boot\n', out) + self.assertIn( + '\nThis bintool supports running `mkimage` with some basic parameters as\n', + out) def testBintoolDocsMissing(self): """Test handling of missing bintool documentation""" diff --git a/tools/binman/missing-blob-help b/tools/binman/missing-blob-help index 27dc620bd35..81806091939 100644 --- a/tools/binman/missing-blob-help +++ b/tools/binman/missing-blob-help @@ -32,6 +32,16 @@ If CONFIG_WDT_K3_RTI_LOAD_FW is enabled, a firmware image is needed for the R5F core(s) to trigger the system reset. One possible source is https://github.com/siemens/k3-rti-wdt. +linux-initrd: +An initramfs to embed in a Falcon mode FIT image, so that the OS can be +started from SPL without a root filesystem being available yet. This is +optional; build with LINUX_INITRD=/path/to/initramfs.cpio.gz to include one. + +linux-kernel: +A Linux kernel image is required to build a Falcon mode FIT image, which +lets SPL start the OS directly instead of U-Boot proper. Build with +LINUX_KERNEL=/path/to/Image, or disable Falcon mode image generation. + opensbi: See the documentation for your board. The OpenSBI git repo is at https://github.com/riscv/opensbi.git diff --git a/tools/binman/test/fit/fit_chosen.dts b/tools/binman/test/fit/fit_chosen.dts new file mode 100644 index 00000000000..ae2e4a084af --- /dev/null +++ b/tools/binman/test/fit/fit_chosen.dts @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Linux kernel"; + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x100>; + entry = <0x100>; + linux-kernel { + }; + }; + ramdisk { + description = "ramdisk"; + type = "ramdisk"; + compression = "none"; + load = <0x1000>; + linux-initrd { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + fit,bootargs = "console=ttyS0 root=/dev/ram"; + fit,initrd = "ramdisk"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "kernel"; + loadables = "ramdisk"; + fdt = "fdt-SEQ"; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/fit/fit_chosen_no_initrd.dts b/tools/binman/test/fit/fit_chosen_no_initrd.dts new file mode 100644 index 00000000000..82468e94dcd --- /dev/null +++ b/tools/binman/test/fit/fit_chosen_no_initrd.dts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Linux kernel"; + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x100>; + entry = <0x100>; + linux-kernel { + }; + }; + ramdisk { + description = "ramdisk"; + type = "ramdisk"; + compression = "none"; + load = <0x1000>; + linux-initrd { + optional; + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + fit,bootargs = "console=ttyS0"; + fit,initrd = "ramdisk"; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + fit,firmware = "kernel", "ramdisk"; + fit,loadables; + fdt = "fdt-SEQ"; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/fit/fit_chosen_no_load.dts b/tools/binman/test/fit/fit_chosen_no_load.dts new file mode 100644 index 00000000000..7a3d393dd21 --- /dev/null +++ b/tools/binman/test/fit/fit_chosen_no_load.dts @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Linux kernel"; + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x100>; + entry = <0x100>; + linux-kernel { + }; + }; + ramdisk { + description = "ramdisk"; + type = "ramdisk"; + compression = "none"; + linux-initrd { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + fit,initrd = "ramdisk"; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "kernel"; + loadables = "ramdisk"; + fdt = "fdt-SEQ"; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/rkcommon.c b/tools/rkcommon.c index b39777fc060..56e6be40b96 100644 --- a/tools/rkcommon.c +++ b/tools/rkcommon.c @@ -34,15 +34,16 @@ enum hash_type { /** * struct image_entry * - * @size_and_off: [31:16]image size;[15:0]image offset - * @address: default as 0xFFFFFFFF + * @offset: image offset (unit as 512 byte blocks) + * @size: image size (unit as 512 byte blocks) + * @address: load address (default as 0xFFFFFFFF) * @flag: no use * @counter: no use * @hash: hash of image - * */ struct image_entry { - uint32_t size_and_off; + uint16_t offset; + uint16_t size; uint32_t address; uint32_t flag; uint32_t counter; @@ -56,18 +57,23 @@ struct image_entry { * This is stored at SD card block 64 (where each block is 512 bytes) * * @magic: Magic (must be RK_MAGIC_V2) - * @size_and_nimage: [31:16]number of images;[15:0] - * offset to hash field of header(unit as 4Byte) - * @boot_flag: [3:0]hash type(0:none,1:sha256,2:sha512) - * @signature: hash or signature for header info + * @size: offset to hash field of header (unit as 4 bytes) + * @num_images: number of images + * @boot_flag: [3:0] hash type (0:none, 1:sha256, 2:sha512) + * @images: images + * @hash: hash or signature for header info * + * Other fields are not used by U-Boot */ struct header0_info_v2 { uint32_t magic; uint8_t reserved[4]; - uint32_t size_and_nimage; + uint16_t size; + uint16_t num_images; uint32_t boot_flag; - uint8_t reserved1[104]; + uint8_t reserved1[32]; + uint32_t boot0_param[10]; + uint32_t boot1_param[8]; struct image_entry images[4]; uint8_t reserved2[1064]; uint8_t hash[512]; @@ -134,6 +140,7 @@ struct spl_info { const uint32_t spl_size; const bool spl_rc4; const uint32_t header_ver; + const uint32_t align; }; static struct spl_info spl_infos[] = { @@ -151,7 +158,7 @@ static struct spl_info spl_infos[] = { { "rk3506", "RK35", 0xC000 - 0x1000, false, RK_HEADER_V2 }, { "rk3528", "RK35", 0x10000 - 0x1000, false, RK_HEADER_V2 }, { "rk3568", "RK35", 0x10000 - 0x1000, false, RK_HEADER_V2 }, - { "rk3576", "RK35", 0x80000 - 0x1000, false, RK_HEADER_V2 }, + { "rk3576", "RK35", 0x80000 - 0x1000, false, RK_HEADER_V2, 8 }, { "rk3588", "RK35", 0x100000 - 0x1000, false, RK_HEADER_V2 }, { "rv1108", "RK11", 0x1800, false, RK_HEADER_V1 }, { "rv1126", "110B", 0x10000 - 0x1000, false, RK_HEADER_V1 }, @@ -160,17 +167,19 @@ static struct spl_info spl_infos[] = { /** * struct spl_params - spl params parsed in check_params() * - * @init_file: Init data file path - * @init_size: Aligned size of init data in bytes - * @boot_file: Boot data file path - * @boot_size: Aligned size of boot data in bytes + * @file: image file path + * @size: aligned size of image in bytes + * @address: image load address + * @flag: no use */ struct spl_params { - char *init_file; - uint32_t init_size; - char *boot_file; - uint32_t boot_size; + struct { + char *file; + uint32_t size; + uint32_t address; + uint32_t flag; + } images[4]; }; static struct spl_params spl_params = { 0 }; @@ -194,14 +203,19 @@ static struct spl_info *rkcommon_get_spl_info(char *imagename) return NULL; } -static int rkcommon_get_aligned_size(struct image_tool_params *params, - const char *fname) +static bool rkcommon_is_header_v2(struct image_tool_params *params) { - int size; + struct spl_info *info = rkcommon_get_spl_info(params->imagename); - size = imagetool_get_filesize(params, fname); - if (size < 0) - return -1; + return (info->header_ver == RK_HEADER_V2); +} + +static int rkcommon_get_aligned_size(struct image_tool_params *params, int size) +{ + struct spl_info *info = rkcommon_get_spl_info(params->imagename); + + if (info->align) + return ROUND(size, info->align * RK_BLK_SIZE); /* * Pad to a 2KB alignment, as required for init/boot size by the ROM @@ -210,6 +224,27 @@ static int rkcommon_get_aligned_size(struct image_tool_params *params, return ROUND(size, RK_SIZE_ALIGN); } +static int rkcommon_get_header_size(struct image_tool_params *params) +{ + int header_size = rkcommon_is_header_v2(params) ? + sizeof(struct header0_info_v2) : + sizeof(struct header0_info); + + return rkcommon_get_aligned_size(params, header_size); +} + +static int rkcommon_get_aligned_filesize(struct image_tool_params *params, + const char *fname) +{ + int size; + + size = imagetool_get_filesize(params, fname); + if (size < 0) + return -1; + + return rkcommon_get_aligned_size(params, size); +} + int rkcommon_check_params(struct image_tool_params *params) { int i, size; @@ -224,34 +259,56 @@ int rkcommon_check_params(struct image_tool_params *params) if (!rkcommon_get_spl_info(params->imagename)) goto err_spl_info; - spl_params.init_file = params->datafile; + spl_params.images[0].file = params->datafile; + for (i = 1; i < ARRAY_SIZE(spl_params.images); i++) { + spl_params.images[i].file = + strchr(spl_params.images[i - 1].file, ':'); + if (!spl_params.images[i].file) + break; - spl_params.boot_file = strchr(spl_params.init_file, ':'); - if (spl_params.boot_file) { - *spl_params.boot_file = '\0'; - spl_params.boot_file += 1; + *spl_params.images[i].file = '\0'; + spl_params.images[i].file += 1; } - size = rkcommon_get_aligned_size(params, spl_params.init_file); - if (size < 0) - return EXIT_FAILURE; - spl_params.init_size = size; + for (i = 0; i < ARRAY_SIZE(spl_params.images); i++) { + if (!spl_params.images[i].file) + break; - /* Boot file is optional, and only for back-to-bootrom functionality. */ - if (spl_params.boot_file) { - size = rkcommon_get_aligned_size(params, spl_params.boot_file); + size = rkcommon_get_aligned_filesize(params, + spl_params.images[i].file); if (size < 0) return EXIT_FAILURE; - spl_params.boot_size = size; + spl_params.images[i].size = size; } - if (spl_params.init_size > rkcommon_get_spl_size(params)) { + if (spl_params.images[0].size > rkcommon_get_spl_size(params)) { fprintf(stderr, "Error: SPL image is too large (size %#x than %#x)\n", - spl_params.init_size, rkcommon_get_spl_size(params)); + spl_params.images[0].size, rkcommon_get_spl_size(params)); return EXIT_FAILURE; } + if (!strcmp(params->imagename, "rk3576")) { + char *boost = "arch/arm/mach-rockchip/rk3576/rk3576-boost.bin"; + + size = rkcommon_get_aligned_filesize(params, boost); + if (size < 0) { + fprintf(stderr, + "Error: rk3576-boost.bin is required but missing\n"); + return EXIT_FAILURE; + } + + for (i = ARRAY_SIZE(spl_params.images) - 1; i > 0; i--) { + spl_params.images[i] = spl_params.images[i - 1]; + } + + spl_params.images[0].file = boost; + spl_params.images[0].size = size; + + spl_params.images[0].address = 0x3ffc0000; + spl_params.images[1].address = 0x3ff81000; + } + return EXIT_SUCCESS; err_spl_info: @@ -296,13 +353,6 @@ bool rkcommon_need_rc4_spl(struct image_tool_params *params) return info->spl_rc4; } -static bool rkcommon_is_header_v2(struct image_tool_params *params) -{ - struct spl_info *info = rkcommon_get_spl_info(params->imagename); - - return (info->header_ver == RK_HEADER_V2); -} - static void do_sha256_hash(uint8_t *buf, uint32_t size, uint8_t *out) { sha256_context ctx; @@ -315,13 +365,14 @@ static void do_sha256_hash(uint8_t *buf, uint32_t size, uint8_t *out) static void rkcommon_set_header0(void *buf, struct image_tool_params *params) { struct header0_info *hdr = buf; - uint32_t init_boot_size; + uint32_t init_boot_size, init_offset; - memset(buf, '\0', RK_INIT_OFFSET * RK_BLK_SIZE); + init_offset = rkcommon_get_header_size(params) / RK_BLK_SIZE; + memset(buf, '\0', init_offset * RK_BLK_SIZE); hdr->magic = cpu_to_le32(RK_MAGIC); hdr->disable_rc4 = cpu_to_le32(!rkcommon_need_rc4_spl(params)); - hdr->init_offset = cpu_to_le16(RK_INIT_OFFSET); - hdr->init_size = cpu_to_le16(spl_params.init_size / RK_BLK_SIZE); + hdr->init_offset = cpu_to_le16(init_offset); + hdr->init_size = cpu_to_le16(spl_params.images[0].size / RK_BLK_SIZE); /* * init_boot_size needs to be set, as it is read by the BootROM @@ -331,10 +382,11 @@ static void rkcommon_set_header0(void *buf, struct image_tool_params *params) * see https://patch.msgid.link/CANbgqAQ5p-fE4T-Ye9UAMs-nRGU8AP_yxDf+5SbZbP8bxLa0dg@mail.gmail.com/ * for a more detailed explanation by Andy Yan */ - if (spl_params.boot_file) - init_boot_size = spl_params.init_size + spl_params.boot_size; + if (spl_params.images[1].file) + init_boot_size = spl_params.images[0].size + + spl_params.images[1].size; else - init_boot_size = spl_params.init_size + RK_MAX_BOOT_SIZE; + init_boot_size = spl_params.images[0].size + RK_MAX_BOOT_SIZE; hdr->init_boot_size = cpu_to_le16(init_boot_size / RK_BLK_SIZE); rc4_encode(buf, RK_BLK_SIZE, rc4_key); @@ -344,43 +396,43 @@ static void rkcommon_set_header0_v2(void *buf, struct image_tool_params *params) { struct header0_info_v2 *hdr = buf; uint32_t sector_offset, image_sector_count; - uint32_t image_size_array[2]; uint8_t *image_ptr = NULL; int i; - printf("Image Type: Rockchip %s boot image\n", - rkcommon_get_spl_hdr(params)); - memset(buf, '\0', RK_INIT_OFFSET * RK_BLK_SIZE); - hdr->magic = cpu_to_le32(RK_MAGIC_V2); - hdr->size_and_nimage = cpu_to_le32((2 << 16) + 384); + sector_offset = rkcommon_get_header_size(params) / RK_BLK_SIZE; + memset(buf, '\0', sector_offset * RK_BLK_SIZE); + hdr->magic = cpu_to_le32(RK_MAGIC_V2); hdr->boot_flag = cpu_to_le32(HASH_SHA256); - sector_offset = 4; - image_size_array[0] = spl_params.init_size; - image_size_array[1] = spl_params.boot_size; - - for (i = 0; i < 2; i++) { - image_sector_count = image_size_array[i] / RK_BLK_SIZE; - hdr->images[i].size_and_off = cpu_to_le32((image_sector_count - << 16) + sector_offset); - hdr->images[i].address = 0xFFFFFFFF; + + for (i = 0; i < ARRAY_SIZE(spl_params.images); i++) { + if (!spl_params.images[i].size) + break; + image_sector_count = spl_params.images[i].size / RK_BLK_SIZE; + hdr->images[i].offset = cpu_to_le16(sector_offset); + hdr->images[i].size = cpu_to_le16(image_sector_count); + hdr->images[i].address = spl_params.images[i].address ?: 0xFFFFFFFF; + hdr->images[i].flag = spl_params.images[i].flag; hdr->images[i].counter = cpu_to_le32(i + 1); image_ptr = buf + sector_offset * RK_BLK_SIZE; - do_sha256_hash(image_ptr, image_size_array[i], + do_sha256_hash(image_ptr, spl_params.images[i].size, hdr->images[i].hash); sector_offset = sector_offset + image_sector_count; } + hdr->num_images = cpu_to_le16(i); + hdr->size = cpu_to_le16(offsetof(typeof(*hdr), hash) / sizeof(uint32_t)); do_sha256_hash(buf, (void *)hdr->hash - buf, hdr->hash); } void rkcommon_set_header(void *buf, struct stat *sbuf, int ifd, struct image_tool_params *params) { - struct header1_info *hdr = buf + RK_SPL_HDR_START; - if (rkcommon_is_header_v2(params)) { rkcommon_set_header0_v2(buf, params); } else { + int header_size = rkcommon_get_header_size(params); + struct header1_info *hdr = buf + header_size; + rkcommon_set_header0(buf, params); /* Set up the SPL name (i.e. copy spl_hdr over) */ @@ -388,14 +440,14 @@ void rkcommon_set_header(void *buf, struct stat *sbuf, int ifd, memcpy(&hdr->magic, rkcommon_get_spl_hdr(params), RK_SPL_HDR_SIZE); if (rkcommon_need_rc4_spl(params)) - rkcommon_rc4_encode_spl(buf, RK_SPL_HDR_START, - spl_params.init_size); + rkcommon_rc4_encode_spl(buf, header_size, + spl_params.images[0].size); - if (spl_params.boot_file) { + if (spl_params.images[1].file) { if (rkcommon_need_rc4_spl(params)) - rkcommon_rc4_encode_spl(buf + RK_SPL_HDR_START, - spl_params.init_size, - spl_params.boot_size); + rkcommon_rc4_encode_spl(buf + header_size, + spl_params.images[0].size, + spl_params.images[1].size); } } } @@ -500,6 +552,41 @@ int rkcommon_verify_header(unsigned char *buf, int size, return -ENOENT; } +static void rkcommon_print_header_v2(const struct header0_info_v2 *hdr) +{ + uint32_t val; + int i; + + printf("Rockchip Boot Image (v2)\n"); + + for (i = 0; i < ARRAY_SIZE(hdr->boot0_param); i++) { + val = le32_to_cpu(hdr->boot0_param[i]); + if (val) + printf("Boot0 %d: 0x%x\n", i, val); + } + + for (i = 0; i < ARRAY_SIZE(hdr->boot1_param); i++) { + val = le32_to_cpu(hdr->boot1_param[i]); + if (val) + printf("Boot1 %d: 0x%x\n", i, val); + } + + for (i = 0; i < le16_to_cpu(hdr->num_images); i++) { + printf("Image %u: %u @ 0x%x\n", + le32_to_cpu(hdr->images[i].counter), + le16_to_cpu(hdr->images[i].size) * RK_BLK_SIZE, + le16_to_cpu(hdr->images[i].offset) * RK_BLK_SIZE); + + val = le32_to_cpu(hdr->images[i].address); + if (val != 0xFFFFFFFF) + printf("- Load address: 0x%x\n", val); + + val = le32_to_cpu(hdr->images[i].flag); + if (val) + printf("- Flag: 0x%x\n", val); + } +} + void rkcommon_print_header(const void *buf, struct image_tool_params *params) { struct header0_info header0; @@ -516,10 +603,7 @@ void rkcommon_print_header(const void *buf, struct image_tool_params *params) return; } - init_size = header0_v2.images[0].size_and_off >> 16; - init_size = init_size * RK_BLK_SIZE; - boot_size = header0_v2.images[1].size_and_off >> 16; - boot_size = boot_size * RK_BLK_SIZE; + rkcommon_print_header_v2(&header0_v2); } else { ret = rkcommon_parse_header(buf, &header0, &spl_info); @@ -533,18 +617,20 @@ void rkcommon_print_header(const void *buf, struct image_tool_params *params) } image_type = ret; - init_size = header0.init_size * RK_BLK_SIZE; - boot_size = header0.init_boot_size * RK_BLK_SIZE - init_size; + init_size = le16_to_cpu(header0.init_size) * RK_BLK_SIZE; + boot_size = le16_to_cpu(header0.init_boot_size) * RK_BLK_SIZE - + init_size; - printf("Image Type: Rockchip %s (%s) boot image\n", - spl_info->spl_hdr, + printf("Rockchip %s (%s) Boot Image\n", spl_info->spl_hdr, (image_type == IH_TYPE_RKSD) ? "SD/MMC" : "SPI"); - } - printf("Init Data Size: %d bytes\n", init_size); + printf("Init Data: %d @ 0x%x\n", init_size, + le16_to_cpu(header0.init_offset) * RK_BLK_SIZE); - if (boot_size != RK_MAX_BOOT_SIZE) - printf("Boot Data Size: %d bytes\n", boot_size); + if (boot_size != RK_MAX_BOOT_SIZE) + printf("Boot Data: %d @ 0x%x\n", boot_size, init_size + + le16_to_cpu(header0.init_offset) * RK_BLK_SIZE); + } } void rkcommon_rc4_encode_spl(void *buf, unsigned int offset, unsigned int size) @@ -584,7 +670,7 @@ int rkcommon_vrec_header(struct image_tool_params *params, * 4 bytes of these images can safely be overwritten using the * boot magic. */ - tparams->header_size = RK_SPL_HDR_START; + tparams->header_size = rkcommon_get_header_size(params); /* Allocate, clear and install the header */ tparams->hdr = malloc(tparams->header_size); @@ -599,10 +685,12 @@ int rkcommon_vrec_header(struct image_tool_params *params, * We need to store the original file-size (i.e. before padding), as * imagetool does not set this during its adjustment of file_size. */ - params->orig_file_size = tparams->header_size + - spl_params.init_size + spl_params.boot_size; + params->orig_file_size = tparams->header_size; + for (int i = 0; i < ARRAY_SIZE(spl_params.images); i++) + params->orig_file_size += spl_params.images[i].size; - params->file_size = ROUND(params->orig_file_size, RK_SIZE_ALIGN); + params->file_size = rkcommon_get_aligned_size(params, + params->orig_file_size); /* Ignoring pad len, since we are using our own copy_image() */ return 0; @@ -686,16 +774,13 @@ static int copy_file(struct image_tool_params *params, int ifd, int rockchip_copy_image(int ifd, struct image_tool_params *params) { - int ret; - - ret = copy_file(params, ifd, spl_params.init_file, - spl_params.init_size); - if (ret) - return ret; + int i, ret; - if (spl_params.boot_file) { - ret = copy_file(params, ifd, spl_params.boot_file, - spl_params.boot_size); + for (i = 0; i < ARRAY_SIZE(spl_params.images); i++) { + if (!spl_params.images[i].size) + break; + ret = copy_file(params, ifd, spl_params.images[i].file, + spl_params.images[i].size); if (ret) return ret; } diff --git a/tools/rkcommon.h b/tools/rkcommon.h index 5d2770a80f1..c887a659a95 100644 --- a/tools/rkcommon.h +++ b/tools/rkcommon.h @@ -10,9 +10,7 @@ enum { RK_BLK_SIZE = 512, RK_SIZE_ALIGN = 2048, - RK_INIT_OFFSET = 4, RK_MAX_BOOT_SIZE = 512 << 10, - RK_SPL_HDR_START = RK_INIT_OFFSET * RK_BLK_SIZE, RK_SPL_HDR_SIZE = 4, };