/* * Copyright (c) 2017-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /** * DOC: qdf_debugfs * This file provides QDF debug file system APIs */ #include #include #include #include #include /* A private structure definition to qdf sequence */ struct qdf_debugfs_seq_priv { bool stop; }; /* entry for root debugfs directory*/ static qdf_dentry_t qdf_debugfs_root; QDF_STATUS qdf_debugfs_init(void) { qdf_debugfs_root = debugfs_create_dir(KBUILD_MODNAME, NULL); if (!qdf_debugfs_root) return QDF_STATUS_E_FAILURE; return QDF_STATUS_SUCCESS; } qdf_export_symbol(qdf_debugfs_init); void qdf_debugfs_exit(void) { if (!qdf_debugfs_root) return; debugfs_remove_recursive(qdf_debugfs_root); qdf_debugfs_root = NULL; } qdf_export_symbol(qdf_debugfs_exit); qdf_dentry_t qdf_debugfs_get_root(void) { return qdf_debugfs_root; } qdf_export_symbol(qdf_debugfs_get_root); umode_t qdf_debugfs_get_filemode(uint16_t mode) { umode_t ret = 0; if (mode & QDF_FILE_USR_READ) ret |= 0400; if (mode & QDF_FILE_USR_WRITE) ret |= 0200; if (mode & QDF_FILE_GRP_READ) ret |= 0040; if (mode & QDF_FILE_GRP_WRITE) ret |= 0020; if (mode & QDF_FILE_OTH_READ) ret |= 0004; if (mode & QDF_FILE_OTH_WRITE) ret |= 0002; return ret; } /* * ---------------------- Implementation note --------------------------------- * * A read in debugfs file triggers seq_read() which calls seq_read api. A * sequence begins with the call of the function start(). If the return is a non * NULL value, the function next() is called. This function is an iterator, the * goal is to go though all the data. Each time next() is called, the function * show() is also called. It writes data values in the buffer read by the user. * The function next() is called until it returns NULL. The sequence ends when * next() returns NULL, then the function stop() is called. * * NOTE: When a sequence is finished, another one starts. That means that * at the end of function stop(), the function start() is called again. This * loop finishes when the function start() returns NULL. * ---------------------------------------------------------------------------- */ /* .seq_start() */ static void *qdf_debugfs_seq_start(struct seq_file *seq, loff_t *pos) { struct qdf_debugfs_seq_priv *priv; priv = qdf_mem_malloc(sizeof(*priv)); if (!priv) return NULL; priv->stop = false; return priv; } /* .seq_next() */ static void *qdf_debugfs_seq_next(struct seq_file *seq, void *v, loff_t *pos) { struct qdf_debugfs_seq_priv *priv = v; if (priv) ++*pos; if (priv && priv->stop) { qdf_mem_free(priv); priv = NULL; } return priv; } /* .seq_stop() */ static void qdf_debugfs_seq_stop(struct seq_file *seq, void *v) { qdf_mem_free(v); } /* .seq_show() */ static int qdf_debugfs_seq_show(struct seq_file *seq, void *v) { struct qdf_debugfs_seq_priv *priv = v; struct qdf_debugfs_fops *fops; QDF_STATUS status; fops = seq->private; if (fops && fops->show) { status = fops->show(seq, fops->priv); if (priv && (status != QDF_STATUS_E_AGAIN)) priv->stop = true; } return 0; } void qdf_debugfs_printf(qdf_debugfs_file_t file, const char *f, ...) { va_list args; va_start(args, f); seq_vprintf(file, f, args); va_end(args); } qdf_export_symbol(qdf_debugfs_printf); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)) void qdf_debugfs_hexdump(qdf_debugfs_file_t file, const uint8_t *buf, qdf_size_t len, int rowsize, int groupsize) { seq_hex_dump(file, "", DUMP_PREFIX_OFFSET, rowsize, groupsize, buf, len, false); } qdf_export_symbol(qdf_debugfs_hexdump); #else void qdf_debugfs_hexdump(qdf_debugfs_file_t file, const uint8_t *buf, qdf_size_t len, int rowsize, int groupsize) { char *dst; size_t dstlen, readlen, remaining = len; int prefix = 0; size_t commitlen; while (remaining > 0 && (file->size > file->count)) { seq_printf(file, "%.8x: ", prefix); readlen = qdf_min(remaining, (qdf_size_t)rowsize); dstlen = seq_get_buf(file, &dst); hex_dump_to_buffer(buf, readlen, rowsize, groupsize, dst, dstlen, false); commitlen = strnlen(dst, dstlen); seq_commit(file, commitlen); seq_putc(file, '\n'); remaining = (remaining > rowsize) ? remaining - rowsize : 0; buf += readlen; prefix += rowsize; } } qdf_export_symbol(qdf_debugfs_hexdump); #endif bool qdf_debugfs_overflow(qdf_debugfs_file_t file) { return seq_has_overflowed(file); } qdf_export_symbol(qdf_debugfs_overflow); void qdf_debugfs_write(qdf_debugfs_file_t file, const uint8_t *buf, qdf_size_t len) { seq_write(file, buf, len); } /* sequential file operation table */ static const struct seq_operations __qdf_debugfs_seq_ops = { .start = qdf_debugfs_seq_start, .next = qdf_debugfs_seq_next, .stop = qdf_debugfs_seq_stop, .show = qdf_debugfs_seq_show, }; /* .open() */ static int qdf_seq_open(struct inode *inode, struct file *file) { void *private = inode->i_private; struct seq_file *seq; int rc; /** * Note: seq_open() will allocate a struct seq_file and store its * pointer in @file->private_data. It warns if private_data is not NULL. */ rc = seq_open(file, &__qdf_debugfs_seq_ops); if (rc == 0) { seq = file->private_data; seq->private = private; } return rc; } /* .write() */ static ssize_t qdf_seq_write(struct file *filp, const char __user *ubuf, size_t len, loff_t *ppos) { struct qdf_debugfs_fops *fops; struct seq_file *seq; u8 *buf; ssize_t rc = 0; if (len == 0) return 0; seq = filp->private_data; fops = seq->private; if (fops && fops->write) { buf = qdf_mem_malloc(len + 1); if (buf) { buf[len] = '\0'; rc = simple_write_to_buffer(buf, len, ppos, ubuf, len); fops->write(fops->priv, buf, len + 1); qdf_mem_free(buf); } } return rc; } /* debugfs file operation table */ static const struct file_operations __qdf_debugfs_fops = { .owner = THIS_MODULE, .open = qdf_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, .write = qdf_seq_write, }; qdf_dentry_t qdf_debugfs_create_dir(const char *name, qdf_dentry_t parent) { qdf_dentry_t dir; if (!name) return NULL; if (!parent) parent = qdf_debugfs_get_root(); dir = debugfs_create_dir(name, parent); if (IS_ERR_OR_NULL(dir)) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, "%s creation failed", name); dir = NULL; } return dir; } qdf_export_symbol(qdf_debugfs_create_dir); qdf_dentry_t qdf_debugfs_create_file(const char *name, uint16_t mode, qdf_dentry_t parent, struct qdf_debugfs_fops *fops) { qdf_dentry_t file; umode_t filemode; if (!name || !fops) return NULL; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); file = debugfs_create_file(name, filemode, parent, fops, &__qdf_debugfs_fops); if (IS_ERR_OR_NULL(file)) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, "%s creation failed 0x%pK", name, file); file = NULL; } return file; } qdf_export_symbol(qdf_debugfs_create_file); void qdf_debugfs_create_u8(const char *name, uint16_t mode, qdf_dentry_t parent, u8 *value) { umode_t filemode; if (!name) return; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); debugfs_create_u8(name, filemode, parent, value); } void qdf_debugfs_create_u16(const char *name, uint16_t mode, qdf_dentry_t parent, u16 *value) { umode_t filemode; if (!name) return; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); debugfs_create_u16(name, filemode, parent, value); } qdf_export_symbol(qdf_debugfs_create_u16); void qdf_debugfs_create_u32(const char *name, uint16_t mode, qdf_dentry_t parent, u32 *value) { umode_t filemode; if (!name) return; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); debugfs_create_u32(name, filemode, parent, value); } qdf_export_symbol(qdf_debugfs_create_u32); void qdf_debugfs_create_u64(const char *name, uint16_t mode, qdf_dentry_t parent, u64 *value) { umode_t filemode; if (!name) return; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); debugfs_create_u64(name, filemode, parent, value); } qdf_export_symbol(qdf_debugfs_create_u64); void qdf_debugfs_create_atomic(const char *name, uint16_t mode, qdf_dentry_t parent, qdf_atomic_t *value) { umode_t filemode; if (!name) return; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); debugfs_create_atomic_t(name, filemode, parent, value); } qdf_export_symbol(qdf_debugfs_create_atomic); static int qdf_debugfs_string_show(struct seq_file *seq, void *pos) { char *str = seq->private; seq_puts(seq, str); seq_putc(seq, '\n'); return 0; } static int qdf_debugfs_string_open(struct inode *inode, struct file *file) { return single_open(file, qdf_debugfs_string_show, inode->i_private); } static const struct file_operations qdf_string_fops = { .owner = THIS_MODULE, .open = qdf_debugfs_string_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; qdf_dentry_t qdf_debugfs_create_string(const char *name, uint16_t mode, qdf_dentry_t parent, char *str) { umode_t filemode; if (!name) return NULL; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); return debugfs_create_file(name, filemode, parent, str, &qdf_string_fops); } qdf_export_symbol(qdf_debugfs_create_string); void qdf_debugfs_remove_dir_recursive(qdf_dentry_t d) { debugfs_remove_recursive(d); } qdf_export_symbol(qdf_debugfs_remove_dir_recursive); void qdf_debugfs_remove_dir(qdf_dentry_t d) { debugfs_remove(d); } qdf_export_symbol(qdf_debugfs_remove_dir); void qdf_debugfs_remove_file(qdf_dentry_t d) { debugfs_remove(d); } qdf_export_symbol(qdf_debugfs_remove_file); static int qdf_debugfs_single_show(struct seq_file *seq, void *v) { struct qdf_debugfs_fops *fops = seq->private; if (fops && fops->show) fops->show(seq, fops->priv); return 0; } /* .open() */ static int qdf_debugfs_single_open(struct inode *inode, struct file *file) { return single_open(file, qdf_debugfs_single_show, inode->i_private); } /* File operations for the simplified version */ static const struct file_operations qdf_debugfs_fops_simple = { .owner = THIS_MODULE, .open = qdf_debugfs_single_open, .release = single_release, .read = seq_read, .llseek = seq_lseek, }; qdf_dentry_t qdf_debugfs_create_file_simplified( const char *name, uint16_t mode, qdf_dentry_t parent, struct qdf_debugfs_fops *fops) { qdf_dentry_t file; umode_t filemode; if (!name || !fops) return NULL; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); file = debugfs_create_file(name, filemode, parent, fops, &qdf_debugfs_fops_simple); if (IS_ERR_OR_NULL(file)) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, "%s creation failed 0x%pK", name, file); file = NULL; } return file; } qdf_export_symbol(qdf_debugfs_create_file_simplified); int qdf_debugfs_printer(void *priv, const char *fmt, ...) { struct seq_file *file = priv; va_list args; va_start(args, fmt); seq_vprintf(file, fmt, args); seq_puts(file, "\n"); va_end(args); return 0; } qdf_export_symbol(qdf_debugfs_printer); qdf_dentry_t qdf_debugfs_create_blob(const char *name, umode_t mode, qdf_dentry_t parent, qdf_debugfs_blob_wrap_t blob) { return debugfs_create_blob(name, mode, parent, blob); } qdf_export_symbol(qdf_debugfs_create_blob); qdf_dentry_t qdf_debugfs_create_entry(const char *name, uint16_t mode, qdf_dentry_t parent, qdf_entry_t data, const qdf_file_ops_t fops) { qdf_dentry_t file; umode_t filemode; if (!name || !fops) return NULL; if (!parent) parent = qdf_debugfs_get_root(); filemode = qdf_debugfs_get_filemode(mode); file = debugfs_create_file(name, filemode, parent, data, fops); if (IS_ERR_OR_NULL(file)) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, "%s creation failed 0x%pK", name, file); file = NULL; } return file; } qdf_export_symbol(qdf_debugfs_create_entry);