Linux字符设备驱动之Tiny6410 LED驱动分析

来源:互联网 发布:数据库备份类型 编辑:程序博客网 时间:2024/05/08 11:27

摘要 : 驱动程序是应用程序和底层硬件之间的桥梁,非常重要。字符设备是一种可以当做一个字节流来存取的设备,这样的设备只能一个字节一个字节的进行数据传输,这样的驱动常常至少实现open、close、read、和write系统条用,常见的有串口、LED、文本控制台等,字符设备通过文件系统节点来存取,例如/dev/tty1和/dev/lp0.在一个字符设备和一个普通文件之间唯一相关的不同就是,你可以在普通的文件中移来移去,但是大部分字符社诶仅仅是数据通道,只能顺序存取。

 

重要概念

1.     用户空间和内核空间

一个驱动模块在内核空间运行,而应用程序则是在用户空间运行,这个概念是操作系统的理论基础。Linux为这两种空间之间的数据传输定义了两个函数,分别为copy_to_user()和copy_from_user(),从字面意思可以知道函数的意义。比如在编写驱动程序时,很显然驱动程序属于内核空间,会经常使用copy_from_user()函数,从应用程序中获取数据给驱动程序。

2.     编译模块

使用make xxxx  modules 生成xxx.ko模块。

3.     加载和卸载模块(驱动)

驱动生成的模块需要加载到内核中,加载模块使用insmod指令,

如:insmod xxx.ko

卸载驱动则用rmmod命令。

4.     所需头文件

#include<linux/module.h>  //包含大量加载模块所需的函数和符号定义

#include<linux/init.h>      //制定初始化和清理函数

 

许可凭证指令:MODULE_LICENSE(“GPL”);

5.     初始化和关停

初始化指令:static  int __init  initialization_function(void)

                             {

}

   module_init(initialization_function);

   初始化函数应声明为静态,因为他们不会再特定文件之外可见,毕竟static的重点应用是“隐藏”。声明中的__init标识是给内核一个暗示,该函数只是在初始化时使用,一旦模块加载者在模块加载后会丢掉这个初始化函数,使它的内存用作他用。

module_init是强制的,该宏定义增加了特别的段到模块目标代码中,表明在哪里找到模块的初始化函数,没有这个定义,初始化函数不会被调用。

 

清理函数:static void__exit  cleanup_function(void)

                           {

}

module_exit(cleanup_function);

     清理函数没有返回值,用于模块的卸载。

6.     主、次设备号

字符设备的索引节点,是操作系统访问该设备驱动的入口点,尤其主设备号尤为重要。

在linux中,设备号是一个dev_t类型数据,其中dev_t其实就是一个32位数,低12位为主设备号,高20位为次设备号。获取主次设备号,使用宏定义:

MAJOR(dev_t  dev);

MINOR(dev_t  dev);

而如果有了主次设备号,转换成dev_t数据,也是用宏定义

MKDEV(intmajor,int minor);

7.     注册和释放设备

在建立一个字符驱动时,第一件重要的事情就是注册设备编号,常见的有3个函数完成这一功能,分别为:register_chrdev_region()、alloc_chrdev_region()和register_chrdev().

在内核中所有已经分配的字符设备驱动都是记录在一个名为chrdevs的列表中。

alloc_chrdev_region()是动态注册分配主次设备号。

register_chrdev_region()是已经知道设备主次设备号进行注册。

   register_chrdev()是老版本的设备号注册方式。

这3个函数一方面是完成设备注册到内核中,另外一方面是将该设备与对应的file_operations结构体进行链接,这样就实现了驱动程序“接口”的链接。

   该3个函数一般用于设备初始化函数 __init   func(void)中。

8.     重要数据结构

file_operations   :文件操作数据结构,定义了字符设备驱动的接口,由于所有的驱动接口名称是相同的,都是open,read,write等,所以要求不同的设备对应不同的xxx_open,xxx_read,xxx_write等,这些的接口就定义在file_openations结构体中。

常见的如:

struct  file_operaions  xxx_fops = {

.owner =THIS_MODULE,

.llseek =xxx_llseek,

.read = xxx_read,

.write =xxx_write,

.ioctl =xxx_ioctl,

.open = xxx_open,

.release =xxx_release,

};

其中open为设备的打开程序,如串口打开,LED接口定义等。而release函数的功能与open相反,用于设备的释放或者关闭,如串口关闭等。


Tiny6410开发板的LED驱动

下面是针对tiny6410开发板的LED进行开发的驱动文件tiny6410_leds.c的详细分析:

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <mach/regs-gpio.h>

#include <mach/hardware.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/mm.h>

#include <linux/fs.h>

#include <linux/types.h>

#include <linux/delay.h>

#include <linux/moduleparam.h>

#include <linux/slab.h>

#include <linux/errno.h>

#include <linux/ioctl.h>

#include <linux/cdev.h>

#include <linux/string.h>

#include <linux/list.h>

#include <linux/pci.h>

#include <asm/uaccess.h>

#include <asm/atomic.h>

#include <asm/unistd.h>

 

#include <mach/map.h>

#include <mach/regs-clock.h>

#include <mach/regs-gpio.h>

 

#include <plat/gpio-cfg.h>

#include <mach/gpio-bank-e.h>

#include <mach/gpio-bank-k.h>

 

#define LED_MAJOR 243

 

#define LED_ON      1

#define LED_OFF     0

#define LED_1_ON   2

#define LED_1_OFF 3

#define LED_2_ON   4

#define LED_2_OFF 5

#define LED_3_ON   6

#define LED_3_OFF 7

#define LED_4_ON   8

#define LED_4_OFF 9

 

/*************.open函数*****************/

static int led_open(struct inode *inode,struct file *file)

{

       unsigned tmp;

       tmp =readl(S3C64XX_GPKCON);

       tmp =(tmp&0x0000ffff)|0x1111ffff;

       writel(tmp,S3C64XX_GPKCON);

       printk("*************open************");

       return 0;

}

/*************.read函数******************/

static int led_read(struct file *file,char __user *buf,size_tcount,loff_t *f_pos)

{

       return count;

}

 

/*************.write函数******************/

static int led_write(struct file *filp,const char __user *buf,size_tcount,loff_t *f_pos)

{

       char wbuf[10];

       unsigned tmp;

       if (copy_from_user(wbuf,buf,count))

              return -EFAULT;

 

       switch(wbuf[0])

       {

              case LED_ON:

                     tmp =readl(S3C64XX_GPKDAT);

                  tmp &=(0x0f);

                  writel(tmp,S3C64XX_GPKDAT);

                  printk("turn on!\n");

                  break;

              case LED_OFF:

                     tmp =readl(S3C64XX_GPKDAT);

                     tmp &=(0xf0);

                  writel(tmp,S3C64XX_GPKDAT);

                  printk("turn off!\n");

                  break;

              case LED_1_ON:

                  tmp = readl(S3C64XX_GPKDAT);

                  tmp &=(0xef);

                  writel(tmp,S3C64XX_GPKDAT);

                  printk("turn LED1 on!\n");

                  break;

              case LED_1_OFF:   

            tmp =readl(S3C64XX_GPKDAT);      

            tmp |= (0xf0);      

            writel(tmp,S3C64XX_GPKDAT); 

            printk("turnLED1 off!\n");     

            break;

        case LED_2_ON:   

            tmp =readl(S3C64XX_GPKDAT);      

            tmp &=(0xdf);      

            writel(tmp,S3C64XX_GPKDAT);  

            printk("turnled2 off!\n");    

            break;   

 

        case LED_2_OFF:  

            tmp =readl(S3C64XX_GPKDAT);      

            tmp |= (0xf0);      

            writel(tmp,S3C64XX_GPKDAT); 

            printk("turnled2 on!\n");     

            break;   

 

        case LED_3_ON:   

            tmp =readl(S3C64XX_GPKDAT);      

            tmp &=(0xbf);      

            writel(tmp,S3C64XX_GPKDAT);  

            printk("turnled3 off!\n");    

            break;   

 

        case LED_3_OFF:  

            tmp =readl(S3C64XX_GPKDAT);      

            tmp |= (0xf0);      

            writel(tmp,S3C64XX_GPKDAT); 

            printk("turn led3 on!\n");     

                break;   

 

        case LED_4_ON:   

            tmp =readl(S3C64XX_GPKDAT);      

            tmp &=(0x7f);      

            writel(tmp,S3C64XX_GPKDAT);  

            printk("turnled4 off!\n");    

            break;   

 

        case LED_4_OFF:  

            tmp  = readl(S3C64XX_GPKDAT);      

            tmp |= (0xf0);      

            writel(tmp,S3C64XX_GPKDAT); 

            printk("turnled4 on!\n");     

            break; 

 

            default:

                break;

       }

       return 0;

}

/*************.releae函数******************/

int led_release(struct inode *inode,struct file *file)

{

       printk("**************release*********");

       return 0;

}

 

/*************.file_operations 结构体******************/

struct file_operations led_fops =

{

       .owner = THIS_MODULE,

       .open = led_open,

       .read = led_read,

       .write = led_write,

       .release = led_release,

};

 

/*************init初始化函数 ******************/

int __init led_init(void)

{

       int rc;

       printk("test leddev\n");

       rc =register_chrdev(LED_MAJOR,"led",&led_fops);   //重要

 

       if(rc<0)

       {

              printk("register%s char dev error\n","led");

              return -1;

       }

       printk("ok!\n");

       return 0;

}

 

void __exit led_exit(void)

{

       unregister_chrdev(LED_MAJOR,"lde");

       printk("moduleexit\n");

       return ;

}

 

module_init(led_init);

module_exit(led_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("JSQ");

 

 

生成tiny6410_led.ko所需的Makefile脚本

obj-m := tiny6410_leds.o   

KDIR :=/opt/FriendlyARM/mini6410/linux/linux-2.6.38

all:   

    make -C $(KDIR) M=$(shellpwd) modules   

install:   

    cp tiny6410_leds.ko  /opt/FriendlyARM/mini6410/linux/linux-2.6.38/drivers/char  

clean:   

    make -C $(KDIR) M=$(shellpwd) clean  

 

其中:make -C $(KDIR)M=$(shell pwd) modules 命令是make modules(生成模块.ko)命令的扩展,它是改变它的目录到-C选项提供的目录下(内核源码目录),它在哪里会发现内核的顶层makefile,这个M=选项使makefile在试图建立模块钱汇到你的模块源码目录,这个目标,依次的是指在obj-m变量中发现的模块列表,在本例中是tiny6410_leds.o

 

加载设备驱动模块,并挂载设备驱动

加载驱动:insmod tiny6410_leds.ko

挂载设备节点: mknod /dev/leds  c  243  0

 

测试程序tiny6410_leds_test.c:

#include <stdio.h>   

#include <sys/types.h>   

#include <sys/stat.h>   

#include <fcntl.h>   

 

 

#define LED_OFF     0 

#define LED_ON      1 

#define LED_1_ON    2 

#define LED_1_OFF   3 

#define LED_2_ON    4 

#define LED_2_OFF   5 

#define LED_3_ON    6 

#define LED_3_OFF   7 

#define LED_4_ON    8 

#define LED_4_OFF   9 

 

int main (void)   

{   

    int  i=0; 

    int  fd;   

    char buf[10]={ 

            LED_ON ,   LED_OFF , 

            LED_1_ON,  LED_1_OFF, 

            LED_2_ON, LED_2_OFF, 

            LED_3_ON,  LED_3_OFF, 

            LED_4_ON,  LED_4_OFF,            

         };   

 

    fd =open("/dev/led",O_RDWR);      //打开驱动,开始使用驱动

    if (fd < 0)   

    {   

        printf ("Open/dev/led file error\n");   

        return -1;   

    }      

 

    while(i<10)   

    {   

       write(fd,&buf[i],4);     //使用驱动接口函数write

        sleep(1);   

        i++; 

    }   

    close (fd);   

    return 0;   

   

}   

 

应用程序tiny6410_leds_test.c的编译 Makefile

 

ifndef DESTDIR

DESTDIR                        ?= /tmp/FriendlyARM/mini6410/rootfs

endif

 

CFLAGS                       =-Wall -O2

CC                               =arm-linux-gcc

INSTALL                      =install

 

TARGET                      =led_test

 

 

all: $(TARGET)

 

led_test: tiny6410_leds_test.c

       $(CC) $(CFLAGS) $< -o $@

 

 

install: $(TARGET)

       $(INSTALL) $^$(DESTDIR)/usr/bin

 

clean distclean:

       rm -rf *.o $(TARGET)

 

 

#----------------------------------------------------------------------------

 

.PHONY: $(PHONY) install clean distclean

 

# End of file

# vim: syntax=make

 

运行测试程序:./tiny610_leds_test 

测试结果---串口输出:


其中tiny6410开发板指示灯与测试程序定义相同,而且通过这个测试发现,release函数还是很有必要的,能够终止该进程,否则会一直死循环。


 

 

 

 

0 0