学习笔记 --- MINI2440 linux按键驱动代码分析

知识点:

1 中断初始化流程

2 poll轮询机制

3 misc驱动框架

下面分析MINI2440按键驱动代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/gpio.h>

#define DEVICE_NAME     "buttons"

//中断传递的参数
struct button_irq_desc {
    int irq;
    int pin;
    int pin_setting;
    int number;
    char *name;	
};

static struct button_irq_desc button_irqs [] = {
    {IRQ_EINT8 , S3C2410_GPG(0) ,  S3C2410_GPG0_EINT8  , 0, "KEY0"},
    {IRQ_EINT11, S3C2410_GPG(3) ,  S3C2410_GPG3_EINT11 , 1, "KEY1"},
    {IRQ_EINT13, S3C2410_GPG(5) ,  S3C2410_GPG5_EINT13 , 2, "KEY2"},
    {IRQ_EINT14, S3C2410_GPG(6) ,  S3C2410_GPG6_EINT14 , 3, "KEY3"},
    {IRQ_EINT15, S3C2410_GPG(7) ,  S3C2410_GPG7_EINT15 , 4, "KEY4"},
    {IRQ_EINT19, S3C2410_GPG(11),  S3C2410_GPG11_EINT19, 5, "KEY5"},
};
static volatile char key_values [] = {'0', '0', '0', '0', '0', '0'};


 //定义一个等待队列,相当于ucos的定义一个队列变量
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);     

 //等待队列触发的条件变量,如果为1唤醒等待进程,为0继续休眠
static volatile int ev_press = 0;  


//dev_id可以传入全局的表达这个驱动的结构体
//dev_id传入中断可以直接访问这些数据
static irqreturn_t buttons_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_values[button_irqs->number] & 1)) { //检查按键值是否一致(防抖)

	key_values[button_irqs->number] = '0' + down;  //赋按键值
	
        ev_press = 1;   //先使能条件,表示唤醒条件OK
        wake_up_interruptible(&button_waitq);   //再通知button_waitq 去触发唤醒
    }
    
    return IRQ_RETVAL(IRQ_HANDLED);
}


static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
    int i;
    int err = 0;
    
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
	if (button_irqs[i].irq < 0) {
		continue;
	}
        err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH, 
                          button_irqs[i].name, (void *)&button_irqs[i]);
	     //打开的时候注册中断,内部会通过IRQ_TYPE_EDGE_BOTH来自动配置引脚
        if (err)
            break;
    }

    if (err) {
        i--;
        for (; i >= 0; i--) {
	    if (button_irqs[i].irq < 0) {
		continue;
	    }
	    disable_irq(button_irqs[i].irq);   //如果有一个注册失败,则取消前面已经注册了的
            free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
        }
        return -EBUSY;
    }

    ev_press = 1;
    
    return 0;
}


static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
    int i;
    
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
	if (button_irqs[i].irq < 0) {
	    continue;
	}
	free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);  //关闭的时候要释放中断
    }

    return 0;
}


static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
    unsigned long err;

    if (!ev_press) {
	if (filp->f_flags & O_NONBLOCK) //如果不阻塞,则直接返回
	    return -EAGAIN;
	else
	    wait_event_interruptible(button_waitq, ev_press);  
	//一直在休眠等待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);
}


/*
poll机制:
这个是提供给应用的另一种获取按键信息的方式
相当于ucos的超时等待一个信号量,如果事件到了就唤醒,否则一直休眠
如果不使用poll,只使用wait_event_interruptible(button_waitq, ev_press);  方式一直傻等
那么应用程序read的时候会一直堵塞,相当于ucos里面的事件超时设置为0
有了poll机制就可以使用超时等待
查看内核源码:
app:poll
kernel: sys_poll(struct pollfd __user *ufds, unsigned int nfds, long timeout_msecs)
    timeout_jiffies = msecs_to_jiffies(timeout_msecs);   //配置超时时间
    do_sys_poll(ufds, nfds, &timeout_jiffies);
         poll_initwait(&table);          //初始化等待队列
               init_poll_funcptr(&pwq->pt, __pollwait);
                      pt->qproc = qproc;    //table->pt->qproc= __pollwait;
         do_poll(nfds, head, &table, timeout);
             for (;;)
            {
                   for (; pfd != pfd_end; pfd++)  //针对多个进程
                   {
                          if (do_pollfd(pfd, pt))  //内部:mask = file->f_op->poll(file, pwait);  f_op->poll就是写得驱动
                          {
                                  count++; pt = NULL;
                          }
                   }
                // 若count不为0或者超时或者有事件发生则会跳出死循环
                 if (count || !*timeout || signal_pending(current))
                       break;
                 //若count为0且未超时且无事件发生则会休眠__timeout时间
                 schedule_timeout(__timeout);
             }
*/
static unsigned int s3c24xx_buttons_poll( struct 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;  //这里返回0之后进程就休眠了,如果不为0,代表进程唤醒
}


static struct file_operations dev_fops = {
    .owner   =   THIS_MODULE,
    .open    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close, 
    .read    =   s3c24xx_buttons_read,
    .poll    =   s3c24xx_buttons_poll,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &dev_fops,
};

static int __init dev_init(void)
{
	int ret;
	 /*这个驱动注册为misc类型的驱动
	 misc本身是一个字符驱动,它可以很方便的注册驱动
	 相当于一个框架已经搭好了的字符设备驱动
	 最大的作用就是驱动注册后自动通过udev 的class机制去创建设备节点
	 */
	ret = misc_register(&misc);  

	printk (DEVICE_NAME"\tinitialized\n");

	return ret;
}

static void __exit dev_exit(void)
{
	misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");


测试程序:
int main(int argc, char **argv)
{
 int fd;
 unsigned char key_val;
 int ret;

 struct pollfd fds[1];   //poll可以轮询多个文件(设备),这里只设置轮询一个
 
 fd = open("/dev/buttons", O_RDWR); 
 if (fd < 0)
 {
  printf("can't open!\n");
 }

 fds[0].fd     = fd;       //设置轮询的文件
 fds[0].events = POLLIN;   //设置触发的事件,这个值和poll驱动返回的值做比较,有这个事件则唤醒
 while (1)  
 { 
  ret = poll(fds, 1, 5000);  //使用了轮询机制,轮询一个文件, 5s未触发则超时返回0
  if (ret == 0)
  {
   printf("time out\n");
  }
  else
  {
   read(fd, &key_val, 1); //如果这个循环里面如果只用这行来等待,就会一直休眠了,无超时的概念;这里使用了poll机制,让poll去检测触发事件
   printf("key_val = 0x%x\n", key_val);
  }
 }
 
 return 0;
}

上面写得驱动我们看到没有看到初始化中断的GPIO口和初始化中断的代码,打开设备就只注册中断,其实在注册中断的时候会调用struct irq_chip里面的set_type去初始化:

request_irq会执行到这个来配置中断:

ret = __irq_set_trigger(desc, irq, new->flags & IRQF_TRIGGER_MASK);

而这个函数执行的就是在系统初始化中断时设置好的set_type,看下:

最开始:

MACHINE_START(MINI2440, "FriendlyARM Mini2440 development board")
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,

	.init_irq	= s3c24xx_init_irq,
	.map_io		= mini2440_map_io,
	.init_machine	= mini2440_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END

s3c24xx_init_irq初始化中断的时候会设置好中断:


void __init s3c24xx_init_irq(void)
{
	unsigned long pend;
	unsigned long last;
	int irqno;
	int i;

#ifdef CONFIG_FIQ
	init_FIQ();
#endif

	irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");

	/* first, clear all interrupts pending... */

	last = 0;
	for (i = 0; i < 4; i++) {
		pend = __raw_readl(S3C24XX_EINTPEND);

		if (pend == 0 || pend == last)
			break;

		__raw_writel(pend, S3C24XX_EINTPEND);
		printk("irq: clearing pending ext status %08x\n", (int)pend);
		last = pend;
	}

	last = 0;
	for (i = 0; i < 4; i++) {
		pend = __raw_readl(S3C2410_INTPND);

		if (pend == 0 || pend == last)
			break;

		__raw_writel(pend, S3C2410_SRCPND);
		__raw_writel(pend, S3C2410_INTPND);
		printk("irq: clearing pending status %08x\n", (int)pend);
		last = pend;
	}

	last = 0;
	for (i = 0; i < 4; i++) {
		pend = __raw_readl(S3C2410_SUBSRCPND);

		if (pend == 0 || pend == last)
			break;

		printk("irq: clearing subpending status %08x\n", (int)pend);
		__raw_writel(pend, S3C2410_SUBSRCPND);
		last = pend;
	}

	/* register the main interrupts */

	irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");

	for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
		/* set all the s3c2410 internal irqs */

		switch (irqno) {
			/* deal with the special IRQs (cascaded) */

		case IRQ_EINT4t7:
		case IRQ_EINT8t23:
		case IRQ_UART0:
		case IRQ_UART1:
		case IRQ_UART2:
		case IRQ_ADCPARENT:
			set_irq_chip(irqno, &s3c_irq_level_chip);
			set_irq_handler(irqno, handle_level_irq);
			break;

		case IRQ_RESERVED6:
		case IRQ_RESERVED24:
			/* no IRQ here */
			break;

		default:
			//irqdbf("registering irq %d (s3c irq)\n", irqno);
			set_irq_chip(irqno, &s3c_irq_chip);
			set_irq_handler(irqno, handle_edge_irq);
			set_irq_flags(irqno, IRQF_VALID);
		}
	}

	/* setup the cascade irq handlers */

	set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
	set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

	set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
	set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
	set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
	set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

	/* external interrupts */

	for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
		irqdbf("registering irq %d (ext int)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_eint0t4); 
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
		irqdbf("registering irq %d (extended s3c irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irqext_chip);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	/* register the uart interrupts */

	irqdbf("s3c2410: registering external interrupts\n");

	for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
		irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_uart0);
		set_irq_handler(irqno, handle_level_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
		irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_uart1);
		set_irq_handler(irqno, handle_level_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
		irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_uart2);
		set_irq_handler(irqno, handle_level_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
		irqdbf("registering irq %d (s3c adc irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_adc);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	irqdbf("s3c2410: registered interrupt handlers\n");
}
上面代码可以看到基本上就是设置不同中断的一些属性,只需要设置自己想要的中断就可以了:

        for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
		irqdbf("registering irq %d (extended s3c irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irqext_chip);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
我们的按键连接外部中断口对应在4-23中间,就是通过上面的代码设置好chip,handler,flags:

chip设置为:

static struct irq_chip s3c_irqext_chip = {
	.name		= "s3c-ext",
	.mask		= s3c_irqext_mask,
	.unmask		= s3c_irqext_unmask,
	.ack		= s3c_irqext_ack,
	.set_type	= s3c_irqext_type,
	.set_wake	= s3c_irqext_wake
};
第6行.set_type = s3c_irqext_type 设置好了,这个chip都是CPU相关的代码,需要去配置寄存器:

mask unmask:中断屏蔽/打开

ack:清中断标志位

set_type:将管脚设置为中断功能,设置中断触发类型

内核已经搭建好了一个中断框架,我们只需要去按照要求填写这个结构体就可以了,真正需要的中断处理在request_irq的时候传入中断服务函数;





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值