File Index
Files and directories
Sign in
FILE

xen_snd_front.c

File preview with syntax highlighting.

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