【萌新入门】D1-H编译运行第一个闪灯驱动
-
纯小白, 头铁直接上的荔枝派的板子, 确实是入门比较困难, 结合着b站的一些教程和社区的资料, 倒腾很久终于写出了能用的闪灯驱动
这里记录和分享下, 欢迎交流指点[社区都是大佬萌新瑟瑟发抖 ]驱动程序如下
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> //file_operations register_chrdev_region #include <linux/kdev_t.h> //MKDEV #include <linux/cdev.h> //cdev_init #include <linux/uaccess.h>//copy_from_user #include <linux/hrtimer.h>//硬件定时器 #include <linux/gpio.h> //gpio #define DEV_LEDFLASH_TYPE 'k' #define DEV_LEDFLASH_GETVALUE _IOR(DEV_LEDFLASH_TYPE, 0x32, int) #define DEV_LEDFLASH_SETVALUE _IOW(DEV_LEDFLASH_TYPE, 0x33, int) #define LED_PIN_NUMBER 65 //PC1=32*2+1 #define LED_FLASH_INTERVAL 500 //1hz static int major= 222;//主设备号 static int minor = 0; //子设备号 static dev_t devno; //设备号 struct class *cls; //设备类 struct device *class_dev = NULL; //设备节点 static struct hrtimer flashTimer; //定时器 static int interval = LED_FLASH_INTERVAL; //定时器中断间隔(ms) /** * @description: 定时器回调函数,这里用来闪灯 * @param {hrtimer} *timer * @return {*} */ static enum hrtimer_restart flashTimer_handler(struct hrtimer *timer){ static int num= 0; if(interval!=0){ __gpio_set_value(LED_PIN_NUMBER, num++%2); // 设置下一次中断间隔 hrtimer_forward_now(&flashTimer, ms_to_ktime(interval)); return HRTIMER_RESTART; }else{ // 关闭led __gpio_set_value(LED_PIN_NUMBER, 0); // 关闭定时器 return HRTIMER_NORESTART; } } /** * @description: 打开设备文件, 启用LED口和定时器 * @param {inode} *inode * @param {file} *filep * @return {*} */ static int ledflash_open(struct inode *inode, struct file *filep){ int result; printk("[DRV] ledflash_open()\n"); /* GPIO初始化 */ printk("[DRV] _____gpio_init____\n"); // 获取LED_GPIO访问权 result= gpio_request(LED_PIN_NUMBER, NULL); if(result< 0){ printk(KERN_ERR "[DRV] gpio_request() failed:%d\n", result); gpio_free(LED_PIN_NUMBER); return -1; } // TODO:使用pinctrl还可以配置上下拉,没找到例子还没有测试 // 设置为输出模式 result= gpio_direction_output(LED_PIN_NUMBER, 1); if(result< 0){ printk(KERN_ERR "[DRV] gpio_direction_output() failed:%d\n", result); gpio_free(LED_PIN_NUMBER); return -1; } /* 定时器初始化 */ printk("[DRV] _____hrtimer_init____\n"); // 配置定时器为基于开机时间时钟的相对模式 //模式定义见time.h 和hrtimer.h hrtimer_init(&flashTimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); // 设置回调函数 flashTimer.function = &flashTimer_handler; // 开启定时器 hrtimer_start(&flashTimer, ms_to_ktime(LED_FLASH_INTERVAL), HRTIMER_MODE_REL); return 0; } /** * @description: 关闭设备文件, 释放LED口和定时器 * @param {inode} *inode * @param {file} *filep * @return {*} */ static int ledflash_release(struct inode *inode, struct file *filep){ // 取消定时器 hrtimer_cancel(&flashTimer); // 关闭led __gpio_set_value(LED_PIN_NUMBER, 0); // 释放gpio控制权 gpio_free(LED_PIN_NUMBER); printk("[DRV] ledflash_release()\n"); return 0; } /** * @description: 设备操作函数接口 * @param {file} *filep * @param {unsigned int} cmd * @param {unsigned long} arg * @return {*} */ long ledflash_ioctl(struct file *filep, unsigned int cmd, unsigned long arg){ long ret; void __user *argp= (void __user *)arg; int __user *p= argp; //参数 int set_val= LED_FLASH_INTERVAL; // 检测参数合法 if(_IOC_TYPE(cmd)!= DEV_LEDFLASH_TYPE){ pr_err("[DRV] cmd %u,bad magic 0x%x/0x%x.\n",cmd,_IOC_TYPE(cmd), DEV_LEDFLASH_TYPE); return -ENOTTY; } if(_IOC_DIR(cmd)& _IOC_READ) ret= !access_ok((void __user*)arg, _IOC_SIZE(cmd)); else if(_IOC_DIR(cmd)& _IOC_WRITE) ret= !access_ok((void __user*)arg, _IOC_SIZE(cmd)); if(ret){ pr_err("[DRV] bad access %ld.\n",ret); return -EFAULT; } // 根据不同操作函数指令指令 switch(cmd){ case DEV_LEDFLASH_GETVALUE: ret = put_user(interval, p); printk("[DRV] DEV_LEDFLASH_GETVALUE %d\n",interval); break; case DEV_LEDFLASH_SETVALUE: ret = get_user(set_val, p); // 设置为0将关闭定时器 if(interval== 0 && set_val> 0) hrtimer_restart(&flashTimer); interval= set_val; printk("[DRV] DEV_LEDFLASH_SETVALUE %d\n",interval); break; default: return -EINVAL; } return ret; } /** * @description: 文件操作接口绑定 */ static struct file_operations ledflash_ops = { .open = ledflash_open, .release= ledflash_release, // .read= ledflash_read, // .write= ledflash_write, .unlocked_ioctl= ledflash_ioctl, }; /** * @description: 初始化程序 */ static int ledflash_init(void){ int result; printk("[DRV] _____ledflashdrv_init____\n"); /* 设备初始化 */ // 注册cdev设备号, 绑定标准文件接口 // 查看验证cat /proc/devices | grep ledflash result = register_chrdev(major, "ledflashCd", &ledflash_ops); if(result <0){ printk(KERN_ERR "[DRV] register_chrdev fail\n"); goto out_err_0; } // 拼接主次设备号 devno = MKDEV(major, minor); // 创建设备类 // 查看验证 ls /sys/class/ | grep ledflash cls = class_create(THIS_MODULE, "ledflashCls"); if (IS_ERR(cls)) { printk(KERN_ERR "[DRV] class_create() failed for cls\n"); result = PTR_ERR(cls); goto out_err_1; } // 创建设备节点,绑定设备类和设备号 // 查看验证 ls /dev | grep ledflash class_dev = device_create(cls, NULL, devno, NULL, "ledflashDev"); if (IS_ERR(class_dev)) { printk(KERN_ERR "[DRV] device_create() failed for class_dev\n"); result = PTR_ERR(class_dev); goto out_err_2; } printk("[DRV] _____ledflashdrv_init_successed____\n"); return 0; out_err_2: class_destroy(cls); out_err_1: unregister_chrdev(major,"ledflash"); out_err_0: return result; } /** * @description: 卸载驱动,依次释放资源 */ static void ledflash_exit(void){ device_destroy(cls, devno); class_destroy(cls); unregister_chrdev(major, "ledflashCd"); printk("[DRV] _____ledflash_exit_____\n"); return; } // 安装驱动时可以设定主设备号 // 设定参数 insmod led_flash.ko major=333 module_param(major, int, 0644); // 绑定驱动安装和卸载程序 module_init(ledflash_init); //insmod module_exit(ledflash_exit); //rmmod // 驱动信息 // 测试modinfo找不到驱动, depmod找不到指令 // 需要手动将ko模块放到/lib/modules/5.4.61目录下 MODULE_LICENSE("GPL"); MODULE_AUTHOR("G");
makefile如下, 完整编译内核用时太久了, 手动设置三个参数后即可单独编译上传, 调试方便很多
# 完整内核编译会定义KERNELRELEASE ifneq ($(KERNELRELEASE),) obj-m := led_flash.o else # 打印一下进到这边直接编译了 $(warning ____alone_compile____) # 配置为riscv架构 export ARCH=riscv # 配置编译器 export CROSS_COMPILE=/home/ubt/tina-d1-h/prebuilt/gcc/linux-x86/riscv/toolchain-thead-glibc/riscv64-glibc-gcc-thead_20200702/bin/riscv64-unknown-linux-gnu- # 配置内核源码位置 KDIR = /home/ubt/tina-d1-h/out/d1-h-nezha/compile_dir/target/linux-d1-h-nezha/linux-5.4.61 # 指定驱动程序源码位置(当前目录) PWD = $(shell pwd) # 将led_flash.o 编译成led_flash.ko, 而led_flash.o 则由make的自动推导功能编译led_flash.c 文件生成 obj-m += led_flash.o all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean endif
Kconfig
config LED_FLASH tristate "led_flash driver, gpio out put pulse by timer" help led_flash help msg.
以上文件放在驱动目录下单独一个文件夹内,注意驱动目录的makefile和Kconfig需要添加该文件夹索引信息
然后make kernel_menuconfig进入驱动项即可配置
我尝试了直接上传ko文件安装报错, 还是直接mp更新了板子, 之后就可以直接上传更新驱动模块了
驱动编写参考教程
编写一个内核驱动,点亮 LED测试的应用程序
#include <stdio.h> // printf #include <unistd.h> // open file #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #define DEV_LEDFLASH_TYPE 'k' #define DEV_LEDFLASH_GETVALUE _IOR(DEV_LEDFLASH_TYPE, 0x32, int) #define DEV_LEDFLASH_SETVALUE _IOW(DEV_LEDFLASH_TYPE, 0x33, int) int fd; int interval= 500; int main(void) { // printf("Hell! O’ world, why won’t my code compile?\n\n"); printf("[USER] led_flash_begin\n"); fd= open("/dev/ledflashDev", O_RDWR); if(fd< 0){ perror("[USER] open failed\n"); return -1; } // 读取默认值 if(ioctl(fd, DEV_LEDFLASH_GETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_GETVALUE failed\n"); return -1; } printf("[USER] default interval= %d\n", interval); sleep(2); // 关闭 interval = 0; if(ioctl(fd, DEV_LEDFLASH_SETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_SETVALUE failed\n"); return -1; } if(ioctl(fd, DEV_LEDFLASH_GETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_GETVALUE failed\n"); return -1; } printf("[USER] set interval= %d\n", interval); sleep(2); // 设置为2hz interval = 250; if(ioctl(fd, DEV_LEDFLASH_SETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_SETVALUE failed\n"); return -1; } if(ioctl(fd, DEV_LEDFLASH_GETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_GETVALUE failed\n"); return -1; } printf("[USER] set interval= %d\n", interval); sleep(2); // 设置为5hz interval = 100; if(ioctl(fd, DEV_LEDFLASH_SETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_SETVALUE failed\n"); return -1; } if(ioctl(fd, DEV_LEDFLASH_GETVALUE, &interval)< 0){ perror("[USER] DEV_LEDFLASH_GETVALUE failed\n"); return -1; } printf("[USER] set interval= %d\n", interval); sleep(2); close(fd); printf("[USER] steper_end\n"); return 0; }
makefile
#设置编译链路径及工具 CTOOL := riscv64-unknown-linux-gnu- CCL := /home/ubt/tina-d1-h/prebuilt/gcc/linux-x86/riscv/toolchain-thead-glibc/riscv64-glibc-gcc-thead_20200702 CC := ${CCL}/bin/${CTOOL}gcc # build led_flash executable when user executes “make” led_flash: led_flash.o $(CC) $(LDFLAGS) led_flash.o -o led_flash led_flash.o: led_flash.c $(CC) $(CFLAGS) -c led_flash.c # remove object files and executable when user executes “make clean” clean: rm *.o led_flash
全部编译好后使用adb上传,安装运行效果如下
root@TinaLinux:/mnt/exUDISK/app# insmod ../mod/led_flash.ko [ 2654.707249] led_flash: loading out-of-tree module taints kernel. [ 2654.714946] [DRV] _____ledflashdrv_init____ [ 2654.720524] [DRV] _____ledflashdrv_init_successed____ root@TinaLinux:/mnt/exUDISK/app# ./led_flash [USER] led_flash_begin[ 2664.649231] [DRV] ledflash_open() [ 2664.654850] [DRV] _____gpio_init____ [ 2664.659067] [DRV] _____hrtimer_init____ [ 2664.663458] [DRV] DEV_LEDFLASH_GETVALUE 500 [USER] default interval= 500 [ 2666.668404] [DRV] DEV_LEDFLASH_SETVALUE 0 [ 2666.672975] [DRV] DEV_LEDFLASH_GETVALUE 0 [USER] set interval= 0 [ 2668.677738] [DRV] DEV_LEDFLASH_SETVALUE 250 [ 2668.682480] [DRV] DEV_LEDFLASH_GETVALUE 250 [USER] set interval= 250 [ 2670.687414] [DRV] DEV_LEDFLASH_SETVALUE 100 [ 2670.692243] [DRV] DEV_LEDFLASH_GETVALUE 100 [USER] set interval= 100 [ 2672.697095] [DRV] ledflash_release() [USER] steper_end
闪灯测试完毕,后面就可以开始正式的深入研究了
Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号