[include][reg.h] define new mmio_read/write accessors

To work properly with some hypervisors on various architectures (ARM,
ARM64, x86), add global routines to allow access to MMIO registers via
architecturally defined accessors.

Add accessors for ARM, ARM64, and x86-32/64. Have the other arches
default to just using whatever the compiler emits.

Will need to generally move things off the legacy REG*() accessors
since they're really not safe going forward with what compilers emit.
This commit is contained in:
Travis Geiselbrecht
2024-05-13 00:28:27 -07:00
parent 356e9adc01
commit 86267ca23c
9 changed files with 275 additions and 9 deletions

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
// Accessor routines for this architecture, used in include/reg.h
//
// Simple load/store instructions with a basic addressing mode of either
// [reg], [reg, #imm] or [reg, reg], with no writeback are guaranteed to be
// fully decoded into ESR_EL2 for hypervisor assist.
#define ARCH_MMIO_READ_WRITE_OVERRIDE 1
#define _ARCH_MMIO_READ8(addr) ({ \
uint8_t val; \
__asm__ volatile("ldrb %0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ16(addr) ({ \
uint16_t val; \
__asm__ volatile("ldrh %0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ32(addr) ({ \
uint32_t val; \
__asm__ volatile("ldr %0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_WRITE8(addr, val) \
__asm__ volatile("strb %1, %0" : "=m"(*(addr)) : "r"(val) : "memory")
#define _ARCH_MMIO_WRITE16(addr, val) \
__asm__ volatile("strh %1, %0" : "=m"(*(addr)) : "r"(val) : "memory")
#define _ARCH_MMIO_WRITE32(addr, val) \
__asm__ volatile("str %1, %0" : "=m"(*(addr)) : "r"(val) : "memory")

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
// Accessor routines for this architecture, used in include/reg.h
//
// Simple load/store instructions with a basic addressing mode of either
// [reg], [reg, #imm] or [reg, reg], with no writeback are guaranteed to be
// fully decoded into ESR_EL2 for hypervisor assist.
#define ARCH_MMIO_READ_WRITE_OVERRIDE 1
#define _ARCH_MMIO_READ8(addr) ({ \
uint8_t val; \
__asm__ volatile("ldrb %w0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ16(addr) ({ \
uint16_t val; \
__asm__ volatile("ldrh %w0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ32(addr) ({ \
uint32_t val; \
__asm__ volatile("ldr %w0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ64(addr) ({ \
uint64_t val; \
__asm__ volatile("ldr %0, %1" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_WRITE8(addr, val) \
__asm__ volatile("strb %w1, %0" : "=m"(*(addr)) : "r"(val) : "memory")
#define _ARCH_MMIO_WRITE16(addr, val) \
__asm__ volatile("strh %w1, %0" : "=m"(*(addr)): "r"(val) : "memory")
#define _ARCH_MMIO_WRITE32(addr, val) \
__asm__ volatile("str %w1, %0" : "=m"(*(addr)) : "r"(val) : "memory")
#define _ARCH_MMIO_WRITE64(addr, val) \
__asm__ volatile("str %1, %0" : "=m"(*(addr)) : "r"(val) : "memory")

View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once

View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once

View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once

View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once

View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
// Accessor routines for this architecture, used in include/reg.h
//
// Simple mov instructions that directly access the mmio address using the
// precise width needed in one instruction.
//
// NOTE: this may not be strict enough, and it may be necessary to load the
// address into a register and use a simple (reg) dereference, instead of a more
// complicated #imm(reg, reg, #imm) addressing mode.
#define ARCH_MMIO_READ_WRITE_OVERRIDE 1
#define _ARCH_MMIO_READ8(addr) ({ \
uint8_t val; \
__asm__ volatile("movb %1, %0" : "=q"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ16(addr) ({ \
uint16_t val; \
__asm__ volatile("movw %1, %0" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#define _ARCH_MMIO_READ32(addr) ({ \
uint32_t val; \
__asm__ volatile("movl %1, %0" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#if _LP64
#define _ARCH_MMIO_READ64(addr) ({ \
uint64_t val; \
__asm__ volatile("movq %1, %0" : "=r"(val) : "m"(*(addr)) : "memory"); \
val; \
})
#endif
#define _ARCH_MMIO_WRITE8(addr, val) \
__asm__ volatile("movb %1, %0" : "=m"(*(addr)) : "iq"(val) : "memory")
#define _ARCH_MMIO_WRITE16(addr, val) \
__asm__ volatile("movw %1, %0" : "=m"(*(addr)) : "ir"(val) : "memory")
#define _ARCH_MMIO_WRITE32(addr, val) \
__asm__ volatile("movl %1, %0" : "=m"(*(addr)) : "ir"(val) : "memory")
#if _LP64
#define _ARCH_MMIO_WRITE64(addr, val) \
__asm__ volatile("movq %1, %0" : "=m"(*(addr)) : "er"(val) : "memory")
#endif

View File

@@ -9,18 +9,92 @@
#include <stdint.h>
/* low level macros for accessing memory mapped hardware registers */
// Low level macros for accessing memory mapped hardware registers
// Each architecture has an ability to provide their own specific accessors, or use the default
#include <arch/reg.h>
// Newer style accessors, generally preferred due to virtual machine virtualization.
//
// If the arch didn't override it, use the default
#if !ARCH_MMIO_READ_WRITE_OVERRIDE
#define _MMIO_READ_DEFAULT(ptr) *ptr
#define _MMIO_WRITE_DEFAULT(ptr, val) *ptr = val
#define _ARCH_MMIO_READ8 _MMIO_READ_DEFAULT
#define _ARCH_MMIO_READ16 _MMIO_READ_DEFAULT
#define _ARCH_MMIO_READ32 _MMIO_READ_DEFAULT
#define _ARCH_MMIO_READ64 _MMIO_READ_DEFAULT
#define _ARCH_MMIO_WRITE8 _MMIO_WRITE_DEFAULT
#define _ARCH_MMIO_WRITE16 _MMIO_WRITE_DEFAULT
#define _ARCH_MMIO_WRITE32 _MMIO_WRITE_DEFAULT
#define _ARCH_MMIO_WRITE64 _MMIO_WRITE_DEFAULT
#endif
static inline uint8_t mmio_read8(volatile uint8_t *ptr) {
return _ARCH_MMIO_READ8(ptr);
}
static inline uint16_t mmio_read16(volatile uint16_t *ptr) {
return _ARCH_MMIO_READ16(ptr);
}
static inline uint32_t mmio_read32(volatile uint32_t *ptr) {
return _ARCH_MMIO_READ32(ptr);
}
#if _LP64
static inline uint64_t mmio_read64(volatile uint64_t *ptr) {
return _ARCH_MMIO_READ64(ptr);
}
#endif
// For architectures that do not need stricter accessor instructions
static inline void mmio_write8(volatile uint8_t *ptr, uint8_t val) {
_ARCH_MMIO_WRITE8(ptr, val);
}
static inline void mmio_write16(volatile uint16_t *ptr, uint16_t val) {
_ARCH_MMIO_WRITE16(ptr, val);
}
static inline void mmio_write32(volatile uint32_t *ptr, uint32_t val) {
_ARCH_MMIO_WRITE32(ptr, val);
}
#if _LP64
static inline void mmio_write64(volatile uint64_t *ptr, uint64_t val) {
_ARCH_MMIO_WRITE64(ptr, val);
}
#endif
// TODO: define C++ accessors here
// Older style accessors:
// NOTE: These are not generally VM safe, since some architectures require specific instruction
// forms to work properly in a trap-and-emulate situation.
#define REG64(addr) ((volatile uint64_t *)(uintptr_t)(addr))
#define REG32(addr) ((volatile uint32_t *)(uintptr_t)(addr))
#define REG16(addr) ((volatile uint16_t *)(uintptr_t)(addr))
#define REG8(addr) ((volatile uint8_t *)(uintptr_t)(addr))
#define RMWREG64(addr, startbit, width, val) *REG64(addr) = (*REG64(addr) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit))
#define RMWREG32(addr, startbit, width, val) *REG32(addr) = (*REG32(addr) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit))
#define RMWREG16(addr, startbit, width, val) *REG16(addr) = (*REG16(addr) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit))
#define RMWREG8(addr, startbit, width, val) *REG8(addr) = (*REG8(addr) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit))
#define RMWREG64(addr, startbit, width, val) mmio_write64((volatile void *)(addr), (mmio_read64((volatile void *)(addr)) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit)))
#define RMWREG32(addr, startbit, width, val) mmio_write32((volatile void *)(addr), (mmio_read32((volatile void *)(addr)) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit)))
#define RMWREG16(addr, startbit, width, val) mmio_write16((volatile void *)(addr), (mmio_read16((volatile void *)(addr)) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit)))
#define RMWREG8(addr, startbit, width, val) mmio_write8((volatile void *)(addr), (mmio_read8((volatile void *)(addr)) & ~(((1<<(width)) - 1) << (startbit))) | ((val) << (startbit)))
// Linux-style accessors
#define readb(a) mmio_read8((volatile uint8_t *)(a))
#define readw(a) mmio_read16((volatile uint16_t *)(a))
#define readl(a) mmio_read32((volatile uint32_t *)(a))
#if _LP64
#define readq(a) mmio_read64((volatile uint64_t *)(a))
#endif
#define writeb(v, a) mmio_write8((volatile uint8_t *)(a), v)
#define writew(v, a) mmio_write16((volatile uint16_t *)(a), v)
#define writel(v, a) mmio_write32((volatile uint32_t *)(a), v)
#if _LP64
#define writeq(v, a) mmio_write64((volatile uint64_t *)(a), v)
#endif
#define writel(v, a) (*REG32(a) = (v))
#define readl(a) (*REG32(a))
#define writeb(v, a) (*REG8(a) = (v))
#define readb(a) (*REG8(a))