| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2019-2021 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the |
| * GNU General Public License version 2 as published by the Free Software |
| * Foundation, and any use by you of this program is subject to the terms |
| * of such GNU license. |
| * |
| * This program is distributed in the hope that 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, you can access it online at |
| * http://www.gnu.org/licenses/gpl-2.0.html. |
| * |
| */ |
| |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| |
| #include "mali_kbase_debugfs_helper.h" |
| |
| /* Arbitrary maximum size to prevent user space allocating too much kernel |
| * memory |
| */ |
| #define DEBUGFS_MEM_POOLS_MAX_WRITE_SIZE (256u) |
| |
| /** |
| * set_attr_from_string - Parse a string to set elements of an array |
| * |
| * This is the core of the implementation of |
| * kbase_debugfs_helper_set_attr_from_string. The only difference between the |
| * two functions is that this one requires the input string to be writable. |
| * |
| * @buf: Input string to parse. Must be nul-terminated! |
| * @array: Address of an object that can be accessed like an array. |
| * @nelems: Number of elements in the array. |
| * @set_attr_fn: Function to be called back for each array element. |
| * |
| * Return: 0 if success, negative error code otherwise. |
| */ |
| static int |
| set_attr_from_string(char *const buf, void *const array, size_t const nelems, |
| kbase_debugfs_helper_set_attr_fn *const set_attr_fn) |
| { |
| size_t index, err = 0; |
| char *ptr = buf; |
| |
| for (index = 0; index < nelems && *ptr; ++index) { |
| unsigned long new_size; |
| size_t len; |
| char sep; |
| |
| /* Drop leading spaces */ |
| while (*ptr == ' ') |
| ptr++; |
| |
| len = strcspn(ptr, "\n "); |
| if (len == 0) { |
| /* No more values (allow this) */ |
| break; |
| } |
| |
| /* Substitute a nul terminator for a space character |
| * to make the substring valid for kstrtoul. |
| */ |
| sep = ptr[len]; |
| if (sep == ' ') |
| ptr[len++] = '\0'; |
| |
| err = kstrtoul(ptr, 0, &new_size); |
| if (err) |
| break; |
| |
| /* Skip the substring (including any premature nul terminator) |
| */ |
| ptr += len; |
| |
| set_attr_fn(array, index, new_size); |
| } |
| |
| return err; |
| } |
| |
| int kbase_debugfs_string_validator(char *const buf) |
| { |
| size_t index; |
| int err = 0; |
| char *ptr = buf; |
| |
| for (index = 0; *ptr; ++index) { |
| unsigned long test_number; |
| size_t len; |
| |
| /* Drop leading spaces */ |
| while (*ptr == ' ') |
| ptr++; |
| |
| /* Strings passed into the validator will be NULL terminated |
| * by nature, so here strcspn only needs to delimit by |
| * newlines, spaces and NULL terminator (delimited natively). |
| */ |
| len = strcspn(ptr, "\n "); |
| if (len == 0) { |
| /* No more values (allow this) */ |
| break; |
| } |
| |
| /* Substitute a nul terminator for a space character to make |
| * the substring valid for kstrtoul, and then replace it back. |
| */ |
| if (ptr[len] == ' ') { |
| ptr[len] = '\0'; |
| err = kstrtoul(ptr, 0, &test_number); |
| ptr[len] = ' '; |
| |
| /* len should only be incremented if there is a valid |
| * number to follow - otherwise this will skip over |
| * the NULL terminator in cases with no ending newline |
| */ |
| len++; |
| } else { |
| /* This would occur at the last element before a space |
| * or a NULL terminator. |
| */ |
| err = kstrtoul(ptr, 0, &test_number); |
| } |
| |
| if (err) |
| break; |
| /* Skip the substring (including any premature nul terminator) |
| */ |
| ptr += len; |
| } |
| return err; |
| } |
| |
| int kbase_debugfs_helper_set_attr_from_string( |
| const char *const buf, void *const array, size_t const nelems, |
| kbase_debugfs_helper_set_attr_fn *const set_attr_fn) |
| { |
| char *const wbuf = kstrdup(buf, GFP_KERNEL); |
| int err = 0; |
| |
| if (!wbuf) |
| return -ENOMEM; |
| |
| /* validate string before actually writing values */ |
| err = kbase_debugfs_string_validator(wbuf); |
| if (err) { |
| kfree(wbuf); |
| return err; |
| } |
| |
| err = set_attr_from_string(wbuf, array, nelems, |
| set_attr_fn); |
| |
| kfree(wbuf); |
| return err; |
| } |
| |
| ssize_t kbase_debugfs_helper_get_attr_to_string( |
| char *const buf, size_t const size, void *const array, |
| size_t const nelems, |
| kbase_debugfs_helper_get_attr_fn *const get_attr_fn) |
| { |
| ssize_t total = 0; |
| size_t index; |
| |
| for (index = 0; index < nelems; ++index) { |
| const char *postfix = " "; |
| |
| if (index == (nelems-1)) |
| postfix = "\n"; |
| |
| total += scnprintf(buf + total, size - total, "%zu%s", |
| get_attr_fn(array, index), postfix); |
| } |
| |
| return total; |
| } |
| |
| int kbase_debugfs_helper_seq_write( |
| struct file *const file, const char __user *const ubuf, |
| size_t const count, size_t const nelems, |
| kbase_debugfs_helper_set_attr_fn *const set_attr_fn) |
| { |
| const struct seq_file *const sfile = file->private_data; |
| void *const array = sfile->private; |
| int err = 0; |
| char *buf; |
| |
| if (WARN_ON(!array)) |
| return -EINVAL; |
| |
| if (WARN_ON(count > DEBUGFS_MEM_POOLS_MAX_WRITE_SIZE)) |
| return -EINVAL; |
| |
| buf = kmalloc(count + 1, GFP_KERNEL); |
| if (buf == NULL) |
| return -ENOMEM; |
| |
| if (copy_from_user(buf, ubuf, count)) { |
| kfree(buf); |
| return -EFAULT; |
| } |
| |
| buf[count] = '\0'; |
| |
| /* validate string before actually writing values */ |
| err = kbase_debugfs_string_validator(buf); |
| if (err) { |
| kfree(buf); |
| return err; |
| } |
| |
| err = set_attr_from_string(buf, |
| array, nelems, set_attr_fn); |
| kfree(buf); |
| |
| return err; |
| } |
| |
| int kbase_debugfs_helper_seq_read( |
| struct seq_file *const sfile, size_t const nelems, |
| kbase_debugfs_helper_get_attr_fn *const get_attr_fn) |
| { |
| void *const array = sfile->private; |
| size_t index; |
| |
| if (WARN_ON(!array)) |
| return -EINVAL; |
| |
| for (index = 0; index < nelems; ++index) { |
| const char *postfix = " "; |
| |
| if (index == (nelems-1)) |
| postfix = "\n"; |
| |
| seq_printf(sfile, "%zu%s", get_attr_fn(array, index), postfix); |
| } |
| return 0; |
| } |