@@ -3107,6 +3107,8 @@ S: Maintained
F: mcd/*
F: docs/interop/mcd.rst
F: qapi/mcd.json
+F: tests/qtest/libmcd-test.*
+F: tests/qtest/mcd-test.c
Memory API
M: Paolo Bonzini <pbonzini@redhat.com>
new file mode 100644
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QAPI marshalling helpers for structures of the MCD API
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libmcd_qapi.h"
+
+MCDAPIVersion *marshal_mcd_api_version(const mcd_api_version_st *api_version)
+{
+ MCDAPIVersion *marshal = g_malloc0(sizeof(*marshal));
+
+ *marshal = (MCDAPIVersion) {
+ .v_api_major = api_version->v_api_major,
+ .v_api_minor = api_version->v_api_minor,
+ .author = g_strdup(api_version->author),
+ };
+
+ return marshal;
+}
+
+mcd_api_version_st unmarshal_mcd_api_version(MCDAPIVersion *api_version)
+{
+ mcd_api_version_st unmarshal = {
+ .v_api_major = api_version->v_api_major,
+ .v_api_minor = api_version->v_api_minor,
+ };
+ strncpy(unmarshal.author, api_version->author, MCD_API_IMP_VENDOR_LEN - 1);
+ return unmarshal;
+}
+
+MCDImplVersionInfo *marshal_mcd_impl_version_info(
+ const mcd_impl_version_info_st *impl_info)
+{
+ MCDImplVersionInfo *marshal = g_malloc0(sizeof(*marshal));
+
+ *marshal = (MCDImplVersionInfo) {
+ .v_api = marshal_mcd_api_version(&impl_info->v_api),
+ .v_imp_major = impl_info->v_imp_major,
+ .v_imp_minor = impl_info->v_imp_minor,
+ .v_imp_build = impl_info->v_imp_build,
+ .vendor = g_strdup(impl_info->vendor),
+ .date = g_strdup(impl_info->date),
+ };
+
+ return marshal;
+}
+
+MCDErrorInfo *marshal_mcd_error_info(const mcd_error_info_st *error_info)
+{
+ MCDErrorInfo *marshal = g_malloc0(sizeof(*marshal));
+
+ *marshal = (MCDErrorInfo) {
+ .return_status = error_info->return_status,
+ .error_code = error_info->error_code,
+ .error_events = error_info->error_events,
+ .error_str = g_strdup(error_info->error_str),
+ };
+
+ return marshal;
+}
new file mode 100644
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QAPI marshalling helpers for structures of the MCD API
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef LIBMCD_QAPI_H
+#define LIBMCD_QAPI_H
+
+#include "mcd_api.h"
+#include "qapi-types-mcd.h"
+
+MCDAPIVersion *marshal_mcd_api_version(const mcd_api_version_st *api_version);
+
+MCDImplVersionInfo *marshal_mcd_impl_version_info(
+ const mcd_impl_version_info_st *impl_info);
+
+MCDErrorInfo *marshal_mcd_error_info(const mcd_error_info_st *error_info);
+
+mcd_api_version_st unmarshal_mcd_api_version(MCDAPIVersion *api_version);
+
+#endif /* LIBMCD_QAPI_H */
@@ -8,6 +8,7 @@
* See the COPYING file in the top-level directory.
*/
+#include "qemu/osdep.h"
#include "mcd_api.h"
static const mcd_error_info_st MCD_ERROR_NOT_IMPLEMENTED = {
@@ -17,6 +18,13 @@ static const mcd_error_info_st MCD_ERROR_NOT_IMPLEMENTED = {
.error_str = "",
};
+static const mcd_error_info_st MCD_ERROR_INVALID_NULL_PARAM = {
+ .return_status = MCD_RET_ACT_HANDLE_ERROR,
+ .error_code = MCD_ERR_PARAM,
+ .error_events = MCD_ERR_EVT_NONE,
+ .error_str = "null was invalidly passed as a parameter",
+};
+
static const mcd_error_info_st MCD_ERROR_NONE = {
.return_status = MCD_RET_ACT_NONE,
.error_code = MCD_ERR_NONE,
@@ -24,6 +32,9 @@ static const mcd_error_info_st MCD_ERROR_NONE = {
.error_str = "",
};
+/* reserves memory for custom errors */
+static mcd_error_info_st custom_mcd_error;
+
/**
* struct mcdserver_state - State of the MCD server
*
@@ -40,12 +51,43 @@ static mcdserver_state g_server_state = {
mcd_return_et mcd_initialize_f(const mcd_api_version_st *version_req,
mcd_impl_version_info_st *impl_info)
{
- g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+ if (!version_req || !impl_info) {
+ g_server_state.last_error = &MCD_ERROR_INVALID_NULL_PARAM;
+ return g_server_state.last_error->return_status;
+ }
+
+ *impl_info = (mcd_impl_version_info_st) {
+ .v_api = (mcd_api_version_st) {
+ .v_api_major = MCD_API_VER_MAJOR,
+ .v_api_minor = MCD_API_VER_MINOR,
+ .author = MCD_API_VER_AUTHOR,
+ },
+ .v_imp_major = QEMU_VERSION_MAJOR,
+ .v_imp_minor = QEMU_VERSION_MINOR,
+ .v_imp_build = 0,
+ .vendor = "QEMU",
+ .date = __DATE__,
+ };
+
+ if (version_req->v_api_major == MCD_API_VER_MAJOR &&
+ version_req->v_api_minor <= MCD_API_VER_MINOR) {
+ g_server_state.last_error = &MCD_ERROR_NONE;
+ } else {
+ custom_mcd_error = (mcd_error_info_st) {
+ .return_status = MCD_RET_ACT_HANDLE_ERROR,
+ .error_code = MCD_ERR_GENERAL,
+ .error_events = MCD_ERR_EVT_NONE,
+ .error_str = "incompatible versions",
+ };
+ g_server_state.last_error = &custom_mcd_error;
+ }
+
return g_server_state.last_error->return_status;
}
void mcd_exit_f(void)
{
+ g_server_state.last_error = &MCD_ERROR_NONE;
return;
}
@@ -10,4 +10,39 @@
* See the COPYING file in the top-level directory.
*/
+#include "qemu/osdep.h"
#include "mcd_api.h"
+#include "libmcd_qapi.h"
+#include "qapi/qapi-commands-mcd.h"
+
+MCDInitializeResult *qmp_mcd_initialize(MCDAPIVersion *version_req,
+ Error **errp)
+{
+ mcd_impl_version_info_st impl_info;
+ MCDInitializeResult *result = g_malloc0(sizeof(*result));
+ mcd_api_version_st version_req_unmarshalled =
+ unmarshal_mcd_api_version(version_req);
+
+ result->return_status = mcd_initialize_f(&version_req_unmarshalled,
+ &impl_info);
+
+ if (result->return_status == MCD_RET_ACT_NONE) {
+ result->impl_info = marshal_mcd_impl_version_info(&impl_info);
+ }
+
+ return result;
+}
+
+void qmp_mcd_exit(Error **errp)
+{
+ mcd_exit_f();
+}
+
+MCDErrorInfo *qmp_mcd_qry_error_info(Error **errp)
+{
+ MCDErrorInfo *result;
+ mcd_error_info_st error_info;
+ mcd_qry_error_info_f(NULL, &error_info);
+ result = marshal_mcd_error_info(&error_info);
+ return result;
+}
@@ -1,7 +1,7 @@
# MCD is only supported in system emulation
mcd_system_ss = ss.source_set()
-mcd_system_ss.add(files('mcdserver.c', 'mcdstub_qapi.c'))
+mcd_system_ss.add(files('mcdserver.c', 'mcdstub_qapi.c', 'libmcd_qapi.c'))
mcd_system_ss = mcd_system_ss.apply({})
libmcd_system = static_library('mcd_system',
@@ -4,3 +4,186 @@
##
# = Multi-Core Debug (MCD) API
##
+
+
+##
+# == Definition of Structures
+##
+
+
+##
+# @MCDAPIVersion:
+#
+# Structure type containing the MCD API version information of the tool.
+#
+# @v-api-major: API major version.
+# @v-api-minor: API minor version.
+# @author: API name of the author of this MCD API version.
+#
+# Since: 9.1
+##
+{ 'struct': 'MCDAPIVersion',
+ 'data': {
+ 'v-api-major': 'uint16',
+ 'v-api-minor': 'uint16',
+ 'author': 'str' } }
+
+
+##
+# @MCDImplVersionInfo:
+#
+# Structure type containing the MCD API implementation information.
+#
+# @v-api: Implemented API version.
+# @v-imp-major: Major version number of this implementation.
+# @v-imp-minor: Minor version number of this implementation.
+# @v-imp-build: Build number of this implementation.
+# @vendor: Name of vendor of the implementation.
+# @date: String from __DATE__ macro at compile time.
+#
+# Since: 9.1
+##
+{ 'struct': 'MCDImplVersionInfo',
+ 'data': {
+ 'v-api' : 'MCDAPIVersion',
+ 'v-imp-major': 'uint16',
+ 'v-imp-minor': 'uint16',
+ 'v-imp-build': 'uint16',
+ 'vendor' : 'str',
+ 'date' : 'str' } }
+
+
+##
+# @MCDErrorInfo:
+#
+# Structure type containing the error status and error event notification.
+#
+# @return-status: Return status from the last API call.
+# @error-code: Detailed error code from the last API call.
+# @error-events: Detailed event code from the last API call.
+# @error-str: Detailed error text string from the last API call.
+#
+# Since: 9.1
+##
+{ 'struct': 'MCDErrorInfo',
+ 'data': {
+ 'return-status': 'uint32',
+ 'error-code' : 'uint32',
+ 'error-events' : 'uint32',
+ 'error-str' : 'str' }}
+
+
+##
+# == Target Initialization API
+##
+
+
+##
+# @MCDInitializeResult:
+#
+# Return value of @mcd-initialize.
+#
+# @return-status: Return code.
+#
+# @impl-info: Information about the QEMU build, its version and the version of
+# the implemented MCD API.
+#
+# Since: 9.1
+##
+{ 'struct': 'MCDInitializeResult',
+ 'data': {
+ 'return-status': 'uint32',
+ '*impl-info' : 'MCDImplVersionInfo' } }
+
+
+##
+# @mcd-initialize:
+#
+# Function initializing the interaction between a tool-side implementation and
+# target-side implementation.
+#
+# @version-req: MCD API version as requested by an upper layer.
+#
+# Returns: @MCDInitializeResult
+#
+# Since: 9.1
+#
+# .. qmp-example::
+# :title: Check compatibility with MCD server
+#
+# -> { "execute": "mcd-initialize",
+# "arguments": { "version-req": { "v-api-major": 1,
+# "v-api-minor": 1,
+# "author": "" } } }
+# <- {
+# "return": {
+# "impl-info": {
+# "v-api": {
+# "v-api-minor": 1,
+# "v-api-major": 1,
+# "author": "QEMU Release"
+# },
+# "vendor": "QEMU",
+# "v-imp-minor": 2,
+# "v-imp-major": 9,
+# "v-imp-build": 0,
+# "date": "Dec 18 2024"
+# },
+# "return-status": 0
+# }
+# }
+##
+{ 'command': 'mcd-initialize',
+ 'data': { 'version-req': 'MCDAPIVersion' },
+ 'returns': 'MCDInitializeResult' }
+
+
+##
+# @mcd-exit:
+#
+# Function cleaning up all core and server connections from a tool.
+#
+# Since: 9.1
+##
+{ 'command': 'mcd-exit' }
+
+
+##
+# == Core Connection API
+##
+
+
+##
+# @mcd-qry-error-info:
+#
+# Function allowing the access to detailed error and/or event information after
+# an API call.
+#
+# Returns: @MCDErrorInfo
+#
+# Since: 9.1
+#
+# .. qmp-example::
+# :title: Incompatible MCD versions
+#
+# -> { "execute": "mcd-initialize",
+# "arguments": { "version-req": { "v-api-major": 2,
+# "v-api-minor": 0,
+# "author": "" } } }
+# <- {
+# "return": {
+# "return-status": 3
+# }
+# }
+# -> { "execute": "mcd-qry-error-info" }
+# <- {
+# "return": {
+# "error-str": "incompatible versions",
+# "error-code": 3840,
+# "error-events": 0,
+# "return-status": 3
+# }
+# }
+##
+{ 'command': 'mcd-qry-error-info',
+ 'returns': 'MCDErrorInfo' }
new file mode 100644
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QTest helpers for functions of the MCD API
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libmcd-test.h"
+#include "mcd/mcd_api.h"
+#include "mcd/libmcd_qapi.h"
+#include "qobject/qdict.h"
+#include "qobject/qlist.h"
+#include "qobject/qjson.h"
+#include "qapi/qapi-commands-mcd.h"
+#include "qapi/qapi-visit-mcd.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/compat-policy.h"
+
+/*
+ * We can use the %p format specifier of qtest_qmp() to automatically
+ * serialize the arguments into JSON.
+ * The serialization works only after the arguments have been converted into
+ * a QDict.
+ */
+
+ #define MARSHAL_ARGS(type) do { \
+ v = qobject_output_visitor_new_qmp(&marshal); \
+ ok = visit_start_struct(v, NULL, (void **)&args, \
+ sizeof(type), NULL); \
+ g_assert(ok); \
+ ok = visit_type_##type##_members(v, args, NULL); \
+ g_assert(ok); \
+ ok = visit_check_struct(v, NULL); \
+ g_assert(ok); \
+ visit_end_struct(v, (void **)&args); \
+ visit_complete(v, &marshal); \
+ visit_free(v); \
+ arg = qobject_to(QDict, marshal); \
+} while (0)
+
+#define UNMARSHAL_RESULT(type) do { \
+ ret = qdict_get(resp, "return"); \
+ g_assert(ret); \
+ v = qobject_input_visitor_new(ret); \
+ ok = visit_type_##type(v, NULL, &unmarshal, NULL); \
+ g_assert(ok); \
+ visit_free(v); \
+ qobject_unref(resp); \
+} while (0)
+
+MCDInitializeResult *qtest_mcd_initialize(QTestState *qts,
+ q_obj_mcd_initialize_arg *args)
+{
+ Visitor *v;
+ QObject *marshal;
+ QDict *arg, *resp;
+ QObject *ret;
+ bool ok;
+ MCDInitializeResult *unmarshal;
+
+ MARSHAL_ARGS(q_obj_mcd_initialize_arg);
+
+ resp = qtest_qmp(qts, "{'execute': 'mcd-initialize',"
+ "'arguments': %p}", arg);
+
+ UNMARSHAL_RESULT(MCDInitializeResult);
+
+ return unmarshal;
+}
+
+MCDErrorInfo *qtest_mcd_qry_error_info(QTestState *qts)
+{
+ Visitor *v;
+ QDict *resp;
+ QObject *ret;
+ bool ok;
+ MCDErrorInfo *unmarshal;
+
+ resp = qtest_qmp(qts, "{'execute': 'mcd-qry-error-info'}");
+
+ UNMARSHAL_RESULT(MCDErrorInfo);
+
+ return unmarshal;
+}
new file mode 100644
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QTest helpers for functions of the MCD API
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef LIBMCD_TEST_H
+#define LIBMCD_TEST_H
+
+#include "libqtest.h"
+#include "mcd/mcd_api.h"
+#include "qapi/qapi-visit-mcd.h"
+
+MCDInitializeResult *qtest_mcd_initialize(QTestState *qts,
+ q_obj_mcd_initialize_arg *args);
+
+MCDErrorInfo *qtest_mcd_qry_error_info(QTestState *qts);
+
+#endif /* LIBMCD_TEST_H */
new file mode 100644
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Architecture independent QTests for the MCD server with QAPI stub
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "mcd/mcd_api.h"
+#include "mcd/libmcd_qapi.h"
+#include "qapi/qapi-visit-mcd.h"
+#include "qapi/qapi-types-mcd.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/compat-policy.h"
+#include "libmcd-test.h"
+
+#define QEMU_EXTRA_ARGS ""
+
+static bool verbose;
+
+static void test_initialize(void)
+{
+ QTestState *qts = qtest_init(QEMU_EXTRA_ARGS);
+ MCDErrorInfo *error_info;
+
+ mcd_api_version_st version_req = {
+ .v_api_major = MCD_API_VER_MAJOR,
+ .v_api_minor = MCD_API_VER_MINOR,
+ .author = "",
+ };
+
+ q_obj_mcd_initialize_arg qapi_args = {
+ .version_req = marshal_mcd_api_version(&version_req),
+ };
+
+ MCDInitializeResult *result = qtest_mcd_initialize(qts, &qapi_args);
+ g_assert(result->return_status == MCD_RET_ACT_NONE);
+
+ if (verbose) {
+ fprintf(stderr, "[INFO]\tAPI v%d.%d (%s)\n",
+ result->impl_info->v_api->v_api_major,
+ result->impl_info->v_api->v_api_minor,
+ result->impl_info->v_api->author);
+ fprintf(stderr, "[INFO]\tImplementation v%d.%d.%d %s (%s)\n",
+ result->impl_info->v_imp_major,
+ result->impl_info->v_imp_minor,
+ result->impl_info->v_imp_build,
+ result->impl_info->date,
+ result->impl_info->vendor);
+ }
+
+ qapi_free_MCDAPIVersion(qapi_args.version_req);
+ qapi_free_MCDInitializeResult(result);
+
+ /* Incompatible version */
+ version_req = (mcd_api_version_st) {
+ .v_api_major = MCD_API_VER_MAJOR,
+ .v_api_minor = MCD_API_VER_MINOR + 1,
+ .author = "",
+ };
+
+ qapi_args.version_req = marshal_mcd_api_version(&version_req);
+ result = qtest_mcd_initialize(qts, &qapi_args);
+ g_assert(result->return_status != MCD_RET_ACT_NONE);
+
+ error_info = qtest_mcd_qry_error_info(qts);
+ g_assert(error_info->error_code == MCD_ERR_GENERAL);
+
+ if (verbose) {
+ fprintf(stderr, "[INFO]\tInitialization with newer API failed "
+ "successfully: %s\n", error_info->error_str);
+ }
+
+ qapi_free_MCDAPIVersion(qapi_args.version_req);
+ qapi_free_MCDInitializeResult(result);
+ qapi_free_MCDErrorInfo(error_info);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+ char *v_env = getenv("V");
+ verbose = v_env && atoi(v_env) >= 1;
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("mcd/initialize", test_initialize);
+ return g_test_run();
+}
@@ -30,6 +30,7 @@ qtests_generic = [
'qos-test',
'readconfig-test',
'netdev-socket',
+ 'mcd-test',
]
if enable_modules
qtests_generic += [ 'modules-test' ]
@@ -383,6 +384,7 @@ qtests = {
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
'ast2700-smc-test': files('aspeed-smc-utils.c', 'ast2700-smc-test.c'),
+ 'mcd-test': files('mcd-test.c', 'libmcd-test.c', '../../mcd/libmcd_qapi.c'),
}
if vnc.found()
The target initialization API ensures that the requested and provided MCD versions are compatible. * implement mcd_initialize_f and mcd_qry_error_info_f in mcdserver * implement QMP stub functionality * add QTest Thanks to the QMP integration in QTest, function arguments and results can be (de)serialized automatically. Signed-off-by: Mario Fleischmann <mario.fleischmann@lauterbach.com> --- MAINTAINERS | 2 + mcd/libmcd_qapi.c | 66 ++++++++++++++ mcd/libmcd_qapi.h | 26 ++++++ mcd/mcdserver.c | 44 ++++++++- mcd/mcdstub_qapi.c | 35 ++++++++ mcd/meson.build | 2 +- qapi/mcd.json | 183 ++++++++++++++++++++++++++++++++++++++ tests/qtest/libmcd-test.c | 88 ++++++++++++++++++ tests/qtest/libmcd-test.h | 23 +++++ tests/qtest/mcd-test.c | 93 +++++++++++++++++++ tests/qtest/meson.build | 2 + 11 files changed, 562 insertions(+), 2 deletions(-) create mode 100644 mcd/libmcd_qapi.c create mode 100644 mcd/libmcd_qapi.h create mode 100644 tests/qtest/libmcd-test.c create mode 100644 tests/qtest/libmcd-test.h create mode 100644 tests/qtest/mcd-test.c