Triggered buffer support觸發緩衝支持html
在許多數據分析應用中,可以基於某些外部信號(觸發器)捕獲數據是比較有用的。 這些觸發器多是:redis
IIO設備驅動程序與觸發器徹底無關。 觸發器能夠初始化一個或多個設備上的數據捕獲。 這些觸發器用於填充緩衝區,而後做爲字符設備暴露給用戶空間。api
能夠開發一個本身的觸發驅動程序,但這超出了本書的範圍。 咱們將嘗試僅關注現有的。 這些是:ide
利用IIO公開的API,咱們能夠:函數
當您的IIO設備支持觸發緩衝區時,您必須設置iio_dev.pollfunc,它在觸發器觸發時執行。 此處理程序負責經過indio_dev-> active_scan_mask查找已啓用的通道,檢索其數據,並使用iio_push_to_buffers_with_timestamp函數將它們提供給indio_dev-> buffer。 所以,緩衝區和觸發器須要在IIO子系統中鏈接。post
IIO核心提供了一組輔助函數來設置觸發緩衝區,能夠在drivers / iio / industrialio-triggered-buffer.c中找到。this
如下是從驅動程序中支持觸發緩衝區的步驟:編碼
1.若是須要,填寫iio_buffer_setup_ops結構:spa
1 const struct iio_buffer_setup_ops sensor_buffer_setup_ops = { 2 .preenable = my_sensor_buffer_preenable, 3 .postenable = my_sensor_buffer_postenable, 4 .postdisable = my_sensor_buffer_postdisable, 5 .predisable = my_sensor_buffer_predisable, 6 };
2. 寫下與觸發器關聯的上半部分。 在99%的狀況下,只需提供與捕獲相關的時間戳:線程
1 irqreturn_t sensor_iio_pollfunc(int irq, void *p) 2 { 3 pf->timestamp = iio_get_time_ns((struct indio_dev *)p); 4 return IRQ_WAKE_THREAD; 5 }
3. 寫入觸發器下半部分,它將從每一個啓用的通道獲取數據,並將它們提供給緩衝區:
1 irqreturn_t sensor_trigger_handler(int irq, void *p) 2 { 3 u16 buf[8]; 4 int bit, i = 0; 5 struct iio_poll_func *pf = p; 6 struct iio_dev *indio_dev = pf->indio_dev; 7 8 /* one can use lock here to protect the buffer */ 9 /* mutex_lock(&my_mutex); */ 10 /* read data for each active channel */ 11 for_each_set_bit(bit, indio_dev->active_scan_mask, 12 indio_dev->masklength) 13 buf[i++] = sensor_get_data(bit) 14 15 /* 16 * If iio_dev.scan_timestamp = true, the capture timestamp 17 * will be pushed and stored too, as the last element in the 18 * sample data buffer before pushing it to the device buffers. 19 */ 20 iio_push_to_buffers_with_timestamp(indio_dev, buf, timestamp); 21 22 /* Please unlock any lock */ 23 24 /* mutex_unlock(&my_mutex); */ 25 26 /* Notify trigger */ 27 28 iio_trigger_notify_done(indio_dev->trig); 29 return IRQ_HANDLED; 30 }
4. 最後,在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( )hook會嘗試同時訪問設備。 用於檢查是否實際使用緩衝模式的函數是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內核將爲您的設備提供默認的iio_triggered_buffer_setup_ops。
sensor_iio_pollfunc是觸發器的上半部分。 與每一個上半部分同樣,它在中斷上下文中運行,而且必須儘量少地處理。 在99%的狀況下,您只需提供與捕獲相關的時間戳。 再次,可使用默認的IIO iio_pollfunc_store_time函數。
sensor_trigger_handler是下半部分,它在內核線程中運行,容許咱們進行任何處理,包括甚至獲取互斥或睡眠。 重處理應該在這裏進行。 它一般從設備讀取數據並將其與上半部分中記錄的時間戳一塊兒存儲在內部緩衝區中,並將其推送到IIO設備緩衝區。
注意:觸發緩衝必須使用觸發器。 它告訴驅動程序什麼時候從設備讀取樣本並將其放入緩衝區。 觸發緩衝對於編寫IIO設備驅動程序不是必需的。 經過讀取通道的原始屬性,也能夠經過sysf使用單次捕獲,這隻會執行單次轉換(對於正在讀取的通道屬性)。 緩衝模式容許連續轉換,從而在單次捕獲多個通道。
IIO trigger and sysfs (user space)用戶空間觸發器和sysfs
sysfs中有兩個與觸發器相關的位置:
Sysfs trigger interface Sysfs觸發器接口
經過CONFIG_IIO_SYSFS_TRIGGER = y config選項在內核中啓用sysfs觸發器,將自動建立/ sys / bus / iio / devices / iio_sysfs_trigger /文件夾,並可用於sysfs觸發器管理。 目錄中將有兩個文件add_trigger和remove_trigger。 它的驅動程序在drivers / iio / trigger / iio-trig-sysfs.c中。
add_trigger file
這用於建立新的sysfs觸發器。 您能夠經過將正值(將用做觸發器ID)寫入該文件來建立新觸發器。 它將建立新的sysfs觸發器,可在/ sys / bus / iio / devices / triggerX中訪問,其中X是觸發器編號。
例如:# echo 2 > add_trigger
這將建立一個新的sysfs觸發器,可在/ sys / bus / iio / devices / trigger2中訪問。 若是系統中已存在具備指定ID的觸發器,則將返回無效的參數消息。 sysfs觸發器名稱模式爲sysfstrig {ID}。 命令echo 2> add_trigger將建立名爲sysfstrig2的trigger / 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)。
remove_trigger file
要刪除觸發器,請使用如下命令:
# 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觸發器的實際示例。
The interrupt trigger interface中斷觸發器接口
請考慮如下代碼示例
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觸發器獨立模塊。 若是其探測功能成功,則會有一個與觸發器對應的目錄。 IRQ觸發器名稱的格式爲irqtrigX,其中X對應於剛剛傳遞的虛擬IRQ,您將在/ proc / interrupt中看到:
$ cd /sys/bus/iio/devices/trigger0/
$ cat name
正如咱們對其餘觸發器所作的那樣,您只需將該觸發器分配給設備current_trigger文件便可將該觸發器分配給您的設備。
# echo "irqtrig85" > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
如今,每次觸發中斷時,都會捕獲設備數據。
注意:IRQ觸發器驅動程序還不支持DT,這就是咱們使用board init文件的緣由。 可是這不要緊; 因爲驅動程序須要資源,咱們可使用DT而無需更改任何代碼。
如下是聲明IRQ觸發器接口的設備樹節點的示例:
mylabel: my_trigger@0{ compatible = "iio_interrupt_trigger"; interrupt-parent = <&gpio4>; interrupts = <30 0x0>; };
該示例假設IRQ線是屬於GPIO控制器節點gpio4的GPIO#30。 這包括使用GPIO做爲中斷源,這樣不管什麼時候GPIO變爲給定狀態,都會引起中斷,從而觸發捕獲。
hrtimer觸發器接口(4.5內核如下可能不支持)
hrtimer觸發器依賴於configfs文件系統(請參閱內核源代碼中的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屬性(/sys/bus/iio/devices/triggerY/文件夾下)。 在使用hrtimer觸發器的數據捕獲一節中的章節中進一步提供了完整且有效的示例。
IIO buffers IIO緩衝區
IIO緩衝區提供連續數據捕獲,可同時讀取多個數據通道。 能夠經過/ dev / iio:device字符設備節點從用戶空間訪問緩衝區。 在觸發器處理程序中,用於填充緩衝區的函數是iio_push_to_buffers_with_timestamp。 負責爲您的設備分配觸發緩衝區的函數是iio_triggered_buffer_setup()。
IIO緩衝sysfs接口
IIO緩衝區在/ sys / bus / iio / iio:deviceX / buffer / *下有一個關聯的屬性目錄。 如下是一些現有屬性:
IIO緩衝區設置
將要讀取數據並將其推入緩衝區的通道稱爲掃描元素(scan element )。 能夠從用戶空間經過/ sys / bus / iio / iio:deviceX / scan_elements / *目錄訪問它們的配置,其中包含如下屬性:
[s|u]bits/storagebitsXrepeat[>>shift].
解釋這一部分的最好方法是經過內核文檔的摘錄,能夠在這裏找到:https://www.kernel.org/doc/html/latest/driver-api/iio/buffers.html。 例如,用於3軸加速度計的驅動程序,具備12位分辨率,其中數據存儲在兩個8位寄存器中,以下所示:
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], ,這是上一節中描述的模式。 讓咱們看看結構的每一個成員:
此時,能夠編寫與前面解釋的類型相對應的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 */ }
Putting it all together
讓咱們仔細看看BOSH的數字三軸加速度傳感器BMA220。 這是一個SPI / I2C兼容器件,具備8位大小的寄存器,以及片上運動觸發中斷控制器,實際上能夠感應傾斜,運動和衝擊振動。 其數據表可從如下網址得到:http://www.mouser.fr/pdfdocs/BSTBMA220DS00308.PDF,其驅動程序自內核v4.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會調用read_raw()掛鉤,並將掩碼設置爲IIO_CHAN_INFO_SCALE。 讀取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; }
如下是讀取功能。 它是一個鉤子,每次讀取設備的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參數中調用相應的通道。 * val和val2其實是輸出參數。 必須使用raw值設置它們(從設備讀取)。 在* scale sysfs文件上執行的任何讀取都將使用掩碼參數中的IIO_CHAN_INFO_SCALE調用掛鉤,依此類推每一個屬性掩碼。
寫入功能也是如此,用於將值寫入設備。 您的驅動程序有80%的可能性不須要寫入功能。 此寫掛鉤容許用戶更改設備的比例:
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.
如今,它來填充一個結構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內核選項啓用此驅動程序。 也就是說,這隻能從內核中的v4.8開始提供。 能夠在較舊的內核版本上使用的最接近的設備是BMA180,可使用CONFIG_BMA180選項啓用它。