mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-03 18:10:13 +00:00
15c6935b0c
This patch contains UDM-design.txt, which is document containing general description of the driver model. The remaining files contains descriptions of conversion process of particular subsystems. Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
315 lines
11 KiB
Text
315 lines
11 KiB
Text
The U-Boot Driver Model Project
|
|
===============================
|
|
Design document
|
|
===============
|
|
Marek Vasut <marek.vasut@gmail.com>
|
|
Pavel Herrmann <morpheus.ibis@gmail.com>
|
|
2012-05-17
|
|
|
|
I) The modular concept
|
|
----------------------
|
|
|
|
The driver core design is done with modularity in mind. The long-term plan is to
|
|
extend this modularity to allow loading not only drivers, but various other
|
|
objects into U-Boot at runtime -- like commands, support for other boards etc.
|
|
|
|
II) Driver core initialization stages
|
|
-------------------------------------
|
|
|
|
The drivers have to be initialized in two stages, since the U-Boot bootloader
|
|
runs in two stages itself. The first stage is the one which is executed before
|
|
the bootloader itself is relocated. The second stage then happens after
|
|
relocation.
|
|
|
|
1) First stage
|
|
--------------
|
|
|
|
The first stage runs after the bootloader did very basic hardware init. This
|
|
means the stack pointer was configured, caches disabled and that's about it.
|
|
The problem with this part is the memory management isn't running at all. To
|
|
make things even worse, at this point, the RAM is still likely uninitialized
|
|
and therefore unavailable.
|
|
|
|
2) Second stage
|
|
---------------
|
|
|
|
At this stage, the bootloader has initialized RAM and is running from it's
|
|
final location. Dynamic memory allocations are working at this point. Most of
|
|
the driver initialization is executed here.
|
|
|
|
III) The drivers
|
|
----------------
|
|
|
|
1) The structure of a driver
|
|
----------------------------
|
|
|
|
The driver will contain a structure located in a separate section, which
|
|
will allow linker to create a list of compiled-in drivers at compile time.
|
|
Let's call this list "driver_list".
|
|
|
|
struct driver __attribute__((section(driver_list))) {
|
|
/* The name of the driver */
|
|
char name[STATIC_CONFIG_DRIVER_NAME_LENGTH];
|
|
|
|
/*
|
|
* This function should connect this driver with cores it depends on and
|
|
* with other drivers, likely bus drivers
|
|
*/
|
|
int (*bind)(struct instance *i);
|
|
|
|
/* This function actually initializes the hardware. */
|
|
int (*probe)(struct instance *i);
|
|
|
|
/*
|
|
* The function of the driver called when U-Boot finished relocation.
|
|
* This is particularly important to eg. move pointers to DMA buffers
|
|
* and such from the location before relocation to their final location.
|
|
*/
|
|
int (*reloc)(struct instance *i);
|
|
|
|
/*
|
|
* This is called when the driver is shuting down, to deinitialize the
|
|
* hardware.
|
|
*/
|
|
int (*remove)(struct instance *i);
|
|
|
|
/* This is called to remove the driver from the driver tree */
|
|
int (*unbind)(struct instance *i);
|
|
|
|
/* This is a list of cores this driver depends on */
|
|
struct driver *cores[];
|
|
};
|
|
|
|
The cores[] array in here is very important. It allows u-boot to figure out,
|
|
in compile-time, which possible cores can be activated at runtime. Therefore
|
|
if there are cores that won't be ever activated, GCC LTO might remove them
|
|
from the final binary. Actually, this information might be used to drive build
|
|
of the cores.
|
|
|
|
FIXME: Should *cores[] be really struct driver, pointing to drivers that
|
|
represent the cores? Shouldn't it be core instance pointer?
|
|
|
|
2) Instantiation of a driver
|
|
----------------------------
|
|
|
|
The driver is instantiated by calling:
|
|
|
|
driver_bind(struct instance *bus, const struct driver_info *di)
|
|
|
|
The "struct instance *bus" is a pointer to a bus with which this driver should
|
|
be registered with. The "root" bus pointer is supplied to the board init
|
|
functions.
|
|
|
|
FIXME: We need some functions that will return list of busses of certain type
|
|
registered with the system so the user can find proper instance even if
|
|
he has no bus pointer (this will come handy if the user isn't
|
|
registering the driver from board init function, but somewhere else).
|
|
|
|
The "const struct driver_info *di" pointer points to a structure defining the
|
|
driver to be registered. The structure is defined as follows:
|
|
|
|
struct driver_info {
|
|
char name[STATIC_CONFIG_DRIVER_NAME_LENGTH];
|
|
void *platform_data;
|
|
}
|
|
|
|
The instantiation of a driver by calling driver_bind() creates an instance
|
|
of the driver by allocating "struct driver_instance". Note that only struct
|
|
instance is passed to the driver. The wrapping struct driver_instance is there
|
|
for purposes of the driver core:
|
|
|
|
struct driver_instance {
|
|
uint32_t flags;
|
|
struct instance i;
|
|
};
|
|
|
|
struct instance {
|
|
/* Pointer to a driver information passed by driver_register() */
|
|
const struct driver_info *info;
|
|
/* Pointer to a bus this driver is bound with */
|
|
struct instance *bus;
|
|
/* Pointer to this driver's own private data */
|
|
void *private_data;
|
|
/* Pointer to the first block of successor nodes (optional) */
|
|
struct successor_block *succ;
|
|
}
|
|
|
|
The instantiation of a driver does not mean the hardware is initialized. The
|
|
driver_bind() call only creates the instance of the driver, fills in the "bus"
|
|
pointer and calls the drivers' .bind() function. The .bind() function of the
|
|
driver should hook the driver with the remaining cores and/or drivers it
|
|
depends on.
|
|
|
|
It's important to note here, that in case the driver instance has multiple
|
|
parents, such parent can be connected with this instance by calling:
|
|
|
|
driver_link(struct instance *parent, struct instance *dev);
|
|
|
|
This will connect the other parent driver with the newly instantiated driver.
|
|
Note that this must be called after driver_bind() and before driver_acticate()
|
|
(driver_activate() will be explained below). To allow struct instance to have
|
|
multiple parent pointer, the struct instance *bus will utilize it's last bit
|
|
to indicate if this is a pointer to struct instance or to an array if
|
|
instances, struct successor block. The approach is similar as the approach to
|
|
*succ in struct instance, described in the following paragraph.
|
|
|
|
The last pointer of the struct instance, the pointer to successor nodes, is
|
|
used only in case of a bus driver. Otherwise the pointer contains NULL value.
|
|
The last bit of this field indicates if this is a bus having a single child
|
|
node (so the last bit is 0) or if this bus has multiple child nodes (the last
|
|
bit is 1). In the former case, the driver core should clear the last bit and
|
|
this pointer points directly to the child node. In the later case of a bus
|
|
driver, the pointer points to an instance of structure:
|
|
|
|
struct successor_block {
|
|
/* Array of pointers to instances of devices attached to this bus */
|
|
struct instance *dev[BLOCKING_FACTOR];
|
|
/* Pointer to next block of successors */
|
|
struct successor_block *next;
|
|
}
|
|
|
|
Some of the *dev[] array members might be NULL in case there are no more
|
|
devices attached. The *next is NULL in case the list of attached devices
|
|
doesn't continue anymore. The BLOCKING_FACTOR is used to allocate multiple
|
|
slots for successor devices at once to avoid fragmentation of memory.
|
|
|
|
3) The bind() function of a driver
|
|
----------------------------------
|
|
|
|
The bind function of a driver connects the driver with various cores the
|
|
driver provides functions for. The driver model related part will look like
|
|
the following example for a bus driver:
|
|
|
|
int driver_bind(struct instance *in)
|
|
{
|
|
...
|
|
core_bind(&core_i2c_static_instance, in, i2c_bus_funcs);
|
|
...
|
|
}
|
|
|
|
FIXME: What if we need to run-time determine, depending on some hardware
|
|
register, what kind of i2c_bus_funcs to pass?
|
|
|
|
This makes the i2c core aware of a new bus. The i2c_bus_funcs is a constant
|
|
structure of functions any i2c bus driver must provide to work. This will
|
|
allow the i2c command operate with the bus. The core_i2c_static_instance is
|
|
the pointer to the instance of a core this driver provides function to.
|
|
|
|
FIXME: Maybe replace "core-i2c" with CORE_I2C global pointer to an instance of
|
|
the core?
|
|
|
|
4) The instantiation of a core driver
|
|
-------------------------------------
|
|
|
|
The core driver is special in the way that it's single-instance driver. It is
|
|
always present in the system, though it might not be activated. The fact that
|
|
it's single instance allows it to be instantiated at compile time.
|
|
|
|
Therefore, all possible structures of this driver can be in read-only memory,
|
|
especially struct driver and struct driver_instance. But the successor list,
|
|
which needs special treatment.
|
|
|
|
To solve the problem with a successor list and the core driver flags, a new
|
|
entry in struct gd (global data) will be introduced. This entry will point to
|
|
runtime allocated array of struct driver_instance. It will be possible to
|
|
allocate the exact amount of struct driver_instance necessary, as the number
|
|
of cores that might be activated will be known at compile time. The cores will
|
|
then behave like any usual driver.
|
|
|
|
Pointers to the struct instance of cores can be computed at compile time,
|
|
therefore allowing the resulting u-boot binary to save some overhead.
|
|
|
|
5) The probe() function of a driver
|
|
-----------------------------------
|
|
|
|
The probe function of a driver allocates necessary resources and does required
|
|
initialization of the hardware itself. This is usually called only when the
|
|
driver is needed, as a part of the defered probe mechanism.
|
|
|
|
The driver core should implement a function called
|
|
|
|
int driver_activate(struct instance *in);
|
|
|
|
which should call the .probe() function of the driver and then configure the
|
|
state of the driver instance to "ACTIVATED". This state of a driver instance
|
|
should be stored in a wrap-around structure for the structure instance, the
|
|
struct driver_instance.
|
|
|
|
6) The command side interface to a driver
|
|
-----------------------------------------
|
|
|
|
The U-Boot command shall communicate only with the specific driver core. The
|
|
driver core in turn exports necessary API towards the command.
|
|
|
|
7) Demonstration imaginary board
|
|
--------------------------------
|
|
|
|
Consider the following computer:
|
|
|
|
*
|
|
|
|
|
+-- System power management logic
|
|
|
|
|
+-- CPU clock controlling logc
|
|
|
|
|
+-- NAND controller
|
|
| |
|
|
| +-- NAND flash chip
|
|
|
|
|
+-- 128MB of DDR DRAM
|
|
|
|
|
+-- I2C bus #0
|
|
| |
|
|
| +-- RTC
|
|
| |
|
|
| +-- EEPROM #0
|
|
| |
|
|
| +-- EEPROM #1
|
|
|
|
|
+-- USB host-only IP core
|
|
| |
|
|
| +-- USB storage device
|
|
|
|
|
+-- USB OTG-capable IP core
|
|
| |
|
|
| +-- connection to the host PC
|
|
|
|
|
+-- GPIO
|
|
| |
|
|
| +-- User LED #0
|
|
| |
|
|
| +-- User LED #1
|
|
|
|
|
+-- UART0
|
|
|
|
|
+-- UART1
|
|
|
|
|
+-- Ethernet controller #0
|
|
|
|
|
+-- Ethernet controller #1
|
|
|
|
|
+-- Audio codec
|
|
|
|
|
+-- PCI bridge
|
|
| |
|
|
| +-- Ethernet controller #2
|
|
| |
|
|
| +-- SPI host card
|
|
| | |
|
|
| | +-- Audio amplifier (must be operational before codec)
|
|
| |
|
|
| +-- GPIO host card
|
|
| |
|
|
| +-- User LED #2
|
|
|
|
|
+-- LCD controller
|
|
|
|
|
+-- PWM controller (must be enabled after LCD controller)
|
|
|
|
|
+-- SPI host controller
|
|
| |
|
|
| +-- SD/MMC connected via SPI
|
|
| |
|
|
| +-- SPI flash
|
|
|
|
|
+-- CPLD/FPGA with stored configuration of the board
|