En 0755-86038900
公司新闻

4412开发板android入门篇_字符设备驱动框架

发布时间:2015-03-11

本章部分内容参看了《linux设备驱动详解》

第一部分为以前学习linux设备驱动详解一书时记录的一些字符设备常用函数及结构体,第二部分为以字符设备为模板写的LED驱动程序。

linux驱动程序一般分为三类:字符设备,块设备,网络设备

 

字符设备:

       字符设备以字节流的方式被访问,也即对字符设备的读写操作是以字节为单位的,字符设备的操作函数一般用到openclosereadwrite等系统调用的函数。

常用的串口等设备的数据传输也是以字节为单位进行数据的交互。

 

块设备:

       在块设备上数据以块的方式被存放,比如flashSD卡等上的数据以页为单位进行读写。对SD卡,硬盘等块设备的读写,应用程序可以使用openopen

       closereadwrite等系统调用函数对块设备以任意字节进行读写。

 

网络设备:

       网络设备同时具有字符设备,块设备的特点,数据的读写有一定的格式,以socket包的方式进行数据的传输。

 

编写设备驱动程序的一般步骤:

      1.查看原理图硬件连接,查看控制设备数据手册,了解kernel中设备的操作函数集

      2.kernel中找到相似的设备驱动程序仿写。一般情况芯片商会提供相应芯片的驱动程序模板

      3.实现驱动程序的初始化,及设备注册到kernel,创建设备节点

      4.实现设备控制的操作函数集,如,常用的系统调用函数openclosereadwrite等。

      5.将驱动程序编译进kernel

      6.编写应用测试驱动程序。

一.字符设备的常用函数

   1.驱动程序中设备的加载和卸载函数

     module_initmodule_exit,在写模块的时候这两个函数分别在insmod的时候和rmmod的时候调用。

      调用module_init函数用来向kernel中注册驱动程序,调用module_exit下载驱动程序。

 

二..字符设备常用函数和结构体

1.描述字符设备的结构体cdev

struct cdev {

 struct kobject kobj;   

 struct module *owner; 

 const struct file_operations *ops;

 struct list_head list;

 dev_t dev;   //设备号

 unsigned int count;

};

cdev结构体的dev_t 成员定义了设备号,为32 位,其中高12 位为主设备号,低 20 位为次设备号。

使用下列宏可以从dev_t获得主设备号和次设备号

MAJOR(dev_t dev)

MINOR(dev_t dev)

而使用下列宏则可以通过主设备号和设备号生成dev_t。

MKDEV(int major, int minor)

2.操作cdev结构体用到的函数

 linux/fs/char_dev.c

此函数用于初始化cdev结构体的成员

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

此函数为cdev结构体申请一块内存

struct cdev *cdev_alloc(void)

此函数向内核注册cdev

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

此函数从内核删除cdev

void cdev_del(struct cdev *p)

3.设备号的申请与释放函数

为一个字符驱动一个或多个设备编号来使用

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数:from:是你要分配的起始设备编号,常常取值为0

           count:是你请求的连续设备编号的总数

           是应当连接到这个编号范围的设备的名字它会出现在 /proc/devices 和 sysfs .

返回值:成功返回0,出错返回负数

用于设备号未知,向系统动态申请未被占用的设备号的情况

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,  const char *name)

作用:该函数需要传递给它指定的第一个次设备号baseminor(一般为0)和要分配的设备数count,以及设备名,调用该函数后自动分配得到的设备号保存在dev中。

baseminor: 通常为0;
*dev:存放返回的设备号;

count:连续编号范围.  
这个意思说假如major是248,count是2的话,249也就是相当于被使用的了

成功返回0,失败返回-1;

释放申请的设备号

void unregister_chrdev_region(dev_t from, unsigned count);

4.file_operations结构体

struct file_operations {

 struct module *owner;  // 拥有该结构的模块的指针,一般为THIS_MODULES

 loff_t (*llseek) (struct file *, loff_t, int);   // 用来修改文件当前的读写位置

 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  // 从设备中同步读取数据

 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  // 向设备发送数据

 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  // 初始化一个异步的读取操作

 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); // 初始化一个异步的写入操作

 int (*readdir) (struct file *, void *, filldir_t);  // 仅用于读取目录,对于设备文件,该字段为NULL

 unsigned int (*poll) (struct file *, struct poll_table_struct *); // 轮询函数,判断目前是否可以进行非阻塞的读取或写入

 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);  // 执行设备I/O控制命令

 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);   // 不使用BLK文件系统,将使用此种函数指针代替ioctl

 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);  // 在64位系统上,32位的ioctl调用将使用此函数指针代替

 int (*mmap) (struct file *, struct vm_area_struct *);  // 用于请求将设备内存映射到进程地址空间

 int (*open) (struct inode *, struct file *);

 int (*flush) (struct file *, fl_owner_t id);

 int (*release) (struct inode *, struct file *);

 int (*fsync) (struct file *, struct dentry *, int datasync);  // 刷新待处理的数据

 int (*aio_fsync) (struct kiocb *, int datasync);

 int (*fasync) (int, struct file *, int);    // 异步fsync

 int (*lock) (struct file *, int, struct file_lock *);   // 通知设备FASYNC标志发生变化

 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

 int (*check_flags)(int);

 int (*flock) (struct file *, int, struct file_lock *);

 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

 int (*setlease)(struct file *, long, struct file_lock **);

};

llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这 个函数返回一个负值。 

read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一 个负值。

write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被 实现,

当用户进行write()系统调用时,将得到-EINVAL返回值。

readdir()函数仅用于目录,设备节点不需要实现它。

ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,

返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的

ioctl()。如果设备不提供ioctl()函数,对于内核不能识别的命令,用户进行ioctl()系统调

用时将获得-EINVAL返回值。

mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进

行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意

义。

poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发 时,用户空间进行select()和poll()系统调用将引起进程的阻塞

aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。 设备实现这两个函数后,

用户空间可以对该设备文件描述符调用aio_read()、aio_write() 等系统调用进行读写

 

5.copy_from_user()和copy_to_user()的原型如下所示

由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_from_user() 完成用户空间到内核空间的复制,

函数copy_to_user()完成内核空间到用户空间的复 制

unsigned long copy_from_user(void *to, const void __user *from, unsigned long count); 

unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count); 

上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。

如果要复制的内存是简单类型,如char、int、long等,则可以使用简单的put_user()

和get_user(),如下所示:

int val; //内核空间整型变量

get_user(val, (int *) arg); //用户空间到内核空间,arg是用户空间的地址

put_user(val, (int *) arg); //内核空间到用户空间,arg是用户空间的地址


第二部分:

第一部分笔记讲解了字符设备驱动的一些常用函数,下面将基于UT4412BV03开发板编写一个控制LED的驱动程序。对于字符设备来说,驱动程序的编写本质上就是实现file_operations结构体的常用函数。 

下面将说明以字符设备为模板LED驱动程序设计。

 

/*************************************

  ** ut_4412 :led驱动程序设计

  **          LED:接口引脚

  **          EINT5--------GPX0_5-----D3

  **       EINT7--------GPX0_7------D4

  **           EINT20-------GPX2_4-----D6

  **           EINT21-------GPX2_5-----D7

*************************************/

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/sched.h>

#include <linux/platform_device.h>

#include <linux/clk.h>

#include <linux/gpio.h>

#include <linux/delay.h>

#include <linux/smsc911x.h>

#include <asm/mach/arch.h>

#include <asm/mach-types.h>

#include <plat/exynos4.h>

#include <plat/cpu.h>

#include <plat/clock.h>

#include <plat/devs.h>

#include <plat/gpio-cfg.h>

#include <mach/gpio.h>

#include <plat/gpio-cfg.h>

#include <linux/miscdevice.h>

#include <mach/regs-gpio.h>

#include <asm/io.h>

#include <linux/regulator/consumer.h>

#include <linux/timer.h> 

#include <mach/hardware.h> 

#include <asm/io.h> 

#include <asm/uaccess.h> 

#include <linux/cdev.h> 

#define LED_MAJOR    97  //设备号

static struct class *led_class;  

#define IOCTL_GPIO_ON 1

#define IOCTL_GPIO_OFF 0

/*   用来指定LED所用的GPIO引脚   */

static unsigned long led_table [] ={

                             EXYNOS4_GPX0(5), //D3

     EXYNOS4_GPX0(7), //D4

     EXYNOS4_GPX2(4),   //D6  

                            EXYNOS4_GPX2(5),   //D7

};

 

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

{

printk("led device open success! ");

        return  0;

}

 

ssize_t ut_4412_leds_write(struct file *filep, const void __user *buf, size_t n,

  loff_t *ppos)

{

    unsigned int  cmd[1],ret;

    ret=copy_from_user(&cmd, buf, 1); //用户空间向内核空间传值。用于控制LED的亮灭 

      printk("cmd=%d ",cmd[0]);//控制带答应用户空间传到内核空间的值

      printk("write cmd to kernel success ");

    switch(cmd[0])

      {

case 48:  //控制LED

   {

gpio_set_value(EXYNOS4_GPX0(5),0);

gpio_set_value(EXYNOS4_GPX0(7),0);

gpio_set_value(EXYNOS4_GPX2(4),0);

gpio_set_value(EXYNOS4_GPX2(5),0);

}   

break;

    

case 49:  //控制LED

   {

gpio_set_value(EXYNOS4_GPX0(5),1);

gpio_set_value(EXYNOS4_GPX0(7),1);

gpio_set_value(EXYNOS4_GPX2(4),1);

gpio_set_value(EXYNOS4_GPX2(5),1);

     }

break;

            

default:

return -EINVAL;        

}

 

return ret;

 

 }

 

 

long ut_4412_leds_ioctl(struct file *file, unsigned int cmd, unsigned long  arg)

{

   if(arg>4)          

return  -EINVAL;

switch(cmd)   

{

case IOCTL_GPIO_OFF:

 gpio_set_value(led_table[arg],0);break;  

    case IOCTL_GPIO_ON:

 gpio_set_value(led_table[arg],1);break;

default:

return -EINVAL;        

}

}

static struct file_operations dev_fops = {

.owner =  THIS_MODULE,

    .open   =    ut_4412_leds_open,

    .unlocked_ioctl  =    ut_4412_leds_ioctl,

 //   .read   =    ut_4412_leds_read,

    .write  =    ut_4412_leds_write,

};

 

static int __init ut_4412_leds_init(void)

{

int ret;

        printk("===led driver init=== ");

    s3c_gpio_cfgpin(EXYNOS4_GPX0(5), S3C_GPIO_OUTPUT);

   s3c_gpio_cfgpin(EXYNOS4_GPX0(7), S3C_GPIO_OUTPUT);

        s3c_gpio_cfgpin(EXYNOS4_GPX2(4), S3C_GPIO_OUTPUT); 

        s3c_gpio_cfgpin(EXYNOS4_GPX2(5), S3C_GPIO_OUTPUT);

        ret=register_chrdev(LED_MAJOR, "myled", &dev_fops); 

if (ret < 0)

{

printk(KERN_ERR "VIB: unable to get major %d ", ret);

return ret; 

}

led_class = class_create(THIS_MODULE, "myled");

if (IS_ERR(led_class)) 

{

unregister_chrdev(LED_MAJOR, "myled"); 

return PTR_ERR(led_class); 

}

device_create(led_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "ledhal"); 

// create a point under /dev/class/vib

  

gpio_set_value(EXYNOS4_GPD0(0),1);  

return ret;

}

 

static void __exit ut_4412_leds_exit(void)

        printk("===led driver exi===t ");

        device_destroy(led_class, MKDEV(LED_MAJOR, 0)); 

class_destroy(led_class); 

unregister_chrdev(LED_MAJOR, "myled"); 

}

 

module_init(ut_4412_leds_init);

module_exit(ut_4412_leds_exit);

  //  MODULE_ALLAS_CHARDEV(LED_MAJOR,0);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("urbetter");

MODULE_DESCRIPTION("ut_4412");

将以上LED驱动程序加入内核:

打开kernel/driver/char目录下的Makefile,在其中加入LED驱动文件,

 

打开kernel/driver/char目录下的Kconfig,在其中加入如下内容

 

 

 

之后编译出kernel镜像文件,运用fastboot或者SD卡升级方式升级镜像。

下面将一命令的方式来测试LED驱动程序

 

在控制台下利用su root转到超级用户

 

之后转到dev目录,找到LED设备的设备节点

 

 

运用ls看到LED设备的设备节点为ledhal,

 

则说明LED驱动程序创建成功

可以用如下命令控制LED的亮灭:

在控制台输入如下命令控制LED打开

    

在控制台输入如下命令控制LED关闭