// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/debugfs.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <soc/tegra/bpmp.h>
#include <soc/tegra/bpmp-abi.h>
static DEFINE_MUTEX(bpmp_debug_lock);
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, u32 *v)
{
return seqbuf_read(seqbuf, v, 4);
}
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)
{
const char *root_path, *filename = NULL;
char *root_path_buf;
size_t root_len;
size_t root_path_buf_len = 512;
root_path_buf = kzalloc(root_path_buf_len, GFP_KERNEL);
if (!root_path_buf)
return NULL;
root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf,
root_path_buf_len);
if (IS_ERR(root_path))
goto out;
root_len = strlen(root_path);
filename = dentry_path(file->f_path.dentry, buf, size);
if (IS_ERR(filename)) {
filename = NULL;
goto out;
}
if (strlen(filename) < root_len || strncmp(filename, root_path, root_len)) {
filename = NULL;
goto out;
}
filename += root_len;
out:
kfree(root_path_buf);
return filename;
}
static int mrq_debug_open(struct tegra_bpmp *bpmp, const char *name,
u32 *fd, u32 *len, bool write)
{
struct mrq_debug_request req = {
.cmd = write ? CMD_DEBUG_OPEN_WO : CMD_DEBUG_OPEN_RO,
};
struct mrq_debug_response resp;
struct tegra_bpmp_message msg = {
.mrq = MRQ_DEBUG,
.tx = {
.data = &req,
.size = sizeof(req),
},
.rx = {
.data = &resp,
.size = sizeof(resp),
},
};
ssize_t sz_name;
int err = 0;
sz_name = strscpy(req.fop.name, name, sizeof(req.fop.name));
if (sz_name < 0) {
pr_err("File name too large: %s\n", name);
return -EINVAL;
}
err = tegra_bpmp_transfer(bpmp, &msg);
if (err < 0)
return err;
else if (msg.rx.ret < 0)
return -EINVAL;
*len = resp.fop.datalen;
*fd = resp.fop.fd;
return 0;
}
static int mrq_debug_close(struct tegra_bpmp *bpmp, u32 fd)
{
struct mrq_debug_request req = {
.cmd = CMD_DEBUG_CLOSE,
.frd = {
.fd = fd,
},
};
struct mrq_debug_response resp;
struct tegra_bpmp_message msg = {
.mrq = MRQ_DEBUG,
.tx = {
.data = &req,
.size = sizeof(req),
},
.rx = {
.data = &resp,
.size = sizeof(resp),
},
<