linux-insides-zh
  • 简介
  • 引导
    • 从引导加载程序内核
    • 在内核安装代码的第一步
    • 视频模式初始化和转换到保护模式
    • 过渡到 64 位模式
    • 内核解压缩
  • 初始化
    • 内核解压之后的首要步骤
    • 早期的中断和异常控制
    • 在到达内核入口之前最后的准备
    • 内核入口 - start_kernel
    • 体系架构初始化
    • 进一步初始化指定体系架构
    • 最后对指定体系架构初始化
    • 调度器初始化
    • RCU 初始化
    • 初始化结束
  • 中断
    • 中断和中断处理第一部分
    • 深入 Linux 内核中的中断
    • 初步中断处理
    • 中断处理
    • 异常处理的实现
    • 处理不可屏蔽中断
    • 深入外部硬件中断
    • IRQs的非早期初始化
    • Softirq, Tasklets and Workqueues
    • 最后一部分
  • 系统调用
    • 系统调用概念简介
    • Linux 内核如何处理系统调用
    • vsyscall and vDSO
    • Linux 内核如何运行程序
    • open 系统调用的实现
    • Linux 资源限制
  • 定时器和时钟管理
    • 简介
    • 时钟源框架简介
    • The tick broadcast framework and dyntick
    • 定时器介绍
    • Clockevents 框架简介
    • x86 相关的时钟源
    • Linux 内核中与时钟相关的系统调用
  • 同步原语
    • 自旋锁简介
    • 队列自旋锁
    • 信号量
    • 互斥锁
    • 读者/写者信号量
    • 顺序锁
    • RCU
    • Lockdep
  • 内存管理
    • 内存块
    • 固定映射地址和 ioremap
    • kmemcheck
  • 控制组
    • 控制组简介
  • 概念
    • 每个 CPU 的变量
    • CPU 掩码
    • initcall 机制
    • Linux 内核的通知链
  • Linux 内核中的数据结构
    • 双向链表
    • 基数树
    • 位数组
  • 理论
    • 分页
    • ELF 文件格式
    • 內联汇编
    • CPUID
    • MSR
Powered by GitBook
On this page
  • Introduction to the clockevents framework
  • API of clockevents framework
  • Conclusion
  • Links
  1. 定时器和时钟管理

Clockevents 框架简介

Previous定时器介绍Nextx86 相关的时钟源

Last updated 1 year ago

Introduction to the clockevents framework

This is fifth part of the which describes timers and time management related stuff in the Linux kernel. As you might noted from the title of this part, the clockevents framework will be discussed. We already saw one framework in the part of this chapter. It was clocksource framework. Both of these frameworks represent timekeeping abstractions in the Linux kernel.

At first let's refresh your memory and try to remember what is it clocksource framework and and what its purpose. The main goal of the clocksource framework is to provide timeline. As described in the :

For example issuing the command 'date' on a Linux system will eventually read the clock source to determine exactly what time it is.

The Linux kernel supports many different clock sources. You can find some of them in the . For example old good - with 1193182 Hz frequency, yet another one - timer with 3579545 Hz frequency. Besides the directory, each architecture may provide own architecture-specific clock sources. For example architecture provides , or for example provides access to the processor timer through timebase register.

Each clock source provides monotonic atomic counter. As I already wrote, the Linux kernel supports a huge set of different clock source and each clock source has own parameters like . The main goal of the clocksource framework is to provide to select best available clock source in the system i.e. a clock source with the highest frequency. Additional goal of the clocksource framework is to represent an atomic counter provided by a clock source in human units. In this time, nanoseconds are the favorite choice for the time value units of the given clock source in the Linux kernel.

The clocksource framework represented by the clocksource structure which is defined in the header code file which contains name of a clock source, rating of certain clock source in the system (a clock source with the higher frequency has the biggest rating in the system), list of all registered clock source in the system, enable and disable fields to enable and disable a clock source, pointer to the read function which must return an atomic counter of a clock source and etc.

Additionally the clocksource structure provides two fields: mult and shift which are needed for translation of an atomic counter which is provided by a certain clock source to the human units, i.e. . Translation occurs via following formula:

ns ~= (clocksource * mult) >> shift

As we already know, besides the clocksource structure, the clocksource framework provides an API for registration of clock source with different frequency scale factor:

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)

A clock source unregistration:

int clocksource_unregister(struct clocksource *cs)

and etc.

Additionally to the clocksource framework, the Linux kernel provides clockevents framework. As described in the :

Clock events are the conceptual reverse of clock sources

API of clockevents framework

static struct clock_event_device lapic_clockevent = {
    .name                   = "lapic",
    ...
    ...
    ...
}
#define CLOCK_EVT_FEAT_PERIODIC	0x000001
#define CLOCK_EVT_FEAT_ONESHOT		0x000002
#define CLOCK_EVT_FEAT_C3STOP		0x000008

After we considered part of the clock_event_device structure, time is to look at the API of the clockevents framework. To work with a clock event device, first of all we need to initialize clock_event_device structure and register a clock events device. The clockevents framework provides following API for registration of clock event devices:

void clockevents_register_device(struct clock_event_device *dev)
{
   ...
   ...
   ...
}
  • address of a clock_event_device structure which represents a clock event device.

First of all let's look at initialization of the clock_event_device structure. This occurs in the at91sam926x_pit_common_init function:

struct pit_data {
    ...
    ...
    struct clock_event_device       clkevt;
    ...
    ...
};

static void __init at91sam926x_pit_common_init(struct pit_data *data)
{
    ...
    ...
    ...
    data->clkevt.name = "pit";
    data->clkevt.features = CLOCK_EVT_FEAT_PERIODIC;
    data->clkevt.shift = 32;
    data->clkevt.mult = div_sc(pit_rate, NSEC_PER_SEC, data->clkevt.shift);
    data->clkevt.rating = 100;
    data->clkevt.cpumask = cpumask_of(0);

    data->clkevt.set_state_shutdown = pit_clkevt_shutdown;
    data->clkevt.set_state_periodic = pit_clkevt_set_periodic;
    data->clkevt.resume = at91sam926x_pit_resume;
    data->clkevt.suspend = at91sam926x_pit_suspend;
    ...
}
#define cpumask_of(cpu) (get_cpu_mask(cpu))

After we finished with the initialization of the at91sam926x periodic timer, we can register it by the call of the following functions:

clockevents_register_device(&data->clkevt);
clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED);

Actually, an event device may be in one of this states:

enum clock_event_state {
	CLOCK_EVT_STATE_DETACHED,
	CLOCK_EVT_STATE_SHUTDOWN,
	CLOCK_EVT_STATE_PERIODIC,
	CLOCK_EVT_STATE_ONESHOT,
	CLOCK_EVT_STATE_ONESHOT_STOPPED,
};

Where:

  • CLOCK_EVT_STATE_DETACHED - a clock event device is not not used by clockevents framework. Actually it is initial state of all clock event devices;

  • CLOCK_EVT_STATE_SHUTDOWN - a clock event device is powered-off;

  • CLOCK_EVT_STATE_PERIODIC - a clock event device may be programmed to generate event periodically;

  • CLOCK_EVT_STATE_ONESHOT - a clock event device may be programmed to generate event only once;

  • CLOCK_EVT_STATE_ONESHOT_STOPPED - a clock event device was programmed to generate event only once and now it is temporary stopped.

The implementation of the clock_event_set_state function is pretty easy:

static inline void clockevent_set_state(struct clock_event_device *dev,
					enum clock_event_state state)
{
	dev->state_use_accessors = state;
}

As we can see, it just fills the state_use_accessors field of the given clock_event_device structure with the given value which is in our case is CLOCK_EVT_STATE_DETACHED. Actually all clock event devices has this initial state during registration. The state_use_accessors field of the clock_event_device structure provides current state of the clock event device.

After we have set initial state of the given clock_event_device structure we check that the cpumask of the given clock event device is not zero:

if (!dev->cpumask) {
	WARN_ON(num_possible_cpus() > 1);
	dev->cpumask = cpumask_of(smp_processor_id());
}

After this check we lock the actual code of the clock event device registration by the call following macros:

raw_spin_lock_irqsave(&clockevents_lock, flags);
...
...
...
raw_spin_unlock_irqrestore(&clockevents_lock, flags);

We can see following code of clock event device registration between the raw_spin_lock_irqsave and raw_spin_unlock_irqrestore macros:

list_add(&dev->list, &clockevent_devices);
tick_check_new_device(dev);
clockevents_notify_released();

First of all we add the given clock event device to the list of clock event devices which is represented by the clockevent_devices:

static LIST_HEAD(clockevent_devices);
static bool tick_check_preferred(struct clock_event_device *curdev,
				 struct clock_event_device *newdev)
{
	if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {
		if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
			return false;
		if (tick_oneshot_mode_active())
			return false;
	}

	return !curdev ||
		newdev->rating > curdev->rating ||
	       !cpumask_equal(curdev->cpumask, newdev->cpumask);
}

If the new registered clock event device is more preferred than old tick device, we exchange old and new registered devices and install new device:

clockevents_exchange_device(curdev, newdev);
tick_setup_device(td, newdev, cpu, cpumask_of(cpu));

The clockevents_exchange_device function releases or in other words deleted the old clock event device from the clockevent_devices list. The next function - tick_setup_device as we may understand from its name, setups new tick device. This function check the mode of the new registered clock event device and call the tick_setup_periodic function or the tick_setup_oneshot depends on the tick device mode:

if (td->mode == TICKDEV_MODE_PERIODIC)
	tick_setup_periodic(newdev, 0);
else
	tick_setup_oneshot(newdev, handler, next_event);

Both of this functions calls the clockevents_switch_state to change state of the clock event device and the clockevents_program_event function to set next event of clock event device based on delta between the maximum and minimum difference current time and time for the next event. The tick_setup_periodic:

clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC);
clockevents_program_event(dev, next, false))

and the tick_setup_oneshot_periodic:

clockevents_switch_state(newdev, CLOCK_EVT_STATE_ONESHOT);
clockevents_program_event(newdev, next_event, true);

The clockevents_switch_state function checks that the clock event device is not in the given state and calls the __clockevents_switch_state function from the same source code file:

if (clockevent_get_state(dev) != state) {
	if (__clockevents_switch_state(dev, state))
		return;

The __clockevents_switch_state function just makes a call of the certain callback depends on the given state:

static int __clockevents_switch_state(struct clock_event_device *dev,
				      enum clock_event_state state)
{
	if (dev->features & CLOCK_EVT_FEAT_DUMMY)
		return 0;

	switch (state) {
	case CLOCK_EVT_STATE_DETACHED:
	case CLOCK_EVT_STATE_SHUTDOWN:
		if (dev->set_state_shutdown)
			return dev->set_state_shutdown(dev);
		return 0;

	case CLOCK_EVT_STATE_PERIODIC:
		if (!(dev->features & CLOCK_EVT_FEAT_PERIODIC))
			return -ENOSYS;
		if (dev->set_state_periodic)
			return dev->set_state_periodic(dev);
		return 0;
    ...
    ...
    ...

In our case for at91sam926x periodic timer, the state is the CLOCK_EVT_FEAT_PERIODIC:

data->clkevt.features = CLOCK_EVT_FEAT_PERIODIC;
data->clkevt.set_state_periodic = pit_clkevt_set_periodic;

It looks like:

31                                                   25        24
+---------------------------------------------------------------+
|                                          |  PITIEN  |  PITEN  |
+---------------------------------------------------------------+
23                            19                               16
+---------------------------------------------------------------+
|                             |               PIV               |
+---------------------------------------------------------------+
15                                                              8
+---------------------------------------------------------------+
|                            PIV                                |
+---------------------------------------------------------------+
7                                                               0
+---------------------------------------------------------------+
|                            PIV                                |
+---------------------------------------------------------------+

Where PIV or Periodic Interval Value - defines the value compared with the primary 20-bit counter of the Periodic Interval Timer. The PITEN or Period Interval Timer Enabled if the bit is 1 and the PITIEN or Periodic Interval Timer Interrupt Enable if the bit is 1. So, to set periodic mode, we need to set 24, 25 bits in the Periodic Interval Timer Mode Register. And we are doing it in the pit_clkevt_set_periodic function:

static int pit_clkevt_set_periodic(struct clock_event_device *dev)
{
        struct pit_data *data = clkevt_to_pit_data(dev);
        ...
        ...
        ...
        pit_write(data->base, AT91_PIT_MR,
                  (data->cycle - 1) | AT91_PIT_PITEN | AT91_PIT_PITIEN);

        return 0;
}

Where the AT91_PT_MR, AT91_PT_PITEN and the AT91_PIT_PITIEN are declared as:

#define AT91_PIT_MR             0x00
#define AT91_PIT_PITIEN       BIT(25)
#define AT91_PIT_PITEN        BIT(24)

After the setup of the new clock event device is finished, we can return to the clockevents_register_device function. The last function in the clockevents_register_device function is:

clockevents_notify_released();

This function checks the clockevents_released list which contains released clock event devices (remember that they may occur after the call of the clockevents_exchange_device function). If this list is not empty, we go through clock event devices from the clock_events_released list and delete it from the clockevent_devices:

static void clockevents_notify_released(void)
{
	struct clock_event_device *dev;

	while (!list_empty(&clockevents_released)) {
		dev = list_entry(clockevents_released.next,
				 struct clock_event_device, list);
		list_del(&dev->list);
		list_add(&dev->list, &clockevent_devices);
		tick_check_new_device(dev);
	}
}

That's all.

Conclusion

Links

Main goal of the is to manage clock event devices or in other words - to manage devices that allow to register an event or in other words that is going to happen at a defined point of time in the future.

Now we know a little about the clockevents framework in the Linux kernel, and now time is to see on it .

The main structure which described a clock event device is clock_event_device structure. This structure is defined in the header file and contains a huge set of fields. as well as the clocksource structure it has name fields which contains human readable name of a clock event device, for example timer:

Addresses of the event_handler, set_next_event, next_event functions for a certain clock event device which are an , setter of next event and local storage for next event respectively. Yet another field of the clock_event_device structure is - features field. Its value maybe on of the following generic features:

Where the CLOCK_EVT_FEAT_PERIODIC represents device which may be programmed to generate events periodically. The CLOCK_EVT_FEAT_ONESHOT represents device which may generate an event only once. Besides these two features, there are also architecture-specific features. For example supports two additional features:

The first CLOCK_EVT_FEAT_C3STOP means that a clock event device will be stopped in the state. Additionally the clock_event_device structure has mult and shift fields as well as clocksource structure. The clocksource structure also contains other fields, but we will consider it later.

This function defined in the source code file and as we may see, the clockevents_register_device function takes only one parameter:

So, to register a clock event device, at first we need to initialize clock_event_device structure with parameters of a certain clock event device. Let's take a look at one random clock event device in the Linux kernel source code. We can find one in the directory or try to take a look at an architecture-specific clock event device. Let's take for example - . You can find its implementation in the .

Here we can see that at91sam926x_pit_common_init takes one parameter - pointer to the pit_data structure which contains clock_event_device structure which will contain clock event related information of the at91sam926x . At the start we fill name of the timer device and its features. In our case we deal with periodic timer which as we already know may be programmed to generate events periodically.

The next two fields shift and mult are familiar to us. They will be used to translate counter of our timer to nanoseconds. After this we set rating of the timer to 100. This means if there will not be timers with higher rating in the system, this timer will be used for timekeeping. The next field - cpumask indicates for which processors in the system the device will work. In our case, the device will work for the first processor. The cpumask_of macro defined in the header file and just expands to the call of the:

Where the get_cpu_mask returns the cpumask containing just a given cpu number. More about cpumasks concept you may read in the part. In the last four lines of code we set callbacks for the clock event device suspend/resume, device shutdown and update of the clock event device state.

Now we can consider implementation of the clockevent_register_device function. As I already wrote above, this function is defined in the source code file and starts from the initialization of the initial event device state:

Remember that we have set the cpumask of the at91sam926x periodic timer to first processor. If the cpumask field is zero, we check the number of possible processors in the system and print warning message if it is less than on. Additionally we set the cpumask of the given clock event device to the current processor. If you are interested in how the smp_processor_id macro is implemented, you can read more about it in the fourth of the Linux kernel initialization process chapter.

Additionally the raw_spin_lock_irqsave and the raw_spin_unlock_irqrestore macros disable local interrupts, however interrupts on other processors still may occur. We need to do it to prevent potential if we adding new clock event device to the list of clock event devices and an interrupt occurs from other clock event device.

At the next step we call the tick_check_new_device function which is defined in the source code file and checks do the new registered clock event device should be used or not. The tick_check_new_device function checks the given clock_event_device gets the current registered tick device which is represented by the tick_device structure and compares their ratings and features. Actually CLOCK_EVT_STATE_ONESHOT is preferred:

So, for the pit_clkevt_set_periodic callback will be called. If we will read the documentation of the , we will see that there is Periodic Interval Timer Mode Register which allows us to control of periodic interval timer.

That's all. From this moment we have registered new clock event device. So the usage of the clockevents framework is simple and clear. Architectures registered their clock event devices, in the clock events core. Users of the clockevents core can get clock event devices for their use. The clockevents framework provides notification mechanisms for various clock related management events like a clock event device registered or unregistered, a processor is offlined in system which supports and etc.

We saw implementation only of the clockevents_register_device function. But generally, the clock event layer is small. Besides the API for clock event device registration, the clockevents framework provides functions to schedule the next event interrupt, clock event device notification service and support for suspend and resume for clock event devices.

If you want to know more about clockevents API you can start to research following source code and header files: , and .

This is the end of the fifth part of the that describes timers and timer management related stuff in the Linux kernel. In the previous part got acquainted with the timers concept. In this part we continued to learn time management related stuff in the Linux kernel and saw a little about yet another framework - clockevents.

If you have questions or suggestions, feel free to ping me in twitter , drop me or just create .

Please note that English is not my first language and I am really sorry for any inconvenience. If you found any mistakes please send me PR to .

chapter
second
documentation
drivers/closksource
Intel 8253
programmable interval timer
ACPI PM
drivers/closksource
x86
High Precision Event Timer
powerpc
frequency
API
include/linux/clocksource.h
nanoseconds
documentation
interrupt
API
include/linux/clockchips.h
local APIC
interrupt handler
x86_64
C3
kernel/time/clockevents.c
drivers/closksource
Periodic Interval Timer (PIT) for at91sam926x
drivers/closksource
periodic Interval Timer
include/linux/cpumask.h
CPU masks in the Linux kernel
kernel/time/clockevents.c
part
deadlock
kernel/time/tick-common.c
Periodic Interval Timer (PIT) for at91sam926x
CPU hotplug
API
kernel/time/tick-common.c
kernel/time/clockevents.c
include/linux/clockchips.h
chapter
0xAX
email
issue
linux-insides
timekeeping documentation
Intel 8253
programmable interval timer
ACPI pdf
x86
High Precision Event Timer
powerpc
frequency
API
nanoseconds
interrupt
interrupt handler
local APIC
C3 state
Periodic Interval Timer (PIT) for at91sam926x
CPU masks in the Linux kernel
deadlock
CPU hotplug
previous part