| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2020-2021 Intel Corporation. |
| */ |
| |
| #include <linux/wwan.h> |
| #include "iosm_ipc_trace.h" |
| |
| /* sub buffer size and number of sub buffer */ |
| #define IOSM_TRC_SUB_BUFF_SIZE 131072 |
| #define IOSM_TRC_N_SUB_BUFF 32 |
| |
| #define IOSM_TRC_FILE_PERM 0600 |
| |
| #define IOSM_TRC_DEBUGFS_TRACE "trace" |
| #define IOSM_TRC_DEBUGFS_TRACE_CTRL "trace_ctrl" |
| |
| /** |
| * ipc_trace_port_rx - Receive trace packet from cp and write to relay buffer |
| * @ipc_imem: Pointer to iosm_imem structure |
| * @skb: Pointer to struct sk_buff |
| */ |
| void ipc_trace_port_rx(struct iosm_imem *ipc_imem, struct sk_buff *skb) |
| { |
| struct iosm_trace *ipc_trace = ipc_imem->trace; |
| |
| if (ipc_trace->ipc_rchan) |
| relay_write(ipc_trace->ipc_rchan, skb->data, skb->len); |
| |
| dev_kfree_skb(skb); |
| } |
| |
| /* Creates relay file in debugfs. */ |
| static struct dentry * |
| ipc_trace_create_buf_file_handler(const char *filename, |
| struct dentry *parent, |
| umode_t mode, |
| struct rchan_buf *buf, |
| int *is_global) |
| { |
| *is_global = 1; |
| return debugfs_create_file(filename, mode, parent, buf, |
| &relay_file_operations); |
| } |
| |
| /* Removes relay file from debugfs. */ |
| static int ipc_trace_remove_buf_file_handler(struct dentry *dentry) |
| { |
| debugfs_remove(dentry); |
| return 0; |
| } |
| |
| static int ipc_trace_subbuf_start_handler(struct rchan_buf *buf, void *subbuf, |
| void *prev_subbuf, |
| size_t prev_padding) |
| { |
| if (relay_buf_full(buf)) { |
| pr_err_ratelimited("Relay_buf full dropping traces"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Relay interface callbacks */ |
| static struct rchan_callbacks relay_callbacks = { |
| .subbuf_start = ipc_trace_subbuf_start_handler, |
| .create_buf_file = ipc_trace_create_buf_file_handler, |
| .remove_buf_file = ipc_trace_remove_buf_file_handler, |
| }; |
| |
| /* Copy the trace control mode to user buffer */ |
| static ssize_t ipc_trace_ctrl_file_read(struct file *filp, char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct iosm_trace *ipc_trace = filp->private_data; |
| char buf[16]; |
| int len; |
| |
| mutex_lock(&ipc_trace->trc_mutex); |
| len = snprintf(buf, sizeof(buf), "%d\n", ipc_trace->mode); |
| mutex_unlock(&ipc_trace->trc_mutex); |
| |
| return simple_read_from_buffer(buffer, count, ppos, buf, len); |
| } |
| |
| /* Open and close the trace channel depending on user input */ |
| static ssize_t ipc_trace_ctrl_file_write(struct file *filp, |
| const char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct iosm_trace *ipc_trace = filp->private_data; |
| unsigned long val; |
| int ret; |
| |
| ret = kstrtoul_from_user(buffer, count, 10, &val); |
| if (ret) |
| return ret; |
| |
| mutex_lock(&ipc_trace->trc_mutex); |
| if (val == TRACE_ENABLE && ipc_trace->mode != TRACE_ENABLE) { |
| ipc_trace->channel = ipc_imem_sys_port_open(ipc_trace->ipc_imem, |
| ipc_trace->chl_id, |
| IPC_HP_CDEV_OPEN); |
| if (!ipc_trace->channel) { |
| ret = -EIO; |
| goto unlock; |
| } |
| ipc_trace->mode = TRACE_ENABLE; |
| } else if (val == TRACE_DISABLE && ipc_trace->mode != TRACE_DISABLE) { |
| ipc_trace->mode = TRACE_DISABLE; |
| /* close trace channel */ |
| ipc_imem_sys_port_close(ipc_trace->ipc_imem, |
| ipc_trace->channel); |
| relay_flush(ipc_trace->ipc_rchan); |
| } |
| ret = count; |
| unlock: |
| mutex_unlock(&ipc_trace->trc_mutex); |
| return ret; |
| } |
| |
| static const struct file_operations ipc_trace_fops = { |
| .open = simple_open, |
| .write = ipc_trace_ctrl_file_write, |
| .read = ipc_trace_ctrl_file_read, |
| }; |
| |
| /** |
| * ipc_trace_init - Create trace interface & debugfs entries |
| * @ipc_imem: Pointer to iosm_imem structure |
| * |
| * Returns: Pointer to trace instance on success else NULL |
| */ |
| struct iosm_trace *ipc_trace_init(struct iosm_imem *ipc_imem) |
| { |
| struct ipc_chnl_cfg chnl_cfg = { 0 }; |
| struct iosm_trace *ipc_trace; |
| |
| ipc_chnl_cfg_get(&chnl_cfg, IPC_MEM_CTRL_CHL_ID_3); |
| ipc_imem_channel_init(ipc_imem, IPC_CTYPE_CTRL, chnl_cfg, |
| IRQ_MOD_OFF); |
| |
| ipc_trace = kzalloc(sizeof(*ipc_trace), GFP_KERNEL); |
| if (!ipc_trace) |
| return NULL; |
| |
| ipc_trace->mode = TRACE_DISABLE; |
| ipc_trace->dev = ipc_imem->dev; |
| ipc_trace->ipc_imem = ipc_imem; |
| ipc_trace->chl_id = IPC_MEM_CTRL_CHL_ID_3; |
| |
| mutex_init(&ipc_trace->trc_mutex); |
| |
| ipc_trace->ctrl_file = debugfs_create_file(IOSM_TRC_DEBUGFS_TRACE_CTRL, |
| IOSM_TRC_FILE_PERM, |
| ipc_imem->debugfs_dir, |
| ipc_trace, &ipc_trace_fops); |
| |
| ipc_trace->ipc_rchan = relay_open(IOSM_TRC_DEBUGFS_TRACE, |
| ipc_imem->debugfs_dir, |
| IOSM_TRC_SUB_BUFF_SIZE, |
| IOSM_TRC_N_SUB_BUFF, |
| &relay_callbacks, NULL); |
| |
| return ipc_trace; |
| } |
| |
| /** |
| * ipc_trace_deinit - Closing relayfs, removing debugfs entries |
| * @ipc_trace: Pointer to the iosm_trace data struct |
| */ |
| void ipc_trace_deinit(struct iosm_trace *ipc_trace) |
| { |
| if (!ipc_trace) |
| return; |
| |
| debugfs_remove(ipc_trace->ctrl_file); |
| relay_close(ipc_trace->ipc_rchan); |
| mutex_destroy(&ipc_trace->trc_mutex); |
| kfree(ipc_trace); |
| } |