162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci#include <linux/fs.h> 362306a36Sopenharmony_ci#include <linux/interrupt.h> 462306a36Sopenharmony_ci#include <asm/octeon/octeon.h> 562306a36Sopenharmony_ci#include <asm/octeon/cvmx-ciu-defs.h> 662306a36Sopenharmony_ci#include <asm/octeon/cvmx.h> 762306a36Sopenharmony_ci#include <linux/debugfs.h> 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/seq_file.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define TIMER_NUM 3 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_cistatic bool reset_stats; 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistruct latency_info { 1762306a36Sopenharmony_ci u64 io_interval; 1862306a36Sopenharmony_ci u64 cpu_interval; 1962306a36Sopenharmony_ci u64 timer_start1; 2062306a36Sopenharmony_ci u64 timer_start2; 2162306a36Sopenharmony_ci u64 max_latency; 2262306a36Sopenharmony_ci u64 min_latency; 2362306a36Sopenharmony_ci u64 latency_sum; 2462306a36Sopenharmony_ci u64 average_latency; 2562306a36Sopenharmony_ci u64 interrupt_cnt; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic struct latency_info li; 2962306a36Sopenharmony_cistatic struct dentry *dir; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic int oct_ilm_show(struct seq_file *m, void *v) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci u64 cpuclk, avg, max, min; 3462306a36Sopenharmony_ci struct latency_info curr_li = li; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci cpuclk = octeon_get_clock_rate(); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci max = (curr_li.max_latency * 1000000000) / cpuclk; 3962306a36Sopenharmony_ci min = (curr_li.min_latency * 1000000000) / cpuclk; 4062306a36Sopenharmony_ci avg = (curr_li.latency_sum * 1000000000) / (cpuclk * curr_li.interrupt_cnt); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci seq_printf(m, "cnt: %10lld, avg: %7lld ns, max: %7lld ns, min: %7lld ns\n", 4362306a36Sopenharmony_ci curr_li.interrupt_cnt, avg, max, min); 4462306a36Sopenharmony_ci return 0; 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(oct_ilm); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int reset_statistics(void *data, u64 value) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci reset_stats = true; 5162306a36Sopenharmony_ci return 0; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(reset_statistics_ops, NULL, reset_statistics, "%llu\n"); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic void init_debugfs(void) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci dir = debugfs_create_dir("oct_ilm", 0); 5962306a36Sopenharmony_ci debugfs_create_file("statistics", 0222, dir, NULL, &oct_ilm_fops); 6062306a36Sopenharmony_ci debugfs_create_file("reset", 0222, dir, NULL, &reset_statistics_ops); 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic void init_latency_info(struct latency_info *li, int startup) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci /* interval in milli seconds after which the interrupt will 6662306a36Sopenharmony_ci * be triggered 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_ci int interval = 1; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (startup) { 7162306a36Sopenharmony_ci /* Calculating by the amounts io clock and cpu clock would 7262306a36Sopenharmony_ci * increment in interval amount of ms 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_ci li->io_interval = (octeon_get_io_clock_rate() * interval) / 1000; 7562306a36Sopenharmony_ci li->cpu_interval = (octeon_get_clock_rate() * interval) / 1000; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci li->timer_start1 = 0; 7862306a36Sopenharmony_ci li->timer_start2 = 0; 7962306a36Sopenharmony_ci li->max_latency = 0; 8062306a36Sopenharmony_ci li->min_latency = (u64)-1; 8162306a36Sopenharmony_ci li->latency_sum = 0; 8262306a36Sopenharmony_ci li->interrupt_cnt = 0; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic void start_timer(int timer, u64 interval) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci union cvmx_ciu_timx timx; 8962306a36Sopenharmony_ci unsigned long flags; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci timx.u64 = 0; 9262306a36Sopenharmony_ci timx.s.one_shot = 1; 9362306a36Sopenharmony_ci timx.s.len = interval; 9462306a36Sopenharmony_ci raw_local_irq_save(flags); 9562306a36Sopenharmony_ci li.timer_start1 = read_c0_cvmcount(); 9662306a36Sopenharmony_ci cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); 9762306a36Sopenharmony_ci /* Read it back to force wait until register is written. */ 9862306a36Sopenharmony_ci timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); 9962306a36Sopenharmony_ci li.timer_start2 = read_c0_cvmcount(); 10062306a36Sopenharmony_ci raw_local_irq_restore(flags); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic irqreturn_t cvm_oct_ciu_timer_interrupt(int cpl, void *dev_id) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci u64 last_latency; 10762306a36Sopenharmony_ci u64 last_int_cnt; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (reset_stats) { 11062306a36Sopenharmony_ci init_latency_info(&li, 0); 11162306a36Sopenharmony_ci reset_stats = false; 11262306a36Sopenharmony_ci } else { 11362306a36Sopenharmony_ci last_int_cnt = read_c0_cvmcount(); 11462306a36Sopenharmony_ci last_latency = last_int_cnt - (li.timer_start1 + li.cpu_interval); 11562306a36Sopenharmony_ci li.interrupt_cnt++; 11662306a36Sopenharmony_ci li.latency_sum += last_latency; 11762306a36Sopenharmony_ci if (last_latency > li.max_latency) 11862306a36Sopenharmony_ci li.max_latency = last_latency; 11962306a36Sopenharmony_ci if (last_latency < li.min_latency) 12062306a36Sopenharmony_ci li.min_latency = last_latency; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci start_timer(TIMER_NUM, li.io_interval); 12362306a36Sopenharmony_ci return IRQ_HANDLED; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic void disable_timer(int timer) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci union cvmx_ciu_timx timx; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci timx.s.one_shot = 0; 13162306a36Sopenharmony_ci timx.s.len = 0; 13262306a36Sopenharmony_ci cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); 13362306a36Sopenharmony_ci /* Read it back to force immediate write of timer register*/ 13462306a36Sopenharmony_ci timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic __init int oct_ilm_module_init(void) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci int rc; 14062306a36Sopenharmony_ci int irq = OCTEON_IRQ_TIMER0 + TIMER_NUM; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci init_debugfs(); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci rc = request_irq(irq, cvm_oct_ciu_timer_interrupt, IRQF_NO_THREAD, 14562306a36Sopenharmony_ci "oct_ilm", 0); 14662306a36Sopenharmony_ci if (rc) { 14762306a36Sopenharmony_ci WARN(1, "Could not acquire IRQ %d", irq); 14862306a36Sopenharmony_ci goto err_irq; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci init_latency_info(&li, 1); 15262306a36Sopenharmony_ci start_timer(TIMER_NUM, li.io_interval); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_cierr_irq: 15662306a36Sopenharmony_ci debugfs_remove_recursive(dir); 15762306a36Sopenharmony_ci return rc; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic __exit void oct_ilm_module_exit(void) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci disable_timer(TIMER_NUM); 16362306a36Sopenharmony_ci debugfs_remove_recursive(dir); 16462306a36Sopenharmony_ci free_irq(OCTEON_IRQ_TIMER0 + TIMER_NUM, 0); 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cimodule_exit(oct_ilm_module_exit); 16862306a36Sopenharmony_cimodule_init(oct_ilm_module_init); 16962306a36Sopenharmony_ciMODULE_AUTHOR("Venkat Subbiah, Cavium"); 17062306a36Sopenharmony_ciMODULE_DESCRIPTION("Measures interrupt latency on Octeon chips."); 17162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 172