文章目录

  • 一、不同点
  • 二、v3s定时器参考驱动
  • 三、编写实验测试
  • 1. 驱动程序
  • 2. 应用程序
  • 3. 运行测试



在之前我们在学习阿尔法开发板的时候编写过这个是实验,那么为什么又要重新写一篇文章呢?


这是因为在v3S的开发板上,软件定时器和一般的定时器稍微有点不同。

一、不同点

一般定时器结构体:

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to     
         * the same cacheline
     */
    struct hlist_node    entry;
    unsigned long        expires;
    void            (*function)(unsigned long);
    unsigned long        data;
    u32            flags;
    int            slack;

#ifdef CONFIG_TIMER_STATS
    int            start_pid;
    void            *start_site;
    char            start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map    lockdep_map;
#endif
};

v3S定时器结构体:

struct timer_list {
        /*
         * All fields that change during normal runtime grouped to the
         * same cacheline
         */
        struct hlist_node       entry;
        unsigned long           expires;
        void                    (*function)(struct timer_list *);
        u32                     flags;

#ifdef CONFIG_LOCKDEP
        struct lockdep_map      lockdep_map;
#endif
};

可以看出,v3s的定时器结构体少了一部分东西。

二、v3s定时器参考驱动


因为链接是github上的,网络有时候可能进不去,不方便随时查看,这里贴出来。

// SPDX-License-Identifier: GPL-2.0-only
/*
 * hangcheck-timer.c
 *
 * Driver for a little io fencing timer.
 *
 * Copyright (C) 2002, 2003 Oracle.  All rights reserved.
 *
 * Author: Joel Becker <joel.becker@oracle.com>
 */

/*
 * The hangcheck-timer driver uses the TSC to catch delays that
 * jiffies does not notice.  A timer is set.  When the timer fires, it
 * checks whether it was delayed and if that delay exceeds a given
 * margin of error.  The hangcheck_tick module parameter takes the timer
 * duration in seconds.  The hangcheck_margin parameter defines the
 * margin of error, in seconds.  The defaults are 60 seconds for the
 * timer and 180 seconds for the margin of error.  IOW, a timer is set
 * for 60 seconds.  When the timer fires, the callback checks the
 * actual duration that the timer waited.  If the duration exceeds the
 * allotted time and margin (here 60 + 180, or 240 seconds), the machine
 * is restarted.  A healthy machine will have the duration match the
 * expected timeout very closely.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/sysrq.h>
#include <linux/timer.h>
#include <linux/hrtimer.h>

#define VERSION_STR "0.9.1"

#define DEFAULT_IOFENCE_MARGIN 60	/* Default fudge factor, in seconds */
#define DEFAULT_IOFENCE_TICK 180	/* Default timer timeout, in seconds */

static int hangcheck_tick = DEFAULT_IOFENCE_TICK;
static int hangcheck_margin = DEFAULT_IOFENCE_MARGIN;
static int hangcheck_reboot;  /* Defaults to not reboot */
static int hangcheck_dump_tasks;  /* Defaults to not dumping SysRQ T */

/* options - modular */
module_param(hangcheck_tick, int, 0);
MODULE_PARM_DESC(hangcheck_tick, "Timer delay.");
module_param(hangcheck_margin, int, 0);
MODULE_PARM_DESC(hangcheck_margin, "If the hangcheck timer has been delayed more than hangcheck_margin seconds, the driver will fire.");
module_param(hangcheck_reboot, int, 0);
MODULE_PARM_DESC(hangcheck_reboot, "If nonzero, the machine will reboot when the timer margin is exceeded.");
module_param(hangcheck_dump_tasks, int, 0);
MODULE_PARM_DESC(hangcheck_dump_tasks, "If nonzero, the machine will dump the system task state when the timer margin is exceeded.");

MODULE_AUTHOR("Oracle");
MODULE_DESCRIPTION("Hangcheck-timer detects when the system has gone out to lunch past a certain margin.");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION_STR);

/* options - nonmodular */
#ifndef MODULE

static int __init hangcheck_parse_tick(char *str)
{
	int par;
	if (get_option(&str,&par))
		hangcheck_tick = par;
	return 1;
}

static int __init hangcheck_parse_margin(char *str)
{
	int par;
	if (get_option(&str,&par))
		hangcheck_margin = par;
	return 1;
}

static int __init hangcheck_parse_reboot(char *str)
{
	int par;
	if (get_option(&str,&par))
		hangcheck_reboot = par;
	return 1;
}

static int __init hangcheck_parse_dump_tasks(char *str)
{
	int par;
	if (get_option(&str,&par))
		hangcheck_dump_tasks = par;
	return 1;
}

__setup("hcheck_tick", hangcheck_parse_tick);
__setup("hcheck_margin", hangcheck_parse_margin);
__setup("hcheck_reboot", hangcheck_parse_reboot);
__setup("hcheck_dump_tasks", hangcheck_parse_dump_tasks);
#endif /* not MODULE */

#define TIMER_FREQ 1000000000ULL

/* Last time scheduled */
static unsigned long long hangcheck_tsc, hangcheck_tsc_margin;

static void hangcheck_fire(struct timer_list *);

static DEFINE_TIMER(hangcheck_ticktock, hangcheck_fire);

static void hangcheck_fire(struct timer_list *unused)
{
	unsigned long long cur_tsc, tsc_diff;

	cur_tsc = ktime_get_ns();

	if (cur_tsc > hangcheck_tsc)
		tsc_diff = cur_tsc - hangcheck_tsc;
	else
		tsc_diff = (cur_tsc + (~0ULL - hangcheck_tsc)); /* or something */

	if (tsc_diff > hangcheck_tsc_margin) {
		if (hangcheck_dump_tasks) {
			printk(KERN_CRIT "Hangcheck: Task state:\n");
#ifdef CONFIG_MAGIC_SYSRQ
			handle_sysrq('t');
#endif  /* CONFIG_MAGIC_SYSRQ */
		}
		if (hangcheck_reboot) {
			printk(KERN_CRIT "Hangcheck: hangcheck is restarting the machine.\n");
			emergency_restart();
		} else {
			printk(KERN_CRIT "Hangcheck: hangcheck value past margin!\n");
		}
	}
#if 0
	/*
	 * Enable to investigate delays in detail
	 */
	printk("Hangcheck: called %Ld ns since last time (%Ld ns overshoot)\n",
			tsc_diff, tsc_diff - hangcheck_tick*TIMER_FREQ);
#endif
	mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
	hangcheck_tsc = ktime_get_ns();
}


static int __init hangcheck_init(void)
{
	printk("Hangcheck: starting hangcheck timer %s (tick is %d seconds, margin is %d seconds).\n",
	       VERSION_STR, hangcheck_tick, hangcheck_margin);
	hangcheck_tsc_margin =
		(unsigned long long)hangcheck_margin + hangcheck_tick;
	hangcheck_tsc_margin *= TIMER_FREQ;

	hangcheck_tsc = ktime_get_ns();
	mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));

	return 0;
}


static void __exit hangcheck_exit(void)
{
	del_timer_sync(&hangcheck_ticktock);
        printk("Hangcheck: Stopped hangcheck timer.\n");
}

module_init(hangcheck_init);
module_exit(hangcheck_exit);

三、编写实验测试

1. 驱动程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include <linux/cdev.h>

#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/of_gpio.h>




/**
 * file name:timer
 * date: 2021-08-31  21:13
 * version:1.0
 * author:luatao
 * describe:timer device drive
 */


#define TIMER_CNT  1     /* 设备号个数 */
#define TIMER_NAME        "timer"      /* 设备名*/

#define LEDOFF 1  /* 关灯 */
#define LEDON  0  /* 开灯 */

#define CLOSE_CMD                     (_IO(0XEF, 0x1))    /* 关闭定时器 */
#define OPEN_CMD                      (_IO(0XEF, 0x2))    /* 打开定时器 */
#define SETPERIOD_CMD          		  (_IO(0XEF, 0x3))    /* 设置定时器周期指令  */

/* 设备结构体 自定义 */
struct timer_dev{
    dev_t devid;     /*设备号  */
    struct cdev cdev;  /* cdev */
    struct class *class;  /* 类*/
    struct device *device;  /* 设备 */
    int major;   /* 主设备号 */
    int minor;  /* 次设备号 */

    struct device_node *nd;  /* 设备节点 */
    int led_gpio;    /* timer所使用的GPIO编号 */

    int timeperiod;   /* 定时周期,单位为ms*/
  //  struct timer_list timer;  /* 定义一个定时器 */
    spinlock_t lock;  /* 自旋锁 */
 };

/* 定义一个设备结构体 */
struct timer_dev timer;   /* timer 设备 */

/* 定义定时器和回调函数 */
static void timer_function(struct timer_list *);
static DEFINE_TIMER(Timer, timer_function);  // Timer:是一个定时器对象  timer_function:回调函数

/* 定时器回掉函数 */
void timer_function(struct timer_list * arg)
{
    static int sta = 1;  // LED灯的状态
    int timeperiod;  // 定时器周期
    unsigned long flags;  // 中断标志

    sta = !sta;  // led状态翻转
    gpio_set_value(timer.led_gpio, sta);

    /* 重启定时器 */
     spin_lock_irqsave(&timer.lock, flags);  // 保存中断 上锁
     timeperiod = timer.timeperiod;  // 获取当前设置的周期
    spin_unlock_irqrestore(&timer.lock, flags);  // 恢复中断 并解锁

     mod_timer(&Timer, jiffies + msecs_to_jiffies(timeperiod)); // 设置定时器新的定时时间

}

/* 打开设备 */
static int timer_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &timer;  /* 设置私有数据 */

    /* 设置默认定时 周期 */
    timer.timeperiod = 1000;  // 默认为1s

    printk("timer open!\r\n");
    return 0;
}

/* 控制设备 */
static long timer_unlock_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int timeperiod;  // 周期
    unsigned long flags;  // 中断标志
    struct timer_dev *dev = filp->private_data;

    switch(cmd){
        case CLOSE_CMD:  /* 关闭定时器 */
                del_timer_sync(&Timer);  // 等待当前定时时间完成之后关闭定时器
                break;
        case OPEN_CMD: /* 打开定时器 */
                spin_lock_irqsave(&dev->lock, flags);  // 保存中断 上锁
                timeperiod = dev->timeperiod;  // 获取当前设置的周期
                spin_unlock_irqrestore(&dev->lock, flags);  // 恢复中断 并解锁

                mod_timer(&Timer, jiffies + msecs_to_jiffies(timeperiod)); // 设置定时器新的定时时间
                break;
        case SETPERIOD_CMD:  /* 设置定时器周期 */
                 spin_lock_irqsave(&dev->lock, flags);  // 保存中断 上锁
                dev->timeperiod = arg;  // 设置周期
                spin_unlock_irqrestore(&dev->lock, flags);  // 恢复中断 并解锁

                mod_timer(&Timer, jiffies + msecs_to_jiffies(arg)); // 设置定时器新的定时时间
                break;
        default:
                break;
    }
    return 0 ;
}

/* 释放设备 */
static int timer_release(struct inode *inode, struct file *filp)
{
    printk("timer release!\r\n");
    return 0;
}


/* 设备操作函数结构体  */
static struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_unlock_ioctl,
    .release = timer_release,
};

/* 驱动入口函数 */
static int __init timer_init(void)
{
    int ret;  // 返回值

    /* 初始化自旋锁 */
    spin_lock_init(&timer.lock);

    /* 获取设备数中的属性数据  */
    /* 1. 获取设备节点 /led*/
    timer.nd = of_find_node_by_path("/leds/blue_led");  // 通过绝对路径查找设备节点
    if(timer.nd == NULL){
        printk("led node no find!\r\n");
        return -EINVAL;  /* 无效参数 不知道这个返回值是啥意思,我觉得返回一个负数就可以,这个值是23,不知道有没有处理*/
    }

    /* 2. 获取设备树中的gpio属性 得到gpioled所使用的gpio编号 */
    timer.led_gpio = of_get_named_gpio(timer.nd, "gpios", 0);
    if(timer.led_gpio < 0 ){
         printk("can't get led-gpio\r\n");
        return -EINVAL;  /* 无效参数 不知道这个返回值是啥意思,我觉得返回一个负数就可以,这个值是23,不知道有没有处理*/
    }
    printk("led-gpio num = %d \r\n", timer.led_gpio);  // 打印获取的led-gpio属性值

    /* 申请IO */
     ret = gpio_request(timer.led_gpio, "led0");
    if(ret != 0){
        printk("gpio request failed!\r\n");
    }

    /* 3. 设置GPIO1_IO03为输出,并且输出高电平,默认关闭led灯 */
    ret = gpio_direction_output(timer.led_gpio, 1);
    if(ret < 0){
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */

    /* 1. 创建设备号 */
    if(timer.major){  // 定义了设备号 
        timer.devid = MKDEV(timer.major, 0 );  // 根据主设备号和次设备号合成设备号 
        register_chrdev_region(timer.devid, TIMER_CNT, TIMER_NAME);  // 注册设备号 
    }else{  // 没有定义设备号 动态生成 
        alloc_chrdev_region(&timer.devid,0,TIMER_CNT, TIMER_NAME ); // 申请设备号
        timer.major = MAJOR(timer.devid);  // 获取主设备号
        timer.minor = MINOR(timer.devid);  // 获取次设备号
    }
    printk("timer major = %d,minor = %d\r\n",timer.major, timer.minor);  // 打印主设备号和次设备号

    /* 2. 初始化 cdev */
    timer.cdev.owner = THIS_MODULE;  
    cdev_init(&timer.cdev, &timer_fops);  // 初始化cdev
    /* 3. 添加cdev */
    cdev_add(&timer.cdev, timer.devid, TIMER_CNT ); // 向linux系统添加cdev

     /* 自动创建设备节点文件 */
    /* 4. 创建类 */
    timer.class = class_create(THIS_MODULE, TIMER_NAME);  // 创建类 
    if(IS_ERR(timer.class)){
        return PTR_ERR(timer.class);
    }
    /* 5. 创建设备 */
    timer.device = device_create(timer.class, NULL, timer.devid, NULL, TIMER_NAME);
      if(IS_ERR(timer.device)){
        return PTR_ERR(timer.device);
    }

    return 0;
}


/* 驱动出口函数 */
static void __exit timer_exit(void)
{

    /* 释放IO */
    gpio_set_value(timer.led_gpio ,1) ; // 关闭LED
    gpio_free(timer.led_gpio);

    /* 删除定时器 */
    del_timer_sync(&Timer);  // 删除timer

    /*  注销字符设备驱动 */
    cdev_del(&timer.cdev);  /* 删除 cdev */
    unregister_chrdev_region(timer.devid, TIMER_CNT ); /* 注销设备号 */

    device_destroy(timer.class, timer.devid);  /* 注销设备  */
    class_destroy(timer.class);  /* 注销类 */


    printk("timer drive unregsister ok !\r\n");
}

/* 加载驱动入口和出口函数 */
module_init(timer_init);
module_exit(timer_exit);

/* LICENSE 和 AUTHOR 信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("luatao");

2. 应用程序

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#include <sys/ioctl.h>

/**
 * file name:timerApp
 * date: 2021-08-31  21:40
 * version:1.0
 * author:luatao
 * describe:定时器测试APP
 * 执行命令:./timerApp 
 */


#define CLOSE_CMD                     (_IO(0XEF, 0x1))    /* 关闭定时器 */
#define OPEN_CMD                      (_IO(0XEF, 0x2))    /* 打开定时器 */
#define SETPERIOD_CMD          		  (_IO(0XEF, 0x3))    /* 设置定时器周期指令  */


/* 主程序 */
int main(int argc, char *argv[])
{
    char *filename;  // 可执行文件名
    int fd,ret;  //  fd: 文件句柄 ret:函数操作返回值
    unsigned int cmd =0 ,arg = 0; // cmd:输入的命令 arg: 要设置的周期值 也就是定时时间
    unsigned char str[100]; 


    /* 先判断输入的参数 */
    if(argc !=  2){  // 本身文件名带1个 执行文件1个  
       printf("parameter error!\r\n");
       return -1;
    }

    /* 分析参数 ,提取有用的信息 */
    filename = argv[1];  // 可执行文件名 
    
    /* 打开key文件 */
    fd = open(filename, O_RDWR);  // 可读可写 
    if(fd < 0){
        printf("can't open file:%s\r\n",filename);
        return -1;
    }

    /* 循环操作定时器*/
    while(1){
            printf("Input CMD:");

            ret = scanf("%d", &cmd); // 输入一个命令 
            if(ret != 1){  // 参数输入错误
                fgets(str,100, stdin);   // 接收一个字符串 防止卡死
            }

            /* 判断执行什么命令 */
            switch(cmd){
                case 1:
                        cmd = CLOSE_CMD;  // 关闭定时器 
                        break;
                case 2:
                        cmd = OPEN_CMD; // 打开定时器
                        break;
                case 3:
                        cmd = SETPERIOD_CMD;  // 设置定时器周期
                        printf("input timer period:");
                        ret = scanf("%d",&arg);
                        if(ret != 1){
                           fgets(str,100, stdin);   // 接收一个字符串 防止卡死
                        }
            }

            ioctl(fd,cmd, arg);  /* 控制定时器*/
    }

    /* 关闭文件 */
    ret = close(fd);
    if(ret < 0){
        printf("can't close file %s \r\n", filename);
        return -1;
    }

    return 0;
}

3. 运行测试

按下面的步骤可以看出效果。

spi 指令操作架构 spd指令是什么意思_#define

输入“2”,打开定时器,此时 LED 灯就会以默认的 1 秒周期开始闪烁。

在输入“3”来设置定时周期,根据提示输入要设置的周期值,输入“500”,表示设置定时器周期值为 500ms,设置好以后 LED 灯就会以 500ms 为间隔,开始闪烁。

最后可以通过输入“1”来关闭定时器,

关闭定时器后输入2 可以继续打开定时器。