355 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			355 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2015 - 2017, Xilinx Inc. and Contributors. All rights reserved.
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * @file	io.h
 | 
						|
 * @brief	I/O access primitives for libmetal.
 | 
						|
 */
 | 
						|
 | 
						|
#ifndef __METAL_IO__H__
 | 
						|
#define __METAL_IO__H__
 | 
						|
 | 
						|
#include <limits.h>
 | 
						|
#include <stdint.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <metal/assert.h>
 | 
						|
#include <metal/compiler.h>
 | 
						|
#include <metal/atomic.h>
 | 
						|
#include <metal/sys.h>
 | 
						|
#include <metal/cpu.h>
 | 
						|
 | 
						|
#ifdef __cplusplus
 | 
						|
extern "C" {
 | 
						|
#endif
 | 
						|
 | 
						|
/** \defgroup io IO Interfaces
 | 
						|
 *  @{ */
 | 
						|
 | 
						|
#ifdef __MICROBLAZE__
 | 
						|
#define NO_ATOMIC_64_SUPPORT
 | 
						|
#endif
 | 
						|
 | 
						|
struct metal_io_region;
 | 
						|
 | 
						|
/** Generic I/O operations. */
 | 
						|
struct metal_io_ops {
 | 
						|
	uint64_t	(*read)(struct metal_io_region *io,
 | 
						|
				unsigned long offset,
 | 
						|
				memory_order order,
 | 
						|
				int width);
 | 
						|
	void		(*write)(struct metal_io_region *io,
 | 
						|
				 unsigned long offset,
 | 
						|
				 uint64_t value,
 | 
						|
				 memory_order order,
 | 
						|
				 int width);
 | 
						|
	int		(*block_read)(struct metal_io_region *io,
 | 
						|
				unsigned long offset,
 | 
						|
				void *restrict dst,
 | 
						|
				memory_order order,
 | 
						|
				int len);
 | 
						|
	int		(*block_write)(struct metal_io_region *io,
 | 
						|
				 unsigned long offset,
 | 
						|
				 const void *restrict src,
 | 
						|
				 memory_order order,
 | 
						|
				 int len);
 | 
						|
	void		(*block_set)(struct metal_io_region *io,
 | 
						|
				 unsigned long offset,
 | 
						|
				 unsigned char value,
 | 
						|
				 memory_order order,
 | 
						|
				 int len);
 | 
						|
	void		(*close)(struct metal_io_region *io);
 | 
						|
};
 | 
						|
 | 
						|
/** Libmetal I/O region structure. */
 | 
						|
struct metal_io_region {
 | 
						|
	void			*virt;      /**< base virtual address */
 | 
						|
	const metal_phys_addr_t	*physmap;   /**< table of base physical address
 | 
						|
                                                 of each of the pages in the I/O
 | 
						|
                                                 region */
 | 
						|
	size_t			size;       /**< size of the I/O region */
 | 
						|
	unsigned long		page_shift; /**< page shift of I/O region */
 | 
						|
	metal_phys_addr_t	page_mask;  /**< page mask of I/O region */
 | 
						|
	unsigned int		mem_flags;  /**< memory attribute of the
 | 
						|
						 I/O region */
 | 
						|
	struct metal_io_ops	ops;        /**< I/O region operations */
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Open a libmetal I/O region.
 | 
						|
 *
 | 
						|
 * @param[in, out]	io		I/O region handle.
 | 
						|
 * @param[in]		virt		Virtual address of region.
 | 
						|
 * @param[in]		physmap		Array of physical addresses per page.
 | 
						|
 * @param[in]		size		Size of region.
 | 
						|
 * @param[in]		page_shift	Log2 of page size (-1 for single page).
 | 
						|
 * @param[in]		mem_flags	Memory flags
 | 
						|
 * @param[in]		ops			ops
 | 
						|
 */
 | 
						|
void
 | 
						|
metal_io_init(struct metal_io_region *io, void *virt,
 | 
						|
	      const metal_phys_addr_t *physmap, size_t size,
 | 
						|
	      unsigned page_shift, unsigned int mem_flags,
 | 
						|
	      const struct metal_io_ops *ops);
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Close a libmetal shared memory segment.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 */
 | 
						|
static inline void metal_io_finish(struct metal_io_region *io)
 | 
						|
{
 | 
						|
	if (io->ops.close)
 | 
						|
		(*io->ops.close)(io);
 | 
						|
	memset(io, 0, sizeof(*io));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Get size of I/O region.
 | 
						|
 *
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @return	Size of I/O region.
 | 
						|
 */
 | 
						|
static inline size_t metal_io_region_size(struct metal_io_region *io)
 | 
						|
{
 | 
						|
	return io->size;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Get virtual address for a given offset into the I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into shared memory segment.
 | 
						|
 * @return	NULL if offset is out of range, or pointer to offset.
 | 
						|
 */
 | 
						|
static inline void *
 | 
						|
metal_io_virt(struct metal_io_region *io, unsigned long offset)
 | 
						|
{
 | 
						|
	return (io->virt != METAL_BAD_VA && offset <= io->size
 | 
						|
		? (uint8_t *)io->virt + offset
 | 
						|
		: NULL);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Convert a virtual address to offset within I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	virt	Virtual address within segment.
 | 
						|
 * @return	METAL_BAD_OFFSET if out of range, or offset.
 | 
						|
 */
 | 
						|
static inline unsigned long
 | 
						|
metal_io_virt_to_offset(struct metal_io_region *io, void *virt)
 | 
						|
{
 | 
						|
	size_t offset = (uint8_t *)virt - (uint8_t *)io->virt;
 | 
						|
	return (offset < io->size ? offset : METAL_BAD_OFFSET);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Get physical address for a given offset into the I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into shared memory segment.
 | 
						|
 * @return	METAL_BAD_PHYS if offset is out of range, or physical address
 | 
						|
 *		of offset.
 | 
						|
 */
 | 
						|
static inline metal_phys_addr_t
 | 
						|
metal_io_phys(struct metal_io_region *io, unsigned long offset)
 | 
						|
{
 | 
						|
	unsigned long page = (io->page_shift >=
 | 
						|
			     sizeof(offset) * CHAR_BIT ?
 | 
						|
			     0 : offset >> io->page_shift);
 | 
						|
	return (io->physmap != NULL && offset <= io->size
 | 
						|
		? io->physmap[page] + (offset & io->page_mask)
 | 
						|
		: METAL_BAD_PHYS);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Convert a physical address to offset within I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	phys	Physical address within segment.
 | 
						|
 * @return	METAL_BAD_OFFSET if out of range, or offset.
 | 
						|
 */
 | 
						|
static inline unsigned long
 | 
						|
metal_io_phys_to_offset(struct metal_io_region *io, metal_phys_addr_t phys)
 | 
						|
{
 | 
						|
	unsigned long offset =
 | 
						|
		(io->page_mask == (metal_phys_addr_t)(-1) ?
 | 
						|
		phys - io->physmap[0] :  phys & io->page_mask);
 | 
						|
	do {
 | 
						|
		if (metal_io_phys(io, offset) == phys)
 | 
						|
			return offset;
 | 
						|
		offset += io->page_mask + 1;
 | 
						|
	} while (offset < io->size);
 | 
						|
	return METAL_BAD_OFFSET;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Convert a physical address to virtual address.
 | 
						|
 * @param[in]	io	Shared memory segment handle.
 | 
						|
 * @param[in]	phys	Physical address within segment.
 | 
						|
 * @return	NULL if out of range, or corresponding virtual address.
 | 
						|
 */
 | 
						|
static inline void *
 | 
						|
metal_io_phys_to_virt(struct metal_io_region *io, metal_phys_addr_t phys)
 | 
						|
{
 | 
						|
	return metal_io_virt(io, metal_io_phys_to_offset(io, phys));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Convert a virtual address to physical address.
 | 
						|
 * @param[in]	io	Shared memory segment handle.
 | 
						|
 * @param[in]	virt	Virtual address within segment.
 | 
						|
 * @return	METAL_BAD_PHYS if out of range, or corresponding
 | 
						|
 *		physical address.
 | 
						|
 */
 | 
						|
static inline metal_phys_addr_t
 | 
						|
metal_io_virt_to_phys(struct metal_io_region *io, void *virt)
 | 
						|
{
 | 
						|
	return metal_io_phys(io, metal_io_virt_to_offset(io, virt));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Read a value from an I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into I/O region.
 | 
						|
 * @param[in]	order	Memory ordering.
 | 
						|
 * @param[in]	width	Width in bytes of datatype to read.  This must be 1, 2,
 | 
						|
 *			4, or 8, and a compile time constant for this function
 | 
						|
 *			to inline cleanly.
 | 
						|
 * @return	Value.
 | 
						|
 */
 | 
						|
static inline uint64_t
 | 
						|
metal_io_read(struct metal_io_region *io, unsigned long offset,
 | 
						|
	      memory_order order, int width)
 | 
						|
{
 | 
						|
	void *ptr = metal_io_virt(io, offset);
 | 
						|
 | 
						|
	if (io->ops.read)
 | 
						|
		return (*io->ops.read)(io, offset, order, width);
 | 
						|
	else if (ptr && sizeof(atomic_uchar) == width)
 | 
						|
		return atomic_load_explicit((atomic_uchar *)ptr, order);
 | 
						|
	else if (ptr && sizeof(atomic_ushort) == width)
 | 
						|
		return atomic_load_explicit((atomic_ushort *)ptr, order);
 | 
						|
	else if (ptr && sizeof(atomic_uint) == width)
 | 
						|
		return atomic_load_explicit((atomic_uint *)ptr, order);
 | 
						|
	else if (ptr && sizeof(atomic_ulong) == width)
 | 
						|
		return atomic_load_explicit((atomic_ulong *)ptr, order);
 | 
						|
#ifndef NO_ATOMIC_64_SUPPORT
 | 
						|
	else if (ptr && sizeof(atomic_ullong) == width)
 | 
						|
		return atomic_load_explicit((atomic_ullong *)ptr, order);
 | 
						|
#endif
 | 
						|
	metal_assert(0);
 | 
						|
	return 0; /* quiet compiler */
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Write a value into an I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into I/O region.
 | 
						|
 * @param[in]	value	Value to write.
 | 
						|
 * @param[in]	order	Memory ordering.
 | 
						|
 * @param[in]	width	Width in bytes of datatype to read.  This must be 1, 2,
 | 
						|
 *			4, or 8, and a compile time constant for this function
 | 
						|
 *			to inline cleanly.
 | 
						|
 */
 | 
						|
static inline void
 | 
						|
metal_io_write(struct metal_io_region *io, unsigned long offset,
 | 
						|
	       uint64_t value, memory_order order, int width)
 | 
						|
{
 | 
						|
	void *ptr = metal_io_virt(io, offset);
 | 
						|
	if (io->ops.write)
 | 
						|
		(*io->ops.write)(io, offset, value, order, width);
 | 
						|
	else if (ptr && sizeof(atomic_uchar) == width)
 | 
						|
		atomic_store_explicit((atomic_uchar *)ptr, value, order);
 | 
						|
	else if (ptr && sizeof(atomic_ushort) == width)
 | 
						|
		atomic_store_explicit((atomic_ushort *)ptr, value, order);
 | 
						|
	else if (ptr && sizeof(atomic_uint) == width)
 | 
						|
		atomic_store_explicit((atomic_uint *)ptr, value, order);
 | 
						|
	else if (ptr && sizeof(atomic_ulong) == width)
 | 
						|
		atomic_store_explicit((atomic_ulong *)ptr, value, order);
 | 
						|
#ifndef NO_ATOMIC_64_SUPPORT
 | 
						|
	else if (ptr && sizeof(atomic_ullong) == width)
 | 
						|
		atomic_store_explicit((atomic_ullong *)ptr, value, order);
 | 
						|
#endif
 | 
						|
	else
 | 
						|
		metal_assert (0);
 | 
						|
}
 | 
						|
 | 
						|
#define metal_io_read8_explicit(_io, _ofs, _order)			\
 | 
						|
	metal_io_read((_io), (_ofs), (_order), 1)
 | 
						|
#define metal_io_read8(_io, _ofs)					\
 | 
						|
	metal_io_read((_io), (_ofs), memory_order_seq_cst, 1)
 | 
						|
#define metal_io_write8_explicit(_io, _ofs, _val, _order)		\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), (_order), 1)
 | 
						|
#define metal_io_write8(_io, _ofs, _val)				\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 1)
 | 
						|
 | 
						|
#define metal_io_read16_explicit(_io, _ofs, _order)			\
 | 
						|
	metal_io_read((_io), (_ofs), (_order), 2)
 | 
						|
#define metal_io_read16(_io, _ofs)					\
 | 
						|
	metal_io_read((_io), (_ofs), memory_order_seq_cst, 2)
 | 
						|
#define metal_io_write16_explicit(_io, _ofs, _val, _order)		\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), (_order), 2)
 | 
						|
#define metal_io_write16(_io, _ofs, _val)				\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 2)
 | 
						|
 | 
						|
#define metal_io_read32_explicit(_io, _ofs, _order)			\
 | 
						|
	metal_io_read((_io), (_ofs), (_order), 4)
 | 
						|
#define metal_io_read32(_io, _ofs)					\
 | 
						|
	metal_io_read((_io), (_ofs), memory_order_seq_cst, 4)
 | 
						|
#define metal_io_write32_explicit(_io, _ofs, _val, _order)		\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), (_order), 4)
 | 
						|
#define metal_io_write32(_io, _ofs, _val)				\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 4)
 | 
						|
 | 
						|
#define metal_io_read64_explicit(_io, _ofs, _order)			\
 | 
						|
	metal_io_read((_io), (_ofs), (_order), 8)
 | 
						|
#define metal_io_read64(_io, _ofs)					\
 | 
						|
	metal_io_read((_io), (_ofs), memory_order_seq_cst, 8)
 | 
						|
#define metal_io_write64_explicit(_io, _ofs, _val, _order)		\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), (_order), 8)
 | 
						|
#define metal_io_write64(_io, _ofs, _val)				\
 | 
						|
	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 8)
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Read a block from an I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into I/O region.
 | 
						|
 * @param[in]	dst	destination to store the read data.
 | 
						|
 * @param[in]	len	length in bytes to read.
 | 
						|
 * @return      On success, number of bytes read. On failure, negative value
 | 
						|
 */
 | 
						|
int metal_io_block_read(struct metal_io_region *io, unsigned long offset,
 | 
						|
	       void *restrict dst, int len);
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	Write a block into an I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into I/O region.
 | 
						|
 * @param[in]	src	source to write.
 | 
						|
 * @param[in]	len	length in bytes to write.
 | 
						|
 * @return      On success, number of bytes written. On failure, negative value
 | 
						|
 */
 | 
						|
int metal_io_block_write(struct metal_io_region *io, unsigned long offset,
 | 
						|
	       const void *restrict src, int len);
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief	fill a block of an I/O region.
 | 
						|
 * @param[in]	io	I/O region handle.
 | 
						|
 * @param[in]	offset	Offset into I/O region.
 | 
						|
 * @param[in]	value	value to fill into the block
 | 
						|
 * @param[in]	len	length in bytes to fill.
 | 
						|
 * @return      On success, number of bytes filled. On failure, negative value
 | 
						|
 */
 | 
						|
int metal_io_block_set(struct metal_io_region *io, unsigned long offset,
 | 
						|
	       unsigned char value, int len);
 | 
						|
 | 
						|
#include <metal/system/generic/io.h>
 | 
						|
 | 
						|
/** @} */
 | 
						|
 | 
						|
#ifdef __cplusplus
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
#endif /* __METAL_IO__H__ */
 |