Skip to content

Latest commit

 

History

History
1300 lines (1013 loc) · 53.4 KB

File metadata and controls

1300 lines (1013 loc) · 53.4 KB

十、IIO 框架

工业 I/O ( IIO )是一个内核子系统,专门用于数模转换器 ( 模数转换器)和数模转换器 ( 数模转换器)。随着越来越多具有不同代码实现的传感器(具有模拟到数字或数字到模拟功能的测量设备)分散在内核资源中,收集它们变得很有必要。这就是 IIO 框架所做的,以一种通用和同质的方式。Jonathan Cameron 和 Linux-IIO 社区从 2009 年开始开发它。

加速度计、陀螺仪、电流/电压测量芯片、光传感器、压力传感器等都属于 IIO 系列器件。

IIO 模式基于设备和渠道架构:

  • 设备代表芯片本身。它是层次结构的顶层。
  • 通道代表设备的一条采集线。一个设备可以有一个或多个通道。例如,加速度计是具有三个通道的设备,每个轴(X、Y 和 Z)一个通道。

IIO 芯片是物理和硬件传感器/转换器。它作为字符设备(当支持触发缓冲时)和一个 sysfs 目录条目暴露给用户空间,该目录条目将包含一组文件,其中一些代表通道。单个通道由单个 sysfs 文件条目表示。

以下是从用户空间与 IIO 驱动交互的两种方式:

  • /sys/bus/iio/iio:deviceX/:代表传感器及其通道
  • /dev/iio:deviceX:这是一个导出设备事件和数据缓冲区的字符设备

IIO framework architecture and layout

上图显示了 IIO 框架是如何在内核和用户空间之间组织的。该驱动使用 IIO 核心公开的一组工具和应用编程接口来管理硬件和对 IIO 核心的报告处理。然后,IIO 子系统通过 sysfs 接口和字符设备将整个底层机制抽象到用户空间,用户可以在此基础上执行系统调用。

IIO API 分布在几个头文件中,如下所示:

#include <linux/iio/iio.h>    /* mandatory */ 
#include <linux/iio/sysfs.h>  /* mandatory since sysfs is used */ 
#include <linux/iio/events.h> /* For advanced users, to manage iio events */ 
#include <linux/iio/buffer.h> /* mandatory to use triggered buffers */ 
#include <linux/iio/trigger.h>/* Only if you implement trigger in your driver (rarely used)*/ 

在本章中,我们将描述和处理 IIO 框架的每个概念,例如

  • 遍历其数据结构(设备、通道等)
  • 触发缓冲区支持和连续捕获,以及它的 sysfs 接口
  • 探索现有的 IIO 触发器
  • 以一次性模式或连续模式捕获数据
  • 列出可以帮助开发人员测试设备的可用工具

IIO 数据结构

IIO 设备在内核中被表示为struct iio_dev的一个实例,并由struct iio_info结构描述。所有重要的 IIO 建筑都在include/linux/iio/iio.h中定义。

iio_dev 结构

该结构表示 IIO 设备,描述设备和驱动。它告诉我们:

  • 设备上有多少个可用频道?
  • 该设备可以在哪些模式下运行:单次触发缓冲?
  • 这个驱动有哪些钩子?
struct iio_dev { 
   [...] 
   int modes; 
   int currentmode; 
   struct device dev; 

   struct iio_buffer *buffer; 
   int scan_bytes; 

   const unsigned long *available_scan_masks; 
   const unsigned long *active_scan_mask; 
   bool scan_timestamp; 
   struct iio_trigger *trig; 
   struct iio_poll_func *pollfunc; 

   struct iio_chan_spec const *channels; 
   int num_channels; 
   const char *name; 
   const struct iio_info *info; 
   const struct iio_buffer_setup_ops *setup_ops; 
   struct cdev chrdev; 
}; 

完整的结构在 IIO 头文件中定义。我们不感兴趣的字段在此删除。

  • modes:代表设备支持的不同模式。支持的模式有:
    • INDIO_DIRECT_MODE表示设备提供 sysfs 类型的接口。
    • INDIO_BUFFER_TRIGGERED表示设备支持硬件触发。当您使用iio_triggered_buffer_setup()功能设置触发缓冲时,该模式会自动添加到您的设备中。
    • INDIO_BUFFER_HARDWARE显示设备有硬件缓冲区。
    • INDIO_ALL_BUFFER_MODES是以上两者的并集。
  • currentmode:表示设备实际使用的模式。
  • dev:表示 IIO 设备绑定到的结构设备(根据 Linux 设备模型)。
  • buffer:这是你的数据缓冲区,使用触发缓冲模式的时候推送到用户空间。当您使用iio_triggered_buffer_setup功能启用触发缓冲支持时,它会自动分配并关联到您的设备。
  • scan_bytes:这是捕获并输入到buffer的字节数。从用户空间使用触发缓冲区时,缓冲区至少要有indio->scan_bytes字节大。
  • available_scan_masks:这是一个可选的允许位掩码数组。使用触发缓冲器时,可以捕获通道并将其送入 IIO 缓冲器。如果您不想允许启用某些通道,您应该仅用允许的通道填充此数组。以下是为加速度计(具有 X、Y 和 Z 通道)提供扫描屏蔽的示例:
/* 
 * Bitmasks 0x7 (0b111) and 0 (0b000) are allowed. 
 * It means one can enable none or all of them. 
 * one can't for example enable only channel X and Y 
 */ 
static const unsigned long my_scan_masks[] = {0x7, 0}; 
indio_dev->available_scan_masks = my_scan_masks; 
  • active_scan_mask:这是使能通道的位掩码。只有那些通道的数据才应该被推入buffer。例如,对于 8 通道 ADC 转换器,如果仅使能第一(0)、第三(2)和最后(7)通道,位掩码将为 0b10000101 (0x85)。active_scan_mask将设置为 0x85。然后,驱动可以使用for_each_set_bit宏遍历每个设置的位,根据通道获取数据,并填充缓冲区。
  • scan_timestamp:这告诉我们是否将捕获时间戳推入缓冲区。如果为真,时间戳将作为缓冲区的最后一个元素被推送。时间戳是 8 字节(64 位)大。
  • trig:这是当前设备触发(支持缓冲模式时)。
  • pollfunc:这是正在接收的触发器上运行的功能。
  • channels:这代表了表通道规范结构,用来描述设备拥有的每一个通道。
  • num_channels:表示channels中指定的通道数。
  • name:表示设备名称。
  • info:来自驱动的回调和常量信息。
  • setup_ops:启用/禁用缓冲区前后要调用的一组回调函数。该结构在include/linux/iio/iio.h中定义如下:
struct iio_buffer_setup_ops { 
    int (* preenable) (struct iio_dev *); 
    int (* postenable) (struct iio_dev *); 
    int (* predisable) (struct iio_dev *); 
    int (* postdisable) (struct iio_dev *); 
    bool (* validate_scan_mask) (struct iio_dev *indio_dev, 
                                 const unsigned long *scan_mask); 
}; 
  • setup_ops:如果没有指定,IIO 核心使用drivers/iio/buffer/industrialio-triggered-buffer.c中定义的默认iio_triggered_buffer_setup_ops
  • chrdev:这是 IIO 核心创建的关联人物装置。

用于为 IIO 设备分配内存的功能是iio_device_alloc():

struct iio_dev *devm_iio_device_alloc(struct device *dev,  
                                      int sizeof_priv) 

dev是为其分配iio_dev的设备,sizeof_priv是用于为任何私有结构分配的内存空间。这样,传递每个设备(私有)的数据结构非常简单。如果分配失败,该功能返回NULL:

struct iio_dev *indio_dev; 
struct my_private_data *data; 
indio_dev = iio_device_alloc(sizeof(*data)); 
if (!indio_dev) 
    return -ENOMEM; 
/*data is given the address of reserved momory for private data */ 
data = iio_priv(indio_dev); 

分配 IIO 设备内存后,下一步是填充不同的字段。完成后,必须使用iio_device_register功能向 IIO 子系统注册设备:

int iio_device_register(struct iio_dev *indio_dev) 

该功能执行后,设备将准备好接受来自用户空间的请求。反向操作(通常在释放功能中完成)为iio_device_unregister():

void iio_device_unregister(struct iio_dev *indio_dev) 

一旦取消注册,iio_device_alloc分配的内存可以通过iio_device_free释放:

void iio_device_free(struct iio_dev *iio_dev) 

给定一个 IIO 设备作为参数,可以通过以下方式检索私有数据:

struct my_private_data *the_data = iio_priv(indio_dev); 

iio_info 结构

struct iio_info结构用于声明 IIO 核使用的钩子,以便读取/写入通道/属性值:

struct iio_info { 
   struct module *driver_module; 
   const struct attribute_group *attrs; 

   int (*read_raw)(struct iio_dev *indio_dev, 
               struct iio_chan_spec const *chan, 
               int *val, int *val2, long mask); 

   int (*write_raw)(struct iio_dev *indio_dev, 
                struct iio_chan_spec const *chan, 
                int val, int val2, long mask); 
    [...] 
}; 

我们不感兴趣的字段已被删除。

  • driver_module:这是用来保证chrdevs正确归属的模块结构,通常设置为THIS_MODULE
  • attrs:代表设备属性。
  • read_raw:这是用户读取设备sysfs文件属性时的回调运行。mask参数是一个位掩码,允许我们知道请求哪种类型的值。channel参数让我们知道相关的频道。它可以用于采样频率、用于将原始值转换为可用值的比例,或者原始值本身。
  • write_raw:这是用于向设备写入值的回调。例如,可以使用它来设置采样频率。

下面的代码展示了如何设置struct iio_info结构:

static const struct iio_info iio_dummy_info = { 
    .driver_module = THIS_MODULE, 
    .read_raw = &iio_dummy_read_raw, 
    .write_raw = &iio_dummy_write_raw, 
[...] 

/* 
 * Provide device type specific interface functions and 
 * constant data. 
 */ 
indio_dev->info = &iio_dummy_info; 

IIO 频道

一个通道代表一条采集线。例如,加速度计将有 3 个通道(X、Y、Z),因为每个轴代表一条采集线。struct iio_chan_spec是表示和描述内核中单个通道的结构:

    struct iio_chan_spec { 
        enum iio_chan_type type; 
        int channel; 
        int channel2; 
        unsigned long address; 
        int scan_index; 
        struct { 
            charsign; 
            u8 realbits; 
            u8 storagebits; 
            u8 shift; 
            u8 repeat; 
            enum iio_endian endianness; 
        } scan_type; 
        long info_mask_separate; 
        long info_mask_shared_by_type; 
        long info_mask_shared_by_dir; 
        long info_mask_shared_by_all; 
        const struct iio_event_spec *event_spec; 
        unsigned int num_event_specs; 
        const struct iio_chan_spec_ext_info *ext_info; 
        const char *extend_name; 
        const char *datasheet_name; 
        unsigned modified:1; 
        unsigned indexed:1; 
        unsigned output:1; 
        unsigned differential:1; 
    }; 

以下是结构中每个柠檬的含义:

  • type:指定通道进行哪种类型的测量。如果是电压测量,应该是IIO_VOLTAGE。对于光传感器,它是IIO_LIGHT。对于加速度计,使用IIO_ACCEL。所有可用类型在include/uapi/linux/iio/types.h中定义为enum iio_chan_type。要为给定的转换器编写驱动,请查看该文件,查看每个通道所属的类型。
  • channel:指定.indexed设为 1 时的频道索引。
  • channel2:当.modified设置为 1 时,指定通道修改器。
  • modified:指定是否将修改器应用于该通道属性名称。在这种情况下,修改器设置在.channel2中。(例如,IIO_MOD_XIIO_MOD_YIIO_MOD_Z是关于 xyz 轴的轴向传感器的修饰符)。可用修改器列表在内核 IIO 头中定义为enum iio_modifier。修改器只会破坏sysfs中的通道属性名称,而不是值。
  • indexed:指定频道属性名是否有索引。如果是,则在.channel字段中指定索引。
  • scan_indexscan_type:当使用缓冲区触发器时,这些字段用于识别缓冲区中的元素。scan_index设置缓冲区内捕获通道的位置。scan_index较低的通道将被置于较高索引的通道之前。将.scan_index设置为-1将防止通道缓冲捕获(在scan_elements目录中没有条目)。

暴露给用户空间的通道 sysfs 属性以位掩码的形式指定。根据共享信息,属性可以设置为以下掩码之一:

  • info_mask_separate将属性标记为特定于该通道。
  • info_mask_shared_by_type将该属性标记为由同一类型的所有通道共享。导出的信息由同一类型的所有通道共享。
  • info_mask_shared_by_dir将属性标记为由同一方向的所有通道共享。导出的信息由同一方向的所有通道共享。
  • info_mask_shared_by_all将属性标记为由所有通道共享,无论它们的类型或方向如何。导出的信息由所有渠道共享。枚举这些属性的位掩码都在include/linux/iio/iio.h : 中定义
enum iio_chan_info_enum { 
    IIO_CHAN_INFO_RAW = 0, 
    IIO_CHAN_INFO_PROCESSED, 
    IIO_CHAN_INFO_SCALE, 
    IIO_CHAN_INFO_OFFSET, 
    IIO_CHAN_INFO_CALIBSCALE, 
    [...] 
    IIO_CHAN_INFO_SAMP_FREQ, 
    IIO_CHAN_INFO_FREQUENCY, 
    IIO_CHAN_INFO_PHASE, 
    IIO_CHAN_INFO_HARDWAREGAIN, 
    IIO_CHAN_INFO_HYSTERESIS, 
    [...] 
}; 

字符顺序字段应该是以下之一:

enum iio_endian { 
    IIO_CPU, 
    IIO_BE, 
    IIO_LE, 
}; 

通道属性命名约定

该属性的名称由 IIO 核心按照以下模式自动生成:{direction}_{type}_{index}_{modifier}_{info_mask}:

  • direction对应属性方向,根据drivers/iio/industrialio-core.c中的struct iio_direction结构:
static const char * const iio_direction[] = { 
   [0] = "in", 
   [1] = "out", 
}; 
  • type对应通道类型,根据 char 数组const iio_chan_type_name_spec:
static const char * const iio_chan_type_name_spec[] = { 
   [IIO_VOLTAGE] = "voltage", 
   [IIO_CURRENT] = "current", 
   [IIO_POWER] = "power", 
   [IIO_ACCEL] = "accel", 
   [...] 
   [IIO_UVINDEX] = "uvindex", 
   [IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity", 
   [IIO_COUNT] = "count", 
   [IIO_INDEX] = "index", 
   [IIO_GRAVITY]  = "gravity", 
}; 
  • index模式取决于通道.indexed字段是否被设置。如果设置,将从.channel字段获取索引,以替换{index}模式。
  • modifier模式取决于通道.modified字段是否被设置。如果设置,修改器将从.channel2字段获取,并且{modifier}模式将根据 char 数组struct iio_modifier_names结构进行替换:
static const char * const iio_modifier_names[] = { 
   [IIO_MOD_X] = "x", 
   [IIO_MOD_Y] = "y", 
   [IIO_MOD_Z] = "z", 
   [IIO_MOD_X_AND_Y] = "x&y", 
   [IIO_MOD_X_AND_Z] = "x&z", 
   [IIO_MOD_Y_AND_Z] = "y&z", 
   [...] 
   [IIO_MOD_CO2] = "co2", 
   [IIO_MOD_VOC] = "voc", 
}; 
  • info_mask取决于通道信息掩码,私有或共享,字符数组中的索引值iio_chan_info_postfix:
/* relies on pairs of these shared then separate */ 
static const char * const iio_chan_info_postfix[] = { 
   [IIO_CHAN_INFO_RAW] = "raw", 
   [IIO_CHAN_INFO_PROCESSED] = "input", 
   [IIO_CHAN_INFO_SCALE] = "scale", 
   [IIO_CHAN_INFO_CALIBBIAS] = "calibbias", 
   [...] 
   [IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency", 
   [IIO_CHAN_INFO_FREQUENCY] = "frequency", 
   [...] 
}; 

区分频道

当每个通道类型有多个数据通道时,您可能会发现自己有麻烦。难题在于:如何识别它们。对此有两种解决方案:索引和修饰符。

使用索引:给定一个具有一条通道线的模数转换器,不需要索引。它的通道定义是:

static const struct iio_chan_spec adc_channels[] = { 
        { 
                .type = IIO_VOLTAGE, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
        }, 
} 

由前面描述的通道产生的属性名将是in_voltage_raw

/sys/bus/iio/iio:deviceX/in_voltage_raw

现在假设转换器有 4 个甚至 8 个通道。我们如何识别它们?解决办法是使用索引。将.indexed字段设置为 1 将会损坏通道属性名称,用.channel值替换{index}模式:

static const struct iio_chan_spec adc_channels[] = { 
        { 
                .type = IIO_VOLTAGE, 
                .indexed = 1, 
                .channel = 0, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
        }, 
        { 
                .type = IIO_VOLTAGE, 
                .indexed = 1, 
                .channel = 1, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
        }, 
        { 
                .type = IIO_VOLTAGE, 
                .indexed = 1, 
                .channel = 2, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
        }, 
        { 
                .type = IIO_VOLTAGE, 
                .indexed = 1, 
                .channel = 3, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
        }, 
} 

产生的通道属性是:

/sys/bus/iio/iio:deviceX/in_voltage0_raw /sys/bus/iio/iio:deviceX/in_voltage1_raw /sys/bus/iio/iio:deviceX/in_voltage2_raw /sys/bus/iio/iio:deviceX/in_voltage3_raw

使用修改器:给定一个有两个通道的光传感器——一个用于红外光,一个用于红外光和可见光,没有索引或修改器,属性名将是in_intensity_raw。在这里使用索引很容易出错,因为拥有in_intensity0_ir_rawin_intensity1_ir_raw是没有意义的。使用修饰符将有助于提供有意义的属性名。该频道的定义可能如下所示:

static const struct iio_chan_spec mylight_channels[] = { 
        { 
                .type = IIO_INTENSITY, 
                .modified = 1, 
                .channel2 = IIO_MOD_LIGHT_IR, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
                .info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ), 
        }, 
        { 
                .type = IIO_INTENSITY, 
                .modified = 1, 
                .channel2 = IIO_MOD_LIGHT_BOTH, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 
                .info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ), 
        }, 
        { 
                .type = IIO_LIGHT, 
                .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 
                .info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ), 
        }, 
} 

结果属性将是:

  • /sys/bus/iio/iio:deviceX/in_intensity_ir_raw用于测量红外强度的通道
  • /sys/bus/iio/iio:deviceX/in_intensity_both_raw用于测量红外线和可见光的通道
  • /sys/bus/iio/iio:deviceX/in_illuminance_input为处理后的数据
  • /sys/bus/iio/iio:deviceX/sampling_frequency为采样频率,由所有人共享

加速度计也是如此,我们将在案例研究中进一步了解。现在,让我们总结一下到目前为止我们在虚拟 IIO 车手中讨论的内容。

把它们放在一起

让我们总结一下到目前为止在一个简单的虚拟驱动中看到的内容,它将暴露四个电压通道。我们将忽略read()write()功能:

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/platform_device.h> 
#include <linux/interrupt.h> 
#include <linux/of.h> 
#include <linux/iio/iio.h> 
#include <linux/iio/sysfs.h> 
#include <linux/iio/events.h> 
#include <linux/iio/buffer.h> 

#define FAKE_VOLTAGE_CHANNEL(num)                  \ 
   {                                               \ 
         .type = IIO_VOLTAGE,                      \ 
         .indexed = 1,                             \ 
         .channel = (num),                         \ 
         .address = (num),                         \ 
         .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),   \ 
         .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ 
   } 

struct my_private_data { 
    int foo; 
    int bar; 
    struct mutex lock; 
}; 

static int fake_read_raw(struct iio_dev *indio_dev, 
                   struct iio_chan_spec const *channel, int *val, 
                   int *val2, long mask) 
{ 
    return 0; 
} 

static int fake_write_raw(struct iio_dev *indio_dev, 
                   struct iio_chan_spec const *chan, 
                   int val, int val2, long mask) 
{ 
    return 0; 
} 

static const struct iio_chan_spec fake_channels[] = { 
   FAKE_VOLTAGE_CHANNEL(0), 
   FAKE_VOLTAGE_CHANNEL(1), 
   FAKE_VOLTAGE_CHANNEL(2), 
   FAKE_VOLTAGE_CHANNEL(3), 
}; 

static const struct of_device_id iio_dummy_ids[] = { 
    { .compatible = "packt,iio-dummy-random", }, 
    { /* sentinel */ } 
}; 

static const struct iio_info fake_iio_info = { 
   .read_raw = fake_read_raw, 
   .write_raw        = fake_write_raw, 
   .driver_module = THIS_MODULE, 
}; 

static int my_pdrv_probe (struct platform_device *pdev) 
{ 
    struct iio_dev *indio_dev; 
    struct my_private_data *data; 

   indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data)); 
   if (!indio_dev) { 
         dev_err(&pdev->dev, "iio allocation failed!\n"); 
         return -ENOMEM; 
   } 

   data = iio_priv(indio_dev); 
   mutex_init(&data->lock); 
   indio_dev->dev.parent = &pdev->dev; 
   indio_dev->info = &fake_iio_info; 
   indio_dev->name = KBUILD_MODNAME; 
   indio_dev->modes = INDIO_DIRECT_MODE; 
   indio_dev->channels = fake_channels; 
   indio_dev->num_channels = ARRAY_SIZE(fake_channels); 
   indio_dev->available_scan_masks = 0xF; 

    iio_device_register(indio_dev); 
    platform_set_drvdata(pdev, indio_dev); 
    return 0; 
} 

static void my_pdrv_remove(struct platform_device *pdev) 
{ 
    struct iio_dev *indio_dev = platform_get_drvdata(pdev); 
    iio_device_unregister(indio_dev); 
} 

static struct platform_driver mypdrv = { 
    .probe      = my_pdrv_probe, 
    .remove     = my_pdrv_remove, 
    .driver     = { 
        .name     = "iio-dummy-random", 
        .of_match_table = of_match_ptr(iio_dummy_ids),   
        .owner    = THIS_MODULE, 
    }, 
}; 
module_platform_driver(mypdrv); 
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>"); 
MODULE_LICENSE("GPL"); 

加载上述模块后,我们将获得以下输出,显示我们的设备确实对应于我们注册的平台设备:

~# ls -l /sys/bus/iio/devices/
lrwxrwxrwx 1 root root 0 Jul 31 20:26 iio:device0 -> ../../../devices/platform/iio-dummy-random.0/iio:device0
lrwxrwxrwx 1 root root 0 Jul 31 20:23 iio_sysfs_trigger -> ../../../devices/iio_sysfs_trigger

下面的列表显示了该设备拥有的频道及其名称,它们与我们在驱动中描述的完全一致:

~# ls /sys/bus/iio/devices/iio\:device0/
dev in_voltage2_raw name uevent
in_voltage0_raw in_voltage3_raw power
in_voltage1_raw in_voltage_scale subsystem
~# cat /sys/bus/iio/devices/iio:device0/name
iio_dummy_random

触发缓冲支持

在许多数据分析应用中,能够基于一些外部信号(触发器)捕获数据是非常有用的。这些触发器可能是:

  • 数据就绪信号
  • 连接到某个外部系统(GPIO 或其他系统)的 IRQ 线路
  • 处理器内周期性中断
  • 在 sysfs 中读取/写入特定文件的用户空间

IIO 设备驱动与触发器完全无关。触发器可以初始化一个或多个设备上的数据捕获。这些触发器用于填充缓冲区,作为字符设备暴露给用户空间。

一个人可以开发自己的触发器驱动,但这超出了本书的范围。我们将尽量只关注现有的。这些是:

  • iio-trig-interrupt:这为使用任何 IRQ 作为 IIO 触发器提供了支持。在旧的内核版本中,它曾经是iio-trig-gpio。启用该触发模式的核心选项是CONFIG_IIO_INTERRUPT_TRIGGER。如果构建为模块,该模块将被称为iio-trig-interrupt
  • iio-trig-hrtimer:这提供了使用 HRT 作为中断源的基于频率的 IIO 触发器(从内核 4.5 开始)。在旧的内核版本中,它曾经是iio-trig-rtc。负责这个触发模式的内核选项是IIO_HRTIMER_TRIGGER。如果构建为模块,该模块将被称为iio-trig-hrtimer
  • iio-trig-sysfs:这允许我们使用 sysfs 条目来触发数据捕获。CONFIG_IIO_SYSFS_TRIGGER是增加该触发模式支持的内核选项。
  • iio-trig-bfin-timer:这允许我们使用一个黑鳍定时器作为 IIO 触发器(还在准备中)。

IIO 公开了应用编程接口,这样我们就可以:

  • 声明任意给定数量的触发器
  • 选择将数据推入缓冲区的通道

当您的 IIO 设备提供触发缓冲区的支持时,您必须设置iio_dev.pollfunc,该设置在触发器触发时执行。该处理程序有责任通过indio_dev->active_scan_mask找到启用的通道,检索它们的数据,并使用iio_push_to_buffers_with_timestamp功能将其输入到indio_dev->buffer中。因此,缓冲区和触发器在 IIO 子系统中紧密相连。

IIO 内核提供了一组帮助函数来设置触发的缓冲区,可以在drivers/iio/industrialio-triggered-buffer.c中找到。

以下是从驱动中支持触发缓冲区的步骤:

  1. 如果需要,填充iio_buffer_setup_ops结构:
const struct iio_buffer_setup_ops sensor_buffer_setup_ops = { 
  .preenable    = my_sensor_buffer_preenable, 
  .postenable   = my_sensor_buffer_postenable, 
  .postdisable  = my_sensor_buffer_postdisable, 
  .predisable   = my_sensor_buffer_predisable, 
}; 
  1. 写下与触发器关联的上半部分。在 99%的情况下,只需输入与捕获相关的时间戳:
irqreturn_t sensor_iio_pollfunc(int irq, void *p) 
{ 
    pf->timestamp = iio_get_time_ns((struct indio_dev *)p); 
    return IRQ_WAKE_THREAD; 
} 
  1. 写触发器下半部分,它将从每个使能的通道获取数据,并将它们送入缓冲器:
irqreturn_t sensor_trigger_handler(int irq, void *p) 
{ 
    u16 buf[8]; 
    int bit, i = 0; 
    struct iio_poll_func *pf = p; 
    struct iio_dev *indio_dev = pf->indio_dev; 

    /* one can use lock here to protect the buffer */ 
    /* mutex_lock(&my_mutex); */ 

    /* read data for each active channel */ 
    for_each_set_bit(bit, indio_dev->active_scan_mask, 
                     indio_dev->masklength) 
        buf[i++] = sensor_get_data(bit) 

    /* 
     * If iio_dev.scan_timestamp = true, the capture timestamp 
     * will be pushed and stored too, as the last element in the 
     * sample data buffer before pushing it to the device buffers. 
     */ 
    iio_push_to_buffers_with_timestamp(indio_dev, buf, timestamp); 

    /* Please unlock any lock */ 
    /* mutex_unlock(&my_mutex); */ 

    /* Notify trigger */ 
    iio_trigger_notify_done(indio_dev->trig); 
    return IRQ_HANDLED; 
} 
  1. 最后,在probe功能中,在向iio_device_register()注册设备之前,必须自行设置缓冲器:
iio_triggered_buffer_setup(indio_dev, sensor_iio_polfunc, 
                           sensor_trigger_handler, 
                           sensor_buffer_setup_ops); 

这里的神奇功能是iio_triggered_buffer_setup。这也将为您的设备提供INDIO_DIRECT_MODE功能。当(从用户空间)向您的设备发出触发器时,您无法知道捕获将在何时触发。

当连续缓冲捕获处于活动状态时,应该防止(通过返回错误)驱动执行 sysfs 每通道数据捕获(由read_raw()钩子执行),以避免不确定的行为,因为触发器处理程序和read_raw()钩子将尝试同时访问设备。用于检查缓冲模式是否实际使用的功能是iio_buffer_enabled()。钩子看起来像这样:

static int my_read_raw(struct iio_dev *indio_dev, 
                     const struct iio_chan_spec *chan, 
                     int *val, int *val2, long mask) 
{ 
      [...] 
      switch (mask) { 
      case IIO_CHAN_INFO_RAW: 
            if (iio_buffer_enabled(indio_dev)) 
                  return -EBUSY; 
      [...]        
}  

iio_buffer_enabled()功能只是测试给定 IIO 设备的缓冲器是否启用。

让我们描述一下前一节中使用的一些重要内容:

  • iio_buffer_setup_ops提供在缓冲配置序列的固定步骤(启用/禁用之前/之后)调用的缓冲设置功能。如果没有指定,默认的iio_triggered_buffer_setup_ops将由 IIO 核心提供给你的设备。
  • sensor_iio_pollfunc是扳机的上半部分。与每个上半部分一样,它在中断上下文中运行,并且必须尽可能少地进行处理。在 99%的情况下,您只需输入与捕获相关的时间戳。同样,可以使用默认的 IIO iio_pollfunc_store_time功能。
  • sensor_trigger_handler是下半部分,它运行在一个内核线程中,允许我们做任何处理,甚至包括获取互斥或睡眠。重加工应该在这里进行。它通常从设备中读取数据,将其与记录在上半部分的时间戳一起存储在内部缓冲区中,并将其推送到您的 IIO 设备缓冲区。

A trigger is mandatory for triggered buffering. It tells the driver when to read the sample from the device and put it into the buffer. Triggered buffering is not mandatory to write IIO device drivers. One can use single shot capture through sysfs too, by reading raw attributesof the channel, which will only perform a single conversion (for the channel attribute being read). Buffer mode allows continuous conversions, thus capturing more than one channel in a single shot.

IIO 触发器和 sysfs(用户空间)

sysfs 中有两个位置与触发器相关:

  • /sys/bus/iio/devices/triggerY/一旦 IIO 触发器在 IIO 内核中注册,就会创建该触发器,并与索引为Y的触发器相对应。目录中至少有一个属性:
    • name这是一个触发器名称,以后可以用来与设备关联
  • /sys/bus/iio/devices/iio:deviceX/trigger/*如果您的设备支持触发缓冲区,将自动创建目录。通过在current_trigger文件中写入触发器的名称,可以将触发器与我们的设备相关联。

Sysfs 触发接口

sysfs 触发器通过CONFIG_IIO_SYSFS_TRIGGER=y配置选项在内核中启用,这将导致/sys/bus/iio/devices/iio_sysfs_trigger/文件夹被自动创建,并可用于 sysfs 触发器管理。目录中会有两个文件,add_triggerremove_trigger。它的司机在drivers/iio/trigger/iio-trig-sysfs.c

添加触发器文件

这用于创建新的 sysfs 触发器。您可以通过在该文件中写入正值(将用作触发器标识)来创建新的触发器。它将创建新的 sysfs 触发器,可在/sys/bus/iio/devices/triggerX访问,其中X是触发器编号。

例如:

 # echo 2 > add_trigger 

这将创建一个新的 sysfs 触发器,可在/sys/bus/iio/devices/trigger2访问。如果系统中已经存在具有指定标识的触发器,将返回无效的参数消息。sysfs 触发器名称模式是sysfstrig{ID}。命令echo 2 > add_trigger将创建名为sysfstrig2的触发器/sys/bus/iio/devices/trigger2:

 $ cat /sys/bus/iio/devices/trigger2/name
 sysfstrig2  

每个 sysfs 触发器至少包含一个文件:trigger_now。将1写入该文件将指示所有在其current_trigger中具有相应触发名称的设备开始捕获,并将数据推入各自的缓冲区。每个设备缓冲区必须设置其大小,并且必须启用(echo 1 > /sys/bus/iio/devices/iio:deviceX/buffer/enable)。

移除触发器文件

要删除触发器,请使用以下命令:

 # echo 2 > remove_trigger  

用扳机系住装置

将设备与给定的触发器相关联包括将触发器的名称写入设备触发器目录下的current_trigger文件。例如,假设我们需要将一个设备与索引为 2 的触发器联系起来:

# set trigger2 as current trigger for device0
# echo sysfstrig2 >    /sys/bus/iio/devices/iio:device0/trigger/current_trigger 

要从设备中分离触发器,应该向设备触发器目录的current_trigger文件中写入一个空字符串,如下所示:

# echo "" > iio:device0/trigger/current_trigger  

我们将在本章中看到一个处理 sysfs 数据捕获触发器的实际例子。

中断触发接口

考虑以下示例:

static struct resource iio_irq_trigger_resources[] = { 
    [0] = { 
        .start = IRQ_NR_FOR_YOUR_IRQ, 
        .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_LOWEDGE, 
    }, 
}; 

static struct platform_device iio_irq_trigger = { 
    .name = "iio_interrupt_trigger", 
    .num_resources = ARRAY_SIZE(iio_irq_trigger_resources), 
    .resource = iio_irq_trigger_resources, 
}; 
platform_device_register(&iio_irq_trigger); 

声明我们的 IRQ 触发器,它将导致 IRQ 触发器独立模块被加载。如果其probe功能成功,就会有一个与触发器对应的目录。IRQ 触发器名称的形式为irqtrigX,其中X对应于您刚刚通过的虚拟 IRQ,您将在/proc/interrupt中看到:

 $ cd /sys/bus/iio/devices/trigger0/
 $ cat name

irqtrig85:就像我们对其他触发器所做的一样,您只需要将该触发器的名称写入您的设备current_trigger文件,将其分配给您的设备。

# echo "irqtrig85" > /sys/bus/iio/devices/iio:device0/trigger/current_trigger

现在,每次触发中断时,都会捕获设备数据。

The IRQ trigger driver does not support DT yet, which is the reason why we used our board init file. But it does not matter; since the driver requires a resource, we can use DT without any code change.

以下是设备树节点声明 IRQ 触发接口的示例:

mylabel: my_trigger@0{ 
    compatible = "iio_interrupt_trigger"; 
    interrupt-parent = <&gpio4>; 
    interrupts = <30 0x0>; 
}; 

该示例假设 IRQ 线是属于 GPIO 控制器节点gpio4的 GPIO#30。这包括使用一个 GPIO 作为中断源,以便每当 GPIO 改变到给定状态时,中断被引发,从而触发捕获。

HR 定时器触发接口

hrtimer触发器依赖于配置文件系统(参见内核源代码中的Documentation/iio/iio _ configfs . txt,可通过CONFIG_IIO_CONFIGFS配置选项启用,并安装在我们的系统上(通常在/config目录下):

 # mkdir /config
  # mount -t configfs none /config

现在,加载模块iio-trig-hrtimer将创建可在/config/iio下访问的 IIO 组,允许用户在/config/iio/triggers/hrtimer下创建 hrtimer 触发器。

例如:

 # create a hrtimer trigger
  $ mkdir /config/iio/triggers/hrtimer/my_trigger_name
  # remove the trigger
  $ rmdir /config/iio/triggers/hrtimer/my_trigger_name 

每个 hrtimer 触发器在触发器目录中包含一个单独的sampling_frequency属性。在章节中使用 hrtimer 触发器的数据捕获中提供了一个完整的工作示例。

IIO 缓冲区

IIO 缓冲器提供连续数据采集,一次可以读取多个数据通道。可通过/dev/iio:device字符设备节点从用户空间访问缓冲区。在触发处理器中,用于填充缓冲区的函数是iio_push_to_buffers_with_timestamp。负责为您的设备分配触发缓冲区的功能是iio_triggered_buffer_setup()

IIO 缓冲 sysfs 接口

IIO 缓冲区在/sys/bus/iio/iio:deviceX/buffer/*下有一个关联的属性目录。以下是一些现有属性:

  • length:缓冲区可以存储的数据样本总数(容量)。这是缓冲区包含的扫描次数。
  • enable:这激活缓冲捕获,开始缓冲捕获。
  • watermark:这个属性从内核版本 4.2 开始就有了。它是一个正数,指定了阻塞读取应该等待多少个扫描元素。例如使用poll的话,会一直阻塞,直到达到水印为止。只有当水印大于请求的读取量时,它才有意义。它不影响非阻塞读取。用户可以通过超时阻止轮询,并在超时后读取可用样本,从而获得最大延迟保证。

IIO 缓冲设置

要读取数据并将其推入缓冲区的通道称为扫描元件。它们的配置可从用户空间通过/sys/bus/iio/iio:deviceX/scan_elements/*目录访问,包含以下属性:

  • en(实际上是属性名称的后缀),用于启用通道。如果且仅当其属性为非零,则触发的捕获将包含此通道的数据样本。比如in_voltage0_enin_voltage1_en等等。
  • type描述缓冲区内的扫描元素数据存储,以及从用户空间读取的形式。比如in_voltage0_type。格式是[be|le]:[s|u]bits/storagebitsXrepeat[>>shift]
    • bele指定字符顺序(大或小)。
    • su指定符号,有符号(2 的补码)或无符号。
    • bits是有效数据位数。
    • storagebits是该通道在缓冲区中占用的位数。也就是说,一个值可能实际上是用 12 位()编码的,但是在缓冲区中占用 16 位(存储位)。因此,必须向右移动数据四次才能获得实际值。该参数取决于器件,应参考其数据手册。
    • shift表示在屏蔽掉未使用的位之前,应该移位数据值的次数。这个参数并不总是需要的。如果有效位数()等于存储位数,则移位为 0。也可以在器件数据手册中找到该参数。
    • repeat指定位/存储位重复的次数。当 repeat 元素为 0 或 1 时,则省略 repeat 值。

解释这一部分最好的方法是摘录内核文档,可以在这里找到:https://www . kernel . org/doc/html/latest/driver-API/iio/buffers . html。例如,数据存储在两个 8 位寄存器中的 12 位分辨率 3 轴加速度计驱动如下:

      7   6   5   4   3   2   1   0 
    +---+---+---+---+---+---+---+---+ 
    |D3 |D2 |D1 |D0 | X | X | X | X | (LOW byte, address 0x06) 
    +---+---+---+---+---+---+---+---+ 
      7   6   5   4   3   2   1   0 
    +---+---+---+---+---+---+---+---+ 
    |D11|D10|D9 |D8 |D7 |D6 |D5 |D4 | (HIGH byte, address 0x07) 
    +---+---+---+---+---+---+---+---+ 

每个轴将具有以下扫描元素类型:

 $ cat /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_y_type
 le:s12/16>>4

人们应该将其理解为 16 位大小的小端符号数据,在屏蔽掉 12 位有效数据之前,需要将其右移 4 位。

struct iio_chan_spec中负责确定通道值应如何存储到缓冲区的元素是scant_type

struct iio_chan_spec { 
        [...] 
        struct { 
            char sign; /* Should be 'u' or 's' as explained above */ 
            u8 realbits; 
            u8 storagebits; 
            u8 shift; 
            u8 repeat; 
            enum iio_endian endianness; 
        } scan_type; 
        [...] 
}; 

这个结构绝对匹配[be|le]:[s|u]bits/storagebitsXrepeat[>>shift],这是上一节描述的模式。让我们看看这个结构的每个成员:

  • sign代表数据的符号,与模式中的[s|u]匹配
  • realbits对应于图案中的bits
  • storagebits匹配模式中的同名
  • shift对应于模式中的移动,与repeat相同
  • iio_indian代表字符顺序,与模式中的[be|le]匹配

在这一点上,人们能够写出对应于前面解释的类型的 IIO 信道结构:

struct struct iio_chan_spec accel_channels[] = { 
        { 
                .type = IIO_ACCEL, 
                .modified = 1, 
                .channel2 = IIO_MOD_X, 
                /* other stuff here */ 
                .scan_index = 0, 
                .scan_type = { 
                        .sign = 's', 
                        .realbits = 12, 
                        .storagebits = 16, 
                        .shift = 4, 
                        .endianness = IIO_LE, 
                }, 
        } 
      /* similar for Y (with channel2 = IIO_MOD_Y, scan_index = 1) 
       * and Z (with channel2 = IIO_MOD_Z, scan_index = 2) axis 
       */ 
} 

把它们放在一起

让我们仔细看看来自 BOSH 的数字三轴加速度传感器 BMA220。这是一款 SPI/I2C 兼容器件,具有 8 位大小的寄存器,以及一个片内运动触发中断控制器,该控制器实际上可以检测倾斜、运动和冲击振动。其数据表可在:http://www.mouser.fr/pdfdocs/BSTBMA220DS00308.PDF获得,其驱动从内核 4.8 ( CONFIG_BMA200)开始引入。让我们走一遍:

首先,我们使用struct iio_chan_spec申报我们的 IIO 频道。一旦触发的缓冲区被使用,那么我们需要填充.scan_index.scan_type字段:

#define BMA220_DATA_SHIFT 2 
#define BMA220_DEVICE_NAME "bma220" 
#define BMA220_SCALE_AVAILABLE "0.623 1.248 2.491 4.983" 

#define BMA220_ACCEL_CHANNEL(index, reg, axis) {           \ 
   .type = IIO_ACCEL,                                      \ 
   .address = reg,                                         \ 
   .modified = 1,                                          \ 
   .channel2 = IIO_MOD_##axis,                             \ 
   .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),           \ 
   .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),   \ 
   .scan_index = index,                                    \ 
   .scan_type = {                                          \ 
         .sign = 's',                                      \ 
         .realbits = 6,                                    \ 
         .storagebits = 8,                                 \ 
         .shift = BMA220_DATA_SHIFT,                       \ 
         .endianness = IIO_CPU,                            \ 
   },                                                      \ 
} 

static const struct iio_chan_spec bma220_channels[] = { 
   BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X), 
   BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y), 
   BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z), 
}; 

.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)表示每个通道将有一个*_raw sysfs 条目(属性),而.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE)表示同一类型的所有通道只有一个*_scale sysfs 条目:

    jma@jma:~$ ls -l /sys/bus/iio/devices/iio:device0/
(...)
# without modifier, a channel name would have in_accel_raw (bad)
-rw-r--r-- 1 root root 4096 jul 20 14:13 in_accel_scale
-rw-r--r-- 1 root root 4096 jul 20 14:13 in_accel_x_raw
-rw-r--r-- 1 root root 4096 jul 20 14:13 in_accel_y_raw
-rw-r--r-- 1 root root 4096 jul 20 14:13 in_accel_z_raw
(...)

读取in_accel_scale将口罩设置为IIO_CHAN_INFO_SCALEread_raw()挂钩调用。读取in_accel_x_raw调用read_raw()钩子,并将面具设置为IIO_CHAN_INFO_RAW。因此真正的价值是raw_value * scale

.scan_type说的是每个通道返回的值是,8 位大小(将占用缓冲区中的 8 位),但有用的有效载荷只占用 6 位,数据必须右移 2 次才能屏蔽掉未使用的位。任何扫描元素类型都如下所示:

$ cat /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_x_type
le:s6/8>>2

下面是我们的pollfunc(实际上是下半部分),它从设备中读取样本,并将读取的值推入缓冲区(iio_push_to_buffers_with_timestamp())。一旦完成,我们通知核心(iio_trigger_notify_done()):

static irqreturn_t bma220_trigger_handler(int irq, void *p) 
{ 
   int ret; 
   struct iio_poll_func *pf = p; 
   struct iio_dev *indio_dev = pf->indio_dev; 
   struct bma220_data *data = iio_priv(indio_dev); 
   struct spi_device *spi = data->spi_device; 

   mutex_lock(&data->lock); 
   data->tx_buf[0] = BMA220_REG_ACCEL_X | BMA220_READ_MASK; 
   ret = spi_write_then_read(spi, data->tx_buf, 1, data->buffer, 
                       ARRAY_SIZE(bma220_channels) - 1); 
   if (ret < 0) 
         goto err; 

   iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, 
                              pf->timestamp); 
err: 
   mutex_unlock(&data->lock); 
   iio_trigger_notify_done(indio_dev->trig); 

   return IRQ_HANDLED; 
} 

以下是read功能。它是一个钩子,每次读取设备的 sysfs 条目时都会调用:

static int bma220_read_raw(struct iio_dev *indio_dev, 
                  struct iio_chan_spec const *chan, 
                  int *val, int *val2, long mask) 
{ 
   int ret; 
   u8 range_idx; 
   struct bma220_data *data = iio_priv(indio_dev); 

   switch (mask) { 
   case IIO_CHAN_INFO_RAW: 
           /* If buffer mode enabled, do not process single-channel read */ 
           if (iio_buffer_enabled(indio_dev)) 
                   return -EBUSY; 
           /* Else we read the channel */ 
           ret = bma220_read_reg(data->spi_device, chan->address); 
           if (ret < 0) 
                   return -EINVAL; 
           *val = sign_extend32(ret >> BMA220_DATA_SHIFT, 5); 
           return IIO_VAL_INT; 
   case IIO_CHAN_INFO_SCALE: 
           ret = bma220_read_reg(data->spi_device, BMA220_REG_RANGE); 
           if (ret < 0) 
                   return ret; 
           range_idx = ret & BMA220_RANGE_MASK; 
           *val = bma220_scale_table[range_idx][0]; 
           *val2 = bma220_scale_table[range_idx][1]; 
           return IIO_VAL_INT_PLUS_MICRO; 
   } 

   return -EINVAL; 
} 

当一个人读取一个*raw sysfs 文件时,调用钩子,在mask参数中给出IIO_CHAN_INFO_RAW,在*chan参数中给出相应的通道。*valval2实际上是输出参数。它们必须用原始值设置(从设备读取)。对*scale sysfs 文件执行的任何读取将调用掩码参数中带有IIO_CHAN_INFO_SCALE的钩子,以此类推每个属性掩码。

用于将值写入设备的write功能也是如此。您的驾驶员有 80%的可能不需要write功能。这个write挂钩可以让用户改变设备的比例:

static int bma220_write_raw(struct iio_dev *indio_dev, 
                   struct iio_chan_spec const *chan, 
                   int val, int val2, long mask) 
{ 
   int i; 
   int ret; 
   int index = -1; 
   struct bma220_data *data = iio_priv(indio_dev); 

   switch (mask) { 
   case IIO_CHAN_INFO_SCALE: 
         for (i = 0; i < ARRAY_SIZE(bma220_scale_table); i++) 
               if (val == bma220_scale_table[i][0] && 
                   val2 == bma220_scale_table[i][1]) { 
                     index = i; 
                     break; 
               } 
         if (index < 0) 
               return -EINVAL; 

         mutex_lock(&data->lock); 
         data->tx_buf[0] = BMA220_REG_RANGE; 
         data->tx_buf[1] = index; 
         ret = spi_write(data->spi_device, data->tx_buf, 
                     sizeof(data->tx_buf)); 
         if (ret < 0) 
               dev_err(&data->spi_device->dev, 
                     "failed to set measurement range\n"); 
         mutex_unlock(&data->lock); 

         return 0; 
   } 

   return -EINVAL; 
} 

每当向设备写入值时,都会调用此函数。经常变化的参数是刻度。例如:echo <desired-scale> > /sys/bus/iio/devices/iio;devices0/in_accel_scale

现在,它来填充一个struct iio_info结构,给我们的iio_device:

static const struct iio_info bma220_info = { 
   .driver_module    = THIS_MODULE, 
   .read_raw         = bma220_read_raw, 
   .write_raw        = bma220_write_raw, /* Only if your driver need it */ 
}; 

probe功能中,我们分配并设置一个struct iio_dev IIO 设备。私有数据的内存也被保留:

/* 
 * We provide only two mask possibility, allowing to select none or every 
 * channels. 
 */ 
static const unsigned long bma220_accel_scan_masks[] = { 
   BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), 
   0 
}; 

static int bma220_probe(struct spi_device *spi) 
{ 
   int ret; 
   struct iio_dev *indio_dev; 
   struct bma220_data *data; 

   indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); 
   if (!indio_dev) { 
         dev_err(&spi->dev, "iio allocation failed!\n"); 
         return -ENOMEM; 
   } 

   data = iio_priv(indio_dev); 
   data->spi_device = spi; 
   spi_set_drvdata(spi, indio_dev); 
   mutex_init(&data->lock); 

   indio_dev->dev.parent = &spi->dev; 
   indio_dev->info = &bma220_info; 
   indio_dev->name = BMA220_DEVICE_NAME; 
   indio_dev->modes = INDIO_DIRECT_MODE; 
   indio_dev->channels = bma220_channels; 
   indio_dev->num_channels = ARRAY_SIZE(bma220_channels); 
   indio_dev->available_scan_masks = bma220_accel_scan_masks; 

   ret = bma220_init(data->spi_device); 
   if (ret < 0) 
         return ret; 

   /* this call will enable trigger buffer support for the device */ 
   ret = iio_triggered_buffer_setup(indio_dev, iio_pollfunc_store_time, 
                            bma220_trigger_handler, NULL); 
   if (ret < 0) { 
         dev_err(&spi->dev, "iio triggered buffer setup failed\n"); 
         goto err_suspend; 
   } 

   ret = iio_device_register(indio_dev); 
   if (ret < 0) { 
         dev_err(&spi->dev, "iio_device_register failed\n"); 
         iio_triggered_buffer_cleanup(indio_dev); 
         goto err_suspend; 
   } 

   return 0; 

err_suspend: 
   return bma220_deinit(spi); 
} 

您可以通过CONFIG_BMA220内核选项启用该驱动。也就是说,这只能从 4.8 版开始在内核中使用。在较旧的内核版本上,最接近的设备是 BMA180,可以使用CONFIG_BMA180选项启用它。

IIO 数据访问

您可能已经猜到,使用 IIO 框架访问数据只有两种方式;通过 sysfs 通道的一次性捕获,或通过 IIO 字符设备的连续模式(触发缓冲区)。

一次性捕获

一次性数据捕获通过 sysfs 接口完成。通过读取对应于一个通道的 sysfs 条目,您将只捕获特定于该通道的数据。假设温度传感器有两个通道:一个用于环境温度,另一个用于热电偶温度:

 # cd /sys/bus/iio/devices/iio:device0
  # cat in_voltage3_raw
  6646
 # cat in_voltage_scale
  0.305175781

已处理值是通过将比例乘以原始值获得的。

Voltage value : 6646 * 0.305175781 = 2028.19824053

器件数据表显示过程值以毫伏为单位。在我们的例子中,它对应于 2.02819V。

缓冲数据访问

要使触发采集工作,必须在您的驱动中实现触发支持。然后,要从用户空间内获取数据,必须:创建一个触发器,分配它,使能 ADC 通道,设置缓冲器的尺寸,并使能它)。这是代码:

使用 sysfs 触发器捕获

使用 sysfs 触发器捕获数据包括发送一组命令几个 sysfs 文件。让我们列举我们应该做些什么来实现这一目标:

  1. 创建触发器:在将触发器分配给任何设备之前,应该创建触发器:
 # echo 0 > /sys/devices/iio_sysfs_trigger/add_trigger

这里,0对应于我们需要分配给触发器的索引。该命令后,触发目录将在*/sys/bus/iio/devices/*下可用,作为trigger0

  1. 将触发器分配给设备:触发器由它的名称唯一标识,我们可以使用它将设备与触发器联系起来。由于我们使用 0 作为索引,触发器将被命名为sysfstrig0:
# echo sysfstrig0 > /sys/bus/iio/devices/iio:device0/trigger/current_trigger 

我们也可以使用这个命令:cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger。也就是说,如果我们写的值不对应于现有的触发器名称,什么也不会发生。为了确保我们真正定义了一个触发器,我们可以使用cat /sys/bus/iio/devices/iio:device0/trigger/current_trigger

  1. 启用一些扫描元素:这一步包括选择哪些通道应该将其数据值推入缓冲区。司机应注意available_scan_masks:
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage4_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage5_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage6_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage7_en 
  1. 设置缓冲区大小:这里需要设置缓冲区可以容纳的样本集数量:
 # echo 100 > /sys/bus/iio/devices/iio:device0/buffer/length
  1. 启用缓冲区:该步骤包括将缓冲区标记为准备接收推送数据:
 # echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable

要停止捕获,我们必须在同一个文件中写入 0。

  1. 触发:发射采集:
 # echo 1 > /sys/bus/iio/devices/trigger0/trigger_now

现在收购已经完成,我们可以:

  1. 禁用缓冲区:
 # echo 0 > /sys/bus/iio/devices/iio:device0/buffer/enable 
  1. 分离触发器:
 # echo "" > /sys/bus/iio/devices/iio:device0/trigger/current_trigger 
  1. 转储我们的 IIO 字符设备的内容:
 # cat /dev/iio\:device0 | xxd -

使用 hrtimer 触发器捕获

以下是允许使用 hrtimer 触发器捕获数据的命令集:

 # echo /sys/kernel/config/iio/triggers/hrtimer/trigger0
 # echo 50 > /sys/bus/iio/devices/trigger0/sampling_frequency
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage4_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage5_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage6_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage7_en
 # echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable
 # cat /dev/iio:device0 | xxd -
 0000000: 0188 1a30 0000 0000 8312 68a8 c24f 5a14 ...0......h..OZ.
  0000010: 0188 1a30 0000 0000 192d 98a9 c24f 5a14 ...0.....-...OZ.
  [...]

然后,我们查看类型以了解如何处理数据:

$ cat /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage_type
be:s14/16>>2

电压处理:0x188 >> 2 = 98 * 250 = 24500 = 24.5 v

IIO 工具公司

有一些有用的工具,你可以使用,以方便和加快您的应用的开发与 IIO 设备。它们在内核树的tools/iio中可用:

  • lsiio.c : 枚举 IIO 触发器、设备和通道
  • iio_event_monitor.c:监控 IIO 设备的 ioctl 接口,查看 IIO 事件
  • generic_buffer.c:检索、处理和打印从 IIO 设备的缓冲器接收的数据
  • libiio:ADI 公司开发的一个强大的库,用于连接 IIO 设备,可在https://github.com/analogdevicesinc/libiio获得。

摘要

到本章结束时,您应该已经熟悉了 IIO 框架和词汇。你知道什么是通道、设备和触发器。您甚至可以从用户空间,通过 sysfs 或字符设备来玩您的 IIO 设备。写你自己的 IIO 司机的时候到了。有许多可用的现有驱动不支持触发缓冲区。您可以尝试在其中一个中添加此类功能。在下一章中,我们将使用系统中最有用的资源:内存。坚强点,比赛才刚刚开始。