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