1 /*
2  * Copyright (c) 2015 - 2017, Xilinx Inc. and Contributors. All rights reserved.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 /*
8  * @file	io.h
9  * @brief	I/O access primitives for libmetal.
10  */
11 
12 #ifndef __METAL_IO__H__
13 #define __METAL_IO__H__
14 
15 #include <limits.h>
16 #include <stdint.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <metal/assert.h>
20 #include <metal/compiler.h>
21 #include <metal/atomic.h>
22 #include <metal/sys.h>
23 #include <metal/cpu.h>
24 
25 #ifdef __cplusplus
26 extern "C" {
27 #endif
28 
29 /** \defgroup io IO Interfaces
30  *  @{ */
31 
32 #ifdef __MICROBLAZE__
33 #define NO_ATOMIC_64_SUPPORT
34 #endif
35 
36 struct metal_io_region;
37 
38 /** Generic I/O operations. */
39 struct metal_io_ops {
40 	uint64_t	(*read)(struct metal_io_region *io,
41 				unsigned long offset,
42 				memory_order order,
43 				int width);
44 	void		(*write)(struct metal_io_region *io,
45 				 unsigned long offset,
46 				 uint64_t value,
47 				 memory_order order,
48 				 int width);
49 	int		(*block_read)(struct metal_io_region *io,
50 				unsigned long offset,
51 				void *restrict dst,
52 				memory_order order,
53 				int len);
54 	int		(*block_write)(struct metal_io_region *io,
55 				 unsigned long offset,
56 				 const void *restrict src,
57 				 memory_order order,
58 				 int len);
59 	void		(*block_set)(struct metal_io_region *io,
60 				 unsigned long offset,
61 				 unsigned char value,
62 				 memory_order order,
63 				 int len);
64 	void		(*close)(struct metal_io_region *io);
65 };
66 
67 /** Libmetal I/O region structure. */
68 struct metal_io_region {
69 	void			*virt;      /**< base virtual address */
70 	const metal_phys_addr_t	*physmap;   /**< table of base physical address
71                                                  of each of the pages in the I/O
72                                                  region */
73 	size_t			size;       /**< size of the I/O region */
74 	unsigned long		page_shift; /**< page shift of I/O region */
75 	metal_phys_addr_t	page_mask;  /**< page mask of I/O region */
76 	unsigned int		mem_flags;  /**< memory attribute of the
77 						 I/O region */
78 	struct metal_io_ops	ops;        /**< I/O region operations */
79 };
80 
81 /**
82  * @brief	Open a libmetal I/O region.
83  *
84  * @param[in, out]	io		I/O region handle.
85  * @param[in]		virt		Virtual address of region.
86  * @param[in]		physmap		Array of physical addresses per page.
87  * @param[in]		size		Size of region.
88  * @param[in]		page_shift	Log2 of page size (-1 for single page).
89  * @param[in]		mem_flags	Memory flags
90  * @param[in]		ops			ops
91  */
92 void
93 metal_io_init(struct metal_io_region *io, void *virt,
94 	      const metal_phys_addr_t *physmap, size_t size,
95 	      unsigned page_shift, unsigned int mem_flags,
96 	      const struct metal_io_ops *ops);
97 
98 /**
99  * @brief	Close a libmetal shared memory segment.
100  * @param[in]	io	I/O region handle.
101  */
metal_io_finish(struct metal_io_region * io)102 static inline void metal_io_finish(struct metal_io_region *io)
103 {
104 	if (io->ops.close)
105 		(*io->ops.close)(io);
106 	memset(io, 0, sizeof(*io));
107 }
108 
109 /**
110  * @brief	Get size of I/O region.
111  *
112  * @param[in]	io	I/O region handle.
113  * @return	Size of I/O region.
114  */
metal_io_region_size(struct metal_io_region * io)115 static inline size_t metal_io_region_size(struct metal_io_region *io)
116 {
117 	return io->size;
118 }
119 
120 /**
121  * @brief	Get virtual address for a given offset into the I/O region.
122  * @param[in]	io	I/O region handle.
123  * @param[in]	offset	Offset into shared memory segment.
124  * @return	NULL if offset is out of range, or pointer to offset.
125  */
126 static inline void *
metal_io_virt(struct metal_io_region * io,unsigned long offset)127 metal_io_virt(struct metal_io_region *io, unsigned long offset)
128 {
129 	return (io->virt != METAL_BAD_VA && offset <= io->size
130 		? (uint8_t *)io->virt + offset
131 		: NULL);
132 }
133 
134 /**
135  * @brief	Convert a virtual address to offset within I/O region.
136  * @param[in]	io	I/O region handle.
137  * @param[in]	virt	Virtual address within segment.
138  * @return	METAL_BAD_OFFSET if out of range, or offset.
139  */
140 static inline unsigned long
metal_io_virt_to_offset(struct metal_io_region * io,void * virt)141 metal_io_virt_to_offset(struct metal_io_region *io, void *virt)
142 {
143 	size_t offset = (uint8_t *)virt - (uint8_t *)io->virt;
144 	return (offset < io->size ? offset : METAL_BAD_OFFSET);
145 }
146 
147 /**
148  * @brief	Get physical address for a given offset into the I/O region.
149  * @param[in]	io	I/O region handle.
150  * @param[in]	offset	Offset into shared memory segment.
151  * @return	METAL_BAD_PHYS if offset is out of range, or physical address
152  *		of offset.
153  */
154 static inline metal_phys_addr_t
metal_io_phys(struct metal_io_region * io,unsigned long offset)155 metal_io_phys(struct metal_io_region *io, unsigned long offset)
156 {
157 	unsigned long page = (io->page_shift >=
158 			     sizeof(offset) * CHAR_BIT ?
159 			     0 : offset >> io->page_shift);
160 	return (io->physmap != NULL && offset <= io->size
161 		? io->physmap[page] + (offset & io->page_mask)
162 		: METAL_BAD_PHYS);
163 }
164 
165 /**
166  * @brief	Convert a physical address to offset within I/O region.
167  * @param[in]	io	I/O region handle.
168  * @param[in]	phys	Physical address within segment.
169  * @return	METAL_BAD_OFFSET if out of range, or offset.
170  */
171 static inline unsigned long
metal_io_phys_to_offset(struct metal_io_region * io,metal_phys_addr_t phys)172 metal_io_phys_to_offset(struct metal_io_region *io, metal_phys_addr_t phys)
173 {
174 	unsigned long offset =
175 		(io->page_mask == (metal_phys_addr_t)(-1) ?
176 		phys - io->physmap[0] :  phys & io->page_mask);
177 	do {
178 		if (metal_io_phys(io, offset) == phys)
179 			return offset;
180 		offset += io->page_mask + 1;
181 	} while (offset < io->size);
182 	return METAL_BAD_OFFSET;
183 }
184 
185 /**
186  * @brief	Convert a physical address to virtual address.
187  * @param[in]	io	Shared memory segment handle.
188  * @param[in]	phys	Physical address within segment.
189  * @return	NULL if out of range, or corresponding virtual address.
190  */
191 static inline void *
metal_io_phys_to_virt(struct metal_io_region * io,metal_phys_addr_t phys)192 metal_io_phys_to_virt(struct metal_io_region *io, metal_phys_addr_t phys)
193 {
194 	return metal_io_virt(io, metal_io_phys_to_offset(io, phys));
195 }
196 
197 /**
198  * @brief	Convert a virtual address to physical address.
199  * @param[in]	io	Shared memory segment handle.
200  * @param[in]	virt	Virtual address within segment.
201  * @return	METAL_BAD_PHYS if out of range, or corresponding
202  *		physical address.
203  */
204 static inline metal_phys_addr_t
metal_io_virt_to_phys(struct metal_io_region * io,void * virt)205 metal_io_virt_to_phys(struct metal_io_region *io, void *virt)
206 {
207 	return metal_io_phys(io, metal_io_virt_to_offset(io, virt));
208 }
209 
210 /**
211  * @brief	Read a value from an I/O region.
212  * @param[in]	io	I/O region handle.
213  * @param[in]	offset	Offset into I/O region.
214  * @param[in]	order	Memory ordering.
215  * @param[in]	width	Width in bytes of datatype to read.  This must be 1, 2,
216  *			4, or 8, and a compile time constant for this function
217  *			to inline cleanly.
218  * @return	Value.
219  */
220 static inline uint64_t
metal_io_read(struct metal_io_region * io,unsigned long offset,memory_order order,int width)221 metal_io_read(struct metal_io_region *io, unsigned long offset,
222 	      memory_order order, int width)
223 {
224 	void *ptr = metal_io_virt(io, offset);
225 
226 	if (io->ops.read)
227 		return (*io->ops.read)(io, offset, order, width);
228 	else if (ptr && sizeof(atomic_uchar) == width)
229 		return atomic_load_explicit((atomic_uchar *)ptr, order);
230 	else if (ptr && sizeof(atomic_ushort) == width)
231 		return atomic_load_explicit((atomic_ushort *)ptr, order);
232 	else if (ptr && sizeof(atomic_uint) == width)
233 		return atomic_load_explicit((atomic_uint *)ptr, order);
234 	else if (ptr && sizeof(atomic_ulong) == width)
235 		return atomic_load_explicit((atomic_ulong *)ptr, order);
236 #ifndef NO_ATOMIC_64_SUPPORT
237 	else if (ptr && sizeof(atomic_ullong) == width)
238 		return atomic_load_explicit((atomic_ullong *)ptr, order);
239 #endif
240 	metal_assert(0);
241 	return 0; /* quiet compiler */
242 }
243 
244 /**
245  * @brief	Write a value into an I/O region.
246  * @param[in]	io	I/O region handle.
247  * @param[in]	offset	Offset into I/O region.
248  * @param[in]	value	Value to write.
249  * @param[in]	order	Memory ordering.
250  * @param[in]	width	Width in bytes of datatype to read.  This must be 1, 2,
251  *			4, or 8, and a compile time constant for this function
252  *			to inline cleanly.
253  */
254 static inline void
metal_io_write(struct metal_io_region * io,unsigned long offset,uint64_t value,memory_order order,int width)255 metal_io_write(struct metal_io_region *io, unsigned long offset,
256 	       uint64_t value, memory_order order, int width)
257 {
258 	void *ptr = metal_io_virt(io, offset);
259 	if (io->ops.write)
260 		(*io->ops.write)(io, offset, value, order, width);
261 	else if (ptr && sizeof(atomic_uchar) == width)
262 		atomic_store_explicit((atomic_uchar *)ptr, value, order);
263 	else if (ptr && sizeof(atomic_ushort) == width)
264 		atomic_store_explicit((atomic_ushort *)ptr, value, order);
265 	else if (ptr && sizeof(atomic_uint) == width)
266 		atomic_store_explicit((atomic_uint *)ptr, value, order);
267 	else if (ptr && sizeof(atomic_ulong) == width)
268 		atomic_store_explicit((atomic_ulong *)ptr, value, order);
269 #ifndef NO_ATOMIC_64_SUPPORT
270 	else if (ptr && sizeof(atomic_ullong) == width)
271 		atomic_store_explicit((atomic_ullong *)ptr, value, order);
272 #endif
273 	else
274 		metal_assert (0);
275 }
276 
277 #define metal_io_read8_explicit(_io, _ofs, _order)			\
278 	metal_io_read((_io), (_ofs), (_order), 1)
279 #define metal_io_read8(_io, _ofs)					\
280 	metal_io_read((_io), (_ofs), memory_order_seq_cst, 1)
281 #define metal_io_write8_explicit(_io, _ofs, _val, _order)		\
282 	metal_io_write((_io), (_ofs), (_val), (_order), 1)
283 #define metal_io_write8(_io, _ofs, _val)				\
284 	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 1)
285 
286 #define metal_io_read16_explicit(_io, _ofs, _order)			\
287 	metal_io_read((_io), (_ofs), (_order), 2)
288 #define metal_io_read16(_io, _ofs)					\
289 	metal_io_read((_io), (_ofs), memory_order_seq_cst, 2)
290 #define metal_io_write16_explicit(_io, _ofs, _val, _order)		\
291 	metal_io_write((_io), (_ofs), (_val), (_order), 2)
292 #define metal_io_write16(_io, _ofs, _val)				\
293 	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 2)
294 
295 #define metal_io_read32_explicit(_io, _ofs, _order)			\
296 	metal_io_read((_io), (_ofs), (_order), 4)
297 #define metal_io_read32(_io, _ofs)					\
298 	metal_io_read((_io), (_ofs), memory_order_seq_cst, 4)
299 #define metal_io_write32_explicit(_io, _ofs, _val, _order)		\
300 	metal_io_write((_io), (_ofs), (_val), (_order), 4)
301 #define metal_io_write32(_io, _ofs, _val)				\
302 	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 4)
303 
304 #define metal_io_read64_explicit(_io, _ofs, _order)			\
305 	metal_io_read((_io), (_ofs), (_order), 8)
306 #define metal_io_read64(_io, _ofs)					\
307 	metal_io_read((_io), (_ofs), memory_order_seq_cst, 8)
308 #define metal_io_write64_explicit(_io, _ofs, _val, _order)		\
309 	metal_io_write((_io), (_ofs), (_val), (_order), 8)
310 #define metal_io_write64(_io, _ofs, _val)				\
311 	metal_io_write((_io), (_ofs), (_val), memory_order_seq_cst, 8)
312 
313 /**
314  * @brief	Read a block from an I/O region.
315  * @param[in]	io	I/O region handle.
316  * @param[in]	offset	Offset into I/O region.
317  * @param[in]	dst	destination to store the read data.
318  * @param[in]	len	length in bytes to read.
319  * @return      On success, number of bytes read. On failure, negative value
320  */
321 int metal_io_block_read(struct metal_io_region *io, unsigned long offset,
322 	       void *restrict dst, int len);
323 
324 /**
325  * @brief	Write a block into an I/O region.
326  * @param[in]	io	I/O region handle.
327  * @param[in]	offset	Offset into I/O region.
328  * @param[in]	src	source to write.
329  * @param[in]	len	length in bytes to write.
330  * @return      On success, number of bytes written. On failure, negative value
331  */
332 int metal_io_block_write(struct metal_io_region *io, unsigned long offset,
333 	       const void *restrict src, int len);
334 
335 /**
336  * @brief	fill a block of an I/O region.
337  * @param[in]	io	I/O region handle.
338  * @param[in]	offset	Offset into I/O region.
339  * @param[in]	value	value to fill into the block
340  * @param[in]	len	length in bytes to fill.
341  * @return      On success, number of bytes filled. On failure, negative value
342  */
343 int metal_io_block_set(struct metal_io_region *io, unsigned long offset,
344 	       unsigned char value, int len);
345 
346 #include <metal/system/generic/io.h>
347 
348 /** @} */
349 
350 #ifdef __cplusplus
351 }
352 #endif
353 
354 #endif /* __METAL_IO__H__ */
355