Linux設備驅動之IIO子系統——IIO框架及IIO數據結構
由于需要對ADC進行驅動設計,因此學習了一下Linux驅動的IIO子系統。本文翻譯自《Linux Device Drivers Development 》--John Madieu,本人水平有限,若有錯誤請大家指出。
IIO Framework
工業I / O(IIO)是專用于模數轉換器(ADC)和數模轉換器(DAC)的內核子系統。隨著越來越多的具有不同代碼實現的傳感器(具有模擬到數字或數字到模擬,功能的測量設備)分散在內核源上,收集它們變得必要。這就是IIO框架以通用的方式所做的事情。自2009年以來,Jonathan Cameron和Linux-IIO社區一直在開發它。
加速度計,陀螺儀,電流/電壓測量芯片,光傳感器,壓力傳感器等都屬于IIO系列器件。
IIO模型基于設備和通道架構:
l 設備代表芯片本身。它是層次結構的頂級。
l 通道代表設備的單個采集線。設備可以具有一個或多個通道。例如,加速度計是具有 三個通道的裝置,每個通道對應一個軸(X,Y和Z)。
IIO芯片是物理和硬件傳感器/轉換器。它作為字符設備(當支持觸發緩沖時)暴露給用戶空間,以及包含一組文件的sysfs目錄條目,其中一些文件代表通道。單個通道用單個sysfs文件條目表示。
下面是從用戶空間與IIO驅動程序交互的兩種方式:
l /sys/bus/iio/iio:deviceX/:表示傳感器及其通道
l /dev/iio:deviceX: 表示導出設備事件和數據緩沖區的字符設備
IIO框架架構和布局
上圖顯示了如何在內核和用戶空間之間組織IIO框架。 驅動程序使用IIO核心公開的一組工具和API來管理硬件并向IIO核心報告處理。 然后,IIO子系統通過sysfs接口和字符設備將整個底層機制抽象到用戶空間,用戶可以在其上執行系統調用。
IIO API分布在多個頭文件中,如下所示:
#include / mandatory /
include / mandatory since sysfs is used /
include / For advanced users, to manage iio events /
include / mandatory to use triggered buffers /
include / Only if you implement trigger in your driver (rarely used)/
在以下文章中,我們將描述和處理IIO框架的每個概念,例如
遍歷其數據結構(設備,通道等)
觸發緩沖支持和連續捕獲,以及其sysfs接口
探索現有的IIO觸發器
以單次模式或連續模式捕獲數據
列出可用于幫助開發人員測試其設備的可用工具
(一):IIO data structures:IIO數據結構
IIO設備在內核中表示為struct iio_dev結構體的一個實例,并由struct iio_info結構體描述。 所有重要的IIO結構都在include/linux/iio/iio.h中定義。
iio_dev structure(iio_dev結構)
該結構代表IIO設備,描述設備和驅動程序。 它告訴我們:
l 設備上有多少個通道?
l 設備可以在哪些模式下運行:單次,觸發緩沖?
l 這個驅動程序可以使用哪些hooks鉤子?
復制代碼
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是上述兩者的聯合。
l currentmode: 這表示設備實際使用的模式。
l dev: 這表示IIO設備所依賴的struct設備(根據Linux設備型號)。
l buffer: 這是您的數據緩沖區,在使用觸發緩沖區模式時會推送到用戶空間。 使用iio_triggered_buffer_setup函數啟用觸發緩沖區支持時,它會自動分配并與您的設備關聯。
l scan_bytes: 這是捕獲并饋送到緩沖區的字節數。 當從用戶空間使用觸發緩沖區時,緩沖區應至少為indio-> scan_bytes字節大。
l 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;
復制代碼
l active_scan_mask: 這是啟用通道的位掩碼。 只有來自這些通道的數據能被推入緩沖區。 例如,對于8通道ADC轉換器,如果只啟用第一個(0),第三個(2)和最后一個(7)通道,則位掩碼將為0b10000101(0x85)。 active_scan_mask將設置為0x85。 然后,驅動程序可以使用for_each_set_bit宏遍歷每個設置位,根據通道獲取數據,并填充緩沖區。
l scan_timestamp: 這告訴我們是否將捕獲時間戳推入緩沖區。 如果為true,則將時間戳作為緩沖區的最后一個元素。 時間戳大8字節(64位)。
l trig: 這是當前設備觸發器(支持緩沖模式時)。
l pollfunc:這是在接收的觸發器上運行的函數。
l channels: 這表示通道規范結構,用于描述設備具有的每個通道。
l num_channels: 這表示通道中指定的通道數。
l name: 這表示設備名稱。
l info: 來自驅動程序的回調和持續信息。
l 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);
};
復制代碼
l setup_ops: 如果未指定,則IIO內核使用drivers / iio / buffer / industrialio-triggered-buffer.c中定義的缺省iio_triggered_buffer_setup_ops。
l chrdev: 這是由IIO核心創建的關聯字符設備。
用于為IIO設備分配內存的函數是iio_device_alloc():
struct iio_dev * iio_device_alloc(int sizeof_priv)
///struct iio_dev devm_iio_device_alloc(struct device dev, int sizeof_priv)
/ Resource-managed iio_device_alloc()/
/*Managed iio_device_alloc. iio_dev allocated with this function is automatically freed on driver detach.
If an iio_dev allocated with this function needs to be freed separately, devm_iio_device_free() must be used. */
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)
//devm_iio_device_register(dev, indio_dev)
/ Resource-managed iio_device_register() /
在執行此功能后,設備將準備好接受來自用戶空間的請求。 反向操作(通常在釋放函數中完成)是iio_device_unregister():
void iio_device_unregister(struct iio_dev *indio_dev)
// void devm_iio_device_unregister(struct device dev, struct iio_dev indio_dev)
一旦取消注冊,iio_device_alloc分配的內存可以用iio_device_free釋放:
void iio_device_free(struct iio_dev *iio_dev)
// void devm_iio_device_free(struct device dev, struct iio_dev iio_dev)
給定IIO設備作為參數,可以通過以下方式檢索私有數據:
struct my_private_data *the_data = iio_priv(indio_dev);
iio_info structure: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);[...]
};
復制代碼
l driver_module: 這是用于確保chrdev正確擁有的模塊結構,通常設置為THIS_MODULE。
l attrs: 這表示設備屬性。
l read_raw: 這是用戶讀取設備sysfs文件屬性時的回調運行。 mask參數是一個位掩碼,它允許我們知道請求了哪種類型的值。 channel參數讓我們知道相關的通道。 它可以是采樣頻率,用于將原始值轉換為可用值的比例,或原始值本身。
l 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 channels: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;};
復制代碼
各個參數意義:
l type: 這指定了通道的測量類型。 在電壓測量的情況下,它應該是IIO_VOLTAGE。 對于光傳感器,它是IIO_LIGHT。 對于加速度計,使用IIO_ACCEL。 所有可用類型都在include / uapi / linux / iio / types.h中定義,如enum iio_chan_type。 要為給定轉換器編寫驅動程序,請查看該文件以查看每個通道所屬的類型。
l channel: 這指定.indexed設置為1時的通道索引。
l channel2: 這指定.modified設置為1時的通道修飾。
l modified: 這指定是否將修飾符應用于此通道屬性名稱。 在這種情況下,修飾符設置在.channel2中。 (例如,IIO_MOD_X,IIO_MOD_Y,IIO_MOD_Z是關于xyz軸的軸向傳感器的修改器)。 可用修飾符列表在內核IIO頭中定義為枚舉iio_modifier。 修飾符只會破壞sysfs中的通道屬性名稱,而不是值。
l indexed: 這指定通道屬性名稱是否具有索引。 如果是,則在.channel字段中指定索引。
l scan_index and scan_type: 當使用緩沖區觸發器時,這些字段用于標識緩沖區中的元素。 scan_index設置緩沖區內捕獲的通道的位置。 具有較低scan_index的通道將放置在具有較高索引的通道之前。 將.scan_index設置為-1將阻止通道進行緩沖捕獲(scan_elements目錄中沒有條目)。
暴露給用戶空間的通道sysfs屬性以位掩碼的形式指定。 根據共享信息,可以將屬性設置為以下掩碼之一:
l info_mask_separate 將屬性標記為特定于此通
l info_mask_shared_by_type 將該屬性標記為由相同類型的所有通道共享。 導出的信息由相同類型的所有通道共享。
l info_mask_shared_by_dir 將屬性標記為由同一方向的所有通道共享。 導出的信息由同一方向的所有通道共享。
l 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,
};
Channel attribute naming conventions:通道屬性命名約定
屬性的名稱由IIO核心自動生成,具有以下模式:{direction} {type} {index} {modifier} {info_mask}:
l direction方向對應于屬性方向,根據drivers / iio / industrialio-core.c中的struct iio_direction結構:
static const char * const iio_direction[] = {
[0] = "in",
[1] = "out",
};
l 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",
};
復制代碼
l index 索引模式取決于是否設置了通道.indexed字段。 如果設置,索引將從.channel字段中獲取,以替換{index}模式。
l 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",
};
復制代碼
l info_mask取決于char數組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",
[...]
};
復制代碼
Distinguishing channels通道區分
當每種通道類型有多個數據通道時,您可能會遇到麻煩。 困境將是:如何識別它們。 有兩種解決方案:索引和修飾符。
使用索引:給定具有一個通道線的ADC器件,不需要索引。通道定義如下:
復制代碼
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_raw和in_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),},}
復制代碼
屬性結果:
l /sys/bus/iio/iio:deviceX/in_intensity_ir_raw 用于測量IR強度的通道
l /sys/bus/iio/iio:deviceX/in_intensity_both_raw用于測量紅外和可見光的通道
l /sys/bus/iio/iio:deviceX/in_illuminance_input用于處理后的數據
l /sys/bus/iio/iio:deviceX/sampling_frequency 用于采樣頻率,由所有人共享
這也適用于加速度計,我們將在案例研究中進一步了解。 現在,讓我們總結一下我們到目前為止在虛擬IIO驅動程序中討論過的內容。
Putting it all together總結
讓我們總結一下迄今為止我們在一個簡單的虛擬驅動器中看到的內容,它將暴露出四個電壓通道。 我們將忽略read()或write()函數:
復制代碼
include
include
include
include
include
include
include
include
include
include
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
原文地址https://www.cnblogs.com/yongleili717/p/10744252.html