 Right. I think that we can start. So, hello everyone. We will be talking about enabling Zephyr on your hardware platform. Anyone here has experience with Zephyr? Development? Well, good audience. What about adding new hardware supporting Zephyr? Maury, of course. Okay, so I am Diego Suero. I am an embedded Linux platform engineer at Zephyr in the UK. They are in Cambridge. And I am the CEO of Embarcados, which means embedded. It is a website where we publish articles about embedded systems development in Portuguese. So, let's see our agenda for today. So, we are going to see how is the hardware support implementation in Zephyr. How we can add a new hall, a hardware abstraction layer. How do we add a new SOC, adding new drivers, a board, some debugging tips, and a hardware support checklist, depending on what you want to add. I will give you a checklist. And how do you contribute to mainline? Please feel free to interrupt me to make questions. I will try to make this presentation in 30 minutes, and if you want, we can have a QA session in the end. So, let's see how it goes. But if we don't have time, I am glad to address any questions that you have offline. So, before we begin, just a few words. This is not an introduction to Zephyr. For these, I think we already had some presentations here about Zephyr, but you can find a lot of documentation and other presentations online. The presentation is very dense. I have to shrink it a lot, but I still think that it's very dense. We will not cover every slide in detail, because the idea is to give you a reference to help when you decide to add a new hardware platform. You can have good slides as reference, some guides to, for example, like what kind of source files you need to look to add support for your new hardware. The source code examples are based on the master branch, on this specific commit, and when I finished this presentation right before the version 113, and then on the week after the version 113, a lot of things changed, some pathes, so I spent a lot of hours updating the presentation, and when I was looking to the Zephyr source code last week, I saw that I had another outdated information. But that's okay, I will point you out what is outdated, but maybe when you actually go to add the support for your hardware, some of this information can be outdated. Some source code that I will show here will be striped just to fit on the screen to give you the main idea, what do we have on this source code. All the examples are based on the Zephyr running on the ARM Cortex-M4 core inside the IMX7 processor, so this processor has Cortex-A7 and Cortex-M4, so I personally worked on porting Zephyr to run on the Cortex-M4 inside of this processor, and this presentation will not cover all the hardware aspects that we have on Zephyr, like adding a new CPU core, and this is actually very well documented in the Zephyr website. So let's start with the hardware support implementation in Zephyr. In this diagram, we represent from bottom to top the hardware configuration hierarchy. So we have the architecture, CPU core, the SOC family, the SOC series, the SOC itself, our drivers, and the board. That can be the hardware platform for our product. We can optionally make use of a hardware abstraction layer to support the implementation of this higher level hardware configuration and implementation. I will give more details about the how later. So let's take a look on that hierarchy of the hardware configuration. So we first have our architecture, that is the CPU core itself, like ARM-ARC-X86 and so on. Then the CPU core is where we find the early bootie sequence, interrupt and exception handling, thread, creation and termination, and context switching, fault management and etc. Some examples like the Cortex M0, M4, Atom and others. Then we have the SOC family, which represents a single SOC type that can have more than one variations in terms of referrals or features like we, for example, the Kinects, the IMX family, the STM32 and other ones. Then we have the SOC series that represents the specific referrals and features for an SOC family variation. I think it's easier to understand by example. So we have the, inside the Kinects, we have the Kinects, K6X, the KW something for the IMX, IMX6, IMX7. And then the SOC that is actually soldered on your hardware and it's all associated configuration like pinmuxing, clock rate and other things. Then you have the drivers. Include a device model that is responsible for configuring and initializing these drivers and each driver follows a device model API and it's specified and each driver has its own API as well. And you have drivers like interrupt controllers, serial communication and timer and what we normally find in Linux or operating systems. And then the board which includes the SOC and it's associated with referrals and features as well as external components and you can represent your product as a board or you have more than 100 boards available to use with Zafer. So in Zafer basically you configure the hardware in a top level fashion and in a low level. In the top level you will be using symbols defined in K-config. And the final processing of this series of K-config will be inside of these build folders. So the normal .config and header file. Then the low level hardware specifically configurations are defined using device tree. And the final processing of these device tree files are in these paths inside the build folder. And the device tree layout is as much as the same as we find in the Linux kernel. There is no difference if you are already familiar with the Linux kernel device tree. Here is the same thing, but the main difference is the Linux kernel. It uses the DTBO and the kernel parses that information at runtime and applies the configuration that you set in the device tree. In Zafer it is basically used to generate hash defines. So it defines that it will be consumed by drivers or your application source code. So let's see now how we add a new hall. So the hall is added to support the SOC board and drivers implementation. It is basically low level libraries mostly implemented by the SOC vendor to interface and configure the hardware. The different types of hall and the pros and cons are covered in Maureen's presentation. I think you've done last year. Yeah, last year. She explains the different types of halls and the abstractions. So when you are adding a new hall in the Zafer source code, the Zafer technical steering commit, we will approve for the source code that is known as Apaches license. It is located on this folder, X-Hall, the vendor and the library name. We will see some examples later. So just bug fixing modifications are allowed on these source or header files. There is no standard coding style or directory structure, how you normally see in the rest of the Zafer source code. Almost all ARM devices follow the CMC's standard headers for registers manipulation. It is enabled with a config option. In this case, it's an example of how we enable the IMX Hall. And it has a set of kconfig and CMakeList file to determine what the directories of the source code include paths and so on. This is an example for the IMX 7, where I imported the source code from the MXP FreeRTOS BSP into this path. So we can see on the root of this path we have a CMakeList file, a kconfig and a readme file. Then we have a drivers folder with the drivers for I2C interface, UART and other processor features and a device folder where here we have the CMC's header, CMC's compatible header. And as you can see inside the same Hall, we support the IMX 7 and the IMX 6. So they are different SOC series from the same SOC family that is the IMX. Any questions? So the readme file. This is what the Zephyr technical history and commit will look like. You need to follow the structure that they put on this web page where you will list from where you are getting the source code, the specific commit that you are importing, the purpose, the description or some dependencies if it has external dependencies or dependencies even inside the Zephyr source code. So this is an example of the kconfig. So you can see that we are declaring the symbol here of the has IMX Hall. And when it is enabled, we have other kconfig symbols that we can use. So like this is an internal processor feature, the RDC that is the resource domain controller when you are sharing resources between the two cores. This is the clock control module. Then you have it for the GPIO, I2C. These symbols are used to control what we want to compile inside the Hall. This is, and then the CMakeList file where CMake will find the source code. So if we are using the IMX7, it will set this IMX device and then it will add this proper folder as an included folder. If you are using the IMX6, it's going to change this folder. And the same thing for compiling as well. The drivers folder is common in this case for both SOC series. So when the configuration process finishes, we generate a .config file. And for the IMX, this is in a specific sample application. This is what the config symbols that were set for the IMX Hall. We are going to use GPIO and I2C. So let's talk about how we add support for a new SOC. When implementing a new SOC, we define the SOC family, the SOC series, the SOC and the SOC part number configurations. It is located on SOC architecture, SOC family and the SOC series. The SOC, it has an SOC.c file that it implements the normal SOC initializations like clock, cache, memories, or even implementing chip erratas. It is called during the system initialization process with the priority zero. It provides SOC header, which will be included by drivers or even by the board implementation. It can extend the functionalities not provided by the vendor hall, so things that you are missing in the vendor hall, because you are not allowed to change the vendor hall, Mary will not accept that. So you put on this folder. And it contains a set of kconfig files and device tree fixups. I will explain later about this device tree fixups. So the default architecture and the SOC family, the SOC series configs are selected in the board defconfig files. And in this example here for the Toradex Colibri module, we set the, so this is the defconfig file for the board. We selected the ARM architecture, the IMX family, and the IMX7 underscore M4 family. And these default configurations will dictate what kconfig files will be sourced and what kconfig entries will be selected and generated for the SOC presented on the hardware platform. Everything we will start from here. And it contains a DTSI file defining the SOC peripherals and features and it's located in this folder, DTS architecture, the vendor, underscore the SOC name and DTSI. So it may have the DTS fixup that contain mappings from the existing kconfig options to the actual underlying DTS derivated configuration defined. I know that it seems a little bit confusing what I wrote here, but I will show you an example and I'm pretty sure that you will understand what I'm trying to say here. And this is one thing that changed recently. The name of the file is not DTS.fixup anymore, it's DTS underscore fixup.h. But the content is pretty much the same, nothing changed. So this is an example of the DTSI file for the IMX7 Cortex M4. So it looks like the normal DTS that we find in the Linux kernel. So we have the CPU node, we set the compatible string, and then we set the memories that we have available for this processor. And note that this source code is striped, we have much more information in here. This is an example how we declared the GPIO7 node. So we put a compatible string, the registers, range, the interrupts, label, this is the resource domain controller, the permissions that we are going to add for this specific resource. And the number of the bits of the RQ priority for this processor. This is an example of the DTS fixup. So now I think you're going to understand what I meant to say on that statement. So in the left side here, the GPIO driver is common for the IMX7 and IMX6. And it is consuming these defines. But when the DTS file is processed, the defines that it generates are these ones, with the register address. So we use the DTS fixup to be able, the driver to consume the data that we defined in the device tree. You can use aliases to get rid of this, but this is now most of the SOCs you are going to see this kind of define is in there. And the same idea applies for the UART and other interfaces. So here we can see an example of the directory structure for the SOC series, the IMX7 SOC series. Again, this root folder is common for the IMX6. So these files, these K-config and CMake lists are common between IMX7 and 6. And then on the IMX7 you have these lists of the K-config files and source code files as well. A lot of K-config files, isn't it? Yes, you need to know in advance what the device tree is generating as a define, and then if you want to abstract that define, you put there. I'm not sure if I understood your question or even if I answered it. Yes. I haven't put it here, but you have some temporary files of the DTS processing that you can understand how it is generating these. Basically, I think it's Python code. It's not very complicated to understand how it's doing stuff. But I think that they are about to change a little bit these, for example, to get rid of these DTS fix-up because it's a manual process and why do we need this manual process. So we have a lot of K-config files, unfortunately. So when you run the CMake command to configure the build, it will source all these K-config files. I haven't found in the Zephyr documentation the exactly order that it does the sourcing of the K-config. Maybe this is... You can have different orders depending on the architecture or the SOC, but what I could find is that mostly for ARM platforms, it will follow this. And here I put the environment variables. So as you can see, a lot of K-config files. And specifically for the Toradex Colibri, resolving that path and environment variables, we will source all these K-config files. And when we are interested about this SOC, these are the files that will be setting the K-config symbols related to the SOC. Unfortunately, we will not have time to go in each file in detail to see what it's setting or not, where you need to set this specific symbol. But I think that maybe this is a good reference for you when you decide, okay, no, I need to add a new SOC. So what kind of file do I need to change? So maybe this will be a good reference. And then you go in those files and see, okay, in here I am setting, I don't know, I'm declaring the symbols for the peripherals interfaces, or in here is different aspects of the hardware. Okay? When the configuration process finishes, we will have all these configurations, these configs related to the SOC in this .config file inside the build folder. In this case it's for the shell module. So we have the SOC, the SOC series, number of our cues, clock information, the SOC part number, the family. So these are all related to the SOC and these are somehow all defined on that different .config files. Now let's talk about adding new drivers. So, as we know, the driver provides interface to the hardware, nothing new. The source code is located on drivers and then the driver type, like I2C, serial, SPI, and so on. It must implement the API defined in the driver type header, so you're going to have include I2C.h, SPI, serial. So it follows the strategy of one driver multiple instances. The selection and configuration is done via kconfigs and device tree. You can mix as well. May use the vendor hall and in this case we call this driver as a shin driver because this is basically an adaptation layer between what we have in the SOC, the vendor hall, and what the driver needs to expose to the applications. Its initialization is performed during the kernel boot. It has a YAML file describing the device tree nodes and properties. It has a device tree file to define the driver's properties and configurations. There is a good run-up documentation available in the Zephyr website and it's a very big top case. It's huge. I even tried to put some source code here but then I said no, I do not have time to go for it. But yeah, if you are familiar with the Linux kernel driver's implementation, you will feel good here, okay? And you can use a lot of drivers for other SOCs as a base to understand the code structure. So following our agenda, let's see how we add a new board. Any questions? No. So like I said, a board cannot represent the application-harder platform. The source code is located in here, boards, architecture, and the board name. It extends the SOC, enabling or disabling its peripherals and features and instantiate external device like accelerometer, thermometers, and it has a device tree associated to it as well and use a K-config. It may apply the P-muxing configuration for the specific SOC, has a board header to be used by drivers or your application, and contains a def-config file like I told you before to select which SOC is used on your hardware platform. It may set flash partitions in the device tree file, may include a DTS fix-up as well, so maybe you will have to do that DTS fix-up for the external I2C devices like accelerometer. When the device tree processing occurs, unfortunately I don't have an example here, but for an accelerometer with a specific I2C address, the define generated will include the I2C address in the defining name. But your driver doesn't care about this. It only wants to know what is the value of the I2C address, for example. So then you can fix up this in the DTS fix-up file. It may include other source files to configure or implement specific features of your board. It may provide a board CMake to instruct how to flash and debug the target. We'll have a YAML file as well to list board properties like flash, RAM sizes, two-chain usage, and must have a documentation file listing the supported features, interfaces. Of course, if you wanted to upstream your hardware support. So here, an example for the Colibri. So we can see the board header, the CMake list file, DAF config, device tree, YAML, the documentation folder. And again, so in this case, we have two config files and source code just for applying P-muxing. The board YAML file, so in this example, we use by default 32 kilobytes of RAM and flash. We say which two-chain we are using and some we wanted to ignore tests for net and Bluetooth. This is when Zephyr in continuous integration, it runs a lot of sample apps and tests. And when it is building, for example, to test Bluetooth, it will not build for the Colibri because Colibri doesn't support Bluetooth. So here is the DTS for the Colibri board. It includes the IMX7 DTSI that we saw earlier, set a compatible string. So we set some aliases. So these aliases will help to not generate that very strange defines that we can use easily on the source code. We choose which flash we are going to use and the RAM. And we are choosing which UART interface is going to be used by the Zephyr console. We set an LED as well to this specific GPIO. Then we have GPIO key as a user switch to this specific GPIO as well for the UART interface. Okay, let's compile, let's use it and set the balder rate for instance. And we enable GPIOs, I2C interfaces, PWM. So it's very simple. This is an example of the kconfig.board file for the Colibri. So yeah, we are basically defining the symbol here. For this board and say that we depends on this SOC series and we are using this specific part number. This is an example of the kconfig.defconfig. So here we basically put the invisible symbols that are not selectable by the user in the make menu config. So it thinks that we don't want the user to be able to select. This is under your control. And you can see, so if we are using Colibri, if the GPIO is enabled, we will by default select the GPIO port 1. If UART is enabled, by default we are selecting the UART 2 and for other interfaces. And this is the board named defconfig in this example for the Colibri with the visible symbols. So this means that when the user type make menu config, it can see the symbols enabled or disabled. And so here is selecting the family, the core architecture, the SOC series, the board and we are going to use UART console and some other default parameters. So let's see some debugging tips. So I think it's more a random debugging tip so when I was implementing these, I've used some of these stuff. So first thing is when you are implementing a new SOC, try to look at some source code or reference to see how it is starting up initializing that SOC. In my case, I used the freer pause. I think it's a very good reference to be used. One thing seems to be not booting. You are not seeing anything in the console. Try on the SOC source code file initialization. Access the UART registers, print a character or something just to see if things are coming up. Okay. Implement the UART driver first. Print the case life. Use some of the available system log-ins. Turn on the asserts to try to catch errors or even use on-sheet debugger like J-Link or U-Link. So this is the hardware support checklist. So basically when you are adding a new whole, you are going to need a k-config file and a CMake list to include the source code that you want. You will have to import all the source code, but you only select and compile what you want. For a new SOC, these are the files that you need to add. It's just a checklist. I'm not going to throw again. And for a new board, these files. So contributing to mainline. You decide to contribute to mainline. You have a fancy board with you and you saw that it's not implemented on Zephyr. So first thing, you have to follow the coding standard. Follow the commit guidelines. When you commit, they have a lot of checks that you'll check if your commit message is following the guidelines. So you will have to write documentation. There are documentation guidelines as well. Run the sanity check. The sanity check, if you run locally, you will take hours. But you can hopefully at least choose your board that you want to test. Then there is a very good example of the contribution workflow, like creating a fork from the Zephyr, your branch, and then creating the pull request and all these kind of things. And even if you needed to change your pull request, how you change. So there is a very good example here. When adding a new platform, so you have to split your pull request in different patches. So a specific patch for adding a new whole, and a specific one for each driver type you are adding, a different one when you are adding an SOC support, and a different one when you are adding a board. And be patient. Sometimes things are not that quick, it takes long, but it will happen. And yeah, this is it. So the references, any questions? Some vendors offer tools to help us start up with our boards. For example, Q by STM32, things like that. So they'll help you configure the GPIOs, the clock, whatever. And they'll generate a start-up code for you that you can just use. It's the way to incorporate some of the tools or the output of the tools when defining a new board. Or is there another graphic tool that Zephyr can offer to generate a start-up code? You mean like ST, you have the Q? Yeah. As far as I know, no, Zephyr doesn't have anything with this. Like this. The STMQube actually is in Zephyr. It's there. It's used by some drivers. Of course, if you are maintaining your own application, you can include if you want. But Zephyr accepting this to mainline is a different story. Maybe Murray can even clarify better this. So we considered that. I think the problem is sometimes those kinds of tools do the same kinds of functionality that you would see in K-Config or Device Tree. So there's some conflicts there. So generally we don't have a lot of those kinds of tool output artifacts imported into the tree. Because at that point, once you import in the tree, then you lose that flexibility that you have with K-Config to enable things, disable things like that. So the closest thing that we have right now, and this isn't really a graphical interface, but there was some work put into making sure that we have at least menu config enabled on all host platforms, because initially, a lot of developers typically use Linux, and that's well supported on Linux, but because we also want to support development on host platforms, there was some work done to make sure that we actually have menu config support as well on Windows. Any more questions? One, two, three. Yeah, this is it. Okay, thank you, guys.