tty.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #include <algorithm>
  2. #include <stdint.h>
  3. #include <stdio.h>
  4. #include <termios.h>
  5. #include <types/lock.hpp>
  6. #include <kernel/event/evtqueue.hpp>
  7. #include <kernel/hw/serial.h>
  8. #include <kernel/process.hpp>
  9. #include <kernel/tty.hpp>
  10. #include <kernel/vga.hpp>
  11. #include <kernel/log.hpp>
  12. #define CTRL(key) ((key)-0x40)
  13. #define TERMIOS_ISET(termios, option) ((option) == ((termios).c_iflag & (option)))
  14. #define TERMIOS_OSET(termios, option) ((option) == ((termios).c_oflag & (option)))
  15. #define TERMIOS_CSET(termios, option) ((option) == ((termios).c_cflag & (option)))
  16. #define TERMIOS_LSET(termios, option) ((option) == ((termios).c_lflag & (option)))
  17. #define TERMIOS_TESTCC(c, termios, cc) ((c != 0xff) && (c == ((termios).c_cc[cc])))
  18. tty::tty()
  19. : termio {
  20. .c_iflag = ICRNL | IXOFF,
  21. .c_oflag = OPOST | ONLCR,
  22. .c_cflag = B38400 | CS8 | CREAD | HUPCL,
  23. .c_lflag = ISIG | ICANON | ECHO | ECHOE |
  24. ECHOK | ECHOCTL | ECHOKE | IEXTEN,
  25. .c_line = N_TTY,
  26. .c_cc {},
  27. .c_ispeed = 38400,
  28. .c_ospeed = 38400,
  29. }
  30. , buf(BUFFER_SIZE)
  31. , fg_pgroup { 0 }
  32. {
  33. memset(this->termio.c_cc, 0x00, sizeof(this->termio.c_cc));
  34. // other special characters is not supported for now
  35. this->termio.c_cc[VINTR] = CTRL('C');
  36. this->termio.c_cc[VQUIT] = CTRL('\\');
  37. this->termio.c_cc[VERASE] = 0x7f;
  38. this->termio.c_cc[VKILL] = CTRL('U');
  39. this->termio.c_cc[VEOF] = CTRL('D');
  40. this->termio.c_cc[VSUSP] = CTRL('Z');
  41. this->termio.c_cc[VMIN] = 1;
  42. }
  43. void tty::print(const char* str)
  44. {
  45. while (*str != '\0')
  46. this->putchar(*(str++));
  47. }
  48. size_t tty::read(char* buf, size_t buf_size, size_t n)
  49. {
  50. n = std::max(buf_size, n);
  51. size_t orig_n = n;
  52. do {
  53. if (n == 0)
  54. break;
  55. auto& mtx = this->m_cv.mtx();
  56. types::lock_guard lck(mtx);
  57. if (this->buf.empty()) {
  58. bool interrupted = !this->m_cv.wait(mtx);
  59. if (interrupted)
  60. break;
  61. }
  62. if (this->buf.empty())
  63. break;
  64. if (!TERMIOS_LSET(this->termio, ICANON)) {
  65. --n, *buf = this->buf.get();
  66. break;
  67. }
  68. while (n &&!this->buf.empty()) {
  69. int c = this->buf.get();
  70. --n, *(buf++) = c;
  71. // canonical mode
  72. if (c == '\n')
  73. break;
  74. }
  75. } while (false);
  76. return orig_n - n;
  77. }
  78. int tty::_do_erase(bool should_echo)
  79. {
  80. if (buf.empty())
  81. return -1;
  82. int back = buf.back();
  83. if (back == '\n' || back == this->termio.c_cc[VEOF])
  84. return -1;
  85. if (back == this->termio.c_cc[VEOL] || back == this->termio.c_cc[VEOL2])
  86. return -1;
  87. buf.pop();
  88. if (should_echo && TERMIOS_LSET(this->termio, ECHO | ECHOE)) {
  89. this->show_char('\b'); // backspace
  90. this->show_char(' '); // space
  91. this->show_char('\b'); // backspace
  92. // xterm's way to show backspace
  93. // serial_send_data(id, '\b');
  94. // serial_send_data(id, CTRL('['));
  95. // serial_send_data(id, '[');
  96. // serial_send_data(id, 'K');
  97. }
  98. return back;
  99. }
  100. void tty::_real_commit_char(int c)
  101. {
  102. switch (c) {
  103. case '\n':
  104. buf.put(c);
  105. if (TERMIOS_LSET(this->termio, ECHONL) || TERMIOS_LSET(this->termio, ECHO))
  106. this->_echo_char(c);
  107. if (TERMIOS_LSET(this->termio, ICANON))
  108. this->m_cv.notify();
  109. break;
  110. default:
  111. buf.put(c);
  112. if (TERMIOS_LSET(this->termio, ECHO))
  113. this->_echo_char(c);
  114. break;
  115. }
  116. }
  117. void tty::_echo_char(int c)
  118. {
  119. // ECHOCTL
  120. do {
  121. if (c < 0 || c >= 32 || !TERMIOS_LSET(this->termio, ECHO | ECHOCTL | IEXTEN))
  122. break;
  123. if (c == '\t' || c == '\n' || c == CTRL('Q') || c == CTRL('S'))
  124. break;
  125. this->show_char('^');
  126. this->show_char(c + 0x40);
  127. return;
  128. } while (false);
  129. this->show_char(c);
  130. }
  131. // do some ignore and remapping work
  132. // real commit operation is in _real_commit_char()
  133. void tty::commit_char(int c)
  134. {
  135. // check special control characters
  136. // if handled, the character is discarded
  137. if (TERMIOS_LSET(this->termio, ISIG)) {
  138. if (TERMIOS_TESTCC(c, this->termio, VINTR)) {
  139. if (!TERMIOS_LSET(this->termio, NOFLSH))
  140. this->clear_read_buf();
  141. this->_echo_char(c);
  142. procs->send_signal_grp(fg_pgroup, SIGINT);
  143. return;
  144. }
  145. if (TERMIOS_TESTCC(c, this->termio, VSUSP)) {
  146. if (!TERMIOS_LSET(this->termio, NOFLSH))
  147. this->clear_read_buf();
  148. this->_echo_char(c);
  149. procs->send_signal_grp(fg_pgroup, SIGTSTP);
  150. return;
  151. }
  152. if (TERMIOS_TESTCC(c, this->termio, VQUIT)) {
  153. if (!TERMIOS_LSET(this->termio, NOFLSH))
  154. this->clear_read_buf();
  155. this->_echo_char(c);
  156. procs->send_signal_grp(fg_pgroup, SIGQUIT);
  157. return;
  158. }
  159. }
  160. // if handled, the character is discarded
  161. if (TERMIOS_LSET(this->termio, ICANON)) {
  162. if (TERMIOS_TESTCC(c, this->termio, VEOF)) {
  163. this->m_cv.notify();
  164. return;
  165. }
  166. if (TERMIOS_TESTCC(c, this->termio, VKILL)) {
  167. if (TERMIOS_LSET(this->termio, ECHOKE | IEXTEN)) {
  168. while (this->_do_erase(true) != -1)
  169. ;
  170. }
  171. else if (TERMIOS_LSET(this->termio, ECHOK)) {
  172. while (this->_do_erase(false) != -1)
  173. ;
  174. this->show_char('\n');
  175. }
  176. return;
  177. }
  178. if (TERMIOS_TESTCC(c, this->termio, VERASE)) {
  179. this->_do_erase(true);
  180. return;
  181. }
  182. }
  183. switch (c) {
  184. case '\r':
  185. if (TERMIOS_ISET(this->termio, IGNCR))
  186. break;
  187. if (TERMIOS_ISET(this->termio, ICRNL)) {
  188. this->_real_commit_char('\n');
  189. break;
  190. }
  191. this->_real_commit_char('\r');
  192. break;
  193. case '\n':
  194. if (TERMIOS_ISET(this->termio, INLCR)) {
  195. this->_real_commit_char('\r');
  196. break;
  197. }
  198. this->_real_commit_char('\n');
  199. break;
  200. default:
  201. this->_real_commit_char(c);
  202. break;
  203. }
  204. }
  205. void tty::show_char(int c)
  206. {
  207. this->putchar(c);
  208. }
  209. vga_tty::vga_tty()
  210. {
  211. snprintf(this->name, sizeof(this->name), "ttyVGA");
  212. }
  213. serial_tty::serial_tty(int id)
  214. : id(id)
  215. {
  216. snprintf(this->name, sizeof(this->name), "ttyS%x", (int)id);
  217. }
  218. void serial_tty::putchar(char c)
  219. {
  220. serial_send_data(id, c);
  221. }
  222. void vga_tty::putchar(char c)
  223. {
  224. static struct vga_char vc = { .c = '\0', .color = VGA_CHAR_COLOR_WHITE };
  225. vc.c = c;
  226. vga_put_char(&vc);
  227. }
  228. void tty::clear_read_buf(void)
  229. {
  230. this->buf.clear();
  231. }