前一段时间项目忙,中断了更新。
要想提高能力,最重要的还是实际动手写代码,这样才能遇到一些问题,解决问题增加经验。
今天介绍的功能是: 在linux中添加一个button驱动,按键控制LED灯亮灭,并且支持应用程序读取按键事件。
关键词:misc设备驱动,工作队列,等待队列。
下面根据代码来一点一点介绍:
1、列出我们定义的数据结构以及全局变量:
#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 <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio-bank-n.h>
#include <mach/gpio-bank-l.h>
#include <mach/gpio-bank-k.h>
#define DEVICE_NAME "my_buttons"
/*button 中断描述*/
struct button_irq_dec{
int irq;
int number;
char * name;
};
/*事件结构,应用程序中要与此结构一致,上报按键消息时直接把该结构体copy到用户空间*/
struct uevent{
char name[5];
char isDown;
};
static LIST_HEAD(button_list);// 消息队列
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); // 等待队列头
static DEFINE_MUTEX(button_mtx); // 锁
/*中断消息,里面包含上报应用程序的event,以及维护的链表和工作队列*/
struct message{
struct uevent event;
struct list_head list;
struct work_struct my_work;
};
/*预先定义好的buttom中断描述*/
struct button_irq_dec button_irqs[] = {
{IRQ_EINT(0), 0, "key1"},
{IRQ_EINT(1), 1, "KEY2"},
{IRQ_EINT(2), 2, "KEY3"},
{IRQ_EINT(3), 3, "KEY4"},
{IRQ_EINT(4), 4, "KEY5"},
{IRQ_EINT(5), 5, "KEY6"},
{IRQ_EINT(19), 6, "KEY7"},
{IRQ_EINT(20), 7, "KEY8"},
};
2、模块初始化函数
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.read = button_message_read,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init dev_init(void)
{
int ret, i, err;
unsigned tmp;
/*设置LED全灭状态*/
tmp = readl(S3C64XX_GPKCON);
tmp = (tmp & ~(0xffffU<<16))|(0x1111U<<16);
writel(tmp, S3C64XX_GPKCON);
tmp = readl(S3C64XX_GPKDAT);
tmp |= (0xF << 4);
writel(tmp, S3C64XX_GPKDAT);
/*申请中断*/
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
/*IRQ_TYPE_EDGE_BOTH 边沿触发
中断处理函数: buttons_interrupt 参数:&button_irqs[i]
*/
err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH,
button_irqs[i].name, (void *)&button_irqs[i]);
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;
}
ret = misc_register(&misc);
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
当中断产生时,就会调用到button_interrupt函数。
3、button_interrupt函数
static irqreturn_t buttons_interrupt(int irq, void* pram)
{
struct button_irq_dec *irq_dec = (struct button_irq_dec *)pram;
char down = 1;
int number;
unsigned tmp_key;
unsigned tmp_led;
struct message *msg;
/*获取产生中断对应的按键号*/
number = irq_dec->number;
/*根据按键号处理LED灯状态*/
switch(number)
{
case 0:
case 1:
case 2:
case 3:
tmp_led = readl(S3C64XX_GPKDAT);
tmp_led = tmp_led & ~(0x10 << number);/*点亮LED灯*/
writel(tmp_led, S3C64XX_GPKDAT);
break;
case 4:
case 5:
case 6:
case 7:
tmp_led = readl(S3C64XX_GPKDAT);
tmp_led = tmp_led | (1 << number);/*熄灭LED灯*/
writel(tmp_led, S3C64XX_GPKDAT);
break;
}
//处理按键消息,获取按键号对应的状态:down or up
switch(number)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
tmp_key = readl(S3C64XX_GPNDAT);
down = !(tmp_key & (1 << number));
break;
case 6:
case 7:
tmp_key = readl(S3C64XX_GPLDAT);
down = !(tmp_key & (1 << (number + 5)));
break;
default:
break;
}
/*申请一个msg空间,并填充其中的event,然后调用工作队列*/
msg = (struct message *)kmalloc(sizeof(struct message), GFP_ATOMIC);
if(msg != NULL)
{
strcpy(msg->event.name, irq_dec->name);
msg->event.isDown = down;
/*设置工作队列的函数*/
INIT_WORK(&msg->my_work, add_message);
/*启动工作队列*/
schedule_work(&msg->my_work);
}
return IRQ_RETVAL(IRQ_HANDLED);
}
上面的中断函数使用工作队列的原因是:新建一个message,填充好event后,要添加到button_list中,但是在read函数中要把该链表的event返回到应用程序,所以要加锁控制同步,在中断程序中不能调用加锁函数(因为会导致睡眠),所以此处使用工作队列。
工作队列的使用:INIT_WORK中只设置了函数地址,而函数的参数是work_struct对象地址。所以要把工作队列定义在message结构体中,这样在工作队列的函数中可以通过contain_of来获取我们要传递的参数。
add_message函数:
static void add_message(struct work_struct * pram)
{
struct message *msg = container_of(pram, struct message, my_work);
if(msg ==NULL)
return ;
mutex_lock(&button_mtx);
list_add(&msg->list, &button_list);
wake_up_interruptible(&button_waitq);
mutex_unlock(&button_mtx);
return ;
}
到这里,按键中断的处理以及返回给应用程序的按键消息已经封装好了,下面来看read函数的实现。
4、read函数
static ssize_t button_message_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned int number, i = 0, counts;
char is_wait = 0;
struct message * msg, *pre = NULL;
DECLARE_WAITQUEUE(my_wait,current);
mutex_lock(&button_mtx);
/*如果链表为空,说明没有按键消息,那么就等待*/
if(list_empty(&button_list))
{
mutex_unlock(&button_mtx);
is_wait = 1;
/*添加等待队列,设置当前进程状态,schedule()让出cpu*/
add_wait_queue(&button_waitq, &my_wait);
current->state = TASK_INTERRUPTIBLE;
schedule();
}
else
{
mutex_unlock(&button_mtx);
}
/*返回整数个struct uevent结构体*/
number = count / (sizeof(struct uevent));
mutex_lock(&button_mtx);
/*遍历链表,获取每个event*/
list_for_each_entry_reverse(msg, &button_list, list)
{
/*遍历的同时,删除上一个event*/
if(pre != NULL)
{
list_del(&pre->list);
kfree(pre);
pre = NULL;
}
/*把event copy到用户空间*/
counts = copy_to_user(buf + (i *(sizeof(struct uevent))), &msg->event, sizeof(struct uevent));
pre = msg;
i++;
if(i == number)
break;
}
if(pre != NULL)
{
list_del(&pre->list);
kfree(pre);
pre = NULL;
}
mutex_unlock(&button_mtx);
if(is_wait){
/*移出等待队列*/
remove_wait_queue(&button_waitq, &my_wait);
set_current_state(TASK_RUNNING);
}
return number*(sizeof(struct uevent));
}
这样,驱动的主要函数都已经实现了,下面就列出应用程序如何来完成。
5、应用程序代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct uevent{
char name[5];
char isDown;
};
int main()
{
struct uevent event;
int fd = open("/dev/my_buttons", 0);
if (fd < 0) {
perror("open device leds");
exit(1);
}
while(1)
{
read(fd, &event, sizeof(struct uevent));
printf("%s is %s", event.name, event.isDown? "down\n": "up\n");
}
close(fd);
}
6、代码的编译
驱动的makefile文件:
KERNELDIR:=/work/tiny6410/Linux/linux-2.6.38 //这是我的kernel代码位置
PWD:=$(shell pwd)
obj-m:=button.o
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko
应用程序的编译:
arm-linux-gcc test.c -omain
本人所有文章目录:http://blog.csdn.net/lrs030740304/article/details/7941984