/* * Copyright (c) 2010-2017 Richard Braun. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Screen dimensions. */ #define CGA_COLUMNS 80 #define CGA_LINES 25 /* * Text mode mapped memory and size. */ #define CGA_MEMORY 0xb8000 #define CGA_MEMORY_LINE_SIZE (CGA_COLUMNS * 2) #define CGA_MEMORY_SIZE (CGA_MEMORY_LINE_SIZE * CGA_LINES) /* * I/O ports. */ #define CGA_PORT_MISC_OUT_READ 0x3cc #define CGA_PORT_MISC_OUT_WRITE 0x3c2 #define CGA_PORT_CRTC_ADDR 0x3d4 #define CGA_PORT_CRTC_DATA 0x3d5 /* * Miscellaneous output register bits. */ #define CGA_MISC_OUT_IOAS 0x1 /* * CRTC registers. */ #define CGA_CRTC_CURSOR_START_REG 0xa #define CGA_CRTC_CURSOR_LOC_HIGH_REG 0xe #define CGA_CRTC_CURSOR_LOC_LOW_REG 0xf /* * Cursor start register bits. */ #define CGA_CSR_DISABLED 0x10 /* * Foreground screen color. */ #define CGA_FOREGROUND_COLOR 0x7 /* * Number of spaces to display for a tabulation. */ #define CGA_TABULATION_SPACES 8 static void *cga_memory __read_mostly; #define CGA_BACK_BUFFER_SIZE (64 * 1024) #if CGA_BACK_BUFFER_SIZE < CGA_MEMORY_SIZE #error "back buffer size must be at least as large as video memory" #endif #define CGA_SCROLL_PAGE ((CGA_LINES / 2) * CGA_MEMORY_LINE_SIZE) /* * Back buffer. */ struct cga_bbuf { struct cbuf cbuf; size_t view; size_t cursor; char buf[CGA_BACK_BUFFER_SIZE]; bool cursor_enabled; }; static struct cga_bbuf cga_bbuf; static uint16_t cga_build_cell(char c) { return (CGA_FOREGROUND_COLOR << 8) | c; } static void cga_write(size_t index, const void *ptr, size_t size) { assert((index + size) <= CGA_MEMORY_SIZE); memcpy(cga_memory + index, ptr, size); } static uint16_t cga_get_cursor_position(void) { uint16_t tmp; io_write_byte(CGA_PORT_CRTC_ADDR, CGA_CRTC_CURSOR_LOC_HIGH_REG); tmp = io_read_byte(CGA_PORT_CRTC_DATA) << 8; io_write_byte(CGA_PORT_CRTC_ADDR, CGA_CRTC_CURSOR_LOC_LOW_REG); tmp |= io_read_byte(CGA_PORT_CRTC_DATA); return tmp; } static void cga_set_cursor_position(uint16_t position) { io_write_byte(CGA_PORT_CRTC_ADDR, CGA_CRTC_CURSOR_LOC_HIGH_REG); io_write_byte(CGA_PORT_CRTC_DATA, position >> 8); io_write_byte(CGA_PORT_CRTC_ADDR, CGA_CRTC_CURSOR_LOC_LOW_REG); io_write_byte(CGA_PORT_CRTC_DATA, position & 0xff); } static void cga_enable_cursor(void) { uint8_t tmp; io_write_byte(CGA_PORT_CRTC_ADDR, CGA_CRTC_CURSOR_START_REG); tmp = io_read_byte(CGA_PORT_CRTC_DATA); io_write_byte(CGA_PORT_CRTC_DATA, tmp & ~CGA_CSR_DISABLED); } static void cga_disable_cursor(void) { uint8_t tmp; io_write_byte(CGA_PORT_CRTC_ADDR, CGA_CRTC_CURSOR_START_REG); tmp = io_read_byte(CGA_PORT_CRTC_DATA); io_write_byte(CGA_PORT_CRTC_DATA, tmp | CGA_CSR_DISABLED); } static void cga_bbuf_init(struct cga_bbuf *bbuf, size_t cursor) { cbuf_init(&bbuf->cbuf, bbuf->buf, sizeof(bbuf->buf)); bbuf->view = cbuf_start(&bbuf->cbuf); cbuf_write(&bbuf->cbuf, bbuf->view, cga_memory, CGA_MEMORY_SIZE); bbuf->cursor = bbuf->view + cursor; bbuf->cursor_enabled = true; } static size_t cga_bbuf_cursor_offset(const struct cga_bbuf *bbuf) { return bbuf->cursor - bbuf->view; } static int cga_bbuf_get_phys_cursor(const struct cga_bbuf *bbuf, uint16_t *cursorp) { uint16_t cursor; cursor = cga_bbuf_cursor_offset(bbuf); assert((cursor & 1) == 0); if (cursor >= CGA_MEMORY_SIZE) { return ENODEV; } *cursorp = (cursor >> 1); return 0; } static void cga_bbuf_update_phys_cursor(struct cga_bbuf *bbuf) { bool cursor_enabled; uint16_t cursor = 0; int error; error = cga_bbuf_get_phys_cursor(bbuf, &cursor); cursor_enabled = !error; if (cursor_enabled != bbuf->cursor_enabled) { bbuf->cursor_enabled = cursor_enabled; if (cursor_enabled) { cga_enable_cursor(); } else { cga_disable_cursor(); } } if (cursor_enabled) { cga_set_cursor_position(cursor); } } static void cga_bbuf_redraw(struct cga_bbuf *bbuf) { size_t size; int error; size = CGA_MEMORY_SIZE; error = cbuf_read(&bbuf->cbuf, bbuf->view, cga_memory, &size); assert(!error); assert(size == CGA_MEMORY_SIZE); cga_bbuf_update_phys_cursor(bbuf); } static bool cga_bbuf_view_needs_scrolling(const struct cga_bbuf *bbuf) { size_t view_size; view_size = cbuf_end(&bbuf->cbuf) - bbuf->view; if (view_size > CGA_MEMORY_SIZE) { return true; } /* Consider the cursor as a valid cell */ view_size = (bbuf->cursor + 1) - bbuf->view; if (view_size > CGA_MEMORY_SIZE) { return true; } return false; } static void cga_bbuf_scroll_once(struct cga_bbuf *bbuf) { uint16_t spaces[CGA_COLUMNS]; size_t i; for (i = 0; i < ARRAY_SIZE(spaces); i++) { spaces[i] = cga_build_cell(' '); } cbuf_write(&bbuf->cbuf, cbuf_end(&bbuf->cbuf), spaces, sizeof(spaces)); bbuf->view += sizeof(spaces); cga_bbuf_redraw(bbuf); } static void cga_bbuf_reset_view(struct cga_bbuf *bbuf) { if ((bbuf->view + CGA_MEMORY_SIZE) == cbuf_end(&bbuf->cbuf)) { return; } bbuf->view = cbuf_end(&bbuf->cbuf) - CGA_MEMORY_SIZE; cga_bbuf_redraw(bbuf); } static void cga_bbuf_push(struct cga_bbuf *bbuf, char c) { size_t offset; uint16_t cell; cga_bbuf_reset_view(bbuf); cell = cga_build_cell(c); cbuf_write(&bbuf->cbuf, bbuf->cursor, &cell, sizeof(cell)); offset = cga_bbuf_cursor_offset(bbuf); bbuf->cursor += sizeof(cell); if (cga_bbuf_view_needs_scrolling(bbuf)) { cga_bbuf_scroll_once(bbuf); } else { cga_write(offset, &cell, sizeof(cell)); cga_bbuf_update_phys_cursor(bbuf); } } static void cga_bbuf_newline(struct cga_bbuf *bbuf) { uint16_t cursor = 0, spaces[CGA_COLUMNS]; size_t i, nr_spaces, offset, size; int error; cga_bbuf_reset_view(bbuf); error = cga_bbuf_get_phys_cursor(bbuf, &cursor); assert(!error); nr_spaces = CGA_COLUMNS - (cursor % CGA_COLUMNS); for (i = 0; i < nr_spaces; i++) { spaces[i] = cga_build_cell(' '); } /* * The cursor may not point at the end of the view, in which case * any existing data must be preserved. */ size = sizeof(spaces); cbuf_read(&bbuf->cbuf, bbuf->cursor, spaces, &size); cbuf_write(&bbuf->cbuf, bbuf->cursor, spaces, nr_spaces * sizeof(spaces[0])); offset = cga_bbuf_cursor_offset(bbuf); bbuf->cursor += nr_spaces * sizeof(spaces[0]); if (cga_bbuf_view_needs_scrolling(bbuf)) { cga_bbuf_scroll_once(bbuf); } else { cga_write(offset, spaces, nr_spaces * sizeof(spaces[0])); cga_bbuf_update_phys_cursor(bbuf); } cga_bbuf_update_phys_cursor(bbuf); } static void cga_bbuf_move_cursor(struct cga_bbuf *bbuf, bool forward) { cga_bbuf_reset_view(bbuf); if ((!forward && (bbuf->cursor == bbuf->view)) || (forward && (bbuf->cursor == cbuf_end(&bbuf->cbuf)))) { return; } if (forward) { bbuf->cursor += sizeof(uint16_t); } else { bbuf->cursor -= sizeof(uint16_t); } cga_bbuf_update_phys_cursor(bbuf); } static void cga_bbuf_move_cursor_left(struct cga_bbuf *bbuf) { cga_bbuf_move_cursor(bbuf, false); } static void cga_bbuf_move_cursor_right(struct cga_bbuf *bbuf) { cga_bbuf_move_cursor(bbuf, true); } static void cga_bbuf_backspace(struct cga_bbuf *bbuf) { cga_bbuf_move_cursor_left(bbuf); } static void cga_bbuf_scroll_up(struct cga_bbuf *bbuf) { size_t start, size; bbuf->view -= CGA_SCROLL_PAGE; /* The back buffer size is a power-of-two, not a line multiple */ size = cbuf_size(&bbuf->cbuf); size -= size % CGA_MEMORY_LINE_SIZE; start = cbuf_end(&bbuf->cbuf) - size; if ((bbuf->view - start) >= size) { bbuf->view = start; } cga_bbuf_redraw(bbuf); } static void cga_bbuf_scroll_down(struct cga_bbuf *bbuf) { size_t end; bbuf->view += CGA_SCROLL_PAGE; end = bbuf->view + CGA_MEMORY_SIZE; if (!cbuf_range_valid(&bbuf->cbuf, bbuf->view, end)) { bbuf->view = cbuf_end(&bbuf->cbuf) - CGA_MEMORY_SIZE; } cga_bbuf_redraw(bbuf); } void cga_putc(char c) { unsigned int i; switch (c) { case '\r': return; case '\n': cga_bbuf_newline(&cga_bbuf); break; case '\b': cga_bbuf_backspace(&cga_bbuf); break; case '\t': for(i = 0; i < CGA_TABULATION_SPACES; i++) { cga_putc(' '); } break; case CONSOLE_SCROLL_UP: cga_bbuf_scroll_up(&cga_bbuf); break; case CONSOLE_SCROLL_DOWN: cga_bbuf_scroll_down(&cga_bbuf); break; default: cga_bbuf_push(&cga_bbuf, c); } } static void __init cga_setup_misc_out(void) { uint8_t reg; reg = io_read_byte(CGA_PORT_MISC_OUT_READ); if (!(reg & CGA_MISC_OUT_IOAS)) { reg |= CGA_MISC_OUT_IOAS; io_write_byte(CGA_PORT_MISC_OUT_WRITE, reg); } } static int __init cga_setup(void) { cga_memory = (void *)vm_page_direct_va(CGA_MEMORY); cga_setup_misc_out(); cga_bbuf_init(&cga_bbuf, cga_get_cursor_position()); return 0; } INIT_OP_DEFINE(cga_setup); void cga_cursor_left(void) { cga_bbuf_move_cursor_left(&cga_bbuf); } void cga_cursor_right(void) { cga_bbuf_move_cursor_right(&cga_bbuf); }