1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * This file contains functions which manage high resolution tick |
4 | * related events. |
5 | * |
6 | * Copyright(C) 2005-2006, Thomas Gleixner <tglx@linutronix.de> |
7 | * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar |
8 | * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner |
9 | */ |
10 | #include <linux/cpu.h> |
11 | #include <linux/err.h> |
12 | #include <linux/hrtimer.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/percpu.h> |
15 | #include <linux/profile.h> |
16 | #include <linux/sched.h> |
17 | |
18 | #include "tick-internal.h" |
19 | |
20 | /** |
21 | * tick_program_event - program the CPU local timer device for the next event |
22 | */ |
23 | int tick_program_event(ktime_t expires, int force) |
24 | { |
25 | struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev); |
26 | |
27 | if (unlikely(expires == KTIME_MAX)) { |
28 | /* |
29 | * We don't need the clock event device any more, stop it. |
30 | */ |
31 | clockevents_switch_state(dev, state: CLOCK_EVT_STATE_ONESHOT_STOPPED); |
32 | dev->next_event = KTIME_MAX; |
33 | return 0; |
34 | } |
35 | |
36 | if (unlikely(clockevent_state_oneshot_stopped(dev))) { |
37 | /* |
38 | * We need the clock event again, configure it in ONESHOT mode |
39 | * before using it. |
40 | */ |
41 | clockevents_switch_state(dev, state: CLOCK_EVT_STATE_ONESHOT); |
42 | } |
43 | |
44 | return clockevents_program_event(dev, expires, force); |
45 | } |
46 | |
47 | /** |
48 | * tick_resume_oneshot - resume oneshot mode |
49 | */ |
50 | void tick_resume_oneshot(void) |
51 | { |
52 | struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev); |
53 | |
54 | clockevents_switch_state(dev, state: CLOCK_EVT_STATE_ONESHOT); |
55 | clockevents_program_event(dev, expires: ktime_get(), force: true); |
56 | } |
57 | |
58 | /** |
59 | * tick_setup_oneshot - setup the event device for oneshot mode (hres or nohz) |
60 | */ |
61 | void tick_setup_oneshot(struct clock_event_device *newdev, |
62 | void (*handler)(struct clock_event_device *), |
63 | ktime_t next_event) |
64 | { |
65 | newdev->event_handler = handler; |
66 | clockevents_switch_state(dev: newdev, state: CLOCK_EVT_STATE_ONESHOT); |
67 | clockevents_program_event(dev: newdev, expires: next_event, force: true); |
68 | } |
69 | |
70 | /** |
71 | * tick_switch_to_oneshot - switch to oneshot mode |
72 | */ |
73 | int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *)) |
74 | { |
75 | struct tick_device *td = this_cpu_ptr(&tick_cpu_device); |
76 | struct clock_event_device *dev = td->evtdev; |
77 | |
78 | if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT) || |
79 | !tick_device_is_functional(dev)) { |
80 | |
81 | pr_info("Clockevents: could not switch to one-shot mode:" ); |
82 | if (!dev) { |
83 | pr_cont(" no tick device\n" ); |
84 | } else { |
85 | if (!tick_device_is_functional(dev)) |
86 | pr_cont(" %s is not functional.\n" , dev->name); |
87 | else |
88 | pr_cont(" %s does not support one-shot mode.\n" , |
89 | dev->name); |
90 | } |
91 | return -EINVAL; |
92 | } |
93 | |
94 | td->mode = TICKDEV_MODE_ONESHOT; |
95 | dev->event_handler = handler; |
96 | clockevents_switch_state(dev, state: CLOCK_EVT_STATE_ONESHOT); |
97 | tick_broadcast_switch_to_oneshot(); |
98 | return 0; |
99 | } |
100 | |
101 | /** |
102 | * tick_oneshot_mode_active - check whether the system is in oneshot mode |
103 | * |
104 | * returns 1 when either nohz or highres are enabled. otherwise 0. |
105 | */ |
106 | int tick_oneshot_mode_active(void) |
107 | { |
108 | unsigned long flags; |
109 | int ret; |
110 | |
111 | local_irq_save(flags); |
112 | ret = __this_cpu_read(tick_cpu_device.mode) == TICKDEV_MODE_ONESHOT; |
113 | local_irq_restore(flags); |
114 | |
115 | return ret; |
116 | } |
117 | |
118 | #ifdef CONFIG_HIGH_RES_TIMERS |
119 | /** |
120 | * tick_init_highres - switch to high resolution mode |
121 | * |
122 | * Called with interrupts disabled. |
123 | */ |
124 | int tick_init_highres(void) |
125 | { |
126 | return tick_switch_to_oneshot(handler: hrtimer_interrupt); |
127 | } |
128 | #endif |
129 | |