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.
In this article, we will uncover the inner workings of Linux Kernel Timers, discussing their role in managing time intervals, their diverse applications in device drivers, and the precise APIs available for their creation and manipulation.
We’ll walk you through code examples, demystifying the process of integrating timers into your own device drivers.
So, if you’ve ever wondered how Linux handles time-sensitive tasks, wished to automate actions after specific intervals, or simply wanted to enhance your understanding of device driver development, you’re in the right place.
You can also read kernel thread, Sysfs, Procfs, Workqueue, Completion, Softirq, and threaded IRQ in the Linux device driver.
Table of Contents
Timer
Introduction
What is a timer in general? According to Wikipedia, A timer is a specialized type of clock used for measuring specific time intervals. Timers can be categorized into two main types.
A timer that counts upwards from zero for measuring elapsed time is often called a stopwatch, while a device that counts down from a specified time interval is more usually called a timer.
Timer in Linux Kernel
In Linux, the kernel keeps track of the flow of time by means of timer interrupts. These timer interrupts are generated at regular timer intervals by using the system’s timing hardware.
Every time a timer interrupt occurs, the value of an internal kernel counter is incremented. The counter is initialized to 0 at the system boot, so it represents the number of clock ticks since the last boot.
The kernel timer offers less precision but is more efficient in situations where the timer will probably be canceled before it fires. There are many places in the kernel where timers are used to detect when a device or a network peer has failed to respond within the expected time.
When you want to do some action after some time, then kernel timers are one of the options for you. These timers are used to schedule the execution of a function at a particular time in the future, based on the clock tick, and can be used for a variety of tasks.
Uses of Kernel Timers
- Polling a device by checking its state at regular intervals when the hardware can’t fire interrupts.
- The user wants to send some messages to another device at regular intervals.
- Send an error when some action didn’t happen in a particular time period.
Kernel Timer API
Linux Kernel provides the driver to create timers that are not periodic by default, register the timers and delete the timers.
We need to include the <linux/timer.h>
(#include <linux/timer.h>
) in order to use kernel timers. Kernel timers are described by the timer_list structure, defined in <linux/timer.h>
:
struct timer_list { /* ... */ unsigned long expires; void (*function)(unsigned long); unsigned long data; };
The expires
field contains the expiration time of the timer (in jiffies).
On expiration, function()
will be called with the given data
value.
Initialize / Setup Kernel Timer
There are multiple ways to Initialize / Setup Kernel Timer. We’ll see one by one.
init_timer:
void fastcall init_timer ( struct timer_list * timer);
This function is used to initialize the timer. init_timer
must be done to a timer prior to calling any of the other timer functions. If you are using this function to initialize the timer, then you need to set the callback function
and data
of the timer_list
structure manually.
Argument:
timer
– the timer to be initialized
setup_timer:
void setup_timer(timer, function, data);
Instead of initializing the timer manually by calling init_timer
, you can use this function to set data
and function
of timer_list
structure and initialize the timer. This is recommended to use.
This API will be available for the older kernel version. If you are using the newer kernel, then you have to use the below API (timer_setup
).
Argument:
timer
– the timer to be initialized
function
– Callback function to be called when the timer expires. In this callback function, the argument will be unsigned long
.
data
– data has to be given to the callback function
Example:
/* setup your timer to call my_timer_callback */ setup_timer(&etx_timer, timer_callback, 0); //Timer Callback function. This will be called when timer expires void timer_callback(unsigned long data) { }
timer_setup:
If you use a newer kernel version, then setup_timer won’t work. So you need to use this timer_setup
function.
void timer_setup(timer, function, data);
Instead of initializing the timer manually by calling init_timer
, you can use this function to set data
and function
of timer_list
structure and initialize the timer. This is recommended to use.
Argument:
timer
– the timer to be initialized
function
– Callback function to be called when the timer expires. In this callback function, the argument will be struct timer_list *
.
data
– data has to be given to the callback function
Example:
/* setup your timer to call my_timer_callback */ timer_setup(&etx_timer, timer_callback, 0); //Timer Callback function. This will be called when timer expires void timer_callback(struct timer_list * data) { }
DEFINE_TIMER:
DEFINE_TIMER(_name, _function, _expires, _data)
If we are using this method, then no need to create the timer_list
structure on our side. The kernel will create the structure in the name of _name
and initialize it.
Argument:
_name
– name of the timer_list structure to be created
_function
– Callback function to be called when the timer expires
_expires
– the expiration time of the timer (in jiffies)
_data
– data has to be given to the callback function
Start a Kernel Timer
add_timer:
void add_timer(struct timer_list *timer);
This will start a timer.
Argument:
timer
– the timer needs to be started
Modifying Linux Kernel Timer’s timeout
mod_timer:
int mod_timer(struct timer_list * timer, unsigned long expires);
This function is used to modify a timer’s timeout. This is a more efficient way to update the expires
field of an active timer (if the timer is inactive it will be activated).
mod_timer(timer, expires)
is equivalent to:
del_timer(timer);
timer->expires = expires;
add_timer(timer);
Argument:
timer
– the timer needs to modify the timer period
expires
– the updated expiration time of the timer (in jiffies)
Return:
The function returns whether it has modified a pending timer or not.
0 – mod_timer
of an inactive timer
1 – mod_timer
of an active timer
Stop a Kernel Timer in Linux
The below functions will be used to deactivate the kernel timers.
del_timer:
int del_timer(struct timer_list * timer);
This will deactivate a timer. This works on both active and inactive timers.
Argument:
timer
– the timer needs to be deactivated
Return:
The function returns whether it has deactivated a pending timer or not.
0 – del_timer
of an inactive timer
1 – del_timer
of an active timer
del_timer_sync:
int del_timer_sync(struct timer_list * timer);
This will deactivate a timer and wait for the handler to finish. This works on both active and inactive timers.
Argument:
timer
– the timer needs to be deactivated
Return:
The function returns whether it has deactivated a pending timer or not.
0 – del_timer_syncof
an inactive timer
1 – del_timer_sync
of an active timer
Note: callers must prevent restarting the timer, otherwise this function is meaningless. It must not be called from interrupt contexts. The caller must not hold locks that would prevent the completion of the timer’s handler. The timer’s handler must not call add_timer_on. Upon exit, the timer is not queued and the handler is not running on any CPU.
Check the Linux Kernel Timer status
timer_pending:
int timer_pending(const struct timer_list * timer);
This will tell whether a given timer is currently pending, or not. Callers must ensure serialization wrt. other operations done to this timer, eg. interrupt contexts or other CPUs on SMP.
Argument:
timer
– the timer needs to check the status
Return:
The function returns whether the timer is pending or not.
0 – timer
is not pending
1 – timer
is pending
Device Driver Source Code
In this example, we took the basic driver source code from the Linux Device Driver Programming tutorial. On top of that code, we have added the timer. The steps are mentioned below.
- Initialize the timer and set the time interval
- After the timeout, a registered timer callback will be called.
- In the timer callback function again we are re-enabling the timer. We have to do this step if we want a periodic timer. Otherwise, we can ignore this.
- Once we have done this, we can disable the timer.
driver.c:
[Get the source code from GitHub]
/***************************************************************************//** * \file driver.c * * \details Simple Linux device driver (Kernel 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/timer.h> #include <linux/jiffies.h> #include <linux/err.h> //Timer Variable #define TIMEOUT 5000 //milliseconds static struct timer_list etx_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 void timer_callback(struct timer_list * data) { /* do your timer stuff here */ pr_info("Timer Callback function Called [%d]\n",count++); /* Re-enable timer. Because this function will be called only first time. If we re-enable this will work like periodic timer. */ mod_timer(&etx_timer, jiffies + msecs_to_jiffies(TIMEOUT)); } /* ** 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) { /*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; } /* setup your timer to call my_timer_callback */ timer_setup(&etx_timer, timer_callback, 0); //If you face some issues and using older kernel version, then you can try setup_timer API(Change Callback function's argument to unsingned long instead of struct timer_list *. /* setup timer interval to based on TIMEOUT Macro */ mod_timer(&etx_timer, jiffies + msecs_to_jiffies(TIMEOUT)); 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) { /* remove kernel timer when unloading module */ del_timer(&etx_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 - Kernel Timer"); MODULE_VERSION("1.21");
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
)
linux@embetronicx-VirtualBox: dmesg [ 2253.635127] Device Driver Insert...Done!!! [ 2258.642048] Timer Callback function Called [0] [ 2263.647050] Timer Callback function Called [1] [ 2268.652684] Timer Callback function Called [2] [ 2273.658274] Timer Callback function Called [3] [ 2278.663885] Timer Callback function Called [4] [ 2283.668997] Timer Callback function Called [5] [ 2288.675109] Timer Callback function Called [6] [ 2293.680160] Timer Callback function Called [7] [ 2298.685771] Timer Callback function Called [8] [ 2303.691392] Timer Callback function Called [9] [ 2308.697013] Timer Callback function Called [10] [ 2313.702033] Timer Callback function Called [11] [ 2318.707772] Timer Callback function Called [12]
- See timestamp. That callback function executes 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 the function in_ interrupt( )
, which takes no parameters and returns nonzero if the processor is currently running in an 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 see the High-Resolution Timer (hrtimer).
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!