Skip to content

Commit 29558bd

Browse files
committed
feature: gate VGA to text mode via sealed capability
1 parent adff4cb commit 29558bd

2 files changed

Lines changed: 48 additions & 67 deletions

File tree

chips/x86_q35/src/vga.rs

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33
// Copyright Tock Contributors 2025.
44

5-
//! Minimal VGA peripheral implementation for the Tock x86_q35 chip crate.
5+
//! Minimal VGA peripheral implementation for the x86_q35 chip.
66
//!
7-
//! Supports classic 80×25 text mode out-of-the-box and exposes a stub for
8-
//! setting planar 16-colour graphics modes (640×480 and 800×600). These
9-
//! extra modes will be filled in later once the driver is integrated with a
10-
//! future framebuffer capsule.
7+
//! This module exposes a **text-mode** VGA writer only. Mode selection is
8+
//! enforced at the type level via a capability trait (`TextModeCap`), and we
9+
//! provide a concrete alias `VgaText = Vga<TextMode>`.
1110
//!
11+
//! Graphics modes are intentionally not present yet; adding them will mean
12+
//! introducing a new capability type (e.g., `GraphicsMode`) and a separate
13+
//! adapter, so code that expects text-only APIs cannot accidentally compile
14+
//! against graphics.
1215
//!
1316
//! NOTE!!!
1417
//!
1518
//! This file compiles and provides working text-
1619
//! mode console support so the board can swap from the UART mux to a VGA
17-
//! console. Graphical modes are *disabled at runtime* until a framebuffer
18-
//! capsule implementation lands. The low-level register writes for 640×480 and 800×600 are
19-
//! nonetheless laid out so they can be enabled by flipping a constant.
20+
//! console.
2021
//!
2122
//! VGA peripheral driver for the x86_q35 chip.
2223
//!
@@ -28,6 +29,7 @@
2829
//! `ProcessConsole` to this driver or to the legacy serial mux.
2930
3031
use core::cell::Cell;
32+
use core::marker::PhantomData;
3133
use kernel::utilities::StaticRef;
3234
use tock_cells::volatile_cell::VolatileCell;
3335

@@ -130,14 +132,20 @@ impl ColorCode {
130132
}
131133
}
132134

133-
/// All VGA modes supported by the x86_q35 chip crate.
134-
#[non_exhaustive]
135-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136-
pub enum VgaMode {
137-
Text80x25,
138-
Graphics640x480_16,
139-
Graphics800x600_16,
135+
// Capability marker traits (type-level gating)
136+
mod screen_cap {
137+
// Sealed so only this module creates capabilities.
138+
pub trait Sealed {}
139+
140+
/// Capability: this instance supports the text-plane writer.
141+
pub trait TextModeCap: Sealed {}
142+
143+
/// The only mode we expose right now.
144+
pub struct TextMode;
145+
impl Sealed for TextMode {}
146+
impl TextModeCap for TextMode {}
140147
}
148+
pub use screen_cap::{TextMode, TextModeCap};
141149

142150
// Constants for memory-mapped text mode buffer
143151

@@ -147,8 +155,6 @@ const TEXT_BUFFER_ADDR: usize = 0xB8000;
147155
// Buffer dimensions
148156
pub(crate) const TEXT_BUFFER_WIDTH: usize = 80;
149157
pub(crate) const TEXT_BUFFER_HEIGHT: usize = 25;
150-
/// Physical address where QEMU exposes the linear-frame-buffer BAR.
151-
const LFB_PHYS_BASE: u32 = 0xE0_00_0000;
152158

153159
const VGA_CELLS: StaticRef<[VolatileCell<u16>; TEXT_BUFFER_WIDTH * TEXT_BUFFER_HEIGHT]> =
154160
unsafe { StaticRef::new(TEXT_BUFFER_ADDR as *const _) };
@@ -224,30 +230,9 @@ pub struct VgaDevice;
224230
impl VgaDevice {
225231
/// Global, row-major, bracket-indexable view.
226232
const TEXT: TextBuf = TextBuf::new();
227-
/// Program the requested mode on the VGA controller.
228-
pub fn set_mode(mode: VgaMode) {
229-
match mode {
230-
VgaMode::Text80x25 => Self::program_text_mode(),
231-
VgaMode::Graphics640x480_16 => panic!("VGA 640×480 mode not implemented"),
232-
VgaMode::Graphics800x600_16 => panic!("VGA 800×600 mode not implemented"),
233-
}
234-
}
235233

236-
/// Only needed for graphics modes (linear framebuffer @ LFB_PHYS_BASE).
237-
pub fn map_for_mode(mode: VgaMode, page_dir: &mut x86::registers::bits32::paging::PD) {
238-
use x86::registers::bits32::paging::{PAddr, PDEntry, PDFlags, PDFLAGS};
239-
240-
if matches!(
241-
mode,
242-
VgaMode::Graphics640x480_16 | VgaMode::Graphics800x600_16
243-
) {
244-
let pde_idx = (LFB_PHYS_BASE >> 22) as usize;
245-
let pa = PAddr::from(LFB_PHYS_BASE);
246-
let mut flags = PDFlags::new(0);
247-
flags.write(PDFLAGS::P::SET + PDFLAGS::RW::SET + PDFLAGS::PS::SET);
248-
page_dir[pde_idx] = PDEntry::new(pa, flags);
249-
}
250-
}
234+
// We only expose programming of the text controller here.
235+
// (No graphics mode type/impl exists yet by design.)
251236

252237
// --- private ---
253238

@@ -308,9 +293,8 @@ impl VgaDevice {
308293
}
309294

310295
// Public API - the VGA struct providing text console implementation
311-
312-
/// Simple text-mode VGA console.
313-
pub struct Vga {
296+
// Generic over capability M. We only implement methods for M: TextModeCap.
297+
pub struct Vga<M> {
314298
col: Cell<usize>,
315299
row: Cell<usize>,
316300
/// Current VGA text attribute byte for newly written characters.
@@ -321,8 +305,9 @@ pub struct Vga {
321305
// UTF-8 decode state
322306
utf8_rem: Cell<u8>, // remaining continuation bytes
323307
utf8_acc: Cell<u32>, // codepoint acc
308+
_mode: PhantomData<M>,
324309
}
325-
impl Vga {
310+
impl<M: TextModeCap> Vga<M> {
326311
pub const fn new() -> Self {
327312
Self {
328313
col: Cell::new(0),
@@ -331,6 +316,7 @@ impl Vga {
331316
attr: Cell::new(ColorCode::new(Color::LightGray, Color::Black, false).as_u8()),
332317
utf8_rem: Cell::new(0),
333318
utf8_acc: Cell::new(0),
319+
_mode: PhantomData,
334320
}
335321
}
336322

@@ -571,14 +557,6 @@ impl Vga {
571557
self.update_hw_cursor();
572558
}
573559
}
574-
const _: () = {
575-
// Exhaustively touch every current VgaMode variant
576-
match VgaMode::Text80x25 {
577-
VgaMode::Text80x25 => (),
578-
VgaMode::Graphics640x480_16 => (),
579-
VgaMode::Graphics800x600_16 => (),
580-
}
581-
};
582560

583561
// stub for future graphic options implementation
584562
pub fn framebuffer() -> Option<(*mut u8, usize)> {
@@ -588,11 +566,14 @@ pub fn framebuffer() -> Option<(*mut u8, usize)> {
588566
/// Initialise 80×25 text mode and start with a clean screen.
589567
pub(crate) fn new_text_console(_page_dir_ptr: &mut x86::registers::bits32::paging::PD) {
590568
// Program 80×25 text mode
591-
VgaDevice::set_mode(VgaMode::Text80x25);
569+
VgaDevice::program_text_mode();
592570

593571
// Wipe the BIOS banner so the kernel starts on a blank page.
594572
let blank: u16 = 0x0720; // white-on-black space
595573
for cell in &VgaDevice::TEXT {
596574
cell.set(blank);
597575
}
598576
}
577+
578+
/// Convenience alias: concrete text-mode VGA writer
579+
pub type VgaText = Vga<TextMode>;

chips/x86_q35/src/vga_textscreen.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@
1010
//!
1111
//! This file intentionally contains **no** UART concepts. It is purely a TextScreen
1212
13-
use crate::vga::{Vga, TEXT_BUFFER_HEIGHT, TEXT_BUFFER_WIDTH};
13+
use crate::vga::{TextMode, TextModeCap, Vga, TEXT_BUFFER_HEIGHT, TEXT_BUFFER_WIDTH};
1414
use core::cell::Cell;
1515
use kernel::deferred_call::{DeferredCall, DeferredCallClient};
1616
use kernel::hil::text_screen::{TextScreen as HilTextScreen, TextScreenClient};
1717
use kernel::utilities::cells::{OptionalCell, TakeCell};
1818
use kernel::ErrorCode;
1919

20-
pub struct VgaTextScreen<'a> {
21-
vga: Vga,
20+
// Generic adapter. We only implement the HIL for M: TextModeCap
21+
pub struct VgaTextScreenImpl<'a, M: TextModeCap> {
22+
vga: Vga<M>,
2223
client: OptionalCell<&'a dyn TextScreenClient>,
2324

2425
// deferred completion for print()
@@ -28,10 +29,10 @@ pub struct VgaTextScreen<'a> {
2829
busy: Cell<bool>,
2930
}
3031

31-
impl VgaTextScreen<'_> {
32+
impl<M: TextModeCap> VgaTextScreenImpl<'_, M> {
3233
pub fn new() -> Self {
3334
Self {
34-
vga: Vga::new(),
35+
vga: Vga::<M>::new(),
3536
client: OptionalCell::empty(),
3637
dcall: DeferredCall::new(),
3738
tx_buf: TakeCell::empty(),
@@ -44,14 +45,9 @@ impl VgaTextScreen<'_> {
4445
pub fn clear_screen(&self) {
4546
self.vga.clear();
4647
}
47-
48-
// Keep the board wiring simple
49-
pub fn register_deferred_call(&'static self) {
50-
self.dcall.register(self);
51-
}
5248
}
5349

54-
impl<'a> HilTextScreen<'a> for VgaTextScreen<'a> {
50+
impl<'a, M: TextModeCap> HilTextScreen<'a> for VgaTextScreenImpl<'a, M> {
5551
fn set_client(&self, client: Option<&'a dyn TextScreenClient>) {
5652
match client {
5753
Some(c) => self.client.set(c),
@@ -89,8 +85,9 @@ impl<'a> HilTextScreen<'a> for VgaTextScreen<'a> {
8985
Ok(())
9086
}
9187

92-
fn set_cursor(&self, _x: usize, _y: usize) -> Result<(), ErrorCode> {
93-
// Minimal first pass: acknowledge immediately.
88+
fn set_cursor(&self, x: usize, y: usize) -> Result<(), ErrorCode> {
89+
// Forward to the writer; it bounds-checks and updates the HW cursor.
90+
self.vga.set_cursor(x, y);
9491
self.client.map(|c| c.command_complete(Ok(())));
9592
Ok(())
9693
}
@@ -132,7 +129,7 @@ impl<'a> HilTextScreen<'a> for VgaTextScreen<'a> {
132129
}
133130
}
134131

135-
impl DeferredCallClient for VgaTextScreen<'_> {
132+
impl<M: TextModeCap> DeferredCallClient for VgaTextScreenImpl<'_, M> {
136133
fn handle_deferred_call(&self) {
137134
if let Some(buf) = self.tx_buf.take() {
138135
let len = self.tx_len.get();
@@ -145,3 +142,6 @@ impl DeferredCallClient for VgaTextScreen<'_> {
145142
self.dcall.register(self);
146143
}
147144
}
145+
146+
/// Public, concrete type the board uses: text-only screen.
147+
pub type VgaTextScreen<'a> = VgaTextScreenImpl<'a, TextMode>;

0 commit comments

Comments
 (0)