2019-09-23 10:44:33 +00:00
|
|
|
/*
|
|
|
|
* qemu_vhost_user.c: QEMU vhost-user
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include "qemu_vhost_user.h"
|
|
|
|
#include "qemu_interop_config.h"
|
|
|
|
#include "virjson.h"
|
|
|
|
#include "virlog.h"
|
|
|
|
#include "virstring.h"
|
|
|
|
#include "viralloc.h"
|
|
|
|
#include "virenum.h"
|
2020-02-16 21:59:28 +00:00
|
|
|
#include "virutil.h"
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
|
|
|
VIR_LOG_INIT("qemu.qemu_vhost_user");
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
QEMU_VHOST_USER_TYPE_NONE = 0,
|
|
|
|
QEMU_VHOST_USER_TYPE_9P,
|
|
|
|
QEMU_VHOST_USER_TYPE_BALLOON,
|
|
|
|
QEMU_VHOST_USER_TYPE_BLOCK,
|
|
|
|
QEMU_VHOST_USER_TYPE_CAIF,
|
|
|
|
QEMU_VHOST_USER_TYPE_CONSOLE,
|
|
|
|
QEMU_VHOST_USER_TYPE_CRYPTO,
|
|
|
|
QEMU_VHOST_USER_TYPE_GPU,
|
|
|
|
QEMU_VHOST_USER_TYPE_INPUT,
|
|
|
|
QEMU_VHOST_USER_TYPE_NET,
|
|
|
|
QEMU_VHOST_USER_TYPE_RNG,
|
|
|
|
QEMU_VHOST_USER_TYPE_RPMSG,
|
|
|
|
QEMU_VHOST_USER_TYPE_RPROC_SERIAL,
|
|
|
|
QEMU_VHOST_USER_TYPE_SCSI,
|
|
|
|
QEMU_VHOST_USER_TYPE_VSOCK,
|
|
|
|
QEMU_VHOST_USER_TYPE_FS,
|
|
|
|
|
|
|
|
QEMU_VHOST_USER_TYPE_LAST
|
|
|
|
} qemuVhostUserType;
|
|
|
|
|
|
|
|
VIR_ENUM_DECL(qemuVhostUserType);
|
|
|
|
VIR_ENUM_IMPL(qemuVhostUserType,
|
|
|
|
QEMU_VHOST_USER_TYPE_LAST,
|
|
|
|
"",
|
|
|
|
"9p",
|
|
|
|
"balloon",
|
|
|
|
"block",
|
|
|
|
"caif",
|
|
|
|
"console",
|
|
|
|
"crypto",
|
|
|
|
"gpu",
|
|
|
|
"input",
|
|
|
|
"net",
|
|
|
|
"rng",
|
|
|
|
"rpmsg",
|
|
|
|
"rproc-serial",
|
|
|
|
"scsi",
|
|
|
|
"vsock",
|
|
|
|
"fs",
|
|
|
|
);
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_NONE = 0,
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_VIRGL,
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_RENDER_NODE,
|
|
|
|
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_LAST
|
|
|
|
} qemuVhostUserGPUFeature;
|
|
|
|
|
|
|
|
VIR_ENUM_DECL(qemuVhostUserGPUFeature);
|
|
|
|
VIR_ENUM_IMPL(qemuVhostUserGPUFeature,
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_LAST,
|
|
|
|
"",
|
|
|
|
"virgl",
|
|
|
|
"render-node",
|
|
|
|
);
|
|
|
|
|
|
|
|
typedef struct _qemuVhostUserGPU qemuVhostUserGPU;
|
|
|
|
typedef qemuVhostUserGPU *qemuVhostUserGPUPtr;
|
|
|
|
struct _qemuVhostUserGPU {
|
|
|
|
size_t nfeatures;
|
|
|
|
qemuVhostUserGPUFeature *features;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _qemuVhostUser {
|
|
|
|
/* Description intentionally not parsed. */
|
|
|
|
qemuVhostUserType type;
|
|
|
|
char *binary;
|
|
|
|
/* Tags intentionally not parsed. */
|
|
|
|
|
|
|
|
union {
|
|
|
|
qemuVhostUserGPU gpu;
|
|
|
|
} capabilities;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuVhostUserGPUFeatureFree(qemuVhostUserGPUFeature *features)
|
|
|
|
{
|
2021-02-03 19:36:01 +00:00
|
|
|
g_free(features);
|
2019-09-23 10:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-15 12:47:50 +00:00
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuVhostUserGPUFeature, qemuVhostUserGPUFeatureFree);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
qemuVhostUserFree(qemuVhostUserPtr vu)
|
|
|
|
{
|
|
|
|
if (!vu)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (vu->type == QEMU_VHOST_USER_TYPE_GPU)
|
2021-02-03 19:36:01 +00:00
|
|
|
g_free(vu->capabilities.gpu.features);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
2021-02-03 19:36:01 +00:00
|
|
|
g_free(vu->binary);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
2021-02-03 19:36:01 +00:00
|
|
|
g_free(vu);
|
2019-09-23 10:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 1MiB should be enough for everybody (TM) */
|
|
|
|
#define DOCUMENT_SIZE (1024 * 1024)
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuVhostUserTypeParse(const char *path,
|
|
|
|
virJSONValuePtr doc,
|
|
|
|
qemuVhostUserPtr vu)
|
|
|
|
{
|
|
|
|
const char *type = virJSONValueObjectGetString(doc, "type");
|
|
|
|
int tmp;
|
|
|
|
|
|
|
|
VIR_DEBUG("vhost-user description path '%s' type : %s",
|
|
|
|
path, type);
|
|
|
|
|
|
|
|
if ((tmp = qemuVhostUserTypeTypeFromString(type)) <= 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unknown vhost-user type: '%s'"),
|
|
|
|
type);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vu->type = tmp;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuVhostUserBinaryParse(const char *path,
|
|
|
|
virJSONValuePtr doc,
|
|
|
|
qemuVhostUserPtr vu)
|
|
|
|
{
|
|
|
|
const char *binary = virJSONValueObjectGetString(doc, "binary");
|
|
|
|
|
|
|
|
VIR_DEBUG("vhost-user description path '%s' binary : %s",
|
|
|
|
path, binary);
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
vu->binary = g_strdup(binary);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
qemuVhostUserPtr
|
|
|
|
qemuVhostUserParse(const char *path)
|
|
|
|
{
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *cont = NULL;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) doc = NULL;
|
|
|
|
g_autoptr(qemuVhostUser) vu = NULL;
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
if (virFileReadAll(path, DOCUMENT_SIZE, &cont) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!(doc = virJSONValueFromString(cont))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unable to parse json file '%s'"),
|
|
|
|
path);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-10-05 10:28:26 +00:00
|
|
|
vu = g_new0(qemuVhostUser, 1);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
if (qemuVhostUserTypeParse(path, doc, vu) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (qemuVhostUserBinaryParse(path, doc, vu) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
2019-10-17 08:10:10 +00:00
|
|
|
return g_steal_pointer(&vu);
|
2019-09-23 10:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
qemuVhostUserFormat(qemuVhostUserPtr vu)
|
|
|
|
{
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) doc = NULL;
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
if (!vu)
|
|
|
|
return NULL;
|
|
|
|
|
2020-03-04 09:04:33 +00:00
|
|
|
doc = virJSONValueNewObject();
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
if (virJSONValueObjectAppendString(doc, "type",
|
|
|
|
qemuVhostUserTypeTypeToString(vu->type)) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (virJSONValueObjectAppendString(doc, "binary", vu->binary) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return virJSONValueToString(doc, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuVhostUserFetchConfigs(char ***configs,
|
|
|
|
bool privileged)
|
|
|
|
{
|
|
|
|
return qemuInteropFetchConfigs("vhost-user", configs, privileged);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
qemuVhostUserFetchParsedConfigs(bool privileged,
|
|
|
|
qemuVhostUserPtr **vhostuserRet,
|
|
|
|
char ***pathsRet)
|
|
|
|
{
|
2020-12-01 08:21:32 +00:00
|
|
|
g_auto(GStrv) paths = NULL;
|
2019-09-23 10:44:33 +00:00
|
|
|
size_t npaths;
|
|
|
|
qemuVhostUserPtr *vus = NULL;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (qemuVhostUserFetchConfigs(&paths, privileged) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
npaths = virStringListLength((const char **)paths);
|
|
|
|
|
2020-10-05 10:28:26 +00:00
|
|
|
vus = g_new0(qemuVhostUserPtr, npaths);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
for (i = 0; i < npaths; i++) {
|
|
|
|
if (!(vus[i] = qemuVhostUserParse(paths[i])))
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2019-10-16 11:43:18 +00:00
|
|
|
*vhostuserRet = g_steal_pointer(&vus);
|
2019-09-23 10:44:33 +00:00
|
|
|
if (pathsRet)
|
2019-10-16 11:43:18 +00:00
|
|
|
*pathsRet = g_steal_pointer(&paths);
|
2019-09-23 10:44:33 +00:00
|
|
|
return npaths;
|
|
|
|
|
|
|
|
error:
|
|
|
|
while (i > 0)
|
|
|
|
qemuVhostUserFree(vus[--i]);
|
|
|
|
VIR_FREE(vus);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuVhostUserGPUFillCapabilities(qemuVhostUserPtr vu,
|
|
|
|
virJSONValuePtr doc)
|
|
|
|
{
|
|
|
|
qemuVhostUserGPUPtr gpu = &vu->capabilities.gpu;
|
|
|
|
virJSONValuePtr featuresJSON;
|
|
|
|
size_t nfeatures;
|
|
|
|
size_t i;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(qemuVhostUserGPUFeature) features = NULL;
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
if (!(featuresJSON = virJSONValueObjectGetArray(doc, "features"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("failed to get features from '%s'"),
|
|
|
|
vu->binary);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
nfeatures = virJSONValueArraySize(featuresJSON);
|
2020-10-05 10:28:26 +00:00
|
|
|
features = g_new0(qemuVhostUserGPUFeature, nfeatures);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
for (i = 0; i < nfeatures; i++) {
|
|
|
|
virJSONValuePtr item = virJSONValueArrayGet(featuresJSON, i);
|
|
|
|
const char *tmpStr = virJSONValueGetString(item);
|
|
|
|
int tmp;
|
|
|
|
|
|
|
|
if ((tmp = qemuVhostUserGPUFeatureTypeFromString(tmpStr)) <= 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unknown feature %s"),
|
|
|
|
tmpStr);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
features[i] = tmp;
|
|
|
|
}
|
|
|
|
|
2019-10-16 11:43:18 +00:00
|
|
|
gpu->features = g_steal_pointer(&features);
|
2019-09-23 10:44:33 +00:00
|
|
|
gpu->nfeatures = nfeatures;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
qemuVhostUserGPUHasFeature(qemuVhostUserGPUPtr gpu,
|
|
|
|
qemuVhostUserGPUFeature feature)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < gpu->nfeatures; i++) {
|
|
|
|
if (gpu->features[i] == feature)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuVhostUserFillDomainGPU(virQEMUDriverPtr driver,
|
|
|
|
virDomainVideoDefPtr video)
|
|
|
|
{
|
|
|
|
qemuVhostUserPtr *vus = NULL;
|
|
|
|
qemuVhostUserPtr vu = NULL;
|
|
|
|
ssize_t nvus = 0;
|
|
|
|
ssize_t i;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
if ((nvus = qemuVhostUserFetchParsedConfigs(driver->privileged,
|
|
|
|
&vus, NULL)) < 0)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
for (i = 0; i < nvus; i++) {
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) doc = NULL;
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *output = NULL;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virCommand) cmd = NULL;
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
vu = vus[i];
|
|
|
|
if (vu->type != QEMU_VHOST_USER_TYPE_GPU)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
cmd = virCommandNewArgList(vu->binary, "--print-capabilities", NULL);
|
|
|
|
virCommandSetOutputBuffer(cmd, &output);
|
|
|
|
if (virCommandRun(cmd, NULL) < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!(doc = virJSONValueFromString(output))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unable to parse json capabilities '%s'"),
|
|
|
|
vu->binary);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qemuVhostUserGPUFillCapabilities(vu, doc) < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (video->accel) {
|
|
|
|
if (video->accel->accel3d &&
|
|
|
|
!qemuVhostUserGPUHasFeature(&vu->capabilities.gpu,
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_VIRGL))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (video->accel->rendernode &&
|
|
|
|
!qemuVhostUserGPUHasFeature(&vu->capabilities.gpu,
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_RENDER_NODE))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-05 10:28:26 +00:00
|
|
|
if (!video->driver)
|
|
|
|
video->driver = g_new0(virDomainVideoDriverDef, 1);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
VIR_FREE(video->driver->vhost_user_binary);
|
2019-10-20 11:49:46 +00:00
|
|
|
video->driver->vhost_user_binary = g_strdup(vu->binary);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == nvus) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
|
|
_("Unable to find a satisfying vhost-user-gpu"));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2020-10-05 10:28:26 +00:00
|
|
|
if (!video->accel)
|
|
|
|
video->accel = g_new0(virDomainVideoAccelDef, 1);
|
2019-09-23 10:44:33 +00:00
|
|
|
|
|
|
|
if (!video->accel->rendernode &&
|
|
|
|
qemuVhostUserGPUHasFeature(&vu->capabilities.gpu,
|
|
|
|
QEMU_VHOST_USER_GPU_FEATURE_RENDER_NODE)) {
|
|
|
|
video->accel->rendernode = virHostGetDRMRenderNode();
|
|
|
|
if (!video->accel->rendernode)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
for (i = 0; i < nvus; i++)
|
|
|
|
qemuVhostUserFree(vus[i]);
|
|
|
|
VIR_FREE(vus);
|
|
|
|
return ret;
|
|
|
|
}
|
2020-01-28 14:38:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuVhostUserFillDomainFS(virQEMUDriverPtr driver,
|
|
|
|
virDomainFSDefPtr fs)
|
|
|
|
{
|
|
|
|
qemuVhostUserPtr *vus = NULL;
|
|
|
|
ssize_t nvus = 0;
|
|
|
|
ssize_t i;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
if ((nvus = qemuVhostUserFetchParsedConfigs(driver->privileged,
|
|
|
|
&vus, NULL)) < 0)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
for (i = 0; i < nvus; i++) {
|
|
|
|
qemuVhostUserPtr vu = vus[i];
|
|
|
|
|
|
|
|
if (vu->type != QEMU_VHOST_USER_TYPE_FS)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
fs->binary = g_strdup(vu->binary);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == nvus) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
|
|
_("Unable to find a satisfying virtiofsd"));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
for (i = 0; i < nvus; i++)
|
|
|
|
qemuVhostUserFree(vus[i]);
|
|
|
|
g_free(vus);
|
|
|
|
return ret;
|
|
|
|
}
|