blob: 063423662f2a932259d4e48b2b08ab1e84274019 [file] [log] [blame]
// 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 *)&current_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, &current_word, bytes_to_copy);
} else {
// When writing to MMIO, we need to write the modified word.
memcpy(&current_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);