feat: shell and signal demo as loadable user executable
[lunaix-os.git] / lunaix-os / kernel / tty / lxconsole.c
1 /**
2  * @file lxconsole.c
3  * @author Lunaixsky (lunaxisky@qq.com)
4  * @brief Provides simple terminal support
5  * @version 0.1
6  * @date 2023-06-18
7  *
8  * @copyright Copyright (c) 2023
9  *
10  */
11
12 #include <klibc/string.h>
13 #include <lunaix/device.h>
14 #include <lunaix/input.h>
15 #include <lunaix/ioctl.h>
16 #include <lunaix/keyboard.h>
17 #include <lunaix/lxconsole.h>
18 #include <lunaix/mm/pmm.h>
19 #include <lunaix/mm/valloc.h>
20 #include <lunaix/mm/vmm.h>
21 #include <lunaix/sched.h>
22 #include <lunaix/signal.h>
23 #include <lunaix/tty/console.h>
24 #include <lunaix/tty/tty.h>
25
26 static struct console lx_console;
27
28 int
29 __tty_write(struct device* dev, void* buf, size_t offset, size_t len);
30
31 int
32 __tty_read(struct device* dev, void* buf, size_t offset, size_t len);
33
34 void
35 console_flush();
36
37 static waitq_t lx_reader;
38 static volatile char ttychr;
39
40 static volatile pid_t fg_pgid = 0;
41
42 static inline void
43 print_control_code(const char cntrl)
44 {
45     console_write_char('^');
46     console_write_char(cntrl + 64);
47 }
48
49 int
50 __lxconsole_listener(struct input_device* dev)
51 {
52     u32_t key = dev->current_pkt.sys_code;
53     u32_t type = dev->current_pkt.pkt_type;
54     kbd_kstate_t state = key >> 16;
55     ttychr = key & 0xff;
56     key = key & 0xffff;
57
58     if (type == PKT_RELEASE) {
59         goto done;
60     }
61
62     if ((state & KBD_KEY_FLCTRL_HELD)) {
63         char cntrl = (char)(ttychr | 0x20);
64         if ('a' > cntrl || cntrl > 'z') {
65             goto done;
66         }
67         ttychr = cntrl - 'a' + 1;
68         switch (ttychr) {
69             case TCINTR:
70                 signal_send(-fg_pgid, _SIGINT);
71                 print_control_code(ttychr);
72                 break;
73             case TCSTOP:
74                 signal_send(-fg_pgid, _SIGSTOP);
75                 print_control_code(ttychr);
76                 break;
77             default:
78                 break;
79         }
80     } else if (key == KEY_PG_UP) {
81         console_view_up();
82         goto done;
83     } else if (key == KEY_PG_DOWN) {
84         console_view_down();
85         goto done;
86     } else if ((key & 0xff00) <= KEYPAD) {
87         ttychr = key;
88     } else {
89         goto done;
90     }
91
92     pwake_all(&lx_reader);
93
94 done:
95     return INPUT_EVT_NEXT;
96 }
97
98 int
99 __tty_exec_cmd(struct device* dev, u32_t req, va_list args)
100 {
101     switch (req) {
102         case TIOCGPGRP:
103             return fg_pgid;
104         case TIOCSPGRP:
105             fg_pgid = va_arg(args, pid_t);
106             break;
107         case TIOCCLSBUF:
108             fifo_clear(&lx_console.output);
109             fifo_clear(&lx_console.input);
110             lx_console.wnd_start = 0;
111             lx_console.lines = 0;
112             lx_console.output.flags |= FIFO_DIRTY;
113             break;
114         case TIOCFLUSH:
115             lx_console.output.flags |= FIFO_DIRTY;
116             console_flush();
117             break;
118         default:
119             return EINVAL;
120     }
121     return 0;
122 }
123
124 void
125 lxconsole_init()
126 {
127     memset(&lx_console, 0, sizeof(lx_console));
128     fifo_init(&lx_console.output, valloc(8192), 8192, 0);
129     fifo_init(&lx_console.input, valloc(4096), 4096, 0);
130
131     lx_console.flush_timer = NULL;
132 }
133
134 int
135 __tty_write_pg(struct device* dev, void* buf, size_t offset)
136 {
137     return __tty_write(dev, buf, offset, PG_SIZE);
138 }
139
140 int
141 __tty_read_pg(struct device* dev, void* buf, size_t offset)
142 {
143     return __tty_read(dev, buf, offset, PG_SIZE);
144 }
145
146 void
147 lxconsole_spawn_ttydev()
148 {
149     struct device* tty_dev = device_addseq(NULL, &lx_console, "tty");
150     tty_dev->write = __tty_write;
151     tty_dev->write_page = __tty_write_pg;
152     tty_dev->read = __tty_read;
153     tty_dev->read_page = __tty_read_pg;
154     tty_dev->exec_cmd = __tty_exec_cmd;
155
156     waitq_init(&lx_reader);
157     input_add_listener(__lxconsole_listener);
158 }
159
160 int
161 __tty_write(struct device* dev, void* buf, size_t offset, size_t len)
162 {
163     struct console* console = (struct console*)dev->underlay;
164     console_write(console, buf, len);
165 }
166
167 int
168 __tty_read(struct device* dev, void* buf, size_t offset, size_t len)
169 {
170     struct console* console = (struct console*)dev->underlay;
171
172     size_t count = fifo_read(&console->input, buf, len);
173     if (count > 0 && ((char*)buf)[count - 1] == '\n') {
174         return count;
175     }
176
177     while (count < len) {
178         pwait(&lx_reader);
179
180         if (ttychr < 0x1B) {
181             // ASCII control codes
182             switch (ttychr) {
183                 case TCINTR:
184                     fifo_clear(&console->input);
185                     return 0;
186                 case TCBS:
187                     if (fifo_backone(&console->input)) {
188                         console_write_char(ttychr);
189                     }
190                     continue;
191                 case TCLF:
192                 case TCCR:
193                     goto proceed;
194                 default:
195                     break;
196             }
197             print_control_code(ttychr);
198             continue;
199         }
200
201     proceed:
202         console_write_char(ttychr);
203         if (!fifo_putone(&console->input, ttychr) || ttychr == '\n') {
204             break;
205         }
206     }
207     return count + fifo_read(&console->input, buf + count, len - count);
208 }
209
210 void
211 console_schedule_flush()
212 {
213     // TODO make the flush on-demand rather than periodic
214 }
215
216 size_t
217 __find_next_line(size_t start)
218 {
219     size_t p = start - 1;
220     struct fifo_buf* buffer = &lx_console.output;
221     do {
222         p = (p + 1) % buffer->size;
223     } while (p != buffer->wr_pos && ((char*)buffer->data)[p] != '\n');
224     return p + (((char*)buffer->data)[p] == '\n');
225 }
226
227 size_t
228 __find_prev_line(size_t start)
229 {
230     size_t p = start - 1;
231     struct fifo_buf* buffer = &lx_console.output;
232     do {
233         p--;
234     } while (p < lx_console.wnd_start && p != buffer->wr_pos &&
235              ((char*)buffer->data)[p] != '\n');
236
237     if (p > lx_console.wnd_start) {
238         return 0;
239     }
240     return p + 1;
241 }
242
243 void
244 console_view_up()
245 {
246     struct fifo_buf* buffer = &lx_console.output;
247     mutex_lock(&buffer->lock);
248     fifo_set_rdptr(buffer, __find_prev_line(buffer->rd_pos));
249     buffer->flags |= FIFO_DIRTY;
250     mutex_unlock(&buffer->lock);
251
252     console_flush();
253 }
254
255 void
256 console_view_down()
257 {
258     struct fifo_buf* buffer = &lx_console.output;
259     mutex_lock(&buffer->lock);
260
261     size_t wnd = lx_console.wnd_start;
262     size_t p = __find_next_line(buffer->rd_pos);
263     fifo_set_rdptr(buffer, p > wnd ? wnd : p);
264     buffer->flags |= FIFO_DIRTY;
265     mutex_unlock(&buffer->lock);
266
267     console_flush();
268 }
269
270 void
271 console_flush()
272 {
273     if (mutex_on_hold(&lx_console.output.lock)) {
274         return;
275     }
276     if (!(lx_console.output.flags & FIFO_DIRTY)) {
277         return;
278     }
279
280     size_t rdpos_save = lx_console.output.rd_pos;
281     tty_flush_buffer(&lx_console.output);
282     fifo_set_rdptr(&lx_console.output, rdpos_save);
283
284     lx_console.output.flags &= ~FIFO_DIRTY;
285 }
286
287 void
288 console_write(struct console* console, uint8_t* data, size_t size)
289 {
290     struct fifo_buf* fbuf = &console->output;
291     mutex_lock(&console->output.lock);
292     fifo_set_rdptr(fbuf, console->wnd_start);
293
294     uint8_t* buffer = fbuf->data;
295     uintptr_t ptr = fbuf->wr_pos;
296     uintptr_t rd_ptr = fbuf->rd_pos;
297
298     char c;
299     for (size_t i = 0; i < size; i++) {
300         c = data[i];
301         if (!c) {
302             continue;
303         }
304         if (c == '\n') {
305             console->lines++;
306         } else if (c == '\x08') {
307             ptr = ptr ? ptr - 1 : fbuf->size - 1;
308             continue;
309         }
310         buffer[ptr] = c;
311         ptr = (ptr + 1) % fbuf->size;
312     }
313
314     fifo_set_wrptr(fbuf, ptr);
315
316     while (console->lines >= TTY_HEIGHT) {
317         rd_ptr = __find_next_line(rd_ptr);
318         console->lines--;
319     }
320
321     fifo_set_rdptr(&lx_console.output, rd_ptr);
322     console->wnd_start = rd_ptr;
323     fbuf->flags |= FIFO_DIRTY;
324     mutex_unlock(&fbuf->lock);
325
326     if (!lx_console.flush_timer) {
327         console_flush();
328     }
329 }
330
331 void
332 console_write_str(char* str)
333 {
334     console_write(&lx_console, str, strlen(str));
335 }
336
337 void
338 console_write_char(char str)
339 {
340     console_write(&lx_console, &str, 1);
341 }
342
343 void
344 console_start_flushing()
345 {
346     struct lx_timer* timer =
347       timer_run_ms(20, console_flush, NULL, TIMER_MODE_PERIODIC);
348     lx_console.flush_timer = timer;
349 }