Skip to content

libiio_0_to_1

Paul Cercueil edited this page Aug 22, 2023 · 4 revisions

Libiio 0.25 to 1.x API update guide

The new Libiio 1.x breaks the ABI from the v0.25 version. This means that a program compiled against libiio v0.25 will not run against Libiio 1.x.

This is because the new library introduced new functions, removed some others, or changed the prototype of some functions. Therefore, to make an application compatible with Libiio 1.x, some changes are needed.

This document aims to list all the changes that are needed for an application designed for Libiio 0.25 to compile and run against Libiio 1.x.

Headers

The iio.h public header has been moved to a iio subdirectory. Therefore you now have to include <iio/iio.h> instead of <iio.h>.

Alongside this file are some new public headers:

  • <iio/iio-debug.h> contains macros that can be used to print messages to the standard or error output paths.

  • <iio/iio-lock.h> provides OS-independent mutex and thread routines.

  • <iio/iio-backend.h> provides structure definitions and symbols that can be used to develop a plug-in backend for Libiio.

  • <iio/iiod-client.h> provides all the definitions needed for a plug-in backend to interface with the IIOD server.

Error handling

Starting from Libiio v1.0, most API functions that return a pointer won't return NULL on error. Instead, they will return an error code encoded into the pointer value. The documentation embedded in iio.h will specify if a given function returns a pointer-encoded error.

You can get the error code from the pointer using the iio_err function. If it returns zero, then the pointer is valid and can be used directly.

Note that the error code, if set, will always be negative. You can get a textual representation of the error using iio_strerror, or use the log macros prm_perror, ctx_perror, dev_perror or chn_perror from <iio/iio-debug.h>.

The errno variable is not used anymore to return error codes, so it is now an error to check it after a libiio call. With that said, it is not guaranteed that libiio won't modify this variable, as system calls may modify it.

Context parameters

Libiio 1.x introduces the notion of context parameters. These are contained in the publicly visible iio_context_params structure. The following parameters can be set:

  • timeout_ms: the delay in milliseconds after which input/output operations (streaming samples, reading attributes, IIOD protocol commands etc.) will throw a timed-out error. If set to zero, the backend's default timeout value is used.

    Note that the timeout value can be changed after the context is created, using the iio_context_set_timeout function.

  • out and err: output paths for the library's debug and information messages (for out) and warning and error messages (for err). If set to NULL, these will default to stdout and stderr respectively.

  • log_level: allow configuring the verbosity of Libiio's output messages. If zero, it will default to the log level configured at compilation time, which is generally LEVEL_INFO. Only the messages whose level is lower or equal than this threshold will be displayed.

  • stderr_level: Messages whose level is lower or equal than this threshold are sent to the error output path; messages whose level is higher are sent to the regular output path. If zero, it will default to LEVEL_WARNING.

    Note that it is possible to send all messages to the standard output path by setting this parameter to LEVEL_NOLOG, and also to send all messages to the error output by setting this parameter to LEVEL_DEBUG.

Context creation

Libiio context creation is now handled uniquely by the iio_create_context function. The first parameter is a pointer to a iio_context_params structure; it is completely optional, and NULL is accepted. The second parameter is the URI that identifies the IIO context. The function will now return pointer-encoded codes en errors, so you must use iio_err to verify that the call succeeded.

Furthermore:

  • iio_create_context_from_uri is replaced by iio_create_context. The same URI argument can be passed as-is.

  • iio_create_local_context is gone. You can still create a local context by using the URI local:.

  • iio_create_default_context is gone. You can still create the default context by passing a NULL pointer as the URI parameter.

  • iio_create_network_context is gone. You can create a network context by using the ip: URI prefix followed by a hostname, IPv4 or IPv6 address.

  • iio_create_xml_context is gone. You can create a XML context by using the xml: URI prefix followed by the path to the XML file on disk.

  • iio_create_xml_context_mem is gone. You can create a XML context from memory by using the xml: URI prefix followed by the NULL-terminated XML string.

  • iio_context_clone is gone. You can however clone a context by retrieving the params with iio_context_get_params (or just use NULL if that's what you used for the first context), and the URI with iio_context_get_attr_value(ctx, "uri"), and just creating a new context with these two parameters.

Context discovery

The API provided by libiio to discover remote libiio contexts has changed completely. Two different yet similar APIs were provided in libiio v0.25 (the one based on iio_scan_context and the one based on iio_scan_block) which was redundant and very confusing.

In libiio 1.0, there is a single scan API based on the iio_scan object.

The function iio_scan will create a scan context with the given backends. The backends string uses the same format as in v0.25, but use commas instead of colons as the delimiter.

Using the newly created object, you can obtain the number of libiio contexts found using iio_scan_get_results_count, and get each entry's description string and URI using iio_scan_get_description and iio_scan_get_uri respectively, passing as argument the index of the context you're interested in.

Finally, you can use iio_scan_destroy to free the iio_scan object.

Attributes

The various IIO objects still have attributes. However, the API changed slightly. In Libiio v0.25, iio_device_attr_read, iio_device_debug_attr_read, iio_device_buffer_attr_read and iio_channel_attr_read could be used to read raw bytes or a string into a buffer. This functionality is now provided by iio_device_attr_read_raw, iio_device_debug_attr_read_raw, iio_device_buffer_attr_read_raw and iio_channel_attr_read_raw respectively.

The old names are now generic (as in C11 _Generic) macros, which means that the ptr argument can point to different types: bool, long long and double. With that said, the specific functions of v0.25 to read these types are still present in Libiio v1.0; the generic macro is only present for convenience, and is not mandatory, which means that it is still possible to use Libiio with C89 or C99 projects.

The exact same can be said for iio_device_attr_write, iio_device_debug_attr_write, iio_device_buffer_attr_write and iio_channel_attr_write, which are now renamed to iio_device_attr_write_string, iio_device_debug_attr_write_string, iio_device_buffer_attr_write_string and iio_channel_attr_write_string, respectively. The old names are now generic (C11 _Generic) macros whose val argument can be of the following types: bool, long long, double, char * and const char *.

The iio_device_attr_write_raw, iio_device_debug_attr_write_raw, iio_device_debug_attr_write_raw and iio_channel_attr_write_raw have not been modified and their prototype is the same as before.

Channel enable / disable

Starting from Libiio 1.0, the channel state (enabled or disabled) is no longer an intrinsic property. Instead, a iio_channels_mask object can be used to store the state of each channel of a given device. This mask object can then be passed to various API functions.

The mask object can be created with the function iio_create_channels_mask, and destroyed with iio_channels_mask_destroy.

The iio_channel_enable and iio_channel_disable functions will now take a pointer to a iio_channels_mask as argument. The channels must be children of the device associated with the mask object.

Buffers

The iio_buffer object changed greatly since Libiio v0.25, and its API is now very different.

Creating a buffer object still happens with iio_device_create_buffer, however the prototype of the function changed. Alongside the iio_device pointer argument, it will now take as argument the hardware index of the buffer (which is most likely 0) and a pointer to a iio_channels_mask associated with the same iio_device (failing to comply to this rule will result in undefined behaviour).

The buffer object gained a few API functions:

  • iio_buffer_get_channels_mask return a pointer to the internal mask object. It does not point to the mask object passed as argument to the create function. The mask returned may contain more enabled channels than what was requested, and corresponds to the list of channels for which the buffer will deliver samples.

  • iio_buffer_enable starts the streaming process. With the Libiio v0.25 API, this was automatically done internally during the first call to iio_buffer_refill or iio_buffer_push, but needs to be done manually now (unless you use the iio_stream API - keep reading).

  • iio_buffer_disable stops the streaming process.

The big change since Libiio v0.25 is that the buffer object does not provide API functions related to data streaming, or functions to access the data.

Therefore, these functions have been removed:

  • iio_buffer_refill, iio_buffer_push and iio_buffer_push_partial

  • iio_buffer_get_poll_fd and iio_buffer_set_blocking_mode

  • iio_buffer_start, iio_buffer_end, iio_buffer_first, iio_buffer_step

  • iio_buffer_foreach_sample.

The iio_buffer_cancel function still exists and its prototype is the same.

Low-level data streaming API

The streaming process changed completely in Libiio v1.0, although it is possible to emulate the old API on top of the new low-level iio_block API.

As its name suggests, the base object for this new API is called iio_block. A block object can be created with iio_buffer_create_block. The only two arguments needed are a pointer to the iio_buffer, and the size (in bytes) of the block. A block can later be destroyed with iio_block_destroy.

Accessing data

The iio_block object contains a "block" of samples (which we won't be calling a "buffer" to avoid confusion with IIO's buffer objects). The samples can be accessed using the following functions:

  • iio_block_start, iio_block_end to get the block's boundaries;

  • iio_block_first to get a pointer to a channel's first sample in the block;

  • iio_block_foreach_sample to iterate over the samples contained in the block.

Note that there is no "iio_block_step" function, since the step size is basically the buffer's sample size. You can retrieve the step size doing:

const struct iio_channels_mask *mask = iio_buffer_get_channels_mask(buffer);
size_t sample_size = iio_device_get_sample_size(dev, mask);

You can also use the same mask that was used to create the iio_buffer, if you know that the hardware will never report a different mask than what was requested. In doubt, retrieving the iio_buffer's mask with iio_buffer_get_channels_mask is the safest option.

Enqueueing and dequeueing blocks

Instead of a push / refill mechanism, the new iio_block API works with a queue mechanism.

  • To push samples to the hardware, fill a block with the samples using the functions described above, then call iio_block_enqueue. You can enqueue as many blocks as you have available.

  • iio_block_enqueue is always a non-blocking operation. Once a block has been enqueued, it should be considered owned by the hardware, and therefore it is forbidden to access or modify the underlying memory. It is possible to reuse a block only after it's been successfully dequeued using iio_block_dequeue.

  • Don't forget to call iio_buffer_start to start the streaming process, either before or after enqueueing the first blocks.

  • iio_block_dequeue is a blocking operation, unless the nonblock parameter is set to true; in which case -EBUSY will be returned if the block is not yet ready to be dequeued.

  • All blocks are in the "dequeued" state after creation. To refill samples from the hardware, you first need to "give back" all the blocks to the hardware, using iio_block_enqueue with a transfer length of 0 bytes. The same blocks, after dequeued, will contain the samples received from the hardware.

Note that even though you can enqueue / dequeue the blocks in any order you want, the blocks will be filled by the hardware in the order at which they were enqueued.

Trying to enqueue an already enqueued block or dequeue an already dequeued block is an invalid operation, and will result in a -EPERM error being returned.

Optional high-level data streaming API

Additionally to the queue mechanism described above, a different, simpler API has been introduced, based on a new iio_stream object.

This different API should be considered optional and is implemented on top of the queue mechanism; its purpose is only to simplify data streaming for simple applications.

A iio_stream object can be created with iio_buffer_create_stream. It takes three parameters: a pointer to the iio_buffer object, the number of blocks to create (a good default is 4), and the size in samples of each block.

This object can later be destroyed with iio_stream_destroy.

Finally, data streaming (be it for pushing or receiving samples) is done with a single function, iio_stream_get_next_block, which will return a pointer to a iio_block object.

This iio_block can then be read from (if receiving samples), or written to (if uploading samples). To receive new samples or request a new block for uploading samples, simply call iio_stream_get_next_block again.

Note that when using the iio_stream API, it is invalid to call iio_block_enqueue, iio_block_dequeue, iio_buffer_enable or iio_buffer_disable.