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 a module for IPC with the Starlet running IOS #22

Draft
wants to merge 4 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
225 changes: 225 additions & 0 deletions luma_core/src/ios.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//! ``ios`` module of ``luma_core``.
//!
//! Contains functions for access to the Starlet when running IOS

use crate::cache::DCFlushRange;
use crate::io::{read32, write32};
use alloc::boxed::Box;

const BASE: u32 = 0x0d000000;

const HW_IPC_PPCMSG: u32 = BASE + 0;
const HW_IPC_PPCCTRL: u32 = BASE + 4;
const HW_IPC_ARMMSG: u32 = BASE + 8;

/// The type of a file descriptor in IOS.
pub type RawFd = i32;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file descriptor is never less then zero, using a u32 would be a better option


/// How to open a file.
#[repr(i32)]
pub enum Mode {
/// With no read/write access.
None = 0,

/// With read only access.
Read = 1,

/// With write only access.
Write = 2,

/// With read/write access.
ReadWrite = 3,
}

/// This is copied from std::io::SeekFrom, with the inner types changed to accomodate IOS.
#[derive(Copy, PartialEq, Eq, Clone, Debug)]
pub enum SeekFrom {
/// From the start of the file.
Start(i32),

/// From the current position.
Current(i32),

/// From the end of the file.
End(i32),
}

#[derive(Debug, Clone, PartialEq)]
#[repr(u32)]
enum Command {
Open = 1,
Close = 2,
Read = 3,
Write = 4,
Seek = 5,
Ioctl = 6,
Ioctlv = 7,
Async = 8,
}

#[derive(Debug, Clone)]
#[repr(C, align(32))]
struct Ipc {
command: Command,
ret: i32,
fd: RawFd,
args: [i32; 5],
}

impl Ipc {
#[inline]
fn new(command: Command, args: [i32; 5]) -> Box<Self> {
Box::new(Ipc {
command,
ret: 0,
fd: -1,
args,
})
}

#[inline]
fn with_fd(command: Command, fd: i32, args: [i32; 5]) -> Box<Self> {
Box::new(Ipc {
command,
ret: 0,
fd,
args,
})
}

#[inline(never)]
fn send(self: Box<Self>) -> Box<Self> {
let ptr = Box::into_raw(self);

// Flush the IPC data from its cache line, so that the Starlet will see it.
unsafe { DCFlushRange(ptr as *const _, core::mem::size_of::<Self>() as u32) };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use try_into here instead of just casting to a u32. maybe using a usize which is the same because of target_ptr_width = 32 could be an option


// Pass the pointer to IOS.
write32(HW_IPC_PPCMSG, ptr as u32 & 0x1fff_ffff);

// Signal to IOS we sent a command.
let ppcctrl = read32(HW_IPC_PPCCTRL);
if ppcctrl & 2 == 2 {
// TODO: Find out why Dolphin signals a command has been acknowledged already on boot.
write32(HW_IPC_PPCCTRL, 3);
} else {
write32(HW_IPC_PPCCTRL, 1);
}

// Busy loop until the Starlet acknowledges our command.
loop {
let ppcctrl = read32(HW_IPC_PPCCTRL);
if ppcctrl & 2 == 2 {
// Our command got acknowledged!
write32(HW_IPC_PPCCTRL, 2);
break;
}
}

// Busy loop until the Starlet replies to our command.
//
// TODO: provide an async API to avoid having to do that, for queries which are quite long.
loop {
let ppcctrl = read32(HW_IPC_PPCCTRL);
if ppcctrl & 4 == 4 {
// We got a reply!
break;
}
}

// Read the reply from IOS.
let armmsg = read32(HW_IPC_ARMMSG);
let command = unsafe { Box::from_raw((armmsg | 0x8000_0000) as *mut Ipc) };
assert_eq!(command.command, Command::Async);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think Command::Async is the right name for this. Maybe change Async to Reply ?


// Acknowledge the reply.
write32(HW_IPC_PPCCTRL, 4);

command
}
}

fn get_physical_and_len(buf: &[u8]) -> (i32, i32) {
let addr = buf.as_ptr();
let len = buf.len() as i32;
unsafe { DCFlushRange(addr as *const _, len as u32) };
(addr as i32 & 0x1fff_ffff, len)
}

/// Open a file and return a new fd.
pub fn open(filename: &str, mode: Mode) -> Result<RawFd, i32> {
let (addr, len) = get_physical_and_len(filename.as_bytes());
// XXX: why 0x40?
assert!(len < 0x40);
ProfElements marked this conversation as resolved.
Show resolved Hide resolved
let request = Ipc::new(Command::Open, [addr, mode as i32, 0, 0, 0]);
let reply = request.send();
if reply.ret < 0 {
Err(reply.ret)
} else {
Ok(reply.ret)
}
}

/// Close an open fd.
pub fn close(fd: RawFd) -> Result<(), i32> {
let request = Ipc::with_fd(Command::Close, fd, [0; 5]);
let reply = request.send();
if reply.ret < 0 {
Err(reply.ret)
} else {
Ok(())
}
}

/// Read from an open fd.
pub fn read(fd: RawFd, buf: &mut [u8]) -> Result<i32, i32> {
let (addr, len) = get_physical_and_len(buf);
let request = Ipc::with_fd(Command::Read, fd, [addr, len, 0, 0, 0]);
let reply = request.send();
if reply.ret < 0 {
Err(reply.ret)
} else {
Ok(reply.ret)
}
}

/// Write into an open fd.
pub fn write(fd: RawFd, buf: &[u8]) -> Result<i32, i32> {
let (addr, len) = get_physical_and_len(buf);
let request = Ipc::with_fd(Command::Write, fd, [addr, len, 0, 0, 0]);
let reply = request.send();
if reply.ret < 0 {
Err(reply.ret)
} else {
Ok(reply.ret)
}
}

/// Seek in an open fd.
pub fn seek(fd: RawFd, pos: SeekFrom) -> Result<(), i32> {
let (pos, whence) = match pos {
SeekFrom::Start(pos) => (pos, 0),
SeekFrom::Current(pos) => (pos, 1),
SeekFrom::End(pos) => (pos, 2),
};
let request = Ipc::with_fd(Command::Seek, fd, [pos, whence, 0, 0, 0]);
let reply = request.send();
if reply.ret < 0 {
Err(reply.ret)
} else {
Ok(())
}
}

/// Perform a specific action on an open fd.
pub fn ioctl(fd: RawFd, num: i32, buf1: &[u8], buf2: &[u8]) -> Result<i32, i32> {
let (addr1, len1) = get_physical_and_len(buf1);
let (addr2, len2) = get_physical_and_len(buf2);
let request = Ipc::with_fd(Command::Ioctl, fd, [num, addr1, len1, addr2, len2]);
let reply = request.send();
if reply.ret < 0 {
Err(reply.ret)
} else {
Ok(reply.ret)
}
}
3 changes: 3 additions & 0 deletions luma_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub mod register;
// Broadway Integer Utilities
pub mod integer;

// Access the Starlet running IOS
pub mod ios;

// Broadway Load and Store Utilities
pub mod loadstore;

Expand Down
13 changes: 3 additions & 10 deletions luma_runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
//!
//! **NOTE**: This is currently in a very experimental state and is subject to change.
#![no_std]
#![feature(asm_experimental_arch, lang_items, alloc_error_handler)]
#![feature(asm_experimental_arch, lang_items)]

extern crate alloc;

use core::arch::global_asm;
use core::fmt::Write;
use core::{alloc::Layout, panic::PanicInfo};
use core::panic::PanicInfo;
use linked_list_allocator::LockedHeap;
#[allow(unused_imports)]
use luma_core::cache::*;
Expand All @@ -35,7 +35,7 @@ global_asm!(include_str!("../asm/system.S"));
/// This is the executable start function, which directly follows the entry point.
#[cfg_attr(not(test), lang = "start")]
#[cfg(not(test))]
fn start<T>(user_main: fn(), _argc: isize, _argv: *const *const u8) -> isize
fn start<T>(user_main: fn() -> T, _argc: isize, _argv: *const *const u8, _sigpipe: u8) -> isize
where
T: Termination,
{
Expand All @@ -54,7 +54,6 @@ where
}

// Jump to user defined main function.
let user_main: fn() -> T = unsafe { core::mem::transmute(user_main) };
user_main();

panic!("main() cannot return");
Expand All @@ -75,12 +74,6 @@ fn panic(info: &PanicInfo) -> ! {
loop {}
}

/// This function is called when the allocator produces an error.
#[cfg_attr(not(test), alloc_error_handler)]
fn alloc_error_handler(_layout: Layout) -> ! {
loop {}
}

/// Error handler personality language item (current no-op, to satisfy clippy).
#[cfg_attr(not(test), lang = "eh_personality")]
#[no_mangle]
Expand Down
14 changes: 14 additions & 0 deletions src/bin/ios.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! This is an example of how to shutdown the Wii using Luma.

#![no_std]

extern crate luma_core;
extern crate luma_runtime;

use luma_core::ios;

fn main() {
let fd = ios::open("/dev/stm/immediate\0", ios::Mode::None).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe using CStr instead of providing the null bytes is a better option here

ios::ioctl(fd, 0x2003, &[], &[]).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hard to read, maybe move 0x2003 to a named const eg. const SOME_IOCTL: u32 = 0x2003

loop {}
}