Path: blob/master/drivers/firmware/tegra/bpmp-debugfs.c
26428 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.3*/4#include <linux/debugfs.h>5#include <linux/dma-mapping.h>6#include <linux/slab.h>7#include <linux/uaccess.h>89#include <soc/tegra/bpmp.h>10#include <soc/tegra/bpmp-abi.h>1112static DEFINE_MUTEX(bpmp_debug_lock);1314struct seqbuf {15char *buf;16size_t pos;17size_t size;18};1920static void seqbuf_init(struct seqbuf *seqbuf, void *buf, size_t size)21{22seqbuf->buf = buf;23seqbuf->size = size;24seqbuf->pos = 0;25}2627static size_t seqbuf_avail(struct seqbuf *seqbuf)28{29return seqbuf->pos < seqbuf->size ? seqbuf->size - seqbuf->pos : 0;30}3132static size_t seqbuf_status(struct seqbuf *seqbuf)33{34return seqbuf->pos <= seqbuf->size ? 0 : -EOVERFLOW;35}3637static int seqbuf_eof(struct seqbuf *seqbuf)38{39return seqbuf->pos >= seqbuf->size;40}4142static int seqbuf_read(struct seqbuf *seqbuf, void *buf, size_t nbyte)43{44nbyte = min(nbyte, seqbuf_avail(seqbuf));45memcpy(buf, seqbuf->buf + seqbuf->pos, nbyte);46seqbuf->pos += nbyte;47return seqbuf_status(seqbuf);48}4950static int seqbuf_read_u32(struct seqbuf *seqbuf, u32 *v)51{52return seqbuf_read(seqbuf, v, 4);53}5455static int seqbuf_read_str(struct seqbuf *seqbuf, const char **str)56{57*str = seqbuf->buf + seqbuf->pos;58seqbuf->pos += strnlen(*str, seqbuf_avail(seqbuf));59seqbuf->pos++;60return seqbuf_status(seqbuf);61}6263static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset)64{65seqbuf->pos += offset;66}6768/* map filename in Linux debugfs to corresponding entry in BPMP */69static const char *get_filename(struct tegra_bpmp *bpmp,70const struct file *file, char *buf, int size)71{72const char *root_path, *filename = NULL;73char *root_path_buf;74size_t root_len;75size_t root_path_buf_len = 512;7677root_path_buf = kzalloc(root_path_buf_len, GFP_KERNEL);78if (!root_path_buf)79return NULL;8081root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf,82root_path_buf_len);83if (IS_ERR(root_path))84goto out;8586root_len = strlen(root_path);8788filename = dentry_path(file->f_path.dentry, buf, size);89if (IS_ERR(filename)) {90filename = NULL;91goto out;92}9394if (strlen(filename) < root_len || strncmp(filename, root_path, root_len)) {95filename = NULL;96goto out;97}9899filename += root_len;100101out:102kfree(root_path_buf);103return filename;104}105106static int mrq_debug_open(struct tegra_bpmp *bpmp, const char *name,107u32 *fd, u32 *len, bool write)108{109struct mrq_debug_request req = {110.cmd = write ? CMD_DEBUG_OPEN_WO : CMD_DEBUG_OPEN_RO,111};112struct mrq_debug_response resp;113struct tegra_bpmp_message msg = {114.mrq = MRQ_DEBUG,115.tx = {116.data = &req,117.size = sizeof(req),118},119.rx = {120.data = &resp,121.size = sizeof(resp),122},123};124ssize_t sz_name;125int err = 0;126127sz_name = strscpy(req.fop.name, name, sizeof(req.fop.name));128if (sz_name < 0) {129pr_err("File name too large: %s\n", name);130return -EINVAL;131}132133err = tegra_bpmp_transfer(bpmp, &msg);134if (err < 0)135return err;136else if (msg.rx.ret < 0)137return -EINVAL;138139*len = resp.fop.datalen;140*fd = resp.fop.fd;141142return 0;143}144145static int mrq_debug_close(struct tegra_bpmp *bpmp, u32 fd)146{147struct mrq_debug_request req = {148.cmd = CMD_DEBUG_CLOSE,149.frd = {150.fd = fd,151},152};153struct mrq_debug_response resp;154struct tegra_bpmp_message msg = {155.mrq = MRQ_DEBUG,156.tx = {157.data = &req,158.size = sizeof(req),159},160.rx = {161.data = &resp,162.size = sizeof(resp),163},164};165int err = 0;166167err = tegra_bpmp_transfer(bpmp, &msg);168if (err < 0)169return err;170else if (msg.rx.ret < 0)171return -EINVAL;172173return 0;174}175176static int mrq_debug_read(struct tegra_bpmp *bpmp, const char *name,177char *data, size_t sz_data, u32 *nbytes)178{179struct mrq_debug_request req = {180.cmd = CMD_DEBUG_READ,181};182struct mrq_debug_response resp;183struct tegra_bpmp_message msg = {184.mrq = MRQ_DEBUG,185.tx = {186.data = &req,187.size = sizeof(req),188},189.rx = {190.data = &resp,191.size = sizeof(resp),192},193};194u32 fd = 0, len = 0;195int remaining, err, close_err;196197mutex_lock(&bpmp_debug_lock);198err = mrq_debug_open(bpmp, name, &fd, &len, 0);199if (err)200goto out;201202if (len > sz_data) {203err = -EFBIG;204goto close;205}206207req.frd.fd = fd;208remaining = len;209210while (remaining > 0) {211err = tegra_bpmp_transfer(bpmp, &msg);212if (err < 0) {213goto close;214} else if (msg.rx.ret < 0) {215err = -EINVAL;216goto close;217}218219if (resp.frd.readlen > remaining) {220pr_err("%s: read data length invalid\n", __func__);221err = -EINVAL;222goto close;223}224225memcpy(data, resp.frd.data, resp.frd.readlen);226data += resp.frd.readlen;227remaining -= resp.frd.readlen;228}229230*nbytes = len;231232close:233close_err = mrq_debug_close(bpmp, fd);234if (!err)235err = close_err;236out:237mutex_unlock(&bpmp_debug_lock);238return err;239}240241static int mrq_debug_write(struct tegra_bpmp *bpmp, const char *name,242uint8_t *data, size_t sz_data)243{244struct mrq_debug_request req = {245.cmd = CMD_DEBUG_WRITE246};247struct mrq_debug_response resp;248struct tegra_bpmp_message msg = {249.mrq = MRQ_DEBUG,250.tx = {251.data = &req,252.size = sizeof(req),253},254.rx = {255.data = &resp,256.size = sizeof(resp),257},258};259u32 fd = 0, len = 0;260size_t remaining;261int err;262263mutex_lock(&bpmp_debug_lock);264err = mrq_debug_open(bpmp, name, &fd, &len, 1);265if (err)266goto out;267268if (sz_data > len) {269err = -EINVAL;270goto close;271}272273req.fwr.fd = fd;274remaining = sz_data;275276while (remaining > 0) {277len = min(remaining, sizeof(req.fwr.data));278memcpy(req.fwr.data, data, len);279req.fwr.datalen = len;280281err = tegra_bpmp_transfer(bpmp, &msg);282if (err < 0) {283goto close;284} else if (msg.rx.ret < 0) {285err = -EINVAL;286goto close;287}288289data += req.fwr.datalen;290remaining -= req.fwr.datalen;291}292293close:294err = mrq_debug_close(bpmp, fd);295out:296mutex_unlock(&bpmp_debug_lock);297return err;298}299300static int bpmp_debug_show(struct seq_file *m, void *p)301{302struct file *file = m->private;303struct inode *inode = file_inode(file);304struct tegra_bpmp *bpmp = inode->i_private;305char fnamebuf[256];306const char *filename;307struct mrq_debug_request req = {308.cmd = CMD_DEBUG_READ,309};310struct mrq_debug_response resp;311struct tegra_bpmp_message msg = {312.mrq = MRQ_DEBUG,313.tx = {314.data = &req,315.size = sizeof(req),316},317.rx = {318.data = &resp,319.size = sizeof(resp),320},321};322u32 fd = 0, len = 0;323int remaining, err, close_err;324325filename = get_filename(bpmp, file, fnamebuf, sizeof(fnamebuf));326if (!filename)327return -ENOENT;328329mutex_lock(&bpmp_debug_lock);330err = mrq_debug_open(bpmp, filename, &fd, &len, 0);331if (err)332goto out;333334req.frd.fd = fd;335remaining = len;336337while (remaining > 0) {338err = tegra_bpmp_transfer(bpmp, &msg);339if (err < 0) {340goto close;341} else if (msg.rx.ret < 0) {342err = -EINVAL;343goto close;344}345346if (resp.frd.readlen > remaining) {347pr_err("%s: read data length invalid\n", __func__);348err = -EINVAL;349goto close;350}351352seq_write(m, resp.frd.data, resp.frd.readlen);353remaining -= resp.frd.readlen;354}355356close:357close_err = mrq_debug_close(bpmp, fd);358if (!err)359err = close_err;360out:361mutex_unlock(&bpmp_debug_lock);362return err;363}364365static ssize_t bpmp_debug_store(struct file *file, const char __user *buf,366size_t count, loff_t *f_pos)367{368struct inode *inode = file_inode(file);369struct tegra_bpmp *bpmp = inode->i_private;370char *databuf = NULL;371char fnamebuf[256];372const char *filename;373ssize_t err;374375filename = get_filename(bpmp, file, fnamebuf, sizeof(fnamebuf));376if (!filename)377return -ENOENT;378379databuf = memdup_user(buf, count);380if (IS_ERR(databuf))381return PTR_ERR(databuf);382383err = mrq_debug_write(bpmp, filename, databuf, count);384kfree(databuf);385386return err ?: count;387}388389static int bpmp_debug_open(struct inode *inode, struct file *file)390{391return single_open_size(file, bpmp_debug_show, file, SZ_256K);392}393394static const struct file_operations bpmp_debug_fops = {395.open = bpmp_debug_open,396.read = seq_read,397.llseek = seq_lseek,398.write = bpmp_debug_store,399.release = single_release,400};401402static int bpmp_populate_debugfs_inband(struct tegra_bpmp *bpmp,403struct dentry *parent,404char *ppath)405{406const size_t pathlen = SZ_256;407const size_t bufsize = SZ_16K;408struct dentry *dentry;409u32 dsize, attrs = 0;410struct seqbuf seqbuf;411char *buf, *pathbuf;412const char *name;413int err = 0;414415if (!bpmp || !parent || !ppath)416return -EINVAL;417418buf = kmalloc(bufsize, GFP_KERNEL);419if (!buf)420return -ENOMEM;421422pathbuf = kzalloc(pathlen, GFP_KERNEL);423if (!pathbuf) {424kfree(buf);425return -ENOMEM;426}427428err = mrq_debug_read(bpmp, ppath, buf, bufsize, &dsize);429if (err)430goto out;431432seqbuf_init(&seqbuf, buf, dsize);433434while (!seqbuf_eof(&seqbuf)) {435err = seqbuf_read_u32(&seqbuf, &attrs);436if (err)437goto out;438439err = seqbuf_read_str(&seqbuf, &name);440if (err < 0)441goto out;442443if (attrs & DEBUGFS_S_ISDIR) {444size_t len;445446dentry = debugfs_create_dir(name, parent);447if (IS_ERR(dentry)) {448err = PTR_ERR(dentry);449goto out;450}451452len = snprintf(pathbuf, pathlen, "%s%s/", ppath, name);453if (len >= pathlen) {454err = -EINVAL;455goto out;456}457458err = bpmp_populate_debugfs_inband(bpmp, dentry,459pathbuf);460if (err < 0)461goto out;462} else {463umode_t mode;464465mode = attrs & DEBUGFS_S_IRUSR ? 0400 : 0;466mode |= attrs & DEBUGFS_S_IWUSR ? 0200 : 0;467dentry = debugfs_create_file(name, mode, parent, bpmp,468&bpmp_debug_fops);469if (IS_ERR(dentry)) {470err = -ENOMEM;471goto out;472}473}474}475476out:477kfree(pathbuf);478kfree(buf);479480return err;481}482483static int mrq_debugfs_read(struct tegra_bpmp *bpmp,484dma_addr_t name, size_t sz_name,485dma_addr_t data, size_t sz_data,486size_t *nbytes)487{488struct mrq_debugfs_request req = {489.cmd = CMD_DEBUGFS_READ,490.fop = {491.fnameaddr = (u32)name,492.fnamelen = (u32)sz_name,493.dataaddr = (u32)data,494.datalen = (u32)sz_data,495},496};497struct mrq_debugfs_response resp;498struct tegra_bpmp_message msg = {499.mrq = MRQ_DEBUGFS,500.tx = {501.data = &req,502.size = sizeof(req),503},504.rx = {505.data = &resp,506.size = sizeof(resp),507},508};509int err;510511err = tegra_bpmp_transfer(bpmp, &msg);512if (err < 0)513return err;514else if (msg.rx.ret < 0)515return -EINVAL;516517*nbytes = (size_t)resp.fop.nbytes;518519return 0;520}521522static int mrq_debugfs_write(struct tegra_bpmp *bpmp,523dma_addr_t name, size_t sz_name,524dma_addr_t data, size_t sz_data)525{526const struct mrq_debugfs_request req = {527.cmd = CMD_DEBUGFS_WRITE,528.fop = {529.fnameaddr = (u32)name,530.fnamelen = (u32)sz_name,531.dataaddr = (u32)data,532.datalen = (u32)sz_data,533},534};535struct tegra_bpmp_message msg = {536.mrq = MRQ_DEBUGFS,537.tx = {538.data = &req,539.size = sizeof(req),540},541};542543return tegra_bpmp_transfer(bpmp, &msg);544}545546static int mrq_debugfs_dumpdir(struct tegra_bpmp *bpmp, dma_addr_t addr,547size_t size, size_t *nbytes)548{549const struct mrq_debugfs_request req = {550.cmd = CMD_DEBUGFS_DUMPDIR,551.dumpdir = {552.dataaddr = (u32)addr,553.datalen = (u32)size,554},555};556struct mrq_debugfs_response resp;557struct tegra_bpmp_message msg = {558.mrq = MRQ_DEBUGFS,559.tx = {560.data = &req,561.size = sizeof(req),562},563.rx = {564.data = &resp,565.size = sizeof(resp),566},567};568int err;569570err = tegra_bpmp_transfer(bpmp, &msg);571if (err < 0)572return err;573else if (msg.rx.ret < 0)574return -EINVAL;575576*nbytes = (size_t)resp.dumpdir.nbytes;577578return 0;579}580581static int debugfs_show(struct seq_file *m, void *p)582{583struct file *file = m->private;584struct inode *inode = file_inode(file);585struct tegra_bpmp *bpmp = inode->i_private;586const size_t datasize = m->size;587const size_t namesize = SZ_256;588void *datavirt, *namevirt;589dma_addr_t dataphys, namephys;590char buf[256];591const char *filename;592size_t len, nbytes;593int err;594595filename = get_filename(bpmp, file, buf, sizeof(buf));596if (!filename)597return -ENOENT;598599namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys,600GFP_KERNEL | GFP_DMA32);601if (!namevirt)602return -ENOMEM;603604datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys,605GFP_KERNEL | GFP_DMA32);606if (!datavirt) {607err = -ENOMEM;608goto free_namebuf;609}610611len = strlen(filename);612strscpy_pad(namevirt, filename, namesize);613614err = mrq_debugfs_read(bpmp, namephys, len, dataphys, datasize,615&nbytes);616617if (!err)618seq_write(m, datavirt, nbytes);619620dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys);621free_namebuf:622dma_free_coherent(bpmp->dev, namesize, namevirt, namephys);623624return err;625}626627static int debugfs_open(struct inode *inode, struct file *file)628{629return single_open_size(file, debugfs_show, file, SZ_128K);630}631632static ssize_t debugfs_store(struct file *file, const char __user *buf,633size_t count, loff_t *f_pos)634{635struct inode *inode = file_inode(file);636struct tegra_bpmp *bpmp = inode->i_private;637const size_t datasize = count;638const size_t namesize = SZ_256;639void *datavirt, *namevirt;640dma_addr_t dataphys, namephys;641char fnamebuf[256];642const char *filename;643size_t len;644int err;645646filename = get_filename(bpmp, file, fnamebuf, sizeof(fnamebuf));647if (!filename)648return -ENOENT;649650namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys,651GFP_KERNEL | GFP_DMA32);652if (!namevirt)653return -ENOMEM;654655datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys,656GFP_KERNEL | GFP_DMA32);657if (!datavirt) {658err = -ENOMEM;659goto free_namebuf;660}661662len = strlen(filename);663strscpy_pad(namevirt, filename, namesize);664665if (copy_from_user(datavirt, buf, count)) {666err = -EFAULT;667goto free_databuf;668}669670err = mrq_debugfs_write(bpmp, namephys, len, dataphys,671count);672673free_databuf:674dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys);675free_namebuf:676dma_free_coherent(bpmp->dev, namesize, namevirt, namephys);677678return err ?: count;679}680681static const struct file_operations debugfs_fops = {682.open = debugfs_open,683.read = seq_read,684.llseek = seq_lseek,685.write = debugfs_store,686.release = single_release,687};688689static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf,690struct dentry *parent, u32 depth)691{692int err;693u32 d, t;694const char *name;695struct dentry *dentry;696697while (!seqbuf_eof(seqbuf)) {698err = seqbuf_read_u32(seqbuf, &d);699if (err < 0)700return err;701702if (d < depth) {703seqbuf_seek(seqbuf, -4);704/* go up a level */705return 0;706} else if (d != depth) {707/* malformed data received from BPMP */708return -EIO;709}710711err = seqbuf_read_u32(seqbuf, &t);712if (err < 0)713return err;714err = seqbuf_read_str(seqbuf, &name);715if (err < 0)716return err;717718if (t & DEBUGFS_S_ISDIR) {719dentry = debugfs_create_dir(name, parent);720if (IS_ERR(dentry))721return -ENOMEM;722err = bpmp_populate_dir(bpmp, seqbuf, dentry, depth+1);723if (err < 0)724return err;725} else {726umode_t mode;727728mode = t & DEBUGFS_S_IRUSR ? S_IRUSR : 0;729mode |= t & DEBUGFS_S_IWUSR ? S_IWUSR : 0;730dentry = debugfs_create_file(name, mode,731parent, bpmp,732&debugfs_fops);733if (IS_ERR(dentry))734return -ENOMEM;735}736}737738return 0;739}740741static int bpmp_populate_debugfs_shmem(struct tegra_bpmp *bpmp)742{743struct seqbuf seqbuf;744const size_t sz = SZ_512K;745dma_addr_t phys;746size_t nbytes;747void *virt;748int err;749750virt = dma_alloc_coherent(bpmp->dev, sz, &phys,751GFP_KERNEL | GFP_DMA32);752if (!virt)753return -ENOMEM;754755err = mrq_debugfs_dumpdir(bpmp, phys, sz, &nbytes);756if (err < 0) {757goto free;758} else if (nbytes > sz) {759err = -EINVAL;760goto free;761}762763seqbuf_init(&seqbuf, virt, nbytes);764err = bpmp_populate_dir(bpmp, &seqbuf, bpmp->debugfs_mirror, 0);765free:766dma_free_coherent(bpmp->dev, sz, virt, phys);767768return err;769}770771int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp)772{773struct dentry *root;774bool inband;775int err;776777inband = tegra_bpmp_mrq_is_supported(bpmp, MRQ_DEBUG);778779if (!inband && !tegra_bpmp_mrq_is_supported(bpmp, MRQ_DEBUGFS))780return 0;781782root = debugfs_create_dir("bpmp", NULL);783if (IS_ERR(root))784return -ENOMEM;785786bpmp->debugfs_mirror = debugfs_create_dir("debug", root);787if (IS_ERR(bpmp->debugfs_mirror)) {788err = -ENOMEM;789goto out;790}791792if (inband)793err = bpmp_populate_debugfs_inband(bpmp, bpmp->debugfs_mirror,794"/");795else796err = bpmp_populate_debugfs_shmem(bpmp);797798out:799if (err < 0)800debugfs_remove_recursive(root);801802return err;803}804805806