// Console input and output. // Input is from the keyboard or serial port. // Output is written to the screen and serial port. #include "types.h" #include "defs.h" #include "param.h" #include "traps.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "file.h" #include "memlayout.h" #include "mmu.h" #include "proc.h" #include "x86.h" #define INPUT_BUF 128 // Proc title length + 3 for the console id + 2 for the left and right border + 2 for space between #define MENUWIDTH 27 // Number of max consoles + 1 for bottom border and another 2 for a space between top and bottom #define MENUHEIGHT (MAXVCONSOLES + 3); static int currentmenuitem = 0; static int menuactive = 0; struct kbdbuffer { char buf[INPUT_BUF]; uint r; // Read index uint w; // Write index uint e; // Edit index }; struct kbdbuffer inputBuffer; struct kbdbuffer *input = 0; struct vconsole { uint consoleindex; struct spinlock lock; struct proc *processowner; ushort screenbuffer[SCRWIDTH * SCRHEIGHT]; struct kbdbuffer keybuffer; int pos; int active; int inuse; uint titlebgcol; char proctitle[20]; int titlelocked; char status[30]; }; struct vconsole consoles[MAXVCONSOLES]; struct vconsole *currentconsole = 0; #define C(x) ((x) - '@') // Control-x struct vconsole* getvalidprocessconsoleptr(void); void clearconsole(ushort *bufferin); void loadscreenbuffer(ushort *bufferin); void savescreenbuffer(ushort *bufferin); void clearscreen(int prelocked); static void consputc(int); static int panicked = 0; static struct { struct spinlock lock; int locking; } cons; // Lock for all consoles (to lock when doing switching/creating?) static struct { struct spinlock lock; int locking; } vcons; static int getcurrentactiveconsolecount(void) { int resultcount = 0; for (int i = 0; i < MAXVCONSOLES; i++) { if (consoles[i].active) { resultcount++; } } return resultcount; } static int getconsolemenuindex(struct vconsole* consolein) { int resultcount = 0; for (int i = 0; i < MAXVCONSOLES; i++) { if (&consoles[i] == consolein && consoles[i].active) { break; } else { resultcount++; } } return resultcount; } static int getmenuindexconsole(int menuindex) { int resultcount = 0; int resultconsoleindex = 0; for (int i = 0; i < MAXVCONSOLES; i++) { if (consoles[i].active) { if (resultcount == menuindex) { resultconsoleindex = consoles[i].consoleindex; break; } else { resultcount++; } } } return resultconsoleindex; } int getconsoleindex(struct vconsole* consolein) { return consolein->consoleindex; } // Split the printint so we can just get the char out in draw title // itoa is the function in the c standard library (?) so i reused the name // since it does something similar // Saves to the provided char array and returns the number of characters for looping static int itoa(int xx, int base, int sign, char* buf) { static char digits[] = "0123456789abcdef"; int i; uint x; if (sign && (sign = xx < 0)) { x = -xx; } else { x = xx; } i = 0; do { buf[i++] = digits[x % base]; } while ((x /= base) != 0); if (sign) { buf[i++] = '-'; } return i; } static void printint(int xx, int base, int sign) { int i = 0; char buf[16]; i = itoa(xx, base, sign, buf); while (--i >= 0) { consputc(buf[i]); } } // Print to the console. only understands %d, %x, %p, %s. void cprintf(char *fmt, ...) { int i, c, locking; uint *argp; char *s; struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); locking = cons.locking; if (locking) { //acquire(&cons.lock); acquire(&inconsoleptr->lock); } if (fmt == 0) { panic("null fmt"); } argp = (uint *)(void *)(&fmt + 1); for (i = 0; (c = fmt[i] & 0xff) != 0; i++) { if (c != '%') { consputc(c); continue; } c = fmt[++i] & 0xff; if (c == 0) { break; } switch (c) { case 'd': printint(*argp++, 10, 1); break; case 'x': case 'p': printint(*argp++, 16, 0); break; case 's': if ((s = (char *)*argp++) == 0) { s = "(null)"; } for (; *s; s++) { consputc(*s); } break; case '%': consputc('%'); break; default: // Print unknown % sequence to draw attention. consputc('%'); consputc(c); break; } } if (locking) { //release(&cons.lock); release(&inconsoleptr->lock); } } void sprintf(char* strbuffer, char *fmt, ...) { int i, c, si; uint *argp; char *s; char buf[16]; int x; if (fmt == 0) { panic("null fmt"); } si = 0; argp = (uint *)(void *)(&fmt + 1); for (i = 0; (c = fmt[i] & 0xff) != 0; i++) { if (c != '%') { strbuffer[si] = c; si++; continue; } c = fmt[++i] & 0xff; if (c == 0) { break; } switch (c) { case 'd': // Clear any existing buf data memset(buf, 0, sizeof buf); x = itoa(*argp++, 10, 1, buf); while (--x >= 0) { strbuffer[si] = buf[x]; si++; } break; case 'x': case 'p': // Clear any existing buf data memset(buf, 0, sizeof buf); x = itoa(*argp++, 16, 0, buf); while (--x >= 0) { strbuffer[si] = buf[x]; si++; } break; case 's': if ((s = (char *)*argp++) == 0) { s = "(null)"; } for (int sli = 0; sli < strlen(s); sli++) { strbuffer[si] = s[sli]; si++; } break; case '%': strbuffer[i] = '%'; si++; break; default: // Print unknown % sequence to draw attention. strbuffer[si] = '%'; si++; strbuffer[si] = c; si++; break; } } // Add string terminator to the end strbuffer[si] = '\0'; } void panic(char *s) { int i; uint pcs[10]; cli(); cons.locking = 0; // use lapiccpunum so that we can call panic from mycpu() cprintf("lapicid %d: panic: ", lapicid()); cprintf(s); cprintf("\n"); getcallerpcs(&s, pcs); for (i = 0; i < 10; i++) { cprintf(" %p", pcs[i]); } panicked = 1; // freeze other CPU for (;;) { ; } } // function that checks and returns a valid vconsole pointer, since i was repeating the same checks i moved it // into its own function to keep things looking clean struct vconsole* getvalidprocessconsoleptr(void) { struct vconsole* inconsoleptr = 0; // Check if a process has actually been created otherwise use the base console if (myproc() == 0x0) { inconsoleptr = currentconsole; if (inconsoleptr == 0) { // fall back to the base console if the process and currentconsole has no console assigned for some reason inconsoleptr = &consoles[0]; } } else { inconsoleptr = myproc()->consoleptr; if (inconsoleptr == 0) { // fall back to the console set in currentconsole inconsoleptr = currentconsole; if (inconsoleptr == 0) { // fall back to the base console if the process and currentconsole has no console assigned for some reason inconsoleptr = &consoles[0]; } } } return inconsoleptr; } #define BACKSPACE 0x100 #define CRTPORT 0x3d4 #define TITLEOFF (SCRWIDTH * 1) // Size of the offset we need for the title bar static ushort *crt = (ushort *)P2V(0xb8000); // CGA memory static void drawmenu(void) { struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); int rows = getcurrentactiveconsolecount() + 3; int pos = TITLEOFF; uint c = 0; char menulines[MAXVCONSOLES][24]; for (int i = 0; i < MAXVCONSOLES; i++) { if (consoles[i].active) { memset(menulines[i], 0, sizeof(menulines[i][0]) * sizeof(menulines[i])); sprintf(menulines[i], "%d: %s", consoles[i].consoleindex, consoles[i].proctitle); } } for (int y = 0; y < rows; y++) { pos = TITLEOFF + (y * SCRWIDTH); for (int x = 0; x < MENUWIDTH; x++) { if (y == 0 || y == (rows - 2)) { if (x == 0 || x == (MENUWIDTH - 1)) { c = 0xB2 | inconsoleptr->titlebgcol; } else { c = 0x0720; } } else if (y == (rows - 1)) { c = 0xB2 | inconsoleptr->titlebgcol; } else { if (x == 0 || x == (MENUWIDTH - 1)) { c = 0xB2 | inconsoleptr->titlebgcol; } else if (x == 1 || x == (MENUWIDTH - 2)) { c = 0x0720; } else { c = menulines[y - 1][x - 2]; c = (c & 0xff); if (currentmenuitem == (y - 1)) { c = c | 0xCF00; } else { c = c | 0x0700; } } } crt[pos] = c; pos++; } } menuactive = 1; } static void navigatemenu(int dir) { int pos = 0; int newindex = currentmenuitem + dir; int totalactiveconsoles = getcurrentactiveconsolecount(); if (newindex < 0) { newindex = 0; } else if (newindex >= totalactiveconsoles) { newindex = totalactiveconsoles - 1; } if (newindex != currentmenuitem) { pos = TITLEOFF + ((currentmenuitem + 1) * SCRWIDTH); for (int x = 2; x < MENUWIDTH - 2; x++) { crt[pos + x] = (crt[pos + x] & 0x00FF) | 0x0700; } pos = TITLEOFF + ((newindex + 1) * SCRWIDTH); for (int x = 2; x < MENUWIDTH - 2; x++) { crt[pos + x] = crt[pos + x] | 0xCF00; } currentmenuitem = newindex; } } static void closemenu(void) { loadscreenbuffer(currentconsole->screenbuffer); int pos = currentconsole->pos; outb(CRTPORT, 14); outb(CRTPORT + 1, pos >> 8); outb(CRTPORT, 15); outb(CRTPORT + 1, pos); menuactive = 0; } static void drawtitle(void) { struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); int pos = 0; char c = ' '; char menutext[] = "(M)enu"; char consoletitletext[10]; // + (inconsoleptr->consoleindex + 1); sprintf(consoletitletext, "Console %d", inconsoleptr->consoleindex); for (pos = 0; pos < SCRWIDTH; pos++) { int offset = 0; if (pos < 6) { c = menutext[pos - offset]; } else if (pos >= 10 && pos < 20) { offset = 10; c = consoletitletext[pos - offset]; } else if (pos >= 25 && pos < 45) { offset = 25; c = inconsoleptr->proctitle[pos - offset]; } else if (pos >= 50 && pos < 80) { offset = 50; c = inconsoleptr->status[pos - offset]; } else { c = ' '; } if (inconsoleptr->inuse) { crt[pos] = (c & 0xff) | inconsoleptr->titlebgcol; } inconsoleptr->screenbuffer[pos] = (c & 0xff) | inconsoleptr->titlebgcol; } } static void cgaputc(int c) { int pos; struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); ushort *currentbuffer = inconsoleptr->screenbuffer; if (inconsoleptr->inuse) { // Cursor position: col + 80*row. outb(CRTPORT, 14); pos = inb(CRTPORT + 1) << 8; outb(CRTPORT, 15); pos |= inb(CRTPORT + 1); } else { pos = inconsoleptr->pos; } if (c == '\n') { pos += SCRWIDTH - pos % SCRWIDTH; } else if (c == BACKSPACE) { if (pos > (TITLEOFF)) { currentbuffer[pos] = 0; // Clear the character from the buffer --pos; } } else { // int posp = pos; if (inconsoleptr->inuse) { crt[pos] = (c & 0xff) | 0x0700; // black on white } currentbuffer[pos] = (c & 0xff) | 0x0700; // black on white pos++; } if (pos < TITLEOFF || pos > SCRHEIGHT * SCRWIDTH) { panic("pos under/overflow"); } if ((pos / 80) >= 24) { // Scroll up. memmove(currentbuffer + TITLEOFF, currentbuffer + (SCRWIDTH + TITLEOFF), sizeof(crt[0]) * (SCRHEIGHT - 1) * SCRWIDTH); pos -= 80; memset(currentbuffer + pos, 0, sizeof(crt[0]) * (SCRHEIGHT * SCRWIDTH - pos)); if (inconsoleptr->inuse) { int menuoffset = 0; if (menuactive) { int menuitems = getcurrentactiveconsolecount() + 3; for (int y = 0; y < menuitems; y++) { memmove(crt + menuoffset + TITLEOFF + MENUWIDTH, crt + menuoffset + (SCRWIDTH + TITLEOFF) + MENUWIDTH, sizeof(crt[0]) * (SCRWIDTH - MENUWIDTH)); menuoffset += SCRWIDTH; } } memmove(crt + TITLEOFF + menuoffset, crt + (SCRWIDTH + TITLEOFF) + menuoffset, (sizeof(crt[0]) * (SCRHEIGHT - 1) * SCRWIDTH) - menuoffset); memset(crt + pos, 0, sizeof(crt[0]) * (SCRHEIGHT * SCRWIDTH - pos)); } } inconsoleptr->pos = pos; if (inconsoleptr->inuse) { outb(CRTPORT, 14); outb(CRTPORT + 1, pos >> 8); outb(CRTPORT, 15); outb(CRTPORT + 1, pos); crt[pos] = ' ' | 0x0700; } } void consputc(int c) { if (panicked) { cli(); for (;;) { ; } } if (c == BACKSPACE) { uartputc('\b'); uartputc(' '); uartputc('\b'); } else { uartputc(c); } cgaputc(c); } int consoleget(void) { //struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); int c; acquire(&cons.lock); while ((c = kbdgetc()) <= 0) { if (c == 0) { c = kbdgetc(); } } release(&cons.lock); return c; } void consoleintr(int (*getc)(void)) { int c; int doprocdump = 0; int doconsoleswitch = 0; int switchto = -1; int doconsolehome = 0; //struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); struct kbdbuffer* consolekbdbuffer = ¤tconsole->keybuffer; acquire(&cons.lock); while ((c = getc()) >= 0) { switch (c) { case 27: if (menuactive) { closemenu(); } break; // Up arrow (found in kbd.h) case 0xE2: if (menuactive) { navigatemenu(-1); } break; // Down arrow (found in kbd.h) case 0xE3: if (menuactive) { navigatemenu(1); } break; case C('P'): // Process listing. // procdump() locks cons.lock indirectly; invoke later doprocdump = 1; break; case C('U'): // Kill line. while (consolekbdbuffer->e != consolekbdbuffer->w && consolekbdbuffer->buf[(consolekbdbuffer->e - 1) % INPUT_BUF] != '\n') { consolekbdbuffer->e--; consputc(BACKSPACE); } break; case C('H'): case '\x7f': // Backspace if (consolekbdbuffer->e != consolekbdbuffer->w) { consolekbdbuffer->e--; consputc(BACKSPACE); } break; case C('T'): doconsoleswitch = 1; break; case C('K'): doconsolehome = 1; break; case C('M'): if (!menuactive) { drawmenu(); } else { closemenu(); } break; // If enter is hit and the menu is active then we want to override the normal behaviour case 10: if (menuactive) { closemenu(); switchto = currentmenuitem; doconsoleswitch = 1; // The menu has been sorted we can break from the switch break; } // if the menu was not active we want the enter to be handled in the default case default: if (!menuactive) { if (c != 0 && consolekbdbuffer->e - consolekbdbuffer->r < INPUT_BUF) { c = (c == '\r') ? '\n' : c; consolekbdbuffer->buf[consolekbdbuffer->e++ % INPUT_BUF] = c; consputc(c); if (c == '\n' || c == C('D') || consolekbdbuffer->e == consolekbdbuffer->r + INPUT_BUF) { consolekbdbuffer->w = consolekbdbuffer->e; wakeup(&(consolekbdbuffer->r)); } } } break; } } release(&cons.lock); if (doprocdump && !menuactive) { procdump(); // now call procdump() wo. cons.lock held } if (doconsolehome) { if (currentconsole->consoleindex != 0) { switchtoconsole(getbaseconsoleptr()); } } if (doconsoleswitch) { struct vconsole* toconsole = 0; acquire(&cons.lock); if (switchto == -1) { for (int i = (currentconsole->consoleindex + 1); i < MAXVCONSOLES; i++) { if (consoles[i].active) { toconsole = &consoles[i]; break; } } if (toconsole == 0) { toconsole = getbaseconsoleptr(); } } else { toconsole = &consoles[getmenuindexconsole(switchto)]; } release(&cons.lock); if (!toconsole->inuse) { switchtoconsole(toconsole); } } } int consoleread(struct inode *ip, char *dst, int n) { uint target; int c; struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); struct kbdbuffer* consolekbdbuffer = &inconsoleptr->keybuffer; iunlock(ip); target = n; //acquire(&cons.lock); acquire(&inconsoleptr->lock); while (n > 0) { while (consolekbdbuffer->r == consolekbdbuffer->w) { if (myproc()->killed) { release(&inconsoleptr->lock); ilock(ip); return -1; } sleep(&(consolekbdbuffer->r), &inconsoleptr->lock); } c = consolekbdbuffer->buf[consolekbdbuffer->r++ % INPUT_BUF]; if (c == C('D')) { // EOF if (n < target) { // Save ^D for next time, to make sure // caller gets a 0-byte result. consolekbdbuffer->r--; } break; } *dst++ = c; --n; if (c == '\n') { break; } } //release(&cons.lock); release(&inconsoleptr->lock); ilock(ip); return target - n; } int consolewrite(struct inode *ip, char *buf, int n) { int i; struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); iunlock(ip); //acquire(&cons.lock); acquire(&inconsoleptr->lock); for (i = 0; i < n; i++) { consputc(buf[i] & 0xff); } //release(&cons.lock); release(&inconsoleptr->lock); ilock(ip); return n; } // Function to flood a buffer with A-Z characters looping (for testing initially) void testfillbuffer(ushort *bufferin) { ushort firstchar = 65; ushort lastchar = 90; ushort currentchar = firstchar; for (int i = 0; i < (SCRHEIGHT * SCRWIDTH); i++) { if (currentchar > lastchar) { currentchar = firstchar; } bufferin[i] = (currentchar & 0xff) | 0x0700; currentchar++; } } struct vconsole* getbaseconsoleptr(void) { return &consoles[0]; } void clearconsole(ushort *bufferin) { // Flood the screen buffer with blank spaces memset(bufferin, 0, sizeof(bufferin[0]) * SCRHEIGHT * SCRWIDTH); } void loadscreenbuffer(ushort *bufferin) { // Copy the memory from the console buffer to the crt buffer memmove(crt, bufferin, sizeof(bufferin[0]) * SCRHEIGHT * SCRWIDTH); } void savescreenbuffer(ushort *bufferin) { // Copy the memory from the console buffer to the crt buffer memmove(bufferin, crt, sizeof(crt[0]) * SCRHEIGHT * SCRWIDTH); } void clearscreen(int prelocked) { // Check if a process has actually been created otherwise use the base console struct vconsole* inconsoleptr = getvalidprocessconsoleptr(); if (!prelocked) { acquire(&cons.lock); } int pos = TITLEOFF; clearconsole(inconsoleptr->screenbuffer); inconsoleptr->pos = pos; if (inconsoleptr->inuse) { loadscreenbuffer(inconsoleptr->screenbuffer); outb(CRTPORT, 14); outb(CRTPORT + 1, pos >> 8); outb(CRTPORT, 15); outb(CRTPORT + 1, pos); } drawtitle(); if (!prelocked) { release(&cons.lock); } } void consoleinit(void) { initlock(&cons.lock, "console"); initlock(&vcons.lock, "vconglobal"); for (int i = 0; i < MAXVCONSOLES; i++) { consoles[i].consoleindex = i; consoles[i].processowner = 0; consoles[i].pos = 0; consoles[i].active = 0; consoles[i].inuse = 0; consoles[i].titlebgcol = 0x4F00; char lockname[8]; sprintf(lockname, "vconsole%d", i); initlock(&consoles[i].lock, lockname); } // Initialise pointer to point to our console input buffer currentconsole = &consoles[0]; currentconsole->active = 1; currentconsole->inuse = 1; input = ¤tconsole->keybuffer; devsw[CONSOLE].write = consolewrite; devsw[CONSOLE].read = consoleread; cons.locking = 1; clearscreen(0); cprintf("Welcome! you are currently in the base console\n"); ioapicenable(IRQ_KBD, 0); } struct vconsole* newconsole(char* title, int bgpreset) { struct vconsole* result = 0; acquire(&vcons.lock); for (int i = 1; i < MAXVCONSOLES; i++) { if (!consoles[i].active) { consoles[i].processowner = myproc(); consoles[i].titlebgcol = bgpreset; if (strlen(title) > 0) { safestrcpy(consoles[i].proctitle, title, sizeof(consoles[i].proctitle)); consoles[i].titlelocked = 1; } else { consoles[i].titlelocked = 0; } result = &consoles[i]; break; } } release(&vcons.lock); return result; } void releaseallconsoles(void) { for (int i = 0; i < MAXVCONSOLES; i++) { consoles[i].inuse = 0; } } int switchtoconsole(struct vconsole* consoleptr) { int pos; acquire(&vcons.lock); releaseallconsoles(); currentconsole = consoleptr; currentconsole->inuse = 1; //input = ¤tconsole->keybuffer; // ioapicenable(IRQ_KBD, 0); acquire(&cons.lock); loadscreenbuffer(currentconsole->screenbuffer); if (!currentconsole->active) { clearscreen(1); cprintf("Welcome to Console: %d\n", currentconsole->consoleindex); currentconsole->active = 1; } else { pos = currentconsole->pos; outb(CRTPORT, 14); outb(CRTPORT + 1, pos >> 8); outb(CRTPORT, 15); outb(CRTPORT + 1, pos); } //drawtitle(); menuactive = 0; currentmenuitem = getconsolemenuindex(currentconsole); release(&cons.lock); release(&vcons.lock); return 0; } int closeconsole(void) { struct vconsole* consoleptr = myproc()->consoleptr; //cprintf("Console Owner PID: %d\n", consoleptr->processowner->pid); acquire(&vcons.lock); if (myproc() == consoleptr->processowner) { clearconsole(consoleptr->screenbuffer); consoleptr->active = 0; if (consoleptr->inuse) { consoleptr->inuse = 0; release(&vcons.lock); switchtoconsole(&consoles[0]); return 1; } } release(&vcons.lock); return 0; } int getcurrentconsoleindex(void) { acquire(&vcons.lock); for (int i = 0; i < MAXVCONSOLES; i++) { if (consoles[i].inuse) { release(&vcons.lock); return consoles[i].consoleindex; } } release(&vcons.lock); return -1; } void setconsoleproctitle(struct vconsole* consoleptr, char* newtitle) { if (!consoleptr->titlelocked) { acquire(&consoleptr->lock); //Clear the current buffer memset(consoleptr->proctitle, 0, sizeof(consoleptr->proctitle[0]) * sizeof(consoleptr->proctitle)); //Copy the new title into the console title buffer safestrcpy(consoleptr->proctitle, newtitle, sizeof(consoleptr->proctitle)); //Redraw the title since its been updated now drawtitle(); release(&consoleptr->lock); } }