作为字符设备驱动的三要素:
crw-r----- 1 root root 13, 64 Mar 28 20:14 event0
crw-r----- 1 root root 13, 65 Mar 28 20:14 event1
crw-r----- 1 root root 13, 66 Mar 28 20:14 event2
申请设备号
int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
参数1:主设备号 设备号(32bit--dev_t)==主设备号(12bit) + 次设备号(20bit) 主设备号:表示一类设备--camera 次设备号: 表示一类设备中某一个:前置,后置 给定到方式有两种: 1,动态--参数1直接填0 2,静态--指定一个整数,250 参数2: 描述一个设备信息,可以自定义 /proc/devices列举出所有到已经注册的设备 参数3: 文件操作对象--提供open, read,write 返回值: 正确返回0,错误返回负数
注销设备号
void unregister_chrdev(unsigned int major, const char * name)
参数1:主设备号 参数2: 描述一个设备信息,可以自定义
示例代码
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> unsigned int major_id = 502; const char major_name[] = "test"; const struct file_operations major_fops = { }; static int __init my_chrdrv_init(void) { //一般都是申请资源 //申请设备号 int ret = register_chrdev(major_id, major_name, &major_fops); if (ret == 0){ printk("register ok\n"); } else{ printk("register failed"); return -EFAULT; } return 0; } static void __init my_chrdrv_exit(void) { //一般都是释放资源 //释放设备号资源 unregister_chrdev(major_id, major_name); } MODULE_LICENSE("GPL"); module_init(my_chrdrv_init); module_exit(my_chrdrv_exit);
效果展示:
[root@farsight ]# cat /proc/devices Character devices: 1 mem 2 pty 3 ttyp 4 /dev/vc/0 4 tty ... 253 tpm 254 gpiochip [root@farsight ]# insmod chr_drv.ko [ 117.470884] chr_drv: loading out-of-tree module taints kernel. [ 117.476144] register ok [root@farsight ]# cat /proc/devices Character devices: 1 mem 2 pty 3 ttyp 4 /dev/vc/0 4 tty ... 253 tpm 254 gpiochip 502 test [root@farsight ]# rmmod chr_drv.ko [root@farsight ]# cat proc/devices Character devices: 1 mem 2 pty 3 ttyp 4 /dev/vc/0 4 tty 4 ttyS ··· 252 rtc 253 tpm 254 gpiochip
1,手动创建--缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失
mknod /dev/设备名 类型 主设备号 次设备号 比如: mknod /dev/mychr0 c 502 0 [root@farsight ]# mknod /dev/mychr0 c 502 0 [root@farsight ]# ls /dev/mychr0 -l crw-r--r-- 1 0 0 502, 0 Jan 1 00:01 /dev/mychr0
2,自动创建(通过udev/mdev机制)
struct class *class_create(owner, name)//创建一个类 参数1: THIS_MODULE 参数2: 字符串名字,自定义 返回一个class指针 //创建一个设备文件 struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt,...) 参数1: class结构体,class_create调用之后到返回值 参数2:表示父亲,一般直接填NULL 参数3: 设备号类型 dev_t dev_t devt #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) 参数4:私有数据,一般直接填NULL 参数5和6:表示可变参数,字符串,表示设备节点名字
销毁动作:
void device_destroy(devcls, MKDEV(dev_major, 0)); 参数1: class结构体,class_create调用之后到返回值 参数2: 设备号类型 dev_t void class_destroy(devcls); 参数1: class结构体,class_create调用之后到返回值
效果展示
[root@farsight ]# insmod chr_drv.ko [ 33.757337] VMEM_VDD_2.8V: disabling [ 34.262594] chr_drv: loading out-of-tree module taints kernel. [ 34.268058] register ok [root@farsight ]# rmmod chr_drv.ko [root@farsight ]#
struct 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 (*iterate) (struct file *, struct dir_context *); 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); int (*show_fdinfo)(struct seq_file *m, struct file *f); }; //函数指针的集合,其实就是接口,我们写驱动到时候需要去实现 const struct file_operations my_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, };
fd = open("/dev/chr2", O_RDWR); if(fd < 0) { perror("open"); exit(1); } read(fd, &value, 4); write(fd, &value, 4); close(fd);
代码示例
//------------------chr_drv.c------------------ //驱动程序代码 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> unsigned int major_id = 502; const char major_name[] = "test"; struct class* pmyclass = NULL; struct device* pmydevice =NULL; int chr_drv_open (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos) { printk("------------%s----------\n",__FUNCTION__); return 0; } ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos) { printk("------------%s----------\n",__FUNCTION__); return 0; } int chr_drv_close (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } const struct file_operations major_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, }; static int __init my_chrdrv_init(void) { //一般都是申请资源 //申请设备号 int ret = register_chrdev(major_id, major_name, &major_fops); if (ret == 0){ printk("register ok\n"); } else{ printk("register failed"); return -EFAULT; } pmyclass = class_create(THIS_MODULE,"TEST1"); pmydevice = device_create(pmyclass,NULL,MKDEV(502, 0),NULL,"mychr0"); return 0; } static void __exit my_chrdrv_exit(void) { //一般都是释放资源 //释放设备号资源 unregister_chrdev(major_id, major_name); device_destroy(pmyclass,MKDEV(502, 0)); class_destroy(pmyclass); } MODULE_LICENSE("GPL"); module_init(my_chrdrv_init); module_exit(my_chrdrv_exit);
//----------------------drvtest.c----------------------- //APP 代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int fd; int buf = 0; fd = open("/dev/mychr0",O_RDWR); if(fd < 0) { perror("open"); exit(1); } write(fd, &buf, 4); read(fd, &buf, 4); close(fd); return 0; }
# ----------------Makefile---------------------- ROOTFS_DIR = /home/linux/Level11 APP_NAME = drvtest CROSS_COMPILE = arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79 CUR_DIR = $(shell pwd) all : make -C $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- $(CC) $(APP_NAME).c -o $(APP_NAME) clean : make -C $(KERNEL_DIR) M=$(CUR_DIR) clean install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += chr_drv.o endif
效果展示
[root@farsight ]# insmod chr_drv.ko [ 5820.214768] register ok [root@farsight ]# ./drvtest [ 5828.839807] ------------chr_drv_open---------- [ 5828.842812] ------------chr_drv_write---------- [ 5828.847978] ------------chr_drv_read---------- [ 5828.851741] ------------chr_drv_close---------- [root@farsight ]#
int copy_to_user(void __user * to, const void * from, unsigned long n)
//将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
参数1:应用驱动中的一个buffer
参数2:内核空间到一个buffer
参数3:个数
返回值:大于0,表示出错,剩下多少个没有拷贝成功
等于0,表示正确
int copy_from_user(void * to, const void __user * from, unsigned long n) //将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用 参数1:内核驱动中的一个buffer 参数2:应用空间到一个buffer 参数3:个数
代码展示:
//------------------chr_drv.c------------------ //驱动程序代码 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/uaccess.h> unsigned int major_id = 502; const char major_name[] = "test"; struct class* pmyclass = NULL; struct device* pmydevice =NULL; int chr_drv_open (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos) { int buf_read = 666; int ret; ret = copy_to_user(buf,&buf_read,count); if(ret == 0) { printk("------------%s----------\n",__FUNCTION__); } else { printk("KERN:Error to read\n"); return -EFAULT; } return 0; } ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos) { int buf_write = 0; int ret; ret = copy_from_user(&buf_write,buf,count); if(ret == 0) { printk("------------KERN:%d----------\n",buf_write); } else { printk("KERN:Error to read\n"); return -EFAULT; } return 0; } int chr_drv_close (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } const struct file_operations major_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, }; static int __init my_chrdrv_init(void) { //一般都是申请资源 //申请设备号 int ret = register_chrdev(major_id, major_name, &major_fops); if (ret == 0){ printk("register ok\n"); } else{ printk("register failed"); return -EFAULT; } pmyclass = class_create(THIS_MODULE,"TEST1"); pmydevice = device_create(pmyclass,NULL,MKDEV(502, 0),NULL,"mychr0"); return 0; } static void __exit my_chrdrv_exit(void) { //一般都是释放资源 //释放设备号资源 unregister_chrdev(major_id, major_name); device_destroy(pmyclass,MKDEV(502, 0)); class_destroy(pmyclass); } MODULE_LICENSE("GPL"); module_init(my_chrdrv_init); module_exit(my_chrdrv_exit);
//--------------drvtest.c------------------- //APP 代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int fd; int buf = 233; fd = open("/dev/mychr0",O_RDWR); if(fd < 0) { perror("open"); exit(1); } write(fd, &buf, 4); read(fd, &buf, 4); printf("----------APP:%d---------\n",buf); close(fd); return 0; }
# ----------------Makefile---------------------- ROOTFS_DIR = /home/linux/Level11 APP_NAME = drvtest CROSS_COMPILE = arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79 CUR_DIR = $(shell pwd) all : make -C $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- $(CC) $(APP_NAME).c -o $(APP_NAME) clean : make -C $(KERNEL_DIR) M=$(CUR_DIR) clean install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += chr_drv.o endif
效果展示:
[root@farsight ]# ./drvtest
[10946.384325] ------------chr_drv_open----------
[10946.387390] ------------KERN:233----------
[10946.391385] ------------chr_drv_read----------
[10946.396507] ------------chr_drv_close----------
----------APP:666---------
[root@farsight ]#
void *ioremap(cookie, size) 参数1: 物理地址 参数2: 长度 返回值: 虚拟地址 去映射--解除映射 void iounmap(void __iomem *addr) 参数1: 映射之后到虚拟地址
led--- GPX2_7 --- GPX2CON ==0x11000C40 GPX2DAT ==0x11000C44 将0x11000C40映射成虚拟地址 对虚拟地址中到[32:28] = 0x1
代码展示:
//------------------chr_drv.c------------------ //驱动程序代码 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/uaccess.h> #include <asm/io.h> #define GPX2CON_ADDR 0x11000c40 #define GPX2CON_SIZE 8 unsigned int major_id = 502; const char major_name[] = "test"; struct class* pmyclass = NULL; struct device* pmydevice =NULL; volatile unsigned int* GPX2CON; volatile unsigned int* GPX2DAT; int chr_drv_open (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos) { int buf_read = 666; int ret; ret = copy_to_user(buf,&buf_read,count); if(ret == 0) { printk("------------%s----------\n",__FUNCTION__); } else { printk("KERN:Error to read\n"); return -EFAULT; } return 0; } ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos) { int buf_write = 0; int ret; ret = copy_from_user(&buf_write,buf,count); if(ret == 0) { printk("__KERN__:%d\n",buf_write); if(buf_write == 0){ *GPX2DAT &= ~(0x1<<7); } else{ *GPX2DAT |= (0x1<<7); } } else { printk("KERN:Error to read\n"); return -EFAULT; } return 0; } int chr_drv_close (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } const struct file_operations major_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, }; static int __init my_chrdrv_init(void) { //一般都是申请资源 //申请设备号 int ret = register_chrdev(major_id, major_name, &major_fops); if (ret == 0){ printk("register ok\n"); } else{ printk("register failed"); return -EFAULT; } pmyclass = class_create(THIS_MODULE,"TEST1"); pmydevice = device_create(pmyclass,NULL,MKDEV(502, 0),NULL,"mychr0"); GPX2CON = (unsigned int*)ioremap(GPX2CON_ADDR,GPX2CON_SIZE) ; GPX2DAT = GPX2CON + 1; *GPX2CON &= ~(0xf << 28); *GPX2CON |= (0x1 << 28); return 0; } static void __exit my_chrdrv_exit(void) { //一般都是释放资源 //释放设备号资源 unregister_chrdev(major_id, major_name); device_destroy(pmyclass,MKDEV(502, 0)); class_destroy(pmyclass); iounmap(GPX2CON); } MODULE_LICENSE("GPL"); module_init(my_chrdrv_init); module_exit(my_chrdrv_exit);
//--------------drvtest.c------------------- //APP 代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int fd; int buf = 233; fd = open("/dev/mychr0",O_RDWR); if(fd < 0) { perror("open"); exit(1); } while(1){ buf = 0; write(fd, &buf, 4); sleep(1); buf = 1; write(fd, &buf, 4); sleep(1); } read(fd, &buf, 4); close(fd); return 0; }
Makefile代码
ROOTFS_DIR = /home/linux/Level11 APP_NAME = drvtest CROSS_COMPILE = arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79 CUR_DIR = $(shell pwd) all : make -C $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- $(CC) $(APP_NAME).c -o $(APP_NAME) clean : make -C $(KERNEL_DIR) M=$(CUR_DIR) clean install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += chr_drv.o endif
效果展示:
[root@farsight ]# insmod chr_drv.ko
[ 287.317368] register ok
[root@farsight ]# ./drvtest
[ 291.632356] ------------chr_drv_open----------
[ 291.635357] __KERN__:0
[ 292.638023] __KERN__:1
[ 293.639042] __KERN__:0
[ 294.640062] __KERN__:1
[ 295.641081] __KERN__:0
[ 296.642106] __KERN__:1
[ 297.643127] __KERN__:0
[ 298.644145] __KERN__:1
[ 299.645167] __KERN__:0
[ 300.646187] __KERN__:1
[ 301.647281] __KERN__:0
[ 302.648296] __KERN__:1
[ 302.732408] ------------chr_drv_close----------
开发板上灯一闪一灭。
各类型形参常用命名方式: struct file * filp, filp = file point ,文件指针 char __user *buf, buf = buffer ,缓冲区 size_t count, 看到size就是指大小,个数 loff_t *fpos, loff_t local offset 地址偏移,fpos=file position 文件位置
应用程序和驱动扮演的是什么角色
用户态:应用程序 玩策略: 怎么去做 1, 一闪一闪 2,10s闪一次,也可以1s闪一次 3,一直亮 4,跑马灯 控制权是在应用程序(程序员) -------------------------------------- 内核态:驱动 玩机制: 能做什么 led:亮 和 灭
步骤:
1,实现模块加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2,在模块加载入口函数中 a, 申请主设备号 (内核中用于区分和管理不同字符设备) register_chrdev(dev_major, "chr_dev_test", &my_fops); b,创建设备节点文件 (为用户提供一个可操作到文件接口--open()) struct class *class_create(THIS_MODULE, "chr_cls"); struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2"); c, 硬件的初始化 1,地址的映射 gpx2conf = ioremap(GPX2_CON, GPX2_SIZE); 2,中断到申请 3,实现硬件的寄存器到初始化 // 需要配置gpio功能为输出 *gpx2conf &= ~(0xf<<28); *gpx2conf |= (0x1<<28); e,实现file_operations const struct file_operations my_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, };
规范:
1,面向对象编程思想
用一个结构体来表示一个对象
//设计一个类型,描述一个设备的信息 struct led_desc{ unsigned int dev_major; //设备号 struct class *cls; struct device *dev; //创建设备文件 void *reg_virt_base; }; struct led_desc *led_dev;//表示一个全局的设备对象 // 0, 实例化全局的设备对象--分配空间 // GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠) // #include <linux/slab.h> led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); if(led_dev == NULL) { printk(KERN_ERR "malloc error\n"); return -ENOMEM; } led_dev->dev_major = 250; 2,做出错处理 在某个位置出错了,要将之前申请到资源进行释放 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); if(led_dev->dev_major < 0) { printk(KERN_ERR "register_chrdev error\n"); ret = -ENODEV; goto err_0; } err_0: kfree(led_dev); return ret;
1, volatile unsigned long *gpxcon; *gpxcon &= ~(0xf<<28); 2, readl/writel(); u32 readl(const volatile void __iomem *addr)//从地址中读取地址空间到值 void writel(unsigned long value , const volatile void __iomem *add) // 将value的值写入到addr地址 例子: // gpio的输出功能的配置 u32 value = readl(led_dev->reg_virt_base); value &= ~(0xf<<28); value |= (0x1<<28) writel(value, led_dev->reg_virt_bas); 或者: *gpx2dat |= (1<<7); 替换成: writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
对应错误代码
代码示例
//------------------led_drv.c------------------ //驱动程序代码 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/uaccess.h> #include <asm/io.h> #include <linux/slab.h> #include <asm/string.h> #define GPX2CON_ADDR 0x11000c40 #define GPX2_SIZE 8 const char led_dev_name[] = "led_dev"; struct led_desc { unsigned int dev_major; //主设备号 unsigned int dev_minor; //次设备号 struct class *cls; struct device* dev; //创建设备文件 void *reg_virt_base; //表示是寄存器地址到基准值 }; struct led_desc *led_dev; //表示一个全局的设备对象 int chr_drv_open (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos) { int buf_read = 666; int ret; ret = copy_to_user(buf,&buf_read,count); if(ret == 0) { printk("------------%s----------\n",__FUNCTION__); } else { printk("KERN:Error to read\n"); return -EFAULT; } return 0; } ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos) { int buf_write = 0; int ret; unsigned int value; ret = copy_from_user(&buf_write,buf,count); if(ret == 0) { printk("__KERN__:%d\n",buf_write); if(buf_write == 0){ value = readl((led_dev->reg_virt_base + 4)); value &= ~(0x1<<7); writel(value,led_dev->reg_virt_base + 4); } else{ value = readl((led_dev->reg_virt_base + 4)); value |= (0x1<<7); writel(value,led_dev->reg_virt_base + 4); } } else { printk("KERN:Error to read\n"); return -EFAULT; } return 0; } int chr_drv_close (struct inode *inode, struct file *filp) { printk("------------%s----------\n",__FUNCTION__); return 0; } const struct file_operations major_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, }; static int __init my_chrdrv_init(void) { long ret; unsigned int value; //0. 实例化全局的设备对象--分配空间 // GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠) led_dev = (struct led_desc *)kmalloc(sizeof(struct led_desc),GFP_KERNEL); if(led_dev == NULL){ printk(KERN_ERR "malloc error\n"); return -ENOMEM; } memset((void*)led_dev,1,sizeof(struct led_desc)); //初始化led_dev //一般都是申请资源 //申请设备号 led_dev->dev_major = register_chrdev(0, led_dev_name, &major_fops); led_dev->dev_minor = 0; if (led_dev->dev_major < 0){ printk(KERN_ERR "register failed\n"); goto err0; } // 2,创建设备文件 led_dev->cls = class_create(THIS_MODULE,"led_cls"); //IS_ERR 用于内核判断指针合法性,包括指针是否为NULL,以及指针访问非法地址 //合法为0 非法不为0 if(IS_ERR(led_dev->cls)) { printk(KERN_ERR "class create failed\n"); ret = PTR_ERR(led_dev->cls); //将指针出错的具体原因转换成一个出错码 goto err1; } // /dev/led0 //下面的"mychr%d",0 等效于 "mychr0" led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major, led_dev->dev_minor),NULL,"myled%d",0); if(IS_ERR(led_dev->dev)) { printk(KERN_ERR "devic create failed\n"); ret = PTR_ERR(led_dev->dev); //将指针出错的具体原因转换成一个出错码 goto err2; } // 3,硬件初始化 // 对地址进行映射 led_dev->reg_virt_base = ioremap(GPX2CON_ADDR, GPX2_SIZE); //下面做判断也可以用IS_ERR() if(led_dev->reg_virt_base == NULL) { printk(KERN_ERR "ioremap error\n"); ret = -ENOMEM; goto err3; } // gpio的输出功能的配置 value = readl(led_dev->reg_virt_base); value &= ~(0xf << 28); value |= (0x1 << 28); writel(value,led_dev->reg_virt_base); return 0; err3: device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, led_dev->dev_minor)); err2: class_destroy(led_dev->cls); err1: unregister_chrdev(led_dev->dev_major, led_dev_name); err0: kfree((void*)led_dev); return -ENOMEM; } static void __exit my_chrdrv_exit(void) { //一般都是释放资源 //释放设备号资源 iounmap(led_dev->reg_virt_base); device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, led_dev->dev_minor)); class_destroy(led_dev->cls); unregister_chrdev(led_dev->dev_major, led_dev_name); kfree((void*)led_dev); } MODULE_LICENSE("GPL"); module_init(my_chrdrv_init); module_exit(my_chrdrv_exit);
//--------------led_test.c------------------- //APP 代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int fd; int buf = 233; fd = open("/dev/myled0",O_RDWR); if(fd < 0) { perror("open"); exit(1); } while(1){ buf = 0; write(fd, &buf, 4); sleep(1); buf = 1; write(fd, &buf, 4); sleep(1); } read(fd, &buf, 4); close(fd); return 0; }
#Makefile代码 ROOTFS_DIR = /home/linux/Level11 APP_NAME = led_test CROSS_COMPILE = arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79 CUR_DIR = $(shell pwd) all : make -C $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- $(CC) $(APP_NAME).c -o $(APP_NAME) clean : make -C $(KERNEL_DIR) M=$(CUR_DIR) clean clear install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += led_drv.o endif
效果展示
[root@farsight ]# insmod led_drv.ko [root@farsight ]# ./led_test [ 1781.874456] ------------chr_drv_open---------- [ 1781.877516] __KERN__:0 [ 1782.880055] __KERN__:1 [ 1783.881074] __KERN__:0 [ 1784.882093] __KERN__:1 [ 1785.883115] __KERN__:0 [ 1786.884136] __KERN__:1 [ 1787.384514] ------------chr_drv_close---------- [root@farsight ]# rmmod led_drv.ko
灯一闪一闪,效果达成。