mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 10:51:30 +11:00
Start towards 0.4
This commit is contained in:
parent
20cd551198
commit
dcd7e40b15
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "gba"
|
||||
description = "A crate (and book) for making GBA games with Rust."
|
||||
version = "0.3.2"
|
||||
version = "0.4.0"
|
||||
authors = ["Lokathor <zefria@gmail.com>", "Thomas Winwood <twwinwood@gmail.com>"]
|
||||
repository = "https://github.com/rust-console/gba"
|
||||
readme = "README.md"
|
||||
|
@ -9,10 +9,11 @@ keywords = ["gba"]
|
|||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
|
||||
#publish = false
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
typenum = "1.10"
|
||||
voladdress = "0.2"
|
||||
gba-proc-macro = "0.5"
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Holds fundamental types/ops which the rest of the crate it built on.
|
||||
//! Holds fundamental types/ops which the rest of the crate is built on.
|
||||
//!
|
||||
//! These things should probably, in time, be extracted out into their own
|
||||
//! crate (or convert to using a robust existing crate).
|
||||
|
||||
pub mod fixed_point;
|
||||
//pub(crate) use self::fixed_point::*;
|
||||
|
||||
pub mod volatile;
|
||||
pub(crate) use self::volatile::*;
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
//! Holds types for correct handling of volatile memory.
|
||||
|
||||
use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize};
|
||||
|
||||
// TODO: striding block/iter
|
||||
|
||||
/// Abstracts the use of a volatile hardware address.
|
||||
///
|
||||
/// If you're trying to do anything other than abstract a volatile hardware
|
||||
/// device then you _do not want to use this type_. Use one of the many other
|
||||
/// smart pointer types.
|
||||
///
|
||||
/// A volatile address doesn't store a value in the normal way: It maps to some
|
||||
/// real hardware _other than_ RAM, and that hardware might have any sort of
|
||||
/// strange rules. The specifics of reading and writing depend on the hardware
|
||||
/// being mapped. For example, a particular address might be read only (ignoring
|
||||
/// writes), write only (returning some arbitrary value if you read it),
|
||||
/// "normal" read write (where you read back what you wrote), or some complex
|
||||
/// read-write situation where writes have an effect but you _don't_ read back
|
||||
/// what you wrote.
|
||||
///
|
||||
/// As you imagine it can be very unsafe. The design of this type is set up so
|
||||
/// that _creation_ is unsafe, and _use_ is safe. This gives an optimal
|
||||
/// experience, since you'll use memory locations a lot more often than you try
|
||||
/// to name them, on average.
|
||||
///
|
||||
/// `VolAddress` is _not_ a thread safe type. If your device is multi-threaded
|
||||
/// then you must arrange for synchronization in some other way. A `VolAddress`
|
||||
/// _can_ be used to share data between an interrupt running on a core and a
|
||||
/// thread running on that core as long as all access of that location is
|
||||
/// volatile (if you're using the `asm!` macro add the "volatile" option, if
|
||||
/// you're linking in ASM with the linker that's effectively volatile since the
|
||||
/// compiler doesn't get a chance to mess with it).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// In order for values of this type to operate correctly they must follow quite
|
||||
/// a few safety limits:
|
||||
///
|
||||
/// * The declared address must be non-null (it uses the `NonNull` optimization
|
||||
/// for better iteration results). This shouldn't be a big problem, since
|
||||
/// hardware can't really live at the null address.
|
||||
/// * The declared address must be aligned for the declared type of `T`.
|
||||
/// * The declared address must _always_ read as something that's a valid bit
|
||||
/// pattern for `T`. Don't pick any enums or things like that if your hardware
|
||||
/// doesn't back it up. If there's _any_ doubt at all, you must instead read
|
||||
/// or write an unsigned int of the correct bit size and then parse the bits
|
||||
/// by hand.
|
||||
/// * The declared address must be a part of the address space that Rust's
|
||||
/// allocator and/or stack frames will never use. If you're not sure, please
|
||||
/// re-read the hardware specs of your device and its memory map until you
|
||||
/// know.
|
||||
///
|
||||
/// The exact points of UB are if the address is ever 0, or if you ever `read`
|
||||
/// or `write` with the invalid pointer. For example, if you offset to some
|
||||
/// crazy (non-zero) value and then never use it that won't be an immediate
|
||||
/// trigger of UB.
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct VolAddress<T> {
|
||||
address: NonZeroUsize,
|
||||
marker: PhantomData<*mut T>,
|
||||
}
|
||||
// Note(Lokathor): We have to hand implement all these traits because if we use
|
||||
// `derive` then they only get derived if the inner `T` has the trait. However,
|
||||
// since we're acting like a pointer to `T`, the capability we offer isn't
|
||||
// affected by whatever type `T` ends up being.
|
||||
impl<T> Clone for VolAddress<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl<T> Copy for VolAddress<T> {}
|
||||
impl<T> PartialEq for VolAddress<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.address == other.address
|
||||
}
|
||||
}
|
||||
impl<T> Eq for VolAddress<T> {}
|
||||
impl<T> PartialOrd for VolAddress<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.address.cmp(&other.address))
|
||||
}
|
||||
}
|
||||
impl<T> Ord for VolAddress<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.address.cmp(&other.address)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VolAddress<T> {
|
||||
/// Constructs a new address.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must follow the standard safety rules as outlined in the type docs.
|
||||
pub const unsafe fn new_unchecked(address: usize) -> Self {
|
||||
VolAddress {
|
||||
address: NonZeroUsize::new_unchecked(address),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts the type of `T` into type `Z`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must follow the standard safety rules as outlined in the type docs.
|
||||
pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
|
||||
VolAddress {
|
||||
address: self.address,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Offsets the address by `offset` slots (like `pointer::wrapping_offset`).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must follow the standard safety rules as outlined in the type docs.
|
||||
pub const unsafe fn offset(self, offset: isize) -> Self {
|
||||
VolAddress {
|
||||
address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::<T>())),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the current target type of this address is aligned at this
|
||||
/// address value.
|
||||
///
|
||||
/// Technically it's a safety violation to even make a `VolAddress` that isn't
|
||||
/// aligned. However, I know you're gonna try doing the bad thing, and it's
|
||||
/// better to give you a chance to call `is_aligned` and potentially back off
|
||||
/// from the operation or throw a `debug_assert!` or something instead of
|
||||
/// triggering UB. Eventually this will be `const fn`, which will potentially
|
||||
/// let you spot errors without even having to run your program.
|
||||
pub const fn is_aligned(self) -> bool {
|
||||
self.address.get() % core::mem::align_of::<T>() == 0
|
||||
}
|
||||
|
||||
/// Makes an iterator starting here across the given number of slots.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The normal safety rules must be correct for each address iterated over.
|
||||
pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
|
||||
VolAddressIter { vol_address: self, slots }
|
||||
}
|
||||
|
||||
// non-const and never can be.
|
||||
|
||||
/// Reads a `Copy` value out of the address.
|
||||
///
|
||||
/// The `Copy` bound is actually supposed to be `!Drop`, but rust doesn't
|
||||
/// allow negative trait bounds. If your type isn't `Copy` you can use the
|
||||
/// `read_non_copy` fallback to do an unsafe read.
|
||||
///
|
||||
/// That said, I don't think that you legitimately have hardware that maps to
|
||||
/// a Rust type with a `Drop` impl. If you do please tell me, I'm interested
|
||||
/// to hear about it.
|
||||
pub fn read(self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
unsafe { (self.address.get() as *mut T).read_volatile() }
|
||||
}
|
||||
|
||||
/// Reads a value out of the address with no trait bound.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This is _not_ a move, it forms a bit duplicate of the current address
|
||||
/// value. If `T` has a `Drop` trait that does anything it is up to you to
|
||||
/// ensure that repeated drops do not cause UB (such as a double free).
|
||||
pub unsafe fn read_non_copy(self) -> T {
|
||||
(self.address.get() as *mut T).read_volatile()
|
||||
}
|
||||
|
||||
/// Writes a value to the address.
|
||||
///
|
||||
/// Semantically, the value is moved into the `VolAddress` and then forgotten,
|
||||
/// so if `T` has a `Drop` impl then that will never get executed. This is
|
||||
/// "safe" under Rust's safety rules, but could cause something unintended
|
||||
/// (eg: a memory leak).
|
||||
pub fn write(self, val: T) {
|
||||
unsafe { (self.address.get() as *mut T).write_volatile(val) }
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator that produces a series of `VolAddress` values.
|
||||
#[derive(Debug)]
|
||||
pub struct VolAddressIter<T> {
|
||||
vol_address: VolAddress<T>,
|
||||
slots: usize,
|
||||
}
|
||||
impl<T> Clone for VolAddressIter<T> {
|
||||
fn clone(&self) -> Self {
|
||||
VolAddressIter {
|
||||
vol_address: self.vol_address,
|
||||
slots: self.slots,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for VolAddressIter<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.vol_address == other.vol_address && self.slots == other.slots
|
||||
}
|
||||
}
|
||||
impl<T> Eq for VolAddressIter<T> {}
|
||||
impl<T> Iterator for VolAddressIter<T> {
|
||||
type Item = VolAddress<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.slots > 0 {
|
||||
let out = self.vol_address;
|
||||
unsafe {
|
||||
self.slots -= 1;
|
||||
self.vol_address = self.vol_address.offset(1);
|
||||
}
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> FusedIterator for VolAddressIter<T> {}
|
||||
|
||||
/// This type is like `VolAddress`, but for when you have a block of values all
|
||||
/// in a row.
|
||||
///
|
||||
/// This is similar to the idea of an array or a slice, but called a "block"
|
||||
/// because you could _also_ construct a `[VolAddress]`, and we want to avoid
|
||||
/// any accidental confusion.
|
||||
#[derive(Debug)]
|
||||
pub struct VolAddressBlock<T> {
|
||||
vol_address: VolAddress<T>,
|
||||
slots: usize,
|
||||
}
|
||||
impl<T> Clone for VolAddressBlock<T> {
|
||||
fn clone(&self) -> Self {
|
||||
VolAddressBlock {
|
||||
vol_address: self.vol_address,
|
||||
slots: self.slots,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for VolAddressBlock<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.vol_address == other.vol_address && self.slots == other.slots
|
||||
}
|
||||
}
|
||||
impl<T> Eq for VolAddressBlock<T> {}
|
||||
|
||||
impl<T> VolAddressBlock<T> {
|
||||
/// Constructs a new `VolAddressBlock`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The given `VolAddress` must be valid when offset by each of `0 .. slots`
|
||||
pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
|
||||
VolAddressBlock { vol_address, slots }
|
||||
}
|
||||
|
||||
/// Gives an iterator over this block's slots.
|
||||
pub const fn iter(self) -> VolAddressIter<T> {
|
||||
VolAddressIter {
|
||||
vol_address: self.vol_address,
|
||||
slots: self.slots,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unchecked indexing into the block.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The slot given must be in bounds.
|
||||
pub const unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
|
||||
self.vol_address.offset(slot as isize)
|
||||
}
|
||||
|
||||
/// Checked "indexing" style access of the block, giving either a `VolAddress` or a panic.
|
||||
pub fn index(self, slot: usize) -> VolAddress<T> {
|
||||
if slot < self.slots {
|
||||
unsafe { self.vol_address.offset(slot as isize) }
|
||||
} else {
|
||||
panic!("Index Requested: {} >= Slot Count: {}", slot, self.slots)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checked "getting" style access of the block, giving an Option value.
|
||||
pub fn get(self, slot: usize) -> Option<VolAddress<T>> {
|
||||
if slot < self.slots {
|
||||
unsafe { Some(self.vol_address.offset(slot as isize)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue