1. Concept
The kernel timer is a mechanism used by the kernel to control the scheduling and execution of a function at a certain point in the future (based on the current time of jiffies). The scheduled function is executed asynchronously, similar to a "software interrupt", and is In a non-process context, the scheduling function needs to follow the following rules:
1) No current pointer, no access to user space. Because there is no process context, the associated code has no connection to the interrupted process.
2) Sleep (or functions that may cause sleep) and scheduling cannot be performed.
3) Any data structure being accessed should be protected against concurrent access to prevent race conditions.
After the scheduling function of the kernel timer runs once, it will not be run again (equivalent to automatic logout), but it can be run periodically by rescheduling itself in the scheduled function .
Kernel timer data structure
struct timer_list { struct list_head entry; unsigned long expires; //Indicates the jiffies value expected to be executed by the timer. When the jiffies value is reached, the function function will be called and data will be passed as a parameter void (*function)(unsigned long); unsigned long data; struct tvec_base *base; /* ... */ };
2. Button + Kernel Timer Debounce Code Download Click to open the link
1. Define struct timer_list buttons_timer; 2. Initialize init_timer (&buttons_timer); 3. Register the timer add_timer (&buttons_timer); 4. Start the timer mod_timer (&buttons_timer, jiffies + (HZ / 10)); /* delay 1s/10=100ms */ 5. Log out the timer del_timer ( &buttons_timer); |
#include <linux/module.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/slab.h>
#define GPH0CON 0xE0200C00
#define GPH0DAT 0xE0200C04
#define DEVICE_NAME "tqkey"
#define LEDCON 0xE0200060
#define LEDDAT 0xE0200064
volatile unsigned int *led_config;
volatile unsigned int *led_data;
volatile unsigned int *key_data;
//1, define the work
struct work_struct *work;
//(Timer) 1. Define the timer structure
struct timer_list buttons_timer;
void work_func(struct work_struct *work)
{
/*Start the timer*/ /*Delay 1s/10=100ms */
mod_timer(&buttons_timer, jiffies + (HZ / 10));
}
//(timer) 5, function
static void buttons_timer_function(unsigned long data)
{
unsigned int key_val;
key_val = readw(key_data) & 0x1; //GPH0_0引角
if(key_val == 0) //The key is pressed to low level
{
volatile unsigned short data;
data = readw(led_data);
if(data == 0)
{
data = 0xFF;
}
else
{
data = 0;
}
writel(data, led_data);
}
}
void timer_init(void)
{
//(timer) 2, initialization
init_timer(&buttons_timer);
buttons_timer.function = &buttons_timer_function;
//(Timer) 3. Register the timer with the kernel
add_timer(&buttons_timer);
}
static irqreturn_t key_int(int irq, void *dev_id)
{
//3. Submit the second half to the kernel default work queue keventd_wq
schedule_work(work);
return 0;
}
void key_hw_init(void)
{
volatile unsigned short data;
volatile unsigned int *gpio_config;
gpio_config = (volatile unsigned int *)ioremap(GPH0CON, 4);
data = readw(gpio_config); //Read the value in the original register
data &= ~0x0F;
data |= 0x0F;
writew(data, gpio_config);
printk("key_hw_init!\n");
led_config = (volatile unsigned int *)ioremap(LEDCON, 4); //Convert physical address to virtual address
writel(0x00011000, led_config);
led_data = (volatile unsigned int *)ioremap(LEDDAT, 4);
writel(0xFF, led_data);
key_data = (volatile unsigned int *)ioremap(GPH0DAT, 4);
}
/*static long key_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return -EINVAL;
}*/
static int key_open(struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations key_fops =
{
.owner = THIS_MODULE,
//.unlocked_ioctl = key_ioctl,
.open = key_open,
.release = NULL,
};
struct miscdevice key_miscdev =
{
.minor = 200,
.name = DEVICE_NAME,
.fops = &key_fops,
};
//register function
static int __init button_init(void)
{
int ret = 0;
misc_register(&key_miscdev);
//register interrupt handler
ret = request_irq(IRQ_EINT0, key_int, IRQF_TRIGGER_FALLING, DEVICE_NAME, 0);
//hardware initialization
key_hw_init();
//2, work initialization
work = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
//(Note: GFP_KERNEL is the most commonly used in kernel memory allocation, it can cause hibernation when no memory is available)
INIT_WORK(work, work_func); //Create work, associate work function
// Kernel timer initialization
timer_init();
return 0;
}
//logout function
static void __exit button_exit(void)
{
misc_deregister(&key_miscdev);
// log out of the interrupt program
free_irq(IRQ_EINT0, 0);
//log out the timer
del_timer(&buttons_timer);
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jerry.Gou");
MODULE_DESCRIPTION("TQ210 button driver");
Multi-key interrupt
#include <linux/module.h> #include <linux/device.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/slab.h> #define GPH0CON 0xE0200C00 #define GPH0DAT 0xE0200C04 #define DEVICE_NAME "tqkey" #define LEDCON 0xE0200060 #define LEDDAT 0xE0200064 volatile unsigned int *led_config; volatile unsigned int *led_data; volatile unsigned int *key_data; //1, define the work struct work_struct *work; //(Timer) 1. Define the timer structure struct timer_list buttons_timer; void work_func(struct work_struct *work) { /*Start the timer*/ /*Delay 1s/10=100ms */ mod_timer(&buttons_timer, jiffies + (HZ / 10)); } //(timer) 5, function static void buttons_timer_function(unsigned long data) { unsigned int key_val; volatile unsigned short leddata; key_val = readw(key_data) & 0x1; //GPH0_0 Key_1引角 if(key_val == 0) //The key is pressed to low level { leddata = 0xFF; // light up the LED writel(leddata, led_data); } key_val = readw(key_data) & 0x02; //GPH0_0 Key_2引角 if(key_val == 0) //The key is pressed to low level { leddata = 0x00; // turn off the LED writel(leddata, led_data); } } void timer_init(void) { //(timer) 2, initialization init_timer(&buttons_timer); buttons_timer.function = &buttons_timer_function; //(Timer) 3. Register the timer with the kernel add_timer(&buttons_timer); } static irqreturn_t key_int(int irq, void *dev_id) { //3. Submit the second half to the kernel default work queue keventd_wq schedule_work(work); return 0; } void key_hw_init(void) { volatile unsigned short data; volatile unsigned int *gpio_config; gpio_config = (volatile unsigned int *)ioremap(GPH0CON, 4); data = readw(gpio_config); //Read the value in the original register data &= ~0xFF; data |= 0xFF; writew(data, gpio_config); printk("key_hw_init!\n"); led_config = (volatile unsigned int *)ioremap(LEDCON, 4); //Convert physical address to virtual address writel(0x00011000, led_config); led_data = (volatile unsigned int *)ioremap(LEDDAT, 4); writel(0xFF, led_data); key_data = (volatile unsigned int *)ioremap(GPH0DAT, 4); } /*static long key_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return -EINVAL; }*/ static int key_open(struct inode *inode, struct file *file) { return 0; } static struct file_operations key_fops = { .owner = THIS_MODULE, //.unlocked_ioctl = key_ioctl, .open = key_open, .release = NULL, }; struct miscdevice key_miscdev = { .minor = 200, .name = DEVICE_NAME, .fops = &key_fops, }; //register function static int __init button_init(void) { int ret = 0; misc_register(&key_miscdev); //register interrupt handler ret = request_irq(IRQ_EINT0, key_int, IRQF_TRIGGER_FALLING, DEVICE_NAME, 0); ret = request_irq(IRQ_EINT1, key_int, IRQF_TRIGGER_FALLING, DEVICE_NAME, 0); //hardware initialization key_hw_init(); //2, work initialization work = kmalloc(sizeof(struct work_struct), GFP_KERNEL); //(Note: GFP_KERNEL is the most commonly used in kernel memory allocation, it can cause hibernation when no memory is available) INIT_WORK(work, work_func); //Create work, associate work function // Kernel timer initialization timer_init(); return 0; } //logout function static void __exit button_exit(void) { misc_deregister(&key_miscdev); // log out of the interrupt program free_irq(IRQ_EINT0, 0); } module_init(button_init); module_exit(button_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jerry.Gou"); MODULE_DESCRIPTION("TQ210 button driver");
implement a timer
/* Print a message to the kernel log every second */ /*__func__ The current function name __TIME__ source file compilation time, the format is micro "hh:mm:ss"*/ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/time.h> #include <linux/timer.h> static struct timer_list tm; struct timeval oldtv; void callback(unsigned long arg) { struct timeval tv; char *strp = (char*)arg; printk("%s: %lu, %s\n", __func__, jiffies, strp); do_gettimeofday(&tv); printk("%s: %ld, %ld\n", __func__, tv.tv_sec - oldtv.tv_sec, //interval s from the last interruption tv.tv_usec- oldtv.tv_usec); //interval ms from the last interrupt oldtv = tv; tm.expires = jiffies+1*HZ; add_timer(&tm); //Restart timing } static int __init demo_init(void) { printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__); init_timer(&tm); //Initialize the kernel timer do_gettimeofday(&oldtv); //Get the current time tm.function=callback; //The callback function after the specified time is up tm.data = (unsigned long)"hello world"; //Parameters of the callback function tm.expires = jiffies+1*HZ; // Timing time add_timer(&tm); //Register the timer return 0; } static void __exit demo_exit(void) { printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__); del_timer(&tm); //Log out the timer } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jerry.Gou"); MODULE_DESCRIPTION("TQ210 button driver");