conf: allow to map sound device to host device

Introduce a new device element "<audio>" which allows
to map guest sound device specified using the "<sound>"
element to specific audio backend.

Example:

  <sound model='ich7'>
     <audio id='1'/>
  </sound>
  <audio id='1' type='oss'>
     <input dev='/dev/dsp0'/>
     <output dev='/dev/dsp0'/>
  </audio>

This block maps to OSS audio backend on the host using
/dev/dsp0 device for both input (recording)
and output (playback).

OSS is the only backend supported so far.

Signed-off-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Roman Bogorodskiy 2020-07-18 10:23:57 +04:00
parent 5d3137bbfb
commit 9375bc7373
12 changed files with 266 additions and 2 deletions

View File

@ -4372,12 +4372,47 @@
<optional>
<ref name="address"/>
</optional>
<optional>
<element name="audio">
<attribute name="id">
<ref name="uint8"/>
</attribute>
</element>
</optional>
<zeroOrMore>
<ref name="codec"/>
</zeroOrMore>
</interleave>
</element>
</define>
<define name="audio">
<element name="audio">
<attribute name="id">
<ref name="uint8"/>
</attribute>
<attribute name="type">
<choice>
<value>oss</value>
</choice>
</attribute>
<interleave>
<optional>
<element name="input">
<attribute name="dev">
<ref name="deviceName"/>
</attribute>
</element>
</optional>
<optional>
<element name="output">
<attribute name="dev">
<ref name="deviceName"/>
</attribute>
</element>
</optional>
</interleave>
</element>
</define>
<define name="watchdog">
<element name="watchdog">
<attribute name="model">
@ -5303,6 +5338,7 @@
<ref name="interface"/>
<ref name="input"/>
<ref name="sound"/>
<ref name="audio"/>
<ref name="hostdev"/>
<ref name="graphic"/>
<ref name="video"/>

View File

@ -688,6 +688,10 @@ virDomainCapsDeviceDefValidate(const virDomainCaps *caps,
ret = virDomainCapsDeviceVideoDefValidate(caps, dev->data.video);
break;
case VIR_DOMAIN_DEVICE_AUDIO:
/* TODO: add validation */
break;
case VIR_DOMAIN_DEVICE_DISK:
case VIR_DOMAIN_DEVICE_REDIRDEV:
case VIR_DOMAIN_DEVICE_NET:

View File

@ -323,6 +323,7 @@ VIR_ENUM_IMPL(virDomainDevice,
"memory",
"iommu",
"vsock",
"audio",
);
VIR_ENUM_IMPL(virDomainDiskDevice,
@ -729,6 +730,11 @@ VIR_ENUM_IMPL(virDomainSoundModel,
"ich7",
);
VIR_ENUM_IMPL(virDomainAudioType,
VIR_DOMAIN_AUDIO_TYPE_LAST,
"oss",
);
VIR_ENUM_IMPL(virDomainKeyWrapCipherName,
VIR_DOMAIN_KEY_WRAP_CIPHER_NAME_LAST,
"aes",
@ -2864,6 +2870,24 @@ void virDomainSoundDefFree(virDomainSoundDefPtr def)
VIR_FREE(def);
}
void virDomainAudioDefFree(virDomainAudioDefPtr def)
{
if (!def)
return;
switch ((virDomainAudioType) def->type) {
case VIR_DOMAIN_AUDIO_TYPE_OSS:
VIR_FREE(def->backend.oss.inputDev);
VIR_FREE(def->backend.oss.outputDev);
break;
case VIR_DOMAIN_AUDIO_TYPE_LAST:
break;
}
VIR_FREE(def);
}
virDomainSoundDefPtr
virDomainSoundDefRemove(virDomainDefPtr def, size_t idx)
{
@ -3217,6 +3241,9 @@ void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
case VIR_DOMAIN_DEVICE_VSOCK:
virDomainVsockDefFree(def->data.vsock);
break;
case VIR_DOMAIN_DEVICE_AUDIO:
virDomainAudioDefFree(def->data.audio);
break;
case VIR_DOMAIN_DEVICE_LAST:
case VIR_DOMAIN_DEVICE_NONE:
break;
@ -3477,6 +3504,10 @@ void virDomainDefFree(virDomainDefPtr def)
virDomainSoundDefFree(def->sounds[i]);
VIR_FREE(def->sounds);
for (i = 0; i < def->naudios; i++)
virDomainAudioDefFree(def->audios[i]);
VIR_FREE(def->audios);
for (i = 0; i < def->nvideos; i++)
virDomainVideoDefFree(def->videos[i]);
VIR_FREE(def->videos);
@ -4065,6 +4096,7 @@ virDomainDeviceGetInfo(virDomainDeviceDefPtr device)
case VIR_DOMAIN_DEVICE_LEASE:
case VIR_DOMAIN_DEVICE_GRAPHICS:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
case VIR_DOMAIN_DEVICE_NONE:
break;
@ -4159,6 +4191,9 @@ virDomainDeviceSetData(virDomainDeviceDefPtr device,
case VIR_DOMAIN_DEVICE_LEASE:
device->data.lease = devicedata;
break;
case VIR_DOMAIN_DEVICE_AUDIO:
device->data.audio = devicedata;
break;
case VIR_DOMAIN_DEVICE_NONE:
case VIR_DOMAIN_DEVICE_LAST:
break;
@ -4425,6 +4460,7 @@ virDomainDeviceInfoIterateInternal(virDomainDefPtr def,
case VIR_DOMAIN_DEVICE_MEMORY:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_VSOCK:
case VIR_DOMAIN_DEVICE_AUDIO:
break;
}
#endif
@ -5417,6 +5453,7 @@ virDomainDeviceDefPostParseCommon(virDomainDeviceDefPtr dev,
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_MEMORY:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
ret = 0;
break;
@ -6806,6 +6843,8 @@ virDomainDeviceDefValidateInternal(const virDomainDeviceDef *dev,
case VIR_DOMAIN_DEVICE_SHMEM:
return virDomainShmemDefValidate(dev->data.shmem);
case VIR_DOMAIN_DEVICE_AUDIO:
/* TODO: validate? */
case VIR_DOMAIN_DEVICE_LEASE:
case VIR_DOMAIN_DEVICE_FS:
case VIR_DOMAIN_DEVICE_SOUND:
@ -15002,10 +15041,10 @@ virDomainSoundDefParseXML(virDomainXMLOptionPtr xmlopt,
virDomainSoundDefPtr def;
VIR_XPATH_NODE_AUTORESTORE(ctxt);
g_autofree char *model = NULL;
xmlNodePtr audioNode;
if (VIR_ALLOC(def) < 0)
return NULL;
ctxt->node = node;
model = virXMLPropString(node, "model");
@ -15042,6 +15081,18 @@ virDomainSoundDefParseXML(virDomainXMLOptionPtr xmlopt,
}
}
audioNode = virXPathNode("./audio", ctxt);
if (audioNode) {
g_autofree char *tmp = NULL;
tmp = virXMLPropString(audioNode, "id");
if (virStrToLong_ui(tmp, NULL, 10, &def->audioId) < 0 ||
def->audioId == 0) {
virReportError(VIR_ERR_XML_ERROR,
_("Invalid audio 'id' value '%s'"), tmp);
goto error;
}
}
if (virDomainDeviceInfoParseXML(xmlopt, node, &def->info, flags) < 0)
goto error;
@ -15093,6 +15144,64 @@ virDomainSoundDefFind(const virDomainDef *def,
}
static virDomainAudioDefPtr
virDomainAudioDefParseXML(virDomainXMLOptionPtr xmlopt G_GNUC_UNUSED,
xmlNodePtr node G_GNUC_UNUSED,
xmlXPathContextPtr ctxt G_GNUC_UNUSED)
{
virDomainAudioDefPtr def;
VIR_XPATH_NODE_AUTORESTORE(ctxt);
g_autofree char *tmp = NULL;
g_autofree char *type = NULL;
if (VIR_ALLOC(def) < 0)
return NULL;
ctxt->node = node;
type = virXMLPropString(node, "type");
if ((def->type = virDomainAudioTypeTypeFromString(type)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown audio type '%s'"), type);
goto error;
}
tmp = virXMLPropString(node, "id");
if (virStrToLong_ui(tmp, NULL, 10, &def->id) < 0 ||
def->id == 0) {
virReportError(VIR_ERR_XML_ERROR,
_("invalid audio 'id' value '%s'"), tmp);
goto error;
}
switch ((virDomainAudioType) def->type) {
case VIR_DOMAIN_AUDIO_TYPE_OSS: {
xmlNodePtr inputDevNode, outputDevNode;
inputDevNode = virXPathNode("./input", ctxt);
outputDevNode = virXPathNode("./output", ctxt);
if (!inputDevNode || !outputDevNode) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Audio type OSS requires to have <input> "
"and <output> specified"));
goto error;
}
def->backend.oss.inputDev = virXMLPropString(inputDevNode, "dev");
def->backend.oss.outputDev = virXMLPropString(outputDevNode, "dev");
break;
}
case VIR_DOMAIN_AUDIO_TYPE_LAST:
break;
}
return def;
error:
virDomainAudioDefFree(def);
return NULL;
}
static virDomainWatchdogDefPtr
virDomainWatchdogDefParseXML(virDomainXMLOptionPtr xmlopt,
xmlNodePtr node,
@ -17049,6 +17158,10 @@ virDomainDeviceDefParse(const char *xmlStr,
ctxt, flags)))
return NULL;
break;
case VIR_DOMAIN_DEVICE_AUDIO:
if (!(dev->data.audio = virDomainAudioDefParseXML(xmlopt, node, ctxt)))
return NULL;
break;
case VIR_DOMAIN_DEVICE_WATCHDOG:
if (!(dev->data.watchdog = virDomainWatchdogDefParseXML(xmlopt,
node, flags)))
@ -21957,6 +22070,22 @@ virDomainDefParseXML(xmlDocPtr xml,
}
VIR_FREE(nodes);
/* analysis of the audio devices */
if ((n = virXPathNodeSet("./devices/audio", ctxt, &nodes)) < 0)
goto error;
if (n && VIR_ALLOC_N(def->audios, n) < 0)
goto error;
for (i = 0; i < n; i++) {
virDomainAudioDefPtr audio = virDomainAudioDefParseXML(xmlopt,
nodes[i],
ctxt);
if (!audio)
goto error;
def->audios[def->naudios++] = audio;
}
VIR_FREE(nodes);
/* analysis of the video devices */
if ((n = virXPathNodeSet("./devices/video", ctxt, &nodes)) < 0)
goto error;
@ -22471,7 +22600,6 @@ virDomainDefParse(const char *xmlStr,
virDomainDefPtr def = NULL;
int keepBlanksDefault = xmlKeepBlanksDefault(0);
xmlNodePtr root;
if (!(xml = virXMLParse(filename, xmlStr, _("(domain_definition)"))))
goto cleanup;
@ -24572,6 +24700,7 @@ virDomainDefCheckABIStabilityFlags(virDomainDefPtr src,
case VIR_DOMAIN_DEVICE_MEMORY:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_VSOCK:
case VIR_DOMAIN_DEVICE_AUDIO:
break;
}
#endif
@ -27340,6 +27469,9 @@ virDomainSoundDefFormat(virBufferPtr buf,
for (i = 0; i < def->ncodecs; i++)
virDomainSoundCodecDefFormat(&childBuf, def->codecs[i]);
if (def->audioId > 0)
virBufferAsprintf(&childBuf, "<audio id='%d'/>\n", def->audioId);
if (virDomainDeviceInfoFormat(&childBuf, &def->info, flags) < 0)
return -1;
@ -27356,6 +27488,44 @@ virDomainSoundDefFormat(virBufferPtr buf,
}
static int
virDomainAudioDefFormat(virBufferPtr buf,
virDomainAudioDefPtr def)
{
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
const char *type = virDomainAudioTypeTypeToString(def->type);
if (!type) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unexpected audio type %d"), def->type);
return -1;
}
virBufferAsprintf(buf, "<audio id='%d' type='%s'", def->id, type);
switch (def->type) {
case VIR_DOMAIN_AUDIO_TYPE_OSS:
if (def->backend.oss.inputDev)
virBufferAsprintf(&childBuf, "<input dev='%s'/>\n",
def->backend.oss.inputDev);
if (def->backend.oss.outputDev)
virBufferAsprintf(&childBuf, "<output dev='%s'/>\n",
def->backend.oss.outputDev);
break;
}
if (virBufferUse(&childBuf)) {
virBufferAddLit(buf, ">\n");
virBufferAddBuffer(buf, &childBuf);
virBufferAddLit(buf, "</audio>\n");
} else {
virBufferAddLit(buf, "/>\n");
}
return 0;
}
static int
virDomainMemballoonDefFormat(virBufferPtr buf,
virDomainMemballoonDefPtr def,
@ -30035,6 +30205,11 @@ virDomainDefFormatInternalSetRootName(virDomainDefPtr def,
return -1;
}
for (n = 0; n < def->naudios; n++) {
if (virDomainAudioDefFormat(buf, def->audios[n]) < 0)
return -1;
}
for (n = 0; n < def->nvideos; n++) {
if (virDomainVideoDefFormat(buf, def->videos[n], flags) < 0)
return -1;
@ -31207,6 +31382,9 @@ virDomainDeviceDefCopy(virDomainDeviceDefPtr src,
case VIR_DOMAIN_DEVICE_VSOCK:
rc = virDomainVsockDefFormat(&buf, src->data.vsock);
break;
case VIR_DOMAIN_DEVICE_AUDIO:
rc = virDomainAudioDefFormat(&buf, src->data.audio);
break;
case VIR_DOMAIN_DEVICE_NONE:
case VIR_DOMAIN_DEVICE_SMARTCARD:

View File

@ -85,6 +85,7 @@ typedef enum {
VIR_DOMAIN_DEVICE_MEMORY,
VIR_DOMAIN_DEVICE_IOMMU,
VIR_DOMAIN_DEVICE_VSOCK,
VIR_DOMAIN_DEVICE_AUDIO,
VIR_DOMAIN_DEVICE_LAST
} virDomainDeviceType;
@ -116,6 +117,7 @@ struct _virDomainDeviceDef {
virDomainMemoryDefPtr memory;
virDomainIOMMUDefPtr iommu;
virDomainVsockDefPtr vsock;
virDomainAudioDefPtr audio;
} data;
};
@ -1417,6 +1419,27 @@ struct _virDomainSoundDef {
size_t ncodecs;
virDomainSoundCodecDefPtr *codecs;
unsigned int audioId;
};
typedef enum {
VIR_DOMAIN_AUDIO_TYPE_OSS,
VIR_DOMAIN_AUDIO_TYPE_LAST
} virDomainAudioType;
struct _virDomainAudioDef {
int type;
unsigned int id;
union {
struct {
char *inputDev;
char *outputDev;
} oss;
} backend;
};
typedef enum {
@ -2602,6 +2625,9 @@ struct _virDomainDef {
size_t nsounds;
virDomainSoundDefPtr *sounds;
size_t naudios;
virDomainAudioDefPtr *audios;
size_t nvideos;
virDomainVideoDefPtr *videos;
@ -3032,6 +3058,7 @@ ssize_t virDomainSoundDefFind(const virDomainDef *def,
const virDomainSoundDef *sound);
void virDomainSoundDefFree(virDomainSoundDefPtr def);
virDomainSoundDefPtr virDomainSoundDefRemove(virDomainDefPtr def, size_t idx);
void virDomainAudioDefFree(virDomainAudioDefPtr def);
void virDomainMemballoonDefFree(virDomainMemballoonDefPtr def);
void virDomainNVRAMDefFree(virDomainNVRAMDefPtr def);
void virDomainWatchdogDefFree(virDomainWatchdogDefPtr def);
@ -3591,6 +3618,7 @@ VIR_ENUM_DECL(virDomainChrTcpProtocol);
VIR_ENUM_DECL(virDomainChrSpicevmc);
VIR_ENUM_DECL(virDomainSoundCodec);
VIR_ENUM_DECL(virDomainSoundModel);
VIR_ENUM_DECL(virDomainAudioType);
VIR_ENUM_DECL(virDomainKeyWrapCipherName);
VIR_ENUM_DECL(virDomainMemballoonModel);
VIR_ENUM_DECL(virDomainSmbiosMode);

View File

@ -309,6 +309,9 @@ typedef virDomainSoundCodecDef *virDomainSoundCodecDefPtr;
typedef struct _virDomainSoundDef virDomainSoundDef;
typedef virDomainSoundDef *virDomainSoundDefPtr;
typedef struct _virDomainAudioDef virDomainAudioDef;
typedef virDomainAudioDef *virDomainAudioDefPtr;
typedef struct _virDomainTPMDef virDomainTPMDef;
typedef virDomainTPMDef *virDomainTPMDefPtr;

View File

@ -224,6 +224,8 @@ virDiskNameToBusDeviceIndex;
virDiskNameToIndex;
virDomainActualNetDefFree;
virDomainActualNetDefValidate;
virDomainAudioTypeTypeFromString;
virDomainAudioTypeTypeToString;
virDomainBlockedReasonTypeFromString;
virDomainBlockedReasonTypeToString;
virDomainBlockIoTuneInfoCopy;

View File

@ -523,6 +523,7 @@ qemuBuildVirtioDevStr(virBufferPtr buf,
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_MEMORY:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
default:
return 0;

View File

@ -5412,6 +5412,7 @@ qemuDomainDeviceDefPostParse(virDomainDeviceDefPtr dev,
case VIR_DOMAIN_DEVICE_RNG:
case VIR_DOMAIN_DEVICE_MEMORY:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
ret = 0;
break;

View File

@ -531,6 +531,7 @@ qemuDomainDeviceSupportZPCI(virDomainDeviceDefPtr device)
case VIR_DOMAIN_DEVICE_MEMORY:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_VSOCK:
case VIR_DOMAIN_DEVICE_AUDIO:
break;
case VIR_DOMAIN_DEVICE_NONE:
@ -1047,6 +1048,7 @@ qemuDomainDeviceCalculatePCIConnectFlags(virDomainDeviceDefPtr dev,
case VIR_DOMAIN_DEVICE_LEASE:
case VIR_DOMAIN_DEVICE_GRAPHICS:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
case VIR_DOMAIN_DEVICE_NONE:
return 0;

View File

@ -6979,6 +6979,7 @@ qemuDomainAttachDeviceLive(virDomainObjPtr vm,
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("live attach of device '%s' is not supported"),
@ -7113,6 +7114,7 @@ qemuDomainUpdateDeviceLive(virDomainObjPtr vm,
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_VSOCK:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("live update of device '%s' is not supported"),
@ -7332,6 +7334,7 @@ qemuDomainAttachDeviceConfig(virDomainDefPtr vmdef,
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("persistent attach of device '%s' is not supported"),
@ -7534,6 +7537,7 @@ qemuDomainDetachDeviceConfig(virDomainDefPtr vmdef,
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("persistent detach of device '%s' is not supported"),
@ -7641,6 +7645,7 @@ qemuDomainUpdateDeviceConfig(virDomainDefPtr vmdef,
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_VSOCK:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("persistent update of device '%s' is not supported"),

View File

@ -4943,6 +4943,7 @@ qemuDomainRemoveAuditDevice(virDomainObjPtr vm,
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
/* libvirt doesn't yet support detaching these devices */
break;
@ -5041,6 +5042,7 @@ qemuDomainRemoveDevice(virQEMUDriverPtr driver,
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("don't know how to remove a %s device"),
@ -5811,6 +5813,7 @@ qemuDomainDetachDeviceLive(virDomainObjPtr vm,
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("live detach of device '%s' is not supported"),

View File

@ -4175,6 +4175,7 @@ qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev,
case VIR_DOMAIN_DEVICE_LEASE:
case VIR_DOMAIN_DEVICE_SHMEM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_NONE:
case VIR_DOMAIN_DEVICE_LAST:
break;