#![no_std]
#![no_main]
use core::fmt::Write;
use core::future::Future;
use core::pin::pin;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use rpi_hal::dma::Dma;
use rpi_hal::sd::{Block, Sd};
use rpi_hal::{halt, irq, lic::Lic, mailbox::Mailbox, pac, sd, timer::Timer, uart::Uart};
const RUN_BLOCKS: usize = 8;
const RUN_WRITE_TEST: bool = true;
const WRITE_TEST_BLOCK: u32 = 0x0010_0000;
#[repr(C, align(64))]
struct Blocks([Block; RUN_BLOCKS]);
static mut REFERENCE: Blocks = Blocks([[0; 512]; RUN_BLOCKS]);
static mut SCRATCH: Blocks = Blocks([[0; 512]; RUN_BLOCKS]);
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
let peripherals = unsafe { pac::Peripherals::steal() };
let mut uart = Uart::init(&peripherals.GPIO, peripherals.UART0);
let _ = writeln!(uart, "PANIC: {info}");
halt();
}
#[no_mangle]
pub extern "C" fn __irq_handler() {
let peripherals = unsafe { pac::Peripherals::steal() };
let lic = Lic::new(peripherals.LIC);
if lic.is_emmc_pending() {
sd::on_irq();
}
}
struct Idle {
wakeups: u32,
parked_us: u64,
}
impl Idle {
fn share_of(&self, elapsed_us: u64) -> u64 {
(self.parked_us * 100).checked_div(elapsed_us).unwrap_or(0)
}
}
fn signal_event() {
unsafe { core::arch::asm!("dsb ish", "sev", options(nomem, nostack)) };
}
fn event_waker() -> Waker {
fn wake(_: *const ()) {
signal_event();
}
fn clone(data: *const ()) -> RawWaker {
RawWaker::new(data, &VTABLE)
}
fn drop(_: *const ()) {}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }
}
fn block_on<F: Future>(future: F, timer: &Timer) -> (F::Output, Idle) {
let mut future = pin!(future);
let waker = event_waker();
let mut context = Context::from_waker(&waker);
let mut idle = Idle {
wakeups: 0,
parked_us: 0,
};
loop {
if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
return (output, idle);
}
let entered = timer.now_micros();
unsafe { core::arch::asm!("wfe", options(nomem, nostack)) };
idle.parked_us += timer.now_micros() - entered;
idle.wakeups += 1;
}
}
fn start_then_cancel<F: Future>(future: F) {
let mut future = pin!(future);
let waker = event_waker();
let mut context = Context::from_waker(&waker);
let _ = future.as_mut().poll(&mut context);
}
fn timed<F>(uart: &mut Uart, timer: &Timer, label: &str, transfer: F) -> bool
where
F: Future<Output = Result<(), sd::Error>>,
{
let started = timer.now_micros();
let (result, idle) = block_on(transfer, timer);
let elapsed = timer.now_micros() - started;
match result {
Ok(()) => {
let _ = writeln!(
uart,
"{label}: ok in {elapsed}us, idle {}us ({}%) over {} wakeup(s)",
idle.parked_us,
idle.share_of(elapsed),
idle.wakeups
);
true
}
Err(e) => {
let _ = writeln!(uart, "{label}: FAILED after {elapsed}us: {e:?}");
false
}
}
}
fn compare(uart: &mut Uart, label: &str, scratch: &Blocks, reference: &Blocks, blocks: usize) {
let (read, expected) = (
scratch.0[..blocks].as_flattened(),
reference.0[..blocks].as_flattened(),
);
let differing = read.iter().zip(expected).filter(|(a, b)| a != b).count();
if differing == 0 {
let _ = writeln!(
uart,
"{label}: PASS, {blocks} block(s) match the blocking read"
);
} else {
let _ = writeln!(uart, "{label}: FAIL, {differing} byte(s) differ");
}
}
#[no_mangle]
pub extern "C" fn kmain() -> ! {
let peripherals = unsafe { pac::Peripherals::steal() };
let mut uart = Uart::init(&peripherals.GPIO, peripherals.UART0);
let timer = Timer::new(peripherals.SYSTMR);
let mut mailbox = Mailbox::new(peripherals.VCMAILBOX);
let lic = Lic::new(peripherals.LIC);
let _ = writeln!(uart, "\r\ninterrupt-driven SD");
let mut sd = match Sd::init(&peripherals.GPIO, peripherals.EMMC, &mut mailbox, &timer) {
Ok(sd) => sd,
Err(e) => {
let _ = writeln!(uart, "SD init failed: {e:?}");
halt();
}
};
let _ = writeln!(
uart,
"card ready ({}, {}-bit bus)",
if sd.high_capacity() {
"SDHC/SDXC"
} else {
"SDSC"
},
if sd.four_bit_bus() { 4 } else { 1 }
);
lic.enable_emmc_irq();
irq::enable_irq();
let mut dma = Dma::new();
let mut channel = dma.channel(5).expect("channel 5 available");
let reference = unsafe { &mut *core::ptr::addr_of_mut!(REFERENCE) };
let scratch = unsafe { &mut *core::ptr::addr_of_mut!(SCRATCH) };
let started = timer.now_micros();
if let Err(e) = sd.read_blocks(0, &mut reference.0, &timer) {
let _ = writeln!(uart, "blocking reference read failed: {e:?}");
halt();
}
let blocking_read_us = timer.now_micros() - started;
let signature = &reference.0[0][510..];
let _ = writeln!(
uart,
"blocking read of {RUN_BLOCKS} blocks: {blocking_read_us}us, no idle time; \
block 0 signature {:02x} {:02x}{}",
signature[0],
signature[1],
if signature == [0x55, 0xaa] {
" (0x55AA)"
} else {
" (unpartitioned card, or a blank region)"
}
);
scratch.0[0] = [0; 512];
if timed(&mut uart, &timer, "async read_block", async {
sd.read_block_async(0, &mut scratch.0[0], &timer).await
}) {
compare(&mut uart, "async read_block", scratch, reference, 1);
}
scratch.0 = [[0; 512]; RUN_BLOCKS];
if timed(&mut uart, &timer, "async read_blocks", async {
sd.read_blocks_async(0, &mut scratch.0, &timer).await
}) {
compare(
&mut uart,
"async read_blocks",
scratch,
reference,
RUN_BLOCKS,
);
}
scratch.0 = [[0; 512]; RUN_BLOCKS];
if timed(&mut uart, &timer, "async read_blocks_dma", async {
sd.read_blocks_dma_async(0, &mut scratch.0, &mut channel, &timer)
.await
}) {
compare(
&mut uart,
"async read_blocks_dma",
scratch,
reference,
RUN_BLOCKS,
);
}
let _ = writeln!(uart, "cancelling a multi-block read after one poll...");
start_then_cancel(sd.read_blocks_async(0, &mut scratch.0, &timer));
scratch.0[0] = [0; 512];
if timed(&mut uart, &timer, "read after cancel", async {
sd.read_block_async(0, &mut scratch.0[0], &timer).await
}) {
compare(&mut uart, "read after cancel", scratch, reference, 1);
}
if RUN_WRITE_TEST {
write_round_trip(&mut sd, &timer, &mut uart, reference, scratch);
}
let _ = writeln!(uart, "done -- halting");
halt();
}
fn write_round_trip(
sd: &mut Sd,
timer: &Timer,
uart: &mut Uart,
reference: &mut Blocks,
scratch: &mut Blocks,
) {
if let Err(e) = sd.read_blocks(WRITE_TEST_BLOCK, &mut reference.0, timer) {
let _ = writeln!(
uart,
"write test: initial read failed: {e:?} (card untouched)"
);
return;
}
for (dst, src) in scratch
.0
.as_flattened_mut()
.iter_mut()
.zip(reference.0.as_flattened())
{
*dst = !*src;
}
let started = timer.now_micros();
let blocking = sd.write_blocks(WRITE_TEST_BLOCK, &scratch.0, timer);
let blocking_us = timer.now_micros() - started;
match blocking {
Ok(()) => {
let _ = writeln!(
uart,
"blocking write_blocks: ok in {blocking_us}us, no idle time"
);
}
Err(e) => {
let _ = writeln!(
uart,
"blocking write_blocks: FAILED after {blocking_us}us: {e:?}"
);
}
}
match sd.read_blocks(WRITE_TEST_BLOCK, &mut scratch.0, timer) {
Ok(()) => {
let landed = scratch
.0
.as_flattened()
.iter()
.zip(reference.0.as_flattened())
.all(|(read, original)| *read == !*original);
let _ = writeln!(
uart,
"blocking write verify: {}",
if landed { "PASS" } else { "FAIL, unchanged" }
);
}
Err(e) => {
let _ = writeln!(uart, "blocking write verify: read failed: {e:?}");
}
}
let restored = timed(uart, timer, "async write_blocks", async {
sd.write_blocks_async(WRITE_TEST_BLOCK, &reference.0, timer)
.await
});
match sd.read_blocks(WRITE_TEST_BLOCK, &mut scratch.0, timer) {
Ok(()) => {
let matches = scratch.0.as_flattened() == reference.0.as_flattened();
let _ = writeln!(
uart,
"async write verify: {}",
if matches {
"PASS, original restored"
} else if restored {
"FAIL, region still inverted"
} else {
"FAIL -- the write itself failed, region left INVERTED"
}
);
}
Err(e) => {
let _ = writeln!(uart, "async write verify: read failed: {e:?}");
}
}
}