如何一步一步执行驱动:
在进行驱动加载的过程中使用 insmod mydev.ko时,会执行驱动的加载函数,
在驱动的加载函数中会进行设备的注册
1. MKDEV (hello_major, hello_minor)去生成设备号
2. 向内核去注册设备节点result = register_chrdev_region (dev, number_of_devices, "hello");
3. 创建一个file_operations hello_fops 结构体,并且对这个结构体初始化,
这个结构体是实现驱动本身的open read write close的功能
4. 使用cdev_init函数进行初始化 cdev_init(&my_cdev, &fops);让my_dev和fops进行绑定, cdev->ops = fops;
cdev结构体定义如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
5. 使用cdev_add()把这个my_cdev这个结构体加入到cdev链表当中, 驱动加载完成
6. 驱动加载后会,会有对应的设备节点,此时使用mknod命令来创建设备号
mknod /dev/mydev c 250 0
7. 执行mknod命令后,时会在内核中创建一个inode的结构体,这里只写inode重要的成员信息
struct inode {
dev_t i_rdev; //该成员表示设备文件的inode结构,它包含了真正的设备编号。
struct cdev *i_cdev; //该成员表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,
//该成员包含了指向struct cdev结构的指针,其中cdev结构是字符设备结构体。
};
首先会对i_rdev 这个成员赋值,会对成员赋值为相应的设备号。i_rdev = (250,0);
其次会对i_cdev 赋值,通过设备号在cdev链表中查找,可以找到我们通过cdev_add()添加的结构体,
故 i_cdev = &my_cdev
8. 应用程序调用 open("/dev/hello")这个函数时,内核会调用sys_open, 内核会在调用open函数时创建struct file 结构体,
并且会对file结构体的成员f_op赋值为驱动中的file_operation结构体(通过设备号可以找到可以找到inode结构体,
inode结构体中有i_cdev指针,指向系统的cdev的结构体,cdev的成员ops指向驱动的file_operation成员hello_ops)
应用程序调用open,驱动中也会调用file_operation的open函数,故系统正确的调用.
应用程序调用open后,对于驱动来说,filep->f_op = hello_ops,最终会调用驱动的open函数
struct file {
struct path f_path;
const struct file_operations *f_op;
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
atomic_long_t f_count;
};
1. 设备号: 包括主设备号和次设备号
(1) 主设备号: 表示是哪一类的驱动 usb adc led,uart
(2) 通过 cat /proc/devices 显示系统设备号
(3) 字符设备和块设备的设备号是独立的
(4) 次设备号 用来表示这一类设备中的哪一个设备
(5) 设备号内核用一个变量来表示 dev_t(ulong)来表示一个设备号
(6) 高20为来表示主设备号 低12为表示次设备号
major = 0 ~ 1M
minor = 0 ~ 4096
(7) 可以使用一个宏函数去生成一个设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
dev_t devno = MKDEV(250,0)
2. 设备的申请:
(1)静态申请设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
依赖的头文件: #include<linux/fs.h>
form : 从哪一个设备号开始向内核去申请设备号,向内核去申请一个或多个设备号,
如果这个设备号内核没有占用,可以申请到,否则申请失败
count : 表示要申请设备的个数
name : 要申请设备的名称
returned: 成功返回 ;失败 负数
实例:
int ret;
dev_t devno = MKDEV(major,minor);
ret = register_chrdev_region(devno,1,"char");
if(ret < 0 )
{
printk("fail to get devno\n");
return ret;
}
使用命令去查看申请到的设备号 cat /proc/devices
(2)动态申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
依赖的头文件: #include<linux/fs.h>
dev : 设备号变量的指针
baseminor : 次设备号开始于那一个数字
count : 需要的多少个设备
name : 要申请设备的名称
returned: 成功返回 0 失败 负数
3.设备号的注销
void unregister_chrdev_region(dev_t from, unsigned count)
依赖的头文件: #include<linux/fs.h>
form : 要释放的设备号
count : 要释放几个设备号
4. 字符设备(cdev)注册:
依赖头文件 linux/cdev.h
(1)struct cdev {
struct kobject kobj; //内嵌的kobject对象
struct module *owner; //所属模块
const struct file_operations *ops; //文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
(2) cdev_init() 初始化一个cdev结构体,把cdev这个结构体和file_operations结构体进行关联
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev: 要初始化的结构体指针
fops : 自定义实现的file_operations结构体指针
(3)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 (*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);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
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 *, loff_t, loff_t, 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 (*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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
(4) cdev.owner = THIS_MODULE; 表示的是指向本模块的指针 #define THIS_MODULE (&__this_module)
(5) cdev_add() 向系统添加一个字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
/*
* 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.
*/
(6) cdev_del()从系统链表中删除一个字符设备
/**
* 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)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
(7)创建一个设备文件 mknod /dev/mychar c 250 0
crw-r--r-- 1 root root 250, 0 7月 30 16:46 /dev/mychar
(8)定义一个io命令
#define LEDON _IO('A',0)
#define LEDOFF _IO('A',1)
依赖一个头文件: #include<asm/ioctl.h>
(9)ioremap : 用来将I/O内存资源的物理地址映射到核心虚拟地址空间(3GB-4GB)中
gpc0con = ioremap(GPC0CON, 4);
gpc0dat = ioremap(GPC0DAT, 4);
头文件: #include <asm/io.h>
writel((readl(gpc0con) & ~(0xff<<12)) | (0x11<<12), gpc0con);
writel((readl(gpc0dat) & ~(0x3 << 3)), gpc0dat);
writel((readl(gpc0dat) | (0x3 << 3)), gpc0dat);
在内核驱动程序的初始化阶段,通过ioremap()将物理地址映射到内核虚拟空间;在驱动程序的mmap系统调用中,使用remap_page_range()将该块ROM映射到用户虚拟空间。这样内核空间和用户空间都能访问这段被映射后的虚拟地址
(10), 驱动中和应用程序中关于ioctl的命令定义格式
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
#define LEDON _IO('A',0)
#define LEDOFF _IO('A',1)
#define LEDSET _IOW('A',2,1)
type : 表示类型,是命令的类别(组),这个数的表示范围是0x0 ~ 0xff led控制
nr : 这一组命令的第几个 led控制的方式, on off
_IOR : 功过ioctl要读
(11) 如果insmod char.ko 出现没有这个目录时, (rmmod: char(3.2.0): No such file or directory)
解决办法: mkdir -p /lib/modules/$(uname -r)
(12) 把内核空间里面的数据传递给用户空间 copy_to_user
依赖头文件: #include<asm/uaccess.h>
extern inline long copy_to_user(void __user *to, const void *from, long n)
{
return __copy_tofrom_user((__force void *)to, from, n, to);
}
参数:
to : 数据的目的
from: 数据的源
n : 数据的大小
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
(13) 把用户空间内的值读到内核空间中 copy_from_user
extern inline long copy_from_user(void *to, const void __user *from, long n)
{
return __copy_tofrom_user(to, (__force void *)from, n, from);
}
to : 数据的目的 ,要写入内核空间的buf内
from: 数据的源 ,来至于用户空间的,
n : 用户空间要向内核空间写入的数据大小
失败返回没有被拷贝的字节数,成功返回0.
num = copy_from_user(buf,from,count);
if(num < 0 )
{
n : 用户空间要向内核空间写入的数据大小
失败返回没有被拷贝的字节数,成功返回0.
num = copy_from_user(buf,from,count);
if(num < 0 )
{
printk("copy_to_user is failed\n");
return ret;
}
(14) ssize_t (*read) (struct file *filep, char __user *to , size_t count , loff_t * off);
filep : 指向内核创建的file结构体指针
to : 要向用户空间发送数据的指针,
count : 用户空间要读取的字节数
off : 文件指针的偏移量
(15) ssize_t (*write) (struct file *, const char __user *from, size_t count, loff_t *off);
filep : 指向内核创建的file结构体指针
from : 要接受用户空间发给内核空间数据的指针
count : 用户空间要写的字节数
off : 文件指针的偏移量