diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c index 84a75d8c4b702..6c0276ba37f86 100644 --- a/drivers/soc/qcom/cmd-db.c +++ b/drivers/soc/qcom/cmd-db.c @@ -267,6 +267,71 @@ enum cmd_db_hw_type cmd_db_read_slave_id(const char *id) } EXPORT_SYMBOL_GPL(cmd_db_read_slave_id); +/** + * cmd_db_hw_type_str() - Return the name string for an RPMh accelerator address. + * @addr: RPMh resource address whose slave ID encodes the accelerator type. + * + * Extracts the slave ID from bits [19:16] of @addr and maps it to the + * corresponding cmd_db_hw_type name. + * + * Return: A constant string: "ARC", "VRM", "BCM", or "unknown". + */ +const char *cmd_db_hw_type_str(u32 addr) +{ + switch (SLAVE_ID(addr)) { + case CMD_DB_HW_ARC: return "ARC"; + case CMD_DB_HW_VRM: return "VRM"; + case CMD_DB_HW_BCM: return "BCM"; + default: return "unknown"; + } +} +EXPORT_SYMBOL_GPL(cmd_db_hw_type_str); + +/** + * cmd_db_read_name() - Look up the resource name for a given RPMh address. + * @addr: RPMh resource address to reverse-look up. + * @buf: Output buffer to write the resource name into. + * @len: Size of @buf; must be at least CMD_DB_ID_SIZE + 1. + * + * Iterates the command DB to find the entry whose address matches @addr. + * For VRM resources, which have up to 4 contiguous 4-byte-aligned addresses + * per resource, the match is performed on bits [19:4] so that any of the + * sub-addresses resolve to the same resource name. + * + * Return: 0 on success, -ENODEV if not found or DB not ready. + */ +int cmd_db_read_name(u32 addr, char *buf, size_t len) +{ + const struct rsc_hdr *rsc_hdr; + const struct entry_header *ent; + int ret, i, j; + + ret = cmd_db_ready(); + if (ret) + return ret; + + for (i = 0; i < MAX_SLV_ID; i++) { + rsc_hdr = &cmd_db_header->header[i]; + if (!rsc_hdr->slv_id) + break; + + ent = rsc_to_entry_header(rsc_hdr); + for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) { + u32 ent_addr = le32_to_cpu(ent->addr); + + if (cmd_db_match_resource_addr(ent_addr, addr)) { + snprintf(buf, len, "%.*s", + (int)strnlen(ent->id, sizeof(ent->id)), + ent->id); + return 0; + } + } + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(cmd_db_read_name); + #ifdef CONFIG_DEBUG_FS static int cmd_db_debugfs_dump(struct seq_file *seq, void *p) { diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index e3cf1beff8038..c06f81da140e0 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -98,6 +98,9 @@ struct rsc_ver { * @tcs_base: Start address of the TCS registers in this controller. * @id: Instance id in the controller (Direct Resource Voter). * @num_tcs: Number of TCSes in this DRV. + * @irq: IRQ number used by the TCS completion interrupt; + * stored to allow querying GIC pending state for + * timeout diagnostics. * @rsc_pm: CPU PM notifier for controller. * Used when solver mode is not present. * @cpus_in_pm: Number of CPUs not in idle power collapse. @@ -123,6 +126,7 @@ struct rsc_drv { void __iomem *tcs_base; int id; int num_tcs; + int irq; struct notifier_block rsc_pm; struct notifier_block genpd_nb; atomic_t cpus_in_pm; @@ -141,6 +145,7 @@ int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg); void rpmh_rsc_invalidate(struct rsc_drv *drv); void rpmh_rsc_write_next_wakeup(struct rsc_drv *drv); +void rpmh_rsc_debug(struct rsc_drv *drv, struct completion *compl); void rpmh_tx_done(const struct tcs_request *msg); int rpmh_flush(struct rpmh_ctrlr *ctrlr); diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index c6f7d5c9c493d..5483fe2f04a60 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ enum { #define CMD_MSGID_LEN 8 #define CMD_MSGID_RESP_REQ BIT(8) #define CMD_MSGID_WRITE BIT(16) +#define CMD_STATUS_TRIGGERED BIT(0) #define CMD_STATUS_ISSUED BIT(8) #define CMD_STATUS_COMPL BIT(16) @@ -684,6 +686,92 @@ int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) return 0; } +static void print_tcs_info(struct rsc_drv *drv, int tcs_id, + bool *aoss_irq_sts) +{ + const struct tcs_request *req = get_req_from_tcs(drv, tcs_id); + unsigned long cmds_enabled; + char rname[CMD_DB_ID_SIZE + 1]; + u32 addr, data, msgid, sts, irq_sts; + bool in_use = test_bit(tcs_id, drv->tcs_in_use); + int i; + + sts = read_tcs_reg(drv, drv->regs[RSC_DRV_STATUS], tcs_id); + cmds_enabled = read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id); + if (!cmds_enabled) + return; + + if (!req) + goto print_tcs_data; + + data = read_tcs_reg(drv, drv->regs[RSC_DRV_CONTROL], tcs_id); + irq_sts = readl_relaxed(drv->tcs_base + drv->regs[RSC_DRV_IRQ_STATUS]); + pr_warn("Request: tcs-in-use:%s state=%d wait_for_compl=%u\n", + in_use ? "YES" : "NO", + req->state, req->wait_for_compl); + pr_warn("TCS=%d [ctrlr-sts:%s amc-mode:0x%x irq-sts:%s]\n", + tcs_id, sts ? "IDLE" : "BUSY", data, + (irq_sts & BIT(tcs_id)) ? "DONE" : "WAITING"); + + *aoss_irq_sts = !!(irq_sts & BIT(tcs_id)); + +print_tcs_data: + /* All TCSes on a given SoC have the same number of commands per TCS. */ + for_each_set_bit(i, &cmds_enabled, drv->tcs[0].ncpt) { + addr = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], tcs_id, i); + data = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, i); + msgid = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_MSGID], tcs_id, i); + sts = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_STATUS], tcs_id, i); + cmd_db_read_name(addr, rname, sizeof(rname)); + pr_warn("\tCMD=%d [addr=0x%x(%s/%s) data=0x%x %s sts=%s%s%s]\n", + i, addr, cmd_db_hw_type_str(addr), rname, data, + (msgid & CMD_MSGID_RESP_REQ) ? "resp-required" : "fire-n-forget", + (sts & CMD_STATUS_TRIGGERED) ? "triggered" : "-", + (sts & CMD_STATUS_ISSUED) ? "+sent-to-aoss" : "", + (sts & CMD_STATUS_COMPL) ? "+resp-received" : ""); + } +} + +/** + * rpmh_rsc_debug() - Dump debug information on a transfer timeout. + * @drv: The RSC controller. + * @compl: The completion object that timed out. + * + * Dumps TCS state for all in-use TCSes and reports which accelerators + * did not respond, to aid in diagnosing RPMH timeout failures. + */ +void rpmh_rsc_debug(struct rsc_drv *drv, struct completion *compl) +{ + struct irq_data *rsc_irq_data = irq_get_irq_data(drv->irq); + bool gic_irq_sts, aoss_irq_sts = false; + int i, busy = 0; + + pr_err("RSC:%s\n", drv->name); + + for (i = 0; i < drv->num_tcs; i++) { + if (!test_bit(i, drv->tcs_in_use)) + continue; + busy++; + print_tcs_info(drv, i, &aoss_irq_sts); + } + + if (!rsc_irq_data) { + pr_err("No IRQ data for RSC:%s\n", drv->name); + return; + } + + irq_get_irqchip_state(drv->irq, IRQCHIP_STATE_PENDING, &gic_irq_sts); + pr_warn("HW IRQ %lu is %s at GIC\n", rsc_irq_data->hwirq, + gic_irq_sts ? "PENDING" : "NOT PENDING"); + pr_warn("Completion is %s\n", + completion_done(compl) ? "done" : "not done"); + + if ((busy && !gic_irq_sts) || !aoss_irq_sts) + pr_err("ERROR: Accelerator(s) at AOSS did not respond\n"); + else if (gic_irq_sts) + pr_err("ERROR: IRQ pending at GIC but not handled within timeout\n"); +} + /** * find_slots() - Find a place to write the given message. * @tcs: The tcs group to search. @@ -1092,6 +1180,8 @@ static int rpmh_rsc_probe(struct platform_device *pdev) if (ret) return ret; + drv->irq = irq; + /* * CPU PM/genpd notification are not required for controllers that support * 'HW solver' mode where they can be in autonomous mode executing low diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 8903ed956312d..c8e7c240746db 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -255,6 +255,7 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, { DECLARE_COMPLETION_ONSTACK(compl); DEFINE_RPMH_MSG_ONSTACK(dev, state, &compl, rpm_msg); + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); int ret; ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n); @@ -266,6 +267,8 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, return ret; ret = wait_for_completion_timeout(&compl, RPMH_TIMEOUT_MS); + if (!ret) + rpmh_rsc_debug(ctrlr_to_drv(ctrlr), &compl); WARN_ON(!ret); return (ret > 0) ? 0 : -ETIMEDOUT; } @@ -383,6 +386,7 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state, * the completion that we're going to free once * we've returned from this function. */ + rpmh_rsc_debug(ctrlr_to_drv(ctrlr), &compls[i]); WARN_ON(1); ret = -ETIMEDOUT; goto exit; diff --git a/include/soc/qcom/cmd-db.h b/include/soc/qcom/cmd-db.h index 47a6cab75e630..d03d64f3407d3 100644 --- a/include/soc/qcom/cmd-db.h +++ b/include/soc/qcom/cmd-db.h @@ -9,6 +9,8 @@ #include +#define CMD_DB_ID_SIZE 8 + enum cmd_db_hw_type { CMD_DB_HW_INVALID = 0, CMD_DB_HW_MIN = 3, @@ -29,6 +31,9 @@ bool cmd_db_match_resource_addr(u32 addr1, u32 addr2); enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id); int cmd_db_ready(void); + +const char *cmd_db_hw_type_str(u32 addr); +int cmd_db_read_name(u32 addr, char *buf, size_t len); #else static inline u32 cmd_db_read_addr(const char *resource_id) { return 0; } @@ -44,5 +49,10 @@ static inline enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id) static inline int cmd_db_ready(void) { return -ENODEV; } + +static inline const char *cmd_db_hw_type_str(u32 addr) +{ return "unknown"; } +static inline int cmd_db_read_name(u32 addr, char *buf, size_t len) +{ return -ENODEV; } #endif /* CONFIG_QCOM_COMMAND_DB */ #endif /* __QCOM_COMMAND_DB_H__ */