1.实现思路:
应用层打开设备后通过write函数向内核中写值,1代表要打开灯,0代表要关闭灯
Linux配置gpio和控制gpio多了一个虚拟内存映射操作
2.注意事项:
配置和读写操作的时候要谨慎,比如先关掉gpio再注销掉虚拟内存否则照成内存泄漏设备直接死机(别问我怎么知道的)
3.实现:
驱动代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>#define LED_MAJOR 200
#define LED_NAME "led"#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)#define LEDOPEN 1
#define LEDCLOSE 0/* 映射后的寄存器虚拟地址指针 */
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;// led gpio初始化操作
void gpio_init(void){u32 val = 0;// 设置GPIO0_c0为GPIO功能val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);val &= ~(0x7 << 0); //最低三位置0val |= ((0x7 << 16) | (0x0 << 0)); // 16 17 18位置1其他不变,bit2:0:0,用作GPIO0_C0writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);// 设置GPIO_C0驱动能力为level5val = readl(PMU_GRF_GPIO0C_DS_0_PI);val &= ~(0x3f << 0); // 0 ~ 5置0val |= ((0x3f << 16) | (0x3f << 0)); // 16 ~ 21置1,0~5置1同时用作GPIO0c0writel(val, PMU_GRF_GPIO0C_DS_0_PI);// 设置GPIOO0_c0为输出val = readl(GPIO0_SWPORT_DDR_H_PI);val &= ~(0x1 << 0); // 0置0val |= ((0x1 << 16) | (0x1 << 0)); // 16置1,0置1writel(val, GPIO0_SWPORT_DDR_H_PI);// 设置GPIO_c0为低电平,关闭LEDval = readl(GPIO0_SWPORT_DR_H_PI);val &= ~(0x1 << 0);val |= ((0x1 << 16) | (0x0 << 0));writel(val, GPIO0_SWPORT_DR_H_PI);
}// 真实物理地址映射虚拟内存函数
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);}void led_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);
}void led_switch(int status){u32 val = 0;if(status == LEDOPEN){// 开灯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(status == LEDCLOSE){// 关灯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); }}static int led_open(struct inode* inode, struct file* filp){return 0;
}static int led_release(struct inode* inode, struct file* filp){return 0;
}static ssize_t led_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos){int ret;unsigned char databuf[1];unsigned char state;ret = copy_from_user(databuf, buf, count);if(ret < 0){printk("kernel write failed\r\n");return -EFAULT;}state = databuf[0];if(state == LEDOPEN){led_switch(LEDOPEN);}else if(state == LEDCLOSE){led_switch(LEDCLOSE);}return 0;
}/* 字符设备操作集*/
static const struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,.release = led_release,
};/*注册驱动加载卸载*/static int __init led_init(void){ // 入口int ret = 0;led_remap(); // 内存映射gpio_init(); // 初始化led灯ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops); // 注册字符设备if(ret < 0){printk("register chrdev failed!\r\n");return -EIO;}printk("led_init\r\n");return 0;
}static void __exit led_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");
应用层代码:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"int main(int argc, char* argv[]){int fd, ret;char* filename;unsigned char databuf[1];if(argc < 3){printf("ERROR Usage\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]);ret = write(fd, databuf, sizeof(databuf));if(ret < 0){printf("led control failed\r\n");close(fd);return -1;}close(fd);return 0;}
额外的:
配置了如下.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": "gnu++14","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返回错误时,内核会撤销分配。