由於需要對ADC進行驅動設計,因此學習了一下Linux驅動的IIO子系統。本文翻譯自《Linux Device Drivers Development 》--John Madieu,本人水平有限,若有錯誤請大家指出。 IIO Framework 工業I / O(IIO)是專用於模數轉換器(ADC)和 ...
由於需要對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 <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 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 <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 <[email protected]>"); 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