| // 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" |
| |
| /** |
| * 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, |
| uint8_t *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 mmio_region_t mmio_region_from_addr(uintptr_t address); |
| 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 void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset, |
| uint8_t value); |
| extern void mmio_region_write32_shadowed(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); |