A fork of pulp-os for the xteink4 adding custom apps
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

init: kernel

hansmrtn ba3751c4 00049043

+558
+14
src/kernel/mod.rs
··· 1 + //! Minimal kernel for pulp-os 2 + //! 3 + //! Provides: 4 + //! - Job scheduler with priority queues 5 + //! - Adaptive polling for power efficiency 6 + //! - Sleep/wake primitives 7 + 8 + pub mod poll; 9 + pub mod scheduler; 10 + pub mod wake; 11 + 12 + pub use scheduler::{Job, Priority, PushError, Scheduler}; 13 + pub use poll::{PollRate, AdaptivePoller, BASE_TICK_MS}; 14 + pub use wake::{WakeReason, sleep_until_wake, signal_button, signal_display, signal_timer};
+137
src/kernel/poll.rs
··· 1 + // "Adaptive" polling for power-efficient input handling 2 + // NOTE: Instead of polling attempt to adjust based on activity: 3 + // - active input: poll fast (10ms) for responsive debouncing 4 + // - recently active: poll moderate (50ms) 5 + // - idle: smoll poll (100ms) to save power 6 + 7 + use core::fmt; 8 + 9 + /// Base timer tick interval (ms) 10 + pub const BASE_TICK_MS: u32 = 10; 11 + 12 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 13 + pub enum PollRate { 14 + #[default] 15 + Fast, 16 + Normal, 17 + Slow, 18 + } 19 + 20 + impl PollRate { 21 + // How many base ticks between polls at this rate 22 + pub const fn divisor(self) -> u32 { 23 + match self { 24 + PollRate::Fast => 1, 25 + PollRate::Normal => 5, 26 + PollRate::Slow => 10, 27 + } 28 + } 29 + 30 + // Effective polling interval in milliseconds 31 + pub const fn interval_ms(self) -> u32 { 32 + self.divisor() * BASE_TICK_MS 33 + } 34 + } 35 + 36 + impl fmt::Display for PollRate { 37 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 + match self { 39 + PollRate::Fast => write!(f, "Fast({}ms)", self.interval_ms()), 40 + PollRate::Normal => write!(f, "Normal({}ms)", self.interval_ms()), 41 + PollRate::Slow => write!(f, "Slow({}ms)", self.interval_ms()), 42 + } 43 + } 44 + } 45 + 46 + // Thresholds for rate transitions 47 + mod thresholds { 48 + pub const FAST_TO_NORMAL: u32 = 20; // 20 × 10ms = 200ms 49 + pub const NORMAL_TO_SLOW: u32 = 20; // 20 × 50ms = 1000ms 50 + } 51 + 52 + // Tracks activity and adjusts polling rate accordingly(?) 53 + pub struct AdaptivePoller { 54 + rate: PollRate, 55 + // since last poll 56 + tick_count: u32, 57 + // consecutive idle polls 58 + idle_count: u32, 59 + } 60 + 61 + impl AdaptivePoller { 62 + pub const fn new() -> Self { 63 + Self { 64 + rate: PollRate::Fast, // default to blazingly fast 65 + tick_count: 0, 66 + idle_count: 0, 67 + } 68 + } 69 + 70 + pub fn tick(&mut self) -> bool { 71 + self.tick_count += 1; 72 + 73 + if self.tick_count >= self.rate.divisor() { 74 + self.tick_count = 0; 75 + true 76 + } else { 77 + false 78 + } 79 + } 80 + 81 + pub fn on_activity(&mut self) { 82 + self.rate = PollRate::Fast; 83 + self.idle_count = 0; 84 + } 85 + 86 + pub fn on_idle(&mut self) { 87 + self.idle_count += 1; 88 + 89 + match self.rate { 90 + PollRate::Fast => { 91 + if self.idle_count >= thresholds::FAST_TO_NORMAL { 92 + self.rate = PollRate::Normal; 93 + self.idle_count = 0; 94 + } 95 + } 96 + PollRate::Normal => { 97 + if self.idle_count >= thresholds::NORMAL_TO_SLOW { 98 + self.rate = PollRate::Slow; 99 + // Keep incrementing idle_count but rate won't change further 100 + } 101 + } 102 + PollRate::Slow => { 103 + // Already at slowest rate, nothing to do 104 + } 105 + } 106 + } 107 + 108 + pub fn rate(&self) -> PollRate { 109 + self.rate 110 + } 111 + 112 + pub fn interval_ms(&self) -> u32 { 113 + self.rate.interval_ms() 114 + } 115 + 116 + pub fn idle_count(&self) -> u32 { 117 + self.idle_count 118 + } 119 + 120 + // force a specific rate 121 + pub fn set_rate(&mut self, rate: PollRate) { 122 + self.rate = rate; 123 + self.idle_count = 0; 124 + self.tick_count = 0; 125 + } 126 + 127 + pub fn reset(&mut self) { 128 + *self = Self::new(); 129 + } 130 + } 131 + 132 + impl Default for AdaptivePoller { 133 + fn default() -> Self { 134 + Self::new() 135 + } 136 + } 137 +
+281
src/kernel/scheduler.rs
··· 1 + // A simple priority-based job scheduler for cooperative multitasking 2 + // NOTE: No dynamic allocation and uses fixed-size queues 3 + use core::fmt; 4 + 5 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 + pub enum Job { 7 + HandleInput, 8 + RenderPage, 9 + 10 + PrefetchNext, 11 + PrefetchPrev, 12 + 13 + LayoutChapter { chapter: u16 }, 14 + CacheChapter { chapter: u16 }, 15 + } 16 + 17 + impl fmt::Display for Job { 18 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 + match self { 20 + Job::HandleInput => write!(f, "HandleInput"), 21 + Job::RenderPage => write!(f, "RenderPage"), 22 + Job::PrefetchNext => write!(f, "PrefetchNext"), 23 + Job::PrefetchPrev => write!(f, "PrefetchPrev"), 24 + Job::LayoutChapter { chapter } => write!(f, "LayoutChapter({})", chapter), 25 + Job::CacheChapter { chapter } => write!(f, "CacheChapter({})", chapter), 26 + } 27 + } 28 + } 29 + 30 + /// Job priority levels 31 + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 32 + pub enum Priority { 33 + High = 0, 34 + Normal = 1, 35 + Low = 2, 36 + } 37 + 38 + impl Job { 39 + pub const fn priority(&self) -> Priority { 40 + match self { 41 + Job::HandleInput | Job::RenderPage => Priority::High, 42 + Job::PrefetchNext | Job::PrefetchPrev => Priority::Normal, 43 + Job::LayoutChapter { .. } | Job::CacheChapter { .. } => Priority::Low, 44 + } 45 + } 46 + } 47 + 48 + #[derive(Debug, Clone, Copy)] 49 + pub enum PushError { 50 + /// Queue for this priority level is full, contains the rejected job 51 + Full(Job), 52 + } 53 + 54 + impl fmt::Display for PushError { 55 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 + match self { 57 + PushError::Full(job) => write!(f, "queue full, rejected {}", job), 58 + } 59 + } 60 + } 61 + 62 + // ring buffer for jobs 63 + pub struct JobQueue<const N: usize> { 64 + buf: [Option<Job>; N], 65 + head: usize, // next to read 66 + tail: usize, // next to write 67 + len: usize, 68 + } 69 + 70 + impl<const N: usize> JobQueue<N> { 71 + pub const fn new() -> Self { 72 + Self { 73 + buf: [None; N], 74 + head: 0, 75 + tail: 0, 76 + len: 0, 77 + } 78 + } 79 + 80 + pub fn push(&mut self, job: Job) -> Result<(), Job> { 81 + if self.len >= N { 82 + return Err(job); 83 + } 84 + self.buf[self.tail] = Some(job); 85 + self.tail = (self.tail + 1) % N; 86 + self.len += 1; 87 + Ok(()) 88 + } 89 + 90 + pub fn pop(&mut self) -> Option<Job> { 91 + if self.len == 0 { 92 + return None; 93 + } 94 + let job = self.buf[self.head].take(); 95 + self.head = (self.head + 1) % N; 96 + self.len -= 1; 97 + job 98 + } 99 + 100 + pub fn peek(&self) -> Option<&Job> { 101 + if self.len == 0 { 102 + None 103 + } else { 104 + self.buf[self.head].as_ref() 105 + } 106 + } 107 + 108 + pub fn is_empty(&self) -> bool { 109 + self.len == 0 110 + } 111 + 112 + pub fn is_full(&self) -> bool { 113 + self.len >= N 114 + } 115 + 116 + pub fn len(&self) -> usize { 117 + self.len 118 + } 119 + 120 + pub const fn capacity(&self) -> usize { 121 + N 122 + } 123 + 124 + pub fn clear(&mut self) { 125 + while self.pop().is_some() {} 126 + } 127 + 128 + pub fn contains(&self, job: &Job) -> bool { 129 + if self.len == 0 { 130 + return false; 131 + } 132 + let mut i = self.head; 133 + for _ in 0..self.len { 134 + if let Some(ref j) = self.buf[i] { 135 + if j == job { 136 + return true; 137 + } 138 + } 139 + i = (i + 1) % N; 140 + } 141 + false 142 + } 143 + } 144 + 145 + impl<const N: usize> Default for JobQueue<N> { 146 + fn default() -> Self { 147 + Self::new() 148 + } 149 + } 150 + 151 + // The job scheduler 152 + pub struct Scheduler { 153 + high: JobQueue<4>, 154 + normal: JobQueue<8>, 155 + low: JobQueue<16>, 156 + } 157 + 158 + impl Scheduler { 159 + pub const fn new() -> Self { 160 + Self { 161 + high: JobQueue::new(), 162 + normal: JobQueue::new(), 163 + low: JobQueue::new(), 164 + } 165 + } 166 + 167 + // push a job and returns error with the job if queue is full 168 + pub fn push(&mut self, job: Job) -> Result<(), PushError> { 169 + let result = match job.priority() { 170 + Priority::High => self.high.push(job), 171 + Priority::Normal => self.normal.push(job), 172 + Priority::Low => self.low.push(job), 173 + }; 174 + result.map_err(PushError::Full) 175 + } 176 + 177 + // push a job dropping silently if queue is full 178 + pub fn push_or_drop(&mut self, job: Job) -> bool { 179 + self.push(job).is_ok() 180 + } 181 + 182 + // push a'job but if queue is full, drop the oldest job of same priority 183 + pub fn push_replacing(&mut self, job: Job) { 184 + match job.priority() { 185 + Priority::High => { 186 + if self.high.is_full() { 187 + self.high.pop(); 188 + } 189 + let _ = self.high.push(job); 190 + } 191 + Priority::Normal => { 192 + if self.normal.is_full() { 193 + self.normal.pop(); 194 + } 195 + let _ = self.normal.push(job); 196 + } 197 + Priority::Low => { 198 + if self.low.is_full() { 199 + self.low.pop(); 200 + } 201 + let _ = self.low.push(job); 202 + } 203 + } 204 + } 205 + 206 + // Schedule a job only if it's not already queued (dedup that queue). 207 + pub fn push_unique(&mut self, job: Job) -> Result<(), PushError> { 208 + match job.priority() { 209 + Priority::High => { 210 + if self.high.contains(&job) { 211 + return Ok(()); 212 + } 213 + self.high.push(job).map_err(PushError::Full) 214 + } 215 + Priority::Normal => { 216 + if self.normal.contains(&job) { 217 + return Ok(()); 218 + } 219 + self.normal.push(job).map_err(PushError::Full) 220 + } 221 + Priority::Low => { 222 + if self.low.contains(&job) { 223 + return Ok(()); 224 + } 225 + self.low.push(job).map_err(PushError::Full) 226 + } 227 + } 228 + } 229 + 230 + // the next job to execute 231 + pub fn pop(&mut self) -> Option<Job> { 232 + self.high 233 + .pop() 234 + .or_else(|| self.normal.pop()) 235 + .or_else(|| self.low.pop()) 236 + } 237 + 238 + pub fn peek(&self) -> Option<&Job> { 239 + self.high 240 + .peek() 241 + .or_else(|| self.normal.peek()) 242 + .or_else(|| self.low.peek()) 243 + } 244 + 245 + pub fn is_empty(&self) -> bool { 246 + self.high.is_empty() && self.normal.is_empty() && self.low.is_empty() 247 + } 248 + 249 + pub fn pending(&self) -> usize { 250 + self.high.len() + self.normal.len() + self.low.len() 251 + } 252 + 253 + pub fn pending_by_priority(&self, priority: Priority) -> usize { 254 + match priority { 255 + Priority::High => self.high.len(), 256 + Priority::Normal => self.normal.len(), 257 + Priority::Low => self.low.len(), 258 + } 259 + } 260 + 261 + pub fn clear(&mut self) { 262 + self.high.clear(); 263 + self.normal.clear(); 264 + self.low.clear(); 265 + } 266 + 267 + pub fn clear_priority(&mut self, priority: Priority) { 268 + match priority { 269 + Priority::High => self.high.clear(), 270 + Priority::Normal => self.normal.clear(), 271 + Priority::Low => self.low.clear(), 272 + } 273 + } 274 + } 275 + 276 + impl Default for Scheduler { 277 + fn default() -> Self { 278 + Self::new() 279 + } 280 + } 281 +
+126
src/kernel/wake.rs
··· 1 + use core::sync::atomic::{AtomicBool, Ordering}; 2 + 3 + // Wake source flags (set by ISR and cleared by main loop) 4 + static WAKE_BUTTON: AtomicBool = AtomicBool::new(false); 5 + static WAKE_DISPLAY: AtomicBool = AtomicBool::new(false); 6 + static WAKE_TIMER: AtomicBool = AtomicBool::new(false); 7 + 8 + // who done woke us up from sleep 9 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 + pub enum WakeReason { 11 + Button, 12 + Display, 13 + Timer, 14 + Multiple, 15 + } 16 + 17 + pub fn take_wake_reason() -> Option<WakeReason> { 18 + critical_section::with(|_| { 19 + let button = WAKE_BUTTON.load(Ordering::Relaxed); 20 + let display = WAKE_DISPLAY.load(Ordering::Relaxed); 21 + let timer = WAKE_TIMER.load(Ordering::Relaxed); 22 + 23 + // Clear flags we read 24 + if button { 25 + WAKE_BUTTON.store(false, Ordering::Relaxed); 26 + } 27 + if display { 28 + WAKE_DISPLAY.store(false, Ordering::Relaxed); 29 + } 30 + if timer { 31 + WAKE_TIMER.store(false, Ordering::Relaxed); 32 + } 33 + 34 + match (button, display, timer) { 35 + (false, false, false) => None, 36 + (true, false, false) => Some(WakeReason::Button), 37 + (false, true, false) => Some(WakeReason::Display), 38 + (false, false, true) => Some(WakeReason::Timer), 39 + _ => Some(WakeReason::Multiple), 40 + } 41 + }) 42 + } 43 + 44 + // Check pending waits (without clearing) 45 + pub fn has_pending_wake() -> bool { 46 + WAKE_BUTTON.load(Ordering::Acquire) 47 + || WAKE_DISPLAY.load(Ordering::Acquire) 48 + || WAKE_TIMER.load(Ordering::Acquire) 49 + } 50 + 51 + // Check individual wake sources (without clearing) 52 + pub fn is_button_pending() -> bool { 53 + WAKE_BUTTON.load(Ordering::Acquire) 54 + } 55 + 56 + pub fn is_display_pending() -> bool { 57 + WAKE_DISPLAY.load(Ordering::Acquire) 58 + } 59 + 60 + pub fn is_timer_pending() -> bool { 61 + WAKE_TIMER.load(Ordering::Acquire) 62 + } 63 + 64 + // power button was pressed. 65 + #[inline] 66 + pub fn signal_button() { 67 + WAKE_BUTTON.store(true, Ordering::Release); 68 + } 69 + 70 + // signal that the display finished refreshing. 71 + #[inline] 72 + pub fn signal_display() { 73 + WAKE_DISPLAY.store(true, Ordering::Release); 74 + } 75 + 76 + // signal a timer tick. 77 + #[inline] 78 + pub fn signal_timer() { 79 + WAKE_TIMER.store(true, Ordering::Release); 80 + } 81 + 82 + 83 + #[inline] 84 + pub fn wait_for_interrupt() { 85 + #[cfg(target_arch = "riscv32")] 86 + unsafe { 87 + core::arch::asm!("wfi", options(nomem, nostack)); 88 + } 89 + 90 + // For testing on host 91 + #[cfg(not(target_arch = "riscv32"))] 92 + { 93 + // On host, just yield to simulate 94 + std::thread::yield_now(); 95 + } 96 + } 97 + 98 + pub fn sleep_until_wake() -> WakeReason { 99 + loop { 100 + if let Some(reason) = take_wake_reason() { 101 + return reason; 102 + } 103 + 104 + wait_for_interrupt(); 105 + } 106 + } 107 + 108 + // Non-blocking wake check. 109 + // If there's a pending wake reason, return it. 110 + pub fn try_wake() -> Option<WakeReason> { 111 + take_wake_reason() 112 + } 113 + 114 + pub fn clear_all_flags() { 115 + WAKE_BUTTON.store(false, Ordering::Release); 116 + WAKE_DISPLAY.store(false, Ordering::Release); 117 + WAKE_TIMER.store(false, Ordering::Release); 118 + } 119 + 120 + pub fn pending_flags() -> (bool, bool, bool) { 121 + ( 122 + WAKE_BUTTON.load(Ordering::Acquire), 123 + WAKE_DISPLAY.load(Ordering::Acquire), 124 + WAKE_TIMER.load(Ordering::Acquire), 125 + ) 126 + }