text: //
  // Console input and output, to the uart.
  // Reads are line at a time.
  // Implements special input characters:
  //   newline -- end of line
  //   control-h -- backspace
  //   control-u -- kill line
  //   control-d -- end of file
  //   control-p -- print process list
  //
  
  #include <stdarg.h>
  
  #include "types.h"
  #include "param.h"
  #include "spinlock.h"
  #include "sleeplock.h"
  #include "fs.h"
  #include "file.h"
  #include "memlayout.h"
  #include "riscv.h"
  #include "defs.h"
  #include "proc.h"
  
  #define BACKSPACE 0x100
  #define C(x)  ((x)-'@')  // Control-x
  #define NBCONSOLES 3
  
  //
  // send one character to the uart.
  //
  void
  consputc(int c)
  {
    extern volatile int panicked; // from printf.c
  
    if(panicked){
      for(;;)
        ;
    }
  
    if(c == BACKSPACE){
      // if the user typed backspace, overwrite with a space.
      uartputc('\b'); uartputc(' '); uartputc('\b');
    } else {
      uartputc(c);
    }
  }
  
  struct cons_t {
    struct spinlock lock;
    
    // input
  #define INPUT_BUF 128
    char buf[INPUT_BUF];
    uint r;  // Read index
    uint w;  // Write index
    uint e;  // Edit index
  } consoles[NBCONSOLES];
  
  struct spinlock console_number_lock;
  int console_number = 0;
  struct cons_t* cons;
  
  //
  // user write()s to the console go here.
  //
  int
  consolewrite(struct file *f, int user_src, uint64 src, int n)
  {
    int i;
  
    struct cons_t* cons = &consoles[f->minor-1];
    acquire(&console_number_lock);
    while(console_number != f->minor - 1){
      sleep(cons, &console_number_lock);
    }
    release(&console_number_lock);
    char* buf = bd_malloc(n);
    for(i = 0; i < n; i++){
      if(either_copyin(&buf[i], user_src, src+i, 1) == -1)
        break;
    }
    acquire(&cons->lock);
    for(int j = 0; j < i; j++){
      consputc(buf[j]);
    }
    release(&cons->lock);
    bd_free(buf);
    return n;
  }
  
  //
  // user read()s from the console go here.
  // copy (up to) a whole input line to dst.
  // user_dist indicates whether dst is a user
  // or kernel address.
  //
  int
  consoleread(struct file *f, int user_dst, uint64 dst, int n)
  {
    uint target;
    int c;
  
    target = n;
    struct cons_t* cons = &consoles[f->minor-1];
    acquire(&console_number_lock);
    while(console_number != f->minor - 1){
      sleep(cons, &console_number_lock);
    }
    release(&console_number_lock);
    int len = 0;
    char* buf = bd_malloc(n);
    acquire(&cons->lock);
    while(n > 0){
      // wait until interrupt handler has put some
      // input into cons->buffer.
      while(cons->r == cons->w){
        if(myproc()->killed){
          release(&cons->lock);
          return -1;
        }
        sleep(&cons->r, &cons->lock);
      }
  
      c = cons->buf[cons->r++ % INPUT_BUF];
  
      if(c == C('D')){  // end-of-file
        if(n < target){
          // Save ^D for next time, to make sure
          // caller gets a 0-byte result.
          cons->r--;
        }
        break;
      }
  
      // copy the input byte to the user-space buffer.
      buf[len++] = c;
      --n;
  
      if(c == '\n'){
        // a whole line has arrived, return to
        // the user-level read().
        break;
      }
    }
    release(&cons->lock);
    either_copyout(user_dst, dst, buf, len);
    bd_free(buf);
    return target - n;
  }
  
  //
  // the console input interrupt handler.
  // uartintr() calls this for input character.
  // do erase/kill processing, append to cons->buf,
  // wake up consoleread() if a whole line has arrived.
  //
  void
  consoleintr(int c)
  {
    acquire(&cons->lock);
  
    switch(c){
    case C('P'):  // Print process list.
      procdump();
      break;
    case C('Q'):  // Print priority list
      priodump();
      break;
    case C('U'):  // Kill line.
      while(cons->e != cons->w &&
            cons->buf[(cons->e-1) % INPUT_BUF] != '\n'){
        cons->e--;
        consputc(BACKSPACE);
      }
      break;
    case C('S'): // switch consoles
    {
      acquire(&console_number_lock);
      struct spinlock* old = &cons->lock;
      console_number = (console_number + 1) % NBCONSOLES;
      cons = &consoles[console_number];
      acquire(&cons->lock);
      release(old);
      wakeup(cons);
      printf("Switched to console number %d\n", console_number);
      release(&console_number_lock);
      break;
    }
    case C('H'): // Backspace
    case '\x7f':
      if(cons->e != cons->w){
        cons->e--;
        consputc(BACKSPACE);
      }
      break;
    default:
      if(c != 0 && cons->e-cons->r < INPUT_BUF){
        c = (c == '\r') ? '\n' : c;
  
        // echo back to the user.
        consputc(c);
  
        // store for consumption by consoleread().
        cons->buf[cons->e++ % INPUT_BUF] = c;
  
        if(c == '\n' || c == C('D') || cons->e == cons->r+INPUT_BUF){
          // wake up consoleread() if a whole line (or end-of-file)
          // has arrived.
          cons->w = cons->e;
          wakeup(&cons->r);
        }
      }
      break;
    }
    
    release(&cons->lock);
  }
  
  void
  consoleinit(void)
  {
    initlock(&console_number_lock, "console_number_lock");
    console_number = 0;
    cons = &consoles[console_number];
    for(int i = 0; i < NBCONSOLES; i++){
      initlock(&consoles[i].lock, "cons");
    }
  
    uartinit();
  
    // connect read and write system calls
    // to consoleread and consolewrite.
    devsw[CONSOLE].read = consoleread;
    devsw[CONSOLE].write = consolewrite;
  }
  