Linux IIO子系统分析3(基于Linux6.6)---TRIGGER设计介绍
一、数据结构简述
include/linux/iio/trigger.h
struct iio_trigger {
const struct iio_trigger_ops *ops;
struct module *owner;
int id;
const char *name;
struct device dev;
struct list_head list;
struct list_head alloc_list;
atomic_t use_count;
struct irq_chip subirq_chip;
int subirq_base;
struct iio_subirq subirqs[CONFIG_IIO_CONSUMERS_PER_TRIGGER];
unsigned long pool[BITS_TO_LONGS(CONFIG_IIO_CONSUMERS_PER_TRIGGER)];
struct mutex pool_lock;
bool attached_own_device;
struct work_struct reenable_work;
};
这两个数据结构主要实现iio 的trigger机制,类似于led子系统的led trigger。主要内容如下:
- id表示trigger的id、name为名称;
- 该iio trigger也使用struct device类型的变量加入到iio总线上,iio trigger与iio device均注册到iio总线上,因此它们在sysfs目录下是同级的;
- list用于将struct iio trigger添加系统全局链表iio_trigger_list中;
- alloc_list主要用于同一类型的trigger可注册多个trigger实例的请求,如trigger-period则使用该变量将trigger插入到iio_prtc_trigger_list中,目前使用这一变量的trigger并不多;
- 使用计数use_count;
- 而subirq_chip、subirq_base、subirqs、pool则主要用于创建虚拟的irq chip,在trigger内部,当多个trigger consumer注册时,则trigger内部会为其分配一个虚拟的irq,并根据trigger consumer提供给pollfunc,为该irq注册中断处理函数,这样当该trigger触发后,则会遍历所有该trigger上已注册的虚拟irq,调用其中断处理函数从而执行trigger consumer提供的处理函数(关于linux中断子系统的内容可参考我之前写的中断子系统专栏,我在中断子系统专栏也实现了一个虚拟的irq chip,实现的原理和此处trigger实现的虚拟irq chip的原理是一样的)。
iio trigger也提供了操作接口,其中set_trigger_state主要设置trigger的状态(使能与否)、reenable接口(try_reenable),validate_device(如实现的trigger只允许父device相同的iio device绑定,则可以实现该接口进行限制操作)。
而iio trigger定义了全局链表iio_trigger_list,用于将系统中所有已注册的trigger链接在一起。
即iio device通过其trig变量连接到其所绑定的iio trigger;但是iio trigger却没有存储所有绑定在该iiotrigger下的iio device,那iio trigger被触发后如何唤醒/调用iio device的回调接口呢?我们知道在led子系统中led trigger通过其led_cdevs链表将所有注册到该trigger的led class连接起来,那iio trigger既然没有这类成员,那它如何实现被触发后如何唤醒/调用iio device的回调接口呢?那就是借助虚拟irq chip。
二、设计实现说明
对于iio trigger而言,其主要工作就是,当某一种条件满足触发trigger后,trigger再调用每一个绑定的consumer,由consumer执行该条件的处理操作等内容。
一种方式就是led class与led trigger的实现方式,led class与led trigger通过实现互相绑定,存储对方的指针。
而iio trigger没有选用该方法,其为每一个trigger实现了一个虚拟的irq chip,而在进行iio trigger与iio device的绑定操作时,为待绑定的iio device申请一个该trigger尚未使用的虚拟irq,并完成对该中断的注册操作;而当外部设备驱动触发该trigger后,该trigger则遍历已注册的中断,调用其对应的中断处理函数进行相应的处理,这也就实现了调用所有已绑定的iio device提供的处理函数,进行trigger的处理操作。
简化流程图:
+-------------------------+
| 外部设备触发 iio_trigger_poll |
+-------------------------+
|
v
+-------------------------------+
| 调用 iio_trigger_poll() 函数 |
+-------------------------------+
|
v
+------------------------------------------+
| 查找与 Trigger 相关的回调函数 |
+------------------------------------------+
|
v
+---------------------------------------------+
| 执行回调函数(例如:数据采集、事件处理等) |
+---------------------------------------------+
|
v
+------------------------------------------+
| 设备完成数据处理 |
+------------------------------------------+
|
v
+-----------------------------------------+
| 如果需要,通知用户空间应用程序 |
+-----------------------------------------+
|
v
+---------------------------------+
| 返回触发状态 |
+---------------------------------+
详细流程解释
-
外部设备触发: 外部设备可以通过硬件中断、时间戳、条件检测等方式触发。触发事件可能是外部传感器状态变化、定时器到期等。
-
iio_trigger_poll()
函数:iio_trigger_poll()
是用于处理触发事件的函数。当外部设备触发了事件时,内核会调用iio_trigger_poll()
来检查是否有任何需要执行的操作。 -
查找回调函数: 在 IIO 子系统中,每个触发器都有一个与之关联的回调函数(通常是设备驱动提供的函数)。该函数会被
iio_trigger_poll()
调用。 -
执行回调函数: 这些回调函数通常包括读取硬件数据、执行设备配置、更新设备状态等任务。例如,如果设备是一个传感器,回调函数可能会触发一次数据采集,将新数据写入缓冲区。
-
设备完成数据处理: 回调函数执行完后,设备的状态会得到更新,数据会被采集和存储。
-
通知用户空间: 如果需要,IIO 子系统将通过事件机制(如
iio_event
)或者字符设备接口(如iio_read()
)将新数据或事件传递给用户空间程序。 -
返回触发状态: 最后,
iio_trigger_poll()
返回并结束该触发过程。触发器可能被配置为周期性触发,或者触发一次后停止,具体取决于设备的配置。
如下所示,当外部设备调用iio_trgger_poll触发一个trigger后,则针对该trigger下所有已使能的irq,均调用generic_handle_irq,执行该中断的中断处理函数。
外部设备 → 调用 iio_trigger_poll
↓
触发器激活(已使能 IRQ)
↓
遍历所有已使能的 IRQ
↓
对每个 IRQ 调用 generic_handle_irq
↓
执行中断处理函数(IRQ handler)
↓
完成设备的中断处理
既然已经明确了iio trigger实现的机制,那看下需要如何实现代码以便完成上面的功能,这大概分为如下几个方面:
- 完成虚拟irq chip的创建及注册操作,而虚拟中断的个数则可通过make menuconfig进行修改,默认是2(CONFIG_IIO_CONSUMERS_PER_TRIGGER表示支持的consumer个数,也即是虚拟irq的个数);
- 提供虚拟irq的申请及注册接口;
- 提供trigger的触发接口,以便外部条件(如soc接收到中断)满足时可触发该trigger。
基本上以上3个功能即是iio trigger所需要实现的主要内容。
三、提供接口说明
此处我们就以上一章节说明的3个主要功能说明iio trigger提供的接口。
3.1、iio trigger的创建与注册接口
iio trigger提供了iio_trigger_alloc、iio_trigger_register用于实现iio trigger的创建与注册操作,其中iio_trigger_alloc接口则主要完成虚拟irq chip的创建功能;而iio_trigger_register接口主要将该iio trigger注册到系统链表iio_trigger_list中。
include/linux/iio/trigger.h
#define iio_trigger_alloc(parent, fmt, ...) \
__iio_trigger_alloc((parent), THIS_MODULE, (fmt), ##__VA_ARGS__)
drivers/iio/industrialio-trigger.c
int iio_trigger_register(struct iio_trigger *trig_info)
{
int ret;
trig_info->id = ida_alloc(&iio_trigger_ida, GFP_KERNEL);
if (trig_info->id < 0)
return trig_info->id;
/* Set the name used for the sysfs directory etc */
dev_set_name(&trig_info->dev, "trigger%d", trig_info->id);
ret = device_add(&trig_info->dev);
if (ret)
goto error_unregister_id;
/* Add to list of available triggers held by the IIO core */
mutex_lock(&iio_trigger_list_lock);
if (__iio_trigger_find_by_name(trig_info->name)) {
pr_err("Duplicate trigger name '%s'\n", trig_info->name);
ret = -EEXIST;
goto error_device_del;
}
list_add_tail(&trig_info->list, &iio_trigger_list);
mutex_unlock(&iio_trigger_list_lock);
return 0;
error_device_del:
mutex_unlock(&iio_trigger_list_lock);
device_del(&trig_info->dev);
error_unregister_id:
ida_free(&iio_trigger_ida, trig_info->id);
return ret;
}
EXPORT_SYMBOL(iio_trigger_register);
3.2、iio trigger与consumer的绑定接口
iio trigger子系统提供接口iio_trigger_write_current实现将一个consumer与iio trigger的绑定。而该函数实现如下几个功能:
- 若该consumer已经绑定了一个iio trigger,需要先解除与iio trigger的绑定(调用iio_trigger_detach_poll_func实现);
- 完成该consumer与新的iio trigger的绑定,通过调用接口iio_trigger_attach_poll_func接口实现,而在该接口中则主要申请一个尚未使用的虚拟irq,并完成中断处理函数的注册操作。
drivers/iio/industrialio-trigger.c
int iio_trigger_detach_poll_func(struct iio_trigger *trig,
struct iio_poll_func *pf)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(pf->indio_dev);
bool no_other_users =
bitmap_weight(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER) == 1;
int ret = 0;
if (trig->ops && trig->ops->set_trigger_state && no_other_users) {
ret = trig->ops->set_trigger_state(trig, false);
if (ret)
return ret;
}
if (pf->indio_dev->dev.parent == trig->dev.parent)
trig->attached_own_device = false;
iio_trigger_put_irq(trig, pf->irq);
free_irq(pf->irq, pf);
module_put(iio_dev_opaque->driver_module);
return ret;
}
3.3、iio trigger的触发接口
iio trigger提供了trigger的触发接口,即为iio_trigger_poll(或者该函数的封装函数),该函数主要该trigger下所有已使能的irq,均调用generic_handle_irq,执行该中断的中断处理函数。该函数的实现如下所示:
drivers/iio/industrialio-trigger.c
/**
* iio_trigger_poll() - Call the IRQ trigger handler of the consumers
* @trig: trigger which occurred
*
* This function should only be called from a hard IRQ context.
*/
void iio_trigger_poll(struct iio_trigger *trig)
{
int i;
if (!atomic_read(&trig->use_count)) {
atomic_set(&trig->use_count, CONFIG_IIO_CONSUMERS_PER_TRIGGER);
for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) {
if (trig->subirqs[i].enabled)
generic_handle_irq(trig->subirq_base + i);
else
iio_trigger_notify_done_atomic(trig);
}
}
}
EXPORT_SYMBOL(iio_trigger_poll);