Print this page
6973 audioens driver does not work on VMWare
Reviewed by: Toomas Soome <tsoome@me.com>
Reviewed by: Igor Kozhukhov <ikozhukhov@gmail.com>
Approved by: TBD

@@ -18,10 +18,11 @@
  *
  * CDDL HEADER END
  */
 /*
  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2016 Garrett D'Amore <garrett@damore.org>
  */
 /*
  * Purpose: Creative/Ensoniq AudioPCI97  driver (ES1371/ES1373)
  *
  * This driver is used with the original Ensoniq AudioPCI97 card and many

@@ -41,10 +42,22 @@
 
 #include <sys/audio/audio_driver.h>
 #include <sys/audio/ac97.h>
 #include <sys/note.h>
 #include <sys/pci.h>
+
+/*
+ * For VMWare platforms, we have to utilize the (emulated) hardware interrupts
+ * of the device.  This is necessary for audio playback to function, as
+ * the toggling of the interrupt bits apparently triggers logic inside the
+ * emulated device.  So we need to detect this platform, and conditionally
+ * wire up the interrupt handler.
+ */
+#ifdef __x86
+#include <sys/x86_archext.h>
+#endif
+
 #include "audioens.h"
 
 /*
  * Set the latency to 32, 64, 96, 128 clocks - some APCI97 devices exhibit
  * garbled audio in some cases and setting the latency to higer values fixes it

@@ -93,10 +106,11 @@
         uint32_t                paddr;
         ddi_acc_handle_t        acch;
         ddi_dma_handle_t        dmah;
         int                     nchan;
         unsigned                nframes;
+        unsigned                iframes;
         unsigned                frameno;
         uint64_t                count;
 
         struct audioens_dev     *dev;
         audio_engine_t  *engine;

@@ -114,10 +128,18 @@
 
         ac97_t                  *ac97;
 
         caddr_t                 regs;
         ddi_acc_handle_t        acch;
+
+        boolean_t               suspended;
+
+#ifdef __x86
+        boolean_t               useintr;
+        ddi_intr_handle_t       intrh;
+        uint_t                  intrpri;
+#endif
 } audioens_dev_t;
 
 static ddi_device_acc_attr_t acc_attr = {
         DDI_DEVICE_ATTR_V0,
         DDI_STRUCTURE_LE_ACC,

@@ -128,16 +150,10 @@
         DDI_DEVICE_ATTR_V0,
         DDI_NEVERSWAP_ACC,
         DDI_STRICTORDER_ACC
 };
 
-/*
- * The hardware appears to be able to address up to 16-bits worth of longwords,
- * giving a total address space of 256K.  But we need substantially less.
- */
-#define AUDIOENS_BUF_LEN        (16384)
-
 static ddi_dma_attr_t dma_attr = {
         DMA_ATTR_VERSION,       /* dma_attr_version */
         0x0,                    /* dma_attr_addr_lo */
         0xffffffffU,            /* dma_attr_addr_hi */
         0x3ffff,                /* dma_attr_count_max */

@@ -387,10 +403,115 @@
 {
         PUT32(dev, CONC_bMEMPAGE_OFF, page);    /* Select memory page */
         return (GET32(dev, offs));
 }
 
+#ifdef __x86
+static unsigned
+audioens_intr(caddr_t arg1, caddr_t arg2)
+{
+        audioens_dev_t *dev = (void *)arg1;
+        uint32_t status;
+        uint32_t frameno;
+        uint32_t n;
+        audioens_port_t *port;
+
+        _NOTE(ARGUNUSED(arg2));
+
+        mutex_enter(&dev->mutex);
+        if (dev->suspended || !dev->useintr) {
+                mutex_exit(&dev->mutex);
+                return (DDI_INTR_UNCLAIMED);
+        }
+
+        status = GET32(dev, CONC_dSTATUS_OFF);
+        if ((status & CONC_STATUS_PENDING) == 0) {
+                mutex_exit(&dev->mutex);
+                return (DDI_INTR_UNCLAIMED);
+        }
+
+        /* Three interrupts, DAC1, DAC2, and ADC.  The UART we just toss. */
+
+        if (status & CONC_STATUS_DAC1INT) {
+                port = &dev->port[PORT_DAC];
+
+                /* current frame counter is in high nybble */
+                frameno = audioens_readmem(dev,
+                    CONC_DAC1CTL_PAGE, CONC_wDAC1FC_OFF) >> 16;
+                n = frameno >= port->frameno ?
+                    frameno - port->frameno :
+                    frameno + port->nframes - port->frameno;
+                port->frameno = frameno;
+                port->count += n;
+                CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC1IE);
+                SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC1IE);
+        }
+        if (status & CONC_STATUS_ADCINT) {
+                port = &dev->port[PORT_ADC];
+
+                /* current frame counter is in high nybble */
+                frameno = audioens_readmem(dev,
+                    CONC_ADCCTL_PAGE, CONC_wADCFC_OFF) >> 16;
+                n = frameno >= port->frameno ?
+                    frameno - port->frameno :
+                    frameno + port->nframes - port->frameno;
+                port->frameno = frameno;
+                port->count += n;
+                CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE);
+                SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE);
+        }
+        if (status & CONC_STATUS_DAC2INT) {
+                CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC2IE);
+                SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC2IE);
+        }
+        if (status & CONC_STATUS_UARTINT) {
+                /*
+                 * Consume data in the UART RX FIFO.  We don't support
+                 * the UART for now, so just eat it.
+                 */
+                while (GET8(dev, CONC_bUARTCSTAT_OFF) & CONC_UART_RXRDY)
+                        continue;
+        }
+        mutex_exit(&dev->mutex);
+
+        return (DDI_INTR_CLAIMED);
+}
+
+static int
+audioens_setup_intr(audioens_dev_t *dev)
+{
+        int     act;
+        uint_t  ipri;
+
+        if ((ddi_intr_alloc(dev->dip, &dev->intrh, DDI_INTR_TYPE_FIXED, 0, 1,
+            &act, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) || (act != 1)) {
+                audio_dev_warn(dev->osdev, "can't alloc intr handle");
+                goto fail;
+        }
+
+        if (ddi_intr_get_pri(dev->intrh, &ipri) != DDI_SUCCESS) {
+                audio_dev_warn(dev->osdev, "can't get interrupt priority");
+                goto fail;
+        }
+        if (ddi_intr_add_handler(dev->intrh, audioens_intr, dev, NULL) !=
+            DDI_SUCCESS) {
+                audio_dev_warn(dev->osdev, "cannot add interrupt handler");
+                goto fail;
+        }
+        dev->intrpri = ipri;
+        return (DDI_SUCCESS);
+
+fail:
+        if (dev->intrh != NULL) {
+                (void) ddi_intr_free(dev->intrh);
+                dev->intrh = NULL;
+        }
+        return (DDI_FAILURE);
+}
+
+#endif  /* __x86 */
+
 /*
  * Audio routines
  */
 static int
 audioens_format(void *arg)

@@ -425,11 +546,10 @@
 
         _NOTE(ARGUNUSED(flag));
 
         mutex_enter(&dev->mutex);
 
-        port->nframes = AUDIOENS_BUF_LEN / (port->nchan * sizeof (int16_t));
         port->count = 0;
 
         *nframes = port->nframes;
         *bufp = port->kaddr;
         mutex_exit(&dev->mutex);

@@ -474,16 +594,19 @@
                 audioens_writemem(dev, CONC_DAC1CTL_PAGE, CONC_wDAC1FC_OFF,
                     port->nframes - 1);
                 audioens_writemem(dev, CONC_DAC2CTL_PAGE, CONC_wDAC2FC_OFF,
                     port->nframes - 1);
 
-                /* Set # of frames between interrupts */
-                PUT16(dev, CONC_wDAC1IC_OFF, port->nframes - 1);
-                PUT16(dev, CONC_wDAC2IC_OFF, port->nframes - 1);
-
+                PUT16(dev, CONC_wDAC1IC_OFF, port->iframes - 1);
+                PUT16(dev, CONC_wDAC2IC_OFF, port->iframes - 1);
                 SET8(dev, CONC_bDEVCTL_OFF,
                     CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_DAC1_EN);
+#ifdef __x86
+                if (dev->useintr) {
+                        SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC1IE);
+                }
+#endif
 
                 break;
 
         case PORT_ADC:
                 /* Set physical address of the DMA buffer */

@@ -505,13 +628,18 @@
                 /* Set the frame count */
                 audioens_writemem(dev, CONC_ADCCTL_PAGE, CONC_wADCFC_OFF,
                     port->nframes - 1);
 
                 /* Set # of frames between interrupts */
-                PUT16(dev, CONC_wADCIC_OFF, port->nframes - 1);
+                PUT16(dev, CONC_wADCIC_OFF, port->iframes - 1);
 
                 SET8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_ADC_EN);
+#ifdef __x86
+                if (dev->useintr) {
+                        SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE);
+                }
+#endif
                 break;
         }
 
         port->frameno = 0;
         mutex_exit(&dev->mutex);

@@ -558,10 +686,14 @@
                 offs = CONC_wADCFC_OFF;
                 break;
         }
 
         mutex_enter(&dev->mutex);
+#ifdef __x86
+        if (!dev->useintr) {
+#endif
+
         /*
          * Note that the current frame counter is in the high nybble.
          */
         frameno = audioens_readmem(port->dev, page, offs) >> 16;
         n = frameno >= port->frameno ?

@@ -568,10 +700,14 @@
             frameno - port->frameno :
             frameno + port->nframes - port->frameno;
         port->frameno = frameno;
         port->count += n;
 
+#ifdef __x86
+        }
+#endif
+
         val = port->count;
         mutex_exit(&dev->mutex);
 
         return (val);
 }

@@ -713,31 +849,44 @@
                 unsigned caps;
                 unsigned dmaflags;
                 size_t rlen;
                 ddi_dma_cookie_t c;
                 unsigned ccnt;
+                size_t bufsz;
 
                 port = &dev->port[i];
                 port->dev = dev;
 
+                /*
+                 * We have 48000Hz.  At that rate, 128 frames will give
+                 * us an interrupt rate of 375Hz.  2048 frames buys about
+                 * 42ms of buffer.  Note that interrupts are only enabled
+                 * for platforms which need them (i.e. VMWare).
+                 */
+
                 switch (i) {
                 case PORT_DAC:
                         port->nchan = 4;
                         port->speed = 48000;
+                        port->iframes = 128;
+                        port->nframes = 2048;
                         caps = ENGINE_OUTPUT_CAP;
                         dmaflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT;
                         break;
 
                 case PORT_ADC:
                         port->nchan = 2;
                         port->speed = 48000;
+                        port->iframes = 128;
+                        port->nframes = 2048;
                         caps = ENGINE_INPUT_CAP;
                         dmaflags = DDI_DMA_READ | DDI_DMA_CONSISTENT;
                         break;
                 }
 
                 port->num = i;
+                bufsz = port->nframes * port->nchan * sizeof (uint16_t);
 
                 /*
                  * Allocate DMA resources.
                  */
 

@@ -745,21 +894,21 @@
                     NULL, &port->dmah) != DDI_SUCCESS) {
                         audio_dev_warn(dev->osdev,
                             "port %d: dma handle allocation failed", i);
                         return (DDI_FAILURE);
                 }
-                if (ddi_dma_mem_alloc(port->dmah, AUDIOENS_BUF_LEN, &buf_attr,
+                if (ddi_dma_mem_alloc(port->dmah, bufsz, &buf_attr,
                     DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->kaddr,
                     &rlen, &port->acch) != DDI_SUCCESS) {
                         audio_dev_warn(dev->osdev,
                             "port %d: dma memory allocation failed", i);
                         return (DDI_FAILURE);
                 }
                 /* ensure that the buffer is zeroed out properly */
                 bzero(port->kaddr, rlen);
                 if (ddi_dma_addr_bind_handle(port->dmah, NULL, port->kaddr,
-                    AUDIOENS_BUF_LEN, dmaflags, DDI_DMA_SLEEP, NULL,
+                    bufsz, dmaflags, DDI_DMA_SLEEP, NULL,
                     &c, &ccnt) != DDI_DMA_MAPPED) {
                         audio_dev_warn(dev->osdev,
                             "port %d: dma binding failed", i);
                         return (DDI_FAILURE);
                 }

@@ -777,13 +926,10 @@
 
                 audio_engine_set_private(port->engine, port);
                 audio_dev_add_engine(dev->osdev, port->engine);
         }
 
-        /*
-         * Set up kstats for interrupt reporting.
-         */
         if (audio_dev_register(dev->osdev) != DDI_SUCCESS) {
                 audio_dev_warn(dev->osdev,
                     "unable to register with audio framework");
                 return (DDI_FAILURE);
         }

@@ -794,10 +940,19 @@
 void
 audioens_destroy(audioens_dev_t *dev)
 {
         int     i;
 
+#ifdef __x86
+        if (dev->useintr && dev->intrh != NULL) {
+                (void) ddi_intr_disable(dev->intrh);
+                (void) ddi_intr_remove_handler(dev->intrh);
+                (void) ddi_intr_free(dev->intrh);
+                dev->intrh = NULL;
+        }
+#endif
+
         mutex_destroy(&dev->mutex);
 
         /* free up ports, including DMA resources for ports */
         for (i = 0; i <= PORT_MAX; i++) {
                 audioens_port_t *port = &dev->port[i];

@@ -845,24 +1000,24 @@
         ddi_set_driver_private(dip, dev);
         mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, NULL);
 
         if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
                 audio_dev_warn(dev->osdev, "pci_config_setup failed");
-                mutex_destroy(&dev->mutex);
-                kmem_free(dev, sizeof (*dev));
-                return (DDI_FAILURE);
+                goto err_exit;
         }
 
         vendor = pci_config_get16(pcih, PCI_CONF_VENID);
         device = pci_config_get16(pcih, PCI_CONF_DEVID);
         revision = pci_config_get8(pcih, PCI_CONF_REVID);
 
         if ((vendor != ENSONIQ_VENDOR_ID && vendor != CREATIVE_VENDOR_ID) ||
             (device != ENSONIQ_ES1371 && device != ENSONIQ_ES5880 &&
             device != ENSONIQ_ES5880A && device != ECTIVA_ES1938 &&
-            device != ENSONIQ_ES5880B))
+            device != ENSONIQ_ES5880B)) {
+                audio_dev_warn(dev->osdev, "unrecognized device");
                 goto err_exit;
+        }
 
         chip_name = "AudioPCI97";
         chip_vers = "unknown";
 
         switch (device) {

@@ -931,16 +1086,40 @@
             &dev->acch) != DDI_SUCCESS) {
                 audio_dev_warn(dev->osdev, "can't map registers");
                 goto err_exit;
         }
 
+#ifdef __x86
+        /*
+         * Virtual platforms (mostly VMWare!) seem to need us to pulse
+         * the interrupt enables to make progress.  So enable (emulated)
+         * hardware interrupts.
+         */
+        dev->useintr = B_FALSE;
+        if (get_hwenv() & HW_VIRTUAL) {
+                dev->useintr = B_TRUE;
+                if (audioens_setup_intr(dev) != DDI_SUCCESS) {
+                        goto err_exit;
+                }
+                /* Reinitialize the mutex with interrupt priority. */
+                mutex_destroy(&dev->mutex);
+                mutex_init(&dev->mutex, NULL, MUTEX_DRIVER,
+                    DDI_INTR_PRI(dev->intrpri));
+        }
+#endif
+
         /* This allocates and configures the engines */
         if (audioens_init(dev) != DDI_SUCCESS) {
                 audio_dev_warn(dev->osdev, "can't init device");
                 goto err_exit;
         }
 
+#ifdef __x86
+        if (dev->useintr) {
+                (void) ddi_intr_enable(dev->intrh);
+        }
+#endif
         pci_config_teardown(&pcih);
 
         ddi_report_dev(dip);
 
         return (DDI_SUCCESS);

@@ -986,10 +1165,14 @@
 }
 
 static int
 audioens_resume(audioens_dev_t *dev)
 {
+        mutex_enter(&dev->mutex);
+        dev->suspended = B_FALSE;
+        mutex_exit(&dev->mutex);
+
         /* reinitialize hardware */
         audioens_init_hw(dev);
 
         /* restore AC97 state */
         ac97_reset(dev->ac97);

@@ -1002,10 +1185,16 @@
 static int
 audioens_suspend(audioens_dev_t *dev)
 {
         audio_dev_suspend(dev->osdev);
 
+        mutex_enter(&dev->mutex);
+        CLR8(dev, CONC_bDEVCTL_OFF,
+            CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_DAC1_EN | CONC_DEVCTL_ADC_EN);
+        dev->suspended = B_TRUE;
+        mutex_exit(&dev->mutex);
+
         return (DDI_SUCCESS);
 }
 
 static int
 audioens_quiesce(dev_info_t *dip)