High Resolution Timer In Linux – Linux Device Driver Tutorial Part 27

This article is a continuation of the  Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the high-resolution timer In Linux Device Driver using Raspberry PI – Linux Device Driver Tutorial Part 27.

High Resolution Timer (HRT/hrtimer)

In our last tutorial, we have seen a kernel timer. Now we are talking about the high-resolution timer. Everyone might have some questions. Why the hell do we need two timers? Why can they merge two timers into one? Can’t able to integrate? Yes. They have tried to merge these two timers. But they have failed. Because Cascading Timer Wheel (CTW) is used in the kernel timer. Cascading Timer Wheel (CTW) code is fundamentally not suitable for such an approach as merging these two timers. Because hrtimer is maintaining a time-ordered data structure of timers (timers are inserted in time order to minimize processing at activation time). The data structure used is a red-black tree, which is ideal for performance-focused applications (and happens to be available generically as a library within the kernel).

Kernel Timers are bound to jiffies. But this High Resolution Timer (HRT) is bound with 64-bit nanoseconds resolution.

With kernel version 2.6.21 onwards, high resolution timers (HRT) are available under Linux. For this, the kernel has to be compiled with the configuration parameter CONFIG_HIGH_RES_TIMERS enabled.

There are many ways to check whether high resolution timers are available,

  • In the /boot directory, check the kernel config file. It should have a line like CONFIG_HIGH_RES_TIMERS=y.
  • Check the contents of /proc/timer_list. For example, the .resolution entry showing 1 nanosecond and event_handler as hrtimer_interrupt in /proc/timer_list indicate that high resolution timers are available.
  • Get the clock resolution using the clock_getres system call.

Users of High Resolution Timer

  • The primary users of precision timers are user-space applications that utilize nanosleep, posix-timers, and Interval Timer (itimer) interfaces.
  • In kernel, users like drivers and subsystems require precisely timed events (e.g. multimedia).

High Resolution timer API

We need to include the <linux/hrtimer.h> (#include <linux/hrtimer.h>) in order to use kernel timers. Kernel timers are described by the hrtimer structure, defined in <linux/hrtimer.h>:

struct hrtimer {
  struct rb_node node;
  ktime_t expires;
  int (* function) (struct hrtimer *);
  struct hrtimer_base * base;
};

Where,

node – red black tree node for time ordered insertion

expires – the absolute expiry time in the hr timers internal representation. The time is related to the clock on which the timer is based.

function – timer expiry callback function. This function has an integer return value, which should be either HRTIMER_NORESTART (for a one-shot timer that should not be started again) or HRTIMER_RESTART for a recurring timer. In the restart case, the callback must set a new expiration time before returning.

base – pointer to the timer base (per CPU and per clock)

The hrtimer structure must be initialized by init_hrtimer_#CLOCKTYPE.

ktime_set

There is a new type, ktime_t, which is used to store a time value in nanoseconds. On 64-bit systems, a ktime_t is really just a 64-bit integer value in nanoseconds. On 32-bit machines, however, it is a two-field structure: one 32-bit value holds the number of seconds, and the other holds nanoseconds. The below function is used to get the ktime_t from seconds and nanoseconds.

ktime_set(long secs, long nanosecs);

Arguments:

secs – seconds to set

nsecs – nanoseconds to set

Return:

The ktime_t representation of the value.

Initialize High Resolution Timer

hrtimer_init

void hrtimer_init( struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode );

Arguments:

timer – the timer to be initialized

clock_id – the clock to be used

The clock to use is defined in ./include/linux/time.h and represents the various clocks that the system supports (such as the real-time clock or a monotonic clock that simply represents the time from a starting point, such as system boot).

CLOCK_MONOTONIC: a clock that is guaranteed always to move forward in time, but which does not reflect “wall clock time” in any specific way. In the current implementation, CLOCK_MONOTONIC resembles the jiffies tick count in that it starts at zero when the system boots and increases monotonically from there.

CLOCK_REALTIME: which matches the current real-world time.

mode – timer mode absolute (HRTIMER_MODE_ABS) or relative (HRTIMER_MODE_REL)

Start High Resolution Timer

Once a timer has been initialized, it can be started with the below-mentioned function.

hrtimer_start

int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);

This call is used to (Re)start an hrtimer on the current CPU.

Arguments:

timer – the timer to be added

time – expiry time

mode – expiry mode: absolute (HRTIMER_MODE_ABS) or relative (HRTIMER_MODE_REL)

Returns:

0 on success 1 when the timer was active

Stop High Resolution Timer

Using the below function, we can able to stop the High Resolution Timer.

hrtimer_cancel

int hrtimer_cancel(struct hrtimer * timer);

This will cancel a timer and wait for the handler to finish.

Arguments:

timer – the timer to be canceled

Returns:

  • 0 when the timer was not active
  • 1 when the timer was active

hrtimer_try_to_cancel

int hrtimer_try_to_cancel(struct hrtimer * timer);

This will try to deactivate a timer.

Arguments:

timer – hrtimer to stop

Returns:

  • 0 when the timer was not active
  • 1 when the timer was active
  • -1 when the timer is currently executing the callback function and cannot be stopped

Changing the High Resolution Timer’s Timeout

If we are using this High Resolution Timer (hrtimer) as a periodic timer, then the callback must set a new expiration time before returning. Usually, restarting timers are used by kernel subsystems that need a callback at a regular interval.

hrtimer_forward

u64 hrtimer_forward(struct hrtimer * timer, ktime_t now, ktime_t interval);

This will forward the timer expiry so it will expire in the future by the given interval.

Arguments:

timer – hrtimer to forward

now – forward past this time

interval – the interval to forward

Returns:

Returns the number of overruns.

hrtimer_forward_now

u64 hrtimer_forward_now(struct hrtimer *timer, ktime_t interval);

This will forward the timer expiry so it will expire in the future from now by the given interval.

Arguments:

timer – hrtimer to forward

interval – the interval to forward

Returns:

Returns the number of overruns.

Check High Resolution Timer’s status

The below-explained functions are used to get the status and timings.

hrtimer_get_remaining

ktime_t hrtimer_get_remaining(const struct hrtimer * timer);

This is used to get the remaining time for the timer.

Arguments:

timer – hrtimer to get the remaining time

Returns:

Returns the remaining time.

hrtimer_callback_running

int hrtimer_callback_running(struct hrtimer *timer);

This is the helper function to check, whether the timer is running the callback function.

Arguments:

timer – hrtimer to check

Returns:

  • 0 when the timer’s callback function is not running
  • 1 when the timer’s callback function is running

hrtimer_cb_get_time

ktime_t hrtimer_cb_get_time(struct hrtimer *timer);

This function used to get the current time of the given timer.

Arguments:

timer – hrtimer to get the time

Returns:

Returns the time.

Using High Resolution Timer In Linux Device Driver

In this example, we took the basic driver source code from this tutorial. On top of that code, we have added the high resolution timer. The steps are mentioned below.

  1. Initialize and start the timer in the init function
  2. After the timeout, a registered timer callback will be called.
  3. In the timer callback function again we are forwarding the time period and return HRTIMER_RESTART. We have to do this step if we want a periodic timer. Otherwise, we can ignore that time forwarding and return HRTIMER_NORESTART.
  4. Once we are done, we can disable the timer.

In this example, We are setting 5 seconds timeout using ktime_set(4, 1000000000). That 4 for 4 seconds and 1000000000 (nano sec) for 1 second.

Driver Source Code

driver.c:

[Get the source code from the GitHub]

/***************************************************************************//**
*  \file       driver.c
*
*  \details    Simple Linux device driver (High Resolution Timer)
*
*  \author     EmbeTronicX
*
*  \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/err.h>
//Timer Variable
#define TIMEOUT_NSEC   ( 1000000000L )      //1 second in nano seconds
#define TIMEOUT_SEC    ( 4 )                //4 seconds

static struct hrtimer etx_hr_timer;
static unsigned int count = 0;
 
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
 
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);

/*************** Driver functions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, 
                                char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, 
                                const char *buf, size_t len, loff_t * off);
/******************************************************/

//File operation structure  
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .release        = etx_release,
};
 
//Timer Callback function. This will be called when timer expires
enum hrtimer_restart timer_callback(struct hrtimer *timer)
{
     /* do your timer stuff here */
    pr_info("Timer Callback function Called [%d]\n",count++);
    hrtimer_forward_now(timer,ktime_set(TIMEOUT_SEC, TIMEOUT_NSEC));
    return HRTIMER_RESTART;
}

/*
** This function will be called when we open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
    pr_info("Device File Opened...!!!\n");
    return 0;
}

/*
** This function will be called when we close the Device file
*/ 
static int etx_release(struct inode *inode, struct file *file)
{
    pr_info("Device File Closed...!!!\n");
    return 0;
}

/*
** This function will be called when we read the Device file
*/
static ssize_t etx_read(struct file *filp, 
                                char __user *buf, size_t len, loff_t *off)
{
    pr_info("Read Function\n");
    return 0;
}

/*
** This function will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, 
                                const char __user *buf, size_t len, loff_t *off)
{
    pr_info("Write function\n");
    return len;
}

/*
** Module Init function
*/ 
static int __init etx_driver_init(void)
{
     ktime_t ktime;
    
    /*Allocating Major number*/
    if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
            pr_err("Cannot allocate major number\n");
            return -1;
    }
    pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
 
    /*Creating cdev structure*/
    cdev_init(&etx_cdev,&fops);
 
    /*Adding character device to the system*/
    if((cdev_add(&etx_cdev,dev,1)) < 0){
        pr_err("Cannot add the device to the system\n");
        goto r_class;
    }
 
    /*Creating struct class*/
    if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
        pr_err("Cannot create the struct class\n");
        goto r_class;
    }
 
    /*Creating device*/
    if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
        pr_err("Cannot create the Device 1\n");
        goto r_device;
    }
    
    ktime = ktime_set(TIMEOUT_SEC, TIMEOUT_NSEC);
    hrtimer_init(&etx_hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    etx_hr_timer.function = &timer_callback;
    hrtimer_start( &etx_hr_timer, ktime, HRTIMER_MODE_REL);
 
    pr_info("Device Driver Insert...Done!!!\n");
    return 0;
r_device:
    class_destroy(dev_class);
r_class:
    unregister_chrdev_region(dev,1);
    return -1;
}

/*
** Module exit function
*/ 
static void __exit etx_driver_exit(void)
{
    //stop the timer
    hrtimer_cancel(&etx_hr_timer);
    device_destroy(dev_class,dev); 
    class_destroy(dev_class);
    cdev_del(&etx_cdev);
    unregister_chrdev_region(dev, 1);
    pr_info("Device Driver Remove...Done!!!\n");
}
 
module_init(etx_driver_init);
module_exit(etx_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <[email protected]>");
MODULE_DESCRIPTION("A simple device driver - High Resolution Timer");
MODULE_VERSION("1.22");

Makefile:

obj-m += driver.o
 
KDIR = /lib/modules/$(shell uname -r)/build
 
 
all:
  make -C $(KDIR)  M=$(shell pwd) modules
 
clean:
  make -C $(KDIR)  M=$(shell pwd) clean

Building and Testing Driver

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod driver.ko
  • Now see the Dmesg (dmesg)
[  +0.000026] Device Driver Insert...Done!!!
[  +5.000073] Timer Callback function Called [0]
[  +5.000052] Timer Callback function Called [1]
[Apr24 15:05] Timer Callback function Called [2]
[  +5.000057] Timer Callback function Called [3]
[  +5.000058] Timer Callback function Called [4]
[  +5.000060] Timer Callback function Called [5]
[  +5.000053] Timer Callback function Called [6]
[  +5.000058] Timer Callback function Called [7]
[  +5.000064] Timer Callback function Called [8]
  • See timestamp. That callback function is executing every 5 seconds.
  • Unload the module using sudo rmmod driver

Points to remember

This timer callback function will be executed from the interrupt context. If you want to check that, you can use function in_ interrupt( ), which takes no parameters and returns nonzero if the processor is currently running in interrupt context, either hardware interrupt or software interrupt. Since it is running in an interrupt context, the user cannot perform some actions inside the callback function mentioned below.

  • Go to sleep or relinquish the processor
  • Acquire a mutex
  • Perform time-consuming tasks
  • Access user space virtual memory

In our next tutorial, we will discuss completion in the Linux device driver.

Please find the other Linux device driver tutorials here.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Table of Contents