diff options
author | Simon Glass <sjg@chromium.org> | 2021-03-18 20:25:12 +1300 |
---|---|---|
committer | Simon Glass <sjg@chromium.org> | 2021-03-26 17:03:10 +1300 |
commit | 5ce319133b2364e3283c3cde7a269681ff8431af (patch) | |
tree | 2badbcc08d4e19d9c1319deac066c54559e919c8 /doc/driver-model/of-plat.rst | |
parent | d1ceeeff6c2ee1e55b7140654c8d6de44b60dab6 (diff) | |
download | u-boot-5ce319133b2364e3283c3cde7a269681ff8431af.zip u-boot-5ce319133b2364e3283c3cde7a269681ff8431af.tar.gz u-boot-5ce319133b2364e3283c3cde7a269681ff8431af.tar.bz2 |
doc: Move driver model docs under develop/
These docs are useful for developers, not users. Move them under that
section.
Suggested-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
Signed-off-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'doc/driver-model/of-plat.rst')
-rw-r--r-- | doc/driver-model/of-plat.rst | 913 |
1 files changed, 0 insertions, 913 deletions
diff --git a/doc/driver-model/of-plat.rst b/doc/driver-model/of-plat.rst deleted file mode 100644 index 74f1932..0000000 --- a/doc/driver-model/of-plat.rst +++ /dev/null @@ -1,913 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0+ - -Compiled-in Device Tree / Platform Data -======================================= - - -Introduction ------------- - -Device tree is the standard configuration method in U-Boot. It is used to -define what devices are in the system and provide configuration information -to these devices. - -The overhead of adding devicetree access to U-Boot is fairly modest, -approximately 3KB on Thumb 2 (plus the size of the DT itself). This means -that in most cases it is best to use devicetree for configuration. - -However there are some very constrained environments where U-Boot needs to -work. These include SPL with severe memory limitations. For example, some -SoCs require a 16KB SPL image which must include a full MMC stack. In this -case the overhead of devicetree access may be too great. - -It is possible to create platform data manually by defining C structures -for it, and reference that data in a `U_BOOT_DRVINFO()` declaration. This -bypasses the use of devicetree completely, effectively creating a parallel -configuration mechanism. But it is an available option for SPL. - -As an alternative, the 'of-platdata' feature is provided. This converts the -devicetree contents into C code which can be compiled into the SPL binary. -This saves the 3KB of code overhead and perhaps a few hundred more bytes due -to more efficient storage of the data. - - -How it works ------------- - -The feature is enabled by CONFIG OF_PLATDATA. This is only available in -SPL/TPL and should be tested with: - -.. code-block:: c - - #if CONFIG_IS_ENABLED(OF_PLATDATA) - -A tool called 'dtoc' converts a devicetree file either into a set of -struct declarations, one for each compatible node, and a set of -`U_BOOT_DRVINFO()` declarations along with the actual platform data for each -device. As an example, consider this MMC node: - -.. code-block:: none - - sdmmc: dwmmc@ff0c0000 { - compatible = "rockchip,rk3288-dw-mshc"; - clock-freq-min-max = <400000 150000000>; - clocks = <&cru HCLK_SDMMC>, <&cru SCLK_SDMMC>, - <&cru SCLK_SDMMC_DRV>, <&cru SCLK_SDMMC_SAMPLE>; - clock-names = "biu", "ciu", "ciu_drv", "ciu_sample"; - fifo-depth = <0x100>; - interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; - reg = <0xff0c0000 0x4000>; - bus-width = <4>; - cap-mmc-highspeed; - cap-sd-highspeed; - card-detect-delay = <200>; - disable-wp; - num-slots = <1>; - pinctrl-names = "default"; - pinctrl-0 = <&sdmmc_clk>, <&sdmmc_cmd>, <&sdmmc_cd>, <&sdmmc_bus4>; - vmmc-supply = <&vcc_sd>; - status = "okay"; - u-boot,dm-pre-reloc; - }; - - -Some of these properties are dropped by U-Boot under control of the -CONFIG_OF_SPL_REMOVE_PROPS option. The rest are processed. This will produce -the following C struct declaration: - -.. code-block:: c - - struct dtd_rockchip_rk3288_dw_mshc { - fdt32_t bus_width; - bool cap_mmc_highspeed; - bool cap_sd_highspeed; - fdt32_t card_detect_delay; - fdt32_t clock_freq_min_max[2]; - struct phandle_1_arg clocks[4]; - bool disable_wp; - fdt32_t fifo_depth; - fdt32_t interrupts[3]; - fdt32_t num_slots; - fdt32_t reg[2]; - fdt32_t vmmc_supply; - }; - -and the following device declarations: - -.. code-block:: c - - /* Node /clock-controller@ff760000 index 0 */ - ... - - /* Node /dwmmc@ff0c0000 index 2 */ - static struct dtd_rockchip_rk3288_dw_mshc dtv_dwmmc_at_ff0c0000 = { - .fifo_depth = 0x100, - .cap_sd_highspeed = true, - .interrupts = {0x0, 0x20, 0x4}, - .clock_freq_min_max = {0x61a80, 0x8f0d180}, - .vmmc_supply = 0xb, - .num_slots = 0x1, - .clocks = {{0, 456}, - {0, 68}, - {0, 114}, - {0, 118}}, - .cap_mmc_highspeed = true, - .disable_wp = true, - .bus_width = 0x4, - .u_boot_dm_pre_reloc = true, - .reg = {0xff0c0000, 0x4000}, - .card_detect_delay = 0xc8, - }; - - U_BOOT_DRVINFO(dwmmc_at_ff0c0000) = { - .name = "rockchip_rk3288_dw_mshc", - .plat = &dtv_dwmmc_at_ff0c0000, - .plat_size = sizeof(dtv_dwmmc_at_ff0c0000), - .parent_idx = -1, - }; - -The device is then instantiated at run-time and the platform data can be -accessed using: - -.. code-block:: c - - struct udevice *dev; - struct dtd_rockchip_rk3288_dw_mshc *plat = dev_get_plat(dev); - -This avoids the code overhead of converting the devicetree data to -platform data in the driver. The `of_to_plat()` method should -therefore do nothing in such a driver. - -Note that for the platform data to be matched with a driver, the 'name' -property of the `U_BOOT_DRVINFO()` declaration has to match a driver declared -via `U_BOOT_DRIVER()`. This effectively means that a `U_BOOT_DRIVER()` with a -'name' corresponding to the devicetree 'compatible' string (after converting -it to a valid name for C) is needed, so a dedicated driver is required for -each 'compatible' string. - -In order to make this a bit more flexible, the `DM_DRIVER_ALIAS()` macro can be -used to declare an alias for a driver name, typically a 'compatible' string. -This macro produces no code, but is used by dtoc tool. It must be located in the -same file as its associated driver, ideally just after it. - -The parent_idx is the index of the parent `driver_info` structure within its -linker list (instantiated by the `U_BOOT_DRVINFO()` macro). This is used to -support `dev_get_parent()`. - -During the build process dtoc parses both `U_BOOT_DRIVER()` and -`DM_DRIVER_ALIAS()` to build a list of valid driver names and driver aliases. -If the 'compatible' string used for a device does not not match a valid driver -name, it will be checked against the list of driver aliases in order to get the -right driver name to use. If in this step there is no match found a warning is -issued to avoid run-time failures. - -Where a node has multiple compatible strings, dtoc generates a `#define` to -make them equivalent, e.g.: - -.. code-block:: c - - #define dtd_rockchip_rk3299_dw_mshc dtd_rockchip_rk3288_dw_mshc - - -Converting of-platdata to a useful form ---------------------------------------- - -Of course it would be possible to use the of-platdata directly in your driver -whenever configuration information is required. However this means that the -driver will not be able to support devicetree, since the of-platdata -structure is not available when devicetree is used. It would make no sense -to use this structure if devicetree were available, since the structure has -all the limitations metioned in caveats below. - -Therefore it is recommended that the of-platdata structure should be used -only in the `probe()` method of your driver. It cannot be used in the -`of_to_plat()` method since this is not called when platform data is -already present. - - -How to structure your driver ----------------------------- - -Drivers should always support devicetree as an option. The of-platdata -feature is intended as a add-on to existing drivers. - -Your driver should convert the plat struct in its `probe()` method. The -existing devicetree decoding logic should be kept in the -`of_to_plat()` method and wrapped with `#if`. - -For example: - -.. code-block:: c - - #include <dt-structs.h> - - struct mmc_plat { - #if CONFIG_IS_ENABLED(OF_PLATDATA) - /* Put this first since driver model will copy the data here */ - struct dtd_mmc dtplat; - #endif - /* - * Other fields can go here, to be filled in by decoding from - * the devicetree (or the C structures when of-platdata is used). - */ - int fifo_depth; - }; - - static int mmc_of_to_plat(struct udevice *dev) - { - #if !CONFIG_IS_ENABLED(OF_PLATDATA) - /* Decode the devicetree data */ - struct mmc_plat *plat = dev_get_plat(dev); - const void *blob = gd->fdt_blob; - int node = dev_of_offset(dev); - - plat->fifo_depth = fdtdec_get_int(blob, node, "fifo-depth", 0); - #endif - - return 0; - } - - static int mmc_probe(struct udevice *dev) - { - struct mmc_plat *plat = dev_get_plat(dev); - - #if CONFIG_IS_ENABLED(OF_PLATDATA) - /* Decode the of-platdata from the C structures */ - struct dtd_mmc *dtplat = &plat->dtplat; - - plat->fifo_depth = dtplat->fifo_depth; - #endif - /* Set up the device from the plat data */ - writel(plat->fifo_depth, ...) - } - - static const struct udevice_id mmc_ids[] = { - { .compatible = "vendor,mmc" }, - { } - }; - - U_BOOT_DRIVER(mmc_drv) = { - .name = "mmc_drv", - .id = UCLASS_MMC, - .of_match = mmc_ids, - .of_to_plat = mmc_of_to_plat, - .probe = mmc_probe, - .priv_auto = sizeof(struct mmc_priv), - .plat_auto = sizeof(struct mmc_plat), - }; - - DM_DRIVER_ALIAS(mmc_drv, vendor_mmc) /* matches compatible string */ - -Note that `struct mmc_plat` is defined in the C file, not in a header. This -is to avoid needing to include dt-structs.h in a header file. The idea is to -keep the use of each of-platdata struct to the smallest possible code area. -There is just one driver C file for each struct, that can convert from the -of-platdata struct to the standard one used by the driver. - -In the case where SPL_OF_PLATDATA is enabled, `plat_auto` is -still used to allocate space for the platform data. This is different from -the normal behaviour and is triggered by the use of of-platdata (strictly -speaking it is a non-zero `plat_size` which triggers this). - -The of-platdata struct contents is copied from the C structure data to the -start of the newly allocated area. In the case where devicetree is used, -the platform data is allocated, and starts zeroed. In this case the -`of_to_plat()` method should still set up the platform data (and the -of-platdata struct will not be present). - -SPL must use either of-platdata or devicetree. Drivers cannot use both at -the same time, but they must support devicetree. Supporting of-platdata is -optional. - -The devicetree becomes inaccessible when CONFIG_SPL_OF_PLATDATA is enabled, -since the devicetree access code is not compiled in. A corollary is that -a board can only move to using of-platdata if all the drivers it uses support -it. There would be little point in having some drivers require the device -tree data, since then libfdt would still be needed for those drivers and -there would be no code-size benefit. - - -Build-time instantiation ------------------------- - -Even with of-platdata there is a fair amount of code required in driver model. -It is possible to have U-Boot handle the instantiation of devices at build-time, -so avoiding the need for the `device_bind()` code and some parts of -`device_probe()`. - -The feature is enabled by CONFIG_OF_PLATDATA_INST. - -Here is an example device, as generated by dtoc:: - - /* - * Node /serial index 6 - * driver sandbox_serial parent root_driver - */ - - #include <asm/serial.h> - struct sandbox_serial_plat __attribute__ ((section (".priv_data"))) - _sandbox_serial_plat_serial = { - .dtplat = { - .sandbox_text_colour = "cyan", - }, - }; - #include <asm/serial.h> - u8 _sandbox_serial_priv_serial[sizeof(struct sandbox_serial_priv)] - __attribute__ ((section (".priv_data"))); - #include <serial.h> - u8 _sandbox_serial_uc_priv_serial[sizeof(struct serial_dev_priv)] - __attribute__ ((section (".priv_data"))); - - DM_DEVICE_INST(serial) = { - .driver = DM_DRIVER_REF(sandbox_serial), - .name = "sandbox_serial", - .plat_ = &_sandbox_serial_plat_serial, - .priv_ = _sandbox_serial_priv_serial, - .uclass = DM_UCLASS_REF(serial), - .uclass_priv_ = _sandbox_serial_uc_priv_serial, - .uclass_node = { - .prev = &DM_UCLASS_REF(serial)->dev_head, - .next = &DM_UCLASS_REF(serial)->dev_head, - }, - .child_head = { - .prev = &DM_DEVICE_REF(serial)->child_head, - .next = &DM_DEVICE_REF(serial)->child_head, - }, - .sibling_node = { - .prev = &DM_DEVICE_REF(i2c_at_0)->sibling_node, - .next = &DM_DEVICE_REF(spl_test)->sibling_node, - }, - .seq_ = 0, - }; - -Here is part of the driver, for reference:: - - static const struct udevice_id sandbox_serial_ids[] = { - { .compatible = "sandbox,serial" }, - { } - }; - - U_BOOT_DRIVER(sandbox_serial) = { - .name = "sandbox_serial", - .id = UCLASS_SERIAL, - .of_match = sandbox_serial_ids, - .of_to_plat = sandbox_serial_of_to_plat, - .plat_auto = sizeof(struct sandbox_serial_plat), - .priv_auto = sizeof(struct sandbox_serial_priv), - .probe = sandbox_serial_probe, - .remove = sandbox_serial_remove, - .ops = &sandbox_serial_ops, - .flags = DM_FLAG_PRE_RELOC, - }; - - -The `DM_DEVICE_INST()` macro declares a struct udevice so you can see that the -members are from that struct. The private data is declared immediately above, -as `_sandbox_serial_priv_serial`, so there is no need for run-time memory -allocation. The #include lines are generated as well, since dtoc searches the -U-Boot source code for the definition of `struct sandbox_serial_priv` and adds -the relevant header so that the code will compile without errors. - -The `plat_` member is set to the dtv data which is declared immediately above -the device. This is similar to how it would look without of-platdata-inst, but -node that the `dtplat` member inside is part of the wider -`_sandbox_serial_plat_serial` struct. This is because the driver declares its -own platform data, and the part generated by dtoc can only be a portion of it. -The `dtplat` part is always first in the struct. If the device has no -`.plat_auto` field, then a simple dtv struct can be used as with this example:: - - static struct dtd_sandbox_clk dtv_clk_sbox = { - .assigned_clock_rates = 0x141, - .assigned_clocks = {0x7, 0x3}, - }; - - #include <asm/clk.h> - u8 _sandbox_clk_priv_clk_sbox[sizeof(struct sandbox_clk_priv)] - __attribute__ ((section (".priv_data"))); - - DM_DEVICE_INST(clk_sbox) = { - .driver = DM_DRIVER_REF(sandbox_clk), - .name = "sandbox_clk", - .plat_ = &dtv_clk_sbox, - -Here is part of the driver, for reference:: - - static const struct udevice_id sandbox_clk_ids[] = { - { .compatible = "sandbox,clk" }, - { } - }; - - U_BOOT_DRIVER(sandbox_clk) = { - .name = "sandbox_clk", - .id = UCLASS_CLK, - .of_match = sandbox_clk_ids, - .ops = &sandbox_clk_ops, - .probe = sandbox_clk_probe, - .priv_auto = sizeof(struct sandbox_clk_priv), - }; - - -You can see that `dtv_clk_sbox` just has the devicetree contents and there is -no need for the `dtplat` separation, since the driver has no platform data of -its own, besides that provided by the devicetree (i.e. no `.plat_auto` field). - -The doubly linked lists are handled by explicitly declaring the value of each -node, as you can see with the `.prev` and `.next` values in the example above. -Since dtoc knows the order of devices it can link them into the appropriate -lists correctly. - -One of the features of driver model is the ability for a uclass to have a -small amount of private data for each device in that uclass. This is used to -provide a generic data structure that the uclass can use for all devices, thus -allowing generic features to be implemented in common code. An example is I2C, -which stores the bus speed there. - -Similarly, parent devices can have data associated with each of their children. -This is used to provide information common to all children of a particular bus. -For an I2C bus, this is used to store the I2C address of each child on the bus. - -This is all handled automatically by dtoc:: - - #include <asm/i2c.h> - u8 _sandbox_i2c_priv_i2c_at_0[sizeof(struct sandbox_i2c_priv)] - __attribute__ ((section (".priv_data"))); - #include <i2c.h> - u8 _sandbox_i2c_uc_priv_i2c_at_0[sizeof(struct dm_i2c_bus)] - __attribute__ ((section (".priv_data"))); - - DM_DEVICE_INST(i2c_at_0) = { - .driver = DM_DRIVER_REF(sandbox_i2c), - .name = "sandbox_i2c", - .plat_ = &dtv_i2c_at_0, - .priv_ = _sandbox_i2c_priv_i2c_at_0, - .uclass = DM_UCLASS_REF(i2c), - .uclass_priv_ = _sandbox_i2c_uc_priv_i2c_at_0, - ... - -Part of driver, for reference:: - - static const struct udevice_id sandbox_i2c_ids[] = { - { .compatible = "sandbox,i2c" }, - { } - }; - - U_BOOT_DRIVER(sandbox_i2c) = { - .name = "sandbox_i2c", - .id = UCLASS_I2C, - .of_match = sandbox_i2c_ids, - .ops = &sandbox_i2c_ops, - .priv_auto = sizeof(struct sandbox_i2c_priv), - }; - -Part of I2C uclass, for reference:: - - UCLASS_DRIVER(i2c) = { - .id = UCLASS_I2C, - .name = "i2c", - .flags = DM_UC_FLAG_SEQ_ALIAS, - .post_bind = i2c_post_bind, - .pre_probe = i2c_pre_probe, - .post_probe = i2c_post_probe, - .per_device_auto = sizeof(struct dm_i2c_bus), - .per_child_plat_auto = sizeof(struct dm_i2c_chip), - .child_post_bind = i2c_child_post_bind, - }; - -Here, `_sandbox_i2c_uc_priv_i2c_at_0` is required by the uclass but is declared -in the device, as required by driver model. The required header file is included -so that the code will compile without errors. A similar mechanism is used for -child devices, but is not shown by this example. - -It would not be that useful to avoid binding devices but still need to allocate -uclasses at runtime. So dtoc generates uclass instances as well:: - - struct list_head uclass_head = { - .prev = &DM_UCLASS_REF(serial)->sibling_node, - .next = &DM_UCLASS_REF(clk)->sibling_node, - }; - - DM_UCLASS_INST(clk) = { - .uc_drv = DM_UCLASS_DRIVER_REF(clk), - .sibling_node = { - .prev = &uclass_head, - .next = &DM_UCLASS_REF(i2c)->sibling_node, - }, - .dev_head = { - .prev = &DM_DEVICE_REF(clk_sbox)->uclass_node, - .next = &DM_DEVICE_REF(clk_fixed)->uclass_node, - }, - }; - -At the top is the list head. Driver model uses this on start-up, instead of -creating its own. - -Below that are a set of `DM_UCLASS_INST()` macros, each declaring a -`struct uclass`. The doubly linked lists work as for devices. - -All private data is placed into a `.priv_data` section so that it is contiguous -in the resulting output binary. - - -Indexes -------- - -U-Boot stores drivers, devices and many other things in linker_list structures. -These are sorted by name, so dtoc knows the order that they will appear when -the linker runs. Each driver_info / udevice is referenced by its index in the -linker_list array, called 'idx' in the code. - -When CONFIG_OF_PLATDATA_INST is enabled, idx is the udevice index, otherwise it -is the driver_info index. In either case, indexes are used to reference devices -using device_get_by_ofplat_idx(). This allows phandles to work as expected. - - -Phases ------- - -U-Boot operates in several phases, typically TPL, SPL and U-Boot proper. -The latter does not use dtoc. - -In some rare cases different drivers are used for two phases. For example, -in TPL it may not be necessary to use the full PCI subsystem, so a simple -driver can be used instead. - -This works in the build system simply by compiling in one driver or the -other (e.g. PCI driver + uclass for SPL; simple_bus for TPL). But dtoc has -no way of knowing which code is compiled in for which phase, since it does -not inspect Makefiles or dependency graphs. - -So to make this work for dtoc, we need to be able to explicitly mark -drivers with their phase. This is done by adding a macro to the driver:: - - /* code in tpl.c only compiled into TPL */ - U_BOOT_DRIVER(pci_x86) = { - .name = "pci_x86", - .id = UCLASS_SIMPLE_BUS, - .of_match = of_match_ptr(tpl_fake_pci_ids), - DM_PHASE(tpl) - }; - - - /* code in pci_x86.c compiled into SPL and U-Boot proper */ - U_BOOT_DRIVER(pci_x86) = { - .name = "pci_x86", - .id = UCLASS_PCI, - .of_match = pci_x86_ids, - .ops = &pci_x86_ops, - }; - - -Notice that the second driver has the same name but no DM_PHASE(), so it will be -used for SPL and U-Boot. - -Note also that this only affects the code generated by dtoc. You still need to -make sure that only the required driver is build into each phase. - - -Header files ------------- - -With OF_PLATDATA_INST, dtoc must include the correct header file in the -generated code for any structs that are used, so that the code will compile. -For example, if `struct ns16550_plat` is used, the code must include the -`ns16550.h` header file. - -Typically dtoc can detect the header file needed for a driver by looking -for the structs that it uses. For example, if a driver as a `.priv_auto` -that uses `struct ns16550_plat`, then dtoc can search header files for the -definition of that struct and use the file. - -In some cases, enums are used in drivers, typically with the `.data` field -of `struct udevice_id`. Since dtoc does not support searching for these, -you must use the `DM_HDR()` macro to tell dtoc which header to use. This works -as a macro included in the driver definition:: - - static const struct udevice_id apl_syscon_ids[] = { - { .compatible = "intel,apl-punit", .data = X86_SYSCON_PUNIT }, - { } - }; - - U_BOOT_DRIVER(intel_apl_punit) = { - .name = "intel_apl_punit", - .id = UCLASS_SYSCON, - .of_match = apl_syscon_ids, - .probe = apl_punit_probe, - DM_HEADER(<asm/cpu.h>) /* for X86_SYSCON_PUNIT */ - }; - - - -Caveats -------- - -There are various complications with this feature which mean it should only -be used when strictly necessary, i.e. in SPL with limited memory. Notable -caveats include: - - - Device tree does not describe data types. But the C code must define a - type for each property. These are guessed using heuristics which - are wrong in several fairly common cases. For example an 8-byte value - is considered to be a 2-item integer array, and is byte-swapped. A - boolean value that is not present means 'false', but cannot be - included in the structures since there is generally no mention of it - in the devicetree file. - - - Naming of nodes and properties is automatic. This means that they follow - the naming in the devicetree, which may result in C identifiers that - look a bit strange. - - - It is not possible to find a value given a property name. Code must use - the associated C member variable directly in the code. This makes - the code less robust in the face of devicetree changes. To avoid having - a second struct with similar members and names you need to explicitly - declare it as an alias with `DM_DRIVER_ALIAS()`. - - - The platform data is provided to drivers as a C structure. The driver - must use the same structure to access the data. Since a driver - normally also supports devicetree it must use `#ifdef` to separate - out this code, since the structures are only available in SPL. This could - be fixed fairly easily by making the structs available outside SPL, so - that `IS_ENABLED()` could be used. - - - With CONFIG_OF_PLATDATA_INST all binding happens at build-time, meaning - that (by default) it is not possible to call `device_bind()` from C code. - This means that all devices must have an associated devicetree node and - compatible string. For example if a GPIO device currently creates child - devices in its `bind()` method, it will not work with - CONFIG_OF_PLATDATA_INST. Arguably this is bad practice anyway and the - devicetree binding should be updated to declare compatible strings for - the child devices. It is possible to disable OF_PLATDATA_NO_BIND but this - is not recommended since it increases code size. - - -Internals ---------- - -Generated files -``````````````` - -When enabled, dtoc generates the following five files: - -include/generated/dt-decl.h (OF_PLATDATA_INST only) - Contains declarations for all drivers, devices and uclasses. This allows - any `struct udevice`, `struct driver` or `struct uclass` to be located by its - name - -include/generated/dt-structs-gen.h - Contains the struct definitions for the devicetree nodes that are used. This - is the same as without OF_PLATDATA_INST - -spl/dts/dt-plat.c (only with !OF_PLATDATA_INST) - Contains the `U_BOOT_DRVINFO()` declarations that U-Boot uses to bind devices - at start-up. See above for an example - -spl/dts/dt-device.c (only with OF_PLATDATA_INST) - Contains `DM_DEVICE_INST()` declarations for each device that can be used at - run-time. These are declared in the file along with any private/platform data - that they use. Every device has an idx, as above. Since each device must be - part of a double-linked list, the nodes are declared in the code as well. - -spl/dts/dt-uclass.c (only with OF_PLATDATA_INST) - Contains `DM_UCLASS_INST()` declarations for each uclass that can be used at - run-time. These are declared in the file along with any private data - associated with the uclass itself (the `.priv_auto` member). Since each - uclass must be part of a double-linked list, the nodes are declared in the - code as well. - -The dt-structs.h file includes the generated file -`(include/generated/dt-structs.h`) if CONFIG_SPL_OF_PLATDATA is enabled. -Otherwise (such as in U-Boot proper) these structs are not available. This -prevents them being used inadvertently. All usage must be bracketed with -`#if CONFIG_IS_ENABLED(OF_PLATDATA)`. - -The dt-plat.c file contains the device declarations and is is built in -spl/dt-plat.c. - - -CONFIG options -`````````````` - -Several CONFIG options are used to control the behaviour of of-platdata, all -available for both SPL and TPL: - -OF_PLATDATA - This is the main option which enables the of-platdata feature - -OF_PLATDATA_PARENT - This allows `device_get_parent()` to work. Without this, all devices exist as - direct children of the root node. This option is highly desirable (if not - always absolutely essential) for buses such as I2C. - -OF_PLATDATA_INST - This controls the instantiation of devices at build time. With it disabled, - only `U_BOOT_DRVINFO()` records are created, with U-Boot handling the binding - in `device_bind()` on start-up. With it enabled, only `DM_DEVICE_INST()` and - `DM_UCLASS_INST()` records are created, and `device_bind()` is not needed at - runtime. - -OF_PLATDATA_NO_BIND - This controls whether `device_bind()` is supported. It is enabled by default - with OF_PLATDATA_INST since code-size reduction is really the main point of - the feature. It can be disabled if needed but is not likely to be supported - in the long term. - -OF_PLATDATA_DRIVER_RT - This controls whether the `struct driver_rt` records are used by U-Boot. - Normally when a device is bound, U-Boot stores the device pointer in one of - these records. There is one for every `struct driver_info` in the system, - i.e. one for every device that is bound from those records. It provides a - way to locate a device in the code and is used by - `device_get_by_ofplat_idx()`. This option is always enabled with of-platdata, - provided OF_PLATDATA_INST is not. In that case the records are useless since - we don't have any `struct driver_info` records. - -OF_PLATDATA_RT - This controls whether the `struct udevice_rt` records are used by U-Boot. - It moves the updatable fields from `struct udevice` (currently only `flags`) - into a separate structure, allowing the records to be kept in read-only - memory. It is generally enabled if OF_PLATDATA_INST is enabled. This option - also controls whether the private data is used in situ, or first copied into - an allocated region. Again this is to allow the private data declared by - dtoc-generated code to be in read-only memory. Note that access to private - data must be done via accessor functions, such as `dev_get_priv()`, so that - the relocation is handled. - -READ_ONLY - This indicates that the data generated by dtoc should not be modified. Only - a few fields actually do get changed in U-Boot, such as device flags. This - option causes those to move into an allocated space (see OF_PLATDATA_RT). - Also, since updating doubly linked lists is generally impossible when some of - the nodes cannot be updated, OF_PLATDATA_NO_BIND is enabled. - -Data structures -``````````````` - -A few extra data structures are used with of-platdata: - -`struct udevice_rt` - Run-time information for devices. When OF_PLATDATA_RT is enabled, this holds - the flags for each device, so that `struct udevice` can remain unchanged by - U-Boot, and potentially reside in read-only memory. Access to flags is then - via functions like `dev_get_flags()` and `dev_or_flags()`. This data - structure is allocated on start-up, where the private data is also copied. - All flags values start at 0 and any changes are handled by `dev_or_flags()` - and `dev_bic_flags()`. It would be more correct for the flags to be set to - `DM_FLAG_BOUND`, or perhaps `DM_FLAG_BOUND | DM_FLAG_ALLOC_PDATA`, but since - there is no code to bind/unbind devices and no code to allocate/free - private data / platform data, it doesn't matter. - -`struct driver_rt` - Run-time information for `struct driver_info` records. When - OF_PLATDATA_DRIVER_RT is enabled, this holds a pointer to the device - created by each record. This is needed so that is it possible to locate a - device from C code. Specifically, the code can use `DM_DRVINFO_GET(name)` to - get a reference to a particular `struct driver_info`, with `name` being the - name of the devicetree node. This is very convenient. It is also fast, since - no searching or string comparison is needed. This data structure is - allocated on start-up, filled out by `device_bind()` and used by - `device_get_by_ofplat_idx()`. - -Other changes -````````````` - -Some other changes are made with of-platdata: - -Accessor functions - Accessing private / platform data via functions such as `dev_get_priv()` has - always been encouraged. With OF_PLATDATA_RT this is essential, since the - `priv_` and `plat_` (etc.) values point to the data generated by dtoc, not - the read-write copy that is sometimes made on start-up. Changing the - private / platform data pointers has always been discouraged (the API is - marked internal) but with OF_PLATDATA_RT this is not currently supported in - general, since it assumes that all such pointers point to the relocated data. - Note also that the renaming of struct members to have a trailing underscore - was partly done to make people aware that they should not be accessed - directly. - -`gd->uclass_root_s` - Normally U-Boot sets up the head of the uclass list here and makes - `gd->uclass_root` point to it. With OF_PLATDATA_INST, dtoc generates a - declaration of `uclass_head` in `dt-uclass.c` since it needs to link the - head node into the list. In that case, `gd->uclass_root_s` is not used and - U-Boot just makes `gd->uclass_root` point to `uclass_head`. - -`gd->dm_driver_rt` - This holds a pointer to a list of `struct driver_rt` records, one for each - `struct driver_info`. The list is in alphabetical order by the name used - in `U_BOOT_DRVINFO(name)` and indexed by idx, with the first record having - an index of 0. It is only used if OF_PLATDATA_INST is not enabled. This is - accessed via macros so that it can be used inside IS_ENABLED(), rather than - requiring #ifdefs in the C code when it is not present. - -`gd->dm_udevice_rt` - This holds a pointer to a list of `struct udevice_rt` records, one for each - `struct udevice`. The list is in alphabetical order by the name used - in `DM_DEVICE_INST(name)` (a C version of the devicetree node) and indexed by - idx, with the first record having an index of 0. It is only used if - OF_PLATDATA_INST is enabled. This is accessed via macros so that it can be - used inside `IS_ENABLED()`, rather than requiring #ifdefs in the C code when - it is not present. - -`gd->dm_priv_base` - When OF_PLATDATA_RT is enabled, the private/platform data for each device is - copied into an allocated region by U-Boot on start-up. This points to that - region. All calls to accessor functions (e.g. `dev_get_priv()`) then - translate from the pointer provided by the caller (assumed to lie between - `__priv_data_start` and `__priv_data_end`) to the new allocated region. This - member is accessed via macros so that it can be used inside IS_ENABLED(), - rather than required #ifdefs in the C code when it is not present. - -`struct udevice->flags_` - When OF_PLATDATA_RT is enabled, device flags are no-longer part of - `struct udevice`, but are instead kept in `struct udevice_rt`, as described - above. Flags are accessed via functions, such as `dev_get_flags()` and - `dev_or_flags()`. - -`struct udevice->node_` - When OF_PLATDATA is enabled, there is no devicetree at runtime, so no need - for this field. It is removed, just to save space. - -`DM_PHASE` - This macro is used to indicate which phase of U-Boot a driver is intended - for. See above for details. - -`DM_HDR` - This macro is used to indicate which header file dtoc should use to allow - a driver declaration to compile correctly. See above for details. - -`device_get_by_ofplat_idx()` - There used to be a function called `device_get_by_driver_info()` which - looked up a `struct driver_info` pointer and returned the `struct udevice` - that was created from it. It was only available for use with of-platdata. - This has been removed in favour of `device_get_by_ofplat_idx()` which uses - `idx`, the index of the `struct driver_info` or `struct udevice` in the - linker_list. Similarly, the `struct phandle_0_arg` (etc.) structs have been - updated to use this index instead of a pointer to `struct driver_info`. - -`DM_DRVINFO_GET` - This has been removed since we now use indexes to obtain a driver from - `struct phandle_0_arg` and the like. - -Two-pass binding - The original of-platdata tried to order `U_BOOT_DRVINFO()` in the generated - files so as to have parents declared ahead of children. This was convenient - as it avoided any special code in U-Boot. With OF_PLATDATA_INST this does - not work as the idx value relies on using alphabetical order for everything, - so that dtoc and U-Boot's linker_lists agree on the idx value. Devices are - then bound in order of idx, having no regard to parent/child relationships. - For this reason, device binding now hapens in multiple passes, with parents - being bound before their children. This is important so that children can - find their parents in the bind() method if needed. - -Root device - The root device is generally bound by U-Boot but with OF_PLATDATA_INST it - cannot be, since binding needs to be done at build time. So in this case - dtoc sets up a root device using `DM_DEVICE_INST()` in `dt-device.c` and - U-Boot makes use of that. When OF_PLATDATA_INST is not enabled, U-Boot - generally ignores the root node and does not create a `U_BOOT_DRVINFO()` - record for it. This means that the idx numbers used by `struct driver_info` - (when OF_PLATDATA_INST is disabled) and the idx numbers used by - `struct udevice` (when OF_PLATDATA_INST is enabled) differ, since one has a - root node and the other does not. This does not actually matter, since only - one of them is actually used for any particular build, but it is worth - keeping in mind if comparing index values and switching OF_PLATDATA_INST on - and off. - -`__priv_data_start` and `__priv_data_end` - The private/platform data declared by dtoc is all collected together in - a linker section and these symbols mark the start and end of it. This allows - U-Boot to relocate the area to a new location if needed (with - OF_PLATDATA_RT) - -`dm_priv_to_rw()` - This function converts a private- or platform-data pointer value generated by - dtoc into one that can be used by U-Boot. It is a NOP unless OF_PLATDATA_RT - is enabled, in which case it translates the address to the relocated - region. See above for more information. - -The dm_populate_phandle_data() function that was previous needed has now been -removed, since dtoc can address the drivers directly from dt-plat.c and does -not need to fix up things at runtime. - -The pylibfdt Python module is used to access the devicetree. - - -Credits -------- - -This is an implementation of an idea by Tom Rini <trini@konsulko.com>. - - -Future work ------------ -- Consider programmatically reading binding files instead of devicetree - contents -- Allow IS_ENABLED() to be used in the C code instead of #if - - -.. Simon Glass <sjg@chromium.org> -.. Google, Inc -.. 6/6/16 -.. Updated Independence Day 2016 -.. Updated 1st October 2020 -.. Updated 5th February 2021 |