Skip to content
Snippets Groups Projects
Commit 50af44a6 authored by Alessandro Rubini's avatar Alessandro Rubini Committed by Federico Vaga
Browse files

Documentation/zio/: new folder


These files document how ZIO works. They are still subject
to modification while we are writing code, and some information,
for example how devices work, are still missing.

Signed-off-by: default avatarAlessandro Rubini <rubini@gnudd.com>
Signed-off-by: default avatarFederico Vaga <federico.vaga@gmail.com>
parent 99e459a4
Branches
Tags
No related merge requests found
00-INDEX
- this file
device.txt
- description of what ZIO devices (and csets and channels) are.
buffer.txt
- description of the buffer, it's methods and how it is used.
trigger.txt
- what is a trigger and what's its role in ZIO.
tools/
- useful and simple user-space programs
\ No newline at end of file
ZIO defines a "buffer" object type, so each device can exploit any
features it may offer to speed up data transfers.
Each cset in a device can use a different buffer, which is specified
as an attribute of the cset. One instance of the buffer type exists
for each channel, and the char devices (control and data: see
device.txt) refer to the buffer.
Please read <linux/zio-buffer.h> together with this file.
Buffer Types
============
Buffers may be device-specific or generic. A generic buffer called
"kmalloc" is provided within zio-core; it uses kmalloc for allocation
of control and data blocks. Such buffer is activated by default on
all new csets being registered but is not special in any other way, you
can write your own generic buffer (and if it's better than ours, we
may use it as default buffer in future releases).
A device-specific buffer declares to be such within its attributes. A
device-specific buffer can only be used by csets that declare its name
as preferred buffer type. When such csets are registered, if the
buffer is already known to ZIO, it will be activated by default instead
of "kmalloc".
Sometimes people write drivers for a set of similar devices, with
similar DMA capabilities. In this case, all of them may refer to the
same device-specific buffer type; the buffer type will be registered
as a standalone kernel module.
The ZIO buffer data structures includes two sets of operations:
file_operations and buffer_operations.
Buffer Operations
=================
Each buffer type must provide the following buffer operations. All
of them are implemented in zio-buf-kmalloc.c, which may be used as
reference to better understand the role of each method:
void *(*create)(struct zio_buffer *buffer, struct zio_channel *ch,
fmode_t f_flags);
void (*destroy)(void *b_instance);
The create operation allocates and initializes an instance of the
buffer type. It is called by ZIO when a channel is opened for the
first time. The instance is a void pointer because each buffer needs a
different (private) data structure to represent an own instance.
Destroy deallocates the buffer. ZIO calls destroy when the channel
is unregistered from ZIO or when the user assigns a different buffer
type to the channel.
When the control/data devices are closed, ZIO doesn't call destroy, so
incoming data can queue up while the application leaves it closed
(e.g., a shell script). Actually, the release file operation is not
handled by ZIO, so it couldn't call destroy on close even if it wanted
to.
void *(*alloc_block)(void *b_instance, void **handle_ret,
size_t size, gfp_t gfp);
void (*free_block)(void *handle);
For input channels, a block is allocated when the trigger fills it,
and it is freed when user has read or ignored it. For output,
allocation happens on user write and free is called by the trigger.
Thus, the function are sometimes called by buffer code itself, and
sometimes by the trigger. The data structure hosting a block is
returned as a void pointer because each buffer needs its own
private structure.
ssize_t (*store_block)(void *handle, struct zio_control *ctrl,
void *data, size_t len);
ssize_t (*retr_block) (void *handle, struct zio_control *ctrl,
void *data, size_t len);
The trigger calls the store method when it has data to push for input
channels. If uses an handle it received from the alloc_block method.
The trigger for output channels uses the retrieve method to extract
data from the buffer and send it to the hardware driver. After
retrieving data, it is responsible for freeing it.
FIXME: how can the trigger know the handle to retrieve?
File Operations
===============
These are the file_operations used by the char devices (control and
data) for every channel using this buffer type. The open method of
the zio file operations kmallocs f->private_data to point to this
structure:
struct zio_f_priv {
void *b_instance;
enum zio_cdev_type type;
};
All buffer file operations can thus refer to the buffer instance for
this channel and know if the current file is ZIO_CDEV_CTRL or
ZIO_CDEV_DATA. The release file operation is expected to free this
structure.
See zio-buf-kmalloc.c for a working example of buffer file operations.
ZIO Device
==========
A device, registered through zio_register_device() is the description
of an I/O peripheral. It is made up of channel-sets, called csets
from now on. A device may represent a PCI board or an SPI integrated
circuit or whatever it makes sense to manage from a single device driver.
All I/O operations are performed on csets, so the device is just an
array of csets.
Csets
=====
A cset (channel-set) is an homogeneous set of I/O channels. All
channels in the set are feature the same physical characteristics;
moreover, a cset refers to a trigger object, so all channels in a set
are triggered by the same event. This is the typical use case for
logic analysers (digital input) or multi-probe scopes (analog input),
as well as multi-waveform output. If your device has several input
channels which are separately triggered, they should be defined as
several cset items, each featuring one channel only. Finally, all
channels in a cset use the same buffer object for in-kernel data
storage. Both the buffer and the trigger for a cset are set by
writing the proper name in sysfs. At device registration defaults
apply. If ZIO can't find the trigger/buffer name you wrote,
it will return EINVAL and leave the previous trigger/buffer in place.
Channels
========
The channel is the lowest object in the ZIO hierarchy. It represents
the single physical connector of the device: analog or digital, input
or output. Time-to-digital and digital-to-time devices can be represented
as channels as well.
Attributes
==========
Most configuration and information in ZIO happens through sysfs.
See sysfs.txt for information about attributes. (FIXME: sysfs.txt)
Data Transfers
==============
Data transfer in ZIO uses two char devices for each channel offered by
the driver. One char device is used to describe data blocks (we call
it control device); the other is used to transfer the data blocks (we
call it data device). The control device returns (or accepts) a
fixed-size structure that describes the next data transfer, including
the trigger in use, data size, number of samples and various
attributes. Applications may choose to read the data device alone,
without retrieving control information: when any data of a new block
is transferred, the associated control information is discarded.
Similarly, data is discarded if you re-read the control device after
having retrieved the description of a data block you are not
interested in. For output, writing data without writing control uses
the default control information, or the one from the previous
transfer).
The full set of rules for data and control transfers is described
elsewhere (FIXME: link to other docs) but it is pretty intuitive once
you get the idea.
See tools/zio-dump.c for an example of generic and simple input
application that shows use of control and data files.
/*
* Trivial utility that reports data from ZIO input channels
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "zio.h"
#include "zio-buffer.h"
#define FD_C 0
#define FD_D 1
unsigned char buf[1024*1024];
int main(int argc, char **argv)
{
FILE *f;
char *outfname;
int fd[2];
int i, j;
if (argc !=3) {
fprintf(stderr, "%s: use \"%s <ctrl-file> <data-file>\"\n",
argv[0], argv[0]);
exit(1);
}
for (i = 0; i < 2; i++) {
fd[i] = open(argv[i + 1], O_RDONLY);
if (fd[i] < 0) {
fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i + 1],
strerror(errno));
exit(1);
}
}
/* the data channel is non-blocking */
fcntl(fd[FD_D], F_SETFL, fcntl(fd[FD_D], F_GETFL) | O_NONBLOCK);
/* always log data we read to some filename */
outfname = getenv("ZIO_DUMP_TO");
if (!outfname)
outfname = "/dev/null";
f = fopen(outfname, "w");
if (!f) {
fprintf(stderr, "%s: %s: %s\n", argv[0], outfname,
strerror(errno));
exit(1);
}
/* ensure proper strace information and output to pipes */
setlinebuf(stdout);
setbuf(f, NULL);
/* now read control and data, forever */
while (1) {
zio_control_t ctrl;
/* This is a blocking read to the control file */
i = read(fd[FD_C], &ctrl, sizeof(ctrl));
switch(i) {
case -1:
fprintf(stderr, "%s: %s: read(): %s\n",
argv[0], argv[1 + FD_C], strerror(errno));
exit(1);
case 0:
fprintf(stderr, "%s: %s: unexpected EOF\n",
argv[0], argv[1 + FD_C], strerror(errno));
exit(1);
default:
fprintf(stderr, "%s: ctrl: read %i bytes (exp %i)\n",
argv[0], i, sizeof(ctrl));
/* continue anyways */
case sizeof(ctrl):
break; /* ok */
}
printf("Ctrl: n %i, size %i, bits %i, flags %08x\n",
ctrl.ctrl.nsamples,
ctrl.ctrl.ssize,
ctrl.ctrl.sbits,
ctrl.ctrl.flags);
printf("Ctrl: stamp %li.%09li (%lli)\n",
ctrl.ctrl.tstamp.tv_sec,
ctrl.ctrl.tstamp.tv_nsec,
ctrl.ctrl.tstamp_extra);
/* FIXME: some control information is missing */
i = read(fd[FD_D], buf, sizeof(buf));
if (i < 0) {
fprintf(stderr, "%s: %s: read(): %s\n",
argv[0], argv[1 + FD_D], strerror(errno));
continue; /* next ctrl, let's see... */
}
if (!i) {
fprintf(stderr, "%s: %s: unexpected EOF\n",
argv[0], argv[1 + FD_D], strerror(errno));
continue;
}
if (i != ctrl.ctrl.nsamples * ctrl.ctrl.ssize) {
if (i == sizeof(buf)) {
fprintf(stderr, "%s: buffer too small\n",
argv[0]);
/* FIXME: empty the data channel */
} else {
fprintf(stderr, "%s: ctrl: read %i bytes "
"(exp %i)\n", argv[0], i,
ctrl.ctrl.nsamples * ctrl.ctrl.ssize);
}
/* continue anyways */
}
fwrite(buf, 1, i, f);
/* report data to stdout */
for (j = 0; j < i; j++) {
if (!(j & 0xf))
printf("Data:");
printf(" %02x", buf[j]);
if ((j & 0xf) == 0xf || j == i - 1)
putchar('\n');
}
putchar('\n');
}
}
ZIO defines a "trigger" object type, and each cset is connected to
a trigger.
Each cset in a device can use a different trigger, which is specified
as an attribute of the cset. When the trigger fires, it acts on all
the non-disabled channels of the cset. Only the "app-request" trigger
can act on a single channel at a time.
Trigger Types
=============
Triggers may be device-specific or generic. A few generic triggers
are part of zio-core. The "app-request" trigger fires input when
the application calls read and fires output when the application calls
write (it acts on a single channel). The "ktimer" trigger uses a kernel
timer as trigger source. The "irq" trigger uses any interrupt (e.g.,
a GPIO interrupt, or pin 10 of the PC parallel port) as trigger event.
A device-specific trigger declares to be such within its attributes. A
device-specific trigger can only be used by csets that declare its name
as preferred trigger type. When such csets are registered, if the
trigger is already known to ZIO, it will be activated by default instead
of "app-request".
Trigger Operations
==================
(NOTE: this is still subject to fine-tuning, as we are still writing code)
Trigger operations are the following:
struct zio_trigger_instance *(*create)(struct zio_buffer *buffer,
struct zio_cset *cset);
void (*destroy)(struct zio_trigger_instance *t_instance);
Create and destroy a trigger instance for a cset. ZIO calls create when
attaching the trigger to a cset; it calls destroy when the trigger is
replaced by a different one or the cset is being unregistered from ZIO.
The instance structure is trigger-specific, but it must include this
generic structure:
struct zio_trigger_instance {
struct zio_trigger *trig;
struct zio_cset *cset;
};
Every time this structure is passed over, trigger code may use container_of
if it needs to access the private enclosing structure.
int (*config)(struct zio_trigger_instance *t_instance);
The method is called by ZIO whenever the attributes for a trigger
instance are modified by the user (by writing to sysfs or otherwise).
int (*enqueue_block)(struct zio_trigger_instance *t_instance,
struct zio_channel *ch,
struct zio_control *ctrl,
void *data, size_t len);
This is used for output channels: when a new data block is ready, it
must be sent to the trigger so it can be output when the event fires.
Buffer code, therefore, is expected to call this trigger method. The
function can return -EAGAIN if it has no space in the queue, or 0 on
success. If EAGAIN happens, the buffer should handle it (by storing
locally or notifying the user).
int (*space_in_buffer)(struct zio_trigger_instance *t_instance);
The method return either 0 (there is space for one block), a positive
value (the maximum size of the next block that can be enqueued) or
-EAGAIN. This can be used by the poll file operation, to ask
whether the next write(2) will succeed or not.
File Operations
===============
The trigger may include a non-NULL f_ops pointer. Most trigger will
not need it, but for example "app-request" does, because it needs to
look at individual read and write calls performed by applications.
ZIO will use these file operations (instead of the buffer file operations)
when the open method of the char device detects that the active trigger
declares a non-NULL f_ops field. These operations will most
likely fall back to buffer->f_ops for most of their actual work.
See zio-trig-app-request.c for details about how this is used.
When the trigger fires
======================
The trigger event may happen for a variety of reasons. It can be
time-driven, data-driven or whatever else. In any case, there is
a time when the trigger fires, so input or output may happen.
(With most hardware-specific triggers, the actual input or output of
data has already happened when the trigger interrupt runs, but this
doesn't change the software flow).
For output triggers, the trigger instance is already hosting the data
blocks (received through the enqueue_block method), so the code will
just loop over all the channels and free such data blocks. In some cases
the trigger will need to perform the output before freeing data, in
most cases data has been prepared for DMA during enqueue_block,
so output already happened when the trigger run.
ZIO offers this help macro to loop over all non-disabled channels:
cset_for_each(struct zio_cset *cset, struct zio_channel *ch)
The macro works like "task_for_each" or "list_for_each" in the kernel
headers.
For input triggers, the asynchronous code that runs the event will just
need to call
zio_fire_trigger(struct zio_trigger_instance *instance);
This function, part of zio-core, internally runs "cset_for_each".
For each non-disabled channel, it calls the drv->input_block method
and the stores it in the active buffer.
You can refer to "zio-trig-ktimer" for an example of a multi-instance
generic timer and to "zio-trig-app-request" for a non-conventional
implementation based on trigger-local file_operations.
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment