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?  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.


What's up everyone? It's your girl, Kenzie here from Layton Sports Cards and I'm ripping a couple personal boxes here for Andrew J. We've got a 2022 Panini Black Football Hobby Box as well as a 2021 Panini Select Football Hobby. All right. Good luck, Andrew. We're going to start here with the 2022 Black Football. Good luck. Up first, we've got a Patrick Mahomes base and a rookie to 25 of Daxon Hill. Bangles, 17 of 25. We've got Titan's Patch Rookie of Hassan Haskins that is 31 of 99. A futuristic Hassan Haskins. First auto up, we've got Rookie Auto 94 of 199. Derrick Young. Derrick Young. And last but not least, how about dual Patch Rookie Auto 99 of 98 of 99. Kenny Pickett. Nice hit there for you, Andrew. That's sick. 98 of 99. We'll go ahead and get this one in a mag for you. Congrats on the black box. I've got 2021 Select. 2021 Select. Derrick Carr, Tyler Wallace, Aaron Rodgers. We have out of 99 a Vince Young on the Patch Auto. Titans. Vince Young. That's a cool one. We have some up on our website. That's Kyle Trask rookie. We've got Sleeves and Top Loaders. I know for a while it was hard to order online because they were out of, we've got four out of five. Green. Mack Jones Patch. Wow. Sick hit there, Andrew. There is a pretty big print line right across through his neck. So I will send the box here with you on that one. But Sick Mack Jones to five. A little lift on the bottom right there as well. I will send the box with that. Antonio Gates Silver and Tyler Wallace Rookie. Yeah, that's sick. Pink Manning. Zach Wilson Rookie on the club level. Tri-color Devonte Smith Concourse. That one is 238 of 249. Silver Rookie club level Gray Russo. Right out. Ian Buck Rookie at Joe Montana. We've got Blue Kylin Granson. We do have 400 points unfortunately there. Colts color match here on this though. 37 of 199 on the concourse and Ramandre Stevenson Rookie Base on our long rookie JJ Watts. Sensations D Swift. We've got Brian Erlacher on this silver field level. Antonio Brown. Tyree Kill, Ramandre Stevenson. We've got Maroon Miles Sanders on the concourse. 53 of 149. Select. We've got Club Level Rookie Kyle Pitts Felkins. Nice one there. Tyson Campbell. Rob Gronkowski. Shorinko. We have a James Cook on the XRC. There you go. Nice sit there. James Cook XRC. Nice and Andrew Nelson Aguilar and a Kenneth Gainwell Rookie field level. Jamar Chase Rookie Base concourse. Alex Leatherwood. We've got Purple Chuba Hubbard. Panthers. 48 of 75 on the concourse. Silver Frank Darby and Jamar Chase on the Premier Level Rookie Base. Josh Allen. That's what's Patrick. We've got White Simifahoko for the Cowboys on the concourse. That's 24 of 35. Silver Najee Harris. Lamar Jackson. Kyle Trask. Silver Sensations of Zach Wilson for the Jets. Josh Palmer on the concourse Silver Rookie and Marquise Brown. Richie Grant. Jillian Waddle Rookie Base. About an hour and a half earth. About an hour and a half wait right now. Right now. Kyle Trask selections. Silver Tony Gonzalez concourse. Andrew Coby Myers.  Kellan Bond Rookie to a Tag of ILO. We've got Silver Rookie Sensations a selections of Tutu Atwell. Kenneth Gainwell on the concourse Silver Rookie and a Travis Kelsey. Kill a Trab there. All right that will do it for your personals there Andrew. Again we are going to send a box of selects with you on that print line on the Mac. Thanks again for your order. Congrats and we'll get this right out to you.


Hi, my name is Haini Reisenen and today I will talk to you about ordinal regression. This first part is an introduction to the method and it will tell you when to use the method and what kind of variables we need if we want to use this. So the outline of the session is as follows. First we will talk about ordinal response variables, which are the type of outcome variables that you would use with these models. Then we will talk about what we mean by cumulative odds and probabilities, which are important for these type of models. And then finally we will look at the model itself. So how does an ordinal regression model work? And sometimes this model is also called proportional odds model or cumulative logic model. So many categorical variables that we use in our research have a natural ordering. So for instance we might be interested in how severe someone's symptoms are if they are suffering from disease and we might categorise them into low, medium and high. While it will be difficult to say if there is a distance between these categories or if the distance is the same between any two categories, we can still easily say that there is an order, low is less serious than medium and medium is less serious than high. We could also examine someone's attitudes towards a social question and we might ask people whether they are in favour of something, whether they are indifferent and gets. And again we have a natural ordering here going from more agreeable towards less agreeable. If we use this information about how the categories are ordered, we can conduct more informative and powerful analysis than if we don't. Because we could use multinomial model to model these type of variables, but actually if we use an ordinal model we get a more informative and powerful analysis. An ordinal model essentially wants to figure out what is the cumulative probability of being in different combinations of the categories of the outcome. And here's some notation. So when you CPK that is the probability of being in the response category K. So I sometimes also call it a response probability. CPK means the cumulative probability of being in category K or lower. So if we had CP2 for instance, that would be the cumulative probability of being either in category one or two combined. And one minus CPK is probability of being above category K, which is basically the inverse of the situation that I just described. And if we know all the response probabilities of a certain variable then we can calculate the different cumulative probabilities for that variable. So CP2 for instance would be P1 plus P2. If we know the response probabilities we can figure out the cumulative probabilities P1. So the probability of being in the lowest category is the same as CP1. So the cumulative probability of being in the lowest category varies for the other categories we would need to combine. The category in question and everything below that. And for the highest response category, so if we had three categories P3 would be 1 minus CPK minus 1, so CP2. Here is an example of how this actually works with some numbers. So let's pretend that we have an outcome variable with three categories. And we have some probabilities of each of our respondents being one of these categories. The probability of being the lowest category P1 is 0.5. The probability of being the middle category is 0.3. And the probability of being the highest category is 0.2. If we want to figure out the different cumulative probabilities we can do that now that we know the response probabilities. So the probability of being in the lowest category CP1 is equal to the probability of being in that category, so that is 0.5. So we don't actually need to calculate anything here, we just know that it's 0.5. The probability of being in category 1 or 2, so in other words CP2, is the probability of being in category 1 plus the probability of being in category 2, so that is 0.5 plus 0.3 equals 0.8. The probability of being in category 3 or lower, so CP3, is 1, because 3 is the highest category that we have in our outcome variable. If you don't believe me, you can calculate 0.5 plus 0.3 plus 0.2 and you'll get 1. But we don't even have to do that because we know that everyone has to be in one category, so the probability of being in the highest category or lower is always 1 or 100%. Like with other probabilities, we can transform these probabilities into odds and log odds. So if we take the cumulative probability of being category K or below CPK and divide that by the inverse of the probability, we get cumulative odds. And if we take a logarithm, a natural logarithm of these odds, then we get cumulative log heads, just like in other regression models, other logistic regression models. We can then use this cumulative logic transformation in our ordinal model. And on the left hand side, you can see that we have a logarithm of the cumulative odds. And then on the right hand side, we have something that looks quite similar, even if not exactly similar to other regression models that you've seen before. So we have an intercept and we have a slope, so here we only have one explanatory variable in our model. So like in a multinomial model, in ordinal model, we are estimating K minus 1 equation simultaneously. So if we have three outcome categories, then we will estimate two different models. The difference to multinomial models, however, is that while each equation has a different intercept, alpha K, the slope in every equation is exactly the same. In a multinomial model, the slope would be different in every equation. The intercepts that we get are always ordered in size, which is also different from a multinomial model. So the intercept in the first equation should always be lower than the intercept in the second equation, etc. You might have noticed that if you look at the regression equation, on the right hand side, you have something a bit strange going on before the beta. So you have a negative sign there before our beta 1. And that is to make interpretation of these models easier. And this is what most statistical software do, state and SPS, for instance. It means that once we take this negative sign and plug it into our equation, a positive slope implies that higher values of the explanatory variable are associated with an increase in odds of being in a high response category rather than a low response category. If we didn't have the negative sign, it would be the other way around, which would be quite unintuitive. The same much ratio applies to all of the thresholds, so in all equations for the different intercepts, because we have made something that is called the proportional odds assumption. So we assume that it actually makes sense to have the same slope for every equation. And there are ways to test whether this is actually the case, but we will talk about that in later sessions. Then we will look at a simple example of an ordinal model. We have some data about undergraduate plans to apply for postgraduate study. So a survey asked undergraduates how likely they were to apply for a postgraduate study. And they had three different options that they could choose from. They could say unlikely, somewhat likely, and very likely. We also collected data from their parents' education, and we want to know whether their parents' education is associated with their own likelihood of applying for postgraduate study. So the parents' education is a binary variable, which takes value one, if at least one of the parents is a graduate themselves, and takes value zero if they are not. And in the table you can see how the outcome variable of the likelihood of applying for a postgraduate study is distributed. So we have 55% of undergraduates in the unlikely category, 35% in the somewhat likely category, 10% in the very likely category, and in total we have 400 respondents. If you use data, here is how your ordinal regression results will look like. So these results are in the logout scale for now. And in the first row, where you have some numbers, you see your coefficients. So the 1.12 here corresponds to the likelihood of applying for postgraduate studies if at least one of your parents has graduated. Then the next two rows tell you what the intercepts are for the two equations that we have run in this model, because we have three categories in the response. So the first of those two rows says 0.376, and that is the intercept for our first equation. And then the second row says 2.452, which is the intercept for our second equation. Like with any logistic model, you can interpret these results using odds. And like with any logistic regression model, you do that by exponentiating your logout value. So if you exponentiate 1.127, which was our coefficient for parent education, you get 3.09. And this 3.09, remember, does apply for all of the two equations that we modeled here. So we could say that for students who have educated parents, the odds of being in the very likely versus the combined somewhat and unlikely categories are three times greater than those whose parents are not educated. We could also say that the odds of being in the combined very and somewhat likely categories versus unlikely are three times higher for those with educated parents compared to those whose parents are not educated. So to sum up, that means that the odds of being in a higher rather than a lower category of the outcome are higher for students with educated parents. Like with other logistic regression models, you can also calculate predicate probabilities. If you want to figure out what is the probability of being in one of the exact response categories out of the three possible categories. And you can see the formula for that calculation here. And you can see that it looks very similar to other logistic models. The difference, the main difference being that on the left hand side, you see that we are calculating cumulative probabilities rather than response probabilities. And on the right hand side, you can see that we must remember to change the sign of our slope. So we have a negative sign before the slope. So if you wanted to calculate the probability of being in category one, for those whose parents are not educated, we would take the first equation which had the lower intercept 0.377. We would take our slope for parent education 1.127. We would change the sign because of the negative sign here in the equation. And then we would multiply that by zero because now we are looking at the reference category of this explanatory variable. So those whose parents were not educated. If you were looking at those whose parents were educated, you would multiply it by one. We exponentiate that equation, divide that by one plus the same thing. So we exponentiate the value of the first equation. If we solve that, we get 0.59 and that 0.59 is both the probability of being in the exact category one and the cumulative probability of being in category one. For category two, we do the same thing except that we changed the intercept. Now for the higher intercept that we had in the second category, and then when we solve that equation, we get 0.92. So now we know that the cumulative probability of being either in category one or two is 0.92. But we don't yet know what is the probability of being in the exact category two. However, we can fairly easily find that out by taking the cumulative probability two that we just calculated and then subtracting the cumulative probability one, which we also just calculated. So we plug in the values 0.92 minus 0.59 equals 0.33. So now we know that the probability of being second category for those whose parents were not educated is 0.33. We can also calculate the probability of being the highest category using the information that we already have. So the probability of being in category three is one minus the cumulative probability two. One minus 0.92 is 0.08. Here is the table of response for probability showing the response, the different probabilities for the two categories. So those whose parents were not educated, so the ones that we just calculated, and those whose parents were educated, I haven't shown how to calculate that, but the only difference is that you multiply the slope by one rather than by zero. And you can see that parents education does make a difference. So those whose parents were educated had a higher probability of being very likely to apply for postgraduate education, 25% versus 8% among those whose parents were not educated, for instance. And if you look at the lowest category, unlikely to apply for postgraduate studies, we can see that for those whose parents were educated, and the probability of being in that category was 32% compared to 59% among those whose parents were not educated. When you use ordinal models, calculating response probabilities is usually quite useful because sometimes dealing with the cumulative odds is a bit difficult to understand if you're not very familiar with these models. So depending on your audience, you might want to consider using probabilities rather than odds when making interpretations from these models. Thank you.


Few leaders throughout history have proven themselves adequate to claim likeness to Plato's philosopher king, an individual free from the lust and greed that so often tempts other, less worthy monarchs into wholesale debauchery and absolute corruption. Which of those great men and women of the past will heed Plato's call? Here we will name those three who have displayed the qualities characteristic of this selfless head of state. Before beginning, if topics such as eastern and western philosophy interest you, consider subscribing to the channel and checking out a few of my other videos. Now, what should be our litmus test to gauge who should stand atop our list? We should first consider what characteristics Plato set forth in the Republic. He said first and foremost the monarch in question should have an undying love of wisdom and a genuine thirst for philosophy. They should also be selfless, reliable, and have a willingness to live a simple life despite their high status. From an early age they should be subject to a demanding education and use their knowledge of goodness and virtue to help other citizens achieve these as far as possible. Most, if not all, of these characteristics must be present for those on our list. But I think it's important to note that while some of these individuals may not be philosophers in the Platonic sense, they are indeed lovers of wisdom and knowledge. Our list begins in 550 BCE following the Persian conquering of the Medes. This decisive victory marked the first union of the Iranian people in the beginning of the reign of Cyrus the Great. Hailed as the gentle conqueror, Cyrus was held in high regard by his countrymen and even well respected as a kind and just ruler by his enemies. Said to be perhaps one of the few deserving of his given title, the Great, Cyrus, while not a philosopher in the traditional Platonic sense, earns a spot on this list because of the keenness of his intelligence, his kindly disposition, extraordinary patience, selflessness, and discipline regardless of circumstance. His reign was marked by a sense of humanitarian equality, seemingly absent in prior generations and shamefully absent many generations thereafter. For example, while popular practice of his time was to conquer, slay, and enslave, Cyrus instead accepted and befriended those nations with all their cultural heritages intact. He also saw the abolishment of forced labor, a practice which took parts of the Western world 1800 years to match. Much of what we know about this archetypal leader of antiquity comes from the Greek historians Herodotus and Xenophon. In the ruins of Babylon around 1879, an artifact was uncovered. This artifact would be named the Cyrus cylinder and has been declared an ancient declaration of human rights. Cyrus's reign would come to an end approximately 20 years after its conception.  Do not therefore begrudge me this bit of earth that covers my bones. The reign of Cyrus the Great most certainly changed the course of political history for the better. For our second philosopher king, we fast forward nearly 1,000 years to yet another Persian king. Known most commonly as Anushu Ruan or the immortal soul, this man was Khazro I. Often hailed by contemporaries as the new Cyrus, Khazro's reign began in 531 A.D.E., during which a sort of Persian renaissance took root where knowledge grew, culture thrived, and philosophy became commonplace. Khazro earns his place on this list due to his keen interest in literature and philosophy, most notably Neoplatonism. This love for philosophy was coupled with his goodness of character and exemplary virtue. Having studied under notable individuals such as Mar Bar Sama, Paul the Persian, and the Syrian bishop of Kwardu, Khazro certainly meets Plato's educational requirement. Following the Emperor Justinian's abolishment of the Neoplatonist schools and Athens, thousands of refugees migrated to Persia. Because of his keen interest in Platonic philosophy, they came hailing Khazro as Plato's philosopher king. Unfortunately, following his death in 579, his ideals quickly faded, thus putting an end to this renaissance period. Who is the most deserving of the title philosopher king? Which individual will stand at the top of our list? Here at last we reach our king of kings and, as said by historian Michael Grant, was a man hailed as the noblest of all the men who, by sheer intelligence and force of character, have prized and achieved goodness for its own sake and not for any reward. This could be no one else but the 16th Emperor of the Roman Empire, Marcus Aurelius. What qualities did he possess that would place him above the likes of Cyrus and Khazro? Luckily, there is no shortage of greatness here. Through floods, wars, famine, pestilence, and revolt, the greatness and character of this man never wavered, and his devotion to the state and its people remained. Fighting wars by day and philosophizing by night, Marcus Aurelius maintained his simple way of living even throughout his reign. He who owned everything the world had to offer, whose very word was absolute law, abstained from ostentation, and remained true to his philosophic ideals, even when lesser individuals would have succumbed to debauchery. No matter the circumstance, he upheld his promises and never went back on his word. Known to be kind, compassionate, and disciplined beyond human limitation, Marcus Aurelius was well respected by his contemporaries and even admired now 2,000 years after his death. Left behind are his private reflections, titled The Meditations, which lay out how one might go about living the best possible life regardless of circumstance. There is no question that Marcus Aurelius stands as the living contradiction to that most famous of quotes by Lord Acton. Power tends to corrupt, and absolute power corrupts absolutely. In my opinion, he stands at the top of past monarchs, unrivaled in virtue, and strength of will. Which ruler do you agree should be at the top of this list? Do you agree with my choices? Comment below with your selection for the most deserving of the title, Philosopher King. Thank you for joining me and exalting these individuals that were both great and good. If you enjoyed this video and would like to see more content on Eastern and Western Philosophy, make sure to subscribe and hit the notification bell. As always, thank you for talking philosophy with me. Until next time.