一、
linux中设备驱动的类型
字符设备
:
GPIO、EINT、ADC、WDT、LCD、CAMERA、JPEG......
块设备
:
nand flash、SD卡、硬盘
网络设备
:有线网卡、无线网卡
===================================================================================
二、字符设备驱动的描述
1、cdev
cdev
是一个描述一个
字符设备的结构体
,我们要设计一个字符设备,就必须定义一个
cdev的结构体,然后对cdev结构进行初始化,初始化结束后,
将
cdev注册到内核
,这样,内核中就添加了一个字符设备驱动。
#include <linux/cdev.h>
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
1)struct kobject kobj;
linux在做字符设备管理的时候,使用的驱动模型,利用kobject生成/sys下的驱动信息。
2)struct module *owner;
这个字符设备输入那一个
module,
一般设备为
THIS_MODULE
3)const struct file_operations *ops;
文件操作集
,给应用程序提供
API接口,应用程序通过系统调用,来访问file_operations中的接口函数。
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
以上的接口函数,是应用程序中,文件
IO的系统调用接口。
open、read、write、lseek、ioctl、mmap、close
提示
1:
文件
IO函数原型中的参数,与文件操作集中对应函数的参数是否一致???
是一个系统调用过程,中间是经过
vfs和linux内核,所以参数是不一致的,与函数调用不同。
提示
2:
ssize_t是什么类型?
typedef __kernel_ssize_t ssize_t;
#ifndef __kernel_size_t
#if __BITS_PER_LONG != 64
typedef unsigned int __kernel_size_t;
typedef int __kernel_ssize_t;
typedef int __kernel_ptrdiff_t;
#else
typedef unsigned long __kernel_size_t;
typedef long __kernel_ssize_t;
typedef long __kernel_ptrdiff_t;
#endif
#endif
4) struct list_head list;
内核双向链表,向内核中注册一个字符设备时,链表增加一项
5)
dev_t dev;
设备号
6)unsigned int count;
次设备号的数量
-----------------------------------------------------------------------------------------------------------
2、设备号
1)
设备号
的定义
dev_t dev;
在
linux内核中,每一个设备驱动都有一个设备号,设备号有
主设备号和次设备号
组成
主设备号:描述设备驱动的具体应用类型
次设备号:描述该具体应用类型下的一个应用实例
例如:
4个uart的驱动
[root@YueQian /]# ls /dev/s3c2410_serial* -l
crw-rw---- 1 root root 204, 64 Jan 2 11:21 /dev/s3c2410_serial0
crw-rw---- 1 root root 204, 65 Jan 2 11:21 /dev/s3c2410_serial1
crw-rw---- 1 root root 204, 66 Jan 2 11:21 /dev/s3c2410_serial2
crw-rw---- 1 root root 204, 67 Jan 2 11:21 /dev/s3c2410_serial3
例如:
nand flash的5个分区
[root@YueQian /]# ls /dev/mtdblock* -l
brw-rw---- 1 root root 31, 0 Jan 2 11:21 /dev/mtdblock0
brw-rw---- 1 root root 31, 1 Jan 2 11:21 /dev/mtdblock1
brw-rw---- 1 root root 31, 2 Jan 2 11:21 /dev/mtdblock2
brw-rw---- 1 root root 31, 3 Jan 2 11:21 /dev/mtdblock3
brw-rw---- 1 root root 31, 4 Jan 2 11:21 /dev/mtdblock4
typedef unsigned int __u32;
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
设备号
是一个
无符号
32位的整型值
,其中
高
12bits是主设备号,低20bits是次设备号
。
-----------------------------------------------------------------------------------------------------------
2)主次设备号之间的函数
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) //0x000 fffff
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //MINORBITS = 20
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
-----------------------------------------------------------------------------------------------------------
3)如何得到一个设备号
我们设置一个设备驱动,需要从内核中,申请一个设备号。
(
1)
静态注册设备号
:我们来指定设备号,不要使用系统已经使用的设备号
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:
dev_t from:注册的一个设备号的开始值。如果是gec210 uart,from=(204<<20 + 64)
unsigned count: 设备实例的数量,即次设备
号的数目。如果是
gec210 uart,count=4
const char *name:字符设备的名称。如果是gec210 uart,name=s3c2410_serial
返回值:
注册成功,返回
0
注册失败,返回一个负数的错误码
(
2)
动态分配设备号
:让系统自动分配空闲的设备号
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数说明:
dev_t *dev:动态分配后的设备号。
unsigned baseminor:我们使用的第一个次设备号,次设备号的开始值,如果是gec210 uart,baseminor=64
unsigned count: 设备实例的数量,即次设备
号的数目。如果是
gec210 uart,count=4
const char *name:字符设备的名称。如果是gec210 uart,name=s3c2410_serial
返回值:
注册成功,返回
0
注册失败,返回一个负数的错误码
===================================================================================
三、字符设备驱动的设计思路
1、
定义一个字符设备
struct cdev chrdev_test;
------------------------------------------------------------------------------------
2、
定义一个文件操作集,并对文件操作集进行初始化
struct
file_operations
chdev_fops = {
.open = test_open,
.read = test_read,
.write = test_write,
.ioctl = test_ioctl,
.release = test_release,
};
------------------------------------------------------------------------------------
3、
字符设备的初始化
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
struct cdev *cdev:定义的字符设备
const struct file_operations *fops:已经完成定义和初始化的一个文件操作集
------------------------------------------------------------------------------------
4、申请设备号
dev_t ndev;//设备号
int TestMajor = 0;//主设备号
int TestMinor = 0;//次设备号
char drv_name[]="chrtest1";
int ret;
if(TestMajor)
{
ndev=MKDEV(TestMajor,TestMinor);//由主次设备号,得到设备号
ret=register_chrdev_region(ndev,1,drv_name);//注册字符设备编号
}
else
{
ret=alloc_chrdev_region(&ndev,TestMinor,1,drv_name);//动态分配一个字符设备号,&ndev存放返回的设备号
TestMajor=MAJOR(ndev);
}
if(ret<0)
{
printk(KERN_WARNING"cannot get major %d \n",TestMajor);
goto fail_chrdev_region; //出错处理 goto
}
------------------------------------------------------------------------------------
5、
将字符设备加入到内核
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:
struct cdev *p :定义并初始化的字符设备cdev
dev_t dev:已经申请到的设备号
unsigned count:次设备的数量
返回值:
注册成功,返回
0
注册失败,返回一个负数的错误码
------------------------------------------------------------------------------------
6、
注销一个设备号
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
参数说明:
dev_t from:注册的一个设备号的开始值。如果是gec210 uart,from=(204<<20 + 64)
unsigned count: 设备实例的数量,即次设备
号的数目。如果是
gec210 uart,count=4
------------------------------------------------------------------------------------
7、
注销一个字符设备
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
void cdev_del(struct cdev *p)
===================================================================================
四、用户空间与内核空间交互数据的函数
1、
将用户空间的数据拷贝到内核空间
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
2、
将内核空间的数据拷贝到用户空间
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
返回值:如果数据拷贝成功,返回是
0;如果
数据拷贝失败,返回是尚未拷贝的字节数。
提示
1:
__must_check
必须检查该函数的返回值,如果不检查,会有警告。
提示
2:
inline --内联函数
。
===================================================================================
五、驱动程序的设计
见
demo1
===================================================================================
六、驱动的调试
写一个应用程序,利用应用程序调试驱动程序。
1、安装驱动
[root@YueQian /test]#
insmod demo1.ko
[24273.778642]
[24273.779733] this is the first demo of driver, inserting successful !
2、查看内核模块
[root@YueQian /test]# lsmod
demo1 2589 0 - Live 0xbf000000
3、查看字符设备的主设备号和设备名称
char drv_name[ ]="chrtest";
int TestMajor = 0;//主设备号
int TestMinor = 0;//次设备号
[root@YueQian /test]#
cat /proc/devices
Character devices:
250 chrtest --->主设备号和设备名称
4、
手动创建设备文件
(后面讲如何自动创建设备文件)
[root@YueQian /test]#
mknod /dev/chr_test1 c 250 0
5、查看设备文件
[root@YueQian /test]#
ls /dev/chr_test1 -l
crw-r--r-- 1 root root 250, 0 Jan 2 18:14 /dev/chr_test1
6、执行应用程序
7、
驱动的卸载
[root@YueQian /]#
rmmod demo1
[25293.152108] the driver is exiting
===================================================================================
七、字符设备驱动的老方法
老的方法更简洁,更直观;新的方法比较灵活。
1、注册字符设备
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
参数说明:
unsigned int major 主设备号,如果major=0,系统会自动分配一个主设备号,如果major!=0,静态注册主设备号。
const char *name 设备名称
const struct file_operations *fops 文件操作集。
返回值:
1)如果unsigned int major 是大于0的值,静态注册字符设备:
注册成功,返回
0
注册失败,返回一个负数的错误码
2)如果unsigned int major 是等于0的值,动态注册字符设备
分配成功,返回分配后的主设备号
注册失败,返回一个负数的错误码
将设备号申请、字符设备的初始化、字符设备的注册使用一个函数实现。
2、注销字符设备
static inline void unregister_chrdev(unsigned int major, const char *name)