]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
boot: Add UCLASS support for extension boards
authorKory Maincent (TI.com) <kory.maincent@bootlin.com>
Thu, 30 Oct 2025 16:45:01 +0000 (17:45 +0100)
committerTom Rini <trini@konsulko.com>
Mon, 3 Nov 2025 16:02:39 +0000 (10:02 -0600)
Introduce UCLASS-based extension board support to enable more
standardized and automatic loading of extension board device tree
overlays in preparation for integration with bootstd and pxe_utils.

Several #if CONFIG_IS_ENABLED are used in cmd/extension_board.c to ease the
development but don't worry they are removed later in the series.

Signed-off-by: Kory Maincent (TI.com) <kory.maincent@bootlin.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
MAINTAINERS
boot/Kconfig
boot/Makefile
boot/extension-uclass.c [new file with mode: 0644]
cmd/Kconfig
cmd/extension_board.c
doc/usage/cmd/extension.rst
include/dm/uclass-id.h
include/extension_board.h

index c67d5ae9d6be307d5b59de18945c6a4d23007839..a63ffa14ef500b2c8377baca5a6d7174a86afa4a 100644 (file)
@@ -1187,6 +1187,7 @@ M:        Kory Maincent <kory.maincent@bootlin.com>
 S:     Maintained
 F:     board/sunxi/chip.c
 F:     board/ti/common/cape_detect.c
+F:     boot/extension-uclass.c
 F:     boot/extension.c
 F:     cmd/extension_board.c
 F:     include/extension_board.h
index a75f6b641ed1c0ee1033b3f0a33fe6f11da68794..08d666d4b93b0a81a48103b86f0ae400a7eba750 100644 (file)
@@ -1910,6 +1910,10 @@ config SUPPORT_EXTENSION_SCAN
        select OF_LIBFDT_OVERLAY
         bool
 
+config SUPPORT_DM_EXTENSION_SCAN
+       select OF_LIBFDT_OVERLAY
+        bool
+
 config USE_BOOTARGS
        bool "Enable boot arguments"
        help
index f60d13130b17117bdf807d73a20c67e5b1d58e8e..aa26070fbb8d209939719a0a1e1778f5dfa495d1 100644 (file)
@@ -10,6 +10,7 @@ obj-$(CONFIG_CMD_BOOTM) += bootm.o bootm_os.o
 obj-$(CONFIG_CMD_BOOTZ) += bootm.o bootm_os.o
 obj-$(CONFIG_CMD_BOOTI) += bootm.o bootm_os.o
 obj-$(CONFIG_SUPPORT_EXTENSION_SCAN) += extension.o
+obj-$(CONFIG_SUPPORT_DM_EXTENSION_SCAN) += extension-uclass.o
 
 obj-$(CONFIG_PXE_UTILS) += pxe_utils.o
 
diff --git a/boot/extension-uclass.c b/boot/extension-uclass.c
new file mode 100644 (file)
index 0000000..9149712
--- /dev/null
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2025 Köry Maincent <kory.maincent@bootlin.com>
+ */
+
+#include <alist.h>
+#include <command.h>
+#include <env.h>
+#include <extension_board.h>
+#include <fdt_support.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/uclass.h>
+
+struct alist *dm_extension_get_list(void)
+{
+       struct udevice *dev;
+
+       if (uclass_first_device_err(UCLASS_EXTENSION, &dev))
+               return NULL;
+
+       return dev_get_priv(dev);
+}
+
+int dm_extension_probe(struct udevice *dev)
+{
+       struct alist *extension_list = dev_get_priv(dev);
+
+       alist_init_struct(extension_list, struct extension);
+       return 0;
+}
+
+int dm_extension_remove(struct udevice *dev)
+{
+       struct alist *extension_list = dev_get_priv(dev);
+
+       alist_uninit(extension_list);
+       return 0;
+}
+
+int dm_extension_scan(void)
+{
+       struct alist *extension_list = dm_extension_get_list();
+       const struct extension_ops *ops;
+       struct udevice *dev;
+       int ret;
+
+       ret = uclass_first_device_err(UCLASS_EXTENSION, &dev);
+       if (ret)
+               return ret;
+
+       if (!extension_list)
+               return -ENODEV;
+
+       ops = extension_get_ops(dev);
+       alist_empty(extension_list);
+       return ops->scan(dev, extension_list);
+}
+
+static int _extension_apply(const struct extension *extension)
+{
+       ulong extrasize, overlay_addr;
+       struct fdt_header *blob;
+       char *overlay_cmd;
+       int ret;
+
+       if (!working_fdt) {
+               printf("No FDT memory address configured. Please configure\n"
+                      "the FDT address via \"fdt addr <address>\" command.\n");
+               return -EINVAL;
+       }
+
+       overlay_cmd = env_get("extension_overlay_cmd");
+       if (!overlay_cmd) {
+               printf("Environment extension_overlay_cmd is missing\n");
+               return -EINVAL;
+       }
+
+       overlay_addr = env_get_hex("extension_overlay_addr", 0);
+       if (!overlay_addr) {
+               printf("Environment extension_overlay_addr is missing\n");
+               return -EINVAL;
+       }
+
+       env_set("extension_overlay_name", extension->overlay);
+       ret = run_command(overlay_cmd, 0);
+       if (ret)
+               return ret;
+
+       extrasize = env_get_hex("filesize", 0);
+       if (!extrasize)
+               return -EINVAL;
+
+       fdt_shrink_to_minimum(working_fdt, extrasize);
+
+       blob = map_sysmem(overlay_addr, 0);
+       if (!fdt_valid(&blob)) {
+               printf("Invalid overlay devicetree %s\n", extension->overlay);
+               return -EINVAL;
+       }
+
+       /* Apply method prints messages on error */
+       ret = fdt_overlay_apply_verbose(working_fdt, blob);
+       if (ret)
+               printf("Failed to apply overlay %s\n", extension->overlay);
+
+       return ret;
+}
+
+int dm_extension_apply(int extension_num)
+{
+       struct alist *extension_list = dm_extension_get_list();
+       const struct extension *extension;
+
+       if (!extension_list)
+               return -ENOENT;
+
+       extension = alist_get(extension_list, extension_num,
+                             struct extension);
+       if (!extension) {
+               printf("Wrong extension number\n");
+               return -EINVAL;
+       }
+
+       return _extension_apply(extension);
+}
+
+int dm_extension_apply_all(void)
+{
+       struct alist *extension_list = dm_extension_get_list();
+       const struct extension *extension;
+       int ret;
+
+       if (!extension_list)
+               return -ENOENT;
+
+       alist_for_each(extension, extension_list) {
+               ret = _extension_apply(extension);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+UCLASS_DRIVER(extension) = {
+       .name   = "extension",
+       .id     = UCLASS_EXTENSION,
+};
index 986eeeba807575d689712c879df9e82ef1593b09..721bdb87c8a18863eb4cc1f08210aaab59fb2de6 100644 (file)
@@ -548,7 +548,7 @@ config CMD_FDT
 config CMD_EXTENSION
        bool "Extension board management command"
        select CMD_FDT
-       depends on SUPPORT_EXTENSION_SCAN
+       depends on SUPPORT_EXTENSION_SCAN || SUPPORT_DM_EXTENSION_SCAN
        help
          Enables the "extension" command, which allows to detect
          extension boards connected to the system, and apply
index 129f3db9e4ce5f267ed6eaa2d9874f18b21c5b73..1f1eddbe976832d7180c22a230830ce1773da46d 100644 (file)
@@ -4,6 +4,7 @@
  * Köry Maincent, Bootlin, <kory.maincent@bootlin.com>
  */
 
+#include <alist.h>
 #include <exports.h>
 #include <command.h>
 #include <extension_board.h>
 static int do_extension_list(struct cmd_tbl *cmdtp, int flag,
                             int argc, char *const argv[])
 {
+#if CONFIG_IS_ENABLED(SUPPORT_DM_EXTENSION_SCAN)
+       struct alist *dm_extension_list;
+#endif
        struct extension *extension;
        int i = 0;
 
+#if CONFIG_IS_ENABLED(SUPPORT_DM_EXTENSION_SCAN)
+       dm_extension_list = dm_extension_get_list();
+
+       if (!alist_get_ptr(dm_extension_list, 0)) {
+               printf("No extension registered - Please run \"extension scan\"\n");
+               return CMD_RET_SUCCESS;
+       }
+
+       alist_for_each(extension, dm_extension_list) {
+               printf("Extension %d: %s\n", i++, extension->name);
+               printf("\tManufacturer: \t\t%s\n", extension->owner);
+               printf("\tVersion: \t\t%s\n", extension->version);
+               printf("\tDevicetree overlay: \t%s\n", extension->overlay);
+               printf("\tOther information: \t%s\n", extension->other);
+       }
+#else
        if (list_empty(&extension_list)) {
                printf("No extension registered - Please run \"extension scan\"\n");
                return CMD_RET_SUCCESS;
@@ -26,6 +46,7 @@ static int do_extension_list(struct cmd_tbl *cmdtp, int flag,
                printf("\tDevicetree overlay: \t%s\n", extension->overlay);
                printf("\tOther information: \t%s\n", extension->other);
        }
+#endif
        return CMD_RET_SUCCESS;
 }
 
@@ -34,9 +55,19 @@ static int do_extension_scan(struct cmd_tbl *cmdtp, int flag,
 {
        int extension_num;
 
+#if CONFIG_IS_ENABLED(SUPPORT_DM_EXTENSION_SCAN)
+       extension_num = dm_extension_scan();
+       if (extension_num == -ENODEV)
+               extension_num = 0;
+       else if (extension_num < 0)
+               return CMD_RET_FAILURE;
+
+       printf("Found %d extension board(s).\n", extension_num);
+#else
        extension_num = extension_scan(true);
-       if (extension_num < 0)
+       if (extension_num < 0 && extension_num != -ENODEV)
                return CMD_RET_FAILURE;
+#endif
 
        return CMD_RET_SUCCESS;
 }
@@ -44,22 +75,34 @@ static int do_extension_scan(struct cmd_tbl *cmdtp, int flag,
 static int do_extension_apply(struct cmd_tbl *cmdtp, int flag,
                              int argc, char *const argv[])
 {
+#if !CONFIG_IS_ENABLED(SUPPORT_DM_EXTENSION_SCAN)
        struct extension *extension = NULL;
-       int i = 0, extension_id, ret;
        struct list_head *entry;
+       int i = 0;
+#endif
+       int extension_id, ret;
 
        if (argc < 2)
                return CMD_RET_USAGE;
 
        if (strcmp(argv[1], "all") == 0) {
                ret = CMD_RET_FAILURE;
+#if CONFIG_IS_ENABLED(SUPPORT_DM_EXTENSION_SCAN)
+               if (dm_extension_apply_all())
+                       return CMD_RET_FAILURE;
+#else
                list_for_each_entry(extension, &extension_list, list) {
                        ret = extension_apply(extension);
                        if (ret != CMD_RET_SUCCESS)
                                break;
                }
+#endif
        } else {
                extension_id = simple_strtol(argv[1], NULL, 10);
+#if CONFIG_IS_ENABLED(SUPPORT_DM_EXTENSION_SCAN)
+               if (dm_extension_apply(extension_id))
+                       return CMD_RET_FAILURE;
+#else
                list_for_each(entry, &extension_list) {
                        if (i == extension_id) {
                                extension = list_entry(entry, struct extension,  list);
@@ -74,9 +117,10 @@ static int do_extension_apply(struct cmd_tbl *cmdtp, int flag,
                }
 
                ret = extension_apply(extension);
+#endif
        }
 
-       return ret;
+       return CMD_RET_SUCCESS;
 }
 
 static struct cmd_tbl cmd_extension[] = {
index 4c261e74951c0fb7cedebe7e95e8ba8a0bc0d8d3..fbe95aace795e280b9e52f6a1d2bbc70fc5152af 100644 (file)
@@ -25,9 +25,8 @@ Device Tree overlays depending on the detected extension boards.
 
 The "extension" command comes with three sub-commands:
 
- - "extension scan" makes the generic code call the board-specific
-   extension_board_scan() function to retrieve the list of detected
-   extension boards.
+ - "extension scan" makes the generic code call a board-specific extension
+   function to retrieve the list of detected extension boards.
 
  - "extension list" allows to list the detected extension boards.
 
@@ -98,17 +97,23 @@ Simple extension_board_scan function example
 
 .. code-block:: c
 
-    int extension_board_scan(struct list_head *extension_list)
+    static int foo_extension_board_scan(struct alist *extension_list)
     {
-        struct extension *extension;
+        struct extension extension = {0};
 
-        extension = calloc(1, sizeof(struct extension));
-        snprintf(extension->overlay, sizeof(extension->overlay), "overlay.dtbo");
-        snprintf(extension->name, sizeof(extension->name), "extension board");
-        snprintf(extension->owner, sizeof(extension->owner), "sandbox");
-        snprintf(extension->version, sizeof(extension->version), "1.1");
-        snprintf(extension->other, sizeof(extension->other), "Extension board information");
-        list_add_tail(&extension->list, extension_list);
+        snprintf(extension.overlay, sizeof(extension.overlay), "overlay.dtbo");
+        snprintf(extension.name, sizeof(extension.name), "extension board");
+        snprintf(extension.owner, sizeof(extension.owner), "sandbox");
+        snprintf(extension.version, sizeof(extension.version), "1.1");
+        snprintf(extension.other, sizeof(extension.other), "Extension board information");
+        if (!alist_add(extension_list, extension))
+                return -ENOMEM;
 
         return 1;
     }
+
+    U_BOOT_EXTENSION(foo_extension_name, foo_extension_board_scan);
+
+    U_BOOT_DRVINFO(foo_extension_name) = {
+        .name = "foo_extension_name",
+    };
index 6be59093160f3990b8a2cdacd9593ed3c7624920..eb6416b591739e7aa6fc8fe627ecd9298f621de1 100644 (file)
@@ -63,6 +63,7 @@ enum uclass_id {
        UCLASS_ETH,             /* Ethernet device */
        UCLASS_ETH_PHY,         /* Ethernet PHY device */
        UCLASS_EXTCON,          /* External Connector Class */
+       UCLASS_EXTENSION,       /* Extension board */
        UCLASS_FFA,             /* Arm Firmware Framework for Armv8-A */
        UCLASS_FFA_EMUL,                /* sandbox FF-A device emulator */
        UCLASS_FIRMWARE,        /* Firmware */
index 0821dddd8ffde54d2888ab3709e371dfe8c1c5f4..a74a9c67c01bd55e6c21e6988224dc44e3c535f2 100644 (file)
@@ -7,10 +7,55 @@
 #ifndef __EXTENSION_SUPPORT_H
 #define __EXTENSION_SUPPORT_H
 
+#include <alist.h>
+#include <dm/device.h>
 #include <linux/list.h>
+#include <dm/platdata.h>
 
 extern struct list_head extension_list;
 
+/**
+ * dm_extension_get_list - Get the extension list
+ * Return: The extension alist pointer, or NULL if no such list exists.
+ *
+ * The caller must not free the list.
+ */
+struct alist *dm_extension_get_list(void);
+
+/**
+ * dm_extension_probe - Probe extension device
+ * @dev: Extension device that needs to be probed
+ * Return: Zero on success, negative on failure.
+ */
+int dm_extension_probe(struct udevice *dev);
+
+/**
+ * dm_extension_remove - Remove extension device
+ * @dev: Extension device that needs to be removed
+ * Return: Zero on success, negative on failure.
+ */
+int dm_extension_remove(struct udevice *dev);
+
+/**
+ * dm_extension_scan - Scan extension boards available.
+ * Return: Zero on success, negative on failure.
+ */
+int dm_extension_scan(void);
+
+/**
+ * dm_extension_apply - Apply extension board overlay to the devicetree
+ * @extension_num: Extension number to be applied
+ * Return: Zero on success, negative on failure.
+ */
+int dm_extension_apply(int extension_num);
+
+/**
+ * dm_extension_apply_all - Apply all extension board overlays to the
+ *                         devicetree
+ * Return: Zero on success, negative on failure.
+ */
+int dm_extension_apply_all(void);
+
 /**
  * extension - Description fields of an extension board
  * @list: List head
@@ -29,6 +74,32 @@ struct extension {
        char other[32];
 };
 
+struct extension_ops {
+       /**
+        * scan - Add system-specific function to scan extension boards.
+        * @dev: extension device
+        * @extension_list: alist of extension to expand
+        * Return: The number of extension or a negative value in case of
+        *         error.
+        */
+       int (*scan)(struct udevice *dev, struct alist *extension_list);
+};
+
+#define extension_get_ops(dev)  ((struct extension_ops *)(dev)->driver->ops)
+
+/* Currently, only one extension driver enabled at a time is supported */
+#define U_BOOT_EXTENSION(_name, _scan_func) \
+       U_BOOT_DRIVER(_name) = { \
+               .name = #_name, \
+               .id = UCLASS_EXTENSION, \
+               .probe = dm_extension_probe, \
+               .remove = dm_extension_remove, \
+               .ops = &(struct extension_ops) { \
+                      .scan = _scan_func, \
+                      }, \
+               .priv_auto = sizeof(struct alist), \
+       }
+
 /**
  * extension_board_scan - Add system-specific function to scan extension board.
  * @param extension_list       List of extension board information to update.