From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <dev-bounces@dpdk.org>
Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124])
	by inbox.dpdk.org (Postfix) with ESMTP id C75F4A00C2;
	Thu,  8 Dec 2022 20:30:09 +0100 (CET)
Received: from mails.dpdk.org (localhost [127.0.0.1])
	by mails.dpdk.org (Postfix) with ESMTP id 474AE42D34;
	Thu,  8 Dec 2022 20:29:52 +0100 (CET)
Received: from mx0b-0016f401.pphosted.com (mx0a-0016f401.pphosted.com
 [67.231.148.174])
 by mails.dpdk.org (Postfix) with ESMTP id B94834003F
 for <dev@dpdk.org>; Thu,  8 Dec 2022 20:29:47 +0100 (CET)
Received: from pps.filterd (m0045849.ppops.net [127.0.0.1])
 by mx0a-0016f401.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id
 2B8J8Kix001346 for <dev@dpdk.org>; Thu, 8 Dec 2022 11:29:46 -0800
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=marvell.com;
 h=from : to : cc :
 subject : date : message-id : in-reply-to : references : mime-version :
 content-type; s=pfpt0220; bh=YZ3ns0a9Ic9tcaRZnYRT37/UYRrne/Ey8ogVRQ+9hhw=;
 b=fGLo7DaPIlK7Tl02zzi9RJpWRk76wtHWy2t1Tv3gJIjefIKjRxMIMl2Qew0WF9G8++z0
 NkUDA1OO8UAj/dE+FIcboIgziUNDuPuzNyGoPGcDbovcY2cjii0ihlVPK+tyYBR5Ia+F
 XavLIZpXNU4xML6/gd9yZ6y/Mhxtlt8IzVWs8tlw/JlawM1pJi+KVno2pzzQQnegnlZW
 xEHuouESfzaAhQtPNYM/Hzo9zeZGElQC0a8/+E2Y/takuaERP0r3h6I2CQMiAML/Gs3w
 dP3VLGTbVsZ1qkirM6zxg7wJpPb+1z1cpwrsUP4lsrd4i7yrQT9382WOZBl+hHKPJ4Sw cw== 
Received: from dc5-exch02.marvell.com ([199.233.59.182])
 by mx0a-0016f401.pphosted.com (PPS) with ESMTPS id 3mb22svjux-2
 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-SHA384 bits=256 verify=NOT)
 for <dev@dpdk.org>; Thu, 08 Dec 2022 11:29:46 -0800
Received: from DC5-EXCH01.marvell.com (10.69.176.38) by DC5-EXCH02.marvell.com
 (10.69.176.39) with Microsoft SMTP Server (TLS) id 15.0.1497.18;
 Thu, 8 Dec 2022 11:29:44 -0800
Received: from maili.marvell.com (10.69.176.80) by DC5-EXCH01.marvell.com
 (10.69.176.38) with Microsoft SMTP Server id 15.0.1497.2 via Frontend
 Transport; Thu, 8 Dec 2022 11:29:44 -0800
Received: from ml-host-33.caveonetworks.com (unknown [10.110.143.233])
 by maili.marvell.com (Postfix) with ESMTP id A41AB3F706F;
 Thu,  8 Dec 2022 11:29:44 -0800 (PST)
From: Srikanth Yalavarthi <syalavarthi@marvell.com>
To: Srikanth Yalavarthi <syalavarthi@marvell.com>
CC: <dev@dpdk.org>, <sshankarnara@marvell.com>, <jerinj@marvell.com>,
 <aprabhu@marvell.com>
Subject: [PATCH v3 04/12] app/mldev: add test case to validate model ops
Date: Thu, 8 Dec 2022 11:29:10 -0800
Message-ID: <20221208192918.25022-4-syalavarthi@marvell.com>
X-Mailer: git-send-email 2.17.1
In-Reply-To: <20221208192918.25022-1-syalavarthi@marvell.com>
References: <20221129082109.6809-1-syalavarthi@marvell.com>
 <20221208192918.25022-1-syalavarthi@marvell.com>
MIME-Version: 1.0
Content-Type: text/plain
X-Proofpoint-ORIG-GUID: RNIwOUhJzbggpCrthr4tbMcgVyKoXGRP
X-Proofpoint-GUID: RNIwOUhJzbggpCrthr4tbMcgVyKoXGRP
X-Proofpoint-Virus-Version: vendor=baseguard
 engine=ICAP:2.0.205,Aquarius:18.0.923,Hydra:6.0.545,FMLib:17.11.122.1
 definitions=2022-12-08_11,2022-12-08_01,2022-06-22_01
X-BeenThere: dev@dpdk.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: DPDK patches and discussions <dev.dpdk.org>
List-Unsubscribe: <https://mails.dpdk.org/options/dev>,
 <mailto:dev-request@dpdk.org?subject=unsubscribe>
List-Archive: <http://mails.dpdk.org/archives/dev/>
List-Post: <mailto:dev@dpdk.org>
List-Help: <mailto:dev-request@dpdk.org?subject=help>
List-Subscribe: <https://mails.dpdk.org/listinfo/dev>,
 <mailto:dev-request@dpdk.org?subject=subscribe>
Errors-To: dev-bounces@dpdk.org

Added test case to validate model operations. Model ops test
is a collection of sub-tests. Each sub-test invokes the model
operations in a specific order.

Sub-test A: (load -> start -> stop -> unload) x n
Sub-test B: load x n -> start x n -> stop x n -> unload x n
Sub-test C: load x n + (start  + stop) x n + unload x n
Sub-test D: (load + start) x n -> (stop + unload) x n

Added internal functions to handle model load, start, stop and
unload. List of models to be used for testing can be specified
through application argument "--models"

Signed-off-by: Srikanth Yalavarthi <syalavarthi@marvell.com>
---
 app/test-mldev/meson.build         |   2 +
 app/test-mldev/ml_options.c        |  45 ++-
 app/test-mldev/ml_options.h        |   9 +
 app/test-mldev/test_model_common.c | 162 +++++++++++
 app/test-mldev/test_model_common.h |  37 +++
 app/test-mldev/test_model_ops.c    | 433 +++++++++++++++++++++++++++++
 app/test-mldev/test_model_ops.h    |  21 ++
 7 files changed, 706 insertions(+), 3 deletions(-)
 create mode 100644 app/test-mldev/test_model_common.c
 create mode 100644 app/test-mldev/test_model_common.h
 create mode 100644 app/test-mldev/test_model_ops.c
 create mode 100644 app/test-mldev/test_model_ops.h

diff --git a/app/test-mldev/meson.build b/app/test-mldev/meson.build
index 60ea23d142..b09e1ccc8a 100644
--- a/app/test-mldev/meson.build
+++ b/app/test-mldev/meson.build
@@ -14,6 +14,8 @@ sources = files(
         'parser.c',
         'test_common.c',
         'test_device_ops.c',
+        'test_model_common.c',
+        'test_model_ops.c',
 )
 
 deps += ['mldev']
diff --git a/app/test-mldev/ml_options.c b/app/test-mldev/ml_options.c
index 2e5f11bca2..15043c0992 100644
--- a/app/test-mldev/ml_options.c
+++ b/app/test-mldev/ml_options.c
@@ -4,6 +4,7 @@
 
 #include <errno.h>
 #include <getopt.h>
+#include <linux/limits.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -27,6 +28,7 @@ ml_options_default(struct ml_options *opt)
 	strlcpy(opt->test_name, "device_ops", ML_TEST_NAME_MAX_LEN);
 	opt->dev_id = 0;
 	opt->socket_id = SOCKET_ID_ANY;
+	opt->nb_filelist = 0;
 	opt->debug = false;
 }
 
@@ -63,11 +65,47 @@ ml_parse_socket_id(struct ml_options *opt, const char *arg)
 	return 0;
 }
 
+static int
+ml_parse_models(struct ml_options *opt, const char *arg)
+{
+	const char *delim = ",";
+	char models[PATH_MAX];
+	char *token;
+	int ret = 0;
+
+	strlcpy(models, arg, PATH_MAX);
+
+	token = strtok(models, delim);
+	while (token != NULL) {
+		strlcpy(opt->filelist[opt->nb_filelist].model, token, PATH_MAX);
+		opt->nb_filelist++;
+
+		if (opt->nb_filelist >= ML_TEST_MAX_MODELS) {
+			ml_err("Exceeded model count, max = %d\n", ML_TEST_MAX_MODELS);
+			ret = -EINVAL;
+			break;
+		}
+		token = strtok(NULL, delim);
+	}
+
+	if (opt->nb_filelist == 0) {
+		ml_err("Models list is empty. Need at least one model for the test");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
 static void
 ml_dump_test_options(const char *testname)
 {
 	if (strcmp(testname, "device_ops") == 0)
 		printf("\n");
+
+	if (strcmp(testname, "model_ops") == 0) {
+		printf("\t\t--models           : comma separated list of models\n");
+		printf("\n");
+	}
 }
 
 static void
@@ -85,9 +123,9 @@ print_usage(char *program)
 	ml_test_dump_names(ml_dump_test_options);
 }
 
-static struct option lgopts[] = {{ML_TEST, 1, 0, 0},	  {ML_DEVICE_ID, 1, 0, 0},
-				 {ML_SOCKET_ID, 1, 0, 0}, {ML_DEBUG, 0, 0, 0},
-				 {ML_HELP, 0, 0, 0},	  {NULL, 0, 0, 0}};
+static struct option lgopts[] = {
+	{ML_TEST, 1, 0, 0},  {ML_DEVICE_ID, 1, 0, 0}, {ML_SOCKET_ID, 1, 0, 0}, {ML_MODELS, 1, 0, 0},
+	{ML_DEBUG, 0, 0, 0}, {ML_HELP, 0, 0, 0},      {NULL, 0, 0, 0}};
 
 static int
 ml_opts_parse_long(int opt_idx, struct ml_options *opt)
@@ -98,6 +136,7 @@ ml_opts_parse_long(int opt_idx, struct ml_options *opt)
 		{ML_TEST, ml_parse_test_name},
 		{ML_DEVICE_ID, ml_parse_dev_id},
 		{ML_SOCKET_ID, ml_parse_socket_id},
+		{ML_MODELS, ml_parse_models},
 	};
 
 	for (i = 0; i < RTE_DIM(parsermap); i++) {
diff --git a/app/test-mldev/ml_options.h b/app/test-mldev/ml_options.h
index 05311a9a47..8faf3b5deb 100644
--- a/app/test-mldev/ml_options.h
+++ b/app/test-mldev/ml_options.h
@@ -5,22 +5,31 @@
 #ifndef _ML_OPTIONS_
 #define _ML_OPTIONS_
 
+#include <linux/limits.h>
 #include <stdbool.h>
 #include <stdint.h>
 
 #define ML_TEST_NAME_MAX_LEN 32
+#define ML_TEST_MAX_MODELS   8
 
 /* Options names */
 #define ML_TEST	     ("test")
 #define ML_DEVICE_ID ("dev_id")
 #define ML_SOCKET_ID ("socket_id")
+#define ML_MODELS    ("models")
 #define ML_DEBUG     ("debug")
 #define ML_HELP	     ("help")
 
+struct ml_filelist {
+	char model[PATH_MAX];
+};
+
 struct ml_options {
 	char test_name[ML_TEST_NAME_MAX_LEN];
 	int16_t dev_id;
 	int socket_id;
+	struct ml_filelist filelist[ML_TEST_MAX_MODELS];
+	uint8_t nb_filelist;
 	bool debug;
 };
 
diff --git a/app/test-mldev/test_model_common.c b/app/test-mldev/test_model_common.c
new file mode 100644
index 0000000000..5368be17fe
--- /dev/null
+++ b/app/test-mldev/test_model_common.c
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 Marvell.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_mldev.h>
+
+#include "ml_common.h"
+#include "ml_options.h"
+#include "ml_test.h"
+#include "test_common.h"
+#include "test_model_common.h"
+
+int
+ml_model_load(struct ml_test *test, struct ml_options *opt, struct ml_model *model, int16_t fid)
+{
+	struct test_common *t = ml_test_priv(test);
+	struct rte_ml_model_params model_params;
+	FILE *fp;
+	int ret;
+
+	if (model->state == MODEL_LOADED)
+		return 0;
+
+	if (model->state != MODEL_INITIAL)
+		return -EINVAL;
+
+	/* read model binary */
+	fp = fopen(opt->filelist[fid].model, "r");
+	if (fp == NULL) {
+		ml_err("Failed to open model file : %s\n", opt->filelist[fid].model);
+		return -1;
+	}
+
+	fseek(fp, 0, SEEK_END);
+	model_params.size = ftell(fp);
+	fseek(fp, 0, SEEK_SET);
+
+	model_params.addr = rte_malloc_socket("ml_model", model_params.size,
+					      t->dev_info.min_align_size, opt->socket_id);
+	if (model_params.addr == NULL) {
+		ml_err("Failed to allocate memory for model: %s\n", opt->filelist[fid].model);
+		fclose(fp);
+		return -ENOMEM;
+	}
+
+	if (fread(model_params.addr, 1, model_params.size, fp) != model_params.size) {
+		ml_err("Failed to read model file : %s\n", opt->filelist[fid].model);
+		rte_free(model_params.addr);
+		fclose(fp);
+		return -1;
+	}
+	fclose(fp);
+
+	/* load model to device */
+	ret = rte_ml_model_load(opt->dev_id, &model_params, &model->id);
+	if (ret != 0) {
+		ml_err("Failed to load model : %s\n", opt->filelist[fid].model);
+		model->state = MODEL_ERROR;
+		rte_free(model_params.addr);
+		return ret;
+	}
+
+	/* release mz */
+	rte_free(model_params.addr);
+
+	/* get model info */
+	ret = rte_ml_model_info_get(opt->dev_id, model->id, &model->info);
+	if (ret != 0) {
+		ml_err("Failed to get model info : %s\n", opt->filelist[fid].model);
+		return ret;
+	}
+
+	model->state = MODEL_LOADED;
+
+	return 0;
+}
+
+int
+ml_model_unload(struct ml_test *test, struct ml_options *opt, struct ml_model *model, int16_t fid)
+{
+	struct test_common *t = ml_test_priv(test);
+	int ret;
+
+	RTE_SET_USED(t);
+
+	if (model->state == MODEL_INITIAL)
+		return 0;
+
+	if (model->state != MODEL_LOADED)
+		return -EINVAL;
+
+	/* unload model */
+	ret = rte_ml_model_unload(opt->dev_id, model->id);
+	if (ret != 0) {
+		ml_err("Failed to unload model: %s\n", opt->filelist[fid].model);
+		model->state = MODEL_ERROR;
+		return ret;
+	}
+
+	model->state = MODEL_INITIAL;
+
+	return 0;
+}
+
+int
+ml_model_start(struct ml_test *test, struct ml_options *opt, struct ml_model *model, int16_t fid)
+{
+	struct test_common *t = ml_test_priv(test);
+	int ret;
+
+	RTE_SET_USED(t);
+
+	if (model->state == MODEL_STARTED)
+		return 0;
+
+	if (model->state != MODEL_LOADED)
+		return -EINVAL;
+
+	/* start model */
+	ret = rte_ml_model_start(opt->dev_id, model->id);
+	if (ret != 0) {
+		ml_err("Failed to start model : %s\n", opt->filelist[fid].model);
+		model->state = MODEL_ERROR;
+		return ret;
+	}
+
+	model->state = MODEL_STARTED;
+
+	return 0;
+}
+
+int
+ml_model_stop(struct ml_test *test, struct ml_options *opt, struct ml_model *model, int16_t fid)
+{
+	struct test_common *t = ml_test_priv(test);
+	int ret;
+
+	RTE_SET_USED(t);
+
+	if (model->state == MODEL_LOADED)
+		return 0;
+
+	if (model->state != MODEL_STARTED)
+		return -EINVAL;
+
+	/* stop model */
+	ret = rte_ml_model_stop(opt->dev_id, model->id);
+	if (ret != 0) {
+		ml_err("Failed to stop model: %s\n", opt->filelist[fid].model);
+		model->state = MODEL_ERROR;
+		return ret;
+	}
+
+	model->state = MODEL_LOADED;
+
+	return 0;
+}
diff --git a/app/test-mldev/test_model_common.h b/app/test-mldev/test_model_common.h
new file mode 100644
index 0000000000..302e4eb45f
--- /dev/null
+++ b/app/test-mldev/test_model_common.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 Marvell.
+ */
+
+#ifndef _ML_TEST_MODEL_COMMON_
+#define _ML_TEST_MODEL_COMMON_
+
+#include <stdint.h>
+
+#include <rte_mldev.h>
+
+#include "ml_options.h"
+#include "ml_test.h"
+
+enum model_state {
+	MODEL_INITIAL,
+	MODEL_LOADED,
+	MODEL_STARTED,
+	MODEL_ERROR,
+};
+
+struct ml_model {
+	int16_t id;
+	struct rte_ml_model_info info;
+	enum model_state state;
+};
+
+int ml_model_load(struct ml_test *test, struct ml_options *opt, struct ml_model *model,
+		  int16_t fid);
+int ml_model_unload(struct ml_test *test, struct ml_options *opt, struct ml_model *model,
+		    int16_t fid);
+int ml_model_start(struct ml_test *test, struct ml_options *opt, struct ml_model *model,
+		   int16_t fid);
+int ml_model_stop(struct ml_test *test, struct ml_options *opt, struct ml_model *model,
+		  int16_t fid);
+
+#endif /* _ML_TEST_MODEL_COMMON_ */
diff --git a/app/test-mldev/test_model_ops.c b/app/test-mldev/test_model_ops.c
new file mode 100644
index 0000000000..69c9df8ed6
--- /dev/null
+++ b/app/test-mldev/test_model_ops.c
@@ -0,0 +1,433 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 Marvell.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_mldev.h>
+
+#include "ml_common.h"
+#include "ml_options.h"
+#include "ml_test.h"
+#include "test_model_ops.h"
+
+static bool
+test_model_ops_cap_check(struct ml_options *opt)
+{
+	if (!ml_test_cap_check(opt))
+		return false;
+
+	return true;
+}
+
+static int
+test_model_ops_opt_check(struct ml_options *opt)
+{
+	uint32_t i;
+	int ret;
+
+	/* check common opts */
+	ret = ml_test_opt_check(opt);
+	if (ret != 0)
+		return ret;
+
+	/* check model file availability */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		if (access(opt->filelist[i].model, F_OK) == -1) {
+			ml_err("Model file not available: id = %u, file = %s", i,
+			       opt->filelist[i].model);
+			return -ENOENT;
+		}
+	}
+
+	return 0;
+}
+
+static void
+test_model_ops_opt_dump(struct ml_options *opt)
+{
+	uint32_t i;
+
+	/* dump common opts */
+	ml_test_opt_dump(opt);
+
+	/* dump test specific opts */
+	ml_dump_begin("models");
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_dump_list("model", i, opt->filelist[i].model);
+	ml_dump_end;
+}
+
+static int
+test_model_ops_setup(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+	void *test_model_ops;
+	int ret = 0;
+	uint32_t i;
+
+	/* allocate model ops test structure */
+	test_model_ops = rte_zmalloc_socket(test->name, sizeof(struct test_model_ops),
+					    RTE_CACHE_LINE_SIZE, opt->socket_id);
+	if (test_model_ops == NULL) {
+		ml_err("Failed to allocate memory for test_model");
+		ret = -ENOMEM;
+		goto error;
+	}
+	test->test_priv = test_model_ops;
+	t = ml_test_priv(test);
+
+	t->cmn.result = ML_TEST_FAILED;
+	t->cmn.opt = opt;
+
+	/* get device info */
+	ret = rte_ml_dev_info_get(opt->dev_id, &t->cmn.dev_info);
+	if (ret < 0) {
+		ml_err("Failed to get device info");
+		goto error;
+	}
+
+	/* set model initial state */
+	for (i = 0; i < opt->nb_filelist; i++)
+		t->model[i].state = MODEL_INITIAL;
+
+	return 0;
+
+error:
+	if (test_model_ops != NULL)
+		rte_free(test_model_ops);
+
+	return ret;
+}
+
+static void
+test_model_ops_destroy(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+
+	RTE_SET_USED(opt);
+
+	t = ml_test_priv(test);
+	if (t != NULL)
+		rte_free(t);
+}
+
+static int
+test_model_ops_mldev_setup(struct ml_test *test, struct ml_options *opt)
+{
+	int ret;
+
+	ret = ml_test_device_configure(test, opt);
+	if (ret != 0)
+		return ret;
+
+	ret = ml_test_device_start(test, opt);
+	if (ret != 0)
+		goto error;
+
+	return 0;
+
+error:
+	ml_test_device_close(test, opt);
+
+	return ret;
+}
+
+static int
+test_model_ops_mldev_destroy(struct ml_test *test, struct ml_options *opt)
+{
+	int ret;
+
+	ret = ml_test_device_stop(test, opt);
+	if (ret != 0)
+		goto error;
+
+	ret = ml_test_device_close(test, opt);
+	if (ret != 0)
+		return ret;
+
+	return 0;
+
+error:
+	ml_test_device_close(test, opt);
+
+	return ret;
+}
+
+/* Sub-test A: (load -> start -> stop -> unload) x n */
+static int
+test_model_ops_subtest_a(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+	int ret = 0;
+	uint32_t i;
+
+	t = ml_test_priv(test);
+
+	/* load + start + stop + unload */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_load(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+
+		ret = ml_model_start(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+
+		ret = ml_model_stop(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+
+		ret = ml_model_unload(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+error:
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_stop(test, opt, &t->model[i], i);
+
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_unload(test, opt, &t->model[i], i);
+
+	return ret;
+}
+
+/* Sub-test B: load x n -> start x n -> stop x n -> unload x n */
+static int
+test_model_ops_subtest_b(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+	int ret = 0;
+	uint32_t i;
+
+	t = ml_test_priv(test);
+
+	/* load */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_load(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	/* start */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_start(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	/* stop */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_stop(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	/* unload */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_unload(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_stop(test, opt, &t->model[i], i);
+
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_unload(test, opt, &t->model[i], i);
+
+	return ret;
+}
+
+/* Sub-test C: load x n + (start  + stop) x n + unload x n */
+static int
+test_model_ops_subtest_c(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+	int ret = 0;
+	uint32_t i;
+
+	t = ml_test_priv(test);
+
+	/* load */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_load(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	/* start + stop */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_start(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+
+		ret = ml_model_stop(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	/* unload */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_unload(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_stop(test, opt, &t->model[i], i);
+
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_unload(test, opt, &t->model[i], i);
+
+	return ret;
+}
+
+/* Sub-test D: (load + start) x n -> (stop + unload) x n */
+static int
+test_model_ops_subtest_d(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+	int ret = 0;
+	uint32_t i;
+
+	t = ml_test_priv(test);
+
+	/* load + start */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_load(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+
+		ret = ml_model_start(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	/* stop + unload */
+	for (i = 0; i < opt->nb_filelist; i++) {
+		ret = ml_model_stop(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+
+		ret = ml_model_unload(test, opt, &t->model[i], i);
+		if (ret != 0)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_stop(test, opt, &t->model[i], i);
+
+	for (i = 0; i < opt->nb_filelist; i++)
+		ml_model_unload(test, opt, &t->model[i], i);
+
+	return ret;
+}
+
+static int
+test_model_ops_driver(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+	int ret = 0;
+
+	t = ml_test_priv(test);
+
+	/* device setup */
+	ret = test_model_ops_mldev_setup(test, opt);
+	if (ret != 0)
+		return ret;
+
+	printf("\n");
+
+	/* sub-test A */
+	ret = test_model_ops_subtest_a(test, opt);
+	if (ret != 0) {
+		printf("Model Ops Sub-test A: " CLRED "%s" CLNRM "\n", "Failed");
+		goto error;
+	} else {
+		printf("Model Ops Sub-test A: " CLYEL "%s" CLNRM "\n", "Passed");
+	}
+
+	/* sub-test B */
+	ret = test_model_ops_subtest_b(test, opt);
+	if (ret != 0) {
+		printf("Model Ops Sub-test B: " CLRED "%s" CLNRM "\n", "Failed");
+		goto error;
+	} else {
+		printf("Model Ops Sub-test B: " CLYEL "%s" CLNRM "\n", "Passed");
+	}
+
+	/* sub-test C */
+	ret = test_model_ops_subtest_c(test, opt);
+	if (ret != 0) {
+		printf("Model Ops Sub-test C: " CLRED "%s" CLNRM "\n", "Failed");
+		goto error;
+	} else {
+		printf("Model Ops Sub-test C: " CLYEL "%s" CLNRM "\n", "Passed");
+	}
+
+	/* sub-test D */
+	ret = test_model_ops_subtest_d(test, opt);
+	if (ret != 0) {
+		printf("Model Ops Sub-test D: " CLRED "%s" CLNRM "\n", "Failed");
+		goto error;
+	} else {
+		printf("Model Ops Sub-test D: " CLYEL "%s" CLNRM "\n", "Passed");
+	}
+
+	printf("\n");
+
+	/* device destroy */
+	ret = test_model_ops_mldev_destroy(test, opt);
+	if (ret != 0)
+		return ret;
+
+	t->cmn.result = ML_TEST_SUCCESS;
+
+	return 0;
+
+error:
+	test_model_ops_mldev_destroy(test, opt);
+
+	t->cmn.result = ML_TEST_FAILED;
+
+	return ret;
+}
+
+static int
+test_model_ops_result(struct ml_test *test, struct ml_options *opt)
+{
+	struct test_model_ops *t;
+
+	RTE_SET_USED(opt);
+
+	t = ml_test_priv(test);
+
+	return t->cmn.result;
+}
+
+static const struct ml_test_ops model_ops = {
+	.cap_check = test_model_ops_cap_check,
+	.opt_check = test_model_ops_opt_check,
+	.opt_dump = test_model_ops_opt_dump,
+	.test_setup = test_model_ops_setup,
+	.test_destroy = test_model_ops_destroy,
+	.test_driver = test_model_ops_driver,
+	.test_result = test_model_ops_result,
+};
+
+ML_TEST_REGISTER(model_ops);
diff --git a/app/test-mldev/test_model_ops.h b/app/test-mldev/test_model_ops.h
new file mode 100644
index 0000000000..9dd8402390
--- /dev/null
+++ b/app/test-mldev/test_model_ops.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 Marvell.
+ */
+
+#ifndef _ML_TEST_MODEL_OPS_
+#define _ML_TEST_MODEL_OPS_
+
+#include <rte_common.h>
+
+#include "test_common.h"
+#include "test_model_common.h"
+
+struct test_model_ops {
+	/* common data */
+	struct test_common cmn;
+
+	/* test specific data */
+	struct ml_model model[ML_TEST_MAX_MODELS];
+} __rte_cache_aligned;
+
+#endif /* _ML_TEST_MODEL_OPS_ */
-- 
2.17.1