mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-10 14:19:09 -04:00
soc: mem_dump: Add support for CPU subsystem register dump
Add support in memory dump driver to dump the cpuss registers for debugging CPUSS issues. Change-Id: I5ab5d2bfc0a34aa9f00c826928046324f1d2c574 Signed-off-by: Jeevan Shriram <jshriram@codeaurora.org>
This commit is contained in:
parent
b3f2f0d43c
commit
b249ddf502
1 changed files with 419 additions and 0 deletions
|
|
@ -21,6 +21,39 @@
|
|||
|
||||
#define MSM_DUMP_TABLE_VERSION MSM_DUMP_MAKE_VERSION(2, 0)
|
||||
|
||||
#define SCM_CMD_DEBUG_LAR_UNLOCK 0x4
|
||||
|
||||
#define CPUSS_REGDUMP 0xEF
|
||||
|
||||
#define INPUT_DATA_BY_HLOS 0x00C0FFEE
|
||||
#define FORMAT_VERSION_1 0x1
|
||||
#define CORE_REG_NUM_DEFAULT 0x1
|
||||
|
||||
#define MAGIC_INDEX 0
|
||||
#define FORMAT_VERSION_INDEX 1
|
||||
#define SYS_REG_INPUT_INDEX 2
|
||||
#define OUTPUT_DUMP_INDEX 3
|
||||
#define PERCORE_INDEX 4
|
||||
#define SYSTEM_REGS_INPUT_INDEX 5
|
||||
|
||||
struct cpuss_dump_data {
|
||||
void *dump_vaddr;
|
||||
u32 size;
|
||||
u32 core_reg_num;
|
||||
u32 core_reg_used_num;
|
||||
u32 core_reg_end_index;
|
||||
u32 sys_reg_size;
|
||||
u32 used_memory;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
struct reg_dump_data {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t system_regs_input_index;
|
||||
uint32_t regdump_output_byte_offset;
|
||||
};
|
||||
|
||||
struct msm_dump_table {
|
||||
uint32_t version;
|
||||
uint32_t num_entries;
|
||||
|
|
@ -34,6 +67,388 @@ struct msm_memory_dump {
|
|||
|
||||
static struct msm_memory_dump memdump;
|
||||
|
||||
/**
|
||||
* update_reg_dump_table - update the register dump table
|
||||
* @core_reg_num: the number of per-core registers
|
||||
*
|
||||
* This function calculates system_regs_input_index and
|
||||
* regdump_output_byte_offset to store into the dump memory.
|
||||
* It also updates members of cpudata by the parameter core_reg_num.
|
||||
*
|
||||
* Returns 0 on success, or -ENOMEM on error of no enough memory.
|
||||
*/
|
||||
static int update_reg_dump_table(struct device *dev, u32 core_reg_num)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 system_regs_input_index = SYSTEM_REGS_INPUT_INDEX +
|
||||
core_reg_num * 2;
|
||||
u32 regdump_output_byte_offset = (system_regs_input_index + 1)
|
||||
* sizeof(uint32_t);
|
||||
struct reg_dump_data *p;
|
||||
struct cpuss_dump_data *cpudata = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&cpudata->mutex);
|
||||
|
||||
if (regdump_output_byte_offset >= cpudata->size ||
|
||||
regdump_output_byte_offset / sizeof(uint32_t)
|
||||
< system_regs_input_index + 1) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
cpudata->core_reg_num = core_reg_num;
|
||||
cpudata->core_reg_used_num = 0;
|
||||
cpudata->core_reg_end_index = PERCORE_INDEX;
|
||||
cpudata->sys_reg_size = 0;
|
||||
cpudata->used_memory = regdump_output_byte_offset;
|
||||
|
||||
memset(cpudata->dump_vaddr, 0xDE, cpudata->size);
|
||||
p = (struct reg_dump_data *)cpudata->dump_vaddr;
|
||||
p->magic = INPUT_DATA_BY_HLOS;
|
||||
p->version = FORMAT_VERSION_1;
|
||||
p->system_regs_input_index = system_regs_input_index;
|
||||
p->regdump_output_byte_offset = regdump_output_byte_offset;
|
||||
memset((uint32_t *)cpudata->dump_vaddr + PERCORE_INDEX, 0x0,
|
||||
(system_regs_input_index - PERCORE_INDEX + 1)
|
||||
* sizeof(uint32_t));
|
||||
|
||||
err:
|
||||
mutex_unlock(&cpudata->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t core_reg_num_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int ret;
|
||||
struct cpuss_dump_data *cpudata = dev_get_drvdata(dev);
|
||||
|
||||
if (!cpudata)
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&cpudata->mutex);
|
||||
|
||||
ret = scnprintf(buf, PAGE_SIZE, "%u\n", cpudata->core_reg_num);
|
||||
|
||||
mutex_unlock(&cpudata->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t core_reg_num_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
int ret;
|
||||
unsigned int val;
|
||||
struct cpuss_dump_data *cpudata = dev_get_drvdata(dev);
|
||||
|
||||
if (kstrtouint(buf, 16, &val))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&cpudata->mutex);
|
||||
|
||||
if (cpudata->core_reg_used_num || cpudata->sys_reg_size) {
|
||||
dev_err(dev, "Couldn't set core_reg_num, register available in list\n");
|
||||
ret = -EPERM;
|
||||
goto err;
|
||||
}
|
||||
if (val == cpudata->core_reg_num) {
|
||||
ret = 0;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_unlock(&cpudata->mutex);
|
||||
|
||||
ret = update_reg_dump_table(dev, val);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't set core_reg_num, no enough memory\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
err:
|
||||
mutex_unlock(&cpudata->mutex);
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_RW(core_reg_num);
|
||||
|
||||
/**
|
||||
* This function shows configs of per-core and system registers.
|
||||
*/
|
||||
static ssize_t register_config_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
char local_buf[64];
|
||||
int len = 0, count = 0;
|
||||
int index, system_index_start, index_end;
|
||||
uint32_t register_offset, length_in_bytes;
|
||||
uint32_t length_in_words;
|
||||
uint32_t *p;
|
||||
struct cpuss_dump_data *cpudata = dev_get_drvdata(dev);
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
if (!cpudata)
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&cpudata->mutex);
|
||||
|
||||
p = (uint32_t *)cpudata->dump_vaddr;
|
||||
|
||||
/* print per-core & system registers */
|
||||
len = snprintf(local_buf, 64, "per-core registers:\n");
|
||||
strlcat(buf, local_buf, PAGE_SIZE);
|
||||
count += len;
|
||||
|
||||
system_index_start = *(p + SYS_REG_INPUT_INDEX);
|
||||
index_end = system_index_start +
|
||||
cpudata->sys_reg_size / sizeof(uint32_t) + 1;
|
||||
for (index = PERCORE_INDEX; index < index_end;) {
|
||||
if (index == system_index_start) {
|
||||
len = snprintf(local_buf, 64, "system registers:\n");
|
||||
if ((count + len) > PAGE_SIZE) {
|
||||
dev_err(dev, "Couldn't write complete config\n");
|
||||
break;
|
||||
}
|
||||
|
||||
strlcat(buf, local_buf, PAGE_SIZE);
|
||||
count += len;
|
||||
}
|
||||
|
||||
register_offset = *(p + index);
|
||||
if (register_offset == 0) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (register_offset & 0x3) {
|
||||
length_in_words = register_offset & 0x3;
|
||||
length_in_bytes = length_in_words << 2;
|
||||
len = snprintf(local_buf, 64,
|
||||
"Index: 0x%x, addr: 0x%x\n",
|
||||
index, register_offset);
|
||||
index++;
|
||||
} else {
|
||||
length_in_bytes = *(p + index + 1);
|
||||
len = snprintf(local_buf, 64,
|
||||
"Index: 0x%x, addr: 0x%x, length: 0x%x\n",
|
||||
index, register_offset, length_in_bytes);
|
||||
index += 2;
|
||||
}
|
||||
|
||||
if ((count + len) > PAGE_SIZE) {
|
||||
dev_err(dev, "Couldn't write complete config\n");
|
||||
break;
|
||||
}
|
||||
|
||||
strlcat(buf, local_buf, PAGE_SIZE);
|
||||
count += len;
|
||||
}
|
||||
|
||||
mutex_unlock(&cpudata->mutex);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function sets configs of per-core or system registers.
|
||||
*/
|
||||
static ssize_t register_config_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
int ret;
|
||||
uint32_t register_offset, length_in_bytes, per_core = 0;
|
||||
uint32_t length_in_words;
|
||||
int nval;
|
||||
uint32_t num_cores;
|
||||
u32 extra_memory;
|
||||
u32 used_memory;
|
||||
u32 system_reg_end_index;
|
||||
uint32_t *p;
|
||||
struct cpuss_dump_data *cpudata = dev_get_drvdata(dev);
|
||||
|
||||
nval = sscanf(buf, "%x %x %u", ®ister_offset,
|
||||
&length_in_bytes, &per_core);
|
||||
if (nval != 2 && nval != 3)
|
||||
return -EINVAL;
|
||||
if (per_core > 1)
|
||||
return -EINVAL;
|
||||
if (register_offset & 0x3) {
|
||||
dev_err(dev, "Invalid address, must be 4 byte aligned\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (length_in_bytes & 0x3) {
|
||||
dev_err(dev, "Invalid length, must be 4 byte aligned\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (length_in_bytes == 0) {
|
||||
dev_err(dev, "Invalid length of 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&cpudata->mutex);
|
||||
|
||||
p = (uint32_t *)cpudata->dump_vaddr;
|
||||
length_in_words = length_in_bytes >> 2;
|
||||
if (per_core) { /* per-core register */
|
||||
if (cpudata->core_reg_used_num == cpudata->core_reg_num) {
|
||||
dev_err(dev, "Couldn't add per-core config, out of range\n");
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
num_cores = num_possible_cpus();
|
||||
extra_memory = length_in_bytes * num_cores;
|
||||
used_memory = cpudata->used_memory + extra_memory;
|
||||
if (extra_memory / num_cores < length_in_bytes ||
|
||||
used_memory > cpudata->size ||
|
||||
used_memory < cpudata->used_memory) {
|
||||
dev_err(dev, "Couldn't add per-core reg config, no enough memory\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (length_in_words > 3) {
|
||||
*(p + cpudata->core_reg_end_index) = register_offset;
|
||||
*(p + cpudata->core_reg_end_index + 1) =
|
||||
length_in_bytes;
|
||||
cpudata->core_reg_end_index += 2;
|
||||
} else {
|
||||
*(p + cpudata->core_reg_end_index) = register_offset |
|
||||
length_in_words;
|
||||
cpudata->core_reg_end_index++;
|
||||
}
|
||||
|
||||
cpudata->core_reg_used_num++;
|
||||
cpudata->used_memory = used_memory;
|
||||
} else { /* system register */
|
||||
system_reg_end_index = *(p + SYS_REG_INPUT_INDEX) +
|
||||
cpudata->sys_reg_size / sizeof(uint32_t);
|
||||
|
||||
if (length_in_words > 3) {
|
||||
extra_memory = sizeof(uint32_t) * 2 + length_in_bytes;
|
||||
used_memory = cpudata->used_memory + extra_memory;
|
||||
if (extra_memory < length_in_bytes ||
|
||||
used_memory > cpudata->size ||
|
||||
used_memory < cpudata->used_memory) {
|
||||
dev_err(dev, "Couldn't add system reg config, no enough memory\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*(p + system_reg_end_index) = register_offset;
|
||||
*(p + system_reg_end_index + 1) = length_in_bytes;
|
||||
system_reg_end_index += 2;
|
||||
cpudata->sys_reg_size += sizeof(uint32_t) * 2;
|
||||
} else {
|
||||
extra_memory = sizeof(uint32_t) + length_in_bytes;
|
||||
used_memory = cpudata->used_memory + extra_memory;
|
||||
if (extra_memory < length_in_bytes ||
|
||||
used_memory > cpudata->size ||
|
||||
used_memory < cpudata->used_memory) {
|
||||
dev_err(dev, "Couldn't add system reg config, no enough memory\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*(p + system_reg_end_index) = register_offset |
|
||||
length_in_words;
|
||||
system_reg_end_index++;
|
||||
cpudata->sys_reg_size += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
cpudata->used_memory = used_memory;
|
||||
|
||||
*(p + system_reg_end_index) = 0x0;
|
||||
*(p + OUTPUT_DUMP_INDEX) = (system_reg_end_index + 1)
|
||||
* sizeof(uint32_t);
|
||||
}
|
||||
|
||||
ret = size;
|
||||
|
||||
err:
|
||||
mutex_unlock(&cpudata->mutex);
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_RW(register_config);
|
||||
|
||||
/**
|
||||
* This function resets the register dump table.
|
||||
*/
|
||||
static ssize_t register_reset_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (kstrtouint(buf, 16, &val))
|
||||
return -EINVAL;
|
||||
if (val != 1)
|
||||
return -EINVAL;
|
||||
|
||||
update_reg_dump_table(dev, CORE_REG_NUM_DEFAULT);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_WO(register_reset);
|
||||
|
||||
static const struct device_attribute *register_dump_attrs[] = {
|
||||
&dev_attr_core_reg_num,
|
||||
&dev_attr_register_config,
|
||||
&dev_attr_register_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int register_dump_create_files(struct device *dev,
|
||||
const struct device_attribute **attrs)
|
||||
{
|
||||
int ret = 0;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; attrs[i] != NULL; i++) {
|
||||
ret = device_create_file(dev, attrs[i]);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't create sysfs attribute: %s\n",
|
||||
attrs[i]->attr.name);
|
||||
for (j = 0; j < i; j++)
|
||||
device_remove_file(dev, attrs[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cpuss_regdump_init(struct platform_device *pdev,
|
||||
void *dump_vaddr, u32 size)
|
||||
{
|
||||
struct cpuss_dump_data *cpudata = NULL;
|
||||
int ret;
|
||||
|
||||
cpudata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct cpuss_dump_data), GFP_KERNEL);
|
||||
if (!cpudata)
|
||||
goto fail;
|
||||
|
||||
cpudata->dump_vaddr = dump_vaddr;
|
||||
cpudata->size = size;
|
||||
|
||||
mutex_init(&cpudata->mutex);
|
||||
ret = register_dump_create_files(&pdev->dev,
|
||||
register_dump_attrs);
|
||||
if (ret) {
|
||||
devm_kfree(&pdev->dev, cpudata);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, cpudata);
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
pr_err("Failed to initialize CPUSS regdump region\n");
|
||||
}
|
||||
|
||||
uint32_t msm_dump_table_version(void)
|
||||
{
|
||||
return MSM_DUMP_TABLE_VERSION;
|
||||
|
|
@ -342,6 +757,10 @@ static int mem_dump_alloc(struct platform_device *pdev)
|
|||
dev_err(&pdev->dev, "Mini dump entry failed id = %d\n",
|
||||
id);
|
||||
|
||||
if (id == CPUSS_REGDUMP)
|
||||
cpuss_regdump_init(pdev,
|
||||
(dump_vaddr + MSM_DUMP_DATA_SIZE), size);
|
||||
|
||||
dump_vaddr += (size + MSM_DUMP_DATA_SIZE);
|
||||
phys_addr += (size + MSM_DUMP_DATA_SIZE);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue