1219019Sgabor// SPDX-License-Identifier: GPL-2.0-only 2219019Sgabor#include <linux/fs.h> 3219019Sgabor#include <linux/interrupt.h> 4219019Sgabor#include <asm/octeon/octeon.h> 5219019Sgabor#include <asm/octeon/cvmx-ciu-defs.h> 6219019Sgabor#include <asm/octeon/cvmx.h> 7219019Sgabor#include <linux/debugfs.h> 8219019Sgabor#include <linux/kernel.h> 9219019Sgabor#include <linux/module.h> 10219019Sgabor#include <linux/seq_file.h> 11219019Sgabor 12219019Sgabor#define TIMER_NUM 3 13219019Sgabor 14219019Sgaborstatic bool reset_stats; 15219019Sgabor 16219019Sgaborstruct latency_info { 17219019Sgabor u64 io_interval; 18219019Sgabor u64 cpu_interval; 19219019Sgabor u64 timer_start1; 20219019Sgabor u64 timer_start2; 21219019Sgabor u64 max_latency; 22219019Sgabor u64 min_latency; 23219019Sgabor u64 latency_sum; 24219019Sgabor u64 average_latency; 25219019Sgabor u64 interrupt_cnt; 26219019Sgabor}; 27219019Sgabor 28219019Sgaborstatic struct latency_info li; 29219019Sgaborstatic struct dentry *dir; 30219019Sgabor 31219019Sgaborstatic int oct_ilm_show(struct seq_file *m, void *v) 32219019Sgabor{ 33219019Sgabor u64 cpuclk, avg, max, min; 34219019Sgabor struct latency_info curr_li = li; 35219019Sgabor 36219019Sgabor cpuclk = octeon_get_clock_rate(); 37219019Sgabor 38219019Sgabor max = (curr_li.max_latency * 1000000000) / cpuclk; 39219019Sgabor min = (curr_li.min_latency * 1000000000) / cpuclk; 40219019Sgabor avg = (curr_li.latency_sum * 1000000000) / (cpuclk * curr_li.interrupt_cnt); 41219019Sgabor 42219019Sgabor seq_printf(m, "cnt: %10lld, avg: %7lld ns, max: %7lld ns, min: %7lld ns\n", 43219019Sgabor curr_li.interrupt_cnt, avg, max, min); 44219019Sgabor return 0; 45219019Sgabor} 46219019SgaborDEFINE_SHOW_ATTRIBUTE(oct_ilm); 47219019Sgabor 48219019Sgaborstatic int reset_statistics(void *data, u64 value) 49219019Sgabor{ 50219019Sgabor reset_stats = true; 51219019Sgabor return 0; 52219019Sgabor} 53219019Sgabor 54219019SgaborDEFINE_DEBUGFS_ATTRIBUTE(reset_statistics_ops, NULL, reset_statistics, "%llu\n"); 55219019Sgabor 56219019Sgaborstatic void init_debugfs(void) 57219019Sgabor{ 58219019Sgabor dir = debugfs_create_dir("oct_ilm", 0); 59219019Sgabor debugfs_create_file("statistics", 0222, dir, NULL, &oct_ilm_fops); 60219019Sgabor debugfs_create_file("reset", 0222, dir, NULL, &reset_statistics_ops); 61219019Sgabor} 62219019Sgabor 63219019Sgaborstatic void init_latency_info(struct latency_info *li, int startup) 64219019Sgabor{ 65219019Sgabor /* interval in milli seconds after which the interrupt will 66219019Sgabor * be triggered 67219019Sgabor */ 68219019Sgabor int interval = 1; 69219019Sgabor 70219019Sgabor if (startup) { 71219019Sgabor /* Calculating by the amounts io clock and cpu clock would 72219019Sgabor * increment in interval amount of ms 73219019Sgabor */ 74219019Sgabor li->io_interval = (octeon_get_io_clock_rate() * interval) / 1000; 75219019Sgabor li->cpu_interval = (octeon_get_clock_rate() * interval) / 1000; 76219019Sgabor } 77219019Sgabor li->timer_start1 = 0; 78219019Sgabor li->timer_start2 = 0; 79219019Sgabor li->max_latency = 0; 80219019Sgabor li->min_latency = (u64)-1; 81219019Sgabor li->latency_sum = 0; 82219019Sgabor li->interrupt_cnt = 0; 83219019Sgabor} 84219019Sgabor 85219019Sgabor 86219019Sgaborstatic void start_timer(int timer, u64 interval) 87219019Sgabor{ 88219019Sgabor union cvmx_ciu_timx timx; 89219019Sgabor unsigned long flags; 90219019Sgabor 91219019Sgabor timx.u64 = 0; 92219019Sgabor timx.s.one_shot = 1; 93219019Sgabor timx.s.len = interval; 94219019Sgabor raw_local_irq_save(flags); 95219019Sgabor li.timer_start1 = read_c0_cvmcount(); 96219019Sgabor cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); 97219019Sgabor /* Read it back to force wait until register is written. */ 98219019Sgabor timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); 99219019Sgabor li.timer_start2 = read_c0_cvmcount(); 100219019Sgabor raw_local_irq_restore(flags); 101219019Sgabor} 102219019Sgabor 103219019Sgabor 104219019Sgaborstatic irqreturn_t cvm_oct_ciu_timer_interrupt(int cpl, void *dev_id) 105219019Sgabor{ 106219019Sgabor u64 last_latency; 107219019Sgabor u64 last_int_cnt; 108219019Sgabor 109219019Sgabor if (reset_stats) { 110219019Sgabor init_latency_info(&li, 0); 111219019Sgabor reset_stats = false; 112219019Sgabor } else { 113219019Sgabor last_int_cnt = read_c0_cvmcount(); 114219019Sgabor last_latency = last_int_cnt - (li.timer_start1 + li.cpu_interval); 115219019Sgabor li.interrupt_cnt++; 116219019Sgabor li.latency_sum += last_latency; 117219019Sgabor if (last_latency > li.max_latency) 118219019Sgabor li.max_latency = last_latency; 119219019Sgabor if (last_latency < li.min_latency) 120219019Sgabor li.min_latency = last_latency; 121219019Sgabor } 122219019Sgabor start_timer(TIMER_NUM, li.io_interval); 123219019Sgabor return IRQ_HANDLED; 124219019Sgabor} 125219019Sgabor 126219019Sgaborstatic void disable_timer(int timer) 127219019Sgabor{ 128219019Sgabor union cvmx_ciu_timx timx; 129219019Sgabor 130219019Sgabor timx.s.one_shot = 0; 131219019Sgabor timx.s.len = 0; 132219019Sgabor cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); 133219019Sgabor /* Read it back to force immediate write of timer register*/ 134219019Sgabor timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); 135219019Sgabor} 136219019Sgabor 137219019Sgaborstatic __init int oct_ilm_module_init(void) 138219019Sgabor{ 139219019Sgabor int rc; 140219019Sgabor int irq = OCTEON_IRQ_TIMER0 + TIMER_NUM; 141219019Sgabor 142219019Sgabor init_debugfs(); 143219019Sgabor 144219019Sgabor rc = request_irq(irq, cvm_oct_ciu_timer_interrupt, IRQF_NO_THREAD, 145219019Sgabor "oct_ilm", 0); 146219019Sgabor if (rc) { 147219019Sgabor WARN(1, "Could not acquire IRQ %d", irq); 148219019Sgabor goto err_irq; 149219019Sgabor } 150219019Sgabor 151219019Sgabor init_latency_info(&li, 1); 152219019Sgabor start_timer(TIMER_NUM, li.io_interval); 153219019Sgabor 154219019Sgabor return 0; 155219019Sgaborerr_irq: 156219019Sgabor debugfs_remove_recursive(dir); 157219019Sgabor return rc; 158219019Sgabor} 159219019Sgabor 160219019Sgaborstatic __exit void oct_ilm_module_exit(void) 161219019Sgabor{ 162219019Sgabor disable_timer(TIMER_NUM); 163219019Sgabor debugfs_remove_recursive(dir); 164219019Sgabor free_irq(OCTEON_IRQ_TIMER0 + TIMER_NUM, 0); 165219019Sgabor} 166219019Sgabor 167219019Sgabormodule_exit(oct_ilm_module_exit); 168219019Sgabormodule_init(oct_ilm_module_init); 169219019SgaborMODULE_AUTHOR("Venkat Subbiah, Cavium"); 170219019SgaborMODULE_DESCRIPTION("Measures interrupt latency on Octeon chips."); 171219019SgaborMODULE_LICENSE("GPL"); 172219019Sgabor