1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #include <linux/fs.h> |
3 | #include <linux/interrupt.h> |
4 | #include <asm/octeon/octeon.h> |
5 | #include <asm/octeon/cvmx-ciu-defs.h> |
6 | #include <asm/octeon/cvmx.h> |
7 | #include <linux/debugfs.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/seq_file.h> |
11 | |
12 | #define TIMER_NUM 3 |
13 | |
14 | static bool reset_stats; |
15 | |
16 | struct latency_info { |
17 | u64 io_interval; |
18 | u64 cpu_interval; |
19 | u64 timer_start1; |
20 | u64 timer_start2; |
21 | u64 max_latency; |
22 | u64 min_latency; |
23 | u64 latency_sum; |
24 | u64 average_latency; |
25 | u64 interrupt_cnt; |
26 | }; |
27 | |
28 | static struct latency_info li; |
29 | static struct dentry *dir; |
30 | |
31 | static int oct_ilm_show(struct seq_file *m, void *v) |
32 | { |
33 | u64 cpuclk, avg, max, min; |
34 | struct latency_info curr_li = li; |
35 | |
36 | cpuclk = octeon_get_clock_rate(); |
37 | |
38 | max = (curr_li.max_latency * 1000000000) / cpuclk; |
39 | min = (curr_li.min_latency * 1000000000) / cpuclk; |
40 | avg = (curr_li.latency_sum * 1000000000) / (cpuclk * curr_li.interrupt_cnt); |
41 | |
42 | seq_printf(m, fmt: "cnt: %10lld, avg: %7lld ns, max: %7lld ns, min: %7lld ns\n" , |
43 | curr_li.interrupt_cnt, avg, max, min); |
44 | return 0; |
45 | } |
46 | DEFINE_SHOW_ATTRIBUTE(oct_ilm); |
47 | |
48 | static int reset_statistics(void *data, u64 value) |
49 | { |
50 | reset_stats = true; |
51 | return 0; |
52 | } |
53 | |
54 | DEFINE_DEBUGFS_ATTRIBUTE(reset_statistics_ops, NULL, reset_statistics, "%llu\n" ); |
55 | |
56 | static void init_debugfs(void) |
57 | { |
58 | dir = debugfs_create_dir(name: "oct_ilm" , parent: 0); |
59 | debugfs_create_file(name: "statistics" , mode: 0222, parent: dir, NULL, fops: &oct_ilm_fops); |
60 | debugfs_create_file(name: "reset" , mode: 0222, parent: dir, NULL, fops: &reset_statistics_ops); |
61 | } |
62 | |
63 | static void init_latency_info(struct latency_info *li, int startup) |
64 | { |
65 | /* interval in milli seconds after which the interrupt will |
66 | * be triggered |
67 | */ |
68 | int interval = 1; |
69 | |
70 | if (startup) { |
71 | /* Calculating by the amounts io clock and cpu clock would |
72 | * increment in interval amount of ms |
73 | */ |
74 | li->io_interval = (octeon_get_io_clock_rate() * interval) / 1000; |
75 | li->cpu_interval = (octeon_get_clock_rate() * interval) / 1000; |
76 | } |
77 | li->timer_start1 = 0; |
78 | li->timer_start2 = 0; |
79 | li->max_latency = 0; |
80 | li->min_latency = (u64)-1; |
81 | li->latency_sum = 0; |
82 | li->interrupt_cnt = 0; |
83 | } |
84 | |
85 | |
86 | static void start_timer(int timer, u64 interval) |
87 | { |
88 | union cvmx_ciu_timx timx; |
89 | unsigned long flags; |
90 | |
91 | timx.u64 = 0; |
92 | timx.s.one_shot = 1; |
93 | timx.s.len = interval; |
94 | raw_local_irq_save(flags); |
95 | li.timer_start1 = read_c0_cvmcount(); |
96 | cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); |
97 | /* Read it back to force wait until register is written. */ |
98 | timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); |
99 | li.timer_start2 = read_c0_cvmcount(); |
100 | raw_local_irq_restore(flags); |
101 | } |
102 | |
103 | |
104 | static irqreturn_t cvm_oct_ciu_timer_interrupt(int cpl, void *dev_id) |
105 | { |
106 | u64 last_latency; |
107 | u64 last_int_cnt; |
108 | |
109 | if (reset_stats) { |
110 | init_latency_info(li: &li, startup: 0); |
111 | reset_stats = false; |
112 | } else { |
113 | last_int_cnt = read_c0_cvmcount(); |
114 | last_latency = last_int_cnt - (li.timer_start1 + li.cpu_interval); |
115 | li.interrupt_cnt++; |
116 | li.latency_sum += last_latency; |
117 | if (last_latency > li.max_latency) |
118 | li.max_latency = last_latency; |
119 | if (last_latency < li.min_latency) |
120 | li.min_latency = last_latency; |
121 | } |
122 | start_timer(TIMER_NUM, interval: li.io_interval); |
123 | return IRQ_HANDLED; |
124 | } |
125 | |
126 | static void disable_timer(int timer) |
127 | { |
128 | union cvmx_ciu_timx timx; |
129 | |
130 | timx.s.one_shot = 0; |
131 | timx.s.len = 0; |
132 | cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); |
133 | /* Read it back to force immediate write of timer register*/ |
134 | timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); |
135 | } |
136 | |
137 | static __init int oct_ilm_module_init(void) |
138 | { |
139 | int rc; |
140 | int irq = OCTEON_IRQ_TIMER0 + TIMER_NUM; |
141 | |
142 | init_debugfs(); |
143 | |
144 | rc = request_irq(irq, handler: cvm_oct_ciu_timer_interrupt, IRQF_NO_THREAD, |
145 | name: "oct_ilm" , dev: 0); |
146 | if (rc) { |
147 | WARN(1, "Could not acquire IRQ %d" , irq); |
148 | goto err_irq; |
149 | } |
150 | |
151 | init_latency_info(li: &li, startup: 1); |
152 | start_timer(TIMER_NUM, interval: li.io_interval); |
153 | |
154 | return 0; |
155 | err_irq: |
156 | debugfs_remove_recursive(dentry: dir); |
157 | return rc; |
158 | } |
159 | |
160 | static __exit void oct_ilm_module_exit(void) |
161 | { |
162 | disable_timer(TIMER_NUM); |
163 | debugfs_remove_recursive(dentry: dir); |
164 | free_irq(OCTEON_IRQ_TIMER0 + TIMER_NUM, 0); |
165 | } |
166 | |
167 | module_exit(oct_ilm_module_exit); |
168 | module_init(oct_ilm_module_init); |
169 | MODULE_AUTHOR("Venkat Subbiah, Cavium" ); |
170 | MODULE_DESCRIPTION("Measures interrupt latency on Octeon chips." ); |
171 | MODULE_LICENSE("GPL" ); |
172 | |