diff --git a/RELEASE-v0.9.md b/RELEASE-v0.9.md index 7a3e95350..c82cd2ca7 100644 --- a/RELEASE-v0.9.md +++ b/RELEASE-v0.9.md @@ -174,6 +174,14 @@ perform the Xous firmware upgrade. This requires running manual update commands, - Autobacklight feature: selectable feature to automatically raise the backlight when keys are pressed. Thanks to gsora for the feature! - More importantly, this adds `crossbeam` into the kernel. This is a somewhat heavyweight item and is not recommended for use in core services, but by including it to drive the auto-backlight feature, we get regular code coverage of the condvar pathway. +- Fix PDDB FSCB generation problems + - some issue were found in free space cache generation, these are now fixed + - fuzzer added to push many more corner cases than before -- disk is filled to ~75% capacity with dozens of Bases, + triggering roughly 4 FSCB ops, and then all Bases and keys are verified to be correct. +- Optimize GAM performance + - context switches that revert to a prior app screen are restored from a stashed bitmap instead of a full redraw. This + prevents old menus, contexts, and defacements from "piling up" when an app is blocked due to an exceptional condition + being raised (such as the PDDB requesting a full basis unlock to generate the FSCB) ## Roadmap to 1.0 diff --git a/apps/ball/src/main.rs b/apps/ball/src/main.rs index 44f6bb775..9a189bc7f 100644 --- a/apps/ball/src/main.rs +++ b/apps/ball/src/main.rs @@ -55,6 +55,7 @@ fn main() -> ! { // this is the main event loop for the app. let mut allow_redraw = true; + let mut into_allow_redraw = false; loop { let msg = xous::receive_message(sid).unwrap(); match FromPrimitive::from_usize(msg.body.id()) { @@ -65,6 +66,10 @@ fn main() -> ! { } } Some(AppOp::Pump) => { // this is a blocking scalar so that pump calls don't initiate faster than the app can draw + if into_allow_redraw { + ball.focus(); + into_allow_redraw = false; + } if allow_redraw { ball.update(); } @@ -81,6 +86,7 @@ fn main() -> ! { }), Some(AppOp::FocusChange) => xous::msg_scalar_unpack!(msg, new_state_code, _, _, _, { let new_state = gam::FocusState::convert_focus_change(new_state_code); + log::info!("focus change: {:?}", new_state); match new_state { gam::FocusState::Background => { allow_redraw = false; // this instantly terminates future updates, even if Pump messages are in our input queue @@ -90,6 +96,7 @@ fn main() -> ! { ).expect("couldn't send stop message to the pump thread"); } gam::FocusState::Foreground => { + into_allow_redraw = true; allow_redraw = true; xous::send_message( cid_to_pump, diff --git a/apps/vault/src/main.rs b/apps/vault/src/main.rs index 82118d9f3..36e4a15e7 100644 --- a/apps/vault/src/main.rs +++ b/apps/vault/src/main.rs @@ -373,7 +373,7 @@ fn main() -> ! { Some(VaultOp::ChangeFocus) => xous::msg_scalar_unpack!(msg, new_state_code, _, _, _, { let new_state = gam::FocusState::convert_focus_change(new_state_code); vaultux.change_focus_to(&new_state); - log::info!("change focus: {:?}", new_state); + log::debug!("change focus: {:?}", new_state); match new_state { gam::FocusState::Background => { allow_redraw = false; diff --git a/services/gam/src/contexts.rs b/services/gam/src/contexts.rs index ac2ebe8f5..6b13d8e00 100644 --- a/services/gam/src/contexts.rs +++ b/services/gam/src/contexts.rs @@ -92,6 +92,7 @@ pub(crate) struct ContextManager { contexts: HashMap::<[u32; 4], UxContext>, focused_context: Option<[u32; 4]>, // app_token of the app that has I/O focus, if any last_context: Option<[u32; 4]>, // previously focused context, if any + context_stack: Vec::<[u32; 4]>, imef: ime_plugin_api::ImeFrontEnd, imef_active: bool, kbd: keyboard::Keyboard, @@ -112,6 +113,7 @@ impl ContextManager { ContextManager { tm: TokenManager::new(&xns), contexts: HashMap::new(), + context_stack: Vec::new(), focused_context: None, last_context: None, imef, @@ -325,7 +327,10 @@ impl ContextManager { token: [u32; 4], clear: bool, ) -> Result<(), xous::Error> { + self.notify_app_switch(token).ok(); + let mut leaving_visibility: bool = false; + let stack_on_entry = self.context_stack.len(); { // using a temp copy of the old focus, check if we need to update any visibility state let maybe_leaving_focused_context = if let Some(focused_token) = self.focused_context { @@ -357,22 +362,27 @@ impl ContextManager { (leaving_focused_context.layout.behavior() == LayoutBehavior::App) { context.layout.set_visibility_state(true, canvases); leaving_visibility = false; + self.context_stack.pop(); + self.context_stack.push(token); } else if // alert covering an app (context.layout.behavior() == LayoutBehavior::Alert) && (leaving_focused_context.layout.behavior() == LayoutBehavior::App) { context.layout.set_visibility_state(true, canvases); leaving_visibility = true; + self.context_stack.push(token); } else if // app covering an alert (context.layout.behavior() == LayoutBehavior::App) && (leaving_focused_context.layout.behavior() == LayoutBehavior::Alert) { context.layout.set_visibility_state(true, canvases); leaving_visibility = false; + self.context_stack.pop(); } } } else { // there was no current focus, just make the activation visible log::debug!("setting first-time visibility to context {:?}", token); context.layout.set_visibility_state(true, canvases); + self.context_stack.push(token); } } } @@ -448,12 +458,27 @@ impl ContextManager { self.last_context = self.focused_context; self.focused_context = Some(last_token); } + log::trace!("context stack: {:x?}", self.context_stack); + if self.context_stack.len() > 1 { // we've now got a stack of contexts, start stashing copies + log::trace!("stashing"); + gfx.stash(true); + } // run the defacement before we redraw all the canvases if deface(gfx, &self.trng, canvases) { log::trace!("activate triggered a defacement"); } log::trace!("activate redraw"); - self.redraw().expect("couldn't redraw the currently focused app"); + if self.context_stack.len() > 0 && stack_on_entry > 1 { + if self.context_stack[self.context_stack.len() - 1] == token { + // if we're returning to the previous context, just pop the image + log::trace!("popping"); + gfx.pop(true); + } else { + self.redraw().expect("couldn't redraw the currently focused app"); + } + } else { + self.redraw().expect("couldn't redraw the currently focused app"); + } } Ok(()) } @@ -477,19 +502,24 @@ impl ContextManager { } } pub(crate) fn notify_app_switch(&self, new_app_token: [u32; 4]) -> Result<(), xous::Error> { - if let Some(old_context) = self.get_context_by_token(self.focused_context.unwrap()) { - if let Some(focuschange_id) = old_context.focuschange_id { - log::trace!("Background focus change to {}, id {}", old_context.listener, old_context.redraw_id); - xous::send_message(old_context.listener, - xous::Message::new_scalar(focuschange_id as usize, gam::FocusState::Background as usize, 0, 0, 0) - ).map(|_| ())?; - } else { - return Err(xous::Error::ServerNotFound); + log::trace!("notify app switch to {:?}", new_app_token); + if let Some(current_focus) = self.focused_context { + if let Some(old_context) = self.get_context_by_token(current_focus) { + if let Some(focuschange_id) = old_context.focuschange_id { + log::trace!("Background focus change to {}, id {} / {:?}", old_context.listener, old_context.redraw_id, current_focus); + xous::send_message(old_context.listener, + xous::Message::new_scalar(focuschange_id as usize, gam::FocusState::Background as usize, 0, 0, 0) + ).map(|_| ())?; + } else { + // don't return an error -- this just means that the listener doesn't recognize focus changes. This should not + // deprive the later app of a notification that it is coming into focus! + } } } + if let Some(new_context) = self.get_context_by_token(new_app_token) { if let Some(focuschange_id) = new_context.focuschange_id { - log::trace!("Foreground focus change to {}, id {}", new_context.listener, new_context.redraw_id); + log::trace!("Foreground focus change to {}, id {} / {:?}", new_context.listener, new_context.redraw_id, new_app_token); xous::send_message(new_context.listener, xous::Message::new_scalar(focuschange_id as usize, gam::FocusState::Foreground as usize, 0, 0, 0) ).map(|_| ())?; @@ -622,7 +652,7 @@ impl ContextManager { pub(crate) fn toggle_menu_mode(&mut self, token: [u32; 4]) { if let Some(context) = self.contexts.get_mut(&token) { context.imef_menu_mode = !context.imef_menu_mode; - log::info!("menu mode for token {:?} is now {}", token, context.imef_menu_mode); + log::debug!("menu mode for token {:?} is now {}", token, context.imef_menu_mode); } } pub(crate) fn raise_menu(&mut self, diff --git a/services/gam/src/main.rs b/services/gam/src/main.rs index f6c62e635..20ebf1d64 100644 --- a/services/gam/src/main.rs +++ b/services/gam/src/main.rs @@ -75,6 +75,7 @@ fn wrapped_main() -> ! { let status_canvas = Canvas::new( Rectangle::new_coords( 0, 0, screensize.x, + // note: if this gets modified, the "pop" routine in gfx/backend/betrusted.rs also needs to be updated gfx.glyph_height_hint(GlyphStyle::Cjk).expect("couldn't get glyph height") as i16 * 2), 255, &trng, None, crate::api::CanvasType::Status ).expect("couldn't create status canvas"); @@ -567,8 +568,6 @@ fn wrapped_main() -> ! { for switchers in authorized_switchers { if let Some(auth_token) = context_mgr.find_app_token_by_name(switchers) { if auth_token == switchapp.token { - context_mgr.notify_app_switch(new_app_token) - .unwrap_or_else(|_| {log::warn!("Application does not recognize focus changes")}); match context_mgr.activate(&gfx, &mut canvases, new_app_token, false) { Ok(_) => (), Err(_) => log::warn!("failed to switch to {}, silent error!", switchapp.app_name.as_str().unwrap()), diff --git a/services/graphics-server/src/api.rs b/services/graphics-server/src/api.rs index 0167fd0bb..b83abe479 100644 --- a/services/graphics-server/src/api.rs +++ b/services/graphics-server/src/api.rs @@ -108,6 +108,11 @@ pub(crate) enum Opcode { BulkReadFonts, RestartBulkRead, + /// sling the framebuffer into and out of the suspend/resume area, abusing this + /// to help accelerate redraws between modal swaps. + Stash, + Pop, + /// generates a test pattern TestPattern, diff --git a/services/graphics-server/src/backend/betrusted.rs b/services/graphics-server/src/backend/betrusted.rs index 2b8c22613..fe0125510 100644 --- a/services/graphics-server/src/backend/betrusted.rs +++ b/services/graphics-server/src/backend/betrusted.rs @@ -1,6 +1,6 @@ use crate::api::Point; use crate::api::{LINES, WIDTH}; -use susres::{ManagedMem, RegManager, RegOrField, SuspendResume}; +use susres::{RegManager, RegOrField, SuspendResume}; use utralib::generated::*; use xous::MemoryRange; @@ -13,7 +13,7 @@ const CONFIG_CLOCK_FREQUENCY: u32 = 100_000_000; pub struct XousDisplay { fb: MemoryRange, hwfb: MemoryRange, - srfb: ManagedMem<{ utralib::generated::HW_MEMLCD_MEM_LEN / core::mem::size_of::() }>, + srfb: [u32; FB_SIZE], csr: utralib::CSR, susres: RegManager<{ utra::memlcd::MEMLCD_NUMREGS }>, } @@ -61,7 +61,7 @@ impl XousDisplay { hwfb, csr: CSR::new(control.as_mut_ptr() as *mut u32), susres: RegManager::new(control.as_mut_ptr() as *mut u32), - srfb: ManagedMem::new(fb), + srfb: [0u32; FB_SIZE], }; display.set_clock(CONFIG_CLOCK_FREQUENCY); @@ -101,18 +101,38 @@ impl XousDisplay { ) } + pub fn stash(&mut self) { + let fb = unsafe{core::slice::from_raw_parts_mut(self.fb.as_mut_ptr() as *mut u32, FB_SIZE)}; + for lines in 0..FB_LINES { + // set the dirty bits prior to stashing the frame buffer + fb[lines * FB_WIDTH_WORDS + (FB_WIDTH_WORDS - 1)] |= 0x1_0000; + } + let srfb_ptr = self.srfb.as_mut_ptr() as *mut u32; + for (index, &src) in fb.iter().enumerate() { + unsafe{srfb_ptr.add(index).write_volatile(src)}; + } + } + pub fn pop(&mut self) { + let fb: &mut [u32] = self.fb.as_slice_mut(); + // skip copying the status bar, so that the status info is not overwritten by the pop. + // this is "fixed" at 32 pixels high (2 * Cjk glyph height hint) per line 79 in gam/src/main.rs + fb[FB_WIDTH_WORDS * 32..FB_SIZE].copy_from_slice(&self.srfb[FB_WIDTH_WORDS * 32..FB_SIZE]); + self.redraw(); + } + pub fn suspend(&mut self) { while self.busy() { // just wait until any pending FB operations are done } - let fb: *mut [u32; FB_SIZE] = self.fb.as_mut_ptr() as *mut [u32; FB_SIZE]; + let fb = unsafe{core::slice::from_raw_parts_mut(self.fb.as_mut_ptr() as *mut u32, FB_SIZE)}; for lines in 0..FB_LINES { // set the dirty bits prior to stashing the frame buffer - unsafe { - (*fb)[lines * FB_WIDTH_WORDS + (FB_WIDTH_WORDS - 1)] |= 0x1_0000; - } + fb[lines * FB_WIDTH_WORDS + (FB_WIDTH_WORDS - 1)] |= 0x1_0000; + } + let srfb_ptr = self.srfb.as_mut_ptr() as *mut u32; + for (index, &src) in fb.iter().enumerate() { + unsafe{srfb_ptr.add(index).write_volatile(src)}; } - self.srfb.suspend(); self.susres.suspend(); let note = crate::sleep_note::LOGO_MAP; @@ -154,7 +174,8 @@ impl XousDisplay { } pub fn resume(&mut self) { self.susres.resume(); - self.srfb.resume(); + let fb: &mut [u32] = self.fb.as_slice_mut(); + fb[..FB_SIZE].copy_from_slice(&self.srfb); self.redraw(); } diff --git a/services/graphics-server/src/backend/minifb.rs b/services/graphics-server/src/backend/minifb.rs index 56f045b71..86e42fbe1 100644 --- a/services/graphics-server/src/backend/minifb.rs +++ b/services/graphics-server/src/backend/minifb.rs @@ -20,6 +20,7 @@ const LIGHT_COLOUR: u32 = 0x1B1B19; pub struct XousDisplay { native_buffer: Vec, //[u32; WIDTH * HEIGHT], emulated_buffer: [u32; FB_SIZE], + srfb: [u32; FB_SIZE], window: Window, devboot: bool, } @@ -70,6 +71,7 @@ impl XousDisplay { native_buffer, window, emulated_buffer: [0u32; FB_SIZE], + srfb: [0u32; FB_SIZE], devboot: true, } } @@ -82,6 +84,15 @@ impl XousDisplay { pub fn suspend(&self) {} pub fn resume(&self) {} + pub fn stash(&mut self) { + self.srfb.copy_from_slice(&self.emulated_buffer); + } + pub fn pop(&mut self) { + self.emulated_buffer[FB_WIDTH_WORDS*32..].copy_from_slice(&self.srfb[FB_WIDTH_WORDS*32..]); + self.redraw(); + self.update(); + } + pub fn screen_size(&self) -> Point { Point::new(WIDTH as i16, HEIGHT as i16) } diff --git a/services/graphics-server/src/lib.rs b/services/graphics-server/src/lib.rs index 825950ca5..f895b230c 100644 --- a/services/graphics-server/src/lib.rs +++ b/services/graphics-server/src/lib.rs @@ -268,6 +268,38 @@ impl Gfx { ) .expect("couldn't self test"); } + + pub fn stash(&self, blocking: bool) { + if blocking { + send_message( + self.conn, + Message::new_blocking_scalar(Opcode::Stash.to_usize().unwrap(), 0, 0, 0, 0) + ) + .expect("couldn't stash"); + } else { + send_message( + self.conn, + Message::new_scalar(Opcode::Stash.to_usize().unwrap(), 0, 0, 0, 0) + ) + .expect("couldn't stash"); + } + } + + pub fn pop(&self, blocking: bool) { + if blocking { + send_message( + self.conn, + Message::new_blocking_scalar(Opcode::Pop.to_usize().unwrap(), 0, 0, 0, 0) + ) + .expect("couldn't stash"); + } else { + send_message( + self.conn, + Message::new_scalar(Opcode::Pop.to_usize().unwrap(), 0, 0, 0, 0) + ) + .expect("couldn't stash"); + } + } } use core::sync::atomic::{AtomicU32, Ordering}; diff --git a/services/graphics-server/src/main.rs b/services/graphics-server/src/main.rs index db0b6cfd0..9b35f4e17 100644 --- a/services/graphics-server/src/main.rs +++ b/services/graphics-server/src/main.rs @@ -606,6 +606,20 @@ fn wrapped_main() -> ! { xous::return_scalar(msg.sender, duration).expect("couldn't ack test pattern"); }), + Some(Opcode::Stash) => { + display.stash(); + match msg.body { // ack the message if it's a blocking scalar + xous::Message::BlockingScalar(_) => xous::return_scalar(msg.sender, 1).unwrap(), + _ => () + } + } + Some(Opcode::Pop) => { + display.pop(); + match msg.body { // ack the message if it's a blocking scalar + xous::Message::BlockingScalar(_) => xous::return_scalar(msg.sender, 1).unwrap(), + _ => () + } + } Some(Opcode::Quit) => break, None => { log::error!("received opcode scalar that is not handled"); diff --git a/services/shellchat/src/cmds/pddb_cmd.rs b/services/shellchat/src/cmds/pddb_cmd.rs index 2e3ff4d9e..ff3f4a720 100644 --- a/services/shellchat/src/cmds/pddb_cmd.rs +++ b/services/shellchat/src/cmds/pddb_cmd.rs @@ -195,7 +195,7 @@ impl<'a> ShellCmdApi<'a> for PddbCmd { "dictlist" => { match self.pddb.list_dict(None) { Ok(list) => { - let checked_len = if list.len() > 6 { + let checked_len = if list.len() > 10 { write!(ret, "First 10 dicts of {}:", list.len()).unwrap(); 10 } else { @@ -594,7 +594,7 @@ impl<'a> ShellCmdApi<'a> for PddbCmd { // 128k chunks of junk const JUNK_CHUNK: usize = 131072; log::info!("fill junk"); - for index in 0..28 { // write ~3 megs of junk, should trigger FSCB unlock at least once... + for index in 0..17 { // write ~2 megs of junk, should trigger FSCB unlock at least once... let mut junk = Vec::::new(); // 128k chunk of junk for i in 0..JUNK_CHUNK {