纯小白, 头铁直接上的荔枝派的板子, 确实是入门比较困难, 结合着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
闪灯测试完毕,后面就可以开始正式的深入研究了