1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright(c) 2023 Google Inc. All rights reserved. |
4 | // |
5 | // Author: Curtis Malainey <cujomalainey@chromium.org> |
6 | // |
7 | |
8 | #include <linux/auxiliary_bus.h> |
9 | #include <linux/debugfs.h> |
10 | #include <linux/mod_devicetable.h> |
11 | #include <linux/module.h> |
12 | #include <linux/pm_runtime.h> |
13 | #include <sound/sof/header.h> |
14 | |
15 | #include "sof-client.h" |
16 | |
17 | #define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 |
18 | |
19 | struct sof_msg_inject_priv { |
20 | struct dentry *kernel_dfs_file; |
21 | size_t max_msg_size; |
22 | |
23 | void *kernel_buffer; |
24 | }; |
25 | |
26 | static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file) |
27 | { |
28 | int ret = debugfs_file_get(dentry: file->f_path.dentry); |
29 | |
30 | if (unlikely(ret)) |
31 | return ret; |
32 | |
33 | ret = simple_open(inode, file); |
34 | if (ret) |
35 | debugfs_file_put(dentry: file->f_path.dentry); |
36 | |
37 | return ret; |
38 | } |
39 | |
40 | static ssize_t sof_kernel_msg_inject_dfs_write(struct file *file, const char __user *buffer, |
41 | size_t count, loff_t *ppos) |
42 | { |
43 | struct sof_client_dev *cdev = file->private_data; |
44 | struct sof_msg_inject_priv *priv = cdev->data; |
45 | struct sof_ipc_cmd_hdr *hdr = priv->kernel_buffer; |
46 | struct device *dev = &cdev->auxdev.dev; |
47 | ssize_t size; |
48 | int ret; |
49 | |
50 | if (*ppos) |
51 | return 0; |
52 | |
53 | size = simple_write_to_buffer(to: priv->kernel_buffer, available: priv->max_msg_size, |
54 | ppos, from: buffer, count); |
55 | if (size < 0) |
56 | return size; |
57 | if (size != count) |
58 | return -EFAULT; |
59 | |
60 | ret = pm_runtime_resume_and_get(dev); |
61 | if (ret < 0 && ret != -EACCES) { |
62 | dev_err_ratelimited(dev, "debugfs write failed to resume %d\n" , ret); |
63 | return ret; |
64 | } |
65 | |
66 | sof_client_ipc_rx_message(cdev, ipc_msg: hdr, msg_buf: priv->kernel_buffer); |
67 | |
68 | pm_runtime_mark_last_busy(dev); |
69 | ret = pm_runtime_put_autosuspend(dev); |
70 | if (ret < 0) |
71 | dev_err_ratelimited(dev, "debugfs write failed to idle %d\n" , ret); |
72 | |
73 | return count; |
74 | }; |
75 | |
76 | static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) |
77 | { |
78 | debugfs_file_put(dentry: file->f_path.dentry); |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static const struct file_operations sof_kernel_msg_inject_fops = { |
84 | .open = sof_msg_inject_dfs_open, |
85 | .write = sof_kernel_msg_inject_dfs_write, |
86 | .release = sof_msg_inject_dfs_release, |
87 | |
88 | .owner = THIS_MODULE, |
89 | }; |
90 | |
91 | static int sof_msg_inject_probe(struct auxiliary_device *auxdev, |
92 | const struct auxiliary_device_id *id) |
93 | { |
94 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
95 | struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); |
96 | struct device *dev = &auxdev->dev; |
97 | struct sof_msg_inject_priv *priv; |
98 | size_t alloc_size; |
99 | |
100 | /* allocate memory for client data */ |
101 | priv = devm_kzalloc(dev: &auxdev->dev, size: sizeof(*priv), GFP_KERNEL); |
102 | if (!priv) |
103 | return -ENOMEM; |
104 | |
105 | priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev); |
106 | alloc_size = priv->max_msg_size; |
107 | priv->kernel_buffer = devm_kmalloc(dev, size: alloc_size, GFP_KERNEL); |
108 | |
109 | if (!priv->kernel_buffer) |
110 | return -ENOMEM; |
111 | |
112 | cdev->data = priv; |
113 | |
114 | priv->kernel_dfs_file = debugfs_create_file(name: "kernel_ipc_msg_inject" , mode: 0644, |
115 | parent: debugfs_root, data: cdev, |
116 | fops: &sof_kernel_msg_inject_fops); |
117 | |
118 | /* enable runtime PM */ |
119 | pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); |
120 | pm_runtime_use_autosuspend(dev); |
121 | pm_runtime_set_active(dev); |
122 | pm_runtime_enable(dev); |
123 | pm_runtime_mark_last_busy(dev); |
124 | pm_runtime_idle(dev); |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static void sof_msg_inject_remove(struct auxiliary_device *auxdev) |
130 | { |
131 | struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); |
132 | struct sof_msg_inject_priv *priv = cdev->data; |
133 | |
134 | pm_runtime_disable(dev: &auxdev->dev); |
135 | |
136 | debugfs_remove(dentry: priv->kernel_dfs_file); |
137 | } |
138 | |
139 | static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = { |
140 | { .name = "snd_sof.kernel_injector" }, |
141 | {}, |
142 | }; |
143 | MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); |
144 | |
145 | /* |
146 | * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus |
147 | * type are enough to ensure that the parent SOF device resumes to bring the DSP |
148 | * back to D0. |
149 | * Driver name will be set based on KBUILD_MODNAME. |
150 | */ |
151 | static struct auxiliary_driver sof_msg_inject_client_drv = { |
152 | .probe = sof_msg_inject_probe, |
153 | .remove = sof_msg_inject_remove, |
154 | |
155 | .id_table = sof_msg_inject_client_id_table, |
156 | }; |
157 | |
158 | module_auxiliary_driver(sof_msg_inject_client_drv); |
159 | |
160 | MODULE_DESCRIPTION("SOF IPC Kernel Injector Client Driver" ); |
161 | MODULE_LICENSE("GPL" ); |
162 | MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); |
163 | |