Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EXI and USB Gecko support #9

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions luma_core/src/exi/mod.rs
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
109 changes: 109 additions & 0 deletions luma_core/src/exi/usb_gecko.rs
Original file line number Diff line number Diff line change
@@ -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<UsbGecko<'a>, 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<u8, Error> {
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<Vec<u8>, 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(())
}
}
3 changes: 3 additions & 0 deletions luma_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ pub mod allocate;

// VI Subsystem
pub mod vi;

// EXI Subsystem
pub mod exi;
22 changes: 22 additions & 0 deletions src/bin/echo-bot.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}