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