Johannes Schlatow
Published

Using AXI DMA on Genode

Transferring data to/from the Zynq's FPGA is an essential ingredient for integrating custom accelerators.

IntermediateProtip3 hours662
Using AXI DMA on Genode

Things used in this project

Story

Read more

Code

run/dma_loopback_test.run

Tcl
Run script
#
# Build
#

create_boot_directory

import_from_depot [depot_user]/src/[base_src] \
                  [depot_user]/src/init \
                  [depot_user]/pkg/drivers_fpga-zynq \
                  [depot_user]/src/libc \
                  [depot_user]/src/vfs \
                  jschlatow/src/zybo_z720_dma_loopback-bitstream/2023-01-16 \
                  [depot_user]/raw/[board]-devices

build {
	test/dma_loopback
}

#
# Config
#

set config  {
	<config verbose="yes">
		<parent-provides>
			<service name="ROM"/>
			<service name="PD"/>
			<service name="CPU"/>
			<service name="LOG"/>
			<service name="IO_MEM"/>
			<service name="IRQ"/>
		</parent-provides>
		<default-route>
			<any-service> <parent/> <any-child/> </any-service>
		</default-route>
		<default caps="200"/>

		<start name="timer">
			<resource name="RAM" quantum="1M"/>
			<provides><service name="Timer"/></provides>
		</start>

		<start name="vfs">
			<resource name="RAM" quantum="8M"/>
			<provides><service name="File_system"/></provides>
			<config>
				<vfs>
					<inline name="config">
						<config>
							<bitstream name="zybo_z720_dma_loopback-bitstream.bit" size="0x3dbafc"/>
						</config>
					</inline>
					<rom name="zybo_z720_dma_loopback-bitstream.bit"/>
				</vfs>
				<default-policy root="/" writeable="no"/>
			</config>
		</start>

		<start name="platform_drv" caps="1000" managing_system="yes">
			<binary name="init"/>
			<resource name="RAM" quantum="24M"/>
			<provides> <service name="Platform"/> </provides>
			<route>
				<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
				<any-service> <parent/> <any-child/> </any-service>
			</route>
		</start>

		<start name="test-dma_loopback">
			<resource name="RAM" quantum="8M"/>
			<route>
				<service name="Platform"> <child name="platform_drv"/> </service>
				<service name="Timer">    <child name="timer"/> </service>
				<any-service> <parent/> </any-service>
			</route>
		</start>

	</config>
}

install_config $config

#
# Create platform_drv policy
#
set policy_fd [open [run_dir]/genode/policy w]
puts $policy_fd {
	<config>
		<policy label="test-dma_loopback -> ">
			<device name="axi_dma_0"/>
		</policy>
	</config>
}
close $policy_fd

#
# Boot modules
#

# generic modules
set boot_modules {
	test-dma_loopback
}
build_boot_image $boot_modules

append qemu_args " -nographic "
run_genode_until forever

src/test/dma_loopback/main.cc

C/C++
Source code of the test comopnent
/*
 * \brief  Test component for xilinx_axidma
 * \author Johannes Schlatow
 * \date   2022-11-11
 */

/*
 * Copyright (C) 2022 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

/* Genode includes */
#include <libc/component.h>
#include <timer_session/connection.h>

/* Xilinx port includes */
#include <xilinx_axidma.h>

/* local includes */
#include <dma_ring_buffer.h>

using namespace Genode;

struct Main {

	enum {
		DMA_BUFFER_SIZE = 1024*1024,
		ACCESS_SIZE     = 64*30
	};

	Env            &env;

	Xilinx::Axidma  axidma  { env, Xilinx::Axidma::Mode::NORMAL };

	Xilinx::Axidma::Transfer_complete_handler<Main> rx_handler {
		*this, &Main::handle_rx_complete };

	/* timer for throughput reporting */
	using Periodic_timeout = Timer::Periodic_timeout<Main>;
	Timer::Connection               timer   { env };
	Constructible<Periodic_timeout> timeout { };

	/* state for throughput test */
	unsigned                        last_counter { 0 };
	unsigned                        counter      { 0 };
	unsigned                        rx_counter   { 0 };
	unsigned                        iterations   { 5 };
	Dma_ring_buffer                 buffers      { axidma.platform(),
	                                               DMA_BUFFER_SIZE,
	                                               UNCACHED };

	/* simple transfer test */
	void test_simple_transfer(size_t, uint8_t);

	/* methods for throughput test */
	void handle_rx_complete();
	void fill_transfers();
	void queue_next_transfer();

	Main(Env & env) : env(env)
	{
		test_simple_transfer(8192,  0x21);

		/* prepare throughput test */
		axidma.rx_complete_handler(rx_handler);
		fill_transfers();

		/* start periodic timer for throughput logging */
		timeout.construct(timer, *this, &Main::handle_timeout, Microseconds { 1000 * 2000U });

		/* start throughput test */
		queue_next_transfer();
	}

	void handle_timeout(Duration)
	{
		unsigned long transmitted = counter - last_counter;
		last_counter = counter;

		log("Current loopback throughput: ", ((transmitted * DMA_BUFFER_SIZE) / 2000000UL), "MB/s");

		iterations--;
		if (iterations == 0)
			env.parent().exit(0);
	}
};


void Main::test_simple_transfer(size_t size, uint8_t value)
{
	Platform::Dma_buffer src_buffer { axidma.platform(), size, UNCACHED };
	Platform::Dma_buffer dst_buffer { axidma.platform(), size, UNCACHED };

	/* initialise src buffer */
	Genode::memset(src_buffer.local_addr<void>(), value, size);
	Genode::memset(dst_buffer.local_addr<void>(), value != 0 ? 0 : -1, size);

	log("initiating simple transfer of size ", (unsigned)size);

	/* perform DMA transfer */
	if (axidma.simple_transfer(src_buffer, size, dst_buffer, size) != Xilinx::Axidma::Result::OKAY) {
		error("DMA transfer failed");
		env.parent().exit(1);
		return;
	}

	/* compare buffers */
	if (Genode::memcmp(src_buffer.local_addr<void>(), dst_buffer.local_addr<void>(), size)) {
		error("DMA transfer failed - Data error");
		env.parent().exit(1);
	} else
		log("DMA transfer succeeded");
}


void Main::handle_rx_complete()
{
	if (!axidma.rx_transfer_complete())
		return;

	/* compare the first ACCESS_SIZE bytes of src and dst buffers */
	Dma_ring_buffer::Dma_buffer_pair bufs = buffers.tail();
	if (Genode::memcmp(bufs.tx.local_addr<void>(),
		                bufs.rx.local_addr<void>(), ACCESS_SIZE)) {
		error("DMA failed - Data error");
		env.parent().exit(1);
	}
	/* check whether memory content has the expected value */
	else if (*bufs.tx.local_addr<unsigned>() != rx_counter++) {
		error("Expected ", rx_counter-1, " but got ", *bufs.tx.local_addr<unsigned>());
		env.parent().exit(1);
	}

	/* advance tail and initiate next transfer */
	buffers.advance_tail();
	queue_next_transfer();
	fill_transfers();
}


void Main::fill_transfers()
{
	/* fill all buffers */
	while (true) {
		Genode::memset(buffers.head().tx.local_addr<void>(), (uint8_t)counter, ACCESS_SIZE);
		*buffers.head().tx.local_addr<unsigned>() = counter;

		if (buffers.advance_head())
			counter++;
		else
			break;
	}
}


void Main::queue_next_transfer()
{
	/* start transfer */
	if (buffers.empty()) {
		warning("unable to queue transfer from empty ring buffer");
		return;
	}

	Dma_ring_buffer::Dma_buffer_pair bufs = buffers.tail();
	if (axidma.start_rx_transfer(bufs.rx, DMA_BUFFER_SIZE) != Xilinx::Axidma::Result::OKAY) {
		error("DMA rx transfer failed");
		env.parent().exit(1);
	}

	if (axidma.start_tx_transfer(bufs.tx, DMA_BUFFER_SIZE) != Xilinx::Axidma::Result::OKAY) {
		error("DMA tx transfer failed");
		env.parent().exit(1);
	}
}


void Libc::Component::construct(Env &env) {
	static Main main(env);
}

src/test/dma_loopback/dma_ring_buffer.h

C/C++
DMA ring buffer implementation
/*
 * \brief  Ring buffer of DMA buffers
 * \author Johannes Schlatow
 * \date   2022-11-11
 */

/*
 * Copyright (C) 2022 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _DMA_RING_BUFFER_H_
#define _DMA_RING_BUFFER_H_

/* Genode includes */
#include <util/lazy_array.h>
#include <platform_session/connection.h>

struct Dma_ring_buffer {

	struct Dma_buffer_pair {
		Platform::Dma_buffer &tx;
		Platform::Dma_buffer &rx;
	};

	unsigned _tail { 0 };
	unsigned _head { 0 };

	Genode::Lazy_array<Platform::Dma_buffer, 3> _tx_buffers;
	Genode::Lazy_array<Platform::Dma_buffer, 3> _rx_buffers;

	Dma_ring_buffer(Platform::Connection &platform, Genode::size_t element_size, Genode::Cache cache)
	: _tx_buffers(3, platform, element_size, cache),
	  _rx_buffers(3, platform, element_size, cache)
	{ }

	bool advance_head() {
		if ((_head+1) % _rx_buffers.count() != _tail) {
			_head = (_head+1) % _rx_buffers.count();
			return true;
		}

		return false;
	}

	bool advance_tail() {
		if (!empty()) {
			_tail = (_tail+1) % _rx_buffers.count();
			return true;
		}

		return false;
	}

	Dma_buffer_pair head() {
		return Dma_buffer_pair { _tx_buffers.value(_head), _rx_buffers.value(_head) };
	}

	Dma_buffer_pair tail() {
		return Dma_buffer_pair { _tx_buffers.value(_tail), _rx_buffers.value(_tail) };
	}

	bool empty() { return _head == _tail; }
};

#endif /* _DMA_RING_BUFFER_H_ */

src/test/dma_loopback/target.mk

C/C++
Makefile
TARGET = test-dma_loopback

REQUIRES := arm_v7a
LIBS = base libc xilinx_axidma

SRC_CC += main.cc

Credits

Johannes Schlatow

Johannes Schlatow

5 projects • 5 followers
Johannes is a developer at Genode Labs and has a research background in embedded systems and real-time operating systems.

Comments