#include #include #include #include #include #include #include #include #include #include #include #define CTRL(key) ((key)-0x40) #define TERMIOS_ISET(termios, option) ((option) == ((termios).c_iflag & (option))) #define TERMIOS_OSET(termios, option) ((option) == ((termios).c_oflag & (option))) #define TERMIOS_CSET(termios, option) ((option) == ((termios).c_cflag & (option))) #define TERMIOS_LSET(termios, option) ((option) == ((termios).c_lflag & (option))) #define TERMIOS_TESTCC(c, termios, cc) ((c != 0xff) && (c == ((termios).c_cc[cc]))) tty::tty() : termio { .c_iflag = ICRNL | IXOFF, .c_oflag = OPOST | ONLCR, .c_cflag = B38400 | CS8 | CREAD | HUPCL, .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN, .c_line = N_TTY, .c_cc {}, .c_ispeed = 38400, .c_ospeed = 38400, } , buf(BUFFER_SIZE) , fg_pgroup { 0 } { memset(this->termio.c_cc, 0x00, sizeof(this->termio.c_cc)); // other special characters is not supported for now this->termio.c_cc[VINTR] = CTRL('C'); this->termio.c_cc[VQUIT] = CTRL('\\'); this->termio.c_cc[VERASE] = 0x7f; this->termio.c_cc[VKILL] = CTRL('U'); this->termio.c_cc[VEOF] = CTRL('D'); this->termio.c_cc[VSUSP] = CTRL('Z'); this->termio.c_cc[VMIN] = 1; } void tty::print(const char* str) { while (*str != '\0') this->putchar(*(str++)); } size_t tty::read(char* buf, size_t buf_size, size_t n) { n = std::max(buf_size, n); size_t orig_n = n; do { if (n == 0) break; auto& mtx = this->m_cv.mtx(); types::lock_guard lck(mtx); if (this->buf.empty()) { bool interrupted = !this->m_cv.wait(mtx); if (interrupted) break; } if (this->buf.empty()) break; if (!TERMIOS_LSET(this->termio, ICANON)) { --n, *buf = this->buf.get(); break; } while (n &&!this->buf.empty()) { int c = this->buf.get(); --n, *(buf++) = c; // canonical mode if (c == '\n') break; } } while (false); return orig_n - n; } int tty::_do_erase(bool should_echo) { if (buf.empty()) return -1; int back = buf.back(); if (back == '\n' || back == this->termio.c_cc[VEOF]) return -1; if (back == this->termio.c_cc[VEOL] || back == this->termio.c_cc[VEOL2]) return -1; buf.pop(); if (should_echo && TERMIOS_LSET(this->termio, ECHO | ECHOE)) { this->show_char('\b'); // backspace this->show_char(' '); // space this->show_char('\b'); // backspace // xterm's way to show backspace // serial_send_data(id, '\b'); // serial_send_data(id, CTRL('[')); // serial_send_data(id, '['); // serial_send_data(id, 'K'); } return back; } void tty::_real_commit_char(int c) { switch (c) { case '\n': buf.put(c); if (TERMIOS_LSET(this->termio, ECHONL) || TERMIOS_LSET(this->termio, ECHO)) this->_echo_char(c); if (TERMIOS_LSET(this->termio, ICANON)) this->m_cv.notify(); break; default: buf.put(c); if (TERMIOS_LSET(this->termio, ECHO)) this->_echo_char(c); break; } } void tty::_echo_char(int c) { // ECHOCTL do { if (c < 0 || c >= 32 || !TERMIOS_LSET(this->termio, ECHO | ECHOCTL | IEXTEN)) break; if (c == '\t' || c == '\n' || c == CTRL('Q') || c == CTRL('S')) break; this->show_char('^'); this->show_char(c + 0x40); return; } while (false); this->show_char(c); } // do some ignore and remapping work // real commit operation is in _real_commit_char() void tty::commit_char(int c) { // check special control characters // if handled, the character is discarded if (TERMIOS_LSET(this->termio, ISIG)) { if (TERMIOS_TESTCC(c, this->termio, VINTR)) { if (!TERMIOS_LSET(this->termio, NOFLSH)) this->clear_read_buf(); this->_echo_char(c); procs->send_signal_grp(fg_pgroup, SIGINT); return; } if (TERMIOS_TESTCC(c, this->termio, VSUSP)) { if (!TERMIOS_LSET(this->termio, NOFLSH)) this->clear_read_buf(); this->_echo_char(c); procs->send_signal_grp(fg_pgroup, SIGTSTP); return; } if (TERMIOS_TESTCC(c, this->termio, VQUIT)) { if (!TERMIOS_LSET(this->termio, NOFLSH)) this->clear_read_buf(); this->_echo_char(c); procs->send_signal_grp(fg_pgroup, SIGQUIT); return; } } // if handled, the character is discarded if (TERMIOS_LSET(this->termio, ICANON)) { if (TERMIOS_TESTCC(c, this->termio, VEOF)) { this->m_cv.notify(); return; } if (TERMIOS_TESTCC(c, this->termio, VKILL)) { if (TERMIOS_LSET(this->termio, ECHOKE | IEXTEN)) { while (this->_do_erase(true) != -1) ; } else if (TERMIOS_LSET(this->termio, ECHOK)) { while (this->_do_erase(false) != -1) ; this->show_char('\n'); } return; } if (TERMIOS_TESTCC(c, this->termio, VERASE)) { this->_do_erase(true); return; } } switch (c) { case '\r': if (TERMIOS_ISET(this->termio, IGNCR)) break; if (TERMIOS_ISET(this->termio, ICRNL)) { this->_real_commit_char('\n'); break; } this->_real_commit_char('\r'); break; case '\n': if (TERMIOS_ISET(this->termio, INLCR)) { this->_real_commit_char('\r'); break; } this->_real_commit_char('\n'); break; default: this->_real_commit_char(c); break; } } void tty::show_char(int c) { this->putchar(c); } vga_tty::vga_tty() { snprintf(this->name, sizeof(this->name), "ttyVGA"); } serial_tty::serial_tty(int id) : id(id) { snprintf(this->name, sizeof(this->name), "ttyS%x", (int)id); } void serial_tty::putchar(char c) { serial_send_data(id, c); } void vga_tty::putchar(char c) { static struct vga_char vc = { .c = '\0', .color = VGA_CHAR_COLOR_WHITE }; vc.c = c; vga_put_char(&vc); } void tty::clear_read_buf(void) { this->buf.clear(); }