1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2020-2021 Intel Corporation.
4 */
5
6#include <linux/wwan.h>
7#include "iosm_ipc_trace.h"
8
9/* sub buffer size and number of sub buffer */
10#define IOSM_TRC_SUB_BUFF_SIZE 131072
11#define IOSM_TRC_N_SUB_BUFF 32
12
13#define IOSM_TRC_FILE_PERM 0600
14
15#define IOSM_TRC_DEBUGFS_TRACE "trace"
16#define IOSM_TRC_DEBUGFS_TRACE_CTRL "trace_ctrl"
17
18/**
19 * ipc_trace_port_rx - Receive trace packet from cp and write to relay buffer
20 * @ipc_imem:   Pointer to iosm_imem structure
21 * @skb:        Pointer to struct sk_buff
22 */
23void ipc_trace_port_rx(struct iosm_imem *ipc_imem, struct sk_buff *skb)
24{
25	struct iosm_trace *ipc_trace = ipc_imem->trace;
26
27	if (ipc_trace->ipc_rchan)
28		relay_write(ipc_trace->ipc_rchan, skb->data, skb->len);
29
30	dev_kfree_skb(skb);
31}
32
33/* Creates relay file in debugfs. */
34static struct dentry *
35ipc_trace_create_buf_file_handler(const char *filename,
36				  struct dentry *parent,
37				  umode_t mode,
38				  struct rchan_buf *buf,
39				  int *is_global)
40{
41	*is_global = 1;
42	return debugfs_create_file(filename, mode, parent, buf,
43				   &relay_file_operations);
44}
45
46/* Removes relay file from debugfs. */
47static int ipc_trace_remove_buf_file_handler(struct dentry *dentry)
48{
49	debugfs_remove(dentry);
50	return 0;
51}
52
53static int ipc_trace_subbuf_start_handler(struct rchan_buf *buf, void *subbuf,
54					  void *prev_subbuf,
55					  size_t prev_padding)
56{
57	if (relay_buf_full(buf)) {
58		pr_err_ratelimited("Relay_buf full dropping traces");
59		return 0;
60	}
61
62	return 1;
63}
64
65/* Relay interface callbacks */
66static struct rchan_callbacks relay_callbacks = {
67	.subbuf_start = ipc_trace_subbuf_start_handler,
68	.create_buf_file = ipc_trace_create_buf_file_handler,
69	.remove_buf_file = ipc_trace_remove_buf_file_handler,
70};
71
72/* Copy the trace control mode to user buffer */
73static ssize_t ipc_trace_ctrl_file_read(struct file *filp, char __user *buffer,
74					size_t count, loff_t *ppos)
75{
76	struct iosm_trace *ipc_trace = filp->private_data;
77	char buf[16];
78	int len;
79
80	mutex_lock(&ipc_trace->trc_mutex);
81	len = snprintf(buf, sizeof(buf), "%d\n", ipc_trace->mode);
82	mutex_unlock(&ipc_trace->trc_mutex);
83
84	return simple_read_from_buffer(buffer, count, ppos, buf, len);
85}
86
87/* Open and close the trace channel depending on user input */
88static ssize_t ipc_trace_ctrl_file_write(struct file *filp,
89					 const char __user *buffer,
90					 size_t count, loff_t *ppos)
91{
92	struct iosm_trace *ipc_trace = filp->private_data;
93	unsigned long val;
94	int ret;
95
96	ret = kstrtoul_from_user(buffer, count, 10, &val);
97	if (ret)
98		return ret;
99
100	mutex_lock(&ipc_trace->trc_mutex);
101	if (val == TRACE_ENABLE && ipc_trace->mode != TRACE_ENABLE) {
102		ipc_trace->channel = ipc_imem_sys_port_open(ipc_trace->ipc_imem,
103							    ipc_trace->chl_id,
104							    IPC_HP_CDEV_OPEN);
105		if (!ipc_trace->channel) {
106			ret = -EIO;
107			goto unlock;
108		}
109		ipc_trace->mode = TRACE_ENABLE;
110	} else if (val == TRACE_DISABLE && ipc_trace->mode != TRACE_DISABLE) {
111		ipc_trace->mode = TRACE_DISABLE;
112		/* close trace channel */
113		ipc_imem_sys_port_close(ipc_trace->ipc_imem,
114					ipc_trace->channel);
115		relay_flush(ipc_trace->ipc_rchan);
116	}
117	ret = count;
118unlock:
119	mutex_unlock(&ipc_trace->trc_mutex);
120	return ret;
121}
122
123static const struct file_operations ipc_trace_fops = {
124	.open = simple_open,
125	.write = ipc_trace_ctrl_file_write,
126	.read  = ipc_trace_ctrl_file_read,
127};
128
129/**
130 * ipc_trace_init - Create trace interface & debugfs entries
131 * @ipc_imem:   Pointer to iosm_imem structure
132 *
133 * Returns: Pointer to trace instance on success else NULL
134 */
135struct iosm_trace *ipc_trace_init(struct iosm_imem *ipc_imem)
136{
137	struct ipc_chnl_cfg chnl_cfg = { 0 };
138	struct iosm_trace *ipc_trace;
139
140	ipc_chnl_cfg_get(&chnl_cfg, IPC_MEM_CTRL_CHL_ID_3);
141	ipc_imem_channel_init(ipc_imem, IPC_CTYPE_CTRL, chnl_cfg,
142			      IRQ_MOD_OFF);
143
144	ipc_trace = kzalloc(sizeof(*ipc_trace), GFP_KERNEL);
145	if (!ipc_trace)
146		return NULL;
147
148	ipc_trace->mode = TRACE_DISABLE;
149	ipc_trace->dev = ipc_imem->dev;
150	ipc_trace->ipc_imem = ipc_imem;
151	ipc_trace->chl_id = IPC_MEM_CTRL_CHL_ID_3;
152
153	mutex_init(&ipc_trace->trc_mutex);
154
155	ipc_trace->ctrl_file = debugfs_create_file(IOSM_TRC_DEBUGFS_TRACE_CTRL,
156						   IOSM_TRC_FILE_PERM,
157						   ipc_imem->debugfs_dir,
158						   ipc_trace, &ipc_trace_fops);
159
160	ipc_trace->ipc_rchan = relay_open(IOSM_TRC_DEBUGFS_TRACE,
161					  ipc_imem->debugfs_dir,
162					  IOSM_TRC_SUB_BUFF_SIZE,
163					  IOSM_TRC_N_SUB_BUFF,
164					  &relay_callbacks, NULL);
165
166	return ipc_trace;
167}
168
169/**
170 * ipc_trace_deinit - Closing relayfs, removing debugfs entries
171 * @ipc_trace: Pointer to the iosm_trace data struct
172 */
173void ipc_trace_deinit(struct iosm_trace *ipc_trace)
174{
175	if (!ipc_trace)
176		return;
177
178	debugfs_remove(ipc_trace->ctrl_file);
179	relay_close(ipc_trace->ipc_rchan);
180	mutex_destroy(&ipc_trace->trc_mutex);
181	kfree(ipc_trace);
182}
183