您的位置:首页 > 其它

ok6410学习笔记(16.按键中断控制led驱动)

2013-07-03 00:48 387 查看
        这一节是我学驱动这么多天来,最头疼的一节,事后想了想应该是poll驱动当时没有做练习,再加上对前面的一些知识开始遗忘了,出现了好多不该出现的错误,还有些错误仍然没有解决,但是好歹功能是实现了,看来不动手写程序真的很伤,就是因为电脑当时出了问题,没有写poll,导致我在中断处理函数,poll,read三个驱动函数之间,不断的徘徊也没找到直接的关系。题外话不多了~~~~~进入正题!!!

本节知识点:

没有解决的问题:

1.在中断处理函数中,我没有清除外部中断标志位,但是仍然可以再次进入中断,我觉得应该是操纵系统帮助清除了,因为ok6410的外部中断0~4是一个外部中断组,共用一个vic,而移植后的linux把每一个外部中断写的像是独立的一样,是因为操作系统在这个中断组中做了判断,判断的标志就是EINT0PEND寄存器,如果不清零的话,下次就进入不了了。所有我觉得操作系统帮助清除标志了,但是没有在源码中求证,所有别的中断,一定要记得清除中断标志位。
2.本想把led设备资源和irq资源都放到一个结构体数组中,在probe函数中,利用那个获得资源的函数获得的,但是没有成功,不知道为什么,各种内核异常啊!!!服了,我觉得这个是可以实现的,以后有机会试试。

本节知识:

1.对于中断驱动我想说比起裸机中断要简单些,因为裸机有好多寄存器要配置,而驱动程序操作系统帮助完成了好多工作,具体完成了什么工作在下文介绍。
2.中断驱动要完成两个步骤:1.向内核注册中断  2.编写中断处理函数
3.快速中断(fiq)与慢速中断(irq)的区别:
       a.快速中断只有一个:
           裸机角度:fiq只有一个  (因为fiq在保存现场的时候是把R0~R7压入栈中,而R8~R12是直接赋值给R8_fiq~R12_fiq保存的,如果有两个即可以中断嵌套,R8_fiq~R12_fiq中的值就会乱,就回不到上一个状态了)irq可以有好多个  中断嵌套由优先级觉得
           驱动角度:因为fiq只有一个,且它的优先级比irq要高,所以在中断驱动中不能有中断嵌套,操作系统把I和F标志位给关了,也是变相说明fiq只能有一个
           这个就是为什么,在编写裸机的时候只能有一个fiq,在编写驱动程序的时候只能有一个flag为快速的
       b.为什么fiq比irq快:
           第一:因为在保存现场的时候R8~R12是由硬件自动赋值给R8_fiq~R12_fiq保存的,所有比irq不断的压栈要快,但是这个压栈的irq可以实现,中断嵌套
   第二:FIQ的中断向量地址在0x0000_001c,而IRQ的在0x0000_0018。我们知道0x0000_0018处只能放置一条指令,而且是跳转指令。而FIQ后面没有其它的中断向量表,我们可以直接在0x0000_001c处放置FIQ的中断处理程序。这样就至少少了一条跳转指令。
4.共享中断:我觉得现在共享中断用的应该不多了,在一起使用共享中断的原因是因为中断线少,所以好多设备放在一个中断线上,使用一个中断号。但是现在芯片的中断控制器都很强大的。这里可以说一说request_irq函数的最后一个参数dev_id,它是唯一的,主要的用途在于注销中断,如果用disable_irq(irq)就会导致整条中断线上所有设备都用不了,所有应该用free(irq,dev_id)这样就不影响其他设备了。
5.中断处理函数中的判断:当中断处理函数被触发的时候首先应该进行的就是判断,1.如果是共享中断,操作系统会调用所有这条中断线上的设备的中断处理函数,所有应该判断中断标志位来看看是不是自己的中断触发了。2.如果好多中断共用一个中断处理函数:也得判断中断标志位来看看是不是自己的中断触发了。
6.中断上下文:中断处理函数就是处在中断上下文,不属于任何进程,没有用户空间,它利用的堆栈是内核空间的,所以在编写中断处理函数的时候要注意很多问题。
    a.不能向用户空间发送或者接受数据  因为中断不属于任何进程,没有用户空间
    b.不能使用可能引起阻塞的函数   他们是阻塞进程线程的   现在没有任何进程线程  所有谈不上阻塞

    c.不能使用可能引起调度的函数   原因还是 没有进程 调度谁

正因为好注意堆栈空间,时间的问题,所有出现了中断上下部分的问题。
7.中断上下部分:因为在中断处理函数中的时候,当前中断线是关闭的,是不能接受其他中断,所以对中断处理函数的时候有严格的时间要求,要求尽可能的快。所有就把一些严格时限的工作放在中断处理函数中叫做中断上半部,把一些不是那么紧急的事情放在中断下半部,下半部的方法有软中断,tasklet,工作队列。工作队列在下节仔细讲解(也可见宋宝华<linux设备驱动>第十章)。
8.对于国嵌给的内核bin文件,因为在配置文件中,添加了GPIO_BUTTON,导致我们要注册的中断,已经被注册过了,所有当我们再去request_irq的时候会失败,应该在make  menuconfig中把这项去掉就ok了。

重点函数:

1.中断注册函数:request_irq(button_irqs.irq, buttons_interrupt, IRQ_TYPE_EDGE_FALLING, button_irqs.name, (void *)&button_irqs[i]);
第一个参数是中断号,是arm中断控制器中的中断信号,一般移植好的linux内核,两者都是一一对应的,也有不一一对应的情况,在文件irqs.h里面
第二个参数是中断处理函数的函数名
第三个参数是中断标志:包含是共享中断,触发方式,快慢速中断等 (重要是根据这个参数,来对注册的中断进行初始化的)
第三个参数是在共享中断的时候使用的,一定是唯一的,来区分共享用的

linux中断原理:

可以参考<linux内核设计与实现>这本书的第七章
先通过裸机中断的原理对比一下,来推测一下操作系统是怎么完成的:
对于只使用vic矢量中断,没有使用vickey协处理器功能的s3c6410来说,无论什么中断来了
第一步,永远都是调用异常向量表中的中断处理函数(当然对于中断的配置如触发方式,io口等,初始化,使能,都是在之前完成的)
第二部,中断处理器会自动根据触发的中断来给VIC0ADDRESS和VIC1ADDRESS寄存器赋值对应的中断处理函数,中断处理函数是在初始化的时候给VIC0VECTADDR0等对应的60多个寄存器赋值的
第三部,就是调用对应中断处理函数
对于裸机的中断大体可以分这四部~~~~~~

对于驱动程序:
1.linux也应该有异常向量表,当中断触发的时候,也是第一个调用总的中断处理函数do_irq
2.在request_irq的时候,注册了中断进入内核,是通过内核对cpu进行了中断的初始化,中断的映射关系是来自irq.h文件的,中断的条件是来自flag参数的。具体是怎么初始化的是根据irq.h文件中IRQ_EINT(0)类似的宏来决定的。
3.当中断触发了,do_irq找到VIC0ADDRESS的值,再调用预先写好的中断处理函数,有些特例,比如IRQ_EINT(0)~IRQ_EINT(4)是共用一个VIC0ADDRESS的,是根据IRQ_EINT(0)区分的。

驱动结构:

本节的驱动结构分析起来有难点,因为基本是用上了前面所有的知识。
在plat_bun_dev.c文件中注册了中断设备资源
在plat_bun_drv.c文件中首先是
a1.platform_driver_register()注册platform平台设备

b1.bun_drv_probe函数
     platform_get_resource获得dev里面注册的资源
             misc_register注册混杂设备驱动
                      c1.有read  ioctl  poll   open   releaseopen是用来初始化的    申请irq    初始化led        
                                    d1.中断处理函数   当中断触发的时候操作系统调用这个函数   它会把一些信息返回给poll(阻塞设备的标志)和read(按键信息)
                        poll是监控文件释放可读
                        read是检测到可读后  把按键信息发送给用户空间(里面有阻塞设备驱动)
                        ioctl是控制led
                        release是释放irq资源   注销led

        b1.bun_drv_remove删除驱动
     misc_deregister删除混杂设备驱动
    
a1.platform_driver_unregister()注销platform平台设备

在中断处理函数中:
1.判断是否是该设备产生了中断,主要是给共享中断用的,也可能是排除误操作的。
2.清除中断标志位
3.中断处理数据,接受数据,给中断下半部用,这里是给read用的
4.唤醒等待数据的进程,这里是唤醒poll的阻塞

本驱动的调用过程:
1、先进入中断处理函数
2、当条件满足的时候调用poll函数
3、然后调用read函数
4、其实现在应该打印 用户空间put key is 这句  但是不知道为什么都是内核打印优先的  用户空间打印可能优先级靠后
5、调用ioctl控制led
如图:



本节代码:

本节的代码是利用platform平台驱动  加上混杂设备驱动   利用poll和ioctl高级功能  通过按键中断控制led的
plat_bun_drv.c:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <mach/map.h>
#include <mach/regs-gpio.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/unistd.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include "plat_bun_drv.h"

#include <mach/gpio-bank-n.h>

MODULE_AUTHOR("Hao");
MODULE_LICENSE("Dual BSD/GPL");

volatile unsigned long  GPIOM_VA_BASE;//定义一个全局变量  保存ioremap映射的地址
#define GPIOM_PA_BASE   0x7f008820
#define GPIOM_CON_VA    GPIOM_VA_BASE
#define GPIOM_DAT_VA    (GPIOM_VA_BASE+0x4)
#define GPIOM_PUD_VA    (GPIOM_VA_BASE+0x8)

struct resource led_res={
.name  = "led io-mem",  //设备资源的名字
.start = GPIOM_PA_BASE,
.end   = GPIOM_PA_BASE + 0xc,
.flags = IORESOURCE_MEM,
};

int key_num=1; //定义一个全局变量 来存放中断处理函数返回的按键值

int key_flag=0;//默认为0 在poll中不返回掩码
static DECLARE_WAIT_QUEUE_HEAD(waitq);  //利用这个宏  定义初始化 等待队列  替代了wait_queue_head_t q和init_waitqueue_head(&q)两个函数

typedef struct {
int irq;
int num;
char *name;
} button_irq_t;

button_irq_t  button_irqs [] = {
{0, 0, "KEY1"},
{0, 1, "KEY2"},
{0, 2, "KEY3"},
{0, 3, "KEY4"},
{0, 4, "KEY5"},
{0, 5, "KEY6"},
};
/*中断处理函数  在混杂设备的open函数中request_irq中定义的名字叫buttons_interrupt*/
/*本来其实在这里判断是那个按键中断  然后让led对应点亮就行了   但是驱动的所有判断都应该是给用户空间传递的  所有判断应该在用户空间进行  点亮led应该在用户空间进行*/
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
printk("you are in buttons_interrupt!!!\n");
button_irq_t *button_irqs = (button_irq_t *)dev_id;  /*在触发中断的时候  操作系统会根据中断源信号调用对应的处理函数
并同时将中断号irq  和 dev_id传递到这个函数 因为在注册中断的时候dev_id为了保证唯一性  输入的是button_irqs结构体的地址 这里直接使用这地址  是操作系统传递的*/
/*判断是否是这个设备产生中断   本来是为了给share中断用的   这里使用是因为所有按键的外部中断都是这一个中断处理函数*/
key_num = button_irqs->num; //数据处理
key_flag=1;
wake_up_interruptible(&waitq);//唤醒poll的阻塞
return IRQ_RETVAL(IRQ_HANDLED);  //接收到了准确的中断信号,并且作了相应正确的处理
/*步骤:1,判断本设备是否发送中断
2,数据处理  这个数据一般都是要通过read传递给用户空间的
3,清楚中断标志位     外部中断其实没有中断标志位 只有一个EINT0PEND寄存器 记录那个外部中断发生了  内核应该就是通过这个寄存器的值来判断同一中断组中不同外部中断的
4,唤醒阻塞的进程  这唤醒就是唤醒poll的阻塞的
*/

}
static int mem_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
printk("you are in mem_read!!!\n");
int ret;
while(!key_flag)
{
if (filp->f_flags & O_NONBLOCK)  //判断文件是否不允许阻塞
{
return -EAGAIN;
}
/*其实这个阻塞的可能性很低 因为都是poll阻塞被唤醒后 才进入read的 完全没必要来一个阻塞型设备驱动  就当练手把*/
wait_event_interruptible(waitq, key_flag);  //可以中断的阻塞  第一个参数是阻塞队列  第二个参数是阻塞条件
}
key_flag=0;//在下次中断来临前  还是让poll阻塞吧
if (copy_to_user((void *)buff,(void *)(&key_num), count))//把字符设备模块从内核空间拷贝到用户空间
{
ret =  - EFAULT;
}
else
{
ret = count;
}
return ret;

}

/*这里面真正找到了poll的意义了    如果没有poll进行文件监控  看那个文件可以读(利用的就是按键被按下  中断处理函数被调用  把poll的标志改变了)
就要不断的用read进行轮询的读取了然后进行阻塞型设备驱动 被挂起   要是多个文件就疯了  各种被挂起就进行不了了*/
static unsigned int mem_poll( struct file *file, struct poll_table_struct *wait)
{

printk("you are in mem_poll!!!\n");
/*步骤:1,poll_wait把文件的等待队列加入*wait
2,判断标志返回掩码*/
unsigned int mask = 0;
poll_wait(file, &waitq, wait);  //加入等待队列
if(key_flag)
{
mask |= POLLIN | POLLRDNORM;//返回可读标志
}
return mask;
}

static void ok6410_led_setup(struct resource *led_resource)
{
unsigned long temp;
led_res=*led_resource;
request_mem_region(led_resource->start,(led_resource->end-led_resource->start),led_resource->name);//申请i/o内存 设备资源的名字
//其实我觉得用上面那个资源的结构体意义不打  因为request_mem_region就是在跟系统申请这个资源  等价于了把上面的那个资源结构体拷贝到了内核中的设备资源链表
GPIOM_VA_BASE = (volatile unsigned long )ioremap(led_resource->start, led_resource->end-led_resource->start);//

/****************************可以直接对地址进行操作***************************************/
/*
(*(volatile unsigned long *)GPIOM_VA_BASE)&=~0xffff;
(*(volatile unsigned long *)GPIOM_VA_BASE)|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12);
temp=0;
(*(volatile unsigned long*)GPIOM_DAT_VA)=temp;  //默认所有灯都亮
*/

/*******************也可以用函数api进行操作  貌似这个方式更加安全***************************/
temp&=~0xffff;
temp|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12);
writel(temp, GPIOM_CON_VA);

temp|=0xf;
temp=0;
writel(temp, GPIOM_DAT_VA);

}

static void ok6410_led_release(void)
{
iounmap((void*)GPIOM_VA_BASE);
release_mem_region(led_res.start,led_res.end-led_res.start);
}

static long memdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("you are in memdev_ioctl!!!\n");
int ret=0;
int err=0;
int kernel_num=1991;
//char kernel_buf[20]="hello kernel!!!";

/*先判断命令号是否正确*/
if (_IOC_TYPE(cmd) != CMD_KTYPE) //获得命令的type类型是否正确
return -EINVAL;
if (_IOC_NR(cmd) > LED_KCMD)    //获得命令的num类型  是否小于命令个数
return -EINVAL;

/*获命令的数据传输方向   根据各自的方向判断*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));/*此函数是根据
内核空间写的 是用来判断 arg应用程序传来的用户空间 是否有效的  所以对于用户空间来说是写*/
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));//对于用户空间来说是读   成功返回1  失败返回0
if (err)
return -EFAULT;

/*实现CMD的用法*/
switch(cmd)
{
case LEDR_KCMD:
ret=__put_user(kernel_num, (int *)arg);  //把内核中的int型数据读入用户空间   unsigned long arg就是一个地址值   kernel->arg
break;
case LEDW_KCMD:
ret=__get_user(kernel_num, (int *)arg);   //arg->kernel_num   把用户空间的数据传递给kernel_num
printk(KERN_EMERG "WRITE_KCMD is in kernel!!!  kernel_num:%d \n",kernel_num);
if(0==kernel_num)
{
writel(0x1e, GPIOM_DAT_VA);//将4个led全部点亮
}
if(1==kernel_num)
{
writel(0x1d, GPIOM_DAT_VA);//将4个led全部熄灭
}
if(2==kernel_num)
{
writel(0x1b, GPIOM_DAT_VA);//将4个led全部点亮
}
if(3==kernel_num)
{
writel(0x17, GPIOM_DAT_VA);//将4个led全部熄灭
}
if(4==kernel_num)
{
writel(0x0, GPIOM_DAT_VA);//将4个led全部点亮
}
if(5==kernel_num)
{
writel(0x1f, GPIOM_DAT_VA);//将4个led全部熄灭
}
break;
default:
return -EINVAL;
break;
}
return 0;
}
/*混杂设备中  当关闭文件的时候 应该先释放中断资源*/
int mem_release(struct inode *inode, struct file *filp)
{
printk("you are in mem_release!!!\n");
int i;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
ok6410_led_release();
return 0;
}
/*混杂设备中  当打开文件的时候 应该先申请中断资源*/
int mem_open(struct inode *inode,struct file *filp)
{
printk("you are in mem_open!!!\n");
int i;
int err;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
{
/*请求中断设备资源  第一个参数是中断号这里面与硬件平台一一对应  第二个参数是中断处理程序  这里面都
公用一个处理程序   第三个参数是标志位 标志是共享中断,快速中断,触发方式   第四个参数是中断id 是区
分共享中断 不同设备时候用的  这里面用的&button_irqs[i]的地址保证它的唯一性    这里没有安全判断*/
err=request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_FALLING,
button_irqs[i].name, (void *)&button_irqs[i]);//在这里设置成下降沿触发  中断处理函数就简单些
if (err)
{
printk("request_irq is error!!!\n");//此时其实应该有disable_irq  或者  free_irq等函数把申请过的irq释放掉  但是我嫌麻烦就没写
return err;
}
}
ok6410_led_setup(&led_res);
return 0;
}

static const struct file_operations mem_fops =  //定义此字符设备的file_operations
{                       //这里是对结构体整体赋值的方式
.owner = THIS_MODULE, //函数名都可以自己定义  都是函数指针
.open = mem_open,
.release = mem_release,
.unlocked_ioctl=memdev_ioctl,
.read=mem_read,
.poll=mem_poll,
};

static struct miscdevice misc = {
.minor = 0,//设置为0  系统自动分配次设备号
.name = "misc_bun",  //我觉得这个是设备节点的名字  就是/dev路径下的文件的名字
.fops = &mem_fops,  //文件操作
};

static int bun_drv_probe(struct platform_device *dev)   //这里面写功能驱动
{
int ret;
int i;
struct resource *res;
printk("Driver found device which my driver can handle!\n");
for(i=0; i<6; i++)
{
res=platform_get_resource(dev,IORESOURCE_IRQ,i);
if(res==NULL)
{
printk("no memory resource %d\n",i);
return 0;
}
button_irqs[i].irq = res->start;  //把设备中的irq资源都带到驱动程序中  赋值给button_irqs结构体数组中
}
misc_register(&misc);
return ret;
}

static int bun_drv_remove(struct platform_device *dev)
{
printk("Driver found device unpluged!\n");
misc_deregister(&misc);
return 0;
}

struct platform_driver bun_drv={
.probe=bun_drv_probe,
.remove=bun_drv_remove,
.driver={
.owner=THIS_MODULE,
.name="plat_bun_dev",  //platform总线  里面驱动的名字   这个名字要和设备的名字一样
}
};

static int __init platform_bun_drv_int(void)
{
return platform_driver_register(&bun_drv);
}

static void __exit platform_bun_drv_exit(void)
{
platform_driver_unregister(&bun_drv);
}

module_init(platform_bun_drv_int);
module_exit(platform_bun_drv_exit);


plat_bun_dev.c:
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>

MODULE_AUTHOR("Hao");
MODULE_LICENSE("Dual BSD/GPL");

static struct resource ok6410_buttons_resource[] = {	//中断设备资源
[0]={
.start = IRQ_EINT(0),
.end   = IRQ_EINT(0),
.flags = IORESOURCE_IRQ,
},
[1]={
.start = IRQ_EINT(1),
.end   = IRQ_EINT(1),
.flags = IORESOURCE_IRQ,
},
[2]={
.start = IRQ_EINT(2),
.end   = IRQ_EINT(2),
.flags = IORESOURCE_IRQ,
},
[3]={
.start = IRQ_EINT(3),
.end   = IRQ_EINT(3),
.flags = IORESOURCE_IRQ,
},
[4]={
.start = IRQ_EINT(4),
.end   = IRQ_EINT(4),
.flags = IORESOURCE_IRQ,
},
[5]={
.start = IRQ_EINT(5),
.end   = IRQ_EINT(5),
.flags = IORESOURCE_IRQ,
},
};

struct platform_device bun_dev={
.name="plat_bun_dev",  //platform总线  里面设备的名字   这个名字要和驱动的名字一样
.id=-1,
.num_resources=ARRAY_SIZE(ok6410_buttons_resource),  //ARRAY_SIZE求资源结构体的个数的
.resource=ok6410_buttons_resource,
};

static int __init platform_bun_dev_init(void)
{
int ret=0;
ret=platform_device_register(&bun_dev);
if(ret)
{
printk("platform_device_register failed!!\n");
}
return ret;
}

static void __exit platform_bun_dev_exit(void)
{
platform_device_unregister(&bun_dev);
}

module_init(platform_bun_dev_init);
module_exit(platform_bun_dev_exit);


app_bun.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <time.h>
#include "plat_bun_drv.h"

int main(void)
{
int key_num;//通过read返回给用户空间 那个按键被按下
fd_set rfds;//监控可读文件集
int fd;
if (-1==(fd=open ("/dev/misc_bun", O_RDONLY))) {
printf("open dev0 error\n");
_exit(EXIT_FAILURE);
}

//先清空集合
FD_ZERO(&rfds);

//设置要监控的文件描述符
FD_SET(fd, &rfds);

while(1)
{
//最后的时间为NULL  表示没有返回mask  马上进入等待队列
if(-1==(select(fd+1, &rfds, NULL, NULL, NULL)))
{
printf("select error\n");
_exit(EXIT_FAILURE);
}
if(FD_ISSET(fd, &rfds)) //判断掩码是否是可读的
{
read(fd, &key_num, sizeof(key_num));
printf("put key is %d!!!\n",key_num);
ioctl(fd,LEDW_KCMD,&key_num);
}
}
close(fd);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: