From patchwork Thu Jul 2 21:10:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 240665 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Thu, 2 Jul 2020 15:10:04 -0600 Subject: [RFC PATCH v2 3/3] dm: Core changes for tiny-dm In-Reply-To: <20200702211004.1491489-1-sjg@chromium.org> References: <20200702211004.1491489-1-sjg@chromium.org> Message-ID: <20200702211004.1491489-4-sjg@chromium.org> This patch includes changes to support tiny-dm in driver model and dtoc. Signed-off-by: Simon Glass --- 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 : ""; } @@ -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 +#include #include #include #include #include #include #include +#include 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(, 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 + 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 +.. 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 #include +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 +#include +#include +#include + +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 +#include #include #include #include @@ -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 + */ + +#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 \n') self.out('#include \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):