| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #include "sw/device/lib/base/mmio.h" | |
| #include <stdalign.h> | |
| #include "sw/device/lib/base/memory.h" | |
| /** | |
| * Computes how many bytes `addr` is ahead of the previous 32-bit word alignment | |
| * boundary. | |
| */ | |
| static ptrdiff_t misalignment32_of(uintptr_t addr) { | |
| return addr % alignof(uint32_t); | |
| } | |
| /** | |
| * Copies a block of memory between MMIO and main memory while ensuring that | |
| * MMIO accesses are word-aligned. | |
| * | |
| * If `from_mmio` is true, data is copied from MMIO to main memory. Otherwise, | |
| * data is copied from main memory to MMIO. This is implemented as a single | |
| * function to avoid code duplication. | |
| * | |
| * @param base the MMIO region to copy from/to. | |
| * @param offset the offset to start copying from/to, in bytes. | |
| * @param buf the main memory location to start copying to/from. | |
| * @param len number of bytes to copy. | |
| * @param from_mmio if true, copy from MMIO to main memory. Otherwise, copy from | |
| * main memory to MMIO. | |
| */ | |
| static void mmio_region_memcpy32(mmio_region_t base, uint32_t offset, void *buf, | |
| size_t len, bool from_mmio) { | |
| if (len == 0) { | |
| return; | |
| } | |
| // First, bring the MMIO address into word alignment, so we can do | |
| // full-word I/O rather than partial word I/O. | |
| ptrdiff_t misalignment = misalignment32_of(offset); | |
| if (misalignment != 0) { | |
| // The number of bytes missing to bring `offset` back into alignment. | |
| // For example, 0x3 has misalignment of 3 and realignment of 1. | |
| ptrdiff_t realignment = sizeof(uint32_t) - misalignment; | |
| // Note that we might be doing less I/O than the misalignment requires; we | |
| // might be off by a single byte, but not have the full three bytes for full | |
| // realignment. | |
| if (realignment > len) { | |
| realignment = len; | |
| } | |
| // Converts `offset`, which points to a subword boundary, to point to the | |
| // start of the current word it points into. | |
| ptrdiff_t current_word_offset = offset - misalignment; | |
| uint32_t current_word = mmio_region_read32(base, current_word_offset); | |
| // Act on only to a suffix of `current_word`, corresponding to the necessary | |
| // realignment. | |
| uint8_t *current_byte = ((uint8_t *)¤t_word) + misalignment; | |
| if (from_mmio) { | |
| memcpy(buf, current_byte, realignment); | |
| } else { | |
| // When writing, we need to write the modified word. | |
| memcpy(current_byte, buf, realignment); | |
| mmio_region_write32(base, current_word_offset, current_word); | |
| } | |
| offset += realignment; | |
| buf += realignment; | |
| len -= realignment; | |
| } | |
| // Now, we just do full word I/O until we run out of stuff to act on. | |
| while (len > 0) { | |
| // At the end, we may not have a full word to copy, but it's otherwise | |
| // the same case as a full word, since we're already word aligned (if | |
| // this would be a subword read, it would end the loop anyway). | |
| uint32_t bytes_to_copy = sizeof(uint32_t); | |
| if (bytes_to_copy > len) { | |
| bytes_to_copy = len; | |
| } | |
| // Read the current word from MMIO. | |
| uint32_t current_word = 0; | |
| if (from_mmio || bytes_to_copy != sizeof(uint32_t)) { | |
| // If reading from MMIO, we need to read this word always. | |
| // If writing to MMIO, we only need to write a prefix when writing a | |
| // subword. In that case, we need to avoid clobbering the word at | |
| // `offset`. | |
| current_word = mmio_region_read32(base, offset); | |
| } | |
| // Copy a prefix; most of the time, this will be the whole word. | |
| if (from_mmio) { | |
| memcpy(buf, ¤t_word, bytes_to_copy); | |
| } else { | |
| // When writing to MMIO, we need to write the modified word. | |
| memcpy(¤t_word, buf, bytes_to_copy); | |
| mmio_region_write32(base, offset, current_word); | |
| } | |
| offset += bytes_to_copy; | |
| buf += bytes_to_copy; | |
| len -= bytes_to_copy; | |
| } | |
| } | |
| void mmio_region_memcpy_from_mmio32(mmio_region_t base, uint32_t offset, | |
| void *dest, size_t len) { | |
| mmio_region_memcpy32(base, offset, dest, len, true); | |
| } | |
| void mmio_region_memcpy_to_mmio32(mmio_region_t base, uint32_t offset, | |
| const void *src, size_t len) { | |
| // Below `const` cast is necessary to be able to use `mmio_region_memcpy32` | |
| // for both read and write operations but `from_mmio = false` means that `src` | |
| // will never be written to. | |
| mmio_region_memcpy32(base, offset, (void *)src, len, false); | |
| } | |
| // `extern` declarations to give the inline functions in the | |
| // corresponding header a link location. | |
| extern uint8_t mmio_region_read8(mmio_region_t base, ptrdiff_t offset); | |
| extern uint32_t mmio_region_read32(mmio_region_t base, ptrdiff_t offset); | |
| extern void mmio_region_write8(mmio_region_t base, ptrdiff_t offset, | |
| uint8_t value); | |
| extern void mmio_region_write32(mmio_region_t base, ptrdiff_t offset, | |
| uint32_t value); | |
| extern uint32_t mmio_region_read_mask32(mmio_region_t base, ptrdiff_t offset, | |
| uint32_t mask, uint32_t mask_index); | |
| extern bool mmio_region_get_bit32(mmio_region_t base, ptrdiff_t offset, | |
| uint32_t bit_index); | |
| extern void mmio_region_nonatomic_clear_mask32(mmio_region_t base, | |
| ptrdiff_t offset, uint32_t mask, | |
| uint32_t mask_index); | |
| extern void mmio_region_nonatomic_set_mask32(mmio_region_t base, | |
| ptrdiff_t offset, uint32_t mask, | |
| uint32_t mask_index); | |
| extern void mmio_region_write_only_set_mask32(mmio_region_t base, | |
| ptrdiff_t offset, uint32_t mask, | |
| uint32_t mask_index); | |
| extern void mmio_region_nonatomic_set_field32(mmio_region_t base, | |
| ptrdiff_t offset, | |
| bitfield_field32_t field, | |
| uint32_t value); | |
| extern void mmio_region_write_only_set_field32(mmio_region_t base, | |
| ptrdiff_t offset, | |
| bitfield_field32_t field, | |
| uint32_t value); | |
| extern void mmio_region_nonatomic_clear_bit32(mmio_region_t base, | |
| ptrdiff_t offset, | |
| uint32_t bit_index); | |
| extern void mmio_region_nonatomic_set_bit32(mmio_region_t base, | |
| ptrdiff_t offset, | |
| uint32_t bit_index); | |
| extern void mmio_region_write_only_set_bit32(mmio_region_t base, | |
| ptrdiff_t offset, | |
| uint32_t bit_index); |