qemu-e2k/hw/audio/intel-hda.c
Gerd Hoffmann 280c1e1cdb audio/hda: create millisecond timers that handle IO
Currently, the HDA device tries to sync itself with the QEMU audio
backend by waiting for the guest driver to handle buffer completion
interrupts. This causes the backend to often read too much data from the
device, as well as running out of data whenever the guest takes too long
to handle the interrupt.

According to the HDA specification, the guest is also not required to
use interrupts, but can also sync itself by polling the LPIB registers.

This patch will introduce high frequency (1000Hz) timers that interface
with the device and allow for much smoother emulation of the LPIB
registers. Since the timing is now provided by these timers, the need
to wait for buffer completion interrupts also ceases.

Signed-off-by: Martin Schrodt <martin@schrodt.org>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Message-id: 20180622111200.30561-2-kraxel@redhat.com
Message-id: 20171015184033.2951-3-martin@schrodt.org

[ kraxel: keep old code for compatibility with older qemu versions,
          add property to switch code paths at runtime ]
[ kraxel: new code is disabled by default, use-timer=on enables it ]

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2018-06-25 13:57:57 +02:00

1324 lines
40 KiB
C

/*
* Copyright (C) 2010 Red Hat, Inc.
*
* written by Gerd Hoffmann <kraxel@redhat.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/hw.h"
#include "hw/pci/pci.h"
#include "hw/pci/msi.h"
#include "qemu/timer.h"
#include "qemu/bitops.h"
#include "hw/audio/soundhw.h"
#include "intel-hda.h"
#include "intel-hda-defs.h"
#include "sysemu/dma.h"
#include "qapi/error.h"
/* --------------------------------------------------------------------- */
/* hda bus */
static Property hda_props[] = {
DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1),
DEFINE_PROP_END_OF_LIST()
};
static const TypeInfo hda_codec_bus_info = {
.name = TYPE_HDA_BUS,
.parent = TYPE_BUS,
.instance_size = sizeof(HDACodecBus),
};
void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, size_t bus_size,
hda_codec_response_func response,
hda_codec_xfer_func xfer)
{
qbus_create_inplace(bus, bus_size, TYPE_HDA_BUS, dev, NULL);
bus->response = response;
bus->xfer = xfer;
}
static void hda_codec_dev_realize(DeviceState *qdev, Error **errp)
{
HDACodecBus *bus = HDA_BUS(qdev->parent_bus);
HDACodecDevice *dev = HDA_CODEC_DEVICE(qdev);
HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);
if (dev->cad == -1) {
dev->cad = bus->next_cad;
}
if (dev->cad >= 15) {
error_setg(errp, "HDA audio codec address is full");
return;
}
bus->next_cad = dev->cad + 1;
if (cdc->init(dev) != 0) {
error_setg(errp, "HDA audio init failed");
}
}
static void hda_codec_dev_unrealize(DeviceState *qdev, Error **errp)
{
HDACodecDevice *dev = HDA_CODEC_DEVICE(qdev);
HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);
if (cdc->exit) {
cdc->exit(dev);
}
}
HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad)
{
BusChild *kid;
HDACodecDevice *cdev;
QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
DeviceState *qdev = kid->child;
cdev = HDA_CODEC_DEVICE(qdev);
if (cdev->cad == cad) {
return cdev;
}
}
return NULL;
}
void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response)
{
HDACodecBus *bus = HDA_BUS(dev->qdev.parent_bus);
bus->response(dev, solicited, response);
}
bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
uint8_t *buf, uint32_t len)
{
HDACodecBus *bus = HDA_BUS(dev->qdev.parent_bus);
return bus->xfer(dev, stnr, output, buf, len);
}
/* --------------------------------------------------------------------- */
/* intel hda emulation */
typedef struct IntelHDAStream IntelHDAStream;
typedef struct IntelHDAState IntelHDAState;
typedef struct IntelHDAReg IntelHDAReg;
typedef struct bpl {
uint64_t addr;
uint32_t len;
uint32_t flags;
} bpl;
struct IntelHDAStream {
/* registers */
uint32_t ctl;
uint32_t lpib;
uint32_t cbl;
uint32_t lvi;
uint32_t fmt;
uint32_t bdlp_lbase;
uint32_t bdlp_ubase;
/* state */
bpl *bpl;
uint32_t bentries;
uint32_t bsize, be, bp;
};
struct IntelHDAState {
PCIDevice pci;
const char *name;
HDACodecBus codecs;
/* registers */
uint32_t g_ctl;
uint32_t wake_en;
uint32_t state_sts;
uint32_t int_ctl;
uint32_t int_sts;
uint32_t wall_clk;
uint32_t corb_lbase;
uint32_t corb_ubase;
uint32_t corb_rp;
uint32_t corb_wp;
uint32_t corb_ctl;
uint32_t corb_sts;
uint32_t corb_size;
uint32_t rirb_lbase;
uint32_t rirb_ubase;
uint32_t rirb_wp;
uint32_t rirb_cnt;
uint32_t rirb_ctl;
uint32_t rirb_sts;
uint32_t rirb_size;
uint32_t dp_lbase;
uint32_t dp_ubase;
uint32_t icw;
uint32_t irr;
uint32_t ics;
/* streams */
IntelHDAStream st[8];
/* state */
MemoryRegion mmio;
uint32_t rirb_count;
int64_t wall_base_ns;
/* debug logging */
const IntelHDAReg *last_reg;
uint32_t last_val;
uint32_t last_write;
uint32_t last_sec;
uint32_t repeat_count;
/* properties */
uint32_t debug;
OnOffAuto msi;
bool old_msi_addr;
};
#define TYPE_INTEL_HDA_GENERIC "intel-hda-generic"
#define INTEL_HDA(obj) \
OBJECT_CHECK(IntelHDAState, (obj), TYPE_INTEL_HDA_GENERIC)
struct IntelHDAReg {
const char *name; /* register name */
uint32_t size; /* size in bytes */
uint32_t reset; /* reset value */
uint32_t wmask; /* write mask */
uint32_t wclear; /* write 1 to clear bits */
uint32_t offset; /* location in IntelHDAState */
uint32_t shift; /* byte access entries for dwords */
uint32_t stream;
void (*whandler)(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old);
void (*rhandler)(IntelHDAState *d, const IntelHDAReg *reg);
};
static void intel_hda_reset(DeviceState *dev);
/* --------------------------------------------------------------------- */
static hwaddr intel_hda_addr(uint32_t lbase, uint32_t ubase)
{
return ((uint64_t)ubase << 32) | lbase;
}
static void intel_hda_update_int_sts(IntelHDAState *d)
{
uint32_t sts = 0;
uint32_t i;
/* update controller status */
if (d->rirb_sts & ICH6_RBSTS_IRQ) {
sts |= (1 << 30);
}
if (d->rirb_sts & ICH6_RBSTS_OVERRUN) {
sts |= (1 << 30);
}
if (d->state_sts & d->wake_en) {
sts |= (1 << 30);
}
/* update stream status */
for (i = 0; i < 8; i++) {
/* buffer completion interrupt */
if (d->st[i].ctl & (1 << 26)) {
sts |= (1 << i);
}
}
/* update global status */
if (sts & d->int_ctl) {
sts |= (1U << 31);
}
d->int_sts = sts;
}
static void intel_hda_update_irq(IntelHDAState *d)
{
bool msi = msi_enabled(&d->pci);
int level;
intel_hda_update_int_sts(d);
if (d->int_sts & (1U << 31) && d->int_ctl & (1U << 31)) {
level = 1;
} else {
level = 0;
}
dprint(d, 2, "%s: level %d [%s]\n", __func__,
level, msi ? "msi" : "intx");
if (msi) {
if (level) {
msi_notify(&d->pci, 0);
}
} else {
pci_set_irq(&d->pci, level);
}
}
static int intel_hda_send_command(IntelHDAState *d, uint32_t verb)
{
uint32_t cad, nid, data;
HDACodecDevice *codec;
HDACodecDeviceClass *cdc;
cad = (verb >> 28) & 0x0f;
if (verb & (1 << 27)) {
/* indirect node addressing, not specified in HDA 1.0 */
dprint(d, 1, "%s: indirect node addressing (guest bug?)\n", __func__);
return -1;
}
nid = (verb >> 20) & 0x7f;
data = verb & 0xfffff;
codec = hda_codec_find(&d->codecs, cad);
if (codec == NULL) {
dprint(d, 1, "%s: addressed non-existing codec\n", __func__);
return -1;
}
cdc = HDA_CODEC_DEVICE_GET_CLASS(codec);
cdc->command(codec, nid, data);
return 0;
}
static void intel_hda_corb_run(IntelHDAState *d)
{
hwaddr addr;
uint32_t rp, verb;
if (d->ics & ICH6_IRS_BUSY) {
dprint(d, 2, "%s: [icw] verb 0x%08x\n", __func__, d->icw);
intel_hda_send_command(d, d->icw);
return;
}
for (;;) {
if (!(d->corb_ctl & ICH6_CORBCTL_RUN)) {
dprint(d, 2, "%s: !run\n", __func__);
return;
}
if ((d->corb_rp & 0xff) == d->corb_wp) {
dprint(d, 2, "%s: corb ring empty\n", __func__);
return;
}
if (d->rirb_count == d->rirb_cnt) {
dprint(d, 2, "%s: rirb count reached\n", __func__);
return;
}
rp = (d->corb_rp + 1) & 0xff;
addr = intel_hda_addr(d->corb_lbase, d->corb_ubase);
verb = ldl_le_pci_dma(&d->pci, addr + 4*rp);
d->corb_rp = rp;
dprint(d, 2, "%s: [rp 0x%x] verb 0x%08x\n", __func__, rp, verb);
intel_hda_send_command(d, verb);
}
}
static void intel_hda_response(HDACodecDevice *dev, bool solicited, uint32_t response)
{
HDACodecBus *bus = HDA_BUS(dev->qdev.parent_bus);
IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
hwaddr addr;
uint32_t wp, ex;
if (d->ics & ICH6_IRS_BUSY) {
dprint(d, 2, "%s: [irr] response 0x%x, cad 0x%x\n",
__func__, response, dev->cad);
d->irr = response;
d->ics &= ~(ICH6_IRS_BUSY | 0xf0);
d->ics |= (ICH6_IRS_VALID | (dev->cad << 4));
return;
}
if (!(d->rirb_ctl & ICH6_RBCTL_DMA_EN)) {
dprint(d, 1, "%s: rirb dma disabled, drop codec response\n", __func__);
return;
}
ex = (solicited ? 0 : (1 << 4)) | dev->cad;
wp = (d->rirb_wp + 1) & 0xff;
addr = intel_hda_addr(d->rirb_lbase, d->rirb_ubase);
stl_le_pci_dma(&d->pci, addr + 8*wp, response);
stl_le_pci_dma(&d->pci, addr + 8*wp + 4, ex);
d->rirb_wp = wp;
dprint(d, 2, "%s: [wp 0x%x] response 0x%x, extra 0x%x\n",
__func__, wp, response, ex);
d->rirb_count++;
if (d->rirb_count == d->rirb_cnt) {
dprint(d, 2, "%s: rirb count reached (%d)\n", __func__, d->rirb_count);
if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
d->rirb_sts |= ICH6_RBSTS_IRQ;
intel_hda_update_irq(d);
}
} else if ((d->corb_rp & 0xff) == d->corb_wp) {
dprint(d, 2, "%s: corb ring empty (%d/%d)\n", __func__,
d->rirb_count, d->rirb_cnt);
if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
d->rirb_sts |= ICH6_RBSTS_IRQ;
intel_hda_update_irq(d);
}
}
}
static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
uint8_t *buf, uint32_t len)
{
HDACodecBus *bus = HDA_BUS(dev->qdev.parent_bus);
IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
hwaddr addr;
uint32_t s, copy, left;
IntelHDAStream *st;
bool irq = false;
st = output ? d->st + 4 : d->st;
for (s = 0; s < 4; s++) {
if (stnr == ((st[s].ctl >> 20) & 0x0f)) {
st = st + s;
break;
}
}
if (s == 4) {
return false;
}
if (st->bpl == NULL) {
return false;
}
left = len;
s = st->bentries;
while (left > 0 && s-- > 0) {
copy = left;
if (copy > st->bsize - st->lpib)
copy = st->bsize - st->lpib;
if (copy > st->bpl[st->be].len - st->bp)
copy = st->bpl[st->be].len - st->bp;
dprint(d, 3, "dma: entry %d, pos %d/%d, copy %d\n",
st->be, st->bp, st->bpl[st->be].len, copy);
pci_dma_rw(&d->pci, st->bpl[st->be].addr + st->bp, buf, copy, !output);
st->lpib += copy;
st->bp += copy;
buf += copy;
left -= copy;
if (st->bpl[st->be].len == st->bp) {
/* bpl entry filled */
if (st->bpl[st->be].flags & 0x01) {
irq = true;
}
st->bp = 0;
st->be++;
if (st->be == st->bentries) {
/* bpl wrap around */
st->be = 0;
st->lpib = 0;
}
}
}
if (d->dp_lbase & 0x01) {
s = st - d->st;
addr = intel_hda_addr(d->dp_lbase & ~0x01, d->dp_ubase);
stl_le_pci_dma(&d->pci, addr + 8*s, st->lpib);
}
dprint(d, 3, "dma: --\n");
if (irq) {
st->ctl |= (1 << 26); /* buffer completion interrupt */
intel_hda_update_irq(d);
}
return true;
}
static void intel_hda_parse_bdl(IntelHDAState *d, IntelHDAStream *st)
{
hwaddr addr;
uint8_t buf[16];
uint32_t i;
addr = intel_hda_addr(st->bdlp_lbase, st->bdlp_ubase);
st->bentries = st->lvi +1;
g_free(st->bpl);
st->bpl = g_malloc(sizeof(bpl) * st->bentries);
for (i = 0; i < st->bentries; i++, addr += 16) {
pci_dma_read(&d->pci, addr, buf, 16);
st->bpl[i].addr = le64_to_cpu(*(uint64_t *)buf);
st->bpl[i].len = le32_to_cpu(*(uint32_t *)(buf + 8));
st->bpl[i].flags = le32_to_cpu(*(uint32_t *)(buf + 12));
dprint(d, 1, "bdl/%d: 0x%" PRIx64 " +0x%x, 0x%x\n",
i, st->bpl[i].addr, st->bpl[i].len, st->bpl[i].flags);
}
st->bsize = st->cbl;
st->lpib = 0;
st->be = 0;
st->bp = 0;
}
static void intel_hda_notify_codecs(IntelHDAState *d, uint32_t stream, bool running, bool output)
{
BusChild *kid;
HDACodecDevice *cdev;
QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) {
DeviceState *qdev = kid->child;
HDACodecDeviceClass *cdc;
cdev = HDA_CODEC_DEVICE(qdev);
cdc = HDA_CODEC_DEVICE_GET_CLASS(cdev);
if (cdc->stream) {
cdc->stream(cdev, stream, running, output);
}
}
}
/* --------------------------------------------------------------------- */
static void intel_hda_set_g_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
if ((d->g_ctl & ICH6_GCTL_RESET) == 0) {
intel_hda_reset(DEVICE(d));
}
}
static void intel_hda_set_wake_en(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
intel_hda_update_irq(d);
}
static void intel_hda_set_state_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
intel_hda_update_irq(d);
}
static void intel_hda_set_int_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
intel_hda_update_irq(d);
}
static void intel_hda_get_wall_clk(IntelHDAState *d, const IntelHDAReg *reg)
{
int64_t ns;
ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - d->wall_base_ns;
d->wall_clk = (uint32_t)(ns * 24 / 1000); /* 24 MHz */
}
static void intel_hda_set_corb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
intel_hda_corb_run(d);
}
static void intel_hda_set_corb_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
intel_hda_corb_run(d);
}
static void intel_hda_set_rirb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
if (d->rirb_wp & ICH6_RIRBWP_RST) {
d->rirb_wp = 0;
}
}
static void intel_hda_set_rirb_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
intel_hda_update_irq(d);
if ((old & ICH6_RBSTS_IRQ) && !(d->rirb_sts & ICH6_RBSTS_IRQ)) {
/* cleared ICH6_RBSTS_IRQ */
d->rirb_count = 0;
intel_hda_corb_run(d);
}
}
static void intel_hda_set_ics(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
if (d->ics & ICH6_IRS_BUSY) {
intel_hda_corb_run(d);
}
}
static void intel_hda_set_st_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
bool output = reg->stream >= 4;
IntelHDAStream *st = d->st + reg->stream;
if (st->ctl & 0x01) {
/* reset */
dprint(d, 1, "st #%d: reset\n", reg->stream);
st->ctl = SD_STS_FIFO_READY << 24;
}
if ((st->ctl & 0x02) != (old & 0x02)) {
uint32_t stnr = (st->ctl >> 20) & 0x0f;
/* run bit flipped */
if (st->ctl & 0x02) {
/* start */
dprint(d, 1, "st #%d: start %d (ring buf %d bytes)\n",
reg->stream, stnr, st->cbl);
intel_hda_parse_bdl(d, st);
intel_hda_notify_codecs(d, stnr, true, output);
} else {
/* stop */
dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr);
intel_hda_notify_codecs(d, stnr, false, output);
}
}
intel_hda_update_irq(d);
}
/* --------------------------------------------------------------------- */
#define ST_REG(_n, _o) (0x80 + (_n) * 0x20 + (_o))
static const struct IntelHDAReg regtab[] = {
/* global */
[ ICH6_REG_GCAP ] = {
.name = "GCAP",
.size = 2,
.reset = 0x4401,
},
[ ICH6_REG_VMIN ] = {
.name = "VMIN",
.size = 1,
},
[ ICH6_REG_VMAJ ] = {
.name = "VMAJ",
.size = 1,
.reset = 1,
},
[ ICH6_REG_OUTPAY ] = {
.name = "OUTPAY",
.size = 2,
.reset = 0x3c,
},
[ ICH6_REG_INPAY ] = {
.name = "INPAY",
.size = 2,
.reset = 0x1d,
},
[ ICH6_REG_GCTL ] = {
.name = "GCTL",
.size = 4,
.wmask = 0x0103,
.offset = offsetof(IntelHDAState, g_ctl),
.whandler = intel_hda_set_g_ctl,
},
[ ICH6_REG_WAKEEN ] = {
.name = "WAKEEN",
.size = 2,
.wmask = 0x7fff,
.offset = offsetof(IntelHDAState, wake_en),
.whandler = intel_hda_set_wake_en,
},
[ ICH6_REG_STATESTS ] = {
.name = "STATESTS",
.size = 2,
.wmask = 0x7fff,
.wclear = 0x7fff,
.offset = offsetof(IntelHDAState, state_sts),
.whandler = intel_hda_set_state_sts,
},
/* interrupts */
[ ICH6_REG_INTCTL ] = {
.name = "INTCTL",
.size = 4,
.wmask = 0xc00000ff,
.offset = offsetof(IntelHDAState, int_ctl),
.whandler = intel_hda_set_int_ctl,
},
[ ICH6_REG_INTSTS ] = {
.name = "INTSTS",
.size = 4,
.wmask = 0xc00000ff,
.wclear = 0xc00000ff,
.offset = offsetof(IntelHDAState, int_sts),
},
/* misc */
[ ICH6_REG_WALLCLK ] = {
.name = "WALLCLK",
.size = 4,
.offset = offsetof(IntelHDAState, wall_clk),
.rhandler = intel_hda_get_wall_clk,
},
[ ICH6_REG_WALLCLK + 0x2000 ] = {
.name = "WALLCLK(alias)",
.size = 4,
.offset = offsetof(IntelHDAState, wall_clk),
.rhandler = intel_hda_get_wall_clk,
},
/* dma engine */
[ ICH6_REG_CORBLBASE ] = {
.name = "CORBLBASE",
.size = 4,
.wmask = 0xffffff80,
.offset = offsetof(IntelHDAState, corb_lbase),
},
[ ICH6_REG_CORBUBASE ] = {
.name = "CORBUBASE",
.size = 4,
.wmask = 0xffffffff,
.offset = offsetof(IntelHDAState, corb_ubase),
},
[ ICH6_REG_CORBWP ] = {
.name = "CORBWP",
.size = 2,
.wmask = 0xff,
.offset = offsetof(IntelHDAState, corb_wp),
.whandler = intel_hda_set_corb_wp,
},
[ ICH6_REG_CORBRP ] = {
.name = "CORBRP",
.size = 2,
.wmask = 0x80ff,
.offset = offsetof(IntelHDAState, corb_rp),
},
[ ICH6_REG_CORBCTL ] = {
.name = "CORBCTL",
.size = 1,
.wmask = 0x03,
.offset = offsetof(IntelHDAState, corb_ctl),
.whandler = intel_hda_set_corb_ctl,
},
[ ICH6_REG_CORBSTS ] = {
.name = "CORBSTS",
.size = 1,
.wmask = 0x01,
.wclear = 0x01,
.offset = offsetof(IntelHDAState, corb_sts),
},
[ ICH6_REG_CORBSIZE ] = {
.name = "CORBSIZE",
.size = 1,
.reset = 0x42,
.offset = offsetof(IntelHDAState, corb_size),
},
[ ICH6_REG_RIRBLBASE ] = {
.name = "RIRBLBASE",
.size = 4,
.wmask = 0xffffff80,
.offset = offsetof(IntelHDAState, rirb_lbase),
},
[ ICH6_REG_RIRBUBASE ] = {
.name = "RIRBUBASE",
.size = 4,
.wmask = 0xffffffff,
.offset = offsetof(IntelHDAState, rirb_ubase),
},
[ ICH6_REG_RIRBWP ] = {
.name = "RIRBWP",
.size = 2,
.wmask = 0x8000,
.offset = offsetof(IntelHDAState, rirb_wp),
.whandler = intel_hda_set_rirb_wp,
},
[ ICH6_REG_RINTCNT ] = {
.name = "RINTCNT",
.size = 2,
.wmask = 0xff,
.offset = offsetof(IntelHDAState, rirb_cnt),
},
[ ICH6_REG_RIRBCTL ] = {
.name = "RIRBCTL",
.size = 1,
.wmask = 0x07,
.offset = offsetof(IntelHDAState, rirb_ctl),
},
[ ICH6_REG_RIRBSTS ] = {
.name = "RIRBSTS",
.size = 1,
.wmask = 0x05,
.wclear = 0x05,
.offset = offsetof(IntelHDAState, rirb_sts),
.whandler = intel_hda_set_rirb_sts,
},
[ ICH6_REG_RIRBSIZE ] = {
.name = "RIRBSIZE",
.size = 1,
.reset = 0x42,
.offset = offsetof(IntelHDAState, rirb_size),
},
[ ICH6_REG_DPLBASE ] = {
.name = "DPLBASE",
.size = 4,
.wmask = 0xffffff81,
.offset = offsetof(IntelHDAState, dp_lbase),
},
[ ICH6_REG_DPUBASE ] = {
.name = "DPUBASE",
.size = 4,
.wmask = 0xffffffff,
.offset = offsetof(IntelHDAState, dp_ubase),
},
[ ICH6_REG_IC ] = {
.name = "ICW",
.size = 4,
.wmask = 0xffffffff,
.offset = offsetof(IntelHDAState, icw),
},
[ ICH6_REG_IR ] = {
.name = "IRR",
.size = 4,
.offset = offsetof(IntelHDAState, irr),
},
[ ICH6_REG_IRS ] = {
.name = "ICS",
.size = 2,
.wmask = 0x0003,
.wclear = 0x0002,
.offset = offsetof(IntelHDAState, ics),
.whandler = intel_hda_set_ics,
},
#define HDA_STREAM(_t, _i) \
[ ST_REG(_i, ICH6_REG_SD_CTL) ] = { \
.stream = _i, \
.name = _t stringify(_i) " CTL", \
.size = 4, \
.wmask = 0x1cff001f, \
.offset = offsetof(IntelHDAState, st[_i].ctl), \
.whandler = intel_hda_set_st_ctl, \
}, \
[ ST_REG(_i, ICH6_REG_SD_CTL) + 2] = { \
.stream = _i, \
.name = _t stringify(_i) " CTL(stnr)", \
.size = 1, \
.shift = 16, \
.wmask = 0x00ff0000, \
.offset = offsetof(IntelHDAState, st[_i].ctl), \
.whandler = intel_hda_set_st_ctl, \
}, \
[ ST_REG(_i, ICH6_REG_SD_STS)] = { \
.stream = _i, \
.name = _t stringify(_i) " CTL(sts)", \
.size = 1, \
.shift = 24, \
.wmask = 0x1c000000, \
.wclear = 0x1c000000, \
.offset = offsetof(IntelHDAState, st[_i].ctl), \
.whandler = intel_hda_set_st_ctl, \
.reset = SD_STS_FIFO_READY << 24 \
}, \
[ ST_REG(_i, ICH6_REG_SD_LPIB) ] = { \
.stream = _i, \
.name = _t stringify(_i) " LPIB", \
.size = 4, \
.offset = offsetof(IntelHDAState, st[_i].lpib), \
}, \
[ ST_REG(_i, ICH6_REG_SD_LPIB) + 0x2000 ] = { \
.stream = _i, \
.name = _t stringify(_i) " LPIB(alias)", \
.size = 4, \
.offset = offsetof(IntelHDAState, st[_i].lpib), \
}, \
[ ST_REG(_i, ICH6_REG_SD_CBL) ] = { \
.stream = _i, \
.name = _t stringify(_i) " CBL", \
.size = 4, \
.wmask = 0xffffffff, \
.offset = offsetof(IntelHDAState, st[_i].cbl), \
}, \
[ ST_REG(_i, ICH6_REG_SD_LVI) ] = { \
.stream = _i, \
.name = _t stringify(_i) " LVI", \
.size = 2, \
.wmask = 0x00ff, \
.offset = offsetof(IntelHDAState, st[_i].lvi), \
}, \
[ ST_REG(_i, ICH6_REG_SD_FIFOSIZE) ] = { \
.stream = _i, \
.name = _t stringify(_i) " FIFOS", \
.size = 2, \
.reset = HDA_BUFFER_SIZE, \
}, \
[ ST_REG(_i, ICH6_REG_SD_FORMAT) ] = { \
.stream = _i, \
.name = _t stringify(_i) " FMT", \
.size = 2, \
.wmask = 0x7f7f, \
.offset = offsetof(IntelHDAState, st[_i].fmt), \
}, \
[ ST_REG(_i, ICH6_REG_SD_BDLPL) ] = { \
.stream = _i, \
.name = _t stringify(_i) " BDLPL", \
.size = 4, \
.wmask = 0xffffff80, \
.offset = offsetof(IntelHDAState, st[_i].bdlp_lbase), \
}, \
[ ST_REG(_i, ICH6_REG_SD_BDLPU) ] = { \
.stream = _i, \
.name = _t stringify(_i) " BDLPU", \
.size = 4, \
.wmask = 0xffffffff, \
.offset = offsetof(IntelHDAState, st[_i].bdlp_ubase), \
}, \
HDA_STREAM("IN", 0)
HDA_STREAM("IN", 1)
HDA_STREAM("IN", 2)
HDA_STREAM("IN", 3)
HDA_STREAM("OUT", 4)
HDA_STREAM("OUT", 5)
HDA_STREAM("OUT", 6)
HDA_STREAM("OUT", 7)
};
static const IntelHDAReg *intel_hda_reg_find(IntelHDAState *d, hwaddr addr)
{
const IntelHDAReg *reg;
if (addr >= ARRAY_SIZE(regtab)) {
goto noreg;
}
reg = regtab+addr;
if (reg->name == NULL) {
goto noreg;
}
return reg;
noreg:
dprint(d, 1, "unknown register, addr 0x%x\n", (int) addr);
return NULL;
}
static uint32_t *intel_hda_reg_addr(IntelHDAState *d, const IntelHDAReg *reg)
{
uint8_t *addr = (void*)d;
addr += reg->offset;
return (uint32_t*)addr;
}
static void intel_hda_reg_write(IntelHDAState *d, const IntelHDAReg *reg, uint32_t val,
uint32_t wmask)
{
uint32_t *addr;
uint32_t old;
if (!reg) {
return;
}
if (d->debug) {
time_t now = time(NULL);
if (d->last_write && d->last_reg == reg && d->last_val == val) {
d->repeat_count++;
if (d->last_sec != now) {
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
d->last_sec = now;
d->repeat_count = 0;
}
} else {
if (d->repeat_count) {
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
}
dprint(d, 2, "write %-16s: 0x%x (%x)\n", reg->name, val, wmask);
d->last_write = 1;
d->last_reg = reg;
d->last_val = val;
d->last_sec = now;
d->repeat_count = 0;
}
}
assert(reg->offset != 0);
addr = intel_hda_reg_addr(d, reg);
old = *addr;
if (reg->shift) {
val <<= reg->shift;
wmask <<= reg->shift;
}
wmask &= reg->wmask;
*addr &= ~wmask;
*addr |= wmask & val;
*addr &= ~(val & reg->wclear);
if (reg->whandler) {
reg->whandler(d, reg, old);
}
}
static uint32_t intel_hda_reg_read(IntelHDAState *d, const IntelHDAReg *reg,
uint32_t rmask)
{
uint32_t *addr, ret;
if (!reg) {
return 0;
}
if (reg->rhandler) {
reg->rhandler(d, reg);
}
if (reg->offset == 0) {
/* constant read-only register */
ret = reg->reset;
} else {
addr = intel_hda_reg_addr(d, reg);
ret = *addr;
if (reg->shift) {
ret >>= reg->shift;
}
ret &= rmask;
}
if (d->debug) {
time_t now = time(NULL);
if (!d->last_write && d->last_reg == reg && d->last_val == ret) {
d->repeat_count++;
if (d->last_sec != now) {
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
d->last_sec = now;
d->repeat_count = 0;
}
} else {
if (d->repeat_count) {
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
}
dprint(d, 2, "read %-16s: 0x%x (%x)\n", reg->name, ret, rmask);
d->last_write = 0;
d->last_reg = reg;
d->last_val = ret;
d->last_sec = now;
d->repeat_count = 0;
}
}
return ret;
}
static void intel_hda_regs_reset(IntelHDAState *d)
{
uint32_t *addr;
int i;
for (i = 0; i < ARRAY_SIZE(regtab); i++) {
if (regtab[i].name == NULL) {
continue;
}
if (regtab[i].offset == 0) {
continue;
}
addr = intel_hda_reg_addr(d, regtab + i);
*addr = regtab[i].reset;
}
}
/* --------------------------------------------------------------------- */
static void intel_hda_mmio_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
{
IntelHDAState *d = opaque;
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
intel_hda_reg_write(d, reg, val, MAKE_64BIT_MASK(0, size * 8));
}
static uint64_t intel_hda_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
IntelHDAState *d = opaque;
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
return intel_hda_reg_read(d, reg, MAKE_64BIT_MASK(0, size * 8));
}
static const MemoryRegionOps intel_hda_mmio_ops = {
.read = intel_hda_mmio_read,
.write = intel_hda_mmio_write,
.impl = {
.min_access_size = 1,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
/* --------------------------------------------------------------------- */
static void intel_hda_reset(DeviceState *dev)
{
BusChild *kid;
IntelHDAState *d = INTEL_HDA(dev);
HDACodecDevice *cdev;
intel_hda_regs_reset(d);
d->wall_base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
/* reset codecs */
QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) {
DeviceState *qdev = kid->child;
cdev = HDA_CODEC_DEVICE(qdev);
device_reset(DEVICE(cdev));
d->state_sts |= (1 << cdev->cad);
}
intel_hda_update_irq(d);
}
static void intel_hda_realize(PCIDevice *pci, Error **errp)
{
IntelHDAState *d = INTEL_HDA(pci);
uint8_t *conf = d->pci.config;
Error *err = NULL;
int ret;
d->name = object_get_typename(OBJECT(d));
pci_config_set_interrupt_pin(conf, 1);
/* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */
conf[0x40] = 0x01;
if (d->msi != ON_OFF_AUTO_OFF) {
ret = msi_init(&d->pci, d->old_msi_addr ? 0x50 : 0x60,
1, true, false, &err);
/* Any error other than -ENOTSUP(board's MSI support is broken)
* is a programming error */
assert(!ret || ret == -ENOTSUP);
if (ret && d->msi == ON_OFF_AUTO_ON) {
/* Can't satisfy user's explicit msi=on request, fail */
error_append_hint(&err, "You have to use msi=auto (default) or "
"msi=off with this machine type.\n");
error_propagate(errp, err);
return;
}
assert(!err || d->msi == ON_OFF_AUTO_AUTO);
/* With msi=auto, we fall back to MSI off silently */
error_free(err);
}
memory_region_init_io(&d->mmio, OBJECT(d), &intel_hda_mmio_ops, d,
"intel-hda", 0x4000);
pci_register_bar(&d->pci, 0, 0, &d->mmio);
hda_codec_bus_init(DEVICE(pci), &d->codecs, sizeof(d->codecs),
intel_hda_response, intel_hda_xfer);
}
static void intel_hda_exit(PCIDevice *pci)
{
IntelHDAState *d = INTEL_HDA(pci);
msi_uninit(&d->pci);
}
static int intel_hda_post_load(void *opaque, int version)
{
IntelHDAState* d = opaque;
int i;
dprint(d, 1, "%s\n", __func__);
for (i = 0; i < ARRAY_SIZE(d->st); i++) {
if (d->st[i].ctl & 0x02) {
intel_hda_parse_bdl(d, &d->st[i]);
}
}
intel_hda_update_irq(d);
return 0;
}
static const VMStateDescription vmstate_intel_hda_stream = {
.name = "intel-hda-stream",
.version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(ctl, IntelHDAStream),
VMSTATE_UINT32(lpib, IntelHDAStream),
VMSTATE_UINT32(cbl, IntelHDAStream),
VMSTATE_UINT32(lvi, IntelHDAStream),
VMSTATE_UINT32(fmt, IntelHDAStream),
VMSTATE_UINT32(bdlp_lbase, IntelHDAStream),
VMSTATE_UINT32(bdlp_ubase, IntelHDAStream),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_intel_hda = {
.name = "intel-hda",
.version_id = 1,
.post_load = intel_hda_post_load,
.fields = (VMStateField[]) {
VMSTATE_PCI_DEVICE(pci, IntelHDAState),
/* registers */
VMSTATE_UINT32(g_ctl, IntelHDAState),
VMSTATE_UINT32(wake_en, IntelHDAState),
VMSTATE_UINT32(state_sts, IntelHDAState),
VMSTATE_UINT32(int_ctl, IntelHDAState),
VMSTATE_UINT32(int_sts, IntelHDAState),
VMSTATE_UINT32(wall_clk, IntelHDAState),
VMSTATE_UINT32(corb_lbase, IntelHDAState),
VMSTATE_UINT32(corb_ubase, IntelHDAState),
VMSTATE_UINT32(corb_rp, IntelHDAState),
VMSTATE_UINT32(corb_wp, IntelHDAState),
VMSTATE_UINT32(corb_ctl, IntelHDAState),
VMSTATE_UINT32(corb_sts, IntelHDAState),
VMSTATE_UINT32(corb_size, IntelHDAState),
VMSTATE_UINT32(rirb_lbase, IntelHDAState),
VMSTATE_UINT32(rirb_ubase, IntelHDAState),
VMSTATE_UINT32(rirb_wp, IntelHDAState),
VMSTATE_UINT32(rirb_cnt, IntelHDAState),
VMSTATE_UINT32(rirb_ctl, IntelHDAState),
VMSTATE_UINT32(rirb_sts, IntelHDAState),
VMSTATE_UINT32(rirb_size, IntelHDAState),
VMSTATE_UINT32(dp_lbase, IntelHDAState),
VMSTATE_UINT32(dp_ubase, IntelHDAState),
VMSTATE_UINT32(icw, IntelHDAState),
VMSTATE_UINT32(irr, IntelHDAState),
VMSTATE_UINT32(ics, IntelHDAState),
VMSTATE_STRUCT_ARRAY(st, IntelHDAState, 8, 0,
vmstate_intel_hda_stream,
IntelHDAStream),
/* additional state info */
VMSTATE_UINT32(rirb_count, IntelHDAState),
VMSTATE_INT64(wall_base_ns, IntelHDAState),
VMSTATE_END_OF_LIST()
}
};
static Property intel_hda_properties[] = {
DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0),
DEFINE_PROP_ON_OFF_AUTO("msi", IntelHDAState, msi, ON_OFF_AUTO_AUTO),
DEFINE_PROP_BOOL("old_msi_addr", IntelHDAState, old_msi_addr, false),
DEFINE_PROP_END_OF_LIST(),
};
static void intel_hda_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->realize = intel_hda_realize;
k->exit = intel_hda_exit;
k->vendor_id = PCI_VENDOR_ID_INTEL;
k->class_id = PCI_CLASS_MULTIMEDIA_HD_AUDIO;
dc->reset = intel_hda_reset;
dc->vmsd = &vmstate_intel_hda;
dc->props = intel_hda_properties;
}
static void intel_hda_class_init_ich6(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->device_id = 0x2668;
k->revision = 1;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->desc = "Intel HD Audio Controller (ich6)";
}
static void intel_hda_class_init_ich9(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->device_id = 0x293e;
k->revision = 3;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->desc = "Intel HD Audio Controller (ich9)";
}
static const TypeInfo intel_hda_info = {
.name = TYPE_INTEL_HDA_GENERIC,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(IntelHDAState),
.class_init = intel_hda_class_init,
.abstract = true,
.interfaces = (InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static const TypeInfo intel_hda_info_ich6 = {
.name = "intel-hda",
.parent = TYPE_INTEL_HDA_GENERIC,
.class_init = intel_hda_class_init_ich6,
};
static const TypeInfo intel_hda_info_ich9 = {
.name = "ich9-intel-hda",
.parent = TYPE_INTEL_HDA_GENERIC,
.class_init = intel_hda_class_init_ich9,
};
static void hda_codec_device_class_init(ObjectClass *klass, void *data)
{
DeviceClass *k = DEVICE_CLASS(klass);
k->realize = hda_codec_dev_realize;
k->unrealize = hda_codec_dev_unrealize;
set_bit(DEVICE_CATEGORY_SOUND, k->categories);
k->bus_type = TYPE_HDA_BUS;
k->props = hda_props;
}
static const TypeInfo hda_codec_device_type_info = {
.name = TYPE_HDA_CODEC_DEVICE,
.parent = TYPE_DEVICE,
.instance_size = sizeof(HDACodecDevice),
.abstract = true,
.class_size = sizeof(HDACodecDeviceClass),
.class_init = hda_codec_device_class_init,
};
/*
* create intel hda controller with codec attached to it,
* so '-soundhw hda' works.
*/
static int intel_hda_and_codec_init(PCIBus *bus)
{
DeviceState *controller;
BusState *hdabus;
DeviceState *codec;
controller = DEVICE(pci_create_simple(bus, -1, "intel-hda"));
hdabus = QLIST_FIRST(&controller->child_bus);
codec = qdev_create(hdabus, "hda-duplex");
qdev_init_nofail(codec);
return 0;
}
static void intel_hda_register_types(void)
{
type_register_static(&hda_codec_bus_info);
type_register_static(&intel_hda_info);
type_register_static(&intel_hda_info_ich6);
type_register_static(&intel_hda_info_ich9);
type_register_static(&hda_codec_device_type_info);
pci_register_soundhw("hda", "Intel HD Audio", intel_hda_and_codec_init);
}
type_init(intel_hda_register_types)