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