 Hello and welcome to Libio, a library for interfacing with Linux IO devices. My name is Dan Nekita, I'm a software development engineer at Analog Devices. One of my responsibilities is to maintain the Libio code. There are five topics I would like to talk about today. I would like to talk about what Libio is and then look at its structure. Then go through the API of Libio and then see what bindings Libio has to offer. And in the end I would also like to talk about some practices that we are hoping to make the Libio more robust, more stable, and as much as possible without bugs. Libio is an open source user space library. It's been around for more than six years and it's consistently being improved. So it has been written in a C language and its cross-platform works only in Windows and Mac OS. And the code is released under the LGPL license version 2.1 and above that version. Libio has been created with one purpose in mind and that was to make things easier and faster when it comes to developing applications that need to interact with the Linux industrial input output devices. On an embedded system running Linux, there will be a Linux kernel that can interact with hardware through IO drivers. And will expose an API for those devices in user space. And this is where Libio comes in. It will interact with the exposed API and will provide the more easier and flexible way to interact with the IO devices. And provide this flexibility to any client application. Before going further, I would like to say a few words about the Linux industrial input output or IO. It is a part of the Linux kernel and it is a subsystem that provides support for devices such as analog to these digital converters, accelerometers, capacitance to digital converters, digital to analog converters, gyroscopes, IMUs, color and light sensors, magnetometers, pressure sensors, proximity sensors or temperature sensors. More about this can be found on kernel.org and also on wiki.analog.com where there are a few other things about IO. The API of IO subsystem is exposed through CSFS at the following location. So all IO devices can be found at this path. Let's see an example. For instance, let's say we have one device. So that can be found at the path CSFS IO devices. And then there will be a directory called IO device zero. And here we can see that there will be a file called name. And then what we typically see here is a channel of that device. In this particular case, the name of the channel is voltage zero. Then there's the out prefix. This signals that the channel has a direction and the direction is of type output. It could also be as an input. Then we can see a V1 which is a name for the voltage zero. So voltage zero represents the ID of the channel while V1 represents the name or alias if you want of the channel. And then the last thing is raw which is a property or attribute or a parameter of the voltage zero channel. As you can see, on line three there's a similar name but the suffix is changed and we can see scale instead of raw. This is another attribute of the voltage zero channel. So you can see a voltage zero has many attributes. Starting from line six we can identify that there is another channel with the ID voltage one and the name V2 which has the same attributes or parameters that the previous channel has. Also it has the same direction and then on line 10 we can see that we have something that it's a little bit different than the channels. We still have a prefix out and then we have voltage but we have no number for this and then we have a power down mode available. So this means that the power down mode available is an attribute of all out voltages. So this attribute applies to both voltage zero and voltage one. Also you can see the power down mode available looks slightly resembles with power down mode. So what the suffix available means is that these attributes will show so its value contains a list of possible values that can be used to set the power down attribute. So going forward to line 11 we can see another attribute which is called sampling rate. It has no voltage or out prefix. This means that it's an attribute of the IO device while power down for instance was an attribute of voltage one channel. The name is again the same as the sampling rate an attribute of IO device zero. Then we can see that inside the IO device zero directory there's a directly called scan element where we have this time we have again two channels with IDs voltage zero and voltage one. This time the direction of the channel is input which means that through this channel we can read the data from the hardware as opposed to the out channel which can be used to provide stream data to the hardware or just send data to the hardware. So going back to the scan elements directory we can see that there are three attributes for each channel and typically NEN start comes from enable so you can enable or disable a channel in this case. Then there's an index which provides the name says the index of the channel and then there's the type which could tell you more information about what type of data the voltage the channel can acquire. So it typically it will tell you that it can get samples that can be eight bit long and then it can tell you about Indians could be big Indian or little Indian. Also if there's some shifting in samples or if samples are shifted to the right or to the left with a specific number of bits that will also be represented in will be set in type and reading the type you will get this kind of information. So what what is different between the between the voltage zero in inside the scan elements and the voltage zero that it's outside the directory is that the channels inside the scan elements can work with the buffer and they can capture data continuously. And their samples they capture samples will get stored in buffer. So all all this logic that I just described is a logic that client application needs to have inside it if the data application wants to interact with directly with the IO API. And what what live IO does is that it will contain this logic and we'll do all this parsing of all these names and prefixes and directories and it will know how to place each of them in categories and classify the device and the channels and which attributes go to which channel and which attributes go to the device. And this, this is one of the core functionalities of live IO and once once you once live IO has been developed and store this logic inside it. Any any client that uses live IO will have direct access access to this, and it won't have to bother thinking about parsing everything and doing the same thing. Because live IO can do that for for you. So, as I mentioned before, live IO will do the following steps will identify the devices that can be used. And it will identify the channels that belong to each device and their direction and if their devices that if they are channels that work with the buffer, or they are, or if they are not, then it will assign the specific attributes for the ones for the channels and the one for the devices. And then it will create a context which is like a place where all devices will will exist and where so it's a place where you can browse through the devices and through the channels and through all the old attributes. Another important question is where live IO can run where it will run. So there are two types of platforms where it can run one is an embedded system that runs Linux and includes IO drivers for devices that are physically connected to the system such as ADC decks and everything that I mentioned in when I was talking about the IO. Sub system. Also, we can run on an embedded system with a non Linux framework. So, when when live IO runs on an embedded system, we call it that live IO runs on on the target. And then there's the other place where live IO can run and that is on a regular PC or laptop that runs some Linux distribution windows macOS open BSD or BSD, which is connected to the embedded system in a way that can be through a network link us billing or cellular serial connection. Now let's have a look at how the library is structured. The library is composed by one high level API, which is the same across all platforms and several back ends. The first is the local backend, which interfaces the Linux through CFF system. This is the backend that we'll use when live IO runs locally on target. Then there's the network backend, which interfaces the ID server through a network link. So ID is part of live IO and it is a software that acts as a server and interact with a network client that usually run on a PC. And then the third backend is the USB backend, which interfaces the ID server, just the same as the network backend through a USB link. There's the XML backend, which faces a file. The last one is the serial backend, which interfaces tiny ID through a serial link. As I mentioned before, serial backend can be used with an ID which will run on an embedded system with a non-Linux framework. The software stack below shows us a client server connection. On the right, there is a client application that, for example, runs on Windows, uses live IO and calls the API and through the network backend sends commands to the ID server, which in turn recognizes the commands and uses live IO and through the high-level API and local backend, it interacts with the IO devices. The job of IOD server is to stay always open and listen to potential new connections with other clients and it basically shares the local or exposes the local backend through the network to clients. The C language API. There are four types that together make almost all of the API. The context, the device, the channel and the buffer. The context represents an instance of the library. The context can have zero or many devices, while the device can have zero or many channels, but can have only zero or one buffer. Each of the four base types can have an attribute which is defined by a name, which is a C string, and each attribute can have a value or multiple values. Besides this, the device can have debug attributes which are used for debug purposes. The API has many functions. First set of functions have to do with scanning for IO context. So first thing to do is look for available context before creating a context. Let's see an example of how we can scan for context. First thing to do is call IO create scan context, which will return a scan context object. Then we will pass the scan context object to the function IO scan context get info list. And also we'll pass by reference the info object. What I will do is allocate memory for the object and fill the list with a number of context information and then it will it will terminate the list with an object. If you want to get information about the description about the context, we can do that with IO context info get a description. And if you want to get the URI of the context, we can do this with IO context info get URI. Once we are done with the info object and the scan context object, you can use these functions to free the memory that Alibio allocated for us. The next set of function has to do with creating context. We can create a default context, which will create a local context, or if it cannot, it will look for the IID remote environment variable. And if that has an IP set, it will create a network context. We can specifically ask to create a local context or an XML context, which gets created from a file. Or if you want, we can create the context from memory. Then we have the option to create a network context from using an IP version 4 or version 6. Both are supported. The more generic function to create a context is IO create context from URI. And then the last function is to clone the context. Once we are done using the context, we can use when we should use IO context destroyed to free all resources. Let's see some examples of how we can create different types of context. To create a local context, we can use this function, which will only work on target and will not work on remotes. To create a network context, we can use this function. All we need to do is provide the IP of the target. To create a USB context, we can use this function. And we need to provide the device, port and instance for that USB device. To create a serial connection, use the same function as for the USB context. And that is IO create context from URI. And then in this case, we need to add the serial prefix. And then we need to specify the serial port, bodily, and other configuration. Once we have the context created, we can navigate through it and look for available devices and their channels. To do that, we have a couple of functions for the device that can give us the number of devices available in the context. Or we can get a device by index or we can find the device by name. Similar functions are available for the channel object. This is an example of how to go through all the devices and through all the channels of each device. First, we create a context. Then we use that context to find the total number of devices available in that context. And then we use IO context get the device and we pass the local context and we pass an index for the device that we want to get a reference of. Once we get that, we can find the number of channels for that device. Next, we can obtain a reference to the channel for that device using IO device get channel and we pass the device reference and then an index for that channel. The context, the device, the channel and the buffer can have attributes or parameters, which can be identified by name. They can represent a value for an action. For instance, for an ADC, which can have an attribute called power mode, which can have a can take values like power on, sleep and power off. Setting one of these values would represent an action. To get the number of attributes for each type, you can use IO context get attributes count. And to enumerate all the attributes, you can use IO context get attribute at the specified index. There are sets of functions that can be used to read from an attribute or write to it. For example, you can use IO device attribute read to store the content of an attribute in a C string. You can also use read ball, read long, long or read double for those attributes that you know that their value can only be integers or double. There is also the IO device attribute read all function allowing you to read all the attributes of a particular device. Same functions applied for writing and also the similar function exists for channel specific attributes. The process of capturing samples from a device or sending the samples to the device can be done only using the IO buffer object and its functions. The first things thing that you need to do is enable the channels. You can do that with IO channel enable and disable. Also you can check the status of the channel with IO channel is enabled. The enable and disable will actually happen when the IO buffer gets created. Not all channels can be enabled, only those of type scan element. A scan element channel is a channel that is capable of shimming data into or from a buff. Then the next step after you have enabled the channels you want to capture samples from is to create a buffer. You can do that with IO create buffer. Once you're done with the buffer, you can use IO buffer destroy to the allocate memory for it. Then after you have the buffer created in order to update the buffer with new samples, you need to call IO buffer or fill. While the previous slide was about getting samples from an input device, here we can see how to push samples to an output device. This is done with a different function called IO buffer push. So if the IO buffer object has been created with the cyclic parameter set to true and the kernel driver supports cyclic buffers, the submitted buffer will be repeated over and over again until the buffer is destroyed. You can also push just a part of the entire buffer size of samples to the device. This is done with IO buffer push partial. Now that we have seen how to update a buffer with new samples, let's see how we can access those samples. The first method is called the callback method and Libio provides a way to iterate over the buffer by registering the callback function with the IO buffer for each sample function. The callback function will be called for each sample start of the buffer, which will contain a valid sample if the buffer has been refilled or correspond to an area where a sample should be stored if using an output device. So this is how the callback should look like. This is the required signature. In order to call this callback for each sample, you need to register it with IO buffer for each sample. The second method to access samples from the buffer is with the for loop method. This method allows you to iterate over the sample slots that correspond to one channel. As such, it is interesting if you want to process the data channel by channel. It basically consists in a for loop that uses the functions IO buffer first, IO buffer step and IO buffer end. So with this free function, you can loop through the entire buffer. Libio can also be accessed from other programming language and this can be done with the help of bindings. So let's see what bindings Libio has to offer. First of all, since Libio has been written in C, it can be used directly in C++. Then there is the Python binding and the C sharp binding. These bindings are available in the same repository as Libio. Besides this, there's the Rust and Node.js bindings which are created by somebody else, not analog devices, and they are maintained on GitHub separately. Then there's the GNU radio binding or the GNU radio blocks, which allow Libio to integrate with GNU radio. This exists in a separate repository but in the analog devices domain. The Python bindings consist of a py file. Location can be found at the below URL. The C type module has been used to write the bindings and since version 0.21, the Python bindings have been available through pypy and therefore can be installed with pip. Also, there is a documentation for the Python bindings. The C sharp bindings cover the full panel of features that Libio provides. The C sharp bindings are spread across multiple files. There's the scan context, the context, the device channel, IO buffer attributes, triggers and IO Lib. Each of these files provides a couple of methods that directly call their C counterparts. Having C sharp bindings allows users to develop client applications on Windows using .NET. Also, there is a documentation for the C sharp bindings at this URL. To integrate Libio with GNU radio, a couple of GNU radio blocks had to be written. IO device source and IO device sync. These blocks allow samples to be streamed from the hardware into the GNU radio context and from the GNU radio context to stream data to the hardware. These blocks are available through the GNU radio IO module. For the IO device source, there are a couple of configuration fields that the user can set. First of all, you can set the type of context that you want to create. Then you can choose which channels to enable. After that, you can choose the buffer size. So even if it's a streaming process, internally an IO buffer will be created. So this is where you choose the size for that buffer. Then there's the decimation and also there's the parameters where you can change specific attributes of the device. The IO device sync provides configuration fields for choosing the context type, enabling the desired channels, setting the buffer size. There's the interpolation option. You can set the cyclic flag if you want to repeat over and over the same set of samples. And also you can change some of the device attributes. For example, you can change the sample rate of the device, like in the example below. The last topic is about practices that aim for robust library. One practice that makes sure that code that goes into master or the development branch is first checked is to use a pull request system where each pull request is subject to review. So no pull request can be merged with at least one approval from a reviewer. Also pushing commits directly to master is disabled. Second practice is about enabling as many warnings as possible. This way the compiler will tell us if there could be potential problems, potential issues or bugs. Our next step on this would be to enable to treat all warnings as errors. This kind of enforces developers to not let not overlook the warnings and just have them pile up. Currently for Libio, treating warnings as errors has been enabled only during the continuous integration. So for somebody that wants to build locally between only get a warning. But once you want to create a pull request, the CI will build Libio before the pull request gets merged. And those warnings will be treated as error and the build will fail. And whoever created the pull request will have to fix those warnings before getting an approval since the build will fail. The third practice is to use continuous integration whenever a pull request is submitted. For Libio, it also works for regular branches. So every time a new commit gets added to any branch, the continuous integration will work on that. So as you can see, Libio has four checks for each pull request. The first is using Codesc, which statically analyzes the code and checks for code defects. The second check is done by Abwehr, which tests if builds on Windows work. The third and fourth checks are made by Travis, which checks if builds on different distribution of Linux and on macOS work. To be more specific, Travis checks macOS builds Ubuntu, Jesse and Stretch versions and also CentOS versions 6, 7 and 8. The fourth practice is to make use of static analyzer to look for code issues. One tool is CoVerity. The check is done as part of one of Travis jobs. The second tool is Codesc, which is integrated with GitHub and we have seen it on the previous slide. The Libio API tries to be both backwards and forwards compatible. This means applications compiled against an older version will work fine with a newer dynamically linked library. Applications compiled against a newer version will work fine with an older dynamically linked library, as long as they don't access any new features. Libio uses CMake to facilitate the building of Libio. To use the library, you only need to include one header, which is io.h, and one shared library to link against. The doxidgen is used to generate the API for the API documentation. The latest release is version 0.21 and so far 26 releases have been made. Libio has dependencies. This can be classified by which part of Libio is using them. There are the core dependencies, Libxml2, Bison, and Flex. Then there are the backend dependencies. For local, it needs libio. For the USB, it uses libusb. For network, it needs libvaki. For backend, it needs libserialport. Also for generating documentation, doxidgen and graphwits are required. Not all backends need to be compiled during a build, so you can opt out a couple of them. You can build without USB, without network, and without a serial backend. Also, you can be built with or without documentation. If you want to read more about Libio, or contribute to the source code, or report any bugs and open issues with feature requests or enhancements, you can go to github.com, and log devices in and Libio. There's also a welcome page where you can find documentation about Libio, about the bindings, and all sorts of other things. Then you can go directly to the API documentation. The last two are pages on wiki.anlog.com, which also talk about Libio. The first one talks a little bit about how to get and install the dependencies and how to get the binaries for Libio. While the last one talks about the internals of Libio, about the mechanisms to a very low detail level. Thank you for following this presentation.