Chinaunix首页 | 论坛 | 博客
  • 博客访问: 46425
  • 博文数量: 13
  • 博客积分: 563
  • 博客等级: 下士
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-18 14:45
文章存档

2014年(1)

2011年(2)

2010年(2)

2009年(8)

分类: 嵌入式

2009-08-19 21:49:10

   这几天正在学习linux的设备驱动,至此算是有了一个皮毛的认识,但谈不上深入,毕竟接触才一个星期,在这里我就算是总结一下自己的笔记,希望能对某某人有点帮助,不对的牛人们帮指点一下,相信虚心努力就会有收获。废话不说,开始了:
   首先,来聊一下自己对驱动的认识,linux的驱动是在内核中实现的,驱动程序占据内核的80%以上,驱动是直接与硬件打交道的,在我们的应用程序中,通过系统调用函数,比如:read、wirte、ioctl、open等,来进入kernel中,kernel处理系统调用的时候就会调用驱动函数。
   由于驱动设备分为字符设备、块设备以及网络设备,一我目前所接触的,在这里只讲一下字符设备,虽然简单,但是对于熟悉驱动程序的修改和编写却是很有用。拿我今天作的 1*4 键盘来举例吧,此程序实现每按一次键就会产生一个中断,我是在ARM机上运行的,我就让它进入中断打印出中断号。首先我们先从最上层开始,即应用程序这里。我们在这一层只定义一个简单的测试程序,即来运用系统调用函数触发软件陷阱,使kernel来调用驱动程序,源码如下

int main()
    {
        int fd;
        char key;
        
        fd = open("filepath",O_RDWR); //打开一个字符设备,在下面驱动中定义

        if (fd < 0)
        {
            printf("open file error!\n");
            return 1;
        }

        while (1)
        {
            read(fd,&key,1);      //将键值读入key中               

    printf("key = %d\n",key);
        }    
    }

    有此上层程序,既可以来根据用到的系统调用函数来编写我们的驱动程序了,由于驱动程序存在于内核中,所以一半的思路是写好程序加进kernel中,然后编译kernel,这样就会带来种种的弊端,耗时耗功不说,很少有人能保证自己编译的驱动不会有BUG,一旦出现BUG,就会直接影响整个kernel,致使系统瘫痪。故linux给我们提供了两个函数和四个宏,在一个驱动模块中,这是必不可少的哦,现就简单的介绍一下他们,两个函数:

static int __init myModule_init (void)
    /*模块加载函数*/
static void __exit myModule_exit (void)
    /*模块卸载函数*/

*——init为驱动初始化函数,当加载这个驱动模块时候,就会进入这个函数中,此时就要提到四个宏中的之一了

module_init(myModule_init);
    /*注册模块加载函数*/

在linux中,系统提供了加载模块的命令即insmod + 模块名,这样就可以将自己编写的模块加载进kernel中了,此时可以用lsmod打印一下加载进来的模块,当输入insmod命令后,系统就会调用module_init()函数,module_init()函数就会调用myModule_init这个函数中,即对整个驱动进行初始化。同时相对应的卸载模块命令rmmod + 模块名,即可以将加载进来的模块从系统中删除,输入以上命令后,系统就会调用四个宏中的之二了

module_exit(myModule_exit);
    /*注册模块卸载函数*/

module_exit()函数就会调用上面的myModule_exit()函数,此函数将完成释放一些我们程序中申请的空间和中断的功能。
   另外两个宏就是对版权的作者声明之类的,形式固定:

MODULE_AUTHOR(“zhanglei”); /*声明模块作者*/
MODULE_LICENSE("Dual BSD/GPL");
/*模块许可证明,描述内核模块的许可权限*/

   linux各种设备驱动程序都是以模块的形式存在的,驱动程序同样遵循模块编程的各项原则,字符设备是最基本、最常用的设备,其本质就是将千差万别的各种硬件设备采用一个统一的接口封装起来,屏蔽了不同设备之间使用上的差异性,简化了应用层对硬件的操作,字符设备将各底层硬件设备封装成统一的结构体,用系统调用函数统一的进行读写,也就是说linux对所有的硬件操作统一做了抽象处理,用file_operations结构体来规定了驱动程序向应用程序提供的操作接口。

static struct file_operations key14_irq_fops = {
    owner:    THIS_MODULE,
    write:       key14_irq_write,  //write的接口
    read:        key14_irq_read,   //read的接口
    ioctl:       key14_irq_ioctl,  //ioctl的接口 
    open:        key14_irq_open,  // open的接口
    release:     key14_irq_release, //程序强制退出的接口
}

   在这里就要参考我们之前的上层应用程序了,在应用程序中我们用到了系统调用中的open和read函数,现在按上层应用程序的顺序来编写驱动程序,当上层调用open函数后,通过file_operations 中的open接口进入驱动程序中的 key14_irq_open 函数中,需要说明的是,底层函数与硬件接触紧密,其函数原型定义的参数基本上是固定的:

static int key14_irq_open(struct inode *inode, struct file *filp)


static int key14_irq_release(struct inode *inode, struct file *filp)

static ssize_t key14_irq_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)

static ssize_t key14_irq_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)

static int key14_irq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

    open 中我们先对键盘的低四位设置成中断方式,上拉电阻,开中断操作,其中使用MOD_INC_USE_COUNT增加驱动程序的使用次数,当模块使用次数不为0时,禁止卸载模块,与此相对应的是当应用程序关闭设备时,处理设备的关闭操作使用MOD_DEC_USE_COUNT来减少驱动程序的使用次数,配合open使用,来对模块使用次数进行计数

static int my_irq_open(struct inode *inode, struct file *filp)
{
    MOD_INC_USE_COUNT;
    GPFCON = 0xffaa; // gpf0~~gpf7 : 1111 1111 1010 1010

    GPFUP = 0xf0; //gpf0~~gpf7 : 1111 0000

    GPFDAT = 0x0f; //gpf0~~gpf7 : 0000 1111

    
    irq_open();
    
    PRINTK("my_irq open called!\n");
    return 0;
}

static int my_irq_release(struct inode *inode, struct file *filp)
{
    MOD_DEC_USE_COUNT;
    PRINTK("my_irq release called!\n");
    return 0;
}

   再回到上层应用程序中,开始进入while死循环中,进行read操作,驱动程序同open的方式一样进入read函数中,在这里需要提一下一个重要的函数,copy_to_user();与它对应的是在wirte函数中的copy_from_user();但在我的这个键盘函数中没有用到write操作,所以就在write中直接返回0了。用这两个函数主要是因为用户应用程序与驱动程序分属于不同的进程空间,因此二者之间的数据应当采用他们进行交换,其函数原型如下:

static inline int copy_from_user(void *to,const void *from, int c)
{
  memcpy_fromfs(to, from, c);
  return 0;
}
# define copy_to_user(t,f,n) (memcpy_tofs(t,f,n), 0)
void * memcpy(void * dest, const void *src, unsigned int count)
{
    unsigned long *tmp = (unsigned long *) dest, *s = (unsigned long *) src;

    count >>= 2;
    while (count--)
        *tmp++ = *s++;

    return dest;
}

   在这里我们就直接引用就可以了:

tatic ssize_t my_irq_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
    unsigned char key;
    down_interruptible(&lock);  //lock为全局信号量 为0时上层应用程序等待
    if (kfifo_len(buffer) >= sizeof(key))
        kfifo_get(buffer,&key,sizeof(key)); //kfifo为kernel中的一个队列

    copy_to_user(buf,&key,sizeof(key));
    return 0;
}

   现在来说上层程序的功能基本上算是实现了,不过还没完,在使用insmod来加载模块时候,要调用模块初始化函数,即上面提到的模块加载函数 *_init();所以要对我们设值的中断,其中我还加入了一些同步机制,如信号量、自旋锁、定时器,对这些都要初始化: 

static int __init s3c2410_irq_init(void)
{
    /* Module init code */
    PRINTK("s3c2410_irq_init\n");
    /* Driver register */
    my_irq_Major = register_chrdev(0, DRIVER_NAME, &my_irq_fops);
    if(my_irq_Major < 0)
    {
        PRINTK("register char device fail!\n");
        return my_irq_Major;
    }
    PRINTK("register my_irq OK! Major = %d\n", my_irq_Major);
    
    set_external_irq(IRQ_EINT0,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT0,key14_irq,SA_INTERRUPT,"key_01",(void *)0);
    set_external_irq(IRQ_EINT1,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT1,key14_irq,SA_INTERRUPT,"key_02",(void *)1);
    set_external_irq(IRQ_EINT2,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT2,key14_irq,SA_INTERRUPT,"key_03",(void *)2);
    set_external_irq(IRQ_EINT3,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT3,key14_irq,SA_INTERRUPT,"key_04",(void *)3);
    
    sema_init(&lock,0);
    
    init_timer(&myTimer);
//    myTimer.expires = jiffies + (5/100)*HZ; can't

    myTimer.data = 0L;
    myTimer.function = &timerHander;  //定时函数
    add_timer(&myTimer);
    
    buffer = kfifo_alloc(BUFFER_SIZE,GFP_KERNEL,&buffer_lock);
#ifdef CONFIG_DEVFS_FS
    devfs_my_irq_dir = devfs_mk_dir(NULL, "my_irq", NULL);
    devfs_my_irq_raw = devfs_register(devfs_my_irq_dir, "raw0", DEVFS_FL_DEFAULT, my_irq_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &my_irq_fops, NULL);
    PRINTK("add dev file to devfs OK!\n");
#endif
    return 0;
}

  至此当要用到rmmod时候,就要调用模块卸载函数,此时与上缅甸相反,将申请的空间和中断都释放了。

static void __exit s3c2410_irq_exit(void)
{
    kfifo_free(buffer);
    del_timer(&myTimer);
    free_irq(IRQ_EINT0,(void *)0);
    free_irq(IRQ_EINT1,(void *)1);
    free_irq(IRQ_EINT2,(void *)2);
    free_irq(IRQ_EINT3,(void *)3);
    /* Module exit code */
    PRINTK("s3c2410_irq_exit\n");
    /* Driver unregister */
    if(my_irq_Major > 0)
    {
#ifdef CONFIG_DEVFS_FS
        devfs_unregister(devfs_my_irq_raw);
        devfs_unregister(devfs_my_irq_dir);
#endif
        unregister_chrdev(my_irq_Major, DRIVER_NAME);
    }
    return;
}

   好了,整个键盘的驱动就做好了。编译好放在ARM上运行后,当按一下键盘就会打印出中断号,现在还有个BUG,就是由于我的键盘比较老,有时候按一下就会打印两次,想将源码附下,望高手指点。

#ifndef __KERNEL__
    #define __KERNEL__
#endif
#ifndef MODULE
    #define MODULE
#endif

#include
#include
#include     
#include     

#include
#include
#include
#include

#include
#include

#define DRIVER_NAME    "irq"
#define BUFFER_SIZE 256

#ifdef DEBUG
#define PRINTK(fmt, arg...)        printk(KERN_NOTICE fmt, ##arg)
#else
#define PRINTK(fmt, arg...)
#endif

unsigned int my_irq_Major = 0;  
static spinlock_t buffer_lock=SPIN_LOCK_UNLOCKED;
struct kfifo *buffer;
struct semaphore lock;
static struct timer_list myTimer;

void irq_open()
{
    enable_irq(IRQ_EINT0);
    enable_irq(IRQ_EINT1);
    enable_irq(IRQ_EINT2);
    enable_irq(IRQ_EINT3);
}
/* Driver Operation Functions */
static int my_irq_open(struct inode *inode, struct file *filp)
{
    MOD_INC_USE_COUNT;
    GPFCON = 0xffaa; // gpf0~~gpf7 : 1111 1111 1010 1010

    GPFUP = 0xf0; //gpf0~~gpf7 : 1111 0000

    GPFDAT = 0x0f; //gpf0~~gpf7 : 0000 1111

    
    irq_open();
    
    PRINTK("my_irq open called!\n");
    return 0;
}

static int my_irq_release(struct inode *inode, struct file *filp)
{
    MOD_DEC_USE_COUNT;
    PRINTK("my_irq release called!\n");
    return 0;
}

static ssize_t my_irq_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
    unsigned char key;
    down_interruptible(&lock);
    if (kfifo_len(buffer) >= sizeof(key))
        kfifo_get(buffer,&key,sizeof(key));

    copy_to_user(buf,&key,sizeof(key));
    return 0;
}

static ssize_t my_irq_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
    PRINTK("my_irq write called!\n");
    return 0;
}

static int my_irq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    return 0;
}

/* Driver Operation structure */
static struct file_operations my_irq_fops = {
    owner:    THIS_MODULE,
    write:        my_irq_write,
    read:        my_irq_read,
    ioctl:        my_irq_ioctl,
    open:        my_irq_open,
    release:    my_irq_release,
};

    
void key14_irq(int irq,void *dev_id,struct pt_regs *regs)
{
    disable_irq(irq);
    myTimer.expires = jiffies + (7/10)*HZ;
    add_timer(&myTimer);
    
    printk("IRQ_EXIT = %d\n",irq);
}

void timerHander(unsigned long data)
{
    unsigned char key;
    GPFCON = 0x00;
    key = GPFDAT;
    mdelay(200);
    key &= 0x0f;
    if (key != 0x0f)
    {
        key = (~key) & 0x0f;
        kfifo_put(buffer,&key,sizeof(key));
        up(&lock);
    }
    GPFCON = 0xaa;
    irq_open();
}

/* Module Init & Exit function */
#ifdef CONFIG_DEVFS_FS
devfs_handle_t devfs_my_irq_dir;
devfs_handle_t devfs_my_irq_raw;
#endif
static int __init s3c2410_irq_init(void)
{
    /* Module init code */
    PRINTK("s3c2410_irq_init\n");
    /* Driver register */
    my_irq_Major = register_chrdev(0, DRIVER_NAME, &my_irq_fops);
    if(my_irq_Major < 0)
    {
        PRINTK("register char device fail!\n");
        return my_irq_Major;
    }
    PRINTK("register my_irq OK! Major = %d\n", my_irq_Major);
    
    set_external_irq(IRQ_EINT0,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT0,key14_irq,SA_INTERRUPT,"key_01",(void *)0);
    set_external_irq(IRQ_EINT1,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT1,key14_irq,SA_INTERRUPT,"key_02",(void *)1);
    set_external_irq(IRQ_EINT2,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT2,key14_irq,SA_INTERRUPT,"key_03",(void *)2);
    set_external_irq(IRQ_EINT3,EXT_FALLING_EDGE,0);
    request_irq(IRQ_EINT3,key14_irq,SA_INTERRUPT,"key_04",(void *)3);
    
    sema_init(&lock,0);
    
    init_timer(&myTimer);
//    myTimer.expires = jiffies + (5/100)*HZ; can't

    myTimer.data = 0L;
    myTimer.function = &timerHander;
    add_timer(&myTimer);
    
    buffer = kfifo_alloc(BUFFER_SIZE,GFP_KERNEL,&buffer_lock);
#ifdef CONFIG_DEVFS_FS
    devfs_my_irq_dir = devfs_mk_dir(NULL, "my_irq", NULL);
    devfs_my_irq_raw = devfs_register(devfs_my_irq_dir, "raw0", DEVFS_FL_DEFAULT, my_irq_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &my_irq_fops, NULL);
    PRINTK("add dev file to devfs OK!\n");
#endif
    return 0;
}

static void __exit s3c2410_irq_exit(void)
{
    kfifo_free(buffer);
    del_timer(&myTimer);
    free_irq(IRQ_EINT0,(void *)0);
    free_irq(IRQ_EINT1,(void *)1);
    free_irq(IRQ_EINT2,(void *)2);
    free_irq(IRQ_EINT3,(void *)3);
    /* Module exit code */
    PRINTK("s3c2410_irq_exit\n");
    /* Driver unregister */
    if(my_irq_Major > 0)
    {
#ifdef CONFIG_DEVFS_FS
        devfs_unregister(devfs_my_irq_raw);
        devfs_unregister(devfs_my_irq_dir);
#endif
        unregister_chrdev(my_irq_Major, DRIVER_NAME);
    }
    return;
}

MODULE_AUTHOR("zhanglei");
MODULE_LICENSE("Dual BSD/GPL");
module_init(s3c2410_irq_init);
module_exit(s3c2410_irq_exit);

阅读(903) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~