Software APIs
mmio.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
7#include <stdalign.h>
8
10
11/**
12 * Copies a block of memory between MMIO and main memory while ensuring that
13 * MMIO accesses are word-aligned.
14 *
15 * If `from_mmio` is true, data is copied from MMIO to main memory. Otherwise,
16 * data is copied from main memory to MMIO. This is implemented as a single
17 * function to avoid code duplication.
18 *
19 * @param base the MMIO region to copy from/to.
20 * @param offset the offset to start copying from/to, in bytes.
21 * @param buf the main memory location to start copying to/from.
22 * @param len number of bytes to copy.
23 * @param from_mmio if true, copy from MMIO to main memory. Otherwise, copy from
24 * main memory to MMIO.
25 */
26static void mmio_region_memcpy32(mmio_region_t base, uint32_t offset,
27 uint8_t *buf, size_t len, bool from_mmio) {
28 if (len == 0) {
29 return;
30 }
31
32 // First, bring the MMIO address into word alignment, so we can do
33 // full-word I/O rather than partial word I/O.
34 ptrdiff_t misalignment = misalignment32_of(offset);
35 if (misalignment != 0) {
36 // The number of bytes missing to bring `offset` back into alignment.
37 // For example, 0x3 has misalignment of 3 and realignment of 1.
38 size_t realignment = sizeof(uint32_t) - OT_UNSIGNED(misalignment);
39 // Note that we might be doing less I/O than the misalignment requires; we
40 // might be off by a single byte, but not have the full three bytes for full
41 // realignment.
42 if (realignment > len) {
43 realignment = len;
44 }
45
46 // Converts `offset`, which points to a subword boundary, to point to the
47 // start of the current word it points into.
48 ptrdiff_t current_word_offset = OT_SIGNED(offset) - misalignment;
49 uint32_t current_word = mmio_region_read32(base, current_word_offset);
50
51 // Act on only to a suffix of `current_word`, corresponding to the necessary
52 // realignment.
53 uint8_t *current_byte = ((uint8_t *)&current_word) + misalignment;
54 if (from_mmio) {
55 memcpy(buf, current_byte, realignment);
56 } else {
57 // When writing, we need to write the modified word.
58 memcpy(current_byte, buf, realignment);
59 mmio_region_write32(base, current_word_offset, current_word);
60 }
61
62 offset += realignment;
63 buf += realignment;
64 len -= realignment;
65 }
66
67 // Now, we just do full word I/O until we run out of stuff to act on.
68 while (len > 0) {
69 // At the end, we may not have a full word to copy, but it's otherwise
70 // the same case as a full word, since we're already word aligned (if
71 // this would be a subword read, it would end the loop anyway).
72 uint32_t bytes_to_copy = sizeof(uint32_t);
73 if (bytes_to_copy > len) {
74 bytes_to_copy = len;
75 }
76
77 // Read the current word from MMIO.
78 uint32_t current_word = 0;
79 if (from_mmio || bytes_to_copy != sizeof(uint32_t)) {
80 // If reading from MMIO, we need to read this word always.
81 // If writing to MMIO, we only need to write a prefix when writing a
82 // subword. In that case, we need to avoid clobbering the word at
83 // `offset`.
84 current_word = mmio_region_read32(base, OT_SIGNED(offset));
85 }
86
87 // Copy a prefix; most of the time, this will be the whole word.
88 if (from_mmio) {
89 memcpy(buf, &current_word, bytes_to_copy);
90 } else {
91 // When writing to MMIO, we need to write the modified word.
92 memcpy(&current_word, buf, bytes_to_copy);
93 mmio_region_write32(base, OT_SIGNED(offset), current_word);
94 }
95
96 offset += bytes_to_copy;
97 buf += bytes_to_copy;
98 len -= bytes_to_copy;
99 }
100}
101
102void mmio_region_memcpy_from_mmio32(mmio_region_t base, uint32_t offset,
103 void *dest, size_t len) {
104 mmio_region_memcpy32(base, offset, dest, len, true);
105}
106
107void mmio_region_memcpy_to_mmio32(mmio_region_t base, uint32_t offset,
108 const void *src, size_t len) {
109 // Below `const` cast is necessary to be able to use `mmio_region_memcpy32`
110 // for both read and write operations but `from_mmio = false` means that `src`
111 // will never be written to.
112 mmio_region_memcpy32(base, offset, (void *)src, len, false);
113}
114
115// `extern` declarations to give the inline functions in the
116// corresponding header a link location.
117extern mmio_region_t mmio_region_from_addr(uintptr_t address);
118extern uint8_t mmio_region_read8(mmio_region_t base, ptrdiff_t offset);
119extern uint32_t mmio_region_read32(mmio_region_t base, ptrdiff_t offset);
120extern void mmio_region_write8(mmio_region_t base, ptrdiff_t offset,
121 uint8_t value);
122extern void mmio_region_write32(mmio_region_t base, ptrdiff_t offset,
123 uint32_t value);
124extern void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
125 uint8_t value);
126extern void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
127 uint32_t value);
128extern uint32_t mmio_region_read_mask32(mmio_region_t base, ptrdiff_t offset,
129 uint32_t mask, uint32_t mask_index);
130extern bool mmio_region_get_bit32(mmio_region_t base, ptrdiff_t offset,
131 uint32_t bit_index);
132extern void mmio_region_nonatomic_clear_mask32(mmio_region_t base,
133 ptrdiff_t offset, uint32_t mask,
134 uint32_t mask_index);
135extern void mmio_region_nonatomic_set_mask32(mmio_region_t base,
136 ptrdiff_t offset, uint32_t mask,
137 uint32_t mask_index);
138extern void mmio_region_write_only_set_mask32(mmio_region_t base,
139 ptrdiff_t offset, uint32_t mask,
140 uint32_t mask_index);
141extern void mmio_region_nonatomic_set_field32(mmio_region_t base,
142 ptrdiff_t offset,
143 bitfield_field32_t field,
144 uint32_t value);
145extern void mmio_region_write_only_set_field32(mmio_region_t base,
146 ptrdiff_t offset,
147 bitfield_field32_t field,
148 uint32_t value);
149extern void mmio_region_nonatomic_clear_bit32(mmio_region_t base,
150 ptrdiff_t offset,
151 uint32_t bit_index);
152extern void mmio_region_nonatomic_set_bit32(mmio_region_t base,
153 ptrdiff_t offset,
154 uint32_t bit_index);
155extern void mmio_region_write_only_set_bit32(mmio_region_t base,
156 ptrdiff_t offset,
157 uint32_t bit_index);