// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) Rockchip Electronics Co., Ltd. * Author: Felix Zeng */ #include #include #include #include #include #include #include #include #ifndef FPGA_PLATFORM #ifdef CONFIG_PM_DEVFREQ #include <../drivers/devfreq/governor.h> #endif #endif #include "rknpu_drv.h" #include "rknpu_mm.h" #include "rknpu_reset.h" #include "rknpu_debugger.h" #define RKNPU_DEBUGGER_ROOT_NAME "rknpu" #if defined(CONFIG_ROCKCHIP_RKNPU_DEBUG_FS) || \ defined(CONFIG_ROCKCHIP_RKNPU_PROC_FS) static int rknpu_version_show(struct seq_file *m, void *data) { seq_printf(m, "%s: v%d.%d.%d\n", DRIVER_DESC, DRIVER_MAJOR, DRIVER_MINOR, DRIVER_PATCHLEVEL); return 0; } static int rknpu_load_show(struct seq_file *m, void *data) { struct rknpu_debugger_node *node = m->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); struct rknpu_subcore_data *subcore_data = NULL; unsigned long flags; int i; int load; uint64_t total_busy_time, div_value; seq_puts(m, "NPU load: "); for (i = 0; i < rknpu_dev->config->num_irqs; i++) { subcore_data = &rknpu_dev->subcore_datas[i]; if (rknpu_dev->config->num_irqs > 1) seq_printf(m, " Core%d: ", i); spin_lock_irqsave(&rknpu_dev->irq_lock, flags); total_busy_time = subcore_data->timer.total_busy_time; spin_unlock_irqrestore(&rknpu_dev->irq_lock, flags); div_value = (RKNPU_LOAD_INTERVAL / 100); do_div(total_busy_time, div_value); load = total_busy_time > 100 ? 100 : total_busy_time; if (rknpu_dev->config->num_irqs > 1) seq_printf(m, "%2.d%%,", load); else seq_printf(m, "%2.d%%", load); } seq_puts(m, "\n"); return 0; } static int rknpu_power_show(struct seq_file *m, void *data) { struct rknpu_debugger_node *node = m->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); if (atomic_read(&rknpu_dev->power_refcount) > 0) seq_puts(m, "on\n"); else seq_puts(m, "off\n"); return 0; } static ssize_t rknpu_power_set(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *priv = file->private_data; struct rknpu_debugger_node *node = priv->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); char buf[8]; if (len > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, ubuf, len)) return -EFAULT; buf[len - 1] = '\0'; if (strcmp(buf, "on") == 0) { atomic_inc(&rknpu_dev->cmdline_power_refcount); rknpu_power_get(rknpu_dev); LOG_INFO("rknpu power is on!"); } else if (strcmp(buf, "off") == 0) { if (atomic_read(&rknpu_dev->power_refcount) > 0 && atomic_dec_if_positive( &rknpu_dev->cmdline_power_refcount) >= 0) { atomic_sub( atomic_read(&rknpu_dev->cmdline_power_refcount), &rknpu_dev->power_refcount); atomic_set(&rknpu_dev->cmdline_power_refcount, 0); rknpu_power_put(rknpu_dev); } if (atomic_read(&rknpu_dev->power_refcount) <= 0) LOG_INFO("rknpu power is off!"); } else { LOG_ERROR("rknpu power node params is invalid!"); } return len; } static int rknpu_power_put_delay_show(struct seq_file *m, void *data) { struct rknpu_debugger_node *node = m->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); seq_printf(m, "%lu\n", rknpu_dev->power_put_delay); return 0; } static ssize_t rknpu_power_put_delay_set(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *priv = file->private_data; struct rknpu_debugger_node *node = priv->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); char buf[16]; unsigned long power_put_delay = 0; int ret = 0; if (len > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, ubuf, len)) return -EFAULT; buf[len - 1] = '\0'; ret = kstrtoul(buf, 10, &power_put_delay); if (ret) { LOG_ERROR("failed to parse power put delay string: %s\n", buf); return -EFAULT; } rknpu_dev->power_put_delay = power_put_delay; LOG_INFO("set rknpu power put delay time %lums\n", rknpu_dev->power_put_delay); return len; } static int rknpu_freq_show(struct seq_file *m, void *data) { struct rknpu_debugger_node *node = m->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); unsigned long current_freq = 0; rknpu_power_get(rknpu_dev); current_freq = clk_get_rate(rknpu_dev->clks[0].clk); rknpu_power_put(rknpu_dev); seq_printf(m, "%lu\n", current_freq); return 0; } #ifdef CONFIG_PM_DEVFREQ static ssize_t rknpu_freq_set(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *priv = file->private_data; struct rknpu_debugger_node *node = priv->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); unsigned long current_freq = 0; char buf[16]; unsigned long freq = 0; int ret = 0; if (len > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, ubuf, len)) return -EFAULT; buf[len - 1] = '\0'; ret = kstrtoul(buf, 10, &freq); if (ret) { LOG_ERROR("failed to parse freq string: %s\n", buf); return -EFAULT; } if (!rknpu_dev->devfreq) return -EFAULT; rknpu_power_get(rknpu_dev); current_freq = clk_get_rate(rknpu_dev->clks[0].clk); if (freq != current_freq) { rknpu_dev->ondemand_freq = freq; mutex_lock(&rknpu_dev->devfreq->lock); update_devfreq(rknpu_dev->devfreq); mutex_unlock(&rknpu_dev->devfreq->lock); } rknpu_power_put(rknpu_dev); return len; } #else static ssize_t rknpu_freq_set(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { return -EFAULT; } #endif static int rknpu_volt_show(struct seq_file *m, void *data) { struct rknpu_debugger_node *node = m->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); unsigned long current_volt = 0; current_volt = regulator_get_voltage(rknpu_dev->vdd); seq_printf(m, "%lu\n", current_volt); return 0; } static int rknpu_reset_show(struct seq_file *m, void *data) { struct rknpu_debugger_node *node = m->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); if (!rknpu_dev->bypass_soft_reset) seq_puts(m, "on\n"); else seq_puts(m, "off\n"); return 0; } static ssize_t rknpu_reset_set(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *priv = file->private_data; struct rknpu_debugger_node *node = priv->private; struct rknpu_debugger *debugger = node->debugger; struct rknpu_device *rknpu_dev = container_of(debugger, struct rknpu_device, debugger); char buf[8]; if (len > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, ubuf, len)) return -EFAULT; buf[len - 1] = '\0'; if (strcmp(buf, "1") == 0 && atomic_read(&rknpu_dev->power_refcount) > 0) rknpu_soft_reset(rknpu_dev); else if (strcmp(buf, "on") == 0) rknpu_dev->bypass_soft_reset = 0; else if (strcmp(buf, "off") == 0) rknpu_dev->bypass_soft_reset = 1; return len; } static struct rknpu_debugger_list rknpu_debugger_root_list[] = { { "version", rknpu_version_show, NULL, NULL }, { "load", rknpu_load_show, NULL, NULL }, { "power", rknpu_power_show, rknpu_power_set, NULL }, { "freq", rknpu_freq_show, rknpu_freq_set, NULL }, { "volt", rknpu_volt_show, NULL, NULL }, { "delayms", rknpu_power_put_delay_show, rknpu_power_put_delay_set, NULL }, { "reset", rknpu_reset_show, rknpu_reset_set, NULL }, #ifdef CONFIG_ROCKCHIP_RKNPU_SRAM { "mm", rknpu_mm_dump, NULL, NULL }, #endif }; static ssize_t rknpu_debugger_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *priv = file->private_data; struct rknpu_debugger_node *node = priv->private; if (node->info_ent->write) return node->info_ent->write(file, ubuf, len, offp); else return len; } static int rknpu_debugfs_open(struct inode *inode, struct file *file) { struct rknpu_debugger_node *node = inode->i_private; return single_open(file, node->info_ent->show, node); } static const struct file_operations rknpu_debugfs_fops = { .owner = THIS_MODULE, .open = rknpu_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = rknpu_debugger_write, }; #endif /* #if defined(CONFIG_ROCKCHIP_RKNPU_DEBUG_FS) || defined(CONFIG_ROCKCHIP_RKNPU_PROC_FS) */ #ifdef CONFIG_ROCKCHIP_RKNPU_DEBUG_FS static int rknpu_debugfs_remove_files(struct rknpu_debugger *debugger) { struct rknpu_debugger_node *pos, *q; struct list_head *entry_list; mutex_lock(&debugger->debugfs_lock); /* Delete debugfs entry list */ entry_list = &debugger->debugfs_entry_list; list_for_each_entry_safe(pos, q, entry_list, list) { if (pos->dent == NULL) continue; list_del(&pos->list); kfree(pos); pos = NULL; } /* Delete all debugfs node in this directory */ debugfs_remove_recursive(debugger->debugfs_dir); debugger->debugfs_dir = NULL; mutex_unlock(&debugger->debugfs_lock); return 0; } static int rknpu_debugfs_create_files(const struct rknpu_debugger_list *files, int count, struct dentry *root, struct rknpu_debugger *debugger) { int i; struct dentry *ent; struct rknpu_debugger_node *tmp; for (i = 0; i < count; i++) { tmp = kmalloc(sizeof(struct rknpu_debugger_node), GFP_KERNEL); if (tmp == NULL) { LOG_ERROR( "Cannot alloc node path /sys/kernel/debug/%pd/%s\n", root, files[i].name); goto MALLOC_FAIL; } tmp->info_ent = &files[i]; tmp->debugger = debugger; ent = debugfs_create_file(files[i].name, S_IFREG | S_IRUGO, root, tmp, &rknpu_debugfs_fops); if (!ent) { LOG_ERROR("Cannot create /sys/kernel/debug/%pd/%s\n", root, files[i].name); goto CREATE_FAIL; } tmp->dent = ent; mutex_lock(&debugger->debugfs_lock); list_add_tail(&tmp->list, &debugger->debugfs_entry_list); mutex_unlock(&debugger->debugfs_lock); } return 0; CREATE_FAIL: kfree(tmp); MALLOC_FAIL: rknpu_debugfs_remove_files(debugger); return -1; } static int rknpu_debugfs_remove(struct rknpu_debugger *debugger) { rknpu_debugfs_remove_files(debugger); return 0; } static int rknpu_debugfs_init(struct rknpu_debugger *debugger) { int ret; debugger->debugfs_dir = debugfs_create_dir(RKNPU_DEBUGGER_ROOT_NAME, NULL); if (IS_ERR_OR_NULL(debugger->debugfs_dir)) { LOG_ERROR("failed on mkdir /sys/kernel/debug/%s\n", RKNPU_DEBUGGER_ROOT_NAME); debugger->debugfs_dir = NULL; return -EIO; } ret = rknpu_debugfs_create_files(rknpu_debugger_root_list, ARRAY_SIZE(rknpu_debugger_root_list), debugger->debugfs_dir, debugger); if (ret) { LOG_ERROR( "Could not install rknpu_debugger_root_list debugfs\n"); goto CREATE_FAIL; } return 0; CREATE_FAIL: rknpu_debugfs_remove(debugger); return ret; } #endif /* #ifdef CONFIG_ROCKCHIP_RKNPU_DEBUG_FS */ #ifdef CONFIG_ROCKCHIP_RKNPU_PROC_FS static int rknpu_procfs_open(struct inode *inode, struct file *file) { #if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE struct rknpu_debugger_node *node = PDE_DATA(inode); #else struct rknpu_debugger_node *node = pde_data(inode); #endif return single_open(file, node->info_ent->show, node); } static const struct proc_ops rknpu_procfs_fops = { .proc_open = rknpu_procfs_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, .proc_write = rknpu_debugger_write, }; static int rknpu_procfs_remove_files(struct rknpu_debugger *debugger) { struct rknpu_debugger_node *pos, *q; struct list_head *entry_list; mutex_lock(&debugger->procfs_lock); /* Delete procfs entry list */ entry_list = &debugger->procfs_entry_list; list_for_each_entry_safe(pos, q, entry_list, list) { if (pos->pent == NULL) continue; list_del(&pos->list); kfree(pos); pos = NULL; } /* Delete all procfs node in this directory */ proc_remove(debugger->procfs_dir); debugger->procfs_dir = NULL; mutex_unlock(&debugger->procfs_lock); return 0; } static int rknpu_procfs_create_files(const struct rknpu_debugger_list *files, int count, struct proc_dir_entry *root, struct rknpu_debugger *debugger) { int i; struct proc_dir_entry *ent; struct rknpu_debugger_node *tmp; for (i = 0; i < count; i++) { tmp = kmalloc(sizeof(struct rknpu_debugger_node), GFP_KERNEL); if (tmp == NULL) { LOG_ERROR("Cannot alloc node path for /proc/%s/%s\n", RKNPU_DEBUGGER_ROOT_NAME, files[i].name); goto MALLOC_FAIL; } tmp->info_ent = &files[i]; tmp->debugger = debugger; ent = proc_create_data(files[i].name, S_IFREG | S_IRUGO, root, &rknpu_procfs_fops, tmp); if (!ent) { LOG_ERROR("Cannot create /proc/%s/%s\n", RKNPU_DEBUGGER_ROOT_NAME, files[i].name); goto CREATE_FAIL; } tmp->pent = ent; mutex_lock(&debugger->procfs_lock); list_add_tail(&tmp->list, &debugger->procfs_entry_list); mutex_unlock(&debugger->procfs_lock); } return 0; CREATE_FAIL: kfree(tmp); MALLOC_FAIL: rknpu_procfs_remove_files(debugger); return -1; } static int rknpu_procfs_remove(struct rknpu_debugger *debugger) { rknpu_procfs_remove_files(debugger); return 0; } static int rknpu_procfs_init(struct rknpu_debugger *debugger) { int ret; debugger->procfs_dir = proc_mkdir(RKNPU_DEBUGGER_ROOT_NAME, NULL); if (IS_ERR_OR_NULL(debugger->procfs_dir)) { pr_err("failed on mkdir /proc/%s\n", RKNPU_DEBUGGER_ROOT_NAME); debugger->procfs_dir = NULL; return -EIO; } ret = rknpu_procfs_create_files(rknpu_debugger_root_list, ARRAY_SIZE(rknpu_debugger_root_list), debugger->procfs_dir, debugger); if (ret) { pr_err("Could not install rknpu_debugger_root_list procfs\n"); goto CREATE_FAIL; } return 0; CREATE_FAIL: rknpu_procfs_remove(debugger); return ret; } #endif /* #ifdef CONFIG_ROCKCHIP_RKNPU_PROC_FS */ int rknpu_debugger_init(struct rknpu_device *rknpu_dev) { #ifdef CONFIG_ROCKCHIP_RKNPU_DEBUG_FS mutex_init(&rknpu_dev->debugger.debugfs_lock); INIT_LIST_HEAD(&rknpu_dev->debugger.debugfs_entry_list); rknpu_debugfs_init(&rknpu_dev->debugger); #endif #ifdef CONFIG_ROCKCHIP_RKNPU_PROC_FS mutex_init(&rknpu_dev->debugger.procfs_lock); INIT_LIST_HEAD(&rknpu_dev->debugger.procfs_entry_list); rknpu_procfs_init(&rknpu_dev->debugger); #endif return 0; } int rknpu_debugger_remove(struct rknpu_device *rknpu_dev) { #ifdef CONFIG_ROCKCHIP_RKNPU_DEBUG_FS rknpu_debugfs_remove(&rknpu_dev->debugger); #endif #ifdef CONFIG_ROCKCHIP_RKNPU_PROC_FS rknpu_procfs_remove(&rknpu_dev->debugger); #endif return 0; }