Libiio 0.x to 1.x API update guide
The new Libiio 1.x breaks the ABI from the v0.x version. This means that a program compiled against libiio v0.x 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.x 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_timeoutfunction.outanderr: output paths for the library’s debug and information messages (forout) and warning and error messages (forerr). If set to NULL, these will default tostdoutandstderrrespectively.log_level: allow configuring the verbosity of Libiio’s output messages. If zero, it will default to the log level configured at compilation time. 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 toLEVEL_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 toLEVEL_DEBUG.flags: a bitmask of values fromenum iio_context_flags. The only flag currently defined isIIO_CTX_XML_INCLUDE_VALUES, which causesiio_context_get_xmlto read and serialize current attribute values into the XML output (see the section below oniio_context_get_xml).
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 on errors, so you must use iio_err to verify that the
call succeeded.
Furthermore:
iio_create_context_from_uriis replaced byiio_create_context. The same URI argument can be passed as-is.iio_create_local_contextis gone. You can still create a local context by using the URIlocal:.iio_create_default_contextis gone. You can still create the default context by passing a NULL pointer as the URI parameter.iio_create_network_contextis gone. You can create a network context by using theip:URI prefix followed by a hostname, IPv4 or IPv6 address.iio_create_xml_contextis gone. You can create a XML context by using thexml:URI prefix followed by the path to the XML file on disk.iio_create_xml_context_memis gone. You can create a XML context from memory by using thexml:URI prefix followed by the NULL-terminated XML string.There is a new
emu:URI for connecting to an IIO emulator. There was no equivalent creator function in v0.x — the emulator backend is new in v1.0.iio_context_cloneis gone. You can however clone a context by retrieving the params withiio_context_get_params(or just use NULL if that’s what you used for the first context), and the URI by looking up the"uri"attribute withiio_context_find_attrand reading it withiio_attr_get_static_value, then creating a new context with those two parameters.
Version functions
iio_library_get_version and iio_context_get_version are gone. They are
replaced by iio_context_get_version_major, iio_context_get_version_minor
and iio_context_get_version_tag. When a non-NULL context is passed, these
functions return the version of the remote IIOD server rather than the local
library version.
iio_context_get_xml now allocates
In v0.x, iio_context_get_xml returned a pointer to a static string owned by
the context. In v1.0 it returns a newly allocated string that the caller is
responsible for freeing with free(). Failing to do so will leak memory.
Additionally, if IIO_CTX_XML_INCLUDE_VALUES is set in the flags field of
iio_context_params, iio_context_get_xml will also read and serialize the
current attribute values into the XML output.
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.x
(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.x, 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.
Backend query API
Three functions related to querying the available backends were renamed to make it clearer that they refer to backends compiled into the library, as opposed to dynamically loaded plug-in backends:
iio_has_backendnow takes an additionaliio_context_paramspointer as its first argument (may be NULL).iio_get_backends_countwas renamed toiio_get_builtin_backends_count.iio_get_backendwas renamed toiio_get_builtin_backend.
Attributes
The various IIO objects still have attributes. However, the API changed
slightly. Note also that v1.0 introduces event attributes as a new category,
exposed on both devices (iio_device_get_event_attr,
iio_device_find_event_attr) and channels (iio_channel_get_event_attr,
iio_channel_find_event_attr). These did not exist in v0.x.
In Libiio v0.x, 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_attr_read_raw, called on a iio_attr * object obtained with one of the
find functions described in the next section. The typed read functions
(iio_attr_read_bool, iio_attr_read_longlong, iio_attr_read_double) work
the same way.
For C code, the iio_attr_read _Generic macro dispatches to the correct
typed function based on the type of the pointer argument (bool *, long long *
or double *). In C++ this macro is not available and you must call the
explicit typed functions directly. The specific typed functions are present in
both C and C++, so they can always be used regardless of language.
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. The raw-string functionality of these functions is
now provided by iio_attr_write_string, and the typed variants
(iio_attr_write_bool, iio_attr_write_longlong, iio_attr_write_double)
operate on a iio_attr * object obtained with one of the find functions
described in the next section.
For C code, the iio_attr_write _Generic macro is provided as a convenience:
it dispatches to the correct typed function based on the type of the val
argument (bool, long long, double, char * or const char *). In C++
this macro is not available and you must call the explicit typed functions
directly. The specific typed functions are present in both C and C++, so they
can always be used regardless of language.
The iio_attr object
In addition to the renamed read/write functions, Libiio 1.0 introduces
struct iio_attr as a first-class object. Rather than addressing attributes by
passing a name string every time you want to read or write, you look up the
attribute once with iio_device_find_attr, iio_channel_find_attr,
iio_buffer_find_attr or iio_device_find_debug_attr, and then call methods
directly on the returned pointer.
The iio_attr object exposes two pieces of metadata beyond the value itself:
iio_attr_get_namereturns the attribute’s name as it appears in the API.iio_attr_get_filenamereturns the underlying sysfs filename, which may differ from the name (for example a channel attribute namedrawmay map to a file calledin_voltage0_raw).
For context attributes that have a fixed value baked into the XML description,
iio_attr_get_static_value returns that value without a round-trip to the
hardware. Note that this only applies to context-level attributes; for device,
channel, buffer and debug attributes it returns NULL.
Availability helpers
Many IIO attributes have a companion _available sysfs file that lists the
values the hardware will accept. Libiio 1.0 provides two helper functions to
parse these without having to implement the parsing yourself.
When the available file uses the range format [min step max], use
iio_attr_get_range, which fills in three double values for the minimum,
step size and maximum.
When the available file is a space-separated list of discrete values, use
iio_attr_get_available, which allocates an array of string pointers and
fills in the count. The array must be freed afterwards with
iio_available_list_free.
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,
passing the total number of channels of the device, 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.
The same mask is also required by iio_device_get_sample_size, which in v0.x
derived the sample size from the device’s internal hidden mask. Passing an
explicit mask gives you full control: you can compute the sample size for any
combination of channels without changing global state.
Buffers
The iio_buffer object changed greatly since Libiio v0.x, and its API is now
very different.
In Libiio 1.0, buffer objects are pre-existing objects exposed by the device
driver — they are not created by the application. You enumerate them with
iio_device_get_buffers_count and retrieve one by index with
iio_device_get_buffer. For the overwhelming majority of devices there is only
one buffer, at index 0.
To open a buffer for streaming, call iio_buffer_open, passing the buffer
object and the channels mask you created earlier. This returns an
iio_buffer_stream object that represents an opened, configured instance of
that buffer.
The buffer object gained a few API functions:
iio_buffer_get_attrandiio_buffer_find_attrprovide access to buffer-level attributes (replacing the oldiio_device_buffer_attr_readandiio_device_buffer_attr_writefamilies).iio_buffer_get_scan_elements_countandiio_buffer_get_scan_elementlet you enumerate the channels that the buffer supports.iio_buffer_is_outputtells you whether the buffer feeds data to the hardware (TX) or receives data from it (RX).iio_buffer_set_dataandiio_buffer_get_datastore a user pointer on the buffer object, consistent with the same pattern on context, device and channel.
The iio_buffer_stream object exposes:
iio_buffer_stream_startandiio_buffer_stream_stopto start and stop the hardware streaming.iio_buffer_stream_get_channels_maskto retrieve the mask that is actually in use, which may contain more enabled channels than what was requested if the hardware requires it.iio_buffer_stream_cancelto unblock any pendingiio_block_dequeuecalls from another thread.
The big change since Libiio v0.x 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_pushandiio_buffer_push_partialiio_buffer_get_poll_fdandiio_buffer_set_blocking_modeiio_buffer_start,iio_buffer_end,iio_buffer_first,iio_buffer_stepiio_buffer_foreach_sample
The v1 equivalent of iio_buffer_cancel is iio_buffer_stream_cancel, called
on the iio_buffer_stream object rather than directly on the buffer.
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_stream_create_block. The only
two arguments needed are a pointer to the iio_buffer_stream, 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_endto get the block’s boundaries;iio_block_firstto get a pointer to a channel’s first sample in the block;iio_block_foreach_sampleto iterate over the samples contained in the block, passing the channels mask so that the iteration knows which channels are active.
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_stream_get_channels_mask(bs);
size_t sample_size = iio_device_get_sample_size(dev, mask);
You can also use the same mask that was used to open the iio_buffer, if
you know that the hardware will never report a different mask than what was
requested. In doubt, retrieving the stream’s mask with
iio_buffer_stream_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_enqueuetakes the number of bytes to transfer and acyclicflag. 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 usingiio_block_dequeue.Don’t forget to call
iio_buffer_stream_startto start the streaming process, either before or after enqueueing the first blocks.iio_block_dequeueis a blocking operation, unless thenonblockparameter is set to true; in which case-EBUSYwill 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_enqueuewith abytes_usedvalue of 0 (which means “use the full block size”). 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
four parameters: a pointer to the iio_buffer object, the number of blocks
to create (a good default is 4), the size in samples of each block, and the
channels mask.
This object can later be destroyed with iio_stream_destroy. If you need to
unblock a pending iio_stream_get_next_block call from another thread, use
iio_stream_cancel before destroying the stream.
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. The returned pointer is const, and pointer-encodes an
error on failure.
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_stream_start or
iio_buffer_stream_stop directly — the stream manages those internally.
Trigger API
The signature of iio_device_get_trigger changed in v1.0. In v0.x it
returned an integer error code and filled in the trigger pointer through an
output parameter. In v1.0, it follows the same pointer-encoded error convention
as the rest of the API:
/* v0.x */
const struct iio_device *trigger;
int ret = iio_device_get_trigger(dev, &trigger);
/* v1.0 */
const struct iio_device *trigger = iio_device_get_trigger(dev);
int ret = iio_err(trigger);
iio_device_set_trigger is unchanged.
Timeout constants
The iio_context_set_timeout function’s parameter type changed from
unsigned int in v0.x to int in v1.0. This was necessary to accommodate
three new sentinel values:
Constant |
Value |
Meaning |
|---|---|---|
|
|
Use the backend’s built-in default timeout |
|
|
Wait indefinitely (no timeout) |
|
|
Return immediately if no data is ready |
The same sentinel values apply to the timeout_ms field of
iio_context_params when creating a context.