diff options
118 files changed, 4787 insertions, 1909 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index d326756079..564b8dba6e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -117,6 +117,8 @@ F: cpus.c F: exec.c F: accel/tcg/ F: accel/stubs/tcg-stub.c +F: scripts/decodetree.py +F: docs/devel/decodetree.rst F: include/exec/cpu*.h F: include/exec/exec-all.h F: include/exec/helper*.h diff --git a/Makefile.objs b/Makefile.objs index 31a84b7d41..72debbf5c5 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -182,6 +182,7 @@ trace-events-subdirs += qapi trace-events-subdirs += qom trace-events-subdirs += scsi trace-events-subdirs += target/arm +trace-events-subdirs += target/hppa trace-events-subdirs += target/i386 trace-events-subdirs += target/mips trace-events-subdirs += target/ppc diff --git a/audio/Makefile.objs b/audio/Makefile.objs index db4fa7f18f..dca87f6347 100644 --- a/audio/Makefile.objs +++ b/audio/Makefile.objs @@ -1,4 +1,4 @@ -common-obj-y = audio.o noaudio.o wavaudio.o mixeng.o +common-obj-y = audio.o audio_legacy.o noaudio.o wavaudio.o mixeng.o common-obj-$(CONFIG_SPICE) += spiceaudio.o common-obj-$(CONFIG_AUDIO_COREAUDIO) += coreaudio.o common-obj-$(CONFIG_AUDIO_DSOUND) += dsoundaudio.o diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c index 635be73bf4..49e6884309 100644 --- a/audio/alsaaudio.c +++ b/audio/alsaaudio.c @@ -33,28 +33,9 @@ #define AUDIO_CAP "alsa" #include "audio_int.h" -typedef struct ALSAConf { - int size_in_usec_in; - int size_in_usec_out; - const char *pcm_name_in; - const char *pcm_name_out; - unsigned int buffer_size_in; - unsigned int period_size_in; - unsigned int buffer_size_out; - unsigned int period_size_out; - unsigned int threshold; - - int buffer_size_in_overridden; - int period_size_in_overridden; - - int buffer_size_out_overridden; - int period_size_out_overridden; -} ALSAConf; - struct pollhlp { snd_pcm_t *handle; struct pollfd *pfds; - ALSAConf *conf; int count; int mask; }; @@ -66,6 +47,7 @@ typedef struct ALSAVoiceOut { void *pcm_buf; snd_pcm_t *handle; struct pollhlp pollhlp; + Audiodev *dev; } ALSAVoiceOut; typedef struct ALSAVoiceIn { @@ -73,21 +55,18 @@ typedef struct ALSAVoiceIn { snd_pcm_t *handle; void *pcm_buf; struct pollhlp pollhlp; + Audiodev *dev; } ALSAVoiceIn; struct alsa_params_req { int freq; snd_pcm_format_t fmt; int nchannels; - int size_in_usec; - int override_mask; - unsigned int buffer_size; - unsigned int period_size; }; struct alsa_params_obt { int freq; - audfmt_e fmt; + AudioFormat fmt; int endianness; int nchannels; snd_pcm_uframes_t samples; @@ -294,16 +273,16 @@ static int alsa_write (SWVoiceOut *sw, void *buf, int len) return audio_pcm_sw_write (sw, buf, len); } -static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) +static snd_pcm_format_t aud_to_alsafmt (AudioFormat fmt, int endianness) { switch (fmt) { - case AUD_FMT_S8: + case AUDIO_FORMAT_S8: return SND_PCM_FORMAT_S8; - case AUD_FMT_U8: + case AUDIO_FORMAT_U8: return SND_PCM_FORMAT_U8; - case AUD_FMT_S16: + case AUDIO_FORMAT_S16: if (endianness) { return SND_PCM_FORMAT_S16_BE; } @@ -311,7 +290,7 @@ static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) return SND_PCM_FORMAT_S16_LE; } - case AUD_FMT_U16: + case AUDIO_FORMAT_U16: if (endianness) { return SND_PCM_FORMAT_U16_BE; } @@ -319,7 +298,7 @@ static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) return SND_PCM_FORMAT_U16_LE; } - case AUD_FMT_S32: + case AUDIO_FORMAT_S32: if (endianness) { return SND_PCM_FORMAT_S32_BE; } @@ -327,7 +306,7 @@ static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) return SND_PCM_FORMAT_S32_LE; } - case AUD_FMT_U32: + case AUDIO_FORMAT_U32: if (endianness) { return SND_PCM_FORMAT_U32_BE; } @@ -344,58 +323,58 @@ static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) } } -static int alsa_to_audfmt (snd_pcm_format_t alsafmt, audfmt_e *fmt, +static int alsa_to_audfmt (snd_pcm_format_t alsafmt, AudioFormat *fmt, int *endianness) { switch (alsafmt) { case SND_PCM_FORMAT_S8: *endianness = 0; - *fmt = AUD_FMT_S8; + *fmt = AUDIO_FORMAT_S8; break; case SND_PCM_FORMAT_U8: *endianness = 0; - *fmt = AUD_FMT_U8; + *fmt = AUDIO_FORMAT_U8; break; case SND_PCM_FORMAT_S16_LE: *endianness = 0; - *fmt = AUD_FMT_S16; + *fmt = AUDIO_FORMAT_S16; break; case SND_PCM_FORMAT_U16_LE: *endianness = 0; - *fmt = AUD_FMT_U16; + *fmt = AUDIO_FORMAT_U16; break; case SND_PCM_FORMAT_S16_BE: *endianness = 1; - *fmt = AUD_FMT_S16; + *fmt = AUDIO_FORMAT_S16; break; case SND_PCM_FORMAT_U16_BE: *endianness = 1; - *fmt = AUD_FMT_U16; + *fmt = AUDIO_FORMAT_U16; break; case SND_PCM_FORMAT_S32_LE: *endianness = 0; - *fmt = AUD_FMT_S32; + *fmt = AUDIO_FORMAT_S32; break; case SND_PCM_FORMAT_U32_LE: *endianness = 0; - *fmt = AUD_FMT_U32; + *fmt = AUDIO_FORMAT_U32; break; case SND_PCM_FORMAT_S32_BE: *endianness = 1; - *fmt = AUD_FMT_S32; + *fmt = AUDIO_FORMAT_S32; break; case SND_PCM_FORMAT_U32_BE: *endianness = 1; - *fmt = AUD_FMT_U32; + *fmt = AUDIO_FORMAT_U32; break; default: @@ -408,17 +387,18 @@ static int alsa_to_audfmt (snd_pcm_format_t alsafmt, audfmt_e *fmt, static void alsa_dump_info (struct alsa_params_req *req, struct alsa_params_obt *obt, - snd_pcm_format_t obtfmt) + snd_pcm_format_t obtfmt, + AudiodevAlsaPerDirectionOptions *apdo) { - dolog ("parameter | requested value | obtained value\n"); - dolog ("format | %10d | %10d\n", req->fmt, obtfmt); - dolog ("channels | %10d | %10d\n", - req->nchannels, obt->nchannels); - dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); - dolog ("============================================\n"); - dolog ("requested: buffer size %d period size %d\n", - req->buffer_size, req->period_size); - dolog ("obtained: samples %ld\n", obt->samples); + dolog("parameter | requested value | obtained value\n"); + dolog("format | %10d | %10d\n", req->fmt, obtfmt); + dolog("channels | %10d | %10d\n", + req->nchannels, obt->nchannels); + dolog("frequency | %10d | %10d\n", req->freq, obt->freq); + dolog("============================================\n"); + dolog("requested: buffer len %" PRId32 " period len %" PRId32 "\n", + apdo->buffer_length, apdo->period_length); + dolog("obtained: samples %ld\n", obt->samples); } static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) @@ -451,23 +431,23 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) } } -static int alsa_open (int in, struct alsa_params_req *req, - struct alsa_params_obt *obt, snd_pcm_t **handlep, - ALSAConf *conf) +static int alsa_open(bool in, struct alsa_params_req *req, + struct alsa_params_obt *obt, snd_pcm_t **handlep, + Audiodev *dev) { + AudiodevAlsaOptions *aopts = &dev->u.alsa; + AudiodevAlsaPerDirectionOptions *apdo = in ? aopts->in : aopts->out; snd_pcm_t *handle; snd_pcm_hw_params_t *hw_params; int err; - int size_in_usec; unsigned int freq, nchannels; - const char *pcm_name = in ? conf->pcm_name_in : conf->pcm_name_out; + const char *pcm_name = apdo->has_dev ? apdo->dev : "default"; snd_pcm_uframes_t obt_buffer_size; const char *typ = in ? "ADC" : "DAC"; snd_pcm_format_t obtfmt; freq = req->freq; nchannels = req->nchannels; - size_in_usec = req->size_in_usec; snd_pcm_hw_params_alloca (&hw_params); @@ -527,79 +507,42 @@ static int alsa_open (int in, struct alsa_params_req *req, goto err; } - if (req->buffer_size) { - unsigned long obt; + if (apdo->buffer_length) { + int dir = 0; + unsigned int btime = apdo->buffer_length; - if (size_in_usec) { - int dir = 0; - unsigned int btime = req->buffer_size; + err = snd_pcm_hw_params_set_buffer_time_near( + handle, hw_params, &btime, &dir); - err = snd_pcm_hw_params_set_buffer_time_near ( - handle, - hw_params, - &btime, - &dir - ); - obt = btime; - } - else { - snd_pcm_uframes_t bsize = req->buffer_size; - - err = snd_pcm_hw_params_set_buffer_size_near ( - handle, - hw_params, - &bsize - ); - obt = bsize; - } if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set buffer %s to %d\n", - size_in_usec ? "time" : "size", req->buffer_size); + alsa_logerr2(err, typ, "Failed to set buffer time to %" PRId32 "\n", + apdo->buffer_length); goto err; } - if ((req->override_mask & 2) && (obt - req->buffer_size)) - dolog ("Requested buffer %s %u was rejected, using %lu\n", - size_in_usec ? "time" : "size", req->buffer_size, obt); + if (apdo->has_buffer_length && btime != apdo->buffer_length) { + dolog("Requested buffer time %" PRId32 + " was rejected, using %u\n", apdo->buffer_length, btime); + } } - if (req->period_size) { - unsigned long obt; + if (apdo->period_length) { + int dir = 0; + unsigned int ptime = apdo->period_length; - if (size_in_usec) { - int dir = 0; - unsigned int ptime = req->period_size; - - err = snd_pcm_hw_params_set_period_time_near ( - handle, - hw_params, - &ptime, - &dir - ); - obt = ptime; - } - else { - int dir = 0; - snd_pcm_uframes_t psize = req->period_size; - - err = snd_pcm_hw_params_set_period_size_near ( - handle, - hw_params, - &psize, - &dir - ); - obt = psize; - } + err = snd_pcm_hw_params_set_period_time_near(handle, hw_params, &ptime, + &dir); if (err < 0) { - alsa_logerr2 (err, typ, "Failed to set period %s to %d\n", - size_in_usec ? "time" : "size", req->period_size); + alsa_logerr2(err, typ, "Failed to set period time to %" PRId32 "\n", + apdo->period_length); goto err; } - if (((req->override_mask & 1) && (obt - req->period_size))) - dolog ("Requested period %s %u was rejected, using %lu\n", - size_in_usec ? "time" : "size", req->period_size, obt); + if (apdo->has_period_length && ptime != apdo->period_length) { + dolog("Requested period time %" PRId32 " was rejected, using %d\n", + apdo->period_length, ptime); + } } err = snd_pcm_hw_params (handle, hw_params); @@ -631,30 +574,12 @@ static int alsa_open (int in, struct alsa_params_req *req, goto err; } - if (!in && conf->threshold) { - snd_pcm_uframes_t threshold; - int bytes_per_sec; - - bytes_per_sec = freq << (nchannels == 2); - - switch (obt->fmt) { - case AUD_FMT_S8: - case AUD_FMT_U8: - break; - - case AUD_FMT_S16: - case AUD_FMT_U16: - bytes_per_sec <<= 1; - break; - - case AUD_FMT_S32: - case AUD_FMT_U32: - bytes_per_sec <<= 2; - break; - } - - threshold = (conf->threshold * bytes_per_sec) / 1000; - alsa_set_threshold (handle, threshold); + if (!in && aopts->has_threshold && aopts->threshold) { + struct audsettings as = { .freq = freq }; + alsa_set_threshold( + handle, + audio_buffer_frames(qapi_AudiodevAlsaPerDirectionOptions_base(apdo), + &as, aopts->threshold)); } obt->nchannels = nchannels; @@ -667,11 +592,11 @@ static int alsa_open (int in, struct alsa_params_req *req, obt->nchannels != req->nchannels || obt->freq != req->freq) { dolog ("Audio parameters for %s\n", typ); - alsa_dump_info (req, obt, obtfmt); + alsa_dump_info(req, obt, obtfmt, apdo); } #ifdef DEBUG - alsa_dump_info (req, obt, obtfmt); + alsa_dump_info(req, obt, obtfmt, pdo); #endif return 0; @@ -797,19 +722,13 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, struct alsa_params_obt obt; snd_pcm_t *handle; struct audsettings obt_as; - ALSAConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; req.fmt = aud_to_alsafmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.period_size = conf->period_size_out; - req.buffer_size = conf->buffer_size_out; - req.size_in_usec = conf->size_in_usec_out; - req.override_mask = - (conf->period_size_out_overridden ? 1 : 0) | - (conf->buffer_size_out_overridden ? 2 : 0); - - if (alsa_open (0, &req, &obt, &handle, conf)) { + + if (alsa_open(0, &req, &obt, &handle, dev)) { return -1; } @@ -830,7 +749,7 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as, } alsa->handle = handle; - alsa->pollhlp.conf = conf; + alsa->dev = dev; return 0; } @@ -870,16 +789,12 @@ static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...) { ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->u.alsa.out; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = apdo->try_poll; ldebug ("enabling voice\n"); if (poll_mode && alsa_poll_out (hw)) { @@ -908,19 +823,13 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) struct alsa_params_obt obt; snd_pcm_t *handle; struct audsettings obt_as; - ALSAConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; req.fmt = aud_to_alsafmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.period_size = conf->period_size_in; - req.buffer_size = conf->buffer_size_in; - req.size_in_usec = conf->size_in_usec_in; - req.override_mask = - (conf->period_size_in_overridden ? 1 : 0) | - (conf->buffer_size_in_overridden ? 2 : 0); - - if (alsa_open (1, &req, &obt, &handle, conf)) { + + if (alsa_open(1, &req, &obt, &handle, dev)) { return -1; } @@ -941,7 +850,7 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } alsa->handle = handle; - alsa->pollhlp.conf = conf; + alsa->dev = dev; return 0; } @@ -1083,16 +992,12 @@ static int alsa_read (SWVoiceIn *sw, void *buf, int size) static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) { ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->u.alsa.in; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = apdo->try_poll; ldebug ("enabling voice\n"); if (poll_mode && alsa_poll_in (hw)) { @@ -1115,88 +1020,54 @@ static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) return -1; } -static ALSAConf glob_conf = { - .buffer_size_out = 4096, - .period_size_out = 1024, - .pcm_name_out = "default", - .pcm_name_in = "default", -}; +static void alsa_init_per_direction(AudiodevAlsaPerDirectionOptions *apdo) +{ + if (!apdo->has_try_poll) { + apdo->try_poll = true; + apdo->has_try_poll = true; + } +} -static void *alsa_audio_init (void) +static void *alsa_audio_init(Audiodev *dev) { - ALSAConf *conf = g_malloc(sizeof(ALSAConf)); - *conf = glob_conf; - return conf; + AudiodevAlsaOptions *aopts; + assert(dev->driver == AUDIODEV_DRIVER_ALSA); + + aopts = &dev->u.alsa; + alsa_init_per_direction(aopts->in); + alsa_init_per_direction(aopts->out); + + /* + * need to define them, as otherwise alsa produces no sound + * doesn't set has_* so alsa_open can identify it wasn't set by the user + */ + if (!dev->u.alsa.out->has_period_length) { + /* 1024 frames assuming 44100Hz */ + dev->u.alsa.out->period_length = 1024 * 1000000 / 44100; + } + if (!dev->u.alsa.out->has_buffer_length) { + /* 4096 frames assuming 44100Hz */ + dev->u.alsa.out->buffer_length = 4096ll * 1000000 / 44100; + } + + /* + * OptsVisitor sets unspecified optional fields to zero, but do not depend + * on it... + */ + if (!dev->u.alsa.in->has_period_length) { + dev->u.alsa.in->period_length = 0; + } + if (!dev->u.alsa.in->has_buffer_length) { + dev->u.alsa.in->buffer_length = 0; + } + + return dev; } static void alsa_audio_fini (void *opaque) { - g_free(opaque); } -static struct audio_option alsa_options[] = { - { - .name = "DAC_SIZE_IN_USEC", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.size_in_usec_out, - .descr = "DAC period/buffer size in microseconds (otherwise in frames)" - }, - { - .name = "DAC_PERIOD_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.period_size_out, - .descr = "DAC period size (0 to go with system default)", - .overriddenp = &glob_conf.period_size_out_overridden - }, - { - .name = "DAC_BUFFER_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.buffer_size_out, - .descr = "DAC buffer size (0 to go with system default)", - .overriddenp = &glob_conf.buffer_size_out_overridden - }, - { - .name = "ADC_SIZE_IN_USEC", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.size_in_usec_in, - .descr = - "ADC period/buffer size in microseconds (otherwise in frames)" - }, - { - .name = "ADC_PERIOD_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.period_size_in, - .descr = "ADC period size (0 to go with system default)", - .overriddenp = &glob_conf.period_size_in_overridden - }, - { - .name = "ADC_BUFFER_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.buffer_size_in, - .descr = "ADC buffer size (0 to go with system default)", - .overriddenp = &glob_conf.buffer_size_in_overridden - }, - { - .name = "THRESHOLD", - .tag = AUD_OPT_INT, - .valp = &glob_conf.threshold, - .descr = "(undocumented)" - }, - { - .name = "DAC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.pcm_name_out, - .descr = "DAC device name (for instance dmix)" - }, - { - .name = "ADC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.pcm_name_in, - .descr = "ADC device name" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops alsa_pcm_ops = { .init_out = alsa_init_out, .fini_out = alsa_fini_out, @@ -1214,7 +1085,6 @@ static struct audio_pcm_ops alsa_pcm_ops = { static struct audio_driver alsa_audio_driver = { .name = "alsa", .descr = "ALSA http://www.alsa-project.org", - .options = alsa_options, .init = alsa_audio_init, .fini = alsa_audio_fini, .pcm_ops = &alsa_pcm_ops, diff --git a/audio/audio.c b/audio/audio.c index 909c817103..5fd9a58a80 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -26,6 +26,9 @@ #include "audio.h" #include "monitor/monitor.h" #include "qemu/timer.h" +#include "qapi/error.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-audio.h" #include "sysemu/sysemu.h" #include "qemu/cutils.h" #include "sysemu/replay.h" @@ -46,14 +49,16 @@ The 1st one is the one used by default, that is the reason that we generate the list. */ -static const char *audio_prio_list[] = { +const char *audio_prio_list[] = { "spice", CONFIG_AUDIO_DRIVERS "none", "wav", + NULL }; static QLIST_HEAD(, audio_driver) audio_drivers; +static AudiodevListHead audiodevs = QSIMPLEQ_HEAD_INITIALIZER(audiodevs); void audio_driver_register(audio_driver *drv) { @@ -80,61 +85,6 @@ audio_driver *audio_driver_lookup(const char *name) return NULL; } -static void audio_module_load_all(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(audio_prio_list); i++) { - audio_driver_lookup(audio_prio_list[i]); - } -} - -struct fixed_settings { - int enabled; - int nb_voices; - int greedy; - struct audsettings settings; -}; - -static struct { - struct fixed_settings fixed_out; - struct fixed_settings fixed_in; - union { - int hertz; - int64_t ticks; - } period; - int try_poll_in; - int try_poll_out; -} conf = { - .fixed_out = { /* DAC fixed settings */ - .enabled = 1, - .nb_voices = 1, - .greedy = 1, - .settings = { - .freq = 44100, - .nchannels = 2, - .fmt = AUD_FMT_S16, - .endianness = AUDIO_HOST_ENDIANNESS, - } - }, - - .fixed_in = { /* ADC fixed settings */ - .enabled = 1, - .nb_voices = 1, - .greedy = 1, - .settings = { - .freq = 44100, - .nchannels = 2, - .fmt = AUD_FMT_S16, - .endianness = AUDIO_HOST_ENDIANNESS, - } - }, - - .period = { .hertz = 100 }, - .try_poll_in = 1, - .try_poll_out = 1, -}; - static AudioState glob_audio_state; const struct mixeng_volume nominal_volume = { @@ -151,9 +101,6 @@ const struct mixeng_volume nominal_volume = { #ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED #error No its not #else -static void audio_print_options (const char *prefix, - struct audio_option *opt); - int audio_bug (const char *funcname, int cond) { if (cond) { @@ -161,16 +108,9 @@ int audio_bug (const char *funcname, int cond) AUD_log (NULL, "A bug was just triggered in %s\n", funcname); if (!shown) { - struct audio_driver *d; - shown = 1; AUD_log (NULL, "Save all your work and restart without audio\n"); - AUD_log (NULL, "Please send bug report to av1474@comtv.ru\n"); AUD_log (NULL, "I am sorry\n"); - d = glob_audio_state.drv; - if (d) { - audio_print_options (d->name, d->options); - } } AUD_log (NULL, "Context:\n"); @@ -232,135 +172,6 @@ void *audio_calloc (const char *funcname, int nmemb, size_t size) return g_malloc0 (len); } -static char *audio_alloc_prefix (const char *s) -{ - const char qemu_prefix[] = "QEMU_"; - size_t len, i; - char *r, *u; - - if (!s) { - return NULL; - } - - len = strlen (s); - r = g_malloc (len + sizeof (qemu_prefix)); - - u = r + sizeof (qemu_prefix) - 1; - - pstrcpy (r, len + sizeof (qemu_prefix), qemu_prefix); - pstrcat (r, len + sizeof (qemu_prefix), s); - - for (i = 0; i < len; ++i) { - u[i] = qemu_toupper(u[i]); - } - - return r; -} - -static const char *audio_audfmt_to_string (audfmt_e fmt) -{ - switch (fmt) { - case AUD_FMT_U8: - return "U8"; - - case AUD_FMT_U16: - return "U16"; - - case AUD_FMT_S8: - return "S8"; - - case AUD_FMT_S16: - return "S16"; - - case AUD_FMT_U32: - return "U32"; - - case AUD_FMT_S32: - return "S32"; - } - - dolog ("Bogus audfmt %d returning S16\n", fmt); - return "S16"; -} - -static audfmt_e audio_string_to_audfmt (const char *s, audfmt_e defval, - int *defaultp) -{ - if (!strcasecmp (s, "u8")) { - *defaultp = 0; - return AUD_FMT_U8; - } - else if (!strcasecmp (s, "u16")) { - *defaultp = 0; - return AUD_FMT_U16; - } - else if (!strcasecmp (s, "u32")) { - *defaultp = 0; - return AUD_FMT_U32; - } - else if (!strcasecmp (s, "s8")) { - *defaultp = 0; - return AUD_FMT_S8; - } - else if (!strcasecmp (s, "s16")) { - *defaultp = 0; - return AUD_FMT_S16; - } - else if (!strcasecmp (s, "s32")) { - *defaultp = 0; - return AUD_FMT_S32; - } - else { - dolog ("Bogus audio format `%s' using %s\n", - s, audio_audfmt_to_string (defval)); - *defaultp = 1; - return defval; - } -} - -static audfmt_e audio_get_conf_fmt (const char *envname, - audfmt_e defval, - int *defaultp) -{ - const char *var = getenv (envname); - if (!var) { - *defaultp = 1; - return defval; - } - return audio_string_to_audfmt (var, defval, defaultp); -} - -static int audio_get_conf_int (const char *key, int defval, int *defaultp) -{ - int val; - char *strval; - - strval = getenv (key); - if (strval && !qemu_strtoi(strval, NULL, 10, &val)) { - *defaultp = 0; - return val; - } - else { - *defaultp = 1; - return defval; - } -} - -static const char *audio_get_conf_str (const char *key, - const char *defval, - int *defaultp) -{ - const char *val = getenv (key); - if (!val) { - *defaultp = 1; - return defval; - } - else { - *defaultp = 0; - return val; - } -} - void AUD_vlog (const char *cap, const char *fmt, va_list ap) { if (cap) { @@ -379,167 +190,27 @@ void AUD_log (const char *cap, const char *fmt, ...) va_end (ap); } -static void audio_print_options (const char *prefix, - struct audio_option *opt) -{ - char *uprefix; - - if (!prefix) { - dolog ("No prefix specified\n"); - return; - } - - if (!opt) { - dolog ("No options\n"); - return; - } - - uprefix = audio_alloc_prefix (prefix); - - for (; opt->name; opt++) { - const char *state = "default"; - printf (" %s_%s: ", uprefix, opt->name); - - if (opt->overriddenp && *opt->overriddenp) { - state = "current"; - } - - switch (opt->tag) { - case AUD_OPT_BOOL: - { - int *intp = opt->valp; - printf ("boolean, %s = %d\n", state, *intp ? 1 : 0); - } - break; - - case AUD_OPT_INT: - { - int *intp = opt->valp; - printf ("integer, %s = %d\n", state, *intp); - } - break; - - case AUD_OPT_FMT: - { - audfmt_e *fmtp = opt->valp; - printf ( - "format, %s = %s, (one of: U8 S8 U16 S16 U32 S32)\n", - state, - audio_audfmt_to_string (*fmtp) - ); - } - break; - - case AUD_OPT_STR: - { - const char **strp = opt->valp; - printf ("string, %s = %s\n", - state, - *strp ? *strp : "(not set)"); - } - break; - - default: - printf ("???\n"); - dolog ("Bad value tag for option %s_%s %d\n", - uprefix, opt->name, opt->tag); - break; - } - printf (" %s\n", opt->descr); - } - - g_free (uprefix); -} - -static void audio_process_options (const char *prefix, - struct audio_option *opt) -{ - gchar *prefix_upper; - - if (audio_bug(__func__, !prefix)) { - dolog ("prefix = NULL\n"); - return; - } - - if (audio_bug(__func__, !opt)) { - dolog ("opt = NULL\n"); - return; - } - - prefix_upper = g_utf8_strup(prefix, -1); - - for (; opt->name; opt++) { - char *optname; - int def; - - if (!opt->valp) { - dolog ("Option value pointer for `%s' is not set\n", - opt->name); - continue; - } - - optname = g_strdup_printf("QEMU_%s_%s", prefix_upper, opt->name); - - def = 1; - switch (opt->tag) { - case AUD_OPT_BOOL: - case AUD_OPT_INT: - { - int *intp = opt->valp; - *intp = audio_get_conf_int (optname, *intp, &def); - } - break; - - case AUD_OPT_FMT: - { - audfmt_e *fmtp = opt->valp; - *fmtp = audio_get_conf_fmt (optname, *fmtp, &def); - } - break; - - case AUD_OPT_STR: - { - const char **strp = opt->valp; - *strp = audio_get_conf_str (optname, *strp, &def); - } - break; - - default: - dolog ("Bad value tag for option `%s' - %d\n", - optname, opt->tag); - break; - } - - if (!opt->overriddenp) { - opt->overriddenp = &opt->overridden; - } - *opt->overriddenp = !def; - g_free (optname); - } - g_free(prefix_upper); -} - static void audio_print_settings (struct audsettings *as) { dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels); switch (as->fmt) { - case AUD_FMT_S8: + case AUDIO_FORMAT_S8: AUD_log (NULL, "S8"); break; - case AUD_FMT_U8: + case AUDIO_FORMAT_U8: AUD_log (NULL, "U8"); break; - case AUD_FMT_S16: + case AUDIO_FORMAT_S16: AUD_log (NULL, "S16"); break; - case AUD_FMT_U16: + case AUDIO_FORMAT_U16: AUD_log (NULL, "U16"); break; - case AUD_FMT_S32: + case AUDIO_FORMAT_S32: AUD_log (NULL, "S32"); break; - case AUD_FMT_U32: + case AUDIO_FORMAT_U32: AUD_log (NULL, "U32"); break; default: @@ -570,12 +241,12 @@ static int audio_validate_settings (struct audsettings *as) invalid |= as->endianness != 0 && as->endianness != 1; switch (as->fmt) { - case AUD_FMT_S8: - case AUD_FMT_U8: - case AUD_FMT_S16: - case AUD_FMT_U16: - case AUD_FMT_S32: - case AUD_FMT_U32: + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_U32: break; default: invalid = 1; @@ -591,25 +262,28 @@ static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *a int bits = 8, sign = 0; switch (as->fmt) { - case AUD_FMT_S8: + case AUDIO_FORMAT_S8: sign = 1; /* fall through */ - case AUD_FMT_U8: + case AUDIO_FORMAT_U8: break; - case AUD_FMT_S16: + case AUDIO_FORMAT_S16: sign = 1; /* fall through */ - case AUD_FMT_U16: + case AUDIO_FORMAT_U16: bits = 16; break; - case AUD_FMT_S32: + case AUDIO_FORMAT_S32: sign = 1; /* fall through */ - case AUD_FMT_U32: + case AUDIO_FORMAT_U32: bits = 32; break; + + default: + abort(); } return info->freq == as->freq && info->nchannels == as->nchannels @@ -623,24 +297,27 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as) int bits = 8, sign = 0, shift = 0; switch (as->fmt) { - case AUD_FMT_S8: + case AUDIO_FORMAT_S8: sign = 1; - case AUD_FMT_U8: + case AUDIO_FORMAT_U8: break; - case AUD_FMT_S16: + case AUDIO_FORMAT_S16: sign = 1; - case AUD_FMT_U16: + case AUDIO_FORMAT_U16: bits = 16; shift = 1; break; - case AUD_FMT_S32: + case AUDIO_FORMAT_S32: sign = 1; - case AUD_FMT_U32: + case AUDIO_FORMAT_U32: bits = 32; shift = 2; break; + + default: + abort(); } info->freq = as->freq; @@ -1132,11 +809,11 @@ static void audio_reset_timer (AudioState *s) { if (audio_is_timer_needed ()) { timer_mod_anticipate_ns(s->ts, - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks); + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks); if (!audio_timer_running) { audio_timer_running = true; audio_timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - trace_audio_timer_start(conf.period.ticks / SCALE_MS); + trace_audio_timer_start(s->period_ticks / SCALE_MS); } } else { timer_del(s->ts); @@ -1150,16 +827,17 @@ static void audio_reset_timer (AudioState *s) static void audio_timer (void *opaque) { int64_t now, diff; + AudioState *s = opaque; now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); diff = now - audio_timer_last; - if (diff > conf.period.ticks * 3 / 2) { + if (diff > s->period_ticks * 3 / 2) { trace_audio_timer_delayed(diff / SCALE_MS); } audio_timer_last = now; - audio_run ("timer"); - audio_reset_timer (opaque); + audio_run("timer"); + audio_reset_timer(s); } /* @@ -1219,7 +897,7 @@ void AUD_set_active_out (SWVoiceOut *sw, int on) if (!hw->enabled) { hw->enabled = 1; if (s->vm_running) { - hw->pcm_ops->ctl_out (hw, VOICE_ENABLE, conf.try_poll_out); + hw->pcm_ops->ctl_out(hw, VOICE_ENABLE); audio_reset_timer (s); } } @@ -1264,7 +942,7 @@ void AUD_set_active_in (SWVoiceIn *sw, int on) if (!hw->enabled) { hw->enabled = 1; if (s->vm_running) { - hw->pcm_ops->ctl_in (hw, VOICE_ENABLE, conf.try_poll_in); + hw->pcm_ops->ctl_in(hw, VOICE_ENABLE); audio_reset_timer (s); } } @@ -1585,169 +1263,10 @@ void audio_run (const char *msg) #endif } -static struct audio_option audio_options[] = { - /* DAC */ - { - .name = "DAC_FIXED_SETTINGS", - .tag = AUD_OPT_BOOL, - .valp = &conf.fixed_out.enabled, - .descr = "Use fixed settings for host DAC" - }, - { - .name = "DAC_FIXED_FREQ", - .tag = AUD_OPT_INT, - .valp = &conf.fixed_out.settings.freq, - .descr = "Frequency for fixed host DAC" - }, - { - .name = "DAC_FIXED_FMT", - .tag = AUD_OPT_FMT, - .valp = &conf.fixed_out.settings.fmt, - .descr = "Format for fixed host DAC" - }, - { - .name = "DAC_FIXED_CHANNELS", - .tag = AUD_OPT_INT, - .valp = &conf.fixed_out.settings.nchannels, - .descr = "Number of channels for fixed DAC (1 - mono, 2 - stereo)" - }, - { - .name = "DAC_VOICES", - .tag = AUD_OPT_INT, - .valp = &conf.fixed_out.nb_voices, - .descr = "Number of voices for DAC" - }, - { - .name = "DAC_TRY_POLL", - .tag = AUD_OPT_BOOL, - .valp = &conf.try_poll_out, - .descr = "Attempt using poll mode for DAC" - }, - /* ADC */ - { - .name = "ADC_FIXED_SETTINGS", - .tag = AUD_OPT_BOOL, - .valp = &conf.fixed_in.enabled, - .descr = "Use fixed settings for host ADC" - }, - { - .name = "ADC_FIXED_FREQ", - .tag = AUD_OPT_INT, - .valp = &conf.fixed_in.settings.freq, - .descr = "Frequency for fixed host ADC" - }, - { - .name = "ADC_FIXED_FMT", - .tag = AUD_OPT_FMT, - .valp = &conf.fixed_in.settings.fmt, - .descr = "Format for fixed host ADC" - }, - { - .name = "ADC_FIXED_CHANNELS", - .tag = AUD_OPT_INT, - .valp = &conf.fixed_in.settings.nchannels, - .descr = "Number of channels for fixed ADC (1 - mono, 2 - stereo)" - }, - { - .name = "ADC_VOICES", - .tag = AUD_OPT_INT, - .valp = &conf.fixed_in.nb_voices, - .descr = "Number of voices for ADC" - }, - { - .name = "ADC_TRY_POLL", - .tag = AUD_OPT_BOOL, - .valp = &conf.try_poll_in, - .descr = "Attempt using poll mode for ADC" - }, - /* Misc */ - { - .name = "TIMER_PERIOD", - .tag = AUD_OPT_INT, - .valp = &conf.period.hertz, - .descr = "Timer period in HZ (0 - use lowest possible)" - }, - { /* End of list */ } -}; - -static void audio_pp_nb_voices (const char *typ, int nb) -{ - switch (nb) { - case 0: - printf ("Does not support %s\n", typ); - break; - case 1: - printf ("One %s voice\n", typ); - break; - case INT_MAX: - printf ("Theoretically supports many %s voices\n", typ); - break; - default: - printf ("Theoretically supports up to %d %s voices\n", nb, typ); - break; - } - -} - -void AUD_help (void) -{ - struct audio_driver *d; - - /* make sure we print the help text for modular drivers too */ - audio_module_load_all(); - - audio_process_options ("AUDIO", audio_options); - QLIST_FOREACH(d, &audio_drivers, next) { - if (d->options) { - audio_process_options (d->name, d->options); - } - } - - printf ("Audio options:\n"); - audio_print_options ("AUDIO", audio_options); - printf ("\n"); - - printf ("Available drivers:\n"); - - QLIST_FOREACH(d, &audio_drivers, next) { - - printf ("Name: %s\n", d->name); - printf ("Description: %s\n", d->descr); - - audio_pp_nb_voices ("playback", d->max_voices_out); - audio_pp_nb_voices ("capture", d->max_voices_in); - - if (d->options) { - printf ("Options:\n"); - audio_print_options (d->name, d->options); - } - else { - printf ("No options\n"); - } - printf ("\n"); - } - - printf ( - "Options are settable through environment variables.\n" - "Example:\n" -#ifdef _WIN32 - " set QEMU_AUDIO_DRV=wav\n" - " set QEMU_WAV_PATH=c:\\tune.wav\n" -#else - " export QEMU_AUDIO_DRV=wav\n" - " export QEMU_WAV_PATH=$HOME/tune.wav\n" - "(for csh replace export with setenv in the above)\n" -#endif - " qemu ...\n\n" - ); -} - -static int audio_driver_init(AudioState *s, struct audio_driver *drv, bool msg) +static int audio_driver_init(AudioState *s, struct audio_driver *drv, + bool msg, Audiodev *dev) { - if (drv->options) { - audio_process_options (drv->name, drv->options); - } - s->drv_opaque = drv->init (); + s->drv_opaque = drv->init(dev); if (s->drv_opaque) { audio_init_nb_voices_out (drv); @@ -1773,11 +1292,11 @@ static void audio_vm_change_state_handler (void *opaque, int running, s->vm_running = running; while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { - hwo->pcm_ops->ctl_out (hwo, op, conf.try_poll_out); + hwo->pcm_ops->ctl_out(hwo, op); } while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { - hwi->pcm_ops->ctl_in (hwi, op, conf.try_poll_in); + hwi->pcm_ops->ctl_in(hwi, op); } audio_reset_timer (s); } @@ -1827,6 +1346,11 @@ void audio_cleanup(void) s->drv->fini (s->drv_opaque); s->drv = NULL; } + + if (s->dev) { + qapi_free_Audiodev(s->dev); + s->dev = NULL; + } } static const VMStateDescription vmstate_audio = { @@ -1838,19 +1362,58 @@ static const VMStateDescription vmstate_audio = { } }; -static void audio_init (void) +static void audio_validate_opts(Audiodev *dev, Error **errp); + +static AudiodevListEntry *audiodev_find( + AudiodevListHead *head, const char *drvname) +{ + AudiodevListEntry *e; + QSIMPLEQ_FOREACH(e, head, next) { + if (strcmp(AudiodevDriver_str(e->dev->driver), drvname) == 0) { + return e; + } + } + + return NULL; +} + +static int audio_init(Audiodev *dev) { size_t i; int done = 0; - const char *drvname; + const char *drvname = NULL; VMChangeStateEntry *e; AudioState *s = &glob_audio_state; struct audio_driver *driver; + /* silence gcc warning about uninitialized variable */ + AudiodevListHead head = QSIMPLEQ_HEAD_INITIALIZER(head); if (s->drv) { - return; + if (dev) { + dolog("Cannot create more than one audio backend, sorry\n"); + qapi_free_Audiodev(dev); + } + return -1; } + if (dev) { + /* -audiodev option */ + drvname = AudiodevDriver_str(dev->driver); + } else { + /* legacy implicit initialization */ + head = audio_handle_legacy_opts(); + /* + * In case of legacy initialization, all Audiodevs in the list will have + * the same configuration (except the driver), so it does't matter which + * one we chose. We need an Audiodev to set up AudioState before we can + * init a driver. Also note that dev at this point is still in the + * list. + */ + dev = QSIMPLEQ_FIRST(&head)->dev; + audio_validate_opts(dev, &error_abort); + } + s->dev = dev; + QLIST_INIT (&s->hw_head_out); QLIST_INIT (&s->hw_head_in); QLIST_INIT (&s->cap_head); @@ -1858,10 +1421,8 @@ static void audio_init (void) s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); - audio_process_options ("AUDIO", audio_options); - - s->nb_hw_voices_out = conf.fixed_out.nb_voices; - s->nb_hw_voices_in = conf.fixed_in.nb_voices; + s->nb_hw_voices_out = audio_get_pdo_out(dev)->voices; + s->nb_hw_voices_in = audio_get_pdo_in(dev)->voices; if (s->nb_hw_voices_out <= 0) { dolog ("Bogus number of playback voices %d, setting to 1\n", @@ -1875,46 +1436,42 @@ static void audio_init (void) s->nb_hw_voices_in = 0; } - { - int def; - drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def); - } - if (drvname) { driver = audio_driver_lookup(drvname); if (driver) { - done = !audio_driver_init(s, driver, true); + done = !audio_driver_init(s, driver, true, dev); } else { dolog ("Unknown audio driver `%s'\n", drvname); - dolog ("Run with -audio-help to list available drivers\n"); } - } - - if (!done) { - for (i = 0; !done && i < ARRAY_SIZE(audio_prio_list); i++) { + } else { + for (i = 0; audio_prio_list[i]; i++) { + AudiodevListEntry *e = audiodev_find(&head, audio_prio_list[i]); driver = audio_driver_lookup(audio_prio_list[i]); - if (driver && driver->can_be_default) { - done = !audio_driver_init(s, driver, false); + + if (e && driver) { + s->dev = dev = e->dev; + audio_validate_opts(dev, &error_abort); + done = !audio_driver_init(s, driver, false, dev); + if (done) { + e->dev = NULL; + break; + } } } } + audio_free_audiodev_list(&head); if (!done) { driver = audio_driver_lookup("none"); - done = !audio_driver_init(s, driver, false); + done = !audio_driver_init(s, driver, false, dev); assert(done); dolog("warning: Using timer based audio emulation\n"); } - if (conf.period.hertz <= 0) { - if (conf.period.hertz < 0) { - dolog ("warning: Timer period is negative - %d " - "treating as zero\n", - conf.period.hertz); - } - conf.period.ticks = 1; + if (dev->timer_period <= 0) { + s->period_ticks = 1; } else { - conf.period.ticks = NANOSECONDS_PER_SECOND / conf.period.hertz; + s->period_ticks = NANOSECONDS_PER_SECOND / dev->timer_period; } e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s); @@ -1925,11 +1482,22 @@ static void audio_init (void) QLIST_INIT (&s->card_head); vmstate_register (NULL, 0, &vmstate_audio, s); + return 0; +} + +void audio_free_audiodev_list(AudiodevListHead *head) +{ + AudiodevListEntry *e; + while ((e = QSIMPLEQ_FIRST(head))) { + QSIMPLEQ_REMOVE_HEAD(head, next); + qapi_free_Audiodev(e->dev); + g_free(e); + } } void AUD_register_card (const char *name, QEMUSoundCard *card) { - audio_init (); + audio_init(NULL); card->name = g_strdup (name); memset (&card->entries, 0, sizeof (card->entries)); QLIST_INSERT_HEAD (&glob_audio_state.card_head, card, entries); @@ -2069,3 +1637,174 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) } } } + +void audio_create_pdos(Audiodev *dev) +{ + switch (dev->driver) { +#define CASE(DRIVER, driver, pdo_name) \ + case AUDIODEV_DRIVER_##DRIVER: \ + if (!dev->u.driver.has_in) { \ + dev->u.driver.in = g_malloc0( \ + sizeof(Audiodev##pdo_name##PerDirectionOptions)); \ + dev->u.driver.has_in = true; \ + } \ + if (!dev->u.driver.has_out) { \ + dev->u.driver.out = g_malloc0( \ + sizeof(AudiodevAlsaPerDirectionOptions)); \ + dev->u.driver.has_out = true; \ + } \ + break + + CASE(NONE, none, ); + CASE(ALSA, alsa, Alsa); + CASE(COREAUDIO, coreaudio, Coreaudio); + CASE(DSOUND, dsound, ); + CASE(OSS, oss, Oss); + CASE(PA, pa, Pa); + CASE(SDL, sdl, ); + CASE(SPICE, spice, ); + CASE(WAV, wav, ); + + case AUDIODEV_DRIVER__MAX: + abort(); + }; +} + +static void audio_validate_per_direction_opts( + AudiodevPerDirectionOptions *pdo, Error **errp) +{ + if (!pdo->has_fixed_settings) { + pdo->has_fixed_settings = true; + pdo->fixed_settings = true; + } + if (!pdo->fixed_settings && + (pdo->has_frequency || pdo->has_channels || pdo->has_format)) { + error_setg(errp, + "You can't use frequency, channels or format with fixed-settings=off"); + return; + } + + if (!pdo->has_frequency) { + pdo->has_frequency = true; + pdo->frequency = 44100; + } + if (!pdo->has_channels) { + pdo->has_channels = true; + pdo->channels = 2; + } + if (!pdo->has_voices) { + pdo->has_voices = true; + pdo->voices = 1; + } + if (!pdo->has_format) { + pdo->has_format = true; + pdo->format = AUDIO_FORMAT_S16; + } +} + +static void audio_validate_opts(Audiodev *dev, Error **errp) +{ + Error *err = NULL; + + audio_create_pdos(dev); + + audio_validate_per_direction_opts(audio_get_pdo_in(dev), &err); + if (err) { + error_propagate(errp, err); + return; + } + + audio_validate_per_direction_opts(audio_get_pdo_out(dev), &err); + if (err) { + error_propagate(errp, err); + return; + } + + if (!dev->has_timer_period) { + dev->has_timer_period = true; + dev->timer_period = 10000; /* 100Hz -> 10ms */ + } +} + +void audio_parse_option(const char *opt) +{ + AudiodevListEntry *e; + Audiodev *dev = NULL; + + Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal); + visit_type_Audiodev(v, NULL, &dev, &error_fatal); + visit_free(v); + + audio_validate_opts(dev, &error_fatal); + + e = g_malloc0(sizeof(AudiodevListEntry)); + e->dev = dev; + QSIMPLEQ_INSERT_TAIL(&audiodevs, e, next); +} + +void audio_init_audiodevs(void) +{ + AudiodevListEntry *e; + + QSIMPLEQ_FOREACH(e, &audiodevs, next) { + audio_init(e->dev); + } +} + +audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo) +{ + return (audsettings) { + .freq = pdo->frequency, + .nchannels = pdo->channels, + .fmt = pdo->format, + .endianness = AUDIO_HOST_ENDIANNESS, + }; +} + +int audioformat_bytes_per_sample(AudioFormat fmt) +{ + switch (fmt) { + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S8: + return 1; + + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_S16: + return 2; + + case AUDIO_FORMAT_U32: + case AUDIO_FORMAT_S32: + return 4; + + case AUDIO_FORMAT__MAX: + ; + } + abort(); +} + + +/* frames = freq * usec / 1e6 */ +int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs; + return (as->freq * usecs + 500000) / 1000000; +} + +/* samples = channels * frames = channels * freq * usec / 1e6 */ +int audio_buffer_samples(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + return as->nchannels * audio_buffer_frames(pdo, as, def_usecs); +} + +/* + * bytes = bytes_per_sample * samples = + * bytes_per_sample * channels * freq * usec / 1e6 + */ +int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + return audio_buffer_samples(pdo, as, def_usecs) * + audioformat_bytes_per_sample(as->fmt); +} diff --git a/audio/audio.h b/audio/audio.h index f4339a185e..64b0f761bc 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -26,30 +26,31 @@ #define QEMU_AUDIO_H #include "qemu/queue.h" +#include "qapi/qapi-types-audio.h" typedef void (*audio_callback_fn) (void *opaque, int avail); -typedef enum { - AUD_FMT_U8, - AUD_FMT_S8, - AUD_FMT_U16, - AUD_FMT_S16, - AUD_FMT_U32, - AUD_FMT_S32 -} audfmt_e; - #ifdef HOST_WORDS_BIGENDIAN #define AUDIO_HOST_ENDIANNESS 1 #else #define AUDIO_HOST_ENDIANNESS 0 #endif -struct audsettings { +typedef struct audsettings { int freq; int nchannels; - audfmt_e fmt; + AudioFormat fmt; int endianness; -}; +} audsettings; + +audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo); +int audioformat_bytes_per_sample(AudioFormat fmt); +int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs); +int audio_buffer_samples(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs); +int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs); typedef enum { AUD_CNOTIFY_ENABLE, @@ -89,7 +90,6 @@ typedef struct QEMUAudioTimeStamp { void AUD_vlog (const char *cap, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0); void AUD_log (const char *cap, const char *fmt, ...) GCC_FMT_ATTR(2, 3); -void AUD_help (void); void AUD_register_card (const char *name, QEMUSoundCard *card); void AUD_remove_card (QEMUSoundCard *card); CaptureVoiceOut *AUD_add_capture ( @@ -171,4 +171,8 @@ void audio_sample_to_uint64(void *samples, int pos, void audio_sample_from_uint64(void *samples, int pos, uint64_t left, uint64_t right); +void audio_parse_option(const char *opt); +void audio_init_audiodevs(void); +void audio_legacy_help(void); + #endif /* QEMU_AUDIO_H */ diff --git a/audio/audio_int.h b/audio/audio_int.h index 6c451b995c..3f14842709 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -33,22 +33,6 @@ struct audio_pcm_ops; -typedef enum { - AUD_OPT_INT, - AUD_OPT_FMT, - AUD_OPT_STR, - AUD_OPT_BOOL -} audio_option_tag_e; - -struct audio_option { - const char *name; - audio_option_tag_e tag; - void *valp; - const char *descr; - int *overriddenp; - int overridden; -}; - struct audio_callback { void *opaque; audio_callback_fn fn; @@ -145,8 +129,7 @@ typedef struct audio_driver audio_driver; struct audio_driver { const char *name; const char *descr; - struct audio_option *options; - void *(*init) (void); + void *(*init) (Audiodev *); void (*fini) (void *); struct audio_pcm_ops *pcm_ops; int can_be_default; @@ -193,6 +176,7 @@ struct SWVoiceCap { typedef struct AudioState { struct audio_driver *drv; + Audiodev *dev; void *drv_opaque; QEMUTimer *ts; @@ -203,10 +187,13 @@ typedef struct AudioState { int nb_hw_voices_out; int nb_hw_voices_in; int vm_running; + int64_t period_ticks; } AudioState; extern const struct mixeng_volume nominal_volume; +extern const char *audio_prio_list[]; + void audio_driver_register(audio_driver *drv); audio_driver *audio_driver_lookup(const char *name); @@ -248,4 +235,18 @@ static inline int audio_ring_dist (int dst, int src, int len) #define AUDIO_STRINGIFY_(n) #n #define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n) +typedef struct AudiodevListEntry { + Audiodev *dev; + QSIMPLEQ_ENTRY(AudiodevListEntry) next; +} AudiodevListEntry; + +typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead; +AudiodevListHead audio_handle_legacy_opts(void); + +void audio_free_audiodev_list(AudiodevListHead *head); + +void audio_create_pdos(Audiodev *dev); +AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev); +AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev); + #endif /* QEMU_AUDIO_INT_H */ diff --git a/audio/audio_legacy.c b/audio/audio_legacy.c new file mode 100644 index 0000000000..6d140119d9 --- /dev/null +++ b/audio/audio_legacy.c @@ -0,0 +1,544 @@ +/* + * QEMU Audio subsystem: legacy configuration handling + * + * Copyright (c) 2015-2019 Zoltán KÅ‘vágó <DirtY.iCE.hu@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu/osdep.h" +#include "audio.h" +#include "audio_int.h" +#include "qemu-common.h" +#include "qemu/cutils.h" +#include "qapi/error.h" +#include "qapi/qapi-visit-audio.h" +#include "qapi/visitor-impl.h" + +#define AUDIO_CAP "audio-legacy" +#include "audio_int.h" + +static uint32_t toui32(const char *str) +{ + unsigned long long ret; + if (parse_uint_full(str, &ret, 10) || ret > UINT32_MAX) { + dolog("Invalid integer value `%s'\n", str); + exit(1); + } + return ret; +} + +/* helper functions to convert env variables */ +static void get_bool(const char *env, bool *dst, bool *has_dst) +{ + const char *val = getenv(env); + if (val) { + *dst = toui32(val) != 0; + *has_dst = true; + } +} + +static void get_int(const char *env, uint32_t *dst, bool *has_dst) +{ + const char *val = getenv(env); + if (val) { + *dst = toui32(val); + *has_dst = true; + } +} + +static void get_str(const char *env, char **dst, bool *has_dst) +{ + const char *val = getenv(env); + if (val) { + if (*has_dst) { + g_free(*dst); + } + *dst = g_strdup(val); + *has_dst = true; + } +} + +static void get_fmt(const char *env, AudioFormat *dst, bool *has_dst) +{ + const char *val = getenv(env); + if (val) { + size_t i; + for (i = 0; AudioFormat_lookup.size; ++i) { + if (strcasecmp(val, AudioFormat_lookup.array[i]) == 0) { + *dst = i; + *has_dst = true; + return; + } + } + + dolog("Invalid audio format `%s'\n", val); + exit(1); + } +} + + +static void get_millis_to_usecs(const char *env, uint32_t *dst, bool *has_dst) +{ + const char *val = getenv(env); + if (val) { + *dst = toui32(val) * 1000; + *has_dst = true; + } +} + +static uint32_t frames_to_usecs(uint32_t frames, + AudiodevPerDirectionOptions *pdo) +{ + uint32_t freq = pdo->has_frequency ? pdo->frequency : 44100; + return (frames * 1000000 + freq / 2) / freq; +} + + +static void get_frames_to_usecs(const char *env, uint32_t *dst, bool *has_dst, + AudiodevPerDirectionOptions *pdo) +{ + const char *val = getenv(env); + if (val) { + *dst = frames_to_usecs(toui32(val), pdo); + *has_dst = true; + } +} + +static uint32_t samples_to_usecs(uint32_t samples, + AudiodevPerDirectionOptions *pdo) +{ + uint32_t channels = pdo->has_channels ? pdo->channels : 2; + return frames_to_usecs(samples / channels, pdo); +} + +static void get_samples_to_usecs(const char *env, uint32_t *dst, bool *has_dst, + AudiodevPerDirectionOptions *pdo) +{ + const char *val = getenv(env); + if (val) { + *dst = samples_to_usecs(toui32(val), pdo); + *has_dst = true; + } +} + +static uint32_t bytes_to_usecs(uint32_t bytes, AudiodevPerDirectionOptions *pdo) +{ + AudioFormat fmt = pdo->has_format ? pdo->format : AUDIO_FORMAT_S16; + uint32_t bytes_per_sample = audioformat_bytes_per_sample(fmt); + return samples_to_usecs(bytes / bytes_per_sample, pdo); +} + +static void get_bytes_to_usecs(const char *env, uint32_t *dst, bool *has_dst, + AudiodevPerDirectionOptions *pdo) +{ + const char *val = getenv(env); + if (val) { + *dst = bytes_to_usecs(toui32(val), pdo); + *has_dst = true; + } +} + +/* backend specific functions */ +/* ALSA */ +static void handle_alsa_per_direction( + AudiodevAlsaPerDirectionOptions *apdo, const char *prefix) +{ + char buf[64]; + size_t len = strlen(prefix); + bool size_in_usecs = false; + bool dummy; + + memcpy(buf, prefix, len); + strcpy(buf + len, "TRY_POLL"); + get_bool(buf, &apdo->try_poll, &apdo->has_try_poll); + + strcpy(buf + len, "DEV"); + get_str(buf, &apdo->dev, &apdo->has_dev); + + strcpy(buf + len, "SIZE_IN_USEC"); + get_bool(buf, &size_in_usecs, &dummy); + + strcpy(buf + len, "PERIOD_SIZE"); + get_int(buf, &apdo->period_length, &apdo->has_period_length); + if (apdo->has_period_length && !size_in_usecs) { + apdo->period_length = frames_to_usecs( + apdo->period_length, + qapi_AudiodevAlsaPerDirectionOptions_base(apdo)); + } + + strcpy(buf + len, "BUFFER_SIZE"); + get_int(buf, &apdo->buffer_length, &apdo->has_buffer_length); + if (apdo->has_buffer_length && !size_in_usecs) { + apdo->buffer_length = frames_to_usecs( + apdo->buffer_length, + qapi_AudiodevAlsaPerDirectionOptions_base(apdo)); + } +} + +static void handle_alsa(Audiodev *dev) +{ + AudiodevAlsaOptions *aopt = &dev->u.alsa; + handle_alsa_per_direction(aopt->in, "QEMU_ALSA_ADC_"); + handle_alsa_per_direction(aopt->out, "QEMU_ALSA_DAC_"); + + get_millis_to_usecs("QEMU_ALSA_THRESHOLD", + &aopt->threshold, &aopt->has_threshold); +} + +/* coreaudio */ +static void handle_coreaudio(Audiodev *dev) +{ + get_frames_to_usecs( + "QEMU_COREAUDIO_BUFFER_SIZE", + &dev->u.coreaudio.out->buffer_length, + &dev->u.coreaudio.out->has_buffer_length, + qapi_AudiodevCoreaudioPerDirectionOptions_base(dev->u.coreaudio.out)); + get_int("QEMU_COREAUDIO_BUFFER_COUNT", + &dev->u.coreaudio.out->buffer_count, + &dev->u.coreaudio.out->has_buffer_count); +} + +/* dsound */ +static void handle_dsound(Audiodev *dev) +{ + get_millis_to_usecs("QEMU_DSOUND_LATENCY_MILLIS", + &dev->u.dsound.latency, &dev->u.dsound.has_latency); + get_bytes_to_usecs("QEMU_DSOUND_BUFSIZE_OUT", + &dev->u.dsound.out->buffer_length, + &dev->u.dsound.out->has_buffer_length, + dev->u.dsound.out); + get_bytes_to_usecs("QEMU_DSOUND_BUFSIZE_IN", + &dev->u.dsound.in->buffer_length, + &dev->u.dsound.in->has_buffer_length, + dev->u.dsound.in); +} + +/* OSS */ +static void handle_oss_per_direction( + AudiodevOssPerDirectionOptions *opdo, const char *try_poll_env, + const char *dev_env) +{ + get_bool(try_poll_env, &opdo->try_poll, &opdo->has_try_poll); + get_str(dev_env, &opdo->dev, &opdo->has_dev); + + get_bytes_to_usecs("QEMU_OSS_FRAGSIZE", + &opdo->buffer_length, &opdo->has_buffer_length, + qapi_AudiodevOssPerDirectionOptions_base(opdo)); + get_int("QEMU_OSS_NFRAGS", &opdo->buffer_count, + &opdo->has_buffer_count); +} + +static void handle_oss(Audiodev *dev) +{ + AudiodevOssOptions *oopt = &dev->u.oss; + handle_oss_per_direction(oopt->in, "QEMU_AUDIO_ADC_TRY_POLL", + "QEMU_OSS_ADC_DEV"); + handle_oss_per_direction(oopt->out, "QEMU_AUDIO_DAC_TRY_POLL", + "QEMU_OSS_DAC_DEV"); + + get_bool("QEMU_OSS_MMAP", &oopt->try_mmap, &oopt->has_try_mmap); + get_bool("QEMU_OSS_EXCLUSIVE", &oopt->exclusive, &oopt->has_exclusive); + get_int("QEMU_OSS_POLICY", &oopt->dsp_policy, &oopt->has_dsp_policy); +} + +/* pulseaudio */ +static void handle_pa_per_direction( + AudiodevPaPerDirectionOptions *ppdo, const char *env) +{ + get_str(env, &ppdo->name, &ppdo->has_name); +} + +static void handle_pa(Audiodev *dev) +{ + handle_pa_per_direction(dev->u.pa.in, "QEMU_PA_SOURCE"); + handle_pa_per_direction(dev->u.pa.out, "QEMU_PA_SINK"); + + get_samples_to_usecs( + "QEMU_PA_SAMPLES", &dev->u.pa.in->buffer_length, + &dev->u.pa.in->has_buffer_length, + qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in)); + get_samples_to_usecs( + "QEMU_PA_SAMPLES", &dev->u.pa.out->buffer_length, + &dev->u.pa.out->has_buffer_length, + qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out)); + + get_str("QEMU_PA_SERVER", &dev->u.pa.server, &dev->u.pa.has_server); +} + +/* SDL */ +static void handle_sdl(Audiodev *dev) +{ + /* SDL is output only */ + get_samples_to_usecs("QEMU_SDL_SAMPLES", &dev->u.sdl.out->buffer_length, + &dev->u.sdl.out->has_buffer_length, dev->u.sdl.out); +} + +/* wav */ +static void handle_wav(Audiodev *dev) +{ + get_int("QEMU_WAV_FREQUENCY", + &dev->u.wav.out->frequency, &dev->u.wav.out->has_frequency); + get_fmt("QEMU_WAV_FORMAT", &dev->u.wav.out->format, + &dev->u.wav.out->has_format); + get_int("QEMU_WAV_DAC_FIXED_CHANNELS", + &dev->u.wav.out->channels, &dev->u.wav.out->has_channels); + get_str("QEMU_WAV_PATH", &dev->u.wav.path, &dev->u.wav.has_path); +} + +/* general */ +static void handle_per_direction( + AudiodevPerDirectionOptions *pdo, const char *prefix) +{ + char buf[64]; + size_t len = strlen(prefix); + + memcpy(buf, prefix, len); + strcpy(buf + len, "FIXED_SETTINGS"); + get_bool(buf, &pdo->fixed_settings, &pdo->has_fixed_settings); + + strcpy(buf + len, "FIXED_FREQ"); + get_int(buf, &pdo->frequency, &pdo->has_frequency); + + strcpy(buf + len, "FIXED_FMT"); + get_fmt(buf, &pdo->format, &pdo->has_format); + + strcpy(buf + len, "FIXED_CHANNELS"); + get_int(buf, &pdo->channels, &pdo->has_channels); + + strcpy(buf + len, "VOICES"); + get_int(buf, &pdo->voices, &pdo->has_voices); +} + +static AudiodevListEntry *legacy_opt(const char *drvname) +{ + AudiodevListEntry *e = g_malloc0(sizeof(AudiodevListEntry)); + e->dev = g_malloc0(sizeof(Audiodev)); + e->dev->id = g_strdup(drvname); + e->dev->driver = qapi_enum_parse( + &AudiodevDriver_lookup, drvname, -1, &error_abort); + + audio_create_pdos(e->dev); + + handle_per_direction(audio_get_pdo_in(e->dev), "QEMU_AUDIO_ADC_"); + handle_per_direction(audio_get_pdo_out(e->dev), "QEMU_AUDIO_DAC_"); + + get_int("QEMU_AUDIO_TIMER_PERIOD", + &e->dev->timer_period, &e->dev->has_timer_period); + + switch (e->dev->driver) { + case AUDIODEV_DRIVER_ALSA: + handle_alsa(e->dev); + break; + + case AUDIODEV_DRIVER_COREAUDIO: + handle_coreaudio(e->dev); + break; + + case AUDIODEV_DRIVER_DSOUND: + handle_dsound(e->dev); + break; + + case AUDIODEV_DRIVER_OSS: + handle_oss(e->dev); + break; + + case AUDIODEV_DRIVER_PA: + handle_pa(e->dev); + break; + + case AUDIODEV_DRIVER_SDL: + handle_sdl(e->dev); + break; + + case AUDIODEV_DRIVER_WAV: + handle_wav(e->dev); + break; + + default: + break; + } + + return e; +} + +AudiodevListHead audio_handle_legacy_opts(void) +{ + const char *drvname = getenv("QEMU_AUDIO_DRV"); + AudiodevListHead head = QSIMPLEQ_HEAD_INITIALIZER(head); + + if (drvname) { + AudiodevListEntry *e; + audio_driver *driver = audio_driver_lookup(drvname); + if (!driver) { + dolog("Unknown audio driver `%s'\n", drvname); + exit(1); + } + e = legacy_opt(drvname); + QSIMPLEQ_INSERT_TAIL(&head, e, next); + } else { + for (int i = 0; audio_prio_list[i]; i++) { + audio_driver *driver = audio_driver_lookup(audio_prio_list[i]); + if (driver && driver->can_be_default) { + AudiodevListEntry *e = legacy_opt(driver->name); + QSIMPLEQ_INSERT_TAIL(&head, e, next); + } + } + if (QSIMPLEQ_EMPTY(&head)) { + dolog("Internal error: no default audio driver available\n"); + exit(1); + } + } + + return head; +} + +/* visitor to print -audiodev option */ +typedef struct { + Visitor visitor; + + bool comma; + GList *path; +} LegacyPrintVisitor; + +static void lv_start_struct(Visitor *v, const char *name, void **obj, + size_t size, Error **errp) +{ + LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v; + lv->path = g_list_append(lv->path, g_strdup(name)); +} + +static void lv_end_struct(Visitor *v, void **obj) +{ + LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v; + lv->path = g_list_delete_link(lv->path, g_list_last(lv->path)); +} + +static void lv_print_key(Visitor *v, const char *name) +{ + GList *e; + LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v; + if (lv->comma) { + putchar(','); + } else { + lv->comma = true; + } + + for (e = lv->path; e; e = e->next) { + if (e->data) { + printf("%s.", (const char *) e->data); + } + } + + printf("%s=", name); +} + +static void lv_type_int64(Visitor *v, const char *name, int64_t *obj, + Error **errp) +{ + lv_print_key(v, name); + printf("%" PRIi64, *obj); +} + +static void lv_type_uint64(Visitor *v, const char *name, uint64_t *obj, + Error **errp) +{ + lv_print_key(v, name); + printf("%" PRIu64, *obj); +} + +static void lv_type_bool(Visitor *v, const char *name, bool *obj, Error **errp) +{ + lv_print_key(v, name); + printf("%s", *obj ? "on" : "off"); +} + +static void lv_type_str(Visitor *v, const char *name, char **obj, Error **errp) +{ + const char *str = *obj; + lv_print_key(v, name); + + while (*str) { + if (*str == ',') { + putchar(','); + } + putchar(*str++); + } +} + +static void lv_complete(Visitor *v, void *opaque) +{ + LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v; + assert(lv->path == NULL); +} + +static void lv_free(Visitor *v) +{ + LegacyPrintVisitor *lv = (LegacyPrintVisitor *) v; + + g_list_free_full(lv->path, g_free); + g_free(lv); +} + +static Visitor *legacy_visitor_new(void) +{ + LegacyPrintVisitor *lv = g_malloc0(sizeof(LegacyPrintVisitor)); + + lv->visitor.start_struct = lv_start_struct; + lv->visitor.end_struct = lv_end_struct; + /* lists not supported */ + lv->visitor.type_int64 = lv_type_int64; + lv->visitor.type_uint64 = lv_type_uint64; + lv->visitor.type_bool = lv_type_bool; + lv->visitor.type_str = lv_type_str; + + lv->visitor.type = VISITOR_OUTPUT; + lv->visitor.complete = lv_complete; + lv->visitor.free = lv_free; + + return &lv->visitor; +} + +void audio_legacy_help(void) +{ + AudiodevListHead head; + AudiodevListEntry *e; + + printf("Environment variable based configuration deprecated.\n"); + printf("Please use the new -audiodev option.\n"); + + head = audio_handle_legacy_opts(); + printf("\nEquivalent -audiodev to your current environment variables:\n"); + if (!getenv("QEMU_AUDIO_DRV")) { + printf("(Since you didn't specify QEMU_AUDIO_DRV, I'll list all " + "possibilities)\n"); + } + + QSIMPLEQ_FOREACH(e, &head, next) { + Visitor *v; + Audiodev *dev = e->dev; + printf("-audiodev "); + + v = legacy_visitor_new(); + visit_type_Audiodev(v, NULL, &dev, &error_abort); + visit_free(v); + + printf("\n"); + } + audio_free_audiodev_list(&head); +} diff --git a/audio/audio_template.h b/audio/audio_template.h index 7de227d2d1..1232bb54db 100644 --- a/audio/audio_template.h +++ b/audio/audio_template.h @@ -299,11 +299,42 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as) return NULL; } +AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev) +{ + switch (dev->driver) { + case AUDIODEV_DRIVER_NONE: + return dev->u.none.TYPE; + case AUDIODEV_DRIVER_ALSA: + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.TYPE); + case AUDIODEV_DRIVER_COREAUDIO: + return qapi_AudiodevCoreaudioPerDirectionOptions_base( + dev->u.coreaudio.TYPE); + case AUDIODEV_DRIVER_DSOUND: + return dev->u.dsound.TYPE; + case AUDIODEV_DRIVER_OSS: + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.TYPE); + case AUDIODEV_DRIVER_PA: + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.TYPE); + case AUDIODEV_DRIVER_SDL: + return dev->u.sdl.TYPE; + case AUDIODEV_DRIVER_SPICE: + return dev->u.spice.TYPE; + case AUDIODEV_DRIVER_WAV: + return dev->u.wav.TYPE; + + case AUDIODEV_DRIVER__MAX: + break; + } + abort(); +} + static HW *glue (audio_pcm_hw_add_, TYPE) (struct audsettings *as) { HW *hw; + AudioState *s = &glob_audio_state; + AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); - if (glue (conf.fixed_, TYPE).enabled && glue (conf.fixed_, TYPE).greedy) { + if (pdo->fixed_settings) { hw = glue (audio_pcm_hw_add_new_, TYPE) (as); if (hw) { return hw; @@ -331,9 +362,11 @@ static SW *glue (audio_pcm_create_voice_pair_, TYPE) ( SW *sw; HW *hw; struct audsettings hw_as; + AudioState *s = &glob_audio_state; + AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); - if (glue (conf.fixed_, TYPE).enabled) { - hw_as = glue (conf.fixed_, TYPE).settings; + if (pdo->fixed_settings) { + hw_as = audiodev_to_audsettings(pdo); } else { hw_as = *as; @@ -398,6 +431,7 @@ SW *glue (AUD_open_, TYPE) ( ) { AudioState *s = &glob_audio_state; + AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); if (audio_bug(__func__, !card || !name || !callback_fn || !as)) { dolog ("card=%p name=%p callback_fn=%p as=%p\n", @@ -422,7 +456,7 @@ SW *glue (AUD_open_, TYPE) ( return sw; } - if (!glue (conf.fixed_, TYPE).enabled && sw) { + if (!pdo->fixed_settings && sw) { glue (AUD_close_, TYPE) (card, sw); sw = NULL; } diff --git a/audio/audio_win_int.c b/audio/audio_win_int.c index 6900008d0c..b938fd667b 100644 --- a/audio/audio_win_int.c +++ b/audio/audio_win_int.c @@ -24,20 +24,20 @@ int waveformat_from_audio_settings (WAVEFORMATEX *wfx, wfx->cbSize = 0; switch (as->fmt) { - case AUD_FMT_S8: - case AUD_FMT_U8: + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: wfx->wBitsPerSample = 8; break; - case AUD_FMT_S16: - case AUD_FMT_U16: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_U16: wfx->wBitsPerSample = 16; wfx->nAvgBytesPerSec <<= 1; wfx->nBlockAlign <<= 1; break; - case AUD_FMT_S32: - case AUD_FMT_U32: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_U32: wfx->wBitsPerSample = 32; wfx->nAvgBytesPerSec <<= 2; wfx->nBlockAlign <<= 2; @@ -85,15 +85,15 @@ int waveformat_to_audio_settings (WAVEFORMATEX *wfx, switch (wfx->wBitsPerSample) { case 8: - as->fmt = AUD_FMT_U8; + as->fmt = AUDIO_FORMAT_U8; break; case 16: - as->fmt = AUD_FMT_S16; + as->fmt = AUDIO_FORMAT_S16; break; case 32: - as->fmt = AUD_FMT_S32; + as->fmt = AUDIO_FORMAT_S32; break; default: diff --git a/audio/coreaudio.c b/audio/coreaudio.c index 638c60b300..1ee43b7d5f 100644 --- a/audio/coreaudio.c +++ b/audio/coreaudio.c @@ -36,11 +36,6 @@ #define MAC_OS_X_VERSION_10_6 1060 #endif -typedef struct { - int buffer_frames; - int nbuffers; -} CoreaudioConf; - typedef struct coreaudioVoiceOut { HWVoiceOut hw; pthread_mutex_t mutex; @@ -507,7 +502,9 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, int err; const char *typ = "playback"; AudioValueRange frameRange; - CoreaudioConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; + AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; + int frames; /* create mutex */ err = pthread_mutex_init(&core->mutex, NULL); @@ -538,16 +535,17 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, return -1; } - if (frameRange.mMinimum > conf->buffer_frames) { + frames = audio_buffer_frames( + qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); + if (frameRange.mMinimum > frames) { core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); - } - else if (frameRange.mMaximum < conf->buffer_frames) { + } else if (frameRange.mMaximum < frames) { core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); } else { - core->audioDevicePropertyBufferFrameSize = conf->buffer_frames; + core->audioDevicePropertyBufferFrameSize = frames; } /* set Buffer Frame Size */ @@ -568,7 +566,8 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, "Could not get device buffer frame size\n"); return -1; } - hw->samples = conf->nbuffers * core->audioDevicePropertyBufferFrameSize; + hw->samples = (cpdo->has_buffer_count ? cpdo->buffer_count : 4) * + core->audioDevicePropertyBufferFrameSize; /* get StreamFormat */ status = coreaudio_get_streamformat(core->outputDeviceID, @@ -680,40 +679,15 @@ static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) return 0; } -static CoreaudioConf glob_conf = { - .buffer_frames = 512, - .nbuffers = 4, -}; - -static void *coreaudio_audio_init (void) +static void *coreaudio_audio_init(Audiodev *dev) { - CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf)); - *conf = glob_conf; - - return conf; + return dev; } static void coreaudio_audio_fini (void *opaque) { - g_free(opaque); } -static struct audio_option coreaudio_options[] = { - { - .name = "BUFFER_SIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.buffer_frames, - .descr = "Size of the buffer in frames" - }, - { - .name = "BUFFER_COUNT", - .tag = AUD_OPT_INT, - .valp = &glob_conf.nbuffers, - .descr = "Number of buffers" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops coreaudio_pcm_ops = { .init_out = coreaudio_init_out, .fini_out = coreaudio_fini_out, @@ -725,7 +699,6 @@ static struct audio_pcm_ops coreaudio_pcm_ops = { static struct audio_driver coreaudio_audio_driver = { .name = "coreaudio", .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", - .options = coreaudio_options, .init = coreaudio_audio_init, .fini = coreaudio_audio_fini, .pcm_ops = &coreaudio_pcm_ops, diff --git a/audio/dsound_template.h b/audio/dsound_template.h index b439f33f58..8ece870c9e 100644 --- a/audio/dsound_template.h +++ b/audio/dsound_template.h @@ -167,17 +167,18 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, dsound *s = drv_opaque; WAVEFORMATEX wfx; struct audsettings obt_as; - DSoundConf *conf = &s->conf; #ifdef DSBTYPE_IN const char *typ = "ADC"; DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; DSCBUFFERDESC bd; DSCBCAPS bc; + AudiodevPerDirectionOptions *pdo = s->dev->u.dsound.in; #else const char *typ = "DAC"; DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; DSBUFFERDESC bd; DSBCAPS bc; + AudiodevPerDirectionOptions *pdo = s->dev->u.dsound.out; #endif if (!s->FIELD2) { @@ -193,8 +194,8 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, memset (&bd, 0, sizeof (bd)); bd.dwSize = sizeof (bd); bd.lpwfxFormat = &wfx; + bd.dwBufferBytes = audio_buffer_bytes(pdo, as, 92880); #ifdef DSBTYPE_IN - bd.dwBufferBytes = conf->bufsize_in; hr = IDirectSoundCapture_CreateCaptureBuffer ( s->dsound_capture, &bd, @@ -203,7 +204,6 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, ); #else bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2; - bd.dwBufferBytes = conf->bufsize_out; hr = IDirectSound_CreateSoundBuffer ( s->dsound, &bd, diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c index 3ed73a30d1..a7d04b5033 100644 --- a/audio/dsoundaudio.c +++ b/audio/dsoundaudio.c @@ -32,6 +32,7 @@ #define AUDIO_CAP "dsound" #include "audio_int.h" +#include "qemu/host-utils.h" #include <windows.h> #include <mmsystem.h> @@ -43,16 +44,10 @@ /* #define DEBUG_DSOUND */ typedef struct { - int bufsize_in; - int bufsize_out; - int latency_millis; -} DSoundConf; - -typedef struct { LPDIRECTSOUND dsound; LPDIRECTSOUNDCAPTURE dsound_capture; struct audsettings settings; - DSoundConf conf; + Audiodev *dev; } dsound; typedef struct { @@ -248,9 +243,9 @@ static void GCC_FMT_ATTR (3, 4) dsound_logerr2 ( dsound_log_hresult (hr); } -static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis) +static uint64_t usecs_to_bytes(struct audio_pcm_info *info, uint32_t usecs) { - return (millis * info->bytes_per_second) / 1000; + return muldiv64(usecs, info->bytes_per_second, 1000000); } #ifdef DEBUG_DSOUND @@ -478,7 +473,7 @@ static int dsound_run_out (HWVoiceOut *hw, int live) LPVOID p1, p2; int bufsize; dsound *s = ds->s; - DSoundConf *conf = &s->conf; + AudiodevDsoundOptions *dso = &s->dev->u.dsound; if (!dsb) { dolog ("Attempt to run empty with playback buffer\n"); @@ -501,14 +496,14 @@ static int dsound_run_out (HWVoiceOut *hw, int live) len = live << hwshift; if (ds->first_time) { - if (conf->latency_millis) { + if (dso->latency) { DWORD cur_blat; cur_blat = audio_ring_dist (wpos, ppos, bufsize); ds->first_time = 0; old_pos = wpos; old_pos += - millis_to_bytes (&hw->info, conf->latency_millis) - cur_blat; + usecs_to_bytes(&hw->info, dso->latency) - cur_blat; old_pos %= bufsize; old_pos &= ~hw->info.align; } @@ -747,12 +742,6 @@ static int dsound_run_in (HWVoiceIn *hw) return decr; } -static DSoundConf glob_conf = { - .bufsize_in = 16384, - .bufsize_out = 16384, - .latency_millis = 10 -}; - static void dsound_audio_fini (void *opaque) { HRESULT hr; @@ -783,13 +772,22 @@ static void dsound_audio_fini (void *opaque) g_free(s); } -static void *dsound_audio_init (void) +static void *dsound_audio_init(Audiodev *dev) { int err; HRESULT hr; dsound *s = g_malloc0(sizeof(dsound)); + AudiodevDsoundOptions *dso; + + assert(dev->driver == AUDIODEV_DRIVER_DSOUND); + s->dev = dev; + dso = &dev->u.dsound; + + if (!dso->has_latency) { + dso->has_latency = true; + dso->latency = 10000; /* 10 ms */ + } - s->conf = glob_conf; hr = CoInitialize (NULL); if (FAILED (hr)) { dsound_logerr (hr, "Could not initialize COM\n"); @@ -854,28 +852,6 @@ static void *dsound_audio_init (void) return s; } -static struct audio_option dsound_options[] = { - { - .name = "LATENCY_MILLIS", - .tag = AUD_OPT_INT, - .valp = &glob_conf.latency_millis, - .descr = "(undocumented)" - }, - { - .name = "BUFSIZE_OUT", - .tag = AUD_OPT_INT, - .valp = &glob_conf.bufsize_out, - .descr = "(undocumented)" - }, - { - .name = "BUFSIZE_IN", - .tag = AUD_OPT_INT, - .valp = &glob_conf.bufsize_in, - .descr = "(undocumented)" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops dsound_pcm_ops = { .init_out = dsound_init_out, .fini_out = dsound_fini_out, @@ -893,7 +869,6 @@ static struct audio_pcm_ops dsound_pcm_ops = { static struct audio_driver dsound_audio_driver = { .name = "dsound", .descr = "DirectSound http://wikipedia.org/wiki/DirectSound", - .options = dsound_options, .init = dsound_audio_init, .fini = dsound_audio_fini, .pcm_ops = &dsound_pcm_ops, diff --git a/audio/noaudio.c b/audio/noaudio.c index 1bfebeca7d..ccc611fc84 100644 --- a/audio/noaudio.c +++ b/audio/noaudio.c @@ -136,7 +136,7 @@ static int no_ctl_in (HWVoiceIn *hw, int cmd, ...) return 0; } -static void *no_audio_init (void) +static void *no_audio_init(Audiodev *dev) { return &no_audio_init; } @@ -163,7 +163,6 @@ static struct audio_pcm_ops no_pcm_ops = { static struct audio_driver no_audio_driver = { .name = "none", .descr = "Timer based audio emulation", - .options = NULL, .init = no_audio_init, .fini = no_audio_fini, .pcm_ops = &no_pcm_ops, diff --git a/audio/ossaudio.c b/audio/ossaudio.c index 6c69622b4c..fc28981a39 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -37,16 +37,6 @@ #define USE_DSP_POLICY #endif -typedef struct OSSConf { - int try_mmap; - int nfrags; - int fragsize; - const char *devpath_out; - const char *devpath_in; - int exclusive; - int policy; -} OSSConf; - typedef struct OSSVoiceOut { HWVoiceOut hw; void *pcm_buf; @@ -56,7 +46,7 @@ typedef struct OSSVoiceOut { int fragsize; int mmapped; int pending; - OSSConf *conf; + Audiodev *dev; } OSSVoiceOut; typedef struct OSSVoiceIn { @@ -65,12 +55,12 @@ typedef struct OSSVoiceIn { int fd; int nfrags; int fragsize; - OSSConf *conf; + Audiodev *dev; } OSSVoiceIn; struct oss_params { int freq; - audfmt_e fmt; + int fmt; int nchannels; int nfrags; int fragsize; @@ -148,16 +138,16 @@ static int oss_write (SWVoiceOut *sw, void *buf, int len) return audio_pcm_sw_write (sw, buf, len); } -static int aud_to_ossfmt (audfmt_e fmt, int endianness) +static int aud_to_ossfmt (AudioFormat fmt, int endianness) { switch (fmt) { - case AUD_FMT_S8: + case AUDIO_FORMAT_S8: return AFMT_S8; - case AUD_FMT_U8: + case AUDIO_FORMAT_U8: return AFMT_U8; - case AUD_FMT_S16: + case AUDIO_FORMAT_S16: if (endianness) { return AFMT_S16_BE; } @@ -165,7 +155,7 @@ static int aud_to_ossfmt (audfmt_e fmt, int endianness) return AFMT_S16_LE; } - case AUD_FMT_U16: + case AUDIO_FORMAT_U16: if (endianness) { return AFMT_U16_BE; } @@ -182,37 +172,37 @@ static int aud_to_ossfmt (audfmt_e fmt, int endianness) } } -static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness) +static int oss_to_audfmt (int ossfmt, AudioFormat *fmt, int *endianness) { switch (ossfmt) { case AFMT_S8: *endianness = 0; - *fmt = AUD_FMT_S8; + *fmt = AUDIO_FORMAT_S8; break; case AFMT_U8: *endianness = 0; - *fmt = AUD_FMT_U8; + *fmt = AUDIO_FORMAT_U8; break; case AFMT_S16_LE: *endianness = 0; - *fmt = AUD_FMT_S16; + *fmt = AUDIO_FORMAT_S16; break; case AFMT_U16_LE: *endianness = 0; - *fmt = AUD_FMT_U16; + *fmt = AUDIO_FORMAT_U16; break; case AFMT_S16_BE: *endianness = 1; - *fmt = AUD_FMT_S16; + *fmt = AUDIO_FORMAT_S16; break; case AFMT_U16_BE: *endianness = 1; - *fmt = AUD_FMT_U16; + *fmt = AUDIO_FORMAT_U16; break; default: @@ -262,19 +252,25 @@ static int oss_get_version (int fd, int *version, const char *typ) } #endif -static int oss_open (int in, struct oss_params *req, - struct oss_params *obt, int *pfd, OSSConf* conf) +static int oss_open(int in, struct oss_params *req, audsettings *as, + struct oss_params *obt, int *pfd, Audiodev *dev) { + AudiodevOssOptions *oopts = &dev->u.oss; + AudiodevOssPerDirectionOptions *opdo = in ? oopts->in : oopts->out; int fd; - int oflags = conf->exclusive ? O_EXCL : 0; + int oflags = (oopts->has_exclusive && oopts->exclusive) ? O_EXCL : 0; audio_buf_info abinfo; int fmt, freq, nchannels; int setfragment = 1; - const char *dspname = in ? conf->devpath_in : conf->devpath_out; + const char *dspname = opdo->has_dev ? opdo->dev : "/dev/dsp"; const char *typ = in ? "ADC" : "DAC"; +#ifdef USE_DSP_POLICY + int policy = oopts->has_dsp_policy ? oopts->dsp_policy : 5; +#endif /* Kludge needed to have working mmap on Linux */ - oflags |= conf->try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY); + oflags |= (oopts->has_try_mmap && oopts->try_mmap) ? + O_RDWR : (in ? O_RDONLY : O_WRONLY); fd = open (dspname, oflags | O_NONBLOCK); if (-1 == fd) { @@ -285,6 +281,9 @@ static int oss_open (int in, struct oss_params *req, freq = req->freq; nchannels = req->nchannels; fmt = req->fmt; + req->nfrags = opdo->has_buffer_count ? opdo->buffer_count : 4; + req->fragsize = audio_buffer_bytes( + qapi_AudiodevOssPerDirectionOptions_base(opdo), as, 23220); if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt); @@ -308,18 +307,18 @@ static int oss_open (int in, struct oss_params *req, } #ifdef USE_DSP_POLICY - if (conf->policy >= 0) { + if (policy >= 0) { int version; if (!oss_get_version (fd, &version, typ)) { trace_oss_version(version); if (version >= 0x040000) { - int policy = conf->policy; - if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) { + int policy2 = policy; + if (ioctl(fd, SNDCTL_DSP_POLICY, &policy2)) { oss_logerr2 (errno, typ, "Failed to set timing policy to %d\n", - conf->policy); + policy); goto err; } setfragment = 0; @@ -500,19 +499,18 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, int endianness; int err; int fd; - audfmt_e effective_fmt; + AudioFormat effective_fmt; struct audsettings obt_as; - OSSConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; + AudiodevOssOptions *oopts = &dev->u.oss; oss->fd = -1; req.fmt = aud_to_ossfmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.fragsize = conf->fragsize; - req.nfrags = conf->nfrags; - if (oss_open (0, &req, &obt, &fd, conf)) { + if (oss_open(0, &req, as, &obt, &fd, dev)) { return -1; } @@ -539,7 +537,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; oss->mmapped = 0; - if (conf->try_mmap) { + if (oopts->has_try_mmap && oopts->try_mmap) { oss->pcm_buf = mmap ( NULL, hw->samples << hw->info.shift, @@ -597,7 +595,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, } oss->fd = fd; - oss->conf = conf; + oss->dev = dev; return 0; } @@ -605,16 +603,12 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) { int trig; OSSVoiceOut *oss = (OSSVoiceOut *) hw; + AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = opdo->try_poll; ldebug ("enabling voice\n"); if (poll_mode) { @@ -667,18 +661,16 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) int endianness; int err; int fd; - audfmt_e effective_fmt; + AudioFormat effective_fmt; struct audsettings obt_as; - OSSConf *conf = drv_opaque; + Audiodev *dev = drv_opaque; oss->fd = -1; req.fmt = aud_to_ossfmt (as->fmt, as->endianness); req.freq = as->freq; req.nchannels = as->nchannels; - req.fragsize = conf->fragsize; - req.nfrags = conf->nfrags; - if (oss_open (1, &req, &obt, &fd, conf)) { + if (oss_open(1, &req, as, &obt, &fd, dev)) { return -1; } @@ -712,7 +704,7 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } oss->fd = fd; - oss->conf = conf; + oss->dev = dev; return 0; } @@ -803,16 +795,12 @@ static int oss_read (SWVoiceIn *sw, void *buf, int size) static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) { OSSVoiceIn *oss = (OSSVoiceIn *) hw; + AudiodevOssPerDirectionOptions *opdo = oss->dev->u.oss.out; switch (cmd) { case VOICE_ENABLE: { - va_list ap; - int poll_mode; - - va_start (ap, cmd); - poll_mode = va_arg (ap, int); - va_end (ap); + bool poll_mode = opdo->try_poll; if (poll_mode) { oss_poll_in (hw); @@ -832,82 +820,36 @@ static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) return 0; } -static OSSConf glob_conf = { - .try_mmap = 0, - .nfrags = 4, - .fragsize = 4096, - .devpath_out = "/dev/dsp", - .devpath_in = "/dev/dsp", - .exclusive = 0, - .policy = 5 -}; +static void oss_init_per_direction(AudiodevOssPerDirectionOptions *opdo) +{ + if (!opdo->has_try_poll) { + opdo->try_poll = true; + opdo->has_try_poll = true; + } +} -static void *oss_audio_init (void) +static void *oss_audio_init(Audiodev *dev) { - OSSConf *conf = g_malloc(sizeof(OSSConf)); - *conf = glob_conf; + AudiodevOssOptions *oopts; + assert(dev->driver == AUDIODEV_DRIVER_OSS); + + oopts = &dev->u.oss; + oss_init_per_direction(oopts->in); + oss_init_per_direction(oopts->out); - if (access(conf->devpath_in, R_OK | W_OK) < 0 || - access(conf->devpath_out, R_OK | W_OK) < 0) { - g_free(conf); + if (access(oopts->in->has_dev ? oopts->in->dev : "/dev/dsp", + R_OK | W_OK) < 0 || + access(oopts->out->has_dev ? oopts->out->dev : "/dev/dsp", + R_OK | W_OK) < 0) { return NULL; } - return conf; + return dev; } static void oss_audio_fini (void *opaque) { - g_free(opaque); } -static struct audio_option oss_options[] = { - { - .name = "FRAGSIZE", - .tag = AUD_OPT_INT, - .valp = &glob_conf.fragsize, - .descr = "Fragment size in bytes" - }, - { - .name = "NFRAGS", - .tag = AUD_OPT_INT, - .valp = &glob_conf.nfrags, - .descr = "Number of fragments" - }, - { - .name = "MMAP", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.try_mmap, - .descr = "Try using memory mapped access" - }, - { - .name = "DAC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.devpath_out, - .descr = "Path to DAC device" - }, - { - .name = "ADC_DEV", - .tag = AUD_OPT_STR, - .valp = &glob_conf.devpath_in, - .descr = "Path to ADC device" - }, - { - .name = "EXCLUSIVE", - .tag = AUD_OPT_BOOL, - .valp = &glob_conf.exclusive, - .descr = "Open device in exclusive mode (vmix won't work)" - }, -#ifdef USE_DSP_POLICY - { - .name = "POLICY", - .tag = AUD_OPT_INT, - .valp = &glob_conf.policy, - .descr = "Set the timing policy of the device, -1 to use fragment mode", - }, -#endif - { /* End of list */ } -}; - static struct audio_pcm_ops oss_pcm_ops = { .init_out = oss_init_out, .fini_out = oss_fini_out, @@ -925,7 +867,6 @@ static struct audio_pcm_ops oss_pcm_ops = { static struct audio_driver oss_audio_driver = { .name = "oss", .descr = "OSS http://www.opensound.com", - .options = oss_options, .init = oss_audio_init, .fini = oss_audio_fini, .pcm_ops = &oss_pcm_ops, diff --git a/audio/paaudio.c b/audio/paaudio.c index 6153b908da..5d410ed73f 100644 --- a/audio/paaudio.c +++ b/audio/paaudio.c @@ -2,6 +2,7 @@ #include "qemu/osdep.h" #include "qemu-common.h" #include "audio.h" +#include "qapi/opts-visitor.h" #include <pulse/pulseaudio.h> @@ -10,14 +11,7 @@ #include "audio_pt_int.h" typedef struct { - int samples; - char *server; - char *sink; - char *source; -} PAConf; - -typedef struct { - PAConf conf; + Audiodev *dev; pa_threaded_mainloop *mainloop; pa_context *context; } paaudio; @@ -32,6 +26,7 @@ typedef struct { void *pcm_buf; struct audio_pt pt; paaudio *g; + int samples; } PAVoiceOut; typedef struct { @@ -46,6 +41,7 @@ typedef struct { const void *read_data; size_t read_index, read_length; paaudio *g; + int samples; } PAVoiceIn; static void qpa_audio_fini(void *opaque); @@ -227,7 +223,7 @@ static void *qpa_thread_out (void *arg) } } - decr = to_mix = audio_MIN(pa->live, pa->g->conf.samples >> 5); + decr = to_mix = audio_MIN(pa->live, pa->samples >> 5); rpos = pa->rpos; if (audio_pt_unlock(&pa->pt, __func__)) { @@ -319,7 +315,7 @@ static void *qpa_thread_in (void *arg) } } - incr = to_grab = audio_MIN(pa->dead, pa->g->conf.samples >> 5); + incr = to_grab = audio_MIN(pa->dead, pa->samples >> 5); wpos = pa->wpos; if (audio_pt_unlock(&pa->pt, __func__)) { @@ -385,21 +381,21 @@ static int qpa_read (SWVoiceIn *sw, void *buf, int len) return audio_pcm_sw_read (sw, buf, len); } -static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) +static pa_sample_format_t audfmt_to_pa (AudioFormat afmt, int endianness) { int format; switch (afmt) { - case AUD_FMT_S8: - case AUD_FMT_U8: + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: format = PA_SAMPLE_U8; break; - case AUD_FMT_S16: - case AUD_FMT_U16: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_U16: format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; break; - case AUD_FMT_S32: - case AUD_FMT_U32: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_U32: format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; break; default: @@ -410,26 +406,26 @@ static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) return format; } -static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness) +static AudioFormat pa_to_audfmt (pa_sample_format_t fmt, int *endianness) { switch (fmt) { case PA_SAMPLE_U8: - return AUD_FMT_U8; + return AUDIO_FORMAT_U8; case PA_SAMPLE_S16BE: *endianness = 1; - return AUD_FMT_S16; + return AUDIO_FORMAT_S16; case PA_SAMPLE_S16LE: *endianness = 0; - return AUD_FMT_S16; + return AUDIO_FORMAT_S16; case PA_SAMPLE_S32BE: *endianness = 1; - return AUD_FMT_S32; + return AUDIO_FORMAT_S32; case PA_SAMPLE_S32LE: *endianness = 0; - return AUD_FMT_S32; + return AUDIO_FORMAT_S32; default: dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); - return AUD_FMT_U8; + return AUDIO_FORMAT_U8; } } @@ -546,6 +542,8 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, struct audsettings obt_as = *as; PAVoiceOut *pa = (PAVoiceOut *) hw; paaudio *g = pa->g = drv_opaque; + AudiodevPaOptions *popts = &g->dev->u.pa; + AudiodevPaPerDirectionOptions *ppdo = popts->out; ss.format = audfmt_to_pa (as->fmt, as->endianness); ss.channels = as->nchannels; @@ -566,7 +564,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, g, "qemu", PA_STREAM_PLAYBACK, - g->conf.sink, + ppdo->has_name ? ppdo->name : NULL, &ss, NULL, /* channel map */ &ba, /* buffering attributes */ @@ -578,7 +576,8 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, } audio_pcm_init_info (&hw->info, &obt_as); - hw->samples = g->conf.samples; + hw->samples = pa->samples = audio_buffer_samples( + qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440); pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); pa->rpos = hw->rpos; if (!pa->pcm_buf) { @@ -612,6 +611,8 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) struct audsettings obt_as = *as; PAVoiceIn *pa = (PAVoiceIn *) hw; paaudio *g = pa->g = drv_opaque; + AudiodevPaOptions *popts = &g->dev->u.pa; + AudiodevPaPerDirectionOptions *ppdo = popts->in; ss.format = audfmt_to_pa (as->fmt, as->endianness); ss.channels = as->nchannels; @@ -623,7 +624,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) g, "qemu", PA_STREAM_RECORD, - g->conf.source, + ppdo->has_name ? ppdo->name : NULL, &ss, NULL, /* channel map */ NULL, /* buffering attributes */ @@ -635,7 +636,8 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) } audio_pcm_init_info (&hw->info, &obt_as); - hw->samples = g->conf.samples; + hw->samples = pa->samples = audio_buffer_samples( + qapi_AudiodevPaPerDirectionOptions_base(ppdo), &obt_as, 46440); pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); pa->wpos = hw->wpos; if (!pa->pcm_buf) { @@ -808,13 +810,13 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) } /* common */ -static PAConf glob_conf = { - .samples = 4096, -}; - -static void *qpa_audio_init (void) +static void *qpa_audio_init(Audiodev *dev) { - if (glob_conf.server == NULL) { + paaudio *g; + AudiodevPaOptions *popts = &dev->u.pa; + const char *server; + + if (!popts->has_server) { char pidfile[64]; char *runtime; struct stat st; @@ -829,8 +831,12 @@ static void *qpa_audio_init (void) } } - paaudio *g = g_malloc(sizeof(paaudio)); - g->conf = glob_conf; + assert(dev->driver == AUDIODEV_DRIVER_PA); + + g = g_malloc(sizeof(paaudio)); + server = popts->has_server ? popts->server : NULL; + + g->dev = dev; g->mainloop = NULL; g->context = NULL; @@ -840,14 +846,14 @@ static void *qpa_audio_init (void) } g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop), - g->conf.server); + server); if (!g->context) { goto fail; } pa_context_set_state_callback (g->context, context_state_cb, g); - if (pa_context_connect (g->context, g->conf.server, 0, NULL) < 0) { + if (pa_context_connect(g->context, server, 0, NULL) < 0) { qpa_logerr (pa_context_errno (g->context), "pa_context_connect() failed\n"); goto fail; @@ -910,34 +916,6 @@ static void qpa_audio_fini (void *opaque) g_free(g); } -struct audio_option qpa_options[] = { - { - .name = "SAMPLES", - .tag = AUD_OPT_INT, - .valp = &glob_conf.samples, - .descr = "buffer size in samples" - }, - { - .name = "SERVER", - .tag = AUD_OPT_STR, - .valp = &glob_conf.server, - .descr = "server address" - }, - { - .name = "SINK", - .tag = AUD_OPT_STR, - .valp = &glob_conf.sink, - .descr = "sink device name" - }, - { - .name = "SOURCE", - .tag = AUD_OPT_STR, - .valp = &glob_conf.source, - .descr = "source device name" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops qpa_pcm_ops = { .init_out = qpa_init_out, .fini_out = qpa_fini_out, @@ -955,7 +933,6 @@ static struct audio_pcm_ops qpa_pcm_ops = { static struct audio_driver pa_audio_driver = { .name = "pa", .descr = "http://www.pulseaudio.org/", - .options = qpa_options, .init = qpa_audio_init, .fini = qpa_audio_fini, .pcm_ops = &qpa_pcm_ops, diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c index f7ee70b153..ff9248ba68 100644 --- a/audio/sdlaudio.c +++ b/audio/sdlaudio.c @@ -44,16 +44,11 @@ typedef struct SDLVoiceOut { int decr; } SDLVoiceOut; -static struct { - int nb_samples; -} conf = { - .nb_samples = 1024 -}; - static struct SDLAudioState { int exit; int initialized; bool driver_created; + Audiodev *dev; } glob_sdl; typedef struct SDLAudioState SDLAudioState; @@ -68,19 +63,19 @@ static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); } -static int aud_to_sdlfmt (audfmt_e fmt) +static int aud_to_sdlfmt (AudioFormat fmt) { switch (fmt) { - case AUD_FMT_S8: + case AUDIO_FORMAT_S8: return AUDIO_S8; - case AUD_FMT_U8: + case AUDIO_FORMAT_U8: return AUDIO_U8; - case AUD_FMT_S16: + case AUDIO_FORMAT_S16: return AUDIO_S16LSB; - case AUD_FMT_U16: + case AUDIO_FORMAT_U16: return AUDIO_U16LSB; default: @@ -92,37 +87,37 @@ static int aud_to_sdlfmt (audfmt_e fmt) } } -static int sdl_to_audfmt(int sdlfmt, audfmt_e *fmt, int *endianness) +static int sdl_to_audfmt(int sdlfmt, AudioFormat *fmt, int *endianness) { switch (sdlfmt) { case AUDIO_S8: *endianness = 0; - *fmt = AUD_FMT_S8; + *fmt = AUDIO_FORMAT_S8; break; case AUDIO_U8: *endianness = 0; - *fmt = AUD_FMT_U8; + *fmt = AUDIO_FORMAT_U8; break; case AUDIO_S16LSB: *endianness = 0; - *fmt = AUD_FMT_S16; + *fmt = AUDIO_FORMAT_S16; break; case AUDIO_U16LSB: *endianness = 0; - *fmt = AUD_FMT_U16; + *fmt = AUDIO_FORMAT_U16; break; case AUDIO_S16MSB: *endianness = 1; - *fmt = AUD_FMT_S16; + *fmt = AUDIO_FORMAT_S16; break; case AUDIO_U16MSB: *endianness = 1; - *fmt = AUD_FMT_U16; + *fmt = AUDIO_FORMAT_U16; break; default: @@ -265,13 +260,13 @@ static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as, SDL_AudioSpec req, obt; int endianness; int err; - audfmt_e effective_fmt; + AudioFormat effective_fmt; struct audsettings obt_as; req.freq = as->freq; req.format = aud_to_sdlfmt (as->fmt); req.channels = as->nchannels; - req.samples = conf.nb_samples; + req.samples = audio_buffer_samples(s->dev->u.sdl.out, as, 11610); req.callback = sdl_callback; req.userdata = sdl; @@ -315,7 +310,7 @@ static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) return 0; } -static void *sdl_audio_init (void) +static void *sdl_audio_init(Audiodev *dev) { SDLAudioState *s = &glob_sdl; if (s->driver_created) { @@ -329,6 +324,7 @@ static void *sdl_audio_init (void) } s->driver_created = true; + s->dev = dev; return s; } @@ -338,18 +334,9 @@ static void sdl_audio_fini (void *opaque) sdl_close (s); SDL_QuitSubSystem (SDL_INIT_AUDIO); s->driver_created = false; + s->dev = NULL; } -static struct audio_option sdl_options[] = { - { - .name = "SAMPLES", - .tag = AUD_OPT_INT, - .valp = &conf.nb_samples, - .descr = "Size of SDL buffer in samples" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops sdl_pcm_ops = { .init_out = sdl_init_out, .fini_out = sdl_fini_out, @@ -361,7 +348,6 @@ static struct audio_pcm_ops sdl_pcm_ops = { static struct audio_driver sdl_audio_driver = { .name = "sdl", .descr = "SDL http://www.libsdl.org", - .options = sdl_options, .init = sdl_audio_init, .fini = sdl_audio_fini, .pcm_ops = &sdl_pcm_ops, diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c index 6ad0eafbc6..4f7873af5a 100644 --- a/audio/spiceaudio.c +++ b/audio/spiceaudio.c @@ -77,7 +77,7 @@ static const SpiceRecordInterface record_sif = { .base.minor_version = SPICE_INTERFACE_RECORD_MINOR, }; -static void *spice_audio_init (void) +static void *spice_audio_init(Audiodev *dev) { if (!using_spice) { return NULL; @@ -130,7 +130,7 @@ static int line_out_init(HWVoiceOut *hw, struct audsettings *as, settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ; #endif settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN; - settings.fmt = AUD_FMT_S16; + settings.fmt = AUDIO_FORMAT_S16; settings.endianness = AUDIO_HOST_ENDIANNESS; audio_pcm_init_info (&hw->info, &settings); @@ -258,7 +258,7 @@ static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) settings.freq = SPICE_INTERFACE_RECORD_FREQ; #endif settings.nchannels = SPICE_INTERFACE_RECORD_CHAN; - settings.fmt = AUD_FMT_S16; + settings.fmt = AUDIO_FORMAT_S16; settings.endianness = AUDIO_HOST_ENDIANNESS; audio_pcm_init_info (&hw->info, &settings); @@ -373,10 +373,6 @@ static int line_in_ctl (HWVoiceIn *hw, int cmd, ...) return 0; } -static struct audio_option audio_options[] = { - { /* end of list */ }, -}; - static struct audio_pcm_ops audio_callbacks = { .init_out = line_out_init, .fini_out = line_out_fini, @@ -394,7 +390,6 @@ static struct audio_pcm_ops audio_callbacks = { static struct audio_driver spice_audio_driver = { .name = "spice", .descr = "spice audio driver", - .options = audio_options, .init = spice_audio_init, .fini = spice_audio_fini, .pcm_ops = &audio_callbacks, diff --git a/audio/wavaudio.c b/audio/wavaudio.c index 40adfa30c3..8d30f57296 100644 --- a/audio/wavaudio.c +++ b/audio/wavaudio.c @@ -24,6 +24,7 @@ #include "qemu/osdep.h" #include "qemu/host-utils.h" #include "qemu/timer.h" +#include "qapi/opts-visitor.h" #include "audio.h" #define AUDIO_CAP "wav" @@ -37,11 +38,6 @@ typedef struct WAVVoiceOut { int total_samples; } WAVVoiceOut; -typedef struct { - struct audsettings settings; - const char *wav_path; -} WAVConf; - static int wav_run_out (HWVoiceOut *hw, int live) { WAVVoiceOut *wav = (WAVVoiceOut *) hw; @@ -112,25 +108,30 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 }; - WAVConf *conf = drv_opaque; - struct audsettings wav_as = conf->settings; + Audiodev *dev = drv_opaque; + AudiodevWavOptions *wopts = &dev->u.wav; + struct audsettings wav_as = audiodev_to_audsettings(dev->u.wav.out); + const char *wav_path = wopts->has_path ? wopts->path : "qemu.wav"; stereo = wav_as.nchannels == 2; switch (wav_as.fmt) { - case AUD_FMT_S8: - case AUD_FMT_U8: + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: bits16 = 0; break; - case AUD_FMT_S16: - case AUD_FMT_U16: + case AUDIO_FORMAT_S16: + case AUDIO_FORMAT_U16: bits16 = 1; break; - case AUD_FMT_S32: - case AUD_FMT_U32: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_U32: dolog ("WAVE files can not handle 32bit formats\n"); return -1; + + default: + abort(); } hdr[34] = bits16 ? 0x10 : 0x08; @@ -151,10 +152,10 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as, le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4); le_store (hdr + 32, 1 << (bits16 + stereo), 2); - wav->f = fopen (conf->wav_path, "wb"); + wav->f = fopen(wav_path, "wb"); if (!wav->f) { dolog ("Failed to open wave file `%s'\nReason: %s\n", - conf->wav_path, strerror (errno)); + wav_path, strerror(errno)); g_free (wav->pcm_buf); wav->pcm_buf = NULL; return -1; @@ -222,54 +223,17 @@ static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) return 0; } -static WAVConf glob_conf = { - .settings.freq = 44100, - .settings.nchannels = 2, - .settings.fmt = AUD_FMT_S16, - .wav_path = "qemu.wav" -}; - -static void *wav_audio_init (void) +static void *wav_audio_init(Audiodev *dev) { - WAVConf *conf = g_malloc(sizeof(WAVConf)); - *conf = glob_conf; - return conf; + assert(dev->driver == AUDIODEV_DRIVER_WAV); + return dev; } static void wav_audio_fini (void *opaque) { ldebug ("wav_fini"); - g_free(opaque); } -static struct audio_option wav_options[] = { - { - .name = "FREQUENCY", - .tag = AUD_OPT_INT, - .valp = &glob_conf.settings.freq, - .descr = "Frequency" - }, - { - .name = "FORMAT", - .tag = AUD_OPT_FMT, - .valp = &glob_conf.settings.fmt, - .descr = "Format" - }, - { - .name = "DAC_FIXED_CHANNELS", - .tag = AUD_OPT_INT, - .valp = &glob_conf.settings.nchannels, - .descr = "Number of channels (1 - mono, 2 - stereo)" - }, - { - .name = "PATH", - .tag = AUD_OPT_STR, - .valp = &glob_conf.wav_path, - .descr = "Path to wave file" - }, - { /* End of list */ } -}; - static struct audio_pcm_ops wav_pcm_ops = { .init_out = wav_init_out, .fini_out = wav_fini_out, @@ -281,7 +245,6 @@ static struct audio_pcm_ops wav_pcm_ops = { static struct audio_driver wav_audio_driver = { .name = "wav", .descr = "WAV renderer http://wikipedia.org/wiki/WAV", - .options = wav_options, .init = wav_audio_init, .fini = wav_audio_fini, .pcm_ops = &wav_pcm_ops, diff --git a/audio/wavcapture.c b/audio/wavcapture.c index cd24570aa7..74320dfecc 100644 --- a/audio/wavcapture.c +++ b/audio/wavcapture.c @@ -136,7 +136,7 @@ int wav_start_capture (CaptureState *s, const char *path, int freq, as.freq = freq; as.nchannels = 1 << stereo; - as.fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8; + as.fmt = bits16 ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_U8; as.endianness = 0; ops.notify = wav_notify; diff --git a/backends/hostmem-file.c b/backends/hostmem-file.c index ce54788048..37ac6445d2 100644 --- a/backends/hostmem-file.c +++ b/backends/hostmem-file.c @@ -56,6 +56,29 @@ file_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) error_setg(errp, "mem-path property not set"); return; } + + /* + * Verify pmem file size since starting a guest with an incorrect size + * leads to confusing failures inside the guest. + */ + if (fb->is_pmem) { + Error *local_err = NULL; + uint64_t size; + + size = qemu_get_pmem_size(fb->mem_path, &local_err); + if (!size) { + error_propagate(errp, local_err); + return; + } + + if (backend->size > size) { + error_setg(errp, "size property %" PRIu64 " is larger than " + "pmem file \"%s\" size %" PRIu64, backend->size, + fb->mem_path, size); + return; + } + } + backend->force_prealloc = mem_prealloc; name = host_memory_backend_get_name(backend); memory_region_init_ram_from_file(&backend->mr, OBJECT(backend), diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c index 98c9bf3240..46b15b916a 100644 --- a/backends/hostmem-memfd.c +++ b/backends/hostmem-memfd.c @@ -154,15 +154,13 @@ memfd_backend_class_init(ObjectClass *oc, void *data) "Huge pages size (ex: 2M, 1G)", &error_abort); } - if (qemu_memfd_check(MFD_ALLOW_SEALING)) { - object_class_property_add_bool(oc, "seal", - memfd_backend_get_seal, - memfd_backend_set_seal, - &error_abort); - object_class_property_set_description(oc, "seal", - "Seal growing & shrinking", - &error_abort); - } + object_class_property_add_bool(oc, "seal", + memfd_backend_get_seal, + memfd_backend_set_seal, + &error_abort); + object_class_property_set_description(oc, "seal", + "Seal growing & shrinking", + &error_abort); } static const TypeInfo memfd_backend_info = { @@ -175,7 +173,7 @@ static const TypeInfo memfd_backend_info = { static void register_types(void) { - if (qemu_memfd_check(0)) { + if (qemu_memfd_check(MFD_ALLOW_SEALING)) { type_register_static(&memfd_backend_info); } } @@ -1163,13 +1163,6 @@ static int bdrv_open_flags(BlockDriverState *bs, int flags) */ open_flags &= ~(BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING | BDRV_O_PROTOCOL); - /* - * Snapshots should be writable. - */ - if (flags & BDRV_O_TEMPORARY) { - open_flags |= BDRV_O_RDWR; - } - return open_flags; } @@ -1698,6 +1691,7 @@ static void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared); typedef struct BlockReopenQueueEntry { bool prepared; + bool perms_checked; BDRVReopenState state; QSIMPLEQ_ENTRY(BlockReopenQueueEntry) entry; } BlockReopenQueueEntry; @@ -2132,6 +2126,8 @@ static void bdrv_replace_child_noperm(BdrvChild *child, BlockDriverState *old_bs = child->bs; int i; + assert(!child->frozen); + if (old_bs && new_bs) { assert(bdrv_get_aio_context(old_bs) == bdrv_get_aio_context(new_bs)); } @@ -2348,6 +2344,10 @@ void bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd, bool update_inherits_from = bdrv_chain_contains(bs, backing_hd) && bdrv_inherits_from_recursive(backing_hd, bs); + if (bdrv_is_backing_chain_frozen(bs, backing_bs(bs), errp)) { + return; + } + if (backing_hd) { bdrv_ref(backing_hd); } @@ -2983,6 +2983,74 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference, NULL, errp); } +/* Return true if the NULL-terminated @list contains @str */ +static bool is_str_in_list(const char *str, const char *const *list) +{ + if (str && list) { + int i; + for (i = 0; list[i] != NULL; i++) { + if (!strcmp(str, list[i])) { + return true; + } + } + } + return false; +} + +/* + * Check that every option set in @bs->options is also set in + * @new_opts. + * + * Options listed in the common_options list and in + * @bs->drv->mutable_opts are skipped. + * + * Return 0 on success, otherwise return -EINVAL and set @errp. + */ +static int bdrv_reset_options_allowed(BlockDriverState *bs, + const QDict *new_opts, Error **errp) +{ + const QDictEntry *e; + /* These options are common to all block drivers and are handled + * in bdrv_reopen_prepare() so they can be left out of @new_opts */ + const char *const common_options[] = { + "node-name", "discard", "cache.direct", "cache.no-flush", + "read-only", "auto-read-only", "detect-zeroes", NULL + }; + + for (e = qdict_first(bs->options); e; e = qdict_next(bs->options, e)) { + if (!qdict_haskey(new_opts, e->key) && + !is_str_in_list(e->key, common_options) && + !is_str_in_list(e->key, bs->drv->mutable_opts)) { + error_setg(errp, "Option '%s' cannot be reset " + "to its default value", e->key); + return -EINVAL; + } + } + + return 0; +} + +/* + * Returns true if @child can be reached recursively from @bs + */ +static bool bdrv_recurse_has_child(BlockDriverState *bs, + BlockDriverState *child) +{ + BdrvChild *c; + + if (bs == child) { + return true; + } + + QLIST_FOREACH(c, &bs->children, next) { + if (bdrv_recurse_has_child(c->bs, child)) { + return true; + } + } + + return false; +} + /* * Adds a BlockDriverState to a simple queue for an atomic, transactional * reopen of multiple devices. @@ -3010,7 +3078,8 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, QDict *options, const BdrvChildRole *role, QDict *parent_options, - int parent_flags) + int parent_flags, + bool keep_old_opts) { assert(bs != NULL); @@ -3050,13 +3119,13 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, */ /* Old explicitly set values (don't overwrite by inherited value) */ - if (bs_entry) { - old_options = qdict_clone_shallow(bs_entry->state.explicit_options); - } else { - old_options = qdict_clone_shallow(bs->explicit_options); + if (bs_entry || keep_old_opts) { + old_options = qdict_clone_shallow(bs_entry ? + bs_entry->state.explicit_options : + bs->explicit_options); + bdrv_join_options(bs, options, old_options); + qobject_unref(old_options); } - bdrv_join_options(bs, options, old_options); - qobject_unref(old_options); explicit_options = qdict_clone_shallow(options); @@ -3068,10 +3137,12 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, flags = bdrv_get_flags(bs); } - /* Old values are used for options that aren't set yet */ - old_options = qdict_clone_shallow(bs->options); - bdrv_join_options(bs, options, old_options); - qobject_unref(old_options); + if (keep_old_opts) { + /* Old values are used for options that aren't set yet */ + old_options = qdict_clone_shallow(bs->options); + bdrv_join_options(bs, options, old_options); + qobject_unref(old_options); + } /* We have the final set of options so let's update the flags */ options_copy = qdict_clone_shallow(options); @@ -3104,9 +3175,21 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, bs_entry->state.perm = UINT64_MAX; bs_entry->state.shared_perm = 0; + /* + * If keep_old_opts is false then it means that unspecified + * options must be reset to their original value. We don't allow + * resetting 'backing' but we need to know if the option is + * missing in order to decide if we have to return an error. + */ + if (!keep_old_opts) { + bs_entry->state.backing_missing = + !qdict_haskey(options, "backing") && + !qdict_haskey(options, "backing.driver"); + } + QLIST_FOREACH(child, &bs->children, next) { - QDict *new_child_options; - char *child_key_dot; + QDict *new_child_options = NULL; + bool child_keep_old = keep_old_opts; /* reopen can only change the options of block devices that were * implicitly created and inherited options. For other (referenced) @@ -3115,13 +3198,32 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, continue; } - child_key_dot = g_strdup_printf("%s.", child->name); - qdict_extract_subqdict(explicit_options, NULL, child_key_dot); - qdict_extract_subqdict(options, &new_child_options, child_key_dot); - g_free(child_key_dot); + /* Check if the options contain a child reference */ + if (qdict_haskey(options, child->name)) { + const char *childref = qdict_get_try_str(options, child->name); + /* + * The current child must not be reopened if the child + * reference is null or points to a different node. + */ + if (g_strcmp0(childref, child->bs->node_name)) { + continue; + } + /* + * If the child reference points to the current child then + * reopen it with its existing set of options (note that + * it can still inherit new options from the parent). + */ + child_keep_old = true; + } else { + /* Extract child options ("child-name.*") */ + char *child_key_dot = g_strdup_printf("%s.", child->name); + qdict_extract_subqdict(explicit_options, NULL, child_key_dot); + qdict_extract_subqdict(options, &new_child_options, child_key_dot); + g_free(child_key_dot); + } bdrv_reopen_queue_child(bs_queue, child->bs, new_child_options, - child->role, options, flags); + child->role, options, flags, child_keep_old); } return bs_queue; @@ -3129,9 +3231,10 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, BlockDriverState *bs, - QDict *options) + QDict *options, bool keep_old_opts) { - return bdrv_reopen_queue_child(bs_queue, bs, options, NULL, NULL, 0); + return bdrv_reopen_queue_child(bs_queue, bs, options, NULL, NULL, 0, + keep_old_opts); } /* @@ -3151,23 +3254,44 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, * All affected nodes must be drained between bdrv_reopen_queue() and * bdrv_reopen_multiple(). */ -int bdrv_reopen_multiple(AioContext *ctx, BlockReopenQueue *bs_queue, Error **errp) +int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp) { int ret = -1; BlockReopenQueueEntry *bs_entry, *next; - Error *local_err = NULL; assert(bs_queue != NULL); QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) { assert(bs_entry->state.bs->quiesce_counter > 0); - if (bdrv_reopen_prepare(&bs_entry->state, bs_queue, &local_err)) { - error_propagate(errp, local_err); + if (bdrv_reopen_prepare(&bs_entry->state, bs_queue, errp)) { goto cleanup; } bs_entry->prepared = true; } + QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) { + BDRVReopenState *state = &bs_entry->state; + ret = bdrv_check_perm(state->bs, bs_queue, state->perm, + state->shared_perm, NULL, errp); + if (ret < 0) { + goto cleanup_perm; + } + /* Check if new_backing_bs would accept the new permissions */ + if (state->replace_backing_bs && state->new_backing_bs) { + uint64_t nperm, nshared; + bdrv_child_perm(state->bs, state->new_backing_bs, + NULL, &child_backing, bs_queue, + state->perm, state->shared_perm, + &nperm, &nshared); + ret = bdrv_check_update_perm(state->new_backing_bs, NULL, + nperm, nshared, NULL, errp); + if (ret < 0) { + goto cleanup_perm; + } + } + bs_entry->perms_checked = true; + } + /* If we reach this point, we have success and just need to apply the * changes */ @@ -3176,7 +3300,23 @@ int bdrv_reopen_multiple(AioContext *ctx, BlockReopenQueue *bs_queue, Error **er } ret = 0; +cleanup_perm: + QSIMPLEQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) { + BDRVReopenState *state = &bs_entry->state; + + if (!bs_entry->perms_checked) { + continue; + } + if (ret == 0) { + bdrv_set_perm(state->bs, state->perm, state->shared_perm); + } else { + bdrv_abort_perm_update(state->bs); + if (state->replace_backing_bs && state->new_backing_bs) { + bdrv_abort_perm_update(state->new_backing_bs); + } + } + } cleanup: QSIMPLEQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) { if (ret) { @@ -3186,6 +3326,9 @@ cleanup: qobject_unref(bs_entry->state.explicit_options); qobject_unref(bs_entry->state.options); } + if (bs_entry->state.new_backing_bs) { + bdrv_unref(bs_entry->state.new_backing_bs); + } g_free(bs_entry); } g_free(bs_queue); @@ -3203,8 +3346,8 @@ int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only); bdrv_subtree_drained_begin(bs); - queue = bdrv_reopen_queue(NULL, bs, opts); - ret = bdrv_reopen_multiple(bdrv_get_aio_context(bs), queue, errp); + queue = bdrv_reopen_queue(NULL, bs, opts, true); + ret = bdrv_reopen_multiple(queue, errp); bdrv_subtree_drained_end(bs); return ret; @@ -3258,6 +3401,101 @@ static void bdrv_reopen_perm(BlockReopenQueue *q, BlockDriverState *bs, } /* + * Take a BDRVReopenState and check if the value of 'backing' in the + * reopen_state->options QDict is valid or not. + * + * If 'backing' is missing from the QDict then return 0. + * + * If 'backing' contains the node name of the backing file of + * reopen_state->bs then return 0. + * + * If 'backing' contains a different node name (or is null) then check + * whether the current backing file can be replaced with the new one. + * If that's the case then reopen_state->replace_backing_bs is set to + * true and reopen_state->new_backing_bs contains a pointer to the new + * backing BlockDriverState (or NULL). + * + * Return 0 on success, otherwise return < 0 and set @errp. + */ +static int bdrv_reopen_parse_backing(BDRVReopenState *reopen_state, + Error **errp) +{ + BlockDriverState *bs = reopen_state->bs; + BlockDriverState *overlay_bs, *new_backing_bs; + QObject *value; + const char *str; + + value = qdict_get(reopen_state->options, "backing"); + if (value == NULL) { + return 0; + } + + switch (qobject_type(value)) { + case QTYPE_QNULL: + new_backing_bs = NULL; + break; + case QTYPE_QSTRING: + str = qobject_get_try_str(value); + new_backing_bs = bdrv_lookup_bs(NULL, str, errp); + if (new_backing_bs == NULL) { + return -EINVAL; + } else if (bdrv_recurse_has_child(new_backing_bs, bs)) { + error_setg(errp, "Making '%s' a backing file of '%s' " + "would create a cycle", str, bs->node_name); + return -EINVAL; + } + break; + default: + /* 'backing' does not allow any other data type */ + g_assert_not_reached(); + } + + /* + * TODO: before removing the x- prefix from x-blockdev-reopen we + * should move the new backing file into the right AioContext + * instead of returning an error. + */ + if (new_backing_bs) { + if (bdrv_get_aio_context(new_backing_bs) != bdrv_get_aio_context(bs)) { + error_setg(errp, "Cannot use a new backing file " + "with a different AioContext"); + return -EINVAL; + } + } + + /* + * Find the "actual" backing file by skipping all links that point + * to an implicit node, if any (e.g. a commit filter node). + */ + overlay_bs = bs; + while (backing_bs(overlay_bs) && backing_bs(overlay_bs)->implicit) { + overlay_bs = backing_bs(overlay_bs); + } + + /* If we want to replace the backing file we need some extra checks */ + if (new_backing_bs != backing_bs(overlay_bs)) { + /* Check for implicit nodes between bs and its backing file */ + if (bs != overlay_bs) { + error_setg(errp, "Cannot change backing link if '%s' has " + "an implicit backing file", bs->node_name); + return -EPERM; + } + /* Check if the backing link that we want to replace is frozen */ + if (bdrv_is_backing_chain_frozen(overlay_bs, backing_bs(overlay_bs), + errp)) { + return -EPERM; + } + reopen_state->replace_backing_bs = true; + if (new_backing_bs) { + bdrv_ref(new_backing_bs); + reopen_state->new_backing_bs = new_backing_bs; + } + } + + return 0; +} + +/* * Prepares a BlockDriverState for reopen. All changes are staged in the * 'opaque' field of the BDRVReopenState, which is used and allocated by * the block driver layer .bdrv_reopen_prepare() @@ -3355,6 +3593,17 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue, } if (drv->bdrv_reopen_prepare) { + /* + * If a driver-specific option is missing, it means that we + * should reset it to its default value. + * But not all options allow that, so we need to check it first. + */ + ret = bdrv_reset_options_allowed(reopen_state->bs, + reopen_state->options, errp); + if (ret) { + goto error; + } + ret = drv->bdrv_reopen_prepare(reopen_state, queue, &local_err); if (ret) { if (local_err != NULL) { @@ -3378,6 +3627,30 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue, drv_prepared = true; + /* + * We must provide the 'backing' option if the BDS has a backing + * file or if the image file has a backing file name as part of + * its metadata. Otherwise the 'backing' option can be omitted. + */ + if (drv->supports_backing && reopen_state->backing_missing && + (backing_bs(reopen_state->bs) || reopen_state->bs->backing_file[0])) { + error_setg(errp, "backing is missing for '%s'", + reopen_state->bs->node_name); + ret = -EINVAL; + goto error; + } + + /* + * Allow changing the 'backing' option. The new value can be + * either a reference to an existing node (using its node name) + * or NULL to simply detach the current backing file. + */ + ret = bdrv_reopen_parse_backing(reopen_state, errp); + if (ret < 0) { + goto error; + } + qdict_del(reopen_state->options, "backing"); + /* Options that are not handled are only okay if they are unchanged * compared to the old state. It is expected that some options are only * used for the initial open, but not reopen (e.g. filename) */ @@ -3430,12 +3703,6 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue, } while ((entry = qdict_next(reopen_state->options, entry))); } - ret = bdrv_check_perm(reopen_state->bs, queue, reopen_state->perm, - reopen_state->shared_perm, NULL, errp); - if (ret < 0) { - goto error; - } - ret = 0; /* Restore the original reopen_state->options QDict */ @@ -3493,6 +3760,11 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state) bs->read_only = !(reopen_state->flags & BDRV_O_RDWR); bs->detect_zeroes = reopen_state->detect_zeroes; + if (reopen_state->replace_backing_bs) { + qdict_del(bs->explicit_options, "backing"); + qdict_del(bs->options, "backing"); + } + /* Remove child references from bs->options and bs->explicit_options. * Child options were already removed in bdrv_reopen_queue_child() */ QLIST_FOREACH(child, &bs->children, next) { @@ -3500,10 +3772,22 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state) qdict_del(bs->options, child->name); } - bdrv_refresh_limits(bs, NULL); + /* + * Change the backing file if a new one was specified. We do this + * after updating bs->options, so bdrv_refresh_filename() (called + * from bdrv_set_backing_hd()) has the new values. + */ + if (reopen_state->replace_backing_bs) { + BlockDriverState *old_backing_bs = backing_bs(bs); + assert(!old_backing_bs || !old_backing_bs->implicit); + /* Abort the permission update on the backing bs we're detaching */ + if (old_backing_bs) { + bdrv_abort_perm_update(old_backing_bs); + } + bdrv_set_backing_hd(bs, reopen_state->new_backing_bs, &error_abort); + } - bdrv_set_perm(reopen_state->bs, reopen_state->perm, - reopen_state->shared_perm); + bdrv_refresh_limits(bs, NULL); new_can_write = !bdrv_is_read_only(bs) && !(bdrv_get_flags(bs) & BDRV_O_INACTIVE); @@ -3536,8 +3820,6 @@ void bdrv_reopen_abort(BDRVReopenState *reopen_state) if (drv->bdrv_reopen_abort) { drv->bdrv_reopen_abort(reopen_state); } - - bdrv_abort_perm_update(reopen_state->bs); } @@ -3717,6 +3999,11 @@ void bdrv_replace_node(BlockDriverState *from, BlockDriverState *to, if (!should_update_child(c, to)) { continue; } + if (c->frozen) { + error_setg(errp, "Cannot change '%s' link to '%s'", + c->name, from->node_name); + goto out; + } list = g_slist_prepend(list, c); perm |= c->perm; shared &= c->shared_perm; @@ -3929,6 +4216,63 @@ BlockDriverState *bdrv_find_base(BlockDriverState *bs) } /* + * Return true if at least one of the backing links between @bs and + * @base is frozen. @errp is set if that's the case. + */ +bool bdrv_is_backing_chain_frozen(BlockDriverState *bs, BlockDriverState *base, + Error **errp) +{ + BlockDriverState *i; + + for (i = bs; i != base && i->backing; i = backing_bs(i)) { + if (i->backing->frozen) { + error_setg(errp, "Cannot change '%s' link from '%s' to '%s'", + i->backing->name, i->node_name, + backing_bs(i)->node_name); + return true; + } + } + + return false; +} + +/* + * Freeze all backing links between @bs and @base. + * If any of the links is already frozen the operation is aborted and + * none of the links are modified. + * Returns 0 on success. On failure returns < 0 and sets @errp. + */ +int bdrv_freeze_backing_chain(BlockDriverState *bs, BlockDriverState *base, + Error **errp) +{ + BlockDriverState *i; + + if (bdrv_is_backing_chain_frozen(bs, base, errp)) { + return -EPERM; + } + + for (i = bs; i != base && i->backing; i = backing_bs(i)) { + i->backing->frozen = true; + } + + return 0; +} + +/* + * Unfreeze all backing links between @bs and @base. The caller must + * ensure that all links are frozen before using this function. + */ +void bdrv_unfreeze_backing_chain(BlockDriverState *bs, BlockDriverState *base) +{ + BlockDriverState *i; + + for (i = bs; i != base && i->backing; i = backing_bs(i)) { + assert(i->backing->frozen); + i->backing->frozen = false; + } +} + +/* * Drops images above 'base' up to and including 'top', and sets the image * above 'top' to have base as its backing file. * @@ -3977,6 +4321,14 @@ int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base, goto exit; } + /* This function changes all links that point to top and makes + * them point to base. Check that none of them is frozen. */ + QLIST_FOREACH(c, &top->parents, next_parent) { + if (c->frozen) { + goto exit; + } + } + /* If 'base' recursively inherits from 'top' then we should set * base->inherits_from to top->inherits_from after 'top' and all * other intermediate nodes have been dropped. diff --git a/block/commit.c b/block/commit.c index 3b46ca7f97..ba60fef58a 100644 --- a/block/commit.c +++ b/block/commit.c @@ -39,6 +39,7 @@ typedef struct CommitBlockJob { BlockDriverState *base_bs; BlockdevOnError on_error; bool base_read_only; + bool chain_frozen; char *backing_file_str; } CommitBlockJob; @@ -68,6 +69,9 @@ static int commit_prepare(Job *job) { CommitBlockJob *s = container_of(job, CommitBlockJob, common.job); + bdrv_unfreeze_backing_chain(s->commit_top_bs, s->base_bs); + s->chain_frozen = false; + /* Remove base node parent that still uses BLK_PERM_WRITE/RESIZE before * the normal backing chain can be restored. */ blk_unref(s->base); @@ -84,6 +88,10 @@ static void commit_abort(Job *job) CommitBlockJob *s = container_of(job, CommitBlockJob, common.job); BlockDriverState *top_bs = blk_bs(s->top); + if (s->chain_frozen) { + bdrv_unfreeze_backing_chain(s->commit_top_bs, s->base_bs); + } + /* Make sure commit_top_bs and top stay around until bdrv_replace_node() */ bdrv_ref(top_bs); bdrv_ref(s->commit_top_bs); @@ -330,6 +338,11 @@ void commit_start(const char *job_id, BlockDriverState *bs, } } + if (bdrv_freeze_backing_chain(commit_top_bs, base, errp) < 0) { + goto fail; + } + s->chain_frozen = true; + ret = block_job_add_bdrv(&s->common, "base", base, 0, BLK_PERM_ALL, errp); if (ret < 0) { goto fail; @@ -362,6 +375,9 @@ void commit_start(const char *job_id, BlockDriverState *bs, return; fail: + if (s->chain_frozen) { + bdrv_unfreeze_backing_chain(commit_top_bs, base); + } if (s->base) { blk_unref(s->base); } diff --git a/block/file-posix.c b/block/file-posix.c index ba6ab62a38..e9fa6aac48 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -144,6 +144,9 @@ typedef struct BDRVRawState { uint64_t locked_perm; uint64_t locked_shared_perm; + int perm_change_fd; + BDRVReopenState *reopen_state; + #ifdef CONFIG_XFS bool is_xfs:1; #endif @@ -373,13 +376,21 @@ static void raw_probe_alignment(BlockDriverState *bs, int fd, Error **errp) } } -static void raw_parse_flags(int bdrv_flags, int *open_flags) +static void raw_parse_flags(int bdrv_flags, int *open_flags, bool has_writers) { + bool read_write = false; assert(open_flags != NULL); *open_flags |= O_BINARY; *open_flags &= ~O_ACCMODE; - if (bdrv_flags & BDRV_O_RDWR) { + + if (bdrv_flags & BDRV_O_AUTO_RDONLY) { + read_write = has_writers; + } else if (bdrv_flags & BDRV_O_RDWR) { + read_write = true; + } + + if (read_write) { *open_flags |= O_RDWR; } else { *open_flags |= O_RDONLY; @@ -431,6 +442,8 @@ static QemuOptsList raw_runtime_opts = { }, }; +static const char *const mutable_opts[] = { "x-check-cache-dropped", NULL }; + static int raw_open_common(BlockDriverState *bs, QDict *options, int bdrv_flags, int open_flags, bool device, Error **errp) @@ -515,24 +528,12 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, false); s->open_flags = open_flags; - raw_parse_flags(bdrv_flags, &s->open_flags); + raw_parse_flags(bdrv_flags, &s->open_flags, false); s->fd = -1; fd = qemu_open(filename, s->open_flags, 0644); ret = fd < 0 ? -errno : 0; - if (ret == -EACCES || ret == -EROFS) { - /* Try to degrade to read-only, but if it doesn't work, still use the - * normal error message. */ - if (bdrv_apply_auto_read_only(bs, NULL, NULL) == 0) { - bdrv_flags &= ~BDRV_O_RDWR; - raw_parse_flags(bdrv_flags, &s->open_flags); - assert(!(s->open_flags & O_CREAT)); - fd = qemu_open(filename, s->open_flags); - ret = fd < 0 ? -errno : 0; - } - } - if (ret < 0) { error_setg_errno(errp, -ret, "Could not open '%s'", filename); if (ret == -EROFS) { @@ -842,13 +843,77 @@ static int raw_handle_perm_lock(BlockDriverState *bs, return ret; } +static int raw_reconfigure_getfd(BlockDriverState *bs, int flags, + int *open_flags, uint64_t perm, bool force_dup, + Error **errp) +{ + BDRVRawState *s = bs->opaque; + int fd = -1; + int ret; + bool has_writers = perm & + (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED | BLK_PERM_RESIZE); + int fcntl_flags = O_APPEND | O_NONBLOCK; +#ifdef O_NOATIME + fcntl_flags |= O_NOATIME; +#endif + + *open_flags = 0; + if (s->type == FTYPE_CD) { + *open_flags |= O_NONBLOCK; + } + + raw_parse_flags(flags, open_flags, has_writers); + +#ifdef O_ASYNC + /* Not all operating systems have O_ASYNC, and those that don't + * will not let us track the state into rs->open_flags (typically + * you achieve the same effect with an ioctl, for example I_SETSIG + * on Solaris). But we do not use O_ASYNC, so that's fine. + */ + assert((s->open_flags & O_ASYNC) == 0); +#endif + + if (!force_dup && *open_flags == s->open_flags) { + /* We're lucky, the existing fd is fine */ + return s->fd; + } + + if ((*open_flags & ~fcntl_flags) == (s->open_flags & ~fcntl_flags)) { + /* dup the original fd */ + fd = qemu_dup(s->fd); + if (fd >= 0) { + ret = fcntl_setfl(fd, *open_flags); + if (ret) { + qemu_close(fd); + fd = -1; + } + } + } + + /* If we cannot use fcntl, or fcntl failed, fall back to qemu_open() */ + if (fd == -1) { + const char *normalized_filename = bs->filename; + ret = raw_normalize_devicepath(&normalized_filename, errp); + if (ret >= 0) { + assert(!(*open_flags & O_CREAT)); + fd = qemu_open(normalized_filename, *open_flags); + if (fd == -1) { + error_setg_errno(errp, errno, "Could not reopen file"); + return -1; + } + } + } + + return fd; +} + static int raw_reopen_prepare(BDRVReopenState *state, BlockReopenQueue *queue, Error **errp) { BDRVRawState *s; BDRVRawReopenState *rs; QemuOpts *opts; - int ret = 0; + int ret; Error *local_err = NULL; assert(state != NULL); @@ -858,7 +923,6 @@ static int raw_reopen_prepare(BDRVReopenState *state, state->opaque = g_new0(BDRVRawReopenState, 1); rs = state->opaque; - rs->fd = -1; /* Handle options changes */ opts = qemu_opts_create(&raw_runtime_opts, NULL, 0, &error_abort); @@ -877,50 +941,12 @@ static int raw_reopen_prepare(BDRVReopenState *state, * bdrv_reopen_prepare() will detect changes and complain. */ qemu_opts_to_qdict(opts, state->options); - if (s->type == FTYPE_CD) { - rs->open_flags |= O_NONBLOCK; - } - - raw_parse_flags(state->flags, &rs->open_flags); - - int fcntl_flags = O_APPEND | O_NONBLOCK; -#ifdef O_NOATIME - fcntl_flags |= O_NOATIME; -#endif - -#ifdef O_ASYNC - /* Not all operating systems have O_ASYNC, and those that don't - * will not let us track the state into rs->open_flags (typically - * you achieve the same effect with an ioctl, for example I_SETSIG - * on Solaris). But we do not use O_ASYNC, so that's fine. - */ - assert((s->open_flags & O_ASYNC) == 0); -#endif - - if ((rs->open_flags & ~fcntl_flags) == (s->open_flags & ~fcntl_flags)) { - /* dup the original fd */ - rs->fd = qemu_dup(s->fd); - if (rs->fd >= 0) { - ret = fcntl_setfl(rs->fd, rs->open_flags); - if (ret) { - qemu_close(rs->fd); - rs->fd = -1; - } - } - } - - /* If we cannot use fcntl, or fcntl failed, fall back to qemu_open() */ - if (rs->fd == -1) { - const char *normalized_filename = state->bs->filename; - ret = raw_normalize_devicepath(&normalized_filename, errp); - if (ret >= 0) { - assert(!(rs->open_flags & O_CREAT)); - rs->fd = qemu_open(normalized_filename, rs->open_flags); - if (rs->fd == -1) { - error_setg_errno(errp, errno, "Could not reopen file"); - ret = -1; - } - } + rs->fd = raw_reconfigure_getfd(state->bs, state->flags, &rs->open_flags, + state->perm, true, &local_err); + if (local_err) { + error_propagate(errp, local_err); + ret = -1; + goto out; } /* Fail already reopen_prepare() if we can't get a working O_DIRECT @@ -928,13 +954,19 @@ static int raw_reopen_prepare(BDRVReopenState *state, if (rs->fd != -1) { raw_probe_alignment(state->bs, rs->fd, &local_err); if (local_err) { - qemu_close(rs->fd); - rs->fd = -1; error_propagate(errp, local_err); ret = -EINVAL; + goto out_fd; } } + s->reopen_state = state; + ret = 0; +out_fd: + if (ret < 0) { + qemu_close(rs->fd); + rs->fd = -1; + } out: qemu_opts_del(opts); return ret; @@ -944,29 +976,25 @@ static void raw_reopen_commit(BDRVReopenState *state) { BDRVRawReopenState *rs = state->opaque; BDRVRawState *s = state->bs->opaque; - Error *local_err = NULL; s->check_cache_dropped = rs->check_cache_dropped; s->open_flags = rs->open_flags; - /* Copy locks to the new fd before closing the old one. */ - raw_apply_lock_bytes(NULL, rs->fd, s->locked_perm, - s->locked_shared_perm, false, &local_err); - if (local_err) { - /* shouldn't fail in a sane host, but report it just in case. */ - error_report_err(local_err); - } qemu_close(s->fd); s->fd = rs->fd; g_free(state->opaque); state->opaque = NULL; + + assert(s->reopen_state == state); + s->reopen_state = NULL; } static void raw_reopen_abort(BDRVReopenState *state) { BDRVRawReopenState *rs = state->opaque; + BDRVRawState *s = state->bs->opaque; /* nothing to do if NULL, we didn't get far enough */ if (rs == NULL) { @@ -979,6 +1007,9 @@ static void raw_reopen_abort(BDRVReopenState *state) } g_free(state->opaque); state->opaque = NULL; + + assert(s->reopen_state == state); + s->reopen_state = NULL; } static int hdev_get_max_transfer_length(BlockDriverState *bs, int fd) @@ -2664,12 +2695,78 @@ static QemuOptsList raw_create_opts = { static int raw_check_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared, Error **errp) { - return raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp); + BDRVRawState *s = bs->opaque; + BDRVRawReopenState *rs = NULL; + int open_flags; + int ret; + + if (s->perm_change_fd) { + /* + * In the context of reopen, this function may be called several times + * (directly and recursively while change permissions of the parent). + * This is even true for children that don't inherit from the original + * reopen node, so s->reopen_state is not set. + * + * Ignore all but the first call. + */ + return 0; + } + + if (s->reopen_state) { + /* We already have a new file descriptor to set permissions for */ + assert(s->reopen_state->perm == perm); + assert(s->reopen_state->shared_perm == shared); + rs = s->reopen_state->opaque; + s->perm_change_fd = rs->fd; + } else { + /* We may need a new fd if auto-read-only switches the mode */ + ret = raw_reconfigure_getfd(bs, bs->open_flags, &open_flags, perm, + false, errp); + if (ret < 0) { + return ret; + } else if (ret != s->fd) { + s->perm_change_fd = ret; + } + } + + /* Prepare permissions on old fd to avoid conflicts between old and new, + * but keep everything locked that new will need. */ + ret = raw_handle_perm_lock(bs, RAW_PL_PREPARE, perm, shared, errp); + if (ret < 0) { + goto fail; + } + + /* Copy locks to the new fd */ + if (s->perm_change_fd) { + ret = raw_apply_lock_bytes(NULL, s->perm_change_fd, perm, ~shared, + false, errp); + if (ret < 0) { + raw_handle_perm_lock(bs, RAW_PL_ABORT, 0, 0, NULL); + goto fail; + } + } + return 0; + +fail: + if (s->perm_change_fd && !s->reopen_state) { + qemu_close(s->perm_change_fd); + } + s->perm_change_fd = 0; + return ret; } static void raw_set_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared) { BDRVRawState *s = bs->opaque; + + /* For reopen, we have already switched to the new fd (.bdrv_set_perm is + * called after .bdrv_reopen_commit) */ + if (s->perm_change_fd && s->fd != s->perm_change_fd) { + qemu_close(s->fd); + s->fd = s->perm_change_fd; + } + s->perm_change_fd = 0; + raw_handle_perm_lock(bs, RAW_PL_COMMIT, perm, shared, NULL); s->perm = perm; s->shared_perm = shared; @@ -2677,6 +2774,15 @@ static void raw_set_perm(BlockDriverState *bs, uint64_t perm, uint64_t shared) static void raw_abort_perm_update(BlockDriverState *bs) { + BDRVRawState *s = bs->opaque; + + /* For reopen, .bdrv_reopen_abort is called afterwards and will close + * the file descriptor. */ + if (s->perm_change_fd && !s->reopen_state) { + qemu_close(s->perm_change_fd); + } + s->perm_change_fd = 0; + raw_handle_perm_lock(bs, RAW_PL_ABORT, 0, 0, NULL); } @@ -2766,6 +2872,7 @@ BlockDriver bdrv_file = { .bdrv_set_perm = raw_set_perm, .bdrv_abort_perm_update = raw_abort_perm_update, .create_opts = &raw_create_opts, + .mutable_opts = mutable_opts, }; /***********************************************/ @@ -3217,6 +3324,7 @@ static BlockDriver bdrv_host_device = { .bdrv_reopen_abort = raw_reopen_abort, .bdrv_co_create_opts = hdev_co_create_opts, .create_opts = &raw_create_opts, + .mutable_opts = mutable_opts, .bdrv_co_invalidate_cache = raw_co_invalidate_cache, .bdrv_co_pwrite_zeroes = hdev_co_pwrite_zeroes, @@ -3343,6 +3451,7 @@ static BlockDriver bdrv_host_cdrom = { .bdrv_reopen_abort = raw_reopen_abort, .bdrv_co_create_opts = hdev_co_create_opts, .create_opts = &raw_create_opts, + .mutable_opts = mutable_opts, .bdrv_co_invalidate_cache = raw_co_invalidate_cache, @@ -3476,6 +3585,7 @@ static BlockDriver bdrv_host_cdrom = { .bdrv_reopen_abort = raw_reopen_abort, .bdrv_co_create_opts = hdev_co_create_opts, .create_opts = &raw_create_opts, + .mutable_opts = mutable_opts, .bdrv_co_preadv = raw_co_preadv, .bdrv_co_pwritev = raw_co_pwritev, diff --git a/block/gluster.c b/block/gluster.c index af64330211..51f184cbd8 100644 --- a/block/gluster.c +++ b/block/gluster.c @@ -20,6 +20,10 @@ #include "qemu/option.h" #include "qemu/cutils.h" +#ifdef CONFIG_GLUSTERFS_FTRUNCATE_HAS_STAT +# define glfs_ftruncate(fd, offset) glfs_ftruncate(fd, offset, NULL, NULL) +#endif + #define GLUSTER_OPT_FILENAME "filename" #define GLUSTER_OPT_VOLUME "volume" #define GLUSTER_OPT_PATH "path" @@ -725,7 +729,11 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf, /* * AIO callback routine called from GlusterFS thread. */ -static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg) +static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, +#ifdef CONFIG_GLUSTERFS_IOCB_HAS_STAT + struct glfs_stat *pre, struct glfs_stat *post, +#endif + void *arg) { GlusterAIOCB *acb = (GlusterAIOCB *)arg; diff --git a/block/mirror.c b/block/mirror.c index 726d3c27fb..010fdafd79 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -630,6 +630,10 @@ static int mirror_exit_common(Job *job) } s->prepared = true; + if (bdrv_chain_contains(src, target_bs)) { + bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs); + } + bdrv_release_dirty_bitmap(src, s->dirty_bitmap); /* Make sure that the source BDS doesn't go away during bdrv_replace_node, @@ -1639,6 +1643,10 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs, goto fail; } } + + if (bdrv_freeze_backing_chain(mirror_top_bs, target, errp) < 0) { + goto fail; + } } QTAILQ_INIT(&s->ops_in_flight); diff --git a/block/qapi.c b/block/qapi.c index 6002a768f8..21edab34fc 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -493,14 +493,14 @@ static void bdrv_query_blk_stats(BlockDeviceStats *ds, BlockBackend *blk) } bdrv_latency_histogram_stats(&stats->latency_histogram[BLOCK_ACCT_READ], - &ds->has_x_rd_latency_histogram, - &ds->x_rd_latency_histogram); + &ds->has_rd_latency_histogram, + &ds->rd_latency_histogram); bdrv_latency_histogram_stats(&stats->latency_histogram[BLOCK_ACCT_WRITE], - &ds->has_x_wr_latency_histogram, - &ds->x_wr_latency_histogram); + &ds->has_wr_latency_histogram, + &ds->wr_latency_histogram); bdrv_latency_histogram_stats(&stats->latency_histogram[BLOCK_ACCT_FLUSH], - &ds->has_x_flush_latency_histogram, - &ds->x_flush_latency_histogram); + &ds->has_flush_latency_histogram, + &ds->flush_latency_histogram); } static BlockStats *bdrv_query_bds_stats(BlockDriverState *bs, diff --git a/block/qcow2.c b/block/qcow2.c index d48e2e56f7..0dd77c6367 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -627,6 +627,30 @@ int qcow2_validate_table(BlockDriverState *bs, uint64_t offset, return 0; } +static const char *const mutable_opts[] = { + QCOW2_OPT_LAZY_REFCOUNTS, + QCOW2_OPT_DISCARD_REQUEST, + QCOW2_OPT_DISCARD_SNAPSHOT, + QCOW2_OPT_DISCARD_OTHER, + QCOW2_OPT_OVERLAP, + QCOW2_OPT_OVERLAP_TEMPLATE, + QCOW2_OPT_OVERLAP_MAIN_HEADER, + QCOW2_OPT_OVERLAP_ACTIVE_L1, + QCOW2_OPT_OVERLAP_ACTIVE_L2, + QCOW2_OPT_OVERLAP_REFCOUNT_TABLE, + QCOW2_OPT_OVERLAP_REFCOUNT_BLOCK, + QCOW2_OPT_OVERLAP_SNAPSHOT_TABLE, + QCOW2_OPT_OVERLAP_INACTIVE_L1, + QCOW2_OPT_OVERLAP_INACTIVE_L2, + QCOW2_OPT_OVERLAP_BITMAP_DIRECTORY, + QCOW2_OPT_CACHE_SIZE, + QCOW2_OPT_L2_CACHE_SIZE, + QCOW2_OPT_L2_CACHE_ENTRY_SIZE, + QCOW2_OPT_REFCOUNT_CACHE_SIZE, + QCOW2_OPT_CACHE_CLEAN_INTERVAL, + NULL +}; + static QemuOptsList qcow2_runtime_opts = { .name = "qcow2", .head = QTAILQ_HEAD_INITIALIZER(qcow2_runtime_opts.head), @@ -5273,6 +5297,7 @@ BlockDriver bdrv_qcow2 = { .create_opts = &qcow2_create_opts, .strong_runtime_opts = qcow2_strong_runtime_opts, + .mutable_opts = mutable_opts, .bdrv_co_check = qcow2_co_check, .bdrv_amend_options = qcow2_amend_options, diff --git a/block/raw-format.c b/block/raw-format.c index e3e5ba2c8a..cec29986cc 100644 --- a/block/raw-format.c +++ b/block/raw-format.c @@ -37,6 +37,8 @@ typedef struct BDRVRawState { bool has_size; } BDRVRawState; +static const char *const mutable_opts[] = { "offset", "size", NULL }; + static QemuOptsList raw_runtime_opts = { .name = "raw", .head = QTAILQ_HEAD_INITIALIZER(raw_runtime_opts.head), @@ -570,6 +572,7 @@ BlockDriver bdrv_raw = { .create_opts = &raw_create_opts, .bdrv_has_zero_init = &raw_has_zero_init, .strong_runtime_opts = raw_strong_runtime_opts, + .mutable_opts = mutable_opts, }; static void bdrv_raw_init(void) diff --git a/block/replication.c b/block/replication.c index 4c80b54daf..b95bd28802 100644 --- a/block/replication.c +++ b/block/replication.c @@ -374,19 +374,18 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable, QDict *opts = qdict_new(); qdict_put_bool(opts, BDRV_OPT_READ_ONLY, !writable); reopen_queue = bdrv_reopen_queue(reopen_queue, s->hidden_disk->bs, - opts); + opts, true); } if (s->orig_secondary_read_only) { QDict *opts = qdict_new(); qdict_put_bool(opts, BDRV_OPT_READ_ONLY, !writable); reopen_queue = bdrv_reopen_queue(reopen_queue, s->secondary_disk->bs, - opts); + opts, true); } if (reopen_queue) { - bdrv_reopen_multiple(bdrv_get_aio_context(bs), - reopen_queue, &local_err); + bdrv_reopen_multiple(reopen_queue, &local_err); error_propagate(errp, local_err); } diff --git a/block/stream.c b/block/stream.c index e14579ff80..6253c86fae 100644 --- a/block/stream.c +++ b/block/stream.c @@ -35,6 +35,7 @@ typedef struct StreamBlockJob { BlockdevOnError on_error; char *backing_file_str; bool bs_read_only; + bool chain_frozen; } StreamBlockJob; static int coroutine_fn stream_populate(BlockBackend *blk, @@ -49,6 +50,16 @@ static int coroutine_fn stream_populate(BlockBackend *blk, return blk_co_preadv(blk, offset, qiov.size, &qiov, BDRV_REQ_COPY_ON_READ); } +static void stream_abort(Job *job) +{ + StreamBlockJob *s = container_of(job, StreamBlockJob, common.job); + + if (s->chain_frozen) { + BlockJob *bjob = &s->common; + bdrv_unfreeze_backing_chain(blk_bs(bjob->blk), s->base); + } +} + static int stream_prepare(Job *job) { StreamBlockJob *s = container_of(job, StreamBlockJob, common.job); @@ -58,6 +69,9 @@ static int stream_prepare(Job *job) Error *local_err = NULL; int ret = 0; + bdrv_unfreeze_backing_chain(bs, base); + s->chain_frozen = false; + if (bs->backing) { const char *base_id = NULL, *base_fmt = NULL; if (base) { @@ -208,6 +222,7 @@ static const BlockJobDriver stream_job_driver = { .free = block_job_free, .run = stream_run, .prepare = stream_prepare, + .abort = stream_abort, .clean = stream_clean, .user_resume = block_job_user_resume, .drain = block_job_drain, @@ -254,9 +269,15 @@ void stream_start(const char *job_id, BlockDriverState *bs, &error_abort); } + if (bdrv_freeze_backing_chain(bs, base, errp) < 0) { + job_early_fail(&s->common.job); + goto fail; + } + s->base = base; s->backing_file_str = g_strdup(backing_file_str); s->bs_read_only = bs_read_only; + s->chain_frozen = true; s->on_error = on_error; trace_stream_start(bs, base, s); diff --git a/blockdev.c b/blockdev.c index 4dbf20bb68..53df2eb875 100644 --- a/blockdev.c +++ b/blockdev.c @@ -4256,6 +4256,53 @@ fail: visit_free(v); } +void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp) +{ + BlockDriverState *bs; + AioContext *ctx; + QObject *obj; + Visitor *v = qobject_output_visitor_new(&obj); + Error *local_err = NULL; + BlockReopenQueue *queue; + QDict *qdict; + + /* Check for the selected node name */ + if (!options->has_node_name) { + error_setg(errp, "Node name not specified"); + goto fail; + } + + bs = bdrv_find_node(options->node_name); + if (!bs) { + error_setg(errp, "Cannot find node named '%s'", options->node_name); + goto fail; + } + + /* Put all options in a QDict and flatten it */ + visit_type_BlockdevOptions(v, NULL, &options, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto fail; + } + + visit_complete(v, &obj); + qdict = qobject_to(QDict, obj); + + qdict_flatten(qdict); + + /* Perform the reopen operation */ + ctx = bdrv_get_aio_context(bs); + aio_context_acquire(ctx); + bdrv_subtree_drained_begin(bs); + queue = bdrv_reopen_queue(NULL, bs, qdict, false); + bdrv_reopen_multiple(queue, errp); + bdrv_subtree_drained_end(bs); + aio_context_release(ctx); + +fail: + visit_free(v); +} + void qmp_blockdev_del(const char *node_name, Error **errp) { AioContext *aio_context; @@ -4421,22 +4468,22 @@ void qmp_x_blockdev_set_iothread(const char *node_name, StrOrNull *iothread, aio_context_release(old_context); } -void qmp_x_block_latency_histogram_set( - const char *device, +void qmp_block_latency_histogram_set( + const char *id, bool has_boundaries, uint64List *boundaries, bool has_boundaries_read, uint64List *boundaries_read, bool has_boundaries_write, uint64List *boundaries_write, bool has_boundaries_flush, uint64List *boundaries_flush, Error **errp) { - BlockBackend *blk = blk_by_name(device); + BlockBackend *blk = qmp_get_blk(NULL, id, errp); BlockAcctStats *stats; int ret; if (!blk) { - error_setg(errp, "Device '%s' not found", device); return; } + stats = blk_get_stats(blk); if (!has_boundaries && !has_boundaries_read && !has_boundaries_write && @@ -4451,7 +4498,7 @@ void qmp_x_block_latency_histogram_set( stats, BLOCK_ACCT_READ, has_boundaries_read ? boundaries_read : boundaries); if (ret) { - error_setg(errp, "Device '%s' set read boundaries fail", device); + error_setg(errp, "Device '%s' set read boundaries fail", id); return; } } @@ -4461,7 +4508,7 @@ void qmp_x_block_latency_histogram_set( stats, BLOCK_ACCT_WRITE, has_boundaries_write ? boundaries_write : boundaries); if (ret) { - error_setg(errp, "Device '%s' set write boundaries fail", device); + error_setg(errp, "Device '%s' set write boundaries fail", id); return; } } @@ -4471,7 +4518,7 @@ void qmp_x_block_latency_histogram_set( stats, BLOCK_ACCT_FLUSH, has_boundaries_flush ? boundaries_flush : boundaries); if (ret) { - error_setg(errp, "Device '%s' set flush boundaries fail", device); + error_setg(errp, "Device '%s' set flush boundaries fail", id); return; } } @@ -456,6 +456,8 @@ glusterfs_xlator_opt="no" glusterfs_discard="no" glusterfs_fallocate="no" glusterfs_zerofill="no" +glusterfs_ftruncate_has_stat="no" +glusterfs_iocb_has_stat="no" gtk="" gtk_gl="no" tls_priority="NORMAL" @@ -4091,6 +4093,38 @@ if test "$glusterfs" != "no" ; then glusterfs_fallocate="yes" glusterfs_zerofill="yes" fi + cat > $TMPC << EOF +#include <glusterfs/api/glfs.h> + +int +main(void) +{ + /* new glfs_ftruncate() passes two additional args */ + return glfs_ftruncate(NULL, 0, NULL, NULL); +} +EOF + if compile_prog "$glusterfs_cflags" "$glusterfs_libs" ; then + glusterfs_ftruncate_has_stat="yes" + fi + cat > $TMPC << EOF +#include <glusterfs/api/glfs.h> + +/* new glfs_io_cbk() passes two additional glfs_stat structs */ +static void +glusterfs_iocb(glfs_fd_t *fd, ssize_t ret, struct glfs_stat *prestat, struct glfs_stat *poststat, void *data) +{} + +int +main(void) +{ + glfs_io_cbk iocb = &glusterfs_iocb; + iocb(NULL, 0 , NULL, NULL, NULL); + return 0; +} +EOF + if compile_prog "$glusterfs_cflags" "$glusterfs_libs" ; then + glusterfs_iocb_has_stat="yes" + fi else if test "$glusterfs" = "yes" ; then feature_not_found "GlusterFS backend support" \ @@ -6976,6 +7010,14 @@ if test "$glusterfs_zerofill" = "yes" ; then echo "CONFIG_GLUSTERFS_ZEROFILL=y" >> $config_host_mak fi +if test "$glusterfs_ftruncate_has_stat" = "yes" ; then + echo "CONFIG_GLUSTERFS_FTRUNCATE_HAS_STAT=y" >> $config_host_mak +fi + +if test "$glusterfs_iocb_has_stat" = "yes" ; then + echo "CONFIG_GLUSTERFS_IOCB_HAS_STAT=y" >> $config_host_mak +fi + if test "$libssh2" = "yes" ; then echo "CONFIG_LIBSSH2=m" >> $config_host_mak echo "LIBSSH2_CFLAGS=$libssh2_cflags" >> $config_host_mak diff --git a/docs/devel/decodetree.rst b/docs/devel/decodetree.rst new file mode 100644 index 0000000000..44ac621ea8 --- /dev/null +++ b/docs/devel/decodetree.rst @@ -0,0 +1,221 @@ +======================== +Decodetree Specification +======================== + +A *decodetree* is built from instruction *patterns*. A pattern may +represent a single architectural instruction or a group of same, depending +on what is convenient for further processing. + +Each pattern has both *fixedbits* and *fixedmask*, the combination of which +describes the condition under which the pattern is matched:: + + (insn & fixedmask) == fixedbits + +Each pattern may have *fields*, which are extracted from the insn and +passed along to the translator. Examples of such are registers, +immediates, and sub-opcodes. + +In support of patterns, one may declare *fields*, *argument sets*, and +*formats*, each of which may be re-used to simplify further definitions. + +Fields +====== + +Syntax:: + + field_def := '%' identifier ( unnamed_field )+ ( !function=identifier )? + unnamed_field := number ':' ( 's' ) number + +For *unnamed_field*, the first number is the least-significant bit position +of the field and the second number is the length of the field. If the 's' is +present, the field is considered signed. If multiple ``unnamed_fields`` are +present, they are concatenated. In this way one can define disjoint fields. + +If ``!function`` is specified, the concatenated result is passed through the +named function, taking and returning an integral value. + +FIXME: the fields of the structure into which this result will be stored +is restricted to ``int``. Which means that we cannot expand 64-bit items. + +Field examples: + ++---------------------------+---------------------------------------------+ +| Input | Generated code | ++===========================+=============================================+ +| %disp 0:s16 | sextract(i, 0, 16) | ++---------------------------+---------------------------------------------+ +| %imm9 16:6 10:3 | extract(i, 16, 6) << 3 | extract(i, 10, 3) | ++---------------------------+---------------------------------------------+ +| %disp12 0:s1 1:1 2:10 | sextract(i, 0, 1) << 11 | | +| | extract(i, 1, 1) << 10 | | +| | extract(i, 2, 10) | ++---------------------------+---------------------------------------------+ +| %shimm8 5:s8 13:1 | expand_shimm8(sextract(i, 5, 8) << 1 | | +| !function=expand_shimm8 | extract(i, 13, 1)) | ++---------------------------+---------------------------------------------+ + +Argument Sets +============= + +Syntax:: + + args_def := '&' identifier ( args_elt )+ ( !extern )? + args_elt := identifier + +Each *args_elt* defines an argument within the argument set. +Each argument set will be rendered as a C structure "arg_$name" +with each of the fields being one of the member arguments. + +If ``!extern`` is specified, the backing structure is assumed +to have been already declared, typically via a second decoder. + +Argument sets are useful when one wants to define helper functions +for the translator functions that can perform operations on a common +set of arguments. This can ensure, for instance, that the ``AND`` +pattern and the ``OR`` pattern put their operands into the same named +structure, so that a common ``gen_logic_insn`` may be able to handle +the operations common between the two. + +Argument set examples:: + + ®3 ra rb rc + &loadstore reg base offset + + +Formats +======= + +Syntax:: + + fmt_def := '@' identifier ( fmt_elt )+ + fmt_elt := fixedbit_elt | field_elt | field_ref | args_ref + fixedbit_elt := [01.-]+ + field_elt := identifier ':' 's'? number + field_ref := '%' identifier | identifier '=' '%' identifier + args_ref := '&' identifier + +Defining a format is a handy way to avoid replicating groups of fields +across many instruction patterns. + +A *fixedbit_elt* describes a contiguous sequence of bits that must +be 1, 0, or don't care. The difference between '.' and '-' +is that '.' means that the bit will be covered with a field or a +final 0 or 1 from the pattern, and '-' means that the bit is really +ignored by the cpu and will not be specified. + +A *field_elt* describes a simple field only given a width; the position of +the field is implied by its position with respect to other *fixedbit_elt* +and *field_elt*. + +If any *fixedbit_elt* or *field_elt* appear, then all bits must be defined. +Padding with a *fixedbit_elt* of all '.' is an easy way to accomplish that. + +A *field_ref* incorporates a field by reference. This is the only way to +add a complex field to a format. A field may be renamed in the process +via assignment to another identifier. This is intended to allow the +same argument set be used with disjoint named fields. + +A single *args_ref* may specify an argument set to use for the format. +The set of fields in the format must be a subset of the arguments in +the argument set. If an argument set is not specified, one will be +inferred from the set of fields. + +It is recommended, but not required, that all *field_ref* and *args_ref* +appear at the end of the line, not interleaving with *fixedbit_elf* or +*field_elt*. + +Format examples:: + + @opr ...... ra:5 rb:5 ... 0 ....... rc:5 + @opi ...... ra:5 lit:8 1 ....... rc:5 + +Patterns +======== + +Syntax:: + + pat_def := identifier ( pat_elt )+ + pat_elt := fixedbit_elt | field_elt | field_ref | args_ref | fmt_ref | const_elt + fmt_ref := '@' identifier + const_elt := identifier '=' number + +The *fixedbit_elt* and *field_elt* specifiers are unchanged from formats. +A pattern that does not specify a named format will have one inferred +from a referenced argument set (if present) and the set of fields. + +A *const_elt* allows a argument to be set to a constant value. This may +come in handy when fields overlap between patterns and one has to +include the values in the *fixedbit_elt* instead. + +The decoder will call a translator function for each pattern matched. + +Pattern examples:: + + addl_r 010000 ..... ..... .... 0000000 ..... @opr + addl_i 010000 ..... ..... .... 0000000 ..... @opi + +which will, in part, invoke:: + + trans_addl_r(ctx, &arg_opr, insn) + +and:: + + trans_addl_i(ctx, &arg_opi, insn) + +Pattern Groups +============== + +Syntax:: + + group := '{' ( pat_def | group )+ '}' + +A *group* begins with a lone open-brace, with all subsequent lines +indented two spaces, and ending with a lone close-brace. Groups +may be nested, increasing the required indentation of the lines +within the nested group to two spaces per nesting level. + +Unlike ungrouped patterns, grouped patterns are allowed to overlap. +Conflicts are resolved by selecting the patterns in order. If all +of the fixedbits for a pattern match, its translate function will +be called. If the translate function returns false, then subsequent +patterns within the group will be matched. + +The following example from PA-RISC shows specialization of the *or* +instruction:: + + { + { + nop 000010 ----- ----- 0000 001001 0 00000 + copy 000010 00000 r1:5 0000 001001 0 rt:5 + } + or 000010 rt2:5 r1:5 cf:4 001001 0 rt:5 + } + +When the *cf* field is zero, the instruction has no side effects, +and may be specialized. When the *rt* field is zero, the output +is discarded and so the instruction has no effect. When the *rt2* +field is zero, the operation is ``reg[rt] | 0`` and so encodes +the canonical register copy operation. + +The output from the generator might look like:: + + switch (insn & 0xfc000fe0) { + case 0x08000240: + /* 000010.. ........ ....0010 010..... */ + if ((insn & 0x0000f000) == 0x00000000) { + /* 000010.. ........ 00000010 010..... */ + if ((insn & 0x0000001f) == 0x00000000) { + /* 000010.. ........ 00000010 01000000 */ + extract_decode_Fmt_0(&u.f_decode0, insn); + if (trans_nop(ctx, &u.f_decode0)) return true; + } + if ((insn & 0x03e00000) == 0x00000000) { + /* 00001000 000..... 00000010 010..... */ + extract_decode_Fmt_1(&u.f_decode1, insn); + if (trans_copy(ctx, &u.f_decode1)) return true; + } + } + extract_decode_Fmt_2(&u.f_decode2, insn); + if (trans_or(ctx, &u.f_decode2)) return true; + return false; + } diff --git a/docs/devel/index.rst b/docs/devel/index.rst index 6b11e49caa..ebbab636ce 100644 --- a/docs/devel/index.rst +++ b/docs/devel/index.rst @@ -19,4 +19,4 @@ Contents: migration stable-process testing - + decodetree diff --git a/hw/acpi/nvdimm.c b/hw/acpi/nvdimm.c index e53b2cb681..f73cfb9d90 100644 --- a/hw/acpi/nvdimm.c +++ b/hw/acpi/nvdimm.c @@ -382,7 +382,7 @@ nvdimm_build_structure_caps(GArray *structures, uint32_t capabilities) nfit_caps->capabilities = cpu_to_le32(capabilities); } -static GArray *nvdimm_build_device_structure(AcpiNVDIMMState *state) +static GArray *nvdimm_build_device_structure(NVDIMMState *state) { GSList *device_list = nvdimm_get_device_list(); GArray *structures = g_array_new(false, true /* clear */, 1); @@ -416,7 +416,7 @@ static void nvdimm_init_fit_buffer(NvdimmFitBuffer *fit_buf) fit_buf->fit = g_array_new(false, true /* clear */, 1); } -static void nvdimm_build_fit_buffer(AcpiNVDIMMState *state) +static void nvdimm_build_fit_buffer(NVDIMMState *state) { NvdimmFitBuffer *fit_buf = &state->fit_buf; @@ -425,12 +425,12 @@ static void nvdimm_build_fit_buffer(AcpiNVDIMMState *state) fit_buf->dirty = true; } -void nvdimm_plug(AcpiNVDIMMState *state) +void nvdimm_plug(NVDIMMState *state) { nvdimm_build_fit_buffer(state); } -static void nvdimm_build_nfit(AcpiNVDIMMState *state, GArray *table_offsets, +static void nvdimm_build_nfit(NVDIMMState *state, GArray *table_offsets, GArray *table_data, BIOSLinker *linker) { NvdimmFitBuffer *fit_buf = &state->fit_buf; @@ -570,7 +570,7 @@ nvdimm_dsm_no_payload(uint32_t func_ret_status, hwaddr dsm_mem_addr) #define NVDIMM_QEMU_RSVD_HANDLE_ROOT 0x10000 /* Read FIT data, defined in docs/specs/acpi_nvdimm.txt. */ -static void nvdimm_dsm_func_read_fit(AcpiNVDIMMState *state, NvdimmDsmIn *in, +static void nvdimm_dsm_func_read_fit(NVDIMMState *state, NvdimmDsmIn *in, hwaddr dsm_mem_addr) { NvdimmFitBuffer *fit_buf = &state->fit_buf; @@ -619,7 +619,7 @@ exit: } static void -nvdimm_dsm_handle_reserved_root_method(AcpiNVDIMMState *state, +nvdimm_dsm_handle_reserved_root_method(NVDIMMState *state, NvdimmDsmIn *in, hwaddr dsm_mem_addr) { switch (in->function) { @@ -863,7 +863,7 @@ nvdimm_dsm_read(void *opaque, hwaddr addr, unsigned size) static void nvdimm_dsm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { - AcpiNVDIMMState *state = opaque; + NVDIMMState *state = opaque; NvdimmDsmIn *in; hwaddr dsm_mem_addr = val; @@ -925,7 +925,7 @@ void nvdimm_acpi_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev) } } -void nvdimm_init_acpi_state(AcpiNVDIMMState *state, MemoryRegion *io, +void nvdimm_init_acpi_state(NVDIMMState *state, MemoryRegion *io, FWCfgState *fw_cfg, Object *owner) { memory_region_init_io(&state->io_mr, owner, &nvdimm_dsm_ops, state, @@ -1319,7 +1319,7 @@ static void nvdimm_build_ssdt(GArray *table_offsets, GArray *table_data, } void nvdimm_build_acpi(GArray *table_offsets, GArray *table_data, - BIOSLinker *linker, AcpiNVDIMMState *state, + BIOSLinker *linker, NVDIMMState *state, uint32_t ram_slots) { GSList *device_list; diff --git a/hw/arm/omap2.c b/hw/arm/omap2.c index 94dffb2f57..446223906e 100644 --- a/hw/arm/omap2.c +++ b/hw/arm/omap2.c @@ -273,7 +273,7 @@ static void omap_eac_format_update(struct omap_eac_s *s) * does I2S specify it? */ /* All register writes are 16 bits so we we store 16-bit samples * in the buffers regardless of AGCFR[B8_16] value. */ - fmt.fmt = AUD_FMT_U16; + fmt.fmt = AUDIO_FORMAT_U16; s->codec.in_voice = AUD_open_in(&s->codec.card, s->codec.in_voice, "eac.codec.in", s, omap_eac_in_cb, &fmt); diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c index d799533aa9..2265622d44 100644 --- a/hw/audio/ac97.c +++ b/hw/audio/ac97.c @@ -365,7 +365,7 @@ static void open_voice (AC97LinkState *s, int index, int freq) as.freq = freq; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = 0; if (freq > 0) { diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c index 97b876c7e0..0957780a3d 100644 --- a/hw/audio/adlib.c +++ b/hw/audio/adlib.c @@ -269,7 +269,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp) as.freq = s->freq; as.nchannels = SHIFT; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = AUDIO_HOST_ENDIANNESS; AUD_register_card ("adlib", &s->card); diff --git a/hw/audio/cs4231a.c b/hw/audio/cs4231a.c index 9089dcb47e..62da75eefe 100644 --- a/hw/audio/cs4231a.c +++ b/hw/audio/cs4231a.c @@ -288,7 +288,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) switch ((val >> 5) & ((s->dregs[MODE_And_ID] & MODE2) ? 7 : 3)) { case 0: - as.fmt = AUD_FMT_U8; + as.fmt = AUDIO_FORMAT_U8; s->shift = as.nchannels == 2; break; @@ -298,7 +298,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) case 3: s->tab = ALawDecompressTable; x_law: - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = AUDIO_HOST_ENDIANNESS; s->shift = as.nchannels == 2; break; @@ -307,7 +307,7 @@ static void cs_reset_voices (CSState *s, uint32_t val) as.endianness = 1; /* fall through */ case 2: - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; s->shift = as.nchannels; break; diff --git a/hw/audio/es1370.c b/hw/audio/es1370.c index 97789a0771..a5314d66fd 100644 --- a/hw/audio/es1370.c +++ b/hw/audio/es1370.c @@ -414,14 +414,14 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl) i, new_freq, 1 << (new_fmt & 1), - (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8, + (new_fmt & 2) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_U8, d->shift); if (new_freq) { struct audsettings as; as.freq = new_freq; as.nchannels = 1 << (new_fmt & 1); - as.fmt = (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8; + as.fmt = (new_fmt & 2) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_U8; as.endianness = 0; if (i == ADC_CHANNEL) { diff --git a/hw/audio/gus.c b/hw/audio/gus.c index 8e0b27e0f2..b3e2a7fdd5 100644 --- a/hw/audio/gus.c +++ b/hw/audio/gus.c @@ -251,7 +251,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp) as.freq = s->freq; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = GUS_ENDIANNESS; s->voice = AUD_open_out ( diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c index 617a1c1016..c25bfa38b1 100644 --- a/hw/audio/hda-codec.c +++ b/hw/audio/hda-codec.c @@ -99,9 +99,9 @@ static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) } 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; @@ -134,12 +134,12 @@ static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) /* -------------------------------------------------------------------------- */ 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; diff --git a/hw/audio/lm4549.c b/hw/audio/lm4549.c index a46f2301af..af8b22b541 100644 --- a/hw/audio/lm4549.c +++ b/hw/audio/lm4549.c @@ -185,7 +185,7 @@ void lm4549_write(lm4549_state *s, struct audsettings as; as.freq = value; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = 0; s->voice = AUD_open_out( @@ -255,7 +255,7 @@ static int lm4549_post_load(void *opaque, int version_id) struct audsettings as; as.freq = freq; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = 0; s->voice = AUD_open_out( @@ -292,7 +292,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque) /* Open a default voice */ as.freq = 48000; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = 0; s->voice = AUD_open_out( diff --git a/hw/audio/milkymist-ac97.c b/hw/audio/milkymist-ac97.c index bc8db71ae0..90cce1e6ed 100644 --- a/hw/audio/milkymist-ac97.c +++ b/hw/audio/milkymist-ac97.c @@ -308,7 +308,7 @@ static void milkymist_ac97_realize(DeviceState *dev, Error **errp) as.freq = 48000; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = 1; s->voice_in = AUD_open_in(&s->card, s->voice_in, diff --git a/hw/audio/pcspk.c b/hw/audio/pcspk.c index b80a62ce90..fdbb4b6e99 100644 --- a/hw/audio/pcspk.c +++ b/hw/audio/pcspk.c @@ -162,7 +162,7 @@ static void pcspk_initfn(Object *obj) static void pcspk_realizefn(DeviceState *dev, Error **errp) { - struct audsettings as = {PCSPK_SAMPLE_RATE, 1, AUD_FMT_U8, 0}; + struct audsettings as = {PCSPK_SAMPLE_RATE, 1, AUDIO_FORMAT_U8, 0}; ISADevice *isadev = ISA_DEVICE(dev); PCSpkState *s = PC_SPEAKER(dev); diff --git a/hw/audio/sb16.c b/hw/audio/sb16.c index c5b9bf79e8..65ea0cd938 100644 --- a/hw/audio/sb16.c +++ b/hw/audio/sb16.c @@ -66,7 +66,7 @@ typedef struct SB16State { int fmt_stereo; int fmt_signed; int fmt_bits; - audfmt_e fmt; + AudioFormat fmt; int dma_auto; int block_size; int fifo; @@ -224,7 +224,7 @@ static void continue_dma8 (SB16State *s) static void dma_cmd8 (SB16State *s, int mask, int dma_len) { - s->fmt = AUD_FMT_U8; + s->fmt = AUDIO_FORMAT_U8; s->use_hdma = 0; s->fmt_bits = 8; s->fmt_signed = 0; @@ -319,18 +319,18 @@ static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len) if (16 == s->fmt_bits) { if (s->fmt_signed) { - s->fmt = AUD_FMT_S16; + s->fmt = AUDIO_FORMAT_S16; } else { - s->fmt = AUD_FMT_U16; + s->fmt = AUDIO_FORMAT_U16; } } else { if (s->fmt_signed) { - s->fmt = AUD_FMT_S8; + s->fmt = AUDIO_FORMAT_S8; } else { - s->fmt = AUD_FMT_U8; + s->fmt = AUDIO_FORMAT_U8; } } @@ -852,7 +852,7 @@ static void legacy_reset (SB16State *s) as.freq = s->freq; as.nchannels = 1; - as.fmt = AUD_FMT_U8; + as.fmt = AUDIO_FORMAT_U8; as.endianness = 0; s->voice = AUD_open_out ( diff --git a/hw/audio/wm8750.c b/hw/audio/wm8750.c index 169b006ade..ca0ad73caf 100644 --- a/hw/audio/wm8750.c +++ b/hw/audio/wm8750.c @@ -201,7 +201,7 @@ static void wm8750_set_format(WM8750State *s) in_fmt.endianness = 0; in_fmt.nchannels = 2; in_fmt.freq = s->adc_hz; - in_fmt.fmt = AUD_FMT_S16; + in_fmt.fmt = AUDIO_FORMAT_S16; s->adc_voice[0] = AUD_open_in(&s->card, s->adc_voice[0], CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt); @@ -214,7 +214,7 @@ static void wm8750_set_format(WM8750State *s) out_fmt.endianness = 0; out_fmt.nchannels = 2; out_fmt.freq = s->dac_hz; - out_fmt.fmt = AUD_FMT_S16; + out_fmt.fmt = AUDIO_FORMAT_S16; s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0], CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt); @@ -681,7 +681,7 @@ uint32_t wm8750_adc_dat(void *opaque) if (s->idx_in >= sizeof(s->data_in)) { wm8750_in_load(s); if (s->idx_in >= sizeof(s->data_in)) { - return 0x80008000; /* silence in AUD_FMT_S16 sample format */ + return 0x80008000; /* silence in AUDIO_FORMAT_S16 sample format */ } } diff --git a/hw/block/nvme.c b/hw/block/nvme.c index 8325b5e88a..7caf92532a 100644 --- a/hw/block/nvme.c +++ b/hw/block/nvme.c @@ -324,8 +324,8 @@ static uint16_t nvme_write_zeros(NvmeCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd, const uint8_t data_shift = ns->id_ns.lbaf[lba_index].ds; uint64_t slba = le64_to_cpu(rw->slba); uint32_t nlb = le16_to_cpu(rw->nlb) + 1; - uint64_t aio_slba = slba << (data_shift - BDRV_SECTOR_BITS); - uint32_t aio_nlb = nlb << (data_shift - BDRV_SECTOR_BITS); + uint64_t offset = slba << data_shift; + uint32_t count = nlb << data_shift; if (unlikely(slba + nlb > ns->id_ns.nsze)) { trace_nvme_err_invalid_lba_range(slba, nlb, ns->id_ns.nsze); @@ -335,7 +335,7 @@ static uint16_t nvme_write_zeros(NvmeCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd, req->has_sg = false; block_acct_start(blk_get_stats(n->conf.blk), &req->acct, 0, BLOCK_ACCT_WRITE); - req->aiocb = blk_aio_pwrite_zeroes(n->conf.blk, aio_slba, aio_nlb, + req->aiocb = blk_aio_pwrite_zeroes(n->conf.blk, offset, count, BDRV_REQ_MAY_UNMAP, nvme_rw_cb, req); return NVME_NO_COMPLETE; } diff --git a/hw/core/machine.c b/hw/core/machine.c index 766ca5899d..743fef2898 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -22,6 +22,7 @@ #include "qemu/error-report.h" #include "sysemu/qtest.h" #include "hw/pci/pci.h" +#include "hw/mem/nvdimm.h" GlobalProperty hw_compat_3_1[] = { { "pcie-root-port", "x-speed", "2_5" }, @@ -481,6 +482,47 @@ static void machine_set_memory_encryption(Object *obj, const char *value, ms->memory_encryption = g_strdup(value); } +static bool machine_get_nvdimm(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return ms->nvdimms_state->is_enabled; +} + +static void machine_set_nvdimm(Object *obj, bool value, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + ms->nvdimms_state->is_enabled = value; +} + +static char *machine_get_nvdimm_persistence(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return g_strdup(ms->nvdimms_state->persistence_string); +} + +static void machine_set_nvdimm_persistence(Object *obj, const char *value, + Error **errp) +{ + MachineState *ms = MACHINE(obj); + NVDIMMState *nvdimms_state = ms->nvdimms_state; + + if (strcmp(value, "cpu") == 0) { + nvdimms_state->persistence = 3; + } else if (strcmp(value, "mem-ctrl") == 0) { + nvdimms_state->persistence = 2; + } else { + error_setg(errp, "-machine nvdimm-persistence=%s: unsupported option", + value); + return; + } + + g_free(nvdimms_state->persistence_string); + nvdimms_state->persistence_string = g_strdup(value); +} + void machine_class_allow_dynamic_sysbus_dev(MachineClass *mc, const char *type) { strList *item = g_new0(strList, 1); @@ -791,6 +833,28 @@ static void machine_initfn(Object *obj) ms->mem_merge = true; ms->enable_graphics = true; + if (mc->nvdimm_supported) { + Object *obj = OBJECT(ms); + + ms->nvdimms_state = g_new0(NVDIMMState, 1); + object_property_add_bool(obj, "nvdimm", + machine_get_nvdimm, machine_set_nvdimm, + &error_abort); + object_property_set_description(obj, "nvdimm", + "Set on/off to enable/disable " + "NVDIMM instantiation", NULL); + + object_property_add_str(obj, "nvdimm-persistence", + machine_get_nvdimm_persistence, + machine_set_nvdimm_persistence, + &error_abort); + object_property_set_description(obj, "nvdimm-persistence", + "Set NVDIMM persistence" + "Valid values are cpu, mem-ctrl", + NULL); + } + + /* Register notifier when init is done for sysbus sanity checks */ ms->sysbus_notifier.notify = machine_init_notify; qemu_add_machine_init_done_notifier(&ms->sysbus_notifier); @@ -809,6 +873,7 @@ static void machine_finalize(Object *obj) g_free(ms->dt_compatible); g_free(ms->firmware); g_free(ms->device_memory); + g_free(ms->nvdimms_state); } bool machine_usb(MachineState *machine) diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c index cc0f9bc9cc..11b09bd18c 100644 --- a/hw/display/xlnx_dp.c +++ b/hw/display/xlnx_dp.c @@ -1260,7 +1260,7 @@ static void xlnx_dp_realize(DeviceState *dev, Error **errp) as.freq = 44100; as.nchannels = 2; - as.fmt = AUD_FMT_S16; + as.fmt = AUDIO_FORMAT_S16; as.endianness = 0; AUD_register_card("xlnx_dp.audio", &s->aud_card); diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c index 9ecc96dcc7..416da318ae 100644 --- a/hw/i386/acpi-build.c +++ b/hw/i386/acpi-build.c @@ -1867,7 +1867,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, aml_append(scope, method); } - if (pcms->acpi_nvdimm_state.is_enabled) { + if (machine->nvdimms_state->is_enabled) { method = aml_method("_E04", 0, AML_NOTSERIALIZED); aml_append(method, aml_notify(aml_name("\\_SB.NVDR"), aml_int(0x80))); @@ -2704,9 +2704,9 @@ void acpi_build(AcpiBuildTables *tables, MachineState *machine) build_dmar_q35(tables_blob, tables->linker); } } - if (pcms->acpi_nvdimm_state.is_enabled) { + if (machine->nvdimms_state->is_enabled) { nvdimm_build_acpi(table_offsets, tables_blob, tables->linker, - &pcms->acpi_nvdimm_state, machine->ram_slots); + machine->nvdimms_state, machine->ram_slots); } /* Add tables supplied by user (if any) */ diff --git a/hw/i386/pc.c b/hw/i386/pc.c index d71dc28ef6..1cdaff5f4d 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -2075,6 +2075,7 @@ static void pc_memory_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, { const PCMachineState *pcms = PC_MACHINE(hotplug_dev); const PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms); + const MachineState *ms = MACHINE(hotplug_dev); const bool is_nvdimm = object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM); const uint64_t legacy_align = TARGET_PAGE_SIZE; @@ -2089,7 +2090,7 @@ static void pc_memory_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, return; } - if (is_nvdimm && !pcms->acpi_nvdimm_state.is_enabled) { + if (is_nvdimm && !ms->nvdimms_state->is_enabled) { error_setg(errp, "nvdimm is not enabled: missing 'nvdimm' in '-M'"); return; } @@ -2103,6 +2104,7 @@ static void pc_memory_plug(HotplugHandler *hotplug_dev, { Error *local_err = NULL; PCMachineState *pcms = PC_MACHINE(hotplug_dev); + MachineState *ms = MACHINE(hotplug_dev); bool is_nvdimm = object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM); pc_dimm_plug(PC_DIMM(dev), MACHINE(pcms), &local_err); @@ -2111,7 +2113,7 @@ static void pc_memory_plug(HotplugHandler *hotplug_dev, } if (is_nvdimm) { - nvdimm_plug(&pcms->acpi_nvdimm_state); + nvdimm_plug(ms->nvdimms_state); } hotplug_handler_plug(HOTPLUG_HANDLER(pcms->acpi_dev), dev, &error_abort); @@ -2552,47 +2554,6 @@ static void pc_machine_set_smm(Object *obj, Visitor *v, const char *name, visit_type_OnOffAuto(v, name, &pcms->smm, errp); } -static bool pc_machine_get_nvdimm(Object *obj, Error **errp) -{ - PCMachineState *pcms = PC_MACHINE(obj); - - return pcms->acpi_nvdimm_state.is_enabled; -} - -static void pc_machine_set_nvdimm(Object *obj, bool value, Error **errp) -{ - PCMachineState *pcms = PC_MACHINE(obj); - - pcms->acpi_nvdimm_state.is_enabled = value; -} - -static char *pc_machine_get_nvdimm_persistence(Object *obj, Error **errp) -{ - PCMachineState *pcms = PC_MACHINE(obj); - - return g_strdup(pcms->acpi_nvdimm_state.persistence_string); -} - -static void pc_machine_set_nvdimm_persistence(Object *obj, const char *value, - Error **errp) -{ - PCMachineState *pcms = PC_MACHINE(obj); - AcpiNVDIMMState *nvdimm_state = &pcms->acpi_nvdimm_state; - - if (strcmp(value, "cpu") == 0) - nvdimm_state->persistence = 3; - else if (strcmp(value, "mem-ctrl") == 0) - nvdimm_state->persistence = 2; - else { - error_setg(errp, "-machine nvdimm-persistence=%s: unsupported option", - value); - return; - } - - g_free(nvdimm_state->persistence_string); - nvdimm_state->persistence_string = g_strdup(value); -} - static bool pc_machine_get_smbus(Object *obj, Error **errp) { PCMachineState *pcms = PC_MACHINE(obj); @@ -2642,8 +2603,6 @@ static void pc_machine_initfn(Object *obj) pcms->max_ram_below_4g = 0; /* use default */ pcms->smm = ON_OFF_AUTO_AUTO; pcms->vmport = ON_OFF_AUTO_AUTO; - /* nvdimm is disabled on default. */ - pcms->acpi_nvdimm_state.is_enabled = false; /* acpi build is enabled by default if machine supports it */ pcms->acpi_build_enabled = PC_MACHINE_GET_CLASS(pcms)->has_acpi_build; pcms->smbus_enabled = true; @@ -2782,6 +2741,7 @@ static void pc_machine_class_init(ObjectClass *oc, void *data) hc->unplug = pc_machine_device_unplug_cb; nc->nmi_monitor_handler = x86_nmi; mc->default_cpu_type = TARGET_DEFAULT_CPU_TYPE; + mc->nvdimm_supported = true; object_class_property_add(oc, PC_MACHINE_DEVMEM_REGION_SIZE, "int", pc_machine_get_device_memory_region_size, NULL, @@ -2806,13 +2766,6 @@ static void pc_machine_class_init(ObjectClass *oc, void *data) object_class_property_set_description(oc, PC_MACHINE_VMPORT, "Enable vmport (pc & q35)", &error_abort); - object_class_property_add_bool(oc, PC_MACHINE_NVDIMM, - pc_machine_get_nvdimm, pc_machine_set_nvdimm, &error_abort); - - object_class_property_add_str(oc, PC_MACHINE_NVDIMM_PERSIST, - pc_machine_get_nvdimm_persistence, - pc_machine_set_nvdimm_persistence, &error_abort); - object_class_property_add_bool(oc, PC_MACHINE_SMBUS, pc_machine_get_smbus, pc_machine_set_smbus, &error_abort); diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index 8770ecada9..8ad8e885c6 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -297,8 +297,8 @@ static void pc_init1(MachineState *machine, PC_MACHINE_ACPI_DEVICE_PROP, &error_abort); } - if (pcms->acpi_nvdimm_state.is_enabled) { - nvdimm_init_acpi_state(&pcms->acpi_nvdimm_state, system_io, + if (machine->nvdimms_state->is_enabled) { + nvdimm_init_acpi_state(machine->nvdimms_state, system_io, pcms->fw_cfg, OBJECT(pcms)); } } diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index cfb9043e12..372c6b73be 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -329,8 +329,8 @@ static void pc_q35_init(MachineState *machine) pc_vga_init(isa_bus, host_bus); pc_nic_init(pcmc, isa_bus, host_bus); - if (pcms->acpi_nvdimm_state.is_enabled) { - nvdimm_init_acpi_state(&pcms->acpi_nvdimm_state, system_io, + if (machine->nvdimms_state->is_enabled) { + nvdimm_init_acpi_state(machine->nvdimms_state, system_io, pcms->fw_cfg, OBJECT(pcms)); } } diff --git a/hw/input/tsc210x.c b/hw/input/tsc210x.c index 2eb3cb9518..41731619bb 100644 --- a/hw/input/tsc210x.c +++ b/hw/input/tsc210x.c @@ -318,7 +318,7 @@ static void tsc2102_audio_output_update(TSC210xState *s) fmt.endianness = 0; fmt.nchannels = 2; fmt.freq = s->codec.tx_rate; - fmt.fmt = AUD_FMT_S16; + fmt.fmt = AUDIO_FORMAT_S16; s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0], "tsc2102.sink", s, (void *) tsc210x_audio_out_cb, &fmt); diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index 864f535011..c5e1e5581c 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -12,6 +12,10 @@ config SD config SDHCI bool + select SD + +config SDHCI_PCI + bool default y if PCI_DEVICES depends on PCI - select SD + select SDHCI diff --git a/hw/sd/Makefile.objs b/hw/sd/Makefile.objs index a99d9fbb04..06657279d1 100644 --- a/hw/sd/Makefile.objs +++ b/hw/sd/Makefile.objs @@ -2,6 +2,7 @@ common-obj-$(CONFIG_PL181) += pl181.o common-obj-$(CONFIG_SSI_SD) += ssi-sd.o common-obj-$(CONFIG_SD) += sd.o core.o sdmmc-internal.o common-obj-$(CONFIG_SDHCI) += sdhci.o +common-obj-$(CONFIG_SDHCI_PCI) += sdhci-pci.o obj-$(CONFIG_MILKYMIST) += milkymist-memcard.o obj-$(CONFIG_OMAP) += omap_mmc.o diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index 19665fd401..34141400f8 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -304,4 +304,38 @@ extern const VMStateDescription sdhci_vmstate; #define ESDHC_PRNSTS_SDSTB (1 << 3) +/* + * Default SD/MMC host controller features information, which will be + * presented in CAPABILITIES register of generic SD host controller at reset. + * + * support: + * - 3.3v and 1.8v voltages + * - SDMA/ADMA1/ADMA2 + * - high-speed + * max host controller R/W buffers size: 512B + * max clock frequency for SDclock: 52 MHz + * timeout clock frequency: 52 MHz + * + * does not support: + * - 3.0v voltage + * - 64-bit system bus + * - suspend/resume + */ +#define SDHC_CAPAB_REG_DEFAULT 0x057834b4 + +#define DEFINE_SDHCI_COMMON_PROPERTIES(_state) \ + DEFINE_PROP_UINT8("sd-spec-version", _state, sd_spec_version, 2), \ + DEFINE_PROP_UINT8("uhs", _state, uhs_mode, UHS_NOT_SUPPORTED), \ + \ + /* Capabilities registers provide information on supported + * features of this specific host controller implementation */ \ + DEFINE_PROP_UINT64("capareg", _state, capareg, SDHC_CAPAB_REG_DEFAULT), \ + DEFINE_PROP_UINT64("maxcurr", _state, maxcurr, 0) + +void sdhci_initfn(SDHCIState *s); +void sdhci_uninitfn(SDHCIState *s); +void sdhci_common_realize(SDHCIState *s, Error **errp); +void sdhci_common_unrealize(SDHCIState *s, Error **errp); +void sdhci_common_class_init(ObjectClass *klass, void *data); + #endif diff --git a/hw/sd/sdhci-pci.c b/hw/sd/sdhci-pci.c new file mode 100644 index 0000000000..f884661862 --- /dev/null +++ b/hw/sd/sdhci-pci.c @@ -0,0 +1,87 @@ +/* + * SDHCI device on PCI + * + * 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 of the License, or (at your + * option) any later version. + * + * 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 "qapi/error.h" +#include "hw/hw.h" +#include "hw/sd/sdhci.h" +#include "sdhci-internal.h" + +static Property sdhci_pci_properties[] = { + DEFINE_SDHCI_COMMON_PROPERTIES(SDHCIState), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sdhci_pci_realize(PCIDevice *dev, Error **errp) +{ + SDHCIState *s = PCI_SDHCI(dev); + Error *local_err = NULL; + + sdhci_initfn(s); + sdhci_common_realize(s, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + dev->config[PCI_CLASS_PROG] = 0x01; /* Standard Host supported DMA */ + dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + s->irq = pci_allocate_irq(dev); + s->dma_as = pci_get_address_space(dev); + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->iomem); +} + +static void sdhci_pci_exit(PCIDevice *dev) +{ + SDHCIState *s = PCI_SDHCI(dev); + + sdhci_common_unrealize(s, &error_abort); + sdhci_uninitfn(s); +} + +static void sdhci_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = sdhci_pci_realize; + k->exit = sdhci_pci_exit; + k->vendor_id = PCI_VENDOR_ID_REDHAT; + k->device_id = PCI_DEVICE_ID_REDHAT_SDHCI; + k->class_id = PCI_CLASS_SYSTEM_SDHCI; + dc->props = sdhci_pci_properties; + + sdhci_common_class_init(klass, data); +} + +static const TypeInfo sdhci_pci_info = { + .name = TYPE_PCI_SDHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(SDHCIState), + .class_init = sdhci_pci_class_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void sdhci_pci_register_type(void) +{ + type_register_static(&sdhci_pci_info); +} + +type_init(sdhci_pci_register_type) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 83f1574ffd..17ad5465a7 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -40,24 +40,6 @@ #define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val)) -/* Default SD/MMC host controller features information, which will be - * presented in CAPABILITIES register of generic SD host controller at reset. - * - * support: - * - 3.3v and 1.8v voltages - * - SDMA/ADMA1/ADMA2 - * - high-speed - * max host controller R/W buffers size: 512B - * max clock frequency for SDclock: 52 MHz - * timeout clock frequency: 52 MHz - * - * does not support: - * - 3.0v voltage - * - 64-bit system bus - * - suspend/resume - */ -#define SDHC_CAPAB_REG_DEFAULT 0x057834b4 - static inline unsigned int sdhci_get_fifolen(SDHCIState *s) { return 1 << (9 + FIELD_EX32(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH)); @@ -1328,16 +1310,7 @@ static void sdhci_init_readonly_registers(SDHCIState *s, Error **errp) /* --- qdev common --- */ -#define DEFINE_SDHCI_COMMON_PROPERTIES(_state) \ - DEFINE_PROP_UINT8("sd-spec-version", _state, sd_spec_version, 2), \ - DEFINE_PROP_UINT8("uhs", _state, uhs_mode, UHS_NOT_SUPPORTED), \ - \ - /* Capabilities registers provide information on supported - * features of this specific host controller implementation */ \ - DEFINE_PROP_UINT64("capareg", _state, capareg, SDHC_CAPAB_REG_DEFAULT), \ - DEFINE_PROP_UINT64("maxcurr", _state, maxcurr, 0) - -static void sdhci_initfn(SDHCIState *s) +void sdhci_initfn(SDHCIState *s) { qbus_create_inplace(&s->sdbus, sizeof(s->sdbus), TYPE_SDHCI_BUS, DEVICE(s), "sd-bus"); @@ -1348,7 +1321,7 @@ static void sdhci_initfn(SDHCIState *s) s->io_ops = &sdhci_mmio_ops; } -static void sdhci_uninitfn(SDHCIState *s) +void sdhci_uninitfn(SDHCIState *s) { timer_del(s->insert_timer); timer_free(s->insert_timer); @@ -1359,7 +1332,7 @@ static void sdhci_uninitfn(SDHCIState *s) s->fifo_buffer = NULL; } -static void sdhci_common_realize(SDHCIState *s, Error **errp) +void sdhci_common_realize(SDHCIState *s, Error **errp) { Error *local_err = NULL; @@ -1375,7 +1348,7 @@ static void sdhci_common_realize(SDHCIState *s, Error **errp) SDHC_REGISTERS_MAP_SIZE); } -static void sdhci_common_unrealize(SDHCIState *s, Error **errp) +void sdhci_common_unrealize(SDHCIState *s, Error **errp) { /* This function is expected to be called only once for each class: * - SysBus: via DeviceClass->unrealize(), @@ -1445,7 +1418,7 @@ const VMStateDescription sdhci_vmstate = { }, }; -static void sdhci_common_class_init(ObjectClass *klass, void *data) +void sdhci_common_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1454,66 +1427,6 @@ static void sdhci_common_class_init(ObjectClass *klass, void *data) dc->reset = sdhci_poweron_reset; } -/* --- qdev PCI --- */ - -static Property sdhci_pci_properties[] = { - DEFINE_SDHCI_COMMON_PROPERTIES(SDHCIState), - DEFINE_PROP_END_OF_LIST(), -}; - -static void sdhci_pci_realize(PCIDevice *dev, Error **errp) -{ - SDHCIState *s = PCI_SDHCI(dev); - Error *local_err = NULL; - - sdhci_initfn(s); - sdhci_common_realize(s, &local_err); - if (local_err) { - error_propagate(errp, local_err); - return; - } - - dev->config[PCI_CLASS_PROG] = 0x01; /* Standard Host supported DMA */ - dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ - s->irq = pci_allocate_irq(dev); - s->dma_as = pci_get_address_space(dev); - pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->iomem); -} - -static void sdhci_pci_exit(PCIDevice *dev) -{ - SDHCIState *s = PCI_SDHCI(dev); - - sdhci_common_unrealize(s, &error_abort); - sdhci_uninitfn(s); -} - -static void sdhci_pci_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->realize = sdhci_pci_realize; - k->exit = sdhci_pci_exit; - k->vendor_id = PCI_VENDOR_ID_REDHAT; - k->device_id = PCI_DEVICE_ID_REDHAT_SDHCI; - k->class_id = PCI_CLASS_SYSTEM_SDHCI; - dc->props = sdhci_pci_properties; - - sdhci_common_class_init(klass, data); -} - -static const TypeInfo sdhci_pci_info = { - .name = TYPE_PCI_SDHCI, - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(SDHCIState), - .class_init = sdhci_pci_class_init, - .interfaces = (InterfaceInfo[]) { - { INTERFACE_CONVENTIONAL_PCI_DEVICE }, - { }, - }, -}; - /* --- qdev SysBus --- */ static Property sdhci_sysbus_properties[] = { @@ -1846,7 +1759,6 @@ static const TypeInfo imx_usdhc_info = { static void sdhci_register_types(void) { - type_register_static(&sdhci_pci_info); type_register_static(&sdhci_sysbus_info); type_register_static(&sdhci_bus_info); type_register_static(&imx_usdhc_info); diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c index 28ac7c5165..c46d5eeb79 100644 --- a/hw/usb/dev-audio.c +++ b/hw/usb/dev-audio.c @@ -650,7 +650,7 @@ static void usb_audio_realize(USBDevice *dev, Error **errp) s->out.vol[1] = 240; /* 0 dB */ s->out.as.freq = USBAUDIO_SAMPLE_RATE; s->out.as.nchannels = 2; - s->out.as.fmt = AUD_FMT_S16; + s->out.as.fmt = AUDIO_FORMAT_S16; s->out.as.endianness = 0; streambuf_init(&s->out.buf, s->buffer); diff --git a/include/block/block.h b/include/block/block.h index 6a758a76f8..e452988b66 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -187,6 +187,9 @@ typedef struct BDRVReopenState { BlockDriverState *bs; int flags; BlockdevDetectZeroesOptions detect_zeroes; + bool backing_missing; + bool replace_backing_bs; /* new_backing_bs is ignored if this is false */ + BlockDriverState *new_backing_bs; /* If NULL then detach the current bs */ uint64_t perm, shared_perm; QDict *options; QDict *explicit_options; @@ -299,8 +302,9 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference, BlockDriverState *bdrv_new_open_driver(BlockDriver *drv, const char *node_name, int flags, Error **errp); BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, - BlockDriverState *bs, QDict *options); -int bdrv_reopen_multiple(AioContext *ctx, BlockReopenQueue *bs_queue, Error **errp); + BlockDriverState *bs, QDict *options, + bool keep_old_opts); +int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp); int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, Error **errp); int bdrv_reopen_prepare(BDRVReopenState *reopen_state, @@ -353,6 +357,11 @@ int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base, BlockDriverState *bdrv_find_overlay(BlockDriverState *active, BlockDriverState *bs); BlockDriverState *bdrv_find_base(BlockDriverState *bs); +bool bdrv_is_backing_chain_frozen(BlockDriverState *bs, BlockDriverState *base, + Error **errp); +int bdrv_freeze_backing_chain(BlockDriverState *bs, BlockDriverState *base, + Error **errp); +void bdrv_unfreeze_backing_chain(BlockDriverState *bs, BlockDriverState *base); typedef struct BdrvCheckResult { diff --git a/include/block/block_int.h b/include/block/block_int.h index a23cabaddd..01e855a066 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -383,6 +383,14 @@ struct BlockDriver { /* List of options for creating images, terminated by name == NULL */ QemuOptsList *create_opts; + /* + * If this driver supports reopening images this contains a + * NULL-terminated list of the runtime options that can be + * modified. If an option in this list is unspecified during + * reopen then it _must_ be reset to its default value or return + * an error. + */ + const char *const *mutable_opts; /* * Returns 0 for completed check, -errno for internal errors. @@ -711,6 +719,12 @@ struct BdrvChild { uint64_t backup_perm; uint64_t backup_shared_perm; + /* + * This link is frozen: the child can neither be replaced nor + * detached from the parent. + */ + bool frozen; + QLIST_ENTRY(BdrvChild) next; QLIST_ENTRY(BdrvChild) next_parent; }; diff --git a/include/hw/boards.h b/include/hw/boards.h index 9690c71a6d..e231860666 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -210,6 +210,7 @@ struct MachineClass { int nb_nodes, ram_addr_t size); bool ignore_boot_device_suffixes; bool smbus_no_migration_support; + bool nvdimm_supported; HotplugHandler *(*get_hotplug_handler)(MachineState *machine, DeviceState *dev); @@ -272,6 +273,7 @@ struct MachineState { const char *cpu_type; AccelState *accelerator; CPUArchIdList *possible_cpus; + struct NVDIMMState *nvdimms_state; }; #define DEFINE_MACHINE(namestr, machine_initfn) \ diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h index 276ff15d4d..ca65ef18af 100644 --- a/include/hw/i386/pc.h +++ b/include/hw/i386/pc.h @@ -47,8 +47,6 @@ struct PCMachineState { OnOffAuto vmport; OnOffAuto smm; - AcpiNVDIMMState acpi_nvdimm_state; - bool acpi_build_enabled; bool smbus_enabled; bool sata_enabled; @@ -76,8 +74,6 @@ struct PCMachineState { #define PC_MACHINE_MAX_RAM_BELOW_4G "max-ram-below-4g" #define PC_MACHINE_VMPORT "vmport" #define PC_MACHINE_SMM "smm" -#define PC_MACHINE_NVDIMM "nvdimm" -#define PC_MACHINE_NVDIMM_PERSIST "nvdimm-persistence" #define PC_MACHINE_SMBUS "smbus" #define PC_MACHINE_SATA "sata" #define PC_MACHINE_PIT "pit" diff --git a/include/hw/mem/nvdimm.h b/include/hw/mem/nvdimm.h index c5c9b3c7f8..523a9b3d4a 100644 --- a/include/hw/mem/nvdimm.h +++ b/include/hw/mem/nvdimm.h @@ -123,7 +123,7 @@ struct NvdimmFitBuffer { }; typedef struct NvdimmFitBuffer NvdimmFitBuffer; -struct AcpiNVDIMMState { +struct NVDIMMState { /* detect if NVDIMM support is enabled. */ bool is_enabled; @@ -141,13 +141,13 @@ struct AcpiNVDIMMState { int32_t persistence; char *persistence_string; }; -typedef struct AcpiNVDIMMState AcpiNVDIMMState; +typedef struct NVDIMMState NVDIMMState; -void nvdimm_init_acpi_state(AcpiNVDIMMState *state, MemoryRegion *io, +void nvdimm_init_acpi_state(NVDIMMState *state, MemoryRegion *io, FWCfgState *fw_cfg, Object *owner); void nvdimm_build_acpi(GArray *table_offsets, GArray *table_data, - BIOSLinker *linker, AcpiNVDIMMState *state, + BIOSLinker *linker, NVDIMMState *state, uint32_t ram_slots); -void nvdimm_plug(AcpiNVDIMMState *state); +void nvdimm_plug(NVDIMMState *state); void nvdimm_acpi_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev); #endif diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h index 840af09cb0..303d315c5d 100644 --- a/include/qemu/osdep.h +++ b/include/qemu/osdep.h @@ -571,6 +571,19 @@ void os_mem_prealloc(int fd, char *area, size_t sz, int smp_cpus, Error **errp); /** + * qemu_get_pmem_size: + * @filename: path to a pmem file + * @errp: pointer to a NULL-initialized error object + * + * Determine the size of a persistent memory file. Besides supporting files on + * DAX file systems, this function also supports Linux devdax character + * devices. + * + * Returns: the size or 0 on failure + */ +uint64_t qemu_get_pmem_size(const char *filename, Error **errp); + +/** * qemu_get_pid_name: * @pid: pid of a process * diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs index 77acca0209..729e5185c5 100644 --- a/qapi/Makefile.objs +++ b/qapi/Makefile.objs @@ -5,9 +5,9 @@ util-obj-y += opts-visitor.o qapi-clone-visitor.o util-obj-y += qmp-event.o util-obj-y += qapi-util.o -QAPI_COMMON_MODULES = authz block-core block char common crypto introspect -QAPI_COMMON_MODULES += job migration misc net rdma rocker run-state -QAPI_COMMON_MODULES += sockets tpm trace transaction ui +QAPI_COMMON_MODULES = audio authz block-core block char common crypto +QAPI_COMMON_MODULES += introspect job migration misc net rdma rocker +QAPI_COMMON_MODULES += run-state sockets tpm trace transaction ui QAPI_TARGET_MODULES = target QAPI_MODULES = $(QAPI_COMMON_MODULES) $(QAPI_TARGET_MODULES) diff --git a/qapi/audio.json b/qapi/audio.json new file mode 100644 index 0000000000..97aee37288 --- /dev/null +++ b/qapi/audio.json @@ -0,0 +1,304 @@ +# -*- mode: python -*- +# +# Copyright (C) 2015-2019 Zoltán KÅ‘vágó <DirtY.iCE.hu@gmail.com> +# +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. + +## +# @AudiodevPerDirectionOptions: +# +# General audio backend options that are used for both playback and +# recording. +# +# @fixed-settings: use fixed settings for host input/output. When off, +# frequency, channels and format must not be +# specified (default true) +# +# @frequency: frequency to use when using fixed settings +# (default 44100) +# +# @channels: number of channels when using fixed settings (default 2) +# +# @voices: number of voices to use (default 1) +# +# @format: sample format to use when using fixed settings +# (default s16) +# +# @buffer-length: the buffer length in microseconds +# +# Since: 4.0 +## +{ 'struct': 'AudiodevPerDirectionOptions', + 'data': { + '*fixed-settings': 'bool', + '*frequency': 'uint32', + '*channels': 'uint32', + '*voices': 'uint32', + '*format': 'AudioFormat', + '*buffer-length': 'uint32' } } + +## +# @AudiodevGenericOptions: +# +# Generic driver-specific options. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# Since: 4.0 +## +{ 'struct': 'AudiodevGenericOptions', + 'data': { + '*in': 'AudiodevPerDirectionOptions', + '*out': 'AudiodevPerDirectionOptions' } } + +## +# @AudiodevAlsaPerDirectionOptions: +# +# Options of the ALSA backend that are used for both playback and +# recording. +# +# @dev: the name of the ALSA device to use (default 'default') +# +# @period-length: the period length in microseconds +# +# @try-poll: attempt to use poll mode, falling back to non-polling +# access on failure (default true) +# +# Since: 4.0 +## +{ 'struct': 'AudiodevAlsaPerDirectionOptions', + 'base': 'AudiodevPerDirectionOptions', + 'data': { + '*dev': 'str', + '*period-length': 'uint32', + '*try-poll': 'bool' } } + +## +# @AudiodevAlsaOptions: +# +# Options of the ALSA audio backend. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# @threshold: set the threshold (in microseconds) when playback starts +# +# Since: 4.0 +## +{ 'struct': 'AudiodevAlsaOptions', + 'data': { + '*in': 'AudiodevAlsaPerDirectionOptions', + '*out': 'AudiodevAlsaPerDirectionOptions', + '*threshold': 'uint32' } } + +## +# @AudiodevCoreaudioPerDirectionOptions: +# +# Options of the Core Audio backend that are used for both playback and +# recording. +# +# @buffer-count: number of buffers +# +# Since: 4.0 +## +{ 'struct': 'AudiodevCoreaudioPerDirectionOptions', + 'base': 'AudiodevPerDirectionOptions', + 'data': { + '*buffer-count': 'uint32' } } + +## +# @AudiodevCoreaudioOptions: +# +# Options of the coreaudio audio backend. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# Since: 4.0 +## +{ 'struct': 'AudiodevCoreaudioOptions', + 'data': { + '*in': 'AudiodevCoreaudioPerDirectionOptions', + '*out': 'AudiodevCoreaudioPerDirectionOptions' } } + +## +# @AudiodevDsoundOptions: +# +# Options of the DirectSound audio backend. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# @latency: add extra latency to playback in microseconds +# (default 10000) +# +# Since: 4.0 +## +{ 'struct': 'AudiodevDsoundOptions', + 'data': { + '*in': 'AudiodevPerDirectionOptions', + '*out': 'AudiodevPerDirectionOptions', + '*latency': 'uint32' } } + +## +# @AudiodevOssPerDirectionOptions: +# +# Options of the OSS backend that are used for both playback and +# recording. +# +# @dev: file name of the OSS device (default '/dev/dsp') +# +# @buffer-count: number of buffers +# +# @try-poll: attempt to use poll mode, falling back to non-polling +# access on failure (default true) +# +# Since: 4.0 +## +{ 'struct': 'AudiodevOssPerDirectionOptions', + 'base': 'AudiodevPerDirectionOptions', + 'data': { + '*dev': 'str', + '*buffer-count': 'uint32', + '*try-poll': 'bool' } } + +## +# @AudiodevOssOptions: +# +# Options of the OSS audio backend. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# @try-mmap: try using memory-mapped access, falling back to +# non-memory-mapped access on failure (default true) +# +# @exclusive: open device in exclusive mode (vmix won't work) +# (default false) +# +# @dsp-policy: set the timing policy of the device (between 0 and 10, +# where smaller number means smaller latency but higher +# CPU usage) or -1 to use fragment mode (option ignored +# on some platforms) (default 5) +# +# Since: 4.0 +## +{ 'struct': 'AudiodevOssOptions', + 'data': { + '*in': 'AudiodevOssPerDirectionOptions', + '*out': 'AudiodevOssPerDirectionOptions', + '*try-mmap': 'bool', + '*exclusive': 'bool', + '*dsp-policy': 'uint32' } } + +## +# @AudiodevPaPerDirectionOptions: +# +# Options of the Pulseaudio backend that are used for both playback and +# recording. +# +# @name: name of the sink/source to use +# +# Since: 4.0 +## +{ 'struct': 'AudiodevPaPerDirectionOptions', + 'base': 'AudiodevPerDirectionOptions', + 'data': { + '*name': 'str' } } + +## +# @AudiodevPaOptions: +# +# Options of the PulseAudio audio backend. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# @server: PulseAudio server address (default: let PulseAudio choose) +# +# Since: 4.0 +## +{ 'struct': 'AudiodevPaOptions', + 'data': { + '*in': 'AudiodevPaPerDirectionOptions', + '*out': 'AudiodevPaPerDirectionOptions', + '*server': 'str' } } + +## +# @AudiodevWavOptions: +# +# Options of the wav audio backend. +# +# @in: options of the capture stream +# +# @out: options of the playback stream +# +# @path: name of the wav file to record (default 'qemu.wav') +# +# Since: 4.0 +## +{ 'struct': 'AudiodevWavOptions', + 'data': { + '*in': 'AudiodevPerDirectionOptions', + '*out': 'AudiodevPerDirectionOptions', + '*path': 'str' } } + + +## +# @AudioFormat: +# +# An enumeration of possible audio formats. +# +# Since: 4.0 +## +{ 'enum': 'AudioFormat', + 'data': [ 'u8', 's8', 'u16', 's16', 'u32', 's32' ] } + +## +# @AudiodevDriver: +# +# An enumeration of possible audio backend drivers. +# +# Since: 4.0 +## +{ 'enum': 'AudiodevDriver', + 'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'oss', 'pa', 'sdl', + 'spice', 'wav' ] } + +## +# @Audiodev: +# +# Options of an audio backend. +# +# @id: identifier of the backend +# +# @driver: the backend driver to use +# +# @timer-period: timer period (in microseconds, 0: use lowest possible) +# +# Since: 4.0 +## +{ 'union': 'Audiodev', + 'base': { + 'id': 'str', + 'driver': 'AudiodevDriver', + '*timer-period': 'uint32' }, + 'discriminator': 'driver', + 'data': { + 'none': 'AudiodevGenericOptions', + 'alsa': 'AudiodevAlsaOptions', + 'coreaudio': 'AudiodevCoreaudioOptions', + 'dsound': 'AudiodevDsoundOptions', + 'oss': 'AudiodevOssOptions', + 'pa': 'AudiodevPaOptions', + 'sdl': 'AudiodevGenericOptions', + 'spice': 'AudiodevGenericOptions', + 'wav': 'AudiodevWavOptions' } } diff --git a/qapi/block-core.json b/qapi/block-core.json index bbb9dcc141..ca684a8a04 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -555,20 +555,20 @@ # +------------------ # 10 50 100 # -# Since: 2.12 +# Since: 4.0 ## { 'struct': 'BlockLatencyHistogramInfo', 'data': {'boundaries': ['uint64'], 'bins': ['uint64'] } } ## -# @x-block-latency-histogram-set: +# @block-latency-histogram-set: # # Manage read, write and flush latency histograms for the device. # # If only @device parameter is specified, remove all present latency histograms # for the device. Otherwise, add/reset some of (or all) latency histograms. # -# @device: device name to set latency histogram for. +# @id: The name or QOM path of the guest device. # # @boundaries: list of interval boundary values (see description in # BlockLatencyHistogramInfo definition). If specified, all @@ -591,7 +591,7 @@ # # Returns: error if device is not found or any boundary arrays are invalid. # -# Since: 2.12 +# Since: 4.0 # # Example: set new histograms for all io types with intervals # [0, 10), [10, 50), [50, 100), [100, +inf): @@ -625,8 +625,8 @@ # "arguments": { "device": "drive0" } } # <- { "return": {} } ## -{ 'command': 'x-block-latency-histogram-set', - 'data': {'device': 'str', +{ 'command': 'block-latency-histogram-set', + 'data': {'id': 'str', '*boundaries': ['uint64'], '*boundaries-read': ['uint64'], '*boundaries-write': ['uint64'], @@ -912,11 +912,11 @@ # @timed_stats: Statistics specific to the set of previously defined # intervals of time (Since 2.5) # -# @x_rd_latency_histogram: @BlockLatencyHistogramInfo. (Since 2.12) +# @rd_latency_histogram: @BlockLatencyHistogramInfo. (Since 4.0) # -# @x_wr_latency_histogram: @BlockLatencyHistogramInfo. (Since 2.12) +# @wr_latency_histogram: @BlockLatencyHistogramInfo. (Since 4.0) # -# @x_flush_latency_histogram: @BlockLatencyHistogramInfo. (Since 2.12) +# @flush_latency_histogram: @BlockLatencyHistogramInfo. (Since 4.0) # # Since: 0.14.0 ## @@ -931,9 +931,9 @@ 'invalid_wr_operations': 'int', 'invalid_flush_operations': 'int', 'account_invalid': 'bool', 'account_failed': 'bool', 'timed_stats': ['BlockDeviceTimedStats'], - '*x_rd_latency_histogram': 'BlockLatencyHistogramInfo', - '*x_wr_latency_histogram': 'BlockLatencyHistogramInfo', - '*x_flush_latency_histogram': 'BlockLatencyHistogramInfo' } } + '*rd_latency_histogram': 'BlockLatencyHistogramInfo', + '*wr_latency_histogram': 'BlockLatencyHistogramInfo', + '*flush_latency_histogram': 'BlockLatencyHistogramInfo' } } ## # @BlockStats: @@ -4016,6 +4016,48 @@ { 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true } ## +# @x-blockdev-reopen: +# +# Reopens a block device using the given set of options. Any option +# not specified will be reset to its default value regardless of its +# previous status. If an option cannot be changed or a particular +# driver does not support reopening then the command will return an +# error. +# +# The top-level @node-name option (from BlockdevOptions) must be +# specified and is used to select the block device to be reopened. +# Other @node-name options must be either omitted or set to the +# current name of the appropriate node. This command won't change any +# node name and any attempt to do it will result in an error. +# +# In the case of options that refer to child nodes, the behavior of +# this command depends on the value: +# +# 1) A set of options (BlockdevOptions): the child is reopened with +# the specified set of options. +# +# 2) A reference to the current child: the child is reopened using +# its existing set of options. +# +# 3) A reference to a different node: the current child is replaced +# with the specified one. +# +# 4) NULL: the current child (if any) is detached. +# +# Options (1) and (2) are supported in all cases, but at the moment +# only @backing allows replacing or detaching an existing child. +# +# Unlike with blockdev-add, the @backing option must always be present +# unless the node being reopened does not have a backing file and its +# image does not have a default backing file name as part of its +# metadata. +# +# Since: 4.0 +## +{ 'command': 'x-blockdev-reopen', + 'data': 'BlockdevOptions', 'boxed': true } + +## # @blockdev-del: # # Deletes a block device that has been added using blockdev-add. diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index a34899c626..4bd1223637 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -99,3 +99,4 @@ { 'include': 'introspect.json' } { 'include': 'misc.json' } { 'include': 'target.json' } +{ 'include': 'audio.json' } diff --git a/qemu-deprecated.texi b/qemu-deprecated.texi index 63a13e6c67..2219386769 100644 --- a/qemu-deprecated.texi +++ b/qemu-deprecated.texi @@ -65,6 +65,13 @@ topologies described with -smp include all possible cpus, i.e. The @code{acl} option to the @code{-vnc} argument has been replaced by the @code{tls-authz} and @code{sasl-authz} options. +@subsection QEMU_AUDIO_ environment variables and -audio-help (since 4.0) + +The ``-audiodev'' argument is now the preferred way to specify audio +backend settings instead of environment variables. To ease migration to +the new format, the ``-audiodev-help'' option can be used to convert +the current values of the environment variables to ``-audiodev'' options. + @section QEMU Machine Protocol (QMP) commands @subsection block-dirty-bitmap-add "autoload" parameter (since 2.12.0) diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c index b9f189f09b..35dcdcf413 100644 --- a/qemu-io-cmds.c +++ b/qemu-io-cmds.c @@ -2080,8 +2080,8 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv) } bdrv_subtree_drained_begin(bs); - brq = bdrv_reopen_queue(NULL, bs, opts); - bdrv_reopen_multiple(bdrv_get_aio_context(bs), brq, &local_err); + brq = bdrv_reopen_queue(NULL, bs, opts, true); + bdrv_reopen_multiple(brq, &local_err); bdrv_subtree_drained_end(bs); if (local_err) { diff --git a/qemu-options.hx b/qemu-options.hx index 7118d90352..8693f5fa3c 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -416,14 +416,244 @@ The default is @code{en-us}. ETEXI +HXCOMM Deprecated by -audiodev DEF("audio-help", 0, QEMU_OPTION_audio_help, - "-audio-help print list of audio drivers and their options\n", + "-audio-help show -audiodev equivalent of the currently specified audio settings\n", QEMU_ARCH_ALL) STEXI @item -audio-help @findex -audio-help -Will show the audio subsystem help: list of drivers, tunable -parameters. +Will show the -audiodev equivalent of the currently specified +(deprecated) environment variables. +ETEXI + +DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev, + "-audiodev [driver=]driver,id=id[,prop[=value][,...]]\n" + " specifies the audio backend to use\n" + " id= identifier of the backend\n" + " timer-period= timer period in microseconds\n" + " in|out.fixed-settings= use fixed settings for host audio\n" + " in|out.frequency= frequency to use with fixed settings\n" + " in|out.channels= number of channels to use with fixed settings\n" + " in|out.format= sample format to use with fixed settings\n" + " valid values: s8, s16, s32, u8, u16, u32\n" + " in|out.voices= number of voices to use\n" + " in|out.buffer-len= length of buffer in microseconds\n" + "-audiodev none,id=id,[,prop[=value][,...]]\n" + " dummy driver that discards all output\n" +#ifdef CONFIG_AUDIO_ALSA + "-audiodev alsa,id=id[,prop[=value][,...]]\n" + " in|out.dev= name of the audio device to use\n" + " in|out.period-len= length of period in microseconds\n" + " in|out.try-poll= attempt to use poll mode\n" + " threshold= threshold (in microseconds) when playback starts\n" +#endif +#ifdef CONFIG_AUDIO_COREAUDIO + "-audiodev coreaudio,id=id[,prop[=value][,...]]\n" + " in|out.buffer-count= number of buffers\n" +#endif +#ifdef CONFIG_AUDIO_DSOUND + "-audiodev dsound,id=id[,prop[=value][,...]]\n" + " latency= add extra latency to playback in microseconds\n" +#endif +#ifdef CONFIG_AUDIO_OSS + "-audiodev oss,id=id[,prop[=value][,...]]\n" + " in|out.dev= path of the audio device to use\n" + " in|out.buffer-count= number of buffers\n" + " in|out.try-poll= attempt to use poll mode\n" + " try-mmap= try using memory mapped access\n" + " exclusive= open device in exclusive mode\n" + " dsp-policy= set timing policy (0..10), -1 to use fragment mode\n" +#endif +#ifdef CONFIG_AUDIO_PA + "-audiodev pa,id=id[,prop[=value][,...]]\n" + " server= PulseAudio server address\n" + " in|out.name= source/sink device name\n" +#endif +#ifdef CONFIG_AUDIO_SDL + "-audiodev sdl,id=id[,prop[=value][,...]]\n" +#endif +#ifdef CONFIG_SPICE + "-audiodev spice,id=id[,prop[=value][,...]]\n" +#endif + "-audiodev wav,id=id[,prop[=value][,...]]\n" + " path= path of wav file to record\n", + QEMU_ARCH_ALL) +STEXI +@item -audiodev [driver=]@var{driver},id=@var{id}[,@var{prop}[=@var{value}][,...]] +@findex -audiodev +Adds a new audio backend @var{driver} identified by @var{id}. There are +global and driver specific properties. Some values can be set +differently for input and output, they're marked with @code{in|out.}. +You can set the input's property with @code{in.@var{prop}} and the +output's property with @code{out.@var{prop}}. For example: +@example +-audiodev alsa,id=example,in.frequency=44110,out.frequency=8000 +-audiodev alsa,id=example,out.channels=1 # leaves in.channels unspecified +@end example + +Valid global options are: + +@table @option +@item id=@var{identifier} +Identifies the audio backend. + +@item timer-period=@var{period} +Sets the timer @var{period} used by the audio subsystem in microseconds. +Default is 10000 (10 ms). + +@item in|out.fixed-settings=on|off +Use fixed settings for host audio. When off, it will change based on +how the guest opens the sound card. In this case you must not specify +@var{frequency}, @var{channels} or @var{format}. Default is on. + +@item in|out.frequency=@var{frequency} +Specify the @var{frequency} to use when using @var{fixed-settings}. +Default is 44100Hz. + +@item in|out.channels=@var{channels} +Specify the number of @var{channels} to use when using +@var{fixed-settings}. Default is 2 (stereo). + +@item in|out.format=@var{format} +Specify the sample @var{format} to use when using @var{fixed-settings}. +Valid values are: @code{s8}, @code{s16}, @code{s32}, @code{u8}, +@code{u16}, @code{u32}. Default is @code{s16}. + +@item in|out.voices=@var{voices} +Specify the number of @var{voices} to use. Default is 1. + +@item in|out.buffer=@var{usecs} +Sets the size of the buffer in microseconds. + +@end table + +@item -audiodev none,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a dummy backend that discards all outputs. This backend has no +backend specific properties. + +@item -audiodev alsa,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates backend using the ALSA. This backend is only available on +Linux. + +ALSA specific options are: + +@table @option + +@item in|out.dev=@var{device} +Specify the ALSA @var{device} to use for input and/or output. Default +is @code{default}. + +@item in|out.period-len=@var{usecs} +Sets the period length in microseconds. + +@item in|out.try-poll=on|off +Attempt to use poll mode with the device. Default is on. + +@item threshold=@var{threshold} +Threshold (in microseconds) when playback starts. Default is 0. + +@end table + +@item -audiodev coreaudio,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using Apple's Core Audio. This backend is only +available on Mac OS and only supports playback. + +Core Audio specific options are: + +@table @option + +@item in|out.buffer-count=@var{count} +Sets the @var{count} of the buffers. + +@end table + +@item -audiodev dsound,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using Microsoft's DirectSound. This backend is only +available on Windows and only supports playback. + +DirectSound specific options are: + +@table @option + +@item latency=@var{usecs} +Add extra @var{usecs} microseconds latency to playback. Default is +10000 (10 ms). + +@end table + +@item -audiodev oss,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using OSS. This backend is available on most +Unix-like systems. + +OSS specific options are: + +@table @option + +@item in|out.dev=@var{device} +Specify the file name of the OSS @var{device} to use. Default is +@code{/dev/dsp}. + +@item in|out.buffer-count=@var{count} +Sets the @var{count} of the buffers. + +@item in|out.try-poll=on|of +Attempt to use poll mode with the device. Default is on. + +@item try-mmap=on|off +Try using memory mapped device access. Default is off. + +@item exclusive=on|off +Open the device in exclusive mode (vmix won't work in this case). +Default is off. + +@item dsp-policy=@var{policy} +Sets the timing policy (between 0 and 10, where smaller number means +smaller latency but higher CPU usage). Use -1 to use buffer sizes +specified by @code{buffer} and @code{buffer-count}. This option is +ignored if you do not have OSS 4. Default is 5. + +@end table + +@item -audiodev pa,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using PulseAudio. This backend is available on most +systems. + +PulseAudio specific options are: + +@table @option + +@item server=@var{server} +Sets the PulseAudio @var{server} to connect to. + +@item in|out.name=@var{sink} +Use the specified source/sink for recording/playback. + +@end table + +@item -audiodev sdl,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend using SDL. This backend is available on most systems, +but you should use your platform's native backend if possible. This +backend has no backend specific properties. + +@item -audiodev spice,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend that sends audio through SPICE. This backend requires +@code{-spice} and automatically selected in that case, so usually you +can ignore this option. This backend has no backend specific +properties. + +@item -audiodev wav,id=@var{id}[,@var{prop}[=@var{value}][,...]] +Creates a backend that writes audio to a WAV file. + +Backend specific options are: + +@table @option + +@item path=@var{path} +Write recorded audio into the specified file. Default is +@code{qemu.wav}. + +@end table ETEXI DEF("soundhw", HAS_ARG, QEMU_OPTION_soundhw, diff --git a/scripts/decodetree.py b/scripts/decodetree.py index e342d278b8..aa790b596a 100755 --- a/scripts/decodetree.py +++ b/scripts/decodetree.py @@ -17,139 +17,7 @@ # # Generate a decoding tree from a specification file. -# -# The tree is built from instruction "patterns". A pattern may represent -# a single architectural instruction or a group of same, depending on what -# is convenient for further processing. -# -# Each pattern has "fixedbits" & "fixedmask", the combination of which -# describes the condition under which the pattern is matched: -# -# (insn & fixedmask) == fixedbits -# -# Each pattern may have "fields", which are extracted from the insn and -# passed along to the translator. Examples of such are registers, -# immediates, and sub-opcodes. -# -# In support of patterns, one may declare fields, argument sets, and -# formats, each of which may be re-used to simplify further definitions. -# -# *** Field syntax: -# -# field_def := '%' identifier ( unnamed_field )+ ( !function=identifier )? -# unnamed_field := number ':' ( 's' ) number -# -# For unnamed_field, the first number is the least-significant bit position of -# the field and the second number is the length of the field. If the 's' is -# present, the field is considered signed. If multiple unnamed_fields are -# present, they are concatenated. In this way one can define disjoint fields. -# -# If !function is specified, the concatenated result is passed through the -# named function, taking and returning an integral value. -# -# FIXME: the fields of the structure into which this result will be stored -# is restricted to "int". Which means that we cannot expand 64-bit items. -# -# Field examples: -# -# %disp 0:s16 -- sextract(i, 0, 16) -# %imm9 16:6 10:3 -- extract(i, 16, 6) << 3 | extract(i, 10, 3) -# %disp12 0:s1 1:1 2:10 -- sextract(i, 0, 1) << 11 -# | extract(i, 1, 1) << 10 -# | extract(i, 2, 10) -# %shimm8 5:s8 13:1 !function=expand_shimm8 -# -- expand_shimm8(sextract(i, 5, 8) << 1 -# | extract(i, 13, 1)) -# -# *** Argument set syntax: -# -# args_def := '&' identifier ( args_elt )+ ( !extern )? -# args_elt := identifier -# -# Each args_elt defines an argument within the argument set. -# Each argument set will be rendered as a C structure "arg_$name" -# with each of the fields being one of the member arguments. -# -# If !extern is specified, the backing structure is assumed to -# have been already declared, typically via a second decoder. -# -# Argument set examples: -# -# ®3 ra rb rc -# &loadstore reg base offset -# -# *** Format syntax: -# -# fmt_def := '@' identifier ( fmt_elt )+ -# fmt_elt := fixedbit_elt | field_elt | field_ref | args_ref -# fixedbit_elt := [01.-]+ -# field_elt := identifier ':' 's'? number -# field_ref := '%' identifier | identifier '=' '%' identifier -# args_ref := '&' identifier -# -# Defining a format is a handy way to avoid replicating groups of fields -# across many instruction patterns. -# -# A fixedbit_elt describes a contiguous sequence of bits that must -# be 1, 0, [.-] for don't care. The difference between '.' and '-' -# is that '.' means that the bit will be covered with a field or a -# final [01] from the pattern, and '-' means that the bit is really -# ignored by the cpu and will not be specified. -# -# A field_elt describes a simple field only given a width; the position of -# the field is implied by its position with respect to other fixedbit_elt -# and field_elt. -# -# If any fixedbit_elt or field_elt appear then all bits must be defined. -# Padding with a fixedbit_elt of all '.' is an easy way to accomplish that. -# -# A field_ref incorporates a field by reference. This is the only way to -# add a complex field to a format. A field may be renamed in the process -# via assignment to another identifier. This is intended to allow the -# same argument set be used with disjoint named fields. -# -# A single args_ref may specify an argument set to use for the format. -# The set of fields in the format must be a subset of the arguments in -# the argument set. If an argument set is not specified, one will be -# inferred from the set of fields. -# -# It is recommended, but not required, that all field_ref and args_ref -# appear at the end of the line, not interleaving with fixedbit_elf or -# field_elt. -# -# Format examples: -# -# @opr ...... ra:5 rb:5 ... 0 ....... rc:5 -# @opi ...... ra:5 lit:8 1 ....... rc:5 -# -# *** Pattern syntax: -# -# pat_def := identifier ( pat_elt )+ -# pat_elt := fixedbit_elt | field_elt | field_ref -# | args_ref | fmt_ref | const_elt -# fmt_ref := '@' identifier -# const_elt := identifier '=' number -# -# The fixedbit_elt and field_elt specifiers are unchanged from formats. -# A pattern that does not specify a named format will have one inferred -# from a referenced argument set (if present) and the set of fields. -# -# A const_elt allows a argument to be set to a constant value. This may -# come in handy when fields overlap between patterns and one has to -# include the values in the fixedbit_elt instead. -# -# The decoder will call a translator function for each pattern matched. -# -# Pattern examples: -# -# addl_r 010000 ..... ..... .... 0000000 ..... @opr -# addl_i 010000 ..... ..... .... 0000000 ..... @opi -# -# which will, in part, invoke -# -# trans_addl_r(ctx, &arg_opr, insn) -# and -# trans_addl_i(ctx, &arg_opi, insn) +# See the syntax and semantics in docs/devel/decodetree.rst. # import os @@ -163,6 +31,7 @@ fields = {} arguments = {} formats = {} patterns = [] +allpatterns = [] translate_prefix = 'trans' translate_scope = 'static ' @@ -432,13 +301,7 @@ class General: self.fields = flds def __str__(self): - r = self.name - if self.base: - r = r + ' ' + self.base.name - else: - r = r + ' ' + str(self.fields) - r = r + ' ' + str_match_bits(self.fixedbits, self.fixedmask) - return r + return self.name + ' ' + str_match_bits(self.fixedbits, self.fixedmask) def str1(self, i): return str_indent(i) + self.__str__() @@ -449,7 +312,8 @@ class Format(General): """Class representing an instruction format""" def extract_name(self): - return 'extract_' + self.name + global decode_function + return decode_function + '_extract_' + self.name def output_extract(self): output('static void ', self.extract_name(), '(', @@ -480,11 +344,52 @@ class Pattern(General): output(ind, self.base.extract_name(), '(&u.f_', arg, ', insn);\n') for n, f in self.fields.items(): output(ind, 'u.f_', arg, '.', n, ' = ', f.str_extract(), ';\n') - output(ind, 'return ', translate_prefix, '_', self.name, - '(ctx, &u.f_', arg, ');\n') + output(ind, 'if (', translate_prefix, '_', self.name, + '(ctx, &u.f_', arg, ')) return true;\n') # end Pattern +class MultiPattern(General): + """Class representing an overlapping set of instruction patterns""" + + def __init__(self, lineno, pats, fixb, fixm, udfm): + self.file = input_file + self.lineno = lineno + self.pats = pats + self.base = None + self.fixedbits = fixb + self.fixedmask = fixm + self.undefmask = udfm + + def __str__(self): + r = "{" + for p in self.pats: + r = r + ' ' + str(p) + return r + "}" + + def output_decl(self): + for p in self.pats: + p.output_decl() + + def output_code(self, i, extracted, outerbits, outermask): + global translate_prefix + ind = str_indent(i) + for p in self.pats: + if outermask != p.fixedmask: + innermask = p.fixedmask & ~outermask + innerbits = p.fixedbits & ~outermask + output(ind, 'if ((insn & ', + '0x{0:08x}) == 0x{1:08x}'.format(innermask, innerbits), + ') {\n') + output(ind, ' /* ', + str_match_bits(p.fixedbits, p.fixedmask), ' */\n') + p.output_code(i + 4, extracted, p.fixedbits, p.fixedmask) + output(ind, '}\n') + else: + p.output_code(i, extracted, p.fixedbits, p.fixedmask) +#end MultiPattern + + def parse_field(lineno, name, toks): """Parse one instruction field from TOKS at LINENO""" global fields @@ -637,6 +542,7 @@ def parse_generic(lineno, is_format, name, toks): global arguments global formats global patterns + global allpatterns global re_ident global insnwidth global insnmask @@ -684,7 +590,7 @@ def parse_generic(lineno, is_format, name, toks): continue # 'Foo=number' sets an argument field to a constant value - if re_fullmatch(re_ident + '=[0-9]+', t): + if re_fullmatch(re_ident + '=[+-]?[0-9]+', t): (fname, value) = t.split('=') value = int(value) flds = add_field(lineno, flds, fname, ConstField(value)) @@ -716,6 +622,8 @@ def parse_generic(lineno, is_format, name, toks): sign = True flen = flen[1:] shift = int(flen, 10) + if shift + width > insnwidth: + error(lineno, 'field {0} exceeds insnwidth'.format(fname)) f = Field(sign, insnwidth - width - shift, shift) flds = add_field(lineno, flds, fname, f) fixedbits <<= shift @@ -781,6 +689,7 @@ def parse_generic(lineno, is_format, name, toks): pat = Pattern(name, lineno, fmt, fixedbits, fixedmask, undefmask, fieldmask, flds) patterns.append(pat) + allpatterns.append(pat) # Validate the masks that we have assembled. if fieldmask & fixedmask: @@ -799,17 +708,66 @@ def parse_generic(lineno, is_format, name, toks): .format(allbits ^ insnmask)) # end parse_general +def build_multi_pattern(lineno, pats): + """Validate the Patterns going into a MultiPattern.""" + global patterns + global insnmask + + if len(pats) < 2: + error(lineno, 'less than two patterns within braces') + + fixedmask = insnmask + undefmask = insnmask + + # Collect fixed/undefmask for all of the children. + # Move the defining lineno back to that of the first child. + for p in pats: + fixedmask &= p.fixedmask + undefmask &= p.undefmask + if p.lineno < lineno: + lineno = p.lineno + + repeat = True + while repeat: + if fixedmask == 0: + error(lineno, 'no overlap in patterns within braces') + fixedbits = None + for p in pats: + thisbits = p.fixedbits & fixedmask + if fixedbits is None: + fixedbits = thisbits + elif fixedbits != thisbits: + fixedmask &= ~(fixedbits ^ thisbits) + break + else: + repeat = False + + mp = MultiPattern(lineno, pats, fixedbits, fixedmask, undefmask) + patterns.append(mp) +# end build_multi_pattern def parse_file(f): """Parse all of the patterns within a file""" + global patterns + # Read all of the lines of the file. Concatenate lines # ending in backslash; discard empty lines and comments. toks = [] lineno = 0 + nesting = 0 + saved_pats = [] + for line in f: lineno += 1 + # Expand and strip spaces, to find indent. + line = line.rstrip() + line = line.expandtabs() + len1 = len(line) + line = line.lstrip() + len2 = len(line) + # Discard comments end = line.find('#') if end >= 0: @@ -819,10 +777,18 @@ def parse_file(f): if len(toks) != 0: # Next line after continuation toks.extend(t) - elif len(t) == 0: - # Empty line - continue else: + # Allow completely blank lines. + if len1 == 0: + continue + indent = len1 - len2 + # Empty line due to comment. + if len(t) == 0: + # Indentation must be correct, even for comment lines. + if indent != nesting: + error(lineno, 'indentation ', indent, ' != ', nesting) + continue + start_lineno = lineno toks = t # Continuation? @@ -830,21 +796,47 @@ def parse_file(f): toks.pop() continue - if len(toks) < 2: - error(lineno, 'short line') - name = toks[0] del toks[0] + # End nesting? + if name == '}': + if nesting == 0: + error(start_lineno, 'mismatched close brace') + if len(toks) != 0: + error(start_lineno, 'extra tokens after close brace') + nesting -= 2 + if indent != nesting: + error(start_lineno, 'indentation ', indent, ' != ', nesting) + pats = patterns + patterns = saved_pats.pop() + build_multi_pattern(lineno, pats) + toks = [] + continue + + # Everything else should have current indentation. + if indent != nesting: + error(start_lineno, 'indentation ', indent, ' != ', nesting) + + # Start nesting? + if name == '{': + if len(toks) != 0: + error(start_lineno, 'extra tokens after open brace') + saved_pats.append(patterns) + patterns = [] + nesting += 2 + toks = [] + continue + # Determine the type of object needing to be parsed. if name[0] == '%': - parse_field(lineno, name[1:], toks) + parse_field(start_lineno, name[1:], toks) elif name[0] == '&': - parse_arguments(lineno, name[1:], toks) + parse_arguments(start_lineno, name[1:], toks) elif name[0] == '@': - parse_generic(lineno, True, name[1:], toks) + parse_generic(start_lineno, True, name[1:], toks) else: - parse_generic(lineno, False, name, toks) + parse_generic(start_lineno, False, name, toks) toks = [] # end parse_file @@ -909,23 +901,22 @@ class Tree: output(ind, ' /* ', str_match_bits(innerbits, innermask), ' */\n') s.output_code(i + 4, extracted, innerbits, innermask) + output(ind, ' return false;\n') output(ind, '}\n') - output(ind, 'return false;\n') # end Tree def build_tree(pats, outerbits, outermask): # Find the intersection of all remaining fixedmask. - innermask = ~outermask + innermask = ~outermask & insnmask for i in pats: innermask &= i.fixedmask if innermask == 0: - pnames = [] + text = 'overlapping patterns:' for p in pats: - pnames.append(p.name + ':' + p.file + ':' + str(p.lineno)) - error_with_file(pats[0].file, pats[0].lineno, - 'overlapping patterns:', pnames) + text += '\n' + p.file + ':' + str(p.lineno) + ': ' + str(p) + error_with_file(pats[0].file, pats[0].lineno, text) fullmask = outermask | innermask @@ -978,6 +969,7 @@ def main(): global arguments global formats global patterns + global allpatterns global translate_scope global translate_prefix global output_fd @@ -990,7 +982,8 @@ def main(): decode_scope = 'static ' - long_opts = ['decode=', 'translate=', 'output=', 'insnwidth='] + long_opts = ['decode=', 'translate=', 'output=', 'insnwidth=', + 'static-decode='] try: (opts, args) = getopt.getopt(sys.argv[1:], 'o:w:', long_opts) except getopt.GetoptError as err: @@ -1001,6 +994,8 @@ def main(): elif o == '--decode': decode_function = a decode_scope = '' + elif o == '--static-decode': + decode_function = a elif o == '--translate': translate_prefix = a translate_scope = '' @@ -1039,7 +1034,7 @@ def main(): # Make sure that the argument sets are the same, and declare the # function only once. out_pats = {} - for i in patterns: + for i in allpatterns: if i.name in out_pats: p = out_pats[i.name] if i.base.base != p.base.base: @@ -1057,14 +1052,16 @@ def main(): '(DisasContext *ctx, ', insntype, ' insn)\n{\n') i4 = str_indent(4) - output(i4, 'union {\n') - for n in sorted(arguments.keys()): - f = arguments[n] - output(i4, i4, f.struct_name(), ' f_', f.name, ';\n') - output(i4, '} u;\n\n') - t.output_code(4, False, 0, 0) + if len(allpatterns) != 0: + output(i4, 'union {\n') + for n in sorted(arguments.keys()): + f = arguments[n] + output(i4, i4, f.struct_name(), ' f_', f.name, ';\n') + output(i4, '} u;\n\n') + t.output_code(4, False, 0, 0) + output(i4, 'return false;\n') output('}\n') if output_file: diff --git a/scripts/qemugdb/timers.py b/scripts/qemugdb/timers.py index 51ea04b5e2..f0e132d27a 100644 --- a/scripts/qemugdb/timers.py +++ b/scripts/qemugdb/timers.py @@ -6,8 +6,10 @@ # # Author: Alex Bennée <alex.bennee@linaro.org> # -# This work is licensed under the terms of the GNU GPL, version 2. See -# the COPYING file in the top-level directory. +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. +# +# SPDX-License-Identifier: GPL-2.0-or-later # 'qemu timers' -- display the current timerlists diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index 9fec46e2ed..7776c7b141 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -73,7 +73,7 @@ import sys import os import errno import atexit -import shlex +import re sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) from qemu import qmp @@ -222,7 +222,7 @@ class QMPShell(qmp.QEMUMonitorProtocol): < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] """ - cmdargs = shlex.split(cmdline) + cmdargs = re.findall(r'''(?:[^\s"']|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+''', cmdline) # Transactional CLI entry/exit: if cmdargs[0] == 'transaction(': diff --git a/target/hppa/cpu.h b/target/hppa/cpu.h index 861bbb1f16..c062c7969c 100644 --- a/target/hppa/cpu.h +++ b/target/hppa/cpu.h @@ -143,6 +143,10 @@ #endif #define CR_RC 0 +#define CR_PID1 8 +#define CR_PID2 9 +#define CR_PID3 12 +#define CR_PID4 13 #define CR_SCRCCR 10 #define CR_SAR 11 #define CR_IVA 14 @@ -341,6 +345,12 @@ target_ureg cpu_hppa_get_psw(CPUHPPAState *env); void cpu_hppa_put_psw(CPUHPPAState *env, target_ureg); void cpu_hppa_loaded_fr0(CPUHPPAState *env); +#ifdef CONFIG_USER_ONLY +static inline void cpu_hppa_change_prot_id(CPUHPPAState *env) { } +#else +void cpu_hppa_change_prot_id(CPUHPPAState *env); +#endif + #define cpu_signal_handler cpu_hppa_signal_handler int cpu_hppa_signal_handler(int host_signum, void *pinfo, void *puc); diff --git a/target/hppa/gdbstub.c b/target/hppa/gdbstub.c index 3157a690f2..983bf92aaf 100644 --- a/target/hppa/gdbstub.c +++ b/target/hppa/gdbstub.c @@ -93,19 +93,19 @@ int hppa_cpu_gdb_read_register(CPUState *cs, uint8_t *mem_buf, int n) val = env->cr[CR_RC]; break; case 52: - val = env->cr[8]; + val = env->cr[CR_PID1]; break; case 53: - val = env->cr[9]; + val = env->cr[CR_PID2]; break; case 54: val = env->cr[CR_SCRCCR]; break; case 55: - val = env->cr[12]; + val = env->cr[CR_PID3]; break; case 56: - val = env->cr[13]; + val = env->cr[CR_PID4]; break; case 57: val = env->cr[24]; @@ -224,19 +224,23 @@ int hppa_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) env->cr[CR_RC] = val; break; case 52: - env->cr[8] = val; + env->cr[CR_PID1] = val; + cpu_hppa_change_prot_id(env); break; case 53: - env->cr[9] = val; + env->cr[CR_PID2] = val; + cpu_hppa_change_prot_id(env); break; case 54: env->cr[CR_SCRCCR] = val; break; case 55: - env->cr[12] = val; + env->cr[CR_PID3] = val; + cpu_hppa_change_prot_id(env); break; case 56: - env->cr[13] = val; + env->cr[CR_PID4] = val; + cpu_hppa_change_prot_id(env); break; case 57: env->cr[24] = val; diff --git a/target/hppa/helper.c b/target/hppa/helper.c index 6539061e52..ac750b62ef 100644 --- a/target/hppa/helper.c +++ b/target/hppa/helper.c @@ -21,6 +21,7 @@ #include "cpu.h" #include "fpu/softfloat.h" +#include "exec/exec-all.h" #include "exec/helper-proto.h" target_ureg cpu_hppa_get_psw(CPUHPPAState *env) @@ -49,6 +50,7 @@ target_ureg cpu_hppa_get_psw(CPUHPPAState *env) void cpu_hppa_put_psw(CPUHPPAState *env, target_ureg psw) { + target_ureg old_psw = env->psw; target_ureg cb = 0; env->psw = psw & ~(PSW_N | PSW_V | PSW_CB); @@ -64,6 +66,14 @@ void cpu_hppa_put_psw(CPUHPPAState *env, target_ureg psw) cb |= ((psw >> 9) & 1) << 8; cb |= ((psw >> 8) & 1) << 4; env->psw_cb = cb; + + /* If PSW_P changes, it affects how we translate addresses. */ + if ((psw ^ old_psw) & PSW_P) { +#ifndef CONFIG_USER_ONLY + CPUState *src = CPU(hppa_env_get_cpu(env)); + tlb_flush_by_mmuidx(src, 0xf); +#endif + } } void hppa_cpu_dump_state(CPUState *cs, FILE *f, diff --git a/target/hppa/helper.h b/target/hppa/helper.h index bfe0dd1db1..38d834ef6b 100644 --- a/target/hppa/helper.h +++ b/target/hppa/helper.h @@ -92,4 +92,5 @@ DEF_HELPER_FLAGS_3(itlbp, TCG_CALL_NO_RWG, void, env, tl, tr) DEF_HELPER_FLAGS_2(ptlb, TCG_CALL_NO_RWG, void, env, tl) DEF_HELPER_FLAGS_1(ptlbe, TCG_CALL_NO_RWG, void, env) DEF_HELPER_FLAGS_2(lpa, TCG_CALL_NO_WG, tr, env, tl) +DEF_HELPER_FLAGS_1(change_prot_id, TCG_CALL_NO_RWG, void, env) #endif diff --git a/target/hppa/insns.decode b/target/hppa/insns.decode index 55ff39dd05..098370c2f0 100644 --- a/target/hppa/insns.decode +++ b/target/hppa/insns.decode @@ -525,3 +525,6 @@ fmpy_d 001110 ..... ..... 010 ..... ... ..... @f0e_d_3 fdiv_d 001110 ..... ..... 011 ..... ... ..... @f0e_d_3 xmpyu 001110 ..... ..... 010 .0111 .00 t:5 r1=%ra64 r2=%rb64 + +# diag +diag 000101 ----- ----- ---- ---- ---- ---- diff --git a/target/hppa/mem_helper.c b/target/hppa/mem_helper.c index aecf3075f6..c9b57d07c3 100644 --- a/target/hppa/mem_helper.c +++ b/target/hppa/mem_helper.c @@ -22,6 +22,7 @@ #include "exec/exec-all.h" #include "exec/helper-proto.h" #include "qom/cpu.h" +#include "trace.h" #ifdef CONFIG_USER_ONLY int hppa_cpu_handle_mmu_fault(CPUState *cs, vaddr address, @@ -43,9 +44,12 @@ static hppa_tlb_entry *hppa_find_tlb(CPUHPPAState *env, vaddr addr) for (i = 0; i < ARRAY_SIZE(env->tlb); ++i) { hppa_tlb_entry *ent = &env->tlb[i]; if (ent->va_b <= addr && addr <= ent->va_e) { + trace_hppa_tlb_find_entry(env, ent + i, ent->entry_valid, + ent->va_b, ent->va_e, ent->pa); return ent; } } + trace_hppa_tlb_find_entry_not_found(env, addr); return NULL; } @@ -55,6 +59,8 @@ static void hppa_flush_tlb_ent(CPUHPPAState *env, hppa_tlb_entry *ent) unsigned i, n = 1 << (2 * ent->page_size); uint64_t addr = ent->va_b; + trace_hppa_tlb_flush_ent(env, ent, ent->va_b, ent->va_e, ent->pa); + for (i = 0; i < n; ++i, addr += TARGET_PAGE_SIZE) { /* Do not flush MMU_PHYS_IDX. */ tlb_flush_page_by_mmuidx(cs, addr, 0xf); @@ -96,9 +102,7 @@ int hppa_get_physical_address(CPUHPPAState *env, vaddr addr, int mmu_idx, if (ent == NULL || !ent->entry_valid) { phys = 0; prot = 0; - /* ??? Unconditionally report data tlb miss, - even if this is an instruction fetch. */ - ret = EXCP_DTLB_MISS; + ret = (type == PAGE_EXEC) ? EXCP_ITLB_MISS : EXCP_DTLB_MISS; goto egress; } @@ -127,7 +131,20 @@ int hppa_get_physical_address(CPUHPPAState *env, vaddr addr, int mmu_idx, break; } - /* ??? Check PSW_P and ent->access_prot. This can remove PAGE_WRITE. */ + /* access_id == 0 means public page and no check is performed */ + if ((env->psw & PSW_P) && ent->access_id) { + /* If bits [31:1] match, and bit 0 is set, suppress write. */ + int match = ent->access_id * 2 + 1; + + if (match == env->cr[CR_PID1] || match == env->cr[CR_PID2] || + match == env->cr[CR_PID3] || match == env->cr[CR_PID4]) { + prot &= PAGE_READ | PAGE_EXEC; + if (type == PAGE_WRITE) { + ret = EXCP_DMPI; + goto egress; + } + } + } /* No guest access type indicates a non-architectural access from within QEMU. Bypass checks for access, D, B and T bits. */ @@ -171,6 +188,7 @@ int hppa_get_physical_address(CPUHPPAState *env, vaddr addr, int mmu_idx, egress: *pphys = phys; *pprot = prot; + trace_hppa_tlb_get_physical_address(env, ret, prot, addr, phys); return ret; } @@ -200,6 +218,7 @@ void tlb_fill(CPUState *cs, target_ulong addr, int size, MMUAccessType type, int mmu_idx, uintptr_t retaddr) { HPPACPU *cpu = HPPA_CPU(cs); + CPUHPPAState *env = &cpu->env; int prot, excp, a_prot; hwaddr phys; @@ -215,9 +234,10 @@ void tlb_fill(CPUState *cs, target_ulong addr, int size, break; } - excp = hppa_get_physical_address(&cpu->env, addr, mmu_idx, + excp = hppa_get_physical_address(env, addr, mmu_idx, a_prot, &phys, &prot); if (unlikely(excp >= 0)) { + trace_hppa_tlb_fill_excp(env, addr, size, type, mmu_idx); /* Failure. Raise the indicated exception. */ cs->exception_index = excp; if (cpu->env.psw & PSW_Q) { @@ -228,6 +248,8 @@ void tlb_fill(CPUState *cs, target_ulong addr, int size, cpu_loop_exit_restore(cs, retaddr); } + trace_hppa_tlb_fill_success(env, addr & TARGET_PAGE_MASK, + phys & TARGET_PAGE_MASK, size, type, mmu_idx); /* Success! Store the translation into the QEMU TLB. */ tlb_set_page(cs, addr & TARGET_PAGE_MASK, phys & TARGET_PAGE_MASK, prot, mmu_idx, TARGET_PAGE_SIZE); @@ -242,11 +264,13 @@ void HELPER(itlba)(CPUHPPAState *env, target_ulong addr, target_ureg reg) /* Zap any old entries covering ADDR; notice empty entries on the way. */ for (i = 0; i < ARRAY_SIZE(env->tlb); ++i) { hppa_tlb_entry *ent = &env->tlb[i]; - if (!ent->entry_valid) { - empty = ent; - } else if (ent->va_b <= addr && addr <= ent->va_e) { - hppa_flush_tlb_ent(env, ent); - empty = ent; + if (ent->va_b <= addr && addr <= ent->va_e) { + if (ent->entry_valid) { + hppa_flush_tlb_ent(env, ent); + } + if (!empty) { + empty = ent; + } } } @@ -259,6 +283,7 @@ void HELPER(itlba)(CPUHPPAState *env, target_ulong addr, target_ureg reg) empty->va_b = addr & TARGET_PAGE_MASK; empty->va_e = empty->va_b + TARGET_PAGE_SIZE - 1; empty->pa = extract32(reg, 5, 20) << TARGET_PAGE_BITS; + trace_hppa_tlb_itlba(env, empty, empty->va_b, empty->va_e, empty->pa); } /* Insert (Insn/Data) TLB Protection. Note this is PA 1.1 only. */ @@ -266,7 +291,7 @@ void HELPER(itlbp)(CPUHPPAState *env, target_ulong addr, target_ureg reg) { hppa_tlb_entry *ent = hppa_find_tlb(env, addr); - if (unlikely(ent == NULL || ent->entry_valid)) { + if (unlikely(ent == NULL)) { qemu_log_mask(LOG_GUEST_ERROR, "ITLBP not following ITLBA\n"); return; } @@ -280,6 +305,8 @@ void HELPER(itlbp)(CPUHPPAState *env, target_ulong addr, target_ureg reg) ent->d = extract32(reg, 28, 1); ent->t = extract32(reg, 29, 1); ent->entry_valid = 1; + trace_hppa_tlb_itlbp(env, ent, ent->access_id, ent->u, ent->ar_pl2, + ent->ar_pl1, ent->ar_type, ent->b, ent->d, ent->t); } /* Purge (Insn/Data) TLB. This is explicitly page-based, and is @@ -299,6 +326,7 @@ void HELPER(ptlb)(CPUHPPAState *env, target_ulong addr) { CPUState *src = CPU(hppa_env_get_cpu(env)); CPUState *cpu; + trace_hppa_tlb_ptlb(env); run_on_cpu_data data = RUN_ON_CPU_TARGET_PTR(addr); CPU_FOREACH(cpu) { @@ -314,11 +342,24 @@ void HELPER(ptlb)(CPUHPPAState *env, target_ulong addr) void HELPER(ptlbe)(CPUHPPAState *env) { CPUState *src = CPU(hppa_env_get_cpu(env)); - + trace_hppa_tlb_ptlbe(env); memset(env->tlb, 0, sizeof(env->tlb)); tlb_flush_by_mmuidx(src, 0xf); } +void cpu_hppa_change_prot_id(CPUHPPAState *env) +{ + if (env->psw & PSW_P) { + CPUState *src = CPU(hppa_env_get_cpu(env)); + tlb_flush_by_mmuidx(src, 0xf); + } +} + +void HELPER(change_prot_id)(CPUHPPAState *env) +{ + cpu_hppa_change_prot_id(env); +} + target_ureg HELPER(lpa)(CPUHPPAState *env, target_ulong addr) { hwaddr phys; @@ -335,8 +376,10 @@ target_ureg HELPER(lpa)(CPUHPPAState *env, target_ulong addr) if (excp == EXCP_DTLB_MISS) { excp = EXCP_NA_DTLB_MISS; } + trace_hppa_tlb_lpa_failed(env, addr); hppa_dynamic_excp(env, excp, GETPC()); } + trace_hppa_tlb_lpa_success(env, addr, phys); return phys; } diff --git a/target/hppa/op_helper.c b/target/hppa/op_helper.c index 268caaaa20..a55a5dfc02 100644 --- a/target/hppa/op_helper.c +++ b/target/hppa/op_helper.c @@ -25,6 +25,7 @@ #include "sysemu/sysemu.h" #include "qemu/timer.h" #include "fpu/softfloat.h" +#include "trace.h" void QEMU_NORETURN HELPER(excp)(CPUHPPAState *env, int excp) { @@ -165,6 +166,7 @@ target_ureg HELPER(probe)(CPUHPPAState *env, target_ulong addr, int prot, excp; hwaddr phys; + trace_hppa_tlb_probe(addr, level, want); /* Fail if the requested privilege level is higher than current. */ if (level < (env->iaoq_f & 3)) { return 0; @@ -676,11 +678,6 @@ target_ureg HELPER(swap_system_mask)(CPUHPPAState *env, target_ureg nsm) void HELPER(rfi)(CPUHPPAState *env) { - /* ??? On second reading this condition simply seems - to be undefined rather than a diagnosed trap. */ - if (env->psw & (PSW_I | PSW_R | PSW_Q)) { - helper_excp(env, EXCP_ILL); - } env->iasq_f = (uint64_t)env->cr[CR_IIASQ] << 32; env->iasq_b = (uint64_t)env->cr_back[0] << 32; env->iaoq_f = env->cr[CR_IIAOQ]; diff --git a/target/hppa/trace-events b/target/hppa/trace-events new file mode 100644 index 0000000000..80dae5bd8b --- /dev/null +++ b/target/hppa/trace-events @@ -0,0 +1,18 @@ +# See docs/devel/tracing.txt for syntax documentation. + +# target/hppa/mem_helper.c +disable hppa_tlb_flush_ent(void *env, void *ent, uint64_t va_b, uint64_t va_e, uint64_t pa) "env=%p ent=%p va_b=0x%lx va_e=0x%lx pa=0x%lx" +disable hppa_tlb_find_entry(void *env, void *ent, int valid, uint64_t va_b, uint64_t va_e, uint64_t pa) "env=%p ent=%p valid=%d va_b=0x%lx va_e=0x%lx pa=0x%lx" +disable hppa_tlb_find_entry_not_found(void *env, uint64_t addr) "env=%p addr=%08lx" +disable hppa_tlb_get_physical_address(void *env, int ret, int prot, uint64_t addr, uint64_t phys) "env=%p ret=%d prot=%d addr=0x%lx phys=0x%lx" +disable hppa_tlb_fill_excp(void *env, uint64_t addr, int size, int type, int mmu_idx) "env=%p addr=0x%lx size=%d type=%d mmu_idx=%d" +disable hppa_tlb_fill_success(void *env, uint64_t addr, uint64_t phys, int size, int type, int mmu_idx) "env=%p addr=0x%lx phys=0x%lx size=%d type=%d mmu_idx=%d" +disable hppa_tlb_itlba(void *env, void *ent, uint64_t va_b, uint64_t va_e, uint64_t pa) "env=%p ent=%p va_b=0x%lx va_e=0x%lx pa=0x%lx" +disable hppa_tlb_itlbp(void *env, void *ent, int access_id, int u, int pl2, int pl1, int type, int b, int d, int t) "env=%p ent=%p access_id=%x u=%d pl2=%d pl1=%d type=%d b=%d d=%d t=%d" +disable hppa_tlb_ptlb(void *env) "env=%p" +disable hppa_tlb_ptlbe(void *env) "env=%p" +disable hppa_tlb_lpa_success(void *env, uint64_t addr, uint64_t phys) "env=%p addr=0x%lx phys=0x%lx" +disable hppa_tlb_lpa_failed(void *env, uint64_t addr) "env=%p addr=0x%lx" + +# target/hppa/op_helper.c +disable hppa_tlb_probe(uint64_t addr, int level, int want) "addr=0x%lx level=%d want=%d" diff --git a/target/hppa/translate.c b/target/hppa/translate.c index dc5636fe94..35c504087f 100644 --- a/target/hppa/translate.c +++ b/target/hppa/translate.c @@ -816,12 +816,10 @@ static bool gen_illegal(DisasContext *ctx) static bool use_goto_tb(DisasContext *ctx, target_ureg dest) { - /* Suppress goto_tb in the case of single-steping and IO. */ - if ((tb_cflags(ctx->base.tb) & CF_LAST_IO) - || ctx->base.singlestep_enabled) { - return false; - } - return true; + /* Suppress goto_tb for page crossing, IO, or single-steping. */ + return !(((ctx->base.pc_first ^ dest) & TARGET_PAGE_MASK) + || (tb_cflags(ctx->base.tb) & CF_LAST_IO) + || ctx->base.singlestep_enabled); } /* If the next insn is to be nullified, and it's on the same page, @@ -2258,6 +2256,16 @@ static bool trans_mtctl(DisasContext *ctx, arg_mtctl *a) offsetof(CPUHPPAState, cr_back[ctl - CR_IIASQ])); break; + case CR_PID1: + case CR_PID2: + case CR_PID3: + case CR_PID4: + tcg_gen_st_reg(reg, cpu_env, offsetof(CPUHPPAState, cr[ctl])); +#ifndef CONFIG_USER_ONLY + gen_helper_change_prot_id(cpu_env); +#endif + break; + default: tcg_gen_st_reg(reg, cpu_env, offsetof(CPUHPPAState, cr[ctl])); break; @@ -2474,9 +2482,8 @@ static bool trans_ixtlbx(DisasContext *ctx, arg_ixtlbx *a) gen_helper_itlbp(cpu_env, addr, reg); } - /* Exit TB for ITLB change if mmu is enabled. This *should* not be - the case, since the OS TLB fill handler runs with mmu disabled. */ - if (!a->data && (ctx->tb_flags & PSW_C)) { + /* Exit TB for TLB change if mmu is enabled. */ + if (ctx->tb_flags & PSW_C) { ctx->base.is_jmp = DISAS_IAQ_N_STALE; } return nullify_end(ctx); @@ -2503,7 +2510,7 @@ static bool trans_pxtlbx(DisasContext *ctx, arg_pxtlbx *a) } /* Exit TB for TLB change if mmu is enabled. */ - if (!a->data && (ctx->tb_flags & PSW_C)) { + if (ctx->tb_flags & PSW_C) { ctx->base.is_jmp = DISAS_IAQ_N_STALE; } return nullify_end(ctx); @@ -3033,7 +3040,7 @@ static bool do_addb(DisasContext *ctx, unsigned r, TCGv_reg in1, DisasCond cond; in2 = load_gpr(ctx, r); - dest = dest_gpr(ctx, r); + dest = tcg_temp_new(); sv = NULL; cb_msb = NULL; @@ -3049,6 +3056,8 @@ static bool do_addb(DisasContext *ctx, unsigned r, TCGv_reg in1, } cond = do_cond(c * 2 + f, dest, cb_msb, sv); + save_gpr(ctx, r, dest); + tcg_temp_free(dest); return do_cbranch(ctx, disp, n, &cond); } @@ -3446,6 +3455,8 @@ static bool trans_b_gate(DisasContext *ctx, arg_b_gate *a) { target_ureg dest = iaoq_dest(ctx, a->disp); + nullify_over(ctx); + /* Make sure the caller hasn't done something weird with the queue. * ??? This is not quite the same as the PSW[B] bit, which would be * expensive to track. Real hardware will trap for @@ -3483,7 +3494,16 @@ static bool trans_b_gate(DisasContext *ctx, arg_b_gate *a) } #endif - return do_dbranch(ctx, dest, a->l, a->n); + if (a->l) { + TCGv_reg tmp = dest_gpr(ctx, a->l); + if (ctx->privilege < 3) { + tcg_gen_andi_reg(tmp, tmp, -4); + } + tcg_gen_ori_reg(tmp, tmp, ctx->privilege); + save_gpr(ctx, a->l, tmp); + } + + return do_dbranch(ctx, dest, 0, a->n); } static bool trans_blr(DisasContext *ctx, arg_blr *a) @@ -4048,6 +4068,13 @@ static bool trans_fmpyfadd_d(DisasContext *ctx, arg_fmpyfadd_d *a) return nullify_end(ctx); } +static bool trans_diag(DisasContext *ctx, arg_diag *a) +{ + qemu_log_mask(LOG_UNIMP, "DIAG opcode ignored\n"); + cond_free(&ctx->null_cond); + return true; +} + static void hppa_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) { DisasContext *ctx = container_of(dcbase, DisasContext, base); diff --git a/tests/ahci-test.c b/tests/ahci-test.c index 9f07e6f2ce..086811e602 100644 --- a/tests/ahci-test.c +++ b/tests/ahci-test.c @@ -38,7 +38,7 @@ #include "hw/pci/pci_regs.h" /* TODO actually test the results and get rid of this */ -#define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__)) +#define qmp_discard_response(s, ...) qobject_unref(qtest_qmp(s, __VA_ARGS__)) /* Test images sizes in MB */ #define TEST_IMAGE_SIZE_MB_LARGE (200 * 1024) @@ -161,7 +161,6 @@ static AHCIQState *ahci_vboot(const char *cli, va_list ap) s = g_new0(AHCIQState, 1); s->parent = qtest_pc_vboot(cli, ap); - global_qtest = s->parent->qts; alloc_set_flags(&s->parent->alloc, ALLOC_LEAK_ASSERT); /* Verify that we have an AHCI device present. */ @@ -201,7 +200,7 @@ static void ahci_shutdown(AHCIQState *ahci) { QOSState *qs = ahci->parent; - set_context(qs); + assert(!global_qtest); ahci_clean_mem(ahci); free_ahci_device(ahci->dev); g_free(ahci); @@ -874,15 +873,15 @@ static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize, /* Write some indicative pattern to our buffer. */ generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE); - bufwrite(ptr, tx, bufsize); + qtest_bufwrite(ahci->parent->qts, ptr, tx, bufsize); /* Write this buffer to disk, then read it back to the DMA buffer. */ ahci_guest_io(ahci, port, write_cmd, ptr, bufsize, sector); - qmemset(ptr, 0x00, bufsize); + qtest_memset(ahci->parent->qts, ptr, 0x00, bufsize); ahci_guest_io(ahci, port, read_cmd, ptr, bufsize, sector); /*** Read back the Data ***/ - bufread(ptr, rx, bufsize); + qtest_bufread(ahci->parent->qts, ptr, rx, bufsize); g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0); ahci_free(ahci, ptr); @@ -923,7 +922,7 @@ static void ahci_test_max(AHCIQState *ahci) } port = ahci_test_nondata(ahci, cmd); - memread(ahci->port[port].fb + 0x40, d2h, 0x20); + qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x40, d2h, 0x20); nsect = (uint64_t)d2h->lba_hi[2] << 40 | (uint64_t)d2h->lba_hi[1] << 32 | (uint64_t)d2h->lba_hi[0] << 24 | @@ -1041,7 +1040,7 @@ static void test_dma_fragmented(void) /* Create a DMA buffer in guest memory, and write our pattern to it. */ ptr = guest_alloc(&ahci->parent->alloc, bufsize); g_assert(ptr); - bufwrite(ptr, tx, bufsize); + qtest_bufwrite(ahci->parent->qts, ptr, tx, bufsize); cmd = ahci_command_create(CMD_WRITE_DMA); ahci_command_adjust(cmd, 0, ptr, bufsize, 32); @@ -1058,7 +1057,7 @@ static void test_dma_fragmented(void) ahci_command_free(cmd); /* Read back the guest's receive buffer into local memory */ - bufread(ptr, rx, bufsize); + qtest_bufread(ahci->parent->qts, ptr, rx, bufsize); guest_free(&ahci->parent->alloc, ptr); g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0); @@ -1169,8 +1168,6 @@ static void ahci_migrate_simple(uint8_t cmd_read, uint8_t cmd_write) "-drive if=ide,format=%s,file=%s " "-incoming %s", imgfmt, tmp_path, uri); - set_context(src->parent); - /* initialize */ px = ahci_port_select(src); ahci_port_clear(src, px); @@ -1238,7 +1235,7 @@ static void ahci_halted_io_test(uint8_t cmd_read, uint8_t cmd_write) generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE); ptr = ahci_alloc(ahci, bufsize); g_assert(ptr); - memwrite(ptr, tx, bufsize); + qtest_memwrite(ahci->parent->qts, ptr, tx, bufsize); /* Attempt to write (and fail) */ cmd = ahci_guest_io_halt(ahci, port, cmd_write, @@ -1304,8 +1301,6 @@ static void ahci_migrate_halted_io(uint8_t cmd_read, uint8_t cmd_write) "-incoming %s", tmp_path, imgfmt, uri); - set_context(src->parent); - /* Initialize and prepare */ port = ahci_port_select(src); ahci_port_clear(src, port); @@ -1314,7 +1309,7 @@ static void ahci_migrate_halted_io(uint8_t cmd_read, uint8_t cmd_write) /* create DMA source buffer and write pattern */ ptr = ahci_alloc(src, bufsize); g_assert(ptr); - memwrite(ptr, tx, bufsize); + qtest_memwrite(src->parent->qts, ptr, tx, bufsize); /* Write, trigger the VM to stop, migrate, then resume. */ cmd = ahci_guest_io_halt(src, port, cmd_write, @@ -1372,8 +1367,6 @@ static void test_flush_migrate(void) "-device ide-hd,drive=drive0 " "-incoming %s", tmp_path, imgfmt, uri); - set_context(src->parent); - px = ahci_port_select(src); ahci_port_clear(src, px); @@ -1384,14 +1377,14 @@ static void test_flush_migrate(void) cmd = ahci_command_create(CMD_FLUSH_CACHE); ahci_command_commit(src, cmd, px); ahci_command_issue_async(src, cmd); - qmp_eventwait("STOP"); + qtest_qmp_eventwait(src->parent->qts, "STOP"); /* Migrate over */ ahci_migrate(src, dst, uri); /* Complete the command */ - qmp_send("{'execute':'cont' }"); - qmp_eventwait("RESUME"); + qtest_qmp_send(dst->parent->qts, "{'execute':'cont' }"); + qtest_qmp_eventwait(dst->parent->qts, "RESUME"); ahci_command_wait(dst, cmd); ahci_command_verify(dst, cmd); @@ -1483,7 +1476,7 @@ static int ahci_cb_cmp_buff(AHCIQState *ahci, AHCICommand *cmd, } rx = g_malloc0(opts->size); - bufread(opts->buffer, rx, opts->size); + qtest_bufread(ahci->parent->qts, opts->buffer, rx, opts->size); g_assert_cmphex(memcmp(tx, rx, opts->size), ==, 0); g_free(rx); @@ -1558,9 +1551,10 @@ static void test_atapi_bcl(void) } -static void atapi_wait_tray(bool open) +static void atapi_wait_tray(AHCIQState *ahci, bool open) { - QDict *rsp = qmp_eventwait_ref("DEVICE_TRAY_MOVED"); + QDict *rsp = qtest_qmp_eventwait_ref(ahci->parent->qts, + "DEVICE_TRAY_MOVED"); QDict *data = qdict_get_qdict(rsp, "data"); if (open) { g_assert(qdict_get_bool(data, "tray-open")); @@ -1587,43 +1581,46 @@ static void test_atapi_tray(void) port = ahci_port_select(ahci); ahci_atapi_eject(ahci, port); - atapi_wait_tray(true); + atapi_wait_tray(ahci, true); ahci_atapi_load(ahci, port); - atapi_wait_tray(false); + atapi_wait_tray(ahci, false); /* Remove media */ - qmp_send("{'execute': 'blockdev-open-tray'," - " 'arguments': {'id': 'cd0'}}"); - atapi_wait_tray(true); - rsp = qmp_receive(); + qtest_qmp_send(ahci->parent->qts, "{'execute': 'blockdev-open-tray', " + "'arguments': {'id': 'cd0'}}"); + atapi_wait_tray(ahci, true); + rsp = qtest_qmp_receive(ahci->parent->qts); qobject_unref(rsp); - qmp_discard_response("{'execute': 'blockdev-remove-medium', " + qmp_discard_response(ahci->parent->qts, + "{'execute': 'blockdev-remove-medium', " "'arguments': {'id': 'cd0'}}"); /* Test the tray without a medium */ ahci_atapi_load(ahci, port); - atapi_wait_tray(false); + atapi_wait_tray(ahci, false); ahci_atapi_eject(ahci, port); - atapi_wait_tray(true); + atapi_wait_tray(ahci, true); /* Re-insert media */ - qmp_discard_response("{'execute': 'blockdev-add', " - "'arguments': {'node-name': 'node0', " + qmp_discard_response(ahci->parent->qts, + "{'execute': 'blockdev-add', " + "'arguments': {'node-name': 'node0', " "'driver': 'raw', " "'file': { 'driver': 'file', " "'filename': %s }}}", iso); - qmp_discard_response("{'execute': 'blockdev-insert-medium'," - "'arguments': { 'id': 'cd0', " + qmp_discard_response(ahci->parent->qts, + "{'execute': 'blockdev-insert-medium'," + "'arguments': { 'id': 'cd0', " "'node-name': 'node0' }}"); /* Again, the event shows up first */ - qmp_send("{'execute': 'blockdev-close-tray'," - " 'arguments': {'id': 'cd0'}}"); - atapi_wait_tray(false); - rsp = qmp_receive(); + qtest_qmp_send(ahci->parent->qts, "{'execute': 'blockdev-close-tray', " + "'arguments': {'id': 'cd0'}}"); + atapi_wait_tray(ahci, false); + rsp = qtest_qmp_receive(ahci->parent->qts); qobject_unref(rsp); /* Now, to convince ATAPI we understand the media has changed... */ @@ -1643,10 +1640,10 @@ static void test_atapi_tray(void) /* Final tray test. */ ahci_atapi_eject(ahci, port); - atapi_wait_tray(true); + atapi_wait_tray(ahci, true); ahci_atapi_load(ahci, port); - atapi_wait_tray(false); + atapi_wait_tray(ahci, false); /* Cleanup */ g_free(tx); diff --git a/tests/decode/check.sh b/tests/decode/check.sh index 79a06c37cd..95445a0115 100755 --- a/tests/decode/check.sh +++ b/tests/decode/check.sh @@ -15,4 +15,10 @@ for i in err_*.decode; do fi done +for i in succ_*.decode; do + if ! $PYTHON $DECODETREE $i > /dev/null 2> /dev/null; then + echo FAIL:$i 1>&2 + fi +done + exit $E diff --git a/tests/decode/err_pattern_group_empty.decode b/tests/decode/err_pattern_group_empty.decode new file mode 100644 index 0000000000..abbff6b528 --- /dev/null +++ b/tests/decode/err_pattern_group_empty.decode @@ -0,0 +1,6 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +# empty groups are not allowed +{ +} diff --git a/tests/decode/err_pattern_group_ident1.decode b/tests/decode/err_pattern_group_ident1.decode new file mode 100644 index 0000000000..3e65fab2f9 --- /dev/null +++ b/tests/decode/err_pattern_group_ident1.decode @@ -0,0 +1,10 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +%sub1 0:8 + +# Make sure that indentation is enforced +{ + top 00000000 00000000 00000000 00000000 + sub1 00000000 00000000 00000000 ........ %sub1 +} diff --git a/tests/decode/err_pattern_group_ident2.decode b/tests/decode/err_pattern_group_ident2.decode new file mode 100644 index 0000000000..bc859233b1 --- /dev/null +++ b/tests/decode/err_pattern_group_ident2.decode @@ -0,0 +1,11 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +%sub1 0:8 + +# Make sure that indentation is enforced +{ + top 00000000 00000000 00000000 00000000 + sub1 00000000 00000000 00000000 ........ %sub1 +# comments are suposed to be indented +} diff --git a/tests/decode/err_pattern_group_nest1.decode b/tests/decode/err_pattern_group_nest1.decode new file mode 100644 index 0000000000..92e971c3c5 --- /dev/null +++ b/tests/decode/err_pattern_group_nest1.decode @@ -0,0 +1,13 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +%sub1 0:8 +%sub2 8:8 +%sub3 16:8 +%sub4 24:8 + +# Groups with no overlap are supposed to fail +{ + top 00000000 00000000 00000000 00000000 + sub4 ........ ........ ........ ........ %sub1 %sub2 %sub3 %sub4 +} diff --git a/tests/decode/err_pattern_group_overlap1.decode b/tests/decode/err_pattern_group_overlap1.decode new file mode 100644 index 0000000000..ebe3030d26 --- /dev/null +++ b/tests/decode/err_pattern_group_overlap1.decode @@ -0,0 +1,6 @@ +one 00000000000000000000000000000000 +{ + two 0000000000000000000000000000000 s:1 + three 000000000000000000000000000000 s:1 0 +} + diff --git a/tests/decode/err_width1.decode b/tests/decode/err_width1.decode new file mode 100644 index 0000000000..0c14f6d73b --- /dev/null +++ b/tests/decode/err_width1.decode @@ -0,0 +1,5 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +# Diagnose too many bits (33 of 32) +one 000000000000000000000000000000000 diff --git a/tests/decode/err_width2.decode b/tests/decode/err_width2.decode new file mode 100644 index 0000000000..47f0acf322 --- /dev/null +++ b/tests/decode/err_width2.decode @@ -0,0 +1,5 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +# Diagnose too few bits (31 of 32) +one 0000000000000000000000000000000 diff --git a/tests/decode/err_width3.decode b/tests/decode/err_width3.decode new file mode 100644 index 0000000000..c5fb6b3699 --- /dev/null +++ b/tests/decode/err_width3.decode @@ -0,0 +1,5 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +# Diagnose too many bits (33 of 32) +one 0 s:32 diff --git a/tests/decode/err_width4.decode b/tests/decode/err_width4.decode new file mode 100644 index 0000000000..1588a63698 --- /dev/null +++ b/tests/decode/err_width4.decode @@ -0,0 +1,5 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +# Diagnose too few bits (31 of 32) +one 0 s:30 diff --git a/tests/decode/succ_pattern_group_nest1.decode b/tests/decode/succ_pattern_group_nest1.decode new file mode 100644 index 0000000000..77b0f48b49 --- /dev/null +++ b/tests/decode/succ_pattern_group_nest1.decode @@ -0,0 +1,22 @@ +# This work is licensed under the terms of the GNU LGPL, version 2 or later. +# See the COPYING.LIB file in the top-level directory. + +%sub1 0:8 +%sub2 8:8 +%sub3 16:8 +%sub4 24:7 + +# Make sure deep netsting works, as few targets will actually exercise it +{ + top 00000000 00000000 00000000 00000000 + { + sub1 00000000 00000000 00000000 ........ %sub1 + { + sub2 00000000 00000000 ........ ........ %sub1 %sub2 + { + sub3 00000000 ........ ........ ........ %sub1 %sub2 %sub3 + sub4 0....... ........ ........ ........ %sub1 %sub2 %sub3 %sub4 + } + } + } +} diff --git a/tests/libqos/libqos.c b/tests/libqos/libqos.c index 636a111a6f..d71557c5cb 100644 --- a/tests/libqos/libqos.c +++ b/tests/libqos/libqos.c @@ -73,11 +73,6 @@ void qtest_shutdown(QOSState *qs) } } -void set_context(QOSState *s) -{ - global_qtest = s->qts; -} - static QDict *qmp_execute(QTestState *qts, const char *command) { return qtest_qmp(qts, "{ 'execute': %s }", command); @@ -89,8 +84,6 @@ void migrate(QOSState *from, QOSState *to, const char *uri) QDict *rsp, *sub; bool running; - set_context(from); - /* Is the machine currently running? */ rsp = qmp_execute(from->qts, "query-status"); g_assert(qdict_haskey(rsp, "return")); @@ -114,7 +107,6 @@ void migrate(QOSState *from, QOSState *to, const char *uri) /* If we were running, we can wait for an event. */ if (running) { migrate_allocator(&from->alloc, &to->alloc); - set_context(to); qtest_qmp_eventwait(to->qts, "RESUME"); return; } @@ -144,7 +136,6 @@ void migrate(QOSState *from, QOSState *to, const char *uri) } migrate_allocator(&from->alloc, &to->alloc); - set_context(to); } bool have_qemu_img(void) diff --git a/tests/libqos/libqos.h b/tests/libqos/libqos.h index 149b0be8bc..8e971c25a3 100644 --- a/tests/libqos/libqos.h +++ b/tests/libqos/libqos.h @@ -28,7 +28,6 @@ void qtest_shutdown(QOSState *qs); bool have_qemu_img(void); void mkimg(const char *file, const char *fmt, unsigned size_mb); void mkqcow2(const char *file, unsigned size_mb); -void set_context(QOSState *s); void migrate(QOSState *from, QOSState *to, const char *uri); void prepare_blkdebug_script(const char *debug_fn, const char *event); void generate_pattern(void *buffer, size_t len, size_t cycle_len); diff --git a/tests/qemu-iotests/051 b/tests/qemu-iotests/051 index 3b50c7f188..6a3b7c2b89 100755 --- a/tests/qemu-iotests/051 +++ b/tests/qemu-iotests/051 @@ -356,6 +356,13 @@ $QEMU_IO -c "read -P 0x33 0 4k" "$TEST_IMG" | _filter_qemu_io # Using snapshot=on with a non-existent TMPDIR TMPDIR=/nonexistent run_qemu -drive driver=null-co,snapshot=on +# Using snapshot=on together with read-only=on +echo "info block" | + run_qemu -drive file="$TEST_IMG",snapshot=on,read-only=on,if=none,id=$device_id | + _filter_qemu_io | + sed -e 's#"/[^"]*/vl\.[A-Za-z0-9]\{6\}"#SNAPSHOT_PATH#g' + + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/051.out b/tests/qemu-iotests/051.out index b900935fbc..9f1cf22608 100644 --- a/tests/qemu-iotests/051.out +++ b/tests/qemu-iotests/051.out @@ -458,4 +458,13 @@ read 4096/4096 bytes at offset 0 Testing: -drive driver=null-co,snapshot=on QEMU_PROG: -drive driver=null-co,snapshot=on: Could not get temporary filename: No such file or directory +Testing: -drive file=TEST_DIR/t.qcow2,snapshot=on,read-only=on,if=none,id=drive0 +QEMU X.Y.Z monitor - type 'help' for more information +(qemu) info block +drive0 (NODE_NAME): json:{"backing": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/t.qcow2"}}, "driver": "qcow2", "file": {"driver": "file", "filename": SNAPSHOT_PATH}} (qcow2, read-only) + Removable device: not locked, tray closed + Cache mode: writeback, ignore flushes + Backing file: TEST_DIR/t.qcow2 (chain depth: 1) +(qemu) quit + *** done diff --git a/tests/qemu-iotests/051.pc.out b/tests/qemu-iotests/051.pc.out index 8c5c735dfd..c4743cc31c 100644 --- a/tests/qemu-iotests/051.pc.out +++ b/tests/qemu-iotests/051.pc.out @@ -530,4 +530,13 @@ read 4096/4096 bytes at offset 0 Testing: -drive driver=null-co,snapshot=on QEMU_PROG: -drive driver=null-co,snapshot=on: Could not get temporary filename: No such file or directory +Testing: -drive file=TEST_DIR/t.qcow2,snapshot=on,read-only=on,if=none,id=drive0 +QEMU X.Y.Z monitor - type 'help' for more information +(qemu) info block +drive0 (NODE_NAME): json:{"backing": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/t.qcow2"}}, "driver": "qcow2", "file": {"driver": "file", "filename": SNAPSHOT_PATH}} (qcow2, read-only) + Removable device: not locked, tray closed + Cache mode: writeback, ignore flushes + Backing file: TEST_DIR/t.qcow2 (chain depth: 1) +(qemu) quit + *** done diff --git a/tests/qemu-iotests/232 b/tests/qemu-iotests/232 index 71fd48eff0..0de097fc88 100755 --- a/tests/qemu-iotests/232 +++ b/tests/qemu-iotests/232 @@ -29,6 +29,7 @@ status=1 # failure is the default! _cleanup() { _cleanup_test_img + rm -f $TEST_IMG.[01234] } trap "_cleanup; exit \$status" 0 1 2 3 15 @@ -143,6 +144,36 @@ run_qemu_info_block -blockdev driver=file,filename="$TEST_IMG",node-name=node0,a run_qemu_info_block -blockdev driver=file,filename="$TEST_IMG",node-name=node0,auto-read-only=on run_qemu_info_block -blockdev driver=file,filename="$TEST_IMG",node-name=node0 +echo +echo "=== Try commit to backing file with auto-read-only ===" +echo + +TEST_IMG="$TEST_IMG.0" _make_test_img $size +TEST_IMG="$TEST_IMG.1" _make_test_img $size +TEST_IMG="$TEST_IMG.2" _make_test_img $size +TEST_IMG="$TEST_IMG.3" _make_test_img $size +TEST_IMG="$TEST_IMG.4" _make_test_img $size + +(cat <<EOF +{"execute":"qmp_capabilities"} +{"execute":"block-commit", + "arguments":{"device":"format-4", "top-node": "format-2", "base-node":"format-0", "job-id":"job0"}} +EOF +sleep 1 +echo '{"execute":"quit"}' +) | $QEMU -qmp stdio -nographic -nodefaults \ + -blockdev file,node-name=file-0,filename=$TEST_IMG.0,auto-read-only=on \ + -blockdev qcow2,node-name=format-0,file=file-0,read-only=on \ + -blockdev file,node-name=file-1,filename=$TEST_IMG.1,auto-read-only=on \ + -blockdev qcow2,node-name=format-1,file=file-1,read-only=on,backing=format-0 \ + -blockdev file,node-name=file-2,filename=$TEST_IMG.2,auto-read-only=on \ + -blockdev qcow2,node-name=format-2,file=file-2,read-only=on,backing=format-1 \ + -blockdev file,node-name=file-3,filename=$TEST_IMG.3,auto-read-only=on \ + -blockdev qcow2,node-name=format-3,file=file-3,read-only=on,backing=format-2 \ + -blockdev file,node-name=file-4,filename=$TEST_IMG.4,auto-read-only=on \ + -blockdev qcow2,node-name=format-4,file=file-4,read-only=on,backing=format-3 | + _filter_qmp + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/232.out b/tests/qemu-iotests/232.out index dcb683afa3..5bcc44bb62 100644 --- a/tests/qemu-iotests/232.out +++ b/tests/qemu-iotests/232.out @@ -22,12 +22,12 @@ NODE_NAME: TEST_DIR/t.IMGFMT (file, read-only) NODE_NAME: TEST_DIR/t.IMGFMT (file, read-only) QEMU_PROG: -drive driver=file,file=TEST_DIR/t.IMGFMT,if=none,read-only=off,auto-read-only=off: Could not open 'TEST_DIR/t.IMGFMT': Permission denied -NODE_NAME: TEST_DIR/t.IMGFMT (file, read-only) -NODE_NAME: TEST_DIR/t.IMGFMT (file, read-only) +NODE_NAME: TEST_DIR/t.IMGFMT (file) +NODE_NAME: TEST_DIR/t.IMGFMT (file) QEMU_PROG: -drive driver=file,file=TEST_DIR/t.IMGFMT,if=none,auto-read-only=off: Could not open 'TEST_DIR/t.IMGFMT': Permission denied -NODE_NAME: TEST_DIR/t.IMGFMT (file, read-only) -NODE_NAME: TEST_DIR/t.IMGFMT (file, read-only) +NODE_NAME: TEST_DIR/t.IMGFMT (file) +NODE_NAME: TEST_DIR/t.IMGFMT (file) === -blockdev with read-write image: read-only/auto-read-only combinations === @@ -50,10 +50,30 @@ node0: TEST_DIR/t.IMGFMT (file, read-only) node0: TEST_DIR/t.IMGFMT (file, read-only) QEMU_PROG: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0,read-only=off,auto-read-only=off: Could not open 'TEST_DIR/t.IMGFMT': Permission denied -node0: TEST_DIR/t.IMGFMT (file, read-only) +node0: TEST_DIR/t.IMGFMT (file) QEMU_PROG: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0,read-only=off: Could not open 'TEST_DIR/t.IMGFMT': Permission denied QEMU_PROG: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0,auto-read-only=off: Could not open 'TEST_DIR/t.IMGFMT': Permission denied -node0: TEST_DIR/t.IMGFMT (file, read-only) +node0: TEST_DIR/t.IMGFMT (file) QEMU_PROG: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0: Could not open 'TEST_DIR/t.IMGFMT': Permission denied + +=== Try commit to backing file with auto-read-only === + +Formatting 'TEST_DIR/t.IMGFMT.0', fmt=IMGFMT size=134217728 +Formatting 'TEST_DIR/t.IMGFMT.1', fmt=IMGFMT size=134217728 +Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=134217728 +Formatting 'TEST_DIR/t.IMGFMT.3', fmt=IMGFMT size=134217728 +Formatting 'TEST_DIR/t.IMGFMT.4', fmt=IMGFMT size=134217728 +QMP_VERSION +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job0", "len": 134217728, "offset": 134217728, "speed": 0, "type": "commit"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} *** done diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245 new file mode 100644 index 0000000000..7891a210c1 --- /dev/null +++ b/tests/qemu-iotests/245 @@ -0,0 +1,991 @@ +#!/usr/bin/env python +# +# Test cases for the QMP 'x-blockdev-reopen' command +# +# Copyright (C) 2018-2019 Igalia, S.L. +# Author: Alberto Garcia <berto@igalia.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 of the License, or +# (at your option) any later version. +# +# 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/>. +# + +import os +import re +import iotests +import copy +import json +from iotests import qemu_img, qemu_io + +hd_path = [ + os.path.join(iotests.test_dir, 'hd0.img'), + os.path.join(iotests.test_dir, 'hd1.img'), + os.path.join(iotests.test_dir, 'hd2.img') +] + +def hd_opts(idx): + return {'driver': iotests.imgfmt, + 'node-name': 'hd%d' % idx, + 'file': {'driver': 'file', + 'node-name': 'hd%d-file' % idx, + 'filename': hd_path[idx] } } + +class TestBlockdevReopen(iotests.QMPTestCase): + total_io_cmds = 0 + + def setUp(self): + qemu_img('create', '-f', iotests.imgfmt, hd_path[0], '3M') + qemu_img('create', '-f', iotests.imgfmt, '-b', hd_path[0], hd_path[1]) + qemu_img('create', '-f', iotests.imgfmt, hd_path[2], '3M') + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa0 0 1M', hd_path[0]) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa1 1M 1M', hd_path[1]) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa2 2M 1M', hd_path[2]) + self.vm = iotests.VM() + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + self.check_qemu_io_errors() + os.remove(hd_path[0]) + os.remove(hd_path[1]) + os.remove(hd_path[2]) + + # The output of qemu-io is not returned by vm.hmp_qemu_io() but + # it's stored in the log and can only be read when the VM has been + # shut down. This function runs qemu-io and keeps track of the + # number of times it's been called. + def run_qemu_io(self, img, cmd): + result = self.vm.hmp_qemu_io(img, cmd) + self.assert_qmp(result, 'return', '') + self.total_io_cmds += 1 + + # Once the VM is shut down we can parse the log and see if qemu-io + # ran without errors. + def check_qemu_io_errors(self): + self.assertFalse(self.vm.is_running()) + found = 0 + log = self.vm.get_log() + for line in log.split("\n"): + if line.startswith("Pattern verification failed"): + raise Exception("%s (command #%d)" % (line, found)) + if re.match("read .*/.* bytes at offset", line): + found += 1 + self.assertEqual(found, self.total_io_cmds, + "Expected output of %d qemu-io commands, found %d" % + (found, self.total_io_cmds)) + + # Run x-blockdev-reopen with 'opts' but applying 'newopts' + # on top of it. The original 'opts' dict is unmodified + def reopen(self, opts, newopts = {}, errmsg = None): + opts = copy.deepcopy(opts) + + # Apply the changes from 'newopts' on top of 'opts' + for key in newopts: + value = newopts[key] + # If key has the form "foo.bar" then we need to do + # opts["foo"]["bar"] = value, not opts["foo.bar"] = value + subdict = opts + while key.find('.') != -1: + [prefix, key] = key.split('.', 1) + subdict = opts[prefix] + subdict[key] = value + + result = self.vm.qmp('x-blockdev-reopen', conv_keys = False, **opts) + if errmsg: + self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/desc', errmsg) + else: + self.assert_qmp(result, 'return', {}) + + + # Run query-named-block-nodes and return the specified entry + def get_node(self, node_name): + result = self.vm.qmp('query-named-block-nodes') + for node in result['return']: + if node['node-name'] == node_name: + return node + return None + + # Run 'query-named-block-nodes' and compare its output with the + # value passed by the user in 'graph' + def check_node_graph(self, graph): + result = self.vm.qmp('query-named-block-nodes') + self.assertEqual(json.dumps(graph, sort_keys=True), + json.dumps(result, sort_keys=True)) + + # This test opens one single disk image (without backing files) + # and tries to reopen it with illegal / incorrect parameters. + def test_incorrect_parameters_single_file(self): + # Open 'hd0' only (no backing files) + opts = hd_opts(0) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + original_graph = self.vm.qmp('query-named-block-nodes') + + # We can reopen the image passing the same options + self.reopen(opts) + + # We can also reopen passing a child reference in 'file' + self.reopen(opts, {'file': 'hd0-file'}) + + # We cannot change any of these + self.reopen(opts, {'node-name': 'not-found'}, "Cannot find node named 'not-found'") + self.reopen(opts, {'node-name': ''}, "Cannot find node named ''") + self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string") + self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'") + self.reopen(opts, {'driver': ''}, "Invalid parameter ''") + self.reopen(opts, {'driver': None}, "Invalid parameter type for 'driver', expected: string") + self.reopen(opts, {'file': 'not-found'}, "Cannot change the option 'file'") + self.reopen(opts, {'file': ''}, "Cannot change the option 'file'") + self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef") + self.reopen(opts, {'file.node-name': 'newname'}, "Cannot change the option 'node-name'") + self.reopen(opts, {'file.driver': 'host_device'}, "Cannot change the option 'driver'") + self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'") + self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'") + self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") + self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'file.filename', expected: string") + + # node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it + del opts['node-name'] + self.reopen(opts, {}, "Node name not specified") + + # Check that nothing has changed + self.check_node_graph(original_graph) + + # Remove the node + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + # This test opens an image with a backing file and tries to reopen + # it with illegal / incorrect parameters. + def test_incorrect_parameters_backing_file(self): + # Open hd1 omitting the backing options (hd0 will be opened + # with the default options) + opts = hd_opts(1) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + original_graph = self.vm.qmp('query-named-block-nodes') + + # We can't reopen the image passing the same options, 'backing' is mandatory + self.reopen(opts, {}, "backing is missing for 'hd1'") + + # Everything works if we pass 'backing' using the existing node name + for node in original_graph['return']: + if node['drv'] == iotests.imgfmt and node['file'] == hd_path[0]: + backing_node_name = node['node-name'] + self.reopen(opts, {'backing': backing_node_name}) + + # We can't use a non-existing or empty (non-NULL) node as the backing image + self.reopen(opts, {'backing': 'not-found'}, "Cannot find device= nor node_name=not-found") + self.reopen(opts, {'backing': ''}, "Cannot find device= nor node_name=") + + # We can reopen the image just fine if we specify the backing options + opts['backing'] = {'driver': iotests.imgfmt, + 'file': {'driver': 'file', + 'filename': hd_path[0]}} + self.reopen(opts) + + # We cannot change any of these options + self.reopen(opts, {'backing.node-name': 'newname'}, "Cannot change the option 'node-name'") + self.reopen(opts, {'backing.driver': 'raw'}, "Cannot change the option 'driver'") + self.reopen(opts, {'backing.file.node-name': 'newname'}, "Cannot change the option 'node-name'") + self.reopen(opts, {'backing.file.driver': 'host_device'}, "Cannot change the option 'driver'") + + # Check that nothing has changed since the beginning + self.check_node_graph(original_graph) + + # Remove the node + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') + self.assert_qmp(result, 'return', {}) + + # Reopen an image several times changing some of its options + def test_reopen(self): + # Open the hd1 image passing all backing options + opts = hd_opts(1) + opts['backing'] = hd_opts(0) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + original_graph = self.vm.qmp('query-named-block-nodes') + + # We can reopen the image passing the same options + self.reopen(opts) + + # Reopen in read-only mode + self.assert_qmp(self.get_node('hd1'), 'ro', False) + + self.reopen(opts, {'read-only': True}) + self.assert_qmp(self.get_node('hd1'), 'ro', True) + self.reopen(opts) + self.assert_qmp(self.get_node('hd1'), 'ro', False) + + # Change the cache options + self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True) + self.assert_qmp(self.get_node('hd1'), 'cache/direct', False) + self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', False) + self.reopen(opts, {'cache': { 'direct': True, 'no-flush': True }}) + self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True) + self.assert_qmp(self.get_node('hd1'), 'cache/direct', True) + self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', True) + + # Reopen again with the original options + self.reopen(opts) + self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True) + self.assert_qmp(self.get_node('hd1'), 'cache/direct', False) + self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', False) + + # Change 'detect-zeroes' and 'discard' + self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'off') + self.reopen(opts, {'detect-zeroes': 'on'}) + self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') + self.reopen(opts, {'detect-zeroes': 'unmap'}, + "setting detect-zeroes to unmap is not allowed " + + "without setting discard operation to unmap") + self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') + self.reopen(opts, {'detect-zeroes': 'unmap', 'discard': 'unmap'}) + self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'unmap') + self.reopen(opts) + self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'off') + + # Changing 'force-share' is currently not supported + self.reopen(opts, {'force-share': True}, "Cannot change the option 'force-share'") + + # Change some qcow2-specific options + # No way to test for success other than checking the return message + if iotests.imgfmt == 'qcow2': + self.reopen(opts, {'l2-cache-entry-size': 128 * 1024}, + "L2 cache entry size must be a power of two "+ + "between 512 and the cluster size (65536)") + self.reopen(opts, {'l2-cache-size': 1024 * 1024, + 'cache-size': 512 * 1024}, + "l2-cache-size may not exceed cache-size") + self.reopen(opts, {'l2-cache-size': 4 * 1024 * 1024, + 'refcount-cache-size': 4 * 1024 * 1024, + 'l2-cache-entry-size': 32 * 1024}) + self.reopen(opts, {'pass-discard-request': True}) + + # Check that nothing has changed since the beginning + # (from the parameters that we can check) + self.check_node_graph(original_graph) + + # Check that the node names (other than the top-level one) are optional + del opts['file']['node-name'] + del opts['backing']['node-name'] + del opts['backing']['file']['node-name'] + self.reopen(opts) + self.check_node_graph(original_graph) + + # Reopen setting backing = null, this removes the backing image from the chain + self.reopen(opts, {'backing': None}) + self.assert_qmp_absent(self.get_node('hd1'), 'image/backing-image') + + # Open the 'hd0' image + result = self.vm.qmp('blockdev-add', conv_keys = False, **hd_opts(0)) + self.assert_qmp(result, 'return', {}) + + # Reopen the hd1 image setting 'hd0' as its backing image + self.reopen(opts, {'backing': 'hd0'}) + self.assert_qmp(self.get_node('hd1'), 'image/backing-image/filename', hd_path[0]) + + # Check that nothing has changed since the beginning + self.reopen(hd_opts(0), {'read-only': True}) + self.check_node_graph(original_graph) + + # The backing file (hd0) is now a reference, we cannot change backing.* anymore + self.reopen(opts, {}, "Cannot change the option 'backing.driver'") + + # We can't remove 'hd0' while it's a backing image of 'hd1' + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/desc', "Node 'hd0' is busy: node is used as backing hd of 'hd1'") + + # But we can remove both nodes if done in the proper order + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') + self.assert_qmp(result, 'return', {}) + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + # Reopen a raw image and see the effect of changing the 'offset' option + def test_reopen_raw(self): + opts = {'driver': 'raw', 'node-name': 'hd0', + 'file': { 'driver': 'file', + 'filename': hd_path[0], + 'node-name': 'hd0-file' } } + + # First we create a 2MB raw file, and fill each half with a + # different value + qemu_img('create', '-f', 'raw', hd_path[0], '2M') + qemu_io('-f', 'raw', '-c', 'write -P 0xa0 0 1M', hd_path[0]) + qemu_io('-f', 'raw', '-c', 'write -P 0xa1 1M 1M', hd_path[0]) + + # Open the raw file with QEMU + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # Read 1MB from offset 0 + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + + # Reopen the image with a 1MB offset. + # Now the results are different + self.reopen(opts, {'offset': 1024*1024}) + self.run_qemu_io("hd0", "read -P 0xa1 0 1M") + + # Reopen again with the original options. + # We get the original results again + self.reopen(opts) + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + + # Remove the block device + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + # Omitting an option should reset it to the default value, but if + # an option cannot be changed it shouldn't be possible to reset it + # to its default value either + def test_reset_default_values(self): + opts = {'driver': 'qcow2', 'node-name': 'hd0', + 'file': { 'driver': 'file', + 'filename': hd_path[0], + 'x-check-cache-dropped': True, # This one can be changed + 'locking': 'off', # This one can NOT be changed + 'node-name': 'hd0-file' } } + + # Open the file with QEMU + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # file.x-check-cache-dropped can be changed... + self.reopen(opts, { 'file.x-check-cache-dropped': False }) + # ...and dropped completely (resetting to the default value) + del opts['file']['x-check-cache-dropped'] + self.reopen(opts) + + # file.locking cannot be changed nor reset to the default value + self.reopen(opts, { 'file.locking': 'on' }, "Cannot change the option 'locking'") + del opts['file']['locking'] + self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") + # But we can reopen it if we maintain its previous value + self.reopen(opts, { 'file.locking': 'off' }) + + # Remove the block device + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + # This test modifies the node graph a few times by changing the + # 'backing' option on reopen and verifies that the guest data that + # is read afterwards is consistent with the graph changes. + def test_io_with_graph_changes(self): + opts = [] + + # Open hd0, hd1 and hd2 without any backing image + for i in range(3): + opts.append(hd_opts(i)) + opts[i]['backing'] = None + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts[i]) + self.assert_qmp(result, 'return', {}) + + # hd0 + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + self.run_qemu_io("hd0", "read -P 0 1M 1M") + self.run_qemu_io("hd0", "read -P 0 2M 1M") + + # hd1 <- hd0 + self.reopen(opts[0], {'backing': 'hd1'}) + + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + self.run_qemu_io("hd0", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd0", "read -P 0 2M 1M") + + # hd1 <- hd0 , hd1 <- hd2 + self.reopen(opts[2], {'backing': 'hd1'}) + + self.run_qemu_io("hd2", "read -P 0 0 1M") + self.run_qemu_io("hd2", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd2", "read -P 0xa2 2M 1M") + + # hd1 <- hd2 <- hd0 + self.reopen(opts[0], {'backing': 'hd2'}) + + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + self.run_qemu_io("hd0", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd0", "read -P 0xa2 2M 1M") + + # hd2 <- hd0 + self.reopen(opts[2], {'backing': None}) + + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + self.run_qemu_io("hd0", "read -P 0 1M 1M") + self.run_qemu_io("hd0", "read -P 0xa2 2M 1M") + + # hd2 <- hd1 <- hd0 + self.reopen(opts[1], {'backing': 'hd2'}) + self.reopen(opts[0], {'backing': 'hd1'}) + + self.run_qemu_io("hd0", "read -P 0xa0 0 1M") + self.run_qemu_io("hd0", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd0", "read -P 0xa2 2M 1M") + + # Illegal operation: hd2 is a child of hd1 + self.reopen(opts[2], {'backing': 'hd1'}, + "Making 'hd1' a backing file of 'hd2' would create a cycle") + + # hd2 <- hd0, hd2 <- hd1 + self.reopen(opts[0], {'backing': 'hd2'}) + + self.run_qemu_io("hd1", "read -P 0 0 1M") + self.run_qemu_io("hd1", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd1", "read -P 0xa2 2M 1M") + + # More illegal operations + self.reopen(opts[2], {'backing': 'hd1'}, + "Making 'hd1' a backing file of 'hd2' would create a cycle") + self.reopen(opts[2], {'file': 'hd0-file'}, "Cannot change the option 'file'") + + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') + self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/desc', "Node 'hd2' is busy: node is used as backing hd of 'hd0'") + + # hd1 doesn't have a backing file now + self.reopen(opts[1], {'backing': None}) + + self.run_qemu_io("hd1", "read -P 0 0 1M") + self.run_qemu_io("hd1", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd1", "read -P 0 2M 1M") + + # We can't remove the 'backing' option if the image has a + # default backing file + del opts[1]['backing'] + self.reopen(opts[1], {}, "backing is missing for 'hd1'") + + self.run_qemu_io("hd1", "read -P 0 0 1M") + self.run_qemu_io("hd1", "read -P 0xa1 1M 1M") + self.run_qemu_io("hd1", "read -P 0 2M 1M") + + # This test verifies that we can't change the children of a block + # device during a reopen operation in a way that would create + # cycles in the node graph + def test_graph_cycles(self): + opts = [] + + # Open all three images without backing file + for i in range(3): + opts.append(hd_opts(i)) + opts[i]['backing'] = None + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts[i]) + self.assert_qmp(result, 'return', {}) + + # hd1 <- hd0, hd1 <- hd2 + self.reopen(opts[0], {'backing': 'hd1'}) + self.reopen(opts[2], {'backing': 'hd1'}) + + # Illegal: hd2 is backed by hd1 + self.reopen(opts[1], {'backing': 'hd2'}, + "Making 'hd2' a backing file of 'hd1' would create a cycle") + + # hd1 <- hd0 <- hd2 + self.reopen(opts[2], {'backing': 'hd0'}) + + # Illegal: hd2 is backed by hd0, which is backed by hd1 + self.reopen(opts[1], {'backing': 'hd2'}, + "Making 'hd2' a backing file of 'hd1' would create a cycle") + + # Illegal: hd1 cannot point to itself + self.reopen(opts[1], {'backing': 'hd1'}, + "Making 'hd1' a backing file of 'hd1' would create a cycle") + + # Remove all backing files + self.reopen(opts[0]) + self.reopen(opts[2]) + + ########################################## + # Add a blkverify node using hd0 and hd1 # + ########################################## + bvopts = {'driver': 'blkverify', + 'node-name': 'bv', + 'test': 'hd0', + 'raw': 'hd1'} + result = self.vm.qmp('blockdev-add', conv_keys = False, **bvopts) + self.assert_qmp(result, 'return', {}) + + # blkverify doesn't currently allow reopening. TODO: implement this + self.reopen(bvopts, {}, "Block format 'blkverify' used by node 'bv'" + + " does not support reopening files") + + # Illegal: hd0 is a child of the blkverify node + self.reopen(opts[0], {'backing': 'bv'}, + "Making 'bv' a backing file of 'hd0' would create a cycle") + + # Delete the blkverify node + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bv') + self.assert_qmp(result, 'return', {}) + + # Misc reopen tests with different block drivers + def test_misc_drivers(self): + #################### + ###### quorum ###### + #################### + for i in range(3): + opts = hd_opts(i) + # Open all three images without backing file + opts['backing'] = None + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + opts = {'driver': 'quorum', + 'node-name': 'quorum0', + 'children': ['hd0', 'hd1', 'hd2'], + 'vote-threshold': 2} + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # Quorum doesn't currently allow reopening. TODO: implement this + self.reopen(opts, {}, "Block format 'quorum' used by node 'quorum0'" + + " does not support reopening files") + + # You can't make quorum0 a backing file of hd0: + # hd0 is already a child of quorum0. + self.reopen(hd_opts(0), {'backing': 'quorum0'}, + "Making 'quorum0' a backing file of 'hd0' would create a cycle") + + # Delete quorum0 + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'quorum0') + self.assert_qmp(result, 'return', {}) + + # Delete hd0, hd1 and hd2 + for i in range(3): + result = self.vm.qmp('blockdev-del', conv_keys = True, + node_name = 'hd%d' % i) + self.assert_qmp(result, 'return', {}) + + ###################### + ###### blkdebug ###### + ###################### + opts = {'driver': 'blkdebug', + 'node-name': 'bd', + 'config': '/dev/null', + 'image': hd_opts(0)} + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # blkdebug allows reopening if we keep the same options + self.reopen(opts) + + # but it currently does not allow changes + self.reopen(opts, {'image': 'hd1'}, "Cannot change the option 'image'") + self.reopen(opts, {'align': 33554432}, "Cannot change the option 'align'") + self.reopen(opts, {'config': '/non/existent'}, "Cannot change the option 'config'") + del opts['config'] + self.reopen(opts, {}, "Option 'config' cannot be reset to its default value") + + # Delete the blkdebug node + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bd') + self.assert_qmp(result, 'return', {}) + + ################## + ###### null ###### + ################## + opts = {'driver': 'null-aio', 'node-name': 'root', 'size': 1024} + + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # 1 << 30 is the default value, but we cannot change it explicitly + self.reopen(opts, {'size': (1 << 30)}, "Cannot change the option 'size'") + + # We cannot change 'size' back to its default value either + del opts['size'] + self.reopen(opts, {}, "Option 'size' cannot be reset to its default value") + + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'root') + self.assert_qmp(result, 'return', {}) + + ################## + ###### file ###### + ################## + opts = hd_opts(0) + opts['file']['locking'] = 'on' + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # 'locking' cannot be changed + del opts['file']['locking'] + self.reopen(opts, {'file.locking': 'on'}) + self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") + self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") + + # Trying to reopen the 'file' node directly does not make a difference + opts = opts['file'] + self.reopen(opts, {'locking': 'on'}) + self.reopen(opts, {'locking': 'off'}, "Cannot change the option 'locking'") + self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") + + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + ###################### + ###### throttle ###### + ###################### + opts = { 'qom-type': 'throttle-group', 'id': 'group0', + 'props': { 'limits': { 'iops-total': 1000 } } } + result = self.vm.qmp('object-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + opts = { 'qom-type': 'throttle-group', 'id': 'group1', + 'props': { 'limits': { 'iops-total': 2000 } } } + result = self.vm.qmp('object-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # Add a throttle filter with group = group0 + opts = { 'driver': 'throttle', 'node-name': 'throttle0', + 'throttle-group': 'group0', 'file': hd_opts(0) } + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # We can reopen it if we keep the same options + self.reopen(opts) + + # We can also reopen if 'file' is a reference to the child + self.reopen(opts, {'file': 'hd0'}) + + # This is illegal + self.reopen(opts, {'throttle-group': 'notfound'}, "Throttle group 'notfound' does not exist") + + # But it's possible to change the group to group1 + self.reopen(opts, {'throttle-group': 'group1'}) + + # Now group1 is in use, it cannot be deleted + result = self.vm.qmp('object-del', id = 'group1') + self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/desc', "object 'group1' is in use, can not be deleted") + + # Default options, this switches the group back to group0 + self.reopen(opts) + + # So now we cannot delete group0 + result = self.vm.qmp('object-del', id = 'group0') + self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/desc', "object 'group0' is in use, can not be deleted") + + # But group1 is free this time, and it can be deleted + result = self.vm.qmp('object-del', id = 'group1') + self.assert_qmp(result, 'return', {}) + + # Let's delete the filter node + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'throttle0') + self.assert_qmp(result, 'return', {}) + + # And we can finally get rid of group0 + result = self.vm.qmp('object-del', id = 'group0') + self.assert_qmp(result, 'return', {}) + + # If an image has a backing file then the 'backing' option must be + # passed on reopen. We don't allow leaving the option out in this + # case because it's unclear what the correct semantics would be. + def test_missing_backing_options_1(self): + # hd2 + opts = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # hd0 + opts = hd_opts(0) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # hd0 has no backing file: we can omit the 'backing' option + self.reopen(opts) + + # hd2 <- hd0 + self.reopen(opts, {'backing': 'hd2'}) + + # hd0 has a backing file: we must set the 'backing' option + self.reopen(opts, {}, "backing is missing for 'hd0'") + + # hd2 can't be removed because it's the backing file of hd0 + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') + self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/desc', "Node 'hd2' is busy: node is used as backing hd of 'hd0'") + + # Detach hd2 from hd0. + self.reopen(opts, {'backing': None}) + self.reopen(opts, {}, "backing is missing for 'hd0'") + + # Remove both hd0 and hd2 + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') + self.assert_qmp(result, 'return', {}) + + # If an image has default backing file (as part of its metadata) + # then the 'backing' option must be passed on reopen. We don't + # allow leaving the option out in this case because it's unclear + # what the correct semantics would be. + def test_missing_backing_options_2(self): + # hd0 <- hd1 + # (hd0 is hd1's default backing file) + opts = hd_opts(1) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # hd1 has a backing file: we can't omit the 'backing' option + self.reopen(opts, {}, "backing is missing for 'hd1'") + + # Let's detach the backing file + self.reopen(opts, {'backing': None}) + + # No backing file attached to hd1 now, but we still can't omit the 'backing' option + self.reopen(opts, {}, "backing is missing for 'hd1'") + + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') + self.assert_qmp(result, 'return', {}) + + # Test that making 'backing' a reference to an existing child + # keeps its current options + def test_backing_reference(self): + # hd2 <- hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = hd_opts(2) + # Enable 'detect-zeroes' on all three nodes + opts['detect-zeroes'] = 'on' + opts['backing']['detect-zeroes'] = 'on' + opts['backing']['backing']['detect-zeroes'] = 'on' + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # Reopen the chain passing the minimum amount of required options. + # By making 'backing' a reference to hd1 (instead of a sub-dict) + # we tell QEMU to keep its current set of options. + opts = {'driver': iotests.imgfmt, + 'node-name': 'hd0', + 'file': 'hd0-file', + 'backing': 'hd1' } + self.reopen(opts) + + # This has reset 'detect-zeroes' on hd0, but not on hd1 and hd2. + self.assert_qmp(self.get_node('hd0'), 'detect_zeroes', 'off') + self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') + self.assert_qmp(self.get_node('hd2'), 'detect_zeroes', 'on') + + # Test what happens if the graph changes due to other operations + # such as block-stream + def test_block_stream_1(self): + # hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = None + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # Stream hd1 into hd0 and wait until it's done + result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', device = 'hd0') + self.assert_qmp(result, 'return', {}) + self.wait_until_completed(drive = 'stream0') + + # Now we have only hd0 + self.assertEqual(self.get_node('hd1'), None) + + # We have backing.* options but there's no backing file anymore + self.reopen(opts, {}, "Cannot change the option 'backing.driver'") + + # If we remove the 'backing' option then we can reopen hd0 just fine + del opts['backing'] + self.reopen(opts) + + # We can also reopen hd0 if we set 'backing' to null + self.reopen(opts, {'backing': None}) + + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + + # Another block_stream test + def test_block_stream_2(self): + # hd2 <- hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # Stream hd1 into hd0 and wait until it's done + result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', + device = 'hd0', base_node = 'hd2') + self.assert_qmp(result, 'return', {}) + self.wait_until_completed(drive = 'stream0') + + # The chain is hd2 <- hd0 now. hd1 is missing + self.assertEqual(self.get_node('hd1'), None) + + # The backing options in the dict were meant for hd1, but we cannot + # use them with hd2 because hd1 had a backing file while hd2 does not. + self.reopen(opts, {}, "Cannot change the option 'backing.driver'") + + # If we remove hd1's options from the dict then things work fine + opts['backing'] = opts['backing']['backing'] + self.reopen(opts) + + # We can also reopen hd0 if we use a reference to the backing file + self.reopen(opts, {'backing': 'hd2'}) + + # But we cannot leave the option out + del opts['backing'] + self.reopen(opts, {}, "backing is missing for 'hd0'") + + # Now we can delete hd0 (and hd2) + result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') + self.assert_qmp(result, 'return', {}) + self.assertEqual(self.get_node('hd2'), None) + + # Reopen the chain during a block-stream job (from hd1 to hd0) + def test_block_stream_3(self): + # hd2 <- hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # hd2 <- hd0 + result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', + device = 'hd0', base_node = 'hd2', speed = 512 * 1024) + self.assert_qmp(result, 'return', {}) + + # We can't remove hd2 while the stream job is ongoing + opts['backing']['backing'] = None + self.reopen(opts, {}, "Cannot change 'backing' link from 'hd1' to 'hd2'") + + # We can't remove hd1 while the stream job is ongoing + opts['backing'] = None + self.reopen(opts, {}, "Cannot change 'backing' link from 'hd0' to 'hd1'") + + self.wait_until_completed(drive = 'stream0') + + # Reopen the chain during a block-stream job (from hd2 to hd1) + def test_block_stream_4(self): + # hd2 <- hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + # hd1 <- hd0 + result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', + device = 'hd1', speed = 512 * 1024) + self.assert_qmp(result, 'return', {}) + + # We can't reopen with the original options because that would + # make hd1 read-only and block-stream requires it to be read-write + self.reopen(opts, {}, "Can't set node 'hd1' to r/o with copy-on-read enabled") + + # We can't remove hd2 while the stream job is ongoing + opts['backing']['backing'] = None + self.reopen(opts, {'backing.read-only': False}, "Cannot change 'backing' link from 'hd1' to 'hd2'") + + # We can detach hd1 from hd0 because it doesn't affect the stream job + opts['backing'] = None + self.reopen(opts) + + self.wait_until_completed(drive = 'stream0') + + # Reopen the chain during a block-commit job (from hd0 to hd2) + def test_block_commit_1(self): + # hd2 <- hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0', + device = 'hd0', speed = 1024 * 1024) + self.assert_qmp(result, 'return', {}) + + # We can't remove hd2 while the commit job is ongoing + opts['backing']['backing'] = None + self.reopen(opts, {}, "Cannot change 'backing' link from 'hd1' to 'hd2'") + + # We can't remove hd1 while the commit job is ongoing + opts['backing'] = None + self.reopen(opts, {}, "Cannot change 'backing' link from 'hd0' to 'hd1'") + + event = self.vm.event_wait(name='BLOCK_JOB_READY') + self.assert_qmp(event, 'data/device', 'commit0') + self.assert_qmp(event, 'data/type', 'commit') + self.assert_qmp_absent(event, 'data/error') + + result = self.vm.qmp('block-job-complete', device='commit0') + self.assert_qmp(result, 'return', {}) + + self.wait_until_completed(drive = 'commit0') + + # Reopen the chain during a block-commit job (from hd1 to hd2) + def test_block_commit_2(self): + # hd2 <- hd1 <- hd0 + opts = hd_opts(0) + opts['backing'] = hd_opts(1) + opts['backing']['backing'] = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0', + device = 'hd0', top_node = 'hd1', speed = 1024 * 1024) + self.assert_qmp(result, 'return', {}) + + # We can't remove hd2 while the commit job is ongoing + opts['backing']['backing'] = None + self.reopen(opts, {}, "Cannot change the option 'backing.driver'") + + # We can't remove hd1 while the commit job is ongoing + opts['backing'] = None + self.reopen(opts, {}, "Cannot change backing link if 'hd0' has an implicit backing file") + + # hd2 <- hd0 + self.wait_until_completed(drive = 'commit0') + + self.assert_qmp(self.get_node('hd0'), 'ro', False) + self.assertEqual(self.get_node('hd1'), None) + self.assert_qmp(self.get_node('hd2'), 'ro', True) + + # We don't allow setting a backing file that uses a different AioContext + def test_iothreads(self): + opts = hd_opts(0) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) + self.assert_qmp(result, 'return', {}) + + opts2 = hd_opts(2) + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('object-add', qom_type='iothread', id='iothread0') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('object-add', qom_type='iothread', id='iothread1') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('x-blockdev-set-iothread', node_name='hd0', iothread='iothread0') + self.assert_qmp(result, 'return', {}) + + self.reopen(opts, {'backing': 'hd2'}, "Cannot use a new backing file with a different AioContext") + + result = self.vm.qmp('x-blockdev-set-iothread', node_name='hd2', iothread='iothread1') + self.assert_qmp(result, 'return', {}) + + self.reopen(opts, {'backing': 'hd2'}, "Cannot use a new backing file with a different AioContext") + + result = self.vm.qmp('x-blockdev-set-iothread', node_name='hd2', iothread='iothread0') + self.assert_qmp(result, 'return', {}) + + self.reopen(opts, {'backing': 'hd2'}) + +if __name__ == '__main__': + iotests.main(supported_fmts=["qcow2"]) diff --git a/tests/qemu-iotests/245.out b/tests/qemu-iotests/245.out new file mode 100644 index 0000000000..71009c239f --- /dev/null +++ b/tests/qemu-iotests/245.out @@ -0,0 +1,5 @@ +.................. +---------------------------------------------------------------------- +Ran 18 tests + +OK diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index b97d83331b..7289309604 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -243,4 +243,5 @@ 242 rw auto quick 243 rw auto quick 244 rw auto quick +245 rw auto 246 rw auto quick diff --git a/tests/test-announce-self.c b/tests/test-announce-self.c index 1644d34a3f..0e6d466aa4 100644 --- a/tests/test-announce-self.c +++ b/tests/test-announce-self.c @@ -21,18 +21,8 @@ #define ETH_P_RARP 0x8035 #endif -static QTestState *test_init(int socket) -{ - char *args; - - args = g_strdup_printf("-netdev socket,fd=%d,id=hs0 -device " - "virtio-net-pci,netdev=hs0", socket); - - return qtest_start(args); -} - -static void test_announce(int socket) +static void test_announce(QTestState *qs, int socket) { char buffer[60]; int len; @@ -40,7 +30,7 @@ static void test_announce(int socket) int ret; uint16_t *proto = (uint16_t *)&buffer[12]; - rsp = qmp("{ 'execute' : 'announce-self', " + rsp = qtest_qmp(qs, "{ 'execute' : 'announce-self', " " 'arguments': {" " 'initial': 50, 'max': 550," " 'rounds': 10, 'step': 50 } }"); @@ -59,14 +49,15 @@ static void test_announce(int socket) static void setup(gconstpointer data) { QTestState *qs; - void (*func) (int socket) = data; + void (*func) (QTestState *qs, int socket) = data; int sv[2], ret; ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv); g_assert_cmpint(ret, !=, -1); - qs = test_init(sv[1]); - func(sv[0]); + qs = qtest_initf("-netdev socket,fd=%d,id=hs0 -device " + "virtio-net-pci,netdev=hs0", sv[1]); + func(qs, sv[0]); /* End test */ close(sv[0]); diff --git a/tests/vhost-user-test.c b/tests/vhost-user-test.c index 0c965b3b1e..3817966010 100644 --- a/tests/vhost-user-test.c +++ b/tests/vhost-user-test.c @@ -178,7 +178,8 @@ static void append_mem_opts(TestServer *server, GString *cmd_line, int size, enum test_memfd memfd) { if (memfd == TEST_MEMFD_AUTO) { - memfd = qemu_memfd_check(0) ? TEST_MEMFD_YES : TEST_MEMFD_NO; + memfd = qemu_memfd_check(MFD_ALLOW_SEALING) ? TEST_MEMFD_YES + : TEST_MEMFD_NO; } if (memfd == TEST_MEMFD_YES) { @@ -930,7 +931,7 @@ static void register_vhost_user_test(void) "virtio-net", test_read_guest_mem, &opts); - if (qemu_memfd_check(0)) { + if (qemu_memfd_check(MFD_ALLOW_SEALING)) { opts.before = vhost_user_test_setup_memfd; qos_add_test("vhost-user/read-guest-mem/memfd", "virtio-net", diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index b02be0274e..b65365934b 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -751,7 +751,7 @@ static void *virtio_blk_test_setup(GString *cmd_line, void *arg) char *tmp_path = drive_create(); g_string_append_printf(cmd_line, - " -drive if=none,id=drive0,file=%s,format=raw " + " -drive if=none,id=drive0,file=%s,format=raw,auto-read-only=off " "-drive if=none,id=drive1,file=null-co://,format=raw ", tmp_path); @@ -1019,16 +1019,16 @@ static void vnc_update_throttle_offset(VncState *vs) int bps; switch (vs->as.fmt) { default: - case AUD_FMT_U8: - case AUD_FMT_S8: + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S8: bps = 1; break; - case AUD_FMT_U16: - case AUD_FMT_S16: + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_S16: bps = 2; break; - case AUD_FMT_U32: - case AUD_FMT_S32: + case AUDIO_FORMAT_U32: + case AUDIO_FORMAT_S32: bps = 4; break; } @@ -2375,12 +2375,12 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) if (len == 4) return 10; switch (read_u8(data, 4)) { - case 0: vs->as.fmt = AUD_FMT_U8; break; - case 1: vs->as.fmt = AUD_FMT_S8; break; - case 2: vs->as.fmt = AUD_FMT_U16; break; - case 3: vs->as.fmt = AUD_FMT_S16; break; - case 4: vs->as.fmt = AUD_FMT_U32; break; - case 5: vs->as.fmt = AUD_FMT_S32; break; + case 0: vs->as.fmt = AUDIO_FORMAT_U8; break; + case 1: vs->as.fmt = AUDIO_FORMAT_S8; break; + case 2: vs->as.fmt = AUDIO_FORMAT_U16; break; + case 3: vs->as.fmt = AUDIO_FORMAT_S16; break; + case 4: vs->as.fmt = AUDIO_FORMAT_U32; break; + case 5: vs->as.fmt = AUDIO_FORMAT_S32; break; default: VNC_DEBUG("Invalid audio format %d\n", read_u8(data, 4)); vnc_client_error(vs); @@ -3111,7 +3111,7 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, vs->as.freq = 44100; vs->as.nchannels = 2; - vs->as.fmt = AUD_FMT_S16; + vs->as.fmt = AUDIO_FORMAT_S16; vs->as.endianness = 0; qemu_mutex_init(&vs->output_mutex); diff --git a/util/memfd.c b/util/memfd.c index 8debd0d037..00334e5b21 100644 --- a/util/memfd.c +++ b/util/memfd.c @@ -40,6 +40,7 @@ static int memfd_create(const char *name, unsigned int flags) #ifdef __NR_memfd_create return syscall(__NR_memfd_create, name, flags); #else + errno = ENOSYS; return -1; #endif } @@ -70,14 +71,18 @@ int qemu_memfd_create(const char *name, size_t size, bool hugetlb, } mfd = memfd_create(name, flags); if (mfd < 0) { + error_setg_errno(errp, errno, + "failed to create memfd with flags 0x%x", flags); goto err; } if (ftruncate(mfd, size) == -1) { + error_setg_errno(errp, errno, "failed to resize memfd to %zu", size); goto err; } if (seals && fcntl(mfd, F_ADD_SEALS, seals) == -1) { + error_setg_errno(errp, errno, "failed to add seals 0x%x", seals); goto err; } @@ -87,8 +92,9 @@ err: if (mfd >= 0) { close(mfd); } +#else + error_setg_errno(errp, ENOSYS, "failed to create memfd"); #endif - error_setg_errno(errp, errno, "failed to create memfd"); return -1; } @@ -188,7 +194,7 @@ bool qemu_memfd_alloc_check(void) bool qemu_memfd_check(unsigned int flags) { #ifdef CONFIG_LINUX - int mfd = memfd_create("test", flags); + int mfd = memfd_create("test", flags | MFD_CLOEXEC); if (mfd >= 0) { close(mfd); diff --git a/util/oslib-posix.c b/util/oslib-posix.c index 326d92dcd2..88dda9cd39 100644 --- a/util/oslib-posix.c +++ b/util/oslib-posix.c @@ -512,6 +512,59 @@ void os_mem_prealloc(int fd, char *area, size_t memory, int smp_cpus, } } +uint64_t qemu_get_pmem_size(const char *filename, Error **errp) +{ + struct stat st; + + if (stat(filename, &st) < 0) { + error_setg(errp, "unable to stat pmem file \"%s\"", filename); + return 0; + } + +#if defined(__linux__) + /* Special handling for devdax character devices */ + if (S_ISCHR(st.st_mode)) { + char *subsystem_path = NULL; + char *subsystem = NULL; + char *size_path = NULL; + char *size_str = NULL; + uint64_t ret = 0; + + subsystem_path = g_strdup_printf("/sys/dev/char/%d:%d/subsystem", + major(st.st_rdev), minor(st.st_rdev)); + subsystem = g_file_read_link(subsystem_path, NULL); + if (!subsystem) { + error_setg(errp, "unable to read subsystem for pmem file \"%s\"", + filename); + goto devdax_err; + } + + if (!g_str_has_suffix(subsystem, "/dax")) { + error_setg(errp, "pmem file \"%s\" is not a dax device", filename); + goto devdax_err; + } + + size_path = g_strdup_printf("/sys/dev/char/%d:%d/size", + major(st.st_rdev), minor(st.st_rdev)); + if (!g_file_get_contents(size_path, &size_str, NULL, NULL)) { + error_setg(errp, "unable to read size for pmem file \"%s\"", + size_path); + goto devdax_err; + } + + ret = g_ascii_strtoull(size_str, NULL, 0); + +devdax_err: + g_free(size_str); + g_free(size_path); + g_free(subsystem); + g_free(subsystem_path); + return ret; + } +#endif /* defined(__linux__) */ + + return st.st_size; +} char *qemu_get_pid_name(pid_t pid) { diff --git a/util/oslib-win32.c b/util/oslib-win32.c index b4c17f5dfa..bd633afab6 100644 --- a/util/oslib-win32.c +++ b/util/oslib-win32.c @@ -560,6 +560,11 @@ void os_mem_prealloc(int fd, char *area, size_t memory, int smp_cpus, } } +uint64_t qemu_get_pmem_size(const char *filename, Error **errp) +{ + error_setg(errp, "pmem support not available"); + return 0; +} char *qemu_get_pid_name(pid_t pid) { @@ -3285,9 +3285,12 @@ int main(int argc, char **argv, char **envp) add_device_config(DEV_BT, optarg); break; case QEMU_OPTION_audio_help: - AUD_help (); + audio_legacy_help(); exit (0); break; + case QEMU_OPTION_audiodev: + audio_parse_option(optarg); + break; case QEMU_OPTION_soundhw: select_soundhw (optarg); break; @@ -4454,6 +4457,8 @@ int main(int argc, char **argv, char **envp) /* do monitor/qmp handling at preconfig state if requested */ main_loop(); + audio_init_audiodevs(); + /* from here on runstate is RUN_STATE_PRELAUNCH */ machine_run_board_init(current_machine); |