1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright(c) 2022 Intel Corporation. All rights reserved. |
4 | // |
5 | // Author: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> |
6 | // |
7 | |
8 | #include <linux/auxiliary_bus.h> |
9 | #include <linux/completion.h> |
10 | #include <linux/debugfs.h> |
11 | #include <linux/ktime.h> |
12 | #include <linux/mod_devicetable.h> |
13 | #include <linux/module.h> |
14 | #include <linux/pm_runtime.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/uaccess.h> |
17 | #include <sound/sof/header.h> |
18 | #include <sound/sof/ipc4/header.h> |
19 | |
20 | #include "sof-client.h" |
21 | |
22 | #define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 |
23 | |
24 | struct sof_msg_inject_priv { |
25 | struct dentry *dfs_file; |
26 | size_t max_msg_size; |
27 | enum sof_ipc_type ipc_type; |
28 | |
29 | void *tx_buffer; |
30 | void *rx_buffer; |
31 | }; |
32 | |
33 | static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file) |
34 | { |
35 | struct sof_client_dev *cdev = inode->i_private; |
36 | int ret; |
37 | |
38 | if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) |
39 | return -ENODEV; |
40 | |
41 | ret = debugfs_file_get(dentry: file->f_path.dentry); |
42 | if (unlikely(ret)) |
43 | return ret; |
44 | |
45 | ret = simple_open(inode, file); |
46 | if (ret) |
47 | debugfs_file_put(dentry: file->f_path.dentry); |
48 | |
49 | return ret; |
50 | } |
51 | |
52 | static ssize_t sof_msg_inject_dfs_read(struct file *file, char __user *buffer, |
53 | size_t count, loff_t *ppos) |
54 | { |
55 | struct sof_client_dev *cdev = file->private_data; |
56 | struct sof_msg_inject_priv *priv = cdev->data; |
57 | struct sof_ipc_reply *rhdr = priv->rx_buffer; |
58 | |
59 | if (!rhdr->hdr.size || !count || *ppos) |
60 | return 0; |
61 | |
62 | if (count > rhdr->hdr.size) |
63 | count = rhdr->hdr.size; |
64 | |
65 | if (copy_to_user(to: buffer, from: priv->rx_buffer, n: count)) |
66 | return -EFAULT; |
67 | |
68 | *ppos += count; |
69 | return count; |
70 | } |
71 | |
72 | static ssize_t sof_msg_inject_ipc4_dfs_read(struct file *file, |
73 | char __user *buffer, |
74 | size_t count, loff_t *ppos) |
75 | { |
76 | struct sof_client_dev *cdev = file->private_data; |
77 | struct sof_msg_inject_priv *priv = cdev->data; |
78 | struct sof_ipc4_msg *ipc4_msg = priv->rx_buffer; |
79 | size_t = sizeof(ipc4_msg->header_u64); |
80 | size_t remaining; |
81 | |
82 | if (!ipc4_msg->header_u64 || !count || *ppos) |
83 | return 0; |
84 | |
85 | /* we need space for the header at minimum (u64) */ |
86 | if (count < header_size) |
87 | return -ENOSPC; |
88 | |
89 | remaining = header_size; |
90 | |
91 | /* Only get large config have payload */ |
92 | if (SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_msg->primary) && |
93 | (SOF_IPC4_MSG_TYPE_GET(ipc4_msg->primary) == SOF_IPC4_MOD_LARGE_CONFIG_GET)) |
94 | remaining += ipc4_msg->data_size; |
95 | |
96 | if (count > remaining) |
97 | count = remaining; |
98 | else if (count < remaining) |
99 | remaining = count; |
100 | |
101 | /* copy the header first */ |
102 | if (copy_to_user(to: buffer, from: &ipc4_msg->header_u64, n: header_size)) |
103 | return -EFAULT; |
104 | |
105 | *ppos += header_size; |
106 | remaining -= header_size; |
107 | |
108 | if (!remaining) |
109 | return count; |
110 | |
111 | if (remaining > ipc4_msg->data_size) |
112 | remaining = ipc4_msg->data_size; |
113 | |
114 | /* Copy the payload */ |
115 | if (copy_to_user(to: buffer + *ppos, from: ipc4_msg->data_ptr, n: remaining)) |
116 | return -EFAULT; |
117 | |
118 | *ppos += remaining; |
119 | return count; |
120 | } |
121 | |
122 | static int sof_msg_inject_send_message(struct sof_client_dev *cdev) |
123 | { |
124 | struct sof_msg_inject_priv *priv = cdev->data; |
125 | struct device *dev = &cdev->auxdev.dev; |
126 | int ret, err; |
127 | |
128 | ret = pm_runtime_resume_and_get(dev); |
129 | if (ret < 0 && ret != -EACCES) { |
130 | dev_err_ratelimited(dev, "debugfs write failed to resume %d\n" , ret); |
131 | return ret; |
132 | } |
133 | |
134 | /* send the message */ |
135 | ret = sof_client_ipc_tx_message(cdev, ipc_msg: priv->tx_buffer, reply_data: priv->rx_buffer, |
136 | reply_bytes: priv->max_msg_size); |
137 | if (ret) |
138 | dev_err(dev, "IPC message send failed: %d\n" , ret); |
139 | |
140 | pm_runtime_mark_last_busy(dev); |
141 | err = pm_runtime_put_autosuspend(dev); |
142 | if (err < 0) |
143 | dev_err_ratelimited(dev, "debugfs write failed to idle %d\n" , err); |
144 | |
145 | return ret; |
146 | } |
147 | |
148 | static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer, |
149 | size_t count, loff_t *ppos) |
150 | { |
151 | struct sof_client_dev *cdev = file->private_data; |
152 | struct sof_msg_inject_priv *priv = cdev->data; |
153 | ssize_t size; |
154 | int ret; |
155 | |
156 | if (*ppos) |
157 | return 0; |
158 | |
159 | size = simple_write_to_buffer(to: priv->tx_buffer, available: priv->max_msg_size, |
160 | ppos, from: buffer, count); |
161 | if (size < 0) |
162 | return size; |
163 | if (size != count) |
164 | return -EFAULT; |
165 | |
166 | memset(priv->rx_buffer, 0, priv->max_msg_size); |
167 | |
168 | ret = sof_msg_inject_send_message(cdev); |
169 | |
170 | /* return the error code if test failed */ |
171 | if (ret < 0) |
172 | size = ret; |
173 | |
174 | return size; |
175 | }; |
176 | |
177 | static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file, |
178 | const char __user *buffer, |
179 | size_t count, loff_t *ppos) |
180 | { |
181 | struct sof_client_dev *cdev = file->private_data; |
182 | struct sof_msg_inject_priv *priv = cdev->data; |
183 | struct sof_ipc4_msg *ipc4_msg = priv->tx_buffer; |
184 | size_t data_size; |
185 | int ret; |
186 | |
187 | if (*ppos) |
188 | return 0; |
189 | |
190 | if (count < sizeof(ipc4_msg->header_u64)) |
191 | return -EINVAL; |
192 | |
193 | /* copy the header first */ |
194 | if (copy_from_user(to: &ipc4_msg->header_u64, from: buffer, |
195 | n: sizeof(ipc4_msg->header_u64))) |
196 | return -EFAULT; |
197 | |
198 | data_size = count - sizeof(ipc4_msg->header_u64); |
199 | if (data_size > priv->max_msg_size) |
200 | return -EINVAL; |
201 | |
202 | /* Copy the payload */ |
203 | if (copy_from_user(to: ipc4_msg->data_ptr, |
204 | from: buffer + sizeof(ipc4_msg->header_u64), n: data_size)) |
205 | return -EFAULT; |
206 | |
207 | ipc4_msg->data_size = data_size; |
208 | |
209 | /* Initialize the reply storage */ |
210 | ipc4_msg = priv->rx_buffer; |
211 | ipc4_msg->header_u64 = 0; |
212 | ipc4_msg->data_size = priv->max_msg_size; |
213 | memset(ipc4_msg->data_ptr, 0, priv->max_msg_size); |
214 | |
215 | ret = sof_msg_inject_send_message(cdev); |
216 | |
217 | /* return the error code if test failed */ |
218 | if (ret < 0) |
219 | return ret; |
220 | |
221 | return count; |
222 | }; |
223 | |
224 | static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) |
225 | { |
226 | debugfs_file_put(dentry: file->f_path.dentry); |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | static const struct file_operations sof_msg_inject_fops = { |
232 | .open = sof_msg_inject_dfs_open, |
233 | .read = sof_msg_inject_dfs_read, |
234 | .write = sof_msg_inject_dfs_write, |
235 | .llseek = default_llseek, |
236 | .release = sof_msg_inject_dfs_release, |
237 | |
238 | .owner = THIS_MODULE, |
239 | }; |
240 | |
241 | static const struct file_operations sof_msg_inject_ipc4_fops = { |
242 | .open = sof_msg_inject_dfs_open, |
243 | .read = sof_msg_inject_ipc4_dfs_read, |
244 | .write = sof_msg_inject_ipc4_dfs_write, |
245 | .llseek = default_llseek, |
246 | .release = sof_msg_inject_dfs_release, |
247 | |
248 | .owner = THIS_MODULE, |
249 | }; |
250 | |
251 | static int sof_msg_inject_probe(struct auxiliary_device *auxdev, |
252 | const struct auxiliary_device_id *id) |
253 | { |
254 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
255 | struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); |
256 | static const struct file_operations *fops; |
257 | struct device *dev = &auxdev->dev; |
258 | struct sof_msg_inject_priv *priv; |
259 | size_t alloc_size; |
260 | |
261 | /* allocate memory for client data */ |
262 | priv = devm_kzalloc(dev: &auxdev->dev, size: sizeof(*priv), GFP_KERNEL); |
263 | if (!priv) |
264 | return -ENOMEM; |
265 | |
266 | priv->ipc_type = sof_client_get_ipc_type(cdev); |
267 | priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev); |
268 | alloc_size = priv->max_msg_size; |
269 | |
270 | if (priv->ipc_type == SOF_IPC_TYPE_4) |
271 | alloc_size += sizeof(struct sof_ipc4_msg); |
272 | |
273 | priv->tx_buffer = devm_kmalloc(dev, size: alloc_size, GFP_KERNEL); |
274 | priv->rx_buffer = devm_kzalloc(dev, size: alloc_size, GFP_KERNEL); |
275 | if (!priv->tx_buffer || !priv->rx_buffer) |
276 | return -ENOMEM; |
277 | |
278 | if (priv->ipc_type == SOF_IPC_TYPE_4) { |
279 | struct sof_ipc4_msg *ipc4_msg; |
280 | |
281 | ipc4_msg = priv->tx_buffer; |
282 | ipc4_msg->data_ptr = priv->tx_buffer + sizeof(struct sof_ipc4_msg); |
283 | |
284 | ipc4_msg = priv->rx_buffer; |
285 | ipc4_msg->data_ptr = priv->rx_buffer + sizeof(struct sof_ipc4_msg); |
286 | |
287 | fops = &sof_msg_inject_ipc4_fops; |
288 | } else { |
289 | fops = &sof_msg_inject_fops; |
290 | } |
291 | |
292 | cdev->data = priv; |
293 | |
294 | priv->dfs_file = debugfs_create_file(name: "ipc_msg_inject" , mode: 0644, parent: debugfs_root, |
295 | data: cdev, fops); |
296 | |
297 | /* enable runtime PM */ |
298 | pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); |
299 | pm_runtime_use_autosuspend(dev); |
300 | pm_runtime_enable(dev); |
301 | pm_runtime_mark_last_busy(dev); |
302 | pm_runtime_idle(dev); |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | static void sof_msg_inject_remove(struct auxiliary_device *auxdev) |
308 | { |
309 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
310 | struct sof_msg_inject_priv *priv = cdev->data; |
311 | |
312 | pm_runtime_disable(dev: &auxdev->dev); |
313 | |
314 | debugfs_remove(dentry: priv->dfs_file); |
315 | } |
316 | |
317 | static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = { |
318 | { .name = "snd_sof.msg_injector" }, |
319 | {}, |
320 | }; |
321 | MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); |
322 | |
323 | /* |
324 | * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus |
325 | * type are enough to ensure that the parent SOF device resumes to bring the DSP |
326 | * back to D0. |
327 | * Driver name will be set based on KBUILD_MODNAME. |
328 | */ |
329 | static struct auxiliary_driver sof_msg_inject_client_drv = { |
330 | .probe = sof_msg_inject_probe, |
331 | .remove = sof_msg_inject_remove, |
332 | |
333 | .id_table = sof_msg_inject_client_id_table, |
334 | }; |
335 | |
336 | module_auxiliary_driver(sof_msg_inject_client_drv); |
337 | |
338 | MODULE_DESCRIPTION("SOF IPC Message Injector Client Driver" ); |
339 | MODULE_LICENSE("GPL" ); |
340 | MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); |
341 | |