diff --git a/luma_core/src/exi/mod.rs b/luma_core/src/exi/mod.rs new file mode 100644 index 0000000..b0ebf00 --- /dev/null +++ b/luma_core/src/exi/mod.rs @@ -0,0 +1,95 @@ +//! ``exi`` module of ``luma_core``. +//! +//! Contains functions to access the EXI bus. + +use crate::io::{read32, write32}; + +pub mod usb_gecko; + +// Are we really on a GameCube in Dolphin? +// TODO: Use 0xcd006800 instead when on a Wii. +const EXI: u32 = 0xcc006800; + +const EXI_CSR: u32 = EXI + 0; +const EXI_DMA_ADDR: u32 = EXI + 4; +const EXI_DMA_SIZE: u32 = EXI + 8; +const EXI_CR: u32 = EXI + 12; +const EXI_DATA: u32 = EXI + 16; + +bitflags::bitflags! { + struct Clock: u32 { + const CLOCK_1MHZ = 0; + const CLOCK_2MHZ = 1; + const CLOCK_4MHZ = 2; + const CLOCK_8MHZ = 3; + const CLOCK_16MHZ = 4; + const CLOCK_32MHZ = 5; + } +} + +bitflags::bitflags! { + struct Cr: u32 { + const TLEN8 = 0x00; + const TLEN16 = 0x10; + const TLEN24 = 0x20; + const TLEN32 = 0x30; + const READ = 0x00; + const WRITE = 0x04; + const RW = 0x08; + const IMMEDIATE = 0x00; + const DMA = 0x02; + const TSTART = 0x01; + } +} + +/// Empty struct which represents initialised access to the EXI bus. +pub struct Exi; + +impl Exi { + /// Initialises the EXI bus. + pub fn init() -> Exi { + let exi = Exi; + for i in 0..2 { + exi.get_channel(i).deselect_device(); + } + exi + } + + fn get_channel(&self, channel: u32) -> Channel { + Channel { exi: self, channel } + } +} + +struct Channel<'a> { + channel: u32, + exi: &'a Exi, +} + +impl<'a> Channel<'a> { + fn select_device(&self, device: u32, clock: Clock) { + write32( + EXI_CSR + 20 * self.channel, + ((1 << device) << 7) | (clock.bits() << 4), + ); + } + + fn deselect_device(&self) { + write32(EXI_CSR + 20 * self.channel, 0); + } + + fn write_data(&self, data: u32) { + write32(EXI_DATA + 20 * self.channel, data); + } + + fn read_data(&self) -> u32 { + read32(EXI_DATA + 20 * self.channel) + } + + fn write_cr(&self, cr: Cr) { + write32(EXI_CR + 20 * self.channel, cr.bits()); + } + + fn wait_for_completion(&self) { + while (read32(EXI_CR + 20 * self.channel) & Cr::TSTART.bits()) != 0 {} + } +} diff --git a/luma_core/src/exi/usb_gecko.rs b/luma_core/src/exi/usb_gecko.rs new file mode 100644 index 0000000..62bb168 --- /dev/null +++ b/luma_core/src/exi/usb_gecko.rs @@ -0,0 +1,109 @@ +//! Contains functions for accessing the USB Gecko. + +use crate::exi::{Channel, Clock, Cr, Exi}; +use alloc::vec::Vec; + +#[derive(Debug)] +pub enum Error { + UsbGeckoNotFound, + SendImpossible, + ReceiveImpossible, +} + +/// Struct which represents an usable USB Gecko. +pub struct UsbGecko<'a> { + channel: Channel<'a>, +} + +impl<'a> UsbGecko<'a> { + /// Try accessing a plugged in USB Gecko, returns Error::UsbGeckoNotFound if none is currently + /// plugged in. + pub fn new(exi: &'a Exi) -> Result, Error> { + let channel = exi.get_channel(1); + let gecko = UsbGecko { channel }; + if !gecko.check_usb_gecko() { + return Err(Error::UsbGeckoNotFound); + } + Ok(gecko) + } + + fn query_usb_gecko(&self, query: u16) -> u16 { + self.channel.select_device(0, Clock::CLOCK_32MHZ); + self.channel.write_data((query as u32) << 16); + self.channel + .write_cr(Cr::TLEN16 | Cr::RW | Cr::IMMEDIATE | Cr::TSTART); + self.channel.wait_for_completion(); + (self.channel.read_data() >> 16) as u16 + } + + fn check_usb_gecko(&self) -> bool { + let query = 0x9000; + let ret = self.query_usb_gecko(query); + ret == 0x0470 + } + + fn receive_byte(&self) -> Result { + let query = 0xa000; + let ret = self.query_usb_gecko(query); + if ret & 0x0800 == 0 { + return Err(Error::ReceiveImpossible); + } + Ok((ret & 0xff) as u8) + } + + fn send_byte(&self, byte: u8) -> Result<(), Error> { + let query = 0xb000 | ((byte as u16) << 4); + let ret = self.query_usb_gecko(query); + if ret & 0x0400 == 0 { + return Err(Error::SendImpossible); + } + Ok(()) + } + + fn can_send(&self) -> bool { + let query = 0xc000; + let ret = self.query_usb_gecko(query); + ret & 0x0400 != 0 + } + + fn can_receive(&self) -> bool { + let query = 0xd000; + let ret = self.query_usb_gecko(query); + ret & 0x0400 != 0 + } + + /// Receive one or more bytes from the USB Gecko and returns them in a Vec. This function + /// returns Error::ReceiveImpossible if no bytes are available. + pub fn receive(&self) -> Result, Error> { + let mut data = Vec::new(); + loop { + if !self.can_receive() { + break; + } + let byte = self.receive_byte()?; + data.push(byte); + } + if data.is_empty() { + return Err(Error::ReceiveImpossible); + } + Ok(data) + } + + /// Send a slice of bytes to the USB Gecko. This function returns Error::SendImpossible if no + /// byte could be transmitted despite the device saying we could, it otherwise blocks until all + /// bytes from the slice have been transmitted. + pub fn send(&self, mut data: &[u8]) -> Result<(), Error> { + loop { + if data.is_empty() { + break; + } + if !self.can_send() { + continue; + } + let byte = data[0]; + data = &data[1..]; + self.send_byte(byte)?; + } + Ok(()) + } +} diff --git a/luma_core/src/lib.rs b/luma_core/src/lib.rs index da5871f..7c68fee 100644 --- a/luma_core/src/lib.rs +++ b/luma_core/src/lib.rs @@ -38,3 +38,6 @@ pub mod allocate; // VI Subsystem pub mod vi; + +// EXI Subsystem +pub mod exi; diff --git a/src/bin/echo-bot.rs b/src/bin/echo-bot.rs new file mode 100644 index 0000000..4daa426 --- /dev/null +++ b/src/bin/echo-bot.rs @@ -0,0 +1,22 @@ +//! This is an example of how to communicate with an USB Gecko using Luma. + +#![no_std] + +extern crate luma_core; +extern crate luma_runtime; + +use luma_core::exi::usb_gecko::UsbGecko; +use luma_core::exi::Exi; + +fn main() { + let exi = Exi::init(); + let gecko = UsbGecko::new(&exi).unwrap(); + loop { + // TODO: use interrupts here, instead of a busy loop. + let buf = match gecko.receive() { + Ok(buf) => buf, + Err(_) => continue, + }; + gecko.send(&buf).unwrap(); + } +}