From f59e8ca5a0c7715d07eea2388c514c08a53d0b52 Mon Sep 17 00:00:00 2001 From: Amir Vajid Date: Mon, 29 Aug 2022 23:03:05 -0700 Subject: [PATCH] drivers: soc: qcom: Add snapshot of cpucp communication drivers Add snapshot of cpucp mailbox and logging drivers from msm-5.15 commit 250869d2bdc2192 ("sched: improve the scheduler")'. Change-Id: Ibe7497e4c27b0d031b6f7857f0a24ff0035e85fa Signed-off-by: Amir Vajid --- drivers/soc/qcom/Kconfig | 21 ++ drivers/soc/qcom/Makefile | 2 + drivers/soc/qcom/cpucp_log.c | 351 +++++++++++++++++++++++++++++++++ drivers/soc/qcom/qcom_cpucp.c | 230 +++++++++++++++++++++ drivers/soc/qcom/trace_cpucp.h | 40 ++++ 5 files changed, 644 insertions(+) create mode 100644 drivers/soc/qcom/cpucp_log.c create mode 100644 drivers/soc/qcom/qcom_cpucp.c create mode 100644 drivers/soc/qcom/trace_cpucp.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 9f8cba1fe784..81f33acdb3f2 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -87,6 +87,27 @@ config QCOM_MSM_IPCC Say Y here to compile the driver as a part of kernel or M to compile as a module. +config QCOM_CPUCP + tristate "Qualcomm Technologies, Inc. CPUCP driver" + depends on MAILBOX + help + Qualcomm Technologies, Inc. CPUCP driver for MSM devices. This driver + acts as a mailbox controller to do doorbell between APSS and CPUCP + subsystem. Say yes here to enable rx and tx channel between both + the subsystems. + If unsure, say n. + +config QTI_CPUCP_LOG + tristate "Qualcomm Technologies Inc. HW CPUCP Logging" + depends on QCOM_CPUCP + default n + help + CPUCP logging driver, this driver has the infra to collect logs + generated in CPUCP HW and log the buffers. + + This driver register with IPC_Logging framework, to have dedicated + buffer for cpucp hw device. + config QCOM_LLCC tristate "Qualcomm Technologies, Inc. LLCC driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index a704d065fae0..0b730aae1b0a 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -60,3 +60,5 @@ obj-$(CONFIG_GH_TLMM_VM_MEM_ACCESS) += gh_tlmm_vm_mem_access.o obj-$(CONFIG_QCOM_RAMDUMP) += qcom_ramdump.o obj-$(CONFIG_QCOM_ICC_BWMON) += icc-bwmon.o obj-$(CONFIG_QCOM_DCVS) += dcvs/ +obj-$(CONFIG_QCOM_CPUCP) += qcom_cpucp.o +obj-$(CONFIG_QTI_CPUCP_LOG) += cpucp_log.o diff --git a/drivers/soc/qcom/cpucp_log.c b/drivers/soc/qcom/cpucp_log.c new file mode 100644 index 000000000000..d033db6a4797 --- /dev/null +++ b/drivers/soc/qcom/cpucp_log.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PRINT_SIZE 1024 +#define MAX_BUF_NUM 4 +#define MAX_RESIDUAL_SIZE MAX_PRINT_SIZE +#define SIZE_ADJUST 4 +#define SRC_OFFSET 4 + +#define CREATE_TRACE_POINTS +#include "trace_cpucp.h" + +struct remote_mem { + void __iomem *start; + unsigned long long size; +}; + +struct cpucp_buf { + struct list_head node; + char *buf; + u32 size; + u32 cpy_idx; +}; + +struct cpucp_log_info { + struct remote_mem *rmem; + struct mbox_client cl; + struct mbox_chan *ch; + struct delayed_work work; + struct device *dev; + void __iomem *base; + unsigned int rmem_idx; + unsigned int num_bufs; + unsigned int total_buf_size; + char *rem_buf; + char *glb_buf; + int rem_len; + spinlock_t free_list_lock; + spinlock_t full_list_lock; +}; + +static LIST_HEAD(full_buffers_list); +static LIST_HEAD(free_buffers_list); +static struct workqueue_struct *cpucp_wq; + +static inline bool get_last_newline(char *buf, int size, int *cnt) +{ + int i; + + for (i = (size - 1); i >= 0 ; i--) { + if (buf[i] == '\n') { + buf[i] = '\0'; + *cnt = i + 1; + return true; + } + } + + *cnt = size; + return false; +} + +static void cpucp_log_work(struct work_struct *work) +{ + struct cpucp_log_info *info = container_of(work, + struct cpucp_log_info, + work.work); + char *src; + int buf_start = 0; + int cnt = 0, print_size = 0, buf_size = 0; + bool ret; + char tmp_buf[MAX_PRINT_SIZE + 1]; + struct cpucp_buf *buf_node; + unsigned long flags; + + while (1) { + spin_lock_irqsave(&info->full_list_lock, flags); + if (list_empty(&full_buffers_list)) { + spin_unlock_irqrestore(&info->full_list_lock, flags); + return; + } + buf_node = list_first_entry(&full_buffers_list, + struct cpucp_buf, node); + list_del(&buf_node->node); + spin_unlock_irqrestore(&info->full_list_lock, flags); + buf_start = buf_node->cpy_idx - info->rem_len; + src = &buf_node->buf[buf_start]; + buf_size = buf_node->size + info->rem_len; + if (info->rem_len) { + memcpy(&buf_node->buf[buf_start], + info->rem_buf, info->rem_len); + info->rem_len = 0; + } + do { + print_size = (buf_size >= MAX_PRINT_SIZE) ? + MAX_PRINT_SIZE : buf_size; + ret = get_last_newline(src, print_size, &cnt); + if (cnt == print_size) { + if (!ret && buf_size < MAX_PRINT_SIZE) { + info->rem_len = buf_size; + memcpy(info->rem_buf, src, buf_size); + goto out; + } else { + snprintf(tmp_buf, print_size + 1, "%s", src); + trace_cpucp_log(tmp_buf); + } + } else + trace_cpucp_log(src); + + buf_start += cnt; + buf_size -= cnt; + src = &buf_node->buf[buf_start]; + } while (buf_size > 0); + +out: + spin_lock_irqsave(&info->free_list_lock, flags); + list_add_tail(&buf_node->node, &free_buffers_list); + spin_unlock_irqrestore(&info->free_list_lock, flags); + } +} + +static struct cpucp_buf *get_free_buffer(struct cpucp_log_info *info) +{ + struct cpucp_buf *buf_node; + unsigned long flags; + + spin_lock_irqsave(&info->free_list_lock, flags); + if (list_empty(&free_buffers_list)) { + spin_unlock_irqrestore(&info->free_list_lock, flags); + return NULL; + } + + buf_node = list_first_entry(&free_buffers_list, + struct cpucp_buf, node); + list_del(&buf_node->node); + spin_unlock_irqrestore(&info->free_list_lock, flags); + return buf_node; +} + +static void cpucp_log_rx(struct mbox_client *client, void *msg) +{ + struct cpucp_log_info *info = dev_get_drvdata(client->dev); + struct device *dev = info->dev; + struct cpucp_buf *buf_node; + struct remote_mem *rmem; + void __iomem *src; + u32 marker; + unsigned long long rmem_size; + unsigned long flags; + int src_offset = 0; + int size_adj = 0; + + buf_node = get_free_buffer(info); + if (!buf_node) { + dev_err(dev, "global buffer full dropping buffers\n"); + return; + } + + marker = *(u32 *)(info->rmem)->start; + if (marker <= info->rmem->size) { + info->rmem_idx = 0; + rmem_size = marker; + } else if (marker <= info->total_buf_size) { + info->rmem_idx = 1; + rmem_size = marker - info->rmem->size; + } else { + pr_err("%s: Log marker incorrect: %u\n", __func__, marker); + return; + } + + if (info->rmem_idx == 0) { + size_adj = SIZE_ADJUST; + src_offset = SRC_OFFSET; + } + + rmem = info->rmem + info->rmem_idx; + rmem_size -= size_adj; + src = rmem->start + src_offset; + memcpy_fromio(&buf_node->buf[buf_node->cpy_idx], src, rmem_size); + buf_node->size = rmem_size; + spin_lock_irqsave(&info->full_list_lock, flags); + list_add_tail(&buf_node->node, &full_buffers_list); + spin_unlock_irqrestore(&info->full_list_lock, flags); + + if (!delayed_work_pending(&info->work)) + queue_delayed_work(cpucp_wq, &info->work, 0); +} + +static int populate_free_buffers(struct cpucp_log_info *info, + int rmem_size) +{ + int i = 0; + struct cpucp_buf *buf_nodes; + + buf_nodes = devm_kzalloc(info->dev, + MAX_BUF_NUM * sizeof(struct cpucp_buf), + GFP_KERNEL); + if (!buf_nodes) + return -ENOMEM; + + for (i = 0; i < MAX_BUF_NUM; i++) { + buf_nodes[i].buf = &info->glb_buf[i * (rmem_size + MAX_PRINT_SIZE)]; + buf_nodes[i].size = rmem_size; + buf_nodes[i].cpy_idx = MAX_PRINT_SIZE; + list_add_tail(&buf_nodes[i].node, &free_buffers_list); + } + return 0; +} + +static int cpucp_log_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cpucp_log_info *info; + struct mbox_client *cl; + int ret, i = 0; + struct resource *res; + void __iomem *mem_base; + struct remote_mem *rmem; + int prev_size = 0; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + + rmem = kcalloc(pdev->num_resources, sizeof(struct remote_mem), + GFP_KERNEL); + if (!rmem) + return -ENOMEM; + + info->rmem = rmem; + + for (i = 0; i < pdev->num_resources; i++) { + struct remote_mem *rmem = &info->rmem[i]; + + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) { + dev_err(dev, + "Failed to get the device base address\n"); + ret = -ENODEV; + goto exit; + } + + mem_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (IS_ERR(mem_base)) { + ret = PTR_ERR(mem_base); + dev_err(dev, "Failed to io remap the region err: %d\n", ret); + goto exit; + } + rmem->start = mem_base; + rmem->size = resource_size(res); + if (prev_size && (rmem->size != prev_size)) { + ret = -EINVAL; + goto exit; + } else if (!prev_size) { + prev_size = rmem->size; + } + + info->total_buf_size += rmem->size; + info->num_bufs++; + } + info->glb_buf = devm_kzalloc(dev, MAX_BUF_NUM * + (rmem->size + MAX_PRINT_SIZE), + GFP_KERNEL); + if (!info->glb_buf) { + ret = -ENOMEM; + goto exit; + } + + info->rem_buf = devm_kzalloc(dev, MAX_RESIDUAL_SIZE, GFP_KERNEL); + if (!info->rem_buf) { + ret = -ENOMEM; + goto exit; + } + + ret = populate_free_buffers(info, rmem->size); + if (ret < 0) + goto exit; + + cl = &info->cl; + cl->dev = dev; + cl->tx_block = false; + cl->knows_txdone = true; + cl->rx_callback = cpucp_log_rx; + + dev_set_drvdata(dev, info); + INIT_DEFERRABLE_WORK(&info->work, &cpucp_log_work); + spin_lock_init(&info->free_list_lock); + spin_lock_init(&info->full_list_lock); + cpucp_wq = create_freezable_workqueue("cpucp_wq"); + + info->ch = mbox_request_channel(cl, 0); + if (IS_ERR(info->ch)) { + ret = PTR_ERR(info->ch); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to request mbox info: %d\n", ret); + goto exit; + } + + dev_dbg(dev, "CPUCP logging initialized\n"); + + return 0; + +exit: + kfree(info->rmem); + return ret; +} + +static int cpucp_log_remove(struct platform_device *pdev) +{ + struct cpucp_log_info *info; + + info = dev_get_drvdata(&pdev->dev); + + mbox_free_channel(info->ch); + + return 0; +} + +static const struct of_device_id cpucp_log[] = { + {.compatible = "qcom,cpucp-log"}, + {}, +}; + +static struct platform_driver cpucp_log_driver = { + .driver = { + .name = "cpucp-log", + .of_match_table = cpucp_log, + }, + .probe = cpucp_log_probe, + .remove = cpucp_log_remove, +}; +builtin_platform_driver(cpucp_log_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/qcom/qcom_cpucp.c b/drivers/soc/qcom/qcom_cpucp.c new file mode 100644 index 000000000000..c3217d35bf2a --- /dev/null +++ b/drivers/soc/qcom/qcom_cpucp.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +/* CPUCP Register offsets */ +#define CPUCP_IPC_CHAN_SUPPORTED 2 +#define CPUCP_SEND_IRQ_OFFSET 0xC +#define CPUCP_SEND_IRQ_VAL BIT(28) +#define CPUCP_CLEAR_IRQ_OFFSET 0x308 +#define CPUCP_STATUS_IRQ_OFFSET 0x30C +#define CPUCP_CLEAR_IRQ_VAL BIT(3) +#define CPUCP_STATUS_IRQ_VAL BIT(3) +#define CPUCP_CLOCK_DOMAIN_OFFSET 0x1000 + +/** + * struct cpucp_ipc ipc per channel + * @mbox: mailbox-controller interface + * @chans: The mailbox clients' channel array + * @tx_irq_base: Memory address for sending irq + * @rx_irq_base: Memory address for receiving irq + * @dev: Device associated with this instance + * @irq: CPUCP to HLOS irq + * @num_chan: Number of ipc channels supported + */ +struct qcom_cpucp_ipc { + struct mbox_controller mbox; + struct mbox_chan chans[CPUCP_IPC_CHAN_SUPPORTED]; + void __iomem *tx_irq_base; + void __iomem *rx_irq_base; + struct device *dev; + int irq; + int num_chan; +}; + +static irqreturn_t qcom_cpucp_rx_interrupt(int irq, void *p) +{ + struct qcom_cpucp_ipc *cpucp_ipc; + u32 val; + int i; + + cpucp_ipc = p; + + for (i = 0; i < cpucp_ipc->num_chan; i++) { + + val = readl(cpucp_ipc->rx_irq_base + + CPUCP_STATUS_IRQ_OFFSET + (i * CPUCP_CLOCK_DOMAIN_OFFSET)); + if (val & CPUCP_STATUS_IRQ_VAL) { + + val = CPUCP_CLEAR_IRQ_VAL; + writel(val, cpucp_ipc->rx_irq_base + + CPUCP_CLEAR_IRQ_OFFSET + + (i * CPUCP_CLOCK_DOMAIN_OFFSET)); + /* Make sure reg write is complete before proceeding */ + mb(); + + if (cpucp_ipc->chans[i].con_priv) + mbox_chan_received_data(&cpucp_ipc->chans[i] + , NULL); + } + } + + return IRQ_HANDLED; +} + +static void qcom_cpucp_mbox_shutdown(struct mbox_chan *chan) +{ + chan->con_priv = NULL; +} + +static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct qcom_cpucp_ipc *cpucp_ipc = container_of(chan->mbox, + struct qcom_cpucp_ipc, mbox); + + writel(CPUCP_SEND_IRQ_VAL, + cpucp_ipc->tx_irq_base + CPUCP_SEND_IRQ_OFFSET); + return 0; +} + +static struct mbox_chan *qcom_cpucp_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + struct qcom_cpucp_ipc *cpucp_ipc = container_of(mbox, + struct qcom_cpucp_ipc, mbox); + unsigned long ind = sp->args[0]; + + if (sp->args_count != 1) + return ERR_PTR(-EINVAL); + + if (ind >= mbox->num_chans) + return ERR_PTR(-EINVAL); + + if (mbox->chans[ind].con_priv) + return ERR_PTR(-EBUSY); + + mbox->chans[ind].con_priv = cpucp_ipc; + return &mbox->chans[ind]; +} + +static const struct mbox_chan_ops cpucp_mbox_chan_ops = { + .send_data = qcom_cpucp_mbox_send_data, + .shutdown = qcom_cpucp_mbox_shutdown +}; + +static int qcom_cpucp_ipc_setup_mbox(struct qcom_cpucp_ipc *cpucp_ipc) +{ + struct mbox_controller *mbox; + struct device *dev = cpucp_ipc->dev; + unsigned long i; + + /* Initialize channel identifiers */ + for (i = 0; i < ARRAY_SIZE(cpucp_ipc->chans); i++) + cpucp_ipc->chans[i].con_priv = NULL; + + mbox = &cpucp_ipc->mbox; + mbox->dev = dev; + mbox->num_chans = cpucp_ipc->num_chan; + mbox->chans = cpucp_ipc->chans; + mbox->ops = &cpucp_mbox_chan_ops; + mbox->of_xlate = qcom_cpucp_mbox_xlate; + mbox->txdone_irq = false; + mbox->txdone_poll = false; + + return mbox_controller_register(mbox); +} + +static int qcom_cpucp_probe(struct platform_device *pdev) +{ + struct qcom_cpucp_ipc *cpucp_ipc; + struct resource *res; + int ret; + + cpucp_ipc = devm_kzalloc(&pdev->dev, sizeof(*cpucp_ipc), GFP_KERNEL); + if (!cpucp_ipc) + return -ENOMEM; + + cpucp_ipc->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get the device base address\n"); + return -ENODEV; + } + + cpucp_ipc->tx_irq_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!cpucp_ipc->tx_irq_base) { + dev_err(&pdev->dev, "Failed to ioremap cpucp tx irq addr\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Failed to get the device base address\n"); + return -ENODEV; + } + + cpucp_ipc->rx_irq_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!cpucp_ipc->rx_irq_base) { + dev_err(&pdev->dev, "Failed to ioremap cpucp rx irq addr\n"); + return -ENOMEM; + } + + cpucp_ipc->irq = platform_get_irq(pdev, 0); + if (cpucp_ipc->irq < 0) { + dev_err(&pdev->dev, "Failed to get the IRQ\n"); + return cpucp_ipc->irq; + } + + cpucp_ipc->num_chan = CPUCP_IPC_CHAN_SUPPORTED; + ret = qcom_cpucp_ipc_setup_mbox(cpucp_ipc); + if (ret) { + dev_err(&pdev->dev, "Failed to create mailbox\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, cpucp_ipc->irq, + qcom_cpucp_rx_interrupt, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, + "qcom_cpucp", cpucp_ipc); + + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); + goto err_mbox; + } + platform_set_drvdata(pdev, cpucp_ipc); + + return 0; + +err_mbox: + mbox_controller_unregister(&cpucp_ipc->mbox); + return ret; +} + +static int qcom_cpucp_remove(struct platform_device *pdev) +{ + struct qcom_cpucp_ipc *cpucp_ipc = platform_get_drvdata(pdev); + + mbox_controller_unregister(&cpucp_ipc->mbox); + + return 0; +} + +static const struct of_device_id qcom_cpucp_of_match[] = { + { .compatible = "qcom,cpucp"}, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_cpucp_of_match); + +static struct platform_driver qcom_cpucp_driver = { + .probe = qcom_cpucp_probe, + .remove = qcom_cpucp_remove, + .driver = { + .name = "qcom_cpucp", + .of_match_table = qcom_cpucp_of_match, + }, +}; +module_platform_driver(qcom_cpucp_driver); + +MODULE_DESCRIPTION("QTI CPUCP Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/qcom/trace_cpucp.h b/drivers/soc/qcom/trace_cpucp.h new file mode 100644 index 000000000000..e7ebd2c7732f --- /dev/null +++ b/drivers/soc/qcom/trace_cpucp.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + */ + +#if !defined(_TRACE_CPUCP_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_CPUCP_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cpucp + +#include + +TRACE_EVENT(cpucp_log, + + TP_PROTO(char *str), + + TP_ARGS(str), + + TP_STRUCT__entry( + __string(str, str) + ), + + TP_fast_assign( + __assign_str(str, str); + ), + + TP_printk("%s\n", __get_str(str)) +); + +#endif /* _TRACE_CPUCP_H */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace_cpucp + +#include