 Hello, thanks for joining this session of the Embedded Linux conference 2021. So today I'm going to talk about how to develop Linux device drivers more focused on the architecture of a Linux device drivers. So I hope you enjoyed this presentation. Before I talk about the agenda, a little bit about myself. So I've been working with Embedded development for 25 plus years. I'm located in Sao Paulo, Brazil. I have here a company called Embedded Leberks, where I provide consulting training services worldwide, actually. I'm an open source contributor, so I contribute to several open source projects, including Beautyhood, Yocto, and the Linux kernel. I have also a blog where I write in English, EmbeddedBits.org. Okay, so a little bit about the talk. So the main objective of this talk is to try to cover the architecture of a Linux device driver. This is a kind of entry-level talk for kernel developers, most for those that are starting to understand how a Linux kernel device driver works. And one of the objectives here is try to connect the dots, try to understand all of the concepts behind a driver in Linux kernel. Unfortunately, we will not have too much time, because deeper concepts like the structures of the kernel driver model or even the kernel API is in detail. But the main objective here is try to make it possible for you to open the search code of a Linux kernel driver and you're going to be able to understand the structure of the driver, right? And you can start from that and then learn to use all of the APIs. Here is the agenda for this talk. I'm going to quickly introduce device drivers and shard drivers. Then I'm going to talk a little bit about how hardware access works. After that, we're going to talk about the driver model and then we're going to discuss what are frameworks and what are buses and device descriptions and device tree. So all of the main concepts that we see today in a modern Linux device driver. I usually on my talks, I try to do a lot of hands-on, so this talk will not be different. We're going to have five hands-on. So here, we're going to try to develop a LED driver, a very simple device driver, because the objective here is not to understand the hardware, but to try to understand the kernel infrastructure for driver development and the kernel API a little bit. So instead of doing development of a USB driver or I2C driver, it could bring some of the complexity of the hardware. We're going to focus on a very simple LED device connected on a GPIO and try to write a driver for it. Okay, let's start with a very simple question. What are device drivers? And I usually say that drivers are abstractions, right? Drivers will convert something more complicated to something more simple to use, right? And so in our example, we have an LED. We want to convert something more complicated like we have to write, to register from a GPIO controller to something more simple, just toggle an LED. So we want to create these abstractions. All of the drivers are this kind of abstraction of converting something more complicated to something more simple. Of course, you can have this concept of drivers implemented in user space. The kernel is able to provide the use some APIs so you can talk to the hardware from the user space level. So for example, we have SPI dev or I2C dev where you can talk directly to the buses. If you want, and then to the devices connected to the buses. We have also the user space IO framework in the kernel where we could use to directly talk to memory map IO devices, for example. But that's not the focus here. Focus here is writing drivers at the kernel space because this is one of the main responsibilities of a kernel, right? To the infrastructure to write device drivers. And when we talk about abstraction on Linux, the main abstraction for almost everything is files, right? Are files. So here we're going to, of course, work with files, so files from the user space perspective are our main abstraction to talk to the hardware. So that's the main original and simple idea. So our driver, we will run in kernel space because it needs privileges to talk to the hardware and it will provide some kind of file so user space applications can talk to the hardware. There are several APIs in the Linux kernel to do this. We have a piece to export files to slash dev. We have a piece APIs to support files to slash sys. And of course, we don't have time to study all of these APIs and that's really not the objective here. The objective is to understand the concepts. So we're going to have some contact with an API to export files to slash dev. They are special files that we usually call advice nodes or advice files. Those files are special because they have three attributes that other files doesn't have like the type. So advice file or advice node can be of type block or char. The idea here is that a char device is a device where you exchange bytes or you talk in terms of string of bytes or you read and write bytes. And block devices are devices where you communicate with blocks. You have a specific amount of space dedicated for these devices like, for example, a disk is exported by disk controllers as block devices. So you can go from starter to the end, you talk to the device using blocks of data. And char devices like serial ports, algae devices, they are char devices because you exchange bytes with those devices. You exchange the stream of bytes. Again, this is just abstractions available in Nuxker. So you can export an interface to a specific hardware. Those device files have also two other attributes that we usually call device number. These are the major number and the minor number. And the idea here is that the major number is a number that identifies the category of the device. Like serial ports have one dedicated major number, algae devices have one dedicated number. Sometimes we could have a block of major numbers dedicated to a category of device. And the minor number is the identifier of that device in the category. So let's say you have four serial ports, all of them will have the same major number, but each one of them will have a minor number. And if you list the devices at slash data, you're going to see these properties in those files there. So that's the main idea, right, this abstraction of a char driver that will export to the user a char device. So you write a char driver that will export to this char device so the user can talk to you. And the idea is very simple. The abstraction is a file, so every file operation will get to your driver. And so when the user opened the file, the kernel will call the open function from your driver. When the user writes the file, the kernel will call the right callback from your driver. So from the driver's perspective, you just write callback functions. You register the kernel and then the application will be able to talk to you. And of course in the driver, you can talk to the hardware because you are running in kernel space. Those are basically the three steps to write a char driver. Again, we will not go much deeper on this, but the idea is to basically understand how that works. We need to allocate the device number, the major and minor number. That's the first thing that you would do in a char driver. And then you implement the callbacks. You don't have to implement all of the file operations callbacks. You just have to implement those that make sense for the driver. And then you initialize this in the Linux kernel. So there are a few functions that you can use to do that. And again, there are variations of these. Like you can have a MISC driver that is more simple to do it. But we're not going to go deeper to this. Just to have an idea of how that would work, it seems complicated. But basically we are kind of creating a link from the driver to the device node that the user can see. So on the left we have everything happening in kernel space. On the right in user space. So in the driver we define a variable of type DevT, that's variable that we need to find our device, our char device. And then we allocated, sorry, the DevT is the variable that stores the major and minor number. So we created this variable in the driver and we allocate in the kernel that could be dynamically allocated by the kernel or you can statically allocate in the driver. But you need a unique pair of major and minor number for your driver. And then you implement the file operations. That's your driver you handle. And then you declare the C Dev variable. That's the variable that will store information about your char device. And then you have to call this function that will initialize this structure, basically linking with the file operations. And then you register this in the kernel with another function C Dev add. And after that you kind of have a char device register in the kernel. So if you create a file in slash dev with those attributes, this is a char device with this major and this minor. Every call to this file will get to your driver. So if you call write to this file, the write will get to the kernel. And that is a thing in the kernel called the virtual file system that will help on this. You will check that this is a char device. This is a file that points into a char device. And then it will check for the major and minor. And then it will link to your driver. And your driver, the callbacks from your driver will be called. So the driver is basically registering the kernel a char device with some callbacks that will be called when an file, a device, file in user space is assessed by an application. That's basically an idea. So let's do our first hands on here. So what I have here is a terminal as you hopefully you can see it. And also a camera pointing to a small board that I have here. And I'm running here a very simple Linux system that I created with BuildiHood. I'm doing a completely network boot here. So I'm booting the kernel and device tree from the network using TFTP. And now also the file system using NFS. So we should take a few seconds to boot. Then we can log in. And here we go. We have our small Linux box so we can play around driver development. In this window, I have the first... So of course we don't have time to start writing code here. So I'm going to quickly show the code and run it and show how this is working. This is the first version of the driver. And here again we are trying to write an LED driver for the Linux kernel. We are starting with the user space part or we are starting with the interface that we want to create to the user using this concept of char device. So we have here an structure with information that our driver needs to run. Again, we don't have time to go deeper into the types and the functions and the API. But I'm going to quickly give an overview of the driver. So this is the variable that stores the device number, measured and minored, and this is the variable that stores the structure, that stores a char device that will be registered in the kernel. It is a kernel module. So in the end we can see we have the init and exit functions. This is the init function. And here we are basically calling those three functions that I mentioned, allocating the measured and minored number, initializing the char device structure, and registering in the kernel. I have two callbacks. We can see here in this fops structure, I have a callback for write and a callback for read. So every time I call read and write to a device file that is mapped to this driver, these callbacks will be called it. And again, I'm not writing to the GPIO registers right now because that's not the focus. I'm just providing an interface to the user. So those read and write functions are just echoing a message in the Linux kernel, right, using PR info called. So nothing fancy here. Just we're just implementing an abstraction. Let's compile it. So here I compile this kernel module and install it in my root file system. And then I'm going to the terminal. It should be there. So I can right now just load this driver. Okay, we can see that it was initialized. To access this driver, we need the measure number to talk to the driver. And one way to get this measure number is just listing the proc devices file. And here we can see 243 is the measure number for this driver that was automatically and dynamically allocated by the Linux kernel. Then now we can create a device node for this file. Then we can talk to the hardware. So I'm going to use the MK node utility to create a slash dash slash LED. A char driver, a char device, that's what our driver is, right? The measure number that was allocated and the minor number is zero because we just registered one device in the driver. We can list this file and we can see this is a char device, right? That is pointed to our driver that is registered with this measure and minor number. Now every time we write or read this file, the callbacks from the driver we call it. As you can see here, this led-on message is coming from the driver. I can also write zero. I can cat and see the content, the value, right? So let's turn on our led and see that it is on. Again, we are not really turning on or off the LED yet because we didn't write this code, but we're just providing an interface to the user. That's the first part of this driver. Now let's go to the second part of this driver. What we have right now is this, right? So our driver is a spoiled interface to the user and that's it. So second part, let's talk to the hardware. Again, we don't have time to go deep into this talk, right? Don't talk into the hardware, but some couple of slides to understand some concepts here. There are a few ways that we can talk to the hardware, depending on the CPU architecture, right? Two common ways. Port IO, in this mechanism that usually, for example, x86 provides kind of a mechanism. You have a bus dedicated to IO. So you have CPU instructions that you can use to talk to IO devices on this special bus. Nowadays, the most common way to talk to the hardware is via MemoryMapid IO or MMIO. Here we have IO devices mapped to our address space. So in the address space, you have the registers there where we can talk to the hardware. So if you have a pointer to an address in memory and if you write that to this address, you're really actually writing to a register, for example. That's how it works, for example, on ARM and other popular architectures. Of course, usually on those modern SOCs, we have MMU in the needle of the communication. Like you go to the datasheet, you have the addresses of the devices. You cannot just take those addresses and set a pointer and just reference this pointer and you are talking to the hardware. It's not that simple. So we have in the MMU memory management unit that's a kind of special hardware that will do the mapping from virtual to physical addresses for you. And you have to work with MMU when you're talking to the hardware. So that means you are going to need to map the virtual addresses to a physical address when working with memory mapped IO devices. Those are again three main steps to talk to a memory mapped IO device. You need to request a region of memory where you have the registers. This is not mandatory, but it is recommended because with this, the kernel knows which drivers are using which part of the registers available in memory. Step two, mapping from virtual to physical addresses. We can use IRMAP for that. And the third step is really writing to those registers. Although you could try to use the pointer that IRMAP would return for you to access those drivers, it is recommended to use some wrapper functions from the kernel because those wrapper functions will take care of things like engine instances and we'll use some internal kernel APIs to make sure that you're really able to access the devices in memory. Let's do our second hands-on again. And now we will be able to see the LED turning on and off. So let's quickly take a look at the search code of the... So here is the search code of the second version of the driver. It's the first version plus access to the hardware. So here we can see a few additional functions being called in the initialization function. We have the request in the region requesting some part of the memory to be used as memory map.io. We can see here, and I didn't explain that, but basically to access... So we have... I have here an LED connected to a GPIO and to access this GPIO controller, we need at least to talk to two registers, the data registry to toggle the I.O. and the direction register to change the direction to output. So that's what I'm doing here. And you can see everything is hard coded here. And we know that for those that work with kernel drivers, we know that this is wrong, but don't care about this now. We're going to improve this driver until the end of the presentation. So we can see here, this is the base address of the GPIO controller. GPIO controller one, that's where I connected the LED. And I need to register as I mentioned, right? And those are the index of those to register the data and the direction. So let's get back to the initialization function. So this request in the region will request the kernel usage of this region of I.O. memory map for the driver. And then after that, I map to a virtual address, this physical address. So this is the physical address and I'm mapping to a virtual address and storing this variable where I can use in the driver. Now we have the calls to register a chart device. And in the end, I call this setDirection function where we can see we are using readL and writeL to read and write to the register, right? So I'm reading the data register, the direction register here, change toggling the bit to set the direction as output and writing back to the register. So, and I'm doing the same thing in the satellite function. That's the function that will be called in the write callback to change the status of the LAD. Let's see it is working right now. So I'm going to compile it. I'm going to the terminal where I'm going to unload the previous driver and load the new one. Now, one thing that we can check is this PROC I.O. file where we can see that we allocated those two registers for this driver. You can see here eight bytes necessary to talk to the two registers from GPIO controller one. And then now we can really turn on and off the LAD. We can see here in my board that the LED was turned on and off, right? You can blink the LED now if we want. Very nice. We have the first version of the driver working. Let's get back to our presentation. That's what we have now. So we have an LED driver that is able to export an interface to the user and also it's able to talk to the hardware. We are finished with the work, right? So let's move to the next project. Actually, our driver is far, far away from being good or from being in a standard way or to follow the rules to write a good kernel driver that uses the modern architecture of the Linux kernel and it is implemented in a way that you can maintain it. So let's talk about the issues that we have in this driver. Problem one, we created an interface to the user. That's not good, right? Because if we say to three developers, please write an LED driver and we don't say anything about the interface that they need to export to the user, they will probably create three different interfaces, right? So the way you write and read from the driver will be different. So we need a way to standardize this interface. Second problem, we are allocated to register from a GPIO controller and that means that no one else will be able to use those registers. And in our driver, we are just using one GPIO from a GPIO controller that has 32 GPIOs. So that means no one will be able to use the other 31 GPIOs. That's really wrong. The third thing is our driver has information about the hardware and that's also wrong. We should remove information because that means that if we change the hardware, we need to change the driver and that's not good. So it's easy to maintain this driver and we need to remove the information from the hardware from the driver, right? And those are the issues that we need to solve on this driver and it will change a lot and that's where the driver model comes. So the driver model provides several abstractions to drivers to make it more modular, reusable and very easy to maintain. Among those abstractions, we have the concept of frameworks. That's basically a standard interface where every type of driver can benefit from, right? For so LED drivers, we have the LED framework for how to codex we have the ALSA framework and so on. So those frameworks, the idea is to make it possible to standardize the interface with the user. And the buses, the buses has a few objects, but one of the main objects is to decouple the driver from the device. So the device carries information about the hardware and the driver implements the logic to talk to the device. So we decouple this. We're going to see how it works. So in the end, that's what we want. And those three points that I mentioned, right? The frameworks, the standardized way to provide interface to the user. We're going to solve the second problem using GPIO lib. And we're going to solve the 30-poB problem, use the bus infrastructure from the kernel. So we're going to discuss these in details in the last three parts of this talk. Let's start with frameworks. So the idea of the framework is to provide a standard interface and abstraction to users. That means device drivers, developers don't need to think about this interface. And users doesn't need to learn new interfaces depending on the driver. So for example, a camera, you always know what to expect, right? As a developer, you know that you have to use the video for Linux framework. As a user, you already know how that will work. And also all of the applications that knows how to talk to a camera will work with our driver. So everything will work because you are providing interface in a standard way. And there are frameworks for almost every kind of hardware that we have, of course, not for everything. But from now and then, it's common that people create new frameworks as the kernel community sees a need for it, right? So input framework for input devices, keyboard, mouse, joystick, also framework for all devices, video for Linux for multimedia devices, industrial framework for sensors, and the list go on and on. And of course, you have a framework for LEDs. And usually to work with a framework is very simple. You create and define an structure, initialize this structure. That's the structure from the framework. And then you provide one more callbacks. It depends on the framework. And then you register this in the framework. That's basically it. Let's do now our 30 demo. I'm going to, again, quickly show the code here. This is the code. What has changed here? We are moving away from a shard driver to use the framework. So we are not inventing the interface here, right? We are leveraging Kern API, the LED framework, to create that interface for us. So we can see here in the initialization function of the module, we can see here that all of those functions related to shard drivers, cdev meet, cdev add, everything is gone. We are here basically initializing the framework structure. This is the name of the LED. And this is the callback. And that's it. And then we register this in the Linux kernel. After that, the framework will create all of the needed interfaces for the user to talk to our driver, right? And that's basically it. And if we look at our callback, it is calling our satellite function, right? So we remove it. They open the read function, the write function. We just have one callback. That's the callback that will change the state of the LED. If you receive a brightness variable, and with this variable, we can see that what's the level of the brightness that the user wants for that LED. And then we can manipulate the hardware from there. Let's compile this kernel module, install and see how it works. I'm going to unload the previous driver and load the new one. Nice. Now I'm going to show you this directory. So here is the directory where interfaces for LED are supported by the LED framework, sys class LED. So if I remove my driver, we can see that we don't have, we just have two MMC LEDs there. But if I load my driver, we can see that we have another one. That's our driver that exported this new LED. And if we go to that directory, we can manipulate this LED. So this is the new interface that we have for the little LEDs. It is standardized. So every new LED driver, we will export the same interface. That's very good. And from the user's perspective, I always know that to toggle an LED, I can just write to this brightness file from the LED. And we can see here. And with the framework, we can leverage common code from this kernel. So that is this concept of trigger in the LED framework. So you can enable a trigger that will automatically turn on or off your LED, depending on some kind of event. So for example, we have a trigger for writes to the SD card. So if you want to provide this picture to your user, you can just enable this trigger. You don't have to implement this code in your driver, because this is part of the framework. And that's very, very nice. For example, we will enable here the heartbeat trigger. That's the trigger that blinks the LED according to the CPU load. You can see there a heartbeat frequency in the LED. Very nice. So I guess now we solve the first problem of our driver, right? Let's now solve the second problem. The remembering, what's the second problem here? We are using two registers from a GPIO controller. And we are located in those two registers. And we are using just to toggle one GPIO. And that's not good, because the other 31 GPIOs are useless in our system right now. So we need someone in the middle to manage GPIOs. That's the fact. And for that, the kernel has this API called GPIO Lib. So the kernel implements a kind of producer-consumer model for GPIOs. We have GPIO producers. For example, GPIO controller drivers that will produce GPIO. And we have consumers. That's the drivers that we'll use GPIOs. Could also be a user application that's using GPIO at the user space level. And GPIO Lib is the API in the middle that will manage that. Those are a few of the functions from the GPIO Lib. And I'm not going over the details here, because we don't have much time, right? So I'm going to quickly show you the fourth version of our driver with the GPIO Lib. And see how that... So what has changed here? We remove all of that code to write directed to memory and API devices, write to registers. And now we are just talking to the GPIO Lib, right? So it's much more simpler as we can see here. This is an initialization function. So the only thing that we do is requesting our GPIO. So we request the GPIO. We convert it to a descriptor. And then we use it, for example, with this GPIOD direction output to set the direction of the ping to output. And in the satellite function, we use the GPIOD set value to change the toggle, the LED. So very, very simple. And of course, this will get to the GPIO Lib, API that will call a function from the GPIO controller. So that means we need, in this case, the GPIO controller working. So we can talk to the GPIO controller that will talk to the LED. And that means to make that works, we need to change the device tree to enable the GPIO controller. Because I basically disable it to write a driver that can write direct to the registers. So I'm going to now enable it again. I'm going to build. While this is building, I just want to quickly show you one thing. Oops. Let me increase here the size of my terminal. We're going to see that although we are kind of making the code more modular, more reusable, using standard kernel APIs, we are adding more features to the driver, like that trigger feature that I showed. The code is becoming smaller, right? We are adding more features and the code is becoming smaller. We can see here the first functional version. Sorry about that. Let me get back here to the directory, like four. So the code, the first workable code was 164 lines. When we move it to the frameworks, we reduce it 40 lines. And now using GPIO LED, we reduce it 30 plus lines. So we reduce it almost in a half the number of lines. Just moving to frameworks and GPIO LED. And using standard kernel APIs and providing the user more features with a standard interface, we reduce the number of lines. That's very nice. Okay. It seems we compiled our device tree. Now we're going to reboot to be able to... I don't remember if I compile this driver, but I'm going to compile again. Nice. So let's wait for the boot. Let me quickly check here the GPIO detect tool. We can see here that now we have a controller for GPIO 0 with that address that we were using the kernel. So that means that our change in the device tree worked. Let's load our driver and see if it's still working. So we loaded our driver. I will run the GPIO IF tool just to check if we can confirm here that the GPIO line 9 for this controller is allocated for our GRV LED driver. That's nice. And now we can just see if it's still working, right? Let me get the camera here and 1, 0. We can see that the LED is still working. Very nice. The last step, and it is one of the most important ones, it is the bus infrastructure. So what is missing right now is working with the bus infrastructure to decouple the driver from the device. And this is also part of the driver model. We have basically for this bus infrastructure, we have four main components, the core, and that's the kind of common code for every kind of bus in the kernel. We have a core like I2C, and we have USB, etc. We have bus adapters, that's for the bus controller. Those are drivers. We have the bus drivers, that's the drivers for devices that will access the bus, and we have bus devices. Those are the devices connected to the bus. So each of those components, they have a structure that they use to represent them in the kernel, right? The bus type for the bus core, the device driver for bus adapters and drivers, and the structure device for the bus device. So that's the main idea here. This is very generic, so a bus driver is the driver for advice that's using a bus. The core is a kernel-genetic code for a specific bus. We have the adapter that's driver for our controllers and the devices that are registered in buses. And there are a lot of advantages if we think about these architecture of buses, right? We have more control over buses in the system. We can really separate the driver from the device. That's the idea. The idea here is that the driver is kind of a class that will be instantiated when you register a device in the bus. That's very nice. It is easy, since everything is connected via buses and devices, we can easily see what's connected to what, and that's very nice because it also improves power management in the system. And so when you think about power management, we can see that every device is really connected to a bus in the system. This is an example for I2C advice. So an accelerometer driver connected to I2C. So it will register the driver in the bus, in this case the I2C bus. And then you're going to register a device in the bus and then when that happens, we will have a match that will call a function, a callback from the driver that's called probe, right? So when you register a driver, that is know how to talk to a device and you register a device in the bus. Then a match will happen and then the driver will be instantiated via a function called probe. That's the same thing. And really the question here is how this device is registered in the bus. We're going to talk about this. About our driver, it's a little bit simpler because there is no bus, right? We're talking about memory map diode devices. In that specific case, there is a kind of virtual bus called platform bus. So our driver register in the platform bus and then when devices are registered in this bus, if the name of the device matches the name of the driver, probe function from the driver is called. As we're going to see when I talk about device tree, when the compatible string, the device tree matches with the compatible register in the driver, the kernel will call the probe function. So how are those devices registered? There are a few ways to register a device, some kind of deprecated mechanisms to do this using some bus's APIs. We don't do this anymore in the Linux kernel. There are some mechanisms provided by the hardware like ACPI from x86 architectures. And of course, there are device trees. That's the main way to do it, right? You register devices in device tree. And then at startup time, the kernel will read the device tree and provide information to buses that will instantiate the drivers to talk to devices, registers in the device tree. And of course, there are buses that auto nominate devices. So we don't need to register a device in the USB bus because the USB bus is able to automatically try the devices. So let's go to our latest hands-on here, our last hands-on, hands-on number five. So let me open the driver here. And the structure of the driver now changes a little bit, right? We still have the initialization and the exit functions, but they are abstracted in this macro model platform driver. And what we are really doing here is we are converting our driver to a platform driver to be registered in the platform bus. So this is the structure of the platform driver. And we can see here the probe function that will be called to an advice that match this driver is registered in the bus. And we can see here the link to the match table of this driver that I have only one string, this compatible string, LabWorks DRV-LED. So if I register this in the device tree, the probe function of my driver will be called. And what has changed in the probe function of the driver? Basically here we remove everything, all of the information from the hardware, we remove it from the driver. And this is going to be provided in the device tree and it's going to be provided by the platform bus. So here what I'm doing here is basically getting the pointer from the device tree node and collecting the GPIO from there. So again, I am decoupling the information from the device that's coming from the device tree from the driver itself. Let's compile and see how it works. Okay, one thing that I forgot to show is the device tree node, right? So I have here in my device tree a node as you can see here with the compatible that matches my driver and here we have a node with a LAD that I want to register. So the driver is reading his information, right? That was before in the code itself, now it's outside in the driver. This is already that in my device tree, so I'm going to, sorry, I'm going to unload the latest driver and load this new one. Very nice. What has changed it? The latest is still there and we can talk to this LAD, so I can again still turn on and off the LAD so that hasn't changed, but now we can see everything in this structure of bus is all belongs kernel in this slash c slash bus. We can see information from all of the buses and here we have information from the platform bus and then here we can have a clear view of the separation between devices and drivers, right? So the devices in the bus are here. We can see information about our device, the LAD's device here that we register in the device tree, right? This comes from the device tree node and here we can see a link to the driver. So a device is linking to a driver that is managing this device and we also have here information about the driver, right? So this is the driver that is handling this device, the LAD's device. That is not, that is very nice, right? The link between devices and drivers at slash sys. To finish my presentation, I want to quickly show how flexible this model is. So this is what we have done so far, right? We went from a very simple LAD driver that was using a char device interface and also accessing directly the registers because it was not reusable. Everything was hard-coded in the driver, it's not maintainable and we changed it to use different carrying APIs and abstractions. Most of them comes from the driver model of the Linux kernel, right? So that's what we have today. So when you write to LAD, right? The call will get to the framework that we'll call a callback from our driver that will ask GPO Lib to turn on the LAD that will ask the driver GPO controller that will talk to the hardware. Let's say now we want to change the hardware. Let's say that now our LAD is connected to a GPO expander in the I2C bus. If we take that first version of our driver, we just have to throw away the code and write anyone, right? Because now the LAD is connected to a very different hardware device, like a GPO expander. With this new approach, you don't need to change anything, right? Everything will still work. But of course, there are some changes that you need to do here. First, that is this I2C bus. So we have to add the... You need I2C controller driver for the bus. And of course, you need the I2C bus there, the code for the I2C bus so you can talk to this bus. And now you need the driver for this GPO expander, right? And this GPO expander will basically talk to the I2C bus, right? That's why we have this line here. And we'll export GPIOs to the system. So it's a GPIO producer. And what you need to do now for a driver to keep working, you go to the device tree and change it, right? So you declare a node for your GPIO expander and then you connect the LAD to this new GPIO controller. Instead of pointing to the GPIO controller off the I2C, you point it to the node of the GPIO controller for the next GPIO expander and that's it. So you just enable a few extra caranomodules to work with the GPIO expander and so on. And you change the device tree and you don't have to change one line of our code. Let's say you want to do another change here and it's very different thing now. So we want to do bitmanging on GPIO. So let's say you don't have I2C bus anymore. So we want to take two GPIOs and we want to take two GPIOs and convert it to a nice crossy bus with a technique that we usually call bitmanging, right? You emulate the bus using the GPIO. Very nice. Do I need to change my ledged driver code to make that work? Of course not. If we work again, what do we need right now for that work? So we basically need a driver that will do this emulation, right? That will convert two GPIOs to one I2C bus and here we go. So this is the driver, right? The driver will use two GPIOs. So it will consume two GPIOs and export a bus. And this is a very important concept here. If we see all of these blocks here, all of them are abstractions, converting something to something else, right? So this I2C GPIO adapter, it's converting two GPIOs to one I2C bus. The GPIO expander driver, it's converting the GPIO expander that's basically a protocol over I2C to GPIOs, right? Our ledged driver is converting one GPIO to one led. So everything here, they are all abstractions to something, usually converting something more complicated to something more simple. What do we need to do now to make that work? You go to the device tree and change one line. So instead of saying that your GPIO expander is connected to I2C Z0, for example, you're going to say that it is connected to the other GPIO controller. That's the adapter for the Bitpengui I2C bus. That's it, just one line in the device tree. So the colors here, they have a meaning. So what's in orange? They are kind of a common kernel code that you don't need to touch. What we have in blue here are the drivers that we usually write for the Linux kernel, right? And the other important thing here, it's kind of, we are playing with Lego. It's very nice, right? So you have these small blocks, right, that you can connect with each other. And how do you connect them using a device tree? So you usually, everything here, you already have the Linux kernel. Everything that you talk about here, like this GPIO expander to Bitpengui, to GPIOs to make it a nice QC bus, we already have this in the Linux kernel. So you just have to go there and enable it in the device tree, right? So in the end, all of the drivers, they are very, with this architecture that we saw here in this presentation, all of the drivers, they are really very modular and reusable and pluggable. So you enable it and you connect the dots using the device tree. Very nice. I hope you enjoy this presentation. It took a little bit more time than I thought, but I think it was useful and I hope it was useful for all of you. Those are my contacts. Feel free to write me, leave me a message, connect me on LinkedIn, Twitter, or follow my blog. Again, I hope you enjoy and have a nice conference. Bye-bye.