rustubs/machine/
cgascr.rs

1use crate::machine::device_io::*;
2use crate::{arch::x86_64::misc::*, P2V};
3use core::{fmt, ptr, slice, str};
4
5// I would consider these cga parameters constant. the scroll() and clear() work
6// with the assumption that the CGAScreen memory buffer is 64-bit aligned
7// luckily it is. if any changes, you'd better hope the assumption still holds
8// For each character, it takes 2 byte in the buffer (one for char and one for
9// attribute) Therefore the MAX_COLS should be a multiple of 4 broken..
10//
11// TODO: clean me up
12const MAX_COLS: usize = 80;
13const MAX_ROWS: usize = 25;
14const CGA_BUFFER_START: usize = P2V(0xb8000).unwrap() as usize;
15
16pub struct CGAScreen {
17	pub cga_mem: &'static mut [u8],
18	cursor_r: usize,
19	cursor_c: usize,
20	attr: u8,
21}
22
23#[inline(always)]
24fn cal_offset(row: usize, col: usize) -> usize { col + row * MAX_COLS }
25
26impl CGAScreen {
27	const IR_PORT: IOPort = IOPort::new(0x3d4);
28	const DR_PORT: IOPort = IOPort::new(0x3d5);
29	pub fn new() -> Self {
30		let cga = Self {
31			cga_mem: unsafe {
32				slice::from_raw_parts_mut(
33					CGA_BUFFER_START as *mut u8,
34					2 * MAX_COLS * MAX_ROWS,
35				)
36			},
37			cursor_r: 0,
38			cursor_c: 0,
39			attr: 0x0f,
40		};
41		cga.init_cursor();
42		return cga;
43	}
44
45	#[inline(always)]
46	/// put a char at a position, it doesn't care about the stored
47	/// cursor location.
48	pub fn show(&mut self, row: usize, col: usize, c: char, attr: u8) {
49		let index = cal_offset(row, col);
50		self.cga_mem[index * 2] = c as u8;
51		self.cga_mem[index * 2 + 1] = attr;
52	}
53
54	/// print a char at the current cursor location and update the cursor.
55	/// Scroll the screen if needed. This doesn't sync the on-screen cursor
56	/// because IO takes time. the print function updates the cursor location in
57	/// the end.
58	fn putchar(&mut self, ch: char) {
59		// TODO align to next tabstop on \t ... but this will make backspace
60		// trickier ..
61		match ch {
62			'\n' => self.newline(),
63			_ => {
64				self.show(self.cursor_r, self.cursor_c, ch, self.attr);
65				self.advance();
66			}
67		}
68	}
69
70	#[inline(always)]
71	pub fn backspace(&mut self) {
72		if self.cursor_c == 0 && self.cursor_r == 0 {
73			return;
74		}
75		if self.cursor_c == 0 {
76			self.cursor_r -= 1;
77			self.cursor_c = MAX_COLS;
78		} else {
79			self.cursor_c -= 1;
80		}
81		self.sync_cursor();
82		self.show(self.cursor_r, self.cursor_c, 0 as char, self.attr);
83	}
84
85	// this assumes 1) every line is at least qword aligned and 2) width=80chars
86	// move a whole line, overwrites the target line.
87	// this is a helper function to scroll up/down
88	fn move_line(from: usize, to: usize) {
89		if !(0..MAX_ROWS).contains(&from) {
90			return;
91		}
92		if !(0..MAX_ROWS).contains(&to) {
93			return;
94		}
95		if from == to {
96			return;
97		}
98		// every line is 80 chars, 160 bytes, 20 x 8 bytes
99		let src: usize = CGA_BUFFER_START + from * 160;
100		let dst: usize = CGA_BUFFER_START + to * 160;
101		unsafe {
102			ptr::copy_nonoverlapping(src as *mut u64, dst as *mut u64, 20);
103		}
104	}
105
106	// clear a line but leave the attributes
107	fn clearline(line: usize, attr: u8) {
108		let mut base: u64 = (attr as u64) << 8;
109		base += base << 16;
110		base += base << 32;
111		if !(0..MAX_ROWS).contains(&line) {
112			return;
113		}
114		unsafe {
115			slice::from_raw_parts_mut(
116				(CGA_BUFFER_START + line * 160) as *mut u64,
117				20,
118			)
119			.fill(base);
120		}
121	}
122
123	/// advance the cga screen's internal book keeping of the cursor location by
124	/// 1 char. Scroll up if a newline is required at the last line.
125	fn advance(&mut self) {
126		if self.cursor_c >= (MAX_COLS - 1) {
127			self.newline();
128		} else {
129			self.cursor_c += 1;
130		}
131	}
132
133	/// move cursor to the start of the next line. Scroll screen if called on
134	/// the last line.
135	fn newline(&mut self) {
136		if self.cursor_r >= (MAX_ROWS - 1) {
137			self.scroll(1);
138		} else {
139			self.cursor_r += 1;
140		}
141		self.cursor_c = 0;
142	}
143
144	pub fn scroll(&self, lines: usize) {
145		if lines >= MAX_ROWS {
146			self.clear();
147		}
148
149		if lines == 0 {
150			return;
151		}
152		// jump over first n rows since they will be overwrriten anyways;
153		for i in lines..MAX_ROWS {
154			Self::move_line(i, i - lines);
155		}
156		// and clear the rest
157		for i in (MAX_ROWS - lines)..MAX_ROWS {
158			Self::clearline(i, self.attr);
159		}
160	}
161
162	pub fn clear(&self) {
163		for i in 0..MAX_ROWS {
164			Self::clearline(i, self.attr);
165		}
166	}
167
168	pub fn reset(&mut self) {
169		self.clear();
170		self.setpos(0, 0);
171		self.sync_cursor();
172	}
173
174	/// the on-screen cursor position is decoupled with the system's own book
175	/// keeping, i.e. we update the cursor position based on our own record, but
176	/// we never read this position back.
177	pub fn setpos(&mut self, row: usize, col: usize) {
178		self.cursor_r = row;
179		self.cursor_c = col;
180	}
181
182	fn sync_cursor(&self) {
183		// io ports for instruction register and data register
184		let offset = cal_offset(self.cursor_r, self.cursor_c);
185		// set lower byte
186		Self::IR_PORT.outb(15_u8);
187		delay();
188		Self::DR_PORT.outb(offset as u8);
189		// set higher byte
190		Self::IR_PORT.outb(14_u8);
191		delay();
192		Self::DR_PORT.outb((offset >> 8) as u8);
193	}
194
195	// make cursor blink (is this necessary??)
196	pub fn init_cursor(&self) {
197		Self::IR_PORT.outb(0x0a);
198		delay();
199		let mut d = Self::DR_PORT.inb();
200		delay();
201		d &= 0xc0;
202		d |= 0xe;
203		Self::DR_PORT.outb(d);
204		delay();
205		Self::IR_PORT.outb(0x0b);
206		delay();
207		let mut d = Self::DR_PORT.inb();
208		d &= 0xe0;
209		d |= 0xf;
210		Self::DR_PORT.outb(d);
211	}
212
213	pub fn print(&mut self, s: &str) {
214		for c in s.bytes() {
215			self.putchar(c as char);
216		}
217		self.sync_cursor();
218	}
219
220	/// this is helpful for some helper text.
221	/// this will clear the bottom line
222	/// this will not update the cursor location.
223	pub fn print_at_bottom(&mut self, s: &str, attr: u8) {
224		Self::clearline(24, self.attr);
225		let s = if s.len() >= MAX_COLS { &s[0..MAX_COLS] } else { s };
226		// ugly!
227		let orig_r = self.cursor_r;
228		let orig_c = self.cursor_c;
229		let orig_a = self.attr;
230		self.cursor_r = 24;
231		self.cursor_c = 0;
232		self.setattr(attr);
233		self.print(s);
234
235		self.setattr(orig_a);
236		self.cursor_r = orig_r;
237		self.cursor_c = orig_c;
238	}
239
240	pub fn setattr(&mut self, attr: u8) { self.attr = attr; }
241}
242
243impl fmt::Write for CGAScreen {
244	fn write_str(&mut self, s: &str) -> fmt::Result {
245		self.print(s);
246		Ok(())
247	}
248}