Punctual Atomic Embedded Linux Driver Development – Linux Kernel Timer

Timer is the most commonly used function. It is generally used to complete timing functions. In this chapter, we will learn about the timer API functions provided by the Linux kernel. Through these timer API functions, many applications that require timing can be completed. . The Linux kernel also provides short delay functions, such as microsecond, nanosecond, and millisecond delay functions. In this chapter, we will learn about these time-related functions.

Introduction to Linux time management and kernel timers

Introduction to kernel time management

FreeRTOS requires a hardware timer to provide the system clock, and Systick is generally used as the system clock source. In the same way, Linux also needs a system clock to run. As for which timer this system clock is provided by, Punctual Atomic’s tutorial does not study the Linux kernel, but there is a general timer in the Cortex-A7 kernel. It is possible to use this, but you are not sure. You can study the Linux kernel by yourself.

There are a large number of functions in the Linux kernel that require time management, such as periodic schedulers, delay programs, and timers, which are most commonly used by driver writers. The hardware timer provides a clock source, and the frequency of the clock source can be set. Once set, a scheduled interrupt will be generated periodically, and the system uses scheduled interrupts for timing. The frequency at which interrupts are periodically generated is the system frequency, also called the tick rate (some information is also called the system frequency). For example, 100Hz, 1000Hz, etc. are the system tick rate. The system beat rate can be set, and the unit is Hz. When compiling the Linux kernel, you can set the system beat rate through the graphical interface. Open the configuration interface according to the following path:

-> Kernel Features
-> Timer frequency ( [=y])

Select “Timer frequency” and open it as shown below:
System beat rate setting
As can be seen from the picture above, the available system beat rates are 100Hz, 200Hz, 250Hz, 300Hz, 500Hz and 1000Hz, with 100Hz selected by default. After setting up, open the .config file in the root directory of the Linux kernel source code. In this file, there are definitions as shown below:
System beat rate
CONFIG_HZ in the picture above is 100. The Linux kernel will use CONFIG_HZ to set its own system clock. Open the file include/asm-generic/param.h, which contains the following content:
include/asm-generic/param.h file code segment
Line 7 defines a macro HZ, which is CONFIG_HZ, so HZ=100. HZ will be often used when writing the Linux driver later, because HZ represents the number of beats in one second, which is the frequency.

100Hz is the smallest among the selectable beat rates. Why not choose a bigger one? This leads to a question: high beat rate and low beat rate
Advantages and disadvantages of shooting rate:

  1. A high beat rate will improve the system time accuracy. If a beat rate of 100Hz is used, the time accuracy is 10ms. If a 1000Hz beat rate is used, the time accuracy is 1ms, which increases the accuracy by 10 times. There are many benefits of a high-precision clock. For functions that have strict time requirements, they can run with higher precision and time measurements are more accurate.
  2. A high beat rate will cause interrupts to occur more frequently, and frequent interrupts will increase the burden on the system. Compared with the system beat rate of 1000Hz and 100Hz, the system spends 10 times more “energy” to handle interrupts. The interrupt service function takes up more processor time, but today’s processors are very powerful, so using a system beat rate of 1000Hz will not increase the load pressure too much. According to your actual situation, choose the appropriate system beat rate. This tutorial uses the default 100Hz system beat rate.

The Linux kernel uses the global variable jiffies to record the number of system beats since the system started. When the system starts, jiffies will be initialized to 0. jiffies is defined in the file include/linux/jiffies.h and is defined as follows :
include/jiffies.h file code segment
Line 80 defines a 64-bit jiffies_64.

Line 81 defines a 32-bit jiffies of type unsigned long.

jiffies_64 and jiffies are actually the same thing, jiffies_64 is used for 64-bit systems, and jiffies is used for 32-bit systems. In order to be compatible with different hardware, jiffies is actually the lower 32 bits of jiffies_64. The structures of jiffies_64 and jiffies are as shown in the figure below:
jiffies_64 and jiffies structure diagram
When accessing jiffies, you actually access the lower 3 bits of jiffies_64. Use the get_jiffies_64 function to get the value of jiffies_64. On a 32-bit system, jiffies is read. On a 64-bit system, jiffes and jiffies_64 represent the same variable, so the value of jiffies can also be read directly. So jiffies can be used no matter it is a 32-bit system or a 64-bit system.

As mentioned earlier, HZ represents the number of beats per second, and jiffies represents the number of jiffies the system runs, so jiffies/HZ is the system running time in seconds. Whether it is 32-bit or 64-bit jiffies, there is a risk of overflow. After overflow, counting will start from 0 again, which is equivalent to wrapping around, so some information also calls this phenomenon wrapping around. If HZ is the maximum value of 1000, 32-bit jiffies only need 49.7 days to wrap around, and it will take about 580 million years for 64-bit jiffies. Wrap around, so wrapping around jiffies_64 is ignored. It is particularly important to handle the wrapping of 32-bit jiffies. The Linux kernel provides several API functions as shown in the figure below to handle wrapping.
API function to handle wraparound
If unknown exceeds known, the time_after function returns true, otherwise it returns false. The time_before function returns true if unknown does not exceed known, otherwise it returns false. The time_after_eq function is similar to the time_after function, except that it has the additional condition of judging equal. In the same way, the time_before_eq function and the time_before function are similar. For example, if you want to determine whether the execution time of a certain piece of code has timed out, you can use the following code:

Sample code 30.1.1.3 Use jiffies to determine timeout
1 unsigned long timeout;
2 timeout = jiffies + (2 * HZ); /* timeout point */
3
4 /*************************************
5 specific codes
6************************************/
7
8 /* Determine whether there is a timeout */
9 if(time_before(jiffies, timeout)) {<!-- -->
10 /* Timeout did not occur */
11 } else {<!-- -->
12 /* Timeout occurs */
13}

Timeout is the timeout point. For example, if you want to determine whether the code execution time exceeds 2 seconds, then the timeout point is jiffies + (2*HZ). If If jiffies is greater than timeout, it means timeout, otherwise there is no timeout. Lines 4-6 are the specific code segments. Line 9 uses the function time_before to determine whether jiffies is less than timeout. If it is less than timeout, it means there is no timeout.

In order to facilitate development, the Linux kernel provides several conversion functions between jiffies and ms, us, and ns, as shown in the following figure:
Conversion function between jiffies and ms, us, ns

Introduction to kernel timers

The timer is a very commonly used function. Timers are used for tasks that require periodic processing. The Linux kernel timer is implemented using the system clock. The Linux kernel timer is very simple to use. You only need to provide a timeout period (equivalent to the timing value) and a timing processing function. When the timeout period is reached, the timing processing function set after the timeout period will be executed, and use hardware The timer routine is the same, except that using the kernel timer does not require a lot of register initialization work. One thing to note when using the kernel timer is that thekernel timer does not run periodically and will automatically turn off after timeout. Therefore, if you want to implement periodic timing, you need to turn it back on in the timing processing function. Timer. The Linux kernel uses the timer_list structure to represent the kernel timer. timer_list is defined in the file include/linux/timer.h and is defined as follows:
timer_list structure
To use the kernel timer, you must first define a timer_list variable to represent the timer. The expires member variable of the tiemr_list structure represents the timeout time, and the unit is the number of beats. For example, if you now need to define a timer with a period of 2 seconds, then the timeout time of this timer is jiffies + (2*HZ), so expires=jiffies + (2*HZ). Function is the timing processing function after the timer times out. You need to write this timing processing function yourself. The formal parameter of the function function is the defined timer_list variable.

After defining the timer, you need to initialize the timer through a series of API functions, as follows:

timer_setup function

The timer_setup function is responsible for initializing timer_list type variables. When a timer_list variable is defined, it must be initialized with timer_setup first. The timer_setup function prototype is as follows:

void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)

The meanings of function parameters and return values are as follows:

  • timer: To initialize the timer.
  • func: The callback function of the timer. The formal parameter of this function is the variable of the current timer.
  • flags: flag bit, just give it 0.
  • Return value: No return value.

add_timer function

The add_timer function is used to register a timer with the Linux kernel. After using the add_timer function to register a timer with the kernel, the timer will start running. The function prototype is as follows:

void add_timer(struct timer_list *timer)

The meanings of function parameters and return values are as follows:

  • timer: the timer to be registered.
  • Return value: No return value.

del_timer function

The del_timer function is used to delete a timer, regardless of whether the timer is activated or not, you can use this function to delete. On a multi-processor system, the timer may be running on other processors, so before calling the del_timer function to delete the timer, you must first wait for the timing processor function of the other processor to exit. The del_timer function prototype is as follows:

int del_timer(struct timer_list * timer)

The meanings of function parameters and return values are as follows:

  • timer: The timer to be deleted.
  • Return value: 0, the timer has not been activated; 1, the timer has been activated.

del_timer_sync function

The del_timer_sync function is a synchronous version of the del_timer function. It will wait for other processors to finish using the timer before deleting it. del_timer_synccannot be used in an interrupt context. The del_timer_sync function prototype is as follows:

int del_timer_sync(struct timer_list *timer)

The meanings of function parameters and return values are as follows:

  • timer: The timer to be deleted.
  • Return value: 0, the timer has not been activated; 1, the timer has been activated.

mod_timer function

The mod_timer function is used to modify the timing value. If the timer has not been activated yet, the mod_timer function will activate the timer! The function prototype is as follows:

int mod_timer(struct timer_list *timer, unsigned long expires)

The meanings of function parameters and return values are as follows:

  • timer: The timer whose timeout period (timing value) is to be modified.
  • expires: modified timeout period.
  • Return value: 0, the timer has not been activated before calling the mod_timer function; 1, the timer has been activated before calling the mod_timer function.

The general use of timers is as shown in the following example:

Sample code 30.1.2.2 Demonstration of using kernel timer
1 struct timer_list timer; /* Define timer */
2 
3 /* Timer callback function */
4 void function(struct timer_list *arg)
5 {<!-- -->
6/*
7 * Timer processing code
8     */ 
9 
10 /* If you need the timer to run periodically, use mod_timer
11 * The function resets the timeout value and starts the timer.
12 */
13 mod_timer( & amp;dev->timertest, jiffies + msecs_to_jiffies(2000));
14}
15
16 /* Initialization function */
17 void init(void)
18 {<!-- -->
19 timer_setup( & amp;timerdev.timer, timer_function, 0); /* Initialize timer */
20 timer.expires=jffies + msecs_to_jiffies(2000);/* timeout 2 seconds */
21 add_timer( & amp;timer); /* Start timer */
twenty two } 
twenty three 
24 /* Exit function */
25 void exit(void)
26 {<!-- -->
27 del_timer( & amp;timer); /* Delete timer */
28 /* or use */
29 del_timer_sync( & amp;timer);
30}

Linux kernel short delay function

Sometimes it is necessary to implement short delays in the kernel, especially in Linux drivers. The Linux kernel provides millisecond, microsecond, and nanosecond delay functions, as shown in the following figure:
Kernel short delay function

Hardware schematic analysis

Here, the timer is used to control the LED periodically, so the hardware is the LED light, which has been done before, so I won’t go into details here.

Experimental program writing

The experiment in this chapter uses the kernel timer to periodically light up and turn off the LED lights on the development board. The flashing period of the LED light is set by the kernel timer. The test application can control the kernel timer period.

Modify device tree files

This can be done directly by using the previous LED light node.

Writing timer driver

The overall structure is modified from the previous LED driver, because the final operated GPIO is the IO port connected to the LED.

First, you need to define a device structure timer_dev, in which you need to define the timeperiod clock cycle in ms; you also need to define a timer such as a struct timer_list timer, and finally add a spinlock_t lock to define the spin lock. Then struct timer_dev timerdev materializes a timer device.

Afterwards, you need to define a timer_open, in which you set the device structure to private data filp->private_data= & timerdev, and then through this structure, set the member variable timeperiod=1000 to indicate that the period is 1s, and then call led_init to initialize the LED. GPIO port.

Then you need to define a timer_unlocked_ioctl function, which corresponds to the ioctl function of the application. The application calls this function to send control information to the driver, and this function will respond and execute. There are three functions in this function: filp, cmd and arg. filp is the device file, cmd is the application command, arg is the parameter sent, and arg here is the timing period. Three types of cmd are defined here: CLOSE_CMD is to close the timer and is closed directly through del_timer_sync; OPEN_CMD is to open the timer. Here, you need to lock it through spin_lock_irqsave, then set the timerperiod, unlock it through spin_unlock_irqrestore after setting, and modify the timing value through mod_timer and activate it. Timer; SETPERIOD_CMD is to set the timing period. The process is similar to OPEN_CMD, except that through dev->timeperiod=arg, pass in the arg parameter to modify the period variable in the device file, and finally mod_timer restarts the timer.

In the device operation function file_operations, named timer_fops, you need to set the .unlocked_ioctl member variable to the timer_unlocked_ioctl you wrote.

After that, you need to write a timer_function as a timer callback function. Each time you enter this function, it is equivalent to entering a timed interrupt in bare metal development. Set a state sta through static, invert each time and then gpio_set_value to set the LED. Flashing. It should be noted here that you need to set a timer_dev structure pointer *dev and obtain the device file through from_timer (dev, arg, timer). Herefrom_timer is actually a macro definition, which is an encapsulation of container_of, and this function It is to get the first address of the structure variable by giving the address of a member variable of the structure, the type of the structure and the name of the member. After that, you need to lock the spin lock, reset the timerperiod, and then mod_timer again to start the timer after unlocking.

In timer_init, you need to initialize the spin lock through spin_lock_init, and then follow the process. Finally, initialize the timer through timer_setup. The timing processing function is the self-written timer_function, and the flag bit is 0. It should be noted here that timer_add is not called to start timing. The activation is through mod_timer in the previous timer_open, which is equivalent to the application sending an open command.

Finally, the driver exports the function timer_exit, which calls del_timer_sync to delete the timer, and then logs out the character device driver.

Write a test APP

The test APP needs to have the following functions:

  1. After running the APP, you will be prompted to enter the command to be tested. Enter 1 to turn off the timer, enter 2 to turn on the timer, and enter 3 to set the timer period.
  2. If you want to set the timer period, you need to let the user enter the period value to be set, in milliseconds.

The difference between here and before is that after opening the device through open, it enters the while(1) loop, scans the input content through scanf, and then judges the situation through the if statement to perform the corresponding operation.

Run the test

Compile driver

The Makefile is always the same. Just modify obj-m to timer.o and then “make”.

Compile and test APP

Compile with the following command:

arm-none-linux-gnueabihf-gcc timerApp.c -o timerApp

Run the test

Copy the timer.ko and timerApp files compiled in the previous section to the rootfs/lib/modules/5.4.31 directory, restart the development board, enter the directory lib/modules/5.4.31, and enter the following command to load timer.ko driver module:

depmod //You need to run this command when loading the driver for the first time
modprobe timer.ko //Load driver

After successful loading, pass the following command to test:

./timerApp /dev/timer

After that, you can enter the corresponding instructions through the if condition set in the previous APP to observe the LED changes. You can uninstall the driver with the following command:

rmmod timer.ko

Summary

The content of this piece is very similar to the software timer of FreeRTOS. They are equivalent to calling a timer through software. It is not like turning on interrupts like bare metal development and completing timing operations through software.

First you need to open the kernel time management of the Linux kernel. You can open the graphical interface through “make menuconfig”, and then configure the system beat in Zian->Kernel Features->Timer frequency. Generally, it is directly Select the default 100Hz.

When developing drivers, you need to add timing cycles and timers to the device structure and define a timer_list structure. Then you need to define a timer_open that opens the timer. After passing in the device file, set the timing period; then in the ioctl function, modify the timer parameters through the passed cmd and arg (you need to lock the spin lock first Then modify, unlock after modification), and finally modify and activate the timer through mod_timer; write a callback function, pass in dev, arg, and timer through from_timer to obtain the device file, write the driver logic, and then mod_timer restarts the timer; in the final driver entry In the function, after the process is completed, the timer is initialized through timer_setup.

syntaxbug.com © 2021 All Rights Reserved.