Skip to content

Linux Signal Generation and Handling Explained


Tabular array of Contents

About libiio

Objectives

Why did Analog Devices develop libiio? To answer that question, information technology is needed to empathize the state of things before libiio was introduced.

Simplification and standardization

The Linux kernel features a IIO subsystem, which provides a standardized interface with the user-space for customer applications. The various drivers designed to interface unlike IIO devices register with the IIO subsystem. Every bit a result, all supported devices can be used past user-space applications with the same interface.

At to the lowest degree, that’s the theory. The reality is more than complex; the interface provides different ways to read or write a device, each driver generally implementing only the oldest and slowest method. The various drivers will too create slightly different files in the sysfs filesystem of Linux, where all the virtual files used to configure the interface are present.

As a result, before libiio was built-in the applications using IIO devices would generally exist designed to support only one particular device, because it was too much work to support several at a time. Because of this, a lot of applications had their own code to interface with the kernel’s IIO subsystem leading to maintenance issues. Furthermore, information technology was very difficult for customers to create applications to utilise their hardware since they had to continuously rewrite the interface code or adapt it from a pre-existing application.

The objective behind libiio is to ease the development procedure of applications using IIO devices, by letting the new library exist the intermediate between the program and the kernel.

By cleverly identifying devices, bachelor input or output channels, libiio allows one application to support a wide range of devices. For instance, if the application requests 1 device that features a capture channel without specifying its proper name, then information technology will be compatible with all IIO devices with at least one capture channel that exist to date, too every bit futurity hardware that has yet to be invented.

Avant-garde features

Beyond resolving bug, libiio was as well announcing new features. The major planned improvement existence a network backend, which would broaden the set of possibilities: running the applications as a single user, reading 1 IIO device from different applications, and of form use a device from anywhere in the network in an application.

That network backend was besides opening questions near other possible improvements: for instance, using IIO devices from applications running on different operating systems, like Windows, using those devices in environments similar GNU Radio, MATLAB or Simulink, etc.

License and code management

Libiio has been adult and is released under the terms of the
GNU
Lesser Full general Public License, version 2. This open-source license allows anyone to use the library for proprietary or open up-source, commercial or non-commercial applications. This pick was motivated past the fact that Analog Devices is a company that principally sells hardware, and this library provides the clients with a amend and easier way of using this hardware.

The full terms of the license tin be found here: http://opensource.org/licenses/LGPL-ii.1

Lawmaking conformance

A good role of libiio fully complies with the C99 and POSIX standards, and should exist compilable on any POSIX-compliant operating system supported by a C99 compiler. The exception is the local backend, which is Linux-specific and thus tin can only be compiled for Linux systems. The library can too be compiled under Visual Studio, which does not fully support C99 (in 2014…), past preferring ANSI C89 when it makes sense. As the conformance to C99 and POSIX was a design decision since the beginning of the project, porting the library to Windows has been uncommonly easy and required very footling alter, thank you to the contempo POSIX sockets compatibility layer provided in Windows.

Code visibility

While the public
API
declares and references iio_context, iio_device, iio_channel and iio_buffer objects, their content is never known to the customer application. All the public functions use pointers to opaque structures:

          struct
          iio_context;
          __api
          void
          iio_context_destroy(
          struct
          iio_context
          *context)
          ;
        

In this example, the content of the iio_context structure is never listed, but the iio_context_destroy function accepts a arrow to an instance of that construction as a parameter. This design choice has several benefits:

  • The customer applications are not able to alter directly the content of the objects; they must apply the public functions of the
    API
    instead. This prevents the applications to mess with the internals of the library.

  • As the client applications manipulate only pointers, the structure of the object can change from one version of the library to another without breaking the ABI (binary interface). As a result, an old program compiled with an old version of the library will run just fine with a new version of the library, even if the structure of all the objects changed in the new version. This brings a lot of flexibility, as a customer software isn’t bound to one specific version of the
    API.

On top of that, the functions that etch the
API
of libiio are all prefixed with “__api”. This token marks the corresponding functions equally visible in the library. The functions that are not marked are considered subconscious and volition not exist callable from outside the library. This ensures that client applications cannot phone call internal functions, and use the
API
functions instead.

Backends

The libiio library has been designed from the start to support multiple backends. The current 0.1 version features three dissimilar backends: a XML backend, a local backend, a network backend. Concretely, a backend is assimilated with a iio_context object. For each backend, 1 office in the public
API
allows the creation of the corresponding iio_context object:

__api
          struct
          iio_context
          *
          iio_create_local_context(
          void
          )
          ;
          __api
          struct
          iio_context
          *
          iio_create_xml_context(
          const
          char
          *xml_file)
          ;
          __api
          struct
          iio_context
          *
          iio_create_xml_context_mem(
          const
          char
          *xml,
          size_t
          len)
          ;
          __api
          struct
          iio_context
          *
          iio_create_network_context(
          const
          char
          *host)
          ;
        

The iio_context object contains a arrow to a iio_backend_ops structure:

          struct
          iio_backend_ops
          {
          ssize_t
          (
          *read)
          (
          const
          struct
          iio_device
          *dev,
          void
          *dst,
          size_t
          len,
          uint32_t
          *mask,
          size_t
          words)
          ;
          ssize_t
          (
          *write)
          (
          const
          struct
          iio_device
          *dev,
          const
          void
          *src,
          size_t
          len)
          ;
          ...
          }
          ;
        

This construction contains a set of function pointers that correspond to low-level functionalities: read an attribute, open up a device, stream data… Those functions are specific to the backend used. For instance, the “read” part arrow will exist empty with the XML backend (every bit it does not support streaming data), point to a function to call up a buffer full of freshly captured samples from the Linux kernel with the local backend, or indicate to a function to send a read request via the network with the network backend.

1 thing to consider is that it is non a requirement to accept all the backends enabled for the library to piece of work. For instance, the Windows versions are built without the local backend, as this one is Linux-specific; and a build of the library meant to run on a evolution board with IIO devices attached could be compiled without the network and XML backends, equally those would exist unused.

Layering

The libiio library is built with two distincts layers. The top layer contains the implementations of all the public functions. It is loftier-level, in the sense that those functions can exist used independently of the backend that was used to create the iio_context object. Those functions just use the content of the various objects of the context, and rely on the functions provided by the backend in its “iio_backend_ops” structure to perform any low-level operation. Those backend-specific functions form the bottom layer of the library.

Here is a short example, extracted from the source code of libiio, that shows how the functions that etch the public
API
of the library tin can phone call the backend-provided functions to perform operations:

          int
          iio_device_close(
          const
          struct
          iio_device
          *dev)
          {
          if
          (dev->ctx->ops->close)
          return
          dev->ctx->ops->close(dev)
          ;
          else
          return
          -ENOSYS;
          }
        

The iio_device_close function is part of the public
API. What it does, is simply phone call the “close” function provided by the backend if available. Of course, this function’southward implementation is very unproblematic, but some other loftier-level functions perform much more than than just a call to a backend function.

The direct consequence of having proper code visibility and layering, is that one application that utilise the libiio library and that was designed with the local backend, will work remotely through the network just past changing ane line of code: create the iio_context object with iio_create_network_backend instead of iio_create_local_backend. All the functions that the application will call will have the exact same effect, but with a different behaviour.

The XML and local backends

The XML backend

One of the first things implemented in the library has been the XML backend. Using this backend, information technology is possible to generate a libiio context from a pre-existing XML file with a specific structure. This backend has been very handy in the kickoff of the development process, for the simple reason that it simplifies the chore of validating the code model: a XML file generated from the code model with the iio_context_get_xml public function must exist parsable, usable past the XML backend, and upshot in the exact same objects being re-created.

The XML backend is the simplest backend. For example, it does not provide whatsoever low-level function to read or write attributes, or stream information. The total C code of this backend fits in effectually 360 lines, so it is extremely small. It uses the libxml2 library, bachelor under Unix operating systems also as Windows and with a compatible license (LGPL) to validate so parse the XML file.

Document Type Definition

<?xml version="1.0" encoding="utf-viii"?> <!DOCTYPE context [     <!ELEMENT context (device)*>     <!ELEMENT device (channel | attribute | debug-aspect)*>     <!Element channel (scan-element?, attribute*)>     <!Element attribute EMPTY>     <!ELEMENT scan-element EMPTY>     <!Chemical element debug-attribute EMPTY>     <!ATTLIST context name CDATA #REQUIRED>     <!ATTLIST device id CDATA #REQUIRED proper name CDATA #IMPLIED>     <!ATTLIST channel id CDATA #REQUIRED type (input|output) #REQUIRED name CDATA #IMPLIED>     <!ATTLIST scan-element index CDATA #REQUIRED format CDATA #REQUIRED scale CDATA #Unsaid>     <!ATTLIST attribute proper name CDATA #REQUIRED filename CDATA #IMPLIED>     <!ATTLIST debug-attribute proper noun CDATA #REQUIRED> ]>

This DTD corresponds to the format expected by the XML backend of the latest libiio. It is e’er embedded at the top of the XML generated with iio_context_get_xml, and the XML backend will verify that the given XML file validates with the embedded format and issue an error if information technology’s not the case.

The format of the XML evolved quite a lot during the development phase of the library, to reflect the new features implemented in the process. The “scan-chemical element” and “debug-aspect” elements, for instance, were added very tardily in the development phase.

The local backend

The primal and most circuitous piece of the libiio library is the local backend. This may be the single most of import part of the library, as it is the only part that volition really interact with the hardware through the sysfs interface of the Linux kernel.

Creation of the context from sysfs

The showtime task, when creating the local backend, has been to implement the part iio_create_local_context, the 1 responsible for the creation of the top-level iio_context object. The implementation of this functionality represents peradventure half of the complexity of the local backend. To understand why, let’s take a look at how the generation of the objects from sysfs works.

Hither is a listing of files that can be found inside /sys/bus/iio/devices one :

1:    /sys/bus/iio/devices/iio:device0/proper noun 2:    /sys/bus/iio/devices/iio:device0/out_voltage0_V1_raw 3:    /sys/bus/iio/devices/iio:device0/out_voltage0_V1_scale four:    /sys/coach/iio/devices/iio:device0/out_voltage0_V1_powerdown five:    /sys/bus/iio/devices/iio:device0/out_voltage0_V1_powerdown_mode 6:    /sys/double-decker/iio/devices/iio:device0/out_voltage1_V2_raw 7:    /sys/bus/iio/devices/iio:device0/out_voltage1_V2_scale viii:    /sys/coach/iio/devices/iio:device0/out_voltage1_V2_powerdown 9:    /sys/bus/iio/devices/iio:device0/out_voltage1_V2_powerdown_mode 10:   /sys/bus/iio/devices/iio:device0/out_voltage_powerdown_mode_available xi:   /sys/autobus/iio/devices/iio:device0/sampling_rate 12:   /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_en 13:   /sys/omnibus/iio/devices/iio:device0/scan_elements/in_voltage0_index 14:   /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_type xv:   /sys/motorcoach/iio/devices/iio:device0/scan_elements/in_voltage1_en xvi:   /sys/passenger vehicle/iio/devices/iio:device0/scan_elements/in_voltage1_index 17:   /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage1_type

The local backend will perform the post-obit steps:

  • Identify the IIO devices that tin be used. In this case, only one device is present, so one single iio_device object will be created, with the ID “iio:device0” and the name respective to the text contained in the “proper name” attribute (line 1).

  • Place the diverse channels for each devices. In this case, the device features four different channels: two output channels with the IDs “voltage0” and “voltage1”, and ii input channels with the IDs “voltage0” and “voltage1”. Note that the IDs can be the aforementioned as long as their direction is opposed, which is the case here. The right way to identify a channel is by looking at their ID and their direction.
    The two output channels likewise have a name: “V1” and “V2”. Having a proper noun is optional, that’s why the two input channels don’t have whatsoever.
    Note that the ii input channels are located in a sub-directory called “scan_elements”. All the files nowadays in that directory correspond to channels which support streaming (either capturing samples from the device for input channels, or submitting samples to the device for output channels).

  • Identify the aqueduct-specific attributes. For example, each one of the two output channels will take the “raw”, “scale”, “powerdown” and “powerdown_mode” attributes, most likely containing singled-out values.

  • Place the attributes shared by all channels. In this example, the 2 output channels volition share one “powerdown_mode_available” aspect; if one channel modifies the content of the attribute, it is modified for all the channels of the device that have this attribute.

  • Place the device-specific attributes, so the ones that don’t apply to channels at all. In this case, this device has i aspect named “sample_rate”. In theory, it also has the “name” aspect, only this one is not registered every bit an attribute in the iio_device object.

Popular:   15 Relaxing Websites to Help When You Feel Overwhelmed

Additionally, the local backend will too add “debug attributes” to whatsoever device whose ID is found in the /sys/kernel/debug/iio directory. Those attributes tin be assimilated to device-specific attributes, their but specificity is that they may not be bachelor all the fourth dimension. This directory existence only browsable past the “root” super-user, they may be establish only if the awarding is started with super-user rights.

Parsing bug

Although the kernel interface has been designed to exist simple, it has not been designed to be parsable. Let’south accept an example:

1:    /sys/coach/iio/devices/iio:device0/out_voltage0_V1_raw ii:    /sys/bus/iio/devices/iio:device0/out_voltage0_V1_scale 3:    /sys/passenger vehicle/iio/devices/iio:device0/out_voltage1_V2_raw

The proper name of the “voltage0” channel is correctly detected as being “V1”, and the two “raw” and “calibration” attributes are detected properly. However, for the channel “voltage1”, it is not possible to differenciate the channel name with the aspect proper noun: maybe the name is “V2” and the attribute proper noun is “raw”, but maybe the channel has no name and the attribute is named “V2_raw”…

The sad fact is that there is no easy way to address this effect; it has been avoided so far, equally channels ofttimes have more than than ane file in sysfs and rarely accept names. The issue is still nowadays in the latest version of libiio, and will probably be worked effectually by using a database to map the filenames in sysfs to the corresponding device, aqueduct and aspect.

The consequence becomes even more circuitous if you consider that the filenames can include “modifiers”. The modifiers are known tokens in filenames, the local backend of the library uses a built-in list of modifiers to parse the filenames improve.

/sys/bus/iio/devices/iio:device0/in_voltage_blue_sample_rate

In that example, “blue” is a known modifier. “sample” could be the channel’due south name, or role of the attribute name. Notation that the channel has no number, which is a pointer that a modifier is used. If the discussion following “voltage” is not recognized as a modifier, then the attribute is considered not to exist a channel attribute, but a device aspect.

Reading and writing

Enabling channels

The first affair to practice to read or write samples to the hardware, is to enable channels. This can be done using the public function iio_channel_enable:

          void
          iio_channel_enable(
          struct
          iio_channel
          *chn)
          {
          if
          (chn->is_scan_element
          &&
          chn->index
          >=
          
          &&
          chn->dev->mask)
          SET_BIT(chn->dev->mask,
          chn->index)
          ;
          }
        

Note that this function does not perform any functioning on the hardware, as it is not a backend function. Instead, it marks the channel every bit enabled inside its parent iio_device structure. The real operation of enabling or disabling the channels is performed by the backend, in its “open up” part.

Creating a buffer

The second stride, is to create a buffer bound to the device that will be used. This can be done with the public function iio_device_create_buffer:

__api
          struct
          iio_buffer
          *
          iio_device_create_buffer(
          const
          struct
          iio_device
          *dev,
          size_t
          samples_count,
          bool cyclic)
          ;
        
  • The “dev” parameter corresponds to the iio_device object that will be used.

  • The “samples_count” will set the size of the kernel’south internal buffer. The value set should correspond to the amount of samples that will be asked in each read or write operation.

  • The “cyclic” variable is used to inform the kernel whether or non the device is to be opened in cyclic manner. In this configuration, the first buffer of samples pushed to the hardware will exist repeated continuously. This will be explained later.

Internally, the iio_device_create_buffer will call the backend’s “open” function, divers with the following epitome:

          struct
          iio_backend_ops
          {
          int
          (
          *open)
          (
          const
          struct
          iio_device
          *dev,
          size_t
          samples_count,
          uint32_t
          *mask,
          size_t
          words,
          bool cyclic)
          ;
          ...
          }
          ;
        

The “dev”, “samples_count” and “circadian” parameters are the same as above. The “mask” and “words” parameters are new and shall be explained. The “mask” variable points to an assortment of 32-scrap words. The exact number of words it contains is prepare in the “words” variable. Each 32-fleck discussion of the array is a bitmask: if the bit Y of the give-and-take 10 is set, then the aqueduct numero (32 * X + Y) is enabled. Typically, a device doesn’t have more than than a handful of channels, and so the “words” variable will most always be gear up to one. But the library must be able to handle even devices with more than 32 channels. Those bits are set or cleared with iio_channel_enable and iio_channel_disable, respectively; that’southward why it’s important to call those functions before creating a buffer.

Reading and writing, the former way

In the infancy of the libiio library, there was no iio_buffer class. Instead, there were functions to open up/close and read/write a device: iio_device_open, iio_device_close, iio_device_read, etc. Information technology worked fine for a start, but was also severely limited, for several reasons:

  • There is no guarantee that the channels that were marked every bit disabled in the device’s channel mask can actually be disabled. Equally a matter of fact, the stream that nosotros read from the hardware tin can contain samples for channels that we did not asking, and when emitting data, the hardware may await more samples than what nosotros’re sending.

  • The iio_device_read and iio_device_write copy the stream, between the kernel’south internal buffer and userspace. That is fine for wearisome devices, non for loftier-speed analog to digital converters that tin produce thirty one thousand thousand samples per 2d (240 MiB/s worth of data) on a lath with a 400
    MHz
    CPU (like the ZedBoard).

  • Those read/write functions worked with the same sample format that the hardware manipulates, so the samples had to be converted to a format that tin can be processed after being copied from the kernel buffer, which resulted in an enormous overhead.

Reading and writing with the iio_buffer class

The iio_buffer object addresses all those issues past providing a smarter
API, and encapsulating a role of the complexity. The iio_buffer object offers several possible methods to read or write samples:

  • If you lot just desire to dump the content of the internal buffer, you tin merely use the functions iio_buffer_start and iio_buffer_end to go the start and finish addresses of the buffer, and and then use the standard memcpy part to dump a part or the totality of the buffer.

  • Use iio_buffer_first, iio_buffer_step and iio_buffer_end to iterate over the samples of one given aqueduct contained inside the internal buffer:

                    for
                    (
                    void
                    *ptr
                    =
                    iio_buffer_first(buf,
                    aqueduct)
                    ;
                    ptr
                    <
                    iio_buffer_end(buf)
                    ;
                    ptr
                    +=
                    iio_buffer_step(buf)
                    )
                    {
                    /* ptr points to one sample of the channel we're interested in */
                    }
                  

    This method tin can exist very useful if the data of a channel has to exist processed sample by sample, as in this case, at that place is no copy to an intermediate buffer. As the inner loops gets a pointer to the sample’due south emplacement, it can be used either to read or write the buffer.

  • Alternatively, the iio_buffer class contains a method chosen iio_buffer_foreach_sample. This part takes a function pointer every bit statement: the supplied callback will exist called for each sample of the buffer. The callback receives iv arguments: a pointer to the iio_channel structure corresponding to the sample, a pointer to the sample itself, the length of the sample in bytes, plus a pointer optionally set as statement of iio_buffer_foreach_sample. Again, this function tin be used to read from or write to the buffer.
    The chief difference with the previous method, is that the callback is called for each sample of the buffer, in the order that they appear in the buffer, and not ordered by channels. As said previously, the buffer can contain samples of channels that we didn’t request; the callback can simply check whether or non the sample’due south channel is enabled with iio_channel_is_enabled, and just return cypher if it’s non.

  • The last method is to use one of the higher-level functions provided by the iio_channel class: iio_channel_read_raw, iio_channel_write_raw, iio_channel_read, iio_channel_write. The former 2 will basically copy the start Northward samples of one aqueduct to/from a user-specified buffer (N depending of the size of this ane buffer). Note that this part can exist replaced with the offset method and a memcpy (that’south what it does internally, subsequently all). The latter two will do the aforementioned, but will additionally catechumen all the samples copied from the raw format to the format that tin can exist used by the applications.

Refilling and submitting a buffer

It has to be noted that all the methods appear in a higher place will only work on the samples contained inside the internal buffer of the iio_buffer class. In fact, 2 successive calls to iio_buffer_foreach_sample for example will iterate over the very same samples twice. To obtain fresh samples from the hardware, it is required to call the office iio_device_refill. Needless to say that the previous content of the buffer is overwritten. On the other mitt, to submit samples that were written inside the iio_buffer, it is possible to call the part iio_buffer_push. Those two functions will phone call either the backend’s “read” or “write” functions, or if available, its “get_buffer” part (used in the local backend every bit a high-speed interface. We’ll see about that later).

Format conversion

As stated previously, the
iio_channel_read
and
iio_channel_write
functions convert the samples from/to their hardware format to/from the format of the architecture on which libiio is running. They actually simply telephone call the iio_channel_convert and iio_channel_convert_inverse public
API
functions. These two deserve some explanations.

Beginning, here is a textual representation of the hardware format equally reported by the kernel:

># true cat /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_type le:s12/sixteen>>iv

What information technology says, is that the hardware samples are in piddling-endian order (“le”), that the sample size is sixteen $.25, but with only 12 $.25 worth of data. The “»four” shift informs that those 12 bits are located after iv most-meaning bits (MSB), then in this particular case they stand for to the 12 less-pregnant bits (LSB), every bit 16 – 4 = 12. The “s” character of “s12” ways that the 12-flake value is signed.

From this hardware representation, the conversion functions volition process the samples so that they become 16-bit unsigned values.

The conversion process may look like an piece of cake chore; however, the implementation is rather complex, for a good reason: it has been designed to handle samples of any possible size. It makes it possible to procedure 256-bit samples on a 32-scrap CPU, for instance, while compilers typically don’t handle numbers that large.

Depression-speed interface

The depression-speed interface of the local backend was the first one to be implemented. It is besides the simplest. It is composed by the four post-obit backend functions:

          struct
          iio_backend_ops
          {
          int
          (
          *open)
          (
          const
          struct
          iio_device
          *dev,
          size_t
          samples_count,
          uint32_t
          *mask,
          size_t
          words,
          bool circadian)
          ;
          int
          (
          *shut)
          (
          const
          struct
          iio_device
          *dev)
          ;
          ssize_t
          (
          *read)
          (
          const
          struct
          iio_device
          *dev,
          void
          *dst,
          size_t
          len,
          uint32_t
          *mask,
          size_t
          words)
          ;
          ssize_t
          (
          *write)
          (
          const
          struct
          iio_device
          *dev,
          const
          void
          *src,
          size_t
          len)
          ;
          ...
          }
          ;
        

The epitome of the “open up” function has been explained previously. In the local backend, when the low-speed interface is used, it will perform the post-obit operations:

  • disable the internal kernel buffer (if information technology was enabled previously),

  • gear up the size of the kernel buffer, by writing the number of samples (given by “samples_count”) inside the “buffer/size” attribute of the device (which corresponds to the file “/sys/bus/iio/devices/iio_deviceX/buffer/size”),

  • open up the “/dev/iio:deviceX” file for reading and writing,

  • and finally, enable the channels which should be used by writing their “en” file (e.thousand.
    “/sys/bus/iio/devices/iio_deviceX/scan_elements/in_voltage0_en”).

Then, the “read” and “write” backend functions will respectively read and write the “/dev/iio:deviceX” node. The stream manipulated contains samples in their raw format. Those 2 functions are used from within the iio_buffer class, when the application requests the buffer to be refilled with iio_buffer_refill or wants to submit a buffer full of data with iio_buffer_push. The data flow occuring with the low-speed interface can be explained with the following schema:

When receiving from an input device (red arrows), the kernel will fill its internal buffer with new samples, direct from the hardware using
DMA
transfers if the hardware supports it, or by reading hardware registers (depression-speed devices are by and large connected with
I2C
or
SPI). When an application calls iio_buffer_refill, the samples contained in the kernel’s buffer will exist copied to the iio_buffer’s internal buffer, where they tin can be manipulated.

When streaming to an output device (blueish arrows), the application will fill the iio_buffer’s internal buffer with samples and so call iio_buffer_push, which will copy those new samples into the kernel’south buffer. Then, the kernel volition transfer them to the hardware.

So what exactly makes it a depression-speed interface? Betwixt the kernel buffer and the iio_buffer object, the samples are copied. On slow devices, it doesn’t matter much. Just on devices that can role at very high frequencies, the overhead caused past the copying process is enormous. As libiio is meant to be fast even on deadening CPUs, a dissimilar approach was necessary.

High-speed mmap interface

The loftier-speed interface is only implemented past the local backend, while the low-speed interface is also supported by the network backend. It requires a very recent Linux kernel, and fifty-fifty with that it is not supported past all kernel drivers. In the divergence of the low-speed interface, it is not implemented with the “read” or “write” backend functions, merely with a part named “get_buffer”; as such, one backend tin provide both interfaces. This is the instance for the local backend, for instance.

Popular:   How to Create Log and Semi-Log Graphs in Google Sheets

The “get_buffer”, as its name implies, retrieves a buffer from kernel infinite, or more specifically a pointer to a buffer allocated in kernel space. The primary difference of this interface, is that the kernel manipulates non merely i buffer, but a multitude of buffers. In the local backend, for instance, information technology manipulates four buffers. For input channels, the “get_buffer” role volition atomically query the kernel for a new buffer with fresh samples, and push button the old buffer back on the kernel’south buffer queue. For output channels, the principle stays the same, but the “get_buffer” function volition atomically query an empty buffer that the application can make full, and push the previous buffer that has been written to.

Amend than a wall of text, this schema sums upwardly the principle of the high-speed interface:

Annotation that this schema represents a capture process. For an output procedure, just consider all the arrows inverted.

Hither is the image of the “get_buffer” backend office:

          struct
          iio_backend_ops
          {
          ssize_t
          (
          *get_buffer)
          (
          const
          struct
          iio_device
          *dev,
          void
          **addr_ptr,
          size_t
          bytes_used)
          ;
          ...
          }
        
  • Equally expected, the “dev” parameter corresponds to the iio_device object that volition exist used.

  • The “addr_ptr” is a pointer to the address of a buffer. The buffer pointed by “addr_ptr” will be enqueued to the kernel’s queue, and “addr_pointer” will exist changed to betoken to a new buffer. Note that the interface makes no difference between input and output devices, as in both cases a buffer is enqueued and a new one is dequeued.

Of course, information technology’due south always preferable to apply the high-speed interface, when the driver allows it. Internally, the iio_buffer object will first try to use the high-speed interface, and if it’s detected every bit not available, information technology will switch back to the old interface.

Cyclic buffers

As stated previously, the iio_buffer class allows the cosmos of cyclic buffers, by setting the “cyclic” parameter of the part iio_device_create_buffer to True. In this instance, the very beginning buffer pushed (either with the dull interface or the newer high-speed interface) will be repeated until the buffer is destroyed with iio_buffer_destroy. Once the starting time buffer has been pushed, any subsequent telephone call to the function iio_buffer_push will issue an error and render a negative fault code. This feature tin can be used to output a specific waveform, for instance.

Annotation that circadian buffers only brand sense for output devices. The “cyclic” parameter of the iio_device_create_buffer part will simply be ignored when the related device is a capture device.

The network backend and IIO Daemon

Formulation of the IIO Daemon

Why a network backend?

Before libiio even existed, it was already decided that the library had to have a network backend, and this for several reasons:

  • The first reason is obviously to let the applications using libiio to stream samples on the network to any connected device. This has some benefits, notably for debugging purposes, and it makes evolution easier equally there is no demand to cross-compile the libiio-powered applications anymore.

  • Previously, the applications developed at Analog Devices that were using the IIO subsystem were for the most part meant to run directly on the target boards, that are by and large equipped with weak CPUs in comparison to regular workstations. While those target do the job when it comes to transferring samples, they are not suitable for processing them, peculiarly at a high speed. It may appear counter-intuitive, but streaming the samples via the network to a more powerful workstation allows to apply a much higher sample rate without having samples lost.

  • Even when used locally, via the “lo” virtual network interface, a network backend makes sense. While the use of the local backend would result in a higher throughput and less resource usage, the IIO interface of the Linux kernel does not let for more than than 1 process or thread to access the same device at the same moment. Past connecting 2 clients to the IIO daemon, which serves the requests from the network, it is possible for both of them to receive a copy of the stream of samples. This becomes extremely interesting, as it is at present possible for case to monitor the input stream received past a given application with the IIO Oscillator software (or whatever other suitable tool).

  • Finally, the network backend brings security. For avant-garde features, the applications using libiio with its local backend require super-user rights, which may non be suitable. When the network backend is used, the same features are bachelor without super-user rights: the applications connect to and dialog with the IIO daemon, which possesses the super-user rights and properly exposes the advanced features to its clients.

Challenges

Developing the IIO Daemon (IIOD) has been the most challenging role of the project; well-nigh of the complication of the library is concentrated in this program. To empathise why, permit’s see a short list of the various characteristics of IIOD:

  • It is a network daemon. As such, it has to handle and manage incoming connections. Doing a network daemon properly means handling several clients in parallel, handling client disconnections without crashing, leaking memory or in more than full general terms leaving the daemon in a bad state. It likewise has to survive a network failure, disconnect unresponsive clients later on a given timeout, etc.

  • To each connected client corresponds a thread in the server, that will interpret the commands sent and answer accordingly. All those unlike threads will execute the very same panel of functions. This means that all those functions must be thread-safe, that is to say safe to use in a multi-thread context: accesses to global resources are protected using locking mechanisms, and avoided every bit much as possible, in favor of a per-thread context construction. Furthermore, to each opened device correspond one other thread, called in this document the read-write (R/Westward) thread. For example, with 4 connected clients opening a dissimilar device each, the IIOD has i+4*ii = nine threads running in parallel: one primary thread, one customer thread per application connected and i R/West thread per device opened.

  • Information technology handles multiples clients using the same device at the same time. Each client receives a stream of samples corresponding to the channels requested; the daemon is in charge of duplicating the stream to the connected clients.

Far-distance view

1 interesting characteristic of IIOD is that it actually uses libiio underneath. In a manner, it is at the same time a customer application for the libiio library, too as a component used by the library. Each command of the interpreter of IIOD is backed by the corresponding
API
function of libiio; and in the network backend, to each backend function corresponds one command within the IIOD server. IIOD has been designed that way to avoid code duplication as much equally possible, and to test the validity and the robustness of the high-level
API
of libiio.

The command interpreter

Flex, Bison

The command interpreter has been written using the popular
GNU
tools Flex and Bison, gratis and open-source variants of the onetime Lex and Yacc tools. Those programs can be used to generate a lexer and a parser from source files defining the commands and the behaviour of the interpreter. The output consists in C files, that tin then exist compiled or cantankerous-compiled forth with the rest of the source code of the IIOD server.

Using Flex and Bison ensured that the code base would stay modest, smart and concise. Writing an interpreter from zippo in pure C can be a hard task, and oft (if non always) transform in a big lawmaking bloat with plenty of infinite for bugs to hide. This is true especially in this case, where the interpreter is meant to be ran across multiple threads.

Protocol

In the version 0.1 of the IIOD server, the following commands are accepted:

Help     Print this assistance message EXIT     Close the current session Print     Display a XML string corresponding to the electric current IIO context VERSION     Get the version of libiio in use TIMEOUT <timeout_ms>     Gear up the timeout (in ms) for I/O operations OPEN <device> <samples_count> <mask> [CYCLIC]     Open up the specified device with the given mask of channels Shut <device>     Shut the specified device READ <device> DEBUG|[INPUT|OUTPUT <channel>] [<attribute>]     Read the value of an aspect WRITE <device> DEBUG|[INPUT|OUTPUT <channel>] [<attribute>] <bytes_count>     Prepare the value of an attribute READBUF <device> <bytes_count>     Read raw information from the specified device WRITEBUF <device> <bytes_count>     Write raw data to the specified device GETTRIG <device>     Go the name of the trigger used by the specified device SETTRIG <device> [<trigger>]     Set the trigger to use for the specified device Fix <device> BUFFERS_COUNT <count>     Prepare the number of kernel buffers for the specified device

When started normally (not in debug fashion), the IIOD will answer those commands by a numeric code sent in evidently text. This code corresponds to -22 (the value of -EINVAL) if the parser did non sympathise the command. If the command has been properly parsed, the server will frontward the value returned by the underlying libiio function, similar iio_device_close for the CLOSE command.

For some operations, in case the return code is strictly positive, the server will also send a number of bytes of data defined past the value of the return code. This is the example for example for the Print, READ and READBUF commands.

On the other manus, the WRITE and WRITEBUF operations will crave a sure number of bytes to follow the command phone call; For those operations, this number must be specified as the “bytes_count” parameter in the command. The client’s thread will then ignore the given corporeality of bytes and just route them to the underlying libiio function. One time all the bytes have been transferred, the interpreter resumes its normal process.

Sending and receiving samples

Opening a device

Opening a device from the network tin can be performed with the Open control.

Open <device> <samples_count> <mask> [CYCLIC]

The customer thread of IIOD that corresponds to the connected client volition then register itself as a potential client for capturing or uploading samples.

  • If the customer application is the first to open up the given device, a new thread volition be started: the read-write (R/W) thread. Its purpose is to monitor the listing of registered clients for R/Westward requests, read or write the device, and if reading upload the captured data to all the clients requesting samples.

  • If the application requests reading and some other application is also using the same device for reading, information technology means that the R/Westward thread is already started. In this instance, the customer thread of IIOD will register itself aside of the other application, and the R/W thread will upload the captured data to both.

  • If the application requests writing, merely another application is already using the same device, an mistake code is returned. In theory, it should exist possible to handle different clients writing to one single device if they are writing to different channels; simply that has non been implemented all the same.

As the local backend only allows one device to be opened by one procedure at a time, the R/Due west thread is responsible for holding the iio_buffer object. When started, due to i client using the OPEN command, it will call iio_device_create_buffer. If all the clients disconnected or used the CLOSE control, the R/Westward thread will stop itself after destroying the buffer with iio_buffer_destroy.

Information technology is of import to annotation that the iio_buffer object is also destroyed and recreated each time a new customer use the Open command. This is done for two reasons:

  • The number of samples requested to libiio must represent to the highest number requested by the connected clients. This ensures that the buffer used inside the IIO subsystem of the Linux kernel is of the right size, and so that no samples will be dropped.

  • The channel mask requested to libiio must include all the channels requested by the diverse clients. For instance, if one application sends the Open up command to utilise the channel 1, and a split application sends this control to utilize the channel 2, then the R/W thread will re-create the iio_buffer object with a channel mask corresponding to the two channels being enabled. This is washed to ensure that each customer application will receive samples for each aqueduct it requested.

The read/write thread

When an application is set to capture or emit samples, it will transport the READBUF or WRITEBUF command (respectively) to the IIOD server, with the number of bytes to read or write as parameter:

READBUF <device> <bytes_count> WRITEBUF <device> <bytes_count>

The respective client thread volition then mark itself as requesting samples to be transferred, and and then sleep while waiting for a point from the R/W thread.

Periodically, the R/W thread volition go through its listing of registered clients. If the R/W thread corresponds to an output device, then but one customer can register at a time; when this client requests samples to exist written to the device, the R/Due west thread volition copy the stream of samples within the iio_buffer object, and then use iio_buffer_push to submit the data to the hardware.

Popular:   15 Windows Command Prompt (CMD) Commands You Must Know

If one or more clients are requesting to read samples, the thread will call the libiio
API
function iio_buffer_refill to fetch a ready of fresh samples, and and so upload them to each customer.

As soon as the amount of bytes requested by one client thread has been transferred, it will be awaken by the R/W thread, and will be passed a return code.

Information transfer

When one client is registered for writing, information technology will send the sample data right after the WRITEBUF command. The number of bytes uploaded to the server must stand for to the number specified in the WRITEBUF command. When receiving the data, the R/W thread will simply copy information technology into the internal buffer of the iio_buffer object and so phone call iio_buffer_push, until all the data has been submitted to the hardware.

When one client is registered for reading, as explained previously, the R/W thread volition first ship a return code. This value represents an fault code if negative, or the amount of bytes that will exist transferred if positive or aught. Right after this value, if no mistake occurred, the channel mask will be appended, followed by the data. If the number of bytes specified in the READBUF command is superior to the size of the buffer, then the information is uploaded in dissever chunks, each chunk containing one return code followed by a number of bytes inferior or equal to the buffer size. This makes sense, considering that the R/W thread can simply refill one buffer worth of data, so it cannot presume that more than one buffer of samples volition exist bachelor.

A return code of zero is really not an mistake code. It is used to notify the client application that no more samples tin can be read in this READBUF command, and that a new READBUF command should be used. This happens for case when a new client registered, and the channel mask was modified.

Server-side and client-side de-multiplexing

When only 1 customer is registered for reading, the process of uploading is rather like shooting fish in a barrel. The R/W thread will just write the content of the iio_buffer’s internal buffer to the socket of the customer. Just this is different when multiple clients are registered for the same device, and try to admission separate channels; in this example, it is required to de-multiplex the samples.

The process of de-multiplexing consists in extracting the samples respective to a given channel set, from a stream that contains samples for more channels. In libiio and IIOD, information technology exists in two different forms:

  • Server-side de-multiplexing ways that the algorithm is executed inside the server itself. When uploading the captured data back to the customer application, the R/Westward thread will simply upload the samples that belong to channels that were enabled by the awarding. This has several advantages: the customer receives but the samples information technology requested, and the network link usage is kept relatively low.

  • Customer-side de-multiplexing means that the algorithm is executed past the customer application. On that case, the IIOD server will upload the same buffer to all the clients who requested samples. This has a big negative bear on on network usage, equally the clients as well receive samples of channels that weren’t requested. Additionally, the applications have to handle the fact that they may obtain less worthy samples than the number they requested.
    On the plus side, client-side de-multiplexing puts all the burden of the process to the clients, which results in a large win when looking at resource usage of the target board. This is specially true on relatively slow processors, which then become capable of streaming at a much college throughput without losing samples (effectually x times the speed on a 400
    MHz
    board).
    Furthermore, de-multiplexing is a very like shooting fish in a barrel process for the application, thanks to the iio_buffer functions: iio_buffer_foreach_sample and the philharmonic of iio_buffer_first / iio_buffer_step / iio_buffer_end will only ignore the samples corresponding to channels that the application didn’t enable. Provided that the application use one of those methods to read the samples (iio_channel_read / iio_channel_read_raw will piece of work besides), and doesn’t expect to receive the exact number of samples it requested, and so information technology already supports both server-side and client-side de-multiplexing.

The network backend

Sending and receiving commands

In comparing with the complexity of the IIOD server, the network backend of the library is extremely simple. Each call to i of its backend part will upshot in a command being sent to the IIOD server. The “open” backend function will send the Open control, the “read” function will ship READBUF, etc. The parameters passed to the backend functions are properly converted to
ASCII
according to the schema that the IIOD understands. The code returned by the server will be used as the return value of the backend part.

Because that the IIOD also uses the public
API
of libiio, the network backend is completely transparent: the backend functions behave just similar their local backend analogue. In theory, information technology would exist completely possible to make IIOD use the network backend, and concatenation 2 IIOD servers together!

Context creation

One interesting annotation about the network backend, is how it creates its context. Once the application calls the
API
function iio_create_network_context, and the network link is established with the IIOD server, the initialization function of the network backend sends the Impress command to the server. For the record, this command returns an XML cord which represents the structure of the whole IIO context every bit seen past the server. From that XML string, the network backend will really create a IIO context from the XML backend, using the public
API
function iio_context_create_xml_mem. Then, information technology volition but piggy-dorsum its own set of backend functions to replace the ones provided past the XML backend.

Zippo-configuration

One late feature implemented in the network backend of libiio and in the IIOD server has been the support of machine-configuration. If a NULL arrow is passed as the “hostname” argument of iio_create_network_context, then the network backend volition attempt to observe and institute a connection to a IIOD server present on the network.

This feature depends on the Avahi nothing-configuration software stack. It can exist enabled by setting the preprocessor flag HAVE_AVAHI to a non-zero value; the IIOD volition then register itself to the Avahi daemon if ane is running on the target, and the network backend will query the local Avahi server for a IIOD server to utilise.

Libiio, now and in the future

Bindings

C is a good linguistic communication, but not everybody is familiar with it; some people might adopt object-oriented languages, like Java or Python for example. This is why we created bindings for the library, and so that information technology can be used with different programming languages. The additional languages supported past libiio are now Python and C#.

Since v0.nineteen all bindings crave explicit flags to exist enabled during builds. Please reference the build instructions to make sure the desired bindings are congenital and installed.

Python bindings

The Python bindings were adult very early in the project to facilitate generating XML strings modeling IIO contexts in lodge to properly test the XML backend. They quickly became outdated equally the project moved on, but were subsequently greatly improved and should now exist on par with the C# bindings.

To create the bindings, The “ctypes” module has been used:

          from
          ctypes
          import
          Pointer,
          Structure,
          cdll,
          c_char_p
          def
          _init(
          ):
          class
          _Device(Construction):
          pass
          DevicePtr
          =
          Pointer(_Device)
          lib
          =
          cdll.LoadLibrary
          (
          'libiio.so.0'
          )
          global
          _d_get_id     _d_get_id
          =
          lib.iio_device_get_id
          _d_get_id.restype
          =
          c_char_p     _d_get_id.archtypes
          =
          (DevicePtr,
          )
          class
          Device(
          object
          ):
          def
          __init__
          (
          cocky
          ,
          _device):
          cocky._device
          =
          _device
          cocky.id
          =
          _d_get_id(
          self._device)
        

This code shows the _init office, whose goal here is to load the library and create wrappers in Python for the C functions of libiio. Objects created from the Device course provide a couple of methods that will telephone call those wrappers. Some of the known abiding values of the IIO objects, like the ID of a device, are cached within the Python classes for faster access. Then, press the ID of a device in a Python script is as easy as typing “print my_device.id”. To better empathize the available calls consult the bindings source and some available examples.

Installation

Since v0.21 the python bindings have been bachelor through pypi, and therefore can exist installed with pip:

pip install pylibiio

Annotation that this will just install the bindings and will error if the library itself is not installed.

PyADI-IIO

An additional module was created which leverages the libiio python bindings call pyadi-iio. pyadi-iio is recommended if a device specific grade exists for your electric current hardware. However, since pyadi-iio uses libiio all the libiio python APIs are available in that module if needed.

C# bindings

The C# bindings in item are fully functional and cover the whole panel of features that libiio provides. Its
API
consists in the Context, Device, Channel and IOBuffer classes, each one providing a couple of methods, that directly call their C analogue:

          namespace
          iio
          {
          public
          form
          Device
          {
          public
          IntPtr dev;
          ...
          [DllImport(
          "libiio.dll", CallingConvention
          =
          CallingConvention.
          Cdecl
          )
          ]
          private
          static
          extern
          IntPtr iio_device_get_id(IntPtr dev)
          ;
          public
          cord
          id(
          )
          {
          return
          Align.
          PtrToStringAnsi
          (iio_device_get_id(dev)
          )
          ;
          }
          }
          }
        

In this case, if you call the id method on an object of type Device, every bit a result the C function iio_device_get_id will exist chosen. All the others methods implement a similar machinery.

The C# bindings are particularly interesting for Windows users, because they permit to use libiio in a .Internet application (with the network backend, of course). By using Mono, it is also possible to utilise libiio in C# programs running on Linux.

Future improvements

Zerocopy

The current version of the IIOD server is extremely fast, provided that client-side demultiplexing is used. On a weak 400
MHz
CPU, it has no problems to stream samples at speeds of 3MSPS (three million samples per second) without dropping a unmarried i.

All the same, 3
MSPS
is really far from the maximum capacity of the typical converters. The AD-FMCOMMS3-EBZ board, for instance, tin digitize at a speed up to 61.44
MSPS
(fourth dimension iv channels, for a total of 245.76
MSPS) … about 100 times faster. And the recent AD-FMCDAQ2-EBZ is capable of g
MSPS
for dual 16-flake channels, which represents a maximum transfer speed of 2000
MSPS, or 32.0 Gb/s.

Then how to achieve those extremely high speeds, with a 400
MHz
CPU? That’s where Goose egg copy comes in play. To understand what zerocopy is, and how useful it can be, allow’s quickly retrieve how the IIOD uploads the samples from one device to a connected client:

  • When the low-speed interface is used, the samples are caused from the hardware using
    DMA, and stored into a kernel buffer; the local backend of libiio volition and so use the CPU to copy the samples to the iio_buffer object, and so copy again to the packet buffer of the Linux kernel by writing the client’south socket.

  • The loftier-speed interface works roughly the same, simply the manual re-create from the kernel’s IIO buffer to the iio_buffer object is avoided by swapping the buffers from user space and kernel space. However, information technology is still required to write the buffer to each client’s socket.

  • Then comes zerocopy. The principle, is to avoid the second manual re-create of the samples, when the data is written to the socket. So how can this piece of work? How is it possible to transport data through the network, without writing the socket?
    The answer is to be found deep inside the Linux kernel. A detail organization phone call, named “vmsplice”, allows to “give” virtual pages of retentiveness to the Linux kernel through a file descriptor. This trick, introduced in Linux 2.6.17, works by re-configuring the Memory Direction Unit of measurement of the CPU. Let’s say the buffer to write to the socket starts at address 0x4000; the vmsplice system call will map the verbal aforementioned expanse to a different address in kernel space,
    e.one thousand.
    0x8000, and then that the buffer tin can be addressed from both addresses. And so, information technology will re-map the original 0x4000 address to point to a different physical location. As a outcome, a complete buffer of samples has been moved to kernel space, without copying a single byte of it! And so, from the hardware to the network link, the CPU never has to copy anything, and the 400
    MHz
    ARM board can push hundreds of megabytes of samples on the network while having a CPU usage staying close to zero.



    While vmsplice moves the pages to kernel infinite, it is likewise possible to indistinguishable them using the “tee” system telephone call, and serve the aforementioned information to multiple clients continued to the IIOD server without using the CPU at all once again.
    This very interesting feature of the kernel doesn’t come without drawbacks: the address and the size of the buffer must exist aligned to the page size for it to work. It is also uncertain how the IIO drivers within the Linux kernel would cope with this characteristic; only that is certainly something that will be attempted in the future.

Linux Signal Generation and Handling Explained

Source: https://wiki.analog.com/resources/tools-software/linux-software/libiio_internals