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:
@@ -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
|
||||
@@ -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
@@ -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 = ð_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.
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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(®s->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, ®s->ch_cfg);
|
||||
writel(SPI_FB_DELAY_180, ®s->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(®s->ch_cfg, SPI_CH_HS_EN, SPI_CH_RST);
|
||||
clrbits_le32(®s->ch_cfg, SPI_CH_RST);
|
||||
setbits_le32(®s->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.
|
||||
@@ -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,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
|
||||
-------
|
||||
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../tools/binman/binman.rst
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../tools/binman/entries.rst
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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`
|
||||
Reference in New Issue
Block a user