/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include #include #include #include #include #include #include #include #include #include "lmh_dbg.h" #define LMH_MON_NAME "lmh_monitor" #define LMH_DBGFS_READ "data" #define LMH_DBGFS_CONFIG_READ "config" #define LMH_DBGFS_READ_TYPES "data_types" #define LMH_DBGFS_CONFIG_TYPES "config_types" #define LMH_SCM_PAYLOAD_SIZE 10 #define LMH_READ_LINE_LENGTH 10 #define LMH_DEBUG_READ_TYPE 0x0 #define LMH_DEBUG_CONFIG_TYPE 0x1 #define LMH_DEBUG_SET 0x08 #define LMH_DEBUG_READ_BUF_SIZE 0x09 #define LMH_DEBUG_READ 0x0A #define LMH_DEBUG_GET_TYPE 0x0B struct lmh_driver_data { struct device *dev; uint32_t *read_type; uint32_t *config_type; uint32_t read_type_count; uint32_t config_type_count; struct dentry *debugfs_parent; struct dentry *debug_read; struct dentry *debug_config; struct dentry *debug_read_type; struct dentry *debug_config_type; }; enum lmh_read_type { LMH_READ_TYPE = 0, LMH_CONFIG_TYPE, }; static struct lmh_driver_data *lmh_data; static int lmh_debug_read(uint32_t **buf) { int ret = 0, size = 0, tz_ret = 0; static uint32_t curr_size; struct scm_desc desc_arg; static uint32_t *payload; desc_arg.arginfo = SCM_ARGS(0); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE), &desc_arg); size = desc_arg.ret[0]; if (ret) { pr_err("Error in SCM get debug buffer size call. err:%d\n", ret); goto get_dbg_exit; } if (!size) { pr_err("No Debug data to read\n"); ret = -ENODEV; goto get_dbg_exit; } size = SCM_BUFFER_SIZE(uint32_t) * size * LMH_READ_LINE_LENGTH; if (curr_size != size) { if (payload) devm_kfree(lmh_data->dev, payload); payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size), GFP_KERNEL); if (!payload) { ret = -ENOMEM; goto get_dbg_exit; } curr_size = size; } /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = curr_size; desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); dmac_flush_range(payload, (void *)payload + curr_size); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ), &desc_arg); dmac_inv_range(payload, (void *)payload + curr_size); tz_ret = desc_arg.ret[0]; /* Have memory barrier before we access the TZ data */ mb(); if (ret) { pr_err("Error in get debug read. err:%d\n", ret); goto get_dbg_exit; } if (tz_ret) { pr_err("TZ API returned error. err:%d\n", tz_ret); ret = tz_ret; goto get_dbg_exit; } get_dbg_exit: if (ret && payload) { devm_kfree(lmh_data->dev, payload); payload = NULL; curr_size = 0; } *buf = payload; return (ret < 0) ? ret : curr_size; } static int lmh_debug_config_write(uint32_t cmd_id, uint32_t *buf, int size) { int ret = 0, size_bytes = 0; struct scm_desc desc_arg; uint32_t *payload = NULL; size_bytes = (size - 3) * sizeof(uint32_t); payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size_bytes), GFP_KERNEL); if (!payload) { ret = -ENOMEM; goto set_cfg_exit; } memcpy(payload, &buf[3], size_bytes); /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = size_bytes; desc_arg.args[2] = buf[0]; desc_arg.args[3] = buf[1]; desc_arg.args[4] = buf[2]; desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL); dmac_flush_range(payload, (void *)payload + size_bytes); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, cmd_id), &desc_arg); /* Have memory barrier before we access the TZ data */ mb(); if (ret) { pr_err("Error in config debug read. err:%d\n", ret); goto set_cfg_exit; } set_cfg_exit: return ret; } static int lmh_parse_and_extract(const char __user *user_buf, size_t count, enum lmh_read_type type) { char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; char *next_line = NULL; int ret = 0, data_ct = 0, i = 0, size = 0; uint32_t *config_buf = NULL; /* Allocate two extra space to add ';' character and NULL terminate */ local_buf = kzalloc(count + 2, GFP_KERNEL); if (!local_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } if (copy_from_user(local_buf, user_buf, count)) { pr_err("user buf error\n"); ret = -EFAULT; goto dfs_cfg_write_exit; } size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); local_buf[size - 2] = ';'; local_buf[size - 1] = '\0'; curr_ptr = next_line = local_buf; while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) != NULL) { data_ct = 0; *token1 = '\0'; curr_ptr = next_line; next_line = token1 + 1; for (token = (char *)curr_ptr; token && ((token = strnchr(token, next_line - token, ' ')) != NULL); token++) data_ct++; if (data_ct < 2) { pr_err("Invalid format string:[%s]\n", curr_ptr); ret = -EINVAL; goto dfs_cfg_write_exit; } config_buf = kzalloc((++data_ct) * sizeof(uint32_t), GFP_KERNEL); if (!config_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); i++) { token = strnchr(token, next_line - token, ' '); if (token) *token = '\0'; ret = kstrtouint(curr_ptr, 0, &config_buf[i]); if (ret < 0) { pr_err("Data[%s] scan error. err:%d\n", curr_ptr, ret); kfree(config_buf); goto dfs_cfg_write_exit; } if (token) curr_ptr = ++token; } switch (type) { case LMH_READ_TYPE: case LMH_CONFIG_TYPE: ret = lmh_debug_config_write(LMH_DEBUG_SET, config_buf, data_ct); break; default: ret = -EINVAL; break; } kfree(config_buf); if (ret) { pr_err("Config error. type:%d err:%d\n", type, ret); goto dfs_cfg_write_exit; } } dfs_cfg_write_exit: kfree(local_buf); return ret; } static ssize_t lmh_dbgfs_config_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_CONFIG_TYPE); return count; } static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) { static uint32_t *read_buf; static int read_buf_size; int idx = 0, ret = 0; if (!read_buf_size) { ret = lmh_debug_read(&read_buf); if (ret <= 0) goto dfs_read_exit; if (!read_buf || ret < sizeof(uint32_t)) { ret = -EINVAL; goto dfs_read_exit; } read_buf_size = ret; ret = 0; } do { seq_printf(seq_fp, "0x%x ", read_buf[idx]); if (seq_has_overflowed(seq_fp)) { pr_err("Seq overflow. idx:%d\n", idx); goto dfs_read_exit; } idx++; if ((idx % LMH_READ_LINE_LENGTH) == 0) { seq_puts(seq_fp, "\n"); if (seq_has_overflowed(seq_fp)) { pr_err("Seq overflow. idx:%d\n", idx); goto dfs_read_exit; } } } while (idx < (read_buf_size / sizeof(uint32_t))); read_buf_size = 0; read_buf = NULL; dfs_read_exit: return ret; } static int lmh_get_recurssive_data(struct scm_desc *desc_arg, uint32_t cmd_idx, uint32_t *payload, uint32_t *size, uint32_t **dest_buf) { int idx = 0, ret = 0; uint32_t next = 0; do { desc_arg->args[cmd_idx] = next; dmac_flush_range(payload, (void *)payload + sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE); ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE), desc_arg); dmac_inv_range(payload, (void *)payload + sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE); *size = desc_arg->ret[0]; /* Have barrier before reading from TZ data */ mb(); if (ret) { pr_err("Error in SCM get type. cmd:%x err:%d\n", LMH_DEBUG_GET_TYPE, ret); return ret; } if (!*size) { pr_err("No LMH device supported\n"); return -ENODEV; } if (!*dest_buf) { *dest_buf = devm_kcalloc(lmh_data->dev, *size, sizeof(**dest_buf), GFP_KERNEL); if (!*dest_buf) return -ENOMEM; } for (idx = next; idx < min((next + LMH_SCM_PAYLOAD_SIZE), *size); idx++) (*dest_buf)[idx] = payload[idx - next]; next += LMH_SCM_PAYLOAD_SIZE; } while (next < *size); return ret; } static ssize_t lmh_dbgfs_data_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_READ_TYPE); return count; } static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_data_read, inode->i_private); } static int lmh_debug_get_types(bool is_read, uint32_t **buf) { int ret = 0; uint32_t size = 0; struct scm_desc desc_arg; uint32_t *payload = NULL, *dest_buf = NULL; if (is_read && lmh_data->read_type) { *buf = lmh_data->read_type; return lmh_data->read_type_count; } else if (!is_read && lmh_data->config_type) { *buf = lmh_data->config_type; return lmh_data->config_type_count; } payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(LMH_SCM_PAYLOAD_SIZE * sizeof(*payload)), GFP_KERNEL); if (!payload) return -ENOMEM; /* &payload may be a physical address > 4 GB */ desc_arg.args[0] = SCM_BUFFER_PHYS(payload); desc_arg.args[1] = SCM_BUFFER_SIZE(uint32_t) * LMH_SCM_PAYLOAD_SIZE; desc_arg.args[2] = (is_read) ? LMH_DEBUG_READ_TYPE : LMH_DEBUG_CONFIG_TYPE; desc_arg.arginfo = SCM_ARGS(4, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL); ret = lmh_get_recurssive_data(&desc_arg, 3, payload, &size, &dest_buf); if (ret) goto get_type_exit; pr_debug("Total %s types:%d\n", (is_read) ? "read" : "config", size); if (is_read) { lmh_data->read_type = *buf = dest_buf; lmh_data->read_type_count = size; } else { lmh_data->config_type = *buf = dest_buf; lmh_data->config_type_count = size; } get_type_exit: if (ret) { if (lmh_data->read_type_count) { devm_kfree(lmh_data->dev, lmh_data->read_type); lmh_data->read_type_count = 0; } if (lmh_data->config_type_count) { devm_kfree(lmh_data->dev, lmh_data->config_type); lmh_data->config_type_count = 0; } } if (payload) devm_kfree(lmh_data->dev, payload); return (ret) ? ret : size; } static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) { int ret = 0, idx = 0, size = 0; uint32_t *type_list = NULL; switch (type) { case LMH_READ_TYPE: ret = lmh_debug_get_types(true, &type_list); break; case LMH_CONFIG_TYPE: ret = lmh_debug_get_types(false, &type_list); break; default: return -EINVAL; } if (ret <= 0 || !type_list) { pr_err("No device information. err:%d\n", ret); return -ENODEV; } size = ret; for (idx = 0; idx < size; idx++) seq_printf(seq_fp, "0x%x ", type_list[idx]); seq_puts(seq_fp, "\n"); return 0; } static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_READ_TYPE); } static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_read_type, inode->i_private); } static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_CONFIG_TYPE); } static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_config_type, inode->i_private); } static const struct file_operations lmh_dbgfs_config_fops = { .write = lmh_dbgfs_config_write, }; static const struct file_operations lmh_dbgfs_read_fops = { .open = lmh_dbgfs_data_open, .read = seq_read, .write = lmh_dbgfs_data_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_read_type_fops = { .open = lmh_dbgfs_read_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_config_type_fops = { .open = lmh_dbgfs_config_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int lmh_check_tz_debug_cmds(void) { if (!scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_SET) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ) || !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE)) { pr_debug("LMH debug scm not available\n"); return -ENODEV; } return 0; } static int lmh_debug_init(void) { int ret = 0; if (lmh_check_tz_debug_cmds()) { pr_debug("Debug commands not available\n"); return -ENODEV; } lmh_data->debugfs_parent = debugfs_create_dir(LMH_MON_NAME, NULL); if (IS_ERR(lmh_data->debugfs_parent)) { ret = PTR_ERR(lmh_data->debugfs_parent); pr_debug("Error creating debugfs dir:%s. err:%d\n", LMH_MON_NAME, ret); return ret; } lmh_data->debug_read = debugfs_create_file(LMH_DBGFS_READ, 0600, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_read_fops); if (IS_ERR(lmh_data->debug_read)) { pr_err("Error creating" LMH_DBGFS_READ "entry\n"); ret = PTR_ERR(lmh_data->debug_read); goto dbg_reg_exit; } lmh_data->debug_config = debugfs_create_file(LMH_DBGFS_CONFIG_READ, 0200, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_config_fops); if (IS_ERR(lmh_data->debug_config)) { pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); ret = PTR_ERR(lmh_data->debug_config); goto dbg_reg_exit; } lmh_data->debug_read_type = debugfs_create_file(LMH_DBGFS_READ_TYPES, 0400, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_read_type_fops); if (IS_ERR(lmh_data->debug_read_type)) { pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); ret = PTR_ERR(lmh_data->debug_read_type); goto dbg_reg_exit; } lmh_data->debug_read_type = debugfs_create_file( LMH_DBGFS_CONFIG_TYPES, 0400, lmh_data->debugfs_parent, NULL, &lmh_dbgfs_config_type_fops); if (IS_ERR(lmh_data->debug_config_type)) { pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); ret = PTR_ERR(lmh_data->debug_config_type); goto dbg_reg_exit; } dbg_reg_exit: if (ret) /*Clean up all the dbg nodes*/ debugfs_remove_recursive(lmh_data->debugfs_parent); return ret; } int lmh_debug_register(struct platform_device *pdev) { int ret = 0; if (lmh_data) { pr_debug("Reinitializing lmh hardware driver\n"); return -EEXIST; } lmh_data = devm_kzalloc(&pdev->dev, sizeof(*lmh_data), GFP_KERNEL); if (!lmh_data) return -ENOMEM; lmh_data->dev = &pdev->dev; ret = lmh_debug_init(); if (ret) { pr_debug("LMH debug init failed. err:%d\n", ret); goto probe_exit; } return ret; probe_exit: lmh_data = NULL; return ret; } EXPORT_SYMBOL(lmh_debug_register);