From a6b266c4449a38656dffdfb5bd721a87e7466dcf Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 19 Apr 2023 12:47:21 +0200 Subject: [PATCH 1/4] Fix runtime build on current nightly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libstd’s runtime changed the prototype of lang = "start" in ddee45e1d7fd34563c13513d974f792fae41a2f7 to support changing the SIGPIPE behaviour on Linux, so this function now takes a third argument on every platform. Additionally, it now supports user_main() returning any Termination, so we don’t need a transmute() any longer. :) --- luma_runtime/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/luma_runtime/src/lib.rs b/luma_runtime/src/lib.rs index 67b872a..1ba73ac 100644 --- a/luma_runtime/src/lib.rs +++ b/luma_runtime/src/lib.rs @@ -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(user_main: fn(), _argc: isize, _argv: *const *const u8) -> isize +fn start(user_main: fn() -> T, _argc: isize, _argv: *const *const u8, _sigpipe: u8) -> isize where T: Termination, { @@ -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"); From e281604abb9b2f1b4075b2a92ebed1c2043e38f6 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 23 Apr 2023 16:52:32 +0200 Subject: [PATCH 2/4] Remove removed alloc_error_handler feature usage This fixes the build on current nightly (since 2023-04-22). --- luma_runtime/src/lib.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/luma_runtime/src/lib.rs b/luma_runtime/src/lib.rs index 1ba73ac..fce8164 100644 --- a/luma_runtime/src/lib.rs +++ b/luma_runtime/src/lib.rs @@ -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::*; @@ -74,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] From d895bac1772ecf923ec3f459b80de121edaf7e28 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 21 Apr 2023 03:23:16 +0200 Subject: [PATCH 3/4] Add a module for IPC with the Starlet running IOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This lets us open file descriptors and perform actions on them. MINI uses a different protocol, so this can’t currently be used to talk with this Starlet firmware, see https://wiibrew.org/wiki/MINI TODO: Figure out why it only works on Dolphin and not on a real Wii. --- luma_core/src/ios.rs | 225 +++++++++++++++++++++++++++++++++++++++++++ luma_core/src/lib.rs | 3 + 2 files changed, 228 insertions(+) create mode 100644 luma_core/src/ios.rs diff --git a/luma_core/src/ios.rs b/luma_core/src/ios.rs new file mode 100644 index 0000000..024a394 --- /dev/null +++ b/luma_core/src/ios.rs @@ -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; + +/// 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 { + Box::new(Ipc { + command, + ret: 0, + fd: -1, + args, + }) + } + + #[inline] + fn with_fd(command: Command, fd: i32, args: [i32; 5]) -> Box { + Box::new(Ipc { + command, + ret: 0, + fd, + args, + }) + } + + #[inline(never)] + fn send(self: Box) -> Box { + 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::() as u32) }; + + // 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); + + // 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 { + let (addr, len) = get_physical_and_len(filename.as_bytes()); + // XXX: why 0x40? + assert!(len < 0x40); + 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 { + 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 { + 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 { + 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) + } +} diff --git a/luma_core/src/lib.rs b/luma_core/src/lib.rs index 297c992..b556429 100644 --- a/luma_core/src/lib.rs +++ b/luma_core/src/lib.rs @@ -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; From 5df190f1b788f1495e891c7a29414d81e976ffef Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 21 Apr 2023 18:10:15 +0200 Subject: [PATCH 4/4] Add an example shutting down the Wii This is done using the /dev/stm/immediate device, see https://wiibrew.org/wiki//dev/stm/immediate --- src/bin/ios.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/bin/ios.rs diff --git a/src/bin/ios.rs b/src/bin/ios.rs new file mode 100644 index 0000000..8667851 --- /dev/null +++ b/src/bin/ios.rs @@ -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(); + ios::ioctl(fd, 0x2003, &[], &[]).unwrap(); + loop {} +}