On Wed 10 Mar 10:46 CST 2021, Souradeep Chowdhury wrote:

> The DCC is a DMA Engine designed to capture and store data
> during system crash or software triggers. The DCC operates
> based on link list entries which provides it with data and
> addresses and the function it needs to perform. These
> functions are read, write and loop. Added the basic driver
> in this patch which contains a probe method which instantiates
> the resources needed by the driver. DCC has it's own SRAM which
> needs to be instantiated at probe time as well.
> 

So to summarize, the DCC will upon a crash copy the configured region
into the dcc-ram, where it can be retrieved either by dumping the memory
over USB or from sysfs on the next boot?

> Signed-off-by: Souradeep Chowdhury <schow...@codeaurora.org>
> ---
>  drivers/soc/qcom/Kconfig  |   8 +
>  drivers/soc/qcom/Makefile |   1 +
>  drivers/soc/qcom/dcc.c    | 388 
> ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 397 insertions(+)
>  create mode 100644 drivers/soc/qcom/dcc.c
> 
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> index 79b568f..8819e0b 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -69,6 +69,14 @@ config QCOM_LLCC
>         SDM845. This provides interfaces to clients that use the LLCC.
>         Say yes here to enable LLCC slice driver.
>  
> +config QCOM_DCC
> +     tristate "Qualcomm Technologies, Inc. Data Capture and Compare engine 
> driver"
> +     depends on ARCH_QCOM || COMPILE_TEST
> +     help
> +       This option enables driver for Data Capture and Compare engine. DCC
> +       driver provides interface to configure DCC block and read back
> +       captured data from DCC's internal SRAM.
> +
>  config QCOM_KRYO_L2_ACCESSORS
>       bool
>       depends on ARCH_QCOM && ARM64 || COMPILE_TEST
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index ad675a6..1b00870 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -26,3 +26,4 @@ obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
>  obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o
>  obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o
>  obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) +=      kryo-l2-accessors.o
> +obj-$(CONFIG_QCOM_DCC) += dcc.o
> diff --git a/drivers/soc/qcom/dcc.c b/drivers/soc/qcom/dcc.c
> new file mode 100644
> index 0000000..89816bf
> --- /dev/null
> +++ b/drivers/soc/qcom/dcc.c
> @@ -0,0 +1,388 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cdev.h>
> +#include <linux/delay.h>
> +#include <linux/fs.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#define dcc_readl(drvdata, off)                                              
> \
> +     readl(drvdata->base + dcc_offset_conv(drvdata, off))

This is only used in probe, please just inline it, and use (a) local
variable(s) to avoid the lengthy lines.

> +
> +/* DCC registers */
> +#define DCC_HW_INFO                  0x04
> +#define DCC_LL_NUM_INFO                      0x10
> +
> +#define DCC_MAP_LEVEL1                       0x18
> +#define DCC_MAP_LEVEL2                       0x34
> +#define DCC_MAP_LEVEL3                       0x4C
> +
> +#define DCC_MAP_OFFSET1                      0x10
> +#define DCC_MAP_OFFSET2                      0x18
> +#define DCC_MAP_OFFSET3                      0x1C
> +#define DCC_MAP_OFFSET4                      0x8
> +
> +#define DCC_FIX_LOOP_OFFSET          16
> +#define DCC_VER_INFO_BIT             9
> +
> +#define DCC_MAX_LINK_LIST            8
> +#define DCC_INVALID_LINK_LIST                GENMASK(7, 0)
> +
> +#define DCC_VER_MASK1                        GENMASK(6, 0)
> +#define DCC_VER_MASK2                        GENMASK(5, 0)
> +
> +#define DCC_RD_MOD_WR_ADDR           0xC105E
> +
> +struct qcom_dcc_config {
> +     const int dcc_ram_offset;
> +};
> +
> +enum dcc_mem_map_ver {
> +     DCC_MEM_MAP_VER1,

As these are just integers, I would suggest skipping them. If you really
like them I would like to see VER1 = 1, to make any future debugging
easier.

> +     DCC_MEM_MAP_VER2,
> +     DCC_MEM_MAP_VER3
> +};
> +
> +enum dcc_descriptor_type {
> +     DCC_ADDR_TYPE,
> +     DCC_LOOP_TYPE,
> +     DCC_READ_WRITE_TYPE,
> +     DCC_WRITE_TYPE
> +};
> +
> +struct dcc_config_entry {
> +     u32                             base;
> +     u32                             offset;
> +     u32                             len;
> +     u32                             index;
> +     u32                             loop_cnt;
> +     u32                             write_val;
> +     u32                             mask;
> +     bool                            apb_bus;
> +     enum dcc_descriptor_type        desc_type;
> +     struct list_head                list;
> +};
> +
> +struct dcc_drvdata {
> +     void __iomem            *base;
> +     u32                     reg_size;
> +     struct device           *dev;
> +     struct mutex            mutex;
> +     void __iomem            *ram_base;
> +     u32                     ram_size;
> +     u32                     ram_offset;
> +     enum dcc_mem_map_ver    mem_map_ver;
> +     u32                     ram_cfg;
> +     u32                     ram_start;
> +     bool                    *enable;
> +     bool                    *configured;
> +     bool                    interrupt_disable;
> +     char                    *sram_node;
> +     struct cdev             sram_dev;
> +     struct class            *sram_class;
> +     struct list_head        *cfg_head;
> +     u32                     *nr_config;
> +     u32                     nr_link_list;
> +     u8                      curr_list;
> +     u8                      loopoff;
> +};
> +
> +static u32 dcc_offset_conv(struct dcc_drvdata *drvdata, u32 off)

I don't see a reason for specifying that the parameter and return value
are 32-bit unsigned ints, please use a generic data type...Like size_t

> +{
> +     if (drvdata->mem_map_ver == DCC_MEM_MAP_VER1) {
> +             if ((off & DCC_VER_MASK1) >= DCC_MAP_LEVEL3)
> +                     return (off - DCC_MAP_OFFSET3);
> +             if ((off & DCC_VER_MASK1) >= DCC_MAP_LEVEL2)
> +                     return (off - DCC_MAP_OFFSET2);
> +             else if ((off & DCC_VER_MASK1) >= DCC_MAP_LEVEL1)
> +                     return (off - DCC_MAP_OFFSET1);
> +     } else if (drvdata->mem_map_ver == DCC_MEM_MAP_VER2) {
> +             if ((off & DCC_VER_MASK1) >= DCC_MAP_LEVEL2)
> +                     return (off - DCC_MAP_OFFSET4);
> +     }
> +     return off;
> +}
> +
> +static void dcc_config_reset(struct dcc_drvdata *drvdata)
> +{
> +     struct dcc_config_entry *entry, *temp;
> +     int curr_list;
> +
> +     mutex_lock(&drvdata->mutex);
> +
> +     for (curr_list = 0; curr_list < drvdata->nr_link_list; curr_list++) {
> +

Unnecessary newline.

> +             list_for_each_entry_safe(entry, temp,
> +                     &drvdata->cfg_head[curr_list], list) {
> +                     list_del(&entry->list);
> +                     devm_kfree(drvdata->dev, entry);
> +                     drvdata->nr_config[curr_list]--;
> +             }
> +     }
> +     drvdata->ram_start = 0;
> +     drvdata->ram_cfg = 0;
> +     mutex_unlock(&drvdata->mutex);
> +}
> +
> +static int dcc_sram_open(struct inode *inode, struct file *file)
> +{
> +     struct dcc_drvdata *drvdata = container_of(inode->i_cdev,
> +             struct dcc_drvdata,
> +             sram_dev);
> +     file->private_data = drvdata;
> +
> +     return  0;
> +}
> +
> +static ssize_t dcc_sram_read(struct file *file, char __user *data,
> +                                             size_t len, loff_t *ppos)
> +{
> +     unsigned char *buf;
> +     struct dcc_drvdata *drvdata = file->private_data;
> +
> +     /* EOF check */
> +     if (drvdata->ram_size <= *ppos)

"Is ppos beyond the EOF" is better expressed as:

        if (*ppos >= drvdata->ram_size)

> +             return 0;
> +
> +     if ((*ppos + len) > drvdata->ram_size)
> +             len = (drvdata->ram_size - *ppos);
> +
> +     buf = kzalloc(len, GFP_KERNEL);
> +     if (!buf)
> +             return -ENOMEM;
> +
> +     memcpy_fromio(buf, (drvdata->ram_base + *ppos), len);

Parenthesis are unnecessary.

> +
> +     if (copy_to_user(data, buf, len)) {
> +             dev_err(drvdata->dev, "DCC: Couldn't copy all data to user\n");
> +             kfree(buf);
> +             return -EFAULT;
> +     }
> +
> +     *ppos += len;
> +
> +     kfree(buf);
> +
> +     return len;
> +}
> +
> +static const struct file_operations dcc_sram_fops = {
> +     .owner          = THIS_MODULE,
> +     .open           = dcc_sram_open,
> +     .read           = dcc_sram_read,
> +     .llseek         = no_llseek,
> +};
> +
> +static int dcc_sram_dev_register(struct dcc_drvdata *drvdata)
> +{
> +     int ret;
> +     struct device *device;
> +     dev_t dev;
> +
> +     ret = alloc_chrdev_region(&dev, 0, 1, drvdata->sram_node);
> +     if (ret)
> +             goto err_alloc;
> +
> +     cdev_init(&drvdata->sram_dev, &dcc_sram_fops);

How about implementing this using pstore instead of exposing it through
a custom /dev/dcc_sram (if I read the code correclty)

> +
> +     drvdata->sram_dev.owner = THIS_MODULE;
> +     ret = cdev_add(&drvdata->sram_dev, dev, 1);
> +     if (ret)
> +             goto err_cdev_add;
> +
> +     drvdata->sram_class = class_create(THIS_MODULE, drvdata->sram_node);
> +     if (IS_ERR(drvdata->sram_class)) {
> +             ret = PTR_ERR(drvdata->sram_class);
> +             goto err_class_create;
> +     }
> +
> +     device = device_create(drvdata->sram_class, NULL,
> +                                             drvdata->sram_dev.dev, drvdata,
> +                                             drvdata->sram_node);
> +     if (IS_ERR(device)) {
> +             ret = PTR_ERR(device);
> +             goto err_dev_create;
> +     }
> +
> +     return 0;
> +err_dev_create:
> +     class_destroy(drvdata->sram_class);
> +err_class_create:
> +     cdev_del(&drvdata->sram_dev);
> +err_cdev_add:
> +     unregister_chrdev_region(drvdata->sram_dev.dev, 1);
> +err_alloc:
> +     return ret;
> +}
> +
> +static void dcc_sram_dev_deregister(struct dcc_drvdata *drvdata)
> +{
> +     device_destroy(drvdata->sram_class, drvdata->sram_dev.dev);
> +     class_destroy(drvdata->sram_class);
> +     cdev_del(&drvdata->sram_dev);
> +     unregister_chrdev_region(drvdata->sram_dev.dev, 1);
> +}
> +
> +static int dcc_sram_dev_init(struct dcc_drvdata *drvdata)
> +{
> +     int ret = 0;
> +     size_t node_size;
> +     char *node_name = "dcc_sram";
> +     struct device *dev = drvdata->dev;
> +
> +     node_size = strlen(node_name) + 1;
> +
> +     drvdata->sram_node = devm_kzalloc(dev, node_size, GFP_KERNEL);

kzalloc + strlcpy can be replaced by kstrdup(), but that said...all this
does seems to be to copy a const string to the heap and lugging it
around. Use a define instead.

> +     if (!drvdata->sram_node)
> +             return -ENOMEM;
> +
> +     strlcpy(drvdata->sram_node, node_name, node_size);
> +     ret = dcc_sram_dev_register(drvdata);
> +     if (ret)
> +             dev_err(drvdata->dev, "DCC: sram node not registered.\n");
> +
> +     return ret;
> +}
> +
> +static void dcc_sram_dev_exit(struct dcc_drvdata *drvdata)
> +{
> +     dcc_sram_dev_deregister(drvdata);
> +}
> +
> +static int dcc_probe(struct platform_device *pdev)
> +{
> +     int ret = 0, i;
> +     struct device *dev = &pdev->dev;
> +     struct dcc_drvdata *drvdata;

I think "dcc" would be a better name than "drvdata"...

> +     struct resource *res;
> +     const struct qcom_dcc_config *cfg;
> +
> +     drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
> +     if (!drvdata)
> +             return -ENOMEM;
> +
> +     drvdata->dev = &pdev->dev;
> +     platform_set_drvdata(pdev, drvdata);
> +
> +     res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dcc-base");

platform_get_resource_byname() + devm_ioremap() is done in one go using
devm_platform_ioremap_resource_byname()

> +     if (!res)
> +             return -EINVAL;
> +
> +     drvdata->reg_size = resource_size(res);

reg_size is unused outside this assignment, no need to lug it around in
drvdata.

> +     drvdata->base = devm_ioremap(dev, res->start, resource_size(res));

drvdata->base is only accessed from this function, use a local variable
instead.

> +     if (!drvdata->base)
> +             return -ENOMEM;
> +
> +     res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                                     "dcc-ram-base");

Here you're actually carrying the resource size, so it makes sense.

But it's okay not to wrap this line.

> +     if (!res)
> +             return -EINVAL;
> +
> +     drvdata->ram_size = resource_size(res);
> +     drvdata->ram_base = devm_ioremap(dev, res->start, resource_size(res));
> +     if (!drvdata->ram_base)
> +             return -ENOMEM;
> +     cfg = of_device_get_match_data(&pdev->dev);
> +     drvdata->ram_offset = cfg->dcc_ram_offset;
> +
> +     if (FIELD_GET(BIT(DCC_VER_INFO_BIT), dcc_readl(drvdata, DCC_HW_INFO))) {
> +             drvdata->mem_map_ver = DCC_MEM_MAP_VER3;
> +             drvdata->nr_link_list = dcc_readl(drvdata, DCC_LL_NUM_INFO);
> +             if (drvdata->nr_link_list == 0)
> +                     return  -EINVAL;

Replace the \t in the middle

> +     } else if ((dcc_readl(drvdata, DCC_HW_INFO) & DCC_VER_MASK2) == 
> DCC_VER_MASK2) {
> +             drvdata->mem_map_ver = DCC_MEM_MAP_VER2;
> +             drvdata->nr_link_list = dcc_readl(drvdata, DCC_LL_NUM_INFO);
> +             if (drvdata->nr_link_list == 0)
> +                     return  -EINVAL;
> +     } else {
> +             drvdata->mem_map_ver = DCC_MEM_MAP_VER1;
> +             drvdata->nr_link_list = DCC_MAX_LINK_LIST;
> +     }
> +
> +     if ((dcc_readl(drvdata, DCC_HW_INFO) & BIT(6)) == BIT(6))
> +             drvdata->loopoff = DCC_FIX_LOOP_OFFSET;
> +     else
> +             drvdata->loopoff = get_bitmask_order((drvdata->ram_size +
> +                             drvdata->ram_offset) / 4 - 1);
> +     mutex_init(&drvdata->mutex);
> +
> +     drvdata->enable = devm_kzalloc(dev, drvdata->nr_link_list *
> +                     sizeof(bool), GFP_KERNEL);

kzalloc(, items * sizeof(), ...) is kcalloc()

> +     if (!drvdata->enable)
> +             return -ENOMEM;

Add a newline between each check and the subsequent allocation, or do
all the allocation then do a
        if (!dcc->enable || !dcc->configured ...)
                return -ENOMEM;

> +     drvdata->configured = devm_kzalloc(dev, drvdata->nr_link_list *
> +                     sizeof(bool), GFP_KERNEL);
> +     if (!drvdata->configured)
> +             return -ENOMEM;
> +     drvdata->nr_config = devm_kzalloc(dev, drvdata->nr_link_list *
> +                     sizeof(u32), GFP_KERNEL);
> +     if (!drvdata->nr_config)
> +             return -ENOMEM;
> +     drvdata->cfg_head = devm_kzalloc(dev, drvdata->nr_link_list *
> +                     sizeof(struct list_head), GFP_KERNEL);
> +     if (!drvdata->cfg_head)
> +             return -ENOMEM;
> +
> +     for (i = 0; i < drvdata->nr_link_list; i++) {
> +             INIT_LIST_HEAD(&drvdata->cfg_head[i]);
> +             drvdata->nr_config[i] = 0;

kzalloc() already initialized nr_config.

> +     }
> +
> +     memset_io(drvdata->ram_base, 0, drvdata->ram_size);
> +
> +     drvdata->curr_list = DCC_INVALID_LINK_LIST;
> +
> +     ret = dcc_sram_dev_init(drvdata);
> +     if (ret)
> +             goto err;
> +
> +     return ret;

We know ret is 0 here, but if you rename "err" to "out" and move it
above this line you can reuse return.

> +err:
> +     return ret;
> +}
> +
> +static int dcc_remove(struct platform_device *pdev)
> +{
> +     struct dcc_drvdata *drvdata = platform_get_drvdata(pdev);
> +
> +     dcc_sram_dev_exit(drvdata);
> +
> +     dcc_config_reset(drvdata);
> +
> +     return 0;
> +}
> +
> +static const struct qcom_dcc_config sm8150_cfg = {
> +     .dcc_ram_offset                         = 0x5000,
> +};
> +
> +static const struct of_device_id dcc_match_table[] = {
> +     { .compatible = "qcom,sm8150-dcc", .data = &sm8150_cfg },
> +};

MODULE_DEVICE_TABLE(of, dcc_match_table);

> +
> +static struct platform_driver dcc_driver = {
> +     .probe                                  = dcc_probe,

The indentation here is excessive, feel free to use a single space.

> +     .remove                                 = dcc_remove,
> +     .driver                                 = {
> +             .name           = "msm-dcc",

We tend to not use "msm" anymore, so how about "qcom-dcc"?


PS. It's hard to comment on some of the things in this patch, as they
are not used until the next patch...

Regards,
Bjorn

> +             .of_match_table = dcc_match_table,
> +     },
> +};
> +
> +module_platform_driver(dcc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Qualcomm Technologies Inc. DCC driver");
> +
> -- 
> QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
> of Code Aurora Forum, hosted by The Linux Foundation
> 

Reply via email to