设备号 在 Linux 系统中每一个设备都有相应的设备号,设备号有主设备号 与次设备号 之分:
主设备号 :标识设备类型 。
次设备号 :在同一个驱动管理的多个设备实例之间进行区分
举例:
主设备号 13 表示 input 子系统 (处理输入设备)。
次设备号 64 → /dev/input/event0(可能是 USB 键盘)
次设备号 65 → /dev/input/event1(可能是 USB 鼠标)
主设备号 188 表示 USB 串行设备 。
次设备号 0 → /dev/ttyUSB0
次设备号 1 → /dev/ttyUSB1
在注册字符设备驱动之前需要先申请设备号 。
include/linux/types.h
1 2 3 4 5 6 7 typedef u32 __kernel_dev_t ;typedef __kernel_fd_set fd_set;typedef __kernel_dev_t dev_t ;typedef __kernel_ino_t ino_t ;typedef __kernel_mode_t mode_t ;typedef unsigned short umode_t ;
设备号 dev_t 是 u32 类型,高 12 位为主设备号 ,低 20 位为次设备号 。
设备号操作宏 include/linux/kdev_t.h
1 2 3 4 5 6 #define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MINORBITS 表示次设备号的位数,一共 20 位
MINORMASK 用于计算次设备号
MAJOR 表示从 dev_t 中获取主设备号,本质是将 dev_t 右移 20 位
MINOR 表示从 dev_t 中获取次设备号,本质是取低 20 位的值
MKDEV 用于将主设备号和次设备号组成 dev_t 类型的设备号
设备号申请函数 在 Linux 驱动中可以使用以下两种方法进行设备号的申请。
include/linux/fs.h
1 2 3 extern int register_chrdev_region (dev_t , unsigned , const char *) ;extern int alloc_chrdev_region (dev_t *, unsigned , unsigned , const char *) ;
register_chrdev_region()
通过 register_chrdev_region(dev_t from, unsigned count, const char *name) 函数进行静态申请设备号。
1 register_chrdev_region(dev_t from, unsigned count, const char *name)
函数作用 :静态申请设备号,对指定好的设备号进行申请。
参数含义 :
from: 自定义的 dev_t 类型设备号,比如 MKDEV(100,0)表示起始主设备号 100,起始次设备号为 0
count: 次设备的数量,表示在主设备号相同的情况下有几个次设备号。
name: 申请的设备名称
函数返回值 :申请成功返回 0,申请失败返回负数
注意,静态申请的设备号是有限制的:include/linux/fs.h中定义:
1 2 3 4 5 6 7 #define CHRDEV_MAJOR_MAX 512 #define CHRDEV_MAJOR_DYN_END 234 #define CHRDEV_MAJOR_DYN_EXT_START 511 #define CHRDEV_MAJOR_DYN_EXT_END 384
alloc_chrdev_region()
通过alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char* name)函数进行动态申请设备号。
1 alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
设备号释放函数 unregister_chrdev_region() include/linux/fs.h
1 extern void unregister_chrdev_region (dev_t , unsigned ) ;
函数功能:设备号释放函数,注销字符设备以后要释放掉设备号
函数参数:
dev_t类型:要释放的设备号
unsigned类型:释放的设备号的数量
示例 my_driver.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); static dev_t dev_id;static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); printk(KERN_INFO "major from module_param: %d" , MAJOR(dev_id)); printk(KERN_INFO "minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { printk(KERN_ERR "register_chrdev_region error\n" ); } else { printk(KERN_INFO "register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { printk(KERN_ERR "alloc_chrdev_region error\n" ); } else { printk(KERN_INFO "alloc_chrdev_region ok\n" ); printk(KERN_INFO "major allocated: %d" , MAJOR(dev_id)); printk(KERN_INFO "minor allocated: %d" , MINOR(dev_id)); } } printk(KERN_INFO "my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { unregister_chrdev_region(dev_id, 1 ); printk(KERN_INFO "unregister_chrdev_region ok\n" ); printk(KERN_INFO "my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ~ [ 18.414274] my_driver: loading out-of-tree module taints kernel. [ 18.421489] alloc_chrdev_region ok [ 18.421652] major allocated: 511 [ 18.421667] minor allocated: 0 [ 18.421910] my_driver: Module loaded ~ 511 my_driver device ~ [ 27.856484] unregister_chrdev_region ok [ 27.856638] my_driver: Module unloaded ~ ~ ~ [ 16.721502] my_driver: loading out-of-tree module taints kernel. [ 16.728530] major from module_param: 512 [ 16.728574] minor from module_param: 0 [ 16.728753] CHRDEV "my_driver device" major requested (512) is greater than the maximum (511) [ 16.729588] register_chrdev_region error [ 16.729760] my_driver: Module loaded ~ [ 36.863583] unregister_chrdev_region ok [ 36.864560] my_driver: Module unloaded ~ [ 40.101231] major from module_param: 500 [ 40.101265] minor from module_param: 0 [ 40.101430] register_chrdev_region ok [ 40.101657] my_driver: Module loaded ~ [ 50.626705] unregister_chrdev_region ok [ 50.626876] my_driver: Module unloaded
注册字符类设备 include/linux/cdev.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include <linux/kobject.h> #include <linux/kdev_t.h> #include <linux/list.h> #include <linux/device.h> struct file_operations ;struct inode ;struct module ;struct cdev { struct kobject kobj ; struct module *owner ; const struct file_operations *ops ; struct list_head list ; dev_t dev; unsigned int count; } __randomize_layout; void cdev_init (struct cdev *, const struct file_operations *) ;struct cdev *cdev_alloc (void ) ;void cdev_put (struct cdev *p) ;int cdev_add (struct cdev *, dev_t , unsigned ) ;void cdev_set_parent (struct cdev *p, struct kobject *kobj) ;int cdev_device_add (struct cdev *cdev, struct device *dev) ;void cdev_device_del (struct cdev *cdev, struct device *dev) ;void cdev_del (struct cdev *) ;void cd_forget (struct inode *) ;#endif
在 C 语言中,如果你只需要使用某个结构体的 指针 ,而不需要知道它的内部成员(即不进行解引用或 sizeof),就可以只做 前置声明 :
这样你就可以定义指针:
1 struct file_operations *fops ;
但不能访问成员(因为编译器还不知道结构体内容):
这在头文件中非常常见,用于 解耦依赖 和 加快编译速度 。
而extern 是 存储类说明符(storage class specifier) ,用于声明 变量 或 函数 的外部链接属性。它的作用是告诉编译器:“这个变量/函数在别处定义,这里只是声明”。
GCC/Clang attribute(__randomize_layout), 用于内核的 结构体布局随机化(struct layout randomization) ,增加攻击者预测结构偏移的难度,提高内核安全性。
cdev 结构体
字段
含义
kobj
继承自 struct kobject,使字符设备能挂在 sysfs 下(即 /sys/class/... 等路径)。
owner
指向拥有该设备的模块(THIS_MODULE),防止模块卸载时设备仍在使用。
ops
指向设备操作函数表(struct file_operations),定义 read/write/ioctl 等行为。
list
内部链表,将所有共享该 cdev 的 inode(即 /dev/xxx 文件)通过 inode->i_devices 链接到一起,形成一个“反向引用链表”。
dev
设备号,类型是 dev_t(包含主设备号 major 和次设备号 minor)。
count
表示这个 cdev 控制的连续设备编号数量(常为 1)。
cdev_init fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; }
作用 :初始化一个静态分配的 struct cdev。建立 cdev 和 file_operations 之间的联系
注意: 调用cdev_init后最好调用cdev_test.owner = THIS_MODULE; 将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
cdev_add fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int cdev_add (struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; if (WARN_ON(dev == WHITEOUT_DEV)) return -EBUSY; error = kobj_map(cdev_map, dev, count, NULL , exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0 ; }
作用 :向系统添加一个 cdev 结构体,也就是添加一个字符设备
cdev_map:全局 kobj_map 结构(哈希表)
p:指向 struct cdev
注意:cdev 内嵌了 struct kobject kobj,所以 cdev 可以被视为一个 kobject
cdev_del fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void cdev_del (struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); static dev_t dev_id;static struct cdev cdev_test ;static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, }; static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); pr_info( "major from module_param: %d" , MAJOR(dev_id)); pr_info( "minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err( "register_chrdev_region error\n" ); return ret; } else { pr_info( "register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err( "alloc_chrdev_region error\n" ); return ret; } else { pr_info( "alloc_chrdev_region ok\n" ); pr_info( "major allocated: %d" , MAJOR(dev_id)); pr_info( "minor allocated: %d" , MINOR(dev_id)); } } cdev_init(&cdev_test, &cdev_test_ops); cdev_test.owner = THIS_MODULE; ret = cdev_add(&cdev_test, dev_id, 1 ); if (ret < 0 ) { pr_err( "cdev_add error\n" ); unregister_chrdev_region(dev_id, 1 ); return ret; }else { pr_info( "cdev_add ok\n" ); } pr_info( "my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { cdev_del(&cdev_test); unregister_chrdev_region(dev_id, 1 ); pr_info( "unregister_chrdev_region ok\n" ); pr_info( "my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
注意先删除设备再释放设备号。
file_operations 结构体 Linux 有一个很重要的概念叫一切皆文件,也就是 Linux 中的设备就像普通的文件一样。访问一个设备好像是在访问一个文件。在应用程序中我们可以使用 open, read, write, close, ioctl 这几个系统调用来操作驱动。当我们在应用程序中调用 open 函数的时候,最终会去执行驱动中的 open 函数。所以 file_operations 将系统调用和驱动程序连接起来了。
应用中调用 open 函数
1 fd = open("/dev/hello" , O_RDWR);
驱动中执行 open 函数
1 2 3 4 5 6 7 8 9 10 11 12 static int cdev_test_open (struct inode *, struct file*) { printk("This is cdev_test open" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }
linux 中 file_operations 定义:
include/linux/fs.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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 (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iopoll)(struct kiocb *kiocb, bool spin); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*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 *); unsigned long mmap_supported_flags; 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 (*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 **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t , struct file *, loff_t , size_t , unsigned int ); loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int remap_flags); int (*fadvise)(struct file *, loff_t , loff_t , int ); bool may_pollfree; } __randomize_layout;
常见字段解析:
函数指针
用户态调用
作用
owner
—
通常写为 THIS_MODULE,防止模块被正在使用时卸载
open
open()
打开设备时调用,一般用于初始化或统计打开次数
release
close()
关闭设备时调用,用于释放资源
read
read()
用户读设备数据
write
write()
用户写数据到设备
unlocked_ioctl
ioctl()
用户发控制命令
poll
poll()/select()
实现非阻塞 I/O(epoll 机制)
mmap
mmap()
将物理内存映射到用户空间
llseek
lseek()
文件位置偏移
fasync
fcntl(F_SETFL, O_ASYNC)
支持异步通知
返回值约定 :
ssize_t:>=0 成功字节数,<0 errno
int / long:0 成功,<0 errno
指针:成功返回指针,失败返回 ERR_PTR(-errno)
llseek 1 loff_t (*llseek)(struct file *file, loff_t offset, int whence);
作用 :文件指针偏移操作(类似 lseek 系统调用)。
参数 :
file:文件对象。
offset:偏移量。
whence:
SEEK_SET:相对于文件开头
SEEK_CUR:相对于当前文件指针
SEEK_END:相对于文件末尾
返回值 :
新的文件指针位置(loff_t)
出错返回负值(如 -EINVAL)
read 1 ssize_t (*read)(struct file *file, char __user *buf, size_t count, loff_t *pos);
作用 :从文件读数据到用户空间。
参数 :
file:文件对象。
buf:用户空间缓冲区指针。
count:希望读取的字节数。
pos:文件指针位置,读完后需要更新。
返回值 :
write 1 ssize_t (*write)(struct file *file, const char __user *buf, size_t count, loff_t *pos);
作用 :把用户数据写入文件/设备。
参数 :
file:文件对象
buf:用户缓冲区
count:写入字节数
pos:文件指针
返回值 :
mmap 1 int (*mmap)(struct file *, struct vm_area_struct *);
作用 :支持用户空间内存映射到设备。
参数 :
返回值 :
mmap_supported_flags:映射时支持的标志。
open 1 int (*open)(struct inode *inode, struct file *file);
作用 :当用户态调用 open() 打开设备或文件时被调用。
参数 :
inode:指向设备对应的 inode 结构,包含文件系统层信息和设备号。
file:指向文件对象(struct file),保存文件状态、偏移量、private_data 等。
返回值 :
0:成功打开
<0:出错,返回对应 errno(如 -EBUSY 表示设备忙,-ENOMEM 内存不足)
典型用途 :
检查设备是否已经被占用
初始化 file->private_data 指针
增加模块引用计数(THIS_MODULE)
flush 1 int (*flush)(struct file *file, fl_owner_t id);
作用 :刷新文件缓冲区,确保用户态写入的数据在内核缓冲中被提交。
参数 :
file:文件对象
id:文件句柄拥有者标识,一般是文件描述符对应的进程
返回值 :
说明 :
对于大多数字符设备驱动,flush 不需要特殊实现,直接返回 0 即可
在块设备或网络设备中,flush 可能用于提交缓存数据
release 1 int (*release)(struct inode *inode, struct file *file);
作用 :当用户态调用 close() 关闭设备或文件时被调用。
参数 :
inode:设备对应 inode
file:文件对象
返回值 :
典型用途 :
释放在 open() 时分配的资源
恢复设备状态(如标记为未被占用)
减少模块引用计数
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> static int major = 0 ;module_param(major, int , S_IRUGO); MODULE_PARM_DESC(major, "cdev_test sample, major device number" ); static int minor = 0 ;module_param(minor, int , S_IRUGO); MODULE_PARM_DESC(minor, "cdev_test sample, minor device number" ); dev_t dev_num;struct cdev cdev ;int cdev_test_open (struct inode *inode, struct file *file) { pr_info("cdev_test open" ); return 0 ; } ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { pr_info("cdev_test read\n" ); return 0 ; } ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t , loff_t *offset) { pr_info("cdev_test write\n" ); return 0 ; } struct file_operations fops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, }; static int __init cdev_test_init (void ) { int err; if (major) { dev_num = MKDEV(major, minor); err = register_chrdev_region(dev_num, 1 , "cdev test dev num" ); if (err < 0 ) { pr_err("register_chrdev_region error\n" ); goto chrdev_region_err; } pr_info("register_chrdev_region success\n" ); } else { err = alloc_chrdev_region(&dev_num, 0 , 1 , "cdev test dev num" ); if (err < 0 ) { pr_err("alloc_chrdev_region error\n" ); goto chrdev_region_err; } pr_info("alloc_chrdev_region success\n" ); } pr_info("dev_num: major[%d] minor[%d]" , MAJOR(dev_num), MINOR(dev_num)); cdev_init(&cdev, &fops); pr_info("cdev_init success\n" ); cdev.owner = THIS_MODULE; err = cdev_add(&cdev, dev_num, 1 ); pr_info("cdev_add success\n" ); if (err < 0 ) { pr_err("cdev_add error\n" ); goto cdev_add_err; } return 0 ; cdev_add_err: unregister_chrdev_region(dev_num, 1 ); chrdev_region_err: return err; } static void __exit cdev_test_exit (void ) { cdev_del(&cdev); pr_info("cdev is deleted\n" ); unregister_chrdev_region(dev_num, 1 ); pr_info("chrdev_region unregister success\n" ); } module_init(cdev_test_init); module_exit(cdev_test_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("even629<asqwgo@163.com>" ); MODULE_DESCRIPTION("This is just a cdev test sample" );
设备结点 在 Linux 操作系统中一切皆文件,对于用来进行设备访问的文件称之为设备节点 。程序通过操作这个”设备文件”,便可以操作对应的硬件。
1 fd = open("/dev/hello" , O_RDWR);
这个“设备文件”就是设备结点。所以 Linux 设备结点是应用程序和驱动程序沟通的一个桥梁。设备节点被创建在/dev 目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [zhaohang@cyberboy linux_driver_learning]$ ll /dev crw------- 4,15 root 11 Nov 10:05 tty15 crw------- 4,16 root 11 Nov 10:05 tty16 crw------- 4,17 root 11 Nov 10:05 tty17 crw------- 4,18 root 11 Nov 10:05 tty18 crw------- 4,19 root 11 Nov 10:05 tty19 crw------- 4,20 root 11 Nov 10:05 tty20 crw------- 4,21 root 11 Nov 10:05 tty21 crw------- 4,22 root 11 Nov 10:05 tty22 crw------- 4,23 root 11 Nov 10:05 tty23 crw------- 4,24 root 11 Nov 10:05 tty24 crw------- 4,25 root 11 Nov 10:05 tty25 crw------- 4,26 root 11 Nov 10:05 tty26 crw------- 4,27 root 11 Nov 10:05 tty27 crw------- 4,28 root 11 Nov 10:05 tty28 crw------- 4,29 root 11 Nov 10:05 tty29
4,15表示主设备号和次设备号。crw 中 c 表示字符设备。
根据设备节点的创建方式不同,分为了手动创建设备节点 和自动创建设备节点 。
Linux 下创建结点的方式 手动创建设备结点 使用 mknod 命令手动创建设备节点,mknod 命令格式为:
1 $ mknod NAME TYPE MAJOR MINOR
参数含义:
NAME: 要创建的节点名称
TYPE: b 表示块设备,c 表示字符设备,p 表示管道
MAJOR:要链接设备的主设备号
MINOR: 要链接设备的从设备号
例如: 使用以下命令创建一个名为 device_test 的字符设备节点,设备的主设备号和从设备号分别为 236 和 0。
1 $ mknod /dev/device_test c 236 0
对于上面 file_operations 的示例:
1 2 3 4 5 6 7 8 9 10 11 12 $ insmod cdev_test.ko [ 43.969734] cdev_test: loading out-of-tree module taints kernel. [ 43.979783] alloc_chrdev_region success [ 43.980242] dev_num: major[241] minor[0] [ 43.980277] cdev_init success [ 43.981192] cdev_add success $ mknod /dev/cdev_test c 241 0 $ ls /dev/cdev_test /dev/cdev_test $ cat /dev/cdev_test [ 68.723315] cdev_test open [ 68.723900] cdev_test read
自动创建设备结点 自动创建设备节点是利用 udev 机制来实现的。
udev 是一个用户程序 ,通过检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件 。
自动创建设备节点需要在驱动中首先使用 class_create()函数创建一个类,创建的类位于于/sys/class/ 目录下 ,之后使用 device_create()函数在这个类下创建相应的设备 ,在加载驱动模块时,用户空间中的 udev 会自动响应根据并/sys/class/ 下的信息创建设备节点。
在嵌入式 Linux 中我们使用的是 mdev,mdev 是 udev 的简化版本 。在使用 busybox 构建的跟文件系统的时候,busybox 会自动创建 mdev。
class_create 函数 linux/device/class.h
引用头文件使用 linux/device/device.h 因为 device.h 引用了 class.h
1 2 3 4 5 6 7 8 #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
函数作用 :用于动态创建设备类。
参数含义 :
owner:struct module 结构体类型的指针。一般赋值为 THIS_MODULE。
name:char 类型的指针,代表即将创建的 struct class 变量的名字。
返回值 :struct class * 类型的结构体
class_destroy 函数 linux/device/class.h
1 extern void class_destroy (struct class *cls) ;
函数作用:用于删除设备类。
参数含义:
cls:调用class_create()函数返回的struct class结构体的指针。
device_create 函数 linux/device/device.h
使用 class_create 创建好类以后,还需要使用 device_create 函数在类下面创建一个设备。
1 2 3 __printf(5 , 6 ) struct device * device_create (struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) ;
函数作用:用来在 class 类下创建一个设备文件。
参数含义:
cls:指定所要创建的设备所从属的类。
parent:指定该设备的父设备,如果没有就指定为 NULL。
devt:指定创建设备的设备号。
drvdata:被添加到该设备回调的数据,没有则指定为 NULL。
fmt:添加到系统的设备节点名称。
返回值:struct device * 类型结构体
device_destroy 函数 linux/device/device.h
1 void device_destroy (struct class *cls, dev_t devt) ;
函数作用 :用来删除 class 类中的设备。
参数含义:
cls:指定所要创建的设备所从属的类。
devt:指定创建设备的设备号。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> static int major = 0 ;module_param(major, int , S_IRUGO); MODULE_PARM_DESC(major, "mknod_test dev major num" ); static int minor = 0 ;module_param(minor, int , S_IRUGO); MODULE_PARM_DESC(minor, "mknod_test dev minor num" ); dev_t dev_num;int mknod_test_open (struct inode *inode, struct file *file) { pr_info("This is mknod_test open\n" ); return 0 ; } ssize_t mknod_test_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { pr_info("This is mknod test read\n" ); return 0 ; } int mknod_test_release (struct inode *inode, struct file *file) { pr_info("This is mknod test release\n" ); return 0 ; } struct file_operations fops = { .owner = THIS_MODULE, .open = mknod_test_open, .read = mknod_test_read, .release = mknod_test_release, }; struct cdev cdev ;struct class *class ;struct device *dev ;static int __init mknod_test_init (void ) { int err; pr_info("mknod_test module init\n" ); if (major) { dev_num = MKDEV(major, minor); err = register_chrdev_region(dev_num, 1 , "mknod test device num" ); if (err < 0 ) { pr_err("register_chrdev_region error\n" ); goto chrdev_region_err; } pr_info("register_chrdev_region success\n" ); } else { err = alloc_chrdev_region(&dev_num, 0 , 1 , "mknod test device num" ); if (err < 0 ) { pr_err("alloc_chrdev_region error\n" ); goto chrdev_region_err; } pr_info("alloc_chrdev_region success\n" ); } pr_info("dev_t dev_num: major[%d], minor[%d]\n" , MAJOR(dev_num), MINOR(dev_num)); cdev_init(&cdev, &fops); cdev.owner = THIS_MODULE; err = cdev_add(&cdev, dev_num, 1 ); if (err < 0 ) { pr_err("cdev add error\n" ); goto cdev_add_err; } class = class_create(THIS_MODULE, "chrdev" ); if (IS_ERR(class)) { err = PTR_ERR(class); pr_err("class_create error\n" ); goto class_create_err; } dev = device_create(class, NULL , dev_num, NULL , "mknod_test_device" ); if (IS_ERR(dev)){ err = PTR_ERR(dev); pr_err("device create error\n" ); goto device_create_err; } return 0 ; device_create_err: class_destroy(class); class_create_err: cdev_del(&cdev); cdev_add_err: unregister_chrdev_region(dev_num, 1 ); chrdev_region_err: return err; } static void __exit mknod_test_exit (void ) { device_destroy(class, dev_num); class_destroy(class); cdev_del(&cdev); unregister_chrdev_region(dev_num, 1 ); pr_info("mknod_test module exit\n" ); } module_init(mknod_test_init); module_exit(mknod_test_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("even629<asqwgo@163.com>" ); MODULE_DESCRIPTION("This is just mknod test sample" );
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 $ insmod mknod_test.ko [ 11.793662] mknod_test: loading out-of-tree module taints kernel. [ 11.803110] mknod_test module init [ 11.803371] alloc_chrdev_region success [ 11.804103] dev_t dev_num: major[241], minor[0] $ ls /dev/mknod_test_device /dev/mknod_test_device $ cat /dev/mknod_test_device [ 22.546620] This is mknod_test open [ 22.547827] This is mknod test read [ 22.548435] This is mknod test release $ rmmod mknod_test.ko [ 28.103036] mknod_test module exit
用户空间和内核空间的数据交换 内核空间和用户空间的内存是不能互相访问的。但是很多应用程序都需要和内核进行数据的交换,例如应用程序使用 read()函数从驱动中读取数据,使用 write()函数向驱动中写数据,上述功能就需要使用 copy_from_user()和 copy_to_user()俩个函数来完成。
copy_from_user()函数是将用户空间的数据拷贝到内核空间。
copy_to_user()函数是将内核空间的数据拷贝到用户空间。
用户空间—->内核空间
include/linux/uaccess.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 static __always_inline __must_check unsigned long __copy_from_user_inatomic(void *to, const void __user *from, unsigned long n) { instrument_copy_from_user(to, from, n); check_object_size(to, n, false ); return raw_copy_from_user(to, from, n); } static __always_inline __must_check unsigned long __copy_from_user(void *to, const void __user *from, unsigned long n) { might_fault(); if (should_fail_usercopy()) return n; instrument_copy_from_user(to, from, n); check_object_size(to, n, false ); return raw_copy_from_user(to, from, n); } static __always_inline __must_check unsigned long __copy_to_user_inatomic(void __user *to, const void *from, unsigned long n) { if (should_fail_usercopy()) return n; instrument_copy_to_user(to, from, n); check_object_size(from, n, true ); return raw_copy_to_user(to, from, n); } static __always_inline __must_check unsigned long __copy_to_user(void __user *to, const void *from, unsigned long n) { might_fault(); if (should_fail_usercopy()) return n; instrument_copy_to_user(to, from, n); check_object_size(from, n, true ); return raw_copy_to_user(to, from, n); } #ifdef INLINE_COPY_FROM_USER static inline __must_check unsigned long _copy_from_user(void *to, const void __user *from, unsigned long n) { unsigned long res = n; might_fault(); if (!should_fail_usercopy() && likely(access_ok(from, n))) { instrument_copy_from_user(to, from, n); res = raw_copy_from_user(to, from, n); } if (unlikely(res)) memset (to + (n - res), 0 , res); return res; } #else extern __must_check unsigned long _copy_from_user(void *, const void __user *, unsigned long ); #endif #ifdef INLINE_COPY_TO_USER static inline __must_check unsigned long _copy_to_user(void __user *to, const void *from, unsigned long n) { might_fault(); if (should_fail_usercopy()) return n; if (access_ok(to, n)) { instrument_copy_to_user(to, from, n); n = raw_copy_to_user(to, from, n); } return n; } #else extern __must_check unsigned long _copy_to_user(void __user *, const void *, unsigned long ); #endif static __always_inline unsigned long __must_checkcopy_from_user (void *to, const void __user *from, unsigned long n) { if (likely(check_copy_size(to, n, false ))) n = _copy_from_user(to, from, n); return n; } static __always_inline unsigned long __must_checkcopy_to_user (void __user *to, const void *from, unsigned long n) { if (likely(check_copy_size(from, n, true ))) n = _copy_to_user(to, from, n); return n; } #ifdef CONFIG_COMPAT static __always_inline unsigned long __must_checkcopy_in_user (void __user *to, const void __user *from, unsigned long n) { might_fault(); if (access_ok(to, n) && access_ok(from, n)) n = raw_copy_in_user(to, from, n); return n; } #endif #ifndef copy_mc_to_kernel static inline unsigned long __must_checkcopy_mc_to_kernel (void *dst, const void *src, size_t cnt) { memcpy (dst, src, cnt); return 0 ; } #endif
整体调用链如下
1 2 3 4 5 6 7 8 9 copy_from_user (to, from, n) └─ check_copy_size (to, n, false) └─ _copy_from_user (to, from, n) ├─ might_fault () ├─ should_fail_usercopy () ├─ access_ok (from, n) ├─ instrument_copy_from_user () ├─ raw_copy_from_user (...) └─ if (res) memset (..., 0 , res)
Zero-padding : 如果只拷贝了部分数据(比如因为页错误),剩余未拷贝的部分会被清零 。这是为了防止内核信息泄露(例如栈上未初始化内存被用户读到)。
access_ok():确保 from 指向用户空间且长度合法(防止访问内核地址)
check_object_size():防止缓冲区溢出(配合 FORTIFY_SOURCE)
instrument_*:KASAN 检测越界访问
即使拷贝失败,也不会 panic,而是返回未拷贝字节数,驱动可据此处理。
copy_to_user()函数 函数原型:
1 2 3 4 5 6 7 static __always_inline unsigned long __must_checkcopy_to_user (void __user *to, const void *from, unsigned long n) { if (likely(check_copy_size(from, n, true ))) n = _copy_to_user(to, from, n); return n; }
函数作用:把内核空间的数据复制到用户空间。
参数含义:
*to 是用户空间的指针
from 是内核空间的指针
n 是从内核空间向用户空间拷贝的字节数
返回值:等于 0 表示成功,其他表示失败
copy_from_user()函数 1 2 3 4 5 6 7 static __always_inline unsigned long __must_checkcopy_from_user (void *to, const void __user *from, unsigned long n) { if (likely(check_copy_size(to, n, false ))) n = _copy_from_user(to, from, n); return n; }
函数作用:把用户空间的数据复制到内核空间。
参数含义
*to 是内核空间的指针
from 是用户空间的指针
n 是从用户空间向内核空间拷贝的字节数
返回值:等于 0 表示成功,其他表示失败
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 #include "linux/printk.h" #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); struct class *class ;struct device *device ;static dev_t dev_id;static struct cdev cdev_test ;int cdev_test_open (struct inode *inode, struct file *file) { pr_info("cdev_test_open was called" ); return 0 ; } ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { char kbuf[] = "This is my_driver read\n" ; if (copy_to_user(buf, kbuf, strlen (kbuf)) != 0 ) { pr_err("copy kbuf to buf from kernel error\n" ); return -1 ; } pr_info("copy kbuf to buf from kernel ok\n" ); pr_info("cdev_test_read was called" ); return 0 ; } ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { char kbuf[32 ] = { 0 }; if (copy_from_user(kbuf, buf, size) != 0 ) { pr_err("copy buf to kbuf from kernel error\n" ); return -1 ; } printk("read from user: %s" , kbuf); pr_info("cdev_test_write was called" ); return 0 ; } int cdev_test_release (struct inode *inode, struct file *file) { pr_info("cdev_test_release was called" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }; static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); pr_info("major from module_param: %d" , MAJOR(dev_id)); pr_info("minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err("register_chrdev_region error\n" ); return ret; } else { pr_info("register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err("alloc_chrdev_region error\n" ); return ret; } else { pr_info("alloc_chrdev_region ok\n" ); pr_info("major allocated: %d" , MAJOR(dev_id)); pr_info("minor allocated: %d" , MINOR(dev_id)); } } cdev_init(&cdev_test, &cdev_test_ops); cdev_test.owner = THIS_MODULE; ret = cdev_add(&cdev_test, dev_id, 1 ); if (ret < 0 ) { pr_err("cdev_add error\n" ); unregister_chrdev_region(dev_id, 1 ); return ret; } else { pr_info("cdev_add ok\n" ); } class = class_create(THIS_MODULE, "test" ); device = device_create(class, NULL , dev_id, NULL , "my_driver" ); pr_info("my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { device_destroy(class, dev_id); class_destroy(class); cdev_del(&cdev_test); unregister_chrdev_region(dev_id, 1 ); pr_info("unregister_chrdev_region ok\n" ); pr_info("my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
测试
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main (int argc, char **argv) { int ret, fd; fd = open("/dev/my_driver" , O_RDWR); if (fd < 0 ) { printf ("open /dev/my_driver error\n" ); return -1 ; } char rbuf[32 ] = { 0 }; ret = read(fd, rbuf, sizeof (rbuf)); if (ret < 0 ) { printf ("read error\n" ); goto err; } printf ("read from driver:%s\n" , rbuf); char wbuf[32 ] = "hello world\n" ; ret = write(fd, wbuf, sizeof (wbuf)); if (ret < 0 ) { printf ("write error\n" ); goto err; } success: close(fd); return 0 ; err: close(fd); return -1 ; }
makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 TARGET_EXEC := test SRC_DIRS := ./ BUILD_DIR := ./build CC = aarch64-linux-gnu-gcc CFLAGS = -g -Wall SRCS := $(shell find $(SRC_DIRS) -name '*.c' -or -name '*.s' -or -name '*.S') OBJS := $(patsubst %.c,$(BUILD_DIR) /%.o,$(SRCS) ) OBJS := $(patsubst %.s,$(BUILD_DIR) /%.o,$(OBJS) ) OBJS := $(patsubst %.S,$(BUILD_DIR) /%.o,$(OBJS) ) INC_DIRS := $(shell find $(SRC_DIRS) -type d) INC_FLAGS := $(addprefix -I,$(INC_DIRS) ) CPPFLAGS := $(INC_FLAGS) -MMD -MP $(BUILD_DIR) /$(TARGET_EXEC) : $(OBJS) @mkdir -p $(dir $@ ) $(CC) $(OBJS) -o $@ $(LDFLAGS) $(BUILD_DIR) /%.o: %.c @mkdir -p $(dir $@ ) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(BUILD_DIR) /%.o: %.s @mkdir -p $(dir $@ ) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(BUILD_DIR) /%.o: %.S @mkdir -p $(dir $@ ) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ .PHONY : clean deployclean: rm -rf $(BUILD_DIR) deploy: cp $(BUILD_DIR) /$(TARGET_EXEC) ~/tftp -include $(OBJS:.o=.d)
测试输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ~ [ 7.250701] my_driver: loading out-of-tree module taints kernel. [ 7.257204] alloc_chrdev_region ok [ 7.257387] major allocated: 511 [ 7.257406] minor allocated: 0 [ 7.257563] cdev_add ok [ 7.258587] my_driver: Module loaded ~ ~ [ 18.006803] cdev_test_open was called [ 18.007016] copy kbuf to buf from kernel ok read from driver:This is my_driver read [ 18.007275] cdev_test_read was called [ 18.010813] read from user: hello world [ 18.011205] cdev_test_write was called
文件私有数据
通常在驱动开发中会为设备自定义设备结构体 并将设备结构体设置为文件私有数据 。设备结构体用来存放硬件相关信息,如设备号、类、设备名称等。
Linux 中并没有明确规定要必须要使用文件私有数据 ,但是在 linux 驱动源码中随处可见文件私有数据,因此可以认为使用文件私有数据是 Linux 驱动遵循的“潜规则”,实际上文件私有数据也体现了 Linux 面向对象的思想。
文件私有数据就是将私有数据 private_data 指向设备结构体。
1 2 3 4 5 6 struct device_test dev1 ;static int cdev_test_open (struct inode *inode, struct file *file) { file->private_data = &dev1; return 0 ; }
然后在 read, write 等函数中通过 private_data 访问设备结构体
1 2 3 4 static ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off_t ) { struct device_test *test_dev = (struct device_test *)file->private_data; return 0 ; }
用全局变量也可以实现这样的功能,但是随着驱动代码复杂度增多,使用 private_data 更利于管理
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/string.h> static int major = 0 ;module_param(major, int , S_IRUGO); MODULE_DESCRIPTION("device num: major" ); static int minor = 0 ;module_param(minor, int , S_IRUGO); MODULE_DESCRIPTION("device num: minor" ); static char buf[] = "Hello World from kernel\n" ;struct test_data { dev_t dev_num; struct cdev cdev ; struct class *class ; struct device *dev ; char kbuf[32 ]; }; static struct test_data *dat ;static int file_private_test_open (struct inode *inode, struct file *file) { pr_info("file_private_test_open is called\n" ); file->private_data = dat; pr_info("file->private_data is set\n" ); return 0 ; } static ssize_t file_private_test_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { struct test_data *data ; size_t len; if (*offset != 0 ) { return 0 ; } data = file->private_data; len = min(size, strlen (dat->kbuf)); pr_info("file_private_test_read is called\n" ); if (copy_to_user(buf, data->kbuf, len) != 0 ) { pr_err("copy_to_user error\n" ); return -EFAULT; } *offset += len; return sizeof (data->kbuf); } static int file_private_test_release (struct inode *inode, struct file *file) { pr_info("file_private_test_release is called\n" ); return 0 ; } static struct file_operations fops = { .owner = THIS_MODULE, .open = file_private_test_open, .read = file_private_test_read, .release = file_private_test_release, }; static int __init file_private_test_init (void ) { int err; pr_info("file_private_test init\n" ); dat = (struct test_data *)kmalloc(sizeof (struct test_data), GFP_KERNEL); if (dat == NULL ) { pr_err("no memory\n" ); goto kmalloc_fail; } strscpy(dat->kbuf, buf, sizeof (dat->kbuf)); if (major) { dat->dev_num = MKDEV(major, minor); err = register_chrdev_region(dat->dev_num, 1 , "file_private_test chrdev region" ); if (err < 0 ) { pr_err("register_chrdev_region error\n" ); goto chrdev_region_fail; } } else { err = alloc_chrdev_region(&dat->dev_num, 0 , 1 , "file_private_test chrdev region" ); if (err < 0 ) { pr_err("register_chrdev_region error\n" ); goto chrdev_region_fail; } } cdev_init(&dat->cdev, &fops); err = cdev_add(&dat->cdev, dat->dev_num, 1 ); if (err < 0 ) { pr_err("cdev_add error\n" ); goto cdev_add_fail; } dat->class = class_create(THIS_MODULE, "char_test" ); if (IS_ERR(dat->class)) { err = PTR_ERR(dat->class); pr_err("create class error" ); goto class_create_fail; } dat->dev = device_create(dat->class, NULL , dat->dev_num, NULL , "char_test_dev" ); if (IS_ERR(dat->dev)) { err = PTR_ERR(dat->dev); pr_err("create device error\n" ); goto device_create_fail; } return 0 ; device_create_fail: class_destroy(dat->class); class_create_fail: cdev_del(&dat->cdev); cdev_add_fail: unregister_chrdev_region(dat->dev_num, 1 ); chrdev_region_fail: kfree(dat); kmalloc_fail: return err; } static void __exit file_private_test_exit (void ) { device_destroy(dat->class, dat->dev_num); class_destroy(dat->class); cdev_del(&dat->cdev); unregister_chrdev_region(dat->dev_num, 1 ); kfree(dat); pr_info("file_private_test exit\n" ); } module_init(file_private_test_init); module_exit(file_private_test_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("even629<asqwgo@163.com>" ); MODULE_DESCRIPTION("This is a test sample for file private data" );
测试:
1 2 3 4 5 6 7 8 9 $ insmod file_private_data_test.ko [ 8.022277] file_private_data_test: loading out-of-tree module taints kernel. [ 8.032581] file_private_test init $ cat /dev/char_test_dev [ 14.998329] file_private_test_open is called [ 14.998580] file->private_data is set [ 14.999659] file_private_test_read is called Hello World from kernel [ 15.000469] file_private_test_release is called
使用文件私有数据的场景 在 Linux 中,使用主设备号来表示对应的某一类驱动。用次设备号表示这类驱动下的各个设备。
假如现在我们的驱动要支持主设备相同,但是次设备号不同的设备 。我们的驱动就可以利用文件私有数据来写。
container_of 宏 include/linux/kernel.h
1 2 3 4 5 6 #define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \ !__same_type(*(ptr), void), \ "pointer type mismatch in container_of()" ); \ ((type *)(__mptr - offsetof(type, member))); })
ptr 指向结构体中某个成员的指针。
type 目标结构体类型。
member 成员在结构体中的名字。
BUILD_BUG_ON_MSG(...) 编译期类型检查,保证 ptr 和结构体成员类型一致,避免类型错误。
__mptr - offsetof(type, member) 核心公式:用成员指针减去该成员在结构体中的偏移,得到结构体起始地址。
offsetof(type, member) 标准宏,计算成员在结构体内的字节偏移。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 #include <linux/module.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/uaccess.h> #define KBUF_SIZE 32 struct container_data { dev_t dev_num; struct cdev cdev ; struct device *dev ; char kbuf[KBUF_SIZE]; }; static struct class *class ;static struct container_data *dat1 ;static struct container_data *dat2 ;int container_of_test_open (struct inode *inode, struct file *file) { struct container_data *dat ; dat = container_of(inode->i_cdev, struct container_data, cdev); file->private_data = dat; pr_info("container_of_test_open is called, dev major: %d, dev minor: %d\n" , MAJOR(dat->dev_num), MINOR(dat->dev_num)); return 0 ; } ssize_t container_of_test_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { struct container_data *dat = file->private_data; size_t kbuf_len = strlen (dat->kbuf); size_t len = min(size, (size_t )(kbuf_len - *offset)); if (*offset >= kbuf_len) return 0 ; if (copy_to_user(buf, dat->kbuf + *offset, len)) return -EFAULT; *offset += len; return len; } ssize_t container_of_test_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { struct container_data *dat = file->private_data; size_t bytes = min(size, (size_t )(KBUF_SIZE - 1 - *offset)); if (*offset >= KBUF_SIZE - 1 ) return -ENOSPC; if (copy_from_user(dat->kbuf + *offset, buf, bytes) != 0 ) return -EFAULT; *offset += bytes; dat->kbuf[*offset] = '\0' ; return bytes; } int container_of_test_release (struct inode *inode, struct file *file) { pr_info("container_of_test_release is called\n" ); return 0 ; } static struct file_operations fops = { .owner = THIS_MODULE, .open = container_of_test_open, .read = container_of_test_read, .write = container_of_test_write, .release = container_of_test_release, }; static int __init container_of_test_init (void ) { int err; dat1 = (struct container_data *)kmalloc(sizeof (struct container_data), GFP_KERNEL); if (dat1 == NULL ) { pr_err("no memory dat1" ); err = -ENOMEM; goto kmalloc_dat1_fail; } memset (dat1, 0 , sizeof (struct container_data)); dat2 = (struct container_data *)kmalloc(sizeof (struct container_data), GFP_KERNEL); if (dat2 == NULL ) { pr_err("no memory dat2" ); err = -ENOMEM; goto kmalloc_dat2_fail; } memset (dat2, 0 , sizeof (struct container_data)); err = alloc_chrdev_region(&dat1->dev_num, 0 , 2 , "container_of_test chrdev region" ); if (err < 0 ) { pr_err("alloc_chrdev_region error\n" ); goto alloc_chrdev_region_fail; } dat2->dev_num = MKDEV(MAJOR(dat1->dev_num), MINOR(dat1->dev_num) + 1 ); cdev_init(&dat1->cdev, &fops); err = cdev_add(&dat1->cdev, dat1->dev_num, 1 ); if (err < 0 ) { pr_err("cdev_add dat1 error\n" ); goto cdev_add_dat1_fail; } cdev_init(&dat2->cdev, &fops); err = cdev_add(&dat2->cdev, dat2->dev_num, 1 ); if (err < 0 ) { pr_err("cdev_add dat2 error\n" ); goto cdev_add_dat2_fail; } class = class_create(THIS_MODULE, "chrdev" ); if (IS_ERR(class)) { err = PTR_ERR(class); goto class_create_fail; } dat1->dev = device_create(class, NULL , dat1->dev_num, NULL , "container_of_test_dev%d" , 0 ); if (IS_ERR(dat1->dev)) { err = PTR_ERR(dat1->dev); goto device_create_dat1_fail; } dat2->dev = device_create(class, NULL , dat2->dev_num, NULL , "container_of_test_dev%d" , 1 ); if (IS_ERR(dat2->dev)) { err = PTR_ERR(dat2->dev); goto device_create_dat2_fail; } return 0 ; device_create_dat2_fail: device_destroy(class, dat1->dev_num); device_create_dat1_fail: class_destroy(class); class_create_fail: cdev_del(&dat2->cdev); cdev_add_dat2_fail: cdev_del(&dat1->cdev); cdev_add_dat1_fail: unregister_chrdev_region(dat1->dev_num, 2 ); alloc_chrdev_region_fail: kfree(dat2); kmalloc_dat2_fail: kfree(dat1); kmalloc_dat1_fail: return err; } static void __exit container_of_test_exit (void ) { device_destroy(class, dat1->dev_num); device_destroy(class, dat2->dev_num); class_destroy(class); cdev_del(&dat2->cdev); cdev_del(&dat1->cdev); unregister_chrdev_region(dat1->dev_num, 2 ); kfree(dat2); kfree(dat1); pr_info("container_of_test exit\n" ); } module_init(container_of_test_init); module_exit(container_of_test_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("even629<asqwgo@163.com>" ); MODULE_DESCRIPTION("This is a test sample for container_of" );
杂项设备驱动 杂项设备属于特殊的一种字符型设备 ,是对字符设备的一种封装,本质也是字符设备。
在 Linux 中,把无法归类的五花八门的设备定义成杂项设备。相较于字符设备,杂项设备有以下两个优点:
节省主设备号 : 杂项设备的主设备号固定为 10 ,当系统中注册了多个杂项设备驱动时,只需使用子设备号进行区分即可。而字符设备不管是动态分配还是静态分配设备号,都会消耗一个主设备号,进而造成了主设备号浪费。
使用简单 :杂项设备驱动本身包含创建设备节点操作,不需要额外使用 class_create()函数和 device_create()函数 实现自动创建设备节点操作。只需要填充驱动中的 file_operations 结构体中的成员即可。
定义 include/linux/miscdevice.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct device ;struct attribute_group ;struct miscdevice { int minor; const char *name; const struct file_operations *fops ; struct list_head list ; struct device *parent ; struct device *this_device ; const struct attribute_group **groups ; const char *nodename; umode_t mode; }; extern int misc_register (struct miscdevice *misc) ;extern void misc_deregister (struct miscdevice *misc) ;
定义一个杂项设备,一般只需要填充 miscdevice 结构体中 minor、name、fops 这三个成员变量。
minor 杂项指次设备号,可以从内核源码include/linux/miscdevice.h 文件中预定义的次设备号挑选,也可以自行定义子设备号(没有被其他设备使用即可),通常情况下将该参数设置为 MISC_DYNAMIC_MINOR ,表示自动分配子设备号。
name 表示杂项设备的名字。也就是杂项设备驱动注册成功之后,会在 dev 目录下生成名为 name 的设备节点。
fops指向了 file_operations 的结构体,表示文件操作集。
misc_register 1 extern int misc_register (struct miscdevice *misc) ;
misc_deregister 1 extern void misc_deregister (struct miscdevice *misc) ;
函数作用 :从 mist_list 中删除 miscdevice,进而起到杂项设备卸载的作用。
参数含义 :
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> static struct file_operations fops = { .owner = THIS_MODULE, }; static struct miscdevice miscdev = { .minor=MISC_DYNAMIC_MINOR, .name="miscdev" , .fops = &fops, }; static int __init misc_device_test_init (void ) { misc_register(&miscdev); return 0 ; } static void __exit misc_device_test_exit (void ) { misc_deregister(&miscdev); } module_init(misc_device_test_init); module_exit(misc_device_test_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("even629<asqwgo@163.com>" ); MODULE_DESCRIPTION("This is a misc device sample" );
Linux 驱动错误处理 goto 逆序释放 逆序释放 :后申请的先释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static int __init my_driver_init (void ) { int ret; ret = alloc_chrdev_region(&dev1.dev_num, 0 , 2 , "my_driver" ); if (ret < 0 ) goto fail_alloc; cdev_init(&dev1.cdev_test, &cdev_test_fops); dev1.cdev_test.owner = THIS_MODULE; ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1 ); if (ret < 0 ) goto fail_cdev_add; dev1.class = class_create(THIS_MODULE, "test1" ); if (IS_ERR(dev1.class)) { ret = PTR_ERR(dev1.class); goto fail_class_create; } dev1.device = device_create(dev1.class, NULL , dev1.dev_num, NULL , "test1" ); if (IS_ERR(dev1.device)) { ret = PTR_ERR(dev1.device); goto fail_device_create; } pr_info("my_driver initialized successfully\n" ); return 0 ; fail_device_create: class_destroy(dev1.class); fail_class_create: cdev_del(&dev1.cdev_test); fail_cdev_add: unregister_chrdev_region(dev1.dev_num, 1 ); fail_alloc: return ret; }
标签顺序是“逆序释放”,即 后申请的先释放 。
PTR_ERR() + IS_ERR() 用于判断 class_create() 和 device_create() 的错误。
PTR_ERR() 将错误指针转换为错误码,并进行错误码的返回
goto 能避免重复写多次 if 检查和清理代码,使代码简洁。
IS_ERR() 如果一个函数返回值是指针类型,在调用出错的情况下会返回 NULL 指针,但是返回 NULL 不能知道问题的确切性,一些函数需要返回一个实际的错误码以便于工程师能够基于返回值作出正确的判断。 对于任何一个指针来说,必然存在三种情况:
合法指针
NULL(也就是空指针)
错误指针(也就是无效指针)
在 Linux 内核中,所谓的错误指针指向了内核空间的最后一页 ,例如,对于一个 64 位系统来说,最后一页的地址是 0xfffffffffffff000~0xffffffffffffffff, 最后一页地址是被保留的,并这段地址与内核定义的错误码相关联,因此可以使用错误码用来指明对应出错的情况,如果一个指针指向了该页的地址范围就被定义为错误指针。 在 Linux 内核源码中提供了错误指针相关的 API 函数,主要有 IS_ERR()、PTR_ERR()、ERR_PTR()函数等,其函数的源码在 include/linux/err.h 文件中。
include/linux/err.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #ifndef _LINUX_ERR_H #define _LINUX_ERR_H #include <linux/compiler.h> #include <linux/types.h> #include <asm/errno.h> #define MAX_ERRNO 4095 #ifndef __ASSEMBLY__ #define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO) static inline void * __must_check ERR_PTR (long error) { return (void *) error; } static inline long __must_check PTR_ERR (__force const void *ptr) { return (long ) ptr; } static inline bool __must_check IS_ERR (__force const void *ptr) { return IS_ERR_VALUE((unsigned long )ptr); } static inline bool __must_check IS_ERR_OR_NULL (__force const void *ptr) { return unlikely(!ptr) || IS_ERR_VALUE((unsigned long )ptr); } static inline void * __must_check ERR_CAST (__force const void *ptr) { return (void *) ptr; } static inline int __must_check PTR_ERR_OR_ZERO (__force const void *ptr) { if (IS_ERR(ptr)) return PTR_ERR(ptr); else return 0 ; } #endif #endif
PTR_ERR() 把错误指针转成错误码
include/linux/err.h
1 2 3 4 static inline long __must_check PTR_ERR (__force const void *ptr) { return (long ) ptr; }
错误码 include/uapi/asm-generic/errno-base.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #ifndef _ASM_GENERIC_ERRNO_BASE_H #define _ASM_GENERIC_ERRNO_BASE_H #define EPERM 1 #define ENOENT 2 #define ESRCH 3 #define EINTR 4 #define EIO 5 #define ENXIO 6 #define E2BIG 7 #define ENOEXEC 8 #define EBADF 9 #define ECHILD 10 #define EAGAIN 11 #define ENOMEM 12 #define EACCES 13 #define EFAULT 14 #define ENOTBLK 15 #define EBUSY 16 #define EEXIST 17 #define EXDEV 18 #define ENODEV 19 #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 #define ENFILE 23 #define EMFILE 24 #define ENOTTY 25 #define ETXTBSY 26 #define EFBIG 27 #define ENOSPC 28 #define ESPIPE 29 #define EROFS 30 #define EMLINK 31 #define EPIPE 32 #define EDOM 33 #define ERANGE 34 #endif
示例:
1 2 3 4 if (IS_ERR(dev1.class)) { ret = PTR_ERR(dev1.class); goto fail_class_create; }
RK3568 点亮 LED 灯 原理图 topeet RK3568 Working LED原理图如下
可以看到LED灯接在了GPIO0_B7 这个GPIO口上。当 GPIO0_B7 为高电平时,三极管 Q16导通,LED9 点亮。当 GPIO0_B7 为低电平时,三极管 Q16 截止,LED9 不亮。
查询寄存器地址 GPIO0B 的引脚复用 首先我们得设置GPIO0B的引脚复用,从PMU_GRF查找
搜索GPIO0B7可以看到其对应寄存器为PMU_GRF_GPIO0B_IOMUX_H的14:12位的设置
而PMU_GRF寄存器地址从下面可以看到是0xFDC20000
所以复用寄存器地址=基地址+偏移地址=0xFDC20000+0xC=0xFDC2000C。
使用 io 命令查看此寄存器的地址:
GPIO 寄存器 由TRM中的Address Mapping可知GPIO0地址位0xFDD60000
RK3568 TRM中GPIO章节介绍:
可知RK3568寄存器分为:
方向寄存器(DDR)
GPIO_SWPORT_DR_L
GPIO_SWPORT_DR_H
数据寄存器(DR)
GPIO_SWPORT_DDR_L
GPIO_SWPORT_DDR_H
外部输入寄存器(EXT_PORT)
即
GPIO_SWPORT_DDR_L/H 决定 引脚是输入还是输出
GPIO_SWPORT_DR_L/H 决定 输出引脚时输出的电平
GPIO_EXT_PORT 反映 引脚当前的实际电平(只读)
数据寄存器和方向寄存器分为L和H
GPIOA和GPIOB地址落在L上
GPIOC和GPIOD地址落在H上
又:
可知GPIO0~GPIO4这五组的寄存器是不同的。
我们需要先设置GPIO方向寄存器为输出,然后设置输出电平为高电平。
GPIO 有四组 GPIO,分别是 GPIOA,GPIOB,GPIOC,GPIOD。每组又以 A0 ~ A7, B0 ~ B7, C0 ~ C7, D0 ~ D7 作为编号区分。
GPIO0B7 在L上,所以方向寄存器的偏移地址为 0x0008,数据寄存器偏移为0x0000。
GPIO0 的基地址为 0xFDD60000。因此
方向寄存器的地址=基地址+偏移地址=0xFDD60000+0x0008=0xFDD60008
数据寄存器的地址=基地址+偏移地址=0xFDD60000+0x0000=0xFDD60000
外部输入寄存器地址=基地址+偏移地址=0xFDD60000+0x0070=0xFDD60070
方向寄存器
可知31:16为可写位,因此GPIOB7应该是16+8+8-1=31,即bit 31设置write access 。
而0+8+8-1=15,即bit 15对应设置GPIO0B7的方向为input或output
数据寄存器
同理16+8+8-1=31,即bit 31设置write access 。
而0+8+8-1=15, 即bit 15对应设置GPIO0B7输出高电平或低电平 。
总结
复用关系寄存器 的基地址为 0xFDC20000
GPIO0B_IOMUX_H偏移地址为 000C ,所以要操作的地址为基地址+偏移地址=0xFDC2000C
GPIO0B_IOMUX_H的 bit 28 ~ bit 30 为bit write access, 0 为disable, 1为enable
GPIO0B_IOMUX_H的 bit 12 ~ bit 14 设置为0为GPIO0_B7,1为PWM0_M0,2为CPU_AVS
GPIO0 的基地址为 0xFDD60000
方向寄存器 地址=基地址+偏移地址=0xFDD60008
bit 31 对应GPIO0_B7的方向write access,0为disable,1为enable
bit 15 对应GPIO0_B7的方向,0为input,1为output
数据寄存器 地址=基地址+偏移地址=0xFDD60000
bit 31 对应GPIO0_B7的数据write access,0为disable,1为enable
bit 15 对应GPIO0_B7的数据,0为输出low,1为输出high
使用io命令测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 su root sudo io -4 -w 0xFDC2000C 0x70001000sudo io -4 -r 0xFDC2000Csudo io -4 -w 0xFDD60008 0x80008000sudo io -4 -r 0xFDD60008sudo io -4 -r 0xFDD60000sudo io -4 -w 0xFDD60000 0x8000C000sudo io -4 -w 0xFDD60000 0x80004000
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 #include <linux/module.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/io.h> #include <linux/uaccess.h> #define PMU_GRF_BASE 0xFDC20000 #define GPIO0B_IOMUX_OFFSET 0xC #define GPIO0B_IOMUX PMU_GRF_BASE + GPIO0B_IOMUX_OFFSET #define GPIO0_BASE 0xFDD60000 #define GPIO_SWPORT_DDR_L_OFFSET 0x8 #define GPIO_SWPORT_DR_L_OFFSET 0x0 #define GPIO_EXT_PORT_OFFSET 0x70 #define GPIO0_SWPORT_DDR_L GPIO0_BASE + GPIO_SWPORT_DDR_L_OFFSET #define GPIO0_SWPORT_DR_L GPIO0_BASE + GPIO_SWPORT_DR_L_OFFSET #define GPIO0_EXT_PORT GPIO0_BASE + GPIO_EXT_PORT_OFFSET struct led_drv_data { dev_t dev_num; struct cdev cdev ; struct class *class ; struct device *dev ; void __iomem *gpio0b_iomux; void __iomem *gpio0_swport_ddr_l; void __iomem *gpio0_swport_dr_l; void __iomem *gpio0_ext_port; }; static struct led_drv_data *led_drv_data ;int light_up_led_open (struct inode *inode, struct file *file) { file->private_data = led_drv_data; return 0 ; } ssize_t light_up_led_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { struct led_drv_data *drv_data = file->private_data; u32 val; if (size != sizeof (u32)) { pr_info("read need 4 bytes\n" ); return -ENOMEM; } val = readl(drv_data->gpio0_ext_port); val &= 1 << 15 ; val = val >> 15 ; if (copy_to_user(buf, &val, sizeof (u32)) != 0 ) { return -EFAULT; } return sizeof (u32); } ssize_t light_up_led_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { struct led_drv_data *drv_data = file->private_data; u32 val; if (size != sizeof (u32)) { pr_err("write need 4 bytes\n" ); return -ENOMEM; } if (copy_from_user(&val, buf, sizeof (u32)) != 0 ) { return -EFAULT; } if (val > 0 ) { val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000 ; writel(val, drv_data->gpio0_swport_ddr_l); val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80008000 ; writel(val, drv_data->gpio0_swport_dr_l); } else if (val == 0 ) { val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000 ; writel(val, drv_data->gpio0_swport_ddr_l); val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80000000 ; val &= 0xffff7fff ; writel(val, drv_data->gpio0_swport_dr_l); } return sizeof (u32); } int light_up_led_release (struct inode *inode, struct file *file) { return 0 ; } struct file_operations fops = { .owner = THIS_MODULE, .open = light_up_led_open, .read = light_up_led_read, .write = light_up_led_write, .release = light_up_led_release, }; static int __init light_up_led_init (void ) { int err; u32 val; led_drv_data = kzalloc(sizeof (struct led_drv_data), GFP_KERNEL); if (led_drv_data == NULL ) { err = -ENOMEM; goto kzalloc_fail; } err = alloc_chrdev_region(&led_drv_data->dev_num, 0 , 1 , "led chrdev region" ); if (err < 0 ) goto alloc_chrdev_region_fail; cdev_init(&led_drv_data->cdev, &fops); led_drv_data->cdev.owner = THIS_MODULE; err = cdev_add(&led_drv_data->cdev, led_drv_data->dev_num, 1 ); if (err < 0 ) goto cdev_add_fail; led_drv_data->class = class_create(THIS_MODULE, "test_led" ); if (IS_ERR(led_drv_data->class)) { err = PTR_ERR(led_drv_data->class); goto create_class_fail; } led_drv_data->dev = device_create(led_drv_data->class, NULL , led_drv_data->dev_num, NULL , "test_led%d" , 0 ); if (IS_ERR(led_drv_data->dev)) { err = PTR_ERR(led_drv_data->dev); goto device_create_fail; } led_drv_data->gpio0b_iomux = ioremap(GPIO0B_IOMUX, 4 ); led_drv_data->gpio0_swport_ddr_l = ioremap(GPIO0_SWPORT_DDR_L, 4 ); led_drv_data->gpio0_swport_dr_l = ioremap(GPIO0_SWPORT_DR_L, 4 ); led_drv_data->gpio0_ext_port = ioremap(GPIO0_EXT_PORT, 4 ); val = readl(led_drv_data->gpio0b_iomux); val |= 0x70000000 ; val &= 0xFFFF8FFF ; writel(val, led_drv_data->gpio0b_iomux); return 0 ; device_create_fail: class_destroy(led_drv_data->class); create_class_fail: cdev_del(&led_drv_data->cdev); cdev_add_fail: unregister_chrdev_region(led_drv_data->dev_num, 1 ); alloc_chrdev_region_fail: kfree(led_drv_data); kzalloc_fail: return err; } static void __exit light_up_led_exit (void ) { iounmap(led_drv_data->gpio0_ext_port); iounmap(led_drv_data->gpio0b_iomux); iounmap(led_drv_data->gpio0_swport_ddr_l); iounmap(led_drv_data->gpio0_swport_dr_l); device_destroy(led_drv_data->class, led_drv_data->dev_num); class_destroy(led_drv_data->class); cdev_del(&led_drv_data->cdev); unregister_chrdev_region(led_drv_data->dev_num, 1 ); kfree(led_drv_data); } module_init(light_up_led_init); module_exit(light_up_led_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("even629<asqwgo@163.com>" ); MODULE_DESCRIPTION("light up a led on topeet RK3568 board" );
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <errno.h> int main (int argc, char **argv) { int fd, err; uint32_t val = 0 ; fd = open("/dev/test_led0" , O_RDWR); if (fd < 0 ) goto fail; if (argc == 1 ) { err = read(fd, &val, 4 ); if (err < 0 ) goto fail; printf ("read:0x%08x\n" , val); } else if (argc >= 2 ) { val = atoi(argv[1 ]); if (val < 0 ) { perror("unsupported\n" ); exit (EXIT_FAILURE); } err = write(fd, &val, 4 ); if (err < 0 ) goto fail; printf ("write val: %u success\n" , val); } else { perror("argc error\n" ); exit (EXIT_FAILURE); } return 0 ; fail: fprintf (stderr , "Error:%s[errno:%d]" , strerror(errno), errno); exit (EXIT_FAILURE); }