axi: Add AXI sandbox driver and simple emulator

Add test infrastructure and tests for the AXI uclass.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Mario Six <mario.six@gdsys.cc>
lime2-spi
Mario Six 6 years ago committed by Anatolij Gustschin
parent 9fc8706d65
commit 9a8bcabd8a
  1. 66
      arch/sandbox/include/asm/axi.h
  2. 7
      drivers/axi/Kconfig
  3. 3
      drivers/axi/Makefile
  4. 85
      drivers/axi/axi-emul-uclass.c
  5. 77
      drivers/axi/axi_sandbox.c
  6. 123
      drivers/axi/sandbox_store.c
  7. 74
      include/axi.h
  8. 1
      include/dm/uclass-id.h

@ -0,0 +1,66 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2018
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
*/
#ifndef __asm_axi_h
#define __asm_axi_h
#define axi_emul_get_ops(dev) ((struct axi_emul_ops *)(dev)->driver->ops)
/**
* axi_sandbox_get_emul() - Retrieve a pointer to a AXI emulation device
* @bus: The AXI bus from which to retrieve a emulation device
* @address: The address of a transfer that should be handled by a emulation
* device
* @length: The data width of a transfer that should be handled by a emulation
* device
* @emulp: Pointer to a buffer receiving the emulation device that handles
* the transfer specified by the address and length parameters
*
* To test the AXI uclass, we implement a simple AXI emulation device, which is
* a virtual device on a AXI bus that exposes a simple storage interface: When
* reading and writing from the device, the addresses are translated to offsets
* within the device's storage. For write accesses the data is written to the
* specified storage offset, and for read accesses the data is read from the
* specified storage offset.
*
* A DTS entry might look like this:
*
* axi: axi@0 {
* compatible = "sandbox,axi";
* #address-cells = <0x1>;
* #size-cells = <0x1>;
* store@0 {
* compatible = "sandbox,sandbox_store";
* reg = <0x0 0x400>;
* };
* };
*
* This function may then be used to retrieve the pointer to the sandbox_store
* emulation device given the AXI bus device, and the data (address, data
* width) of a AXI transfer which should be handled by a emulation device.
*
* Return: 0 of OK, -ENODEV if no device capable of handling the specified
* transfer exists or the device could not be retrieved
*/
int axi_sandbox_get_emul(struct udevice *bus, ulong address, uint length,
struct udevice **emulp);
/**
* axi_get_store() - Get address of internal storage of a emulated AXI device
* @dev: Emulated AXI device to get the pointer of the internal storage
* for.
* @storep: Pointer to the internal storage of the emulated AXI device.
*
* To preset or read back the contents internal storage of the emulated AXI
* device, this function returns the pointer to the storage. Changes to the
* contents of the storage are reflected when using the AXI read/write API
* methods, and vice versa, so by using this method expected read data can be
* set up in advance, and written data can be checked in unit tests.
*
* Return: 0 if OK, -ve on error.
*/
int axi_get_store(struct udevice *dev, u8 **storep);
#endif /* __asm_axi_h */

@ -22,4 +22,11 @@ config IHS_AXI
Interface (IHS AXI) bus on a gdsys IHS FPGA used to communicate with
IP cores in the FPGA (e.g. video transmitter cores).
config AXI_SANDBOX
bool "Enable AXI sandbox driver"
depends on DM
help
Support AXI (Advanced eXtensible Interface) emulation for the sandbox
environment.
endif

@ -7,3 +7,6 @@
obj-$(CONFIG_AXI) += axi-uclass.o
obj-$(CONFIG_IHS_AXI) += ihs_axi.o
obj-$(CONFIG_SANDBOX) += axi-emul-uclass.o
obj-$(CONFIG_SANDBOX) += sandbox_store.o
obj-$(CONFIG_AXI_SANDBOX) += axi_sandbox.o

@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2018
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
*/
#include <common.h>
#include <axi.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <asm/axi.h>
int axi_sandbox_get_emul(struct udevice *bus, ulong address,
enum axi_size_t size, struct udevice **emulp)
{
struct udevice *dev;
u32 reg[2];
uint offset;
switch (size) {
case AXI_SIZE_8:
offset = 1;
break;
case AXI_SIZE_16:
offset = 2;
break;
case AXI_SIZE_32:
offset = 4;
break;
default:
debug("%s: Unknown AXI transfer size '%d'", bus->name, size);
offset = 0;
}
/*
* Note: device_find_* don't activate the devices; they're activated
* as-needed below.
*/
for (device_find_first_child(bus, &dev);
dev;
device_find_next_child(&dev)) {
int ret;
ret = dev_read_u32_array(dev, "reg", reg, ARRAY_SIZE(reg));
if (ret) {
debug("%s: Could not read 'reg' property of %s\n",
bus->name, dev->name);
continue;
}
/*
* Does the transfer's address fall into this device's address
* space?
*/
if (address >= reg[0] && address <= reg[0] + reg[1] - offset) {
/* If yes, activate it... */
if (device_probe(dev)) {
debug("%s: Could not activate %s\n",
bus->name, dev->name);
return -ENODEV;
}
/* ...and return it */
*emulp = dev;
return 0;
}
}
return -ENODEV;
}
int axi_get_store(struct udevice *dev, u8 **storep)
{
struct axi_emul_ops *ops = axi_emul_get_ops(dev);
if (!ops->get_store)
return -ENOSYS;
return ops->get_store(dev, storep);
}
UCLASS_DRIVER(axi_emul) = {
.id = UCLASS_AXI_EMUL,
.name = "axi_emul",
};

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2018
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
*/
#include <common.h>
#include <axi.h>
#include <dm.h>
#include <asm/axi.h>
/*
* This driver implements a AXI bus for the sandbox architecture for testing
* purposes.
*
* The bus forwards every access to it to a special AXI emulation device (which
* it gets via the axi_emul_get_ops function) that implements a simple
* read/write storage.
*
* The emulator device must still be contained in the device tree in the usual
* way, since configuration data for the storage is read from the DT.
*/
static int axi_sandbox_read(struct udevice *bus, ulong address, void *data,
enum axi_size_t size)
{
struct axi_emul_ops *ops;
struct udevice *emul;
int ret;
/* Get emulator device */
ret = axi_sandbox_get_emul(bus, address, size, &emul);
if (ret)
return ret == -ENODEV ? 0 : ret;
/* Forward all reads to the AXI emulator */
ops = axi_emul_get_ops(emul);
if (!ops || !ops->read)
return -ENOSYS;
return ops->read(emul, address, data, size);
}
static int axi_sandbox_write(struct udevice *bus, ulong address, void *data,
enum axi_size_t size)
{
struct axi_emul_ops *ops;
struct udevice *emul;
int ret;
/* Get emulator device */
ret = axi_sandbox_get_emul(bus, address, size, &emul);
if (ret)
return ret == -ENODEV ? 0 : ret;
/* Forward all writes to the AXI emulator */
ops = axi_emul_get_ops(emul);
if (!ops || !ops->write)
return -ENOSYS;
return ops->write(emul, address, data, size);
}
static const struct udevice_id axi_sandbox_ids[] = {
{ .compatible = "sandbox,axi" },
{ /* sentinel */ }
};
static const struct axi_ops axi_sandbox_ops = {
.read = axi_sandbox_read,
.write = axi_sandbox_write,
};
U_BOOT_DRIVER(axi_sandbox_bus) = {
.name = "axi_sandbox_bus",
.id = UCLASS_AXI,
.of_match = axi_sandbox_ids,
.ops = &axi_sandbox_ops,
};

@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2018
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
*/
#include <common.h>
#include <axi.h>
#include <dm.h>
/**
* struct sandbox_store_priv - Private data structure of a AXI store device
* @store: The buffer holding the device's internal memory, which is read from
* and written to using the driver's methods
*/
struct sandbox_store_priv {
u8 *store;
};
/**
* copy_axi_data() - Copy data from source to destination with a given AXI
* transfer width
* @src: Pointer to the data source from where data will be read
* @dst: Pointer to the data destination where data will be written to
* @size: Size of the data to be copied given by a axi_size_t enum value
*
* Return: 0 if OK, -ve on error
*/
static int copy_axi_data(void *src, void *dst, enum axi_size_t size)
{
switch (size) {
case AXI_SIZE_8:
*((u8 *)dst) = *((u8 *)src);
return 0;
case AXI_SIZE_16:
*((u16 *)dst) = be16_to_cpu(*((u16 *)src));
return 0;
case AXI_SIZE_32:
*((u32 *)dst) = be32_to_cpu(*((u32 *)src));
return 0;
default:
debug("%s: Unknown AXI transfer size '%d'\n", __func__, size);
return -EINVAL;
}
}
static int sandbox_store_read(struct udevice *dev, ulong address, void *data,
enum axi_size_t size)
{
struct sandbox_store_priv *priv = dev_get_priv(dev);
return copy_axi_data(priv->store + address, data, size);
}
static int sandbox_store_write(struct udevice *dev, ulong address, void *data,
enum axi_size_t size)
{
struct sandbox_store_priv *priv = dev_get_priv(dev);
return copy_axi_data(data, priv->store + address, size);
}
static int sandbox_store_get_store(struct udevice *dev, u8 **store)
{
struct sandbox_store_priv *priv = dev_get_priv(dev);
*store = priv->store;
return 0;
}
static const struct udevice_id sandbox_store_ids[] = {
{ .compatible = "sandbox,sandbox_store" },
{ /* sentinel */ }
};
static const struct axi_emul_ops sandbox_store_ops = {
.read = sandbox_store_read,
.write = sandbox_store_write,
.get_store = sandbox_store_get_store,
};
static int sandbox_store_probe(struct udevice *dev)
{
struct sandbox_store_priv *priv = dev_get_priv(dev);
u32 reg[2];
int ret;
ret = dev_read_u32_array(dev, "reg", reg, ARRAY_SIZE(reg));
if (ret) {
debug("%s: Could not read 'reg' property\n", dev->name);
return -EINVAL;
}
/*
* Allocate the device's internal storage that will be read
* from/written to
*/
priv->store = calloc(reg[1], 1);
if (!priv->store)
return -ENOMEM;
return 0;
}
static int sandbox_store_remove(struct udevice *dev)
{
struct sandbox_store_priv *priv = dev_get_priv(dev);
free(priv->store);
return 0;
}
U_BOOT_DRIVER(sandbox_axi_store) = {
.name = "sandbox_axi_store",
.id = UCLASS_AXI_EMUL,
.of_match = sandbox_store_ids,
.ops = &sandbox_store_ops,
.priv_auto_alloc_size = sizeof(struct sandbox_store_priv),
.probe = sandbox_store_probe,
.remove = sandbox_store_remove,
};

@ -1,48 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2017
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
* (C) Copyright 2017, 2018
* Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
*/
#ifndef _AXI_H_
#define _AXI_H_
/**
* enum axi_size_t - Determine size of AXI transfer
* @AXI_SIZE_8: AXI sransfer is 8-bit wide
* @AXI_SIZE_16: AXI sransfer is 16-bit wide
* @AXI_SIZE_32: AXI sransfer is 32-bit wide
*/
enum axi_size_t {
AXI_SIZE_8,
AXI_SIZE_16,
AXI_SIZE_32,
};
/**
* struct axi_ops - driver operations for AXI uclass
*
* Drivers should support these operations unless otherwise noted. These
* operations are intended to be used by uclass code, not directly from
* other code.
*/
struct axi_ops {
/**
* read() - Read a single value from a specified address on a AXI bus
*
* @dev: AXI bus to read from.
* @address: The address to read from.
* @data: Pointer to a variable that takes the data value read
* from the address on the AXI bus.
* @size: The size of the data to be read.
* @return 0 if OK, -ve on error.
*
* Return: 0 if OK, -ve on error.
*/
int (*read)(struct udevice *dev, ulong address, void *data,
enum axi_size_t size);
/**
* write() - Write a single value to a specified address on a AXI bus
*
* @dev: AXI bus to write to.
* @address: The address to write to.
* @data: Pointer to the data value to be written to the address
* on the AXI bus.
* @size: The size of the data to write.
* @return 0 if OK, -ve on error.
*
* Return 0 if OK, -ve on error.
*/
int (*write)(struct udevice *dev, ulong address, void *data,
enum axi_size_t size);
@ -52,27 +51,68 @@ struct axi_ops {
/**
* axi_read() - Read a single value from a specified address on a AXI bus
*
* @dev: AXI bus to read from.
* @address: The address to read from.
* @data: Pointer to a variable that takes the data value read from the
* address on the AXI bus.
* @size: The size of the data to write.
* @return 0 if OK, -ve on error.
*
* Return: 0 if OK, -ve on error.
*/
int axi_read(struct udevice *dev, ulong address, void *data,
enum axi_size_t size);
/**
* axi_write() - Write a single value to a specified address on a AXI bus
*
* @dev: AXI bus to write to.
* @address: The address to write to.
* @data: Pointer to the data value to be written to the address on the
* AXI bus.
* @size: The size of the data to write.
* @return 0 if OK, -ve on error.
*
* Return: 0 if OK, -ve on error.
*/
int axi_write(struct udevice *dev, ulong address, void *data,
enum axi_size_t size);
struct axi_emul_ops {
/**
* read() - Read a single value from a specified address on a AXI bus
* @dev: AXI bus to read from.
* @address: The address to read from.
* @data: Pointer to a variable that takes the data value read
* from the address on the AXI bus.
* @size: The size of the data to be read.
*
* Return: 0 if OK, -ve on error.
*/
int (*read)(struct udevice *dev, ulong address, void *data,
enum axi_size_t size);
/**
* write() - Write a single value to a specified address on a AXI bus
* @dev: AXI bus to write to.
* @address: The address to write to.
* @data: Pointer to the data value to be written to the address
* on the AXI bus.
* @size: The size of the data to write.
*
* Return: 0 if OK, -ve on error.
*/
int (*write)(struct udevice *dev, ulong address, void *data,
enum axi_size_t size);
/**
* get_store() - Get address of internal storage of a emulated AXI
* device
* @dev: Emulated AXI device to get the pointer of the internal
* storage for.
* @storep: Pointer to the internal storage of the emulated AXI
* device.
*
* Return: 0 if OK, -ve on error.
*/
int (*get_store)(struct udevice *dev, u8 **storep);
};
#endif

@ -23,6 +23,7 @@ enum uclass_id {
UCLASS_I2C_EMUL, /* sandbox I2C device emulator */
UCLASS_PCI_EMUL, /* sandbox PCI device emulator */
UCLASS_USB_EMUL, /* sandbox USB bus device emulator */
UCLASS_AXI_EMUL, /* sandbox AXI bus device emulator */
UCLASS_SIMPLE_BUS, /* bus with child devices */
/* U-Boot uclasses start here - in alphabetical order */

Loading…
Cancel
Save