diff options
author | Freya Murphy <freya@freyacat.org> | 2025-03-25 17:36:52 -0400 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2025-03-25 17:38:22 -0400 |
commit | 6af21e6a4f2251e71353562d5df7f376fdffc270 (patch) | |
tree | de20c7afc9878422c81e34f30c6b010075e9e69a /kernel/cio.c | |
download | comus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.gz comus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.bz2 comus-6af21e6a4f2251e71353562d5df7f376fdffc270.zip |
initial checkout from wrc
Diffstat (limited to 'kernel/cio.c')
-rw-r--r-- | kernel/cio.c | 796 |
1 files changed, 796 insertions, 0 deletions
diff --git a/kernel/cio.c b/kernel/cio.c new file mode 100644 index 0000000..cfff543 --- /dev/null +++ b/kernel/cio.c @@ -0,0 +1,796 @@ +/* +** SCCS ID: @(#)cio.c 2.10 1/22/25 +** +** @file cio.c +** +** @author Warren R. Carithers +** +** Based on: c_io.c 1.13 (Ken Reek, Jon Coles, Warren R. Carithers) +** +** Console I/O routines +** +** This module implements a simple set of input and output routines +** for the console screen and keyboard on the machines in the DSL. +** Refer to the header file comments for complete details. +** +** Naming conventions: +** +** Externally-visible functions have names beginning with the +** characters "cio_". +** +*/ + +#include <cio.h> +#include <lib.h> +#include <support.h> +#include <x86/arch.h> +#include <x86/pic.h> +#include <x86/ops.h> + +/* +** Bit masks for the lower five and eight bits of a value +*/ +#define BMASK5 0x1f +#define BMASK8 0xff + +/* +** Video parameters +*/ +#define SCREEN_MIN_X 0 +#define SCREEN_MIN_Y 0 +#define SCREEN_X_SIZE 80 +#define SCREEN_Y_SIZE 25 +#define SCREEN_MAX_X ( SCREEN_X_SIZE - 1 ) +#define SCREEN_MAX_Y ( SCREEN_Y_SIZE - 1 ) + +/* +** Video state +*/ +static unsigned int scroll_min_x, scroll_min_y; +static unsigned int scroll_max_x, scroll_max_y; +static unsigned int curr_x, curr_y; +static unsigned int min_x, min_y; +static unsigned int max_x, max_y; + +// pointer to input notification function +static void (*notify)(int); + +#ifdef SA_DEBUG +#include <stdio.h> +#define cio_putchar putchar +#define cio_puts(x) fputs( x, stdout ) +#endif + + +/* +** VGA definitions. +*/ + +// calculate the memory address of a specific character position +// within VGA memory +#define VIDEO_ADDR(x,y) ( unsigned short * ) \ + ( VID_BASE_ADDR + 2 * ( (y) * SCREEN_X_SIZE + (x) ) ) + +// port addresses +#define VGA_CTRL_IX_ADDR 0x3d4 +# define VGA_CTRL_CUR_HIGH 0x0e // cursor location, high byte +# define VGA_CTRL_CUR_LOW 0x0f // cursor location, low byte +#define VGA_CTRL_IX_DATA 0x3d5 + +// attribute bits +#define VGA_ATT_BBI 0x80 // blink, or background intensity +#define VGA_ATT_BGC 0x70 // background color +#define VGA_ATT_FICS 0x80 // foreground intensity or char font select +#define VGA_ATT_FGC 0x70 // foreground color + +// color selections +#define VGA_BG_BLACK 0x0000 // background colors +#define VGA_BG_BLUE 0x1000 +#define VGA_BG_GREEN 0x2000 +#define VGA_BG_CYAN 0x3000 +#define VGA_BG_RED 0x4000 +#define VGA_BG_MAGENTA 0x5000 +#define VGA_BG_BROWN 0x6000 +#define VGA_BG_WHITE 0x7000 + +#define VGA_FG_BLACK 0x0000 // foreground colors +#define VGA_FG_BLUE 0x0100 +#define VGA_FG_GREEN 0x0200 +#define VGA_FG_CYAN 0x0300 +#define VGA_FG_RED 0x0400 +#define VGA_FG_MAGENTA 0x0500 +#define VGA_FG_BROWN 0x0600 +#define VGA_FG_WHITE 0x0700 + +// color combinations +#define VGA_WHITE_ON_BLACK (VGA_FG_WHITE | VGA_BG_BLACK) +#define VGA_BLACK_ON_WHITE (VGA_FG_BLACK | VGA_BG_WHITE) + +/* +** Internal support routines. +*/ + +/* +** setcursor: set the cursor location (screen coordinates) +*/ +static void setcursor( void ) { + unsigned addr; + unsigned int y = curr_y; + + if( y > scroll_max_y ) { + y = scroll_max_y; + } + + addr = (unsigned)( y * SCREEN_X_SIZE + curr_x ); + + outb( VGA_CTRL_IX_ADDR, VGA_CTRL_CUR_HIGH ); + outb( VGA_CTRL_IX_DATA, ( addr >> 8 ) & BMASK8 ); + outb( VGA_CTRL_IX_ADDR, VGA_CTRL_CUR_LOW ); + outb( VGA_CTRL_IX_DATA, addr & BMASK8 ); +} + +/* +** putchar_at: physical output to the video memory +*/ +static void putchar_at( unsigned int x, unsigned int y, unsigned int c ) { + /* + ** If x or y is too big or small, don't do any output. + */ + if( x <= max_x && y <= max_y ) { + unsigned short *addr = VIDEO_ADDR( x, y ); + + /* + ** The character may have attributes associated with it; if + ** so, use those, otherwise use white on black. + */ + c &= 0xffff; // keep only the lower bytes + if( c > BMASK8 ) { + *addr = (unsigned short)c; + } else { + *addr = (unsigned short)c | VGA_WHITE_ON_BLACK; + } + } +} + +/* +** Globally-visible support routines. +*/ + +/* +** Set the scrolling region +*/ +void cio_setscroll( unsigned int s_min_x, unsigned int s_min_y, + unsigned int s_max_x, unsigned int s_max_y ) { + scroll_min_x = bound( min_x, s_min_x, max_x ); + scroll_min_y = bound( min_y, s_min_y, max_y ); + scroll_max_x = bound( scroll_min_x, s_max_x, max_x ); + scroll_max_y = bound( scroll_min_y, s_max_y, max_y ); + curr_x = scroll_min_x; + curr_y = scroll_min_y; + setcursor(); +} + +/* +** Cursor movement in the scroll region +*/ +void cio_moveto( unsigned int x, unsigned int y ) { + curr_x = bound( scroll_min_x, x + scroll_min_x, scroll_max_x ); + curr_y = bound( scroll_min_y, y + scroll_min_y, scroll_max_y ); + setcursor(); +} + +/* +** The putchar family +*/ +void cio_putchar_at( unsigned int x, unsigned int y, unsigned int c ) { + if( ( c & 0x7f ) == '\n' ) { + unsigned int limit; + + /* + ** If we're in the scroll region, don't let this loop + ** leave it. If we're not in the scroll region, don't + ** let this loop enter it. + */ + if( x > scroll_max_x ) { + limit = max_x; + } + else if( x >= scroll_min_x ) { + limit = scroll_max_x; + } + else { + limit = scroll_min_x - 1; + } + while( x <= limit ) { + putchar_at( x, y, ' ' ); + x += 1; + } + } + else { + putchar_at( x, y, c ); + } +} + +#ifndef SA_DEBUG +void cio_putchar( unsigned int c ) { + /* + ** If we're off the bottom of the screen, scroll the window. + */ + if( curr_y > scroll_max_y ) { + cio_scroll( curr_y - scroll_max_y ); + curr_y = scroll_max_y; + } + + switch( c & BMASK8 ) { + case '\n': + /* + ** Erase to the end of the line, then move to new line + ** (actual scroll is delayed until next output appears). + */ + while( curr_x <= scroll_max_x ) { + putchar_at( curr_x, curr_y, ' ' ); + curr_x += 1; + } + curr_x = scroll_min_x; + curr_y += 1; + break; + + case '\r': + curr_x = scroll_min_x; + break; + + default: + putchar_at( curr_x, curr_y, c ); + curr_x += 1; + if( curr_x > scroll_max_x ) { + curr_x = scroll_min_x; + curr_y += 1; + } + break; + } + setcursor(); +} +#endif + +/* +** The puts family +*/ +void cio_puts_at( unsigned int x, unsigned int y, const char *str ) { + unsigned int ch; + + while( (ch = *str++) != '\0' && x <= max_x ) { + cio_putchar_at( x, y, ch ); + x += 1; + } +} + +#ifndef SA_DEBUG +void cio_puts( const char *str ) { + unsigned int ch; + + while( (ch = *str++) != '\0' ) { + cio_putchar( ch ); + } +} +#endif + +/* +** Write a "sized" buffer (like cio_puts(), but no NUL) +*/ +void cio_write( const char *buf, int length ) { + for( int i = 0; i < length; ++i ) { + cio_putchar( buf[i] ); + } +} + +void cio_clearscroll( void ) { + unsigned int nchars = scroll_max_x - scroll_min_x + 1; + unsigned int l; + unsigned int c; + + for( l = scroll_min_y; l <= scroll_max_y; l += 1 ) { + unsigned short *to = VIDEO_ADDR( scroll_min_x, l ); + + for( c = 0; c < nchars; c += 1 ) { + *to++ = ' ' | 0x0700; + } + } +} + +void cio_clearscreen( void ) { + unsigned short *to = VIDEO_ADDR( min_x, min_y ); + unsigned int nchars = ( max_y - min_y + 1 ) * ( max_x - min_x + 1 ); + + while( nchars > 0 ) { + *to++ = ' ' | 0x0700; + nchars -= 1; + } +} + + +void cio_scroll( unsigned int lines ) { + unsigned short *from; + unsigned short *to; + int nchars = scroll_max_x - scroll_min_x + 1; + int line, c; + + /* + ** If # of lines is the whole scrolling region or more, just clear. + */ + if( lines > scroll_max_y - scroll_min_y ) { + cio_clearscroll(); + curr_x = scroll_min_x; + curr_y = scroll_min_y; + setcursor(); + return; + } + + /* + ** Must copy it line by line. + */ + for( line = scroll_min_y; line <= scroll_max_y - lines; line += 1 ) { + from = VIDEO_ADDR( scroll_min_x, line + lines ); + to = VIDEO_ADDR( scroll_min_x, line ); + for( c = 0; c < nchars; c += 1 ) { + *to++ = *from++; + } + } + + for( ; line <= scroll_max_y; line += 1 ) { + to = VIDEO_ADDR( scroll_min_x, line ); + for( c = 0; c < nchars; c += 1 ) { + *to++ = ' ' | 0x0700; + } + } +} + +static int mypad( int x, int y, int extra, int padchar ) { + while( extra > 0 ) { + if( x != -1 || y != -1 ) { + cio_putchar_at( x, y, padchar ); + x += 1; + } + else { + cio_putchar( padchar ); + } + extra -= 1; + } + return x; +} + +static int mypadstr( int x, int y, char *str, int len, int width, + int leftadjust, int padchar ) { + int extra; + + if( len < 0 ) { + len = strlen( str ); + } + extra = width - len; + if( extra > 0 && !leftadjust ) { + x = mypad( x, y, extra, padchar ); + } + if( x != -1 || y != -1 ) { + cio_puts_at( x, y, str ); + x += len; + } + else { + cio_puts( str ); + } + if( extra > 0 && leftadjust ) { + x = mypad( x, y, extra, padchar ); + } + return x; +} + +static void do_printf( int x, int y, char **f ) { + char *fmt = *f; + int *ap; + char buf[ 12 ]; + char ch; + char *str; + int leftadjust; + int width; + int len; + int padchar; + + /* + ** Get characters from the format string and process them + */ + + ap = (int *)( f + 1 ); + + while( (ch = *fmt++) != '\0' ) { + + /* + ** Is it the start of a format code? + */ + + if( ch == '%' ) { + + /* + ** Yes, get the padding and width options (if there). + ** Alignment must come at the beginning, then fill, + ** then width. + */ + + leftadjust = 0; + padchar = ' '; + width = 0; + + ch = *fmt++; + + if( ch == '-' ) { + leftadjust = 1; + ch = *fmt++; + } + + if( ch == '0' ) { + padchar = '0'; + ch = *fmt++; + } + + while( ch >= '0' && ch <= '9' ) { + width *= 10; + width += ch - '0'; + ch = *fmt++; + } + + /* + ** What data type do we have? + */ + switch( ch ) { + + case 'c': + // ch = *( (int *)ap )++; + ch = *ap++; + buf[ 0 ] = ch; + buf[ 1 ] = '\0'; + x = mypadstr( x, y, buf, 1, width, leftadjust, padchar ); + break; + + case 'd': + // len = cvtdec( buf, *( (int *)ap )++ ); + len = cvtdec( buf, *ap++ ); + x = mypadstr( x, y, buf, len, width, leftadjust, padchar ); + break; + + case 's': + // str = *( (char **)ap )++; + str = (char *) (*ap++); + x = mypadstr( x, y, str, -1, width, leftadjust, padchar ); + break; + + case 'x': + // len = cvthex( buf, *( (int *)ap )++ ); + len = cvthex( buf, *ap++ ); + x = mypadstr( x, y, buf, len, width, leftadjust, padchar ); + break; + + case 'o': + // len = cvtoct( buf, *( (int *)ap )++ ); + len = cvtoct( buf, *ap++ ); + x = mypadstr( x, y, buf, len, width, leftadjust, padchar ); + break; + + case 'u': + len = cvtuns( buf, *ap++ ); + x = mypadstr( x, y, buf, len, width, leftadjust, padchar ); + break; + + } + } else { + + /* + ** No - just print it normally. + */ + + if( x != -1 || y != -1 ) { + cio_putchar_at( x, y, ch ); + switch( ch ) { + case '\n': + y += 1; + /* FALL THRU */ + + case '\r': + x = scroll_min_x; + break; + + default: + x += 1; + } + } + else { + cio_putchar( ch ); + } + } + } +} + +void cio_printf_at( unsigned int x, unsigned int y, char *fmt, ... ) { + do_printf( x, y, &fmt ); +} + +void cio_printf( char *fmt, ... ) { + do_printf( -1, -1, &fmt ); +} + +/* +** These are the "standard" IBM AT "Set 1" keycodes. +*/ + +static unsigned char scan_code[ 2 ][ 128 ] = { + { // unshifted characters +/* 00-07 */ '\377', '\033', '1', '2', '3', '4', '5', '6', +/* 08-0f */ '7', '8', '9', '0', '-', '=', '\b', '\t', +/* 10-17 */ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', +/* 18-1f */ 'o', 'p', '[', ']', '\n', '\377', 'a', 's', +/* 20-27 */ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', +/* 28-2f */ '\'', '`', '\377', '\\', 'z', 'x', 'c', 'v', +/* 30-37 */ 'b', 'n', 'm', ',', '.', '/', '\377', '*', +/* 38-3f */ '\377', ' ', '\377', '\377', '\377', '\377', '\377', '\377', +/* 40-47 */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '7', +/* 48-4f */ '8', '9', '-', '4', '5', '6', '+', '1', +/* 50-57 */ '2', '3', '0', '.', '\377', '\377', '\377', '\377', +/* 58-5f */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 60-67 */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 68-6f */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 70-77 */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 78-7f */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377' + }, + + { // shifted characters +/* 00-07 */ '\377', '\033', '!', '@', '#', '$', '%', '^', +/* 08-0f */ '&', '*', '(', ')', '_', '+', '\b', '\t', +/* 10-17 */ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', +/* 18-1f */ 'O', 'P', '{', '}', '\n', '\377', 'A', 'S', +/* 20-27 */ 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', +/* 28-2f */ '"', '~', '\377', '|', 'Z', 'X', 'C', 'V', +/* 30-37 */ 'B', 'N', 'M', '<', '>', '?', '\377', '*', +/* 38-3f */ '\377', ' ', '\377', '\377', '\377', '\377', '\377', '\377', +/* 40-47 */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '7', +/* 48-4f */ '8', '9', '-', '4', '5', '6', '+', '1', +/* 50-57 */ '2', '3', '0', '.', '\377', '\377', '\377', '\377', +/* 58-5f */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 60-67 */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 68-6f */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 70-77 */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +/* 78-7f */ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377' + } +}; + +/* +** Scan code masks +*/ + +// 'release' bit +#define REL_BIT 0x80 +#define CODE_BITS 0x7f + +#define IS_PRESS(c) (((c) & REL_BIT) == 0) +#define IS_RELEASE(c) (((c) & REL_BIT) != 0) + +/* +** Scan codes for some special characters +*/ + +// escape code - followed by another code byte +#define SCAN_ESC 0xe0 + +// shift keys: press, release +#define L_SHIFT_DN 0x2a +#define R_SHIFT_DN 0x36 +#define L_SHIFT_UP 0xaa +#define R_SHIFT_UP 0xb6 + +// control keys +#define L_CTRL_DN 0x1d +#define L_CTRL_UP 0x9d + +/* +** I/O communication constants +*/ +#define KBD_DATA 0x60 +#define KBD_STATUS 0x64 +#define READY 0x1 + +/* +** Circular buffer for input characters. Characters are inserted at +** next_space, and are removed at next_char. Buffer is empty if +** these are equal. +*/ +#define C_BUFSIZE 200 + +static char input_buffer[ C_BUFSIZE ]; +static volatile char *next_char = input_buffer; +static volatile char *next_space = input_buffer; + +static volatile char *increment( volatile char *pointer ) { + if( ++pointer >= input_buffer + C_BUFSIZE ) { + pointer = input_buffer; + } + return pointer; +} + +static int input_scan_code( int code ) { + static int shift = 0; + static int ctrl_mask = BMASK8; + int rval = -1; + + /* + ** Do the shift processing + */ + code &= BMASK8; + switch( code ) { + case L_SHIFT_DN: + case R_SHIFT_DN: + shift = 1; + break; + + case L_SHIFT_UP: + case R_SHIFT_UP: + shift = 0; + break; + + case L_CTRL_DN: + ctrl_mask = BMASK5; + break; + + case L_CTRL_UP: + ctrl_mask = BMASK8; + break; + + default: + /* + ** Process ordinary characters only on the press (to handle + ** autorepeat). Ignore undefined scan codes. + */ + if( IS_PRESS(code) ) { + code = scan_code[ shift ][ (int)code ]; + if( code != '\377' ) { + volatile char *next = increment( next_space ); + + /* + ** Store character only if there's room + */ + rval = code & ctrl_mask; + if( next != next_char ) { + *next_space = code & ctrl_mask; + next_space = next; + } + } + } + } + return( rval ); +} + +static void keyboard_isr( int vector, int code ) { + + int data = inb( KBD_DATA ); + int val = input_scan_code( data ); + + // if there is a notification function, call it + if( val != -1 && notify ) + notify( val ); + + outb( PIC1_CMD, PIC_EOI ); +} + +int cio_getchar( void ) { + char c; + int interrupts_enabled = r_eflags() & EFL_IF; + + while( next_char == next_space ) { + if( !interrupts_enabled ) { + /* + ** Must read the next keystroke ourselves. + */ + while( ( inb( KBD_STATUS ) & READY ) == 0 ) { + ; + } + (void) input_scan_code( inb( KBD_DATA ) ); + } + } + + c = *next_char & BMASK8; + next_char = increment( next_char ); + if( c != EOT ) { + cio_putchar( c ); + } + return c; +} + +int cio_gets( char *buffer, unsigned int size ) { + char ch; + int count = 0; + + while( size > 1 ) { + ch = cio_getchar(); + if( ch == EOT ) { + break; + } + *buffer++ = ch; + count += 1; + size -= 1; + if( ch == '\n' ) { + break; + } + } + *buffer = '\0'; + return count; +} + +int cio_input_queue( void ) { + int n_chars = next_space - next_char; + + if( n_chars < 0 ) { + n_chars += C_BUFSIZE; + } + return n_chars; +} + +/* +** Initialization routines +*/ +void cio_init( void (*fcn)(int) ) { + /* + ** Screen dimensions + */ + min_x = SCREEN_MIN_X; + min_y = SCREEN_MIN_Y; + max_x = SCREEN_MAX_X; + max_y = SCREEN_MAX_Y; + + /* + ** Scrolling region + */ + scroll_min_x = SCREEN_MIN_X; + scroll_min_y = SCREEN_MIN_Y; + scroll_max_x = SCREEN_MAX_X; + scroll_max_y = SCREEN_MAX_Y; + + /* + ** Initial cursor location + */ + curr_y = min_y; + curr_x = min_x; + setcursor(); + + /* + ** Notification function (or NULL) + */ + notify = fcn; + + /* + ** Set up the interrupt handler for the keyboard + */ + install_isr( VEC_KBD, keyboard_isr ); +} + +#ifdef SA_DEBUG +int main() { + cio_printf( "%d\n", 123 ); + cio_printf( "%d\n", -123 ); + cio_printf( "%d\n", 0x7fffffff ); + cio_printf( "%d\n", 0x80000001 ); + cio_printf( "%d\n", 0x80000000 ); + cio_printf( "x%14dy\n", 0x80000000 ); + cio_printf( "x%-14dy\n", 0x80000000 ); + cio_printf( "x%014dy\n", 0x80000000 ); + cio_printf( "x%-014dy\n", 0x80000000 ); + cio_printf( "%s\n", "xyz" ); + cio_printf( "|%10s|\n", "xyz" ); + cio_printf( "|%-10s|\n", "xyz" ); + cio_printf( "%c\n", 'x' ); + cio_printf( "|%4c|\n", 'y' ); + cio_printf( "|%-4c|\n", 'y' ); + cio_printf( "|%04c|\n", 'y' ); + cio_printf( "|%-04c|\n", 'y' ); + cio_printf( "|%3d|\n", 5 ); + cio_printf( "|%3d|\n", 54321 ); + cio_printf( "%x\n", 0x123abc ); + cio_printf( "|%04x|\n", 20 ); + cio_printf( "|%012x|\n", 0xfedcba98 ); + cio_printf( "|%-012x|\n", 0x76543210 ); +} + +int curr_x, curr_y, max_x, max_y; +#endif |