linux gpio_to_irq()源码分析

本文详细解析了Linux系统中从GPIO编号转换到软件中断号的机制,介绍了gpio_to_irq()函数的工作原理,深入分析了其内部实现过程,包括gpio_to_desc()和gpiod_to_irq()的调用流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) 

linux gpio_to_irq()源码分析

背景说明:
  在Linux设备树(linux 3.x版本引入)中, 设备的中断号不再在"irq.h"中硬编码定义, 而是在需要时自己手动去申请获得对应的硬件中断的软件中断号.( 前提是GPIO的相关模块已经被编入内核 )

1. gpio_to_irq()的函数作用

/**
 * include/linux/gpio.h
 * 
 * @param gpio 为要操作的GPIO编号, 该编号等于GPIO组号*8 + 组内偏移号, 例如GPIO4_2的编号为4*8 + 2 = 34 (每组GPIO有8个GPIO管脚)
 */ 
static inline int gpio_to_irq(unsigned int gpio)
{
	return __gpio_to_irq(gpio);
}

   通过GPIO号得到对应的软件中断号, 该中断号是request_irq()函数的第一个参数.

2. gpio_to_irq()的源码分析

/**
 * include/asm-generic/gpio.h
 */
static inline int __gpio_to_irq(unsigned gpio)
{
	return gpiod_to_irq(gpio_to_desc(gpio));
}
2.1 首先, 我们来看看gpio_to_desc(gpio)的含义:
/**
 * list_for_each_entry	-	iterate over list of given type
	迭代给定类型的列表, head为typeof(*pos)结构体中的其中一员且为一个双向链表, 我们通过
遍历这个链表循环得到每一个typeof(*pos)结构体.
 * @pos:	the type * to use as a loop cursor.
	用作循环游标, 每一次得到typeof(*pos)类型的变量.
 * @head:	the head for your list.
	列表头, 该变量为typeof(*pos)结构体中的其中一员.
 * @member:	the name of the list_head within the struct.
	在结构体中list_head的名称
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))


/**
 * Convert a GPIO number to its descriptor
 */
struct gpio_desc *gpio_to_desc(unsigned gpio)
{
	struct gpio_device *gdev;
	unsigned long flags;

	/**
	 * 内核同步自旋锁.
	 * 禁止内核抢占, 关闭中断, 保存中断状态寄存器的标志位.
	 */
	spin_lock_irqsave(&gpio_lock, flags);

	list_for_each_entry(gdev, &gpio_devices, list) {
		if (gdev->base <= gpio &&
		    gdev->base + gdev->ngpio > gpio) {
		    /**
		     * 当查找到gpio所属的组时, 返回该GPIO的descs, 即&gdev->descs[gpio - gdev->base]
		     */
			spin_unlock_irqrestore(&gpio_lock, flags);
			return &gdev->descs[gpio - gdev->base];
		}
	}

	spin_unlock_irqrestore(&gpio_lock, flags);

	if (!gpio_is_valid(gpio))
		WARN(1, "invalid GPIO %d\n", gpio);

	return NULL;
}

关键处: 当查找到gpio所属的组时, 返回该GPIO的descs, 即&gdev->descs[gpio - gdev->base].
在这里, 我们拿到了该GPIO的描述信息. 该结构体的定义如下:(drivers/gpio/gpiolib.h)

/**
 * GPIO设备的内部状态容器.
 */
struct gpio_device {
	int			id;
	struct device		dev;
	struct cdev		chrdev;
	struct device		*mockdev;
	struct module		*owner;
	struct gpio_chip	*chip;
	struct gpio_desc	*descs;
	int			base;
	u16			ngpio;
	char			*label;
	void			*data;
	struct list_head        list;

#ifdef CONFIG_PINCTRL
	/*
	 * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
	 * describe the actual pin range which they serve in an SoC. This
	 * information would be used by pinctrl subsystem to configure
	 * corresponding pins for gpio usage.
	 */
	struct list_head pin_ranges;
#endif
};



struct gpio_desc {
	struct gpio_device	*gdev;
	unsigned long		flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED	0
#define FLAG_IS_OUT	1
#define FLAG_EXPORT	2	/* protected by sysfs_lock */
#define FLAG_SYSFS	3	/* exported via /sys/class/gpio/control */
#define FLAG_ACTIVE_LOW	6	/* value has active low */
#define FLAG_OPEN_DRAIN	7	/* Gpio is open drain type */
#define FLAG_OPEN_SOURCE 8	/* Gpio is open source type */
#define FLAG_USED_AS_IRQ 9	/* GPIO is connected to an IRQ */
#define FLAG_IS_HOGGED	11	/* GPIO is hogged */

	/* Connection label */
	const char		*label;
	/* Name of the GPIO */
	const char		*name;
};
2.2 然后, 我们来看看gpiod_to_irq(const struct gpio_desc *desc);的含义:
/**
 * gpiod_to_irq() - return the IRQ corresponding to a GPIO
 * @desc: gpio whose IRQ will be returned (already requested)
 *
 * Return the IRQ corresponding to the passed GPIO, or an error code in case of
 * error.
 */
int gpiod_to_irq(const struct gpio_desc *desc)
{
	struct gpio_chip *chip;
	int offset;

	/*
	 * Cannot VALIDATE_DESC() here as gpiod_to_irq() consumer semantics
	 * requires this function to not return zero on an invalid descriptor
	 * but rather a negative error number.
	 */
	if (!desc || IS_ERR(desc) || !desc->gdev || !desc->gdev->chip)
		return -EINVAL;

	chip = desc->gdev->chip;
	offset = gpio_chip_hwgpio(desc);
	if (chip->to_irq) {
		int retirq = chip->to_irq(chip, offset);

		/* Zero means NO_IRQ */
		if (!retirq)
			return -ENXIO;

		return retirq;
	}
	return -ENXIO;
}
<think>我们正在讨论LinuxGPIO子系统,用户的问题是如何通过GPIO描述符(structgpio_desc)反向查找对应的GPIO编号(即全局的GPIO号,也就是我们常说的gpionumber,比如gpio0,gpio1等)。在Linux内核中,GPIO子系统使用描述符(gpio_desc)来抽象GPIO引脚。每个GPIO描述符对应一个具体的GPIO引脚。有时候,我们有一个gpio_desc结构体指针,需要获取它所对应的全局GPIO编号。根据GPIO子系统的实现(主要参考drivers/gpio/gpiolib.c),我们可以通过以下方式获取:1.每个gpio_desc结构体都包含一个指向gpio_chip的指针(即GPIO控制器),以及在该控制器内部的偏移量(即在该组GPIO中的偏移)。2.全局的GPIO编号(gpionumber)是由GPIO子系统在注册GPIO控制器(gpio_chip)时动态分配的。每个gpio_chip会分配一段连续的全局GPIO号,起始号为base,结束号为base+ngpio-1。因此,如果我们有一个gpio_desc,那么可以通过以下步骤获取全局GPIO编号:全局GPIO编号=gpio_chip->base+gpio_desc在该gpio_chip中的偏移量但是,我们需要注意:gpio_desc结构体中并没有直接存储全局GPIO编号,而是存储了在gpio_chip中的偏移(我们通常称之为hwirq,即硬件相关的编号)。同时,gpio_chip结构体中有一个base字段,表示该控制器分配的起始全局编号。在gpiolib中,提供了一个函数`desc_to_gpio`来实现这个功能:```cintdesc_to_gpio(conststructgpio_desc*desc){if(!desc)return-EINVAL;returndesc->gdev->base+(desc-&desc->gdev->descs[0]);}```这个函数在drivers/gpio/gpiolib.c中定义(具体位置可能随内核版本变化,但功能一致)。它的作用是:-首先检查desc是否有效。-然后通过desc找到它所属的gpio_device(在desc->gdev中)。gpio_device结构体包含了gpio_chip以及该控制器所分配的全局GPIO基地址(base)。-计算该desc在gpio_device的descs数组中的索引(偏移量),然后加上基地址base,就得到了全局GPIO编号。注意:在较新的内核版本中,gpio_desc结构体是包含在gpio_device结构体中的,gpio_device结构体包含一个descs数组,该数组的大小等于该控制器支持的GPIO数量。因此,我们可以直接使用这个函数来获取全局GPIO编号。示例代码:```c#include<linux/gpio/consumer.h>//包含gpio_desc的定义#include<linux/gpio.h>//包含desc_to_gpio的声明structgpio_desc*desc;intgpio_num;//假设desc已经通过某种方式获得(例如通过gpiod_get获取)gpio_num=desc_to_gpio(desc);if(gpio_num<0){//错误处理}else{//使用gpio_num}```注意:desc_to_gpio函数返回的gpio_num就是全局的GPIO编号,我们可以用这个编号去调用一些旧的基于编号的GPIO函数(如gpio_set_value等),但不推荐在新代码中使用,因为新代码推荐使用基于描述符的API。另外,需要特别注意的是,这个函数只有在GPIO描述符已经被正确请求(request)之后才能使用,因为只有请求后,GPIO描述符才与具体的GPIO引脚关联。但是,实际上,desc_to_gpio函数并不检查是否已经请求,它只是计算编号。所以即使没有请求,它也会返回正确的编号,因为描述符在创建时就已经与一个固定的引脚关联了。然而,在驱动中,我们通常不会随意获取一个未请求的描述符,因为那样可能该描述符并没有被正确初始化或者已经被释放。总结:通过desc_to_gpio函数,我们可以从gpio_desc获取全局GPIO编号。参考文献:[^1]:Linux内核源码:drivers/gpio/gpiolib.c[^2]:LinuxGPIO子系统文档:Documentation/gpio/</think>### 通过 GPIO 描述符获取 GPIO 编号的方法 在 Linux GPIO 子系统中,可以通过 `gpio_desc` 结构体反向获取对应的 GPIO 全局编号。以下是实现方法及原理: #### 核心函数:`desc_to_gpio()` Linux 内核提供了专用函数实现此转换: ```c #include <linux/gpio/consumer.h> int desc_to_gpio(const struct gpio_desc *desc); ``` **返回值**: - 成功:返回全局 GPIO 编号(非负整数) - 失败:返回错误码(如 `-EINVAL` 表示无效描述符) --- #### 实现原理分析 1. **数据结构关系** GPIO 子系统通过两层结构管理引脚: ```mermaid graph LR gpio_desc -->|指向| gpio_device gpio_device -->|包含| gpio_chip gpio_chip -->|维护| base[全局基地址] ``` 2. **转换公式** 全局 GPIO 编号计算本质是地址偏移转换: $$ \text{gpio_number} = \text{gpio_chip->base} + (\text{desc} - \text{gdev->descs}) $$ 其中: - `gpio_chip->base`:控制器分配的全局起始编号 - `desc`:目标描述符指针 - `gdev->descs`:控制器描述符数组起始地址 3. **内核源码实现**(drivers/gpio/gpiolib.c) ```c int desc_to_gpio(const struct gpio_desc *desc) { struct gpio_device *gdev = desc->gdev; return gdev->base + (desc - &gdev->descs[0]); } ``` --- #### 使用示例 ```c struct gpio_desc *led_desc; // 已初始化的GPIO描述符 /* 通过设备树获取描述符 */ led_desc = gpiod_get(dev, "led", GPIOD_OUT_HIGH); /* 转换为全局GPIO编号 */ int gpio_num = desc_to_gpio(led_desc); if (gpio_num < 0) { dev_err(dev, "转换失败: %d\n", gpio_num); } else { pr_info("GPIO编号: %d\n", gpio_num); // 可兼容旧API:gpio_set_value(gpio_num, 1); } ``` --- #### 关键注意事项 1. **有效性验证** 转换前需确保描述符已通过 `gpiod_get()` 等接口正确获取,未初始化的描述符会导致错误。 2. **生命周期管理** 全局编号仅在描述符有效期内稳定,若控制器被卸载,编号可能失效。 3. **新代码推荐** 优先使用基于描述符的新 API(如 `gpiod_set_value()`),避免直接操作全局编号[^4][^5]。 4. **设备树关联** 描述符与设备树中定义的 GPIO 属性直接对应,例如: ```dts leds { compatible = "gpio-leds"; led1 { gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; // 控制器gpio1, 偏移5 }; } ``` 此时获取的描述符对应全局编号 `= gpio1_base + 5`[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安河桥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值