[PATCH v2 4/5] misc: fw_loader: introduce FIP loader driver
Francis, Neha
n-francis at ti.com
Tue Mar 17 07:54:24 CET 2026
On 3/7/2026 12:35 AM, Christian Marangi wrote:
> Introduce a variant of the FS loader driver to extract images from FIP
> image. These image can contain additional binary used to init Network
> accellerator or PHY firmware blob.
>
> The way FIP handle image type is with the usage of UUID.
>
> This FIP loader driver implement a simple FIP image parser that check
> every entry for a matching UUID.
>
> Similar to FS loader, this driver also support both UBI and Block
> devices.
>
> Also an additional property is added to handle special case with eMMC
> that doesn't have a GPT partition and require a global offset to
> reference the FIP partition.
>
> An example usage of this driver is the following:
>
> Entry in DTS:
>
> fs_loader0: fip-loader {
> bootph-all;
> compatible = "u-boot,fip-loader";
> phandlepart = <&mmc0 0>;
> partoffset = <0x100>;
> };
>
> ethernet at 1fb50000 {
> firmware-loader = <&fs_loader0>;
> }
>
> FIP loader user:
>
> /* get the FW loader from the ethernet node */
> get_fw_loader_from_node(dev_ofnode(dev), &fw_loader);
>
> /* read the blob identified by "58704aef-389f-3e52-b475-e0bf2234a6a2" UUID */
> request_firmware_into_buf(fw_loader, "58704aef-389f-3e52-b475-e0bf2234a6a2",
> buf, 261400, 0);
>
> Signed-off-by: Christian Marangi <ansuelsmth at gmail.com>
> ---
> drivers/misc/Kconfig | 11 +
> drivers/misc/fw_loader/Makefile | 1 +
> drivers/misc/fw_loader/fip_loader.c | 575 ++++++++++++++++++++++++++++
> drivers/misc/fw_loader/fw_loader.c | 3 +
> drivers/misc/fw_loader/internal.h | 2 +
> include/dm/uclass-id.h | 3 +-
> 6 files changed, 594 insertions(+), 1 deletion(-)
> create mode 100644 drivers/misc/fw_loader/fip_loader.c
>
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 9d332230b1f9..8d2f11de0fe7 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -613,6 +613,17 @@ config MPC83XX_SERDES
> config FW_LOADER
> bool
>
> +config FIP_LOADER
> + bool "Enable loader driver from FIP partition"
> + select LIB_UUID
> + select FW_LOADER
> + help
> + This is FIP partition generic loader which can be used to load
> + the file image from the FIP image into target such as memory.
> +
> + The consumer driver would then use this loader to program whatever,
> + ie. the FPGA device/PHY firmware.
> +
> config FS_LOADER
> bool "Enable loader driver for file system"
> select FW_LOADER
> diff --git a/drivers/misc/fw_loader/Makefile b/drivers/misc/fw_loader/Makefile
> index 96baebede788..7854b64148e6 100644
> --- a/drivers/misc/fw_loader/Makefile
> +++ b/drivers/misc/fw_loader/Makefile
> @@ -1,4 +1,5 @@
> # SPDX-License-Identifier: GPL-2.0+
>
> obj-y += fw_loader.o
> +obj-$(CONFIG_FIP_LOADER) += fip_loader.o
> obj-$(CONFIG_$(PHASE_)FS_LOADER) += fs_loader.o
> diff --git a/drivers/misc/fw_loader/fip_loader.c b/drivers/misc/fw_loader/fip_loader.c
> new file mode 100644
> index 000000000000..376e1d2a59d8
> --- /dev/null
> +++ b/drivers/misc/fw_loader/fip_loader.c
> @@ -0,0 +1,575 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 Christian Marangi <ansuelsmth at gmail.com>
> + *
> + */
> +
> +#define LOG_CATEGORY UCLASS_FIP_FIRMWARE_LOADER
> +
> +#include <dm.h>
> +#include <div64.h>
> +#include <env.h>
> +#include <errno.h>
> +#include <blk.h>
> +#include <fs.h>
> +#include <fs_loader.h>
> +#include <log.h>
> +#include <mapmem.h>
> +#include <malloc.h>
> +#include <memalign.h>
> +#include <part.h>
> +#include <u-boot/uuid.h>
> +
> +#ifdef CONFIG_CMD_UBIFS
> +#include <ubi_uboot.h>
> +#endif
> +
> +#include "internal.h"
> +
> +#define MIN(a, b) ((a) < (b) ? (a) : (b))
> +
> +#define TOC_HEADER_NAME 0xaa640001
> +
> +struct fip_toc_header {
> + u32 name;
> + u32 serial_number;
> + u64 flags;
> +};
> +
> +struct fip_toc_entry {
> + struct uuid uuid;
> + u64 offset_address;
> + u64 size;
> + u64 flags;
> +};
> +
> +enum fip_storage_interface {
> + FIP_STORAGE_INTERFACE_BLK,
> + FIP_STORAGE_INTERFACE_UBI,
> +};
> +
> +struct fip_storage_info {
> + enum fip_storage_interface storage_interface;
> +
> + /* BLK info */
> + struct disk_partition part_info;
> + struct blk_desc *desc;
> + unsigned int part_offset;
> +
> + /* UBI info */
> + char *ubi_volume;
> +};
> +
> +#ifdef CONFIG_CMD_UBIFS
> +static int mount_ubifs(char *mtdpart, char *ubivol)
> +{
> + int ret;
> +
> + ret = ubi_part(mtdpart, NULL);
> + if (ret)
> + log_err("Cannot find mtd partition %s\n", mtdpart);
> +
You seem to not be calling cmd_ubifs_mount to actually mount UBIFS.
Can we move out the common code between *_loader.c such as these as well?
> + return ret;
> +}
> +
> +static void umount_ubifs(void)
> +{
> + cmd_ubifs_umount();
> +}
> +#else
> +static int mount_ubifs(char *mtdpart, char *ubivol)
> +{
> + log_err("Cannot load image: no UBIFS support.\n");
> + return -ENOSYS;
> +}
> +
> +static void umount_ubifs(void)
> +{
> +}
> +#endif
> +
> +static bool validate_fip_toc_header(struct fip_toc_header *hdr)
> +{
> + if (hdr->name != TOC_HEADER_NAME) {
> + log_err("Invalid FIP header\n");
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static int firmware_name_to_uuid(struct firmware *firmwarep,
> + struct uuid *uuid)
> +{
> + const char *uuid_str = firmwarep->name;
> + int ret;
> +
> + ret = uuid_str_to_bin(uuid_str, (unsigned char *)uuid,
> + UUID_STR_FORMAT_STD);
> + if (ret)
> + log_err("Invalid UUID str: %s\n", uuid_str);
> +
> + return ret;
> +}
> +
> +static int check_fip_toc_entry(struct fip_toc_entry *ent,
> + struct uuid *uuid,
> + struct fip_toc_entry *dent)
> +{
> + struct uuid uuid_null = { };
> +
> + /* NULL uuid. We parsed every entry */
> + if (!memcmp(&ent->uuid, &uuid_null, sizeof(uuid_null)))
> + return -ENOENT;
> +
> + /* We found the related uuid */
> + if (!memcmp(&ent->uuid, uuid, sizeof(*uuid))) {
> + log_debug("Found matching FIP entry. offset: 0x%llx size: %lld\n",
> + ent->offset_address, ent->size);
> + memcpy(dent, ent, sizeof(*ent));
> + return 0;
> + }
> +
> + return -EAGAIN;
> +}
> +
> +static int blk_read_fip_toc_header(struct blk_desc *desc, u32 offset,
> + char *buf, struct fip_toc_header *hdr)
> +{
> + unsigned int blkcnt = BLOCK_CNT(sizeof(*hdr), desc);
> + size_t read = 0;
> + int i;
> +
> + for (i = 0; i < blkcnt && read < sizeof(*hdr); i++) {
> + unsigned int to_read = MIN(desc->blksz,
> + sizeof(*hdr) - read);
> +
> + blk_dread(desc, offset + i, 1, buf);
blk_dread returns the number of blocks read, we should have a check if it
returns 1 or not.
> +
> + memcpy((u8 *)hdr + read, buf, to_read);
> + read += to_read;
> + }
> +
> + return read;
> +}
> +
> +static int blk_read_fip_toc_entry(struct blk_desc *desc, u32 offset,
> + int pos, char *buf,
> + struct fip_toc_entry *ent)
> +{
> + unsigned int left, consumed, to_read, read = 0;
> + unsigned int blkstart, blkcnt;
> + int i;
> +
> + consumed = pos % desc->blksz;
> + left = desc->blksz - consumed;
> + to_read = MIN(left, sizeof(*ent));
> +
> + blkstart = BLOCK_CNT(pos, desc);
> + blkcnt = BLOCK_CNT(sizeof(*ent) - to_read, desc);
> +
> + /* Read data from previous cached block if present */
> + if (left) {
> + memcpy(ent, buf + consumed, to_read);
> + read += to_read;
> + }
> +
> + for (i = 0; i < blkcnt && read < sizeof(*ent); i++) {
> + to_read = MIN(desc->blksz, sizeof(*ent) - read);
> +
> + blk_dread(desc, offset + blkstart + i, 1, buf);
Same here and below
> +
> + memcpy((u8 *)ent + read, buf, to_read);
> + read += to_read;
> + }
> +
> + return read;
> +}
> +
> +static int blk_parse_fip_firmware(struct firmware *firmwarep,
> + struct blk_desc *desc,
> + struct disk_partition *part_info,
> + unsigned int part_offset,
> + struct fip_toc_entry *dent)
> +{
> + unsigned int offset = part_info->start + part_offset;
> + struct fip_toc_header hdr;
> + struct fip_toc_entry ent;
> + struct uuid uuid;
> + unsigned int pos;
> + char *read_buf;
> + int ret;
> +
> + /* Allocate a Scratch Buffer for FIP parsing */
> + read_buf = malloc(desc->blksz);
> + if (!read_buf)
> + return -ENOMEM;
> +
> + pos = blk_read_fip_toc_header(desc, offset, read_buf, &hdr);
> +
> + if (!validate_fip_toc_header(&hdr)) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + ret = firmware_name_to_uuid(firmwarep, &uuid);
> + if (ret)
> + goto out;
> +
> + /* Loop for every FIP entry searching for uuid */
> + while (true) {
> + pos += blk_read_fip_toc_entry(desc, offset, pos,
> + read_buf, &ent);
> +
> + ret = check_fip_toc_entry(&ent, &uuid, dent);
> + if (ret != -EAGAIN)
> + break;
> + }
> +
> +out:
> + free(read_buf);
> + return ret;
> +}
> +
> +#ifdef CONFIG_CMD_UBIFS
> +static int ubi_parse_fip_firmware(struct firmware *firmwarep,
> + char *ubi_vol,
> + struct fip_toc_entry *dent)
> +{
> + struct fip_toc_header hdr;
> + struct fip_toc_entry ent;
> + struct uuid uuid;
> + unsigned int pos;
> + int ret;
> +
> + ret = ubi_volume_read(ubi_vol, (char *)&hdr, 0, sizeof(hdr));
> + if (ret)
> + return ret;
> +
> + pos = sizeof(hdr);
> +
> + if (!validate_fip_toc_header(&hdr))
> + return -EINVAL;
> +
> + ret = firmware_name_to_uuid(firmwarep, &uuid);
> + if (ret)
> + return ret;
> +
> + /* Loop for every FIP entry searching for uuid */
> + while (true) {
> + ret = ubi_volume_read(ubi_vol, (char *)&ent, pos,
> + sizeof(ent));
> + if (ret)
> + return ret;
> +
> + ret = check_fip_toc_entry(&ent, &uuid, dent);
> + if (ret != -EAGAIN)
> + break;
> +
> + pos += sizeof(ent);
> + }
> +
> + return ret;
> +}
> +#endif
> +
> +static int parse_fip_firmware(struct firmware *firmwarep,
> + struct fip_storage_info *info,
> + struct fip_toc_entry *dent)
> +{
> + switch (info->storage_interface) {
> + case FIP_STORAGE_INTERFACE_BLK:
> + return blk_parse_fip_firmware(firmwarep, info->desc,
> + &info->part_info,
> + info->part_offset,
> + dent);
> +#ifdef CONFIG_CMD_UBIFS
> + case FIP_STORAGE_INTERFACE_UBI:
> + return ubi_parse_fip_firmware(firmwarep,
> + info->ubi_volume,
> + dent);
> +#endif
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int blk_read_fip_firmware(struct firmware *firmwarep,
> + struct blk_desc *desc,
> + struct disk_partition *part_info,
> + unsigned int part_offset,
> + const struct fip_toc_entry *ent)
> +{
> + unsigned int offset = part_info->start + part_offset;
> + unsigned int pos, to_read, read = 0;
> + unsigned long long blkstart;
> + size_t size = ent->size;
> + unsigned int blkcnt;
> + char *read_buf;
> + int i, ret;
> +
> + read_buf = malloc(desc->blksz);
> + if (!read_buf)
> + return -ENOMEM;
> +
> + blkcnt = BLOCK_CNT(size + firmwarep->offset, desc);
> + blkstart = ent->offset_address + firmwarep->offset;
> + pos = do_div(blkstart, desc->blksz);
> +
> + /* Read data in the middle of a block */
> + if (pos) {
> + to_read = MIN(desc->blksz - pos, size);
> + blk_dread(desc, offset + blkstart, 1, read_buf);
> +
> + memcpy((u8 *)firmwarep->data, read_buf + pos, to_read);
> + read += to_read;
> + blkstart++;
> + }
> +
> + /* Consume all the remaining block */
> + for (i = 0; i < blkcnt && read < size; i++) {
> + to_read = MIN(desc->blksz, size - read);
> + blk_dread(desc, offset + blkstart + i, 1, read_buf);
> +
> + memcpy((u8 *)firmwarep->data + read, read_buf, to_read);
> + read += to_read;
> + }
> +
> + ret = read;
> +
> + free(read_buf);
> + return ret;
> +}
> +
> +#ifdef CONFIG_CMD_UBIFS
> +static int ubi_read_fip_firmware(struct firmware *firmwarep,
> + char *ubi_vol,
> + const struct fip_toc_entry *ent)
> +{
> + unsigned int offset = firmwarep->offset;
> + size_t size = ent->size;
> + int ret;
> +
> + ret = ubi_volume_read(ubi_vol,
> + (u8 *)firmwarep->data,
> + ent->offset_address + offset,
> + size - offset);
> + if (ret)
> + return ret;
> +
> + return size - firmwarep->offset;
> +}
> +#endif
> +
> +static int read_fip_firmware(struct firmware *firmwarep,
> + struct fip_storage_info *info,
> + const struct fip_toc_entry *dent)
> +{
> + switch (info->storage_interface) {
> + case FIP_STORAGE_INTERFACE_BLK:
> + return blk_read_fip_firmware(firmwarep, info->desc,
> + &info->part_info,
> + info->part_offset,
> + dent);
> +#ifdef CONFIG_CMD_UBIFS
> + case FIP_STORAGE_INTERFACE_UBI:
> + return ubi_read_fip_firmware(firmwarep,
> + info->ubi_volume,
> + dent);
> +#endif
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int fw_parse_storage_info(struct udevice *dev,
> + struct fip_storage_info *info)
> +{
> + char *storage_interface, *dev_part, *ubi_mtdpart, *ubi_volume;
> + struct device_plat *plat = dev_get_plat(dev);
> + int ret;
> +
> + storage_interface = env_get("storage_interface");
> + dev_part = env_get("fw_dev_part");
> + ubi_mtdpart = env_get("fw_ubi_mtdpart");
> + ubi_volume = env_get("fw_ubi_volume");
> + info->part_offset = env_get_hex("fw_partoffset", 0);
> +
> + if (storage_interface && dev_part) {
> + int part;
> +
> + part = part_get_info_by_dev_and_name_or_num(storage_interface,
> + dev_part,
> + &info->desc,
> + &info->part_info, 1);
> + if (part < 0)
> + return part;
> +
> + info->storage_interface = FIP_STORAGE_INTERFACE_BLK;
> +
> + return 0;
> + }
> +
> + if (storage_interface && ubi_mtdpart && ubi_volume) {
> + if (strcmp("ubi", storage_interface))
> + return -ENODEV;
> +
> + ret = mount_ubifs(ubi_mtdpart, ubi_volume);
> + if (ret)
> + return ret;
> +
> + info->ubi_volume = ubi_volume;
> + info->storage_interface = FIP_STORAGE_INTERFACE_UBI;
> +
> + return 0;
> + }
> +
> + info->part_offset = plat->partoffset;
> +
> + if (plat->phandlepart.phandle) {
> + struct udevice *disk_dev;
> + ofnode node;
> + int part;
> +
> + node = ofnode_get_by_phandle(plat->phandlepart.phandle);
> +
> + ret = device_get_global_by_ofnode(node, &disk_dev);
> + if (ret)
> + return ret;
> +
> + info->desc = blk_get_by_device(disk_dev);
> + if (!info->desc)
> + return -ENODEV;
> +
> + part = plat->phandlepart.partition;
> + if (part >= 1)
> + ret = part_get_info(info->desc, part,
> + &info->part_info);
> + else
> + ret = part_get_info_whole_disk(info->desc,
> + &info->part_info);
> +
> + info->storage_interface = FIP_STORAGE_INTERFACE_BLK;
> +
> + return ret;
> + }
> +
> + if (plat->mtdpart && plat->ubivol) {
> + ret = mount_ubifs(plat->mtdpart, plat->ubivol);
> + if (ret)
> + return ret;
> +
> + info->ubi_volume = plat->ubivol;
> + info->storage_interface = FIP_STORAGE_INTERFACE_UBI;
> +
> + return 0;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static void fw_storage_info_release(struct udevice *dev,
> + const struct fip_storage_info *info)
> +{
> + switch (info->storage_interface) {
> + case FIP_STORAGE_INTERFACE_UBI:
> + umount_ubifs();
> + return;
> + default:
> + return;
> + }
> +}
> +
> +/**
> + * fw_get_fip_firmware - load firmware into an allocated buffer.
> + * @dev: An instance of a driver.
> + *
> + * Return: Size of total read, negative value when error.
> + */
> +static int fw_get_fip_firmware(struct udevice *dev)
> +{
> + struct fip_toc_entry ent;
> + struct fip_storage_info info = { };
> + int ret;
> +
> + ret = fw_parse_storage_info(dev, &info);
> + if (ret)
> + goto out;
> +
> + struct firmware *firmwarep = dev_get_priv(dev);
> +
> + if (!firmwarep) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + ret = parse_fip_firmware(firmwarep, &info, &ent);
> + if (ret)
> + goto out;
> +
> + if (ent.size + firmwarep->offset > firmwarep->size) {
> + log_err("Not enough space to read firmware\n");
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + ret = read_fip_firmware(firmwarep, &info, &ent);
> + if (ret < 0)
> + log_err("Failed to read %s from FIP: %d.\n",
> + firmwarep->name, ret);
> +
> +out:
> + fw_storage_info_release(dev, &info);
> + return ret;
> +}
> +
> +static int fip_loader_probe(struct udevice *dev)
> +{
> + struct device_plat *plat = dev_get_plat(dev);
> + int ret;
> +
> + ret = generic_fw_loader_probe(dev);
> + if (ret)
> + return ret;
> +
> + plat->get_firmware = fw_get_fip_firmware;
> +
> + return 0;
> +};
> +
> +static int fip_loader_of_to_plat(struct udevice *dev)
> +{
> + struct device_plat *plat = dev_get_plat(dev);
> + ofnode fip_loader_node = dev_ofnode(dev);
> + int ret;
> +
> + ret = generic_fw_loader_of_to_plat(dev);
> + if (ret)
> + return ret;
> +
> + /* Node validation is already done by the generic function */
> + ofnode_read_u32(fip_loader_node, "partoffset",
> + &plat->partoffset);
> +
> + return 0;
> +}
> +
> +static const struct udevice_id fip_loader_ids[] = {
> + { .compatible = "u-boot,fip-loader"},
> + { }
> +};
> +
> +U_BOOT_DRIVER(fip_loader) = {
> + .name = "fip-loader",
> + .id = UCLASS_FIP_FIRMWARE_LOADER,
> + .of_match = fip_loader_ids,
> + .probe = fip_loader_probe,
> + .of_to_plat = fip_loader_of_to_plat,
> + .plat_auto = sizeof(struct device_plat),
> + .priv_auto = sizeof(struct firmware),
> +};
> +
> +UCLASS_DRIVER(fip_loader) = {
> + .id = UCLASS_FIP_FIRMWARE_LOADER,
> + .name = "fip-loader",
> +};
> diff --git a/drivers/misc/fw_loader/fw_loader.c b/drivers/misc/fw_loader/fw_loader.c
> index e477a631fae3..d2446fdc07da 100644
> --- a/drivers/misc/fw_loader/fw_loader.c
> +++ b/drivers/misc/fw_loader/fw_loader.c
> @@ -72,6 +72,9 @@ static int fw_loaders[] = {
> #if CONFIG_IS_ENABLED(FS_LOADER)
> UCLASS_FS_FIRMWARE_LOADER,
> #endif
> +#if CONFIG_IS_ENABLED(FIP_LOADER)
> + UCLASS_FIP_FIRMWARE_LOADER,
> +#endif
> };
>
> /**
> diff --git a/drivers/misc/fw_loader/internal.h b/drivers/misc/fw_loader/internal.h
> index fc78a4add59d..5513e043925f 100644
> --- a/drivers/misc/fw_loader/internal.h
> +++ b/drivers/misc/fw_loader/internal.h
> @@ -25,11 +25,13 @@ struct phandle_part {
> * This holds information about all supported storage devices for driver use.
> *
> * @phandlepart: Attribute data for block device.
> + * @partoffset: Global offset for BLK partition.
> * @mtdpart: MTD partition for ubi partition.
> * @ubivol: UBI volume-name for ubifsmount.
> */
> struct device_plat {
> struct phandle_part phandlepart;
> + int partoffset;
> char *mtdpart;
> char *ubivol;
>
> diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
> index 36b5d87c304f..641d8e1e42fa 100644
> --- a/include/dm/uclass-id.h
> +++ b/include/dm/uclass-id.h
> @@ -69,7 +69,8 @@ enum uclass_id {
> UCLASS_FIRMWARE, /* Firmware */
> UCLASS_FPGA, /* FPGA device */
> UCLASS_FUZZING_ENGINE, /* Fuzzing engine */
> - UCLASS_FS_FIRMWARE_LOADER, /* Generic loader */
> + UCLASS_FIP_FIRMWARE_LOADER, /* FIP image loader */
> + UCLASS_FS_FIRMWARE_LOADER, /* Generic loader */
> UCLASS_FWU_MDATA, /* FWU Metadata Access */
> UCLASS_GPIO, /* Bank of general-purpose I/O pins */
> UCLASS_HASH, /* Hash device */
--
Thanking You
Neha Malcom Francis
More information about the U-Boot
mailing list