184 lines
4.4 KiB
C
184 lines
4.4 KiB
C
/*
|
|
* 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 <linux/spinlock.h>
|
|
#include <linux/module.h>
|
|
#include <linux/msm_rtb.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <asm/cputype.h>
|
|
|
|
#include <soc/qcom/kryo-l2-accessors.h>
|
|
|
|
#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 */
|