[RFC PATCH v2 3/3] dm: Core changes for tiny-dm
Simon Glass
sjg at chromium.org
Thu Jul 2 23:10:04 CEST 2020
This patch includes changes to support tiny-dm in driver model and dtoc.
Signed-off-by: Simon Glass <sjg at chromium.org>
---
Changes in v2:
- Various updates, and ported to chromebook_jerry (rockchip)
board/Synology/ds109/ds109.c | 3 +-
common/console.c | 2 +-
common/log.c | 36 +++-
common/malloc_simple.c | 31 ++++
common/spl/spl.c | 17 +-
common/spl/spl_spi.c | 91 +++++----
doc/develop/debugging.rst | 35 ++++
doc/driver-model/tiny-dm.rst | 315 +++++++++++++++++++++++++++++++
drivers/core/Kconfig | 106 +++++++++++
drivers/core/Makefile | 3 +
drivers/core/of_extra.c | 49 ++++-
drivers/core/regmap.c | 3 +-
drivers/core/syscon-uclass.c | 68 +++++--
drivers/core/tiny.c | 249 +++++++++++++++++++++++++
include/dm/device.h | 121 ++++++++++++
include/dm/of_extra.h | 6 +
include/dm/platdata.h | 20 +-
include/dm/tiny_struct.h | 42 +++++
include/linker_lists.h | 6 +
scripts/Makefile.spl | 6 +-
tools/dtoc/dtb_platdata.py | 316 +++++++++++++++++++++++++++-----
tools/dtoc/dtoc_test_simple.dts | 12 +-
tools/dtoc/fdt.py | 7 +-
tools/dtoc/main.py | 9 +-
tools/dtoc/test_dtoc.py | 91 ++++++++-
tools/patman/tools.py | 4 +-
26 files changed, 1525 insertions(+), 123 deletions(-)
create mode 100644 doc/develop/debugging.rst
create mode 100644 doc/driver-model/tiny-dm.rst
create mode 100644 drivers/core/tiny.c
create mode 100644 include/dm/tiny_struct.h
diff --git a/board/Synology/ds109/ds109.c b/board/Synology/ds109/ds109.c
index aa2987d924..d5d7396ad3 100644
--- a/board/Synology/ds109/ds109.c
+++ b/board/Synology/ds109/ds109.c
@@ -106,8 +106,7 @@ void reset_misc(void)
printf("Synology reset...");
udelay(50000);
- b_d = ns16550_calc_divisor((NS16550_t)CONFIG_SYS_NS16550_COM2,
- CONFIG_SYS_NS16550_CLK, 9600);
+ b_d = ns16550_calc_divisor(CONFIG_SYS_NS16550_CLK, 9600);
NS16550_init((NS16550_t)CONFIG_SYS_NS16550_COM2, b_d);
NS16550_putc((NS16550_t)CONFIG_SYS_NS16550_COM2, SOFTWARE_REBOOT);
}
diff --git a/common/console.c b/common/console.c
index f149624954..466e45ae1b 100644
--- a/common/console.c
+++ b/common/console.c
@@ -259,7 +259,7 @@ static inline void console_doenv(int file, struct stdio_dev *dev)
iomux_doenv(file, dev->name);
}
#endif
-#else
+#else /* !CONSOLE_MUX */
static inline int console_getc(int file)
{
return stdio_devices[file]->getc(stdio_devices[file]);
diff --git a/common/log.c b/common/log.c
index f44f15743f..abe6fe7096 100644
--- a/common/log.c
+++ b/common/log.c
@@ -45,11 +45,13 @@ const char *log_get_cat_name(enum log_category_t cat)
if (cat >= LOGC_NONE)
return log_cat_name[cat - LOGC_NONE];
-#if CONFIG_IS_ENABLED(DM)
- name = uclass_get_name((enum uclass_id)cat);
-#else
- name = NULL;
-#endif
+ /* With tiny-dm we don't have uclasses, so cannot get the name */
+ if (CONFIG_IS_ENABLED(TINY_ONLY))
+ return "tiny";
+ if (CONFIG_IS_ENABLED(DM))
+ name = uclass_get_name((enum uclass_id)cat);
+ else
+ name = NULL;
return name ? name : "<missing>";
}
@@ -182,6 +184,21 @@ static bool log_passes_filters(struct log_device *ldev, struct log_rec *rec)
return false;
}
+void log_check(const char *msg)
+{
+ struct log_device *ldev;
+ int count = 0;
+
+ list_for_each_entry(ldev, &gd->log_head, sibling_node) {
+ count++;
+ if (count > 1) {
+ printf("%s: %s error\n", msg, __func__);
+ panic("log");
+ }
+ }
+ printf("%s: log OK\n", msg);
+}
+
/**
* log_dispatch() - Send a log record to all log devices for processing
*
@@ -296,6 +313,15 @@ int log_remove_filter(const char *drv_name, int filter_num)
return -ENOENT;
}
+void log_fixup_for_gd_move(struct global_data *new_gd)
+{
+ /* The sentinel node has moved, so update things that point to it */
+ if (gd->log_head.next) {
+ new_gd->log_head.next->prev = &new_gd->log_head;
+ new_gd->log_head.prev->next = &new_gd->log_head;
+ }
+}
+
int log_init(void)
{
struct log_driver *drv = ll_entry_start(struct log_driver, log_driver);
diff --git a/common/malloc_simple.c b/common/malloc_simple.c
index 34f0b49093..7e73fd7231 100644
--- a/common/malloc_simple.c
+++ b/common/malloc_simple.c
@@ -80,3 +80,34 @@ void malloc_simple_info(void)
log_info("malloc_simple: %lx bytes used, %lx remain\n", gd->malloc_ptr,
CONFIG_VAL(SYS_MALLOC_F_LEN) - gd->malloc_ptr);
}
+
+uint malloc_ptr_to_ofs(void *ptr)
+{
+ ulong addr = map_to_sysmem(ptr);
+ ulong offset;
+
+ offset = addr - gd->malloc_base;
+ if (offset >= gd->malloc_limit) {
+ log_debug("Invalid malloc ptr %p (base=%lx, size=%lx)\n", ptr,
+ gd->malloc_base, gd->malloc_limit);
+ panic("malloc ptr invalid");
+ }
+
+ return offset;
+}
+
+void *malloc_ofs_to_ptr(uint offset)
+{
+ void *base = map_sysmem(gd->malloc_base, gd->malloc_limit);
+ void *ptr;
+
+ if (offset >= gd->malloc_limit) {
+ log_debug("Invalid malloc offset %lx (size=%lx)\n",
+ gd->malloc_base, gd->malloc_limit);
+ panic("malloc offset invalid");
+ }
+ ptr = base + offset;
+ unmap_sysmem(base);
+
+ return ptr;
+}
diff --git a/common/spl/spl.c b/common/spl/spl.c
index 0e96a8cd10..61867c81a3 100644
--- a/common/spl/spl.c
+++ b/common/spl/spl.c
@@ -399,7 +399,7 @@ static int spl_common_init(bool setup_malloc)
if (ret) {
debug("%s: Failed to set up bootstage: ret=%d\n", __func__,
ret);
- return ret;
+ return log_msg_ret("bootstage", ret);
}
#ifdef CONFIG_BOOTSTAGE_STASH
if (!u_boot_first_phase()) {
@@ -418,17 +418,17 @@ static int spl_common_init(bool setup_malloc)
ret = log_init();
if (ret) {
debug("%s: Failed to set up logging\n", __func__);
- return ret;
+ return log_msg_ret("log", ret);
}
#endif
if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
ret = fdtdec_setup();
if (ret) {
debug("fdtdec_setup() returned error %d\n", ret);
- return ret;
+ return log_msg_ret("fdtdec", ret);
}
}
- if (CONFIG_IS_ENABLED(DM)) {
+ if (CONFIG_IS_ENABLED(DM) && !CONFIG_IS_ENABLED(TINY_ONLY)) {
bootstage_start(BOOTSTAGE_ID_ACCUM_DM_SPL,
spl_phase() == PHASE_TPL ? "dm tpl" : "dm_spl");
/* With CONFIG_SPL_OF_PLATDATA, bring in all devices */
@@ -436,7 +436,7 @@ static int spl_common_init(bool setup_malloc)
bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_SPL);
if (ret) {
debug("dm_init_and_scan() returned error %d\n", ret);
- return ret;
+ return log_msg_ret("init", ret);
}
}
@@ -819,9 +819,10 @@ ulong spl_relocate_stack_gd(void)
ptr = CONFIG_SPL_STACK_R_ADDR - roundup(sizeof(gd_t),16);
new_gd = (gd_t *)ptr;
memcpy(new_gd, (void *)gd, sizeof(gd_t));
-#if CONFIG_IS_ENABLED(DM)
- dm_fixup_for_gd_move(new_gd);
-#endif
+ if (CONFIG_IS_ENABLED(DM) && !CONFIG_IS_ENABLED(TINY_ONLY))
+ dm_fixup_for_gd_move(new_gd);
+ if (CONFIG_IS_ENABLED(LOG))
+ log_fixup_for_gd_move(new_gd);
#if !defined(CONFIG_ARM) && !defined(CONFIG_RISCV)
gd = new_gd;
#endif
diff --git a/common/spl/spl_spi.c b/common/spl/spl_spi.c
index 2744fb5d52..fe198354f3 100644
--- a/common/spl/spl_spi.c
+++ b/common/spl/spl_spi.c
@@ -9,12 +9,14 @@
*/
#include <common.h>
+#include <dm.h>
#include <image.h>
#include <log.h>
#include <spi.h>
#include <spi_flash.h>
#include <errno.h>
#include <spl.h>
+#include <dm/of_extra.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -55,7 +57,7 @@ static int spi_load_image_os(struct spl_image_info *spl_image,
static ulong spl_spi_fit_read(struct spl_load_info *load, ulong sector,
ulong count, void *buf)
{
- struct spi_flash *flash = load->dev;
+ struct spi_flash *flash = load->legacy_dev;
ulong ret;
ret = spi_flash_read(flash, sector, count, buf);
@@ -70,6 +72,21 @@ unsigned int __weak spl_spi_get_uboot_offs(struct spi_flash *flash)
return CONFIG_SYS_SPI_U_BOOT_OFFS;
}
+static int spl_read(struct spl_load_info *load, u32 offset, size_t len,
+ void *buf)
+{
+ int ret;
+
+ if (!CONFIG_IS_ENABLED(TINY_SPI_FLASH))
+ ret = spi_flash_read(load->legacy_dev, offset, len, buf);
+ else
+ ret = tiny_spi_flash_read(load->tdev, offset, len, buf);
+
+ if (ret)
+ return log_ret(ret);
+
+ return 0;
+}
/*
* The main entry for SPI booting. It's necessary that SDRAM is already
* configured and available since this code loads the main U-Boot image
@@ -80,41 +97,53 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
{
int err = 0;
unsigned int payload_offs;
+ struct spl_load_info load;
struct spi_flash *flash;
struct image_header *header;
+ struct tinydev *tdev;
/*
* Load U-Boot image from SPI flash into RAM
* In DM mode: defaults speed and mode will be
* taken from DT when available
*/
-
- flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS,
- CONFIG_SF_DEFAULT_CS,
- CONFIG_SF_DEFAULT_SPEED,
- CONFIG_SF_DEFAULT_MODE);
- if (!flash) {
- puts("SPI probe failed.\n");
- return -ENODEV;
+ memset(&load, '\0', sizeof(load));
+ if (!CONFIG_IS_ENABLED(TINY_SPI_FLASH)) {
+ flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS,
+ CONFIG_SF_DEFAULT_CS,
+ CONFIG_SF_DEFAULT_SPEED,
+ CONFIG_SF_DEFAULT_MODE);
+ if (!flash) {
+ puts("SPI probe failed\n");
+ return -ENODEV;
+ }
+ payload_offs = spl_spi_get_uboot_offs(flash);
+ if (CONFIG_IS_ENABLED(OF_CONTROL) &&
+ !CONFIG_IS_ENABLED(OF_PLATDATA)) {
+ payload_offs = ofnode_read_config_int(
+ "u-boot,spl-payload-offset",
+ payload_offs);
+ }
+ load.legacy_dev = flash;
+ } else {
+ tdev = tiny_dev_get(UCLASS_SPI_FLASH, 0);
+ if (!tdev) {
+ puts("SPI probe failed\n");
+ return -ENODEV;
+ }
+ load.tdev = tdev;
+ payload_offs = CONFIG_SYS_SPI_U_BOOT_OFFS;
}
- payload_offs = spl_spi_get_uboot_offs(flash);
-
header = spl_get_load_buffer(-sizeof(*header), sizeof(*header));
-#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
- payload_offs = fdtdec_get_config_int(gd->fdt_blob,
- "u-boot,spl-payload-offset",
- payload_offs);
-#endif
-
#ifdef CONFIG_SPL_OS_BOOT
if (spl_start_uboot() || spi_load_image_os(spl_image, flash, header))
#endif
{
- /* Load u-boot, mkimage header is 64 bytes. */
- err = spi_flash_read(flash, payload_offs, sizeof(*header),
- (void *)header);
+ /* Load U-Boot, mkimage header is 64 bytes. */
+ err = spl_read(&load, payload_offs, sizeof(*header),
+ (void *)header);
if (err) {
debug("%s: Failed to read from SPI flash (err=%d)\n",
__func__, err);
@@ -123,30 +152,23 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT_FULL) &&
image_get_magic(header) == FDT_MAGIC) {
- err = spi_flash_read(flash, payload_offs,
- roundup(fdt_totalsize(header), 4),
- (void *)CONFIG_SYS_LOAD_ADDR);
+ err = spl_read(&load, payload_offs,
+ roundup(fdt_totalsize(header), 4),
+ (void *)CONFIG_SYS_LOAD_ADDR);
if (err)
return err;
err = spl_parse_image_header(spl_image,
- (struct image_header *)CONFIG_SYS_LOAD_ADDR);
+ (struct image_header *)CONFIG_SYS_LOAD_ADDR);
} else if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&
image_get_magic(header) == FDT_MAGIC) {
- struct spl_load_info load;
-
debug("Found FIT\n");
- load.dev = flash;
load.priv = NULL;
load.filename = NULL;
load.bl_len = 1;
load.read = spl_spi_fit_read;
err = spl_load_simple_fit(spl_image, &load,
- payload_offs,
- header);
+ payload_offs, header);
} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
- struct spl_load_info load;
-
- load.dev = flash;
load.priv = NULL;
load.filename = NULL;
load.bl_len = 1;
@@ -158,9 +180,8 @@ static int spl_spi_load_image(struct spl_image_info *spl_image,
err = spl_parse_image_header(spl_image, header);
if (err)
return err;
- err = spi_flash_read(flash, payload_offs,
- spl_image->size,
- (void *)spl_image->load_addr);
+ err = spl_read(&load, payload_offs, spl_image->size,
+ (void *)spl_image->load_addr);
}
}
diff --git a/doc/develop/debugging.rst b/doc/develop/debugging.rst
new file mode 100644
index 0000000000..2b06a4a38f
--- /dev/null
+++ b/doc/develop/debugging.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: GPL-2.0+
+.. Copyright (c) 2020 Heinrich Schuchardt
+
+Debugging
+=========
+
+This describes a few debugging techniques for different parts of U-Boot.
+
+Makefiles
+---------
+
+You can use $(warning) to show debugging information in a makefile::
+
+ $(warning SPL: $(CONFIG_SPL_BUILD) . $(SPL_TPL_))
+
+When make executes these they produce a message. If you put them in a rule, they
+are executed when the rule is executed. For example, to show the value of a
+variable at the point where it is used::
+
+ tools-only: scripts_basic $(version_h) $(timestamp_h) tools/version.h
+ $(warning version_h: $(version_h))
+ $(Q)$(MAKE) $(build)=tools
+
+You can use ifndef in makefiles for simple CONFIG checks::
+
+ ifndef CONFIG_DM_DEV_READ_INLINE
+ obj-$(CONFIG_OF_CONTROL) += read.o
+ endif
+
+but for those which require variable expansion you should use ifeq or ifneq::
+
+ ifeq ($(CONFIG_$(SPL_TPL_)TINY_ONLY),)
+ obj-y += device.o fdtaddr.o lists.o root.o uclass.o util.o
+ endif
+
diff --git a/doc/driver-model/tiny-dm.rst b/doc/driver-model/tiny-dm.rst
new file mode 100644
index 0000000000..cc5c3e00b1
--- /dev/null
+++ b/doc/driver-model/tiny-dm.rst
@@ -0,0 +1,315 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Tiny driver model (tiny-dm)
+===========================
+
+Purpose
+-------
+
+Reduce the overhead of using driver model in SPL and TPL.
+
+
+Introduction
+------------
+
+On some platforms that use SPL [1]_, SRAM is extremely limited. There is a
+need to use as little space as possible for U-Boot SPL.
+
+With the migration to driver model and devicetree, the extra software complexity
+has created more pressure on U-Boot's code and data size.
+
+A few features have been introduced to help with this problem:
+
+ - fdtgrep, introduced in 2015, automatically removes unnecessary parts of the
+ device tree, e.g. those used by drivers not present in SPL. At the time,
+ this typically reduced SPL size from about 40KB to perhaps 3KB and made
+ it feasible to look at using driver model with SPL. The minimum overhead
+ was reduced to approximately 7KB on Thumb systems, for example [2]_
+ - of-platdata, introduced in 2016 [3]_, converts the device tree into C data
+ structures which are placed in the SPL image. This saves approximately
+ 3KB of code and replaces the devicetree with something typically 30%
+ smaller.
+
+However the problem still exists. Even with of-platdata, the driver-model
+overhead is typically 3KB at the minimum. This excludes the size of allocated
+data structures, which is 84 bytes per device and 76 bytes per uclass on
+32-bit machines. On 64-bit machines these sizes approximately double.
+
+With the driver-model migration deadlines passing, a solution is needed to
+allow boards to complete migration to driver model in SPL, without taking on
+the full ~5KB overhead that this entails.
+
+
+Concept
+-------
+
+The idea of tiny devices ('tiny-dm') builds on of-platdata, but additionally
+removes most of the rich feature-set of driver model.
+
+In particular tiny-dm takes away the concept of a uclass (except that it stil
+uses uclass IDs), drastically reduces the size of a device (to 16 bytes on
+32-bit) and removes the need for a driver_info structure.
+
+With tiny-dm, dtoc outputs U_BOOT_TINY_DEVICE() instead of U_BOOT_DEVICE().
+A new 'struct tiny_dev' is used instead of 'struct udevice'. Devices can be
+located based on uclass ID and sequence number with tiny_dev_find(). Devices can
+be probed with tiny_dev_probe().
+
+In fact, tiny-dm is effectively a bypass for most of driver model. It retains
+some capability with in (chiefly by using the same device tree), but new code
+is added to implement simple features in a simple way.
+
+Tiny-dm is not suitable for complex device and interactions, but it can
+support a serial port (output only), I2C buses and other features needed to
+set up the machine just enough to load U-Boot proper.
+
+It is possible to enable Tiny-dm on a subsystem-by-subsystem basis. For example,
+enabling CONFIG_TPL_TINY_SERIAL on chromebook_coral saves about 900 bytes of
+code and data, with no perceptable difference in operation.
+
+
+Tiny devices
+------------
+
+Below is an example of a tiny device, a UART that uses NS16550. It works by
+setting up a platform structure to pass to the ns16550 driver, perhaps the
+worst driver in U-Boot.
+
+.. code-block:: c
+
+ static int apl_ns16550_tiny_probe(struct tiny_dev *tdev)
+ {
+ struct dtd_intel_apl_ns16550 *dtplat = tdev->dtplat;
+ struct ns16550_platdata *plat = tdev->priv;
+ ulong base;
+ pci_dev_t bdf;
+
+ base = dtplat->early_regs[0];
+ bdf = pci_ofplat_get_devfn(dtplat->reg[0]);
+
+ if (!CONFIG_IS_ENABLED(PCI))
+ apl_uart_init(bdf, base);
+
+ plat->base = base;
+ plat->reg_shift = dtplat->reg_shift;
+ plat->reg_width = 1;
+ plat->clock = dtplat->clock_frequency;
+ plat->fcr = UART_FCR_DEFVAL;
+
+ return ns16550_tiny_probe_plat(plat);
+ }
+
+ static int apl_ns16550_tiny_setbrg(struct tiny_dev *tdev, int baudrate)
+ {
+ struct ns16550_platdata *plat = tdev->priv;
+
+ return ns16550_tiny_setbrg(plat, baudrate);
+ }
+
+ static int apl_ns16550_tiny_putc(struct tiny_dev *tdev, const char ch)
+ {
+ struct ns16550_platdata *plat = tdev->priv;
+
+ return ns16550_tiny_putc(plat, ch);
+ }
+
+ struct tiny_serial_ops apl_ns16550_tiny_ops = {
+ .probe = apl_ns16550_tiny_probe,
+ .setbrg = apl_ns16550_tiny_setbrg,
+ .putc = apl_ns16550_tiny_putc,
+ };
+
+ U_BOOT_TINY_DRIVER(apl_ns16550) = {
+ .uclass_id = UCLASS_SERIAL,
+ .probe = apl_ns16550_tiny_probe,
+ .ops = &apl_ns16550_tiny_ops,
+ DM_TINY_PRIV(<ns16550.h>, sizeof(struct ns16550_platdata))
+ };
+
+The probe function is responsible for setting up the hardware so that the UART
+can output characters. This driver enables the device on PCI and assigns an
+address to its BAR (Base-Address Register). That code is in apl_uart_init() and
+is not show here. Then it sets up a platdata data structure for use by the
+ns16550 driver and calls its probe function.
+
+The 'tdev' device is declared like this in the device tree:
+
+.. code-block:: c
+
+ serial: serial at 18,2 {
+ reg = <0x0200c210 0 0 0 0>;
+ u-boot,dm-pre-reloc;
+ compatible = "intel,apl-ns16550";
+ early-regs = <0xde000000 0x20>;
+ reg-shift = <2>;
+ clock-frequency = <1843200>;
+ current-speed = <115200>;
+ };
+
+When dtoc runs it outputs the following code for this, into dt-platdata.c:
+
+.. code-block:: c
+
+ static struct dtd_intel_apl_ns16550 dtv_serial_at_18_2 = {
+ .clock_frequency = 0x1c2000,
+ .current_speed = 0x1c200,
+ .early_regs = {0xde000000, 0x20},
+ .reg = {0x200c210, 0x0},
+ .reg_shift = 0x2,
+ };
+
+ DM_DECL_TINY_DRIVER(apl_ns16550);
+ #include <ns16550.h>
+ u8 _serial_at_18_2_priv[sizeof(struct ns16550_platdata)] __attribute__ ((section (".data")));
+ U_BOOT_TINY_DEVICE(serial_at_18_2) = {
+ .dtplat = &dtv_serial_at_18_2,
+ .drv = DM_REF_TINY_DRIVER(apl_ns16550),
+ .priv = _serial_at_18_2_priv,
+ };
+
+This basically creates a device, with a pointer to the dtplat data (a C
+structure similar to the devicetree node) and a pointer to the driver, the
+U_BOOT_TINY_DRIVER() thing shown above.
+
+So far, tiny-dm might look pretty similar to the full driver model, but there
+are quite a few differences that may not be immediately apparent:
+
+ - Whereas U_BOOT_DEVICE() emits a driver_info structure and then allocates
+ the udevice structure at runtime, U_BOOT_TINY_DEVICE() emits an actual
+ tiny_dev device structure into the image. On platforms where SPL runs in
+ read-only memory, U-Boot automatically copies this into RAM as needed.
+ - The DM_TINY_PRIV() macro tells U-Boot about the private data needed by
+ the device. But this is not allocated at runtime. Instead it is declared
+ in the C structure above. However on platforms where SPL runs in read-only
+ memory, allocation is left until runtime.
+ - There is a corresponding 'full' driver in the same file with the same name.
+ Like of-platdata, it is not possible to use tiny-dm without 'full' support
+ added as well. This makes sense because the device needs to be supported
+ in U-Boot proper as well.
+ - While this driver is in the UCLASS_SERIAL uclass, there is in fact no
+ uclass available. The serial-uclass.c implementation has an entirely
+ separate (small) piece of code to support tiny-dm:
+
+.. code-block:: c
+
+ int serial_init(void)
+ {
+ struct tiny_dev *tdev;
+ int ret;
+
+ tdev = tiny_dev_find(UCLASS_SERIAL, 0);
+ if (!tdev) {
+ if (IS_ENABLED(CONFIG_REQUIRE_SERIAL_CONSOLE))
+ panic_str("No serial");
+ return -ENODEV;
+ }
+ ret = tiny_dev_probe(tdev);
+ if (ret)
+ return log_msg_ret("probe", ret);
+ gd->tiny_serial = tdev;
+ gd->flags |= GD_FLG_SERIAL_READY;
+ serial_setbrg();
+
+ return 0;
+ }
+
+ void serial_putc(const char ch)
+ {
+ struct tiny_dev *tdev = gd->tiny_serial;
+ struct tiny_serial_ops *ops;
+
+ if (!tdev)
+ goto err;
+
+ ops = tdev->drv->ops;
+ if (!ops->putc)
+ goto err;
+ if (ch == '\n')
+ ops->putc(tdev, '\r');
+ ops->putc(tdev, ch);
+
+ return;
+ err:
+ if (IS_ENABLED(DEBUG_UART))
+ printch(ch);
+ }
+
+ void serial_puts(const char *str)
+ {
+ for (const char *s = str; *s; s++)
+ serial_putc(*s);
+ }
+
+
+When serial_putc() is called from within U-Boot, this code looks up the tiny-dm
+device and sends it the character.
+
+
+Potential costs and benefits
+----------------------------
+
+It is hard to estimate the savings to be had by switching a subsystem over to
+tiny-dm. Further work will illuminate this. In the example above (on x86),
+about 1KB bytes is saved (code and data), but this may or may not be
+representative of other subsystems.
+
+If all devices in an image use tiny-dm then it is possible to remove all the
+core driver-model support. This is the 3KB mentioned earlier. Of course, tiny-dm
+has its own overhead, although it is substantialy less than the full driver
+model.
+
+These benefits come with some drawbacks:
+
+ - Drivers that want to use it must implement tiny-dm in addition to their
+ normal support.
+ - of-platdata must be used. This cannot be made to work with device tree.
+ - Tiny-dm drivers have none of the rich support provided by driver model.
+ There is no pre-probe support, no concept of buses holding information
+ about child devices, no automatic pin control or power control when a
+ device is probed. Tiny-dm is designed to save memory, not to make it easy
+ to write complex device drivers.
+ - Subsystems must be fully migrated to driver model with the old code
+ removed. This is partly a technical limitation (see ns16550.c for how ugly
+ it is to support both, let alone three) and partly a quid-pro-quo for
+ this feature, since it should remove existing concerns about migrating to
+ driver model.
+
+
+Next steps
+----------
+
+This is currently an RFC so the final result may change somewhat from what is
+presented here. Some features are missing, in particular the concept of sequence
+numbers is designed but not implemented. The code is extremely rough.
+
+To judge the impact of tiny-dm a suitable board needs to be fully converted to
+it. At present I am leaning towards rock2, since it already supports
+of-platdata.
+
+The goal is to sent initial patches in June 2020 with the first version in
+mainline in July 2020 ready for the October release. Refinements based on
+feedback and patches received can come after that. It isn't clear yet when this
+could become a 'stable' feature, but likely after a release or two, perhaps with
+5-10 boards converted.
+
+
+Trying it out
+-------------
+
+The source tree is available at https://github.com/sjg20/u-boot/tree/dtoc-working
+
+Only two boards are supported at present:
+
+ - sandbox_spl - run spl/u-boot-spl to try the SPL with tiny-dm
+ - chromebook_coral - TPL uses tiny-dm
+
+
+.. [1] This discussion refers to SPL but for devices that use TPL, the same
+ features are available there.
+.. [2] https://www.elinux.org/images/c/c4/\Order_at_last_-_U-Boot_driver_model_slides_%282%29.pdf
+.. [3] https://elinux.org/images/8/82/What%27s_New_with_U-Boot_%281%29.pdf
+
+
+.. Simon Glass <sjg at chromium.org>
+.. Google LLC
+.. Memorial Day 2020
diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig
index 3942d11f2b..c4ea6e5a1a 100644
--- a/drivers/core/Kconfig
+++ b/drivers/core/Kconfig
@@ -36,6 +36,94 @@ config TPL_DM
CONFIG_SPL_SYS_MALLOC_F_LEN for more details on how to enable it.
Disable this for very small implementations.
+config SPL_TINY
+ bool "Support tiny drivers in TPL without full driver-model support"
+ depends on SPL_OF_PLATDATA
+ default y
+ help
+ Enable support for reducing driver-model overhead with 'tiny'
+ devices. These drivers have very basic support and do not support
+ the full driver-model infrastructure. This can be useful for saving
+ memory in SPL.
+
+config TIMYDEV_SHRINK_DATA
+ bool "Use smaller data structures"
+ help
+ Tinydev supports storing some data structures in a smaller form to
+ save memory. However this does increase code sixe so only enable
+ this option if you have quite a lot of devices used by your board
+ in SPL/TPL.
+
+config TINYDEV_DATA_MAX_COUNT
+ int "Number of tinydev data records to allow"
+ default 10
+ help
+ With tinydev each device has a single priv pointer but it is possible
+ to attach other kinds of pointers to tiny devices using a separate
+ mechanism. This sets the maximum number that can be attached. See
+ struct tinydev_info for the details. Mostly these slots are used by
+ buses, so if you have a lot of I2C or SPI devices you may need to
+ increase it.
+
+config SPL_TINY_RELOC
+ bool "Relocate devices into allocated memory before using them"
+ depends on SPL_TINY
+ help
+ Some architectures load SPL into read-only memory. U-Boot needs write
+ access to tiny devices (specifically struct tinydev) so this cannot
+ work. If this option is enabled, U-Boot relocates these devices into
+ RAM when they are needed.
+
+config SPL_TINY_ONLY
+ bool "Only support tiny drivers in SPL, not full drivers"
+ depends on SPL_TINY
+ help
+ Enable this to drop support for full drivers in SPL. This enables
+ 'tiny' drivers for all subsystems and removes the core driver-model
+ support for full drivers. This can same space, but only works if all
+ the subsystems used by your board support tiny drivers.
+
+config TPL_TINY
+ bool "Support tiny drivers in TPL without full driver-model support"
+ depends on TPL_OF_PLATDATA
+ default y
+ help
+ Enable support for reducing driver-model overhead with 'tiny'
+ devices. These drivers have very basic support and do not support
+ the full driver-model infrastructure. This can be useful for saving
+ memory in TPL.
+
+config TPL_TINY_RELOC
+ bool "Relocate devices into allocated memory before using them"
+ depends on TPL_TINY
+ default y if X86
+ help
+ Some architectures load SPL into read-only memory. U-Boot needs write
+ access to tiny devices (specifically struct tinydev) so this cannot
+ work. If this option is enabled, U-Boot relocates these devices into
+ RAM when they are needed.
+
+config TPL_TINY_ONLY
+ bool "Only support tiny drivers in SPL, not full drivers"
+ depends on TPL_TINY
+ help
+ Enable this to drop support for full drivers in SPL. This enables
+ 'tiny' drivers for all subsystems and removes the core driver-model
+ support for full drivers. This can same space, but only works if all
+ the subsystems used by your board support tiny drivers.
+
+config TINY_CHECK
+ bool "Enable run-time consistency checks"
+ default y
+ help
+ Enabling this catches some errors like drivers with required but
+ missing options. It adds slightly to code size. Provided that your
+ code is written correct and doesn't produce errors with this option
+ enabled, it is generally safe to disable it for production.
+
+ Note that this does not add checks for drivers without an operations
+ struct. A few drivers don't need operations. Otherwise, don't do that.
+
config DM_WARN
bool "Enable warnings in driver model"
depends on DM
@@ -148,6 +236,15 @@ config SPL_SYSCON
by this uclass, including accessing registers via regmap and
assigning a unique number to each.
+config SPL_TINY_SYSCON
+ bool "Support tiny syscon drivers in SPL"
+ depends on SPL_TINY
+ default y if SPL_TINY_ONLY
+ help
+ In constrained environments the driver-model overhead of several KB
+ of code and data structures can be problematic. Enable this to use a
+ tiny implementation that only supports a single driver.
+
config TPL_SYSCON
bool "Support system controllers in TPL"
depends on TPL_REGMAP
@@ -157,6 +254,15 @@ config TPL_SYSCON
by this uclass, including accessing registers via regmap and
assigning a unique number to each.
+config TPL_TINY_SYSCON
+ bool "Support tiny syscon drivers in TPL"
+ depends on TPL_TINY
+ default y if TPL_TINY_ONLY
+ help
+ In constrained environments the driver-model overhead of several KB
+ of code and data structures can be problematic. Enable this to use a
+ tiny implementation that only supports a single driver.
+
config DEVRES
bool "Managed device resources"
depends on DM
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
index c707026a3a..f3a863c27e 100644
--- a/drivers/core/Makefile
+++ b/drivers/core/Makefile
@@ -2,7 +2,9 @@
#
# Copyright (c) 2013 Google, Inc
+ifeq ($(CONFIG_$(SPL_TPL_)TINY_ONLY),)
obj-y += device.o fdtaddr.o lists.o root.o uclass.o util.o
+endif
obj-$(CONFIG_$(SPL_TPL_)ACPIGEN) += acpi.o
obj-$(CONFIG_DEVRES) += devres.o
obj-$(CONFIG_$(SPL_)DM_DEVICE_REMOVE) += device-remove.o
@@ -15,5 +17,6 @@ ifndef CONFIG_DM_DEV_READ_INLINE
obj-$(CONFIG_OF_CONTROL) += read.o
endif
obj-$(CONFIG_OF_CONTROL) += of_extra.o ofnode.o read_extra.o
+obj-$(CONFIG_$(SPL_TPL_)TINY) += tiny.o
ccflags-$(CONFIG_DM_DEBUG) += -DDEBUG
diff --git a/drivers/core/of_extra.c b/drivers/core/of_extra.c
index 6420e6ec44..b93b7e43c8 100644
--- a/drivers/core/of_extra.c
+++ b/drivers/core/of_extra.c
@@ -79,11 +79,9 @@ int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
ofnode node;
if (!ofnode_valid(config_node)) {
- config_node = ofnode_path("/config");
- if (!ofnode_valid(config_node)) {
- debug("%s: Cannot find /config node\n", __func__);
+ config_node = ofnode_get_config_node();
+ if (!ofnode_valid(config_node))
return -ENOENT;
- }
}
if (!suffix)
suffix = "";
@@ -127,3 +125,46 @@ int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
return 0;
}
+
+ofnode ofnode_get_config_node(void)
+{
+ ofnode node;
+
+ node = ofnode_path("/config");
+ if (!ofnode_valid(node))
+ debug("%s: Cannot find /config node\n", __func__);
+
+ return node;
+}
+
+int ofnode_read_config_int(const char *prop_name, int default_val)
+{
+ ofnode node;
+
+ log_debug("%s\n", prop_name);
+ node = ofnode_get_config_node();
+ if (!ofnode_valid(node))
+ return default_val;
+
+ return ofnode_read_u32_default(node, prop_name, default_val);
+}
+
+int ofnode_read_config_bool(const char *prop_name)
+{
+ ofnode node;
+
+ log_debug("%s\n", prop_name);
+ node = ofnode_get_config_node();
+
+ return ofnode_read_bool(node, prop_name);
+}
+
+const char *ofnode_read_config_string(const char *prop_name)
+{
+ ofnode node;
+
+ log_debug("%s\n", prop_name);
+ node = ofnode_get_config_node();
+
+ return ofnode_read_string(node, prop_name);
+}
diff --git a/drivers/core/regmap.c b/drivers/core/regmap.c
index a67a237b88..4c33234d1d 100644
--- a/drivers/core/regmap.c
+++ b/drivers/core/regmap.c
@@ -37,8 +37,7 @@ static struct regmap *regmap_alloc(int count)
}
#if CONFIG_IS_ENABLED(OF_PLATDATA)
-int regmap_init_mem_platdata(struct udevice *dev, fdt_val_t *reg, int count,
- struct regmap **mapp)
+int regmap_init_mem_platdata(fdt_val_t *reg, int count, struct regmap **mapp)
{
struct regmap_range *range;
struct regmap *map;
diff --git a/drivers/core/syscon-uclass.c b/drivers/core/syscon-uclass.c
index b5cd763b6b..ec91964a32 100644
--- a/drivers/core/syscon-uclass.c
+++ b/drivers/core/syscon-uclass.c
@@ -16,6 +16,17 @@
#include <dm/root.h>
#include <linux/err.h>
+void *syscon_get_first_range(ulong driver_data)
+{
+ struct regmap *map;
+
+ map = syscon_get_regmap_by_driver_data(driver_data);
+ if (IS_ERR(map))
+ return map;
+ return regmap_get_range(map, 0);
+}
+
+#if !CONFIG_IS_ENABLED(TINY_SYSCON)
/*
* Caution:
* This API requires the given device has alerady been bound to syscon driver.
@@ -52,7 +63,7 @@ static int syscon_pre_probe(struct udevice *dev)
#if CONFIG_IS_ENABLED(OF_PLATDATA)
struct syscon_base_platdata *plat = dev_get_platdata(dev);
- return regmap_init_mem_platdata(dev, plat->reg, ARRAY_SIZE(plat->reg),
+ return regmap_init_mem_platdata(plat->reg, ARRAY_SIZE(plat->reg),
&priv->regmap);
#else
return regmap_init_mem(dev_ofnode(dev), &priv->regmap);
@@ -155,16 +166,6 @@ struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
return priv->regmap;
}
-void *syscon_get_first_range(ulong driver_data)
-{
- struct regmap *map;
-
- map = syscon_get_regmap_by_driver_data(driver_data);
- if (IS_ERR(map))
- return map;
- return regmap_get_range(map, 0);
-}
-
UCLASS_DRIVER(syscon) = {
.id = UCLASS_SYSCON,
.name = "syscon",
@@ -208,3 +209,48 @@ struct regmap *syscon_node_to_regmap(ofnode node)
return r;
}
+#else
+struct tinydev *tiny_syscon_get_by_driver_data(ulong driver_data)
+{
+ struct tinydev *tdev;
+
+ tdev = tiny_dev_get_by_drvdata(UCLASS_SYSCON, driver_data);
+ if (!tdev)
+ return NULL;
+
+ return tdev;
+}
+
+struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
+{
+ struct syscon_uc_info *uc_priv;
+ struct tinydev *tdev;
+
+ tdev = tiny_syscon_get_by_driver_data(driver_data);
+ if (!tdev)
+ return ERR_PTR(-ENODEV);
+ /*
+ * We assume that the device has struct syscon_uc_info at the start of
+ * its private data
+ */
+ uc_priv = tinydev_get_priv(tdev);
+
+ return uc_priv->regmap;
+}
+
+int tiny_syscon_setup(struct tinydev *tdev)
+{
+ struct syscon_uc_info *priv = tinydev_get_priv(tdev);
+
+ /*
+ * With OF_PLATDATA we really have no way of knowing the format of
+ * the device-specific platform data. So we assume that it starts with
+ * a 'reg' member, and this holds a single address and size. Drivers
+ * using OF_PLATDATA will need to ensure that this is true.
+ */
+ struct syscon_base_platdata *plat = tdev->dtplat;
+
+ return regmap_init_mem_platdata(plat->reg, ARRAY_SIZE(plat->reg),
+ &priv->regmap);
+}
+#endif
diff --git a/drivers/core/tiny.c b/drivers/core/tiny.c
new file mode 100644
index 0000000000..4c8d0ced20
--- /dev/null
+++ b/drivers/core/tiny.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for tiny device (those without a fully uclass and driver)
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#define LOG_CATEGORY LOGC_TINYDEV
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <malloc.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+const char *tiny_dev_name(struct tinydev *tdev)
+{
+ return tdev->name;
+}
+
+static struct tinydev *tiny_dev_find_tail(struct tinydev *tdev)
+{
+ if (CONFIG_IS_ENABLED(TINY_RELOC)) {
+ struct tinydev *copy;
+
+ copy = malloc(sizeof(*copy));
+ if (!copy)
+ return NULL;
+ memcpy(copy, tdev, sizeof(*copy));
+ log_debug(" - found, copied to %p\n", copy);
+ return copy;
+ }
+ log_debug(" - found at %p\n", tdev);
+
+ return tdev;
+}
+
+struct tinydev *tiny_dev_find(enum uclass_id uclass_id, int seq)
+{
+ struct tinydev *info = ll_entry_start(struct tinydev, tiny_dev);
+ const int n_ents = ll_entry_count(struct tinydev, tiny_dev);
+ struct tinydev *entry;
+
+ log_debug("find %d seq %d: n_ents=%d\n", uclass_id, seq, n_ents);
+ for (entry = info; entry != info + n_ents; entry++) {
+ struct tiny_drv *drv = entry->drv;
+
+ log_content(" - entry %p, uclass %d %d\n", entry,
+ drv->uclass_id, uclass_id);
+ if (drv->uclass_id == uclass_id)
+ return tiny_dev_find_tail(entry);
+ }
+ log_debug(" - not found\n");
+
+ return NULL;
+}
+
+int tiny_dev_probe(struct tinydev *tdev)
+{
+ struct tiny_drv *drv;
+ int ret;
+
+ if (tdev->flags & DM_FLAG_ACTIVATED)
+ return 0;
+ if (tdev->parent) {
+ ret = tiny_dev_probe(tdev->parent);
+ if (ret)
+ return log_msg_ret("parent", ret);
+ /*
+ * The device might have already been probed during the call to
+ * tiny_dev_probe() on its parent device.
+ */
+ if (tdev->flags & DM_FLAG_ACTIVATED)
+ return 0;
+ }
+ drv = tdev->drv;
+
+ if (!tdev->priv && drv->priv_size) {
+ void *priv;
+
+ // This doesn't work with TINY_RELOC
+ priv = calloc(1, drv->priv_size);
+ if (!priv)
+ return -ENOMEM;
+ tdev->priv = priv;
+ log_debug("probe: %s: priv=%p\n", tiny_dev_name(tdev), priv);
+ }
+ if (drv->probe) {
+ ret = drv->probe(tdev);
+ if (ret)
+ return log_msg_ret("probe", ret);
+ }
+
+ tdev->flags |= DM_FLAG_ACTIVATED;
+
+ return 0;
+}
+
+struct tinydev *tiny_dev_get(enum uclass_id uclass_id, int seq)
+{
+ struct tinydev *dev;
+ int ret;
+
+ dev = tiny_dev_find(uclass_id, seq);
+ if (!dev)
+ return NULL;
+
+ ret = tiny_dev_probe(dev);
+ if (ret)
+ return NULL;
+
+ return dev;
+}
+
+struct tinydev *tinydev_from_dev_idx(tinydev_idx_t index)
+{
+ struct tinydev *start = U_BOOT_TINY_DEVICE_START;
+
+ return start + index;
+}
+
+tinydev_idx_t tinydev_to_dev_idx(const struct tinydev *tdev)
+{
+ struct tinydev *start = U_BOOT_TINY_DEVICE_START;
+
+ return tdev - start;
+}
+
+struct tinydev *tinydev_get_parent(const struct tinydev *tdev)
+{
+ return tdev->parent;
+}
+
+#ifndef CONFIG_SYS_MALLOC_F
+#error "Must enable CONFIG_SYS_MALLOC_F with tinydev"
+#endif
+
+static void *tinydev_lookup_data(struct tinydev *tdev, enum dm_data_t type)
+{
+ struct tinydev_info *info = &((gd_t *)gd)->tinydev_info;
+ struct tinydev_data *data;
+ int i;
+#ifdef TIMYDEV_SHRINK_DATA
+ uint idx = tinydev_to_dev_idx(tdev);
+
+ for (i = 0, data = info->data; i < info->data_count; i++, data++) {
+ if (data->type == type && data->tdev_idx == idx)
+ return malloc_ofs_to_ptr(data->ofs);
+ }
+#else
+ for (i = 0, data = info->data; i < info->data_count; i++, data++) {
+ if (data->type == type && data->tdev == tdev)
+ return data->ptr;
+ }
+#endif
+
+ return NULL;
+}
+
+void *tinydev_alloc_data(struct tinydev *tdev, enum dm_data_t type, int size)
+{
+ struct tinydev_info *info = &((gd_t *)gd)->tinydev_info;
+ struct tinydev_data *data;
+ void *ptr;
+
+ if (info->data_count == ARRAY_SIZE(info->data)) {
+ /* To fix this, increase CONFIG_TINYDEV_DATA_MAX_COUNT */
+ panic("tinydev data exhusted");
+ return NULL;
+ }
+ data = &info->data[info->data_count];
+ ptr = calloc(1, size);
+ if (!ptr)
+ return NULL; /* alloc_simple() has already written a message */
+ data->type = type;
+#ifdef TIMYDEV_SHRINK_DATA
+ data->tdev_idx = tinydev_to_dev_idx(tdev);
+ data->ofs = malloc_ptr_to_ofs(ptr);
+#else
+ data->tdev = tdev;
+ data->ptr = ptr;
+#endif
+ log_debug("alloc_data: %d: %s: tdev=%p, type=%d, size=%x, ptr=%p\n",
+ info->data_count, tiny_dev_name(tdev), tdev, type, size, ptr);
+ info->data_count++;
+
+ return ptr;
+}
+
+void *tinydev_ensure_data(struct tinydev *tdev, enum dm_data_t type, int size,
+ bool *existsp)
+{
+ bool exists = true;
+ void *ptr;
+
+ ptr = tinydev_lookup_data(tdev, type);
+ if (!ptr) {
+ exists = false;
+ ptr = tinydev_alloc_data(tdev, type, size);
+ }
+ if (existsp)
+ *existsp = exists;
+
+ return ptr;
+}
+
+void *tinydev_get_data(struct tinydev *tdev, enum dm_data_t type)
+{
+ void *ptr = tinydev_lookup_data(tdev, type);
+
+ if (!ptr) {
+ log_debug("Cannot find type %d for device %p\n", type, tdev);
+ panic("tinydev missing data");
+ }
+
+ return ptr;
+}
+
+struct tinydev *tiny_dev_get_by_drvdata(enum uclass_id uclass_id,
+ ulong driver_data)
+{
+ struct tinydev *info = ll_entry_start(struct tinydev, tiny_dev);
+ const int n_ents = ll_entry_count(struct tinydev, tiny_dev);
+ struct tinydev *entry;
+
+ log_debug("find %d driver_data %lx: n_ents=%d\n", uclass_id,
+ driver_data, n_ents);
+ for (entry = info; entry != info + n_ents; entry++) {
+ struct tiny_drv *drv = entry->drv;
+
+ log_content(" - entry %p, uclass %d, driver_data %x\n", entry,
+ drv->uclass_id, entry->driver_data);
+ if (drv->uclass_id == uclass_id &&
+ entry->driver_data == driver_data) {
+ struct tinydev *tdev = tiny_dev_find_tail(entry);
+ int ret;
+
+ if (!tdev)
+ return NULL;
+ ret = tiny_dev_probe(tdev);
+ if (ret)
+ return NULL;
+ return tdev;
+ }
+ }
+
+ return NULL;
+}
diff --git a/include/dm/device.h b/include/dm/device.h
index f5738a0cee..1229f0aea6 100644
--- a/include/dm/device.h
+++ b/include/dm/device.h
@@ -11,6 +11,7 @@
#define _DM_DEVICE_H
#include <dm/ofnode.h>
+#include <dm/tiny_struct.h>
#include <dm/uclass-id.h>
#include <fdtdec.h>
#include <linker_lists.h>
@@ -289,6 +290,126 @@ struct driver {
*/
#define U_BOOT_DRIVER_ALIAS(__name, __alias)
+/**
+ * struct tiny_drv: A tiny driver
+ *
+ * This provides a smaller driver than the full-blown driver-model. It is
+ * intended for SPL or TPL and offers just a probe() function and some basic
+ * operations. It has a limit of 256 bytes for the private size.
+ *
+ * Note that tiny drivers exist alongside normal ones. Each subsystem must be
+ * enabled for tiny drivers (e.g. CONFIG_SPL_TINY_SERIAL for serial).
+ *
+ * Naming here is changed from struct driver, to make it easier to search code.
+ *
+ * @uclass_id: Identifies the uclass we belong to
+ * @priv_size: If non-zero this is the size of the private data to be allocated
+ * in the device's ->priv pointer. If zero, then the driver is responsible for
+ * allocating any data required (but malloc() is discouraged in tiny drivers)
+ * @ops: Driver-specific operations. This is typically a list of function
+ * pointers defined by the driver, to implement driver functions required by
+ * the uclass.
+ */
+struct tiny_drv {
+ u8 uclass_id;
+ u8 priv_size;
+ int (*probe)(struct tinydev *dev);
+ struct tinydev *tdev;
+ void *ops;
+};
+
+/* Declare a new 'tiny' U-Boot driver */
+#define U_BOOT_TINY_DRIVER(__name) \
+ ll_entry_declare(struct tiny_drv, __name, tiny_drv)
+
+/* Get a pointer to a given tiny driver */
+#define DM_GET_TINY_DRIVER(__name) \
+ ll_entry_get(struct tiny_drv, __name, tiny_drv)
+
+#define DM_DECL_TINY_DRIVER(__name) \
+ ll_entry_decl(struct tiny_drv, __name, tiny_drv)
+
+/*
+ * Get a pointer to a given tiny driver, for use in data structures. This
+ * requires that the symbol be declared with DM_DECL_TINY_DRIVER() first
+ */
+#define DM_REF_TINY_DRIVER(__name) \
+ ll_entry_ref(struct tiny_drv, __name, tiny_drv)
+
+/**
+ * DM_TINY_PRIV() - Specifies the size of the private data
+ *
+ * This does not generate any code, but is parsed by dtoc. Put it inside the
+ * U_BOOT_TINY_DRIVER on its own lines to specify the amount of data to be
+ * allocated in tiny_dev->priv
+ */
+#define DM_TINY_PRIV(hdr,size) .priv_size = size,
+
+/**
+ * struct tinydev - A tiny device
+ *
+ * This does not have a separate struct driver_info like full devices. The
+ * platform data is combined into this struct, which is used by dtoc to declare
+ * devices it finds in the devicetree.
+ *
+ * @dtplat: Pointer to the platform data, as generated by dtoc
+ * @drv: Pointer to the driver
+ * @flags: Flags for this device DM_FLAG_...
+ */
+struct tinydev {
+ const char *name; /* allow this to be dropped */
+ /* TODO: Convert these into ushort offsets to a base address */
+ void *dtplat; /* u16 word index into dtd data section */
+ void *priv; /* u16 word index into device priv section */
+ struct tiny_drv *drv; /* u8 index into driver list? */
+ u16 flags; /* switch to u8? */
+ u8 driver_data;
+ struct tinydev *parent;
+};
+
+/* Declare a tiny device with a given name */
+#define U_BOOT_TINY_DEVICE(__name) \
+ ll_entry_declare(struct tinydev, __name, tiny_dev)
+
+#define DM_REF_TINY_DEVICE(__name) \
+ ll_entry_ref(struct tinydev, __name, tiny_dev)
+
+/**
+ * U_BOOT_TINY_DEVICE_START - Find the start of the list of tiny devices
+ *
+ * Use this like this:
+ * struct tinydev *start = U_BOOT_TINY_DEVICE_START;
+ */
+#define U_BOOT_TINY_DEVICE_START \
+ ll_entry_start(struct tinydev, tiny_dev)
+
+struct tinydev *tiny_dev_find(enum uclass_id uclass_id, int seq);
+
+int tiny_dev_probe(struct tinydev *tdev);
+
+struct tinydev *tiny_dev_get(enum uclass_id uclass_id, int seq);
+
+struct tinydev *tinydev_from_dev_idx(tinydev_idx_t index);
+
+tinydev_idx_t tinydev_to_dev_idx(const struct tinydev *tdev);
+
+struct tinydev *tinydev_get_parent(const struct tinydev *tdev);
+
+static inline void *tinydev_get_priv(const struct tinydev *tdev)
+{
+ return tdev->priv;
+}
+
+void *tinydev_get_data(struct tinydev *tdev, enum dm_data_t type);
+
+void *tinydev_alloc_data(struct tinydev *tdev, enum dm_data_t type, int size);
+
+void *tinydev_ensure_data(struct tinydev *tdev, enum dm_data_t type, int size,
+ bool *existsp);
+
+struct tinydev *tiny_dev_get_by_drvdata(enum uclass_id uclass_id,
+ ulong driver_data);
+
/**
* dev_get_platdata() - Get the platform data for a device
*
diff --git a/include/dm/of_extra.h b/include/dm/of_extra.h
index ca15df21b0..ccc58b228b 100644
--- a/include/dm/of_extra.h
+++ b/include/dm/of_extra.h
@@ -86,4 +86,10 @@ int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
const char *suffix, fdt_addr_t *basep,
fdt_size_t *sizep);
+ofnode ofnode_get_config_node(void);
+
+int ofnode_read_config_int(const char *prop_name, int default_val);
+int ofnode_read_config_bool(const char *prop_name);
+const char *ofnode_read_config_string(const char *prop_name);
+
#endif
diff --git a/include/dm/platdata.h b/include/dm/platdata.h
index cab93b071b..f75f7d9cee 100644
--- a/include/dm/platdata.h
+++ b/include/dm/platdata.h
@@ -45,7 +45,22 @@ struct driver_info {
#define U_BOOT_DEVICES(__name) \
ll_entry_declare_list(struct driver_info, __name, driver_info)
-/* Get a pointer to a given driver */
+/**
+ * Get a pointer to a given device info given its name
+ *
+ * With the declaration U_BOOT_DEVICE(name), DM_GET_DEVICE(name) will return a
+ * pointer to the struct driver_info created by that declaration.
+ *
+ * if OF_PLATDATA is enabled, from this it is possible to use the @dev member of
+ * struct driver_info to find the device pointer itself.
+ *
+ * TODO(sjg at chromium.org): U_BOOT_DEVICE() tells U-Boot to create a device, so
+ * the naming seems sensible, but DM_GET_DEVICE() is a bit of misnomer, since it
+ * finds the driver_info record, not the device.
+ *
+ * @__name: Driver name (C identifier, not a string. E.g. gpio7_at_ff7e0000)
+ * @return struct driver_info * to the driver that created the device
+ */
#define DM_GET_DEVICE(__name) \
ll_entry_get(struct driver_info, __name, driver_info)
@@ -57,4 +72,7 @@ struct driver_info {
* by dtoc when parsing dtb.
*/
void dm_populate_phandle_data(void);
+
+#define IF_OF_PLATDATA(x) CONFIG_IS_ENABLED(OF_PLATDATA, (x))
+
#endif
diff --git a/include/dm/tiny_struct.h b/include/dm/tiny_struct.h
new file mode 100644
index 0000000000..3de52d83ec
--- /dev/null
+++ b/include/dm/tiny_struct.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Structures for inclusion in global_data
+ *
+ * Copyright 2020 Google LLC
+ * Written by Simon Glass <sjg at chromium.org>
+ */
+
+#ifndef __DM_TINY_STRUCT_H
+#define __DM_TINY_STRUCT_H
+
+/* A struct tinydev * stored as an index into the device linker-list */
+typedef u8 tinydev_idx_t;
+
+/* enum dm_data_t - Types of data that can be attached to devices */
+enum dm_data_t {
+ DEVDATAT_PLAT,
+ DEVDATAT_PARENT_PLAT,
+ DEVDATAT_UC_PLAT,
+
+ DEVDATAT_PRIV,
+ DEVDATAT_PARENT_PRIV,
+ DEVDATAT_UC_PRIV,
+};
+
+struct tinydev_data {
+ u8 type;
+#ifdef TIMYDEV_SHRINK_DATA
+ tinydev_idx_t tdev_idx;
+ ushort ofs;
+#else
+ struct tinydev *tdev;
+ void *ptr;
+#endif
+};
+
+struct tinydev_info {
+ int data_count;
+ struct tinydev_data data[CONFIG_TINYDEV_DATA_MAX_COUNT];
+};
+
+#endif
diff --git a/include/linker_lists.h b/include/linker_lists.h
index d775d041e0..8419e752b1 100644
--- a/include/linker_lists.h
+++ b/include/linker_lists.h
@@ -210,6 +210,12 @@
_ll_result; \
})
+#define ll_entry_decl(_type, _name, _list) \
+ extern _type _u_boot_list_2_##_list##_2_##_name
+
+#define ll_entry_ref(_type, _name, _list) \
+ (_type *)&_u_boot_list_2_##_list##_2_##_name
+
/**
* ll_start() - Point to first entry of first linker-generated array
* @_type: Data type of the entry
diff --git a/scripts/Makefile.spl b/scripts/Makefile.spl
index e6d56a1286..47d9a60c22 100644
--- a/scripts/Makefile.spl
+++ b/scripts/Makefile.spl
@@ -303,10 +303,12 @@ $(obj)/$(SPL_BIN).dtb: dts/dt-spl.dtb FORCE
pythonpath = PYTHONPATH=scripts/dtc/pylibfdt
quiet_cmd_dtocc = DTOC C $@
-cmd_dtocc = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb -o $@ platdata
+cmd_dtocc = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb \
+ -o $@ -c $(KCONFIG_CONFIG) -s $(srctree) platdata
quiet_cmd_dtoch = DTOC H $@
-cmd_dtoch = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb -o $@ struct
+cmd_dtoch = $(pythonpath) $(srctree)/tools/dtoc/dtoc -d $(obj)/$(SPL_BIN).dtb \
+ -o $@ -s $(srctree) struct
quiet_cmd_plat = PLAT $@
cmd_plat = $(CC) $(c_flags) -c $< -o $(filter-out $(PHONY),$@)
diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py
index 6afe1e0aad..4e5fdfa8ba 100644
--- a/tools/dtoc/dtb_platdata.py
+++ b/tools/dtoc/dtb_platdata.py
@@ -21,6 +21,7 @@ import sys
from dtoc import fdt
from dtoc import fdt_util
+from patman import tools
# When we see these properties we ignore them - i.e. do not create a structure
# member
@@ -56,6 +57,22 @@ VAL_PREFIX = 'dtv_'
# phandles is len(args). This is a list of integers.
PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args'])
+class DriverInfo:
+ def __init__(self, name, uclass_id, compat):
+ self.name = name
+ self.uclass_id = uclass_id
+ self.compat = compat
+ self.priv_size = 0
+
+ def __eq__(self, other):
+ return (self.name == other.name and
+ self.uclass_id == other.uclass_id and
+ self.compat == other.compat and
+ self.priv_size == other.priv_size)
+
+ def __repr__(self):
+ return ("DriverInfo(name='%s', uclass_id='%s', compat=%s, priv_size=%s)" %
+ (self.name, self.uclass_id, self.compat, self.priv_size))
def conv_name_to_c(name):
"""Convert a device-tree name to a C identifier
@@ -144,6 +161,7 @@ class DtbPlatdata(object):
Properties:
_fdt: Fdt object, referencing the device tree
_dtb_fname: Filename of the input device tree binary file
+ _config_fname: Filename of the .config file for the build
_valid_nodes: A list of Node object with compatible strings
_include_disabled: true to include nodes marked status = "disabled"
_outfile: The current output file (sys.stdout or a real file)
@@ -158,19 +176,27 @@ class DtbPlatdata(object):
U_BOOT_DRIVER_ALIAS(driver_alias, driver_name)
value: Driver name declared with U_BOOT_DRIVER(driver_name)
_links: List of links to be included in dm_populate_phandle_data()
+ _tiny_uclasses: List of uclass names that are marked as 'tiny'
"""
- def __init__(self, dtb_fname, include_disabled, warning_disabled):
+ def __init__(self, dtb_fname, config_fname, include_disabled,
+ warning_disabled):
self._fdt = None
self._dtb_fname = dtb_fname
+ self._config_fname = config_fname
self._valid_nodes = None
self._include_disabled = include_disabled
self._outfile = None
self._warning_disabled = warning_disabled
self._lines = []
- self._aliases = {}
- self._drivers = []
+ self._compat_aliases = {}
+ self._drivers = {}
self._driver_aliases = {}
self._links = []
+ self._aliases = {}
+ self._aliases_by_path = {}
+ self._tiny_uclasses = []
+ self._of_match = {}
+ self._compat_to_driver = {}
def get_normalized_compat_name(self, node):
"""Get a node's normalized compat name
@@ -195,8 +221,8 @@ class DtbPlatdata(object):
compat_c = self._driver_aliases.get(compat_c)
if not compat_c:
if not self._warning_disabled:
- print('WARNING: the driver %s was not found in the driver list'
- % (compat_c_old))
+ print('WARNING: the driver %s was not found in the driver list. Check that your driver has the same name as one of its compatible strings' %
+ (compat_c_old))
compat_c = compat_c_old
else:
aliases_c = [compat_c_old] + aliases_c
@@ -217,6 +243,10 @@ class DtbPlatdata(object):
else:
self._outfile = open(fname, 'w')
+ def close_output(self):
+ if self._outfile is not sys.stdout:
+ self._outfile.close()
+
def out(self, line):
"""Output a string to the output file
@@ -287,8 +317,8 @@ class DtbPlatdata(object):
break
target = self._fdt.phandle_to_node.get(phandle)
if not target:
- raise ValueError("Cannot parse '%s' in node '%s'" %
- (prop.name, node_name))
+ raise ValueError("Cannot parse '%s' in node '%s' (phandle=%d)" %
+ (prop.name, node_name, phandle))
cells = None
for prop_name in ['#clock-cells', '#gpio-cells']:
cells = target.props.get(prop_name)
@@ -315,33 +345,144 @@ class DtbPlatdata(object):
with open(fn) as fd:
buff = fd.read()
- # The following re will search for driver names declared as
- # U_BOOT_DRIVER(driver_name)
- drivers = re.findall('U_BOOT_DRIVER\((.*)\)', buff)
+ drivers = {}
+
+ # Dict of compatible strings in a udevice_id array:
+ # key: udevice_id array name (e.g. 'rk3288_syscon_ids_noc')
+ # value: Dict of compatible strings in that array:
+ # key: Compatible string, e.g. 'rockchip,rk3288-grf'
+ # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
+ of_match = {}
+
+ m_drivers = re.findall(r'U_BOOT_DRIVER\((.*)\)', buff)
+ if m_drivers:
+ driver_name = None
+
+ # Collect the uclass ID, e.g. 'UCLASS_SPI'
+ uclass_id = None
+ re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
+
+ # Collect the compatible string, e.g. 'rockchip,rk3288-grf'
+ compat = None
+ #re_compat = re.compile('{\s*.compatible\s*=\s*"(.*)"\s*},')
+
+ re_compat = re.compile('{\s*.compatible\s*=\s*"(.*)"\s*'
+ '(,\s*.data\s*=\s*(.*))?\s*},')
+
+ # This is a dict of compatible strings that were found:
+ # key: Compatible string, e.g. 'rockchip,rk3288-grf'
+ # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
+ compat_dict = {}
+
+ # Holds the var nane of the udevice_id list, e.g.
+ # 'rk3288_syscon_ids_noc' in
+ # static const struct udevice_id rk3288_syscon_ids_noc[] = {
+ ids_name = None
+ re_ids = re.compile('struct udevice_id (.*)\[\]\s*=')
+
+ # Matches the references to the udevice_id list
+ re_of_match = re.compile('\.of_match\s*=\s*([a-z0-9_]+),')
+
+ # Matches the header/size information for tinydev
+ re_tiny_priv = re.compile('^\s*DM_TINY_PRIV\((.*)\)$')
+ tiny_name = None
+
+ prefix = ''
+ for line in buff.splitlines():
+ # Handle line continuation
+ if prefix:
+ line = prefix + line
+ prefix = ''
+ if line.endswith('\\'):
+ prefix = line[:-1]
+ continue
- for driver in drivers:
- self._drivers.append(driver)
+ # If we have seen U_BOOT_DRIVER()...
+ if driver_name:
+ id_m = re_id.search(line)
+ id_of_match = re_of_match.search(line)
+ if id_m:
+ uclass_id = id_m.group(1)
+ elif id_of_match:
+ compat = id_of_match.group(1)
+ elif '};' in line:
+ if uclass_id and compat:
+ if compat not in of_match:
+ raise ValueError("%s: Unknown compatible var '%s' (found %s)" %
+ (fn, compat, ','.join(of_match.keys())))
+ driver = DriverInfo(driver_name, uclass_id,
+ of_match[compat])
+ drivers[driver_name] = driver
+
+ # This needs to be deterministic, since a driver may
+ # have multiple compatible strings pointing to it.
+ # We record the one earliest in the alphabet so it
+ # will produce the same result on all machines.
+ for id in of_match[compat]:
+ old = self._compat_to_driver.get(id)
+ if not old or driver.name < old.name:
+ self._compat_to_driver[id] = driver
+ else:
+ pass
+ #print("%s: Cannot find .id/.of_match in driver '%s': uclass_id=%s, compat=%s" %
+ #(fn, driver_name, uclass_id, compat))
+ driver_name = None
+ uclass_id = None
+ ids_name = None
+ compat = None
+ compat_dict = {}
+
+ # If we have seen U_BOOT_TINY_DRIVER()...
+ elif tiny_name:
+ tiny_priv = re_tiny_priv.match(line)
+ if tiny_priv:
+ drivers[tiny_name].priv_size = tiny_priv.group(1)
+ elif '};' in line:
+ tiny_name = None
+ elif ids_name:
+ compat_m = re_compat.search(line)
+ if compat_m:
+ compat_dict[compat_m.group(1)] = compat_m.group(3)
+ elif '};' in line:
+ of_match[ids_name] = compat_dict
+ ids_name = None
+ elif 'U_BOOT_DRIVER' in line:
+ match = re.search(r'U_BOOT_DRIVER\((.*)\)', line)
+ if match:
+ driver_name = match.group(1)
+ elif 'U_BOOT_TINY_DRIVER' in line:
+ match = re.search(r'U_BOOT_TINY_DRIVER\((.*)\)', line)
+ if match:
+ tiny_name = match.group(1)
+ if tiny_name not in drivers:
+ raise ValueError("%s: Tiny driver '%s' must have a corresponding full driver in the same file (found %s)" %
+ (fn, tiny_name, drivers))
+ else:
+ ids_m = re_ids.search(line)
+ if ids_m:
+ ids_name = ids_m.group(1)
- # The following re will search for driver aliases declared as
- # U_BOOT_DRIVER_ALIAS(alias, driver_name)
- driver_aliases = re.findall('U_BOOT_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
- buff)
+ self._drivers.update(drivers)
+ self._of_match.update(of_match)
- for alias in driver_aliases: # pragma: no cover
- if len(alias) != 2:
- continue
- self._driver_aliases[alias[1]] = alias[0]
+ # The following re will search for driver aliases declared as
+ # U_BOOT_DRIVER_ALIAS(alias, driver_name)
+ driver_aliases = re.findall(
+ 'U_BOOT_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
+ buff)
- def scan_drivers(self):
+ for alias in driver_aliases: # pragma: no cover
+ if len(alias) != 2:
+ continue
+ self._driver_aliases[alias[1]] = alias[0]
+
+ def scan_drivers(self, srcpath):
"""Scan the driver folders to build a list of driver names and aliases
This procedure will populate self._drivers and self._driver_aliases
"""
- basedir = sys.argv[0].replace('tools/dtoc/dtoc', '')
- if basedir == '':
- basedir = './'
- for (dirpath, dirnames, filenames) in os.walk(basedir):
+ for (dirpath, dirnames, filenames) in os.walk(srcpath):
for fn in filenames:
if not fn.endswith('.c'):
continue
@@ -355,23 +496,30 @@ class DtbPlatdata(object):
"""
self._fdt = fdt.FdtScan(self._dtb_fname)
- def scan_node(self, root):
+ def scan_node(self, parent, level):
"""Scan a node and subnodes to build a tree of node and phandle info
This adds each node to self._valid_nodes.
Args:
- root: Root node for scan
+ parent: Parent node for scan
"""
- for node in root.subnodes:
+ for node in parent.subnodes:
if 'compatible' in node.props:
status = node.props.get('status')
if (not self._include_disabled and not status or
status.value != 'disabled'):
self._valid_nodes.append(node)
+ if level == 0 and node.name == 'aliases':
+ for prop in node.props.values():
+ self._aliases[prop.name] = prop.value
+ match = re.match('^(.*[a-z])[0-9]+', prop.name)
+ if match:
+ self._aliases_by_path[prop.value] = match.group(1)
+
# recurse to handle any subnodes
- self.scan_node(node)
+ self.scan_node(node, level + 1)
def scan_tree(self):
"""Scan the device tree for useful information
@@ -381,7 +529,26 @@ class DtbPlatdata(object):
platform data
"""
self._valid_nodes = []
- return self.scan_node(self._fdt.GetRoot())
+ self.scan_node(self._fdt.GetRoot(), 0)
+
+ def parse_config(self, config_data):
+ tiny_list = re.findall(r'CONFIG_[ST]PL_TINY_(.*)=y', config_data)
+ self._tiny_uclasses = [n.lower() for n in tiny_list
+ if n not in ['MEMSET', 'RELOC', 'ONLY']]
+
+ def scan_config(self):
+ if self._config_fname:
+ self.parse_config(tools.ReadFile(self._config_fname, binary=False))
+ unused = set(self._tiny_uclasses)
+ for node in self._valid_nodes:
+ node.is_tiny = False
+ alias = self._aliases_by_path.get(node.path)
+ if alias and alias in self._tiny_uclasses:
+ node.is_tiny = True
+ unused.discard(alias)
+ if unused:
+ print('Warning: Some tiny uclasses lack aliases or a device: %s' %
+ ', '.join(unused))
@staticmethod
def get_num_cells(node):
@@ -491,7 +658,7 @@ class DtbPlatdata(object):
struct_name, aliases = self.get_normalized_compat_name(node)
for alias in aliases:
- self._aliases[alias] = struct_name
+ self._compat_aliases[alias] = struct_name
return structs
@@ -555,7 +722,7 @@ class DtbPlatdata(object):
self.out(';\n')
self.out('};\n')
- for alias, struct_name in self._aliases.items():
+ for alias, struct_name in self._compat_aliases.items():
if alias not in sorted(structs):
self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
STRUCT_PREFIX, struct_name))
@@ -600,7 +767,8 @@ class DtbPlatdata(object):
(VAL_PREFIX, var_name, member_name, item)
# Save the the link information to be use to define
# dm_populate_phandle_data()
- self._links.append({'var_node': var_node, 'dev_name': name})
+ if not target_node.is_tiny:
+ self._links.append({'var_node': var_node, 'dev_name': name})
item += 1
for val in vals:
self.buf('\n\t\t%s,' % val)
@@ -617,6 +785,8 @@ class DtbPlatdata(object):
struct_name, _ = self.get_normalized_compat_name(node)
var_name = conv_name_to_c(node.name)
+
+ # Tiny devices don't have 'static' since it is used by the driver
self.buf('static struct %s%s %s%s = {\n' %
(STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
for pname in sorted(node.props):
@@ -634,13 +804,59 @@ class DtbPlatdata(object):
self.buf(',\n')
self.buf('};\n')
- # Add a device declaration
- self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
- self.buf('\t.name\t\t= "%s",\n' % struct_name)
- self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
- self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
- self.buf('};\n')
- self.buf('\n')
+ if node.is_tiny:
+ val = node.props['compatible'].value
+ if not isinstance(val, list):
+ val = [val]
+ for compat in val:
+ driver = self._compat_to_driver.get(compat)
+ if driver:
+ break
+ if not driver:
+ raise ValueError("Cant' find driver for compatible '%s' (%s)'" %
+ (', '.join(val), 'all'))
+ self.buf('DM_DECL_TINY_DRIVER(%s);\n' % driver.name);
+ priv_name = None
+ inline = True
+ if inline and driver.priv_size:
+ parts = driver.priv_size.split(',')
+ if len(parts) == 2:
+ hdr, size = parts
+ else:
+ hdr = None
+ size = parts[0]
+ priv_name = '_%s_priv' % var_name
+ if hdr:
+ self.buf('#include %s\n' % hdr)
+ section = '__attribute__ ((section (".data")))'
+
+ self.buf('u8 %s[%s] %s;\n' % (priv_name, size.strip(), section))
+
+ self.buf('U_BOOT_TINY_DEVICE(%s) = {\n' % var_name)
+ self.buf('\t.dtplat\t\t= &%s%s,\n' % (VAL_PREFIX, var_name))
+ self.buf('\t.drv\t\t= DM_REF_TINY_DRIVER(%s),\n' % driver.name)
+ driver_data = driver.compat[compat]
+ if driver_data is not None:
+ self.buf('\t.driver_data\t\t= %s,\n' % driver_data)
+ if priv_name:
+ self.buf('\t.priv\t\t= %s,\n' % priv_name)
+ self.buf('\t.name\t\t= "%s",\n' % node.name)
+ if node.parent and node.parent.name != '/':
+ parent_name = conv_name_to_c(node.parent.name)
+ self.buf('\t.parent\t\t= DM_REF_TINY_DEVICE(%s),\n' %
+ parent_name)
+ self.buf('};\n')
+ self.buf('\n')
+ else:
+ # Add a device declaration
+ self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
+ self.buf('\t.name\t\t= "%s",\n' % struct_name)
+ self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
+ self.buf('\t.platdata_size\t= sizeof(%s%s),\n' %
+ (VAL_PREFIX, var_name))
+ self.buf('\t.dev\t\t= NULL,\n')
+ self.buf('};\n')
+ self.buf('\n')
self.out(''.join(self.get_buf()))
@@ -659,6 +875,15 @@ class DtbPlatdata(object):
self.out('#include <dm.h>\n')
self.out('#include <dt-structs.h>\n')
self.out('\n')
+
+ self.out('/*\n')
+ self.out(' * Tiny uclasses: %s\n' % (', '.join(self._tiny_uclasses)))
+ self.out(' * Aliases with CONFIG_SPL_TINY_... enabled\n')
+ for path, alias in self._aliases_by_path.items():
+ if alias in self._tiny_uclasses:
+ self.out(' * %s: %s\n' % (path, alias))
+ self.out('*/\n')
+ self.out('\n')
nodes_to_output = list(self._valid_nodes)
# Keep outputing nodes until there is none left
@@ -682,8 +907,10 @@ class DtbPlatdata(object):
self.buf('}\n')
self.out(''.join(self.get_buf()))
+ self.close_output()
-def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False):
+def run_steps(args, dtb_file, config_file, include_disabled, output, srcpath,
+ warning_disabled=False):
"""Run all the steps of the dtoc tool
Args:
@@ -697,10 +924,12 @@ def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False):
if not args:
raise ValueError('Please specify a command: struct, platdata')
- plat = DtbPlatdata(dtb_file, include_disabled, warning_disabled)
- plat.scan_drivers()
+ plat = DtbPlatdata(dtb_file, config_file, include_disabled,
+ warning_disabled)
+ plat.scan_drivers(srcpath)
plat.scan_dtb()
plat.scan_tree()
+ plat.scan_config()
plat.scan_reg_sizes()
plat.setup_output(output)
structs = plat.scan_structs()
@@ -714,3 +943,4 @@ def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False):
else:
raise ValueError("Unknown command '%s': (use: struct, platdata)" %
cmd)
+ return plat
diff --git a/tools/dtoc/dtoc_test_simple.dts b/tools/dtoc/dtoc_test_simple.dts
index 165680bd4b..43d347bad3 100644
--- a/tools/dtoc/dtoc_test_simple.dts
+++ b/tools/dtoc/dtoc_test_simple.dts
@@ -10,6 +10,12 @@
/ {
#address-cells = <1>;
#size-cells = <1>;
+
+ aliases {
+ i2c0 = &i2c0;
+ serial0 = &serial0;
+ };
+
spl-test {
u-boot,dm-pre-reloc;
compatible = "sandbox,spl-test";
@@ -47,7 +53,7 @@
compatible = "sandbox,spl-test.2";
};
- i2c at 0 {
+ i2c0: i2c at 0 {
compatible = "sandbox,i2c-test";
u-boot,dm-pre-reloc;
#address-cells = <1>;
@@ -59,4 +65,8 @@
low-power;
};
};
+
+ serial0: serial {
+ compatible = "sandbox,serial";
+ };
};
diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py
index f78b7f84f0..3d4bc3b2ef 100644
--- a/tools/dtoc/fdt.py
+++ b/tools/dtoc/fdt.py
@@ -60,9 +60,9 @@ def BytesToValue(data):
Type of data
Data, either a single element or a list of elements. Each element
is one of:
- Type.STRING: str/bytes value from the property
- Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
- Type.BYTE: a byte stored as a single-byte str/bytes
+ Type.STRING: str value from the property
+ Type.INT: a big-endian integer stored as a 4-byte bytes
+ Type.BYTE: a byte stored as a single-byte bytes
"""
data = bytes(data)
size = len(data)
@@ -104,6 +104,7 @@ class Prop:
Properties:
name: Property name (as per the device tree)
+ bytes: Property value as raw bytes
value: Property value as a string of bytes, or a list of strings of
bytes
type: Value type
diff --git a/tools/dtoc/main.py b/tools/dtoc/main.py
index b94d9c301f..2fa3e26cf8 100755
--- a/tools/dtoc/main.py
+++ b/tools/dtoc/main.py
@@ -87,6 +87,8 @@ if __name__ != '__main__':
parser = OptionParser()
parser.add_option('-B', '--build-dir', type='string', default='b',
help='Directory containing the build output')
+parser.add_option('-c', '--config', action='store',
+ help='Select .config filename')
parser.add_option('-d', '--dtb-file', action='store',
help='Specify the .dtb input file')
parser.add_option('--include-disabled', action='store_true',
@@ -95,6 +97,8 @@ parser.add_option('-o', '--output', action='store', default='-',
help='Select output filename')
parser.add_option('-P', '--processes', type=int,
help='set number of processes to use for running tests')
+parser.add_option('-s', '--srcpath', type='string',
+ help='Specify the source directory for U-Boot')
parser.add_option('-t', '--test', action='store_true', dest='test',
default=False, help='run tests')
parser.add_option('-T', '--test-coverage', action='store_true',
@@ -110,5 +114,6 @@ elif options.test_coverage:
RunTestCoverage()
else:
- dtb_platdata.run_steps(args, options.dtb_file, options.include_disabled,
- options.output)
+ dtb_platdata.run_steps(args, options.dtb_file, options.config,
+ options.include_disabled, options.output,
+ options.srcpath)
diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py
index 7afab2ef66..c730ecdb1a 100755
--- a/tools/dtoc/test_dtoc.py
+++ b/tools/dtoc/test_dtoc.py
@@ -11,12 +11,14 @@ tool.
import collections
import os
+import re
import struct
import sys
import unittest
from dtoc import dtb_platdata
from dtb_platdata import conv_name_to_c
+from dtb_platdata import DriverInfo
from dtb_platdata import get_compat_name
from dtb_platdata import get_value
from dtb_platdata import tab_to
@@ -52,6 +54,38 @@ C_EMPTY_POPULATE_PHANDLE_DATA = '''void dm_populate_phandle_data(void) {
}
'''
+CONFIG_FILE_DATA = '''
+CONFIG_SOMETHING=1234
+CONFIG_SPL_TINY_SPI=y
+# CONFIG_SPL_TINY_SPI is not set
+CONFIG_SPL_TINY_I2C=y
+CONFIG_SOMETHING_ELSE=5678
+'''
+
+DRIVER_FILE_DATA = '''
+static const struct udevice_id xhci_usb_ids[] = {
+ { .compatible = "rockchip,rk3328-xhci" },
+ { }
+};
+
+U_BOOT_DRIVER(usb_xhci) = {
+ .name = "xhci_rockchip",
+ .id = UCLASS_USB,
+ .of_match = xhci_usb_ids,
+ .ops = &xhci_usb_ops,
+};
+
+static const struct udevice_id usb_phy_ids[] = {
+ { .compatible = "rockchip,rk3328-usb3-phy" },
+ { }
+};
+
+U_BOOT_DRIVER(usb_phy) = {
+ .name = "usb_phy_rockchip",
+ .id = UCLASS_PHY,
+ .of_match = usb_phy_ids,
+};
+'''
def get_dtb_file(dts_fname, capture_stderr=False):
"""Compile a .dts file to a .dtb
@@ -75,7 +109,8 @@ class TestDtoc(unittest.TestCase):
@classmethod
def tearDownClass(cls):
- tools._RemoveOutputDir()
+ #tools._RemoveOutputDir()
+ pass
def _WritePythonString(self, fname, data):
"""Write a string with tabs expanded as done in this Python file
@@ -860,3 +895,57 @@ U_BOOT_DEVICE(spl_test2) = {
self.run_test(['invalid-cmd'], dtb_file, output)
self.assertIn("Unknown command 'invalid-cmd': (use: struct, platdata)",
str(e.exception))
+
+ def testAliases(self):
+ """Test we can get a list of aliases"""
+ dtb_file = get_dtb_file('dtoc_test_simple.dts')
+ plat = dtb_platdata.DtbPlatdata(dtb_file, False)
+ plat.scan_dtb()
+ plat.scan_tree()
+ self.assertEqual({'i2c0': '/i2c at 0'}, plat._aliases)
+
+ def testReadConfig(self):
+ """Test we can get a list of 'tiny' uclasses"""
+ plat = dtb_platdata.DtbPlatdata(None, None, None)
+ plat.parse_config(CONFIG_FILE_DATA)
+ self.assertEqual(['serial', 'i2c'], plat._tiny_uclasses)
+
+ def do_platdata_run(self):
+ dtb_file = get_dtb_file('dtoc_test_simple.dts')
+ output = tools.GetOutputFilename('output')
+ config_file = tools.GetOutputFilename('config')
+ tools.WriteFile(config_file, CONFIG_FILE_DATA, binary=False)
+ plat = dtb_platdata.run_steps(['platdata'], dtb_file, config_file,
+ False, output)
+ return plat, output
+
+ def testDetectTiny(self):
+ """Test we detect devices that need to be 'tiny'"""
+ plat, _ = self.do_platdata_run()
+ tiny_nodes = [node.name for node in plat._valid_nodes if node.is_tiny]
+ self.assertEqual(['i2c at 0'], tiny_nodes)
+
+ def testTinyNoStruct(self):
+ """Test we don't output U_BOOT_DEVICE for 'tiny' devices"""
+ plat, output = self.do_platdata_run()
+ data = tools.ReadFile(output)
+ dev_list = re.findall(b'U_BOOT_DEVICE\((.*)\)', data)
+ self.assertNotIn(b'i2c_at_0', dev_list)
+
+ # Find dtd declarations with 'static' in them
+ #dtv_list = re.findall(b'(.*)struct dtd.* dtv_(.*) =', data)
+ #has_static = [b for a, b in dtv_list if a == 'static ']
+ #self.assertNotIn(b'i2c_at_0', has_static)
+
+ def testTinyUclass(self):
+ plat = dtb_platdata.DtbPlatdata(None, None, None)
+ driver_file = tools.GetOutputFilename('driver.c')
+ tools.WriteFile(driver_file, DRIVER_FILE_DATA, binary=False)
+ plat.scan_driver(driver_file)
+ self.assertEqual(['usb_phy', 'usb_xhci'], sorted(plat._drivers.keys()))
+ self.assertEqual(DriverInfo(name='usb_phy', uclass_id='UCLASS_PHY',
+ compat=['rockchip,rk3328-usb3-phy']),
+ plat._drivers['usb_phy'])
+ self.assertEqual(DriverInfo(name='usb_xhci', uclass_id='UCLASS_USB',
+ compat=['rockchip,rk3328-xhci']),
+ plat._drivers['usb_xhci'])
diff --git a/tools/patman/tools.py b/tools/patman/tools.py
index e1977a2ff3..c81dfabfe2 100644
--- a/tools/patman/tools.py
+++ b/tools/patman/tools.py
@@ -270,7 +270,7 @@ def ReadFile(fname, binary=True):
#(fname, len(data), len(data)))
return data
-def WriteFile(fname, data):
+def WriteFile(fname, data, binary=True):
"""Write data into a file.
Args:
@@ -279,7 +279,7 @@ def WriteFile(fname, data):
"""
#self._out.Info("Write file '%s' size %d (%#0x)" %
#(fname, len(data), len(data)))
- with open(Filename(fname), 'wb') as fd:
+ with open(Filename(fname), binary and 'wb' or 'w') as fd:
fd.write(data)
def GetBytes(byte, size):
--
2.27.0.212.ge8ba1cc988-goog
More information about the U-Boot
mailing list