From b249ddf5026575b215347e31aa0504701ae1b702 Mon Sep 17 00:00:00 2001 From: Jeevan Shriram Date: Tue, 22 Oct 2019 05:59:47 -0700 Subject: [PATCH] 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 --- drivers/soc/qcom/memory_dump_v2.c | 419 ++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) diff --git a/drivers/soc/qcom/memory_dump_v2.c b/drivers/soc/qcom/memory_dump_v2.c index cb1ce074d205..ed29e058f787 100644 --- a/drivers/soc/qcom/memory_dump_v2.c +++ b/drivers/soc/qcom/memory_dump_v2.c @@ -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); }