diff options
author | Vaporeon | 2017-11-01 23:36:35 +1100 |
---|---|---|
committer | Vaporeon | 2017-11-01 23:36:35 +1100 |
commit | ca7a46333893766416173a33ee0b5f73a1447d8b (patch) | |
tree | 9186e977720486f4aa0931cac16cac194143f19d | |
download | aur-ca7a46333893766416173a33ee0b5f73a1447d8b.tar.gz |
add patched package
-rw-r--r-- | .SRCINFO | 127 | ||||
-rw-r--r-- | 65-kvm.rules | 2 | ||||
-rw-r--r-- | PKGBUILD | 244 | ||||
-rw-r--r-- | allow_elf64.patch | 27 | ||||
-rw-r--r-- | audio-improvements.patch | 1125 | ||||
-rw-r--r-- | cpu-pinning.patch | 186 | ||||
-rw-r--r-- | qemu-ga.service | 9 | ||||
-rw-r--r-- | qemu.install | 8 | ||||
-rw-r--r-- | qemu.sysusers | 1 |
9 files changed, 1729 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 000000000000..056e4d4abec4 --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,127 @@ +# Generated by mksrcinfo v8 +# Wed Nov 1 12:36:02 UTC 2017 +pkgbase = qemu-patched + pkgdesc = A generic and open source machine emulator and virtualizer - Patched for extra functionality + pkgver = 2.10.1 + pkgrel = 1 + url = http://wiki.qemu.org/ + arch = i686 + arch = x86_64 + license = GPL2 + license = LGPL2.1 + makedepends = spice-protocol + makedepends = python2 + makedepends = ceph + makedepends = libiscsi + makedepends = glusterfs + depends = virglrenderer + depends = sdl2 + depends = vte3 + depends = brltty + depends = seabios + depends = gnutls + depends = libpng + depends = libaio + depends = numactl + depends = jemalloc + depends = xfsprogs + depends = libnfs + depends = lzo + depends = snappy + depends = curl + depends = vde2 + depends = libcap-ng + depends = spice + depends = libcacard + depends = usbredir + source = http://wiki.qemu.org//download/qemu-2.10.1.tar.bz2 + source = http://wiki.qemu.org//download/qemu-2.10.1.tar.bz2.sig + source = qemu.sysusers + source = qemu-ga.service + source = 65-kvm.rules + source = allow_elf64.patch + source = cpu-pinning.patch + source = audio-improvements.patch + sha256sums = 8e040bc7556401ebb3a347a8f7878e9d4028cf71b2744b1a1699f4e741966ba8 + sha256sums = SKIP + sha256sums = dd43e2ef062b071a0b9d0d5ea54737f41600ca8a84a8aefbebb1ff09f978acfb + sha256sums = c39bcde4a09165e64419fd2033b3532378bba84d509d39e2d51694d44c1f8d88 + sha256sums = 60dcde5002c7c0b983952746e6fb2cf06d6c5b425d64f340f819356e561e7fc7 + sha256sums = 13a6d9e678bdc9e1f051006cfd0555f5a80582368f54c8a1bb5a78ece3832ac4 + sha256sums = 8d4a7e35ab1a0a465f737cf60fc0392afc430e22354a40a89505f8766a3a3ee8 + sha256sums = 23338655345d0ee535f34acc124f1ddd75e5ad4483e2bd87294b7ac4fe3fa859 + +pkgname = qemu-patched + optdepends = qemu-patched-arch-extra: extra architectures support + provides = qemu-headless + provides = qemu + conflicts = qemu-headless + conflicts = qemu + replaces = qemu-kvm + +pkgname = qemu-patched-headless + pkgdesc = QEMU without GUI + depends = seabios + depends = gnutls + depends = libpng + depends = libaio + depends = numactl + depends = jemalloc + depends = xfsprogs + depends = libnfs + depends = lzo + depends = snappy + depends = curl + depends = vde2 + depends = libcap-ng + depends = spice + depends = libcacard + depends = usbredir + optdepends = qemu-patched-headless-arch-extra: extra architectures support + conflicts = qemu-headless + +pkgname = qemu-patched-arch-extra + pkgdesc = QEMU for foreign architectures + depends = qemu + provides = qemu-headless-arch-extra + provides = qemu-arch-extra + conflicts = qemu-headless-arch-extra + conflicts = qemu-arch-extra + options = !strip + +pkgname = qemu-patched-headless-arch-extra + pkgdesc = QEMU without GUI, for foreign architectures + depends = qemu-headless + provides = qemu-headless-arch-extra + conflicts = qemu-headless-arch-extra + options = !strip + +pkgname = qemu-patched-block-iscsi + pkgdesc = QEMU iSCSI block module + depends = glib2 + depends = libiscsi + depends = jemalloc + provides = qemu-block-iscsi + conflicts = qemu-block-iscsi + +pkgname = qemu-patched-block-rbd + pkgdesc = QEMU RBD block module + depends = glib2 + depends = ceph + provides = qemu-block-rbd + conflicts = qemu-block-rbd + +pkgname = qemu-patched-block-gluster + pkgdesc = QEMU GlusterFS block module + depends = glib2 + depends = glusterfs + provides = qemu-block-gluster + conflicts = qemu-block-gluster + +pkgname = qemu-patched-guest-agent + pkgdesc = QEMU Guest Agent + depends = gcc-libs + depends = glib2 + provides = qemu-guest-agent + conflicts = qemu-guest-agent + diff --git a/65-kvm.rules b/65-kvm.rules new file mode 100644 index 000000000000..569ded9f972f --- /dev/null +++ b/65-kvm.rules @@ -0,0 +1,2 @@ +KERNEL=="kvm", GROUP="kvm", MODE="0660" +KERNEL=="vhost-net", GROUP="kvm", MODE="0660", TAG+="uaccess", OPTIONS+="static_node=vhost-net" diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 000000000000..d61cc8536ce7 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,244 @@ +# Maintainer: Vaporeon <vaporeon@vaporeon.io> +# Contributor: Tobias Powalowski <tpowa@archlinux.org> +# Contributor: Sébastien "Seblu" Luttringer <seblu@seblu.net> + +pkgbase=qemu-patched +pkgname=(qemu-patched qemu-patched-headless qemu-patched-arch-extra qemu-patched-headless-arch-extra + qemu-patched-block-{iscsi,rbd,gluster} qemu-patched-guest-agent) +_pkgname=qemu +pkgdesc="A generic and open source machine emulator and virtualizer - Patched for extra functionality" +pkgver=2.10.1 +pkgrel=1 +arch=(i686 x86_64) +license=(GPL2 LGPL2.1) +url="http://wiki.qemu.org/" +_headlessdeps=(seabios gnutls libpng libaio numactl jemalloc xfsprogs libnfs + lzo snappy curl vde2 libcap-ng spice libcacard usbredir) +depends=(virglrenderer sdl2 vte3 brltty "${_headlessdeps[@]}") +makedepends=(spice-protocol python2 ceph libiscsi glusterfs) +source=("$url/download/${_pkgname}-${pkgver}.tar.bz2"{,.sig} + qemu.sysusers + qemu-ga.service + 65-kvm.rules + allow_elf64.patch + cpu-pinning.patch + audio-improvements.patch) +sha256sums=('8e040bc7556401ebb3a347a8f7878e9d4028cf71b2744b1a1699f4e741966ba8' + 'SKIP' + 'dd43e2ef062b071a0b9d0d5ea54737f41600ca8a84a8aefbebb1ff09f978acfb' + 'c39bcde4a09165e64419fd2033b3532378bba84d509d39e2d51694d44c1f8d88' + '60dcde5002c7c0b983952746e6fb2cf06d6c5b425d64f340f819356e561e7fc7' + '13a6d9e678bdc9e1f051006cfd0555f5a80582368f54c8a1bb5a78ece3832ac4' + '8d4a7e35ab1a0a465f737cf60fc0392afc430e22354a40a89505f8766a3a3ee8' + '23338655345d0ee535f34acc124f1ddd75e5ad4483e2bd87294b7ac4fe3fa859') +validpgpkeys=('CEACC9E15534EBABB82D3FA03353C9CEF108B584') + +case $CARCH in + i?86) _corearch=i386 ;; + x86_64) _corearch=x86_64 ;; +esac + +prepare() { + mkdir build-{full,headless} + mkdir -p extra-arch-{full,headless}/usr/{bin,share/qemu} + + cd ${_pkgname}-${pkgver} + sed -i 's/vte-2\.90/vte-2.91/g' configure + + patch -p1 < ../allow_elf64.patch + patch -p1 < ../cpu-pinning.patch + patch -p0 < ../audio-improvements.patch +} + +build() { + _build full \ + --audio-drv-list="pa alsa sdl" + + _build headless \ + --audio-drv-list= \ + --disable-bluez \ + --disable-sdl \ + --disable-gtk \ + --disable-vte \ + --disable-opengl \ + --disable-virglrenderer \ + --disable-brlapi +} + +_build() ( + cd build-$1 + + # qemu vs. make 4 == bad + export ARFLAGS=rv + + # http://permalink.gmane.org/gmane.comp.emulators.qemu/238740 + export CFLAGS+=" -fPIC" + + ../${_pkgname}-${pkgver}/configure \ + --prefix=/usr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --libexecdir=/usr/lib/qemu \ + --python=/usr/bin/python2 \ + --smbd=/usr/bin/smbd \ + --with-gtkabi=3.0 \ + --with-sdlabi=2.0 \ + --enable-modules \ + --enable-jemalloc \ + "${@:2}" + + make +) + +package_qemu-patched() { + optdepends=('qemu-patched-arch-extra: extra architectures support') + provides=(qemu-headless qemu) + conflicts=(qemu-headless qemu) + replaces=(qemu-kvm) + + _package full +} + +package_qemu-patched-headless() { + pkgdesc="QEMU without GUI" + depends=("${_headlessdeps[@]}") + optdepends=('qemu-patched-headless-arch-extra: extra architectures support') + conflicts=(qemu-headless) + _package headless +} + +_package() { + optdepends+=('ovmf: Tianocore UEFI firmware for qemu' + 'samba: SMB/CIFS server support' + 'qemu-patched-block-iscsi: iSCSI block support' + 'qemu-patched-block-rbd: RBD block support' + 'qemu-patched-block-gluster: glusterfs block support') + install=qemu.install + options=(!strip) + + make -C build-$1 DESTDIR="$pkgdir" install "${@:2}" + + # systemd stuff + install -Dm644 65-kvm.rules "$pkgdir/usr/lib/udev/rules.d/65-kvm.rules" + install -Dm644 qemu.sysusers "$pkgdir/usr/lib/sysusers.d/qemu.conf" + + # remove conflicting /var/run directory + cd "$pkgdir" + rm -r var + + cd usr/lib + tidy_strip + + # bridge_helper needs suid + # https://bugs.archlinux.org/task/32565 + chmod u+s qemu/qemu-bridge-helper + + # remove split block modules + rm qemu/block-{iscsi,rbd,gluster}.so + + cd ../bin + tidy_strip + + # remove extra arch + for _bin in qemu-*; do + [[ -f $_bin ]] || continue + + case ${_bin#qemu-} in + # guest agent + ga) rm "$_bin"; continue ;; + + # tools + img|io|nbd) continue ;; + + # core emu + system-${_corearch}) continue ;; + esac + + mv "$_bin" "$srcdir/extra-arch-$1/usr/bin" + done + + cd ../share/qemu + for _blob in *; do + [[ -f $_blob ]] || continue + + case $_blob in + # provided by seabios package + bios.bin|acpi-dsdt.aml|bios-256k.bin|vgabios-cirrus.bin|vgabios-qxl.bin|\ + vgabios-stdvga.bin|vgabios-vmware.bin) rm "$_blob"; continue ;; + + # iPXE ROMs + efi-*|pxe-*) continue ;; + + # core blobs + kvmvapic.bin|linuxboot*|multiboot.bin|sgabios.bin|vgabios*) continue ;; + + # Trace events definitions + trace-events*) continue ;; + + # Logos + *.bmp|*.svg) continue ;; + esac + + mv "$_blob" "$srcdir/extra-arch-$1/usr/share/qemu" + done +} + +package_qemu-patched-arch-extra() { + pkgdesc="QEMU for foreign architectures" + depends=(qemu) + provides=(qemu-headless-arch-extra qemu-arch-extra) + conflicts=(qemu-headless-arch-extra qemu-arch-extra) + options=(!strip) + + mv extra-arch-full/usr "$pkgdir" +} + +package_qemu-patched-headless-arch-extra() { + pkgdesc="QEMU without GUI, for foreign architectures" + depends=(qemu-headless) + options=(!strip) + conflicts=(qemu-headless-arch-extra) + provides=(qemu-headless-arch-extra) + + mv extra-arch-headless/usr "$pkgdir" +} + +package_qemu-patched-block-iscsi() { + pkgdesc="QEMU iSCSI block module" + depends=(glib2 libiscsi jemalloc) + conflicts=(qemu-block-iscsi) + provides=(qemu-block-iscsi) + + install -D build-full/block-iscsi.so "$pkgdir/usr/lib/qemu/block-iscsi.so" +} + +package_qemu-patched-block-rbd() { + pkgdesc="QEMU RBD block module" + depends=(glib2 ceph) + conflicts=(qemu-block-rbd) + provides=(qemu-block-rbd) + + install -D build-full/block-rbd.so "$pkgdir/usr/lib/qemu/block-rbd.so" +} + +package_qemu-patched-block-gluster() { + pkgdesc="QEMU GlusterFS block module" + depends=(glib2 glusterfs) + conflicts=(qemu-block-gluster) + provides=(qemu-block-gluster) + + install -D build-full/block-gluster.so "$pkgdir/usr/lib/qemu/block-gluster.so" +} + +package_qemu-patched-guest-agent() { + pkgdesc="QEMU Guest Agent" + depends=(gcc-libs glib2) + conflicts=(qemu-guest-agent) + provides=(qemu-guest-agent) + + install -D build-full/qemu-ga "$pkgdir/usr/bin/qemu-ga" + install -Dm644 qemu-ga.service "$pkgdir/usr/lib/systemd/system/qemu-ga.service" + install -Dm755 "$srcdir/${_pkgname}-${pkgver}/scripts/qemu-guest-agent/fsfreeze-hook" "$pkgdir/etc/qemu/fsfreeze-hook" +} + +# vim:set ts=2 sw=2 et: diff --git a/allow_elf64.patch b/allow_elf64.patch new file mode 100644 index 000000000000..07f27a038b1b --- /dev/null +++ b/allow_elf64.patch @@ -0,0 +1,27 @@ +commit 3c72765ec760a51f0e879dc792be82c93141e318 +Author: Anatol Pomozov <anatol.pomozov@gmail.com> +Date: Tue Jun 6 20:07:03 2017 -0700 + + Remove restriction that prevents bootimg elf64 images + + It is possible to create a 64 bit elf image that has valid multiboot header. + qemu should be able to boot such images. + + Signed-off-by: Anatol Pomozov <anatol.pomozov@gmail.com> + +diff --git a/hw/i386/multiboot.c b/hw/i386/multiboot.c +index 663f35a658..cf1b4f5fb3 100644 +--- a/hw/i386/multiboot.c ++++ b/hw/i386/multiboot.c +@@ -192,11 +192,6 @@ int load_multiboot(FWCfgState *fw_cfg, + int kernel_size; + fclose(f); + +- if (((struct elf64_hdr*)header)->e_machine == EM_X86_64) { +- fprintf(stderr, "Cannot load x86-64 image, give a 32bit one.\n"); +- exit(1); +- } +- + kernel_size = load_elf(kernel_filename, NULL, NULL, &elf_entry, + &elf_low, &elf_high, 0, I386_ELF_MACHINE, + 0, 0); diff --git a/audio-improvements.patch b/audio-improvements.patch new file mode 100644 index 000000000000..47ae53e8d7b1 --- /dev/null +++ b/audio-improvements.patch @@ -0,0 +1,1125 @@ +diff --git audio/audio.c audio/audio.c +index beafed209b..6f42a019b0 100644 +--- audio/audio.c ++++ audio/audio.c +@@ -2066,3 +2066,8 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) + } + } + } ++ ++int64_t audio_get_timer_ticks(void) ++{ ++ return conf.period.ticks; ++} +diff --git audio/audio_int.h audio/audio_int.h +index 5bcb1c60e1..2f7fc4f8ac 100644 +--- audio/audio_int.h ++++ audio/audio_int.h +@@ -214,6 +214,8 @@ extern struct audio_driver pa_audio_driver; + extern struct audio_driver spice_audio_driver; + extern const struct mixeng_volume nominal_volume; + ++int64_t audio_get_timer_ticks(void); ++ + void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); + void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); + +diff --git audio/paaudio.c audio/paaudio.c +index 65beb6f010..b46beeea92 100644 +--- audio/paaudio.c ++++ audio/paaudio.c +@@ -1,16 +1,22 @@ + /* public domain */ + #include "qemu/osdep.h" +-#include "qemu-common.h" ++#include "qemu/timer.h" + #include "audio.h" + + #include <pulse/pulseaudio.h> + + #define AUDIO_CAP "pulseaudio" ++#define DEBUG + #include "audio_int.h" +-#include "audio_pt_int.h" + + typedef struct { +- int samples; ++ int buffer_size_out; ++ int buffer_size_in; ++ int tlength; ++ int fragsize; ++ int maxlength_in; ++ int adjust_latency_out; ++ int adjust_latency_in; + char *server; + char *sink; + char *source; +@@ -24,28 +30,18 @@ typedef struct { + + typedef struct { + HWVoiceOut hw; +- int done; +- int live; +- int decr; +- int rpos; + pa_stream *stream; +- void *pcm_buf; +- struct audio_pt pt; + paaudio *g; ++ pa_sample_spec ss; ++ pa_buffer_attr ba; + } PAVoiceOut; + + typedef struct { + HWVoiceIn hw; +- int done; +- int dead; +- int incr; +- int wpos; + pa_stream *stream; +- void *pcm_buf; +- struct audio_pt pt; +- const void *read_data; +- size_t read_index, read_length; + paaudio *g; ++ pa_sample_spec ss; ++ pa_buffer_attr ba; + } PAVoiceIn; + + static void qpa_audio_fini(void *opaque); +@@ -109,182 +105,59 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) + } \ + } while (0); + +-static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) +-{ +- paaudio *g = p->g; +- +- pa_threaded_mainloop_lock (g->mainloop); +- +- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); +- +- while (length > 0) { +- size_t l; +- +- while (!p->read_data) { +- int r; +- +- r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); +- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); +- +- if (!p->read_data) { +- pa_threaded_mainloop_wait (g->mainloop); +- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); +- } else { +- p->read_index = 0; +- } +- } +- +- l = p->read_length < length ? p->read_length : length; +- memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); +- +- data = (uint8_t *) data + l; +- length -= l; +- +- p->read_index += l; +- p->read_length -= l; +- +- if (!p->read_length) { +- int r; +- +- r = pa_stream_drop (p->stream); +- p->read_data = NULL; +- p->read_length = 0; +- p->read_index = 0; +- +- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); +- } +- } +- +- pa_threaded_mainloop_unlock (g->mainloop); +- return 0; +- +-unlock_and_fail: +- pa_threaded_mainloop_unlock (g->mainloop); +- return -1; +-} +- +-static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) ++static int qpa_run_out(HWVoiceOut *hw, int live) + { +- paaudio *g = p->g; +- +- pa_threaded_mainloop_lock (g->mainloop); +- +- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); +- +- while (length > 0) { +- size_t l; +- int r; +- +- while (!(l = pa_stream_writable_size (p->stream))) { +- pa_threaded_mainloop_wait (g->mainloop); +- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); +- } +- +- CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); +- +- if (l > length) { +- l = length; +- } +- +- r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); +- CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); +- +- data = (const uint8_t *) data + l; +- length -= l; +- } +- +- pa_threaded_mainloop_unlock (g->mainloop); +- return 0; +- +-unlock_and_fail: +- pa_threaded_mainloop_unlock (g->mainloop); +- return -1; +-} +- +-static void *qpa_thread_out (void *arg) +-{ +- PAVoiceOut *pa = arg; +- HWVoiceOut *hw = &pa->hw; +- +- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +- return NULL; +- } ++ PAVoiceOut *pa = (PAVoiceOut *) hw; ++ int rpos, decr, samples; ++ size_t avail_bytes, max_bytes; ++ struct st_sample *src; ++ void *pa_dst; ++ int error = 0; ++ int *rerror = &error; ++ int r; + +- for (;;) { +- int decr, to_mix, rpos; ++ decr = 0; ++ rpos = hw->rpos; + +- for (;;) { +- if (pa->done) { +- goto exit; +- } ++ pa_threaded_mainloop_lock(pa->g->mainloop); ++ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); + +- if (pa->live > 0) { +- break; +- } ++ avail_bytes = (size_t) live << hw->info.shift; + +- if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { +- goto exit; +- } +- } ++ max_bytes = pa_stream_writable_size(pa->stream); ++ CHECK_SUCCESS_GOTO(pa->g, rerror, max_bytes != -1, fail); + +- decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); +- rpos = pa->rpos; ++ samples = (int)(audio_MIN(avail_bytes, max_bytes)) >> hw->info.shift; ++ while (samples) { ++ int convert_samples = audio_MIN(samples, hw->samples - rpos); ++ size_t b_wanted = (size_t) convert_samples << hw->info.shift; ++ size_t b_effective = b_wanted; + +- if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { +- return NULL; +- } ++ r = pa_stream_begin_write(pa->stream, &pa_dst, &b_effective); ++ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); ++ CHECK_SUCCESS_GOTO(pa->g, (int *)0, b_effective == b_wanted, fail); + +- while (to_mix) { +- int error; +- int chunk = audio_MIN (to_mix, hw->samples - rpos); +- struct st_sample *src = hw->mix_buf + rpos; ++ src = hw->mix_buf + rpos; ++ hw->clip(pa_dst, src, convert_samples); + +- hw->clip (pa->pcm_buf, src, chunk); +- +- if (qpa_simple_write (pa, pa->pcm_buf, +- chunk << hw->info.shift, &error) < 0) { +- qpa_logerr (error, "pa_simple_write failed\n"); +- return NULL; +- } ++ r = pa_stream_write(pa->stream, pa_dst, b_effective, ++ NULL, 0LL, PA_SEEK_RELATIVE); ++ CHECK_SUCCESS_GOTO(pa->g, rerror, r >= 0, fail); + +- rpos = (rpos + chunk) % hw->samples; +- to_mix -= chunk; +- } +- +- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +- return NULL; +- } +- +- pa->rpos = rpos; +- pa->live -= decr; +- pa->decr += decr; ++ rpos = (rpos + convert_samples) % hw->samples; ++ samples -= convert_samples; ++ decr += convert_samples; + } + +- exit: +- audio_pt_unlock (&pa->pt, AUDIO_FUNC); +- return NULL; +-} +- +-static int qpa_run_out (HWVoiceOut *hw, int live) +-{ +- int decr; +- PAVoiceOut *pa = (PAVoiceOut *) hw; +- +- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +- return 0; +- } ++ bail: ++ pa_threaded_mainloop_unlock(pa->g->mainloop); + +- decr = audio_MIN (live, pa->decr); +- pa->decr -= decr; +- pa->live = live - decr; +- hw->rpos = pa->rpos; +- if (pa->live > 0) { +- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +- } +- else { +- audio_pt_unlock (&pa->pt, AUDIO_FUNC); +- } ++ hw->rpos = rpos; + return decr; ++ ++fail: ++ qpa_logerr(error, "qpa_run_out failed\n"); ++ goto bail; + } + + static int qpa_write (SWVoiceOut *sw, void *buf, int len) +@@ -292,92 +165,68 @@ static int qpa_write (SWVoiceOut *sw, void *buf, int len) + return audio_pcm_sw_write (sw, buf, len); + } + +-/* capture */ +-static void *qpa_thread_in (void *arg) ++static int qpa_run_in(HWVoiceIn *hw) + { +- PAVoiceIn *pa = arg; +- HWVoiceIn *hw = &pa->hw; ++ PAVoiceIn *pa = (PAVoiceIn *) hw; ++ int wpos, incr; ++ char *pa_src; ++ int error = 0; ++ int *rerror = &error; ++ int r; ++ size_t pa_avail; ++ incr = 0; ++ wpos = hw->wpos; + +- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +- return NULL; +- } ++ pa_threaded_mainloop_lock(pa->g->mainloop); ++ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); + +- for (;;) { +- int incr, to_grab, wpos; ++ size_t bytes_wanted = ((unsigned int) ++ (hw->samples - audio_pcm_hw_get_live_in(hw)) << hw->info.shift); + +- for (;;) { +- if (pa->done) { +- goto exit; +- } ++ if (bytes_wanted == 0) { ++ /* no room */ ++ goto bail; ++ } + +- if (pa->dead > 0) { +- break; +- } ++ size_t bytes_avail = pa_stream_readable_size(pa->stream); + +- if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { +- goto exit; +- } +- } ++ if (bytes_wanted > bytes_avail) { ++ bytes_wanted = bytes_avail; ++ } + +- incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); +- wpos = pa->wpos; ++ while (bytes_wanted) { ++ r = pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_avail); ++ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); + +- if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { +- return NULL; ++ if (pa_avail == 0 || pa_avail > bytes_wanted) { ++ break; + } + +- while (to_grab) { +- int error; +- int chunk = audio_MIN (to_grab, hw->samples - wpos); +- void *buf = advance (pa->pcm_buf, wpos); ++ bytes_wanted -= pa_avail; + +- if (qpa_simple_read (pa, buf, +- chunk << hw->info.shift, &error) < 0) { +- qpa_logerr (error, "pa_simple_read failed\n"); +- return NULL; +- } +- +- hw->conv (hw->conv_buf + wpos, buf, chunk); ++ while (pa_avail) { ++ int chunk = audio_MIN( ++ (int)(pa_avail >> hw->info.shift), hw->samples - wpos); ++ hw->conv(hw->conv_buf + wpos, pa_src, chunk); + wpos = (wpos + chunk) % hw->samples; +- to_grab -= chunk; +- } +- +- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +- return NULL; ++ pa_src += chunk << hw->info.shift; ++ pa_avail -= chunk << hw->info.shift; ++ incr += chunk; + } + +- pa->wpos = wpos; +- pa->dead -= incr; +- pa->incr += incr; ++ r = pa_stream_drop(pa->stream); ++ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); + } + +- exit: +- audio_pt_unlock (&pa->pt, AUDIO_FUNC); +- return NULL; +-} +- +-static int qpa_run_in (HWVoiceIn *hw) +-{ +- int live, incr, dead; +- PAVoiceIn *pa = (PAVoiceIn *) hw; +- +- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { +- return 0; +- } ++bail: ++ pa_threaded_mainloop_unlock(pa->g->mainloop); + +- live = audio_pcm_hw_get_live_in (hw); +- dead = hw->samples - live; +- incr = audio_MIN (dead, pa->incr); +- pa->incr -= incr; +- pa->dead = dead - incr; +- hw->wpos = pa->wpos; +- if (pa->dead > 0) { +- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +- } +- else { +- audio_pt_unlock (&pa->pt, AUDIO_FUNC); +- } ++ hw->wpos = wpos; + return incr; ++ ++fail: ++ qpa_logerr(error, "qpa_run_in failed\n"); ++ goto bail; + } + + static int qpa_read (SWVoiceIn *sw, void *buf, int len) +@@ -470,13 +319,6 @@ static void stream_state_cb (pa_stream *s, void * userdata) + } + } + +-static void stream_request_cb (pa_stream *s, size_t length, void *userdata) +-{ +- paaudio *g = userdata; +- +- pa_threaded_mainloop_signal (g->mainloop, 0); +-} +- + static pa_stream *qpa_simple_new ( + paaudio *g, + const char *name, +@@ -498,23 +340,17 @@ static pa_stream *qpa_simple_new ( + } + + pa_stream_set_state_callback (stream, stream_state_cb, g); +- pa_stream_set_read_callback (stream, stream_request_cb, g); +- pa_stream_set_write_callback (stream, stream_request_cb, g); + + if (dir == PA_STREAM_PLAYBACK) { +- r = pa_stream_connect_playback (stream, dev, attr, +- PA_STREAM_INTERPOLATE_TIMING +-#ifdef PA_STREAM_ADJUST_LATENCY +- |PA_STREAM_ADJUST_LATENCY +-#endif +- |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); ++ r = pa_stream_connect_playback(stream, dev, attr, ++ PA_STREAM_INTERPOLATE_TIMING ++ | (g->conf.adjust_latency_out ? PA_STREAM_ADJUST_LATENCY : 0) ++ | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + } else { +- r = pa_stream_connect_record (stream, dev, attr, +- PA_STREAM_INTERPOLATE_TIMING +-#ifdef PA_STREAM_ADJUST_LATENCY +- |PA_STREAM_ADJUST_LATENCY +-#endif +- |PA_STREAM_AUTO_TIMING_UPDATE); ++ r = pa_stream_connect_record(stream, dev, attr, ++ PA_STREAM_INTERPOLATE_TIMING ++ | (g->conf.adjust_latency_in ? PA_STREAM_ADJUST_LATENCY : 0) ++ | PA_STREAM_AUTO_TIMING_UPDATE); + } + + if (r < 0) { +@@ -541,165 +377,167 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) + { + int error; +- pa_sample_spec ss; +- pa_buffer_attr ba; + struct audsettings obt_as = *as; + PAVoiceOut *pa = (PAVoiceOut *) hw; + paaudio *g = pa->g = drv_opaque; + +- ss.format = audfmt_to_pa (as->fmt, as->endianness); +- ss.channels = as->nchannels; +- ss.rate = as->freq; +- +- /* +- * qemu audio tick runs at 100 Hz (by default), so processing +- * data chunks worth 10 ms of sound should be a good fit. +- */ +- ba.tlength = pa_usec_to_bytes (10 * 1000, &ss); +- ba.minreq = pa_usec_to_bytes (5 * 1000, &ss); +- ba.maxlength = -1; +- ba.prebuf = -1; +- +- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); +- +- pa->stream = qpa_simple_new ( +- g, +- "qemu", +- PA_STREAM_PLAYBACK, +- g->conf.sink, +- &ss, +- NULL, /* channel map */ +- &ba, /* buffering attributes */ +- &error +- ); ++ int64_t timer_tick_duration = ++ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); ++ int64_t frames_per_tick_x1000 = ++ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); ++ ++ int64_t tlength = g->conf.tlength; ++ if (tlength == 0) { ++ tlength = (frames_per_tick_x1000) / 400; ++ } ++ int64_t buflen = g->conf.buffer_size_out; ++ if (buflen == 0) { ++ buflen = frames_per_tick_x1000 / 400; ++ } ++ ++ ldebug("tick duration: %.2f ms (%.3f frames)\n", ++ ((float)timer_tick_duration) / SCALE_MS, ++ (float)frames_per_tick_x1000 / 1000.0f); ++ ++ ldebug("OUT internal buffer: %.2f ms (%"PRId64" frames)\n", ++ buflen * (1000.0f / as->freq), ++ buflen); ++ ++ ldebug("OUT tlength: %.2f ms (%"PRId64" frames)\n", ++ tlength * (1000.0f / as->freq), ++ tlength); ++ ++ ldebug("OUT adjust latency: %s\n", ++ g->conf.adjust_latency_out ? "yes" : "no"); ++ ++ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); ++ pa->ss.channels = as->nchannels; ++ pa->ss.rate = as->freq; ++ ++ pa->ba.tlength = tlength * pa_frame_size(&pa->ss); ++ pa->ba.maxlength = -1; ++ pa->ba.minreq = -1; ++ pa->ba.prebuf = -1; ++ ++ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); ++ ++ pa->stream = qpa_simple_new( ++ g, ++ "qemu", ++ PA_STREAM_PLAYBACK, ++ g->conf.sink, ++ &pa->ss, ++ NULL, /* channel map */ ++ &pa->ba, /* buffering attributes */ ++ &error ++ ); + if (!pa->stream) { + qpa_logerr (error, "pa_simple_new for playback failed\n"); + goto fail1; + } + +- audio_pcm_init_info (&hw->info, &obt_as); +- hw->samples = g->conf.samples; +- pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +- pa->rpos = hw->rpos; +- if (!pa->pcm_buf) { +- dolog ("Could not allocate buffer (%d bytes)\n", +- hw->samples << hw->info.shift); +- goto fail2; +- } +- +- if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) { +- goto fail3; +- } ++ audio_pcm_init_info(&hw->info, &obt_as); ++ hw->samples = buflen; + + return 0; + +- fail3: +- g_free (pa->pcm_buf); +- pa->pcm_buf = NULL; +- fail2: +- if (pa->stream) { +- pa_stream_unref (pa->stream); +- pa->stream = NULL; +- } +- fail1: ++fail1: + return -1; + } + + static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) + { + int error; +- pa_sample_spec ss; + struct audsettings obt_as = *as; + PAVoiceIn *pa = (PAVoiceIn *) hw; + paaudio *g = pa->g = drv_opaque; + +- ss.format = audfmt_to_pa (as->fmt, as->endianness); +- ss.channels = as->nchannels; +- ss.rate = as->freq; +- +- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); +- +- pa->stream = qpa_simple_new ( +- g, +- "qemu", +- PA_STREAM_RECORD, +- g->conf.source, +- &ss, +- NULL, /* channel map */ +- NULL, /* buffering attributes */ +- &error +- ); ++ int64_t timer_tick_duration = ++ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); ++ int64_t frames_per_tick_x1000 = ++ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); ++ ++ int64_t fragsize = g->conf.fragsize; ++ if (fragsize == 0) { ++ fragsize = frames_per_tick_x1000 / 1000; ++ } ++ int64_t buflen = g->conf.buffer_size_in; ++ if (buflen == 0) { ++ buflen = frames_per_tick_x1000 / 400; ++ } ++ int64_t maxlength = g->conf.maxlength_in; ++ if (maxlength == 0) { ++ maxlength = fragsize * 2; ++ } ++ ++ ldebug("IN internal buffer: %.2f ms (%"PRId64" frames)\n", ++ buflen * (1000.0f / as->freq), ++ buflen); ++ ++ ldebug("IN fragsize: %.2f ms (%"PRId64" frames)\n", ++ fragsize * (1000.0f / as->freq), ++ fragsize); ++ ++ ldebug("IN maxlength: %.2f ms (%"PRId64" frames)\n", ++ maxlength * (1000.0f / as->freq), ++ maxlength); ++ ++ ldebug("IN adjust latency: %s\n", ++ g->conf.adjust_latency_in ? "yes" : "no"); ++ ++ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); ++ pa->ss.channels = as->nchannels; ++ pa->ss.rate = as->freq; ++ ++ pa->ba.fragsize = fragsize * pa_frame_size(&pa->ss); ++ pa->ba.maxlength = maxlength * pa_frame_size(&pa->ss); ++ pa->ba.minreq = -1; ++ pa->ba.prebuf = -1; ++ ++ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); ++ ++ pa->stream = qpa_simple_new( ++ g, ++ "qemu", ++ PA_STREAM_RECORD, ++ g->conf.source, ++ &pa->ss, ++ NULL, /* channel map */ ++ &pa->ba, /* buffering attributes */ ++ &error ++ ); + if (!pa->stream) { + qpa_logerr (error, "pa_simple_new for capture failed\n"); + goto fail1; + } + +- audio_pcm_init_info (&hw->info, &obt_as); +- hw->samples = g->conf.samples; +- pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); +- pa->wpos = hw->wpos; +- if (!pa->pcm_buf) { +- dolog ("Could not allocate buffer (%d bytes)\n", +- hw->samples << hw->info.shift); +- goto fail2; +- } +- +- if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) { +- goto fail3; +- } ++ audio_pcm_init_info(&hw->info, &obt_as); ++ hw->samples = buflen; + + return 0; + +- fail3: +- g_free (pa->pcm_buf); +- pa->pcm_buf = NULL; +- fail2: +- if (pa->stream) { +- pa_stream_unref (pa->stream); +- pa->stream = NULL; +- } +- fail1: ++ fail1: + return -1; + } + + static void qpa_fini_out (HWVoiceOut *hw) + { +- void *ret; + PAVoiceOut *pa = (PAVoiceOut *) hw; + +- audio_pt_lock (&pa->pt, AUDIO_FUNC); +- pa->done = 1; +- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +- audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); +- + if (pa->stream) { + pa_stream_unref (pa->stream); + pa->stream = NULL; + } +- +- audio_pt_fini (&pa->pt, AUDIO_FUNC); +- g_free (pa->pcm_buf); +- pa->pcm_buf = NULL; + } + + static void qpa_fini_in (HWVoiceIn *hw) + { +- void *ret; + PAVoiceIn *pa = (PAVoiceIn *) hw; + +- audio_pt_lock (&pa->pt, AUDIO_FUNC); +- pa->done = 1; +- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); +- audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); +- + if (pa->stream) { + pa_stream_unref (pa->stream); + pa->stream = NULL; + } +- +- audio_pt_fini (&pa->pt, AUDIO_FUNC); +- g_free (pa->pcm_buf); +- pa->pcm_buf = NULL; + } + + static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) +@@ -809,7 +647,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) + + /* common */ + static PAConf glob_conf = { +- .samples = 4096, ++ .adjust_latency_out = 0, ++ .adjust_latency_in = 1, + }; + + static void *qpa_audio_init (void) +@@ -897,10 +736,46 @@ static void qpa_audio_fini (void *opaque) + + struct audio_option qpa_options[] = { + { +- .name = "SAMPLES", ++ .name = "BUFFER_SIZE_OUT", ++ .tag = AUD_OPT_INT, ++ .valp = &glob_conf.buffer_size_out, ++ .descr = "internal buffer size in frames for playback device" ++ }, ++ { ++ .name = "BUFFER_SIZE_IN", ++ .tag = AUD_OPT_INT, ++ .valp = &glob_conf.buffer_size_in, ++ .descr = "internal buffer size in frames for recording device" ++ }, ++ { ++ .name = "TLENGTH", + .tag = AUD_OPT_INT, +- .valp = &glob_conf.samples, +- .descr = "buffer size in samples" ++ .valp = &glob_conf.tlength, ++ .descr = "playback buffer target length in frames" ++ }, ++ { ++ .name = "FRAGSIZE", ++ .tag = AUD_OPT_INT, ++ .valp = &glob_conf.fragsize, ++ .descr = "fragment length of recording device in frames" ++ }, ++ { ++ .name = "MAXLENGTH_IN", ++ .tag = AUD_OPT_INT, ++ .valp = &glob_conf.maxlength_in, ++ .descr = "maximum length of PA recording buffer in frames" ++ }, ++ { ++ .name = "ADJUST_LATENCY_OUT", ++ .tag = AUD_OPT_BOOL, ++ .valp = &glob_conf.adjust_latency_out, ++ .descr = "instruct PA to adjust latency for playback device" ++ }, ++ { ++ .name = "ADJUST_LATENCY_IN", ++ .tag = AUD_OPT_BOOL, ++ .valp = &glob_conf.adjust_latency_in, ++ .descr = "instruct PA to adjust latency for recording device" + }, + { + .name = "SERVER", +diff --git hw/audio/hda-codec.c hw/audio/hda-codec.c +index 5402cd196c..ab89158bfc 100644 +--- hw/audio/hda-codec.c ++++ hw/audio/hda-codec.c +@@ -18,6 +18,7 @@ + */ + + #include "qemu/osdep.h" ++#include "qemu/atomic.h" + #include "hw/hw.h" + #include "hw/pci/pci.h" + #include "intel-hda.h" +@@ -126,6 +127,11 @@ static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) + #define PARAM nomixemu + #include "hda-codec-common.h" + ++#define HDA_TIMER_TICKS (SCALE_MS) ++#define MAX_CORR (SCALE_US * 100) ++#define B_SIZE sizeof(st->buf) ++#define B_MASK (sizeof(st->buf) - 1) ++ + /* -------------------------------------------------------------------------- */ + + static const char *fmt2name[] = { +@@ -154,8 +160,13 @@ struct HDAAudioStream { + 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" +@@ -176,53 +187,146 @@ struct HDAAudioState { + bool mixer; + }; + ++static inline int64_t hda_bytes_per_second(HDAAudioStream *st) ++{ ++ return 2 * st->as.nchannels * st->as.freq; ++} ++ ++static inline void hda_timer_sync_adjust(HDAAudioStream *st, int64_t target_pos) ++{ ++ int64_t corr = ++ NANOSECONDS_PER_SECOND * target_pos / hda_bytes_per_second(st); ++ if (corr > MAX_CORR) { ++ corr = MAX_CORR; ++ } else if (corr < -MAX_CORR) { ++ corr = -MAX_CORR; ++ } ++ atomic_fetch_add(&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 = atomic_fetch_add(&st->buft_start, 0); ++ int64_t wpos = atomic_fetch_add(&st->wpos, 0); ++ int64_t rpos = atomic_fetch_add(&st->rpos, 0); ++ ++ 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; ++ atomic_fetch_add(&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; +- 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; +- recv += len; +- if (st->bpos != sizeof(st->buf)) { +- break; +- } ++ ++ int64_t wpos = atomic_fetch_add(&st->wpos, 0); ++ int64_t rpos = atomic_fetch_add(&st->rpos, 0); ++ ++ 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; ++ atomic_fetch_add(&st->wpos, read); ++ if (chunk != read) { ++ break; + } +- rc = hda_codec_xfer(&st->state->hda, st->stream, false, +- st->buf, sizeof(st->buf)); ++ } ++} ++ ++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 = atomic_fetch_add(&st->buft_start, 0); ++ int64_t wpos = atomic_fetch_add(&st->wpos, 0); ++ int64_t rpos = atomic_fetch_add(&st->rpos, 0); ++ ++ 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; + } +- st->bpos = 0; ++ wpos += chunk; ++ to_transfer -= chunk; ++ atomic_fetch_add(&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; +- int sent = 0; +- int len; +- bool rc; +- +- while (avail - sent >= sizeof(st->buf)) { +- if (st->bpos == sizeof(st->buf)) { +- rc = hda_codec_xfer(&st->state->hda, st->stream, true, +- st->buf, sizeof(st->buf)); +- if (!rc) { +- break; +- } +- st->bpos = 0; +- } +- len = AUD_write(st->voice.out, st->buf + st->bpos, +- sizeof(st->buf) - st->bpos); +- st->bpos += len; +- sent += len; +- if (st->bpos != sizeof(st->buf)) { ++ ++ int64_t wpos = atomic_fetch_add(&st->wpos, 0); ++ int64_t rpos = atomic_fetch_add(&st->rpos, 0); ++ ++ int64_t to_transfer = audio_MIN(wpos - rpos, avail); ++ ++ 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; ++ atomic_fetch_add(&st->rpos, written); ++ if (chunk != written) { + break; + } + } +@@ -239,6 +343,15 @@ static void hda_audio_set_running(HDAAudioStream *st, bool running) + st->running = running; + dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name, + st->running ? "on" : "off", st->stream); ++ 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 { +@@ -286,10 +399,12 @@ static void hda_audio_setup(HDAAudioStream *st) + st->voice.out = AUD_open_out(&st->state->card, st->voice.out, + st->node->name, st, + hda_audio_output_cb, &st->as); ++ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_output_timer, st); + } else { + st->voice.in = AUD_open_in(&st->state->card, st->voice.in, + st->node->name, st, + hda_audio_input_cb, &st->as); ++ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_input_timer, st); + } + } + +@@ -505,7 +620,6 @@ static int hda_audio_init(HDACodecDevice *hda, const struct desc_codec *desc) + /* unmute output by default */ + st->gain_left = QEMU_HDA_AMP_STEPS; + st->gain_right = QEMU_HDA_AMP_STEPS; +- st->bpos = sizeof(st->buf); + st->output = true; + } else { + st->output = false; +@@ -532,6 +646,7 @@ static void hda_audio_exit(HDACodecDevice *hda) + if (st->node == NULL) { + continue; + } ++ timer_del(st->buft); + if (st->output) { + AUD_close_out(&a->card, st->voice.out); + } else { +@@ -592,8 +707,8 @@ static const VMStateDescription vmstate_hda_audio_stream = { + 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() + } + }; +diff --git hw/audio/intel-hda.c hw/audio/intel-hda.c +index 18a50a8f83..721eba792d 100644 +--- hw/audio/intel-hda.c ++++ hw/audio/intel-hda.c +@@ -407,13 +407,6 @@ static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, + if (st->bpl == NULL) { + return false; + } +- if (st->ctl & (1 << 26)) { +- /* +- * Wait with the next DMA xfer until the guest +- * has acked the buffer completion interrupt +- */ +- return false; +- } + + left = len; + s = st->bentries; diff --git a/cpu-pinning.patch b/cpu-pinning.patch new file mode 100644 index 000000000000..46efee5d88a5 --- /dev/null +++ b/cpu-pinning.patch @@ -0,0 +1,186 @@ +From e392e5516e6ae66db0f05775a22c0abf39f033f0 Mon Sep 17 00:00:00 2001 +From: Saverio Miroddi <saverio.pub2@gmail.com> +Date: Tue, 31 Oct 2017 20:59:05 +0100 +Subject: [PATCH] Current pinning patch + +Changes 2017/10/31: + +- Fix: the MAX_VCPUS was arbitrary; it's now set to CPU_SETSIZE +- Fix: the allowed vcpus were equated to the cores number, without accounting sockets and threads +- Change: removed all the debug information, and a now unneded warning +- Change: cleaned spacing +--- + cpus.c | 12 +++++++++++ + qemu-options.hx | 10 ++++++++++ + vl.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 84 insertions(+) + +diff --git a/cpus.c b/cpus.c +index 9bed61eefcc..7437e3a00c7 100644 +--- a/cpus.c ++++ b/cpus.c +@@ -55,6 +55,9 @@ + #ifdef CONFIG_LINUX + + #include <sys/prctl.h> ++#include <unistd.h> ++#include <stdint.h> ++#include <inttypes.h> + + #ifndef PR_MCE_KILL + #define PR_MCE_KILL 33 +@@ -1722,9 +1725,11 @@ static void qemu_hax_start_vcpu(CPUState *cpu) + } + } + ++extern int vcpu_affinity[]; + static void qemu_kvm_start_vcpu(CPUState *cpu) + { + char thread_name[VCPU_THREAD_NAME_SIZE]; ++ cpu_set_t cpuset; + + cpu->thread = g_malloc0(sizeof(QemuThread)); + cpu->halt_cond = g_malloc0(sizeof(QemuCond)); +@@ -1733,6 +1738,13 @@ static void qemu_kvm_start_vcpu(CPUState *cpu) + cpu->cpu_index); + qemu_thread_create(cpu->thread, thread_name, qemu_kvm_cpu_thread_fn, + cpu, QEMU_THREAD_JOINABLE); ++ ++ if (vcpu_affinity[cpu->cpu_index] != -1) { ++ CPU_ZERO(&cpuset); ++ CPU_SET(vcpu_affinity[cpu->cpu_index], &cpuset); ++ pthread_setaffinity_np((cpu->thread)->thread, sizeof(cpu_set_t), &cpuset); ++ } ++ + while (!cpu->created) { + qemu_cond_wait(&qemu_cpu_cond, &qemu_global_mutex); + } +diff --git a/qemu-options.hx b/qemu-options.hx +index 9f6e2adfffb..1d38fc86c81 100644 +--- a/qemu-options.hx ++++ b/qemu-options.hx +@@ -160,6 +160,16 @@ given, the total number of CPUs @var{n} can be omitted. @var{maxcpus} + specifies the maximum number of hotpluggable CPUs. + ETEXI + ++DEF("vcpu", HAS_ARG, QEMU_OPTION_vcpu, ++ "-vcpu [vcpunum=]n[,affinity=affinity]\n" ++ "-vcpu [vcpunum=]n[,affinity=affinity]\n", QEMU_ARCH_ALL) ++STEXI ++@item -vcpu [vcpunum=]@var{n}[,affinity=@var{affinity}] ++@itemx -vcpu [vcpunum=]@var{n}[,affinity=@var{affinity}] ++@findex -vcpu ++VCPU Affinity. If specified, specify for all the CPUs. ++ETEXI ++ + DEF("numa", HAS_ARG, QEMU_OPTION_numa, + "-numa node[,mem=size][,cpus=firstcpu[-lastcpu]][,nodeid=node]\n" + "-numa node[,memdev=id][,cpus=firstcpu[-lastcpu]][,nodeid=node]\n" +diff --git a/vl.c b/vl.c +index d63269332fe..754a03c9a5f 100644 +--- a/vl.c ++++ b/vl.c +@@ -135,6 +135,7 @@ int main(int argc, char **argv) + #define MAX_VIRTIO_CONSOLES 1 + #define MAX_SCLP_CONSOLES 1 + ++#define MAX_VCPUS CPU_SETSIZE + static const char *data_dir[16]; + static int data_dir_idx; + const char *bios_name = NULL; +@@ -167,6 +168,8 @@ int smp_cpus = 1; + int max_cpus = 1; + int smp_cores = 1; + int smp_threads = 1; ++int vcpu_affinity[MAX_VCPUS]; ++int num_affinity = 0; + int acpi_enabled = 1; + int no_hpet = 0; + int fd_bootchk = 1; +@@ -1212,6 +1215,57 @@ static QemuOptsList qemu_smp_opts = { + }, + }; + ++static QemuOptsList qemu_vcpu_opts = { ++ .name = "vcpu-opts", ++ .implied_opt_name = "vcpunum", ++ .head = QTAILQ_HEAD_INITIALIZER(qemu_vcpu_opts.head), ++ .desc = { ++ { ++ .name = "vcpunum", ++ .type = QEMU_OPT_NUMBER, ++ }, { ++ .name = "affinity", ++ .type = QEMU_OPT_NUMBER, ++ }, ++ { /*End of list */ } ++ }, ++}; ++ ++static int parse_vcpu(void *opaque, QemuOpts *opts, Error **errp) ++{ ++ if (opts) { ++ unsigned vcpu = qemu_opt_get_number(opts, "vcpunum", 0); ++ unsigned affinity = qemu_opt_get_number(opts,"affinity", 0); ++ ++ if (vcpu < smp_cpus * smp_cores * smp_threads) { ++ if (vcpu_affinity[vcpu] == -1) { ++ vcpu_affinity[vcpu] = affinity; ++ } ++ else { ++ error_report("Duplicate affinity statement for vcpu %d\n", vcpu); ++ return -1; ++ } ++ num_affinity += 1; ++ } ++ else { ++ error_report("VCPU %d is more than allowed %d VCPUs in the system\n", vcpu, smp_cores); ++ return -1; ++ } ++ } ++ return 0; ++} ++ ++static void parse_vcpu_opts(MachineClass *mc) ++{ ++ int i; ++ for (i = 0; i < MAX_VCPUS; i++) ++ vcpu_affinity[i] = -1; ++ ++ if (qemu_opts_foreach(qemu_find_opts("vcpu-opts"), parse_vcpu, NULL, NULL)) { ++ exit(1); ++ } ++} ++ + static void smp_parse(QemuOpts *opts) + { + if (opts) { +@@ -3067,6 +3121,7 @@ int main(int argc, char **argv, char **envp) + qemu_add_opts(&qemu_accel_opts); + qemu_add_opts(&qemu_mem_opts); + qemu_add_opts(&qemu_smp_opts); ++ qemu_add_opts(&qemu_vcpu_opts); + qemu_add_opts(&qemu_boot_opts); + qemu_add_opts(&qemu_sandbox_opts); + qemu_add_opts(&qemu_add_fd_opts); +@@ -3818,6 +3873,12 @@ int main(int argc, char **argv, char **envp) + exit(1); + } + break; ++ case QEMU_OPTION_vcpu: ++ if (!qemu_opts_parse_noisily(qemu_find_opts("vcpu-opts"), ++ optarg, true)) { ++ exit(1); ++ } ++ break; + case QEMU_OPTION_vnc: + vnc_parse(optarg, &error_fatal); + break; +@@ -4243,6 +4304,7 @@ int main(int argc, char **argv, char **envp) + exit(1); + } + ++ parse_vcpu_opts(machine_class); + /* + * Get the default machine options from the machine if it is not already + * specified either by the configuration file or by the command line. diff --git a/qemu-ga.service b/qemu-ga.service new file mode 100644 index 000000000000..abbb6ab9dfe2 --- /dev/null +++ b/qemu-ga.service @@ -0,0 +1,9 @@ +[Unit] +Description=QEMU Guest Agent +ConditionPathExists=/dev/virtio-ports/org.qemu.guest_agent.0 + +[Service] +ExecStart=/usr/bin/qemu-ga + +[Install] +WantedBy=multi-user.target diff --git a/qemu.install b/qemu.install new file mode 100644 index 000000000000..9fe850d4ed7e --- /dev/null +++ b/qemu.install @@ -0,0 +1,8 @@ +# Arg 1: the new package version +post_install() { + # trigger events on modules files when already loaded + for _f in /sys/devices/virtual/misc/{kvm,vhost-net}; do + [[ -e "$_f" ]] && udevadm trigger "$_f" + done + : +} diff --git a/qemu.sysusers b/qemu.sysusers new file mode 100644 index 000000000000..4c03e8242d92 --- /dev/null +++ b/qemu.sysusers @@ -0,0 +1 @@ +g kvm 78 - |