Linux driver kernel timer driver design

Publisher:MysticJourneyLatest update time:2015-05-04 Source: 51heiKeywords:linux Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere
My environment:
Fedora 14 kernel version is 2.6.38.1
Development board: ARM9 TQ2440
Ported kernel version: linux-2.6.30.4
The timer is mainly implemented in the Linux kernel using a structure. However, it should be noted that the timer is an object that only runs once, that is, when a timer ends, it is necessary to add a timer again. However, the mod_timer() function can be used to dynamically change the timer's arrival time.
This driver mainly implements the basic operation of kernel timer. Kernel timer is mainly implemented by the following structure struct timer_list. The required header file includes #include, but it is not necessary to include this header file in the actual development process because it is included in sched.h.
    struct timer_list {
        struct list_head entry;
        unsigned long expires;
        void (*function)(unsigned long);
        unsigned long data;
        struct tvec_base *base;
    #ifdef CONFIG_TIMER_STATS
        void *start_site;
        char start_comm[16];
        int start_pid;
    #endif
    #ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
    #endif
    };
The implementation of the timer is mainly completed by filling the structure and coordinating some functions. The red parts are the main elements. 1. expires is mainly used to define the time when the timer expires. The value of this element is usually set by coordinating the global variables jiffies and HZ. For example, expires = jiffies + n*HZ, where jiffies is the number of ticks since startup and HZ is the number of ticks in one second.
2. Function is a function pointer. This function is the timer processing function, which is similar to the interrupt function in the interrupt. In fact, the timer and the interrupt have great similarities. The timer processing function is a self-defined function.
3. Data is usually used to implement parameter transfer. From the parameter type of function, we can know that data can be used as a parameter of the timer processing function.
Other elements can be initialized by kernel functions.
The initialization function is:
init_timer(struct timer_list * timer);
Or directly use the DEFINE_TIMER macro to implement definition and initialization operations.
    #define DEFINE_TIMER(_name, _function, _expires, _data)        
        struct timer_list _name =                
            TIMER_INITIALIZER(_function, _expires, _data)
Function to add timer to kernel:
    void add_timer(struct timer_list *timer)
    {
        BUG_ON(timer_pending(timer));
        mod_timer(timer, timer->expires);
    }
Delete the timer function. If the timer's timing has not arrived, the timer can be deleted:
int del_timer(struct timer_list *timer)
Modify the arrival time of the timer. The feature of this function is that no matter whether the timer reaches the time or not, it will re-add a timer to the kernel. Therefore, this function can be called in the timing processing function to modify the arrival time that needs to be redefined.
int mode_timer(struct timer_list *timer,unsigned long expires)
    int mod_timer(struct timer_list *timer, unsigned long expires)
    {
        /*
         * This is a common optimization triggered by the
         * networking code - if the timer is re-modified
         * to be the same thing then just return:
         */
        if (timer->expires == expires && timer_pending(timer))
            return 1;
        /*Note the calling condition, which means the current timer is the last one in the linked list*/
        return __mod_timer(timer, expires, false);
    }
    static inline int
    __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
    {
        struct tvec_base *base, *new_base;
        unsigned long flags;
        int ret;
        ret = 0;
        timer_stats_timer_set_start_info(timer);
        BUG_ON(!timer->function);
        base = lock_timer_base(timer, &flags);
        if (timer_pending(timer)) {
            detach_timer(timer, 0);
            ret = 1;
        } else {
            if (pending_only)
                goto out_unlock;
        }
        debug_timer_activate(timer);
        new_base = __get_cpu_var(tvec_bases);
        if (base != new_base) {
            /*
             * We are trying to schedule the timer on the local CPU.
             * However we can't change timer's base while it is running,
             * otherwise del_timer_sync() can't detect that the timer's
             * handler yet has not finished. This also guarantees that
             * the timer is serialized wrt itself.
             */
            if (likely(base->running_timer != timer)) {
                /* See the comment in lock_timer_base() */
                timer_set_base(timer, NULL);
                spin_unlock(&base->lock);
                base = new_base;
                spin_lock(&base->lock);
                timer_set_base(timer, base);
            }
        }
        timer->expires = expires;
        internal_add_timer(base, timer);
    out_unlock:
        spin_unlock_irqrestore(&base->lock, flags);
        return ret;
    }
    static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
    {
        unsigned long expires = timer->expires;
        unsigned long idx = expires - base->timer_jiffies;
        struct list_head *vec;
        if (idx < TVR_SIZE) {
            int i = expires & TVR_MASK;
            vec = base->tv1.vec + i;
        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
            int i = (expires >> TVR_BITS) & TVN_MASK;
            vec = base->tv2.vec + i;
        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
            int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
            vec = base->tv3.vec + i;
        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
            int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
            vec = base->tv4.vec + i;
        } else if ((signed long) idx < 0) {
            /*
             * Can happen if you add a timer with expires == jiffies,
             * or you set a timer to go off in the past
             */
            vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
        } else {
            int i;
            /* If the timeout is larger than 0xffffffff on 64-bit
             * architectures then we use the maximum timeout:
             */
            if (idx > 0xffffffffUL) {
                idx = 0xffffffffUL;
                expires = idx + base->timer_jiffies;
            }
            i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
            vec = base->tv5.vec + i;
        }
        /*
         * Timers are FIFO:
         */
        /*Added to the end of the linked list, this shows that mod_timer implements the operation of re-registering a timer*/
        list_add_tail(&timer->entry, vec);
    }
From the above analysis, we can see that the implementation process of mod_timer is relatively complicated, but it basically explains the operation process of mod_timer function to re-register the timer.
Generally speaking, the basic operations of the timer are mainly the above functions.
My kernel timer-based driver function is as follows, referring to Song Baohua's Linux Device Driver Development Detailed Explanation (Second Edition). [page]
driver:
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    /*Use macro definition to set the device's major device number*/
    #define SECOND_MAJOR 0
    /*Statically save the variables of static major device numbers*/
    static int second_major = SECOND_MAJOR;
    /*Device structure, usually contains the required devices in the device, such as character, block and other types*/
    struct second_dev{
        /*Add device type,
        I think we can adopt a union.
        Contains block devices or character devices, similar to the implementation method of inode,
        This can improve the versatility of the structure
        */
        struct cdev cdev;
        /*Atomic variable, used for statistics*/
        atomic_t counter;
        /*Add kernel timer structure variable*/
        struct timer_list s_timer;
        
        /*Device class used to dynamically create device files*/
        struct class *myclass;
    };
    /*Structure pointer or global variable can be used to directly define the structure*/
    struct second_dev *second_devp;
    /*If the timing time is reached, the timer processing function*/
    static void second_timer_handler(unsigned long arg)
    {
        /*
        Modify the expiration time in the timer and increase the time to 1s.
        It should be noted that the mod_timer function re-registers the timer to the kernel
        Regardless of whether the timer has been run
        */
        mod_timer(&second_devp->s_timer,jiffies + HZ);
        /*Increase atomic variable*/
        atomic_inc(&second_devp->counter);
        /*Output jiffies value*/
        printk(KERN_NOTICE "Current jiffies is %d ",jiffies);
    }
    /*open function implementation*/
    static int second_open(struct inode *inode,struct file *filp)
    {
        /* Initialize the defined kernel timer */
        init_timer(&second_devp->s_timer);
        /*Specify the kernel timer processing function to be the function defined above*/
        second_devp->s_timer.function = second_timer_handler;
        /*Specify the timing interval to be 1s*/
        second_devp->s_timer.expires = jiffies + HZ;
        /*Add timer to kernel*/
        add_timer(&second_devp->s_timer);
        /*At the same time, the device-related statistics value is 0*/
        atomic_set(&second_devp->counter,0);
        return 0;
    }
    /*Release function implementation*/
    static int second_release(struct inode *inode,struct file *filp)
    {
        /*If the time is not reached, turn off the device and delete the timer directly*/
        del_timer(&second_devp->s_timer);
        return 0;
    }
    /*read function implementation*/
    static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
    {
        int counter;
        
        /*Read the current value*/
        counter = atomic_read(&second_devp->counter);
        
         /*
         Use put_user to transfer values
         The put_user function checks the pointer variable.
         Therefore, there is no need to check whether the pointer is correct.
         */
        if(put_user(counter,(int *)buf))
            return -EFAULT;
        else
            /*Return data size*/
            return sizeof(unsigned int);
    }
    /*Specific file operation set*/
    static const struct file_operations second_fops =
    {
        /*This is the owner*/
        .owner = THIS_MODULE,
        .open = second_open,
        .release = second_release,
        .read = second_read,
    };
    /* Initialization function */
    static int __init second_init(void)
    {
        int ret;
        /*Application and creation of device number*/
        dev_t devno = MKDEV(second_major,0);
        /*Statically apply for device number*/
        if(second_major)
        {
            ret = register_chrdev_region(devno,1,"second");
        }
        /*Dynamically apply for device number*/
        else
        {
            ret = alloc_chrdev_region(&devno,0,1,"second");
            second_major = MAJOR(devno);
        }
        if(ret < 0)
        {
            return ret;
        }
        
        /* Allocate address space for device structure */
        second_devp = kmalloc(sizeof(struct second_dev),GFP_KERNEL);
        /* Check if the allocation is correct */
        if(!second_devp)
        {
            ret = -ENOMEM;
            goto fail_malloc;
        }
        /*Clear the allocated space*/
        memset(second_devp,0,sizeof(struct second_dev));
        /*Create a device class to automatically create device files*/
        second_devp->myclass = class_create(THIS_MODULE,"second_timer_class");
        
        /*Initialize character device and bind related operations to the device*/
        cdev_init(&second_devp->cdev,&second_fops);
        /*Owner of the device*/
        second_devp->cdev.owner = THIS_MODULE,
        /*Add device to kernel*/
        ret = cdev_add(&second_devp->cdev,devno,1);
        
        /* Error handling */
        if(ret)
        {
            printk(KERN_NOTICE "ERROR %d ",ret);
            goto fail_malloc;
        }
        /*Create a device based on the previously created device class*/
        device_create(second_devp->myclass,NULL,devno,NULL,"second%d",0);
        return 0;
    /*Error operation*/
    fail_malloc:
        unregister_chrdev_region(devno,1);
        return ret;
    }
    /*Exit function*/
    static void __exit second_exit(void)
    {
        /*Release the device*/
        device_destroy(second_devp->myclass,MKDEV(second_major,0));
        /*Delete character device*/
        cdev_del(&second_devp->cdev);
        /*Release device class*/
        class_destroy(second_devp->myclass);
        /*Release the allocated memory size*/    
        kfree(second_devp);
        /*Release device number*/
        unregister_chrdev_region(MKDEV(second_major,0),1);
    }
    /*Unload and load*/
    module_init(second_init);
    module_exit(second_exit);
    /*LICENSE and author information*/
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("GP-");
app:
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
        int fd;
        int counter = 0;
        int old_counter = 0;
        
        fd = open("/dev/second0",O_RDONLY);
        if(fd != -1)
        {
            while(1)
            {
                read(fd,&counter,sizeof(unsigned int));
                if(counter != old_counter)
                {
                    printf("second after open /dev/second0 : %d ",counter);
                    old_counter = counter;
                }
            }
        }
        else
        {
            printf("Device open failure ");
            exit(1);
        }
        exit(0);
    }
Experimental results:
[root@EmbedSky Test]# ./app-timer                                               
Current jiffies is 2137721                                                      
second after open /dev/second0 : 1                                              
Current jiffies is 2137921                                                      
second after open /dev/second0 : 2                                              
Current jiffies is 2138121                                                      
second after open /dev/second0 : 3                                              
Current jiffies is 2138321                                                      
second after open /dev/second0 : 4                                              
Current jiffies is 2138521                                                      
second after open /dev/second0 : 5                                              
Current jiffies is 2138721                                                      
second after open /dev/second0 : 6   
The above results show that the kernel timer basically achieves the effect, but from the experimental results it seems to be displayed once every two seconds. The specific reason needs to be further analyzed, because the HZ in arm should be 100, not 200.
Keywords:linux Reference address:Linux driver kernel timer driver design

Previous article:Summary of bus device driver model
Next article:S3C2440 touch screen driver code analysis

Recommended ReadingLatest update time:2024-11-15 07:25

Choosing Driver Topology in LED Design
Over the past few years, the performance of high-brightness light-emitting diodes (HB- LEDs ) has rapidly improved in terms of lumen output per package and luminous efficacy (in lm/W). Commercial 1W LEDs have provided lumen output per package of more than 1001m and luminous efficacy of 1001m/W for cool color temperatu
[Power Management]
Domestic W806 SPI master/slave driver
After a period of chip panic, foreign chip prices soared, and domestic chip manufacturers suddenly rose. Taking this opportunity, we used the chip to make a batch of products. After a period of testing, the chip stability was good, and there was no reset crash when working in the power supply monitoring in a poor elec
[Microcontroller]
Domestic W806 SPI master/slave driver
24c02 c51 driver
#include AT89X51.H //#include stdio.h //#include absacc.h #include intrins.h #define uchar unsigned char #define uint unsigned int sbit led=P1^0; sbit led2=P1^1; sbit SCL=P3^5; //24c02 SCL sbit SDA=P3^4; //24c02 SDA //sbit DOG=P1^7; //狗 uchar x24c02_read(uchar address); //Read a byte of data from the address add
[Microcontroller]
24c02 c51 driver
Analysis of Linux serial port driver
1. Data structure in serial port driver • UART driver structure: struct uart_driver driver • UART port structure: struct uart_port serial port • UART related operation function structure: struct uart_ops serial port operation function set • UART status structure: struct uart_state serial port sta
[Microcontroller]
Analysis of Linux serial port driver
Two-way motor drive H-bridge L9110
#include reg52.h //LMD298 input control terminal definition sbit IN1 = P1^0; sbit IN2 = P1^1;         sbit IN3 = P1^2;         sbit IN4 = P1^3; sbit ENA = P1^4;         sbit ENB = P1^5; sbit CW = P3^0; //Forward run button sbit STOP = P3^1; //Stop button sbit CCW = P3^2; //Reverse run button sbit ADJ = P3^3; //
[Microcontroller]
Two-way motor drive H-bridge L9110
TDK magnet solutions improve EV drive motor efficiency
Electric vehicles (EVs) have been widely promoted as a solution to global environmental issues. However, many technical barriers must be cleared before EVs can be fully operated safely and comfortably around the world. One solution is to improve the performance of the drive motor (downsizing, weight reduction, and eff
[Power Management]
TDK magnet solutions improve EV drive motor efficiency
Bus Device Driver Model
led_dev.c 1 #include linux/module.h 2 #include linux/version.h 3 4 #include linux/init.h 5 6 #include linux/kernel.h 7 #include linux/types.h 8 #include linux/interrupt.h 9 #include linux/list.h 10 #include linux/timer.h 11 #include linux/init.h
[Microcontroller]
LED Driver Design for High Power LED Bulb Replacement Applications
This article introduces an LED driver reference design for high-power LED bulb replacement applications. This driver can provide the required power for LED replacement lamps for 100W A19 incandescent bulbs. It is a non-isolated, high-efficiency (about 93%), high power factor (PF) LED driver that fits neatly into the A
[Analog Electronics]
LED Driver Design for High Power LED Bulb Replacement Applications
Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号