static volatile uint16_t* VGA_BUFFER = 0;
#define VGA_WIDTH 80
#define VGA_HEIGHT 25
+#define VGA_CELLS (VGA_WIDTH * VGA_HEIGHT)
static int term_col = 0;
static int term_row = 0;
static spinlock_t vga_lock = {0};
+/* Shadow buffer in RAM — all writes target this, flushed to VGA MMIO lazily */
+static uint16_t shadow[VGA_CELLS];
+static int dirty_lo = VGA_CELLS; /* first dirty cell index */
+static int dirty_hi = -1; /* last dirty cell index */
+
+static void dirty_mark(int lo, int hi) {
+ if (lo < dirty_lo) dirty_lo = lo;
+ if (hi > dirty_hi) dirty_hi = hi;
+}
+
+static void vga_flush_to_hw(void) {
+ if (dirty_lo <= dirty_hi && VGA_BUFFER) {
+ for (int i = dirty_lo; i <= dirty_hi; i++) {
+ VGA_BUFFER[i] = shadow[i];
+ }
+ dirty_lo = VGA_CELLS;
+ dirty_hi = -1;
+ }
+ hal_video_set_cursor(term_row, term_col);
+}
+
/* --- Scrollback buffer --- */
#define SB_LINES 200
static uint16_t sb_buf[SB_LINES * VGA_WIDTH];
static int sb_count = 0; /* stored lines (max SB_LINES) */
static int view_offset = 0; /* 0 = live view, >0 = scrolled back N lines */
-static uint16_t live_buf[VGA_HEIGHT * VGA_WIDTH]; /* saved live VGA when scrolled */
-
-static void vga_update_hw_cursor(void) {
- hal_video_set_cursor(term_row, term_col);
-}
+static uint16_t live_buf[VGA_CELLS]; /* saved live screen when scrolled */
static void vga_scroll(void) {
/* Save row 0 (about to be lost) into scrollback ring */
- for (int x = 0; x < VGA_WIDTH; x++) {
- sb_buf[sb_head * VGA_WIDTH + x] = VGA_BUFFER[x];
- }
+ __builtin_memcpy(&sb_buf[sb_head * VGA_WIDTH], &shadow[0],
+ VGA_WIDTH * sizeof(uint16_t));
sb_head = (sb_head + 1) % SB_LINES;
if (sb_count < SB_LINES) sb_count++;
- /* Shift visible content up */
- for (int y = 1; y < VGA_HEIGHT; y++) {
- for (int x = 0; x < VGA_WIDTH; x++) {
- VGA_BUFFER[(y - 1) * VGA_WIDTH + x] = VGA_BUFFER[y * VGA_WIDTH + x];
- }
- }
- for (int x = 0; x < VGA_WIDTH; x++) {
- VGA_BUFFER[(VGA_HEIGHT - 1) * VGA_WIDTH + x] = (uint16_t)' ' | (uint16_t)term_color << 8;
+ /* Shift shadow content up (RAM speed — no MMIO) */
+ __builtin_memmove(&shadow[0], &shadow[VGA_WIDTH],
+ (VGA_HEIGHT - 1) * VGA_WIDTH * sizeof(uint16_t));
+ {
+ uint16_t blank = (uint16_t)' ' | (uint16_t)term_color << 8;
+ for (int x = 0; x < VGA_WIDTH; x++)
+ shadow[(VGA_HEIGHT - 1) * VGA_WIDTH + x] = blank;
}
+ dirty_mark(0, VGA_CELLS - 1);
term_row = VGA_HEIGHT - 1;
}
/* Restore live view if currently scrolled back */
static void vga_unscroll(void) {
if (view_offset > 0) {
- for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
- VGA_BUFFER[i] = live_buf[i];
- }
+ __builtin_memcpy(shadow, live_buf, sizeof(shadow));
+ dirty_mark(0, VGA_CELLS - 1);
view_offset = 0;
}
}
if (line_from_end < VGA_HEIGHT) {
/* Live content */
int live_row = VGA_HEIGHT - 1 - line_from_end;
- for (int x = 0; x < VGA_WIDTH; x++) {
- VGA_BUFFER[y * VGA_WIDTH + x] = live_buf[live_row * VGA_WIDTH + x];
+ if (VGA_BUFFER) {
+ for (int x = 0; x < VGA_WIDTH; x++)
+ VGA_BUFFER[y * VGA_WIDTH + x] = live_buf[live_row * VGA_WIDTH + x];
}
} else {
/* Scrollback: sb_idx 0 = most recent scrolled-off line */
int sb_idx = line_from_end - VGA_HEIGHT;
if (sb_idx < sb_count) {
int buf_line = (sb_head - 1 - sb_idx + SB_LINES) % SB_LINES;
- for (int x = 0; x < VGA_WIDTH; x++) {
- VGA_BUFFER[y * VGA_WIDTH + x] = sb_buf[buf_line * VGA_WIDTH + x];
+ if (VGA_BUFFER) {
+ for (int x = 0; x < VGA_WIDTH; x++)
+ VGA_BUFFER[y * VGA_WIDTH + x] = sb_buf[buf_line * VGA_WIDTH + x];
}
- } else {
+ } else if (VGA_BUFFER) {
/* Beyond scrollback — blank */
- for (int x = 0; x < VGA_WIDTH; x++) {
+ for (int x = 0; x < VGA_WIDTH; x++)
VGA_BUFFER[y * VGA_WIDTH + x] = (uint16_t)' ' | (uint16_t)term_color << 8;
- }
}
}
}
}
static void vga_put_char_unlocked(char c) {
- if (!VGA_BUFFER) return;
-
/* Any new output auto-returns to live view */
vga_unscroll();
default:
if ((unsigned char)c >= ' ') {
const int index = term_row * VGA_WIDTH + term_col;
- VGA_BUFFER[index] = (uint16_t)(unsigned char)c | (uint16_t)term_color << 8;
+ shadow[index] = (uint16_t)(unsigned char)c | (uint16_t)term_color << 8;
+ dirty_mark(index, index);
term_col++;
}
break;
term_row = 0;
term_color = 0x07; // Light Grey on Black
- if (!VGA_BUFFER) {
- return;
- }
-
- for (int y = 0; y < VGA_HEIGHT; y++) {
- for (int x = 0; x < VGA_WIDTH; x++) {
- const int index = y * VGA_WIDTH + x;
- VGA_BUFFER[index] = (uint16_t) ' ' | (uint16_t) term_color << 8;
- }
- }
- vga_update_hw_cursor();
+ uint16_t blank = (uint16_t)' ' | (uint16_t)term_color << 8;
+ for (int i = 0; i < VGA_CELLS; i++)
+ shadow[i] = blank;
+
+ if (!VGA_BUFFER) return;
+
+ for (int i = 0; i < VGA_CELLS; i++)
+ VGA_BUFFER[i] = blank;
+
+ dirty_lo = VGA_CELLS;
+ dirty_hi = -1;
+ hal_video_set_cursor(0, 0);
}
void vga_set_color(uint8_t fg, uint8_t bg) {
void vga_put_char(char c) {
uintptr_t flags = spin_lock_irqsave(&vga_lock);
vga_put_char_unlocked(c);
- vga_update_hw_cursor();
+ vga_flush_to_hw();
spin_unlock_irqrestore(&vga_lock, flags);
}
-void vga_print(const char* str) {
+void vga_write_buf(const char* buf, uint32_t len) {
uintptr_t flags = spin_lock_irqsave(&vga_lock);
-
- if (!VGA_BUFFER) {
- spin_unlock_irqrestore(&vga_lock, flags);
- return;
+ for (uint32_t i = 0; i < len; i++) {
+ vga_put_char_unlocked(buf[i]);
}
+ /* No MMIO flush here — deferred to vga_flush() on timer tick */
+ spin_unlock_irqrestore(&vga_lock, flags);
+}
+void vga_print(const char* str) {
+ uintptr_t flags = spin_lock_irqsave(&vga_lock);
for (int i = 0; str[i] != '\0'; i++) {
vga_put_char_unlocked(str[i]);
}
+ vga_flush_to_hw();
+ spin_unlock_irqrestore(&vga_lock, flags);
+}
- vga_update_hw_cursor();
+void vga_flush(void) {
+ uintptr_t flags = spin_lock_irqsave(&vga_lock);
+ vga_flush_to_hw();
spin_unlock_irqrestore(&vga_lock, flags);
}
void vga_clear(void) {
uintptr_t flags = spin_lock_irqsave(&vga_lock);
- if (!VGA_BUFFER) {
- spin_unlock_irqrestore(&vga_lock, flags);
- return;
- }
-
- for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
- VGA_BUFFER[i] = (uint16_t)' ' | (uint16_t)term_color << 8;
- }
+ uint16_t blank = (uint16_t)' ' | (uint16_t)term_color << 8;
+ for (int i = 0; i < VGA_CELLS; i++)
+ shadow[i] = blank;
+ dirty_mark(0, VGA_CELLS - 1);
term_col = 0;
term_row = 0;
view_offset = 0;
sb_count = 0;
sb_head = 0;
- vga_update_hw_cursor();
+ vga_flush_to_hw();
spin_unlock_irqrestore(&vga_lock, flags);
}
}
if (view_offset == 0) {
- /* First scroll back — save current live screen */
- for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
- live_buf[i] = VGA_BUFFER[i];
- }
+ /* First scroll back — save current live screen from shadow */
+ __builtin_memcpy(live_buf, shadow, sizeof(live_buf));
}
view_offset += VGA_HEIGHT / 2;
if (view_offset <= VGA_HEIGHT / 2) {
/* Return to live view */
- for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
- VGA_BUFFER[i] = live_buf[i];
- }
+ __builtin_memcpy(shadow, live_buf, sizeof(shadow));
+ dirty_mark(0, VGA_CELLS - 1);
view_offset = 0;
- vga_update_hw_cursor();
+ vga_flush_to_hw();
} else {
view_offset -= VGA_HEIGHT / 2;
render_scrollback_view();