// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include "generic_debugfs.h" #define PREALLOC_RBUFFER_SIZE (32) #define PREALLOC_WBUFFER_SIZE (1000) static int data_debug_show(struct seq_file *s, void *data) { struct dbg_info *di = s->private; struct dbg_internal *d = &di->internal; void *buffer; u8 *pdata; int i, ret; if (d->data_buffer_size < d->size) { buffer = kzalloc(d->size, GFP_KERNEL); if (!buffer) return -ENOMEM; kfree(d->data_buffer); d->data_buffer = buffer; d->data_buffer_size = d->size; } /* read transfer */ if (!di->io_read) return -EPERM; ret = di->io_read(di->io_drvdata, d->reg, d->data_buffer, d->size); if (ret < 0) return ret; pdata = d->data_buffer; seq_puts(s, "0x"); for (i = 0; i < d->size; i++) seq_printf(s, "%02x,", *(pdata + i)); seq_puts(s, "\n"); return 0; } static int data_debug_open(struct inode *inode, struct file *file) { return single_open(file, data_debug_show, inode->i_private); } static ssize_t data_debug_write(struct file *file, const char __user *user_buf, size_t cnt, loff_t *loff) { struct seq_file *seq = file->private_data; struct dbg_info *di = seq->private; struct dbg_internal *d = &di->internal; void *buffer; u8 *pdata; char buf[PREALLOC_WBUFFER_SIZE + 1], *token, *cur; int val_cnt = 0, ret; if (cnt > PREALLOC_WBUFFER_SIZE) return -ENOMEM; if (copy_from_user(buf, user_buf, cnt)) return -EFAULT; buf[cnt] = 0; /* buffer size check */ if (d->data_buffer_size < d->size) { buffer = kzalloc(d->size, GFP_KERNEL); if (!buffer) return -ENOMEM; kfree(d->data_buffer); d->data_buffer = buffer; d->data_buffer_size = d->size; } /* data parsing */ cur = buf; pdata = d->data_buffer; while ((token = strsep(&cur, ",\n")) != NULL) { if (!*token) break; if (val_cnt++ >= d->size) break; if (kstrtou8(token, 16, pdata++)) return -EINVAL; } if (val_cnt != d->size) return -EINVAL; /* write transfer */ if (!di->io_write) return -EPERM; ret = di->io_write(di->io_drvdata, d->reg, d->data_buffer, d->size); return (ret < 0) ? ret : cnt; } static const struct file_operations data_debug_fops = { .open = data_debug_open, .read = seq_read, .write = data_debug_write, .llseek = seq_lseek, .release = single_release, }; static int type_debug_show(struct seq_file *s, void *data) { struct dbg_info *di = s->private; seq_printf(s, "%s,%s\n", di->typestr, di->devname); return 0; } static int type_debug_open(struct inode *inode, struct file *file) { return single_open(file, type_debug_show, inode->i_private); } static const struct file_operations type_debug_fops = { .open = type_debug_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static ssize_t lock_debug_read(struct file *file, char __user *user_buf, size_t cnt, loff_t *loff) { struct dbg_info *di = file->private_data; struct dbg_internal *d = &di->internal; char buf[10]; bool lock; mutex_lock(&d->io_lock); lock = d->access_lock; mutex_unlock(&d->io_lock); snprintf(buf, sizeof(buf), "%d\n", lock); return simple_read_from_buffer(user_buf, cnt, loff, buf, strlen(buf)); } static ssize_t lock_debug_write(struct file *file, const char __user *user_buf, size_t cnt, loff_t *loff) { struct dbg_info *di = file->private_data; struct dbg_internal *d = &di->internal; u32 lock; int ret; ret = kstrtou32_from_user(user_buf, cnt, 0, &lock); if (ret < 0) return ret; mutex_lock(&d->io_lock); if (!!lock == d->access_lock) ret = -EFAULT; d->access_lock = !!lock; mutex_unlock(&d->io_lock); return (ret < 0) ? ret : cnt; } static const struct file_operations lock_debug_fops = { .open = simple_open, .read = lock_debug_read, .write = lock_debug_write, }; int generic_debugfs_register(struct dbg_info *di) { struct dbg_internal *d = &di->internal; /* valid check */ if (!di->dirname || !di->devname || !di->typestr) return -EINVAL; d->data_buffer_size = PREALLOC_RBUFFER_SIZE; /* for MTK engineer setting */ d->size = 1; d->data_buffer = kzalloc(PREALLOC_RBUFFER_SIZE, GFP_KERNEL); if (!d->data_buffer) return -ENOMEM; /* create debugfs */ d->rt_root = debugfs_lookup("ext_dev_io", NULL); if (!d->rt_root) { d->rt_root = debugfs_create_dir("ext_dev_io", NULL); if (!d->rt_root) return -ENODEV; d->rt_dir_create = true; } mutex_init(&d->io_lock); d->ic_root = debugfs_create_dir(di->dirname, d->rt_root); if (!d->ic_root) goto err_cleanup_rt; debugfs_create_u16("reg", 0644, d->ic_root, &d->reg); debugfs_create_u16("size", 0644, d->ic_root, &d->size); if (!debugfs_create_file("data", 0644, d->ic_root, di, &data_debug_fops)) goto err_cleanup_ic; if (!debugfs_create_file("type", 0444, d->ic_root, di, &type_debug_fops)) goto err_cleanup_ic; if (!debugfs_create_file("lock", 0644, d->ic_root, di, &lock_debug_fops)) goto err_cleanup_ic; return 0; err_cleanup_ic: debugfs_remove_recursive(d->ic_root); err_cleanup_rt: mutex_destroy(&d->io_lock); if (d->rt_dir_create) debugfs_remove_recursive(d->rt_root); kfree(d->data_buffer); return -ENODEV; } EXPORT_SYMBOL_GPL(generic_debugfs_register); void generic_debugfs_unregister(struct dbg_info *di) { struct dbg_internal *d = &di->internal; debugfs_remove_recursive(d->ic_root); mutex_destroy(&d->io_lock); if (d->rt_dir_create) debugfs_remove_recursive(d->rt_root); kfree(d->data_buffer); } EXPORT_SYMBOL_GPL(generic_debugfs_unregister); static int __init generic_debugfs_init(void) { return 0; } static void __exit generic_debugfs_exit(void) { } subsys_initcall(generic_debugfs_init); module_exit(generic_debugfs_exit); MODULE_DESCRIPTION("Generic Debugfs"); MODULE_AUTHOR("Gene Chen "); MODULE_LICENSE("GPL");