Browse Source

feature(tty): support of tty termios

greatbridf 11 months ago
parent
commit
698266b865

+ 2 - 0
gblibc/include/bits/ioctl.h

@@ -3,6 +3,8 @@
 
 #include <sys/uio.h>
 
+#define TCGETS (0x5401)
+#define TCSETS (0x5402)
 #define TIOCGPGRP (0x540f)
 #define TIOCSPGRP (0x5410)
 #define TIOCGWINSZ (0x5413)

+ 182 - 0
gblibc/include/termios.h

@@ -0,0 +1,182 @@
+#ifndef __GBLIBC_TERMIOS_H_
+#define __GBLIBC_TERMIOS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NCCS 32
+
+typedef unsigned char cc_t;
+typedef unsigned int speed_t;
+typedef unsigned int tcflag_t;
+
+struct termios {
+    tcflag_t c_iflag;
+    tcflag_t c_oflag;
+    tcflag_t c_cflag;
+    tcflag_t c_lflag;
+
+    cc_t c_line;
+    cc_t c_cc[NCCS];
+
+    speed_t c_ispeed;
+    speed_t c_ospeed;
+};
+
+// taken from linux kernel code
+
+/* c_cc characters */
+#define VINTR		 0
+#define VQUIT		 1
+#define VERASE		 2
+#define VKILL		 3
+#define VEOF		 4
+#define VTIME		 5
+#define VMIN		 6
+#define VSWTC		 7
+#define VSTART		 8
+#define VSTOP		 9
+#define VSUSP		10
+#define VEOL		11
+#define VREPRINT	12
+#define VDISCARD	13
+#define VWERASE		14
+#define VLNEXT		15
+#define VEOL2		16
+
+/* c_iflag bits */
+#define IGNBRK	0x0001			/* Ignore break condition */
+#define BRKINT	0x0002			/* Signal interrupt on break */
+#define IGNPAR	0x0004			/* Ignore characters with parity errors */
+#define PARMRK	0x0008			/* Mark parity and framing errors */
+#define INPCK	0x0010			/* Enable input parity check */
+#define ISTRIP	0x0020			/* Strip 8th bit off characters */
+#define INLCR	0x0040			/* Map NL to CR on input */
+#define IGNCR	0x0080			/* Ignore CR */
+#define ICRNL	0x0100			/* Map CR to NL on input */
+#define IUCLC	0x0200
+#define IXON	0x0400
+#define IXANY	0x0800			/* Any character will restart after stop */
+#define IXOFF	0x1000
+#define IMAXBEL	0x2000
+#define IUTF8	0x4000
+
+/* c_oflag bits */
+#define OPOST	0x00001			/* Perform output processing */
+#define OLCUC	0x00002
+#define ONLCR	0x00004
+#define OCRNL	0x00008
+#define ONOCR	0x00010
+#define ONLRET	0x00020
+#define OFILL	0x00040
+#define OFDEL	0x00080
+#define NLDLY	0x00100
+#define   NL0	0x00000
+#define   NL1	0x00100
+#define CRDLY	0x00600
+#define   CR0	0x00000
+#define   CR1	0x00200
+#define   CR2	0x00400
+#define   CR3	0x00600
+#define TABDLY	0x01800
+#define   TAB0	0x00000
+#define   TAB1	0x00800
+#define   TAB2	0x01000
+#define   TAB3	0x01800
+#define   XTABS	0x01800
+#define BSDLY	0x02000
+#define   BS0	0x00000
+#define   BS1	0x02000
+#define VTDLY	0x04000
+#define   VT0	0x00000
+#define   VT1	0x04000
+#define FFDLY	0x08000
+#define   FF0	0x00000
+#define   FF1	0x08000
+
+/* c_cflag bit meaning */
+/* Common CBAUD rates */
+#define     B0		0x00000000	/* hang up */
+#define    B50		0x00000001
+#define    B75		0x00000002
+#define   B110		0x00000003
+#define   B134		0x00000004
+#define   B150		0x00000005
+#define   B200		0x00000006
+#define   B300		0x00000007
+#define   B600		0x00000008
+#define  B1200		0x00000009
+#define  B1800		0x0000000a
+#define  B2400		0x0000000b
+#define  B4800		0x0000000c
+#define  B9600		0x0000000d
+#define B19200		0x0000000e
+#define B38400		0x0000000f
+#define EXTA		B19200
+#define EXTB		B38400
+
+#define ADDRB		0x20000000	/* address bit */
+#define CMSPAR		0x40000000	/* mark or space (stick) parity */
+#define CRTSCTS		0x80000000	/* flow control */
+
+#define IBSHIFT		16		/* Shift from CBAUD to CIBAUD */
+
+#define CBAUD		0x0000100f
+#define CSIZE		0x00000030
+#define   CS5		0x00000000
+#define   CS6		0x00000010
+#define   CS7		0x00000020
+#define   CS8		0x00000030
+#define CSTOPB		0x00000040
+#define CREAD		0x00000080
+#define PARENB		0x00000100
+#define PARODD		0x00000200
+#define HUPCL		0x00000400
+#define CLOCAL		0x00000800
+#define CBAUDEX		0x00001000
+#define BOTHER		0x00001000
+#define     B57600	0x00001001
+#define    B115200	0x00001002
+#define    B230400	0x00001003
+#define    B460800	0x00001004
+#define    B500000	0x00001005
+#define    B576000	0x00001006
+#define    B921600	0x00001007
+#define   B1000000	0x00001008
+#define   B1152000	0x00001009
+#define   B1500000	0x0000100a
+#define   B2000000	0x0000100b
+#define   B2500000	0x0000100c
+#define   B3000000	0x0000100d
+#define   B3500000	0x0000100e
+#define   B4000000	0x0000100f
+#define CIBAUD		0x100f0000	/* input baud rate */
+
+/* c_lflag bits */
+#define ISIG	0x00001
+#define ICANON	0x00002
+#define XCASE	0x00004
+#define ECHO	0x00008
+#define ECHOE	0x00010
+#define ECHOK	0x00020
+#define ECHONL	0x00040
+#define NOFLSH	0x00080
+#define TOSTOP	0x00100
+#define ECHOCTL	0x00200
+#define ECHOPRT	0x00400
+#define ECHOKE	0x00800
+#define FLUSHO	0x01000
+#define PENDIN	0x04000
+#define IEXTEN	0x08000
+#define EXTPROC	0x10000
+
+// line disciplines
+
+#define N_TTY 0
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 1 - 1
include/kernel/log.hpp

@@ -7,7 +7,7 @@
 #define kmsgf(fmt, ...) \
     if (1) {\
         char buf[512]; \
-        snprintf(buf, sizeof(buf), fmt __VA_OPT__(,) __VA_ARGS__); \
+        snprintf(buf, sizeof(buf), fmt "\n" __VA_OPT__(,) __VA_ARGS__); \
         console->print(buf); \
     }
 

+ 19 - 5
include/kernel/tty.hpp

@@ -1,7 +1,10 @@
 #pragma once
-#include <kernel/event/evtqueue.hpp>
+
 #include <stdint.h>
 #include <sys/types.h>
+#include <termios.h>
+
+#include <kernel/event/evtqueue.hpp>
 #include <types/allocator.hpp>
 #include <types/buffer.hpp>
 #include <types/cplusplus.hpp>
@@ -11,13 +14,26 @@ public:
     static constexpr size_t BUFFER_SIZE = 4096;
     static constexpr size_t NAME_SIZE = 32;
 
+private:
+    void _real_commit_char(int c);
+    void _echo_char(int c);
+
+    int _do_erase(bool should_echo);
+
 public:
     tty();
     virtual void putchar(char c) = 0;
-    virtual void recvchar(char c) = 0;
     void print(const char* str);
     size_t read(char* buf, size_t buf_size, size_t n);
 
+    // characters committed to buffer will be handled
+    // by the input line discipline (N_TTY)
+    void commit_char(int c);
+
+    // print character to the output
+    // characters will be handled by the output line discipline
+    void show_char(int c);
+
     void clear_read_buf(void);
 
     constexpr void set_pgrp(pid_t pgid)
@@ -31,7 +47,7 @@ public:
     }
 
     char name[NAME_SIZE];
-    bool echo = true;
+    termios termio;
 
 protected:
     types::buffer buf;
@@ -44,14 +60,12 @@ class vga_tty : public virtual tty {
 public:
     vga_tty();
     virtual void putchar(char c) override;
-    virtual void recvchar(char c) override;
 };
 
 class serial_tty : public virtual tty {
 public:
     serial_tty(int id);
     virtual void putchar(char c) override;
-    virtual void recvchar(char c) override;
 
 public:
     uint16_t id;

+ 1 - 1
init_script.sh

@@ -15,7 +15,7 @@ mknod -m 666 /dev/sda b 8 0
 mknod -m 666 /dev/sda1 b 8 1
 
 cat > /etc/passwd <<EOF
-root:x:0:0:root:/root:/mnt/busybox_ sh
+root:x:0:0:root:/root:/mnt/busybox sh
 EOF
 
 cat > /etc/group <<EOF

+ 1 - 1
src/kernel/hw/serial.cpp

@@ -9,7 +9,7 @@ static void serial_receive_data_interrupt(void)
 {
     while (is_serial_has_data(PORT_SERIAL0)) {
         uint8_t data = serial_read_data(PORT_SERIAL0);
-        console->recvchar(data);
+        console->commit_char(data);
     }
 }
 

+ 0 - 7
src/kernel/process.cpp

@@ -434,13 +434,6 @@ void proclist::kill(pid_t pid, int exit_code)
     for (auto& thd : proc.thds)
         thd.sleep();
 
-    // if current process is connected to a tty
-    // clear its read buffer
-    // TODO: make tty line discipline handle this
-    tty* ctrl_tty = current_process->control_tty;
-    if (ctrl_tty)
-        ctrl_tty->clear_read_buf();
-
     // write back mmap'ped files and close them
     proc.files.close_all();
 

+ 26 - 1
src/kernel/syscall.cpp

@@ -7,6 +7,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <time.h>
+#include <termios.h>
 #include <unistd.h>
 #include <bits/alltypes.h>
 #include <bits/ioctl.h>
@@ -431,8 +432,32 @@ int _syscall_ioctl(interrupt_stack* data)
         ws->ws_row = 10;
         break;
     }
+    case TCGETS: {
+        SYSCALL_ARG3(struct termios* __user, argp);
+
+        tty* ctrl_tty = current_process->control_tty;
+        if (!ctrl_tty)
+            return -EINVAL;
+
+        // TODO: use copy_to_user
+        memcpy(argp, &ctrl_tty->termio, sizeof(ctrl_tty->termio));
+
+        break;
+    }
+    case TCSETS: {
+        SYSCALL_ARG3(const struct termios* __user, argp);
+
+        tty* ctrl_tty = current_process->control_tty;
+        if (!ctrl_tty)
+            return -EINVAL;
+
+        // TODO: use copy_from_user
+        memcpy(&ctrl_tty->termio, argp, sizeof(ctrl_tty->termio));
+
+        break;
+    }
     default:
-        not_implemented();
+        kmsgf("[error] the ioctl() function %x is not implemented", request);
         return -EINVAL;
     }
 

+ 233 - 92
src/kernel/tty.cpp

@@ -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();