驱动学习笔记5 混杂设备驱动,内核中断编程

1.linux内核混杂设备驱动


1.1.混杂设备定义


    从软件实现角度去看,混杂设备本质还是字符设备
    只是它的主设备号由内核已经定义好为10
    各个混杂设备驱动通过次设备号来区分


1.2.linux内核描述混杂设备的结构体


    struct miscdevice {
        const char *name;
        int minor;
        const struct file_operations *fops;
        ...
    };
    功能:描述混杂设备属性
    name:混杂设备文件名,并且由linux自动帮你创建,无需调用四个函数
    minor:给混杂设备指定的次设备号,一般指定宏:MISC_DYNAMIC_MINOR
        表示让linux内核帮你分配一个次设备号
    fops:混杂设备的硬件操作接口
    
    配套函数:
    misc_register(&混杂设备对象)
    功能:向内核注册一个混杂设备对象
    misc_deregister(&混杂设备对象);
    功能:从内核中卸载混杂设备对象

1.3.案例:编写按键的混杂设备驱动,实现能够获取按键的操作状态,打印按键按下或者松开


参考代码:day05/1.0

头文件,定义,初始化

定义接口函数,定义硬件操作接口对象,定义设备驱动对象

驱动入口,出口函数

%s/led/btn/g  在Makefile中将所有led替换为btn
下位机测试:
cd /home/drivers/
insmod btn_drv.ko 
ls -l /dev/mybtn
./btn_test //然后操作按键观察打印信息


然后去上位机把btn_test.c的打印函数printf注释掉,然后交叉编译拷贝,下位机继续测试
./btn_test & //让btn_test后台运行,继续执行命令
ps  //查看后台运行的btn_test
top //观察btn_test的CPU占用率


按Q键退出top命令
结论:发现btn_test占用了100%的CPU资源,太浪费CPU资源了
此问题表面看是应用程序造成的,但实际是驱动程序太烂了!
问:如何解决此问题,必须采用轮询的死对头中断方式来解决

混杂设备驱动与字符设备驱动的对比

混杂设备驱动:代码量少,无法用次设备号区分多个硬件个体

字符设备驱动:可以通过次设备号管理多个硬件个体

字符设备驱动:                                                         混杂设备驱动:

定义设备号变量                                                         

static dev_t dev;

定义设备驱动对象                                                     定义初始化设备驱动对象,完成2.3.6

static struct cdev led_cdev;                                       static struct miscdevice btn_misc

定义硬件操作接口对象                                              定义硬件操作接口对象 

static struct file_operations led_fops={                      static struct file_operations btn_fops={

};                                                                                 };

定义设备类对象指针 

static struct class *cls;

入口函数                                                                        入口函数 

static int led_init(void){                                                   static int btn_init(void){

1.申请GPIO资源,配置                                                  1.申请GPIO资源,配置

2.申请设备号(用到变量dev)

alloc_chrdev_region(&dev,0,4,"tarena);

3.初始化设备驱动对象,关联接口对象(用到led_cdev,led_fops)

cdev_init(&led_cdev, &led_fops);

4.注册设备驱动对象                                                        2.注册混杂设备驱动对象

5.创建设备类对象

6.创建设备文件

2.linux内核中断编程  


2.1.计算机中为什么有中断机制


    就一句话:CPU的数据处理速度要远远快于外设的数据处理速度所以采用中断
    面试时:一定要举例子阐述中断诞生的本质原因:
    以CPU读取UART接收缓冲区数据为例:
    1.当CPU读取UART接收缓冲区数据时,发现数据还没有准备就绪,首先想到采用轮询方式,
    也就是CPU什么事情都不干,原地死等直到UART接收缓冲区数据准备就绪,然后CPU将数据读走
    此等待过程CPU做大量无用功,降低了CPU的利用率,CPU只能干一件事
    2.所以对于此种情况立刻想到轮询的死对头中断方式,当CPU读取UART接收缓冲区数据时,
    发现数据还没有准备就绪,CPU就去干别的事情(处理一个其他程序),一旦将来UART接收缓冲区数据
    准备就绪,UART控制器就会给CPU核发送一个中断电信号,通知CPU核数据准备就绪了,CPU核立刻
    停止当前执行的这个程序转去处理UART接收缓冲区的数据,处理完毕,CPU核再次回到原先被打断的
    程序中继续执行,等待着下一次UART通知它,此过程CPU至少做两件事,大大提高了CPU利用率
    并且CPU没有做无用功!


2.2.然后谈谈轮询和中断各自的应用场景


    轮询方式应用于等待时间极短的场合,例如:纳秒,微秒,10毫秒以内
    中断方式应用于等待时间较长或者随机场合,例如:毫秒,秒或者随机场合

2.3.linux系统中,中断处理都是在内核空间完成,将来编写的中断处理函数永远运行在内核空间


问:linux内核如何编写一个外设的硬件中断处理函数呢?
一旦编写完成,就可以静静等待着硬件中断触发,然后调用其中断处理函数了!
答:利用大名鼎鼎的request_irq/free_irq来完成

2.4.详解linux内核中断编程的两个重要函数:


函数原型:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
功能:在linux内核中,处理器的任何一个硬件中断资源对于linux内核来说都是一种宝贵的资源
    如果驱动想要访问某个硬件中断资源,必须先向内核申请这个硬件中断资源
    一旦申请成功,然后向内核注册这个硬件中断对应的中断处理函数,一旦注册成功,静静等待着
    硬件中断触发,一旦触发,将来内核自动调用注册的中断处理函数
    所以此函数完成两个工作:
    1.申请硬件中断资源
    2.注册中断处理函数
参数:
irq:在linux内核中,linux内核给每个硬件中断都分配一个软件编号,类似硬件中断的身份证号,简称中断号
    中断号由GPIO编号经过gpio_to_irq函数进行换算而得:
    硬件GPIO        GPIO编号        中断号
    GPIOA28        PAD_GPIO_A+28    gpio_to_irq(PAD_GPIO_A + 28);
    GPIOB9        PAD_GPIO_B+9    gpio_to_irq(PAD_GPIO_B + 9);
    ...        ...        ...
    所以第一个参数irq就是传递要申请的硬件中断的中断号
handler:传递要注册的中断处理函数,其实就是传递要注册的中断处理函数名即可(函数名就是函数地址),
    一旦注册完毕,静静等待硬件中断触发,一旦触发,内核自动调用此函数
    编写一个中断处理函数例子:
    irqreturn_t  中断处理函数名(int  irq, void *dev) {
        //根据用户需求编写中断处理函数
        ....
        return IRQ_NONE; //中断处理函数执行失败
        或者
        return IRQ_HANDLED; //中断处理函数执行成功    
    }
    注意中断处理函数形参问题:
    irq:保存当前触发中断的中断号,例如:GPIOA28产生的中断,irq=gpio_to_irq(PAD_GPIO_A+28);
    dev:保存给中断处理函数传递的参数
        问:谁来传递参数呢?
        答:request_irq函数的最后一个形参(void *dev)用来给中断处理函数传递参数
        而中断处理函数的第二个形参dev来保存传递的参数!
flags:指定中断触发的类型
    对于外部中断(中断线在原理图上可见),flags一般指定以下宏:
    IRQF_TRIGGER_HIGH:指定为高电平触发
    IRQF_TRIGGER_LOW:指定为低电平触发
    IRQF_TRIGGER_FALLING:指定为下降沿触发
    IRQF_TRIGGER_RISING:指定为上升沿触发
    IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING:指定为双边沿触发
    对于内部中断(就是各种控制器给中断控制器发送的中断,此中断线不可见),一律给0
name:指定中断的名称,将来可以通过cat /proc/interrupts查看注册的中断名称,可以用于调试
dev:给中断处理函数传递的参数,如果不传递参数给NULL
    回想创建线程:
    void *thread_func(void *dev) {
        //dev=&data
        printf("data = %d\n", *(int *)dev);
    }
    pthread_t  tid;
    int data = 520;
    pthread_create(&tid, NULL, thread_func, &data/*给thread_func传递的参数*//);    

free_irq函数原型:
void free_irq(int irq, void *dev)
功能:如果驱动不再使用某个硬件中断资源,必须要释放这个硬件中断资源并且删除之前注册的中断处理函数
参数:
irq:传递要释放的硬件中断的中断号
dev:传递给中断处理函数的参数,此参数务必要和request_irq的最后一个参数保持一致,否则系统崩溃
案例:利用中断实现按键驱动,操作按键能够打印按键的状态
参考代码:day05/2.0

上位机执行:
配置linux内核去除官方的按键驱动:
cd /opt/kernel
make menuconfig
    Device Drivers->
        Input device support->
            <*>Keyboard->
                <*>..... //最后一个选项,这个选项对应的按键驱动就是官方的按键驱动
                    按N键去除
保存退出
make uImage -j4
将arch/arm/boot/uImage拷贝到windows下的fastboot目录中
然后下位机上利用fastboot命令重新烧写uImage(具体烧写步骤参看之前的笔记)

mkdir /opt/drivers/day05/2.0 -p
cd /opt/drivers/day05/2.0
vim btn_drv.c

  1 #include<linux/init.h>
  2 #include<linux/module.h>
  3 #include<linux/irq.h>
  4 #include<linux/interrupt.h>
  5 #include<linux/gpio.h>
  6 #include<mach/platform.h>
  7 
  8 //声明按键的硬件信息
  9 struct btn_resource{
 10     char *name;
 11     int gpio;
 12 };
 13 
 14 //定义按键的硬件信息对象
 15 static struct btn_resource btn_info[] = {
 16     {
 17         .name = "KEY_UP",
 18         .gpio = PAD_GPIO_A + 28
 19     },
 20     {
 21         .name = "KEY_DOWN",
 22         .gpio = PAD_GPIO_B + 9
 23     }
 24 };
 25 
 26 //定义按键的中断处理函数
 27 //按键松开,按下,内核调用此中断处理函数
 28 //irq:保存当前触发中断的中断号
 29 //例如:
 30 //KEY_UP产生中断,irq=gpio_to_irq(btn_info[0].gpio)
 31 //KEY_DOWN产生中断,irq=gpio_to_irq(btn_info[1].gpio)
 32 //dev:保存给中断处理函数传递的参数
 33 //例如:
 34 //KET_UP产生中断,dev=&btn_info[0]
 35 //KEY_DOWN产生中断,dev=&btn_info[1]
 36 static irqreturn_t button_isr(int irq, void *dev){
 37     //1.暂存当前触发中断的按键硬件信息
 38     //KET_UP产生中断,pdata=&btn_info[0]
 39     //KEY_DOWN产生中断,pdata=&btn_info[1]
 40     struct btn_resource *pdata = (struct btn_resource *)dev;
 41 
 42     //2.分配内核缓冲区暂存按键的状态
 43     int kstate;
 44 
 45     //3.获取按键的状态保存到内核缓冲区
 46     kstate = gpio_get_value(pdata->gpio);
 47 
 48     //4.打印按键的状态
 49     printk("%s:按键[%s]的状态是:%s\n", __func__, pdata->name, kstate?"松开":"按下");
 50     return IRQ_HANDLED;//中观处理函数执行成功返回,执行失败返回IRQ_NONE
 51 }
 52 
 53 static int btn_init(void){
 54     int i;
 55     //1.申请GPIO资源
 56     //2.申请中断资源,注册中断处理函数
 57     for(i = 0; i < ARRAY_SIZE(btn_info); i++){
 58         int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
 59         gpio_request(btn_info[i].gpio, btn_info[i].name);
 60         request_irq(irq,//申请的硬件中断的中断号
 61                     button_isr,//注册的按键中断处理函数,目前是四个按键共用一个中断处理函数
 62                     IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,//指定按键中断触发的类型
 63                     btn_info[i].name,//指定按键中断的名称
 64                     &btn_info[i]//把每个按键的硬件信息的首地址传递给中断处理函数
 65                 );
 66     }
 67     return 0;
 68 }
 69 
 70 static void btn_exit(void){
 71     int i;
 72     //1.释放GPIO资源
 73     //2.释放中断资源和删除中断处理函数
 74     for(i = 0; i < ARRAY_SIZE(btn_info); i++){
 75         int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
 76         gpio_free(btn_info[i].gpio);
 77         free_irq(irq, &btn_info[i]);
 78     }
 79 }
 80 module_init(btn_init);
 81 module_exit(btn_exit);
 82 MODULE_LICENSE("GPL");


vim Makefile
make
cp btn_drv.ko /opt/rootfs/home/drivers

下位机linux系统启动之后执行:
cd /home/drivers
insmod btn_drv.ko
cat /proc/interrupts //查看注册的中断信息
          CPU0       
 33:          0       GIC  pl08xdmac
 34:          0       GIC  pl08xdmac
 37:          0       GIC  rtc 1hz
 39:         38      GIC  nxp-uart
 ...          ....        ...    ...
134:          0      GPIO  KEY_UP
147:          0      GPIO  KEY_DOWN

第一列:表示硬件中断对应的中断号
第二列:表示硬件中断触发的次数,可以通过第二列来判断中断是否触发
第三列:中断类型
第四列:中断的名称,就是request_irq函数的第四个参数:const char *name,可以通过名称来判断中断是否申请和注册成功

然后疯狂操作按键,按下松开,然后观察打印信息

需要消除按键抖动

操作完毕,继续执行:cat /proc/interrupts //观察按键中断触发的次数

rmmod btn_drv 
cat /proc/interrupts //还有中断的信息吗?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值