1.实验代码
驱动
cs#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <asm/uaccess.h> #include <asm/io.h> /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : newchrled.c 作者 : 正点原子 版本 : V1.0 描述 : LED驱动文件。 其他 : 无 论坛 : www.openedv.com 日志 : 初版V1.0 2022/12/02 正点原子团队创建 ***************************************************************/ #define NEWCHRLED_CNT 1 /* 设备号个数 */ #define NEWCHRLED_NAME "newchrled" /* 名字 */ #define LEDOFF 0 /* 关灯 */ #define LEDON 1 /* 开灯 */ #define PMU_GRF_BASE (0xFDC20000) #define PMU_GRF_GPIO0C_IOMUX_L (PMU_GRF_BASE + 0x0010) #define PMU_GRF_GPIO0C_DS_0 (PMU_GRF_BASE + 0X0090) #define GPIO0_BASE (0xFDD60000) #define GPIO0_SWPORT_DR_H (GPIO0_BASE + 0X0004) #define GPIO0_SWPORT_DDR_H (GPIO0_BASE + 0X000C) /* 映射后的寄存器虚拟地址指针 */ static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI; static void __iomem *PMU_GRF_GPIO0C_DS_0_PI; static void __iomem *GPIO0_SWPORT_DR_H_PI; static void __iomem *GPIO0_SWPORT_DDR_H_PI; /* newchrled设备结构体 */ struct newchrled_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ }; struct newchrled_dev newchrled; /* led设备 */ /* * @description : LED打开/关闭 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED * @return : 无 */ void led_switch(u8 sta) { u32 val = 0; if(sta == LEDON) { val = readl(GPIO0_SWPORT_DR_H_PI); val &= ~(0X1 << 0); /* bit0 清零*/ val |= ((0X1 << 16) | (0X1 << 0)); /* bit16 置1,允许写bit0, bit0,高电平*/ writel(val, GPIO0_SWPORT_DR_H_PI); }else if(sta == LEDOFF) { val = readl(GPIO0_SWPORT_DR_H_PI); val &= ~(0X1 << 0); /* bit0 清零*/ val |= ((0X1 << 16) | (0X0 << 0)); /* bit16 置1,允许写bit0, bit0,低电平 */ writel(val, GPIO0_SWPORT_DR_H_PI); } } /* * @description : 物理地址映射 * @return : 无 */ void led_remap(void) { PMU_GRF_GPIO0C_IOMUX_L_PI = ioremap(PMU_GRF_GPIO0C_IOMUX_L, 4); PMU_GRF_GPIO0C_DS_0_PI = ioremap(PMU_GRF_GPIO0C_DS_0, 4); GPIO0_SWPORT_DR_H_PI = ioremap(GPIO0_SWPORT_DR_H, 4); GPIO0_SWPORT_DDR_H_PI = ioremap(GPIO0_SWPORT_DDR_H, 4); } /* * @description : 取消映射 * @return : 无 */ void led_unmap(void) { /* 取消映射 */ iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI); iounmap(PMU_GRF_GPIO0C_DS_0_PI); iounmap(GPIO0_SWPORT_DR_H_PI); iounmap(GPIO0_SWPORT_DDR_H_PI); } /* * @description : 打开设备 * @param -- inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int led_open(struct inode *inode, struct file *filp) { filp->private_data = &newchrled; /* 设置私有数据 */ return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } /* * @description : 向设备写数据 * @param -- filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue; unsigned char databuf[1]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if(retvalue < 0) { printk("kernel write failed!\r\n"); return -EFAULT; } ledstat = databuf[0]; /* 获取状态值 */ if(ledstat == LEDON) { led_switch(LEDON); /* 打开LED灯 */ } else if(ledstat == LEDOFF) { led_switch(LEDOFF); /* 关闭LED灯 */ } return 0; } /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ static int led_release(struct inode *inode, struct file *filp) { return 0; } /* 设备操作函数 */ static struct file_operations newchrled_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static int __init led_init(void) { u32 val = 0; int ret; /* 初始化LED */ /* 1、寄存器地址映射 */ led_remap(); /* 2、设置GPIO0_C0为GPIO功能。*/ val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI); val &= ~(0X7 << 0); /* bit2:0,清零 */ val |= ((0X7 << 16) | (0X0 << 0)); /* bit18:16 置1,允许写bit2:0, bit2:0:0,用作GPIO0_C0 */ writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI); /* 3、设置GPIO0_C0驱动能力为level5 */ val = readl(PMU_GRF_GPIO0C_DS_0_PI); val &= ~(0X3F << 0); /* bit5:0清零*/ val |= ((0X3F << 16) | (0X3F << 0)); /* bit21:16 置1,允许写bit5:0, bit5:0:0,用作GPIO0_C0 */ writel(val, PMU_GRF_GPIO0C_DS_0_PI); /* 4、设置GPIO0_C0为输出 */ val = readl(GPIO0_SWPORT_DDR_H_PI); val &= ~(0X1 << 0); /* bit0 清零*/ val |= ((0X1 << 16) | (0X1 << 0)); /* bit16 置1,允许写bit0, bit0,高电平 */ writel(val, GPIO0_SWPORT_DDR_H_PI); /* 5、设置GPIO0_C0为低电平,关闭LED灯。*/ val = readl(GPIO0_SWPORT_DR_H_PI); val &= ~(0X1 << 0); /* bit0 清零*/ val |= ((0X1 << 16) | (0X0 << 0)); /* bit16 置1,允许写bit0, bit0,低电平 */ writel(val, GPIO0_SWPORT_DR_H_PI); /* 注册字符设备驱动 */ /* 1、创建设备号 */ if (newchrled.major) { /* 定义了设备号 */ newchrled.devid = MKDEV(newchrled.major, 0); ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME); if(ret < 0) { pr_err("cannot register %s char driver [ret=%d]\n",NEWCHRLED_NAME, NEWCHRLED_CNT); goto fail_map; } } else { /* 没有定义设备号 */ ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */ if(ret < 0) { pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", NEWCHRLED_NAME, ret); goto fail_map; } newchrled.major = MAJOR(newchrled.devid);/* 获取主设备号 */ newchrled.minor = MINOR(newchrled.devid);/* 获取次设备号 */ } printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor); /* 2、初始化cdev */ newchrled.cdev.owner = THIS_MODULE; cdev_init(&newchrled.cdev, &newchrled_fops); /* 3、添加一个cdev */ ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT); if(ret < 0) goto del_unregister; /* 4、创建类 */ newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); if (IS_ERR(newchrled.class)) { goto del_cdev; } /* 5、创建设备 */ newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); if (IS_ERR(newchrled.device)) { goto destroy_class; } return 0; destroy_class: class_destroy(newchrled.class); del_cdev: cdev_del(&newchrled.cdev); del_unregister: unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); fail_map: led_unmap(); return -EIO; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit led_exit(void) { /* 取消映射 */ led_unmap(); /* 注销字符设备驱动 */ cdev_del(&newchrled.cdev);/* 删除cdev */ unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ALIENTEK"); MODULE_INFO(intree, "Y");应用
cs#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : ledApp.c 作者 : 正点原子 版本 : V1.0 描述 : chrdevbase驱测试APP。 其他 : 无 使用方法 :./ledtest /dev/led 0 关闭LED ./ledtest /dev/led 1 打开LED 论坛 : www.openedv.com 日志 : 初版V1.0 2022/12/02 正点原子团队创建 ***************************************************************/ #define LEDOFF 0 #define LEDON 1 /* * @description : main主程序 * @param - argc : argv数组元素个数 * @param - argv : 具体参数 * @return : 0 成功;其他 失败 */ int main(int argc, char *argv[]) { int fd, retvalue; char *filename; unsigned char databuf[1]; if(argc != 3){ printf("Error Usage!\r\n"); return -1; } filename = argv[1]; /* 打开led驱动 */ fd = open(filename, O_RDWR); if(fd < 0){ printf("file %s open failed!\r\n", argv[1]); return -1; } databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */ /* 向/dev/led文件写入数据 */ retvalue = write(fd, databuf, sizeof(databuf)); if(retvalue < 0){ printf("LED Control Failed!\r\n"); close(fd); return -1; } retvalue = close(fd); /* 关闭文件 */ if(retvalue < 0){ printf("file %s close failed!\r\n", argv[1]); return -1; } return 0; }makefile
bashKERNELDIR := /home/alientek/rk3568_linux_sdk/kernel CURRENT_PATH := $(shell pwd) obj-m := newchrled.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
2.代码详细解释
驱动
新字符设备驱动框架
首先要放在私有数据里,私有数据是一个结构体,里面要又设备号,cdev,类,设备
cs/* newchrled设备结构体 */ struct newchrled_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ }; struct newchrled_dev newchrled; /* led设备 */然后操作集,这个操作集是进行操作的函数,开读写释放,用户和内核之间的接口,系统调用
cs/* 设备操作函数 */ static struct file_operations newchrled_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, .llseek = eep_llseek, .poll = eep_poll, .unlocked_ioctl = eep_ioctl, }; }; /*开*/ /* struct inode 内核中的文件 struct file 实际打开的文件 */ static int led_open(struct inode *inode, struct file *filp) { filp->private_data = &newchrled; /* 设置私有数据 */ return 0; } /*读*/ /* struct file 实际打开的文件 用户, 大小, 偏移 */ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } /*写*/ /* struct file 实际打开的文件 用户, 大小, 偏移 */ static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } /*关闭*/ /* struct inode 内核中的文件 struct file 实际打开的文件 */ static int led_release(struct inode *inode, struct file *filp) { return 0; } /*文件移动位置*/ /* 文件 偏移 开始地址找 */ loff_t(*llseek) (struct file *filp, loff_t *offset, int whence); /*被动等待*/ /* 文件 poll_table结构体 */ unsigned int (*poll) (struct file *, struct poll_table *) /*输入/输出控制*/ /* 文件 cmd arg */ long ioctl(struct file *f, unsigned int cmd, unsigned long arg)中间的用户和内核数据交互
cscopy_from_user(databuf, buf, cnt); copy_to_user(buf, databuf, cnt);接着就是设备节点的创建,会在/dev目录下,过程是自动创建设备号,初始化cdev,添加cdev,创建类,创建设备
cs/* 注册字符设备驱动 */ /* 1、创建设备号 */ /* devid 次设备号最小数量 设备数量 名字 */ ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); newchrled.major = MAJOR(newchrled.devid);/* 获取主设备号 */ newchrled.minor = MINOR(newchrled.devid);/* 获取次设备号 */ /* 2、初始化cdev */ /* cdev fops */ newchrled.cdev.owner = THIS_MODULE; cdev_init(&newchrled.cdev, &newchrled_fops); /* 3、添加一个cdev */ /* cdev, devid, 设备数量 */ ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT); /* 4、创建类 */ /* /sys/class THIS_MODULE 名字 */ newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); /* 5、创建设备 */ /* /dev目录 类 父设备 devid data 名字 */ newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); /* 注销字符设备驱动 */ device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); cdev_del(&newchrled.cdev); unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);中间的处理空指针
csvoid *ERR_PTR(long error); /*错误值作为指针返回*/ long IS_ERR(const void *ptr); /*检查返回值是否是指针错误*/ IS_ERR(newchrled.class) long PTR_ERR(const void *ptr); /*返回实际错误代码*/整个框架就是在module_init,module_exit
csstatic int __init led_init(void) static void __exit led_exit(void) module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ALIENTEK"); MODULE_INFO(intree, "Y");虚拟地址映射ioremap, iounmap, readl, witel,
cs#define PMU_GRF_BASE (0xFDC20000) #define PMU_GRF_GPIO0C_IOMUX_L (PMU_GRF_BASE + 0x0010) /* 指向iomem的指针*/ static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI; /*注册*/ /* 地址 大小 */ void __iomem *ioremap(resource_size_t res_cookie, size_t size); PMU_GRF_GPIO0C_IOMUX_L_PI = ioremap(PMU_GRF_GPIO0C_IOMUX_L, 4); /*注销*/ /* 地址 */ void iounmap (volatile void __iomem *addr) iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI); /*读*/ /* 地址 */ u32 readl(const volatile void __iomem *addr) val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI); /*写*/ /* 地址 */ void writel(u32 value, volatile void __iomem *addr) writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);
应用
csint main(int argc, char *argv[]) /*argc命令个数,argv[]字符串*/ /*文件 标志 访问权限*/ int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); open(filename, O_RDWR) /*文件 缓冲区 大小*/ ssize_t read(int fd, void *buf, size_t count); /*文件 缓冲区 大小*/ ssize_t write(int fd, const void *buf, size_t count); /*文件*/ int close(int fd); /*读写位置偏移量*/ /*文件 偏移量 开始位置*/ off_t lseek(int fd, off_t offset, int whence);makefile
csARCH CROSS_COMPILE交叉编译器位置 模块 KDIR 内核位置 PWD $取地址的意思 export ARCH=arm64#设置平台架构 export CROSS_COMPILE=/opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-#交叉编译器前缀 obj-m += newchrled.o #此处要和你的驱动源文件同名 KDIR :=/home/alientek/rk3568_linux5.10_sdk/kernel #这里是你的内核目录 PWD ?= $(shell pwd) all: make -C $(KDIR) M=$(PWD) modules #make操作 clean: make -C $(KDIR) M=$(PWD) clean #make clean操作