tty.cpp 6.9 KB

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