Driver Model ============ This README contains high-level information about driver model, a unified way of declaring and accessing drivers in U-Boot. The original work was done by: Marek Vasut Pavel Herrmann Viktor Křivák Tomas Hlavacek This has been both simplified and extended into the current implementation by: Simon Glass Terminology ----------- Uclass - a group of devices which operate in the same way. A uclass provides a way of accessing invidual devices within the group, but always using the same interface. For example a GPIO uclass provides operations for get/set value. An I2C uclass may have 10 I2C ports, 4 with one driver, and 6 with another. Driver - some code which talks to a peripheral and presents a higher-level interface to it. Device - an instance of a driver, tied to a particular port or peripheral. How to try it ------------- Build U-Boot sandbox and run it: make sandbox_config make ./u-boot (type 'reset' to exit U-Boot) There is a uclass called 'demo'. This uclass handles saying hello, and reporting its status. There are two drivers in this uclass: - simple: Just prints a message for hello, doesn't implement status - shape: Prints shapes and reports number of characters printed as status The demo class is pretty simple, but not trivial. The intention is that it can be used for testing, so it will implement all driver model features and provide good code coverage of them. It does have multiple drivers, it handles parameter data and platdata (data which tells the driver how to operate on a particular platform) and it uses private driver data. To try it, see the example session below: =>demo hello 1 Hello '@' from 07981110: red 4 =>demo status 2 Status: 0 =>demo hello 2 g r@ e@@ e@@@ n@@@@ g@@@@@ =>demo status 2 Status: 21 =>demo hello 4 ^ y^^^ e^^^^^ l^^^^^^^ l^^^^^^^ o^^^^^ w^^^ =>demo status 4 Status: 36 => Running the tests ----------------- The intent with driver model is that the core portion has 100% test coverage in sandbox, and every uclass has its own test. As a move towards this, tests are provided in test/dm. To run them, try: ./test/dm/test-dm.sh You should see something like this: <...U-Boot banner...> Running 12 driver model tests Test: dm_test_autobind Test: dm_test_autoprobe Test: dm_test_children Test: dm_test_fdt Test: dm_test_gpio sandbox_gpio: sb_gpio_get_value: error: offset 4 not reserved Test: dm_test_leak Warning: Please add '#define DEBUG' to the top of common/dlmalloc.c Warning: Please add '#define DEBUG' to the top of common/dlmalloc.c Test: dm_test_lifecycle Test: dm_test_operations Test: dm_test_ordering Test: dm_test_platdata Test: dm_test_remove Test: dm_test_uclass Failures: 0 (You can add '#define DEBUG' as suggested to check for memory leaks) What is going on? ----------------- Let's start at the top. The demo command is in common/cmd_demo.c. It does the usual command procesing and then: struct udevice *demo_dev; ret = uclass_get_device(UCLASS_DEMO, devnum, &demo_dev); UCLASS_DEMO means the class of devices which implement 'demo'. Other classes might be MMC, or GPIO, hashing or serial. The idea is that the devices in the class all share a particular way of working. The class presents a unified view of all these devices to U-Boot. This function looks up a device for the demo uclass. Given a device number we can find the device because all devices have registered with the UCLASS_DEMO uclass. The device is automatically activated ready for use by uclass_get_device(). Now that we have the device we can do things like: return demo_hello(demo_dev, ch); This function is in the demo uclass. It takes care of calling the 'hello' method of the relevant driver. Bearing in mind that there are two drivers, this particular device may use one or other of them. The code for demo_hello() is in drivers/demo/demo-uclass.c: int demo_hello(struct udevice *dev, int ch) { const struct demo_ops *ops = device_get_ops(dev); if (!ops->hello) return -ENOSYS; return ops->hello(dev, ch); } As you can see it just calls the relevant driver method. One of these is in drivers/demo/demo-simple.c: static int simple_hello(struct udevice *dev, int ch) { const struct dm_demo_pdata *pdata = dev_get_platdata(dev); printf("Hello from %08x: %s %d\n", map_to_sysmem(dev), pdata->colour, pdata->sides); return 0; } So that is a trip from top (command execution) to bottom (driver action) but it leaves a lot of topics to address. Declaring Drivers ----------------- A driver declaration looks something like this (see drivers/demo/demo-shape.c): static const struct demo_ops shape_ops = { .hello = shape_hello, .status = shape_status, }; U_BOOT_DRIVER(demo_shape_drv) = { .name = "demo_shape_drv", .id = UCLASS_DEMO, .ops = &shape_ops, .priv_data_size = sizeof(struct shape_data), }; This driver has two methods (hello and status) and requires a bit of private data (accessible through dev_get_priv(dev) once the driver has been probed). It is a member of UCLASS_DEMO so will register itself there. In U_BOOT_DRIVER it is also possible to specify special methods for bind and unbind, and these are called at appropriate times. For many drivers it is hoped that only 'probe' and 'remove' will be needed. The U_BOOT_DRIVER macro creates a data structure accessible from C, so driver model can find the drivers that are available. The methods a device can provide are documented in the device.h header. Briefly, they are: bind - make the driver model aware of a device (bind it to its driver) unbind - make the driver model forget the device ofdata_to_platdata - convert device tree data to platdata - see later probe - make a device ready for use remove - remove a device so it cannot be used until probed again The sequence to get a device to work is bind, ofdata_to_platdata (if using device tree) and probe. Platform Data ------------- Where does the platform data come from? See demo-pdata.c which sets up a table of driver names and their associated platform data. The data can be interpreted by the drivers however they like - it is basically a communication scheme between the board-specific code and the generic drivers, which are intended to work on any board. Drivers can acceess their data via dev->info->platdata. Here is the declaration for the platform data, which would normally appear in the board file. static const struct dm_demo_cdata red_square = { .colour = "red", .sides = 4. }; static const struct driver_info info[] = { { .name = "demo_shape_drv", .platdata = &red_square, }, }; demo1 = driver_bind(root, &info[0]); Device Tree ----------- While platdata is useful, a more flexible way of providing device data is by using device tree. With device tree we replace the above code with the following device tree fragment: red-square { compatible = "demo-shape"; colour = "red"; sides = <4>; }; The easiest way to make this work it to add a few members to the driver: .platdata_auto_alloc_size = sizeof(struct dm_test_pdata), .ofdata_to_platdata = testfdt_ofdata_to_platdata, .probe = testfdt_drv_probe, The 'auto_alloc' feature allowed space for the platdata to be allocated and zeroed before the driver's ofdata_to_platdata method is called. This method reads the information out of the device tree and puts it in dev->platdata. Then the probe method is called to set up the device. Note that both methods are optional. If you provide an ofdata_to_platdata method then it wlil be called first (after bind). If you provide a probe method it will be called next. If you don't want to have the platdata automatically allocated then you can leave out platdata_auto_alloc_size. In this case you can use malloc in your ofdata_to_platdata (or probe) method to allocate the required memory, and you should free it in the remove method. Declaring Uclasses ------------------ The demo uclass is declared like this: U_BOOT_CLASS(demo) = { .id = UCLASS_DEMO, }; It is also possible to specify special methods for probe, etc. The uclass numbering comes from include/dm/uclass.h. To add a new uclass, add to the end of the enum there, then declare your uclass as above. Data Structures --------------- Driver model uses a doubly-linked list as the basic data structure. Some nodes have several lists running through them. Creating a more efficient data structure might be worthwhile in some rare cases, once we understand what the bottlenecks are. Changes since v1 ---------------- For the record, this implementation uses a very similar approach to the original patches, but makes at least the following changes: - Tried to agressively remove boilerplate, so that for most drivers there is little or no 'driver model' code to write. - Moved some data from code into data structure - e.g. store a pointer to the driver operations structure in the driver, rather than passing it to the driver bind function. - Rename some structures to make them more similar to Linux (struct device instead of struct instance, struct platdata, etc.) - Change the name 'core' to 'uclass', meaning U-Boot class. It seems that this concept relates to a class of drivers (or a subsystem). We shouldn't use 'class' since it is a C++ reserved word, so U-Boot class (uclass) seems better than 'core'. - Remove 'struct driver_instance' and just use a single 'struct udevice'. This removes a level of indirection that doesn't seem necessary. - Built in device tree support, to avoid the need for platdata - Removed the concept of driver relocation, and just make it possible for the new driver (created after relocation) to access the old driver data. I feel that relocation is a very special case and will only apply to a few drivers, many of which can/will just re-init anyway. So the overhead of dealing with this might not be worth it. - Implemented a GPIO system, trying to keep it simple Things to punt for later ------------------------ - SPL support - this will have to be present before many drivers can be converted, but it seems like we can add it once we are happy with the core implementation. - Pre-relocation support - similar story That is not to say that no thinking has gone into these - in fact there is quite a lot there. However, getting these right is non-trivial and there is a high cost associated with going down the wrong path. For SPL, it may be possible to fit in a simplified driver model with only bind and probe methods, to reduce size. For pre-relocation we can simply call the driver model init function. Then post relocation we throw that away and re-init driver model again. For drivers which require some sort of continuity between pre- and post-relocation devices, we can provide access to the pre-relocation device pointers. Uclasses are statically numbered at compile time. It would be possible to change this to dynamic numbering, but then we would require some sort of lookup service, perhaps searching by name. This is slightly less efficient so has been left out for now. One small advantage of dynamic numbering might be fewer merge conflicts in uclass-id.h. Simon Glass sjg@chromium.org April 2013 Updated 7-May-13 Updated 14-Jun-13 Updated 18-Oct-13 Updated 5-Nov-13