Linux IIO子系统分析3

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。主要内容如下:

  1. id表示trigger的id、name为名称;
  2. 该iio trigger也使用struct device类型的变量加入到iio总线上,iio trigger与iio device均注册到iio总线上,因此它们在sysfs目录下是同级的;
  3. list用于将struct iio trigger添加系统全局链表iio_trigger_list中;
  4. alloc_list主要用于同一类型的trigger可注册多个trigger实例的请求,如trigger-period则使用该变量将trigger插入到iio_prtc_trigger_list中,目前使用这一变量的trigger并不多;
  5. 使用计数use_count;
  6. 而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
+---------------------------------+
| 返回触发状态                    |
+---------------------------------+

详细流程解释

  1. 外部设备触发: 外部设备可以通过硬件中断、时间戳、条件检测等方式触发。触发事件可能是外部传感器状态变化、定时器到期等。

  2. iio_trigger_poll() 函数iio_trigger_poll() 是用于处理触发事件的函数。当外部设备触发了事件时,内核会调用 iio_trigger_poll() 来检查是否有任何需要执行的操作。

  3. 查找回调函数: 在 IIO 子系统中,每个触发器都有一个与之关联的回调函数(通常是设备驱动提供的函数)。该函数会被 iio_trigger_poll() 调用。

  4. 执行回调函数: 这些回调函数通常包括读取硬件数据、执行设备配置、更新设备状态等任务。例如,如果设备是一个传感器,回调函数可能会触发一次数据采集,将新数据写入缓冲区。

  5. 设备完成数据处理: 回调函数执行完后,设备的状态会得到更新,数据会被采集和存储。

  6. 通知用户空间: 如果需要,IIO 子系统将通过事件机制(如 iio_event)或者字符设备接口(如 iio_read())将新数据或事件传递给用户空间程序。

  7. 返回触发状态: 最后,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实现的机制,那看下需要如何实现代码以便完成上面的功能,这大概分为如下几个方面:

  1. 完成虚拟irq chip的创建及注册操作,而虚拟中断的个数则可通过make menuconfig进行修改,默认是2(CONFIG_IIO_CONSUMERS_PER_TRIGGER表示支持的consumer个数,也即是虚拟irq的个数);
  2. 提供虚拟irq的申请及注册接口;
  3. 提供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的绑定。而该函数实现如下几个功能:

  1. 若该consumer已经绑定了一个iio trigger,需要先解除与iio trigger的绑定(调用iio_trigger_detach_poll_func实现);
  2. 完成该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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值