[v3,3/3] test/dma: add SG copy tests

Message ID 7c61824f1ed1e720c974d1baadfa7c55578c0c4f.1699025423.git.gmuthukrishn@marvell.com (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series test/dma: add vchan reconfig and SG tests |

Checks

Context Check Description
ci/checkpatch success coding style OK
ci/loongarch-compilation success Compilation OK
ci/loongarch-unit-testing success Unit Testing PASS
ci/Intel-compilation success Compilation OK
ci/intel-Testing success Testing PASS
ci/github-robot: build success github build: passed
ci/intel-Functional success Functional PASS
ci/iol-mellanox-Performance success Performance Testing PASS
ci/iol-broadcom-Performance success Performance Testing PASS
ci/iol-intel-Functional success Functional Testing PASS
ci/iol-compile-amd64-testing success Testing PASS
ci/iol-intel-Performance success Performance Testing PASS
ci/iol-broadcom-Functional success Functional Testing PASS
ci/iol-unit-amd64-testing success Testing PASS
ci/iol-unit-arm64-testing success Testing PASS
ci/iol-sample-apps-testing success Testing PASS
ci/iol-compile-arm64-testing success Testing PASS

Commit Message

Gowrishankar Muthukrishnan Nov. 3, 2023, 3:38 p.m. UTC
  Add scatter-gather copy tests.

Signed-off-by: Vidya Sagar Velumuri <vvelumuri@marvell.com>
Signed-off-by: Gowrishankar Muthukrishnan <gmuthukrishn@marvell.com>
---
 app/test/test_dmadev.c     | 132 +++++++++++++++++++++++++++++-
 app/test/test_dmadev_api.c | 163 ++++++++++++++++++++++++++++++++++---
 app/test/test_dmadev_api.h |   2 +
 3 files changed, 283 insertions(+), 14 deletions(-)
  

Comments

fengchengwen Nov. 6, 2023, 6:43 a.m. UTC | #1
Hi Gowrishankar,

On 2023/11/3 23:38, Gowrishankar Muthukrishnan wrote:
> Add scatter-gather copy tests.
> 
> Signed-off-by: Vidya Sagar Velumuri <vvelumuri@marvell.com>
> Signed-off-by: Gowrishankar Muthukrishnan <gmuthukrishn@marvell.com>
> ---
>  app/test/test_dmadev.c     | 132 +++++++++++++++++++++++++++++-
>  app/test/test_dmadev_api.c | 163 ++++++++++++++++++++++++++++++++++---
>  app/test/test_dmadev_api.h |   2 +
>  3 files changed, 283 insertions(+), 14 deletions(-)
> 
> diff --git a/app/test/test_dmadev.c b/app/test/test_dmadev.c
> index 780941fc1e..a2f3a7f999 100644
> --- a/app/test/test_dmadev.c
> +++ b/app/test/test_dmadev.c
> @@ -19,7 +19,7 @@
>  #define ERR_RETURN(...) do { print_err(__func__, __LINE__, __VA_ARGS__); return -1; } while (0)
>  
>  #define TEST_RINGSIZE 512
> -#define COPY_LEN 1024
> +#define COPY_LEN 1032

The test MAX_SG_NUM is limit 4, so it could be 1/2/3/4 segment, and 1032 can both div 1/2/3/4, but 1024 couldn't

I think this is why change 1024->1032.
Suggest add some comment about it.

>  
>  static struct rte_mempool *pool;
>  static int16_t test_dev_id;
> @@ -396,6 +396,120 @@ test_stop_start(int16_t dev_id, uint16_t vchan)
>  	return 0;
>  }
>  
> +static int
> +test_enqueue_sg_copies(int16_t dev_id, uint16_t vchan)
> +{
> +	unsigned int src_len, dst_len, n_sge, len, i, j, k;
> +	char orig_src[COPY_LEN], orig_dst[COPY_LEN];
> +	struct rte_dma_info info = { 0 };
> +	enum rte_dma_status_code status;
> +	uint16_t id, n_src, n_dst;
> +
> +	if (rte_dma_info_get(dev_id, &info) < 0)
> +		ERR_RETURN("Failed to get dev info");
> +
> +	n_sge = RTE_MIN(info.max_sges, TEST_SG_MAX);
> +	len = COPY_LEN;
> +
> +	for (n_src = 1; n_src <= n_sge; n_src++) {
> +		src_len = len / n_src;
> +		for (n_dst = 1; n_dst <= n_sge; n_dst++) {
> +			dst_len = len / n_dst;

If the len % [1~n_dst] not zero, how to process it ?

I see, it was ensured by adjust copy_len value. Suggest add comments for it.

Also suggest extra above to one function.

> +
> +			struct rte_dma_sge sg_src[n_sge], sg_dst[n_sge];
> +			struct rte_mbuf *src[n_sge], *dst[n_sge];
> +			char *src_data[n_sge], *dst_data[n_sge];
> +
> +			for (i = 0 ; i < COPY_LEN; i++)
> +				orig_src[i] = rte_rand() & 0xFF;
> +
> +			memset(orig_dst, 0, COPY_LEN);
> +
> +			for (i = 0; i < n_src; i++) {
> +				src[i] = rte_pktmbuf_alloc(pool);
> +				RTE_ASSERT(src[i] != NULL);
> +				sg_src[i].addr = rte_pktmbuf_iova(src[i]);
> +				sg_src[i].length = src_len;
> +				src_data[i] = rte_pktmbuf_mtod(src[i], char *);
> +			}
> +
> +			for (k = 0; k < n_dst; k++) {
> +				dst[k] = rte_pktmbuf_alloc(pool);
> +				RTE_ASSERT(dst[k] != NULL);
> +				sg_dst[k].addr = rte_pktmbuf_iova(dst[k]);
> +				sg_dst[k].length = dst_len;
> +				dst_data[k] = rte_pktmbuf_mtod(dst[k], char *);
> +			}
> +
> +			for (i = 0; i < n_src; i++) {
> +				for (j = 0; j < src_len; j++)
> +					src_data[i][j] = orig_src[i * src_len + j];
> +			}
> +
> +			for (k = 0; k < n_dst; k++)
> +				memset(dst_data[k], 0, dst_len);
> +
> +			printf("\tsrc segs: %2d [seg len: %4d] - dst segs: %2d [seg len : %4d]\n",
> +				n_src, src_len, n_dst, dst_len);
> +
> +			id = rte_dma_copy_sg(dev_id, vchan, sg_src, sg_dst, n_src, n_dst,
> +					     RTE_DMA_OP_FLAG_SUBMIT);
> +
> +			if (id != id_count)
> +				ERR_RETURN("Error with rte_dma_copy_sg, got %u, expected %u\n",
> +					id, id_count);
> +
> +			/* Give time for copy to finish, then check it was done */
> +			await_hw(dev_id, vchan);
> +
> +			for (k = 0; k < n_dst; k++)
> +				memcpy((&orig_dst[0] + k * dst_len), dst_data[k], dst_len);
> +
> +			if (memcmp(orig_src, orig_dst, COPY_LEN))
> +				ERR_RETURN("Data mismatch");
> +
> +			/* Verify completion */
> +			id = ~id;
> +			if (rte_dma_completed(dev_id, vchan, 1, &id, NULL) != 1)
> +				ERR_RETURN("Error with rte_dma_completed\n");
> +
> +			/* Verify expected index(id_count) */
> +			if (id != id_count)
> +				ERR_RETURN("Error:incorrect job id received, %u [expected %u]\n",
> +						id, id_count);
> +
> +			/* Check for completed and id when no job done */
> +			id = ~id;
> +			if (rte_dma_completed(dev_id, vchan, 1, &id, NULL) != 0)
> +				ERR_RETURN("Error with rte_dma_completed when no job done\n");
> +
> +			if (id != id_count)
> +				ERR_RETURN("Error:incorrect job id received when no job done, %u [expected %u]\n",
> +					   id, id_count);
> +
> +			/* Check for completed_status and id when no job done */
> +			id = ~id;
> +			if (rte_dma_completed_status(dev_id, vchan, 1, &id, &status) != 0)
> +				ERR_RETURN("Error with rte_dma_completed_status when no job done\n");
> +			if (id != id_count)
> +				ERR_RETURN("Error:incorrect job id received when no job done, %u [expected %u]\n",
> +						id, 0);
> +
> +			for (i = 0; i < n_src; i++)
> +				rte_pktmbuf_free(src[i]);
> +			for (i = 0; i < n_dst; i++)
> +				rte_pktmbuf_free(dst[i]);
> +
> +			/* Verify that completion returns nothing more */
> +			if (rte_dma_completed(dev_id, 0, 1, NULL, NULL) != 0)
> +				ERR_RETURN("Error with rte_dma_completed in empty check\n");

already verify this, no need do more

> +
> +			id_count++;
> +		}
> +	}
> +	return 0;
> +}
> +
>  /* Failure handling test cases - global macros and variables for those tests*/
>  #define COMP_BURST_SZ	16
>  #define OPT_FENCE(idx) ((fence && idx == 8) ? RTE_DMA_OP_FLAG_FENCE : 0)
> @@ -1003,7 +1117,7 @@ test_dmadev_setup(void)
>  			TEST_RINGSIZE * 2, /* n == num elements */
>  			32,  /* cache size */
>  			0,   /* priv size */
> -			2048, /* data room size */
> +			COPY_LEN + RTE_PKTMBUF_HEADROOM, /* data room size */
>  			info.numa_node);
>  	if (pool == NULL)
>  		ERR_RETURN("Error with mempool creation\n");
> @@ -1026,6 +1140,7 @@ test_dmadev_instance(int16_t dev_id)
>  #define CHECK_ERRS    true
>  	enum {
>  		  TEST_COPY = 0,
> +		  TEST_COPY_SG,
>  		  TEST_START,
>  		  TEST_BURST,
>  		  TEST_ERR,
> @@ -1060,6 +1175,13 @@ test_dmadev_instance(int16_t dev_id)
>  	param[TEST_COPY].vchan = vchan;
>  	param[TEST_COPY].check_err_stats = CHECK_ERRS;
>  
> +	param[TEST_COPY_SG].printable = "copy";

should be SG copy.

> +	param[TEST_COPY_SG].test_fn = test_enqueue_sg_copies;
> +	param[TEST_COPY_SG].iterations = 1;
> +	param[TEST_COPY_SG].dev_id = dev_id;
> +	param[TEST_COPY_SG].vchan = vchan;
> +	param[TEST_COPY_SG].check_err_stats = CHECK_ERRS;
> +
>  	param[TEST_START].printable = "stop-start";
>  	param[TEST_START].test_fn = test_stop_start;
>  	param[TEST_START].iterations = 1;
> @@ -1122,6 +1244,12 @@ test_dmadev_instance(int16_t dev_id)
>  	/* run tests stopping/starting devices and check jobs still work after restart */
>  	ts->unit_test_cases[TEST_START].enabled = 1;
>  
> +	/* run SG test cases */
> +	if ((dev_info.dev_capa & RTE_DMA_CAPA_OPS_COPY_SG) == 0)
> +		printf("DMA Dev %u: No SG support, skipping SG copy tests\n", dev_id);
> +	else
> +		ts->unit_test_cases[TEST_COPY_SG].enabled = 1;

suggest wrap it as test_dmadev_sg_copy_setup, just like test_dmadev_burst_setup

> +
>  	/* run some burst capacity tests */
>  	ts->unit_test_cases[TEST_BURST].setup = test_dmadev_burst_setup;
>  	ts->unit_test_cases[TEST_BURST].enabled = 1;
> diff --git a/app/test/test_dmadev_api.c b/app/test/test_dmadev_api.c
> index aa07d2b359..37e43c9336 100644
> --- a/app/test/test_dmadev_api.c
> +++ b/app/test/test_dmadev_api.c
> @@ -10,6 +10,7 @@
>  #include <rte_dmadev.h>
>  
>  #include "test.h"
> +#include "test_dmadev_api.h"
>  
>  extern int test_dma_api(uint16_t dev_id);
>  
> @@ -21,36 +22,62 @@ static int16_t invalid_dev_id;
>  
>  static char *src;
>  static char *dst;
> +static char *src_sg[TEST_SG_MAX];
> +static char *dst_sg[TEST_SG_MAX];
>  
>  static int
>  testsuite_setup(void)
>  {
>  	invalid_dev_id = -1;
> -
> -	src = rte_malloc("dmadev_test_src", TEST_MEMCPY_SIZE, 0);
> -	if (src == NULL)
> -		return -ENOMEM;
> -	dst = rte_malloc("dmadev_test_dst", TEST_MEMCPY_SIZE, 0);
> -	if (dst == NULL) {
> -		rte_free(src);
> -		src = NULL;
> -		return -ENOMEM;
> +	int i, rc = 0;
> +
> +	for (i = 0; i < TEST_SG_MAX; i++) {
> +		src_sg[i] = rte_malloc("dmadev_test_src", TEST_MEMCPY_SIZE, 0);
> +		if (src_sg[i] == NULL) {
> +			rc = -ENOMEM;
> +			goto exit;
> +		}
> +
> +		dst_sg[i] = rte_malloc("dmadev_test_dst", TEST_MEMCPY_SIZE, 0);
> +		if (dst_sg[i] == NULL) {
> +			rte_free(src_sg[i]);
> +			src_sg[i] = NULL;
> +			rc = -ENOMEM;
> +			goto exit;
> +		}
>  	}
>  
> +	src = src_sg[0];
> +	dst = dst_sg[0];
> +
>  	/* Set dmadev log level to critical to suppress unnecessary output
>  	 * during API tests.
>  	 */
>  	rte_log_set_level_pattern("lib.dmadev", RTE_LOG_CRIT);
>  
> -	return 0;
> +	return rc;
> +exit:
> +	while (--i >= 0) {
> +		rte_free(src_sg[i]);
> +		rte_free(dst_sg[i]);
> +	}
> +
> +	return rc;
>  }
>  
>  static void
>  testsuite_teardown(void)
>  {
> -	rte_free(src);
> +	int i;
> +
> +	for (i = 0; i < TEST_SG_MAX; i++) {
> +		rte_free(src_sg[i]);
> +		src_sg[i] = NULL;
> +		rte_free(dst_sg[i]);
> +		dst_sg[i] = NULL;
> +	}
> +
>  	src = NULL;
> -	rte_free(dst);
>  	dst = NULL;
>  	/* Ensure the dmadev is stopped. */
>  	rte_dma_stop(test_dev_id);
> @@ -437,6 +464,37 @@ verify_memory(void)
>  	return 0;
>  }
>  
> +static void
> +sg_memory_setup(int n)
> +{
> +	int i, j;
> +
> +	for (i = 0; i < n; i++) {
> +		for (j = 0; j < TEST_MEMCPY_SIZE; j++)
> +			src_sg[i][j] = (char)j;
> +
> +		memset(dst_sg[i], 0, TEST_MEMCPY_SIZE);
> +	}
> +}
> +
> +static int
> +sg_memory_verify(int n)
> +{
> +	int i, j;
> +
> +	for (i = 0; i < n; i++) {
> +		for (j = 0; j < TEST_MEMCPY_SIZE; j++) {
> +			if (src_sg[i][j] == dst_sg[i][j])
> +				continue;
> +
> +			RTE_TEST_ASSERT_EQUAL(src_sg[i][j], dst_sg[i][j], "Failed to copy memory, %d %d",
> +				src_sg[i][j], dst_sg[i][j]);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
>  static int
>  test_dma_completed(void)
>  {
> @@ -551,6 +609,86 @@ test_dma_completed_status(void)
>  	return TEST_SUCCESS;
>  }
>  
> +static int
> +test_dma_sg(void)
> +{
> +	struct rte_dma_sge src_sge[TEST_SG_MAX], dst_sge[TEST_SG_MAX];
> +	struct rte_dma_info dev_info = { 0 };
> +	uint16_t last_idx = -1;
> +	bool has_error = true;
> +	int n_sge, i, ret;
> +	uint16_t cpl_ret;
> +
> +	ret = rte_dma_info_get(test_dev_id, &dev_info);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to obtain device info, %d", ret);
> +
> +	if ((dev_info.dev_capa & RTE_DMA_CAPA_OPS_COPY_SG) == 0)
> +		return TEST_SKIPPED;
> +
> +	n_sge = RTE_MIN(dev_info.max_sges, TEST_SG_MAX);
> +
> +	ret = setup_vchan(1);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to setup one vchan, %d", ret);
> +
> +	ret = rte_dma_start(test_dev_id);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to start, %d", ret);
> +
> +	for (i = 0; i < n_sge; i++) {
> +		src_sge[i].addr = rte_malloc_virt2iova(src_sg[i]);
> +		src_sge[i].length = TEST_MEMCPY_SIZE;
> +		dst_sge[i].addr = rte_malloc_virt2iova(dst_sg[i]);
> +		dst_sge[i].length = TEST_MEMCPY_SIZE;
> +	}
> +
> +	sg_memory_setup(n_sge);
> +
> +	/* Check enqueue without submit */
> +	ret = rte_dma_copy_sg(test_dev_id, 0, src_sge, dst_sge, n_sge, n_sge, 0);
> +	RTE_TEST_ASSERT_EQUAL(ret, 0, "Failed to enqueue copy, %d", ret);
> +
> +	rte_delay_us_sleep(TEST_WAIT_US_VAL);
> +
> +	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
> +	RTE_TEST_ASSERT_EQUAL(cpl_ret, 0, "Failed to get completed");
> +
> +	/* Check DMA submit */
> +	ret = rte_dma_submit(test_dev_id, 0);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to submit, %d", ret);
> +
> +	rte_delay_us_sleep(TEST_WAIT_US_VAL);
> +
> +	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
> +	RTE_TEST_ASSERT_EQUAL(cpl_ret, 1, "Failed to get completed");
> +	RTE_TEST_ASSERT_EQUAL(last_idx, 0, "Last idx should be zero, %u", last_idx);
> +	RTE_TEST_ASSERT_EQUAL(has_error, false, "Should have no error");
> +
> +	ret = sg_memory_verify(n_sge);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to verify memory");
> +
> +	sg_memory_setup(n_sge);
> +
> +	/* Check for enqueue with submit */
> +	ret = rte_dma_copy_sg(test_dev_id, 0, src_sge, dst_sge, n_sge, n_sge,
> +			      RTE_DMA_OP_FLAG_SUBMIT);
> +	RTE_TEST_ASSERT_EQUAL(ret, 1, "Failed to enqueue copy, %d", ret);
> +
> +	rte_delay_us_sleep(TEST_WAIT_US_VAL);
> +
> +	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
> +	RTE_TEST_ASSERT_EQUAL(cpl_ret, 1, "Failed to get completed");
> +	RTE_TEST_ASSERT_EQUAL(last_idx, 1, "Last idx should be 1, %u", last_idx);
> +	RTE_TEST_ASSERT_EQUAL(has_error, false, "Should have no error");
> +
> +	ret = sg_memory_verify(n_sge);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to verify memory");
> +
> +	/* Stop dmadev to make sure dmadev to a known state */
> +	ret = rte_dma_stop(test_dev_id);
> +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to stop, %d", ret);
> +
> +	return TEST_SUCCESS;
> +}
> +
>  static struct unit_test_suite dma_api_testsuite = {
>  	.suite_name = "DMA API Test Suite",
>  	.setup = testsuite_setup,
> @@ -568,6 +706,7 @@ static struct unit_test_suite dma_api_testsuite = {
>  		TEST_CASE(test_dma_dump),
>  		TEST_CASE(test_dma_completed),
>  		TEST_CASE(test_dma_completed_status),
> +		TEST_CASE(test_dma_sg),
>  		TEST_CASES_END()
>  	}
>  };
> diff --git a/app/test/test_dmadev_api.h b/app/test/test_dmadev_api.h
> index 33fbc5bd41..10ab925f80 100644
> --- a/app/test/test_dmadev_api.h
> +++ b/app/test/test_dmadev_api.h
> @@ -2,4 +2,6 @@
>   * Copyright(c) 2021 HiSilicon Limited
>   */
>  
> +#define TEST_SG_MAX 4

suggest TEST_MAX_SGES which corresponding dev_info.max_sges

Thanks
Chengwen

> +
>  int test_dma_api(uint16_t dev_id);
>
  
Gowrishankar Muthukrishnan Nov. 13, 2023, 9:27 a.m. UTC | #2
Hi Fengchengwen,

> 
> On 2023/11/3 23:38, Gowrishankar Muthukrishnan wrote:
> > Add scatter-gather copy tests.
> >
> > Signed-off-by: Vidya Sagar Velumuri <vvelumuri@marvell.com>
> > Signed-off-by: Gowrishankar Muthukrishnan <gmuthukrishn@marvell.com>
> > ---
> >  app/test/test_dmadev.c     | 132 +++++++++++++++++++++++++++++-
> >  app/test/test_dmadev_api.c | 163 ++++++++++++++++++++++++++++++++++-
> --
> >  app/test/test_dmadev_api.h |   2 +
> >  3 files changed, 283 insertions(+), 14 deletions(-)
> >
> > diff --git a/app/test/test_dmadev.c b/app/test/test_dmadev.c index
> > 780941fc1e..a2f3a7f999 100644
> > --- a/app/test/test_dmadev.c
> > +++ b/app/test/test_dmadev.c
> > @@ -19,7 +19,7 @@
> >  #define ERR_RETURN(...) do { print_err(__func__, __LINE__,
> > __VA_ARGS__); return -1; } while (0)
> >
> >  #define TEST_RINGSIZE 512
> > -#define COPY_LEN 1024
> > +#define COPY_LEN 1032
> 
> The test MAX_SG_NUM is limit 4, so it could be 1/2/3/4 segment, and 1032 can
> both div 1/2/3/4, but 1024 couldn't
> 
> I think this is why change 1024->1032.
> Suggest add some comment about it.
>

This change is not required after normalization as done in next patch.
 
> >
> >  static struct rte_mempool *pool;
> >  static int16_t test_dev_id;
> > @@ -396,6 +396,120 @@ test_stop_start(int16_t dev_id, uint16_t vchan)
> >  	return 0;
> >  }
> >
> > +static int
> > +test_enqueue_sg_copies(int16_t dev_id, uint16_t vchan) {
> > +	unsigned int src_len, dst_len, n_sge, len, i, j, k;
> > +	char orig_src[COPY_LEN], orig_dst[COPY_LEN];
> > +	struct rte_dma_info info = { 0 };
> > +	enum rte_dma_status_code status;
> > +	uint16_t id, n_src, n_dst;
> > +
> > +	if (rte_dma_info_get(dev_id, &info) < 0)
> > +		ERR_RETURN("Failed to get dev info");
> > +
> > +	n_sge = RTE_MIN(info.max_sges, TEST_SG_MAX);
> > +	len = COPY_LEN;
> > +
> > +	for (n_src = 1; n_src <= n_sge; n_src++) {
> > +		src_len = len / n_src;
> > +		for (n_dst = 1; n_dst <= n_sge; n_dst++) {
> > +			dst_len = len / n_dst;
> 
> If the len % [1~n_dst] not zero, how to process it ?
> 
> I see, it was ensured by adjust copy_len value. Suggest add comments for it.
> 
> Also suggest extra above to one function.

I have added minor fix here to normalize buffer lengths. Please check in next patch version.
> 
> > +
> > +			struct rte_dma_sge sg_src[n_sge], sg_dst[n_sge];
> > +			struct rte_mbuf *src[n_sge], *dst[n_sge];
> > +			char *src_data[n_sge], *dst_data[n_sge];
> > +
> > +			for (i = 0 ; i < COPY_LEN; i++)
> > +				orig_src[i] = rte_rand() & 0xFF;
> > +
> > +			memset(orig_dst, 0, COPY_LEN);
> > +
> > +			for (i = 0; i < n_src; i++) {
> > +				src[i] = rte_pktmbuf_alloc(pool);
> > +				RTE_ASSERT(src[i] != NULL);
> > +				sg_src[i].addr = rte_pktmbuf_iova(src[i]);
> > +				sg_src[i].length = src_len;
> > +				src_data[i] = rte_pktmbuf_mtod(src[i], char *);
> > +			}
> > +
> > +			for (k = 0; k < n_dst; k++) {
> > +				dst[k] = rte_pktmbuf_alloc(pool);
> > +				RTE_ASSERT(dst[k] != NULL);
> > +				sg_dst[k].addr = rte_pktmbuf_iova(dst[k]);
> > +				sg_dst[k].length = dst_len;
> > +				dst_data[k] = rte_pktmbuf_mtod(dst[k], char
> *);
> > +			}
> > +
> > +			for (i = 0; i < n_src; i++) {
> > +				for (j = 0; j < src_len; j++)
> > +					src_data[i][j] = orig_src[i * src_len + j];
> > +			}
> > +
> > +			for (k = 0; k < n_dst; k++)
> > +				memset(dst_data[k], 0, dst_len);
> > +
> > +			printf("\tsrc segs: %2d [seg len: %4d] - dst segs: %2d
> [seg len : %4d]\n",
> > +				n_src, src_len, n_dst, dst_len);
> > +
> > +			id = rte_dma_copy_sg(dev_id, vchan, sg_src, sg_dst,
> n_src, n_dst,
> > +					     RTE_DMA_OP_FLAG_SUBMIT);
> > +
> > +			if (id != id_count)
> > +				ERR_RETURN("Error with rte_dma_copy_sg,
> got %u, expected %u\n",
> > +					id, id_count);
> > +
> > +			/* Give time for copy to finish, then check it was done
> */
> > +			await_hw(dev_id, vchan);
> > +
> > +			for (k = 0; k < n_dst; k++)
> > +				memcpy((&orig_dst[0] + k * dst_len),
> dst_data[k], dst_len);
> > +
> > +			if (memcmp(orig_src, orig_dst, COPY_LEN))
> > +				ERR_RETURN("Data mismatch");
> > +
> > +			/* Verify completion */
> > +			id = ~id;
> > +			if (rte_dma_completed(dev_id, vchan, 1, &id, NULL) !=
> 1)
> > +				ERR_RETURN("Error with
> rte_dma_completed\n");
> > +
> > +			/* Verify expected index(id_count) */
> > +			if (id != id_count)
> > +				ERR_RETURN("Error:incorrect job id received,
> %u [expected %u]\n",
> > +						id, id_count);
> > +
> > +			/* Check for completed and id when no job done */
> > +			id = ~id;
> > +			if (rte_dma_completed(dev_id, vchan, 1, &id, NULL) !=
> 0)
> > +				ERR_RETURN("Error with rte_dma_completed
> when no job done\n");
> > +
> > +			if (id != id_count)
> > +				ERR_RETURN("Error:incorrect job id received
> when no job done, %u [expected %u]\n",
> > +					   id, id_count);
> > +
> > +			/* Check for completed_status and id when no job done
> */
> > +			id = ~id;
> > +			if (rte_dma_completed_status(dev_id, vchan, 1, &id,
> &status) != 0)
> > +				ERR_RETURN("Error with
> rte_dma_completed_status when no job done\n");
> > +			if (id != id_count)
> > +				ERR_RETURN("Error:incorrect job id received
> when no job done, %u [expected %u]\n",
> > +						id, 0);
> > +
> > +			for (i = 0; i < n_src; i++)
> > +				rte_pktmbuf_free(src[i]);
> > +			for (i = 0; i < n_dst; i++)
> > +				rte_pktmbuf_free(dst[i]);
> > +
> > +			/* Verify that completion returns nothing more */
> > +			if (rte_dma_completed(dev_id, 0, 1, NULL, NULL) != 0)
> > +				ERR_RETURN("Error with rte_dma_completed
> in empty check\n");
> 
> already verify this, no need do more

But this is against empty param check (as it is also done in plain copy test).

> 
> > +
> > +			id_count++;
> > +		}
> > +	}
> > +	return 0;
> > +}
> > +
> >  /* Failure handling test cases - global macros and variables for those tests*/
> >  #define COMP_BURST_SZ	16
> >  #define OPT_FENCE(idx) ((fence && idx == 8) ? RTE_DMA_OP_FLAG_FENCE :
> > 0) @@ -1003,7 +1117,7 @@ test_dmadev_setup(void)
> >  			TEST_RINGSIZE * 2, /* n == num elements */
> >  			32,  /* cache size */
> >  			0,   /* priv size */
> > -			2048, /* data room size */
> > +			COPY_LEN + RTE_PKTMBUF_HEADROOM, /* data
> room size */
> >  			info.numa_node);
> >  	if (pool == NULL)
> >  		ERR_RETURN("Error with mempool creation\n"); @@ -1026,6
> +1140,7 @@
> > test_dmadev_instance(int16_t dev_id)
> >  #define CHECK_ERRS    true
> >  	enum {
> >  		  TEST_COPY = 0,
> > +		  TEST_COPY_SG,
> >  		  TEST_START,
> >  		  TEST_BURST,
> >  		  TEST_ERR,
> > @@ -1060,6 +1175,13 @@ test_dmadev_instance(int16_t dev_id)
> >  	param[TEST_COPY].vchan = vchan;
> >  	param[TEST_COPY].check_err_stats = CHECK_ERRS;
> >
> > +	param[TEST_COPY_SG].printable = "copy";
> 
> should be SG copy.

Ack.
> 
> > +	param[TEST_COPY_SG].test_fn = test_enqueue_sg_copies;
> > +	param[TEST_COPY_SG].iterations = 1;
> > +	param[TEST_COPY_SG].dev_id = dev_id;
> > +	param[TEST_COPY_SG].vchan = vchan;
> > +	param[TEST_COPY_SG].check_err_stats = CHECK_ERRS;
> > +
> >  	param[TEST_START].printable = "stop-start";
> >  	param[TEST_START].test_fn = test_stop_start;
> >  	param[TEST_START].iterations = 1;
> > @@ -1122,6 +1244,12 @@ test_dmadev_instance(int16_t dev_id)
> >  	/* run tests stopping/starting devices and check jobs still work after
> restart */
> >  	ts->unit_test_cases[TEST_START].enabled = 1;
> >
> > +	/* run SG test cases */
> > +	if ((dev_info.dev_capa & RTE_DMA_CAPA_OPS_COPY_SG) == 0)
> > +		printf("DMA Dev %u: No SG support, skipping SG copy tests\n",
> dev_id);
> > +	else
> > +		ts->unit_test_cases[TEST_COPY_SG].enabled = 1;
> 
> suggest wrap it as test_dmadev_sg_copy_setup, just like
> test_dmadev_burst_setup
> 
> > +
> >  	/* run some burst capacity tests */
> >  	ts->unit_test_cases[TEST_BURST].setup = test_dmadev_burst_setup;
> >  	ts->unit_test_cases[TEST_BURST].enabled = 1; diff --git
> > a/app/test/test_dmadev_api.c b/app/test/test_dmadev_api.c index
> > aa07d2b359..37e43c9336 100644
> > --- a/app/test/test_dmadev_api.c
> > +++ b/app/test/test_dmadev_api.c
> > @@ -10,6 +10,7 @@
> >  #include <rte_dmadev.h>
> >
> >  #include "test.h"
> > +#include "test_dmadev_api.h"
> >
> >  extern int test_dma_api(uint16_t dev_id);
> >
> > @@ -21,36 +22,62 @@ static int16_t invalid_dev_id;
> >
> >  static char *src;
> >  static char *dst;
> > +static char *src_sg[TEST_SG_MAX];
> > +static char *dst_sg[TEST_SG_MAX];
> >
> >  static int
> >  testsuite_setup(void)
> >  {
> >  	invalid_dev_id = -1;
> > -
> > -	src = rte_malloc("dmadev_test_src", TEST_MEMCPY_SIZE, 0);
> > -	if (src == NULL)
> > -		return -ENOMEM;
> > -	dst = rte_malloc("dmadev_test_dst", TEST_MEMCPY_SIZE, 0);
> > -	if (dst == NULL) {
> > -		rte_free(src);
> > -		src = NULL;
> > -		return -ENOMEM;
> > +	int i, rc = 0;
> > +
> > +	for (i = 0; i < TEST_SG_MAX; i++) {
> > +		src_sg[i] = rte_malloc("dmadev_test_src", TEST_MEMCPY_SIZE,
> 0);
> > +		if (src_sg[i] == NULL) {
> > +			rc = -ENOMEM;
> > +			goto exit;
> > +		}
> > +
> > +		dst_sg[i] = rte_malloc("dmadev_test_dst", TEST_MEMCPY_SIZE,
> 0);
> > +		if (dst_sg[i] == NULL) {
> > +			rte_free(src_sg[i]);
> > +			src_sg[i] = NULL;
> > +			rc = -ENOMEM;
> > +			goto exit;
> > +		}
> >  	}
> >
> > +	src = src_sg[0];
> > +	dst = dst_sg[0];
> > +
> >  	/* Set dmadev log level to critical to suppress unnecessary output
> >  	 * during API tests.
> >  	 */
> >  	rte_log_set_level_pattern("lib.dmadev", RTE_LOG_CRIT);
> >
> > -	return 0;
> > +	return rc;
> > +exit:
> > +	while (--i >= 0) {
> > +		rte_free(src_sg[i]);
> > +		rte_free(dst_sg[i]);
> > +	}
> > +
> > +	return rc;
> >  }
> >
> >  static void
> >  testsuite_teardown(void)
> >  {
> > -	rte_free(src);
> > +	int i;
> > +
> > +	for (i = 0; i < TEST_SG_MAX; i++) {
> > +		rte_free(src_sg[i]);
> > +		src_sg[i] = NULL;
> > +		rte_free(dst_sg[i]);
> > +		dst_sg[i] = NULL;
> > +	}
> > +
> >  	src = NULL;
> > -	rte_free(dst);
> >  	dst = NULL;
> >  	/* Ensure the dmadev is stopped. */
> >  	rte_dma_stop(test_dev_id);
> > @@ -437,6 +464,37 @@ verify_memory(void)
> >  	return 0;
> >  }
> >
> > +static void
> > +sg_memory_setup(int n)
> > +{
> > +	int i, j;
> > +
> > +	for (i = 0; i < n; i++) {
> > +		for (j = 0; j < TEST_MEMCPY_SIZE; j++)
> > +			src_sg[i][j] = (char)j;
> > +
> > +		memset(dst_sg[i], 0, TEST_MEMCPY_SIZE);
> > +	}
> > +}
> > +
> > +static int
> > +sg_memory_verify(int n)
> > +{
> > +	int i, j;
> > +
> > +	for (i = 0; i < n; i++) {
> > +		for (j = 0; j < TEST_MEMCPY_SIZE; j++) {
> > +			if (src_sg[i][j] == dst_sg[i][j])
> > +				continue;
> > +
> > +			RTE_TEST_ASSERT_EQUAL(src_sg[i][j], dst_sg[i][j],
> "Failed to copy memory, %d %d",
> > +				src_sg[i][j], dst_sg[i][j]);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> >  static int
> >  test_dma_completed(void)
> >  {
> > @@ -551,6 +609,86 @@ test_dma_completed_status(void)
> >  	return TEST_SUCCESS;
> >  }
> >
> > +static int
> > +test_dma_sg(void)
> > +{
> > +	struct rte_dma_sge src_sge[TEST_SG_MAX], dst_sge[TEST_SG_MAX];
> > +	struct rte_dma_info dev_info = { 0 };
> > +	uint16_t last_idx = -1;
> > +	bool has_error = true;
> > +	int n_sge, i, ret;
> > +	uint16_t cpl_ret;
> > +
> > +	ret = rte_dma_info_get(test_dev_id, &dev_info);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to obtain device info, %d",
> > +ret);
> > +
> > +	if ((dev_info.dev_capa & RTE_DMA_CAPA_OPS_COPY_SG) == 0)
> > +		return TEST_SKIPPED;
> > +
> > +	n_sge = RTE_MIN(dev_info.max_sges, TEST_SG_MAX);
> > +
> > +	ret = setup_vchan(1);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to setup one vchan, %d", ret);
> > +
> > +	ret = rte_dma_start(test_dev_id);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to start, %d", ret);
> > +
> > +	for (i = 0; i < n_sge; i++) {
> > +		src_sge[i].addr = rte_malloc_virt2iova(src_sg[i]);
> > +		src_sge[i].length = TEST_MEMCPY_SIZE;
> > +		dst_sge[i].addr = rte_malloc_virt2iova(dst_sg[i]);
> > +		dst_sge[i].length = TEST_MEMCPY_SIZE;
> > +	}
> > +
> > +	sg_memory_setup(n_sge);
> > +
> > +	/* Check enqueue without submit */
> > +	ret = rte_dma_copy_sg(test_dev_id, 0, src_sge, dst_sge, n_sge, n_sge,
> 0);
> > +	RTE_TEST_ASSERT_EQUAL(ret, 0, "Failed to enqueue copy, %d", ret);
> > +
> > +	rte_delay_us_sleep(TEST_WAIT_US_VAL);
> > +
> > +	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
> > +	RTE_TEST_ASSERT_EQUAL(cpl_ret, 0, "Failed to get completed");
> > +
> > +	/* Check DMA submit */
> > +	ret = rte_dma_submit(test_dev_id, 0);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to submit, %d", ret);
> > +
> > +	rte_delay_us_sleep(TEST_WAIT_US_VAL);
> > +
> > +	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
> > +	RTE_TEST_ASSERT_EQUAL(cpl_ret, 1, "Failed to get completed");
> > +	RTE_TEST_ASSERT_EQUAL(last_idx, 0, "Last idx should be zero, %u",
> last_idx);
> > +	RTE_TEST_ASSERT_EQUAL(has_error, false, "Should have no error");
> > +
> > +	ret = sg_memory_verify(n_sge);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to verify memory");
> > +
> > +	sg_memory_setup(n_sge);
> > +
> > +	/* Check for enqueue with submit */
> > +	ret = rte_dma_copy_sg(test_dev_id, 0, src_sge, dst_sge, n_sge, n_sge,
> > +			      RTE_DMA_OP_FLAG_SUBMIT);
> > +	RTE_TEST_ASSERT_EQUAL(ret, 1, "Failed to enqueue copy, %d", ret);
> > +
> > +	rte_delay_us_sleep(TEST_WAIT_US_VAL);
> > +
> > +	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
> > +	RTE_TEST_ASSERT_EQUAL(cpl_ret, 1, "Failed to get completed");
> > +	RTE_TEST_ASSERT_EQUAL(last_idx, 1, "Last idx should be 1, %u",
> last_idx);
> > +	RTE_TEST_ASSERT_EQUAL(has_error, false, "Should have no error");
> > +
> > +	ret = sg_memory_verify(n_sge);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to verify memory");
> > +
> > +	/* Stop dmadev to make sure dmadev to a known state */
> > +	ret = rte_dma_stop(test_dev_id);
> > +	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to stop, %d", ret);
> > +
> > +	return TEST_SUCCESS;
> > +}
> > +
> >  static struct unit_test_suite dma_api_testsuite = {
> >  	.suite_name = "DMA API Test Suite",
> >  	.setup = testsuite_setup,
> > @@ -568,6 +706,7 @@ static struct unit_test_suite dma_api_testsuite = {
> >  		TEST_CASE(test_dma_dump),
> >  		TEST_CASE(test_dma_completed),
> >  		TEST_CASE(test_dma_completed_status),
> > +		TEST_CASE(test_dma_sg),
> >  		TEST_CASES_END()
> >  	}
> >  };
> > diff --git a/app/test/test_dmadev_api.h b/app/test/test_dmadev_api.h
> > index 33fbc5bd41..10ab925f80 100644
> > --- a/app/test/test_dmadev_api.h
> > +++ b/app/test/test_dmadev_api.h
> > @@ -2,4 +2,6 @@
> >   * Copyright(c) 2021 HiSilicon Limited
> >   */
> >
> > +#define TEST_SG_MAX 4
> 
> suggest TEST_MAX_SGES which corresponding dev_info.max_sges

Ok, I have fixed API test to validate against max_ages.

Thanks,
Gowrishankar
> 
> Thanks
> Chengwen
> 
> > +
> >  int test_dma_api(uint16_t dev_id);
> >
  

Patch

diff --git a/app/test/test_dmadev.c b/app/test/test_dmadev.c
index 780941fc1e..a2f3a7f999 100644
--- a/app/test/test_dmadev.c
+++ b/app/test/test_dmadev.c
@@ -19,7 +19,7 @@ 
 #define ERR_RETURN(...) do { print_err(__func__, __LINE__, __VA_ARGS__); return -1; } while (0)
 
 #define TEST_RINGSIZE 512
-#define COPY_LEN 1024
+#define COPY_LEN 1032
 
 static struct rte_mempool *pool;
 static int16_t test_dev_id;
@@ -396,6 +396,120 @@  test_stop_start(int16_t dev_id, uint16_t vchan)
 	return 0;
 }
 
+static int
+test_enqueue_sg_copies(int16_t dev_id, uint16_t vchan)
+{
+	unsigned int src_len, dst_len, n_sge, len, i, j, k;
+	char orig_src[COPY_LEN], orig_dst[COPY_LEN];
+	struct rte_dma_info info = { 0 };
+	enum rte_dma_status_code status;
+	uint16_t id, n_src, n_dst;
+
+	if (rte_dma_info_get(dev_id, &info) < 0)
+		ERR_RETURN("Failed to get dev info");
+
+	n_sge = RTE_MIN(info.max_sges, TEST_SG_MAX);
+	len = COPY_LEN;
+
+	for (n_src = 1; n_src <= n_sge; n_src++) {
+		src_len = len / n_src;
+		for (n_dst = 1; n_dst <= n_sge; n_dst++) {
+			dst_len = len / n_dst;
+
+			struct rte_dma_sge sg_src[n_sge], sg_dst[n_sge];
+			struct rte_mbuf *src[n_sge], *dst[n_sge];
+			char *src_data[n_sge], *dst_data[n_sge];
+
+			for (i = 0 ; i < COPY_LEN; i++)
+				orig_src[i] = rte_rand() & 0xFF;
+
+			memset(orig_dst, 0, COPY_LEN);
+
+			for (i = 0; i < n_src; i++) {
+				src[i] = rte_pktmbuf_alloc(pool);
+				RTE_ASSERT(src[i] != NULL);
+				sg_src[i].addr = rte_pktmbuf_iova(src[i]);
+				sg_src[i].length = src_len;
+				src_data[i] = rte_pktmbuf_mtod(src[i], char *);
+			}
+
+			for (k = 0; k < n_dst; k++) {
+				dst[k] = rte_pktmbuf_alloc(pool);
+				RTE_ASSERT(dst[k] != NULL);
+				sg_dst[k].addr = rte_pktmbuf_iova(dst[k]);
+				sg_dst[k].length = dst_len;
+				dst_data[k] = rte_pktmbuf_mtod(dst[k], char *);
+			}
+
+			for (i = 0; i < n_src; i++) {
+				for (j = 0; j < src_len; j++)
+					src_data[i][j] = orig_src[i * src_len + j];
+			}
+
+			for (k = 0; k < n_dst; k++)
+				memset(dst_data[k], 0, dst_len);
+
+			printf("\tsrc segs: %2d [seg len: %4d] - dst segs: %2d [seg len : %4d]\n",
+				n_src, src_len, n_dst, dst_len);
+
+			id = rte_dma_copy_sg(dev_id, vchan, sg_src, sg_dst, n_src, n_dst,
+					     RTE_DMA_OP_FLAG_SUBMIT);
+
+			if (id != id_count)
+				ERR_RETURN("Error with rte_dma_copy_sg, got %u, expected %u\n",
+					id, id_count);
+
+			/* Give time for copy to finish, then check it was done */
+			await_hw(dev_id, vchan);
+
+			for (k = 0; k < n_dst; k++)
+				memcpy((&orig_dst[0] + k * dst_len), dst_data[k], dst_len);
+
+			if (memcmp(orig_src, orig_dst, COPY_LEN))
+				ERR_RETURN("Data mismatch");
+
+			/* Verify completion */
+			id = ~id;
+			if (rte_dma_completed(dev_id, vchan, 1, &id, NULL) != 1)
+				ERR_RETURN("Error with rte_dma_completed\n");
+
+			/* Verify expected index(id_count) */
+			if (id != id_count)
+				ERR_RETURN("Error:incorrect job id received, %u [expected %u]\n",
+						id, id_count);
+
+			/* Check for completed and id when no job done */
+			id = ~id;
+			if (rte_dma_completed(dev_id, vchan, 1, &id, NULL) != 0)
+				ERR_RETURN("Error with rte_dma_completed when no job done\n");
+
+			if (id != id_count)
+				ERR_RETURN("Error:incorrect job id received when no job done, %u [expected %u]\n",
+					   id, id_count);
+
+			/* Check for completed_status and id when no job done */
+			id = ~id;
+			if (rte_dma_completed_status(dev_id, vchan, 1, &id, &status) != 0)
+				ERR_RETURN("Error with rte_dma_completed_status when no job done\n");
+			if (id != id_count)
+				ERR_RETURN("Error:incorrect job id received when no job done, %u [expected %u]\n",
+						id, 0);
+
+			for (i = 0; i < n_src; i++)
+				rte_pktmbuf_free(src[i]);
+			for (i = 0; i < n_dst; i++)
+				rte_pktmbuf_free(dst[i]);
+
+			/* Verify that completion returns nothing more */
+			if (rte_dma_completed(dev_id, 0, 1, NULL, NULL) != 0)
+				ERR_RETURN("Error with rte_dma_completed in empty check\n");
+
+			id_count++;
+		}
+	}
+	return 0;
+}
+
 /* Failure handling test cases - global macros and variables for those tests*/
 #define COMP_BURST_SZ	16
 #define OPT_FENCE(idx) ((fence && idx == 8) ? RTE_DMA_OP_FLAG_FENCE : 0)
@@ -1003,7 +1117,7 @@  test_dmadev_setup(void)
 			TEST_RINGSIZE * 2, /* n == num elements */
 			32,  /* cache size */
 			0,   /* priv size */
-			2048, /* data room size */
+			COPY_LEN + RTE_PKTMBUF_HEADROOM, /* data room size */
 			info.numa_node);
 	if (pool == NULL)
 		ERR_RETURN("Error with mempool creation\n");
@@ -1026,6 +1140,7 @@  test_dmadev_instance(int16_t dev_id)
 #define CHECK_ERRS    true
 	enum {
 		  TEST_COPY = 0,
+		  TEST_COPY_SG,
 		  TEST_START,
 		  TEST_BURST,
 		  TEST_ERR,
@@ -1060,6 +1175,13 @@  test_dmadev_instance(int16_t dev_id)
 	param[TEST_COPY].vchan = vchan;
 	param[TEST_COPY].check_err_stats = CHECK_ERRS;
 
+	param[TEST_COPY_SG].printable = "copy";
+	param[TEST_COPY_SG].test_fn = test_enqueue_sg_copies;
+	param[TEST_COPY_SG].iterations = 1;
+	param[TEST_COPY_SG].dev_id = dev_id;
+	param[TEST_COPY_SG].vchan = vchan;
+	param[TEST_COPY_SG].check_err_stats = CHECK_ERRS;
+
 	param[TEST_START].printable = "stop-start";
 	param[TEST_START].test_fn = test_stop_start;
 	param[TEST_START].iterations = 1;
@@ -1122,6 +1244,12 @@  test_dmadev_instance(int16_t dev_id)
 	/* run tests stopping/starting devices and check jobs still work after restart */
 	ts->unit_test_cases[TEST_START].enabled = 1;
 
+	/* run SG test cases */
+	if ((dev_info.dev_capa & RTE_DMA_CAPA_OPS_COPY_SG) == 0)
+		printf("DMA Dev %u: No SG support, skipping SG copy tests\n", dev_id);
+	else
+		ts->unit_test_cases[TEST_COPY_SG].enabled = 1;
+
 	/* run some burst capacity tests */
 	ts->unit_test_cases[TEST_BURST].setup = test_dmadev_burst_setup;
 	ts->unit_test_cases[TEST_BURST].enabled = 1;
diff --git a/app/test/test_dmadev_api.c b/app/test/test_dmadev_api.c
index aa07d2b359..37e43c9336 100644
--- a/app/test/test_dmadev_api.c
+++ b/app/test/test_dmadev_api.c
@@ -10,6 +10,7 @@ 
 #include <rte_dmadev.h>
 
 #include "test.h"
+#include "test_dmadev_api.h"
 
 extern int test_dma_api(uint16_t dev_id);
 
@@ -21,36 +22,62 @@  static int16_t invalid_dev_id;
 
 static char *src;
 static char *dst;
+static char *src_sg[TEST_SG_MAX];
+static char *dst_sg[TEST_SG_MAX];
 
 static int
 testsuite_setup(void)
 {
 	invalid_dev_id = -1;
-
-	src = rte_malloc("dmadev_test_src", TEST_MEMCPY_SIZE, 0);
-	if (src == NULL)
-		return -ENOMEM;
-	dst = rte_malloc("dmadev_test_dst", TEST_MEMCPY_SIZE, 0);
-	if (dst == NULL) {
-		rte_free(src);
-		src = NULL;
-		return -ENOMEM;
+	int i, rc = 0;
+
+	for (i = 0; i < TEST_SG_MAX; i++) {
+		src_sg[i] = rte_malloc("dmadev_test_src", TEST_MEMCPY_SIZE, 0);
+		if (src_sg[i] == NULL) {
+			rc = -ENOMEM;
+			goto exit;
+		}
+
+		dst_sg[i] = rte_malloc("dmadev_test_dst", TEST_MEMCPY_SIZE, 0);
+		if (dst_sg[i] == NULL) {
+			rte_free(src_sg[i]);
+			src_sg[i] = NULL;
+			rc = -ENOMEM;
+			goto exit;
+		}
 	}
 
+	src = src_sg[0];
+	dst = dst_sg[0];
+
 	/* Set dmadev log level to critical to suppress unnecessary output
 	 * during API tests.
 	 */
 	rte_log_set_level_pattern("lib.dmadev", RTE_LOG_CRIT);
 
-	return 0;
+	return rc;
+exit:
+	while (--i >= 0) {
+		rte_free(src_sg[i]);
+		rte_free(dst_sg[i]);
+	}
+
+	return rc;
 }
 
 static void
 testsuite_teardown(void)
 {
-	rte_free(src);
+	int i;
+
+	for (i = 0; i < TEST_SG_MAX; i++) {
+		rte_free(src_sg[i]);
+		src_sg[i] = NULL;
+		rte_free(dst_sg[i]);
+		dst_sg[i] = NULL;
+	}
+
 	src = NULL;
-	rte_free(dst);
 	dst = NULL;
 	/* Ensure the dmadev is stopped. */
 	rte_dma_stop(test_dev_id);
@@ -437,6 +464,37 @@  verify_memory(void)
 	return 0;
 }
 
+static void
+sg_memory_setup(int n)
+{
+	int i, j;
+
+	for (i = 0; i < n; i++) {
+		for (j = 0; j < TEST_MEMCPY_SIZE; j++)
+			src_sg[i][j] = (char)j;
+
+		memset(dst_sg[i], 0, TEST_MEMCPY_SIZE);
+	}
+}
+
+static int
+sg_memory_verify(int n)
+{
+	int i, j;
+
+	for (i = 0; i < n; i++) {
+		for (j = 0; j < TEST_MEMCPY_SIZE; j++) {
+			if (src_sg[i][j] == dst_sg[i][j])
+				continue;
+
+			RTE_TEST_ASSERT_EQUAL(src_sg[i][j], dst_sg[i][j], "Failed to copy memory, %d %d",
+				src_sg[i][j], dst_sg[i][j]);
+		}
+	}
+
+	return 0;
+}
+
 static int
 test_dma_completed(void)
 {
@@ -551,6 +609,86 @@  test_dma_completed_status(void)
 	return TEST_SUCCESS;
 }
 
+static int
+test_dma_sg(void)
+{
+	struct rte_dma_sge src_sge[TEST_SG_MAX], dst_sge[TEST_SG_MAX];
+	struct rte_dma_info dev_info = { 0 };
+	uint16_t last_idx = -1;
+	bool has_error = true;
+	int n_sge, i, ret;
+	uint16_t cpl_ret;
+
+	ret = rte_dma_info_get(test_dev_id, &dev_info);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to obtain device info, %d", ret);
+
+	if ((dev_info.dev_capa & RTE_DMA_CAPA_OPS_COPY_SG) == 0)
+		return TEST_SKIPPED;
+
+	n_sge = RTE_MIN(dev_info.max_sges, TEST_SG_MAX);
+
+	ret = setup_vchan(1);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to setup one vchan, %d", ret);
+
+	ret = rte_dma_start(test_dev_id);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to start, %d", ret);
+
+	for (i = 0; i < n_sge; i++) {
+		src_sge[i].addr = rte_malloc_virt2iova(src_sg[i]);
+		src_sge[i].length = TEST_MEMCPY_SIZE;
+		dst_sge[i].addr = rte_malloc_virt2iova(dst_sg[i]);
+		dst_sge[i].length = TEST_MEMCPY_SIZE;
+	}
+
+	sg_memory_setup(n_sge);
+
+	/* Check enqueue without submit */
+	ret = rte_dma_copy_sg(test_dev_id, 0, src_sge, dst_sge, n_sge, n_sge, 0);
+	RTE_TEST_ASSERT_EQUAL(ret, 0, "Failed to enqueue copy, %d", ret);
+
+	rte_delay_us_sleep(TEST_WAIT_US_VAL);
+
+	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
+	RTE_TEST_ASSERT_EQUAL(cpl_ret, 0, "Failed to get completed");
+
+	/* Check DMA submit */
+	ret = rte_dma_submit(test_dev_id, 0);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to submit, %d", ret);
+
+	rte_delay_us_sleep(TEST_WAIT_US_VAL);
+
+	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
+	RTE_TEST_ASSERT_EQUAL(cpl_ret, 1, "Failed to get completed");
+	RTE_TEST_ASSERT_EQUAL(last_idx, 0, "Last idx should be zero, %u", last_idx);
+	RTE_TEST_ASSERT_EQUAL(has_error, false, "Should have no error");
+
+	ret = sg_memory_verify(n_sge);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to verify memory");
+
+	sg_memory_setup(n_sge);
+
+	/* Check for enqueue with submit */
+	ret = rte_dma_copy_sg(test_dev_id, 0, src_sge, dst_sge, n_sge, n_sge,
+			      RTE_DMA_OP_FLAG_SUBMIT);
+	RTE_TEST_ASSERT_EQUAL(ret, 1, "Failed to enqueue copy, %d", ret);
+
+	rte_delay_us_sleep(TEST_WAIT_US_VAL);
+
+	cpl_ret = rte_dma_completed(test_dev_id, 0, 1, &last_idx, &has_error);
+	RTE_TEST_ASSERT_EQUAL(cpl_ret, 1, "Failed to get completed");
+	RTE_TEST_ASSERT_EQUAL(last_idx, 1, "Last idx should be 1, %u", last_idx);
+	RTE_TEST_ASSERT_EQUAL(has_error, false, "Should have no error");
+
+	ret = sg_memory_verify(n_sge);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to verify memory");
+
+	/* Stop dmadev to make sure dmadev to a known state */
+	ret = rte_dma_stop(test_dev_id);
+	RTE_TEST_ASSERT_SUCCESS(ret, "Failed to stop, %d", ret);
+
+	return TEST_SUCCESS;
+}
+
 static struct unit_test_suite dma_api_testsuite = {
 	.suite_name = "DMA API Test Suite",
 	.setup = testsuite_setup,
@@ -568,6 +706,7 @@  static struct unit_test_suite dma_api_testsuite = {
 		TEST_CASE(test_dma_dump),
 		TEST_CASE(test_dma_completed),
 		TEST_CASE(test_dma_completed_status),
+		TEST_CASE(test_dma_sg),
 		TEST_CASES_END()
 	}
 };
diff --git a/app/test/test_dmadev_api.h b/app/test/test_dmadev_api.h
index 33fbc5bd41..10ab925f80 100644
--- a/app/test/test_dmadev_api.h
+++ b/app/test/test_dmadev_api.h
@@ -2,4 +2,6 @@ 
  * Copyright(c) 2021 HiSilicon Limited
  */
 
+#define TEST_SG_MAX 4
+
 int test_dma_api(uint16_t dev_id);