diff options
Diffstat (limited to 'doc/driver-model/UDM-design.txt')
-rw-r--r-- | doc/driver-model/UDM-design.txt | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/doc/driver-model/UDM-design.txt b/doc/driver-model/UDM-design.txt new file mode 100644 index 0000000..185f477 --- /dev/null +++ b/doc/driver-model/UDM-design.txt @@ -0,0 +1,315 @@ +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 |