一、驱动初始化
1.1分配设备描述结构
1.2初始化设备描述结构
1.3.注册设备描述结构
1.4.硬件初始化
二、实现设备操作
2.1open
2.2read
2.3.write
2.4.lseek
2.5close
2.6参数分析
三、驱动注销
1设备描述结构
在任何一种驱动模型,字符、网卡驱动等,设备都会用内核的一种结构来描述。我们的字符设备在内核中使用struct cdev来描述
struct cdev{
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_lead list;
dev_t dev;//设备号
unsigned int count;//设备数
};
2、设备号
1主设备号
字符设备文件如何与字符驱动程序建立关系?
通过字符设备文件找到字符驱动程序,字符设备文件与字符驱动程序对应数字相同,即主设备号设备相同
我们创建字符设备文件时。cat /proc/devices查看设备驱动程序的主设备号,然后在根据这个主设备下建立一个同样主设备号的字符设备文件,主设备号反应设备类型。
2.次设备号
一个驱动程序并不是只可以处理一个设备,例如开发板上面的3个串口只有一个串口驱动程序,但3个串口会对应3个不同的设备文件,3个设备文件的不同之处是次设备号,不同的次设备号区分同类型的不同设备。应用程序带调用3个设备文件,所以次设备号不同,3个设备文件需要通过主设备号访问同一个驱动程序,因而主设备号相同,如果3个设备文件没有其他信息,则驱动程序也无法区分是哪个文件,所以不同文件的次设备号不同。
3.Linux内核中使用dev_t,通过source insight查询到即为32位的unsigned int, 其中高12位为主设备号,低20位为次设备号。
主设备号和次设备号组合成dev_t类型
dev_t dev = MKDEV(主设备号,次设备号);
dev_t分解主设备号
次设备号=NAJOR(dev_t dev);
dev_t分解次设备号
次设备号=MINOR(dev_t dev);
3.设备号分配
次设备号可以自己定义,但主设备分配的不对会造成冲突。
*3.1.静态申请
开发者自己选择一个数字作为主设备号,然后通过函数register_chrdev_region向内核申请使用。缺点:如果申请
使用的设备号已经被内核中的其他驱动使用了,则申请失败。
*3.2动态分配
使用alloc_chrdev_region由内核分配一个可用的主设备号。优点:因为内核知道哪些号已经被使用了,所以不会导致分
配到已经被使用的号。
*3.3.设备号注销
在退出来注销设备号,unregister_chrdev_region函数释放设备号
4、操作函数集
struct file_operations是一个函数指针的集合,定义能在设备上进行操作。结构中的函数指针指向驱动中的函数。
struct file_operations dev_fops{
.llseek = NULL;
.read = dev_read;
.write = dev_write;
.ioctl = dev_ioctl;
.open = dev_open;
.release = dev_release;
};
1.1分配设备描述结构。字符设备是cdev
cdev变量的定义采用动态和静态分配
静态:struct cdev mdev
动态:struct cdev *pdev = cdev_alloc();
1.2描述结构初始化
struct_cdev的初始化使用cdev_init函数来完成
cdev_init(struct cdev *cdev,const struct file_operation *fops)
参数:
cdev:待初始化的cdev结构
fops:设备对应的操作函数集5.3
1.3字符设备注册
字符设备注册使用cdev_add函数来完成
cdev_add(struct cdev *p,dev_t dev,unsigned count)
参数:
p:待添加到内核的字符设备结构
dev:设备号
count:该设备的设备个数
1.4.硬件初始化
2.1 int (open)(struct inode,struct file *)
打开设备,响应open系统
设备操作-open
open设备方法是驱动程序用来为以后的操作完成初始化准备工作的,在大部分驱动程序中,open完成如下工作
标明次设备号
启动设备
2.2 int (release)(struct inode ,struct file *)
关闭设备,响应close系统调用,release方法的作用正好与open相反。这个设备方法有时也称为close,它应该:
关闭设备
2.3 loff_t (llseek) (struct file , loff_t, int)
重定位读写指针,响应lseek系统调用
2.4ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)
从设备读取数据,响应read系统调用,read设备方法通常完成2件事情:
从设备中读取数据(属于硬件访问类操作)
将读取到的数据返回给应用程序
参数分析:
filp:与字符设备文件关联的file结构指针, 由内核创建。
buff : 从设备读取到的数据,需要保存到的位置。由read系统调用提供该参数。
count: 请求传输的数据量,由read系统调用提供该参数。
offp: 文件的读写位置,由内核从file结构中取出后,传递进来。
buff参数是来源于用户空间的指针,这类指针都不能被内核代码直接引用,必须使用专门的函数
int copy_to_user(void __user *to, const void *from, int n)//read使用
2.5ssize_t (write) (struct file , const char __user , size_t, loff_t )
向设备写入数据,响应write系统调用,write设备方法通常完成2件事情:
从应用程序提供的地址中取出数据
将数据写入设备(属于硬件访问类操作)
其参数类似于read
int copy_from_user(void *to, const void __user *from, int n)
2.6参数分析
以上函数里面都使用了struct file类型,linux系统中,每一个打开的文件,在内核中都会关联一个struct file,它由打开文件时创建,在文件关闭后释放
重要成员:
loff_t f_pos/文件读写指针/
struct file_operation fop/该文件所对应的操作*/
struct inode
每一个存在于文件系统里面的文件都会关联一个inode结构,该结构主要用来记录文件上的信息,因此,它和代表打开文件的file结构是不同的。一个文件没有被打开时不会关联file结构,但是却会关联一个inode结果。
重要成员:
devt i_rdev:设备号
struct inode记录文件物理信息
cdev_del函数来完成字符设备的注销
LED驱动
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int major;
static struct cdev first_cdev;
static dev_t devid;
static struct class *firstdrv_class;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode, struct file *file)
{
*gpfcon &= ~((0x3<<8) | (0x3<<10) | (0x3>>12) );
*gpfcon |= (0x1<<8) | (0x1<<10) | (0x1<<12);
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *userbuf,size_t bytes, loff_t *off)
{
int val;
copy_from_user(&val, userbuf, bytes);
if(val)
*gpfdat &= ~( (1<<4) | (1<<5) | (1<<6) );
else
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
static int first_drv_init(void)
{
if(major){
devid = MKDEV(major, 0); //从此设备号0开始分配
register_chrdev_region(devid, 2, "first_drv");//注册,告诉内核
}else{
alloc_chrdev_region(&devid, 0, 2, "first_drv");//自动分配
major = MAJOR(devid);
devid = MKDEV(major, 0);
}
//将cdev结构体和file_operations结构体关联起来
cdev_init(&first_cdev, &first_drv_fops);
cdev_add(&first_cdev, devid, 2);
//使用udev机制自动创建设备节点
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "firstdrv");//dev/first_drv
gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
iounmap(gpfcon);
device_destroy(firstdrv_class, MKDEV(major, 0));
class_destroy(firstdrv_class);
cdev_del(&first_cdev);
unregister_chrdev_region(devid, 2);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL")