_6)
1.实现思路应用层打开设备后通过write函数向内核中写值1代表要打开灯0代表要关闭灯Linux配置gpio和控制gpio多了一个虚拟内存映射操作2.注意事项配置和读写操作的时候要谨慎比如先关掉gpio再注销掉虚拟内存否则照成内存泄漏设备直接死机别问我怎么知道的3.实现驱动代码#includelinux/module.h#includelinux/kernel.h#includelinux/init.h#includelinux/fs.h#includelinux/slab.h#includelinux/uaccess.h#includelinux/io.h#defineLED_MAJOR200#defineLED_NAMEled#definePMU_GRF_BASE(0xFDC20000)#definePMU_GRF_GPIO0C_IOMUX_L(PMU_GRF_BASE0x0010)#definePMU_GRF_GPIO0C_DS_0(PMU_GRF_BASE0X0090)#defineGPIO0_BASE(0xFDD60000)#defineGPIO0_SWPORT_DR_H(GPIO0_BASE0X0004)#defineGPIO0_SWPORT_DDR_H(GPIO0_BASE0X000C)#defineLEDOPEN1#defineLEDCLOSE0/* 映射后的寄存器虚拟地址指针 */staticvoid__iomem*PMU_GRF_GPIO0C_IOMUX_L_PI;staticvoid__iomem*PMU_GRF_GPIO0C_DS_0_PI;staticvoid__iomem*GPIO0_SWPORT_DR_H_PI;staticvoid__iomem*GPIO0_SWPORT_DDR_H_PI;// led gpio初始化操作voidgpio_init(void){u32 val0;// 设置GPIO0_c0为GPIO功能valreadl(PMU_GRF_GPIO0C_IOMUX_L_PI);val~(0x70);//最低三位置0val|((0x716)|(0x00));// 16 17 18位置1其他不变bit2:00用作GPIO0_C0writel(val,PMU_GRF_GPIO0C_IOMUX_L_PI);// 设置GPIO_C0驱动能力为level5valreadl(PMU_GRF_GPIO0C_DS_0_PI);val~(0x3f0);// 0 ~ 5置0val|((0x3f16)|(0x3f0));// 16 ~ 21置10~5置1同时用作GPIO0c0writel(val,PMU_GRF_GPIO0C_DS_0_PI);// 设置GPIOO0_c0为输出valreadl(GPIO0_SWPORT_DDR_H_PI);val~(0x10);// 0置0val|((0x116)|(0x10));// 16置1,0置1writel(val,GPIO0_SWPORT_DDR_H_PI);// 设置GPIO_c0为低电平关闭LEDvalreadl(GPIO0_SWPORT_DR_H_PI);val~(0x10);val|((0x116)|(0x00));writel(val,GPIO0_SWPORT_DR_H_PI);}// 真实物理地址映射虚拟内存函数voidled_remap(void){PMU_GRF_GPIO0C_IOMUX_L_PIioremap(PMU_GRF_GPIO0C_IOMUX_L,4);PMU_GRF_GPIO0C_DS_0_PIioremap(PMU_GRF_GPIO0C_DS_0,4);GPIO0_SWPORT_DR_H_PIioremap(GPIO0_SWPORT_DR_H,4);GPIO0_SWPORT_DDR_H_PIioremap(GPIO0_SWPORT_DDR_H,4);}voidled_releaseMap(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);}voidled_switch(intstatus){u32 val0;if(statusLEDOPEN){// 开灯valreadl(GPIO0_SWPORT_DR_H_PI);val~(0X10);/* bit0 清零*/val|((0X116)|(0X10));/* bit16 置1允许写bit0 bit0高电平*/writel(val,GPIO0_SWPORT_DR_H_PI);}elseif(statusLEDCLOSE){// 关灯valreadl(GPIO0_SWPORT_DR_H_PI);val~(0X10);/* bit0 清零*/val|((0X116)|(0X00));/* bit16 置1允许写bit0 bit0低电平 */writel(val,GPIO0_SWPORT_DR_H_PI);}}staticintled_open(structinode*inode,structfile*filp){return0;}staticintled_release(structinode*inode,structfile*filp){return0;}staticssize_tled_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*ppos){intret;unsignedchardatabuf[1];unsignedcharstate;retcopy_from_user(databuf,buf,count);if(ret0){printk(kernel write failed\r\n);return-EFAULT;}statedatabuf[0];if(stateLEDOPEN){led_switch(LEDOPEN);}elseif(stateLEDCLOSE){led_switch(LEDCLOSE);}return0;}/* 字符设备操作集*/staticconststructfile_operationsled_fops{.ownerTHIS_MODULE,.writeled_write,.openled_open,.releaseled_release,};/*注册驱动加载卸载*/staticint__initled_init(void){// 入口intret0;led_remap();// 内存映射gpio_init();// 初始化led灯retregister_chrdev(LED_MAJOR,LED_NAME,led_fops);// 注册字符设备if(ret0){printk(register chrdev failed!\r\n);return-EIO;}printk(led_init\r\n);return0;}staticvoid__exitled_exit(void){// 出口led_releaseMap();// 注销内存映射unregister_chrdev(LED_MAJOR,LED_NAME);// 注销printk(led_exit\r\n);}module_init(led_init);module_exit(led_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(Narnat);应用层代码#includestdio.h#includeunistd.h#includesys/types.h#includesys/stat.h#includefcntl.h#includestdlib.h#includestring.hintmain(intargc,char*argv[]){intfd,ret;char*filename;unsignedchardatabuf[1];if(argc3){printf(ERROR Usage\r\n);return-1;}filenameargv[1];fdopen(filename,O_RDWR);if(fd0){printf(file %s open failed\r\n,argv[1]);return-1;}databuf[0]atoi(argv[2]);retwrite(fd,databuf,sizeof(databuf));if(ret0){printf(led control failed\r\n);close(fd);return-1;}close(fd);return0;}额外的配置了如下.vscode文件让内核函数能跳转到内核函数实现c_cpp_properties.json:{configurations:[{name:RK3568 Linux,includePath:[${workspaceFolder}/**,// ARM64架构核心头文件/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/include,/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/arch/arm64/include,// Rockchip芯片专用头文件/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/drivers/soc/rockchip,// 编译生成的头文件需先编译内核/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/output/include/generated,/home/saisi/RK3568/SDK/linux/rk3568_linux_sdk/kernel/output/include/config],defines:[__KERNEL__,// 必须定义内核模式CONFIG_ARM64,// 明确ARM64架构CONFIG_ARCH_ROCKCHIP],compilerPath:/usr/bin/aarch64-linux-gnu-gcc,// 交叉编译器cStandard:gnu11,cppStandard:gnu14,intelliSenseMode:linux-gcc-arm64}],version:4}setting.json:{search.exclude:{**/node_modules:true,**/bower_components:true,**/*.o:true,**/*.su:true,**/*.cmd:true,Documentation:true},files.exclude:{**/.git:true,**/.svn:true,**/.hg:true,**/CVS:true,**/.DS_Store:true,**/*.o:true,**/*.su:true,**/*.cmd:true,Documentation:true},C_Cpp.intelliSenseEngineFallback:Disabled,C_Cpp.intelliSenseEngine:Tag Parser}4.现象1为开灯0为关灯5.额外补充:由于最开始设备led灯会闪烁应该是有这个驱动用echo 0 /sys/class/leds/work/brightness命令关闭后再挂载驱动一、open函数的特殊性:内核默认实现当驱动未显式实现file_operations结构体中的.open函数时内核会自动提供一个默认的open函数实现。该默认实现仅完成以下操作分配文件描述符在内核的文件描述符表中分配一个未使用的索引。初始化文件对象创建struct file对象并关联到设备的file_operations结构体。权限校验检查用户是否有权限访问设备如设备文件的权限位。这解释了为什么用户层调用open(“/dev/led”, O_RDWR)即使驱动未实现.open也能成功返回文件描述符open函数在驱动中属于可选实现而write/read等函数则是必须实现的。如果驱动未实现write用户层调用write会直接返回-EINVAL无效操作)open函数作用驱动的.open函数是设备初始化的入口但不影响文件描述符的分配机制。文件描述符的返还是内核的默认行为仅当驱动.open返回错误时内核会撤销分配。