嵌入式实践教程--设备树驱动下的中断开发

简介: 嵌入式实践教程--设备树驱动下的中断开发

一、上半部和下半部(顶半部和底半部)



上半部:我们在使用request_irq申请中断时注册的中断服务函数属于中断处理的上半部。适合耗时不长的程序。


下半部:适合处理过程耗时的代码。


①、 如果要处理的内容不希望被其他中断打断,可以放到上半部。

②、如果要处理的任务对时间敏感,可以放到上半部。

③、如果要处理的任务与硬件有关,可以放到上半部


1、软中断


Linux 内核使用结构体 softirq_action 表示软中断, softirq_action

结构体定义在文件 include/linux/interrupt.h 中


2、tasklet


tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和tasklet 之间,建议大家使用 tasklet。Linux 内核使用结构体


struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};


使用示例


/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data) {
 /* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id) {
 ......
 /* 调度 tasklet */
 tasklet_schedule(&testtasklet);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void) {
 ......
 /* 初始化 tasklet */
 tasklet_init(&testtasklet, testtasklet_func, data);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}


3、工作队列


工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。


使用示例


/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
 /* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id) {
 ......
 /* 调度 work */
 schedule_work(&testwork);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void) {
 ......
 /* 初始化 work */
 INIT_WORK(&testwork, testwork_func_t);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}


4、中断示例


(1)设备树修改


(2)主程序


(2.1)open函数


static int imx6uirq_open(struct inode *inode, struct file *filp)
{
  filp->private_data = imx6uirq;
  return 0;
}


在OPEN函数里添加中断结构体为私有数据


(2.2)read函数


static int imx6uirq_read(struct file *filp, char __user *buf, size_t cnt loff_t *offt)
{
  int ret = 0;
  unsigned char keyvalue = 0;
  unsigned char releasekey = 0;
  struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
  //加锁读值
  keyvalue = atomic_read(&dev->keyvalue);
  releasekey = atomic_read(&dev->releasekey);
  if(releasekey){
  if(keyvalue & 0x80){
    keyvalue &= ~0x80;
    ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));//传入用户层
  }else{
    goto data_error;
  }
  atomic_set(&dev->releasekey, 0);
  }else{
  goto data_error;
  }
  return 0;
data_error:
  return -EINVAL;
}


加原子锁读取按键值并复制到用户空间


(2.3)IO初始化


从设备树中提取GPIO然后设置成中断模式,根据中断处理函数和中断号申请中断。中断服务函数里负责处理定时器,再进入定时器函数里读取IO值,延时的目的为了消除按键抖动带来的误差。


static int keyio_init(void)
{
  unsigned char i = 0;
  char name[10];
  int ret = 0;
  imx6uirq.nd = of_find_node_by_path("key");
  if(imx6uirq.nd == NULL){
  printk("key node can not find!|r\n");
  return -EINVAL;
  }
  //提取GPIO
  for(i = 0; i< KEY_NUM; i++){
  imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", i);
  if(imx6uirq.irqkeydesc[i]<0){
    printk("can not get key%d\r\n", i);
  }
  }
  //初始化IO ,设置中断模式
  for(i = 0; i < KEY_NUM; i++){
  memset(imx6uirq.irqkeydesc[i].name,0, sizeof(name));
  sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
  gpio_request(imx6uirq.irqkeydesc[i].gpio, name);//获取gpio
  gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);//设置为输出
  imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);//获取中断号并设置
  //申请中断
  imx6uirq.irqkeydesc[0].handler = key0_handler;
  imx6uirq.irqkeydesc[0].value = KEY0VALUE;
  for(i=0; i<KEY_NUM; i++){
    ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
        imx6uirq.irqkeydesc[i].handler,
        IRQ_TRIGGER_EDGE_FALLING|IRQ_TRIGGER_EDGE_RISING,
        imx6uirq.irqkeydesc[i].name, &imx6uirq);
    if(ret < 0){
    printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
    return -EFAULT;
    }    
  }
  //创建定时器
  init_timer(&imx6uirq.timer);
  imx6uirq.timer.function = timer_function;
  return 0;
  }
}
void timer_function(unsigned long arg)
{
  unsigned char value;
  unsigned char num;
  struct irq_keydesc *keydesc;
  struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
  num = dev->cur_keynum;
  keydesc = &dev->irqkeydesc[num];
  //
  value = gpio_get_value(keydesc->gpio);  //读取IO值
  if(value == 0){
  atomic_set(&dev->keyvalue, keydesc->value); //按下按键
  }else{
  atomic_set(&dev->keyvalue, 0x80);   //按键松开
  atomic_set(&dev->releasekey, 1);    //标记松开
  }
}


总结中断的使用:驱动入口函数里正常注册字符设备后,进行按键IO的初始化,进入IO初始化函数里,获取设备树IO并设置中断模式、中断的处理函数然后申请中断、创建定时器读取按键值。

相关文章
|
Linux
一文搞懂 USB 设备端驱动框架
hello 大家好,今天带领大家学习一下USB 设备端驱动 内核版本:4.4.94
824 0
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十)LED模板驱动程序的改造:总线设备驱动模型
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十)LED模板驱动程序的改造:总线设备驱动模型
203 1
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十)LED模板驱动程序的改造:总线设备驱动模型
|
传感器 网络协议 Linux
ARM嵌入式学习笔记——《设备驱动基础》(三)
ARM嵌入式学习笔记——《设备驱动基础》
176 0
|
Linux
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十九)驱动进化之路:总线设备驱动模型
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十九)驱动进化之路:总线设备驱动模型
144 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十九)驱动进化之路:总线设备驱动模型
|
Ubuntu Linux 程序员
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(中)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程
706 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(中)
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(下)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程
203 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(下)
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(上)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程
364 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(上)
嵌入式实践教程--设备树下的LED驱动开发
嵌入式实践教程--设备树下的LED驱动开发
嵌入式实践教程--设备树下的LED驱动开发
|
Linux API
嵌入式实践教程--设备树下的input子系统驱动开发
嵌入式实践教程--设备树下的input子系统驱动开发
嵌入式实践教程--设备树下的input子系统驱动开发
|
传感器 Linux SoC
嵌入式实践教程--设备树下的platform驱动
嵌入式实践教程--设备树下的platform驱动
嵌入式实践教程--设备树下的platform驱动