Merge tag 'dm-pull-26mar21-take2' of git://git.denx.de/u-boot-dm into next

dtoc support for of-platdata-inst
driver model support for of-platdata-inst
support of-platdata-inst on x86 / coral
binman support for exapanded entries
binman convert docs to reST
ti-sysc fix for duplicate uclass driver
patman minor improvements
pylibfdt build only if needed
correct obscure CI error with OF_PLATDATA_INST
This commit is contained in:
Tom Rini
2021-03-26 12:15:26 -04:00
192 changed files with 6561 additions and 1546 deletions
+49
View File
@@ -0,0 +1,49 @@
.. SPDX-License-Identifier: GPL-2.0+
.. sectionauthor:: Patrice Chotard <patrice.chotard@foss.st.com>
Binding/unbinding a driver
==========================
This document aims to describe the bind and unbind commands.
For debugging purpose, it should be useful to bind or unbind a driver from
the U-boot command line.
The unbind command calls the remove device driver callback and unbind the
device from its driver.
The bind command binds a device to its driver.
In some cases it can be useful to be able to bind a device to a driver from
the command line.
The obvious example is for versatile devices such as USB gadget.
Another use case is when the devices are not yet ready at startup and
require some setup before the drivers are bound (ex: FPGA which bitsream is
fetched from a mass storage or ethernet)
usage:
bind <node path> <driver>
bind <class> <index> <driver>
unbind <node path>
unbind <class> <index>
unbind <class> <index> <driver>
Where:
- <node path> is the node's device tree path
- <class> is one of the class available in the list given by the "dm uclass"
command or first column of "dm tree" command.
- <index> is the index of the parent's node (second column of "dm tree" output).
- <driver> is the driver name to bind given by the "dm drivers" command or the by
the fourth column of "dm tree" output.
example:
bind usb_dev_generic 0 usb_ether
unbind usb_dev_generic 0 usb_ether
or
unbind eth 1
bind /ocp/omap_dwc3@48380000/usb@48390000 usb_ether
unbind /ocp/omap_dwc3@48380000/usb@48390000
+62
View File
@@ -0,0 +1,62 @@
.. SPDX-License-Identifier: GPL-2.0+
.. sectionauthor:: Simon Glass <sjg@chromium.org>
Debugging driver model
======================
This document aims to provide help when you cannot work out why driver model is
not doing what you expect.
Useful techniques in general
----------------------------
Here are some useful debugging features generally.
- If you are writing a new feature, consider doing it in sandbox instead of
on your board. Sandbox has no limits, allows easy debugging (e.g. gdb) and
you can write emulators for most common devices.
- Put '#define DEBUG' at the top of a file, to activate all the debug() and
log_debug() statements in that file.
- Where logging is used, change the logging level, e.g. in SPL with
CONFIG_SPL_LOG_MAX_LEVEL=7 (which is LOGL_DEBUG) and
CONFIG_LOG_DEFAULT_LEVEL=7
- Where logging of return values is implemented with log_msg_ret(), set
CONFIG_LOG_ERROR_RETURN=y to see exactly where the error is happening
- Make sure you have a debug UART enabled - see CONFIG_DEBUG_UART. With this
you can get serial output (printf(), etc.) before the serial driver is
running.
- Use a JTAG emulator to set breakpoints and single-step through code
Not that most of these increase code/data size somewhat when enabled.
Failure to locate a device
--------------------------
Let's say you have uclass_first_device_err() and it is not finding anything.
If it is returning an error, then that gives you a clue. Look up linux/errno.h
to see errors. Common ones are:
- -ENOMEM which indicates that memory is short. If it happens in SPL or
before relocation in U-Boot, check CONFIG_SPL_SYS_MALLOC_F_LEN and
CONFIG_SYS_MALLOC_F_LEN as they may need to be larger. Add '#define DEBUG'
at the very top of malloc_simple.c to get an idea of where your memory is
going.
- -EINVAL which typically indicates that something was missing or wrong in
the device tree node. Check that everything is correct and look at the
of_to_plat() method in the driver.
If there is no error, you should check if the device is actually bound. Call
dm_dump_all() just before you locate the device to make sure it exists.
If it does not exist, check your device tree compatible strings match up with
what the driver expects (in the struct udevice_id array).
If you are using of-platdata (e.g. CONFIG_SPL_OF_PLATDATA), check that the
driver name is the same as the first compatible string in the device tree (with
invalid-variable characters converted to underscore).
If you are really stuck, putting '#define LOG_DEBUG' at the top of
drivers/core/lists.c should show you what is going on.
File diff suppressed because it is too large Load Diff
+321
View File
@@ -0,0 +1,321 @@
Ethernet Driver Guide
=======================
The networking stack in Das U-Boot is designed for multiple network devices
to be easily added and controlled at runtime. This guide is meant for people
who wish to review the net driver stack with an eye towards implementing your
own ethernet device driver. Here we will describe a new pseudo 'APE' driver.
Most existing drivers do already - and new network driver MUST - use the
U-Boot core driver model. Generic information about this can be found in
doc/driver-model/design.rst, this document will thus focus on the network
specific code parts.
Some drivers are still using the old Ethernet interface, differences between
the two and hints about porting will be handled at the end.
Driver framework
------------------
A network driver following the driver model must declare itself using
the UCLASS_ETH .id field in the U-Boot driver struct:
.. code-block:: c
U_BOOT_DRIVER(eth_ape) = {
.name = "eth_ape",
.id = UCLASS_ETH,
.of_match = eth_ape_ids,
.of_to_plat = eth_ape_of_to_plat,
.probe = eth_ape_probe,
.ops = &eth_ape_ops,
.priv_auto = sizeof(struct eth_ape_priv),
.plat_auto = sizeof(struct eth_ape_pdata),
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};
struct eth_ape_priv contains runtime per-instance data, like buffers, pointers
to current descriptors, current speed settings, pointers to PHY related data
(like struct mii_dev) and so on. Declaring its size in .priv_auto
will let the driver framework allocate it at the right time.
It can be retrieved using a dev_get_priv(dev) call.
struct eth_ape_pdata contains static platform data, like the MMIO base address,
a hardware variant, the MAC address. ``struct eth_pdata eth_pdata``
as the first member of this struct helps to avoid duplicated code.
If you don't need any more platform data beside the standard member,
just use sizeof(struct eth_pdata) for the plat_auto.
PCI devices add a line pointing to supported vendor/device ID pairs:
.. code-block:: c
static struct pci_device_id supported[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_APE, 0x4223) },
{}
};
U_BOOT_PCI_DEVICE(eth_ape, supported);
It is also possible to declare support for a whole class of PCI devices::
{ PCI_DEVICE_CLASS(PCI_CLASS_SYSTEM_SDHCI << 8, 0xffff00) },
Device probing and instantiation will be handled by the driver model framework,
so follow the guidelines there. The probe() function would initialise the
platform specific parts of the hardware, like clocks, resets, GPIOs, the MDIO
bus. Also it would take care of any special PHY setup (power rails, enable
bits for internal PHYs, etc.).
Driver methods
----------------
The real work will be done in the driver method functions the driver provides
by defining the members of struct eth_ops:
.. code-block:: c
struct eth_ops {
int (*start)(struct udevice *dev);
int (*send)(struct udevice *dev, void *packet, int length);
int (*recv)(struct udevice *dev, int flags, uchar **packetp);
int (*free_pkt)(struct udevice *dev, uchar *packet, int length);
void (*stop)(struct udevice *dev);
int (*mcast)(struct udevice *dev, const u8 *enetaddr, int join);
int (*write_hwaddr)(struct udevice *dev);
int (*read_rom_hwaddr)(struct udevice *dev);
};
An up-to-date version of this struct together with more information can be
found in include/net.h.
Only start, stop, send and recv are required, the rest are optional and are
handled by generic code or ignored if not provided.
The **start** function initialises the hardware and gets it ready for send/recv
operations. You often do things here such as resetting the MAC
and/or PHY, and waiting for the link to autonegotiate. You should also take
the opportunity to program the device's MAC address with the enetaddr member
of the generic struct eth_pdata (which would be the first member of your
own plat struct). This allows the rest of U-Boot to dynamically change
the MAC address and have the new settings be respected.
The **send** function does what you think -- transmit the specified packet
whose size is specified by length (in bytes). The packet buffer can (and
will!) be reused for subsequent calls to send(), so it must be no longer
used when the send() function returns. The easiest way to achieve this is
to wait until the transmission is complete. Alternatively, if supported by
the hardware, just waiting for the buffer to be consumed (by some DMA engine)
might be an option as well.
Another way of consuming the buffer could be to copy the data to be send,
then just queue the copied packet (for instance handing it over to a DMA
engine), and return immediately afterwards.
In any case you should leave the state such that the send function can be
called multiple times in a row.
The **recv** function polls for availability of a new packet. If none is
available, it must return with -EAGAIN.
If a packet has been received, make sure it is accessible to the CPU
(invalidate caches if needed), then write its address to the packetp pointer,
and return the length. If there is an error (receive error, too short or too
long packet), return 0 if you require the packet to be cleaned up normally,
or a negative error code otherwise (cleanup not necessary or already done).
The U-Boot network stack will then process the packet.
If **free_pkt** is defined, U-Boot will call it after a received packet has
been processed, so the packet buffer can be freed or recycled. Typically you
would hand it back to the hardware to acquire another packet. free_pkt() will
be called after recv(), for the same packet, so you don't necessarily need
to infer the buffer to free from the ``packet`` pointer, but can rely on that
being the last packet that recv() handled.
The common code sets up packet buffers for you already in the .bss
(net_rx_packets), so there should be no need to allocate your own. This doesn't
mean you must use the net_rx_packets array however; you're free to use any
buffer you wish.
The **stop** function should turn off / disable the hardware and place it back
in its reset state. It can be called at any time (before any call to the
related start() function), so make sure it can handle this sort of thing.
The (optional) **write_hwaddr** function should program the MAC address stored
in pdata->enetaddr into the Ethernet controller.
So the call graph at this stage would look something like:
.. code-block:: c
(some net operation (ping / tftp / whatever...))
eth_init()
ops->start()
eth_send()
ops->send()
eth_rx()
ops->recv()
(process packet)
if (ops->free_pkt)
ops->free_pkt()
eth_halt()
ops->stop()
CONFIG_PHYLIB / CONFIG_CMD_MII
--------------------------------
If your device supports banging arbitrary values on the MII bus (pretty much
every device does), you should add support for the mii command. Doing so is
fairly trivial and makes debugging mii issues a lot easier at runtime.
In your driver's ``probe()`` function, add a call to mdio_alloc() and
mdio_register() like so:
.. code-block:: c
bus = mdio_alloc();
if (!bus) {
...
return -ENOMEM;
}
bus->read = ape_mii_read;
bus->write = ape_mii_write;
mdio_register(bus);
And then define the mii_read and mii_write functions if you haven't already.
Their syntax is straightforward::
int mii_read(struct mii_dev *bus, int addr, int devad, int reg);
int mii_write(struct mii_dev *bus, int addr, int devad, int reg,
u16 val);
The read function should read the register 'reg' from the phy at address 'addr'
and return the result to its caller. The implementation for the write function
should logically follow.
................................................................
Legacy network drivers
------------------------
!!! WARNING !!!
This section below describes the old way of doing things. No new Ethernet
drivers should be implemented this way. All new drivers should be written
against the U-Boot core driver model, as described above.
The actual callback functions are fairly similar, the differences are:
- ``start()`` is called ``init()``
- ``stop()`` is called ``halt()``
- The ``recv()`` function must loop until all packets have been received, for
each packet it must call the net_process_received_packet() function,
handing it over the pointer and the length. Afterwards it should free
the packet, before checking for new data.
For porting an old driver to the new driver model, split the existing recv()
function into the actual new recv() function, just fetching **one** packet,
remove the call to net_process_received_packet(), then move the packet
cleanup into the ``free_pkt()`` function.
Registering the driver and probing a device is handled very differently,
follow the recommendations in the driver model design documentation for
instructions on how to port this over. For the records, the old way of
initialising a network driver is as follows:
Old network driver registration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When U-Boot initializes, it will call the common function eth_initialize().
This will in turn call the board-specific board_eth_init() (or if that fails,
the cpu-specific cpu_eth_init()). These board-specific functions can do random
system handling, but ultimately they will call the driver-specific register
function which in turn takes care of initializing that particular instance.
Keep in mind that you should code the driver to avoid storing state in global
data as someone might want to hook up two of the same devices to one board.
Any such information that is specific to an interface should be stored in a
private, driver-defined data structure and pointed to by eth->priv (see below).
So the call graph at this stage would look something like:
.. code-block:: c
board_init()
eth_initialize()
board_eth_init() / cpu_eth_init()
driver_register()
initialize eth_device
eth_register()
At this point in time, the only thing you need to worry about is the driver's
register function. The pseudo code would look something like:
.. code-block:: c
int ape_register(struct bd_info *bis, int iobase)
{
struct ape_priv *priv;
struct eth_device *dev;
struct mii_dev *bus;
priv = malloc(sizeof(*priv));
if (priv == NULL)
return -ENOMEM;
dev = malloc(sizeof(*dev));
if (dev == NULL) {
free(priv);
return -ENOMEM;
}
/* setup whatever private state you need */
memset(dev, 0, sizeof(*dev));
sprintf(dev->name, "APE");
/*
* if your device has dedicated hardware storage for the
* MAC, read it and initialize dev->enetaddr with it
*/
ape_mac_read(dev->enetaddr);
dev->iobase = iobase;
dev->priv = priv;
dev->init = ape_init;
dev->halt = ape_halt;
dev->send = ape_send;
dev->recv = ape_recv;
dev->write_hwaddr = ape_write_hwaddr;
eth_register(dev);
#ifdef CONFIG_PHYLIB
bus = mdio_alloc();
if (!bus) {
free(priv);
free(dev);
return -ENOMEM;
}
bus->read = ape_mii_read;
bus->write = ape_mii_write;
mdio_register(bus);
#endif
return 1;
}
The exact arguments needed to initialize your device are up to you. If you
need to pass more/less arguments, that's fine. You should also add the
prototype for your new register function to include/netdev.h.
The return value for this function should be as follows:
< 0 - failure (hardware failure, not probe failure)
>=0 - number of interfaces detected
You might notice that many drivers seem to use xxx_initialize() rather than
xxx_register(). This is the old naming convention and should be avoided as it
causes confusion with the driver-specific init function.
Other than locating the MAC address in dedicated hardware storage, you should
not touch the hardware in anyway. That step is handled in the driver-specific
init function. Remember that we are only registering the device here, we are
not checking its state or doing random probing.
+132
View File
@@ -0,0 +1,132 @@
.. SPDX-License-Identifier: GPL-2.0+
.. 2017-01-06, Mario Six <mario.six@gdsys.cc>
Pre-relocation device tree manipulation
=======================================
Purpose
-------
In certain markets, it is beneficial for manufacturers of embedded devices to
offer certain ranges of products, where the functionality of the devices within
one series either don't differ greatly from another, or can be thought of as
"extensions" of each other, where one device only differs from another in the
addition of a small number of features (e.g. an additional output connector).
To realize this in hardware, one method is to have a motherboard, and several
possible daughter boards that can be attached to this mother board. Different
daughter boards then either offer the slightly different functionality, or the
addition of the daughter board to the device realizes the "extension" of
functionality to the device described previously.
For the software, we obviously want to reuse components for all these
variations of the device. This means that the software somehow needs to cope
with the situation that certain ICs may or may not be present on any given
system, depending on which daughter boards are connected to the motherboard.
In the Linux kernel, one possible solution to this problem is to employ the
device tree overlay mechanism: There exists one "base" device tree, which
features only the components guaranteed to exist in all varieties of the
device. At the start of the kernel, the presence and type of the daughter
boards is then detected, and the corresponding device tree overlays are applied
to support the components on the daughter boards.
Note that the components present on every variety of the board must, of course,
provide a way to find out if and which daughter boards are installed for this
mechanism to work.
In the U-Boot boot loader, support for device tree overlays has recently been
integrated, and is used on some boards to alter the device tree that is later
passed to Linux. But since U-Boot's driver model, which is device tree-based as
well, is being used in more and more drivers, the same problem of altering the
device tree starts cropping up in U-Boot itself as well.
An additional problem with the device tree in U-Boot is that it is read-only,
and the current mechanisms don't allow easy manipulation of the device tree
after the driver model has been initialized. While migrating to a live device
tree (at least after the relocation) would greatly simplify the solution of
this problem, it is a non-negligible task to implement it, an a interim
solution is needed to address the problem at least in the medium-term.
Hence, we propose a solution to this problem by offering a board-specific
call-back function, which is passed a writeable pointer to the device tree.
This function is called before the device tree is relocated, and specifically
before the main U-Boot's driver model is instantiated, hence the main U-Boot
"sees" all modifications to the device tree made in this function. Furthermore,
we have the pre-relocation driver model at our disposal at this stage, which
means that we can query the hardware for the existence and variety of the
components easily.
Implementation
--------------
To take advantage of the pre-relocation device tree manipulation mechanism,
boards have to implement the function board_fix_fdt, which has the following
signature:
.. code-block:: c
int board_fix_fdt (void *rw_fdt_blob)
The passed-in void pointer is a writeable pointer to the device tree, which can
be used to manipulate the device tree using e.g. functions from
include/fdt_support.h. The return value should either be 0 in case of
successful execution of the device tree manipulation or something else for a
failure. Note that returning a non-null value from the function will
unrecoverably halt the boot process, as with any function from init_sequence_f
(in common/board_f.c).
Furthermore, the Kconfig option OF_BOARD_FIXUP has to be set for the function
to be called::
Device Tree Control
-> [*] Board-specific manipulation of Device Tree
+----------------------------------------------------------+
| WARNING: The actual manipulation of the device tree has |
| to be the _last_ set of operations in board_fix_fdt! |
| Since the pre-relocation driver model does not adapt to |
| changes made to the device tree either, its references |
| into the device tree will be invalid after manipulating |
| it, and unpredictable behavior might occur when |
| functions that rely on them are executed! |
+----------------------------------------------------------+
Hence, the recommended layout of the board_fixup_fdt call-back function is the
following:
.. code-block:: c
int board_fix_fdt(void *rw_fdt_blob)
{
/*
* Collect information about device's hardware and store
* them in e.g. local variables
*/
/* Do device tree manipulation using the values previously collected */
/* Return 0 on successful manipulation and non-zero otherwise */
}
If this convention is kept, both an "additive" approach, meaning that nodes for
detected components are added to the device tree, as well as a "subtractive"
approach, meaning that nodes for absent components are removed from the tree,
as well as a combination of both approaches should work.
Example
-------
The controlcenterdc board (board/gdsys/a38x/controlcenterdc.c) features a
board_fix_fdt function, in which six GPIO expanders (which might be present or
not, since they are on daughter boards) on a I2C bus are queried for, and
subsequently deactivated in the device tree if they are not present.
Note that the dm_i2c_simple_probe function does not use the device tree, hence
it is safe to call it after the tree has already been manipulated.
Work to be done
---------------
* The application of device tree overlay should be possible in board_fixup_fdt,
but has not been tested at this stage.
@@ -0,0 +1,154 @@
.. SPDX-License-Identifier: GPL-2.0+
.. Copyright (C) 2018-2019 Intel Corporation <www.intel.com>
File System Firmware Loader
===========================
This is file system firmware loader for U-Boot framework, which has very close
to some Linux Firmware API. For the details of Linux Firmware API, you can refer
to https://01.org/linuxgraphics/gfx-docs/drm/driver-api/firmware/index.html.
File system firmware loader can be used to load whatever(firmware, image,
and binary) from the storage device in file system format into target location
such as memory, then consumer driver such as FPGA driver can program FPGA image
from the target location into FPGA.
To enable firmware loader, CONFIG_FS_LOADER need to be set at
<board_name>_defconfig such as "CONFIG_FS_LOADER=y".
Firmware Loader API core features
---------------------------------
Firmware storage device described in device tree source
-------------------------------------------------------
For passing data like storage device phandle and partition where the
firmware loading from to the firmware loader driver, those data could be
defined in fs-loader node as shown in below:
Example for block device::
fs_loader0: fs-loader {
u-boot,dm-pre-reloc;
compatible = "u-boot,fs-loader";
phandlepart = <&mmc 1>;
};
<&mmc 1> means block storage device pointer and its partition.
Above example is a description for block storage, but for UBI storage
device, it can be described in FDT as shown in below:
Example for ubi::
fs_loader1: fs-loader {
u-boot,dm-pre-reloc;
compatible = "u-boot,fs-loader";
mtdpart = "UBI",
ubivol = "ubi0";
};
Then, firmware-loader property can be added with any device node, which
driver would use the firmware loader for loading.
The value of the firmware-loader property should be set with phandle
of the fs-loader node. For example::
firmware-loader = <&fs_loader0>;
If there are majority of devices using the same fs-loader node, then
firmware-loader property can be added under /chosen node instead of
adding to each of device node.
For example::
/{
chosen {
firmware-loader = <&fs_loader0>;
};
};
In each respective driver of devices using firmware loader, the firmware
loaded instance should be created by DT phandle.
For example of getting DT phandle from /chosen and creating instance:
.. code-block:: c
chosen_node = ofnode_path("/chosen");
if (!ofnode_valid(chosen_node)) {
debug("/chosen node was not found.\n");
return -ENOENT;
}
phandle_p = ofnode_get_property(chosen_node, "firmware-loader", &size);
if (!phandle_p) {
debug("firmware-loader property was not found.\n");
return -ENOENT;
}
phandle = fdt32_to_cpu(*phandle_p);
ret = uclass_get_device_by_phandle_id(UCLASS_FS_FIRMWARE_LOADER,
phandle, &dev);
if (ret)
return ret;
Firmware loader driver is also designed to support U-boot environment
variables, so all these data from FDT can be overwritten
through the U-boot environment variable during run time.
For examples:
storage_interface:
Storage interface, it can be "mmc", "usb", "sata" or "ubi".
fw_dev_part:
Block device number and its partition, it can be "0:1".
fw_ubi_mtdpart:
UBI device mtd partition, it can be "UBI".
fw_ubi_volume:
UBI volume, it can be "ubi0".
When above environment variables are set, environment values would be
used instead of data from FDT.
The benefit of this design allows user to change storage attribute data
at run time through U-boot console and saving the setting as default
environment values in the storage for the next power cycle, so no
compilation is required for both driver and FDT.
File system firmware Loader API
-------------------------------
.. code-block:: c
int request_firmware_into_buf(struct udevice *dev,
const char *name,
void *buf, size_t size, u32 offset)
Load firmware into a previously allocated buffer
Parameters:
* struct udevice \*dev: An instance of a driver
* const char \*name: name of firmware file
* void \*buf: address of buffer to load firmware into
* size_t size: size of buffer
* u32 offset: offset of a file for start reading into buffer
Returns:
size of total read
-ve when error
Description:
The firmware is loaded directly into the buffer pointed to by buf
Example of calling request_firmware_into_buf API after creating firmware loader
instance:
.. code-block:: c
ret = uclass_get_device_by_phandle_id(UCLASS_FS_FIRMWARE_LOADER,
phandle, &dev);
if (ret)
return ret;
request_firmware_into_buf(dev, filename, buffer_location, buffer_size,
offset_ofreading);
+56
View File
@@ -0,0 +1,56 @@
.. SPDX-License-Identifier: GPL-2.0+
How to port an I2C driver to driver model
=========================================
Over half of the I2C drivers have been converted as at November 2016. These
ones remain:
* adi_i2c
* davinci_i2c
* fti2c010
* ihs_i2c
* kona_i2c
* lpc32xx_i2c
* pca9564_i2c
* ppc4xx_i2c
* rcar_i2c
* sh_i2c
* soft_i2c
* zynq_i2c
The deadline for this work is the end of June 2017. If no one steps
forward to convert these, at some point there may come a patch to remove them!
Here is a suggested approach for converting your I2C driver over to driver
model. Please feel free to update this file with your ideas and suggestions.
- #ifdef out all your own I2C driver code (#if !CONFIG_IS_ENABLED(DM_I2C))
- Define CONFIG_DM_I2C for your board, vendor or architecture
- If the board does not already use driver model, you need CONFIG_DM also
- Your board should then build, but will not work fully since there will be
no I2C driver
- Add the U_BOOT_DRIVER piece at the end (e.g. copy tegra_i2c.c for example)
- Add a private struct for the driver data - avoid using static variables
- Implement each of the driver methods, perhaps by calling your old methods
- You may need to adjust the function parameters so that the old and new
implementations can share most of the existing code
- If you convert all existing users of the driver, remove the pre-driver-model
code
In terms of patches a conversion series typically has these patches:
- clean up / prepare the driver for conversion
- add driver model code
- convert at least one existing board to use driver model serial
- (if no boards remain that don't use driver model) remove the old code
This may be a good time to move your board to use device tree also. Mostly
this involves these steps:
- define CONFIG_OF_CONTROL and CONFIG_OF_SEPARATE
- add your device tree files to arch/<arch>/dts
- update the Makefile there
- Add stdout-path to your /chosen device tree node if it is not already there
- build and get u-boot-dtb.bin so you can test it
- Your drivers can now use device tree
- For device tree in SPL, define CONFIG_SPL_OF_CONTROL
+29
View File
@@ -0,0 +1,29 @@
.. SPDX-License-Identifier: GPL-2.0+
Driver Model
============
The following holds information on the U-Boot device driver framework:
driver-model, including the design details of itself and several driver
subsystems
.. toctree::
:maxdepth: 2
bind
debugging
design
ethernet
fdt-fixup
fs_firmware_loader
i2c-howto
livetree
migration
of-plat
pci-info
pmic-framework
remoteproc-framework
serial-howto
soc-framework
spi-howto
usb-info
+286
View File
@@ -0,0 +1,286 @@
.. SPDX-License-Identifier: GPL-2.0+
.. sectionauthor:: Simon Glass <sjg@chromium.org>
Live Device Tree
================
Introduction
------------
Traditionally U-Boot has used a 'flat' device tree. This means that it
reads directly from the device tree binary structure. It is called a flat
device tree because nodes are listed one after the other, with the
hierarchy detected by tags in the format.
This document describes U-Boot's support for a 'live' device tree, meaning
that the tree is loaded into a hierarchical data structure within U-Boot.
Motivation
----------
The flat device tree has several advantages:
- it is the format produced by the device tree compiler, so no translation
is needed
- it is fairly compact (e.g. there is no need for pointers)
- it is accessed by the libfdt library, which is well tested and stable
However the flat device tree does have some limitations. Adding new
properties can involve copying large amounts of data around to make room.
The overall tree has a fixed maximum size so sometimes the tree must be
rebuilt in a new location to create more space. Even if not adding new
properties or nodes, scanning the tree can be slow. For example, finding
the parent of a node is a slow process. Reading from nodes involves a
small amount parsing which takes a little time.
Driver model scans the entire device tree sequentially on start-up which
avoids the worst of the flat tree's limitations. But if the tree is to be
modified at run-time, a live tree is much faster. Even if no modification
is necessary, parsing the tree once and using a live tree from then on
seems to save a little time.
Implementation
--------------
In U-Boot a live device tree ('livetree') is currently supported only
after relocation. Therefore we need a mechanism to specify a device
tree node regardless of whether it is in the flat tree or livetree.
The 'ofnode' type provides this. An ofnode can point to either a flat tree
node (when the live tree node is not yet set up) or a livetree node. The
caller of an ofnode function does not need to worry about these details.
The main users of the information in a device tree are drivers. These have
a 'struct udevice \*' which is attached to a device tree node. Therefore it
makes sense to be able to read device tree properties using the
'struct udevice \*', rather than having to obtain the ofnode first.
The 'dev_read\_...()' interface provides this. It allows properties to be
easily read from the device tree using only a device pointer. Under the
hood it uses ofnode so it works with both flat and live device trees.
Enabling livetree
-----------------
CONFIG_OF_LIVE enables livetree. When this option is enabled, the flat
tree will be used in SPL and before relocation in U-Boot proper. Just
before relocation a livetree is built, and this is used for U-Boot proper
after relocation.
Most checks for livetree use CONFIG_IS_ENABLED(OF_LIVE). This means that
for SPL, the CONFIG_SPL_OF_LIVE option is checked. At present this does
not exist, since SPL does not support livetree.
Porting drivers
---------------
Many existing drivers use the fdtdec interface to read device tree
properties. This only works with a flat device tree. The drivers should be
converted to use the dev_read_() interface.
For example, the old code may be like this:
.. code-block:: c
struct udevice *bus;
const void *blob = gd->fdt_blob;
int node = dev_of_offset(bus);
i2c_bus->regs = (struct i2c_ctlr *)devfdt_get_addr(dev);
plat->frequency = fdtdec_get_int(blob, node, "spi-max-frequency", 500000);
The new code is:
.. code-block:: c
struct udevice *bus;
i2c_bus->regs = (struct i2c_ctlr *)dev_read_addr(dev);
plat->frequency = dev_read_u32_default(bus, "spi-max-frequency", 500000);
The dev_read\_...() interface is more convenient and works with both the
flat and live device trees. See include/dm/read.h for a list of functions.
Where properties must be read from sub-nodes or other nodes, you must fall
back to using ofnode. For example, for old code like this:
.. code-block:: c
const void *blob = gd->fdt_blob;
int subnode;
fdt_for_each_subnode(subnode, blob, dev_of_offset(dev)) {
freq = fdtdec_get_int(blob, node, "spi-max-frequency", 500000);
...
}
you should use:
.. code-block:: c
ofnode subnode;
ofnode_for_each_subnode(subnode, dev_ofnode(dev)) {
freq = ofnode_read_u32(node, "spi-max-frequency", 500000);
...
}
Useful ofnode functions
-----------------------
The internal data structures of the livetree are defined in include/dm/of.h :
:struct device_node: holds information about a device tree node
:struct property: holds information about a property within a node
Nodes have pointers to their first property, their parent, their first child
and their sibling. This allows nodes to be linked together in a hierarchical
tree.
Properties have pointers to the next property. This allows all properties of
a node to be linked together in a chain.
It should not be necessary to use these data structures in normal code. In
particular, you should refrain from using functions which access the livetree
directly, such as of_read_u32(). Use ofnode functions instead, to allow your
code to work with a flat tree also.
Some conversion functions are used internally. Generally these are not needed
for driver code. Note that they will not work if called in the wrong context.
For example it is invalid to call ofnode_to_no() when a flat tree is being
used. Similarly it is not possible to call ofnode_to_offset() on a livetree
node.
ofnode_to_np():
converts ofnode to struct device_node *
ofnode_to_offset():
converts ofnode to offset
no_to_ofnode():
converts node pointer to ofnode
offset_to_ofnode():
converts offset to ofnode
Other useful functions:
of_live_active():
returns true if livetree is in use, false if flat tree
ofnode_valid():
return true if a given node is valid
ofnode_is_np():
returns true if a given node is a livetree node
ofnode_equal():
compares two ofnodes
ofnode_null():
returns a null ofnode (for which ofnode_valid() returns false)
Phandles
--------
There is full phandle support for live tree. All functions make use of
struct ofnode_phandle_args, which has an ofnode within it. This supports both
livetree and flat tree transparently. See for example
ofnode_parse_phandle_with_args().
Reading addresses
-----------------
You should use dev_read_addr() and friends to read addresses from device-tree
nodes.
fdtdec
------
The existing fdtdec interface will eventually be retired. Please try to avoid
using it in new code.
Modifying the livetree
----------------------
This is not currently supported. Once implemented it should provide a much
more efficient implementation for modification of the device tree than using
the flat tree.
Internal implementation
-----------------------
The dev_read\_...() functions have two implementations. When
CONFIG_DM_DEV_READ_INLINE is enabled, these functions simply call the ofnode
functions directly. This is useful when livetree is not enabled. The ofnode
functions call ofnode_is_np(node) which will always return false if livetree
is disabled, just falling back to flat tree code.
This optimisation means that without livetree enabled, the dev_read\_...() and
ofnode interfaces do not noticeably add to code size.
The CONFIG_DM_DEV_READ_INLINE option defaults to enabled when livetree is
disabled.
Most livetree code comes directly from Linux and is modified as little as
possible. This is deliberate since this code is fairly stable and does what
we want. Some features (such as get/put) are not supported. Internal macros
take care of removing these features silently.
Within the of_access.c file there are pointers to the alias node, the chosen
node and the stdout-path alias.
Errors
------
With a flat device tree, libfdt errors are returned (e.g. -FDT_ERR_NOTFOUND).
For livetree normal 'errno' errors are returned (e.g. -ENOTFOUND). At present
the ofnode and dev_read\_...() functions return either one or other type of
error. This is clearly not desirable. Once tests are added for all the
functions this can be tidied up.
Adding new access functions
---------------------------
Adding a new function for device-tree access involves the following steps:
- Add two dev_read() functions:
- inline version in the read.h header file, which calls an ofnode function
- standard version in the read.c file (or perhaps another file), which
also calls an ofnode function
The implementations of these functions can be the same. The purpose
of the inline version is purely to reduce code size impact.
- Add an ofnode function. This should call ofnode_is_np() to work out
whether a livetree or flat tree is used. For the livetree it should
call an of\_...() function. For the flat tree it should call an
fdt\_...() function. The livetree version will be optimised out at
compile time if livetree is not enabled.
- Add an of\_...() function for the livetree implementation. If a similar
function is available in Linux, the implementation should be taken
from there and modified as little as possible (generally not at all).
Future work
-----------
Live tree support was introduced in U-Boot 2017.07. There is still quite a bit
of work to do to flesh this out:
- tests for all access functions
- support for livetree modification
- addition of more access functions as needed
- support for livetree in SPL and before relocation (if desired)
+101
View File
@@ -0,0 +1,101 @@
.. SPDX-License-Identifier: GPL-2.0+
Migration Schedule
==================
U-Boot has been migrating to a new driver model since its introduction in
2014. This file describes the schedule for deprecation of pre-driver-model
features.
CONFIG_DM
---------
* Status: In progress
* Deadline: 2020.01
Starting with the 2010.01 release CONFIG_DM will be enabled for all boards.
This does not concern CONFIG_DM_SPL and CONFIG_DM_TPL. The conversion date for
these configuration items still needs to be defined.
CONFIG_DM_MMC
-------------
* Status: In progress
* Deadline: 2019.04
The subsystem itself has been converted and maintainers should submit patches
switching over to using CONFIG_DM_MMC and other base driver model options in
time for inclusion in the 2019.04 rerelease.
CONFIG_DM_USB
-------------
* Status: In progress
* Deadline: 2019.07
The subsystem itself has been converted along with many of the host controller
and maintainers should submit patches switching over to using CONFIG_DM_USB and
other base driver model options in time for inclusion in the 2019.07 rerelease.
CONFIG_SATA
-----------
* Status: In progress
* Deadline: 2019.07
The subsystem itself has been converted along with many of the host controller
and maintainers should submit patches switching over to using CONFIG_AHCI and
other base driver model options in time for inclusion in the 2019.07 rerelease.
CONFIG_BLK
----------
* Status: In progress
* Deadline: 2019.07
In concert with maintainers migrating their block device usage to the
appropriate DM driver, CONFIG_BLK needs to be set as well. The final deadline
here coincides with the final deadline for migration of the various block
subsystems. At this point we will be able to audit and correct the logic in
Kconfig around using CONFIG_PARTITIONS and CONFIG_HAVE_BLOCK_DEVICE and make
use of CONFIG_BLK / CONFIG_SPL_BLK as needed.
CONFIG_DM_SPI / CONFIG_DM_SPI_FLASH
-----------------------------------
Board Maintainers should submit the patches for enabling DM_SPI and DM_SPI_FLASH
to move the migration with in the deadline.
Partially converted::
drivers/spi/fsl_espi.c
drivers/spi/mxc_spi.c
drivers/spi/sh_qspi.c
* Status: In progress
* Deadline: 2019.07
CONFIG_DM_PCI
-------------
Deadline: 2019.07
The PCI subsystem has supported driver model since mid 2015. Maintainers should
submit patches switching over to using CONFIG_DM_PCI and other base driver
model options in time for inclusion in the 2019.07 release.
CONFIG_DM_VIDEO
---------------
Deadline: 2019.07
The video subsystem has supported driver model since early 2016. Maintainers
should submit patches switching over to using CONFIG_DM_VIDEO and other base
driver model options in time for inclusion in the 2019.07 release.
CONFIG_DM_ETH
-------------
Deadline: 2020.07
The network subsystem has supported the driver model since early 2015.
Maintainers should submit patches switching over to using CONFIG_DM_ETH and
other base driver model options in time for inclusion in the 2020.07 release.
+913
View File
@@ -0,0 +1,913 @@
.. 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
+172
View File
@@ -0,0 +1,172 @@
.. SPDX-License-Identifier: GPL-2.0+
PCI with Driver Model
=====================
How busses are scanned
----------------------
Any config read will end up at pci_read_config(). This uses
uclass_get_device_by_seq() to get the PCI bus for a particular bus number.
Bus number 0 will need to be requested first, and the alias in the device
tree file will point to the correct device::
aliases {
pci0 = &pcic;
};
pcic: pci@0 {
compatible = "sandbox,pci";
...
};
If there is no alias the devices will be numbered sequentially in the device
tree.
The call to uclass_get_device() will cause the PCI bus to be probed.
This does a scan of the bus to locate available devices. These devices are
bound to their appropriate driver if available. If there is no driver, then
they are bound to a generic PCI driver which does nothing.
After probing a bus, the available devices will appear in the device tree
under that bus.
Note that this is all done on a lazy basis, as needed, so until something is
touched on PCI (eg: a call to pci_find_devices()) it will not be probed.
PCI devices can appear in the flattened device tree. If they do, their node
often contains extra information which cannot be derived from the PCI IDs or
PCI class of the device. Each PCI device node must have a <reg> property, as
defined by the IEEE Std 1275-1994 PCI bus binding document v2.1. Compatible
string list is optional and generally not needed, since PCI is discoverable
bus, albeit there are justified exceptions. If the compatible string is
present, matching on it takes precedence over PCI IDs and PCI classes.
Note we must describe PCI devices with the same bus hierarchy as the
hardware, otherwise driver model cannot detect the correct parent/children
relationship during PCI bus enumeration thus PCI devices won't be bound to
their drivers accordingly. A working example like below::
pci {
#address-cells = <3>;
#size-cells = <2>;
compatible = "pci-x86";
u-boot,dm-pre-reloc;
ranges = <0x02000000 0x0 0x40000000 0x40000000 0 0x80000000
0x42000000 0x0 0xc0000000 0xc0000000 0 0x20000000
0x01000000 0x0 0x2000 0x2000 0 0xe000>;
pcie@17,0 {
#address-cells = <3>;
#size-cells = <2>;
compatible = "pci-bridge";
u-boot,dm-pre-reloc;
reg = <0x0000b800 0x0 0x0 0x0 0x0>;
topcliff@0,0 {
#address-cells = <3>;
#size-cells = <2>;
compatible = "pci-bridge";
u-boot,dm-pre-reloc;
reg = <0x00010000 0x0 0x0 0x0 0x0>;
pciuart0: uart@a,1 {
compatible = "pci8086,8811.00",
"pci8086,8811",
"pciclass,070002",
"pciclass,0700",
"x86-uart";
u-boot,dm-pre-reloc;
reg = <0x00025100 0x0 0x0 0x0 0x0
0x01025110 0x0 0x0 0x0 0x0>;
......
};
......
};
};
......
};
In this example, the root PCI bus node is the "/pci" which matches "pci-x86"
driver. It has a subnode "pcie@17,0" with driver "pci-bridge". "pcie@17,0"
also has subnode "topcliff@0,0" which is a "pci-bridge" too. Under that bridge,
a PCI UART device "uart@a,1" is described. This exactly reflects the hardware
bus hierarchy: on the root PCI bus, there is a PCIe root port which connects
to a downstream device Topcliff chipset. Inside Topcliff chipset, it has a
PCIe-to-PCI bridge and all the chipset integrated devices like the PCI UART
device are on the PCI bus. Like other devices in the device tree, if we want
to bind PCI devices before relocation, "u-boot,dm-pre-reloc" must be declared
in each of these nodes.
If PCI devices are not listed in the device tree, U_BOOT_PCI_DEVICE can be used
to specify the driver to use for the device. The device tree takes precedence
over U_BOOT_PCI_DEVICE. Please note with U_BOOT_PCI_DEVICE, only drivers with
DM_FLAG_PRE_RELOC will be bound before relocation. If neither device tree nor
U_BOOT_PCI_DEVICE is provided, the built-in driver (either pci_bridge_drv or
pci_generic_drv) will be used.
Sandbox
-------
With sandbox we need a device emulator for each device on the bus since there
is no real PCI bus. This works by looking in the device tree node for an
emulator driver. For example::
pci@1f,0 {
compatible = "pci-generic";
reg = <0xf800 0 0 0 0>;
sandbox,emul = <&emul_1f>;
};
pci-emul {
compatible = "sandbox,pci-emul-parent";
emul_1f: emul@1f,0 {
compatible = "sandbox,swap-case";
#emul-cells = <0>;
};
};
This means that there is a 'sandbox,swap-case' driver at that bus position.
Note that the first cell in the 'reg' value is the bus/device/function. See
PCI_BDF() for the encoding (it is also specified in the IEEE Std 1275-1994
PCI bus binding document, v2.1)
The pci-emul node should go outside the pci bus node, since otherwise it will
be scanned as a PCI device, causing confusion.
When this bus is scanned we will end up with something like this::
`- * pci@0 @ 05c660c8, 0
`- pci@1f,0 @ 05c661c8, 63488
`- emul@1f,0 @ 05c662c8
When accesses go to the pci@1f,0 device they are forwarded to its emulator.
The sandbox PCI drivers also support dynamic driver binding, allowing device
driver to declare the driver binding information via U_BOOT_PCI_DEVICE(),
eliminating the need to provide any device tree node under the host controller
node. It is required a "sandbox,dev-info" property must be provided in the
host controller node for this functionality to work.
.. code-block:: none
pci1: pci@1 {
compatible = "sandbox,pci";
...
sandbox,dev-info = <0x08 0x00 0x1234 0x5678
0x0c 0x00 0x1234 0x5678>;
};
The "sandbox,dev-info" property specifies all dynamic PCI devices on this bus.
Each dynamic PCI device is encoded as 4 cells a group. The first and second
cells are PCI device number and function number respectively. The third and
fourth cells are PCI vendor ID and device ID respectively.
When this bus is scanned we will end up with something like this::
pci [ + ] pci_sandbo |-- pci1
pci_emul [ ] sandbox_sw | |-- sandbox_swap_case_emul
pci_emul [ ] sandbox_sw | `-- sandbox_swap_case_emul
+143
View File
@@ -0,0 +1,143 @@
.. SPDX-License-Identifier: GPL-2.0+
.. (C) Copyright 2014-2015 Samsung Electronics
.. sectionauthor:: Przemyslaw Marczak <p.marczak@samsung.com>
PMIC framework based on Driver Model
====================================
Introduction
------------
This is an introduction to driver-model multi uclass PMIC IC's support.
At present it's based on two uclass types:
UCLASS_PMIC:
basic uclass type for PMIC I/O, which provides common
read/write interface.
UCLASS_REGULATOR:
additional uclass type for specific PMIC features, which are
Voltage/Current regulators.
New files:
UCLASS_PMIC:
- drivers/power/pmic/pmic-uclass.c
- include/power/pmic.h
UCLASS_REGULATOR:
- drivers/power/regulator/regulator-uclass.c
- include/power/regulator.h
Commands:
- common/cmd_pmic.c
- common/cmd_regulator.c
How doees it work
-----------------
The Power Management Integrated Circuits (PMIC) are used in embedded systems
to provide stable, precise and specific voltage power source with over-voltage
and thermal protection circuits.
The single PMIC can provide various functions by single or multiple interfaces,
like in the example below::
-- SoC
|
| ______________________________________
| BUS 0 | Multi interface PMIC IC |--> LDO out 1
| e.g.I2C0 | |--> LDO out N
|-----------|---- PMIC device 0 (READ/WRITE ops) |
| or SPI0 | |_ REGULATOR device (ldo/... ops) |--> BUCK out 1
| | |_ CHARGER device (charger ops) |--> BUCK out M
| | |_ MUIC device (microUSB con ops) |
| BUS 1 | |_ ... |---> BATTERY
| e.g.I2C1 | |
|-----------|---- PMIC device 1 (READ/WRITE ops) |---> USB in 1
. or SPI1 | |_ RTC device (rtc ops) |---> USB in 2
. |______________________________________|---> USB out
.
Since U-Boot provides driver model features for I2C and SPI bus drivers,
the PMIC devices should also support this. By the pmic and regulator API's,
PMIC drivers can simply provide a common functions, for multi-interface and
and multi-instance device support.
Basic design assumptions:
- Common I/O API:
UCLASS_PMIC. For the multi-function PMIC devices, this can be used as
parent I/O device for each IC's interface. Then, each children uses the
same dev for read/write.
- Common regulator API:
UCLASS_REGULATOR. For driving the regulator attributes, auto setting
function or command line interface, based on kernel-style regulator device
tree constraints.
For simple implementations, regulator drivers are not required, so the code can
use pmic read/write directly.
Pmic uclass
-----------
The basic information:
* Uclass: 'UCLASS_PMIC'
* Header: 'include/power/pmic.h'
* Core: 'drivers/power/pmic/pmic-uclass.c' (config 'CONFIG_DM_PMIC')
* Command: 'common/cmd_pmic.c' (config 'CONFIG_CMD_PMIC')
* Example: 'drivers/power/pmic/max77686.c'
For detailed API description, please refer to the header file.
As an example of the pmic driver, please refer to the MAX77686 driver.
Please pay attention for the driver's bind() method. Exactly the function call:
'pmic_bind_children()', which is used to bind the regulators by using the array
of regulator's node, compatible prefixes.
The 'pmic; command also supports the new API. So the pmic command can be enabled
by adding CONFIG_CMD_PMIC.
The new pmic command allows to:
- list pmic devices
- choose the current device (like the mmc command)
- read or write the pmic register
- dump all pmic registers
This command can use only UCLASS_PMIC devices, since this uclass is designed
for pmic I/O operations only.
For more information, please refer to the core file.
Regulator uclass
----------------
The basic information:
* Uclass: 'UCLASS_REGULATOR'
* Header: 'include/power/regulator.h'
* Core: 'drivers/power/regulator/regulator-uclass.c'
(config 'CONFIG_DM_REGULATOR')
* Binding: 'doc/device-tree-bindings/regulator/regulator.txt'
* Command: 'common/cmd_regulator.c' (config 'CONFIG_CMD_REGULATOR')
* Example: 'drivers/power/regulator/max77686.c'
'drivers/power/pmic/max77686.c' (required I/O driver for the above)
* Example: 'drivers/power/regulator/fixed.c'
(config 'CONFIG_DM_REGULATOR_FIXED')
For detailed API description, please refer to the header file.
For the example regulator driver, please refer to the MAX77686 regulator driver,
but this driver can't operate without pmic's example driver, which provides an
I/O interface for MAX77686 regulator.
The second example is a fixed Voltage/Current regulator for a common use.
The 'regulator' command also supports the new API. The command allow:
- list regulator devices
- choose the current device (like the mmc command)
- do all regulator-specific operations
For more information, please refer to the command file.
@@ -0,0 +1,169 @@
.. SPDX-License-Identifier: GPL-2.0+
.. (C) Copyright 2015
.. Texas Instruments Incorporated - http://www.ti.com/
Remote Processor Framework
==========================
Introduction
------------
This is an introduction to driver-model for Remote Processors found
on various System on Chip(SoCs). The term remote processor is used to
indicate that this is not the processor on which U-Boot is operating
on, instead is yet another processing entity that may be controlled by
the processor on which we are functional.
The simplified model depends on a single UCLASS - UCLASS_REMOTEPROC
UCLASS_REMOTEPROC:
- drivers/remoteproc/rproc-uclass.c
- include/remoteproc.h
Commands:
- common/cmd_remoteproc.c
Configuration:
- CONFIG_REMOTEPROC is selected by drivers as needed
- CONFIG_CMD_REMOTEPROC for the commands if required.
How does it work - The driver
-----------------------------
Overall, the driver statemachine transitions are typically as follows::
(entry)
+-------+
+---+ init |
| | | <---------------------+
| +-------+ |
| |
| |
| +--------+ |
Load| | reset | |
| | | <----------+ |
| +--------+ | |
| |Load | |
| | | |
| +----v----+ reset | |
+-> | | (opt) | |
| Loaded +-----------+ |
| | |
+----+----+ |
| Start |
+---v-----+ (opt) |
+->| Running | Stop |
Ping +- | +--------------------+
(opt) +---------+
(is_running does not change state)
opt: Optional state transition implemented by driver.
NOTE: It depends on the remote processor as to the exact behavior
of the statemachine, remoteproc core does not intent to implement
statemachine logic. Certain processors may allow start/stop without
reloading the image in the middle, certain other processors may only
allow us to start the processor(image from a EEPROM/OTP) etc.
It is hence the responsibility of the driver to handle the requisite
state transitions of the device as necessary.
Basic design assumptions:
Remote processor can operate on a certain firmware that maybe loaded
and released from reset.
The driver follows a standard UCLASS DM.
in the bare minimum form:
.. code-block:: c
static const struct dm_rproc_ops sandbox_testproc_ops = {
.load = sandbox_testproc_load,
.start = sandbox_testproc_start,
};
static const struct udevice_id sandbox_ids[] = {
{.compatible = "sandbox,test-processor"},
{}
};
U_BOOT_DRIVER(sandbox_testproc) = {
.name = "sandbox_test_proc",
.of_match = sandbox_ids,
.id = UCLASS_REMOTEPROC,
.ops = &sandbox_testproc_ops,
.probe = sandbox_testproc_probe,
};
This allows for the device to be probed as part of the "init" command
or invocation of 'rproc_init()' function as the system dependencies define.
The driver is expected to maintain it's own statemachine which is
appropriate for the device it maintains. It must, at the very least
provide a load and start function. We assume here that the device
needs to be loaded and started, else, there is no real purpose of
using the remoteproc framework.
Describing the device using platform data
-----------------------------------------
*IMPORTANT* NOTE: THIS SUPPORT IS NOT MEANT FOR USE WITH NEWER PLATFORM
SUPPORT. THIS IS ONLY FOR LEGACY DEVICES. THIS MODE OF INITIALIZATION
*WILL* BE EVENTUALLY REMOVED ONCE ALL NECESSARY PLATFORMS HAVE MOVED
TO DM/FDT.
Considering that many platforms are yet to move to device-tree model,
a simplified definition of a device is as follows:
.. code-block:: c
struct dm_rproc_uclass_pdata proc_3_test = {
.name = "proc_3_legacy",
.mem_type = RPROC_INTERNAL_MEMORY_MAPPED,
.driver_plat_data = &mydriver_data;
};
U_BOOT_DRVINFO(proc_3_demo) = {
.name = "sandbox_test_proc",
.plat = &proc_3_test,
};
There can be additional data that may be desired depending on the
remoteproc driver specific needs (for example: SoC integration
details such as clock handle or something similar). See appropriate
documentation for specific remoteproc driver for further details.
These are passed via driver_plat_data.
Describing the device using device tree
---------------------------------------
.. code-block: none
/ {
...
aliases {
...
remoteproc0 = &rproc_1;
remoteproc1 = &rproc_2;
};
...
rproc_1: rproc@1 {
compatible = "sandbox,test-processor";
remoteproc-name = "remoteproc-test-dev1";
};
rproc_2: rproc@2 {
compatible = "sandbox,test-processor";
internal-memory-mapped;
remoteproc-name = "remoteproc-test-dev2";
};
...
};
aliases usage is optional, but it is usually recommended to ensure the
users have a consistent usage model for a platform.
the compatible string used here is specific to the remoteproc driver involved.
+46
View File
@@ -0,0 +1,46 @@
.. SPDX-License-Identifier: GPL-2.0+
How to port a serial driver to driver model
===========================================
Almost all of the serial drivers have been converted as at January 2016. These
ones remain:
* serial_bfin.c
* serial_pxa.c
The deadline for this work was the end of January 2016. If no one steps
forward to convert these, at some point there may come a patch to remove them!
Here is a suggested approach for converting your serial driver over to driver
model. Please feel free to update this file with your ideas and suggestions.
- #ifdef out all your own serial driver code (#ifndef CONFIG_DM_SERIAL)
- Define CONFIG_DM_SERIAL for your board, vendor or architecture
- If the board does not already use driver model, you need CONFIG_DM also
- Your board should then build, but will not boot since there will be no serial
driver
- Add the U_BOOT_DRIVER piece at the end (e.g. copy serial_s5p.c for example)
- Add a private struct for the driver data - avoid using static variables
- Implement each of the driver methods, perhaps by calling your old methods
- You may need to adjust the function parameters so that the old and new
implementations can share most of the existing code
- If you convert all existing users of the driver, remove the pre-driver-model
code
In terms of patches a conversion series typically has these patches:
- clean up / prepare the driver for conversion
- add driver model code
- convert at least one existing board to use driver model serial
- (if no boards remain that don't use driver model) remove the old code
This may be a good time to move your board to use device tree also. Mostly
this involves these steps:
- define CONFIG_OF_CONTROL and CONFIG_OF_SEPARATE
- add your device tree files to arch/<arch>/dts
- update the Makefile there
- Add stdout-path to your /chosen device tree node if it is not already there
- build and get u-boot-dtb.bin so you can test it
- Your drivers can now use device tree
- For device tree in SPL, define CONFIG_SPL_OF_CONTROL
@@ -0,0 +1,68 @@
.. SPDX-License-Identifier: GPL-2.0+
.. (C) Copyright 2020
.. Texas Instruments Incorporated - http://www.ti.com/
SOC ID Framework
================
Introduction
------------
The driver-model SOC ID framework is able to provide identification
information about a specific SoC in use at runtime, and also provide matching
from a set of identification information from an array. This can be useful for
enabling small quirks in drivers that exist between SoC variants that are
impractical to implement using device tree flags. It is based on UCLASS_SOC.
UCLASS_SOC:
- drivers/soc/soc-uclass.c
- include/soc.h
Configuration:
- CONFIG_SOC_DEVICE is selected by drivers as needed.
Implementing a UCLASS_SOC provider
----------------------------------
The purpose of this framework is to allow UCLASS_SOC provider drivers to supply
identification information about the SoC in use at runtime. The framework
allows drivers to define soc_ops that return identification strings. All
soc_ops need not be defined and can be left as NULL, in which case the
framework will return -ENOSYS and not consider the value when doing an
soc_device_match.
It is left to the driver implementor to decide how the information returned is
determined, but in general the same SOC should always return the same set of
identifying information. Information returned must be in the form of a NULL
terminated string.
See include/soc.h for documentation of the available soc_ops and the intended
meaning of the values that can be returned. See drivers/soc/soc_sandbox.c for
an example UCLASS_SOC provider driver.
Using a UCLASS_SOC driver
-------------------------
The framework provides the ability to retrieve and use the identification
strings directly. It also has the ability to return a match from a list of
different sets of SoC data using soc_device_match.
An array of 'struct soc_attr' can be defined, each containing ID information
for a specific SoC, and when passed to soc_device_match, the identifier values
for each entry in the list will be compared against the values provided by the
UCLASS_SOC driver that is in use. The first entry in the list that matches all
non-null values will be returned by soc_device_match.
An example of various uses of the framework can be found at test/dm/soc.c.
Describing the device using device tree
---------------------------------------
.. code-block:: none
chipid: chipid {
compatible = "sandbox,soc";
};
All that is required in a DT node is a compatible for a corresponding
UCLASS_SOC driver.
+692
View File
@@ -0,0 +1,692 @@
.. SPDX-License-Identifier: GPL-2.0+
How to port a SPI driver to driver model
========================================
Here is a rough step-by-step guide. It is based around converting the
exynos SPI driver to driver model (DM) and the example code is based
around U-Boot v2014.10-rc2 (commit be9f643). This has been updated for
v2015.04.
It is quite long since it includes actual code examples.
Before driver model, SPI drivers have their own private structure which
contains 'struct spi_slave'. With driver model, 'struct spi_slave' still
exists, but now it is 'per-child data' for the SPI bus. Each child of the
SPI bus is a SPI slave. The information that was stored in the
driver-specific slave structure can now be port in private data for the
SPI bus.
For example, struct tegra_spi_slave looks like this:
.. code-block:: c
struct tegra_spi_slave {
struct spi_slave slave;
struct tegra_spi_ctrl *ctrl;
};
In this case 'slave' will be in per-child data, and 'ctrl' will be in the
SPI's buses private data.
How long does this take?
------------------------
You should be able to complete this within 2 hours, including testing but
excluding preparing the patches. The API is basically the same as before
with only minor changes:
- methods to set speed and mode are separated out
- cs_info is used to get information on a chip select
Enable driver mode for SPI and SPI flash
----------------------------------------
Add these to your board config:
* CONFIG_DM_SPI
* CONFIG_DM_SPI_FLASH
Add the skeleton
----------------
Put this code at the bottom of your existing driver file:
.. code-block:: c
struct spi_slave *spi_setup_slave(unsigned int busnum, unsigned int cs,
unsigned int max_hz, unsigned int mode)
{
return NULL;
}
struct spi_slave *spi_setup_slave_fdt(const void *blob, int slave_node,
int spi_node)
{
return NULL;
}
static int exynos_spi_of_to_plat(struct udevice *dev)
{
return -ENODEV;
}
static int exynos_spi_probe(struct udevice *dev)
{
return -ENODEV;
}
static int exynos_spi_remove(struct udevice *dev)
{
return -ENODEV;
}
static int exynos_spi_claim_bus(struct udevice *dev)
{
return -ENODEV;
}
static int exynos_spi_release_bus(struct udevice *dev)
{
return -ENODEV;
}
static int exynos_spi_xfer(struct udevice *dev, unsigned int bitlen,
const void *dout, void *din, unsigned long flags)
{
return -ENODEV;
}
static int exynos_spi_set_speed(struct udevice *dev, uint speed)
{
return -ENODEV;
}
static int exynos_spi_set_mode(struct udevice *dev, uint mode)
{
return -ENODEV;
}
static int exynos_cs_info(struct udevice *bus, uint cs,
struct spi_cs_info *info)
{
return -EINVAL;
}
static const struct dm_spi_ops exynos_spi_ops = {
.claim_bus = exynos_spi_claim_bus,
.release_bus = exynos_spi_release_bus,
.xfer = exynos_spi_xfer,
.set_speed = exynos_spi_set_speed,
.set_mode = exynos_spi_set_mode,
.cs_info = exynos_cs_info,
};
static const struct udevice_id exynos_spi_ids[] = {
{ .compatible = "samsung,exynos-spi" },
{ }
};
U_BOOT_DRIVER(exynos_spi) = {
.name = "exynos_spi",
.id = UCLASS_SPI,
.of_match = exynos_spi_ids,
.ops = &exynos_spi_ops,
.of_to_plat = exynos_spi_of_to_plat,
.probe = exynos_spi_probe,
.remove = exynos_spi_remove,
};
Replace 'exynos' in the above code with your driver name
--------------------------------------------------------
#ifdef out all of the code in your driver except for the above
--------------------------------------------------------------
This will allow you to get it building, which means you can work
incrementally. Since all the methods return an error initially, there is
less chance that you will accidentally leave something in.
Also, even though your conversion is basically a rewrite, it might help
reviewers if you leave functions in the same place in the file,
particularly for large drivers.
Add some includes
-----------------
Add these includes to your driver:
.. code-block:: c
#include <dm.h>
#include <errno.h>
Build
-----
At this point you should be able to build U-Boot for your board with the
empty SPI driver. You still have empty methods in your driver, but we will
write these one by one.
Set up your platform data structure
-----------------------------------
This will hold the information your driver to operate, like its hardware
address or maximum frequency.
You may already have a struct like this, or you may need to create one
from some of the #defines or global variables in the driver.
Note that this information is not the run-time information. It should not
include state that changes. It should be fixed throughout the live of
U-Boot. Run-time information comes later.
Here is what was in the exynos spi driver:
.. code-block:: c
struct spi_bus {
enum periph_id periph_id;
s32 frequency; /* Default clock frequency, -1 for none */
struct exynos_spi *regs;
int inited; /* 1 if this bus is ready for use */
int node;
uint deactivate_delay_us; /* Delay to wait after deactivate */
};
Of these, inited is handled by DM and node is the device tree node, which
DM tells you. The name is not quite right. So in this case we would use:
.. code-block:: c
struct exynos_spi_plat {
enum periph_id periph_id;
s32 frequency; /* Default clock frequency, -1 for none */
struct exynos_spi *regs;
uint deactivate_delay_us; /* Delay to wait after deactivate */
};
Write of_to_plat() [for device tree only]
-------------------------------------------------
This method will convert information in the device tree node into a C
structure in your driver (called platform data). If you are not using
device tree, go to 8b.
DM will automatically allocate the struct for us when we are using device
tree, but we need to tell it the size:
.. code-block:: c
U_BOOT_DRIVER(spi_exynos) = {
...
.plat_auto = sizeof(struct exynos_spi_plat),
Here is a sample function. It gets a pointer to the platform data and
fills in the fields from device tree.
.. code-block:: c
static int exynos_spi_of_to_plat(struct udevice *bus)
{
struct exynos_spi_plat *plat = bus->plat;
const void *blob = gd->fdt_blob;
int node = dev_of_offset(bus);
plat->regs = (struct exynos_spi *)fdtdec_get_addr(blob, node, "reg");
plat->periph_id = pinmux_decode_periph_id(blob, node);
if (plat->periph_id == PERIPH_ID_NONE) {
debug("%s: Invalid peripheral ID %d\n", __func__,
plat->periph_id);
return -FDT_ERR_NOTFOUND;
}
/* Use 500KHz as a suitable default */
plat->frequency = fdtdec_get_int(blob, node, "spi-max-frequency",
500000);
plat->deactivate_delay_us = fdtdec_get_int(blob, node,
"spi-deactivate-delay", 0);
debug("%s: regs=%p, periph_id=%d, max-frequency=%d, deactivate_delay=%d\n",
__func__, plat->regs, plat->periph_id, plat->frequency,
plat->deactivate_delay_us);
return 0;
}
Add the platform data [non-device-tree only]
--------------------------------------------
Specify this data in a U_BOOT_DRVINFO() declaration in your board file:
.. code-block:: c
struct exynos_spi_plat platdata_spi0 = {
.periph_id = ...
.frequency = ...
.regs = ...
.deactivate_delay_us = ...
};
U_BOOT_DRVINFO(board_spi0) = {
.name = "exynos_spi",
.plat = &platdata_spi0,
};
You will unfortunately need to put the struct definition into a header file
in this case so that your board file can use it.
Add the device private data
---------------------------
Most devices have some private data which they use to keep track of things
while active. This is the run-time information and needs to be stored in
a structure. There is probably a structure in the driver that includes a
'struct spi_slave', so you can use that.
.. code-block:: c
struct exynos_spi_slave {
struct spi_slave slave;
struct exynos_spi *regs;
unsigned int freq; /* Default frequency */
unsigned int mode;
enum periph_id periph_id; /* Peripheral ID for this device */
unsigned int fifo_size;
int skip_preamble;
struct spi_bus *bus; /* Pointer to our SPI bus info */
ulong last_transaction_us; /* Time of last transaction end */
};
We should rename this to make its purpose more obvious, and get rid of
the slave structure, so we have:
.. code-block:: c
struct exynos_spi_priv {
struct exynos_spi *regs;
unsigned int freq; /* Default frequency */
unsigned int mode;
enum periph_id periph_id; /* Peripheral ID for this device */
unsigned int fifo_size;
int skip_preamble;
ulong last_transaction_us; /* Time of last transaction end */
};
DM can auto-allocate this also:
.. code-block:: c
U_BOOT_DRIVER(spi_exynos) = {
...
.priv_auto = sizeof(struct exynos_spi_priv),
Note that this is created before the probe method is called, and destroyed
after the remove method is called. It will be zeroed when the probe
method is called.
Add the probe() and remove() methods
------------------------------------
Note: It's a good idea to build repeatedly as you are working, to avoid a
huge amount of work getting things compiling at the end.
The probe method is supposed to set up the hardware. U-Boot used to use
spi_setup_slave() to do this. So take a look at this function and see
what you can copy out to set things up.
.. code-block:: c
static int exynos_spi_probe(struct udevice *bus)
{
struct exynos_spi_plat *plat = dev_get_plat(bus);
struct exynos_spi_priv *priv = dev_get_priv(bus);
priv->regs = plat->regs;
if (plat->periph_id == PERIPH_ID_SPI1 ||
plat->periph_id == PERIPH_ID_SPI2)
priv->fifo_size = 64;
else
priv->fifo_size = 256;
priv->skip_preamble = 0;
priv->last_transaction_us = timer_get_us();
priv->freq = plat->frequency;
priv->periph_id = plat->periph_id;
return 0;
}
This implementation doesn't actually touch the hardware, which is somewhat
unusual for a driver. In this case we will do that when the device is
claimed by something that wants to use the SPI bus.
For remove we could shut down the clocks, but in this case there is
nothing to do. DM frees any memory that it allocated, so we can just
remove exynos_spi_remove() and its reference in U_BOOT_DRIVER.
Implement set_speed()
---------------------
This should set up clocks so that the SPI bus is running at the right
speed. With the old API spi_claim_bus() would normally do this and several
of the following functions, so let's look at that function:
.. code-block:: c
int spi_claim_bus(struct spi_slave *slave)
{
struct exynos_spi_slave *spi_slave = to_exynos_spi(slave);
struct exynos_spi *regs = spi_slave->regs;
u32 reg = 0;
int ret;
ret = set_spi_clk(spi_slave->periph_id,
spi_slave->freq);
if (ret < 0) {
debug("%s: Failed to setup spi clock\n", __func__);
return ret;
}
exynos_pinmux_config(spi_slave->periph_id, PINMUX_FLAG_NONE);
spi_flush_fifo(slave);
reg = readl(&regs->ch_cfg);
reg &= ~(SPI_CH_CPHA_B | SPI_CH_CPOL_L);
if (spi_slave->mode & SPI_CPHA)
reg |= SPI_CH_CPHA_B;
if (spi_slave->mode & SPI_CPOL)
reg |= SPI_CH_CPOL_L;
writel(reg, &regs->ch_cfg);
writel(SPI_FB_DELAY_180, &regs->fb_clk);
return 0;
}
It sets up the speed, mode, pinmux, feedback delay and clears the FIFOs.
With DM these will happen in separate methods.
Here is an example for the speed part:
.. code-block:: c
static int exynos_spi_set_speed(struct udevice *bus, uint speed)
{
struct exynos_spi_plat *plat = bus->plat;
struct exynos_spi_priv *priv = dev_get_priv(bus);
int ret;
if (speed > plat->frequency)
speed = plat->frequency;
ret = set_spi_clk(priv->periph_id, speed);
if (ret)
return ret;
priv->freq = speed;
debug("%s: regs=%p, speed=%d\n", __func__, priv->regs, priv->freq);
return 0;
}
Implement set_mode()
--------------------
This should adjust the SPI mode (polarity, etc.). Again this code probably
comes from the old spi_claim_bus(). Here is an example:
.. code-block:: c
static int exynos_spi_set_mode(struct udevice *bus, uint mode)
{
struct exynos_spi_priv *priv = dev_get_priv(bus);
uint32_t reg;
reg = readl(&priv->regs->ch_cfg);
reg &= ~(SPI_CH_CPHA_B | SPI_CH_CPOL_L);
if (mode & SPI_CPHA)
reg |= SPI_CH_CPHA_B;
if (mode & SPI_CPOL)
reg |= SPI_CH_CPOL_L;
writel(reg, &priv->regs->ch_cfg);
priv->mode = mode;
debug("%s: regs=%p, mode=%d\n", __func__, priv->regs, priv->mode);
return 0;
}
Implement claim_bus()
---------------------
This is where a client wants to make use of the bus, so claims it first.
At this point we need to make sure everything is set up ready for data
transfer. Note that this function is wholly internal to the driver - at
present the SPI uclass never calls it.
Here again we look at the old claim function and see some code that is
needed. It is anything unrelated to speed and mode:
.. code-block:: c
static int exynos_spi_claim_bus(struct udevice *bus)
{
struct exynos_spi_priv *priv = dev_get_priv(bus);
exynos_pinmux_config(priv->periph_id, PINMUX_FLAG_NONE);
spi_flush_fifo(priv->regs);
writel(SPI_FB_DELAY_180, &priv->regs->fb_clk);
return 0;
}
The spi_flush_fifo() function is in the removed part of the code, so we
need to expose it again (perhaps with an #endif before it and '#if 0'
after it). It only needs access to priv->regs which is why we have
passed that in:
.. code-block:: c
/**
* Flush spi tx, rx fifos and reset the SPI controller
*
* @param regs Pointer to SPI registers
*/
static void spi_flush_fifo(struct exynos_spi *regs)
{
clrsetbits_le32(&regs->ch_cfg, SPI_CH_HS_EN, SPI_CH_RST);
clrbits_le32(&regs->ch_cfg, SPI_CH_RST);
setbits_le32(&regs->ch_cfg, SPI_TX_CH_ON | SPI_RX_CH_ON);
}
Implement release_bus()
-----------------------
This releases the bus - in our example the old code in spi_release_bus()
is a call to spi_flush_fifo, so we add:
.. code-block:: c
static int exynos_spi_release_bus(struct udevice *bus)
{
struct exynos_spi_priv *priv = dev_get_priv(bus);
spi_flush_fifo(priv->regs);
return 0;
}
Implement xfer()
----------------
This is the final method that we need to create, and it is where all the
work happens. The method parameters are the same as the old spi_xfer() with
the addition of a 'struct udevice' so conversion is pretty easy. Start
by copying the contents of spi_xfer() to your new xfer() method and proceed
from there.
If (flags & SPI_XFER_BEGIN) is non-zero then xfer() normally calls an
activate function, something like this:
.. code-block:: c
void spi_cs_activate(struct spi_slave *slave)
{
struct exynos_spi_slave *spi_slave = to_exynos_spi(slave);
/* If it's too soon to do another transaction, wait */
if (spi_slave->bus->deactivate_delay_us &&
spi_slave->last_transaction_us) {
ulong delay_us; /* The delay completed so far */
delay_us = timer_get_us() - spi_slave->last_transaction_us;
if (delay_us < spi_slave->bus->deactivate_delay_us)
udelay(spi_slave->bus->deactivate_delay_us - delay_us);
}
clrbits_le32(&spi_slave->regs->cs_reg, SPI_SLAVE_SIG_INACT);
debug("Activate CS, bus %d\n", spi_slave->slave.bus);
spi_slave->skip_preamble = spi_slave->mode & SPI_PREAMBLE;
}
The new version looks like this:
.. code-block:: c
static void spi_cs_activate(struct udevice *dev)
{
struct udevice *bus = dev->parent;
struct exynos_spi_plat *pdata = dev_get_plat(bus);
struct exynos_spi_priv *priv = dev_get_priv(bus);
/* If it's too soon to do another transaction, wait */
if (pdata->deactivate_delay_us &&
priv->last_transaction_us) {
ulong delay_us; /* The delay completed so far */
delay_us = timer_get_us() - priv->last_transaction_us;
if (delay_us < pdata->deactivate_delay_us)
udelay(pdata->deactivate_delay_us - delay_us);
}
clrbits_le32(&priv->regs->cs_reg, SPI_SLAVE_SIG_INACT);
debug("Activate CS, bus '%s'\n", bus->name);
priv->skip_preamble = priv->mode & SPI_PREAMBLE;
}
All we have really done here is change the pointers and print the device name
instead of the bus number. Other local static functions can be treated in
the same way.
Set up the per-child data and child pre-probe function
------------------------------------------------------
To minimise the pain and complexity of the SPI subsystem while the driver
model change-over is in place, struct spi_slave is used to reference a
SPI bus slave, even though that slave is actually a struct udevice. In fact
struct spi_slave is the device's child data. We need to make sure this space
is available. It is possible to allocate more space that struct spi_slave
needs, but this is the minimum.
.. code-block:: c
U_BOOT_DRIVER(exynos_spi) = {
...
.per_child_auto = sizeof(struct spi_slave),
}
Optional: Set up cs_info() if you want it
-----------------------------------------
Sometimes it is useful to know whether a SPI chip select is valid, but this
is not obvious from outside the driver. In this case you can provide a
method for cs_info() to deal with this. If you don't provide it, then the
device tree will be used to determine what chip selects are valid.
Return -EINVAL if the supplied chip select is invalid, or 0 if it is valid.
If you don't provide the cs_info() method, 0 is assumed for all chip selects
that do not appear in the device tree.
Test it
-------
Now that you have the code written and it compiles, try testing it using
the 'sf test' command. You may need to enable CONFIG_CMD_SF_TEST for your
board.
Prepare patches and send them to the mailing lists
--------------------------------------------------
You can use 'tools/patman/patman' to prepare, check and send patches for
your work. See tools/patman/README for details.
A little note about SPI uclass features
---------------------------------------
The SPI uclass keeps some information about each device 'dev' on the bus:
struct dm_spi_slave_plat:
This is device_get_parent_plat(dev).
This is where the chip select number is stored, along with
the default bus speed and mode. It is automatically read
from the device tree in spi_child_post_bind(). It must not
be changed at run-time after being set up because platform
data is supposed to be immutable at run-time.
struct spi_slave:
This is device_get_parentdata(dev).
Already mentioned above. It holds run-time information about
the device.
There are also some SPI uclass methods that get called behind the scenes:
spi_post_bind():
Called when a new bus is bound.
This scans the device tree for devices on the bus, and binds
each one. This in turn causes spi_child_post_bind() to be
called for each, which reads the device tree information
into the parent (per-child) platform data.
spi_child_post_bind():
Called when a new child is bound.
As mentioned above this reads the device tree information
into the per-child platform data
spi_child_pre_probe():
Called before a new child is probed.
This sets up the mode and speed in struct spi_slave by
copying it from the parent's platform data for this child.
It also sets the 'dev' pointer, needed to permit passing
'struct spi_slave' around the place without needing a
separate 'struct udevice' pointer.
The above housekeeping makes it easier to write your SPI driver.
+423
View File
@@ -0,0 +1,423 @@
.. SPDX-License-Identifier: GPL-2.0+
How USB works with driver model
===============================
Introduction
------------
Driver model USB support makes use of existing features but changes how
drivers are found. This document provides some information intended to help
understand how things work with USB in U-Boot when driver model is enabled.
Enabling driver model for USB
-----------------------------
A new CONFIG_DM_USB option is provided to enable driver model for USB. This
causes the USB uclass to be included, and drops the equivalent code in
usb.c. In particular the usb_init() function is then implemented by the
uclass.
Support for EHCI and XHCI
-------------------------
So far OHCI is not supported. Both EHCI and XHCI drivers should be declared
as drivers in the USB uclass. For example:
.. code-block:: c
static const struct udevice_id ehci_usb_ids[] = {
{ .compatible = "nvidia,tegra20-ehci", .data = USB_CTLR_T20 },
{ .compatible = "nvidia,tegra30-ehci", .data = USB_CTLR_T30 },
{ .compatible = "nvidia,tegra114-ehci", .data = USB_CTLR_T114 },
{ }
};
U_BOOT_DRIVER(usb_ehci) = {
.name = "ehci_tegra",
.id = UCLASS_USB,
.of_match = ehci_usb_ids,
.of_to_plat = ehci_usb_of_to_plat,
.probe = tegra_ehci_usb_probe,
.remove = tegra_ehci_usb_remove,
.ops = &ehci_usb_ops,
.plat_auto = sizeof(struct usb_plat),
.priv_auto = sizeof(struct fdt_usb),
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};
Here ehci_usb_ids is used to list the controllers that the driver supports.
Each has its own data value. Controllers must be in the UCLASS_USB uclass.
The of_to_plat() method allows the controller driver to grab any
necessary settings from the device tree.
The ops here are ehci_usb_ops. All EHCI drivers will use these same ops in
most cases, since they are all EHCI-compatible. For EHCI there are also some
special operations that can be overridden when calling ehci_register().
The driver can use priv_auto to set the size of its private data.
This can hold run-time information needed by the driver for operation. It
exists when the device is probed (not when it is bound) and is removed when
the driver is removed.
Note that usb_plat is currently only used to deal with setting up a bus
in USB device mode (OTG operation). It can be omitted if that is not
supported.
The driver's probe() method should do the basic controller init and then
call ehci_register() to register itself as an EHCI device. It should call
ehci_deregister() in the remove() method. Registering a new EHCI device
does not by itself cause the bus to be scanned.
The old ehci_hcd_init() function is no-longer used. Nor is it necessary to
set up the USB controllers from board init code. When 'usb start' is used,
each controller will be probed and its bus scanned.
XHCI works in a similar way.
Data structures
---------------
The following primary data structures are in use:
- struct usb_device:
This holds information about a device on the bus. All devices have
this structure, even the root hub. The controller itself does not
have this structure. You can access it for a device 'dev' with
dev_get_parent_priv(dev). It matches the old structure except that the
parent and child information is not present (since driver model
handles that). Once the device is set up, you can find the device
descriptor and current configuration descriptor in this structure.
- struct usb_plat:
This holds platform data for a controller. So far this is only used
as a work-around for controllers which can act as USB devices in OTG
mode, since the gadget framework does not use driver model.
- struct usb_dev_plat:
This holds platform data for a device. You can access it for a
device 'dev' with dev_get_parent_plat(dev). It holds the device
address and speed - anything that can be determined before the device
driver is actually set up. When probing the bus this structure is
used to provide essential information to the device driver.
- struct usb_bus_priv:
This is private information for each controller, maintained by the
controller uclass. It is mostly used to keep track of the next
device address to use.
Of these, only struct usb_device was used prior to driver model.
USB buses
---------
Given a controller, you know the bus - it is the one attached to the
controller. Each controller handles exactly one bus. Every controller has a
root hub attached to it. This hub, which is itself a USB device, can provide
one or more 'ports' to which additional devices can be attached. It is
possible to power up a hub and find out which of its ports have devices
attached.
Devices are given addresses starting at 1. The root hub is always address 1,
and from there the devices are numbered in sequence. The USB uclass takes
care of this numbering automatically during enumeration.
USB devices are enumerated by finding a device on a particular hub, and
setting its address to the next available address. The USB bus stretches out
in a tree structure, potentially with multiple hubs each with several ports
and perhaps other hubs. Some hubs will have their own power since otherwise
the 5V 500mA power supplied by the controller will not be sufficient to run
very many devices.
Enumeration in U-Boot takes a long time since devices are probed one at a
time, and each is given sufficient time to wake up and announce itself. The
timeouts are set for the slowest device.
Up to 127 devices can be on each bus. USB has four bus speeds: low
(1.5Mbps), full (12Mbps), high (480Mbps) which is only available with USB2
and newer (EHCI), and super (5Gbps) which is only available with USB3 and
newer (XHCI). If you connect a super-speed device to a high-speed hub, you
will only get high-speed.
USB operations
--------------
As before driver model, messages can be sent using submit_bulk_msg() and the
like. These are now implemented by the USB uclass and route through the
controller drivers. Note that messages are not sent to the driver of the
device itself - i.e. they don't pass down the stack to the controller.
U-Boot simply finds the controller to which the device is attached, and sends
the message there with an appropriate 'pipe' value so it can be addressed
properly. Having said that, the USB device which should receive the message
is passed in to the driver methods, for use by sandbox. This design decision
is open for review and the code impact of changing it is small since the
methods are typically implemented by the EHCI and XHCI stacks.
Controller drivers (in UCLASS_USB) themselves provide methods for sending
each message type. For XHCI an additional alloc_device() method is provided
since XHCI needs to allocate a device context before it can even read the
device's descriptor.
These methods use a 'pipe' which is a collection of bit fields used to
describe the type of message, direction of transfer and the intended
recipient (device number).
USB Devices
-----------
USB devices are found using a simple algorithm which works through the
available hubs in a depth-first search. Devices can be in any uclass, but
are attached to a parent hub (or controller in the case of the root hub) and
so have parent data attached to them (this is struct usb_device).
By the time the device's probe() method is called, it is enumerated and is
ready to talk to the host.
The enumeration process needs to work out which driver to attach to each USB
device. It does this by examining the device class, interface class, vendor
ID, product ID, etc. See struct usb_driver_entry for how drivers are matched
with USB devices - you can use the USB_DEVICE() macro to declare a USB
driver. For example, usb_storage.c defines a USB_DEVICE() to handle storage
devices, and it will be used for all USB devices which match.
Technical details on enumeration flow
-------------------------------------
It is useful to understand precisely how a USB bus is enumerating to avoid
confusion when dealing with USB devices.
Device initialisation happens roughly like this:
- At some point the 'usb start' command is run
- This calls usb_init() which works through each controller in turn
- The controller is probed(). This does no enumeration.
- Then usb_scan_bus() is called. This calls usb_scan_device() to scan the
(only) device that is attached to the controller - a root hub
- usb_scan_device() sets up a fake struct usb_device and calls
usb_setup_device(), passing the port number to be scanned, in this case
port 0
- usb_setup_device() first calls usb_prepare_device() to set the device
address, then usb_select_config() to select the first configuration
- at this point the device is enumerated but we do not have a real struct
udevice for it. But we do have the descriptor in struct usb_device so we can
use this to figure out what driver to use
- back in usb_scan_device(), we call usb_find_child() to try to find an
existing device which matches the one we just found on the bus. This can
happen if the device is mentioned in the device tree, or if we previously
scanned the bus and so the device was created before
- if usb_find_child() does not find an existing device, we call
usb_find_and_bind_driver() which tries to bind one
- usb_find_and_bind_driver() searches all available USB drivers (declared
with USB_DEVICE()). If it finds a match it binds that driver to create a
new device.
- If it does not, it binds a generic driver. A generic driver is good enough
to allow access to the device (sending it packets, etc.) but all
functionality will need to be implemented outside the driver model.
- in any case, when usb_find_child() and/or usb_find_and_bind_driver() are
done, we have a device with the correct uclass. At this point we want to
probe the device
- first we store basic information about the new device (address, port,
speed) in its parent platform data. We cannot store it its private data
since that will not exist until the device is probed.
- then we call device_probe() which probes the device
- the first probe step is actually the USB controller's (or USB hubs's)
child_pre_probe() method. This gets called before anything else and is
intended to set up a child device ready to be used with its parent bus. For
USB this calls usb_child_pre_probe() which grabs the information that was
stored in the parent platform data and stores it in the parent private data
(which is struct usb_device, a real one this time). It then calls
usb_select_config() again to make sure that everything about the device is
set up
- note that we have called usb_select_config() twice. This is inefficient
but the alternative is to store additional information in the platform data.
The time taken is minimal and this way is simpler
- at this point the device is set up and ready for use so far as the USB
subsystem is concerned
- the device's probe() method is then called. It can send messages and do
whatever else it wants to make the device work.
Note that the first device is always a root hub, and this must be scanned to
find any devices. The above steps will have created a hub (UCLASS_USB_HUB),
given it address 1 and set the configuration.
For hubs, the hub uclass has a post_probe() method. This means that after
any hub is probed, the uclass gets to do some processing. In this case
usb_hub_post_probe() is called, and the following steps take place:
- usb_hub_post_probe() calls usb_hub_scan() to scan the hub, which in turn
calls usb_hub_configure()
- hub power is enabled
- we loop through each port on the hub, performing the same steps for each
- first, check if there is a device present. This happens in
usb_hub_port_connect_change(). If so, then usb_scan_device() is called to
scan the device, passing the appropriate port number.
- you will recognise usb_scan_device() from the steps above. It sets up the
device ready for use. If it is a hub, it will scan that hub before it
continues here (recursively, depth-first)
- once all hub ports are scanned in this way, the hub is ready for use and
all of its downstream devices also
- additional controllers are scanned in the same way
The above method has some nice properties:
- the bus enumeration happens by virtue of driver model's natural device flow
- most logic is in the USB controller and hub uclasses; the actual device
drivers do not need to know they are on a USB bus, at least so far as
enumeration goes
- hub scanning happens automatically after a hub is probed
Hubs
----
USB hubs are scanned as in the section above. While hubs have their own
uclass, they share some common elements with controllers:
- they both attach private data to their children (struct usb_device,
accessible for a child with dev_get_parent_priv(child))
- they both use usb_child_pre_probe() to set up their children as proper USB
devices
Example - Mass Storage
----------------------
As an example of a USB device driver, see usb_storage.c. It uses its own
uclass and declares itself as follows:
.. code-block:: c
U_BOOT_DRIVER(usb_mass_storage) = {
.name = "usb_mass_storage",
.id = UCLASS_MASS_STORAGE,
.of_match = usb_mass_storage_ids,
.probe = usb_mass_storage_probe,
};
static const struct usb_device_id mass_storage_id_table[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_CLASS_MASS_STORAGE},
{ } /* Terminating entry */
};
USB_DEVICE(usb_mass_storage, mass_storage_id_table);
The USB_DEVICE() macro attaches the given table of matching information to
the given driver. Note that the driver is declared in U_BOOT_DRIVER() as
'usb_mass_storage' and this must match the first parameter of USB_DEVICE.
When usb_find_and_bind_driver() is called on a USB device with the
bInterfaceClass value of USB_CLASS_MASS_STORAGE, it will automatically find
this driver and use it.
Counter-example: USB Ethernet
-----------------------------
As an example of the old way of doing things, see usb_ether.c. When the bus
is scanned, all Ethernet devices will be created as generic USB devices (in
uclass UCLASS_USB_DEV_GENERIC). Then, when the scan is completed,
usb_host_eth_scan() will be called. This looks through all the devices on
each bus and manually figures out which are Ethernet devices in the ways of
yore.
In fact, usb_ether should be moved to driver model. Each USB Ethernet driver
(e.g drivers/usb/eth/asix.c) should include a USB_DEVICE() declaration, so
that it will be found as part of normal USB enumeration. Then, instead of a
generic USB driver, a real (driver-model-aware) driver will be used. Since
Ethernet now supports driver model, this should be fairly easy to achieve,
and then usb_ether.c and the usb_host_eth_scan() will melt away.
Sandbox
-------
All driver model uclasses must have tests and USB is no exception. To
achieve this, a sandbox USB controller is provided. This can make use of
emulation drivers which pretend to be USB devices. Emulations are provided
for a hub and a flash stick. These are enough to create a pretend USB bus
(defined by the sandbox device tree sandbox.dts) which can be scanned and
used.
Tests in test/dm/usb.c make use of this feature. It allows much of the USB
stack to be tested without real hardware being needed.
Here is an example device tree fragment:
.. code-block:: none
usb@1 {
compatible = "sandbox,usb";
hub {
compatible = "usb-hub";
usb,device-class = <USB_CLASS_HUB>;
hub-emul {
compatible = "sandbox,usb-hub";
#address-cells = <1>;
#size-cells = <0>;
flash-stick {
reg = <0>;
compatible = "sandbox,usb-flash";
sandbox,filepath = "flash.bin";
};
};
};
};
This defines a single controller, containing a root hub (which is required).
The hub is emulated by a hub emulator, and the emulated hub has a single
flash stick to emulate on one of its ports.
When 'usb start' is used, the following 'dm tree' output will be available::
usb [ + ] `-- usb@1
usb_hub [ + ] `-- hub
usb_emul [ + ] |-- hub-emul
usb_emul [ + ] | `-- flash-stick
usb_mass_st [ + ] `-- usb_mass_storage
This may look confusing. Most of it mirrors the device tree, but the
'usb_mass_storage' device is not in the device tree. This is created by
usb_find_and_bind_driver() based on the USB_DRIVER in usb_storage.c. While
'flash-stick' is the emulation device, 'usb_mass_storage' is the real U-Boot
USB device driver that talks to it.
Future work
-----------
It is pretty uncommon to have a large USB bus with lots of hubs on an
embedded system. In fact anything other than a root hub is uncommon. Still
it would be possible to speed up enumeration in two ways:
- breadth-first search would allow devices to be reset and probed in
parallel to some extent
- enumeration could be lazy, in the sense that we could enumerate just the
root hub at first, then only progress to the next 'level' when a device is
used that we cannot find. This could be made easier if the devices were
statically declared in the device tree (which is acceptable for production
boards where the same, known, things are on each bus).
But in common cases the current algorithm is sufficient.
Other things that need doing:
- Convert usb_ether to use driver model as described above
- Test that keyboards work (and convert to driver model)
- Move the USB gadget framework to driver model
- Implement OHCI in driver model
- Implement USB PHYs in driver model
- Work out a clever way to provide lazy init for USB devices
.. Simon Glass <sjg@chromium.org>
.. 23-Mar-15
+10
View File
@@ -10,9 +10,11 @@ Implementation
:maxdepth: 1
commands
driver-model/index
global_data
logging
menus
uefi/index
version
Debugging
@@ -24,6 +26,14 @@ Debugging
crash_dumps
trace
Packaging
---------
.. toctree::
:maxdepth: 1
package/index
Testing
-------
+1
View File
@@ -0,0 +1 @@
../../../tools/binman/binman.rst
+1
View File
@@ -0,0 +1 @@
../../../tools/binman/entries.rst
+19
View File
@@ -0,0 +1,19 @@
.. SPDX-License-Identifier: GPL-2.0+
Package U-Boot
==============
U-Boot uses Flat Image Tree (FIT) as a standard file format for packaging
images that it it reads and boots. Documentation about FIT is available at
doc/uImage.FIT
U-Boot also provides binman for cases not covered by FIT. Examples include
initial execution (since FIT itself does not have an executable header) and
dealing with device boundaries, such as the read-only/read-write separation in
SPI flash.
.. toctree::
:maxdepth: 2
binman
+15
View File
@@ -0,0 +1,15 @@
.. SPDX-License-Identifier: GPL-2.0+
Unified Extensible Firmware (UEFI)
==================================
U-Boot provides an implementation of the UEFI API allowing to run UEFI
compliant software like Linux, GRUB, and iPXE. Furthermore U-Boot itself
can be run an UEFI payload.
.. toctree::
:maxdepth: 2
uefi.rst
u-boot_on_efi.rst
iscsi.rst
+184
View File
@@ -0,0 +1,184 @@
.. SPDX-License-Identifier: GPL-2.0+
.. Copyright (c) 2018 Heinrich Schuchardt
iSCSI booting with U-Boot and iPXE
==================================
Motivation
----------
U-Boot has only a reduced set of supported network protocols. The focus for
network booting has been on UDP based protocols. A TCP stack and HTTP support
are expected to be integrated in 2018 together with a wget command.
For booting a diskless computer this leaves us with BOOTP or DHCP to get the
address of a boot script. TFTP or NFS can be used to load the boot script, the
operating system kernel and the initial file system (initrd).
These protocols are insecure. The client cannot validate the authenticity
of the contacted servers. And the server cannot verify the identity of the
client.
Furthermore the services providing the operating system loader or kernel are
not the ones that the operating system typically will use. Especially in a SAN
environment this makes updating the operating system a hassle. After installing
a new kernel version the boot files have to be copied to the TFTP server
directory.
The HTTPS protocol provides certificate based validation of servers. Sensitive
data like passwords can be securely transmitted.
The iSCSI protocol is used for connecting storage attached networks. It
provides mutual authentication using the CHAP protocol. It typically runs on
a TCP transport.
Thus a better solution than DHCP/TFTP/NFS boot would be to load a boot script
via HTTPS and to download any other files needed for booting via iSCSI from the
same target where the operating system is installed.
An alternative to implementing these protocols in U-Boot is to use an existing
software that can run on top of U-Boot. iPXE[1] is the "swiss army knife" of
network booting. It supports both HTTPS and iSCSI. It has a scripting engine for
fine grained control of the boot process and can provide a command shell.
iPXE can be built as an EFI application (named snp.efi) which can be loaded and
run by U-Boot.
Boot sequence
-------------
U-Boot loads the EFI application iPXE snp.efi using the bootefi command. This
application has network access via the simple network protocol offered by
U-Boot.
iPXE executes its internal script. This script may optionally chain load a
secondary boot script via HTTPS or open a shell.
For the further boot process iPXE connects to the iSCSI server. This includes
the mutual authentication using the CHAP protocol. After the authentication iPXE
has access to the iSCSI targets.
For a selected iSCSI target iPXE sets up a handle with the block IO protocol. It
uses the ConnectController boot service of U-Boot to request U-Boot to connect a
file system driver. U-Boot reads from the iSCSI drive via the block IO protocol
offered by iPXE. It creates the partition handles and installs the simple file
protocol. Now iPXE can call the simple file protocol to load GRUB[2]. U-Boot
uses the block IO protocol offered by iPXE to fulfill the request.
Once GRUB is started it uses the same block IO protocol to load Linux. Via
the EFI stub Linux is called as an EFI application::
+--------+ +--------+
| | Runs | |
| U-Boot |========>| iPXE |
| EFI | | snp.efi|
+--------+ | | DHCP | |
| |<===|********|<========| |
| DHCP | | | Get IP | |
| Server | | | Address | |
| |===>|********|========>| |
+--------+ | | Response| |
| | | |
| | | |
+--------+ | | HTTPS | |
| |<===|********|<========| |
| HTTPS | | | Load | |
| Server | | | Script | |
| |===>|********|========>| |
+--------+ | | | |
| | | |
| | | |
+--------+ | | iSCSI | |
| |<===|********|<========| |
| iSCSI | | | Auth | |
| Server |===>|********|========>| |
| | | | | |
| | | | Loads | |
| |<===|********|<========| | +--------+
| | | | GRUB | | Runs | |
| |===>|********|========>| |======>| GRUB |
| | | | | | | |
| | | | | | | |
| | | | | | Loads | |
| |<===|********|<========|********|<======| | +--------+
| | | | | | Linux | | Runs | |
| |===>|********|========>|********|======>| |=====>| Linux |
| | | | | | | | | |
+--------+ +--------+ +--------+ +--------+ | |
| |
| |
| ~ ~ ~ ~|
Security
--------
The iSCSI protocol is not encrypted. The traffic could be secured using IPsec
but neither U-Boot nor iPXE does support this. So we should at least separate
the iSCSI traffic from all other network traffic. This can be achieved using a
virtual local area network (VLAN).
Configuration
-------------
iPXE
~~~~
For running iPXE on arm64 the bin-arm64-efi/snp.efi build target is needed::
git clone http://git.ipxe.org/ipxe.git
cd ipxe/src
make bin-arm64-efi/snp.efi -j6 EMBED=myscript.ipxe
The available commands for the boot script are documented at:
http://ipxe.org/cmd
Credentials are managed as environment variables. These are described here:
http://ipxe.org/cfg
iPXE by default will put the CPU to rest when waiting for input. U-Boot does
not wake it up due to missing interrupt support. To avoid this behavior create
file src/config/local/nap.h:
.. code-block:: c
/* nap.h */
#undef NAP_EFIX86
#undef NAP_EFIARM
#define NAP_NULL
The supported commands in iPXE are controlled by an include, too. Putting the
following into src/config/local/general.h is sufficient for most use cases:
.. code-block:: c
/* general.h */
#define NSLOOKUP_CMD /* Name resolution command */
#define PING_CMD /* Ping command */
#define NTP_CMD /* NTP commands */
#define VLAN_CMD /* VLAN commands */
#define IMAGE_EFI /* EFI image support */
#define DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */
#define DOWNLOAD_PROTO_FTP /* File Transfer Protocol */
#define DOWNLOAD_PROTO_NFS /* Network File System Protocol */
#define DOWNLOAD_PROTO_FILE /* Local file system access */
Open-iSCSI
~~~~~~~~~~
When the root file system is on an iSCSI drive you should disable pings and set
the replacement timer to a high value in the configuration file [3]::
node.conn[0].timeo.noop_out_interval = 0
node.conn[0].timeo.noop_out_timeout = 0
node.session.timeo.replacement_timeout = 86400
Links
-----
* [1] https://ipxe.org - iPXE open source boot firmware
* [2] https://www.gnu.org/software/grub/ -
GNU GRUB (Grand Unified Bootloader)
* [3] https://github.com/open-iscsi/open-iscsi/blob/master/README -
Open-iSCSI README
+235
View File
@@ -0,0 +1,235 @@
.. SPDX-License-Identifier: GPL-2.0+
.. Copyright (C) 2015 Google, Inc
U-Boot on EFI
=============
This document provides information about U-Boot running on top of EFI, either
as an application or just as a means of getting U-Boot onto a new platform.
Motivation
----------
Running U-Boot on EFI is useful in several situations:
- You have EFI running on a board but U-Boot does not natively support it
fully yet. You can boot into U-Boot from EFI and use that until U-Boot is
fully ported
- You need to use an EFI implementation (e.g. UEFI) because your vendor
requires it in order to provide support
- You plan to use coreboot to boot into U-Boot but coreboot support does
not currently exist for your platform. In the meantime you can use U-Boot
on EFI and then move to U-Boot on coreboot when ready
- You use EFI but want to experiment with a simpler alternative like U-Boot
Status
------
Only x86 is supported at present. If you are using EFI on another architecture
you may want to reconsider. However, much of the code is generic so could be
ported.
U-Boot supports running as an EFI application for 32-bit EFI only. This is
not very useful since only a serial port is provided. You can look around at
memory and type 'help' but that is about it.
More usefully, U-Boot supports building itself as a payload for either 32-bit
or 64-bit EFI. U-Boot is packaged up and loaded in its entirety by EFI. Once
started, U-Boot changes to 32-bit mode (currently) and takes over the
machine. You can use devices, boot a kernel, etc.
Build Instructions
------------------
First choose a board that has EFI support and obtain an EFI implementation
for that board. It will be either 32-bit or 64-bit. Alternatively, you can
opt for using QEMU [1] and the OVMF [2], as detailed below.
To build U-Boot as an EFI application (32-bit EFI required), enable CONFIG_EFI
and CONFIG_EFI_APP. The efi-x86_app config (efi-x86_app_defconfig) is set up
for this. Just build U-Boot as normal, e.g.::
make efi-x86_app_defconfig
make
To build U-Boot as an EFI payload (32-bit or 64-bit EFI can be used), enable
CONFIG_EFI, CONFIG_EFI_STUB, and select either CONFIG_EFI_STUB_32BIT or
CONFIG_EFI_STUB_64BIT. The efi-x86_payload configs (efi-x86_payload32_defconfig
and efi-x86_payload32_defconfig) are set up for this. Then build U-Boot as
normal, e.g.::
make efi-x86_payload32_defconfig (or efi-x86_payload64_defconfig)
make
You will end up with one of these files depending on what you build for:
* u-boot-app.efi - U-Boot EFI application
* u-boot-payload.efi - U-Boot EFI payload application
Trying it out
-------------
QEMU is an emulator and it can emulate an x86 machine. Please make sure your
QEMU version is 2.3.0 or above to test this. You can run the payload with
something like this::
mkdir /tmp/efi
cp /path/to/u-boot*.efi /tmp/efi
qemu-system-x86_64 -bios bios.bin -hda fat:/tmp/efi/
Add -nographic if you want to use the terminal for output. Once it starts
type 'fs0:u-boot-payload.efi' to run the payload or 'fs0:u-boot-app.efi' to
run the application. 'bios.bin' is the EFI 'BIOS'. Check [2] to obtain a
prebuilt EFI BIOS for QEMU or you can build one from source as well.
To try it on real hardware, put u-boot-app.efi on a suitable boot medium,
such as a USB stick. Then you can type something like this to start it::
fs0:u-boot-payload.efi
(or fs0:u-boot-app.efi for the application)
This will start the payload, copy U-Boot into RAM and start U-Boot. Note
that EFI does not support booting a 64-bit application from a 32-bit
EFI (or vice versa). Also it will often fail to print an error message if
you get this wrong.
Inner workings
--------------
Here follow a few implementation notes for those who want to fiddle with
this and perhaps contribute patches.
The application and payload approaches sound similar but are in fact
implemented completely differently.
EFI Application
~~~~~~~~~~~~~~~
For the application the whole of U-Boot is built as a shared library. The
efi_main() function is in lib/efi/efi_app.c. It sets up some basic EFI
functions with efi_init(), sets up U-Boot global_data, allocates memory for
U-Boot's malloc(), etc. and enters the normal init sequence (board_init_f()
and board_init_r()).
Since U-Boot limits its memory access to the allocated regions very little
special code is needed. The CONFIG_EFI_APP option controls a few things
that need to change so 'git grep CONFIG_EFI_APP' may be instructive.
The CONFIG_EFI option controls more general EFI adjustments.
The only available driver is the serial driver. This calls back into EFI
'boot services' to send and receive characters. Although it is implemented
as a serial driver the console device is not necessarilly serial. If you
boot EFI with video output then the 'serial' device will operate on your
target devices's display instead and the device's USB keyboard will also
work if connected. If you have both serial and video output, then both
consoles will be active. Even though U-Boot does the same thing normally,
These are features of EFI, not U-Boot.
Very little code is involved in implementing the EFI application feature.
U-Boot is highly portable. Most of the difficulty is in modifying the
Makefile settings to pass the right build flags. In particular there is very
little x86-specific code involved - you can find most of it in
arch/x86/cpu. Porting to ARM (which can also use EFI if you are brave
enough) should be straightforward.
Use the 'reset' command to get back to EFI.
EFI Payload
~~~~~~~~~~~
The payload approach is a different kettle of fish. It works by building
U-Boot exactly as normal for your target board, then adding the entire
image (including device tree) into a small EFI stub application responsible
for booting it. The stub application is built as a normal EFI application
except that it has a lot of data attached to it.
The stub application is implemented in lib/efi/efi_stub.c. The efi_main()
function is called by EFI. It is responsible for copying U-Boot from its
original location into memory, disabling EFI boot services and starting
U-Boot. U-Boot then starts as normal, relocates, starts all drivers, etc.
The stub application is architecture-dependent. At present it has some
x86-specific code and a comment at the top of efi_stub.c describes this.
While the stub application does allocate some memory from EFI this is not
used by U-Boot (the payload). In fact when U-Boot starts it has all of the
memory available to it and can operate as it pleases (but see the next
section).
Tables
~~~~~~
The payload can pass information to U-Boot in the form of EFI tables. At
present this feature is used to pass the EFI memory map, an inordinately
large list of memory regions. You can use the 'efi mem all' command to
display this list. U-Boot uses the list to work out where to relocate
itself.
Although U-Boot can use any memory it likes, EFI marks some memory as used
by 'run-time services', code that hangs around while U-Boot is running and
is even present when Linux is running. This is common on x86 and provides
a way for Linux to call back into the firmware to control things like CPU
fan speed. U-Boot uses only 'conventional' memory, in EFI terminology. It
will relocate itself to the top of the largest block of memory it can find
below 4GB.
Interrupts
~~~~~~~~~~
U-Boot drivers typically don't use interrupts. Since EFI enables interrupts
it is possible that an interrupt will fire that U-Boot cannot handle. This
seems to cause problems. For this reason the U-Boot payload runs with
interrupts disabled at present.
32/64-bit
~~~~~~~~~
While the EFI application can in principle be built as either 32- or 64-bit,
only 32-bit is currently supported. This means that the application can only
be used with 32-bit EFI.
The payload stub can be build as either 32- or 64-bits. Only a small amount
of code is built this way (see the extra- line in lib/efi/Makefile).
Everything else is built as a normal U-Boot, so is always 32-bit on x86 at
present.
Future work
-----------
This work could be extended in a number of ways:
- Add ARM support
- Add 64-bit application support
- Figure out how to solve the interrupt problem
- Add more drivers to the application side (e.g. video, block devices, USB,
environment access). This would mostly be an academic exercise as a strong
use case is not readily apparent, but it might be fun.
- Avoid turning off boot services in the stub. Instead allow U-Boot to make
use of boot services in case it wants to. It is unclear what it might want
though.
Where is the code?
------------------
lib/efi
payload stub, application, support code. Mostly arch-neutral
arch/x86/cpu/efi
x86 support code for running as an EFI application and payload
board/efi/efi-x86_app/efi.c
x86 board code for running as an EFI application
board/efi/efi-x86_payload
generic x86 EFI payload board support code
common/cmd_efi.c
the 'efi' command
--
Ben Stoltz, Simon Glass
Google, Inc
July 2015
* [1] http://www.qemu.org
* [2] http://www.tianocore.org/ovmf/
+498
View File
@@ -0,0 +1,498 @@
.. SPDX-License-Identifier: GPL-2.0+
.. Copyright (c) 2018 Heinrich Schuchardt
UEFI on U-Boot
==============
The Unified Extensible Firmware Interface Specification (UEFI) [1] has become
the default for booting on AArch64 and x86 systems. It provides a stable API for
the interaction of drivers and applications with the firmware. The API comprises
access to block storage, network, and console to name a few. The Linux kernel
and boot loaders like GRUB or the FreeBSD loader can be executed.
Development target
------------------
The implementation of UEFI in U-Boot strives to reach the requirements described
in the "Embedded Base Boot Requirements (EBBR) Specification - Release v1.0"
[2]. The "Server Base Boot Requirements System Software on ARM Platforms" [3]
describes a superset of the EBBR specification and may be used as further
reference.
A full blown UEFI implementation would contradict the U-Boot design principle
"keep it small".
Building U-Boot for UEFI
------------------------
The UEFI standard supports only little-endian systems. The UEFI support can be
activated for ARM and x86 by specifying::
CONFIG_CMD_BOOTEFI=y
CONFIG_EFI_LOADER=y
in the .config file.
Support for attaching virtual block devices, e.g. iSCSI drives connected by the
loaded UEFI application [4], requires::
CONFIG_BLK=y
CONFIG_PARTITIONS=y
Executing a UEFI binary
~~~~~~~~~~~~~~~~~~~~~~~
The bootefi command is used to start UEFI applications or to install UEFI
drivers. It takes two parameters::
bootefi <image address> [fdt address]
* image address - the memory address of the UEFI binary
* fdt address - the memory address of the flattened device tree
Below you find the output of an example session starting GRUB::
=> load mmc 0:2 ${fdt_addr_r} boot/dtb
29830 bytes read in 14 ms (2 MiB/s)
=> load mmc 0:1 ${kernel_addr_r} efi/debian/grubaa64.efi
reading efi/debian/grubaa64.efi
120832 bytes read in 7 ms (16.5 MiB/s)
=> bootefi ${kernel_addr_r} ${fdt_addr_r}
When booting from a memory location it is unknown from which file it was loaded.
Therefore the bootefi command uses the device path of the block device partition
or the network adapter and the file name of the most recently loaded PE-COFF
file when setting up the loaded image protocol.
Launching a UEFI binary from a FIT image
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A signed FIT image can be used to securely boot a UEFI image via the
bootm command. This feature is available if U-Boot is configured with::
CONFIG_BOOTM_EFI=y
A sample configuration is provided as file doc/uImage.FIT/uefi.its.
Below you find the output of an example session starting GRUB::
=> load mmc 0:1 ${kernel_addr_r} image.fit
4620426 bytes read in 83 ms (53.1 MiB/s)
=> bootm ${kernel_addr_r}#config-grub-nofdt
## Loading kernel from FIT Image at 40400000 ...
Using 'config-grub-nofdt' configuration
Verifying Hash Integrity ... sha256,rsa2048:dev+ OK
Trying 'efi-grub' kernel subimage
Description: GRUB EFI Firmware
Created: 2019-11-20 8:18:16 UTC
Type: Kernel Image (no loading done)
Compression: uncompressed
Data Start: 0x404000d0
Data Size: 450560 Bytes = 440 KiB
Hash algo: sha256
Hash value: 4dbee00021112df618f58b3f7cf5e1595533d543094064b9ce991e8b054a9eec
Verifying Hash Integrity ... sha256+ OK
XIP Kernel Image (no loading done)
## Transferring control to EFI (at address 404000d0) ...
Welcome to GRUB!
See doc/uImage.FIT/howto.txt for an introduction to FIT images.
Configuring UEFI secure boot
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The UEFI specification[1] defines a secure way of executing UEFI images
by verifying a signature (or message digest) of image with certificates.
This feature on U-Boot is enabled with::
CONFIG_UEFI_SECURE_BOOT=y
To make the boot sequence safe, you need to establish a chain of trust;
In UEFI secure boot the chain trust is defined by the following UEFI variables
* PK - Platform Key
* KEK - Key Exchange Keys
* db - white list database
* dbx - black list database
An in depth description of UEFI secure boot is beyond the scope of this
document. Please, refer to the UEFI specification and available online
documentation. Here is a simple example that you can follow for your initial
attempt (Please note that the actual steps will depend on your system and
environment.):
Install the required tools on your host
* openssl
* efitools
* sbsigntool
Create signing keys and the key database on your host:
The platform key
.. code-block:: bash
openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ \
-keyout PK.key -out PK.crt -nodes -days 365
cert-to-efi-sig-list -g 11111111-2222-3333-4444-123456789abc \
PK.crt PK.esl;
sign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth
The key exchange keys
.. code-block:: bash
openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ \
-keyout KEK.key -out KEK.crt -nodes -days 365
cert-to-efi-sig-list -g 11111111-2222-3333-4444-123456789abc \
KEK.crt KEK.esl
sign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth
The whitelist database
.. code-block:: bash
openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_db/ \
-keyout db.key -out db.crt -nodes -days 365
cert-to-efi-sig-list -g 11111111-2222-3333-4444-123456789abc \
db.crt db.esl
sign-efi-sig-list -c KEK.crt -k KEK.key db db.esl db.auth
Copy the \*.auth files to media, say mmc, that is accessible from U-Boot.
Sign an image with one of the keys in "db" on your host
.. code-block:: bash
sbsign --key db.key --cert db.crt helloworld.efi
Now in U-Boot install the keys on your board::
fatload mmc 0:1 <tmpaddr> PK.auth
setenv -e -nv -bs -rt -at -i <tmpaddr>:$filesize PK
fatload mmc 0:1 <tmpaddr> KEK.auth
setenv -e -nv -bs -rt -at -i <tmpaddr>:$filesize KEK
fatload mmc 0:1 <tmpaddr> db.auth
setenv -e -nv -bs -rt -at -i <tmpaddr>:$filesize db
Set up boot parameters on your board::
efidebug boot add -b 1 HELLO mmc 0:1 /helloworld.efi.signed ""
Now your board can run the signed image via the boot manager (see below).
You can also try this sequence by running Pytest, test_efi_secboot,
on the sandbox
.. code-block:: bash
cd <U-Boot source directory>
pytest.py test/py/tests/test_efi_secboot/test_signed.py --bd sandbox
UEFI binaries may be signed by Microsoft using the following certificates:
* KEK: Microsoft Corporation KEK CA 2011
http://go.microsoft.com/fwlink/?LinkId=321185.
* db: Microsoft Windows Production PCA 2011
http://go.microsoft.com/fwlink/p/?linkid=321192.
* db: Microsoft Corporation UEFI CA 2011
http://go.microsoft.com/fwlink/p/?linkid=321194.
Using OP-TEE for EFI variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of implementing UEFI variable services inside U-Boot they can
also be provided in the secure world by a module for OP-TEE[1]. The
interface between U-Boot and OP-TEE for variable services is enabled by
CONFIG_EFI_MM_COMM_TEE=y.
Tianocore EDK II's standalone management mode driver for variables can
be linked to OP-TEE for this purpose. This module uses the Replay
Protected Memory Block (RPMB) of an eMMC device for persisting
non-volatile variables. When calling the variable services via the
OP-TEE API U-Boot's OP-TEE supplicant relays calls to the RPMB driver
which has to be enabled via CONFIG_SUPPORT_EMMC_RPMB=y.
[1] https://optee.readthedocs.io/ - OP-TEE documentation
Executing the boot manager
~~~~~~~~~~~~~~~~~~~~~~~~~~
The UEFI specification foresees to define boot entries and boot sequence via
UEFI variables. Booting according to these variables is possible via::
bootefi bootmgr [fdt address]
As of U-Boot v2020.10 UEFI variables cannot be set at runtime. The U-Boot
command 'efidebug' can be used to set the variables.
Executing the built in hello world application
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A hello world UEFI application can be built with::
CONFIG_CMD_BOOTEFI_HELLO_COMPILE=y
It can be embedded into the U-Boot binary with::
CONFIG_CMD_BOOTEFI_HELLO=y
The bootefi command is used to start the embedded hello world application::
bootefi hello [fdt address]
Below you find the output of an example session::
=> bootefi hello ${fdtcontroladdr}
## Starting EFI application at 01000000 ...
WARNING: using memory device/image path, this may confuse some payloads!
Hello, world!
Running on UEFI 2.7
Have SMBIOS table
Have device tree
Load options: root=/dev/sdb3 init=/sbin/init rootwait ro
## Application terminated, r = 0
The environment variable fdtcontroladdr points to U-Boot's internal device tree
(if available).
Executing the built-in self-test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An UEFI self-test suite can be embedded in U-Boot by building with::
CONFIG_CMD_BOOTEFI_SELFTEST=y
For testing the UEFI implementation the bootefi command can be used to start the
self-test::
bootefi selftest [fdt address]
The environment variable 'efi_selftest' can be used to select a single test. If
it is not provided all tests are executed except those marked as 'on request'.
If the environment variable is set to 'list' a list of all tests is shown.
Below you can find the output of an example session::
=> setenv efi_selftest simple network protocol
=> bootefi selftest
Testing EFI API implementation
Selected test: 'simple network protocol'
Setting up 'simple network protocol'
Setting up 'simple network protocol' succeeded
Executing 'simple network protocol'
DHCP Discover
DHCP reply received from 192.168.76.2 (52:55:c0:a8:4c:02)
as broadcast message.
Executing 'simple network protocol' succeeded
Tearing down 'simple network protocol'
Tearing down 'simple network protocol' succeeded
Boot services terminated
Summary: 0 failures
Preparing for reset. Press any key.
The UEFI life cycle
-------------------
After the U-Boot platform has been initialized the UEFI API provides two kinds
of services:
* boot services
* runtime services
The API can be extended by loading UEFI drivers which come in two variants:
* boot drivers
* runtime drivers
UEFI drivers are installed with U-Boot's bootefi command. With the same command
UEFI applications can be executed.
Loaded images of UEFI drivers stay in memory after returning to U-Boot while
loaded images of applications are removed from memory.
An UEFI application (e.g. an operating system) that wants to take full control
of the system calls ExitBootServices. After a UEFI application calls
ExitBootServices
* boot services are not available anymore
* timer events are stopped
* the memory used by U-Boot except for runtime services is released
* the memory used by boot time drivers is released
So this is a point of no return. Afterwards the UEFI application can only return
to U-Boot by rebooting.
The UEFI object model
---------------------
UEFI offers a flexible and expandable object model. The objects in the UEFI API
are devices, drivers, and loaded images. These objects are referenced by
handles.
The interfaces implemented by the objects are referred to as protocols. These
are identified by GUIDs. They can be installed and uninstalled by calling the
appropriate boot services.
Handles are created by the InstallProtocolInterface or the
InstallMultipleProtocolinterfaces service if NULL is passed as handle.
Handles are deleted when the last protocol has been removed with the
UninstallProtocolInterface or the UninstallMultipleProtocolInterfaces service.
Devices offer the EFI_DEVICE_PATH_PROTOCOL. A device path is the concatenation
of device nodes. By their device paths all devices of a system are arranged in a
tree.
Drivers offer the EFI_DRIVER_BINDING_PROTOCOL. This protocol is used to connect
a driver to devices (which are referenced as controllers in this context).
Loaded images offer the EFI_LOADED_IMAGE_PROTOCOL. This protocol provides meta
information about the image and a pointer to the unload callback function.
The UEFI events
---------------
In the UEFI terminology an event is a data object referencing a notification
function which is queued for calling when the event is signaled. The following
types of events exist:
* periodic and single shot timer events
* exit boot services events, triggered by calling the ExitBootServices() service
* virtual address change events
* memory map change events
* read to boot events
* reset system events
* system table events
* events that are only triggered programmatically
Events can be created with the CreateEvent service and deleted with CloseEvent
service.
Events can be assigned to an event group. If any of the events in a group is
signaled, all other events in the group are also set to the signaled state.
The UEFI driver model
---------------------
A driver is specific for a single protocol installed on a device. To install a
driver on a device the ConnectController service is called. In this context
controller refers to the device for which the driver is installed.
The relevant drivers are identified using the EFI_DRIVER_BINDING_PROTOCOL. This
protocol has has three functions:
* supported - determines if the driver is compatible with the device
* start - installs the driver by opening the relevant protocol with
attribute EFI_OPEN_PROTOCOL_BY_DRIVER
* stop - uninstalls the driver
The driver may create child controllers (child devices). E.g. a driver for block
IO devices will create the device handles for the partitions. The child
controllers will open the supported protocol with the attribute
EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
A driver can be detached from a device using the DisconnectController service.
U-Boot devices mapped as UEFI devices
-------------------------------------
Some of the U-Boot devices are mapped as UEFI devices
* block IO devices
* console
* graphical output
* network adapter
As of U-Boot 2018.03 the logic for doing this is hard coded.
The development target is to integrate the setup of these UEFI devices with the
U-Boot driver model [5]. So when a U-Boot device is discovered a handle should
be created and the device path protocol and the relevant IO protocol should be
installed. The UEFI driver then would be attached by calling ConnectController.
When a U-Boot device is removed DisconnectController should be called.
UEFI devices mapped as U-Boot devices
-------------------------------------
UEFI drivers binaries and applications may create new (virtual) devices, install
a protocol and call the ConnectController service. Now the matching UEFI driver
is determined by iterating over the implementations of the
EFI_DRIVER_BINDING_PROTOCOL.
It is the task of the UEFI driver to create a corresponding U-Boot device and to
proxy calls for this U-Boot device to the controller.
In U-Boot 2018.03 this has only been implemented for block IO devices.
UEFI uclass
~~~~~~~~~~~
An UEFI uclass driver (lib/efi_driver/efi_uclass.c) has been created that
takes care of initializing the UEFI drivers and providing the
EFI_DRIVER_BINDING_PROTOCOL implementation for the UEFI drivers.
A linker created list is used to keep track of the UEFI drivers. To create an
entry in the list the UEFI driver uses the U_BOOT_DRIVER macro specifying
UCLASS_EFI as the ID of its uclass, e.g::
/* Identify as UEFI driver */
U_BOOT_DRIVER(efi_block) = {
.name = "EFI block driver",
.id = UCLASS_EFI,
.ops = &driver_ops,
};
The available operations are defined via the structure struct efi_driver_ops::
struct efi_driver_ops {
const efi_guid_t *protocol;
const efi_guid_t *child_protocol;
int (*bind)(efi_handle_t handle, void *interface);
};
When the supported() function of the EFI_DRIVER_BINDING_PROTOCOL is called the
uclass checks if the protocol GUID matches the protocol GUID of the UEFI driver.
In the start() function the bind() function of the UEFI driver is called after
checking the GUID.
The stop() function of the EFI_DRIVER_BINDING_PROTOCOL disconnects the child
controllers created by the UEFI driver and the UEFI driver. (In U-Boot v2013.03
this is not yet completely implemented.)
UEFI block IO driver
~~~~~~~~~~~~~~~~~~~~
The UEFI block IO driver supports devices exposing the EFI_BLOCK_IO_PROTOCOL.
When connected it creates a new U-Boot block IO device with interface type
IF_TYPE_EFI, adds child controllers mapping the partitions, and installs the
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL on these. This can be used together with the
software iPXE to boot from iSCSI network drives [4].
This driver is only available if U-Boot is configured with::
CONFIG_BLK=y
CONFIG_PARTITIONS=y
Miscellaneous
-------------
Load file 2 protocol
~~~~~~~~~~~~~~~~~~~~
The load file 2 protocol can be used by the Linux kernel to load the initial
RAM disk. U-Boot can be configured to provide an implementation with::
EFI_LOAD_FILE2_INITRD=y
EFI_INITRD_FILESPEC=interface dev:part path_to_initrd
Links
-----
* [1] http://uefi.org/specifications - UEFI specifications
* [2] https://github.com/ARM-software/ebbr/releases/download/v1.0/ebbr-v1.0.pdf -
Embedded Base Boot Requirements (EBBR) Specification - Release v1.0
* [3] https://developer.arm.com/docs/den0044/latest/server-base-boot-requirements-system-software-on-arm-platforms-version-11 -
Server Base Boot Requirements System Software on ARM Platforms - Version 1.1
* [4] :doc:`iscsi`
* [5] :doc:`../driver-model/index`