/* * Copyright (c) 2015-2018, 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "bpmp.h" #define BPMP_MODULE_MAGIC 0x646f6d static struct device *device; extern const struct of_device_id bpmp_of_matches[]; 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, min(nbyte, seqbuf_avail(seqbuf))); seqbuf->pos += nbyte; return seqbuf_status(seqbuf); } static int seqbuf_read_u32(struct seqbuf *seqbuf, u32 *v) { int err; err = seqbuf_read(seqbuf, v, 4); *v = le32_to_cpu(*v); return err; } static const char *seqbuf_strget(struct seqbuf *seqbuf) { const char *ptr = seqbuf->buf + seqbuf->pos; seqbuf->pos += strnlen(seqbuf->buf + seqbuf->pos, seqbuf_avail(seqbuf)); seqbuf->pos++; if (seqbuf_status(seqbuf)) return NULL; else return ptr; } static int seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset) { seqbuf->pos += offset; return seqbuf_status(seqbuf); } static const char *root_path; static const char *get_filename(const struct file *file, char *buf, int size) { const char *filename; size_t root_len; if (!root_path) return NULL; root_len = strlen(root_path); filename = dentry_path(file->f_path.dentry, buf, size); if (IS_ERR_OR_NULL(filename)) return NULL; if (strlen(filename) < root_len || strncmp(filename, root_path, root_len)) return NULL; filename += root_len; return filename; } static int bpmp_debugfs_read(uint32_t name, uint32_t sz_name, dma_addr_t data, size_t sz_data, uint32_t *nbytes) { struct mrq_debugfs_request rq; struct mrq_debugfs_response re; int r; rq.cmd = (uint32_t)cpu_to_le32(CMD_DEBUGFS_READ); rq.fop.fnameaddr = (uint32_t)cpu_to_le32(name); rq.fop.fnamelen = (uint32_t)cpu_to_le32(sz_name); rq.fop.dataaddr = (uint32_t)cpu_to_le32(data); rq.fop.datalen = (uint32_t)cpu_to_le32(sz_data); r = tegra_bpmp_send_receive(MRQ_DEBUGFS, &rq, sizeof(rq), &re, sizeof(re)); if (r) return r; *nbytes = re.fop.nbytes; return 0; } static int debugfs_show(struct seq_file *m, void *p) { struct file *file = m->private; const size_t namesize = SZ_256; char *databuf = NULL; char *namebuf = NULL; dma_addr_t dataphys; dma_addr_t namephys; const char *filename; size_t off; uint32_t nbytes; int len; int ret; namebuf = tegra_bpmp_alloc_coherent(namesize, &namephys, GFP_KERNEL); if (!namebuf) return -ENOMEM; filename = get_filename(file, namebuf, namesize); if (!filename) { ret = -ENOENT; goto out; } databuf = tegra_bpmp_alloc_coherent(m->size, &dataphys, GFP_KERNEL); if (!databuf) { ret = -ENOMEM; goto out; } off = filename - namebuf; len = strlen(filename); ret = bpmp_debugfs_read(namephys + off, len, dataphys, m->size, &nbytes); if (!ret) seq_write(m, databuf, nbytes); out: tegra_bpmp_free_coherent(namesize, namebuf, namephys); if (databuf) tegra_bpmp_free_coherent(m->size, databuf, dataphys); return ret; } static int debugfs_open(struct inode *inode, struct file *file) { return single_open_size(file, debugfs_show, file, SZ_256K); } static int bpmp_debugfs_write(uint32_t name, size_t sz_name, uint32_t data, size_t sz_data) { struct mrq_debugfs_request rq; rq.cmd = (uint32_t)cpu_to_le32(CMD_DEBUGFS_WRITE); rq.fop.fnameaddr = (uint32_t)cpu_to_le32(name); rq.fop.fnamelen = (uint32_t)cpu_to_le32(sz_name); rq.fop.dataaddr = (uint32_t)cpu_to_le32(data); rq.fop.datalen = (uint32_t)cpu_to_le32(sz_data); return tegra_bpmp_send_receive(MRQ_DEBUGFS, &rq, sizeof(rq), NULL, 0); } static ssize_t debugfs_store(struct file *file, const char __user *buf, size_t count, loff_t *f_pos) { const size_t namesize = SZ_256; char *databuf = NULL; char *namebuf = NULL; const char *filename; ssize_t ret; dma_addr_t phys_data; dma_addr_t phys_name; size_t off; int len; databuf = tegra_bpmp_alloc_coherent(count, &phys_data, GFP_KERNEL); if (!databuf) return -ENOMEM; namebuf = tegra_bpmp_alloc_coherent(namesize, &phys_name, GFP_KERNEL); if (!namebuf) { ret = -ENOMEM; goto out; } if (copy_from_user(databuf, buf, count)) { ret = -EFAULT; goto out; } filename = get_filename(file, namebuf, namesize); if (!filename) { ret = -EFAULT; goto out; } off = filename - namebuf; len = strlen(filename); ret = bpmp_debugfs_write(phys_name + off, len, phys_data, count); out: tegra_bpmp_free_coherent(namesize, namebuf, phys_name); if (databuf) tegra_bpmp_free_coherent(count, databuf, phys_data); 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 seqbuf *seqbuf, struct dentry *parent, u32 depth) { int err; while (!seqbuf_eof(seqbuf)) { u32 d, t; const char *name; struct dentry *dentry; seqbuf_read_u32(seqbuf, &d); if (d < depth) { seqbuf_seek(seqbuf, -4); /* go up a level */ return 0; } seqbuf_read_u32(seqbuf, &t); name = seqbuf_strget(seqbuf); if (seqbuf_status(seqbuf)) return seqbuf_status(seqbuf); if (d != depth) { /* malformed data received from BPMP */ return -EIO; } if (t & DEBUGFS_S_ISDIR) { dentry = debugfs_create_dir(name, parent); if (IS_ERR_OR_NULL(dentry)) return dentry ? PTR_ERR(dentry) : -ENOMEM; err = bpmp_populate_dir(seqbuf, dentry, depth+1); if (err) 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, NULL, &debugfs_fops); if (IS_ERR_OR_NULL(dentry)) return -ENOMEM; } } return 0; } static DEFINE_MUTEX(lock); static struct dentry *bpmp_debugfs_root; static char root_path_buf[256]; static int bpmp_fwdebug_create(void *buf, size_t bufsize, struct dentry *root) { struct seqbuf seqbuf; int err; bpmp_debugfs_root = debugfs_create_dir("debug", root); if (IS_ERR_OR_NULL(bpmp_debugfs_root)) { pr_err("failed to create bpmp debugfs directory\n"); bpmp_debugfs_root = NULL; return -ENOMEM; } root_path = dentry_path_raw(bpmp_debugfs_root, root_path_buf, sizeof(root_path_buf)); if (IS_ERR_OR_NULL(root_path)) { /* if this happens, then to recover bpmp debugfs needs * to be unmounted from userspace */ pr_err("failed to figure out bpmp root path\n"); err = root_path ? PTR_ERR(root_path) : -ENOENT; root_path = NULL; return err; } seqbuf_init(&seqbuf, buf, bufsize); err = bpmp_populate_dir(&seqbuf, bpmp_debugfs_root, 0); return err; } static int bpmp_debugfs_dumpdir(uint32_t addr, size_t size, uint32_t *nbytes) { struct mrq_debugfs_request rq; struct mrq_debugfs_response re; int r; rq.cmd = (uint32_t)cpu_to_le32(CMD_DEBUGFS_DUMPDIR); rq.dumpdir.dataaddr = (uint32_t)cpu_to_le32(addr); rq.dumpdir.datalen = (uint32_t)cpu_to_le32(size); r = tegra_bpmp_send_receive(MRQ_DEBUGFS, &rq, sizeof(rq), &re, sizeof(re)); if (r) return r; *nbytes = re.dumpdir.nbytes; return 0; } static void do_debugfs_unmount(struct work_struct *work) { debugfs_remove_recursive(bpmp_debugfs_root); bpmp_debugfs_root = NULL; } static DECLARE_WORK(debugfs_unmount_work, do_debugfs_unmount); static int bpmp_mrq_is_supported(int mrq) { struct mrq_query_abi_request rq; struct mrq_query_abi_response re; int r; rq.mrq = mrq; r = tegra_bpmp_send_receive(MRQ_QUERY_ABI, &rq, sizeof(rq), &re, sizeof(re)); /* something went wrong; assume not supported */ if (WARN_ON(r)) return 0; return re.status ? 0 : 1; } static int bpmp_fwdebug_init(struct dentry *root) { dma_addr_t phys; void *virt; const int sz = SZ_256K; uint32_t nbytes; int ret; if (!root) return -EINVAL; if (!bpmp_mrq_is_supported(MRQ_DEBUGFS)) return 0; mutex_lock(&lock); if (bpmp_debugfs_root) { mutex_unlock(&lock); return -EINVAL; } virt = tegra_bpmp_alloc_coherent(sz, &phys, GFP_KERNEL); if (!virt) { pr_err("%s: dma_alloc_coherent() failed\n", __func__); mutex_unlock(&lock); return -ENOMEM; } ret = bpmp_debugfs_dumpdir(phys, sz, &nbytes); if (ret) { pr_err("bpmp_debugfs_dumpdir() failed (%d)\n", ret); goto out; } cancel_work_sync(&debugfs_unmount_work); ret = bpmp_fwdebug_create(virt, nbytes, root); if (ret) { pr_err("create_bpmp_debugfs() failed (%d)\n", ret); goto out; } pr_info("bpmp: mounted debugfs mirror\n"); out: tegra_bpmp_free_coherent(sz, virt, phys); mutex_unlock(&lock); return ret; } static int bpmp_fwdebug_uninit(struct dentry *root) { mutex_lock(&lock); if (!bpmp_debugfs_root) { mutex_unlock(&lock); return -EINVAL; } schedule_work(&debugfs_unmount_work); mutex_unlock(&lock); return 0; } static struct dentry *bpmp_root; static struct dentry *module_root; static LIST_HEAD(modules); static DEFINE_MUTEX(bpmp_lock); struct bpmp_module { struct list_head entry; struct work_struct unload_work; char name[MODULE_NAME_LEN]; struct dentry *root; u32 handle; u32 size; }; struct module_hdr { u32 magic; u32 size; u32 reloc_size; u32 bss_size; u32 init_offset; u32 cleanup_offset; u8 reserved[72]; u8 parent_tag[32]; }; int bpmp_create_attrs(const struct fops_entry *fent, struct dentry *parent, void *data) { struct dentry *d; while (fent->name) { d = debugfs_create_file(fent->name, fent->mode, parent, data, fent->fops); if (IS_ERR_OR_NULL(d)) return -EFAULT; fent++; } return 0; } static struct bpmp_module *bpmp_find_module(const char *name) { struct bpmp_module *m; list_for_each_entry(m, &modules, entry) { if (!strncmp(m->name, name, MODULE_NAME_LEN)) return m; } return NULL; } static int bpmp_module_load(struct device *dev, const void *base, u32 size, u32 *handle) { void *virt; dma_addr_t phys; struct { u32 phys; u32 size; } __packed msg; int r; virt = tegra_bpmp_alloc_coherent(size, &phys, GFP_KERNEL); if (virt == NULL) return -ENOMEM; memcpy(virt, base, size); msg.phys = phys; msg.size = size; r = tegra_bpmp_send_receive(MRQ_MODULE_LOAD, &msg, sizeof(msg), handle, sizeof(*handle)); tegra_bpmp_free_coherent(size, virt, phys); return r; } static int bpmp_module_unload(struct device *dev, u32 handle) { return tegra_bpmp_send_receive(MRQ_MODULE_UNLOAD, &handle, sizeof(handle), NULL, 0); } static void do_unload_module(struct work_struct *w) { struct bpmp_module *m; int err; m = container_of(w, struct bpmp_module, unload_work); if (m->handle) { err = bpmp_module_unload(device, m->handle); if (err) { dev_err(device, "%s: failed to unload module (%d)\n", m->name, err); return; } } debugfs_remove_recursive(m->root); kfree(m); } static ssize_t bpmp_module_unload_store(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct bpmp_module *m; char buf[MODULE_NAME_LEN]; char *name; if (count >= sizeof(buf)) return -EINVAL; if (strncpy_from_user(buf, user_buf, count) <= 0) return -EFAULT; buf[count] = 0; name = strim(buf); mutex_lock(&bpmp_lock); m = bpmp_find_module(name); if (!m) { mutex_unlock(&bpmp_lock); return -ENODEV; } list_del(&m->entry); schedule_work(&m->unload_work); mutex_unlock(&bpmp_lock); return count; } static const struct file_operations bpmp_module_unload_fops = { .write = bpmp_module_unload_store }; static int bpmp_module_ready(const char *name, const struct firmware *fw, struct bpmp_module *m) { struct module_hdr *hdr; const int sz = sizeof(firmware_tag); char fmt[sz + 1]; int err; hdr = (struct module_hdr *)fw->data; if (fw->size < sizeof(struct module_hdr) || hdr->magic != BPMP_MODULE_MAGIC || hdr->size + hdr->reloc_size != fw->size) { dev_err(device, "%s: invalid module format\n", name); return -EINVAL; } if (memcmp(hdr->parent_tag, firmware_tag, sz)) { dev_err(device, "%s: bad module - tag mismatch\n", name); memcpy(fmt, firmware_tag, sz); fmt[sz] = 0; dev_err(device, "firmware: %s\n", fmt); memcpy(fmt, hdr->parent_tag, sz); fmt[sz] = 0; dev_err(device, "%s : %s\n", name, fmt); return -EINVAL; } m->size = hdr->size + hdr->bss_size; err = bpmp_module_load(device, fw->data, fw->size, &m->handle); if (err) { dev_err(device, "failed to load module, code=%d\n", err); return err; } if (!debugfs_create_x32("handle", S_IRUGO, m->root, &m->handle)) return -ENOMEM; if (!debugfs_create_x32("size", S_IRUGO, m->root, &m->size)) return -ENOMEM; list_add_tail(&m->entry, &modules); return 0; } static ssize_t bpmp_module_load_store(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { const struct firmware *fw; struct bpmp_module *m; char buf[MODULE_NAME_LEN]; int r; if (count >= sizeof(buf)) return -EINVAL; if (strncpy_from_user(buf, user_buf, count) <= 0) return -EFAULT; buf[count] = 0; m = kzalloc(sizeof(*m), GFP_KERNEL); if (m == NULL) return -ENOMEM; mutex_lock(&bpmp_lock); strncpy(m->name, strim(buf), sizeof(m->name) - 1); m->name[sizeof(m->name) - 1] = 0; INIT_WORK(&m->unload_work, do_unload_module); if (bpmp_find_module(m->name)) { dev_err(device, "module %s exist\n", m->name); r = -EEXIST; goto clean; } m->root = debugfs_create_dir(m->name, module_root); if (!m->root) { r = -ENOMEM; goto clean; } r = request_firmware(&fw, m->name, device); if (r) { dev_err(device, "request_firmware() failed: %d\n", r); goto clean; } if (!fw) { r = -EFAULT; WARN_ON(0); goto clean; } dev_info(device, "%s: module ready %zu@%p\n", m->name, fw->size, fw->data); r = bpmp_module_ready(m->name, fw, m); release_firmware(fw); clean: mutex_unlock(&bpmp_lock); if (r) { schedule_work(&m->unload_work); return r; } return count; } static const struct file_operations bpmp_module_load_fops = { .write = bpmp_module_load_store }; static int bpmp_init_modules(struct platform_device *pdev, struct dentry *parent) { const struct fops_entry mod_attrs[] = { { "load", &bpmp_module_load_fops, S_IWUSR }, { "unload", &bpmp_module_unload_fops, S_IWUSR }, { NULL, NULL, 0 } }; module_root = debugfs_create_dir("module", parent); if (IS_ERR_OR_NULL(module_root)) goto clean; if (bpmp_create_attrs(mod_attrs, module_root, pdev)) goto clean; return 0; clean: WARN_ON(1); debugfs_remove_recursive(module_root); module_root = NULL; return -EFAULT; } static int bpmp_ping_show(void *data, u64 *val) { unsigned long flags; ktime_t tm; int ret; local_irq_save(flags); tm = ktime_get(); ret = __bpmp_do_ping(); tm = ktime_sub(ktime_get(), tm); local_irq_restore(flags); *val = ret ?: ktime_to_us(tm); return 0; } static int bpmp_modify_trace_mask(uint32_t clr, uint32_t set) { uint32_t mb[] = { clr, set }; uint32_t new; return tegra_bpmp_send_receive(MRQ_TRACE_MODIFY, mb, sizeof(mb), &new, sizeof(new)) ?: new; } static int bpmp_trace_enable_show(void *data, u64 *val) { *val = bpmp_modify_trace_mask(0, 0); return 0; } static int bpmp_trace_enable_store(void *data, u64 val) { int r; r = bpmp_modify_trace_mask(0, val); if (r < 0) return r; return 0; } static int bpmp_trace_disable_store(void *data, u64 val) { int r; r = bpmp_modify_trace_mask(val, 0); if (r < 0) return r; return 0; } static int bpmp_mount_show(void *data, u64 *val) { *val = bpmp_fwdebug_init(bpmp_root); return 0; } static int bpmp_unmount_show(void *data, u64 *val) { *val = bpmp_fwdebug_uninit(bpmp_root); return 0; } DEFINE_SIMPLE_ATTRIBUTE(bpmp_ping_fops, bpmp_ping_show, NULL, "%lld\n"); DEFINE_SIMPLE_ATTRIBUTE(trace_enable_fops, bpmp_trace_enable_show, bpmp_trace_enable_store, "0x%llx\n"); DEFINE_SIMPLE_ATTRIBUTE(trace_disable_fops, NULL, bpmp_trace_disable_store, "0x%llx\n"); DEFINE_SIMPLE_ATTRIBUTE(bpmp_mount_fops, bpmp_mount_show, NULL, "%lld\n"); DEFINE_SIMPLE_ATTRIBUTE(bpmp_unmount_fops, bpmp_unmount_show, NULL, "%lld\n"); #ifdef CONFIG_BPMP_DEBUGFS_MOUNT_ON_BOOT static __init int bpmp_init_mount(void) { struct device_node *np = NULL; /* mirroring takes a while */ if (!tegra_platform_is_silicon()) return 0; /* continue with the init only if the bpmp node is active in the DTB */ np = of_find_matching_node(NULL, bpmp_of_matches); if (!np || !of_device_is_available(np)) return 0; return bpmp_fwdebug_init(bpmp_root); } late_initcall(bpmp_init_mount); #endif struct bpmp_trace_iter { dma_addr_t phys; void *virt; int eof; }; static int bpmp_trace_show(struct seq_file *file, void *v) { struct bpmp_trace_iter *i = file->private; uint32_t mb[] = { i->phys, PAGE_SIZE }; int ret; i->eof = 0; ret = tegra_bpmp_send_receive(MRQ_WRITE_TRACE, mb, sizeof(mb), &i->eof, sizeof(i->eof)); pr_debug("%s: ret %d eof %d\n", __func__, ret, i->eof); if (ret < 0) return ret; ret = seq_write(file, i->virt, ret); if (ret < 0) return ret; return 0; } static void *bpmp_trace_start(struct seq_file *file, loff_t *pos) { struct bpmp_trace_iter *i = file->private; uint32_t cmd; int first; int ret; first = (*pos == 0); if (first && bpmp_mrq_is_supported(MRQ_TRACE_ITER)) { cmd = 0; ret = tegra_bpmp_send_receive(MRQ_TRACE_ITER, &cmd, sizeof(cmd), NULL, 0); if (WARN_ON(ret)) return NULL; } pr_debug("%s: first %d eof %d pos %llu\n", __func__, first, i->eof, *pos); if (!first && i->eof == 1) return NULL; i->virt = tegra_bpmp_alloc_coherent(PAGE_SIZE, &i->phys, GFP_KERNEL); if (!i->virt) return NULL; return i; } static void *bpmp_trace_next(struct seq_file *file, void *v, loff_t *pos) { struct bpmp_trace_iter *i = file->private; pr_debug("%s: eof %d pos %llu\n", __func__, i->eof, *pos); return NULL; } static void bpmp_trace_stop(struct seq_file *file, void *v) { struct bpmp_trace_iter *i = file->private; pr_debug("%s: eof %d\n", __func__, i->eof); if (i->virt) { tegra_bpmp_free_coherent(PAGE_SIZE, i->virt, i->phys); i->virt = NULL; } } static const struct seq_operations bpmp_trace_seq_ops = { .start = bpmp_trace_start, .show = bpmp_trace_show, .next = bpmp_trace_next, .stop = bpmp_trace_stop }; static ssize_t bpmp_trace_store(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { uint32_t cmd = 1; int ret; if (!bpmp_mrq_is_supported(MRQ_TRACE_ITER)) return count; ret = tegra_bpmp_send_receive(MRQ_TRACE_ITER, &cmd, sizeof(cmd), NULL, 0); return ret ?: count; } static int bpmp_trace_open(struct inode *inode, struct file *file) { return seq_open_private(file, &bpmp_trace_seq_ops, sizeof(struct bpmp_trace_iter)); } static const struct file_operations trace_fops = { .open = bpmp_trace_open, .read = seq_read, .llseek = seq_lseek, .write = bpmp_trace_store, .release = seq_release_private, }; static int bpmp_tag_show(struct seq_file *file, void *data) { seq_write(file, firmware_tag, sizeof(firmware_tag)); seq_putc(file, '\n'); return 0; } static int bpmp_tag_open(struct inode *inode, struct file *file) { return single_open(file, bpmp_tag_show, inode->i_private); } static const struct file_operations bpmp_tag_fops = { .open = bpmp_tag_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; #define MSG_NR_FIELDS ((MSG_DATA_MIN_SZ + 3) / 4) #define MSG_DATA_COUNT (MSG_NR_FIELDS + 1) static uint32_t inbox_data[MSG_DATA_COUNT]; static ssize_t bpmp_mrq_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { /* size in dec, space, new line, terminator */ char buf[MSG_DATA_COUNT * 11 + 1 + 1]; uint32_t outbox_data[MSG_DATA_COUNT]; char *line; char *p; int i; int ret; memset(outbox_data, 0, sizeof(outbox_data)); memset(inbox_data, 0, sizeof(inbox_data)); count = min(count, sizeof(buf) - 1); if (copy_from_user(buf, user_buf, count)) { ret = -EFAULT; goto complete; } buf[count] = 0; line = strim(buf); for (i = 0; i < MSG_DATA_COUNT && line; i++) { p = strsep(&line, " "); ret = kstrtouint(p, 0, outbox_data + i); if (ret) break; } if (!i) { ret = -EINVAL; goto complete; } ret = tegra_bpmp_send_receive(outbox_data[0], outbox_data + 1, MSG_DATA_MIN_SZ, inbox_data + 1, MSG_DATA_MIN_SZ); complete: inbox_data[0] = ret; return ret ?: count; } static int bpmp_mrq_show(struct seq_file *file, void *data) { int i; for (i = 0; i < MSG_DATA_COUNT; i++) { seq_printf(file, "0x%x%s", inbox_data[i], i == MSG_DATA_COUNT - 1 ? "\n" : " "); } return 0; } static int bpmp_mrq_open(struct inode *inode, struct file *file) { return single_open(file, bpmp_mrq_show, inode->i_private); } static const struct file_operations bpmp_mrq_fops = { .open = bpmp_mrq_open, .llseek = seq_lseek, .read = seq_read, .write = bpmp_mrq_write, .release = single_release }; static const struct fops_entry root_attrs[] = { { "ping", &bpmp_ping_fops, S_IRUGO }, { "trace_enable", &trace_enable_fops, S_IRUGO | S_IWUSR }, { "trace_disable", &trace_disable_fops, S_IWUSR }, { "trace", &trace_fops, S_IRUGO | S_IWUSR }, { "tag", &bpmp_tag_fops, S_IRUGO }, { "mrq", &bpmp_mrq_fops, S_IRUGO | S_IWUSR }, { "mount", &bpmp_mount_fops, S_IRUGO }, { "unmount", &bpmp_unmount_fops, S_IRUGO }, { NULL, NULL, 0 } }; struct dentry *bpmp_init_debug(struct platform_device *pdev) { struct dentry *root; root = debugfs_create_dir("bpmp", NULL); if (IS_ERR_OR_NULL(root)) goto clean; if (bpmp_create_attrs(root_attrs, root, pdev)) goto clean; if (bpmp_init_modules(pdev, root)) goto clean; device = &pdev->dev; bpmp_root = root; return root; clean: WARN_ON(1); debugfs_remove_recursive(root); return NULL; } struct dentry *tegra_bpmp_debugfs_add_file(char *name, umode_t mode, void *data, const struct file_operations *fops) { if (!bpmp_root) return NULL; return debugfs_create_file(name, mode, bpmp_root, data, fops); }