tiny6410 蜂鸣器字符设备驱动<1>

来源:互联网 发布:手机记工天软件 编辑:程序博客网 时间:2024/05/22 15:57

这两周主要学习了字符设备驱动、杂项设备驱动以及驱动程序设计的核心理论与技巧。完成了一个蜂鸣器的字符设备驱动和一个LED杂项设备驱动。做一下总结。

 

        字符设备驱动:字符设备驱动是基本的串行输入输出驱动,将一些按字节、字进行读取、写入的设备做成驱动模块。Insmod装载驱动模块到内核,则用户空间与内核空间的交互就可以达成控制设备的目的。为了方便驱动的设计与调试,将驱动做成模块obj-m。

        字符设备设计模式:内核提供了一套字符设备的接口,利用这些接口完成字符设备的注册和注销。

        注册:

                1、获取设备号:字符设备的设备后数据类型为dev_t,用函数MKDEV(MAJOR,MINOR)获得设备号。但还要向内核申请到这一个设备号(想要的设备号与实际可以得到的设备号的区别)。因此还要通过静态获取设备号或者动态获取设备号(区别是,主设备号是否为0)。静态申请获取设备号的前提是已知设备号的情况。动态申请获取设备号则是未知的,但可以避免设备号冲突。在使用动态申请获取设备号时,函数调用成功会将设备号放入到参数dev_t dev中这时应该用MAJOR(dev)获得主设备号并在成功注册字符设备后将信息打印出来,利于创建设备结点。

                          int register_chrdev_region(dev_tfrom,unsigned count,const char *name);

                          intalloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char*name);

          如果函数调用发生错误,返回小于0的值。

                2、初始化化字符设备:字符设备结构体(struct cdev)的初始化包括file_operations、owner。file_oerations包含有字符设备的控制方法。owner为驱动模块的拥有者,为权限设置,一般设置为THIS_MODULE。

                          voidcdev_init(struct cdev *cdevp,struct file_operations *ops);

                          cdevp->owner= THIS_MODULE;

                          cdevp->fops= ops;

                3、添加字符设备:将初始化的字符设备添加到字符设备队列。完成这一步,字符设备的注册就全部做完了。参数unsigned count表明添加到设备队列的字符设备个数。

                          voidcdev_add(struct cdev *cdevp,dev_t devn,unsigned count);

        注销:

1、 从字符设备队列删除字符设备:将字符设备结构体从设备队列删除。

voidcdev_del(struct cdev *cdevp);

2、注销字符设备号:释放掉设备号(设备号属于有限资源,主设备号为212个,次设备号为220个)。参数unsigned count为要注销的设备号个数。

voidunregister_chrdev_region(dev_t from,unsigned count);

字符设备基本的注册、注销过程就是这个模式。如果加入了私有数据filp->private_data、信号量或者其他的驱动程序设计的核心理论,则是在这个基础上进行添加。这里注明一下在显示的定义了设备结构体的情况下,进行字符设备注销注册的不同的地方。

定义了设备结构体dev_name_t及其变量dev

struct dev_name_t ={

                                   struct cdev cdev;

                                   char buf[1024];

                                   }dev;

那么在字符设备的初试化函数****_init(void)中,则需要给该自定义的设备结构体分配内核空间。调用kmalloc(size,GFP_KERNEL)分配内核空间,成功则返回所分配的内核空间的首地址。再调用初始化函数memset(addr,0,size);清空addr地址的内核空间。需要注意的是,在ARM的设计中需要对ARM寄存器进行设置,使用的是寄存器的物理地址。而在Linux系统上进行设备的驱动设计时,内核是不能识别物理地址的,需要将设备的物理地址映射为内核空间的虚拟地址。调用ioremap(PADDR,int);将物理地址PADDR,映射为长度为int字节的虚拟地址。然互内核空间就可以通过ioread32()\iowrite32()对设备进行读写操作。结合蜂鸣器的字符设备驱动做进一步的解释。

蜂鸣器驱动程序源码:

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 名称:  beep_modu.c

// 工程:  beep

// 作者:  Nozuowilldie

// 描述: 

//    蜂鸣器驱动,打开、关闭蜂鸣器。

// 主要函数:

//    beep_init()\beep_exit()

// 版本:  V1.0 2014/05/03

// 修改记录:

// 1 2014/5/3

//////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/cdev.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <asm/io.h>

#include <linux/fs.h>

#include <asm/uaccess.h>

 

#define BEEP_MAJOR 0

#define MAGIC 'f'

#define START_CMD0 _IO(MAGIC,0)

#define STOP_CMD1 _IO(MAGIC,1)

 

#define GPFCON 0x7F0080A0

#define GPFDAT 0x7F0080A4

#define REG_AND_VAL 0xc0000000

#define REG_OR_VAL 0x10000000

 

unsigned int unContrlReg,unDataReg;

unsigned long *beepCtrlAddr;

unsigned long *beepDataAddr;

 

static int beep_major = BEEP_MAJOR;

static char *beep_dev_name = "beep_modu";

 

struct beep_dev{

struct cdevcdev;

};

struct beep_dev dev;     

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 函数名称: start_beep

// 函数功能:

//                   打开蜂鸣器

// 参数说明:无

// 返回值:无

// 全局变量:

                     beepDataAddr        GPFDAT寄存器物理地址映射后的虚拟地址

                     unDataReg           写入GPFDAT中的暂存值

// 备注:无

// 修改记录:

//                   1:    Nozuowilldie 2014.05.03撰写

//////////////////////////////////////////////////////////////////////////////////////////////////////////

static void start_beep(void)

{

printk("Startbeep\n");

unDataReg =ioread32(beepDataAddr);

unDataReg &=0xBFFF;

unDataReg |=0x4000;

iowrite32(unDataReg,beepDataAddr); 

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 函数名称: stop_beep

// 函数功能:

//                   关闭蜂鸣器

// 参数说明:无

// 返回值:无

// 全局变量:

                     beepDataAddr        GPFDAT寄存器物理地址映射后的虚拟地址

                     unDataReg           写入GPFDAT中的暂存值

// 备注:无

// 修改记录:

//                   1:    Nozuowilldie 2014.05.03撰写

//////////////////////////////////////////////////////////////////////////////////////////////////////////

static void stop_beep(void)

{

printk("Stopbeep\n");

unDataReg =ioread32(beepDataAddr);

unDataReg &=0xBFFF;

iowrite32(unDataReg,beepDataAddr); 

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 函数名称: beep_ioctl

// 函数功能:

//                   蜂鸣器I/O控制函数,打开、关闭蜂鸣器

// 参数说明:

//                   filp:             输入参数,设备句柄

//                   cmd:                      输入参数,I/O控制命令

//                   arg:                   输入参数,补充I/O控制命令

// 返回值:

//                   0:                         正确

//                   -EINVAL:             错误的I/O命令

// 全局变量:

//                   START_CMD0    打开蜂鸣器

//                   STOP_CMD1     关闭蜂鸣器

// 备注:在linux-2.6.38内核上加载编译驱动时,出现error:unknown field 'ioctl'specified in initializer

//       原因是:在2.6.38内核上file_operations发生了重大的改变:

//            原先的int (*ioctl)(struct inode*,struct file*, unsigned int, unsigned long);被改为了       

//            long(*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//            long(*compat_ioctl) (struct file *, unsigned int, unsigned long);

//            因而在实际驱动中,我们需要将原先的写的ioctl函数头给改成下面的unlocked_ioctl,在file_operations结构

//            体的填充中也是一样。

// 修改记录:

//                   1:    Nozuowilldie 2014.05.03撰写

//////////////////////////////////////////////////////////////////////////////////////////////////////////

static long beep_ioctl(struct file *filp,unsigned intcmd,unsigned long arg)

{

switch(cmd)   

{

case START_CMD0:

start_beep();  

                     break;

case STOP_CMD1:

                     stop_beep();

                     break;

default:

                     return-EINVAL;

}

return 0;

}

struct file_operations fops = {

.owner =THIS_MODULE,

.unlocked_ioctl= beep_ioctl,

};

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 函数名称: setup_chrdev

// 函数功能:

//                   初试化字符设备,添加字符设备

// 参数说明:

//                   devn:       输入参数,设备号

// 返回值:无

// 全局变量:

//                   dev           蜂鸣器设备结构体

// 备注:无

// 修改记录:

//                   1:    Nozuowilldie 2014.05.03撰写

//////////////////////////////////////////////////////////////////////////////////////////////////////////

static void setup_chrdev(dev_t devn)

{

int err;

cdev_init(&dev.cdev,&fops);

dev.cdev.owner =THIS_MODULE;

dev.cdev.ops =&fops;

err =cdev_add(&dev.cdev,devn,1);

if(err)

printk("Error %d adding beepmem\n",err);

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 函数名称: beep_init

// 函数功能:

//                   完成蜂鸣器设备的注册

// 参数说明:无

// 返回值:

//                   0:                         正确

//                   其他:                  错误号

// 全局变量:

//                   beep_major            主设备号,初始值为0

//                   beep_dev_name      设备名,beep_modu

//                   GPFCON                     GPIO F端的控制寄存器物理地址

//                   GPFDAT               GPIO F端的数据寄存器物理地址

//                   beepCtrAddr          GPFCON映射到内核空间的虚拟地址

//                   beepDataAddr      GPFDAT映射到内核空间的虚拟地址

//                   unContrlReg          读写寄存器值的暂存空间

//                   REG_AND_VAL    清除GPFCON寄存器的值,0xcfffffff

//                   REG_OR_VAL      设置GPFCON寄存器的值,0x10000000

// 备注:

//                   1:    MKDEV()得到设备号

//                   2:    register_chrdev_region()静态申请设备号,alloc_chrdev_region()动态申请设备号

//                   3:    setup_chrdev()初始化字符设备,添加字符设备,完成字符设备的注册

//                   4:    ioremap()完成物理地址到虚拟地址的映射,ioread32()/iowrite32()虚拟地址读写操作

// 修改记录:

//                   1:    Nozuowilldie 2014.05.03撰写

//////////////////////////////////////////////////////////////////////////////////////////////////////////

static int beep_init(void)

{

int ret = 0;

dev_t devn = MKDEV(beep_major,0);

 

if(BEEP_MAJOR)

register_chrdev_region(devn,1, beep_dev_name);

else

{

ret = alloc_chrdev_region(&devn,0,1, beep_dev_name);

beep_major = MAJOR(devn);

}

if(ret < 0)

{

printk("failed register\n");

return ret;

}    

                    

setup_chrdev(devn);

                    

beepCtrlAddr =ioremap(GPFCON,4);

beepDataAddr =ioremap(GPFDAT,4);

unContrlReg =ioread32(beepCtrlAddr);     

unContrlReg&= REG_AND_VAL;

unContrlReg |=REG_OR_VAL;

iowrite32(unContrlReg,beepCtrlAddr);

 

printk("Deviceinstall success!\n");

printk("DeviceMajor :%d\n",beep_major);

printk("Devicename :%s\n",beep_dev_name);

return 0;

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////

// 函数名称: beep_exit

// 函数功能:

//                   完成蜂鸣器设备的注销

// 参数说明:无

// 返回值:无

// 全局变量:

//                   beep_major            主设备号

//                   dev                       设备结构体

// 备注:

//                   1:    cdev_del删除字符设备

//                   2:    unregister_chrdev_region()注销字符设备

// 修改记录:

//                   1:    Nozuowilldie 2014.05.03撰写

//////////////////////////////////////////////////////////////////////////////////////////////////////////

static void __exit beep_exit(void)

{

cdev_del(&dev.cdev);

unregister_chrdev_region(MKDEV(beep_major,0),1);

}

module_init(beep_init);

module_exit(beep_exit);

/*beep_major设置为驱动模块参数,手动设置主设备号*/

module_param(beep_major,int,S_IRUGO);

 

MODULE_AUTHOR("Nozuowilldie");

MODULE_LICENSE("Dual BSD/GPL");    

0 0
原创粉丝点击