Re: [PATCH V2] firmware: tegra: add BPMP debugfs support
From: Jon Hunter
Date: Tue Oct 03 2017 - 06:55:27 EST
On 03/10/17 07:12, Timo Alho wrote:
> Tegra power management firmware running on co-processor (BPMP)
> implements a simple pseudo file system akin to debugfs. The file
> system can be used for debugging purposes to examine and change the
> status of selected resources controlled by the firmware (such as
> clocks, resets, voltages, powergates, ...).
>
> Add support to "mirror" the firmware's file system to debugfs. At
> boot, query firmware for a list of all possible files and create
> corresponding debugfs entries. Read/write of individual files is
> implemented by sending a Message ReQuest (MRQ) that passes the full
> file path name and data to firmware via DRAM.
>
> Signed-off-by: Timo Alho <talho@xxxxxxxxxx>
> ---
> Changes in v2:
> - Address Jonathan's review feedback
> * restructure error printing and what error codes passed to higher
> layers
> * don't use IS_ERR_OR_NULL()
> * avoid overwriting last-character of filename in one corner case
> (name length = 255)
>
> drivers/firmware/tegra/Makefile | 4 +-
> drivers/firmware/tegra/bpmp.c | 4 +
> drivers/firmware/tegra/bpmp_debugfs.c | 444 ++++++++++++++++++++++++++++++++++
> include/soc/tegra/bpmp.h | 14 ++
> 4 files changed, 465 insertions(+), 1 deletion(-)
> create mode 100644 drivers/firmware/tegra/bpmp_debugfs.c
>
> diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
> index e34a2f7..0314568 100644
> --- a/drivers/firmware/tegra/Makefile
> +++ b/drivers/firmware/tegra/Makefile
> @@ -1,2 +1,4 @@
> -obj-$(CONFIG_TEGRA_BPMP) += bpmp.o
> +tegra-bpmp-y = bpmp.o
> +tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp_debugfs.o
> +obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o
> obj-$(CONFIG_TEGRA_IVC) += ivc.o
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index 73ca55b..d29c593 100644
> --- a/drivers/firmware/tegra/bpmp.c
> +++ b/drivers/firmware/tegra/bpmp.c
> @@ -824,6 +824,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
> if (err < 0)
> goto free_mrq;
>
> + err = tegra_bpmp_init_debugfs(bpmp);
> + if (err < 0)
> + dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err);
> +
> return 0;
>
> free_mrq:
> diff --git a/drivers/firmware/tegra/bpmp_debugfs.c b/drivers/firmware/tegra/bpmp_debugfs.c
> new file mode 100644
> index 0000000..f7f6a0a
> --- /dev/null
> +++ b/drivers/firmware/tegra/bpmp_debugfs.c
> @@ -0,0 +1,444 @@
> +/*
> + * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
> + * more details.
> + *
> + */
> +#include <linux/debugfs.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/uaccess.h>
> +
> +#include <soc/tegra/bpmp.h>
> +#include <soc/tegra/bpmp-abi.h>
> +
> +struct seqbuf {
> + char *buf;
> + size_t pos;
> + size_t size;
> +};
> +
> +static void seqbuf_init(struct seqbuf *seqbuf, void *buf, size_t size)
> +{
> + seqbuf->buf = buf;
> + seqbuf->size = size;
> + seqbuf->pos = 0;
> +}
> +
> +static size_t seqbuf_avail(struct seqbuf *seqbuf)
> +{
> + return seqbuf->pos < seqbuf->size ? seqbuf->size - seqbuf->pos : 0;
> +}
> +
> +static size_t seqbuf_status(struct seqbuf *seqbuf)
> +{
> + return seqbuf->pos <= seqbuf->size ? 0 : -EOVERFLOW;
> +}
> +
> +static int seqbuf_eof(struct seqbuf *seqbuf)
> +{
> + return seqbuf->pos >= seqbuf->size;
> +}
> +
> +static int seqbuf_read(struct seqbuf *seqbuf, void *buf, size_t nbyte)
> +{
> + nbyte = min(nbyte, seqbuf_avail(seqbuf));
> + memcpy(buf, seqbuf->buf + seqbuf->pos, nbyte);
> + seqbuf->pos += nbyte;
> + return seqbuf_status(seqbuf);
> +}
> +
> +static int seqbuf_read_u32(struct seqbuf *seqbuf, uint32_t *v)
> +{
> + int err;
> +
> + err = seqbuf_read(seqbuf, v, 4);
> + *v = le32_to_cpu(*v);
> + return err;
> +}
> +
> +static int seqbuf_read_str(struct seqbuf *seqbuf, const char **str)
> +{
> + *str = seqbuf->buf + seqbuf->pos;
> + seqbuf->pos += strnlen(*str, seqbuf_avail(seqbuf));
> + seqbuf->pos++;
> + return seqbuf_status(seqbuf);
> +}
> +
> +static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset)
> +{
> + seqbuf->pos += offset;
> +}
> +
> +/* map filename in Linux debugfs to corresponding entry in BPMP */
> +static const char *get_filename(struct tegra_bpmp *bpmp,
> + const struct file *file, char *buf, int size)
> +{
> + char root_path_buf[512];
> + const char *root_path;
> + const char *filename;
> + size_t root_len;
> +
> + root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf,
> + sizeof(root_path_buf));
> + if (IS_ERR(root_path))
> + return NULL;
> +
> + root_len = strlen(root_path);
> +
> + filename = dentry_path(file->f_path.dentry, buf, size);
> + if (IS_ERR(filename))
> + return NULL;
> +
> + if (strlen(filename) < root_len ||
> + strncmp(filename, root_path, root_len))
> + return NULL;
> +
> + filename += root_len;
> +
> + return filename;
> +}
> +
> +static int mrq_debugfs_read(struct tegra_bpmp *bpmp,
> + dma_addr_t name, size_t sz_name,
> + dma_addr_t data, size_t sz_data,
> + size_t *nbytes)
> +{
> + struct mrq_debugfs_request req = {
> + .cmd = cpu_to_le32(CMD_DEBUGFS_READ),
> + .fop = {
> + .fnameaddr = cpu_to_le32((uint32_t)name),
> + .fnamelen = cpu_to_le32((uint32_t)sz_name),
> + .dataaddr = cpu_to_le32((uint32_t)data),
> + .datalen = cpu_to_le32((uint32_t)sz_data),
> + },
> + };
> + struct mrq_debugfs_response resp;
> + struct tegra_bpmp_message msg = {
> + .mrq = MRQ_DEBUGFS,
> + .tx = {
> + .data = &req,
> + .size = sizeof(req),
> + },
> + .rx = {
> + .data = &resp,
> + .size = sizeof(resp),
> + },
> + };
> + int err;
> +
> + err = tegra_bpmp_transfer(bpmp, &msg);
> + if (err < 0)
> + return err;
I guess that we will need to update this to check the response return
code per your other series? Which is fine to add later given the dependency.
> +
> + *nbytes = (size_t)resp.fop.nbytes;
> +
> + return 0;
> +}
> +
> +static int mrq_debugfs_write(struct tegra_bpmp *bpmp,
> + dma_addr_t name, size_t sz_name,
> + dma_addr_t data, size_t sz_data)
> +{
> + const struct mrq_debugfs_request req = {
> + .cmd = cpu_to_le32(CMD_DEBUGFS_WRITE),
> + .fop = {
> + .fnameaddr = cpu_to_le32((uint32_t)name),
> + .fnamelen = cpu_to_le32((uint32_t)sz_name),
> + .dataaddr = cpu_to_le32((uint32_t)data),
> + .datalen = cpu_to_le32((uint32_t)sz_data),
> + },
> + };
> + struct tegra_bpmp_message msg = {
> + .mrq = MRQ_DEBUGFS,
> + .tx = {
> + .data = &req,
> + .size = sizeof(req),
> + },
> + };
> +
> + return tegra_bpmp_transfer(bpmp, &msg);
> +}
> +
> +static int mrq_debugfs_dumpdir(struct tegra_bpmp *bpmp, dma_addr_t addr,
> + size_t size, size_t *nbytes)
> +{
> + const struct mrq_debugfs_request req = {
> + .cmd = cpu_to_le32(CMD_DEBUGFS_DUMPDIR),
> + .dumpdir = {
> + .dataaddr = cpu_to_le32((uint32_t)addr),
> + .datalen = cpu_to_le32((uint32_t)size),
> + },
> + };
> + struct mrq_debugfs_response resp;
> + struct tegra_bpmp_message msg = {
> + .mrq = MRQ_DEBUGFS,
> + .tx = {
> + .data = &req,
> + .size = sizeof(req),
> + },
> + .rx = {
> + .data = &resp,
> + .size = sizeof(resp),
> + },
> + };
> + int err;
> +
> + err = tegra_bpmp_transfer(bpmp, &msg);
> + if (err < 0)
> + return err;
> +
> + *nbytes = (size_t)resp.dumpdir.nbytes;
> +
> + return 0;
> +}
> +
> +static int debugfs_show(struct seq_file *m, void *p)
> +{
> + struct file *file = m->private;
> + struct inode *inode = file_inode(file);
> + struct tegra_bpmp *bpmp = inode->i_private;
> + const size_t datasize = m->size;
> + const size_t namesize = SZ_256;
> + void *datavirt, *namevirt;
> + dma_addr_t dataphys, namephys;
> + char buf[256];
> + const char *filename;
> + size_t len, nbytes;
> + int ret;
> +
> + filename = get_filename(bpmp, file, buf, sizeof(buf));
> + if (!filename)
> + return -ENOENT;
> +
> + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys,
> + GFP_KERNEL | GFP_DMA32);
> + if (!namevirt)
> + return -ENOMEM;
> +
> + datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys,
> + GFP_KERNEL | GFP_DMA32);
> + if (!datavirt) {
> + ret = -ENOMEM;
> + goto free_namebuf;
> + }
> +
> + len = strlen(filename);
> + strncpy(namevirt, filename, namesize);
> +
> + ret = mrq_debugfs_read(bpmp, namephys, len, dataphys, datasize,
> + &nbytes);
> +
> + if (!ret)
> + seq_write(m, datavirt, nbytes);
> +
> + dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys);
> +free_namebuf:
> + dma_free_coherent(bpmp->dev, namesize, namevirt, namephys);
> +
> + return ret;
> +}
> +
> +static int debugfs_open(struct inode *inode, struct file *file)
> +{
> + return single_open_size(file, debugfs_show, file, SZ_128K);
> +}
> +
> +static ssize_t debugfs_store(struct file *file, const char __user *buf,
> + size_t count, loff_t *f_pos)
> +{
> + struct inode *inode = file_inode(file);
> + struct tegra_bpmp *bpmp = inode->i_private;
> + const size_t datasize = count;
> + const size_t namesize = SZ_256;
> + void *datavirt, *namevirt;
> + dma_addr_t dataphys, namephys;
> + char fnamebuf[256];
> + const char *filename;
> + size_t len;
> + int ret;
> +
> + filename = get_filename(bpmp, file, fnamebuf, sizeof(fnamebuf));
> + if (!filename)
> + return -ENOENT;
> +
> + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys,
> + GFP_KERNEL | GFP_DMA32);
> + if (!namevirt)
> + return -ENOMEM;
> +
> + datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys,
> + GFP_KERNEL | GFP_DMA32);
> + if (!datavirt) {
> + ret = -ENOMEM;
> + goto free_namebuf;
> + }
> +
> + len = strlen(filename);
> + strncpy(namevirt, filename, namesize);
> +
> + if (copy_from_user(datavirt, buf, count)) {
> + ret = -EFAULT;
> + goto free_databuf;
> + }
> +
> + ret = mrq_debugfs_write(bpmp, namephys, len, dataphys,
> + count);
> +
> +free_databuf:
> + dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys);
> +free_namebuf:
> + dma_free_coherent(bpmp->dev, namesize, namevirt, namephys);
> +
> + return ret ?: count;
> +}
> +
> +static const struct file_operations debugfs_fops = {
> + .open = debugfs_open,
> + .read = seq_read,
> + .llseek = seq_lseek,
> + .write = debugfs_store,
> + .release = single_release,
> +};
> +
> +static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf,
> + struct dentry *parent, uint32_t depth)
> +{
> + int err;
> + uint32_t d, t;
> + const char *name;
> + struct dentry *dentry;
> +
> + while (!seqbuf_eof(seqbuf)) {
> + err = seqbuf_read_u32(seqbuf, &d);
> + if (err < 0)
> + return err;
> +
> + if (d < depth) {
> + seqbuf_seek(seqbuf, -4);
> + /* go up a level */
> + return 0;
> + } else if (d != depth) {
> + /* malformed data received from BPMP */
> + return -EIO;
> + }
> +
> + err = seqbuf_read_u32(seqbuf, &t);
> + if (err < 0)
> + return err;
> + err = seqbuf_read_str(seqbuf, &name);
> + if (err < 0)
> + return err;
> +
> + if (t & DEBUGFS_S_ISDIR) {
> + dentry = debugfs_create_dir(name, parent);
> + if (!dentry)
> + return -ENOMEM;
> + err = bpmp_populate_dir(bpmp, seqbuf, dentry, depth+1);
> + if (err < 0)
> + return err;
> + } else {
> + umode_t mode;
> +
> + mode = t & DEBUGFS_S_IRUSR ? S_IRUSR : 0;
> + mode |= t & DEBUGFS_S_IWUSR ? S_IWUSR : 0;
> + dentry = debugfs_create_file(name, mode,
> + parent, bpmp,
> + &debugfs_fops);
> + if (!dentry)
> + return -ENOMEM;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int create_debugfs_mirror(struct tegra_bpmp *bpmp, void *buf,
> + size_t bufsize, struct dentry *root)
> +{
> + struct seqbuf seqbuf;
> + int err;
> +
> + bpmp->debugfs_mirror = debugfs_create_dir("debug", root);
> + if (!bpmp->debugfs_mirror)
> + return -ENOMEM;
Is this extra level needed? Do you plan to have other sub-directories
under the main bpmp directory?
> +
> + seqbuf_init(&seqbuf, buf, bufsize);
> + err = bpmp_populate_dir(bpmp, &seqbuf, bpmp->debugfs_mirror, 0);
> + if (err < 0) {
> + debugfs_remove_recursive(bpmp->debugfs_mirror);
> + bpmp->debugfs_mirror = NULL;
> + }
> +
> + return err;
> +}
> +
> +static int mrq_is_supported(struct tegra_bpmp *bpmp, unsigned int mrq)
> +{
> + struct mrq_query_abi_request req = { .mrq = cpu_to_le32(mrq) };
> + struct mrq_query_abi_response resp;
> + struct tegra_bpmp_message msg = {
> + .mrq = MRQ_QUERY_ABI,
> + .tx = {
> + .data = &req,
> + .size = sizeof(req),
> + },
> + .rx = {
> + .data = &resp,
> + .size = sizeof(resp),
> + },
> + };
> + int ret;
> +
> + ret = tegra_bpmp_transfer(bpmp, &msg);
> + if (ret < 0) {
> + /* something went wrong; assume not supported */
> + dev_warn(bpmp->dev, "tegra_bpmp_transfer failed (%d)\n", ret);
> + return 0;
> + }
> +
> + return resp.status ? 0 : 1;
> +}
> +
> +int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp)
> +{
> + dma_addr_t phys;
> + void *virt;
> + const size_t sz = SZ_256K;
> + size_t nbytes;
> + int ret;
> + struct dentry *root;
> +
> + if (!mrq_is_supported(bpmp, MRQ_DEBUGFS))
> + return 0;
> +
> + root = debugfs_create_dir("bpmp", NULL);
> + if (!root)
> + return -ENOMEM;
> +
> + virt = dma_alloc_coherent(bpmp->dev, sz, &phys,
> + GFP_KERNEL | GFP_DMA32);
> + if (!virt) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + ret = mrq_debugfs_dumpdir(bpmp, phys, sz, &nbytes);
> + if (ret < 0)
> + goto free;
> +
> + ret = create_debugfs_mirror(bpmp, virt, nbytes, root);
> +free:
> + dma_free_coherent(bpmp->dev, sz, virt, phys);
> +out:
> + if (ret < 0)
> + debugfs_remove(root);
> +
> + return ret;
> +}
> diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h
> index 9ba6522..21a522a 100644
> --- a/include/soc/tegra/bpmp.h
> +++ b/include/soc/tegra/bpmp.h
> @@ -94,6 +94,10 @@ struct tegra_bpmp {
> struct reset_controller_dev rstc;
>
> struct genpd_onecell_data genpd;
> +
> +#ifdef CONFIG_DEBUG_FS
> + struct dentry *debugfs_mirror;
> +#endif
> };
>
> struct tegra_bpmp *tegra_bpmp_get(struct device *dev);
> @@ -150,4 +154,14 @@ static inline int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp)
> }
> #endif
>
> +#if IS_ENABLED(CONFIG_DEBUG_FS)
> +int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp);
> +#else
> +static inline int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp)
> +{
> + return 0;
> +}
> +#endif
> +
> +
> #endif /* __SOC_TEGRA_BPMP_H */
>
Otherwise, looks good to me!
Reviewed-by: Jon Hunter <jonathanh@xxxxxxxxxx>
Cheers
Jon
--
nvpublic