From 939df1bda27c7c1b69374736e41d2a67c6aebe1f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 11 Apr 2024 22:42:55 +0100 Subject: [PATCH 1/4] Update to 0.8.0 --- CHANGELOG.md | 10 +++++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f69fd..e91d6a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log -## Unreleased changes ([Source](https://github.com/neotron-compute/neotron-os/tree/develop) | [Changes](https://github.com/neotron-compute/neotron-os/compare/v0.7.1...develop)) +## Unreleased changes ([Source](https://github.com/neotron-compute/neotron-os/tree/develop) | [Changes](https://github.com/neotron-compute/neotron-os/compare/v0.8.0...develop)) * None +## v0.8.0 - 2024-04-11 ([Source](https://github.com/neotron-compute/neotron-os/tree/v0.8.0) | [Release](https://github.com/neotron-compute/neotron-os/releases/tag/v0.8.0)) + +* Added a global `FILESYSTEM` object +* Updated to embedded-sdmmc 0.7 +* Updated to Neotron Common BIOS 0.12 +* Add a bitmap viewer command - `gfx` +* Treat text buffer as 32-bit values + ## v0.7.1 - 2023-10-21 ([Source](https://github.com/neotron-compute/neotron-os/tree/v0.7.1) | [Release](https://github.com/neotron-compute/neotron-os/releases/tag/v0.7.1)) * Update `Cargo.lock` so build string no longer shows build as *dirty* diff --git a/Cargo.lock b/Cargo.lock index e5928b9..b001355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,7 @@ checksum = "b9b8634a088b9d5b338a96b3f6ef45a3bc0b9c0f0d562c7d00e498265fd96e8f" [[package]] name = "neotron-os" -version = "0.7.1" +version = "0.8.0" dependencies = [ "chrono", "embedded-sdmmc", diff --git a/Cargo.toml b/Cargo.toml index 43f00a5..19a5f7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "neotron-os" -version = "0.7.1" +version = "0.8.0" authors = [ "Jonathan 'theJPster' Pallant ", "The Neotron Developers" From 78bde4be6251280c3d1f8ca9b946faa90f685256 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 12 Apr 2024 15:51:59 +0100 Subject: [PATCH 2/4] Run command takes arguments. --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/commands/ram.rs | 25 +++++++++++++++++++++---- src/program.rs | 27 ++++++++++++--------------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b001355..ebd1b74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "b03d7f798bfe97329ad6df937951142eec93886b37d87010502dd25e8cc75fd5" [[package]] name = "neotron-api" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c00f842e7006421002e67a53866b90ddd6f7d86137b52d2147f5e38b35e82f" +checksum = "67d6c96706b6f3ec069abfb042cadfd2d701980fa4940f407c0bc28ee1e1c493" dependencies = [ "bitflags", "neotron-ffi", diff --git a/Cargo.toml b/Cargo.toml index 19a5f7f..0e58895 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ chrono = { version = "0.4", default-features = false } embedded-sdmmc = { version = "0.7", default-features = false } heapless = "0.7" menu = "0.3" -neotron-api = "0.1" +neotron-api = "0.2" neotron-common-bios = "0.12.0" neotron-loader = "0.1" pc-keyboard = "0.7" diff --git a/src/commands/ram.rs b/src/commands/ram.rs index a7ebac6..85c5163 100644 --- a/src/commands/ram.rs +++ b/src/commands/ram.rs @@ -24,10 +24,27 @@ pub static HEXDUMP_ITEM: menu::Item = menu::Item { pub static RUN_ITEM: menu::Item = menu::Item { item_type: menu::ItemType::Callback { function: run, - parameters: &[], + parameters: &[ + menu::Parameter::Optional { + parameter_name: "arg1", + help: None, + }, + menu::Parameter::Optional { + parameter_name: "arg2", + help: None, + }, + menu::Parameter::Optional { + parameter_name: "arg3", + help: None, + }, + menu::Parameter::Optional { + parameter_name: "arg4", + help: None, + }, + ], }, command: "run", - help: Some("Jump to start of application area"), + help: Some("Run a program (with up to four arguments)"), }; pub static LOAD_ITEM: menu::Item = menu::Item { @@ -90,8 +107,8 @@ fn hexdump(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], _ctx } /// Called when the "run" command is executed. -fn run(_menu: &menu::Menu, _item: &menu::Item, _args: &[&str], ctx: &mut Ctx) { - match ctx.tpa.execute() { +fn run(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], ctx: &mut Ctx) { + match ctx.tpa.execute(args) { Ok(0) => { osprintln!(); } diff --git a/src/program.rs b/src/program.rs index 5755667..6ffca49 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,6 +1,6 @@ //! Program Loading and Execution -use crate::{osprint, osprintln, FILESYSTEM}; +use crate::{osprintln, FILESYSTEM}; #[allow(unused)] static CALLBACK_TABLE: neotron_api::Api = neotron_api::Api { @@ -230,15 +230,23 @@ impl TransientProgramArea { /// an exit code that is non-zero is not considered a failure from the point /// of view of this API. You wanted to run a program, and the program was /// run. - pub fn execute(&mut self) -> Result { + pub fn execute(&mut self, args: &[&str]) -> Result { if self.last_entry == 0 { return Err(Error::NothingLoaded); } + // We support a maximum of four arguments. + let ffi_args = [ + neotron_api::FfiString::new(args.get(0).unwrap_or(&"")), + neotron_api::FfiString::new(args.get(1).unwrap_or(&"")), + neotron_api::FfiString::new(args.get(2).unwrap_or(&"")), + neotron_api::FfiString::new(args.get(3).unwrap_or(&"")), + ]; + let result = unsafe { - let code: extern "C" fn(*const neotron_api::Api) -> i32 = + let code: neotron_api::AppStartFn = ::core::mem::transmute(self.last_entry as *const ()); - code(&CALLBACK_TABLE) + code(&CALLBACK_TABLE, args.len(), ffi_args.as_ptr()) }; self.last_entry = 0; @@ -278,17 +286,6 @@ impl TransientProgramArea { } } -/// Application API to print things to the console. -#[allow(unused)] -extern "C" fn print_fn(data: *const u8, len: usize) { - let slice = unsafe { core::slice::from_raw_parts(data, len) }; - if let Ok(s) = core::str::from_utf8(slice) { - osprint!("{}", s); - } else { - // Ignore App output - not UTF-8 - } -} - /// Open a file, given a path as UTF-8 string. /// /// If the file does not exist, or is already open, it returns an error. From b6dda07e260b55fd772179bbf7d73ab74bf5a9f9 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 12 Apr 2024 15:56:37 +0100 Subject: [PATCH 3/4] Applications can open/close/read/write files. --- src/fs.rs | 16 +++++ src/program.rs | 183 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 171 insertions(+), 28 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 9adc436..a010968 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -110,6 +110,11 @@ impl File { FILESYSTEM.file_read(self, buffer) } + /// Write to a file + pub fn write(&self, buffer: &[u8]) -> Result<(), Error> { + FILESYSTEM.file_write(self, buffer) + } + /// Are we at the end of the file pub fn is_eof(&self) -> bool { FILESYSTEM @@ -202,6 +207,17 @@ impl Filesystem { Ok(bytes_read) } + /// Write to an open file + pub fn file_write(&self, file: &File, buffer: &[u8]) -> Result<(), Error> { + let mut fs = self.volume_manager.lock(); + if fs.is_none() { + *fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime())); + } + let fs = fs.as_mut().unwrap(); + fs.write(file.inner, buffer)?; + Ok(()) + } + /// How large is a file? pub fn file_length(&self, file: &File) -> Result { let mut fs = self.volume_manager.lock(); diff --git a/src/program.rs b/src/program.rs index 6ffca49..73b1684 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,6 +1,6 @@ //! Program Loading and Execution -use crate::{osprintln, FILESYSTEM}; +use crate::{fs, osprintln, refcell::CsRefCell, FILESYSTEM}; #[allow(unused)] static CALLBACK_TABLE: neotron_api::Api = neotron_api::Api { @@ -27,6 +27,41 @@ static CALLBACK_TABLE: neotron_api::Api = neotron_api::Api { free: api_free, }; +/// The different kinds of state each open handle can be in +pub enum OpenHandle { + /// Represents Standard Input + StdIn, + /// Represents Standard Output + Stdout, + /// Represents Standard Error + StdErr, + /// Represents an open file in the filesystem + File(fs::File), + /// Represents a closed handle. + /// + /// This is the default state for handles. + Closed, +} + +/// The open handle table +/// +/// This is indexed by the file descriptors (or handles) that the application +/// uses. When an application says "write to handle 4", we look at the 4th entry +/// in here to work out what they are writing to. +/// +/// The table is initialised when a program is started, and any open files are +/// closed when the program ends. +static OPEN_HANDLES: CsRefCell<[OpenHandle; 8]> = CsRefCell::new([ + OpenHandle::Closed, + OpenHandle::Closed, + OpenHandle::Closed, + OpenHandle::Closed, + OpenHandle::Closed, + OpenHandle::Closed, + OpenHandle::Closed, + OpenHandle::Closed, +]); + /// Ways in which loading a program can fail. #[derive(Debug)] pub enum Error { @@ -71,7 +106,6 @@ impl FileSource { } fn uncached_read(&self, offset: u32, out_buffer: &mut [u8]) -> Result<(), crate::fs::Error> { - osprintln!("Reading from {}", offset); self.file.seek_from_start(offset)?; self.file.read(out_buffer)?; Ok(()) @@ -96,7 +130,6 @@ impl neotron_loader::traits::Source for &FileSource { } } - osprintln!("Reading from {}", offset); self.file.seek_from_start(offset)?; self.file.read(self.buffer.borrow_mut().as_mut_slice())?; self.offset_cached.set(Some(offset)); @@ -235,7 +268,15 @@ impl TransientProgramArea { return Err(Error::NothingLoaded); } + // Setup the default file handles + let mut open_handles = OPEN_HANDLES.lock(); + open_handles[0] = OpenHandle::StdIn; + open_handles[1] = OpenHandle::Stdout; + open_handles[2] = OpenHandle::StdErr; + drop(open_handles); + // We support a maximum of four arguments. + #[allow(clippy::get_first)] let ffi_args = [ neotron_api::FfiString::new(args.get(0).unwrap_or(&"")), neotron_api::FfiString::new(args.get(1).unwrap_or(&"")), @@ -249,6 +290,13 @@ impl TransientProgramArea { code(&CALLBACK_TABLE, args.len(), ffi_args.as_ptr()) }; + // Close any files the program left open + let mut open_handles = OPEN_HANDLES.lock(); + for h in open_handles.iter_mut() { + *h = OpenHandle::Closed; + } + drop(open_handles); + self.last_entry = 0; Ok(result) } @@ -293,15 +341,46 @@ impl TransientProgramArea { /// Path may be relative to current directory, or it may be an absolute /// path. extern "C" fn api_open( - _path: neotron_api::FfiString, + path: neotron_api::FfiString, _flags: neotron_api::file::Flags, ) -> neotron_api::Result { - neotron_api::Result::Err(neotron_api::Error::Unimplemented) + let f = match FILESYSTEM.open_file(path.as_str(), embedded_sdmmc::Mode::ReadOnly) { + Ok(f) => f, + Err(fs::Error::Io(embedded_sdmmc::Error::NotFound)) => { + return neotron_api::Result::Err(neotron_api::Error::InvalidPath); + } + Err(_e) => { + return neotron_api::Result::Err(neotron_api::Error::DeviceSpecific); + } + }; + + // 1. Put the file into the open handles array and get the index (or return an error) + let mut result = None; + for (idx, slot) in OPEN_HANDLES.lock().iter_mut().enumerate() { + if matches!(*slot, OpenHandle::Closed) { + result = Some(idx); + *slot = OpenHandle::File(f); + break; + } + } + + // 2. give the caller the index into the open handles array + match result { + Some(n) => neotron_api::Result::Ok(neotron_api::file::Handle::new(n as u8)), + None => neotron_api::Result::Err(neotron_api::Error::OutOfMemory), + } } /// Close a previously opened file. -extern "C" fn api_close(_fd: neotron_api::file::Handle) -> neotron_api::Result<()> { - neotron_api::Result::Err(neotron_api::Error::Unimplemented) +extern "C" fn api_close(fd: neotron_api::file::Handle) -> neotron_api::Result<()> { + let mut open_handles = OPEN_HANDLES.lock(); + match open_handles.get_mut(fd.value() as usize) { + Some(h) => { + *h = OpenHandle::Closed; + neotron_api::Result::Ok(()) + } + None => neotron_api::Result::Err(neotron_api::Error::BadHandle), + } } /// Write to an open file handle, blocking until everything is written. @@ -311,19 +390,28 @@ extern "C" fn api_write( fd: neotron_api::file::Handle, buffer: neotron_api::FfiByteSlice, ) -> neotron_api::Result<()> { - if fd == neotron_api::file::Handle::new_stdout() { - let mut guard = crate::VGA_CONSOLE.lock(); - if let Some(console) = guard.as_mut() { - console.write_bstr(buffer.as_slice()); + let mut open_handles = OPEN_HANDLES.lock(); + match open_handles.get_mut(fd.value() as usize) { + Some(OpenHandle::StdErr | OpenHandle::Stdout) => { + // Treat stderr and stdout the same + let mut guard = crate::VGA_CONSOLE.lock(); + if let Some(console) = guard.as_mut() { + console.write_bstr(buffer.as_slice()); + } + let mut guard = crate::SERIAL_CONSOLE.lock(); + if let Some(console) = guard.as_mut() { + // Ignore serial errors on stdout + let _ = console.write_bstr(buffer.as_slice()); + } + neotron_api::Result::Ok(()) } - let mut guard = crate::SERIAL_CONSOLE.lock(); - if let Some(console) = guard.as_mut() { - // Ignore serial errors on stdout - let _ = console.write_bstr(buffer.as_slice()); + Some(OpenHandle::File(f)) => match f.write(buffer.as_slice()) { + Ok(_) => neotron_api::Result::Ok(()), + Err(_e) => neotron_api::Result::Err(neotron_api::Error::DeviceSpecific), + }, + Some(OpenHandle::StdIn | OpenHandle::Closed) | None => { + neotron_api::Result::Err(neotron_api::Error::BadHandle) } - neotron_api::Result::Ok(()) - } else { - neotron_api::Result::Err(neotron_api::Error::BadHandle) } } @@ -334,15 +422,28 @@ extern "C" fn api_read( fd: neotron_api::file::Handle, mut buffer: neotron_api::FfiBuffer, ) -> neotron_api::Result { - if fd == neotron_api::file::Handle::new_stdin() { - if let Some(buffer) = buffer.as_mut_slice() { - let count = { crate::STD_INPUT.lock().get_data(buffer) }; - Ok(count).into() - } else { - neotron_api::Result::Err(neotron_api::Error::DeviceSpecific) + let mut open_handles = OPEN_HANDLES.lock(); + match open_handles.get_mut(fd.value() as usize) { + Some(OpenHandle::StdIn) => { + if let Some(buffer) = buffer.as_mut_slice() { + let count = { crate::STD_INPUT.lock().get_data(buffer) }; + Ok(count).into() + } else { + neotron_api::Result::Err(neotron_api::Error::DeviceSpecific) + } + } + Some(OpenHandle::File(f)) => { + let Some(buffer) = buffer.as_mut_slice() else { + return neotron_api::Result::Err(neotron_api::Error::InvalidArg); + }; + match f.read(buffer) { + Ok(n) => neotron_api::Result::Ok(n), + Err(_e) => neotron_api::Result::Err(neotron_api::Error::DeviceSpecific), + } + } + Some(OpenHandle::Stdout | OpenHandle::StdErr | OpenHandle::Closed) | None => { + neotron_api::Result::Err(neotron_api::Error::BadHandle) } - } else { - neotron_api::Result::Err(neotron_api::Error::BadHandle) } } @@ -418,9 +519,35 @@ extern "C" fn api_stat( /// Get information about an open file extern "C" fn api_fstat( - _fd: neotron_api::file::Handle, + fd: neotron_api::file::Handle, ) -> neotron_api::Result { - neotron_api::Result::Err(neotron_api::Error::Unimplemented) + let mut open_handles = OPEN_HANDLES.lock(); + match open_handles.get_mut(fd.value() as usize) { + Some(OpenHandle::File(f)) => { + let stat = neotron_api::file::Stat { + file_size: f.length() as u64, + ctime: neotron_api::file::Time { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + }, + mtime: neotron_api::file::Time { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + }, + attr: neotron_api::file::Attributes::empty(), + }; + neotron_api::Result::Ok(stat) + } + _ => neotron_api::Result::Err(neotron_api::Error::InvalidArg), + } } /// Delete a file. From 78b64b9e10d04a0ea419361eb96a0cfccdd45d10 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 12 Apr 2024 21:53:15 +0100 Subject: [PATCH 4/4] Add some Audio APIs --- src/program.rs | 174 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 148 insertions(+), 26 deletions(-) diff --git a/src/program.rs b/src/program.rs index 73b1684..8523c8e 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,6 +1,8 @@ //! Program Loading and Execution -use crate::{fs, osprintln, refcell::CsRefCell, FILESYSTEM}; +use neotron_api::FfiByteSlice; + +use crate::{fs, osprintln, refcell::CsRefCell, API, FILESYSTEM}; #[allow(unused)] static CALLBACK_TABLE: neotron_api::Api = neotron_api::Api { @@ -41,6 +43,8 @@ pub enum OpenHandle { /// /// This is the default state for handles. Closed, + /// Represents the audio device, + Audio, } /// The open handle table @@ -334,6 +338,17 @@ impl TransientProgramArea { } } +/// Store an open handle, or fail if we're out of space +fn allocate_handle(h: OpenHandle) -> Result { + for (idx, slot) in OPEN_HANDLES.lock().iter_mut().enumerate() { + if matches!(*slot, OpenHandle::Closed) { + *slot = h; + return Ok(idx); + } + } + Err(h) +} + /// Open a file, given a path as UTF-8 string. /// /// If the file does not exist, or is already open, it returns an error. @@ -344,6 +359,19 @@ extern "C" fn api_open( path: neotron_api::FfiString, _flags: neotron_api::file::Flags, ) -> neotron_api::Result { + // Check for special devices + if path.as_str().eq_ignore_ascii_case("AUDIO:") { + match allocate_handle(OpenHandle::Audio) { + Ok(n) => { + return neotron_api::Result::Ok(neotron_api::file::Handle::new(n as u8)); + } + Err(_f) => { + return neotron_api::Result::Err(neotron_api::Error::OutOfMemory); + } + } + } + + // OK, let's assume it's a file relative to the root of our one and only volume let f = match FILESYSTEM.open_file(path.as_str(), embedded_sdmmc::Mode::ReadOnly) { Ok(f) => f, Err(fs::Error::Io(embedded_sdmmc::Error::NotFound)) => { @@ -355,19 +383,9 @@ extern "C" fn api_open( }; // 1. Put the file into the open handles array and get the index (or return an error) - let mut result = None; - for (idx, slot) in OPEN_HANDLES.lock().iter_mut().enumerate() { - if matches!(*slot, OpenHandle::Closed) { - result = Some(idx); - *slot = OpenHandle::File(f); - break; - } - } - - // 2. give the caller the index into the open handles array - match result { - Some(n) => neotron_api::Result::Ok(neotron_api::file::Handle::new(n as u8)), - None => neotron_api::Result::Err(neotron_api::Error::OutOfMemory), + match allocate_handle(OpenHandle::File(f)) { + Ok(n) => neotron_api::Result::Ok(neotron_api::file::Handle::new(n as u8)), + Err(_f) => neotron_api::Result::Err(neotron_api::Error::OutOfMemory), } } @@ -391,8 +409,11 @@ extern "C" fn api_write( buffer: neotron_api::FfiByteSlice, ) -> neotron_api::Result<()> { let mut open_handles = OPEN_HANDLES.lock(); - match open_handles.get_mut(fd.value() as usize) { - Some(OpenHandle::StdErr | OpenHandle::Stdout) => { + let Some(h) = open_handles.get_mut(fd.value() as usize) else { + return neotron_api::Result::Err(neotron_api::Error::BadHandle); + }; + match h { + OpenHandle::StdErr | OpenHandle::Stdout => { // Treat stderr and stdout the same let mut guard = crate::VGA_CONSOLE.lock(); if let Some(console) = guard.as_mut() { @@ -405,11 +426,27 @@ extern "C" fn api_write( } neotron_api::Result::Ok(()) } - Some(OpenHandle::File(f)) => match f.write(buffer.as_slice()) { + OpenHandle::File(f) => match f.write(buffer.as_slice()) { Ok(_) => neotron_api::Result::Ok(()), Err(_e) => neotron_api::Result::Err(neotron_api::Error::DeviceSpecific), }, - Some(OpenHandle::StdIn | OpenHandle::Closed) | None => { + OpenHandle::Audio => { + let api = API.get(); + let mut slice = buffer.as_slice(); + // loop until we've sent all of it + while !slice.is_empty() { + let result = unsafe { (api.audio_output_data)(FfiByteSlice::new(slice)) }; + let this_time = match result { + neotron_common_bios::FfiResult::Ok(n) => n, + neotron_common_bios::FfiResult::Err(_e) => { + return neotron_api::Result::Err(neotron_api::Error::DeviceSpecific); + } + }; + slice = &slice[this_time..]; + } + neotron_api::Result::Ok(()) + } + OpenHandle::StdIn | OpenHandle::Closed => { neotron_api::Result::Err(neotron_api::Error::BadHandle) } } @@ -423,8 +460,11 @@ extern "C" fn api_read( mut buffer: neotron_api::FfiBuffer, ) -> neotron_api::Result { let mut open_handles = OPEN_HANDLES.lock(); - match open_handles.get_mut(fd.value() as usize) { - Some(OpenHandle::StdIn) => { + let Some(h) = open_handles.get_mut(fd.value() as usize) else { + return neotron_api::Result::Err(neotron_api::Error::BadHandle); + }; + match h { + OpenHandle::StdIn => { if let Some(buffer) = buffer.as_mut_slice() { let count = { crate::STD_INPUT.lock().get_data(buffer) }; Ok(count).into() @@ -432,7 +472,7 @@ extern "C" fn api_read( neotron_api::Result::Err(neotron_api::Error::DeviceSpecific) } } - Some(OpenHandle::File(f)) => { + OpenHandle::File(f) => { let Some(buffer) = buffer.as_mut_slice() else { return neotron_api::Result::Err(neotron_api::Error::InvalidArg); }; @@ -441,7 +481,17 @@ extern "C" fn api_read( Err(_e) => neotron_api::Result::Err(neotron_api::Error::DeviceSpecific), } } - Some(OpenHandle::Stdout | OpenHandle::StdErr | OpenHandle::Closed) | None => { + OpenHandle::Audio => { + let api = API.get(); + let result = unsafe { (api.audio_input_data)(buffer) }; + match result { + neotron_common_bios::FfiResult::Ok(n) => neotron_api::Result::Ok(n), + neotron_common_bios::FfiResult::Err(_e) => { + neotron_api::Result::Err(neotron_api::Error::DeviceSpecific) + } + } + } + OpenHandle::Stdout | OpenHandle::StdErr | OpenHandle::Closed => { neotron_api::Result::Err(neotron_api::Error::BadHandle) } } @@ -483,12 +533,84 @@ extern "C" fn api_rename( } /// Perform a special I/O control operation. +/// +/// # Audio Devices +/// +/// * `0` - get output sample rate/format (0xN000_0000_) where N indicates the sample format +/// * N = 0 => Eight bit mono, one byte per sample +/// * N = 1 => Eight bit stereo, two byte per samples +/// * N = 2 => Sixteen bit mono, two byte per samples +/// * N = 3 => Sixteen bit stereo, four byte per samples +/// * `1` - set output sample rate/format +/// * As above +/// * `2` - get output sample space available +/// * Gets a value in bytes extern "C" fn api_ioctl( - _fd: neotron_api::file::Handle, - _command: u64, - _value: u64, + fd: neotron_api::file::Handle, + command: u64, + value: u64, ) -> neotron_api::Result { - neotron_api::Result::Err(neotron_api::Error::Unimplemented) + let mut open_handles = OPEN_HANDLES.lock(); + let Some(h) = open_handles.get_mut(fd.value() as usize) else { + return neotron_api::Result::Err(neotron_api::Error::BadHandle); + }; + let api = API.get(); + match (h, command) { + (OpenHandle::Audio, 0) => { + // Getting sample rate + let neotron_common_bios::FfiResult::Ok(config) = (api.audio_output_get_config)() else { + return neotron_api::Result::Err(neotron_api::Error::DeviceSpecific); + }; + let mut result: u64 = config.sample_rate_hz as u64; + let nibble = match config.sample_format.make_safe() { + Ok(neotron_common_bios::audio::SampleFormat::EightBitMono) => 0, + Ok(neotron_common_bios::audio::SampleFormat::EightBitStereo) => 1, + Ok(neotron_common_bios::audio::SampleFormat::SixteenBitMono) => 2, + Ok(neotron_common_bios::audio::SampleFormat::SixteenBitStereo) => 3, + _ => { + return neotron_api::Result::Err(neotron_api::Error::DeviceSpecific); + } + }; + result |= nibble << 60; + neotron_api::Result::Ok(result) + } + (OpenHandle::Audio, 1) => { + // Setting sample rate + let sample_rate = value as u32; + let format = match value >> 60 { + 0 => neotron_common_bios::audio::SampleFormat::EightBitMono, + 1 => neotron_common_bios::audio::SampleFormat::EightBitStereo, + 2 => neotron_common_bios::audio::SampleFormat::SixteenBitMono, + 3 => neotron_common_bios::audio::SampleFormat::SixteenBitStereo, + _ => { + return neotron_api::Result::Err(neotron_api::Error::InvalidArg); + } + }; + let config = neotron_common_bios::audio::Config { + sample_format: format.make_ffi_safe(), + sample_rate_hz: sample_rate, + }; + match (api.audio_output_set_config)(config) { + neotron_common_bios::FfiResult::Ok(_) => { + osprintln!("audio {}, {:?}", sample_rate, format); + neotron_api::Result::Ok(0) + } + neotron_common_bios::FfiResult::Err(_) => { + neotron_api::Result::Err(neotron_api::Error::DeviceSpecific) + } + } + } + (OpenHandle::Audio, 2) => { + // Setting sample space + match (api.audio_output_get_space)() { + neotron_common_bios::FfiResult::Ok(n) => neotron_api::Result::Ok(n as u64), + neotron_common_bios::FfiResult::Err(_) => { + neotron_api::Result::Err(neotron_api::Error::DeviceSpecific) + } + } + } + _ => neotron_api::Result::Err(neotron_api::Error::InvalidArg), + } } /// Open a directory, given a path as a UTF-8 string.