字符设备驱动的编写入门---蜂鸣器驱动

引言

    总结一下最近设备驱动的学习成果,记录一下心得。文章里穿插记录字符设备驱动的相关知识。

硬件相关

    

字符设备驱动的编写入门---蜂鸣器驱动_第1张图片
beep

一 编写驱动程序


字符设备驱动的编写入门---蜂鸣器驱动_第2张图片
图1 驱动程序的结构

如上图所示,应用层要操作硬件,需要调用驱动。驱动程序是直接操作硬件的。从这个图中我们可以看出,驱动程序需要具备以下内容:

1. 模块加载,模块删除

2. 填充cdev结构体,dev_t,file_operations

3. 完善read(),write(),ioctl(),open(),close()等函数

首先说加载驱动。驱动写完了,需要动态加载如内核,让内核知道有这个驱动设备。如何注册进内核呢?一般在驱动程序里指定:

module_init(beep_init);

module_exit(beep_cleanup);

指定好加载,卸载函数以后,然后再在初始化设备,注册设备

/*注册设备*/

static int beep_init(void)

{

dev_t dev = MKDEV(beep_major, 0);

char dev_name[]="beep";

if (beep_major)

            result = register_chrdev_region(dev, 1, dev_name);

else {

             result = alloc_chrdev_region(&dev, 0, 1, dev_name);

                beep_major = MAJOR(dev);

         }

beep_setup_cdev(&BeepDevs, 0, &beep_remap_ops);

}

static void beep_setup_cdev(struct cdev *dev, int minor,struct file_operations *fops)

{

int err, devno = MKDEV(beep_major, minor);

cdev_init(dev, fops);

dev->owner = THIS_MODULE;

dev->ops = fops;

err = cdev_add (dev, devno, 1);

}

/*删除设备*/

static void beep_cleanup(void)

{

cdev_del(&BeepDevs);

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

}

如果是2.6以上的内核,注册设备和删除设备是用如下函数:

注册:       cdev_init(struct cdev *dev, struct file_operations *fops);

               cdev_add (struct cdev *dev, dev_t num, unsigned count);

删除:        cdev_del(struct cdev *dev);

在注册或者删除设备号的之前,需要知道设备号,设备号如何分配。所以接下来再讲结构体,设备号的分配。

从上面的图1可以看出,内核删除、加载都是直接操作cdev结构体,那cdev结构体是什么?

struct cdev {

struct kobject kobj;

struct module *owner;   //所属模块

const struct file_operations *ops;//文件操作结构,在写驱动时,其结构体内的大部分函数要被实现

struct list_head list;

dev_t dev;          //设备号,int 类型,高12位为主设备号,低20位为次设备号

unsigned int count;

};

cdev结构体里包含了dev_t和file_operations结构体。这也是为什么图1中cdev可以指向dev_t类型和file_operations结构体。也就是说,内核操作cdev,然后cdev找到dev_t设备号和file_operations函数,最终利用file_operations里面的函数操作硬件。接下来说明一下这两个数据类型。

dev_t其实是一个32为的unsigned int类型数据。用来区分设备号。设备可以分为主设备和次设备。

主设备是一类设备的标识,占12位。

次设备是一类设备的具体设备,占20位。

比如说tty0,tty就是主设备,0表示次设备。但是如何给设备编号呢?一般有两种方式,一种是手动分配,另一种是自动分配。

手动:register_chrdev_region(dev_t first, unsigned int count, char *name);

自动:alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, cchar *name);

删除:unregister_chrdev_region(dev_t dev, unsigned int count);

有了主设备号和次设备号,这都是int型。为了方便,还需要与dev_t进行转换,所以右多了几个转换函数:

从dev_t获得主、次设备号;MAJOR(dev_t),MINOR(dev_t)

将主、次设备号转换成dev_t类型:MKDEV(int major ,int minor)

file_operations结构体内容如下:

struct file_operations {

struct module *owner;

loff_t(*llseek) (struct file *, loff_t, int);

ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);

ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

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

int (*flush) (struct file *);

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);

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

ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);

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);

}

这个结构体包含了对硬件的具体操作函数。所有的系统调用,最终都是操作这里面的函数。这些函数,比如说open,需要根据硬件编写程序,这里就不详述了。注意看一下file_operations里面的open等函数的参数是file,indoe两个结构体。为什么又搞出来两个结构体,这两个结构体有什么用呢?

我们知道,系统调用open等函数的时候,都是使用文件名,没有使用cdev结构体的,

int open(const char *pathname,flags,int perms)

那我们打开文件名的时候,它就如何找到了cdev呢?因为我们在使用的时候都创建了节点:

mknod /dev/node_name c major minor

我们通过这个方法,将文件名与我们的节点连接起来了。为什么这样弄?因为设备文件名主要在用户空间使用(比如用户空间程序调用open函数时),而内核空间则使用inode来表示相应的文件。那接下来inode节点如何连接到cdev呢?这里我们就要说说inode结构体了。inode有两个总要的字段:

dev_t i_rdev                        该字段包含了真正的设备编号

struct   cdev  *i_cdev;         这就是指向了我们的cdev结构体了,连接了我们的用户空间和内核空间

这里再扩展一下,从一个inode中获得主设备和次设备号:

unsigned  int  iminor(struct inode *inode)

unsigned  int  imajor(struct inode *inode)

这里还有一个file结构体需要说明一下(只是自己的理解,没有确认)。如果一个设备可以同时被多个进程操作,比如说读取nand flash。如果一个进程process1读到了p1位置,另外一个进程process2也开始读nand了,假设读到了p2,这个时候第一个进程process1难道要从p2开始读?肯定不能吧,它肯定想接着p1开始读。那怎么办呢?所以用一个file结构体表示正在打开的设备文件,来区别。file结构体重要成员如下:

1.mode_t f_mode;

文件模式确定文件是可读的或者是可写的(或者都是),通过位FMODE_READ和FMODE_WRITE.你可能想在你的open或者ioctl函数中检查这个成员的读写许可,但是不需要检查读写许可,因为内核在调用你的方法之前检查.当文件还没有为那种存取而打开时读或写的企图被拒绝,驱动甚至不知道这个情况.

2.loff_t f_pos;

当前读写位置. loff_t在所有平台都是64位(在gcc术语里是long long ).驱动可以读这个值,如果它需要知道文件中的当前位置,但是正常地不应该改变它;读和写应当使用它们作为最后参数而收到的指针来更新一个位置,代替直接作用于filp->f_pos.这个规则的一个例外是在llseek方法中,它的目的就是改变文件位置.

3.unsigned int f_flags;

这些是文件标志,例如O_RDONLY, O_NONBLOCK,和O_SYNC.驱动应当检查O_NONBLOCK标志来看是否是请求非阻塞操作;其他标志很少使用.特别地,应当检查读/写许可,使用f_mode而不是f_flags.所有的标志在头文件中定义.

4.struct file_operations *f_op;

和文件关联的操作.内核安排指针作为它的open实现的一部分,接着读取它当它需要分派任何的操作时. filp->f_op中的值从不由内核保存为后面的引用;这意味着你可改变你的文件关联的文件操作,在你返回调用者之后新方法会起作用.例如,关联到主编号1 (/dev/null, /dev/zero,等等)的open代码根据打开的次编号来替代filp->f_op中的操作.这个做法允许实现几种行为,在同一个主编号下而不必在每个系统调用中引入开销.替换文件操作的能力是面向

对象编程的"方法重载"的内核对等体.

5.void *private_data;

open系统调用设置这个指针为NULL,在为驱动调用open方法之前.你可自由使用这个成员或者忽略它;你可以使用这个成员来指向分配的数据,但是接着你必须记住在内核销毁文件结构之前,在release方法中释放那个内存. private_data是一个有用的资源,在系统调用间保留状态信息,我们大部分例子模块都使用它.

6.struct dentry *f_dentry;

关联到文件的目录入口( dentry )结构.

二 编写makefile

ifneq ($(KERNELRELEASE),)

obj-m := led.o

else

PWD  := $(shell pwd)

KVER ?= 2.6.31

KDIR :=/usr/local/linux-2.6.31

all:

$(MAKE) -C $(KDIR) M=$(PWD)

clean:

rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions

endif

先说明以下makefile中一些变量意义:

(1)KERNELRELEASE在linux内核源代码中的顶层makefile中有定义

(2)PWD会取得当前工作路径

(3)KVER 取得当前内核的版本号

(4)KDIR变量便是当前内核的源代码目录。

这段代码,这个maikefile会被编译两次。

1. 编译开始的时候,没有定义KERNELRELEASE,执行else,获取linux内核,模块的地址,然后执行make。但是这个时候有-C,会改变工作目录,去linux内核的目录下

2. 到linux内核中,执行linux顶层的makefile,这里定义了KERNELRELEASE,执行完了返回模块的当前地址

3. 到了模块的当前地址,再次执行这个makefile,由于前面已经定义了KERNELRELEASE,会执行if的那一段,生成Ko文件。

三 下载到开发板,并装载insmod beep.ko。

四 查看主设备号

如果装载的时候打印了设备号,就直接可以查看。如果没有,利用命令查看

cat /proc/devices|grep beep

五 设置设备节点

mknod  /dev/beep  c  major 0

六 编写测试代码


参考文献:

http://blog.chinaunix.net/uid-26960488-id-3250671.html

http://blog.csdn.net/shanzhizi/article/details/8626474

http://blog.chinaunix.net/uid-21161467-id-108104.html

驱动程序例程:

c int beep_major = 0;

module_param(beep_major, int, 0);

MODULE_AUTHOR("Hanson He");

MODULE_LICENSE("Dual BSD/GPL");

#define BEEP_MAGIC 'k'

#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)

#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)

int beep_open (struct inode *inode, struct file *filp)

{

return 0;

}

ssize_t beep_read(struct file *file, char __user *buff, size_t count, loff_t *offp)

{

return 0;

}

ssize_t beep_write(struct file *file, const char __user *buff, size_t count, loff_t *offp)

{

return 0;

}

void beep_stop( void )

{

s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);

s3c2410_gpio_setpin(S3C2410_GPB(0),0);

}

void beep_start( void )

{

s3c2410_gpio_pullup(S3C2410_GPB(0),1);

s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);

s3c2410_gpio_setpin(S3C2410_GPB(0),1);

}

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

{

switch ( cmd ) {

case BEEP_START_CMD: 

{

beep_start(); break;

}

case BEEP_STOP_CMD: {

beep_stop(); break;

}

default: {

break;

}

}

return 0;

}

static int beep_release(struct inode *node, struct file *file)

{

return 0;

}

static void beep_setup_cdev(struct cdev *dev, int minor,

struct file_operations *fops)

{

int err, devno = MKDEV(beep_major, minor);

cdev_init(dev, fops);

dev->owner = THIS_MODULE;

dev->ops = fops;

err = cdev_add (dev, devno, 1);

if (err)

printk (KERN_NOTICE "Error %d adding beep%d", err, minor);

}

static struct file_operations beep_remap_ops = {

.owner  = THIS_MODULE,

.open    = beep_open,

.release = beep_release,

.read    = beep_read,

.write  = beep_write,

.ioctl  = beep_ioctl,

};

static struct cdev BeepDevs;

static int beep_init(void)

{

int result;

dev_t dev = MKDEV(beep_major, 0);

char dev_name[]="beep";

/* Figure out our device number. */

if (beep_major)

result = register_chrdev_region(dev, 1, dev_name);

else {

result = alloc_chrdev_region(&dev, 0, 1, dev_name);

beep_major = MAJOR(dev);

}

if (result < 0) {

printk(KERN_WARNING "beep: unable to get major %d\n", beep_major);

return result;

}

if (beep_major == 0)

beep_major = result;

/* Now set up cdev. */

beep_setup_cdev(&BeepDevs, 0, &beep_remap_ops);

printk("beep device installed, with major %d\n", beep_major);

printk("The device name is: %s\n", dev_name);

return 0;

}

static void beep_cleanup(void)

{

cdev_del(&BeepDevs);

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

printk("beep device uninstalled\n");

}

module_init(beep_init);

module_exit(beep_cleanup);

EXPORT_SYMBOL(beep_major);

你可能感兴趣的:(字符设备驱动的编写入门---蜂鸣器驱动)