当前位置: 首页 > news >正文

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";};
http://www.lqws.cn/news/543691.html

相关文章:

  • 华为云Flexus+DeepSeek征文|基于Dify构建故事绘本制作工作流
  • Spark 写入hive表解析
  • Spring Boot项目开发实战销售管理系统——系统设计!
  • 知名流体控制解决方案供应商“永盛科技”与商派ShopeX达成B2B商城项目合作
  • iOS 远程调试与离线排查实战:构建非现场问题复现机制
  • 报道称CoreWeave洽谈收购Core Scientific,后者涨超30%
  • NV025NV033美光固态闪存NV038NV040
  • 《二分枚举答案(配合数据结构)》题集
  • Python Selenium 滚动到特定元素
  • Selenium基本用法
  • Spring Boot 性能优化与最佳实践
  • 6.27_JAVA_面试(被抽到了)
  • 洛谷P5021 [NOIP 2018 提高组] 赛道修建
  • 深入理解 Linux `poll` 模型:`select` 的增强版
  • 记录一次飞书文档转md嵌入vitepress做静态站点
  • 微信小程序进度条progress支持渐变色
  • Stable Diffusion入门-ControlNet 深入理解-第三课:结构类模型大揭秘——深度、分割与法线贴图
  • 【LeetCode 热题 100】42. 接雨水——(解法三)单调栈
  • FPGA在嵌入式图像处理中的深度应用!
  • 深圳中青宝互动网络股份有限公司游戏运维工程师面试题(笔
  • python实战项目79:采集知乎话题下的所有回答
  • 【用户权限】超级用户(二)
  • win7实现永恒之蓝ms17_010漏洞之445端口
  • matlab实现相控超声波成像
  • 推荐一个基于C#开发的跨平台构建自动化系统!
  • 通信无BUG,ethernet ip转profinet网关,汽车焊接设备通信有心机
  • 面向大语言模型幻觉的关键数据集:系统性综述与分类法_DEEPSEEK
  • Spring Boot整合Redis指南
  • 从电费追缴到碳减排:一个预付费系统如何重塑校园能源生态
  • 使用 Vcpkg 安装 Qt 时的常见问题与解决方法