 Good afternoon everybody. If you are here today, probably you know what Zephyr is. Probably you have heard of it in this conference or in other conferences. For the ones who don't, it is an open source project aimed at simplifying the development of new lightweight IoT solutions. My name is Vincenzo Fraschino and I am here today to present to you the results of the porting of Zephyr OS on ARM Beetle and more in general to provide hints and tips for porting the OS to new platforms. This presentation is not meant to be a fully comprehensive porting guide, but a starting point for those of you who are interested in porting in a new platform disoperating system. But let's begin. In today's presentation we will go through a brief overview. We will analyze Zephyr's architecture. We will focus on the porting of Zephyr on Beetle starting from the basic concepts up to the device model and the drivers. And hoping that at that point I have inspired you a bit, we will see how to contribute to the project. And at the end we will have a little demo and some examples running on Beetle. What is Zephyr? As we have seen this morning from Anna's presentation, basically Zephyr is an operating system that runs on microcontroller units with a small footprint. It's codebase was established in 2000, more than 15 years ago, and it has been made open source in February 2016, almost a year ago. It is licensed under Apache 2.0. It is modular and configurable. It uses in particular k-config and k-build and now even the device tree to allow versatility and configuration. It does not provide a user space and dynamic runtime. It means that basically all the system is described at compile time. It allocates memory and resources when possible statically. And it is obviously close platform as we have heard. It runs actually on ARK, on X86, on ARK and on many other platform like RISC-5 or other systems. But how Zephyr does that? Basically, Zephyr OS can be divided in a BSP, a set of kernel components, an high level API and an application. This is basically a generic image that runs on your hardware like it does on Beetle, for example. The Zephyr kernel offers a single address space. Basically, the application and your kernel is compiled into a single image. It's highly configurable, as we have seen with k-build and k-config. It has a compile time resource definition, so you describe all your system at compile time. When the image comes out, it contains all the information already defined. It has a minimal error checking to allow high performances. And basically, if you are in the bugging mode, you can enable extra error checking. And it provides a suite of services. This suite of services is, for example, multi-tread IRQ management and others. Today, in this presentation, we will focus on this part, in particular on board support, on the SOC support, on device drivers and a little bit on power management. Even if it requires, what did it happen? Let's hope that this time it works. OK. But how Zephyr does that, as we were seeing it as a set of facilities in order to enable these operations. And basically, in today's presentation, we will focus on the BSP and on the kernel parts that have direct impact on it, and, in particular, on the board support, on the SOC support, on the device driver part, and a little bit on the power management. How can we take advantage of all of these? Let's start from some basics, like the setup of the environment. Before setting up the environment, of course, we need to know what is our board and what we are compiling for. So basically, in our case, we are using this device here that is a Beetle board. A Beetle board is Cortex M3 based, and it's an arms IoT evaluation platform that includes the IoT subsystem for Cortex M and the Cordio BLE smart radio. It has 256 kilobytes of flash, 128 kilobytes of RAM, and does an external flash of 2 megabytes, offers some debug interfaces, and it is compliant with the Arduino shields. Let's see how to compile for this platform a single application, like it can be a low-word. So this example is tested on Ubuntu 14.4 and to set up your development environment, basically what you need to do is install the SDK provided by the Zephyr project and a set of other utilities that you can use together with your Ubuntu pre-installed, and then clone the source code and provide to the Zephyr OS compilation system environment variables that provide to the compilation system the information in order to compile. Zephyr supports even external SDKs, and in particular, as you can see from here, I provide another example with the GCC arm. The only difference is that basically you install it from an external repository, and when you set up your environment variables, you define the GCC arm as the main compiler that you wanna use. Now that we have set up our environment, we will use it to compile a single application and to verify that this is working. The simplest application that we can compile and that is provided together with the Zephyr project is the low-word application. The low-word application basically can be compiled using this set of commands. It resides in samples, low-word, and after you have set up the environment with the Zephyr-M.sh script, you can compile it for a specific platform using the switch board equal. In our case, board equal is V2M beetle that is the name of the board that we are using. Once we have finished, we flash the binary and we can have an output like this one. Obviously, this is the final result of a basic porting because a basic porting is a set of features plus a serial port, and the rest we can figure it out going basically once we have the serial port. But let's see what we have do-do to get there. And in particular, let's see how do we port the BSP to Zephyr. To port a BSP to Zephyr, basically, what we need to focus on is the sock porting that resides in Arc, Arc that can be ARM, AI32, or X86, SOC, and inside there, we will have a directory called, like the name of our SOC in which we put all the files that we need to do the sock porting. Same thing we will do for the board. In particular, with the board, we will provide the defconfig file, and the defconfig file will have the name board underscore defconfig, like in Linux, this. So after we did that, we will start implementing a set of drivers that are required by our platform, like NBP in Maxing, GPIO, each one residing on different directories into drivers. And once we've finished, we will have to provide a set of documentation in order to cover the basic information that we are putting into our implementation. Let's see how this works all together, and in particular, let's see how on ARM platform, Zephyr does the boot, and on which moment of the boot our drivers are called in order to understand how to plug our drivers in the big system. So basically, at the resettendler, what happens is that Zephyr setups an initial stack, verifies if we are running an executing place system, and this is true for almost all the ARM platform that I saw. And if this is true, it copies the initialized data from ROM to RAM. Once this is done, locks the interrupt that will be unlocked once the main task was in execution. If present, initializes the platform watchdog, and once this is done, switches the stack from the main stack pointer to the program stack pointer. Once this operation is complete, it jumps to the prep-c. Prep-c is an architecture-specific feature that allows to set up a basic system to run C code. So to do that, basically what prep-c does, it relocates the vector table if the option is enabled. It enables the FPU, again if the option is enabled, it zeroes the BSS section, and once this is finished, it jumps to the main initialization to the kernel that is done through the start functionality that resides in kernel init.c. The responsibility of C start is to context switch from a fake context that is the one in which we are running at reserve in a startup context, or better said, the main thread. Now, at this point, once this is done, we are able to execute C code. In particular, the perspective on which C start does this is initializing the kernel data structures and the interrupt subsystem, and it performs the initial driver initialization. In particular, you can see here three levels, kernel one, kernel two, and primary. Kernel one and kernel two are the one introduced by the new unified kernel. The primary is heritage from the past, and it's actually going to be deprecated with 1.8. Once this is done, basically, let me spend another couple of words on these initialization features. Basically, when we plug our drivers, we need to plug the basic system here. For example, if we want to do pin maxing, the initial pin maxing of the board, probably it will be a kernel one. Once this is done, basically, it initializes the canaries, and if configured, it prints the banner of the operating system. Finished with this, basically, goes to the real initialization of the multitasking. So, in particular, initialize the main thread. On our platforms, the main thread is implemented via this function here, and, in particular, what it does, it moves the program stack pointer to the higher address in the stack, unlocks the interrupts, and branches to the underscore main functionality that is the functionality that at the end will call the main of our application. Underscore main basically does the second part of the initialization of the kernel. So, basically, if we have a high-level feature in our operating system, we can initialize it here, or if we have a functionality that takes advantage of what we have initialized in the kernel one, kernel two initialization, we will initialize it here. In particular, as you can notice, even for the second part, here there are some features that are deprecated because they are initialization features of the nano-kernel and micro-kernel that are being rewritten into the unified kernel. That is the variant that we are using now in Toothsefer. Once this initialization is complete, basically the underscore main initialize the static threads like the idle, for example, and jumps to the main. The main is the main of our application, and at that point, basically, the firmware does what we have decided for it to do. Said that, basically, let's see how the sock porting plugs in this infrastructure. In order to have a complete sock porting, we need four k-config file. In particular, one that defines the sock, one that defines the series on which we are working, one that defines the dev config of the series, and one that defines the dev config of the variant. So the variant is the specific chip of the series that we are using. For example, the series is beetle. Beetle are zero that has specific features, like a dimension of the RAM, a dimension of the ROM is our variant. K-config series, what does, in particular, it enables the family and the specification of the sock to which we have to rely in order to boot our system. Done that, basically, we have to start adding a set of source files, and in particular, the boot entry code of our platform that is defined in sock.c, the IRQs definition that is defined in sock underscore IRQs.h, the pin definitions that is defined in pins.h, underscore pins.h, the registers that are defined in sock underscore registers, and the power management that is defined in power management.c. Some of these include probably will go away when the device tree will be there completely. If we look at this implementation of the sock.c file, basically what we do at the beginning of the life of PowerSock is lock the IRQs, initialize the power management. Basically, in the ARM platform, we have three states in the state machine of the power management that are active, sleep and deep sleep. In each of them, we need to initialize a set of clocks, and we need to initialize the wake-up sources that are allowed to wake up our system when we go to sleep. All these operations are done in the sock power unit, and if you got a bit of code and developed to the functionality, you can easily identify what I'm talking about right now. Once you have done that, you initialize the non-masquerable interrupt, and once this is done, basically, we unlock the interrupt and the execution will continue. Obviously, for what we have said in the initial configuration of our bootstrap of the kernel on ARM architecture, this is executed in kernel one. How can we identify that? Basically, device.h provides a set of macros that allow the kernel of compile type to identify in what it has to do in order to statically allocate the resources. Two of these macros that are very important are CC units, like this one, and it's used mainly when, basically, you have a simple unit function and don't have to register APIs for that particular function, and in this case, you can identify that it's a pre-kernel one, so it's plugged into the execution at pre-kernel one, and basically, it calls this unit function that I am defining here. Once this is executed, basically, our basic sock, it is initialized. Let's see now how do we plug the board. The board is responsible, mainly, for the definitions that are related to the board. The initial pin maxing, it's responsible for the configuration files that are board-related. For example, if you have some sensors that have specific configuration on the board, you put the configuration at this level. It's responsible for the main platform make file that is the one that defines all the make configurations that you need in order to compile your board and of the board documentation. So, basically, the documentation is very important and it has to provide at least one example of application with which you have tested your board once you deliver it for acceptance into the zephyr kernel. Another thing that is very important at this level, we define our dev config, and once you are ready to submit your patch a third action that you have to take in order to have it accepted is add your board to the sanity check. This is very important because sanity check is an automatic test environment that is inside zephyr that allows you to have static test at build time, and let's say dynamic at runtime. You can set different options. Let's see now how we define our dev config and how this will take action on our kconfig file. So, basically, in our dev config we define all the principal features of the ARM architecture and how it has to be invoked and defined and which are the features in the kernel that it has to enable. And we define a set of IPs that needs to be initialized. So, for example, in the case of Bitol, we define here the config GPIO, and once this takes effect in the dev config for the generation of the final configuration file, the dev config basically enables all the ports that are defined in the digital architecture. Now that we have defined the socket of the board, our attention goes to the drivers. And in particular, Zephyr OS supports different types of drivers. It provides a consistent device model that is responsible for the configuration of the drivers that are part of the system and for initializing them at when the drivers are configured. Each type of driver is supported by a generic API and this API is defined in device.h. And basically, this API is done in a way that is independent from the system. Let's see in particular what these independence mean. And to do that, we have to reference the device model. In particular, what this does mean is basically that your application will get a binding of the IP that is using and this binding will provide a generic API. In order to be sure that you are providing the generic API, the initialization function that you have to call is device and the API in and we will see an example in the GPIO later. Once this is done, basically your API is exposed to the application that can access to it like a function pointer. This generic API is done in a way that basically allows the system to be independent from the driver implementation and in particular defines a set of actions that are implemented as a function pointer by your drivers. Obviously, if you have, when you are writing your code, you are just starting a b driver to the system. So let's say that the a driver is already there, you are adding a b driver, but obviously, when you are trying to access to the device, the scenario is like this. So basically you are trying to access only to another of which you are getting the binding. Let's see now how this device model reflects on the drivers. And in particular, let's start from the pin maxing because it's the easier one. What the pin maxing defines is the pin maxing at boot time. So we have two types of pin maxing, we have static pin maxing that is this one and runtime pin maxing that is a different driver that we will see later. The static pin maxing is defined inside pin maxing.c and basically it rotates the pin, it maxes the pin that are allocated for specific functionality. For example, if you have a look there, I have a new port that needs to be enabled and in order to access to let me API access to its own pins, I have to rotate it in this way. Even in this case, this is executed through a minute function so I am reusing this in it. And for obvious reasons, this is executed again in kernel one. The runtime pin maxing allows instead to have the opportunity to rotate the pins from your application. This is mainly a prototyping feature in Zephyr because when it is possible, things are done statically. So suggestion is in order to not break shields that you plug on your system to check the TRM. And more than that, I shouldn't say this, but it's something that I like to suggest and to encourage people to do before they use the dynamic pin maxing. And basically it is mainly used for early testing and prototyping. It can be used even for power management if you wanna do particular operations. In this case, it provides an API and as we can see, based on what we were saying in relation to the device model, it provides a set of generic functions that are called by your application, like set, get, pull up, and input. These are filled to function point test implemented in this file and are passed to the device and the PI in it through a folder to the structure. So yes. And basically, once this is done, it's not much visible for me, because then you see it, guys. Okay. It's passed here on the folder. And basically, when your program get the binding, it can access to this functionality in this way. Okay. There are some IPs in certain sources that manage both GPIOs and pin maxing in a single IP. But there are others that basically are split. They split the functionality. So that's the reason why in Zephyr you find a driver that is responsible for the GPIOs and a driver that is responsible for the pin maxing. Even if on beetle, this differentiation could be avoided because we have a single IP that manages both the functionalities. Anyway, following the implementation that is provided by Zephyr OS, the API for the GPIOs is provided by GPIO.h, and it offers the standard functionalities to access to the some GPIOs, like the configuration, the read, the write, and the callback for the RQ when the RQ is raised. So this is the way on which the GPIO is initialized in code. So what we do here is we provide a static structure that is initialized only if your GPIO 0.4 is configured in the config, and it uses the same logic of the device and the API in that we saw for the pin maxing. Done that, basically you will be able to access to your GPIOs implementation. Same logic and philosophy follows the UART, but the UART, the difference is that it can be accessed both in interrupt mode and in polling. So to do that, as you can see from the feature set, provides a couple of different options of configuration. You have the default that is the polling, you have polling and poll out, and you have all the features that you need to implement in order to have access interrupt driven. The API is provided by UART.dutage and the UART is initialized in kernel 1 because, obviously for debugging, the most common way is using the printk. So basically you wanna have access to it. Let's see the watchdog. The watchdog on Beetle, I use this example because it's a bit particular. So shares they interrupt with the nonmasquerable interrupt. So basically what you need to do to enable the interrupt on a platform that is configured in this way, you need to configure something called runtime NMI. So runtime NMI allows you to replace the interrupt into the interrupt associated to your IRQ number with the new one that you are going to define when you initialize your driver. And in particular, the functionality that allows you to do that is the NMI handler set. So done that basically next time the interrupt handler is the interrupt, the NMI interrupt is sketched. Instead of calling the initial routine that you set up when we define the SOC, we are calling our routine. Obviously doing that, we are going to take care of managing a real NMI app. So basically, we have to discriminate if it is coming from the watchdog or it's a real NMI. So done that, let's see now how we build a driver into our Zephyr system. We saw few classes of drivers. Now we see how this can be built. In particular, the strategy that I like to follow when I do these things and maybe it can be useful for someone of you is before configure the driver where it sits so in the directory where it sits and after configure the platform to use the driver. In particular, what I do is modifying the make file that handles the driver itself, modifying the k-config that is local to the driver in order to enable the features and the functionalities and then enabling the dev config of the platform and the k-config of the platform and the dev config of the platform, as you can see here, basically. Once this is done, basically, you can use one of the examples to compile this particular IP and compile it into your application and use it with the API, get device binding and providing an app. Basically, this is mostly all what you need to do in order to port a new platform into Zephyr, a basic platform. Obviously, as I said at the beginning, this is a starting point, so there are more things that you need to do if you wanna enhance it and provide new functionalities, like, for example, the clock control or another functionalities can be the timer and so far and so on. In fact, to complete the porting of Beetle, we need to take some extra steps. So let's see what are these next steps. What we will need to do is basically to continue improving the code base to remain aligned with the new development of Zephyr, enable the missing IPs, the missing IP drivers and complete the power management. Our implementation still doesn't support deep sleep. Enable the connectivity. Our chip, as I said, has a little power and we are planning to enable it in the coming months. And enhance the documentation. That is something that it's very important for this project. Sorry if I stress a lot on this. Now, I hope, apart the accident that we had with my laptop switching that I have inspired you a bit, so let's see how to contribute to the project because it's very good to have new hands and new people that look at our code and provide new idea because the powerfulness of an open source project is getting in new ideas and get inspiration for your new people that just start working on the project and provide their view on things. To contribute to this project, basically, what you need to do is pretty easy, are five, six steps. You need to request an account to the Linux Foundation. Probably if you are here, you already have one because you have to subscribe to Kamiya. So this step we can cancel it. You have to clone the Zephyr source code and start hacking, that is the funniest part. Create a patch from the later source tree. Check the code style and some other things and submit the changes through the gerrit. Once this is done, the review will start and hoping that everything is done well, you will get your code in and you can continue the development. To make something useful, I want to provide to you the links to which you can do these operations. So basically, the gerrit repository is the first one and basically this is the way to clone it via Git. You can find a lot of information on the Zephyr project website, but if you are not satisfied, you want more, you want to know details of something, there are two very useful mailing lists to which you can subscribe and become part of the community. And these are in particular the development mailing list and the user's mailing list. Development mailing list is more used for problems like that you can encounter during the development and you want to submit to the rest of the community and the user's mailing list is mostly used if you are trying to use the kernel, so making an application and other stuff. And then we have an IRC channel that is for the project and one for the BLE related topics that are listed that I pointed out there. Now, let's go to the demo. It was already, but I had to reserve my laptop obviously. So basically, this is a beetle and let's say that you are trying to debug a new shield. Okay, so you just created soldered your shield because you are trying to prototype something and you want to know if all the things that you put on your shield are behaving correctly. Now, away can be write a lot of lines of code in C and discover later if this is true or use another open source project that is called MicroPython and write simple scripts to do the same thing. What I want to show to you today is basically how I can leverage MicroPython, putting it on my Zephyre implementation using the GPIOs in order to verify if my LED and my two sensors here are working. And to do that, I will use simple scripts. But let's start. Let me do this a bit here. Please tell me you can see it. So, basically, let's start with the LED. Let's say that you soldered the LED and you wanna verify that the LED is working. Once your, obviously, LED is connected to a GPIO through a resistor, and let's see how this can be blinked in order to verify that it's working. I'm going to copy and paste a script that we will comment together in a second. Yes, okay. So, basically, this is the basic script to integrate. I am selecting the GPIO of the LED's connected. I am doing a while through and then putting the LED to 1 and to 0. This is all what you need to do. And the first time I wrote it, I was surprised it was working. Seriously, because you have to write at least three times C code in order to have similar functionalities, considering make files, project files, and everything to put it into Zephyr. So, can you see the LED blinking? Okay. So, now, let's complicate it a bit. Let's say that you are in a scenario in which you wanna debug one of the sensors that are here, and in particular this one, that is, forgot what is this, is a sonar. So, basically, this is a sonar access. You can access to it through the wired protocol. So, basically, you define a time slice and it keeps sending to you 1 and 0 based on the value that you wanna send and there is a conversion formula on the technical reference manual of the sensor. So, in order to basically access to it, this is the Python code that I need to write. Now, I'll show you. Yeah, it's all here, again. So, basically, I am expecting 0 and 1 from the sensor. I am accessing to the GPIO and I am verifying that 0 and 1 is coming back. It's working. So, now, let's complicate it a bit more. Let's say that for the third sensor I wanna write a driver. It's, again, using the wired protocol, but in this case, I wanna write a full driver. So, the third sensor is Max 9814 and basically is this guy here. So, it measures the level of noise. Stop this. This is all what I need to do to read the noise level in the room. So, considering that you are very quiet, it should be low. Yep, it's very low. If we wanna verify that it's working, I can keep blipping on the sensor, executing the same code and verify that the value goes high. Yep. So, basically, now we can go back to the presentation. If I find them out. What we have seen here. We have seen, basically, a micropiton and you can compile micropiton if you are interested on your platform using the easiest ways using the linear release, but you can rely on the micropiton official repository in order to compile it. This example is based on the linear release. That's why I put it on these slides. The source code can be accessed there. What you need to do is set of simple operations in order to get it. Again, you are compiling for the beetle board and what we have seen right now is basically something similar to this to verify that it was working. Said that, I leave you with one example that I run here if you wanna try it at home on a new board. In conclusion. So, we have seen Zephyr architecture today. We have seen the setup of the environment. We have seen basic porting of Zephyr on beetle. We have seen how to contribute to the project and we have seen micropiton. But before concluding, let me say that I joined the Zephyr project. Less than six months ago and this for me is a tremendous momentum of being there. I was too young in the early 90s so we are hosted by the ELC here and I was too young to leave the early days of Linux but reading from the books and the mailing list of the page, basically I feel the similar sensation that my code can really change something in the project in this historical moment of the project. So, again, please come and join us. Thank you for coming. So, if you have any questions, these are my contacts if you wanna refer to them, take a picture, I don't know. Yes, please. It provides a console. You have a console shell that you can compile inside your application. It's already, it has already some comments for debugging purposes but you can extend it if you are interested in. I started six months ago the porting and basically I am already at this stage. I did even some work with Andy for the device tree as you have seen from his presentation this morning. So, basically it takes, let's say a month to port the basic platform in Turampap and then the easiest thing that I found in the project is literally access to the channel and ask to people. So, it's the easiest or just send an email or you have my reference on IRC, you can ask to me directly and if I don't know, I can point you to the right people that can provide to you the information that you want. Sure, what you mean with WebKit because WebKit is an engine for web browsing, right? So, yeah, because this is a system that runs on as less as eight kilobytes. So, basically if you wanna implement WebKit, you would need megabytes, I guess. I am not expert of the technology, but I guess you need megabytes. Sorry. With MicroPython is a bit bigger, it's like 100, but it's because it implements a lot of things. So, I can compile the same example with lead blinking in nine to 10 kilobytes. I am here three days, I mean I can show it to you if you want. I have the image here. We can check how big it is. Yes, you should. I can provide to you the precise number. It's 127Ks. Yeah, no problem. Yeah. Okay, basically most of the pain points were because of the way on which I approach the project. So, I give you an example. If you go into the project and you push 10 patches, 15 patches in one go, for our guarantees organized, the chance that you get to have people looking at all 15 the patches, it's not big, okay? So, something goes missing, something remains left out and maybe it's crucial for you to get the change in. Especially if you are pushing a new platform is what I did at the beginning with Beetle. This is my experience. Then I learned and basically now I push maximum three, four patches based on functionality and I use topic branches that is something very useful inside the project. When you send together for review, basically when you do refs for some branch, then you can add another slash and add a topic. For example, V2M Beetle in my case or a particular feature on Beetle. This helps the maintainers or who ask to review the patches to understand what you are going to target and makes easier for them to review the code. We have a question. Okay, can you rise because I have the phone here. There's a set of functions that are being implemented for whatever back end you have for your board. Is it, do they have support for other drivers than just GPIO or is it, or is that all you've explored for now? Basically, what I was explaining during the presentation is that there is a full device model divided in classes. So basically each class of drivers that is supported like the serial, like the pin max, like it can be timers, clock control, everyone has this device model and basically you can rely on the device model file, either file as part of the documentation in order to implement what you need to implement. Question. We have still eight minutes if you want. I'm here. Okay, I think we are done. Thanks again for coming and see you in the next few days.