/* ** 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))) | \ 0x80000000) // 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