/* * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #define L2CPUSRSELR_EL1 S3_3_c15_c0_6 #define L2CPUSRDR_EL1 S3_3_c15_c0_7 #define arm64_sys_reg_read(reg) ({ \ u64 __val; \ asm volatile("mrs %0, " __stringify(reg) : "=r" (__val)); \ __val; \ }) #define arm64_sys_reg_write(reg, val) { \ asm volatile("msr " __stringify(reg) ", %0" : : "r" (val)); \ } static DEFINE_RAW_SPINLOCK(l2_access_lock); /** * set_l2_indirect_reg: write value to an L2 register * @reg: Address of L2 register. * @value: Value to be written to register. * * Use architecturally required barriers for ordering between system register * accesses, and system registers with respect to device memory */ void set_l2_indirect_reg(u64 reg, u64 val) { unsigned long flags; mb(); raw_spin_lock_irqsave(&l2_access_lock, flags); uncached_logk(LOGK_L2CPWRITE, (void *)reg); arm64_sys_reg_write(L2CPUSRSELR_EL1, reg); isb(); arm64_sys_reg_write(L2CPUSRDR_EL1, val); isb(); raw_spin_unlock_irqrestore(&l2_access_lock, flags); } EXPORT_SYMBOL(set_l2_indirect_reg); /** * get_l2_indirect_reg: read an L2 register value * @reg: Address of L2 register. * * Use architecturally required barriers for ordering between system register * accesses, and system registers with respect to device memory */ u64 get_l2_indirect_reg(u64 reg) { u64 val; unsigned long flags; raw_spin_lock_irqsave(&l2_access_lock, flags); uncached_logk(LOGK_L2CPREAD, (void *)reg); arm64_sys_reg_write(L2CPUSRSELR_EL1, reg); isb(); val = arm64_sys_reg_read(L2CPUSRDR_EL1); raw_spin_unlock_irqrestore(&l2_access_lock, flags); return val; } EXPORT_SYMBOL(get_l2_indirect_reg); #if defined(CONFIG_DEBUG_FS) static u32 debug_addr; static int debug_target_cpu; static void remote_l2_ia_read(void *data) { u64 *val = data; *val = get_l2_indirect_reg(debug_addr); } static void remote_l2_ia_write(void *data) { u64 *val = data; set_l2_indirect_reg(debug_addr, *val); } static int l2_indirect_target_cpu_set(void *data, u64 val) { if (val > num_possible_cpus()) return -EINVAL; debug_target_cpu = val; return 0; } static int l2_indirect_target_cpu_get(void *data, u64 *val) { *val = debug_target_cpu; return 0; } DEFINE_SIMPLE_ATTRIBUTE(l2_ia_cpu_fops, l2_indirect_target_cpu_get, l2_indirect_target_cpu_set, "%llu\n"); static int l2_indirect_addr_set(void *data, u64 val) { debug_addr = val; return 0; } static int l2_indirect_addr_get(void *data, u64 *val) { *val = debug_addr; return 0; } DEFINE_SIMPLE_ATTRIBUTE(l2_ia_addr_fops, l2_indirect_addr_get, l2_indirect_addr_set, "%llu\n"); static int l2_indirect_val_set(void *data, u64 val) { return smp_call_function_single(debug_target_cpu, remote_l2_ia_write, &val, 1); } DEFINE_SIMPLE_ATTRIBUTE(l2_ia_set_fops, NULL, l2_indirect_val_set, "%llu\n"); static int l2_indirect_get(void *data, u64 *val) { return smp_call_function_single(debug_target_cpu, remote_l2_ia_read, val, 1); } DEFINE_SIMPLE_ATTRIBUTE(l2_ia_get_fops, l2_indirect_get, NULL, "%llu\n"); /** * l2_ia_debug_init() - Initialize L2 indirect access register debugfs */ static int l2_ia_debug_init(void) { static struct dentry *debugfs_base; debugfs_base = debugfs_create_dir("l2_indirect", NULL); if (!debugfs_base) return -ENOMEM; if (!debugfs_create_file("get", S_IRUGO, debugfs_base, NULL, &l2_ia_get_fops)) return -ENOMEM; if (!debugfs_create_file("set", S_IRUGO | S_IWUSR, debugfs_base, NULL, &l2_ia_set_fops)) return -ENOMEM; if (!debugfs_create_file("address", S_IRUGO | S_IWUSR, debugfs_base, NULL, &l2_ia_addr_fops)) return -ENOMEM; if (!debugfs_create_file("target_cpu", S_IRUGO | S_IWUSR, debugfs_base, NULL, &l2_ia_cpu_fops)) return -ENOMEM; return 0; } late_initcall(l2_ia_debug_init); #endif /* CONFIG_DEBUG_FS */