diff --git a/crates/init/src/load.rs b/crates/init/src/load.rs index cbae7b0..7f5e19b 100644 --- a/crates/init/src/load.rs +++ b/crates/init/src/load.rs @@ -13,8 +13,8 @@ pub(crate) const STACK_PAGES: usize = 4; /// The most there is room for under the image (page 0 stays unmapped). const MAX_STACK_PAGES: usize = USER_CODE_BASE / PAGE_SIZE - 1; const CREATE_CAP: CapSlot = CapSlot::const_new(0); -/// Where a program file is read to before it is loaded (32 pages: the -/// largest program init will load; the gatekeeper is past 16). +/// Where a program file is read through on its way into a process, a +/// chunk at a time, so a program may be any size. const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x0120_0000); const SCRATCH_PAGES: u16 = 32; @@ -23,14 +23,54 @@ pub(crate) struct LoadedProcess { pub(crate) entry_point: EntryPoint, } -/// Reads all of `filename` into `scratch`; its size, or `None` if it is -/// missing, unreadable or larger than `scratch`. -fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option { - let handle = storage.open(filename).ok()?; - let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len()); - let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok()); - let _ = storage.close(handle); - read.filter(|r| Some(*r) == size) +/// A program file read forwards through a buffer (storage has no seek): a +/// segment's bytes are asked for in increasing file order. +struct Stream<'a> { + storage: &'a mut Storage, + handle: libkos::fs::FileHandle, + buf: &'a mut [u8], + /// The file offset of `buf[0]`, and how much of `buf` holds file. + at: usize, + len: usize, + size: usize, +} + +impl Stream<'_> { + /// Reads the next chunk after what `buf` holds; false at the end or + /// on an error. + fn advance(&mut self) -> bool { + let next = self.at + self.len; + let want = self.buf.len().min(self.size - next.min(self.size)); + match self.storage.read(self.handle, &mut self.buf[..want]) { + Ok(n) if n > 0 => { + (self.at, self.len) = (next, n); + true + } + _ => false, + } + } + + /// Hands the file's bytes `from..from + len` to `sink`, piece by piece, + /// with each piece's place in that range. + fn copy(&mut self, from: usize, len: usize, mut sink: impl FnMut(usize, &[u8])) -> bool { + if from < self.at || from.checked_add(len).is_none_or(|end| end > self.size) { + return false; + } + let mut done = 0; + while done < len { + let want = from + done; + while want >= self.at + self.len { + if !self.advance() { + return false; + } + } + let start = want - self.at; + let n = (self.len - start).min(len - done); + sink(done, &self.buf[start..start + n]); + done += n; + } + true + } } pub(crate) fn load_binary( @@ -40,53 +80,57 @@ pub(crate) fn load_binary( limits: Limits, stack_pages: usize, ) -> Option { + let handle = storage.open(filename).ok()?; + let loaded = storage + .file_size(handle) + .ok() + .and_then(|size| load_from(&mut Stream { storage, handle, buf: scratch, at: 0, len: 0, size }, limits, stack_pages)); + let _ = storage.close(handle); + loaded +} + +fn load_from(file: &mut Stream, limits: Limits, stack_pages: usize) -> Option { let stack_pages = stack_pages.min(MAX_STACK_PAGES); - let file_size = read_file(storage, scratch, filename)?; - let elf_data = &scratch[..file_size]; - let elf_info = match elf::parse(elf_data) { - Ok(info) => info, - Err(_) => return None, - }; - + // The headers are in the first chunk. + if !file.advance() { + return None; + } + let elf_info = elf::parse(&file.buf[..file.len]).ok()?; let process_cap = syscall::create_process(CREATE_CAP, limits)?; - - for segment in elf_info.segments() { - let page_start = segment.vaddr & !0xFFF; - let offset_in_page = segment.vaddr & 0xFFF; - let total_size = offset_in_page + segment.mem_size; - let seg_pages = total_size.div_ceil(PAGE_SIZE); - - let page_count = PageCount::new(u16::try_from(seg_pages).ok()?)?; - let local = libkos::window::request(page_count)?; - local.fill_u32(ByteOffset::ZERO, seg_pages * PAGE_SIZE / 4, 0); - - let copy_len = segment.file_size.min(segment.mem_size); - let src = segment - .file_offset - .checked_add(copy_len) - .and_then(|end| elf_data.get(segment.file_offset..end)); - if let Some(src) = src.filter(|_| copy_len > 0) { - local.copy_from(ByteOffset::new(offset_in_page), src); - } - - for p in 0..seg_pages { - let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE); - let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE); - syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute); - } + let filled = elf_info.segments().all(|segment| load_segment(file, process_cap, segment).is_some()) + && (0..stack_pages).all(|i| { + let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE); + PageCount::new(1).and_then(syscall::request_pages).is_some_and(|va| { + syscall::map_page(process_cap, va, dst, false); + true + }) + }); + if !filled { + let _ = syscall::kill(process_cap); + syscall::drop_cap(process_cap); + return None; } + Some(LoadedProcess { process_cap, entry_point: EntryPoint::from_raw(elf_info.entry_point) }) +} - for i in 0..stack_pages { - let page_count = PageCount::new(1)?; - let stack_va = syscall::request_pages(page_count)?; - let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE); - syscall::map_page(process_cap, stack_va, dst, false); +/// Fresh pages with the segment's bytes, zero past them, moved into the +/// process at its address. +fn load_segment(file: &mut Stream, process_cap: CapSlot, segment: &elf::Segment) -> Option<()> { + let page_start = segment.vaddr & !0xFFF; + let offset_in_page = segment.vaddr & 0xFFF; + let seg_pages = (offset_in_page + segment.mem_size).div_ceil(PAGE_SIZE); + let local = libkos::window::request(PageCount::new(u16::try_from(seg_pages).ok()?)?)?; + local.fill_u32(ByteOffset::ZERO, seg_pages * PAGE_SIZE / 4, 0); + let copy_len = segment.file_size.min(segment.mem_size); + if copy_len > 0 && !file.copy(segment.file_offset, copy_len, |at, bytes| local.copy_from(ByteOffset::new(offset_in_page + at), bytes)) { + return None; } - - Some(LoadedProcess { - process_cap, - entry_point: EntryPoint::from_raw(elf_info.entry_point), - }) + for p in 0..seg_pages { + let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE); + let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE); + syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute); + } + Some(()) } /// A region of init's own to read program files into. diff --git a/crates/libkos/src/display.rs b/crates/libkos/src/display.rs index a8848b5..59b18d0 100644 --- a/crates/libkos/src/display.rs +++ b/crates/libkos/src/display.rs @@ -5,12 +5,10 @@ //! front. use crate::syscall; -use crate::window::Window; -use types::address::{ByteOffset, VirtualAddress}; +use types::address::VirtualAddress; use types::bounded::CapSlot; use types::display::window::{self as w, PIXEL_BYTES}; use types::display::{Color, Dimension, ScreenCoord}; -use types::mapping::PagePerms; /// Where an app's canvas is mapped. const CANVAS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000); @@ -67,11 +65,13 @@ struct Damage { y1: u16, } -/// A window's pixels, mapped here. Draw with the `Framebuffer` methods (and -/// `raster`); `flush` presents everything drawn since the last one. +/// A window's pixels, mapped here as plain memory. Draw with the +/// `Framebuffer` methods (and `raster`), or on the bytes themselves +/// (`pixels_mut`, then `touch`); `flush` presents everything changed since +/// the last one. pub struct Canvas { window: CapSlot, - pixels: Window, + pixels: &'static mut [u8], width: Dimension, height: Dimension, damage: Option, @@ -84,7 +84,7 @@ impl Canvas { syscall::Received::Message { words, cap: Some(buffer), .. } if words[0] == w::OK => (words, buffer), _ => return None, }; - let pixels = crate::window::map(buffer, CANVAS_VA, PagePerms::ReadWrite).ok()?; + let pixels = crate::window::map_buffer(buffer, CANVAS_VA).ok()?; let (width, height) = (Dimension::new(words[1] as u16), Dimension::new(words[2] as u16)); // The display says how big; the mapping says how much is there. (width.raw() as usize * height.raw() as usize * PIXEL_BYTES <= pixels.len()) @@ -95,12 +95,19 @@ impl Canvas { self.window } - fn offset(&self, x: u16, y: u16) -> ByteOffset { - ByteOffset::new((y as usize * self.width.raw() as usize + x as usize) * PIXEL_BYTES) + /// The pixels, `width * height` of them, each the bytes R, G, B, A. + pub fn pixels_mut(&mut self) -> &mut [u8] { + let n = self.width.raw() as usize * self.height.raw() as usize * PIXEL_BYTES; + &mut self.pixels[..n] } - /// Marks `x..x+w`, `y..y+h` (already clipped) as changed. - fn touch(&mut self, x: u16, y: u16, w: u16, h: u16) { + fn offset(&self, x: u16, y: u16) -> usize { + (y as usize * self.width.raw() as usize + x as usize) * PIXEL_BYTES + } + + /// Marks a rectangle as changed, for the next `flush`; clipped. + pub fn touch(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) { + let Some((x, y, w, h)) = self.clip(x, y, w, h) else { return }; let (x1, y1) = (x + w, y + h); self.damage = Some(match self.damage { None => Damage { x0: x, y0: y, x1, y1 }, @@ -112,7 +119,15 @@ impl Canvas { fn clip(&self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) -> Option<(u16, u16, u16, u16)> { let (cw, ch) = (self.width.raw(), self.height.raw()); let (x, y) = (x.raw(), y.raw()); - (x < cw && y < ch).then(|| (x, y, w.raw().min(cw - x), h.raw().min(ch - y))) + (x < cw && y < ch && w.raw() > 0 && h.raw() > 0).then(|| (x, y, w.raw().min(cw - x), h.raw().min(ch - y))) + } + + /// Fills `len` pixels from byte `at` with `color`. + fn fill(&mut self, at: usize, len: usize, color: Color) { + let px = color.to_rgba(); + for chunk in self.pixels[at..at + len * PIXEL_BYTES].chunks_exact_mut(PIXEL_BYTES) { + chunk.copy_from_slice(&px); + } } } @@ -126,28 +141,24 @@ impl port_framebuffer::Framebuffer for Canvas { } fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) { - if let Some((x, y, _, _)) = self.clip(x, y, Dimension::new(1), Dimension::new(1)) { - self.pixels.write(self.offset(x, y), color.raw()); - self.touch(x, y, 1, 1); - } + self.fill_rect(x, y, Dimension::new(1), Dimension::new(1), color); } fn write_row(&mut self, x: ScreenCoord, y: ScreenCoord, row: &[Color]) { - let Some((x, y, w, _)) = self.clip(x, y, Dimension::new(row.len().min(u16::MAX as usize) as u16), Dimension::new(1)) else { return }; - let mut run = [0u32; 64]; - for (i, chunk) in row[..w as usize].chunks(run.len()).enumerate() { - for (slot, c) in run.iter_mut().zip(chunk) { - *slot = c.raw(); - } - self.pixels.write_u32s(self.offset(x + (i * 64) as u16, y), &run[..chunk.len()]); + let len = Dimension::new(row.len().min(u16::MAX as usize) as u16); + let Some((cx, cy, w, _)) = self.clip(x, y, len, Dimension::new(1)) else { return }; + let at = self.offset(cx, cy); + for (chunk, c) in self.pixels[at..at + w as usize * PIXEL_BYTES].chunks_exact_mut(PIXEL_BYTES).zip(row) { + chunk.copy_from_slice(&c.to_rgba()); } - self.touch(x, y, w, 1); + self.touch(x, y, len, Dimension::new(1)); } fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) { - let Some((x, y, w, h)) = self.clip(x, y, w, h) else { return }; - for row in y..y + h { - self.pixels.fill_u32(self.offset(x, row), w as usize, color.raw()); + let Some((cx, cy, cw, ch)) = self.clip(x, y, w, h) else { return }; + for row in cy..cy + ch { + let at = self.offset(cx, row); + self.fill(at, cw as usize, color); } self.touch(x, y, w, h); } @@ -165,14 +176,14 @@ impl port_framebuffer::Framebuffer for Canvas { } fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) { - let Some((_, top, _, height)) = self.clip(ScreenCoord::new(0), top, self.width, height) else { return }; - let by = by.raw().min(height); - let row = self.width.raw() as usize * PIXEL_BYTES; - let kept = (height - by) as usize * row; - self.pixels.copy_within(self.offset(0, top + by), self.offset(0, top), kept); let width = self.width; + let Some((_, top, _, height)) = self.clip(ScreenCoord::new(0), top, width, height) else { return }; + let by = by.raw().min(height); + let kept = (height - by) as usize * width.raw() as usize * PIXEL_BYTES; + let (from, to) = (self.offset(0, top + by), self.offset(0, top)); + self.pixels.copy_within(from..from + kept, to); self.fill_rect(ScreenCoord::new(0), ScreenCoord::new(top + height - by), width, Dimension::new(by), fill); - self.touch(0, top, width.raw(), height); + self.touch(ScreenCoord::new(0), ScreenCoord::new(top), width, Dimension::new(height)); } } diff --git a/crates/servers/display-server/src/host.rs b/crates/servers/display-server/src/host.rs index 4e7a22a..61f81da 100644 --- a/crates/servers/display-server/src/host.rs +++ b/crates/servers/display-server/src/host.rs @@ -67,7 +67,7 @@ impl Host for KernelHost { if at + PIXEL_BYTES > window.len() { return; } - *px = Color::from_raw(window.read::(ByteOffset::new(at))); + *px = Color::from_rgba(window.read::(ByteOffset::new(at)).to_le_bytes()); } } diff --git a/crates/types/src/display.rs b/crates/types/src/display.rs index e30c80e..d7ca4f5 100644 --- a/crates/types/src/display.rs +++ b/crates/types/src/display.rs @@ -16,6 +16,17 @@ impl Color { pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self { Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)) } + + /// As a window's pixel is stored: the bytes R, G, B, A (see + /// `window::BUFFER`). + pub const fn to_rgba(self) -> [u8; 4] { + let [b, g, r, a] = self.0.to_le_bytes(); + [r, g, b, a] + } + + pub const fn from_rgba([r, g, b, a]: [u8; 4]) -> Self { + Self(u32::from_le_bytes([b, g, r, a])) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -61,9 +72,10 @@ pub mod window { /// (`pack_name`). Reply `[OK, width, height, 0]` carrying the window /// capability, which the launcher passes on; the new window is in front. pub const OPEN: usize = 1; - /// Through a window: reply `[OK, width, height, 0]` carrying its pixel buffer, a - /// region the holder maps read-write: `width * height` pixels, rows - /// one after another, each pixel a `Color` as a little-endian u32. + /// Through a window: reply `[OK, width, height, 0]` carrying its pixel + /// buffer, a region the holder maps read-write: `width * height` + /// pixels, rows one after another, each pixel the bytes R, G, B, A + /// (alpha premultiplied; the display shows it as opaque). pub const BUFFER: usize = 2; /// Through a window: the rectangle (`pack_rect`) that changed. pub const PRESENT: usize = 3;