Kernel Driver - Kernel Timer

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");




Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325688038&siteId=291194637