static struct cdev my_cdev; //设备属性结构体 static dev_t dev_from; //设备号
static struct class *test_class = NULL;
test_class = class_create(THIS_MODULE, “ljj_class”); //创建设备类 device_create(test_class, NULL, dev_from, NULL, “test”); //创建设备文件/dev/test
1.注册字符设备驱动新接口1
1.1、新接口与老接口
(1)老接口:register_chrdev
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops){ return __register_chrdev(major, 0, 256, name, fops);}
register_chrdev = MKDEV(MYMAJOR, 0) + register_chrdev_region + cdev_init + cdev_add
(2)新接口:
register_chrdev_region (dev_t from, unsigned count, const char *name)
函数用于已知起始设备的设备号的情况
-
define MYMAJOR 200
- mydev = MKDEV(MYMAJOR, 0); //生成设备号
- retval = register_chrdev_region(mydev, MYCNT, MYNAME);
alloc_chrdev_region (dev_t dev, unsigned baseminor, unsigned count,const char name);
用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功之后,会把得到的设备号放入第一个参数dev中
unregister_chrdev_region (dev_t from, unsigned count)
释放原先申请的设备号
1.2、cdev介绍 在cdev.h文件定义
(1)结构体
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
(2)相关函数:
cdev_alloc:用于动态申请一个cdev内存,减少栈的压力
cdev_init:用于初始化cdev的成员,并建立cdev和file_operations之间的连接
cdev_add:注册,它的调用通常发生在字符设备驱动模块加载函数中
cdev_del:注销,它的函数的调用则通常发生在字符设备驱动模块卸载函数中
- cdev = cdev_alloc();
- cdev->owner = fops->owner;
- cdev->ops = fops;
这几步等于:cdev_init(),因为cdev_alloc()里面做一些cdev_init()的工作
1.3、设备号
(1)主设备号和次设备号
(2)dev_t类型
dev设备号的主设备号占几位,在各个系统给定义不一样,所以用下面的宏来操作
(3)MKDEV、MAJOR、MINOR三个宏
1) — 从设备号中提取major和minor
MAJOR(dev_t dev);
MINOR(dev_t dev);
2) — 通过major和minor构建设备号
MKDEV(int major,int minor);
注:这只是构建设备号。并未注册,需要调用 register_chrdev_region 静态申请;
实例代码:
static struct cdev my_cdev;static dev_t dev_from;dev_from = MKDEV(MYMAJOR, 0);retval = register_chrdev_region(dev_from, MYCOUNT, MYNAME);if (retval){ printk(KERN_INFO "register_chrdev_region error\n"); return -EINVAL; }else{ printk(KERN_INFO "register number sucess\n"); }cdev_init(&my_cdev, &test_fops);if (cdev_add(&my_cdev, dev_from, MYCOUNT)) { printk(KERN_INFO "cdev_add error\n"); return -EINVAL;}else{ printk(KERN_INFO "register driver sucess\n"); }
3.注册字符设备驱动新接口3
2.1、使用alloc_chrdev_region自动分配设备号
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
static struct cdev *p_my_cdev;static dev_t dev_from;retval = alloc_chrdev_region(&dev_from, 0, MYCOUNT, MYNAME); if (retval){ printk(KERN_INFO "register_chrdev_region error\n"); goto err; }else{ printk(KERN_INFO "主设备:= %d ,次设备 = %d\n", MAJOR(dev_from), MINOR(dev_from)); printk(KERN_INFO "register number sucess\n"); }p_my_cdev = cdev_alloc(); cdev_init(p_my_cdev, &test_fops);cdev_add(p_my_cdev, dev_from, MYCOUNT)
2.2、得到分配的主设备号和次设备号
(1)使用MAJOR宏和MINOR宏从dev_t得到major和minor
(2)反过来使用MKDEV宏从major和minor得到dev_t。
(3)使用这些宏的代码具有可移植性
4.cdev 结构和inode 结构体
cdev结构体:
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count;};
一个cdev结构相当于一个字符设备
inode结构体:
inode和cdev的关系
每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应的字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点。这样可以通过inode结点的i_cdev字段找到cdev字符结构体。通过cdev的ops指针,就能找到设备A的操作函数。
内核使用inode结构在内部表示文件。inode一般作为file_operations结构中函数的参数传递过来。例如, open()函数将传递一个inode指针进来,表示目前打开的文件结点。需要注意的是, inode的成员已经被系统赋予了合适的值,驱动程序只需要使用该结点中的信息,而不用更改。Oepn()函数为:
int (*open) (struct inode *, struct file *);
除了从dev_t得到主设备号和次设备号外,这里还可以使用imajor()和iminor)函数从 inode->irdev中得到主设备号和次设备号。
static inline unsigned iminor(const struct inode *inode){ return MINOR(inode->i_rdev);}static inline unsigned imajor(const struct inode *inode){ return MAJOR(inode->i_rdev);}
cdev_alloc
struct cdev *cdev_alloc(void){ struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p;}
cdev_init
一般的赋值步骤:
- cdev = cdev_alloc();
- cdev->owner = fops->owner;
- cdev->init(cdev, fops);
- cdev_add(&dev->cdev, 设备号, 1);
void cdev_init(struct cdev *cdev, const struct file_operations *fops){ memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops;}
6.自动创建字符设备驱动的设备文件
1.使用mknod创建设备文件的缺点
麻烦,还要手动创建,可移植性差
2、解决方案:udev(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3)应用层启用udev,内核驱动中使用相应接口
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除
3、内核驱动设备类相关函数
(1)class_create// 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
// 给udev,让udev自动创建和删除设备文件
(2)device_create// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
// 所以我们这里要的文件名是/dev/test
4、编程实践
class_create(owner, name) //创建一个设备类
owner:THIS_MODULE
name : 名字
//创建后会产生/sys/class/ljj_class,/sys/devices/virtual/ljj_class
//和 ls /sys/class/ljj_class/test/
-r–r–r– 1 root 0 4096 Jan 1 12:01 dev
drwxr-xr-x 2 root 0 0 Jan 1 12:03 power
lrwxrwxrwx 1 root 0 0 Jan 1 12:03 subsystem -> ../../../../class/ljj_class
-rw-r–r– 1 root 0 4096 Jan 1 12:03 uevent
void class_destroy(struct class *cls) //销毁一个设备类
struct device device_create(struct class class, struct device parent,
dev_t devt, void drvdata, const char *fmt, …) //创建一个字符设备文件
struct class class :类
struct device parent:NULL
dev_t devt :设备号
void drvdata :NULL
const char fmt :名字
device_destroy(struct class *class, dev_t devt); //销毁一个字符设备文件
test_class = class_create(THIS_MODULE, "ljj_class"); if (IS_ERR(test_class)) goto error;device_create(test_class, NULL, dev_from, NULL, "test"); device_destroy(test_class, dev_from);class_destroy(test_class);
7.设备类相关代码分析1
1、sys文件系统简介
(1)sys文件系统的设计思想
(2)设备类的概念
(3)/sys/class/xxx/中的文件的作用
2、
(1)
class_create
__class_create
__class_register
kset_register
kobject_uevent
(2)
device_create
device_create_vargs
kobject_set_name_vargs
device_register
device_add
kobject_add
device_create_file
device_create_sys_dev_entry
devtmpfs_create_node
device_add_class_symlinks
device_add_attrs
device_pm_add
kobject_uevent
8.静态映射表建立过程分析
1、建立映射表的三个关键部分
(1)映射表具体物理地址和虚拟地址的值相关的宏定义
(2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io
s5p_init_io
iotable_init
结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
(3)开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
start_kernel
setup_arch
paging_init
devicemaps_init
if (mdesc->map_io)
mdesc->map_io();
动态建立映射:
request_mem_region(FB_GPJ0_BASE + FB_GPJ0_CON, 4, "GPJ0CON") request_mem_region(FB_GPJ0_BASE + FB_GPJ0_DAT, 4, "GPJ0DAT") pGPJ0CON = ioremap(FB_GPJ0_BASE + FB_GPJ0_CON, 4); pGPJ0DAT = ioremap(FB_GPJ0_BASE + FB_GPJ0_DAT, 4); *(pGPJ0CON) = 0x11111111; *(pGPJ0DAT) = ((0<<3) | (0<<4) | (0<<5)); iounmap(pGPJ0CON); iounmap(pGPJ0DAT); release_mem_region(FB_GPJ0_BASE + FB_GPJ0_DAT, 4); release_mem_region(FB_GPJ0_BASE + FB_GPJ0_CON, 4);
9.内核提供的读写寄存器接口
1、前面访问寄存器的方式
优点:方便快捷
缺点:可以移植性差,在不同的CPU架构中,它的直接寻址会不一样,采用writel就会避免这样的情况。
2、内核提供的寄存器读写接口
(1)writel和readl
writel(v, c) :v为要写的值,c为要写的地址
(2)iowrite32和ioread32
实例代码:
static struct cdev *p_my_cdev;
static dev_t dev_from;
static struct class *test_class = NULL;
unsigned int *gpj0_base = NULL;
unsigned int *pGPJ0CON = NULL;
unsigned int *pGPJ0DAT = NULL;
char kernel_buf[100] = {0};
int mymajor = -1;
static int test_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test open function\n");
++p_my_cdev->count;
rGPJ0CON = 0x11111111;
return 0;
}
static int test_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test release function\n");
--p_my_cdev->count;
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
static ssize_t test_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
printk(KERN_INFO "test read function\n");
if (copy_to_user(buf, kernel_buf, strlen(kernel_buf)))
return -EINVAL;
printk(KERN_INFO "user-->kernel sucess \n");
return 0;
}
static ssize_t test_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
printk(KERN_INFO "test write function\n");
memset(kernel_buf, 0, sizeof(kernel_buf));
if (copy_from_user(kernel_buf, user_buf, strlen(user_buf)))
return -EINVAL;
switch(kernel_buf[0]){
case '1':
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
break;
case '0':
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
break;
}
printk(KERN_INFO "kernel-->user sucess \n");
return 0;
}
static const struct file_operations test_fops = {
.open = test_open,
.write = test_write,
.read = test_read,
.release = test_release,
.owner = THIS_MODULE,
};
static int __init chrdev_init(void)
{
int retval = -1;
printk(KERN_INFO "chrdev_init helloworld init\n");
retval = alloc_chrdev_region(&dev_from, 0, MYCOUNT, MYNAME);
if (retval){
printk(KERN_INFO "register_chrdev_region error\n");
goto err;
}else{
printk(KERN_INFO "主设备:= %d ,次设备 = %d\n", MAJOR(dev_from), MINOR(dev_from));
printk(KERN_INFO "register number sucess\n");
}
p_my_cdev = cdev_alloc();
cdev_init(p_my_cdev, &test_fops);
if (cdev_add(p_my_cdev, dev_from, MYCOUNT)) {
printk(KERN_INFO "cdev_add error\n");
goto err_cdev_init;
}else{
printk(KERN_INFO "register driver sucess\n");
}
test_class = class_create(THIS_MODULE, "ljj_class");
if (IS_ERR(test_class))
goto err_mem_region_con;
device_create(test_class, NULL, dev_from, NULL, "test");
if (!request_mem_region(FB_GPJ0_BASE + FB_GPJ0_CON, 4, "GPJ0CON"))
{
printk(KERN_ERR "request_mem_region error\n");
goto err_mem_region_con;
}
if (!request_mem_region(FB_GPJ0_BASE + FB_GPJ0_DAT, 4, "GPJ0DAT"))
{
printk(KERN_ERR "request_mem_region error\n");
goto err_mem_region_dat;
}
pGPJ0CON = ioremap(FB_GPJ0_BASE + FB_GPJ0_CON, 4);
pGPJ0DAT = ioremap(FB_GPJ0_BASE + FB_GPJ0_DAT, 4);
*(pGPJ0CON) = 0x11111111;
*(pGPJ0DAT) = ((0<<3) | (0<<4) | (0<<5));
printk(KERN_INFO "register_chrdev success...\n");
return 0;
err_mem_region_dat:
release_mem_region(FB_GPJ0_BASE + FB_GPJ0_DAT, 4);
release_mem_region(FB_GPJ0_BASE + FB_GPJ0_CON, 4);
err_mem_region_con:
cdev_del(p_my_cdev);
err_cdev_init:
unregister_chrdev_region(dev_from, MYCOUNT);
err:
return -EINVAL;
}
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
*(pGPJ0DAT) = ((1<<3) | (1<<4) | (1<<5));
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(FB_GPJ0_BASE + FB_GPJ0_CON, 4);
release_mem_region(FB_GPJ0_BASE + FB_GPJ0_DAT, 4);
device_destroy(test_class, dev_from);
class_destroy(test_class);
cdev_del(p_my_cdev);
unregister_chrdev_region(dev_from, MYCOUNT);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aston");
MODULE_DESCRIPTION("module test");
MODULE_ALIAS("alias xxx");
Original: https://blog.51cto.com/u_13267193/5371008
Author: QtHalcon
Title: 3.字符设备驱动高级
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/517524/
转载文章受原作者版权保护。转载请注明原作者出处!