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:
Jeevan Shriram 2019-10-22 05:59:47 -07:00 • committed by Gerrit - the friendly Code Review server
commit b249ddf502

View file

@ -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", &register_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);
}