Commit | Line | Data |
---|---|---|
cc3196ae OA |
1 | // SPDX-License-Identifier: GPL-2.0 OR MIT |
2 | ||
3 | /* | |
4 | * Xen para-virtual sound device | |
5 | * | |
6 | * Copyright (C) 2016-2018 EPAM Systems Inc. | |
7 | * | |
8 | * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> | |
9 | */ | |
10 | ||
11 | #include <linux/delay.h> | |
12 | #include <linux/module.h> | |
13 | ||
d6e0fbb8 | 14 | #include <xen/page.h> |
cc3196ae OA |
15 | #include <xen/platform_pci.h> |
16 | #include <xen/xen.h> | |
17 | #include <xen/xenbus.h> | |
18 | ||
58f9d806 | 19 | #include <xen/xen-front-pgdir-shbuf.h> |
cc3196ae OA |
20 | #include <xen/interface/io/sndif.h> |
21 | ||
22 | #include "xen_snd_front.h" | |
1cee5593 | 23 | #include "xen_snd_front_alsa.h" |
788ef64a | 24 | #include "xen_snd_front_evtchnl.h" |
1cee5593 OA |
25 | |
26 | static struct xensnd_req * | |
27 | be_stream_prepare_req(struct xen_snd_front_evtchnl *evtchnl, u8 operation) | |
28 | { | |
29 | struct xensnd_req *req; | |
30 | ||
31 | req = RING_GET_REQUEST(&evtchnl->u.req.ring, | |
32 | evtchnl->u.req.ring.req_prod_pvt); | |
33 | req->operation = operation; | |
34 | req->id = evtchnl->evt_next_id++; | |
35 | evtchnl->evt_id = req->id; | |
36 | return req; | |
37 | } | |
38 | ||
39 | static int be_stream_do_io(struct xen_snd_front_evtchnl *evtchnl) | |
40 | { | |
41 | if (unlikely(evtchnl->state != EVTCHNL_STATE_CONNECTED)) | |
42 | return -EIO; | |
43 | ||
44 | reinit_completion(&evtchnl->u.req.completion); | |
45 | xen_snd_front_evtchnl_flush(evtchnl); | |
46 | return 0; | |
47 | } | |
48 | ||
49 | static int be_stream_wait_io(struct xen_snd_front_evtchnl *evtchnl) | |
50 | { | |
51 | if (wait_for_completion_timeout(&evtchnl->u.req.completion, | |
52 | msecs_to_jiffies(VSND_WAIT_BACK_MS)) <= 0) | |
53 | return -ETIMEDOUT; | |
54 | ||
55 | return evtchnl->u.req.resp_status; | |
56 | } | |
57 | ||
58 | int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl, | |
59 | struct xensnd_query_hw_param *hw_param_req, | |
60 | struct xensnd_query_hw_param *hw_param_resp) | |
61 | { | |
62 | struct xensnd_req *req; | |
63 | int ret; | |
64 | ||
65 | mutex_lock(&evtchnl->u.req.req_io_lock); | |
66 | ||
67 | mutex_lock(&evtchnl->ring_io_lock); | |
68 | req = be_stream_prepare_req(evtchnl, XENSND_OP_HW_PARAM_QUERY); | |
69 | req->op.hw_param = *hw_param_req; | |
70 | mutex_unlock(&evtchnl->ring_io_lock); | |
71 | ||
72 | ret = be_stream_do_io(evtchnl); | |
73 | ||
74 | if (ret == 0) | |
75 | ret = be_stream_wait_io(evtchnl); | |
76 | ||
77 | if (ret == 0) | |
78 | *hw_param_resp = evtchnl->u.req.resp.hw_param; | |
79 | ||
80 | mutex_unlock(&evtchnl->u.req.req_io_lock); | |
81 | return ret; | |
82 | } | |
83 | ||
84 | int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl, | |
58f9d806 | 85 | struct xen_front_pgdir_shbuf *shbuf, |
1cee5593 OA |
86 | u8 format, unsigned int channels, |
87 | unsigned int rate, u32 buffer_sz, | |
88 | u32 period_sz) | |
89 | { | |
90 | struct xensnd_req *req; | |
91 | int ret; | |
92 | ||
93 | mutex_lock(&evtchnl->u.req.req_io_lock); | |
94 | ||
95 | mutex_lock(&evtchnl->ring_io_lock); | |
96 | req = be_stream_prepare_req(evtchnl, XENSND_OP_OPEN); | |
97 | req->op.open.pcm_format = format; | |
98 | req->op.open.pcm_channels = channels; | |
99 | req->op.open.pcm_rate = rate; | |
100 | req->op.open.buffer_sz = buffer_sz; | |
101 | req->op.open.period_sz = period_sz; | |
58f9d806 OA |
102 | req->op.open.gref_directory = |
103 | xen_front_pgdir_shbuf_get_dir_start(shbuf); | |
1cee5593 OA |
104 | mutex_unlock(&evtchnl->ring_io_lock); |
105 | ||
106 | ret = be_stream_do_io(evtchnl); | |
107 | ||
108 | if (ret == 0) | |
109 | ret = be_stream_wait_io(evtchnl); | |
110 | ||
111 | mutex_unlock(&evtchnl->u.req.req_io_lock); | |
112 | return ret; | |
113 | } | |
114 | ||
115 | int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl) | |
116 | { | |
04b3c795 | 117 | __always_unused struct xensnd_req *req; |
1cee5593 OA |
118 | int ret; |
119 | ||
120 | mutex_lock(&evtchnl->u.req.req_io_lock); | |
121 | ||
122 | mutex_lock(&evtchnl->ring_io_lock); | |
123 | req = be_stream_prepare_req(evtchnl, XENSND_OP_CLOSE); | |
124 | mutex_unlock(&evtchnl->ring_io_lock); | |
125 | ||
126 | ret = be_stream_do_io(evtchnl); | |
127 | ||
128 | if (ret == 0) | |
129 | ret = be_stream_wait_io(evtchnl); | |
130 | ||
131 | mutex_unlock(&evtchnl->u.req.req_io_lock); | |
132 | return ret; | |
133 | } | |
134 | ||
135 | int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl, | |
136 | unsigned long pos, unsigned long count) | |
137 | { | |
138 | struct xensnd_req *req; | |
139 | int ret; | |
140 | ||
141 | mutex_lock(&evtchnl->u.req.req_io_lock); | |
142 | ||
143 | mutex_lock(&evtchnl->ring_io_lock); | |
144 | req = be_stream_prepare_req(evtchnl, XENSND_OP_WRITE); | |
145 | req->op.rw.length = count; | |
146 | req->op.rw.offset = pos; | |
147 | mutex_unlock(&evtchnl->ring_io_lock); | |
148 | ||
149 | ret = be_stream_do_io(evtchnl); | |
150 | ||
151 | if (ret == 0) | |
152 | ret = be_stream_wait_io(evtchnl); | |
153 | ||
154 | mutex_unlock(&evtchnl->u.req.req_io_lock); | |
155 | return ret; | |
156 | } | |
157 | ||
158 | int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl, | |
159 | unsigned long pos, unsigned long count) | |
160 | { | |
161 | struct xensnd_req *req; | |
162 | int ret; | |
163 | ||
164 | mutex_lock(&evtchnl->u.req.req_io_lock); | |
165 | ||
166 | mutex_lock(&evtchnl->ring_io_lock); | |
167 | req = be_stream_prepare_req(evtchnl, XENSND_OP_READ); | |
168 | req->op.rw.length = count; | |
169 | req->op.rw.offset = pos; | |
170 | mutex_unlock(&evtchnl->ring_io_lock); | |
171 | ||
172 | ret = be_stream_do_io(evtchnl); | |
173 | ||
174 | if (ret == 0) | |
175 | ret = be_stream_wait_io(evtchnl); | |
176 | ||
177 | mutex_unlock(&evtchnl->u.req.req_io_lock); | |
178 | return ret; | |
179 | } | |
180 | ||
181 | int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl, | |
182 | int type) | |
183 | { | |
184 | struct xensnd_req *req; | |
185 | int ret; | |
186 | ||
187 | mutex_lock(&evtchnl->u.req.req_io_lock); | |
188 | ||
189 | mutex_lock(&evtchnl->ring_io_lock); | |
190 | req = be_stream_prepare_req(evtchnl, XENSND_OP_TRIGGER); | |
191 | req->op.trigger.type = type; | |
192 | mutex_unlock(&evtchnl->ring_io_lock); | |
193 | ||
194 | ret = be_stream_do_io(evtchnl); | |
195 | ||
196 | if (ret == 0) | |
197 | ret = be_stream_wait_io(evtchnl); | |
198 | ||
199 | mutex_unlock(&evtchnl->u.req.req_io_lock); | |
200 | return ret; | |
201 | } | |
cc3196ae OA |
202 | |
203 | static void xen_snd_drv_fini(struct xen_snd_front_info *front_info) | |
204 | { | |
1cee5593 | 205 | xen_snd_front_alsa_fini(front_info); |
788ef64a | 206 | xen_snd_front_evtchnl_free_all(front_info); |
cc3196ae OA |
207 | } |
208 | ||
209 | static int sndback_initwait(struct xen_snd_front_info *front_info) | |
210 | { | |
fd3b3604 OA |
211 | int num_streams; |
212 | int ret; | |
213 | ||
214 | ret = xen_snd_front_cfg_card(front_info, &num_streams); | |
215 | if (ret < 0) | |
216 | return ret; | |
217 | ||
788ef64a OA |
218 | /* create event channels for all streams and publish */ |
219 | ret = xen_snd_front_evtchnl_create_all(front_info, num_streams); | |
220 | if (ret < 0) | |
221 | return ret; | |
222 | ||
223 | return xen_snd_front_evtchnl_publish_all(front_info); | |
cc3196ae OA |
224 | } |
225 | ||
226 | static int sndback_connect(struct xen_snd_front_info *front_info) | |
227 | { | |
1cee5593 | 228 | return xen_snd_front_alsa_init(front_info); |
cc3196ae OA |
229 | } |
230 | ||
231 | static void sndback_disconnect(struct xen_snd_front_info *front_info) | |
232 | { | |
233 | xen_snd_drv_fini(front_info); | |
234 | xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising); | |
235 | } | |
236 | ||
237 | static void sndback_changed(struct xenbus_device *xb_dev, | |
238 | enum xenbus_state backend_state) | |
239 | { | |
240 | struct xen_snd_front_info *front_info = dev_get_drvdata(&xb_dev->dev); | |
241 | int ret; | |
242 | ||
243 | dev_dbg(&xb_dev->dev, "Backend state is %s, front is %s\n", | |
244 | xenbus_strstate(backend_state), | |
245 | xenbus_strstate(xb_dev->state)); | |
246 | ||
247 | switch (backend_state) { | |
248 | case XenbusStateReconfiguring: | |
cc3196ae | 249 | case XenbusStateReconfigured: |
cc3196ae | 250 | case XenbusStateInitialised: |
cc3196ae OA |
251 | break; |
252 | ||
253 | case XenbusStateInitialising: | |
254 | /* Recovering after backend unexpected closure. */ | |
255 | sndback_disconnect(front_info); | |
256 | break; | |
257 | ||
258 | case XenbusStateInitWait: | |
259 | /* Recovering after backend unexpected closure. */ | |
260 | sndback_disconnect(front_info); | |
261 | ||
262 | ret = sndback_initwait(front_info); | |
263 | if (ret < 0) | |
264 | xenbus_dev_fatal(xb_dev, ret, "initializing frontend"); | |
265 | else | |
266 | xenbus_switch_state(xb_dev, XenbusStateInitialised); | |
267 | break; | |
268 | ||
269 | case XenbusStateConnected: | |
270 | if (xb_dev->state != XenbusStateInitialised) | |
271 | break; | |
272 | ||
273 | ret = sndback_connect(front_info); | |
274 | if (ret < 0) | |
275 | xenbus_dev_fatal(xb_dev, ret, "initializing frontend"); | |
276 | else | |
277 | xenbus_switch_state(xb_dev, XenbusStateConnected); | |
278 | break; | |
279 | ||
280 | case XenbusStateClosing: | |
281 | /* | |
282 | * In this state backend starts freeing resources, | |
283 | * so let it go into closed state first, so we can also | |
284 | * remove ours. | |
285 | */ | |
286 | break; | |
287 | ||
288 | case XenbusStateUnknown: | |
cc3196ae OA |
289 | case XenbusStateClosed: |
290 | if (xb_dev->state == XenbusStateClosed) | |
291 | break; | |
292 | ||
293 | sndback_disconnect(front_info); | |
294 | break; | |
295 | } | |
296 | } | |
297 | ||
298 | static int xen_drv_probe(struct xenbus_device *xb_dev, | |
299 | const struct xenbus_device_id *id) | |
300 | { | |
301 | struct xen_snd_front_info *front_info; | |
302 | ||
303 | front_info = devm_kzalloc(&xb_dev->dev, | |
304 | sizeof(*front_info), GFP_KERNEL); | |
305 | if (!front_info) | |
306 | return -ENOMEM; | |
307 | ||
308 | front_info->xb_dev = xb_dev; | |
309 | dev_set_drvdata(&xb_dev->dev, front_info); | |
310 | ||
311 | return xenbus_switch_state(xb_dev, XenbusStateInitialising); | |
312 | } | |
313 | ||
314 | static int xen_drv_remove(struct xenbus_device *dev) | |
315 | { | |
316 | struct xen_snd_front_info *front_info = dev_get_drvdata(&dev->dev); | |
317 | int to = 100; | |
318 | ||
319 | xenbus_switch_state(dev, XenbusStateClosing); | |
320 | ||
321 | /* | |
322 | * On driver removal it is disconnected from XenBus, | |
323 | * so no backend state change events come via .otherend_changed | |
324 | * callback. This prevents us from exiting gracefully, e.g. | |
325 | * signaling the backend to free event channels, waiting for its | |
326 | * state to change to XenbusStateClosed and cleaning at our end. | |
327 | * Normally when front driver removed backend will finally go into | |
328 | * XenbusStateInitWait state. | |
329 | * | |
330 | * Workaround: read backend's state manually and wait with time-out. | |
331 | */ | |
332 | while ((xenbus_read_unsigned(front_info->xb_dev->otherend, "state", | |
333 | XenbusStateUnknown) != XenbusStateInitWait) && | |
0d5bcfc9 | 334 | --to) |
cc3196ae OA |
335 | msleep(10); |
336 | ||
337 | if (!to) { | |
338 | unsigned int state; | |
339 | ||
340 | state = xenbus_read_unsigned(front_info->xb_dev->otherend, | |
341 | "state", XenbusStateUnknown); | |
342 | pr_err("Backend state is %s while removing driver\n", | |
343 | xenbus_strstate(state)); | |
344 | } | |
345 | ||
346 | xen_snd_drv_fini(front_info); | |
347 | xenbus_frontend_closed(dev); | |
348 | return 0; | |
349 | } | |
350 | ||
351 | static const struct xenbus_device_id xen_drv_ids[] = { | |
352 | { XENSND_DRIVER_NAME }, | |
353 | { "" } | |
354 | }; | |
355 | ||
356 | static struct xenbus_driver xen_driver = { | |
357 | .ids = xen_drv_ids, | |
358 | .probe = xen_drv_probe, | |
359 | .remove = xen_drv_remove, | |
360 | .otherend_changed = sndback_changed, | |
361 | }; | |
362 | ||
363 | static int __init xen_drv_init(void) | |
364 | { | |
365 | if (!xen_domain()) | |
366 | return -ENODEV; | |
367 | ||
368 | if (!xen_has_pv_devices()) | |
369 | return -ENODEV; | |
370 | ||
d6e0fbb8 OA |
371 | /* At the moment we only support case with XEN_PAGE_SIZE == PAGE_SIZE */ |
372 | if (XEN_PAGE_SIZE != PAGE_SIZE) { | |
373 | pr_err(XENSND_DRIVER_NAME ": different kernel and Xen page sizes are not supported: XEN_PAGE_SIZE (%lu) != PAGE_SIZE (%lu)\n", | |
374 | XEN_PAGE_SIZE, PAGE_SIZE); | |
375 | return -ENODEV; | |
376 | } | |
377 | ||
cc3196ae OA |
378 | pr_info("Initialising Xen " XENSND_DRIVER_NAME " frontend driver\n"); |
379 | return xenbus_register_frontend(&xen_driver); | |
380 | } | |
381 | ||
382 | static void __exit xen_drv_fini(void) | |
383 | { | |
384 | pr_info("Unregistering Xen " XENSND_DRIVER_NAME " frontend driver\n"); | |
385 | xenbus_unregister_driver(&xen_driver); | |
386 | } | |
387 | ||
388 | module_init(xen_drv_init); | |
389 | module_exit(xen_drv_fini); | |
390 | ||
391 | MODULE_DESCRIPTION("Xen virtual sound device frontend"); | |
392 | MODULE_LICENSE("GPL"); | |
393 | MODULE_ALIAS("xen:" XENSND_DRIVER_NAME); |