kern/kernel/cio.c
2025-03-27 17:56:39 -04:00

1028 lines
20 KiB
C

/*
** 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