1、S3C2440按键中断驱动程序的设计 S3C2440按键驱动的设计(内核2.6.30.4)下图为S3C2440的按键连接电路图:在开始设计程序之前介绍一下与Linux设备中断处理程序相关的知识。首先是申请与释放IRQ的API request_irq()和free_irq(),request_irq()的原型为:int request_irq(unsigned int irq,void(*handler)(int irq,void *dev_id,struct pt_regs *regs),unsigned long irqflags,const char *devname,void *dev_
2、id);irq是要申请的硬件中断号;handler是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递; irqflags是中断处理的属性,若设置SA_INTERRUPT,表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,而慢速处理程序不屏蔽;若设置SA_SHIRQ,则多个设备共享中断;dev_id在中断共享时会用到,一般设置为这个设备的device结构本身或者NULL。free_irq()的原型为: void free_irq(unsigned int irq,void *dev_id);下面我就开始写驱动程序,源码如下(详解在源
3、码后面说明):#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME IRQ-Teststruct button_irq_desc int irq; int pin; int pin_setting; int number; char *name; ;static struct button_irq_desc button_irqs = IRQ
4、_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, KEY1, /* K1 */ IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, KEY2, /* K2 */ IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, KEY3, /* K3 */ IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, KEY4, /* K4 */;static volatile char key_values = 0, 0, 0, 0;static
5、 DECLARE_WAIT_QUEUE_HEAD(button_waitq);static volatile int ev_press = 0;static irqreturn_t irq_interrupt(int irq, void *dev_id) struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id; int down; down = !s3c2410_gpio_getpin(button_irqs-pin); if (down != (key_valuesbutton_irqs-number &
6、1) key_valuesbutton_irqs-number = 0 + down; ev_press = 1; wake_up_interruptible(&button_waitq); return IRQ_RETVAL(IRQ_HANDLED);static int tq2440_irq_open(struct inode *inode, struct file *file) int i; int err = 0; for (i = 0; i sizeof(button_irqs)/sizeof(button_irqs0); i+) if (button_irqsi.irq = 0;
7、i-) if (button_irqsi.irq 0) continue; disable_irq(button_irqsi.irq); free_irq(button_irqsi.irq, (void *)&button_irqsi); return -EBUSY; ev_press = 1; return 0;static int tq2440_irq_close(struct inode *inode, struct file *file) int i; for (i = 0; i sizeof(button_irqs)/sizeof(button_irqs0); i+) if (but
8、ton_irqsi.irq f_flags & O_NONBLOCK) return -EAGAIN; else wait_event_interruptible(button_waitq, ev_press); ev_press = 0; err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count); return err ? -EFAULT : min(sizeof(key_values), count);static unsigned int tq2440_irq_poll( struc
9、t file *file, struct poll_table_struct *wait) unsigned int mask = 0; poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN | POLLRDNORM; return mask;static struct file_operations dev_fops = .owner = THIS_MODULE, .open = tq2440_irq_open, .release = tq2440_irq_close, .read = tq2440_irq_re
10、ad, .poll = tq2440_irq_poll,;static struct miscdevice misc = .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops,;static int _init dev_init(void) int ret; ret = misc_register(&misc); printk (DEVICE_NAME initializedn); return ret;static void _exit dev_exit(void) misc_deregister(&misc)
11、;module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(www.MODULE_DESCRIPTION(IRQ Test for EmbedSky TQ2440 Board);下面是对这个驱动代码的的详细解释:头文件的解释:#include所有模块都需要这个头文件#includeinit和exit相关宏#include 声明了printk()这个内核态用的函数#include 文件系统有关的,结构体file_operations在头文件 linux/fs.h中定义#include 寄存器设置#i
12、nclude Linux 中的用户和内核态内存交互函数(这些函数在 include/asm/uaccess.h 中被声 明): unsigned long copy_from_user(void *to, const void *from, unsigned long n); unsigned long copy_to_user (void * to, void * from, unsigned long len)#include 在这个头文件中主要是misc(混合)设备注册和注销linux中用struct miscdevice来描述一个混杂设备#include Linux中断定义#inclu
13、de arm中断定义,如中断号的定义IRQ_EINT1#include interrupt.h中包含了与中断相关的大部分宏及struct结构的定义,如request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);#include IO空间的物理地址定义#include 延迟函数的定义声明(本程序未用延迟函数,所以这个头文件不是必须的)#include 本例中定义了POLLIN | POLLRDNORM#include 通常在Linux中,把SoC系统中
14、集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。在Linux中用platform_device结构体来描述一个平台设备,在2.6.30.4内核中定义在:include/linux/platform_device.h中(本例中这个头文件是非必须的,因为没有把这个中断实现定义为平台设备,如果要注册为平台设备,这个头文件就需要包含进来了)#include 注册字符设备(在本例中,这个头文件也是非必须的)下面三段代码在设计中不是必须的:MODULE_LICENSE(GPL)用于声明模块的许可证MODULE_AUTHOR(www.声明模块的作者MODULE_DESCRI
15、PTION(IRQ Test for EmbedSky TQ2440 Board)对模块的描述代码的说明:#define DEVICE_NAME IRQ-Test加载模式后,执行“cat /proc/devices命令看到的设备名称struct button_irq_desc int irq; int pin; int pin_setting; int number; char *name; ;定义了一个类型名为button_irq_desc的结构体类型,由它把按钮中断的信息综合起来int irq 中断号,中断号唯一表示一个中断int pin; 中断控制的寄存器,该寄存器的值由中断引脚设置,我
16、们希望从该寄存器读出控制信息int pin_setting;中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值int number;编号char *name; 名称static struct button_irq_desc button_irqs = IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, KEY1, /* K1 */ IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, KEY2, /* K2 */ IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2
17、_EINT2, 2, KEY3, /* K3 */ IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, KEY4, /* K4 */;定义了一个button_irq_desc结构体类型的数组变量button_irq,并对结构体的各个元素赋值。注:static的说明:Linux内核是一个十分庞大的系统。随着Linux内核的发展,其核心代码包括几千个文件,代码量超过百万行。对于用C语言编写的大型程序,需要解决的一个问题就是“名字空间污染”。由于C语言的函数和全局变量的作用空间都是全局的,在另外一个文件中,使用extern关键字,就可以实现对于其他文件中的
18、全局变量和函数的访问。虽然这样很方便,但是源码中多个文件全局变量和函数定义的名称不允许重复;否则,编译器在连接时,就会报告命名重复的错误。这就是“名字空间污染”的问题。当系统庞大并且由多个程序员共同开发时,这个问题就更加突出。因此,需要引用一些封装的特性,限制源码中函数和变量作用的空间。C语言中的static关键字解决了这个问题。从上面代码中可以看到所有的全局变量和函数都是用static关键字修饰的。这是一个很好的习惯。被static关键字修饰的函数和全局变量,其名字作用的范围仅仅是当前的文件,而不是整个系统。static volatile char key_values = 0, 0, 0,
19、 0;数组中是否有数据的标志,0表示无数据可读,1表示有数据可读。注:volatile就象大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。下面我们来一个个说明。 考虑下面的代码:class Gadget public: void Wait() while (!flag_) Sleep(1000); / sleeps for 1000 milliseconds void Wakeup() flag_ =
20、 true; . private: bool flag_; ; 上面代码中Gadget:Wait的目的是每过一秒钟去检查一下flag_成员变量,当flag_被另一个线程设为true时,该函数才会返回。至少这是程序作者的意图,然而,这个Wait函数是错误的。 假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性:当你调用了某个Gadget的Wait函数后,即使另一个线程调用了W
21、akeup,Wait还是会一直循环下去。这是因为flag_的改变没有反映到缓存它的寄存器中去。编译器的优化未免有点太乐观了。 在大多数情况下,把变量缓存在寄存器中是一个非常有价值的优化方法,如果不用的话很可惜。C和C+给你提供了显式禁用这种缓存优化的机会。如果你声明变量是使用了volatile修饰符,编译器就不会把这个变量缓存在寄存器里每次访问都将去存取变量在内存中的实际位置。这样你要对Gadget的Wait/Wakeup做的修改就是给flag_加上正确的修饰: class Gadget public: . as above . private: volatile bool flag_; ;
22、static DECLARE_WAIT_QUEUE_HEAD(button_waitq);睡眠的介绍对于一个进程睡眠意味着什么? 当一个进程被置为睡眠, 它被标识为处于一个特殊的状态并且从调度器的运行队列中去除. 直到发生某些事情改变了那个状态。这个进程将不被在任何 CPU 上调度, 并且, 因此, 将不会运行. 一个睡着的进程已被搁置到系统的一边, 等待以后发生事件.对于一个 Linux 驱动使一个进程睡眠是一个容易做的事情. 但是, 有几个规则必须记住以安全的方式编码睡眠.这些规则的第一个是: 当你运行在原子上下文时不能睡眠. 我们在第 5 章介绍过原子操作; 一个原子上下文只是一个状态,
23、 这里多个步骤必须在没有任何类型的并发存取的情况下进行. 这意味着, 对于睡眠, 是你的驱动在持有一个自旋锁, seqlock, 或者 RCU 锁时不能睡眠. 如果你已关闭中断你也不能睡眠. 在持有一个旗标时睡眠是合法的, 但是你应当仔细查看这样做的任何代码. 如果代码在持有一个旗标时睡眠, 任何其他的等待这个旗标的线程也睡眠. 因此发生在持有旗标时的任何睡眠应当短暂, 并且你应当说服自己, 由于持有这个旗标, 你不能阻塞这个将最终唤醒你的进程.另一件要记住的事情是, 当你醒来, 你从不知道你的进程离开 CPU 多长时间或者同时已经发生了什么改变. 你也常常不知道是否另一个进程已经睡眠等待同一
24、个事件; 那个进程可能在你之前醒来并且获取了你在等待的资源. 结果是你不能关于你醒后的系统状态做任何的假设, 并且你必须检查来确保你在等待的条件是, 确实, 真的.一个另外的相关的点, 当然, 是你的进程不能睡眠除非确信其他人, 在某处的, 将唤醒它. 做唤醒工作的代码必须也能够找到你的进程来做它的工作. 确保一个唤醒发生, 是深入考虑你的代码和对于每次睡眠, 确切知道什么系列的事件将结束那次睡眠. 使你的进程可能被找到, 真正地, 通过一个称为等待队列的数据结构实现的. 一个等待队列就是它听起来的样子:一个进程列表, 都等待一个特定的事件.在 Linux 中, 一个等待队列由一个等待队列头来
25、管理, 一个 wait_queue_head_t 类型的结构, 定义在中. 一个等待队列头可被定义和初始化, 使用:DECLARE_WAIT_QUEUE_HEAD(name);或者动态地, 如下:wait_queue_head_t my_queue;init_waitqueue_head(&my_queue);我们将很快返回到等待队列结构, 但是我们知道了足够多的来首先看看睡眠和唤醒.6.2.2. 简单睡眠当一个进程睡眠, 它这样做以期望某些条件在以后会成真. 如我们之前注意到的, 任何睡眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真. Linux 内核中睡眠的最简单方式是一个宏定
26、义, 称为 wait_event(有几个变体); 它结合了处理睡眠的细节和进程在等待的条件的检查. wait_event 的形式是:wait_event(queue, condition)wait_event_interruptible(queue, condition)wait_event_timeout(queue, condition, timeout)wait_event_interruptible_timeout(queue, condition, timeout)在所有上面的形式中, queue 是要用的等待队列头. 注意它是通过值传递的. 条件是一个被这个宏在睡眠前后所求值的任意
27、的布尔表达式; 直到条件求值为真值, 进程继续睡眠. 注意条件可能被任意次地求值, 因此它不应当有任何边界效应.如果你使用 wait_event, 你的进程被置为不可中断地睡眠, 如同我们之前已经提到的, 它常常不是你所要的. 首选的选择是 wait_event_interruptible, 它可能被信号中断. 这个版本返回一个你应当检查的整数值; 一个非零值意味着你的睡眠被某些信号打断, 并且你的驱动可能应当返回 -ERESTARTSYS. 最后的版本(wait_event_timeout 和 wait_event_interruptible_timeout)等待一段有限的时间; 在这个时间期间(以嘀哒数表达的, 我们将在第 7 章讨论)超时后, 这个宏返回一个 0 值而不管条件是如何求值的.图片的另一半, 当然, 是唤
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1