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