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 likeCONFIG_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.
- Initialize and start the timer in the init function
- After the timeout, a registered timer callback will be called.
- 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 returnHRTIMER_NORESTART
. - 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.

Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!