硬件环境:Samsung Cortex-A9 Exynos4412 BSP
软件环境:Ubuntu
=====================================================
从ARM裸机看驱动相关文章列表:
从ARM裸机看驱动之按键中断方式控制LED(一) http://blog.csdn.net/u010872301/article/details/78494383
=====================================================
为了解决既要中断执行快,又要做事情多的矛盾,在linux中将中断处理分为上下半部。在上半部,中断处理程序完成中断请求的响应以及完成那些对时间要求紧迫的工作;在下半部,完成那些耗时的工作。从下半部分执行机制来看——不管是tasklet(运行于中断上下文)还是工作队列( 运行于进程上下文)——这些推后的工作总是在上半部分被调用,然后交给内核在适当的时间来完成。
void task_func(unsigned long data) //tasklet 的中断底半部处理函数
{
int ret;
static int gpx1_1dat;
static int count1 = 0;
ret = gpio_get_value(gpx1_1dat);
printk("ret:%d\n",ret);
if(ret){
printk("key interrupt fail...\n");
}else{
mdelay(data);
while(!gpio_get_value(gpx1_1dat));//等待按键抬起
count1++;
printk("key interrupting..., count;%d\n",count1);
}
}
struct tasklet_struct task1={ //底半部结构体
.func = task_func,
.data = 5,
};
//DECLARE_TASKLET(task1,task_func,0); //绑定tasklet结构体及其中断处理函数
irqreturn_t key_handler(int irq, void *data) //中断顶半部处理函数
{
tasklet_schedule(&task1); //在中断顶半部处理函数中对底半部函数进行调度
return IRQ_HANDLED; //中断处理完成返回标志
}
static int __init demo_init(void)
{
int ret = 0;
。。。。。
ret = request_irq(irq,key_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,DEV_NAME,NULL);//申请中断号
if(ret){
printk("request irq fail!\n");
return ret;
}
。。。。。
}
arm裸机中,在执行程序的过程中首先将寄存器的值压入堆栈,保存现场。然后调用do_IRQ函数,在do_IRQ函数中会调用到handle_IRQ_event函数,执行中断服务程序。
linux内核中,在do_IRQ函数中,通过对irq_desc结构体中handler_irq字段的引用,调用handler_irq所指向的服务程序;在这个服务程序中会调用hand_IRQ_event函数;在hand_IRQ_event函数中,通过对irqaction结构体中handler字段的引用最终调用我们所写的中断处理程序。
三个数据结构关系:
(1)struct irq_chip描述了中断最底层的部分,提供底层的中断处理接口函数。
(2)struct irq_desc将中断中的硬件相关的部分和软件相关的部分连接起来,每个中断源对应一个irq_desc结构体。
(3)struct irqacton则描述上层具体的中断处理函数,每个设备共享一条中断请求(IRQ)线时,通过irqaction结构体区分不同设备中断服务程序(ISR)。
功能实现
通过中断方式检测按键是否按下并在中断服务函数中写处理函数,灯会闪烁一次。
(1)设备树配置
在fs4421开发板的根节点下创建两个子节点LED和KEY按键,并添加属性信息。设备树是从Linux3.x内核开始引入的概念,具体的使用请参考 ARM Linux 3.x的设备树(Device Tree) http://blog.csdn.net/u010872301/article/details/72599115(2)使用jiffies消抖
按键按下时LED在闪烁,因为按下和释放的瞬间都有抖动现象,为了稳定按一下灯亮再按一下灭,我们用一个jiffies时间差值做判断(if(jiffies - last > 50)),进行消抖。
(3)在linux中设备是设备,驱动是驱动,根据linux设备驱动分离思想,我们通过APP应用程序控制两个的字符设备驱动(LED、按键)。
源代码
1、LED设备驱动#include <linux/module.h>#include <linux/kernel.h>2、KEY按键设备驱动
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include "led.h"
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("for linux driver led");
struct resource * gpx2con_res;
struct resource * gpx2dat_res;
unsigned int * gpx2con;
unsigned int * gpx2dat;
int major = 255;
int min = 0;
dev_t devno;
struct cdev cdev;
struct class * led_class;
static int led_open(struct inode * inodep, struct file * file)
{
printk("led_open\n");
return 0;
}
static int led_release(struct inode * inodep, struct file * file)
{
printk("led_release\n");
return 0;
}
static long led_ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
//printk("led_ioctl\n");
switch(cmd){
case LED_ON:
printk("LED_ON\n");
writel(readl(gpx2dat)|(0x1<<7), gpx2dat);
break;
case LED_OFF:
writel((readl(gpx2dat)&(~(1<<7))), gpx2dat);
break;
default:
break;
}
return 0;
}
struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int led_probe(struct platform_device * dev)
{
int ret;
printk("platform: match ok!\n");
gpx2con_res = platform_get_resource(dev, IORESOURCE_MEM, 0);
if (gpx2con_res == NULL){
printk("no resource gpx2con_res\n");
return -1;
}
printk("start: %#x\n", gpx2con_res->start);
printk("end: %#x\n", gpx2con_res->end);
printk("+++++++++++++++++++++++++++++++++++++++\n");
gpx2dat_res = platform_get_resource(dev, IORESOURCE_MEM, 1);
if (gpx2dat_res == NULL){
printk("no resource gpx2dat_res\n");
return -1;
}
printk("start: %#x\n", gpx2dat_res->start);
printk("end: %#x\n", gpx2dat_res->end);
gpx2con = ioremap(gpx2con_res->start, gpx2con_res->end - gpx2con_res->start);
if (gpx2con == NULL){
printk("ioremap err\n");
goto err1;
}
gpx2dat = ioremap(gpx2dat_res->start, gpx2dat_res->end - gpx2dat_res->start);
if (gpx2dat == NULL){
printk("ioremap err\n");
goto err2;
}
writel((readl(gpx2con)&(~(0xf<<28)))|(0x1<<28), gpx2con);
writel(readl(gpx2dat)|(0x1<<7), gpx2dat);
devno = MKDEV(major, min);
ret = register_chrdev_region(devno, 1, "led device");
if (ret < 0){
printk("register_chrdev_region err\n");
goto err3;
}
cdev_init(&cdev, &led_fops);
cdev.owner = THIS_MODULE;
ret = cdev_add(&cdev, devno, 1);
if (ret < 0){
printk("cdev_add err\n");
goto err4;
}
led_class = class_create(THIS_MODULE, "led_class");
device_create(led_class, NULL, devno, NULL, "led");
return 0;
err4:
unregister_chrdev_region(devno, 1);
err3:
iounmap(gpx2dat);
err2:
iounmap(gpx2con);
err1:
return -1;
}
static int led_remove(struct platform_device * dev)
{
printk("platform: led_remove!\n");
writel((readl(gpx2dat)&(~(1<<7))), gpx2dat);
device_destroy(led_class, devno);
class_destroy(led_class);
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
iounmap(gpx2dat);
iounmap(gpx2con);
return 0;
}
struct of_device_id led2_dev[] = {
{
.compatible = "farsight, led2",
},
{},
};
struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "11111111111111",
.of_match_table = led2_dev,
},
};
static int led_init(void)
{
printk("led_init\n");
platform_driver_register(&led_driver);
return 0;
}
static void led_exit(void)
{
printk("led_exit\n");
platform_driver_unregister(&led_driver);
return;
}
module_init(led_init);
module_exit(led_exit);
#include <linux/init.h>#include <linux/time.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/version.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/gpio.h>#include <linux/of_gpio.h>#include <linux/irq.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/version.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/fs.h>#include <asm/uaccess.h> #include <asm/ioctl.h>#include <linux/mm.h>#include <linux/wait.h>MODULE_LICENSE("Dual BSD/GPL");MODULE_DESCRIPTION("for linux driver key");unsigned int num1 = 0;struct mycdev{ dev_t devnum; struct cdev *cdev; wait_queue_head_t readque; int irqnum; int keyflag; int keycode;}mydev;int myopen(struct inode *inodep, struct file *filep){ printk("open: %d\n", iminor(inodep)); return 0;}irqreturn_t myhandler(int num, void *data){ static unsigned long last = 0; if(jiffies - last > 50){ if(num1 % 2){ mydev.keycode = '0'; num1++; } else { mydev.keycode = '1'; num1++; } mydev.keyflag = 1; wake_up_interruptible(&mydev.readque); last = jiffies; } return IRQ_HANDLED;}ssize_t myread(struct file *filep, char __user *ubufer, size_t size, loff_t *offset){ wait_event_interruptible(mydev.readque, 0 != mydev.keyflag); put_user(mydev.keycode, ubufer); mydev.keyflag = 0; return 1;}ssize_t mywrite(struct file *filep, const char __user *ubufer, size_t size, loff_t *offset){ return 0;}int myclose(struct inode *inodep, struct file *filep){ return 0;}long myunlocked_ioctl(struct file *filep, unsigned int cmd, unsigned long arg){ return 0;}int mymmap(struct file *filep, struct vm_area_struct *vma){ return 0;}struct file_operations mycdev_ops = { .owner = THIS_MODULE, .open = myopen, .read = myread, .write = mywrite, .unlocked_ioctl = myunlocked_ioctl, .mmap = mymmap, .release = myclose,};int setup_mydev(void){ int ret; ret = alloc_chrdev_region(&mydev.devnum, 0, 1, "mycdev_test"); if(ret < 0) { printk("devnum alloc failed !\n"); return ret; } printk("num: %d\n", MAJOR(mydev.devnum) ); init_waitqueue_head(&mydev.readque); mydev.keyflag = 0; mydev.cdev = cdev_alloc(); mydev.cdev->ops = &mycdev_ops; mydev.cdev->owner = THIS_MODULE; ret = cdev_add(mydev.cdev, mydev.devnum, 1); if(ret){ printk("devnum alloc failed !\n"); goto add_failed; } if( request_irq(mydev.irqnum, myhandler, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "test irq", NULL) ){ ;//... } return 0; cdev_del(mydev.cdev);add_failed: unregister_chrdev_region(mydev.devnum, 1); return ret;}void remove_mycdev(void){ cdev_del(mydev.cdev); unregister_chrdev_region(mydev.devnum, 4);}struct platform_driver myplatformdriver = {0};struct resource *myresource_irq;int myprobe(struct platform_device *platformdev){ printk("matched !\n"); myresource_irq = platform_get_resource(platformdev, IORESOURCE_IRQ, 0); mydev.irqnum = myresource_irq->start; setup_mydev();/* cdev_add(); ... ...*/ return 0;}int myremove(struct platform_device *platformdev){ printk("dev removed !\n"); free_irq(mydev.irqnum, NULL); remove_mycdev(); return 0;}struct of_device_id my_dts_table[2] = { [0] = { .compatible = "xxx", },};static int mymodule_init(void){ printk("module install\n"); myplatformdriver.driver.name = "aaaaaaaaaaa"; myplatformdriver.driver.of_match_table = my_dts_table; myplatformdriver.driver.owner = THIS_MODULE; myplatformdriver.probe = myprobe; myplatformdriver.remove = myremove; platform_driver_register(&myplatformdriver); return 0;}static void mymodule_exit(void){ printk("module release\n"); free_irq(myresource_irq->start, NULL); platform_driver_unregister(&myplatformdriver);}module_init(mymodule_init);module_exit(mymodule_exit);3、应用程序
#include <stdio.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#include <sys/ioctl.h>#include <sys/types.h>#include "led.h"int main(int argc, const char *argv[]){ int fd,fd1; char buf[64] = ""; fd = open("/dev/key", O_RDWR); fd1 = open("/dev/led", O_RDWR); if (fd < 0){ perror("open /dev/key err"); return -1; } if (fd1 < 0) { perror("open /dev/led err"); return -1; } //printf("open success\n"); while(1) { read(fd, buf, sizeof(buf)); //printf("buf = %s\n", buf); switch (buf[0]){ case '1': printf("LED_OFF\n"); ioctl(fd1, LED_OFF); //printf("<%d>\n",cmd); break; case '0': ioctl(fd1, LED_ON); break; default: break; } //printf("buf = %s\n", buf); } close(fd); return 0;}
运行结果
1、 进入内核源码修改 arm/arm/boot/dts/exynos4412-fs4412.dts 设备树文件,添加如下内容
2、 重新编译 dts 文件并拷贝到arm开发板共享的/tftpboot 目录下
$ make dtbs
$ cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot
3、 编译驱动模块
$ make
4、 将 ko 文件和测试程序拷贝到根文件系统中
$ cp *.ko /source/rootfs
$ cp a.out /source/rootfs
5、 板子上插入led和按键模块
# sudo insmod fs4412-led2.ko
# mknod /dev/led c 250 0
# sudo insmod platform_driver.ko
# mknod /dev/key c 251 0
6、 测试功能
# ./a.out