Linux驱动学习day9(异常与中断处理)
异常与中断处理(中断是一种异常)
ARM对异常(中断)的处理
1、(a)设置中断源,让其可以产生中断。(b)设置中断控制器(屏蔽/优先级)。(c)打开CPU总开关(使能中断)。
2、执行程序
3、产生中断:按下按键-->发送信号-->中断控制器-->cpu。
4、CPU每执行完一段指令都会检查有无中断异常产生。
5、发现有中断异常产生,开始处理,对于不同的异常跳去不同的地址执行程序。这些地址上只是一条跳转指令,跳去执行某个指令。(地址-->异常向量)
6、处理中断异常(保存现场(各种寄存器)、处理异常中断、恢复现场)。
linux系统对中断的处理
进程、线程、中断的核心是:栈
Linux系统中资源分配的单位是进程,调度的单位是线程。
进程调度的核心
Linux对硬件中断处理的原则:中断不能嵌套,中断处理函数越快越好。
使用 线程来处理中断下半部流程,1、构造work结构体,里面含有.func函数 2、在中断上半部将结构体work放入work queue中,这样就可以在下半部开启一个内核线程去处理耗时间的中断部分。
内核源码
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}/* handler是中断的上半部分,可以为空 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id)
{struct irqaction *action;struct irq_desc *desc;int retval;if (irq == IRQ_NOTCONNECTED)return -ENOTCONN;/** Sanity-check: shared interrupts must pass in a real dev-ID,* otherwise we'll have trouble later trying to figure out* which interrupt is which (messes up the interrupt freeing* logic etc).** Also IRQF_COND_SUSPEND only makes sense for shared interrupts and* it cannot be set along with IRQF_NO_SUSPEND.*/if (((irqflags & IRQF_SHARED) && !dev_id) ||(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))return -EINVAL;desc = irq_to_desc(irq);if (!desc)return -EINVAL;if (!irq_settings_can_request(desc) ||WARN_ON(irq_settings_is_per_cpu_devid(desc)))return -EINVAL;if (!handler) {if (!thread_fn)return -EINVAL;handler = irq_default_primary_handler;}action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);if (!action)return -ENOMEM;action->handler = handler;action->thread_fn = thread_fn;action->flags = irqflags;action->name = devname;action->dev_id = dev_id;retval = irq_chip_pm_get(&desc->irq_data);if (retval < 0) {kfree(action);return retval;}retval = __setup_irq(irq, desc, action);if (retval) {irq_chip_pm_put(&desc->irq_data);kfree(action->secondary);kfree(action);}#ifdef CONFIG_DEBUG_SHIRQ_FIXMEif (!retval && (irqflags & IRQF_SHARED)) {/** It's a shared IRQ -- the driver ought to be prepared for it* to happen immediately, so let's make sure....* We disable the irq to make sure that a 'real' IRQ doesn't* run in parallel with our fake.*/unsigned long flags;disable_irq(irq);local_irq_save(flags);handler(irq, dev_id);local_irq_restore(flags);enable_irq(irq);}
#endifreturn retval;
}
EXPORT_SYMBOL(request_threaded_irq);
新技术
int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id)/* irq:哪个中断,虚拟中断号handler是中断的上半部分,可以为空,完全使用线程来处理中断thread_fn在线程里运行的中断处理函数
*/
流程是这样,当中断发生时,就会调用handler,中断上半部,上半部分执行完毕之后会为thread_fn函数创建一个内核线程,内核线程就会执行这个下半部函数。(只需要提供一个thread_fn,系统会为函数创建一个内核线程)。
硬件中断发生的过程图
软件处理流程(和硬件相反)
按照硬件产生中断的流程图,来理解一下软件处理的流程,当按键按下,或者网卡产生中断,传到GIC中断控制器,中断控制器发送中断给CPU,当CPU收到中断之后,发现是GIC中的A号中断,会查询irq_desc这个数组中的第A号中断的结构体,调用A号中断处理函数handle_irq(读取GPIO引脚,确定是GPIO中断 , 其实就是读取GPIO寄存器得到hwirq ,根据hwirq得到之前映射的irq),调用B号中断处理函数handle_irq,如果这是一个共享中断的话遍历结构体中的action 链表,依次调用每个设备的中断处理函数handle_irq,执行完handle_irq内核会唤醒内核线程执行thread_fn确定是否是自己产生的中断,是的话去处理中断。
中断设备树语法
设备树语法:interrupt-parent指定的是使用哪一组GPIO模块,interrupts指定的是哪一个引脚(硬件中断号 , hwirq)以及触发中断的方式。
内核会使用irq_domian结构体中.xlate和.map函数,.xlate函数解析设备树,.map函数将hwirq(硬件中断号)转化为irq(虚拟中断号)。irq会保存到platform_device结构体中,这样就可以使用request_irq注册中断,给中断提供中断处理函数。(hwirq和irq会保存在irq_domain结构体里面,当发生某个硬件中断时,可以读取寄存器去确定hwirq,根据hwirq可以从数组重找到对应的irq)
设备树中指定中断
GIC、GPIO等模块的中断控制器BSP工程师已经写好了,我们只需要指定使用哪个中断即可。
在设备树中描述一个中断控制器需要三个属性1、compatible属性 2、interrupt-controller 3、#interrupt-cell = <>。如果是GPIO模块需要1、compatible属性 2、interrupt-controller 3、#interrupt-cell = <> 4、使用上一级哪一个中断控制器的哪一个中断。5、interrupt-parent = <&xxx> 6、interrupts = <>。
作为用户需要指定 使用上一级哪一个中断控制器的哪一个中断。(interrupt-parent = <&xxx> interrupts = <>)。如上图所示。也可以使用interrupts-extended = <&intcl 5 1><&intc2 1 0>代替上面两个属性。
获取中断号
如果该设备节点能够生成platform_device,直接使用platform_get_resource函数。type为IORESOURCE_IRQ
/*** platform_get_resource - get a resource for a device* @dev: platform device* @type: resource type* @num: resource index*/
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
{u32 i;for (i = 0; i < dev->num_resources; i++) {struct resource *r = &dev->resource[i];if (type == resource_type(r) && num-- == 0)return r;}return NULL;
}
对于I2C、SPI设备下很多子结点无法生成platform_device。在这些子节点中一个设备会被转化为相应的device结构体(eg spi_device i2c_client),这时候可以使用of_irq_get函数解析设备树,将中断信息取出转换成中断号。
使用of_irq_get函数解析设备树,获取中断号。
/*** of_irq_get - Decode a node's IRQ and return it as a Linux IRQ number* @dev: pointer to device tree node* @index: zero-based index of the IRQ** Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or* -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case* of any other failure.*/
int of_irq_get(struct device_node *dev, int index)
{int rc;struct of_phandle_args oirq;struct irq_domain *domain;rc = of_irq_parse_one(dev, index, &oirq);if (rc)return rc;domain = irq_find_host(oirq.np);if (!domain)return -EPROBE_DEFER;return irq_create_of_mapping(&oirq);
}
对于GPIO模块,可以使用gpiod_to_irq函数获得中断号,在设备节点不需要指定中断号。
假设设备树结点如下:
my_gpio_keys{compatible = "my,my_gpio_key";gpios = <&gpio5 1 GPIO_ACTIVE_HIGH&gpio4 14 GPIO_ACTIVE_HIGH>;pinctrl-names = "default";pinctrl-0 = <&key1_pinctrl&key2_pinctrl>;
}
使用中断的按键驱动程序
主要部分代码详解:获取中断号、注册中断处理函数
获取gpio个数 static inline int of_gpio_count(struct device_node *np);
内核指针开辟内存static inline void *kzalloc(size_t size, gfp_t flags);
获取GPIO引脚编号同时将设备树中的flags取出来static inline int of_get_gpio_flags(struct device_node *np, int index, enum of_gpio_flags *flags);
获取irq中断号static inline int gpio_to_irq(unsigned int gpio);
读取按键值static inline int gpio_get_value(unsigned int gpio);
struct gpio_inf
{int gpio;int irq;enum of_gpio_flags flag;
}static strcut gpio_inf *gpio_if;static int chip_demo_gpio_probe(struct platform_device *pdev)
{int count , i;enum of_gpio_flags flag;struct device_node *node;node = pdev->dev.of_node;/* 有几个中断引脚 */count = of_gpio_count(node);/* 开辟内存 */gpio_if = kzalloc(count * sizeof(struct gpio_inf) , GFP_KERNEL);/*获取GPIO编号和irq中断号*/for(i = 0 ; i < count ; i++){gpio_if[i].gpio = of_get_gpio_flags(node , i , &flag);gpio_if[i].flag = flag;gpio_if[i].irq = gpio_to_irq(gpio);/* 得到irq就可以使用irq_request 注册中断*/ irq_requset(gpio_if[i].irq , my_key_handler , IRQ_TRIGGER_RISING | IRQF_TRIGGER_GALLING , "my_key" , &gpio_if[i]);}return 0;
}
#include <linux/module.h> // 最基本模块宏
#include <linux/kernel.h> // printk
#include <linux/init.h> // __init/__exit
#include <linux/fs.h> // register_chrdev 等
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/types.h> // dev_t, bool 等类型
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/gfp.h>
#include <linux/of_irq.h>struct gpio_inf{int gpio;int irq;enum of_gpio_flags flag;
};static struct gpio_inf * gpio_if;static const struct of_device_id my_key[] = {{ .compatible = "my,myled" },{},
};static irqreturn_t my_key_handler(int irq, void *dev_id)
{struct gpio_inf * gf = (struct gpio_inf *)dev_id;printk("key %d value%d\n" , irq , gpio_get_value(gf->gpio));return IRQ_HANDLED;
}static int chip_demo_gpio_probe(struct platform_device *pdev)
{int count;int i;int err;struct device_node *node;enum of_gpio_flags flag;printk("%s %s %d \n", __FILE__ , __FUNCTION__ , __LINE__);node = pdev->dev.of_node;count = of_gpio_count(node);gpio_if = kzalloc(count * sizeof(struct gpio_inf), GFP_KERNEL);for(i = 0 ; i < count ; i++){/* get gpio */gpio_if[i].gpio = of_get_gpio_flags(node, i, &flag);/* gpio to irq :static inline int gpio_to_irq(unsigned int gpio)*/gpio_if[i].irq = gpio_to_irq(gpio_if[i].gpio);gpio_if[i].flag = flag;err = request_irq(gpio_if[i].irq, my_key_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "my_key", &gpio_if[i]);}return 0;
}static int chip_demo_gpio_remove(struct platform_device *pdev)
{int count;int i;struct device_node *node;printk("%s %s %d \n", __FILE__ , __FUNCTION__ , __LINE__);node = pdev->dev.of_node;count = of_gpio_count(node);for(i = 0 ; i < count ; i++){free_irq(gpio_if[i].irq, &gpio_if[i]);}return 0;
}static struct platform_driver my_key_drv={.probe = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "my_key_drv",.of_match_table = my_key,}
};static int gpio_key_drv_init(void)
{int err;err = platform_driver_register(&my_key_drv);return 0;
}static void gpio_key_drv_exit(void)
{platform_driver_unregister(&my_key_drv);
}module_init(gpio_key_drv_init);
module_exit(gpio_key_drv_exit);
MODULE_LICENSE("GPL");
上机测试(设备树)
1、需要通过pinctrl将该引脚配置为GPIO模式。
2、表明自身使用哪一个引脚。
进入到/home/dd/RK3568/SDK/linux/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchi目录下,编辑rk3568-pinctrl.dtsi pinctrl子系统
key_gpios{/omit-if-no-ref//* lable : 节点名_pins*//* lable 是在设备树中被引用的 */key_pins:key_pins {rockchip,pins =<3 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>;};};
编写规则
key_gpios:逻辑包裹节点,便于分类,一般可以省略或改名为 keys、buttons。/omit-if-no-ref/:如果没有地方引用该节点,编译时自动省略,避免无效配置进入最终设备树。key_pins: 是 label,可被 pinctrl-0 = <&key_pins>; 引用。key-pins 是节点名称,约定俗成为 xxx-pins,用于组织。
然后修改设备树文件rk3568-atk-evb1-ddr4-v10.dtsi,添加一个结点
key{compatible = "my,mybutton";pinctrl-0 = <&key_pins>;pinctrl-names = "default";key-gpio = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;status = "okay";};