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