libmetal/libmetal/lib/io.h

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__ */