18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci#include <linux/fs.h> 38c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 48c2ecf20Sopenharmony_ci#include <asm/octeon/octeon.h> 58c2ecf20Sopenharmony_ci#include <asm/octeon/cvmx-ciu-defs.h> 68c2ecf20Sopenharmony_ci#include <asm/octeon/cvmx.h> 78c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#define TIMER_NUM 3 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistatic bool reset_stats; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct latency_info { 178c2ecf20Sopenharmony_ci u64 io_interval; 188c2ecf20Sopenharmony_ci u64 cpu_interval; 198c2ecf20Sopenharmony_ci u64 timer_start1; 208c2ecf20Sopenharmony_ci u64 timer_start2; 218c2ecf20Sopenharmony_ci u64 max_latency; 228c2ecf20Sopenharmony_ci u64 min_latency; 238c2ecf20Sopenharmony_ci u64 latency_sum; 248c2ecf20Sopenharmony_ci u64 average_latency; 258c2ecf20Sopenharmony_ci u64 interrupt_cnt; 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistatic struct latency_info li; 298c2ecf20Sopenharmony_cistatic struct dentry *dir; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int show_latency(struct seq_file *m, void *v) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci u64 cpuclk, avg, max, min; 348c2ecf20Sopenharmony_ci struct latency_info curr_li = li; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci cpuclk = octeon_get_clock_rate(); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci max = (curr_li.max_latency * 1000000000) / cpuclk; 398c2ecf20Sopenharmony_ci min = (curr_li.min_latency * 1000000000) / cpuclk; 408c2ecf20Sopenharmony_ci avg = (curr_li.latency_sum * 1000000000) / (cpuclk * curr_li.interrupt_cnt); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci seq_printf(m, "cnt: %10lld, avg: %7lld ns, max: %7lld ns, min: %7lld ns\n", 438c2ecf20Sopenharmony_ci curr_li.interrupt_cnt, avg, max, min); 448c2ecf20Sopenharmony_ci return 0; 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic int oct_ilm_open(struct inode *inode, struct file *file) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci return single_open(file, show_latency, NULL); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic const struct file_operations oct_ilm_ops = { 538c2ecf20Sopenharmony_ci .open = oct_ilm_open, 548c2ecf20Sopenharmony_ci .read = seq_read, 558c2ecf20Sopenharmony_ci .llseek = seq_lseek, 568c2ecf20Sopenharmony_ci .release = single_release, 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int reset_statistics(void *data, u64 value) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci reset_stats = true; 628c2ecf20Sopenharmony_ci return 0; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ciDEFINE_SIMPLE_ATTRIBUTE(reset_statistics_ops, NULL, reset_statistics, "%llu\n"); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic void init_debugfs(void) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci dir = debugfs_create_dir("oct_ilm", 0); 708c2ecf20Sopenharmony_ci debugfs_create_file("statistics", 0222, dir, NULL, &oct_ilm_ops); 718c2ecf20Sopenharmony_ci debugfs_create_file("reset", 0222, dir, NULL, &reset_statistics_ops); 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic void init_latency_info(struct latency_info *li, int startup) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci /* interval in milli seconds after which the interrupt will 778c2ecf20Sopenharmony_ci * be triggered 788c2ecf20Sopenharmony_ci */ 798c2ecf20Sopenharmony_ci int interval = 1; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci if (startup) { 828c2ecf20Sopenharmony_ci /* Calculating by the amounts io clock and cpu clock would 838c2ecf20Sopenharmony_ci * increment in interval amount of ms 848c2ecf20Sopenharmony_ci */ 858c2ecf20Sopenharmony_ci li->io_interval = (octeon_get_io_clock_rate() * interval) / 1000; 868c2ecf20Sopenharmony_ci li->cpu_interval = (octeon_get_clock_rate() * interval) / 1000; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci li->timer_start1 = 0; 898c2ecf20Sopenharmony_ci li->timer_start2 = 0; 908c2ecf20Sopenharmony_ci li->max_latency = 0; 918c2ecf20Sopenharmony_ci li->min_latency = (u64)-1; 928c2ecf20Sopenharmony_ci li->latency_sum = 0; 938c2ecf20Sopenharmony_ci li->interrupt_cnt = 0; 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic void start_timer(int timer, u64 interval) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci union cvmx_ciu_timx timx; 1008c2ecf20Sopenharmony_ci unsigned long flags; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci timx.u64 = 0; 1038c2ecf20Sopenharmony_ci timx.s.one_shot = 1; 1048c2ecf20Sopenharmony_ci timx.s.len = interval; 1058c2ecf20Sopenharmony_ci raw_local_irq_save(flags); 1068c2ecf20Sopenharmony_ci li.timer_start1 = read_c0_cvmcount(); 1078c2ecf20Sopenharmony_ci cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); 1088c2ecf20Sopenharmony_ci /* Read it back to force wait until register is written. */ 1098c2ecf20Sopenharmony_ci timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); 1108c2ecf20Sopenharmony_ci li.timer_start2 = read_c0_cvmcount(); 1118c2ecf20Sopenharmony_ci raw_local_irq_restore(flags); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic irqreturn_t cvm_oct_ciu_timer_interrupt(int cpl, void *dev_id) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci u64 last_latency; 1188c2ecf20Sopenharmony_ci u64 last_int_cnt; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci if (reset_stats) { 1218c2ecf20Sopenharmony_ci init_latency_info(&li, 0); 1228c2ecf20Sopenharmony_ci reset_stats = false; 1238c2ecf20Sopenharmony_ci } else { 1248c2ecf20Sopenharmony_ci last_int_cnt = read_c0_cvmcount(); 1258c2ecf20Sopenharmony_ci last_latency = last_int_cnt - (li.timer_start1 + li.cpu_interval); 1268c2ecf20Sopenharmony_ci li.interrupt_cnt++; 1278c2ecf20Sopenharmony_ci li.latency_sum += last_latency; 1288c2ecf20Sopenharmony_ci if (last_latency > li.max_latency) 1298c2ecf20Sopenharmony_ci li.max_latency = last_latency; 1308c2ecf20Sopenharmony_ci if (last_latency < li.min_latency) 1318c2ecf20Sopenharmony_ci li.min_latency = last_latency; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci start_timer(TIMER_NUM, li.io_interval); 1348c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic void disable_timer(int timer) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci union cvmx_ciu_timx timx; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci timx.s.one_shot = 0; 1428c2ecf20Sopenharmony_ci timx.s.len = 0; 1438c2ecf20Sopenharmony_ci cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64); 1448c2ecf20Sopenharmony_ci /* Read it back to force immediate write of timer register*/ 1458c2ecf20Sopenharmony_ci timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer)); 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic __init int oct_ilm_module_init(void) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci int rc; 1518c2ecf20Sopenharmony_ci int irq = OCTEON_IRQ_TIMER0 + TIMER_NUM; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci init_debugfs(); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci rc = request_irq(irq, cvm_oct_ciu_timer_interrupt, IRQF_NO_THREAD, 1568c2ecf20Sopenharmony_ci "oct_ilm", 0); 1578c2ecf20Sopenharmony_ci if (rc) { 1588c2ecf20Sopenharmony_ci WARN(1, "Could not acquire IRQ %d", irq); 1598c2ecf20Sopenharmony_ci goto err_irq; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci init_latency_info(&li, 1); 1638c2ecf20Sopenharmony_ci start_timer(TIMER_NUM, li.io_interval); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_cierr_irq: 1678c2ecf20Sopenharmony_ci debugfs_remove_recursive(dir); 1688c2ecf20Sopenharmony_ci return rc; 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic __exit void oct_ilm_module_exit(void) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci disable_timer(TIMER_NUM); 1748c2ecf20Sopenharmony_ci debugfs_remove_recursive(dir); 1758c2ecf20Sopenharmony_ci free_irq(OCTEON_IRQ_TIMER0 + TIMER_NUM, 0); 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cimodule_exit(oct_ilm_module_exit); 1798c2ecf20Sopenharmony_cimodule_init(oct_ilm_module_init); 1808c2ecf20Sopenharmony_ciMODULE_AUTHOR("Venkat Subbiah, Cavium"); 1818c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Measures interrupt latency on Octeon chips."); 1828c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 183