diff options
Diffstat (limited to 'drivers/soc/marvell/cn10k-fwlog.c')
-rw-r--r-- | drivers/soc/marvell/cn10k-fwlog.c | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/drivers/soc/marvell/cn10k-fwlog.c b/drivers/soc/marvell/cn10k-fwlog.c new file mode 100644 index 000000000000..745bafe736d1 --- /dev/null +++ b/drivers/soc/marvell/cn10k-fwlog.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/of.h> +#include <linux/of_fdt.h> + +#define DEFAULT_FWLOG_MEMBASE (48 * 1024 * 1024) +#define DEFAULT_FWLOG_MEMSIZE (4 * 1024 * 1024) + +static uint64_t fwlog_mem_base, fwlog_mem_size; +static char *fwlog_buf, *fwlog_mem; +static int dev_major; + +struct fw_logbuf_header { + uint64_t fwlog_base; + uint64_t fwlog_end; + uint64_t fwlog_ptr; + uint64_t wraparound; +} __packed; + +static ssize_t fwlogs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + ssize_t ret = 0; + char *rdbuf; + int rdlen; + struct fw_logbuf_header *fwlog_hdr = (struct fw_logbuf_header *) fwlog_mem; + + if (!fwlog_hdr->wraparound) { + rdlen = fwlog_hdr->fwlog_ptr - fwlog_hdr->fwlog_base; + if (*ppos >= rdlen) + return 0; + rdbuf = fwlog_buf + *ppos; + } else { + /* If the buffer is wrappedaround , rdlen is always max buffer size */ + if (*ppos >= (fwlog_hdr->fwlog_end - fwlog_hdr->fwlog_base)) + return 0; + + rdlen = fwlog_hdr->fwlog_end - fwlog_hdr->fwlog_base - *ppos; + if (rdlen <= 0) + return 0; + + /* adjust the bytes left to read */ + if ((char *)(fwlog_hdr->fwlog_ptr + *ppos) == fwlog_hdr->fwlog_end) + rdbuf = fwlog_buf; + else + rdbuf = (char *)(fwlog_buf + + (fwlog_hdr->fwlog_ptr - fwlog_hdr->fwlog_base) + + *ppos); + + if ((uint64_t) (*ppos + rdlen) > (fwlog_hdr->fwlog_end - fwlog_hdr->fwlog_base)) + rdlen = fwlog_hdr->fwlog_end - (fwlog_hdr->fwlog_ptr + *ppos); + } + + count = min_t(size_t, count, rdlen); + count = min_t(ssize_t, count, PAGE_SIZE); + + if (copy_to_user(buf, rdbuf, count)) + return -EFAULT; + + *ppos += count; + + return count; +} + +static int fwlogs_open(struct inode *inode, struct file *filep) +{ + struct fw_logbuf_header *fwlog_hdr; + + fwlog_mem = memremap(fwlog_mem_base, fwlog_mem_size, MEMREMAP_WB); + if (!fwlog_mem) { + pr_err("Could not map FWLOG Memory\n"); + return -ENOMEM; + } + + fwlog_hdr = (struct fw_logbuf_header *) fwlog_mem; + fwlog_buf = fwlog_mem + sizeof(struct fw_logbuf_header); + + return 0; +} + +static int fwlogs_release(struct inode *inode, struct file *filep) +{ + if (!fwlog_mem) + memunmap((void *) fwlog_mem); + + return 0; +} + +const struct file_operations fwlogs_ops = { + .open = fwlogs_open, + .read = fwlogs_read, + .release = fwlogs_release, +}; + +struct fwifdev { + const char *name; + umode_t mode; + const struct file_operations *fops; +}; + +const static struct fwifdev fwlog_dev = { + .name = "fwlogs", + .mode = 0644, + .fops = &fwlogs_ops, +}; + +static int fwlog_dev_open(struct inode *inode, struct file *filp) +{ + int minor; + const struct fwifdev *dev; + + minor = iminor(inode); + if (minor < 0) + return -ENXIO; + + dev = &fwlog_dev; + filp->f_op = dev->fops; + + if (dev->fops->open) + return dev->fops->open(inode, filp); + + return 0; +} + +static const struct file_operations fwlog_dev_fops = { + .open = fwlog_dev_open, + .llseek = noop_llseek, +}; + +static char *fwlog_devnode(struct device *dev, umode_t *mode) +{ + if (mode && fwlog_dev.mode) + *mode = fwlog_dev.mode; + return NULL; +} + +static struct class *fwif_class; + +static int __init fwlog_dev_init(void) +{ + struct device_node *parent, *node; + + dev_major = register_chrdev(0, "fwif", &fwlog_dev_fops); + + parent = of_find_node_by_path("/reserved-memory"); + if (!parent) { + unregister_chrdev(dev_major, "fwif"); + } else { + for_each_child_of_node(parent, node) { + const __be32 *prop; + u64 size; + + if (of_node_name_prefix(node, "fwlogs")) { + prop = of_get_property(node, "reg", NULL); + if (!prop) + break; + fwlog_mem_base = be32_to_cpu(prop[1]) | + (unsigned long long)be32_to_cpu(prop[0]) << 32; + size = be32_to_cpu(prop[3]) | + (unsigned long long)be32_to_cpu(prop[2]) << 32; + if (fwlog_mem_base == 0ULL) + fwlog_mem_base = DEFAULT_FWLOG_MEMBASE; + + if (size == 0ULL) + size = DEFAULT_FWLOG_MEMSIZE; + + fwlog_mem_size = size / 1024 / 1024; + } + } + } + + fwif_class = class_create(THIS_MODULE, "fwif"); + if (IS_ERR(fwif_class)) { + unregister_chrdev(dev_major, "fwif"); + return PTR_ERR(fwif_class); + } + + fwif_class->devnode = fwlog_devnode; + + device_create(fwif_class, NULL, MKDEV(dev_major, 0), + NULL, fwlog_dev.name); + + return 0; +} + +static void __exit fwlog_dev_exit(void) +{ + unregister_chrdev(dev_major, "fwif"); +} + +module_init(fwlog_dev_init); +module_exit(fwlog_dev_exit); |