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