375 lines
12 KiB
C
375 lines
12 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);
|
|
metal_phys_addr_t (*offset_to_phys)(struct metal_io_region *io,
|
|
unsigned long offset);
|
|
unsigned long (*phys_to_offset)(struct metal_io_region *io,
|
|
metal_phys_addr_t phys);
|
|
};
|
|
|
|
/** 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 int 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)
|
|
{
|
|
if (!io->ops.offset_to_phys) {
|
|
unsigned long page = (io->page_shift >=
|
|
sizeof(offset) * CHAR_BIT ?
|
|
0 : offset >> io->page_shift);
|
|
return (io->physmap && offset < io->size
|
|
? io->physmap[page] + (offset & io->page_mask)
|
|
: METAL_BAD_PHYS);
|
|
}
|
|
|
|
return io->ops.offset_to_phys(io, offset);
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
if (!io->ops.phys_to_offset) {
|
|
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;
|
|
}
|
|
|
|
return (*io->ops.phys_to_offset)(io, phys);
|
|
}
|
|
|
|
/**
|
|
* @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, (unsigned char)value,
|
|
order);
|
|
else if (ptr && sizeof(atomic_ushort) == width)
|
|
atomic_store_explicit((atomic_ushort *)ptr,
|
|
(unsigned short)value, order);
|
|
else if (ptr && sizeof(atomic_uint) == width)
|
|
atomic_store_explicit((atomic_uint *)ptr, (unsigned int)value,
|
|
order);
|
|
else if (ptr && sizeof(atomic_ulong) == width)
|
|
atomic_store_explicit((atomic_ulong *)ptr, (unsigned long)value,
|
|
order);
|
|
#ifndef NO_ATOMIC_64_SUPPORT
|
|
else if (ptr && sizeof(atomic_ullong) == width)
|
|
atomic_store_explicit((atomic_ullong *)ptr,
|
|
(unsigned long long)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/@PROJECT_SYSTEM@/io.h>
|
|
|
|
/** @} */
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* __METAL_IO__H__ */
|