ALSA: xen-front: Implement handling of shared buffers
[linux-2.6-block.git] / sound / xen / xen_snd_front.c
CommitLineData
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
19#include <xen/interface/io/sndif.h>
20
21#include "xen_snd_front.h"
788ef64a 22#include "xen_snd_front_evtchnl.h"
cc3196ae
OA
23
24static void xen_snd_drv_fini(struct xen_snd_front_info *front_info)
25{
788ef64a 26 xen_snd_front_evtchnl_free_all(front_info);
cc3196ae
OA
27}
28
29static int sndback_initwait(struct xen_snd_front_info *front_info)
30{
fd3b3604
OA
31 int num_streams;
32 int ret;
33
34 ret = xen_snd_front_cfg_card(front_info, &num_streams);
35 if (ret < 0)
36 return ret;
37
788ef64a
OA
38 /* create event channels for all streams and publish */
39 ret = xen_snd_front_evtchnl_create_all(front_info, num_streams);
40 if (ret < 0)
41 return ret;
42
43 return xen_snd_front_evtchnl_publish_all(front_info);
cc3196ae
OA
44}
45
46static int sndback_connect(struct xen_snd_front_info *front_info)
47{
48 return 0;
49}
50
51static void sndback_disconnect(struct xen_snd_front_info *front_info)
52{
53 xen_snd_drv_fini(front_info);
54 xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising);
55}
56
57static void sndback_changed(struct xenbus_device *xb_dev,
58 enum xenbus_state backend_state)
59{
60 struct xen_snd_front_info *front_info = dev_get_drvdata(&xb_dev->dev);
61 int ret;
62
63 dev_dbg(&xb_dev->dev, "Backend state is %s, front is %s\n",
64 xenbus_strstate(backend_state),
65 xenbus_strstate(xb_dev->state));
66
67 switch (backend_state) {
68 case XenbusStateReconfiguring:
69 /* fall through */
70 case XenbusStateReconfigured:
71 /* fall through */
72 case XenbusStateInitialised:
73 /* fall through */
74 break;
75
76 case XenbusStateInitialising:
77 /* Recovering after backend unexpected closure. */
78 sndback_disconnect(front_info);
79 break;
80
81 case XenbusStateInitWait:
82 /* Recovering after backend unexpected closure. */
83 sndback_disconnect(front_info);
84
85 ret = sndback_initwait(front_info);
86 if (ret < 0)
87 xenbus_dev_fatal(xb_dev, ret, "initializing frontend");
88 else
89 xenbus_switch_state(xb_dev, XenbusStateInitialised);
90 break;
91
92 case XenbusStateConnected:
93 if (xb_dev->state != XenbusStateInitialised)
94 break;
95
96 ret = sndback_connect(front_info);
97 if (ret < 0)
98 xenbus_dev_fatal(xb_dev, ret, "initializing frontend");
99 else
100 xenbus_switch_state(xb_dev, XenbusStateConnected);
101 break;
102
103 case XenbusStateClosing:
104 /*
105 * In this state backend starts freeing resources,
106 * so let it go into closed state first, so we can also
107 * remove ours.
108 */
109 break;
110
111 case XenbusStateUnknown:
112 /* fall through */
113 case XenbusStateClosed:
114 if (xb_dev->state == XenbusStateClosed)
115 break;
116
117 sndback_disconnect(front_info);
118 break;
119 }
120}
121
122static int xen_drv_probe(struct xenbus_device *xb_dev,
123 const struct xenbus_device_id *id)
124{
125 struct xen_snd_front_info *front_info;
126
127 front_info = devm_kzalloc(&xb_dev->dev,
128 sizeof(*front_info), GFP_KERNEL);
129 if (!front_info)
130 return -ENOMEM;
131
132 front_info->xb_dev = xb_dev;
133 dev_set_drvdata(&xb_dev->dev, front_info);
134
135 return xenbus_switch_state(xb_dev, XenbusStateInitialising);
136}
137
138static int xen_drv_remove(struct xenbus_device *dev)
139{
140 struct xen_snd_front_info *front_info = dev_get_drvdata(&dev->dev);
141 int to = 100;
142
143 xenbus_switch_state(dev, XenbusStateClosing);
144
145 /*
146 * On driver removal it is disconnected from XenBus,
147 * so no backend state change events come via .otherend_changed
148 * callback. This prevents us from exiting gracefully, e.g.
149 * signaling the backend to free event channels, waiting for its
150 * state to change to XenbusStateClosed and cleaning at our end.
151 * Normally when front driver removed backend will finally go into
152 * XenbusStateInitWait state.
153 *
154 * Workaround: read backend's state manually and wait with time-out.
155 */
156 while ((xenbus_read_unsigned(front_info->xb_dev->otherend, "state",
157 XenbusStateUnknown) != XenbusStateInitWait) &&
158 to--)
159 msleep(10);
160
161 if (!to) {
162 unsigned int state;
163
164 state = xenbus_read_unsigned(front_info->xb_dev->otherend,
165 "state", XenbusStateUnknown);
166 pr_err("Backend state is %s while removing driver\n",
167 xenbus_strstate(state));
168 }
169
170 xen_snd_drv_fini(front_info);
171 xenbus_frontend_closed(dev);
172 return 0;
173}
174
175static const struct xenbus_device_id xen_drv_ids[] = {
176 { XENSND_DRIVER_NAME },
177 { "" }
178};
179
180static struct xenbus_driver xen_driver = {
181 .ids = xen_drv_ids,
182 .probe = xen_drv_probe,
183 .remove = xen_drv_remove,
184 .otherend_changed = sndback_changed,
185};
186
187static int __init xen_drv_init(void)
188{
189 if (!xen_domain())
190 return -ENODEV;
191
192 if (!xen_has_pv_devices())
193 return -ENODEV;
194
d6e0fbb8
OA
195 /* At the moment we only support case with XEN_PAGE_SIZE == PAGE_SIZE */
196 if (XEN_PAGE_SIZE != PAGE_SIZE) {
197 pr_err(XENSND_DRIVER_NAME ": different kernel and Xen page sizes are not supported: XEN_PAGE_SIZE (%lu) != PAGE_SIZE (%lu)\n",
198 XEN_PAGE_SIZE, PAGE_SIZE);
199 return -ENODEV;
200 }
201
cc3196ae
OA
202 pr_info("Initialising Xen " XENSND_DRIVER_NAME " frontend driver\n");
203 return xenbus_register_frontend(&xen_driver);
204}
205
206static void __exit xen_drv_fini(void)
207{
208 pr_info("Unregistering Xen " XENSND_DRIVER_NAME " frontend driver\n");
209 xenbus_unregister_driver(&xen_driver);
210}
211
212module_init(xen_drv_init);
213module_exit(xen_drv_fini);
214
215MODULE_DESCRIPTION("Xen virtual sound device frontend");
216MODULE_LICENSE("GPL");
217MODULE_ALIAS("xen:" XENSND_DRIVER_NAME);
218MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual soundcard}}");