mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2026-07-23 21:32:22 +00:00
WIP
This commit is contained in:
@@ -16,7 +16,7 @@ use goblin::{
|
|||||||
elf32::{reloc::Rel, section_header::SectionHeader, sym::Sym},
|
elf32::{reloc::Rel, section_header::SectionHeader, sym::Sym},
|
||||||
};
|
};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use userlib_sys::{EntryFn, SyscallTable};
|
use userlib_sys::{EntryFn, SYS_CALL_TABLE_COUNT, SyscallTable};
|
||||||
|
|
||||||
const ELF32_HDR_SIZE: usize = 52;
|
const ELF32_HDR_SIZE: usize = 52;
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ pub enum LoadError {
|
|||||||
ElfIsNotPie,
|
ElfIsNotPie,
|
||||||
UnknownRelocationType,
|
UnknownRelocationType,
|
||||||
SyscallTableNotFound,
|
SyscallTableNotFound,
|
||||||
|
SyscallTableSizeMismatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async unsafe fn load_binary(name: &ShortFileName) -> Result<(EntryFn, Bump), LoadError> {
|
pub async unsafe fn load_binary(name: &ShortFileName) -> Result<(EntryFn, Bump), LoadError> {
|
||||||
@@ -212,6 +213,15 @@ fn patch_syscalls(
|
|||||||
|
|
||||||
let symbol_name = core::str::from_utf8(&name).expect("symbol was not utf8");
|
let symbol_name = core::str::from_utf8(&name).expect("symbol was not utf8");
|
||||||
if symbol_name == stringify!(SYS_CALL_TABLE) {
|
if symbol_name == stringify!(SYS_CALL_TABLE) {
|
||||||
|
// The binary was linked against a different SyscallTable
|
||||||
|
// than this kernel (e.g. built before a syscall was
|
||||||
|
// added) -- writing the full table would overrun its
|
||||||
|
// `.syscall_table` array and corrupt whatever follows it.
|
||||||
|
let expected_size = SYS_CALL_TABLE_COUNT * size_of::<usize>();
|
||||||
|
if sym.st_size as usize != expected_size {
|
||||||
|
return Err(LoadError::SyscallTableSizeMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
let table_base =
|
let table_base =
|
||||||
unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) }
|
unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) }
|
||||||
as *mut usize;
|
as *mut usize;
|
||||||
@@ -235,6 +245,8 @@ fn patch_syscalls(
|
|||||||
}
|
}
|
||||||
SyscallTable::AudioBufferReady => syscalls::audio_buffer_ready as usize,
|
SyscallTable::AudioBufferReady => syscalls::audio_buffer_ready as usize,
|
||||||
SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize,
|
SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize,
|
||||||
|
SyscallTable::FillRect => syscalls::fill_rect as usize,
|
||||||
|
SyscallTable::Blit => syscalls::blit as usize,
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
table_base.add(idx).write(ptr);
|
table_base.add(idx).write(ptr);
|
||||||
|
|||||||
@@ -329,7 +329,10 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
|
|||||||
|
|
||||||
if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT {
|
if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT {
|
||||||
let idx = (y as usize) * SCREEN_WIDTH + (x as usize);
|
let idx = (y as usize) * SCREEN_WIDTH + (x as usize);
|
||||||
let raw_color = RawU16::from(color).into_inner();
|
// Stored pre-swapped so a full/partial draw can hand the
|
||||||
|
// buffer straight to the display as raw bytes, see
|
||||||
|
// `ST7365P::write_words_buffered`.
|
||||||
|
let raw_color = RawU16::from(color).into_inner().swap_bytes();
|
||||||
if self.fb[idx] != raw_color {
|
if self.fb[idx] != raw_color {
|
||||||
self.fb[idx] = raw_color;
|
self.fb[idx] = raw_color;
|
||||||
changed = true;
|
changed = true;
|
||||||
@@ -377,7 +380,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
|
|||||||
if drawable_area.contains(p) {
|
if drawable_area.contains(p) {
|
||||||
if let Some(color) = colors.next() {
|
if let Some(color) = colors.next() {
|
||||||
let idx = (p.y as usize * SCREEN_WIDTH) + (p.x as usize);
|
let idx = (p.y as usize * SCREEN_WIDTH) + (p.x as usize);
|
||||||
let raw_color = RawU16::from(color).into_inner();
|
let raw_color = RawU16::from(color).into_inner().swap_bytes();
|
||||||
if self.fb[idx] != raw_color {
|
if self.fb[idx] != raw_color {
|
||||||
self.fb[idx] = raw_color;
|
self.fb[idx] = raw_color;
|
||||||
changed = true;
|
changed = true;
|
||||||
@@ -414,7 +417,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
|
|||||||
self.size().width as u16 - 1,
|
self.size().width as u16 - 1,
|
||||||
self.size().height as u16 - 1,
|
self.size().height as u16 - 1,
|
||||||
core::iter::repeat_n(
|
core::iter::repeat_n(
|
||||||
RawU16::from(color).into_inner(),
|
RawU16::from(color).into_inner().swap_bytes(),
|
||||||
(self.size().width * self.size().height) as usize,
|
(self.size().width * self.size().height) as usize,
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
@@ -517,7 +520,9 @@ pub mod fps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize;
|
let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize;
|
||||||
self.canvas[index] = color.into_storage();
|
// Pre-swapped to match the main framebuffer's storage, since
|
||||||
|
// this canvas is copied directly into it (see draw_fps_into_fb).
|
||||||
|
self.canvas[index] = color.into_storage().swap_bytes();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,19 @@ use alloc::{string::ToString, vec::Vec};
|
|||||||
use core::{ffi::c_char, ptr, slice, sync::atomic::Ordering};
|
use core::{ffi::c_char, ptr, slice, sync::atomic::Ordering};
|
||||||
use embassy_rp::clocks::{RoscRng, clk_sys_freq};
|
use embassy_rp::clocks::{RoscRng, clk_sys_freq};
|
||||||
use embassy_time::Instant;
|
use embassy_time::Instant;
|
||||||
use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565};
|
use embedded_graphics::{
|
||||||
|
Pixel,
|
||||||
|
draw_target::DrawTarget,
|
||||||
|
geometry::{Point, Size},
|
||||||
|
pixelcolor::{Rgb565, raw::RawU16},
|
||||||
|
primitives::Rectangle,
|
||||||
|
};
|
||||||
use embedded_sdmmc::LfnBuffer;
|
use embedded_sdmmc::LfnBuffer;
|
||||||
use heapless::spsc::Queue;
|
use heapless::spsc::Queue;
|
||||||
use userlib_sys::{
|
use userlib_sys::{
|
||||||
AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, CLayout, CPixel, Dealloc, DrawIter, FileLen,
|
AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, Blit, CLayout, CPixel, Dealloc, DrawIter,
|
||||||
GenRand, GetMs, ListDir, Print, ReadFile, ReconfigureAudioSampleRate, RngRequest,
|
FileLen, FillRect, GenRand, GetMs, ListDir, Print, ReadFile, ReconfigureAudioSampleRate,
|
||||||
SendAudioBuffer, SleepMs, WriteFile, keyboard::*,
|
RngRequest, SendAudioBuffer, SleepMs, WriteFile, keyboard::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "psram")]
|
#[cfg(feature = "psram")]
|
||||||
@@ -102,6 +108,44 @@ pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
|
|||||||
FB_PAUSED.store(false, Ordering::Release);
|
FB_PAUSED.store(false, Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: FillRect = fill_rect;
|
||||||
|
pub extern "C" fn fill_rect(x: u16, y: u16, w: u16, h: u16, color: u16) {
|
||||||
|
let area = Rectangle::new(
|
||||||
|
Point::new(x as i32, y as i32),
|
||||||
|
Size::new(w as u32, h as u32),
|
||||||
|
);
|
||||||
|
let color: Rgb565 = RawU16::new(color).into();
|
||||||
|
|
||||||
|
FB_PAUSED.store(true, Ordering::Release);
|
||||||
|
unsafe {
|
||||||
|
FRAMEBUFFER
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.fill_solid(&area, color)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
FB_PAUSED.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: Blit = blit;
|
||||||
|
pub extern "C" fn blit(x: u16, y: u16, w: u16, h: u16, colors: *const u16, len: usize) {
|
||||||
|
let area = Rectangle::new(
|
||||||
|
Point::new(x as i32, y as i32),
|
||||||
|
Size::new(w as u32, h as u32),
|
||||||
|
);
|
||||||
|
let raw: &[u16] = unsafe { slice::from_raw_parts(colors, len) };
|
||||||
|
|
||||||
|
FB_PAUSED.store(true, Ordering::Release);
|
||||||
|
unsafe {
|
||||||
|
FRAMEBUFFER
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.fill_contiguous(&area, raw.iter().map(|&c| RawU16::new(c).into()))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
FB_PAUSED.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
pub static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new();
|
pub static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new();
|
||||||
|
|
||||||
const _: GetKey = get_key;
|
const _: GetKey = get_key;
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ pub mod display {
|
|||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
Pixel,
|
Pixel,
|
||||||
geometry::{Dimensions, Point},
|
geometry::{Dimensions, Point},
|
||||||
pixelcolor::Rgb565,
|
pixelcolor::{
|
||||||
|
Rgb565,
|
||||||
|
raw::{RawData, RawU16},
|
||||||
|
},
|
||||||
prelude::{DrawTarget, Size},
|
prelude::{DrawTarget, Size},
|
||||||
primitives::Rectangle,
|
primitives::Rectangle,
|
||||||
};
|
};
|
||||||
@@ -121,6 +124,111 @@ pub mod display {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One `fill_rect` syscall instead of decomposing the area into
|
||||||
|
// individual pixels through `draw_iter` (the default impl this
|
||||||
|
// overrides), e.g. for game backgrounds and UI panels.
|
||||||
|
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
|
||||||
|
let drawable = area.intersection(&self.bounding_box());
|
||||||
|
if drawable.size.width == 0 || drawable.size.height == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
userlib_sys::fill_rect(
|
||||||
|
drawable.top_left.x as u16,
|
||||||
|
drawable.top_left.y as u16,
|
||||||
|
drawable.size.width as u16,
|
||||||
|
drawable.size.height as u16,
|
||||||
|
RawU16::from(color).into_inner(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batches contiguous rows into `blit` calls instead of decomposing
|
||||||
|
// the area into individual pixels through `draw_iter` (the default
|
||||||
|
// impl this overrides), e.g. for images and animation frames. Every
|
||||||
|
// row within the screen-clipped area shares the same horizontal
|
||||||
|
// clip, so runs of rows are batched into a single rectangular blit.
|
||||||
|
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Self::Color>,
|
||||||
|
{
|
||||||
|
let drawable = area.intersection(&self.bounding_box());
|
||||||
|
if drawable.size.width == 0 || drawable.size.height == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let area_width = area.size.width;
|
||||||
|
let area_height = area.size.height;
|
||||||
|
let clip_w = drawable.size.width as usize;
|
||||||
|
let clip_x0 = drawable.top_left.x;
|
||||||
|
let clip_y0 = drawable.top_left.y;
|
||||||
|
let clip_y1 = clip_y0 + drawable.size.height as i32;
|
||||||
|
|
||||||
|
const CHUNK_LEN: usize = 4096;
|
||||||
|
static mut CHUNK: [u16; CHUNK_LEN] = [0; CHUNK_LEN];
|
||||||
|
let rows_per_chunk = (CHUNK_LEN / clip_w).max(1);
|
||||||
|
|
||||||
|
let mut colors = colors.into_iter();
|
||||||
|
let mut chunk_count = 0usize;
|
||||||
|
let mut chunk_row0 = clip_y0;
|
||||||
|
|
||||||
|
for y in 0..area_height {
|
||||||
|
let py = area.top_left.y + y as i32;
|
||||||
|
let row_in_bounds = py >= clip_y0 && py < clip_y1;
|
||||||
|
|
||||||
|
for x in 0..area_width {
|
||||||
|
let px = area.top_left.x + x as i32;
|
||||||
|
let in_bounds = row_in_bounds && px >= clip_x0 && px < clip_x0 + clip_w as i32;
|
||||||
|
|
||||||
|
let Some(color) = colors.next() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if in_bounds {
|
||||||
|
if chunk_count == 0 {
|
||||||
|
chunk_row0 = py;
|
||||||
|
}
|
||||||
|
unsafe { CHUNK[chunk_count] = RawU16::from(color).into_inner() };
|
||||||
|
chunk_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row_in_bounds {
|
||||||
|
let rows_buffered = chunk_count / clip_w;
|
||||||
|
if rows_buffered >= rows_per_chunk {
|
||||||
|
unsafe {
|
||||||
|
userlib_sys::blit(
|
||||||
|
clip_x0 as u16,
|
||||||
|
chunk_row0 as u16,
|
||||||
|
clip_w as u16,
|
||||||
|
rows_buffered as u16,
|
||||||
|
CHUNK.as_ptr(),
|
||||||
|
chunk_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
chunk_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if chunk_count > 0 {
|
||||||
|
let rows_buffered = chunk_count / clip_w;
|
||||||
|
unsafe {
|
||||||
|
userlib_sys::blit(
|
||||||
|
clip_x0 as u16,
|
||||||
|
chunk_row0 as u16,
|
||||||
|
clip_w as u16,
|
||||||
|
rows_buffered as u16,
|
||||||
|
CHUNK.as_ptr(),
|
||||||
|
chunk_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use strum::{EnumCount, EnumIter};
|
|||||||
|
|
||||||
pub type EntryFn = fn();
|
pub type EntryFn = fn();
|
||||||
|
|
||||||
pub const SYS_CALL_TABLE_COUNT: usize = 15;
|
pub const SYS_CALL_TABLE_COUNT: usize = 17;
|
||||||
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT);
|
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT);
|
||||||
|
|
||||||
#[derive(Clone, Copy, EnumIter, EnumCount)]
|
#[derive(Clone, Copy, EnumIter, EnumCount)]
|
||||||
@@ -33,6 +33,8 @@ pub enum SyscallTable {
|
|||||||
ReconfigureAudioSampleRate = 12,
|
ReconfigureAudioSampleRate = 12,
|
||||||
AudioBufferReady = 13,
|
AudioBufferReady = 13,
|
||||||
SendAudioBuffer = 14,
|
SendAudioBuffer = 14,
|
||||||
|
FillRect = 15,
|
||||||
|
Blit = 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
@@ -152,6 +154,27 @@ pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) {
|
|||||||
f(ptr, len);
|
f(ptr, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fills an axis-aligned rectangle with a single color in one call, instead
|
||||||
|
/// of marshaling it as a stream of individual `CPixel`s through `draw_iter`.
|
||||||
|
pub type FillRect = extern "C" fn(x: u16, y: u16, w: u16, h: u16, color: u16);
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn fill_rect(x: u16, y: u16, w: u16, h: u16, color: u16) {
|
||||||
|
let f: FillRect =
|
||||||
|
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::FillRect as usize]) };
|
||||||
|
f(x, y, w, h, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a row-major block of raw RGB565 colors into a rectangle in one
|
||||||
|
/// call, instead of marshaling it pixel-by-pixel through `draw_iter`.
|
||||||
|
pub type Blit = extern "C" fn(x: u16, y: u16, w: u16, h: u16, colors: *const u16, len: usize);
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn blit(x: u16, y: u16, w: u16, h: u16, colors: *const u16, len: usize) {
|
||||||
|
let f: Blit = unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Blit as usize]) };
|
||||||
|
f(x, y, w, h, colors, len);
|
||||||
|
}
|
||||||
|
|
||||||
pub mod keyboard {
|
pub mod keyboard {
|
||||||
use crate::{SYS_CALL_TABLE, SyscallTable};
|
use crate::{SYS_CALL_TABLE, SyscallTable};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user