|
@@ -1,15 +1,52 @@
|
|
|
+#include <algorithm>
|
|
|
+
|
|
|
+#include <stdint.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <termios.h>
|
|
|
+
|
|
|
+#include <types/lock.hpp>
|
|
|
+
|
|
|
#include <kernel/event/evtqueue.hpp>
|
|
|
#include <kernel/hw/serial.h>
|
|
|
#include <kernel/process.hpp>
|
|
|
#include <kernel/tty.hpp>
|
|
|
#include <kernel/vga.hpp>
|
|
|
-#include <stdint.h>
|
|
|
-#include <stdio.h>
|
|
|
-#include <types/lock.hpp>
|
|
|
+#include <kernel/log.hpp>
|
|
|
+
|
|
|
+#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()
|
|
|
- : buf(BUFFER_SIZE)
|
|
|
+ : 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)
|
|
@@ -20,134 +57,238 @@ void tty::print(const char* 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;
|
|
|
|
|
|
- while (buf_size && n) {
|
|
|
+ do {
|
|
|
+ if (n == 0)
|
|
|
+ break;
|
|
|
+
|
|
|
auto& mtx = this->m_cv.mtx();
|
|
|
types::lock_guard lck(mtx);
|
|
|
|
|
|
if (this->buf.empty()) {
|
|
|
- bool intr = !this->m_cv.wait(mtx);
|
|
|
+ bool interrupted = !this->m_cv.wait(mtx);
|
|
|
|
|
|
- if (intr || this->buf.empty())
|
|
|
+ if (interrupted)
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- *buf = this->buf.get();
|
|
|
- --buf_size;
|
|
|
- --n;
|
|
|
+ if (this->buf.empty())
|
|
|
+ break;
|
|
|
|
|
|
- if (*(buf++) == '\n')
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
-vga_tty::vga_tty()
|
|
|
+int tty::_do_erase(bool should_echo)
|
|
|
{
|
|
|
- snprintf(this->name, sizeof(this->name), "ttyVGA");
|
|
|
-}
|
|
|
+ if (buf.empty())
|
|
|
+ return -1;
|
|
|
|
|
|
-serial_tty::serial_tty(int id)
|
|
|
- : id(id)
|
|
|
-{
|
|
|
- snprintf(this->name, sizeof(this->name), "ttyS%x", (int)id);
|
|
|
-}
|
|
|
+ int back = buf.back();
|
|
|
|
|
|
-void serial_tty::putchar(char c)
|
|
|
-{
|
|
|
- serial_send_data(id, c);
|
|
|
+ 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 vga_tty::putchar(char c)
|
|
|
+void tty::_real_commit_char(int c)
|
|
|
{
|
|
|
- static struct vga_char vc = { .c = '\0', .color = VGA_CHAR_COLOR_WHITE };
|
|
|
- vc.c = c;
|
|
|
- vga_put_char(&vc);
|
|
|
+ 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 vga_tty::recvchar(char c)
|
|
|
+void tty::_echo_char(int c)
|
|
|
{
|
|
|
- // TODO: keyboard scan code
|
|
|
- buf.put(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);
|
|
|
}
|
|
|
|
|
|
-void serial_tty::recvchar(char c)
|
|
|
+// do some ignore and remapping work
|
|
|
+// real commit operation is in _real_commit_char()
|
|
|
+void tty::commit_char(int c)
|
|
|
{
|
|
|
- switch (c) {
|
|
|
- case '\r':
|
|
|
- buf.put('\n');
|
|
|
- if (echo) {
|
|
|
- serial_send_data(id, '\r');
|
|
|
- serial_send_data(id, '\n');
|
|
|
+ // 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;
|
|
|
}
|
|
|
- this->m_cv.notify();
|
|
|
- break;
|
|
|
- // ^?: backspace
|
|
|
- case 0x7f:
|
|
|
- if (!buf.empty() && buf.back() != '\n') {
|
|
|
- buf.pop();
|
|
|
-
|
|
|
- if (echo) {
|
|
|
- serial_send_data(id, 0x08);
|
|
|
- serial_send_data(id, '\x1b');
|
|
|
- serial_send_data(id, '[');
|
|
|
- serial_send_data(id, 'K');
|
|
|
- }
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
- break;
|
|
|
- // ^U: clear the line
|
|
|
- case 0x15:
|
|
|
- while (!buf.empty() && buf.back() != '\n') {
|
|
|
- buf.pop();
|
|
|
-
|
|
|
- if (echo) {
|
|
|
- // clear the line
|
|
|
- // serial_send_data(id, '\r');
|
|
|
- // serial_send_data(id, '\x1b');
|
|
|
- // serial_send_data(id, '[');
|
|
|
- // serial_send_data(id, '2');
|
|
|
- // serial_send_data(id, 'K');
|
|
|
- serial_send_data(id, 0x08);
|
|
|
- serial_send_data(id, '\x1b');
|
|
|
- serial_send_data(id, '[');
|
|
|
- serial_send_data(id, 'K');
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
- break;
|
|
|
- // ^C: SIGINT
|
|
|
- case 0x03:
|
|
|
- procs->send_signal_grp(fg_pgroup, SIGINT);
|
|
|
- break;
|
|
|
- // ^D: EOF
|
|
|
- case 0x04:
|
|
|
- this->m_cv.notify();
|
|
|
- break;
|
|
|
- // ^Z: SIGTSTP
|
|
|
- case 0x1a:
|
|
|
- procs->send_signal_grp(fg_pgroup, SIGTSTP);
|
|
|
- break;
|
|
|
- // ^[: ESCAPE
|
|
|
- case 0x1b:
|
|
|
- buf.put('\x1b');
|
|
|
- if (echo) {
|
|
|
- serial_send_data(id, '^');
|
|
|
- serial_send_data(id, '[');
|
|
|
+
|
|
|
+ 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;
|
|
|
- // ^\: SIGQUIT
|
|
|
- case 0x1c:
|
|
|
- procs->send_signal_grp(fg_pgroup, SIGQUIT);
|
|
|
+
|
|
|
+ case '\n':
|
|
|
+ if (TERMIOS_ISET(this->termio, INLCR)) {
|
|
|
+ this->_real_commit_char('\r');
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ this->_real_commit_char('\n');
|
|
|
break;
|
|
|
+
|
|
|
default:
|
|
|
- buf.put(c);
|
|
|
- if (echo)
|
|
|
- serial_send_data(id, c);
|
|
|
+ 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();
|