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..21eb396 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; @@ -34,6 +37,9 @@ pub mod cache; // Helper functions to allocate aligned memory on the heap pub mod allocate; +// /shared2/sys/SYSCONF parser +pub mod sysconf; + // VI Subsystem pub mod vi; diff --git a/luma_core/src/sysconf.rs b/luma_core/src/sysconf.rs new file mode 100644 index 0000000..9606119 --- /dev/null +++ b/luma_core/src/sysconf.rs @@ -0,0 +1,184 @@ +//! ``sysconf`` module of ``luma_core``. +//! +//! Provides access to the Wii system settings, as set by the system menu. + +use crate::ios; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::fmt; + +#[derive(Debug, Clone)] +enum Item { + BigArray(Vec), + SmallArray(Vec), + Byte(u8), + Short(u16), + Long(u32), + LongLong(u64), + Bool(bool), +} + +#[derive(Clone, Copy)] +pub enum AspectRatio { + A4_3, + A16_9, + Unknown, +} + +impl fmt::Debug for AspectRatio { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + AspectRatio::A4_3 => write!(fmt, "4:3"), + AspectRatio::A16_9 => write!(fmt, "16:9"), + AspectRatio::Unknown => write!(fmt, "unknown"), + } + } +} + +#[derive(Clone, Copy)] +pub enum Scan { + Interlaced, + Progressive, + Unknown, +} + +impl fmt::Debug for Scan { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Scan::Interlaced => write!(fmt, "interlaced"), + Scan::Progressive => write!(fmt, "progressive"), + Scan::Unknown => write!(fmt, "unknown"), + } + } +} + +#[derive(Clone, Copy)] +pub enum RefreshRate { + R50, + R60, +} + +impl fmt::Debug for RefreshRate { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + RefreshRate::R50 => write!(fmt, "50 Hz"), + RefreshRate::R60 => write!(fmt, "60 Hz"), + } + } +} + +/// Parsed structure containing the system settings. +pub struct Sysconf(BTreeMap); + +impl Sysconf { + fn parse(data: [u8; 0x4000]) -> Result { + assert_eq!(&data[..4], b"SCv0"); + + let num_items = u16::from_be_bytes([data[4], data[5]]); + let mut offsets = Vec::with_capacity(num_items as usize); + for i in 0..num_items { + let offset = u16::from_be_bytes([data[6 + 2 * i as usize], data[7 + 2 * i as usize]]); + offsets.push(offset); + } + + let mut tree = BTreeMap::new(); + for offset in offsets { + let type_and_name_length = data[offset as usize]; + let type_ = type_and_name_length >> 5; + let name_length = 1 + type_and_name_length & 0x1f; + let name = alloc::str::from_utf8( + &data[offset as usize + 1..offset as usize + 1 + name_length as usize], + )? + .to_string(); + + let offset = offset as usize + 1 + name_length as usize; + match type_ { + 1 => { + let length = u16::from_be_bytes([data[offset], data[offset + 1]]); + let value = &data[offset + 2..offset + 2 + length as usize + 1]; + tree.insert(name, Item::BigArray(value.to_vec())); + } + 2 => { + let length = data[offset]; + let value = &data[offset + 1..offset + 1 + length as usize + 1]; + tree.insert(name, Item::SmallArray(value.to_vec())); + } + 3 => { + let value = data[offset]; + tree.insert(name, Item::Byte(value)); + } + 4 => { + let value = u16::from_be_bytes([data[offset], data[offset + 1]]); + tree.insert(name, Item::Short(value)); + } + 5 => { + let value = u32::from_be_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]); + tree.insert(name, Item::Long(value)); + } + 6 => { + let value = u64::from_be_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + data[offset + 4], + data[offset + 5], + data[offset + 6], + data[offset + 7], + ]); + tree.insert(name, Item::LongLong(value)); + } + 7 => { + let value = data[offset] != 0; + tree.insert(name, Item::Bool(value)); + } + _ => unreachable!("Unknown type {}!", type_), + } + } + Ok(Sysconf(tree)) + } + + /// Return the preferred aspect ratio. + pub fn aspect_ratio(&self) -> AspectRatio { + match self.0.get("IPL.AR") { + Some(Item::Byte(value)) if *value == 0 => AspectRatio::A4_3, + Some(Item::Byte(_)) => AspectRatio::A16_9, + _ => AspectRatio::Unknown, + } + } + + /// Return whether interlaced or progressive scan is preferred. + pub fn progressive(&self) -> Scan { + match self.0.get("IPL.PGS") { + Some(Item::Byte(value)) if *value == 0 => Scan::Interlaced, + Some(Item::Byte(_)) => Scan::Progressive, + _ => Scan::Unknown, + } + } + + /// Return the preferred refresh rate. + pub fn refresh_rate(&self) -> RefreshRate { + match self.0.get("IPL.E60") { + Some(Item::Byte(value)) if *value == 0 => RefreshRate::R50, + _ => RefreshRate::R60, + } + } +} + +/// Read and parse the SYSCONF file from /shared2/sys on the NAND. +pub fn read_and_parse() -> Result { + let fd = ios::open("/shared2/sys/SYSCONF\0", ios::Mode::Read)?; + + let mut buf = [0u8; 0x4000]; + ios::read(fd, &mut buf)?; + ios::close(fd)?; + + // TODO: Use a better way to bubble up errors. + Sysconf::parse(buf).map_err(|_| -103) +} diff --git a/luma_runtime/src/lib.rs b/luma_runtime/src/lib.rs index 67b872a..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::*; @@ -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"); @@ -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] 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 {} +} diff --git a/src/bin/sysconf.rs b/src/bin/sysconf.rs new file mode 100644 index 0000000..38ab1e2 --- /dev/null +++ b/src/bin/sysconf.rs @@ -0,0 +1,20 @@ +//! This is an example of how to read the Wii system settings using Luma. + +#![no_std] + +extern crate alloc; +extern crate luma_core; +extern crate luma_runtime; + +use core::fmt::Write; +use luma_core::println; +use luma_core::sysconf; + +fn main() { + let sysconf = sysconf::read_and_parse().unwrap(); + println!("Aspect ratio {:?}", sysconf.aspect_ratio()); + println!("{:?}", sysconf.refresh_rate()); + println!("{:?}", sysconf.progressive()); + + loop {} +}