导航

    全志在线开发者论坛

    • 注册
    • 登录
    • 搜索
    • 版块
    • 话题
    • 在线文档
    • 社区主页

    【萌新入门】D1-H编译运行第一个闪灯驱动

    MR Series
    1
    1
    1065
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • allwinner_account
      allwinner_account LV 5 最后由 xiaowenge 编辑

      纯小白, 头铁直接上的荔枝派的板子, 确实是入门比较困难, 结合着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
      

      闪灯测试完毕,后面就可以开始正式的深入研究了🤠

      1 条回复 最后回复 回复 引用 分享 3
      • 1 / 1
      • First post
        Last post

      Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号

      行为准则 | 用户协议 | 隐私权政策