* 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 "intel-hda.h"
#include "intel-hda-defs.h"
#include "audio/audio.h"
+#include "trace.h"
/* -------------------------------------------------------------------------- */
}
switch (format & AC_FMT_BITS_MASK) {
- case AC_FMT_BITS_8: as->fmt = AUD_FMT_S8; break;
- case AC_FMT_BITS_16: as->fmt = AUD_FMT_S16; break;
- case AC_FMT_BITS_32: as->fmt = AUD_FMT_S32; break;
+ case AC_FMT_BITS_8: as->fmt = AUDIO_FORMAT_S8; break;
+ case AC_FMT_BITS_16: as->fmt = AUDIO_FORMAT_S16; break;
+ case AC_FMT_BITS_32: as->fmt = AUDIO_FORMAT_S32; break;
}
as->nchannels = ((format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT) + 1;
#define PARAM nomixemu
#include "hda-codec-common.h"
+#define HDA_TIMER_TICKS (SCALE_MS)
+#define B_SIZE sizeof(st->buf)
+#define B_MASK (sizeof(st->buf) - 1)
+
/* -------------------------------------------------------------------------- */
static const char *fmt2name[] = {
- [ AUD_FMT_U8 ] = "PCM-U8",
- [ AUD_FMT_S8 ] = "PCM-S8",
- [ AUD_FMT_U16 ] = "PCM-U16",
- [ AUD_FMT_S16 ] = "PCM-S16",
- [ AUD_FMT_U32 ] = "PCM-U32",
- [ AUD_FMT_S32 ] = "PCM-S32",
+ [ AUDIO_FORMAT_U8 ] = "PCM-U8",
+ [ AUDIO_FORMAT_S8 ] = "PCM-S8",
+ [ AUDIO_FORMAT_U16 ] = "PCM-U16",
+ [ AUDIO_FORMAT_S16 ] = "PCM-S16",
+ [ AUDIO_FORMAT_U32 ] = "PCM-U32",
+ [ AUDIO_FORMAT_S32 ] = "PCM-S32",
};
typedef struct HDAAudioState HDAAudioState;
SWVoiceIn *in;
SWVoiceOut *out;
} voice;
- uint8_t buf[HDA_BUFFER_SIZE];
- uint32_t bpos;
+ uint8_t compat_buf[HDA_BUFFER_SIZE];
+ uint32_t compat_bpos;
+ uint8_t buf[8192]; /* size must be power of two */
+ int64_t rpos;
+ int64_t wpos;
+ QEMUTimer *buft;
+ int64_t buft_start;
};
+#define TYPE_HDA_AUDIO "hda-audio"
+#define HDA_AUDIO(obj) OBJECT_CHECK(HDAAudioState, (obj), TYPE_HDA_AUDIO)
+
struct HDAAudioState {
HDACodecDevice hda;
const char *name;
/* properties */
uint32_t debug;
bool mixer;
+ bool use_timer;
};
+static inline int64_t hda_bytes_per_second(HDAAudioStream *st)
+{
+ return 2LL * st->as.nchannels * st->as.freq;
+}
+
+static inline void hda_timer_sync_adjust(HDAAudioStream *st, int64_t target_pos)
+{
+ int64_t limit = B_SIZE / 8;
+ int64_t corr = 0;
+
+ if (target_pos > limit) {
+ corr = HDA_TIMER_TICKS;
+ }
+ if (target_pos < -limit) {
+ corr = -HDA_TIMER_TICKS;
+ }
+ if (target_pos < -(2 * limit)) {
+ corr = -(4 * HDA_TIMER_TICKS);
+ }
+ if (corr == 0) {
+ return;
+ }
+
+ trace_hda_audio_adjust(st->node->name, target_pos);
+ st->buft_start += corr;
+}
+
+static void hda_audio_input_timer(void *opaque)
+{
+ HDAAudioStream *st = opaque;
+
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ int64_t buft_start = st->buft_start;
+ int64_t wpos = st->wpos;
+ int64_t rpos = st->rpos;
+
+ int64_t wanted_rpos = hda_bytes_per_second(st) * (now - buft_start)
+ / NANOSECONDS_PER_SECOND;
+ wanted_rpos &= -4; /* IMPORTANT! clip to frames */
+
+ if (wanted_rpos <= rpos) {
+ /* we already transmitted the data */
+ goto out_timer;
+ }
+
+ int64_t to_transfer = audio_MIN(wpos - rpos, wanted_rpos - rpos);
+ while (to_transfer) {
+ uint32_t start = (rpos & B_MASK);
+ uint32_t chunk = audio_MIN(B_SIZE - start, to_transfer);
+ int rc = hda_codec_xfer(
+ &st->state->hda, st->stream, false, st->buf + start, chunk);
+ if (!rc) {
+ break;
+ }
+ rpos += chunk;
+ to_transfer -= chunk;
+ st->rpos += chunk;
+ }
+
+out_timer:
+
+ if (st->running) {
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS);
+ }
+}
+
static void hda_audio_input_cb(void *opaque, int avail)
+{
+ HDAAudioStream *st = opaque;
+
+ int64_t wpos = st->wpos;
+ int64_t rpos = st->rpos;
+
+ int64_t to_transfer = audio_MIN(B_SIZE - (wpos - rpos), avail);
+
+ hda_timer_sync_adjust(st, -((wpos - rpos) + to_transfer - (B_SIZE >> 1)));
+
+ while (to_transfer) {
+ uint32_t start = (uint32_t) (wpos & B_MASK);
+ uint32_t chunk = (uint32_t) audio_MIN(B_SIZE - start, to_transfer);
+ uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk);
+ wpos += read;
+ to_transfer -= read;
+ st->wpos += read;
+ if (chunk != read) {
+ break;
+ }
+ }
+}
+
+static void hda_audio_output_timer(void *opaque)
+{
+ HDAAudioStream *st = opaque;
+
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ int64_t buft_start = st->buft_start;
+ int64_t wpos = st->wpos;
+ int64_t rpos = st->rpos;
+
+ int64_t wanted_wpos = hda_bytes_per_second(st) * (now - buft_start)
+ / NANOSECONDS_PER_SECOND;
+ wanted_wpos &= -4; /* IMPORTANT! clip to frames */
+
+ if (wanted_wpos <= wpos) {
+ /* we already received the data */
+ goto out_timer;
+ }
+
+ int64_t to_transfer = audio_MIN(B_SIZE - (wpos - rpos), wanted_wpos - wpos);
+ while (to_transfer) {
+ uint32_t start = (wpos & B_MASK);
+ uint32_t chunk = audio_MIN(B_SIZE - start, to_transfer);
+ int rc = hda_codec_xfer(
+ &st->state->hda, st->stream, true, st->buf + start, chunk);
+ if (!rc) {
+ break;
+ }
+ wpos += chunk;
+ to_transfer -= chunk;
+ st->wpos += chunk;
+ }
+
+out_timer:
+
+ if (st->running) {
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS);
+ }
+}
+
+static void hda_audio_output_cb(void *opaque, int avail)
+{
+ HDAAudioStream *st = opaque;
+
+ int64_t wpos = st->wpos;
+ int64_t rpos = st->rpos;
+
+ int64_t to_transfer = audio_MIN(wpos - rpos, avail);
+
+ if (wpos - rpos == B_SIZE) {
+ /* drop buffer, reset timer adjust */
+ st->rpos = 0;
+ st->wpos = 0;
+ st->buft_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ trace_hda_audio_overrun(st->node->name);
+ return;
+ }
+
+ hda_timer_sync_adjust(st, (wpos - rpos) - to_transfer - (B_SIZE >> 1));
+
+ while (to_transfer) {
+ uint32_t start = (uint32_t) (rpos & B_MASK);
+ uint32_t chunk = (uint32_t) audio_MIN(B_SIZE - start, to_transfer);
+ uint32_t written = AUD_write(st->voice.out, st->buf + start, chunk);
+ rpos += written;
+ to_transfer -= written;
+ st->rpos += written;
+ if (chunk != written) {
+ break;
+ }
+ }
+}
+
+static void hda_audio_compat_input_cb(void *opaque, int avail)
{
HDAAudioStream *st = opaque;
int recv = 0;
int len;
bool rc;
- while (avail - recv >= sizeof(st->buf)) {
- if (st->bpos != sizeof(st->buf)) {
- len = AUD_read(st->voice.in, st->buf + st->bpos,
- sizeof(st->buf) - st->bpos);
- st->bpos += len;
+ while (avail - recv >= sizeof(st->compat_buf)) {
+ if (st->compat_bpos != sizeof(st->compat_buf)) {
+ len = AUD_read(st->voice.in, st->compat_buf + st->compat_bpos,
+ sizeof(st->compat_buf) - st->compat_bpos);
+ st->compat_bpos += len;
recv += len;
- if (st->bpos != sizeof(st->buf)) {
+ if (st->compat_bpos != sizeof(st->compat_buf)) {
break;
}
}
rc = hda_codec_xfer(&st->state->hda, st->stream, false,
- st->buf, sizeof(st->buf));
+ st->compat_buf, sizeof(st->compat_buf));
if (!rc) {
break;
}
- st->bpos = 0;
+ st->compat_bpos = 0;
}
}
-static void hda_audio_output_cb(void *opaque, int avail)
+static void hda_audio_compat_output_cb(void *opaque, int avail)
{
HDAAudioStream *st = opaque;
int sent = 0;
int len;
bool rc;
- while (avail - sent >= sizeof(st->buf)) {
- if (st->bpos == sizeof(st->buf)) {
+ while (avail - sent >= sizeof(st->compat_buf)) {
+ if (st->compat_bpos == sizeof(st->compat_buf)) {
rc = hda_codec_xfer(&st->state->hda, st->stream, true,
- st->buf, sizeof(st->buf));
+ st->compat_buf, sizeof(st->compat_buf));
if (!rc) {
break;
}
- st->bpos = 0;
+ st->compat_bpos = 0;
}
- len = AUD_write(st->voice.out, st->buf + st->bpos,
- sizeof(st->buf) - st->bpos);
- st->bpos += len;
+ len = AUD_write(st->voice.out, st->compat_buf + st->compat_bpos,
+ sizeof(st->compat_buf) - st->compat_bpos);
+ st->compat_bpos += len;
sent += len;
- if (st->bpos != sizeof(st->buf)) {
+ if (st->compat_bpos != sizeof(st->compat_buf)) {
break;
}
}
return;
}
st->running = running;
- dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name,
- st->running ? "on" : "off", st->stream);
+ trace_hda_audio_running(st->node->name, st->stream, st->running);
+ if (st->state->use_timer) {
+ if (running) {
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ st->rpos = 0;
+ st->wpos = 0;
+ st->buft_start = now;
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS);
+ } else {
+ timer_del(st->buft);
+ }
+ }
if (st->output) {
AUD_set_active_out(st->voice.out, st->running);
} else {
left = left * 255 / QEMU_HDA_AMP_STEPS;
right = right * 255 / QEMU_HDA_AMP_STEPS;
+ if (!st->state->mixer) {
+ return;
+ }
if (st->output) {
AUD_set_volume_out(st->voice.out, muted, left, right);
} else {
static void hda_audio_setup(HDAAudioStream *st)
{
+ bool use_timer = st->state->use_timer;
+ audio_callback_fn cb;
+
if (st->node == NULL) {
return;
}
- dprint(st->state, 1, "%s: format: %d x %s @ %d Hz\n",
- st->node->name, st->as.nchannels,
- fmt2name[st->as.fmt], st->as.freq);
+ trace_hda_audio_format(st->node->name, st->as.nchannels,
+ fmt2name[st->as.fmt], st->as.freq);
if (st->output) {
+ if (use_timer) {
+ cb = hda_audio_output_cb;
+ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ hda_audio_output_timer, st);
+ } else {
+ cb = hda_audio_compat_output_cb;
+ }
st->voice.out = AUD_open_out(&st->state->card, st->voice.out,
- st->node->name, st,
- hda_audio_output_cb, &st->as);
+ st->node->name, st, cb, &st->as);
} else {
+ if (use_timer) {
+ cb = hda_audio_input_cb;
+ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ hda_audio_input_timer, st);
+ } else {
+ cb = hda_audio_compat_input_cb;
+ }
st->voice.in = AUD_open_in(&st->state->card, st->voice.in,
- st->node->name, st,
- hda_audio_input_cb, &st->as);
+ st->node->name, st, cb, &st->as);
}
}
static void hda_audio_command(HDACodecDevice *hda, uint32_t nid, uint32_t data)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
HDAAudioStream *st;
const desc_node *node = NULL;
const desc_param *param;
goto fail;
}
dprint(a, 2, "%s: nid %d (%s), verb 0x%x, payload 0x%x\n",
- __FUNCTION__, nid, node->name, verb, payload);
+ __func__, nid, node->name, verb, payload);
switch (verb) {
/* all nodes */
fail:
dprint(a, 1, "%s: not handled: nid %d (%s), verb 0x%x, payload 0x%x\n",
- __FUNCTION__, nid, node ? node->name : "?", verb, payload);
+ __func__, nid, node ? node->name : "?", verb, payload);
hda_codec_response(hda, true, 0);
}
static void hda_audio_stream(HDACodecDevice *hda, uint32_t stnr, bool running, bool output)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
int s;
a->running_compat[stnr] = running;
static int hda_audio_init(HDACodecDevice *hda, const struct desc_codec *desc)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
HDAAudioStream *st;
const desc_node *node;
const desc_param *param;
a->desc = desc;
a->name = object_get_typename(OBJECT(a));
- dprint(a, 1, "%s: cad %d\n", __FUNCTION__, a->hda.cad);
+ dprint(a, 1, "%s: cad %d\n", __func__, a->hda.cad);
AUD_register_card("hda", &a->card);
for (i = 0; i < a->desc->nnodes; i++) {
node = a->desc->nodes + i;
param = hda_codec_find_param(node, AC_PAR_AUDIO_WIDGET_CAP);
- if (NULL == param)
+ if (param == NULL) {
continue;
+ }
type = (param->val & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
switch (type) {
case AC_WID_AUD_OUT:
/* unmute output by default */
st->gain_left = QEMU_HDA_AMP_STEPS;
st->gain_right = QEMU_HDA_AMP_STEPS;
- st->bpos = sizeof(st->buf);
+ st->compat_bpos = sizeof(st->compat_buf);
st->output = true;
} else {
st->output = false;
return 0;
}
-static int hda_audio_exit(HDACodecDevice *hda)
+static void hda_audio_exit(HDACodecDevice *hda)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
HDAAudioStream *st;
int i;
- dprint(a, 1, "%s\n", __FUNCTION__);
+ dprint(a, 1, "%s\n", __func__);
for (i = 0; i < ARRAY_SIZE(a->st); i++) {
st = a->st + i;
if (st->node == NULL) {
continue;
}
+ if (a->use_timer) {
+ timer_del(st->buft);
+ }
if (st->output) {
AUD_close_out(&a->card, st->voice.out);
} else {
}
}
AUD_remove_card(&a->card);
- return 0;
}
static int hda_audio_post_load(void *opaque, int version)
HDAAudioStream *st;
int i;
- dprint(a, 1, "%s\n", __FUNCTION__);
+ dprint(a, 1, "%s\n", __func__);
if (version == 1) {
/* assume running_compat[] is for output streams */
for (i = 0; i < ARRAY_SIZE(a->running_compat); i++)
static void hda_audio_reset(DeviceState *dev)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda.qdev, dev);
+ HDAAudioState *a = HDA_AUDIO(dev);
HDAAudioStream *st;
int i;
}
}
+static bool vmstate_hda_audio_stream_buf_needed(void *opaque)
+{
+ HDAAudioStream *st = opaque;
+ return st->state && st->state->use_timer;
+}
+
+static const VMStateDescription vmstate_hda_audio_stream_buf = {
+ .name = "hda-audio-stream/buffer",
+ .version_id = 1,
+ .needed = vmstate_hda_audio_stream_buf_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(buf, HDAAudioStream),
+ VMSTATE_INT64(rpos, HDAAudioStream),
+ VMSTATE_INT64(wpos, HDAAudioStream),
+ VMSTATE_TIMER_PTR(buft, HDAAudioStream),
+ VMSTATE_INT64(buft_start, HDAAudioStream),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_hda_audio_stream = {
.name = "hda-audio-stream",
.version_id = 1,
- .fields = (VMStateField []) {
+ .fields = (VMStateField[]) {
VMSTATE_UINT32(stream, HDAAudioStream),
VMSTATE_UINT32(channel, HDAAudioStream),
VMSTATE_UINT32(format, HDAAudioStream),
VMSTATE_UINT32(gain_right, HDAAudioStream),
VMSTATE_BOOL(mute_left, HDAAudioStream),
VMSTATE_BOOL(mute_right, HDAAudioStream),
- VMSTATE_UINT32(bpos, HDAAudioStream),
- VMSTATE_BUFFER(buf, HDAAudioStream),
+ VMSTATE_UINT32(compat_bpos, HDAAudioStream),
+ VMSTATE_BUFFER(compat_buf, HDAAudioStream),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_hda_audio_stream_buf,
+ NULL
}
};
.name = "hda-audio",
.version_id = 2,
.post_load = hda_audio_post_load,
- .fields = (VMStateField []) {
+ .fields = (VMStateField[]) {
VMSTATE_STRUCT_ARRAY(st, HDAAudioState, 4, 0,
vmstate_hda_audio_stream,
HDAAudioStream),
static Property hda_audio_properties[] = {
DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0),
DEFINE_PROP_BOOL("mixer", HDAAudioState, mixer, true),
+ DEFINE_PROP_BOOL("use-timer", HDAAudioState, use_timer, true),
DEFINE_PROP_END_OF_LIST(),
};
static int hda_audio_init_output(HDACodecDevice *hda)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
if (!a->mixer) {
return hda_audio_init(hda, &output_nomixemu);
static int hda_audio_init_duplex(HDACodecDevice *hda)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
if (!a->mixer) {
return hda_audio_init(hda, &duplex_nomixemu);
static int hda_audio_init_micro(HDACodecDevice *hda)
{
- HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda);
+ HDAAudioState *a = HDA_AUDIO(hda);
if (!a->mixer) {
return hda_audio_init(hda, µ_nomixemu);
}
}
-static void hda_audio_output_class_init(ObjectClass *klass, void *data)
+static void hda_audio_base_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
- k->init = hda_audio_init_output;
k->exit = hda_audio_exit;
k->command = hda_audio_command;
k->stream = hda_audio_stream;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
- dc->desc = "HDA Audio Codec, output-only (line-out)";
dc->reset = hda_audio_reset;
dc->vmsd = &vmstate_hda_audio;
dc->props = hda_audio_properties;
}
+static const TypeInfo hda_audio_info = {
+ .name = TYPE_HDA_AUDIO,
+ .parent = TYPE_HDA_CODEC_DEVICE,
+ .class_init = hda_audio_base_class_init,
+ .abstract = true,
+};
+
+static void hda_audio_output_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
+
+ k->init = hda_audio_init_output;
+ dc->desc = "HDA Audio Codec, output-only (line-out)";
+}
+
static const TypeInfo hda_audio_output_info = {
.name = "hda-output",
- .parent = TYPE_HDA_CODEC_DEVICE,
+ .parent = TYPE_HDA_AUDIO,
.instance_size = sizeof(HDAAudioState),
.class_init = hda_audio_output_class_init,
};
HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
k->init = hda_audio_init_duplex;
- k->exit = hda_audio_exit;
- k->command = hda_audio_command;
- k->stream = hda_audio_stream;
- set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->desc = "HDA Audio Codec, duplex (line-out, line-in)";
- dc->reset = hda_audio_reset;
- dc->vmsd = &vmstate_hda_audio;
- dc->props = hda_audio_properties;
}
static const TypeInfo hda_audio_duplex_info = {
.name = "hda-duplex",
- .parent = TYPE_HDA_CODEC_DEVICE,
+ .parent = TYPE_HDA_AUDIO,
.instance_size = sizeof(HDAAudioState),
.class_init = hda_audio_duplex_class_init,
};
HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
k->init = hda_audio_init_micro;
- k->exit = hda_audio_exit;
- k->command = hda_audio_command;
- k->stream = hda_audio_stream;
- set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->desc = "HDA Audio Codec, duplex (speaker, microphone)";
- dc->reset = hda_audio_reset;
- dc->vmsd = &vmstate_hda_audio;
- dc->props = hda_audio_properties;
}
static const TypeInfo hda_audio_micro_info = {
.name = "hda-micro",
- .parent = TYPE_HDA_CODEC_DEVICE,
+ .parent = TYPE_HDA_AUDIO,
.instance_size = sizeof(HDAAudioState),
.class_init = hda_audio_micro_class_init,
};
static void hda_audio_register_types(void)
{
+ type_register_static(&hda_audio_info);
type_register_static(&hda_audio_output_info);
type_register_static(&hda_audio_duplex_info);
type_register_static(&hda_audio_micro_info);