feat: better rtc framework which aims to remove single rtc restrictions.
[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 void
150 lxconsole_spawn_ttydev()
151 {
152     struct device* tty_dev = device_addseq(NULL, &lx_console, "tty");
153     tty_dev->ops.write = __tty_write;
154     tty_dev->ops.write_page = __tty_write_pg;
155     tty_dev->ops.read = __tty_read;
156     tty_dev->ops.read_page = __tty_read_pg;
157     tty_dev->ops.exec_cmd = __tty_exec_cmd;
158
159     waitq_init(&lx_reader);
160     input_add_listener(__lxconsole_listener);
161 }
162
163 int
164 __tty_write(struct device* dev, void* buf, size_t offset, size_t len)
165 {
166     struct console* console = (struct console*)dev->underlay;
167     console_write(console, buf, len);
168
169     return len;
170 }
171
172 int
173 __tty_read(struct device* dev, void* buf, size_t offset, size_t len)
174 {
175     struct console* console = (struct console*)dev->underlay;
176
177     size_t count = fifo_read(&console->input, buf, len);
178     if (count > 0 && ((char*)buf)[count - 1] == '\n') {
179         return count;
180     }
181
182     while (count < len) {
183         pwait(&lx_reader);
184
185         if (ttychr < 0x1B) {
186             // ASCII control codes
187             switch (ttychr) {
188                 case TCINTR:
189                     fifo_clear(&console->input);
190                     return 0;
191                 case TCBS:
192                     if (fifo_backone(&console->input)) {
193                         console_write_char(ttychr);
194                     }
195                     continue;
196                 case TCLF:
197                 case TCCR:
198                     goto proceed;
199                 default:
200                     break;
201             }
202             print_control_code(ttychr);
203             continue;
204         }
205
206     proceed:
207         console_write_char(ttychr);
208         if (!fifo_putone(&console->input, ttychr) || ttychr == '\n') {
209             break;
210         }
211     }
212
213     return count + fifo_read(&console->input, buf + count, len - count);
214 }
215
216 void
217 console_schedule_flush()
218 {
219     // TODO make the flush on-demand rather than periodic
220 }
221
222 size_t
223 __find_next_line(size_t start)
224 {
225     size_t p = start - 1;
226     struct fifo_buf* buffer = &lx_console.output;
227     do {
228         p = (p + 1) % buffer->size;
229     } while (p != buffer->wr_pos && ((char*)buffer->data)[p] != '\n');
230     return p + (((char*)buffer->data)[p] == '\n');
231 }
232
233 size_t
234 __find_prev_line(size_t start)
235 {
236     size_t p = start - 1;
237     struct fifo_buf* buffer = &lx_console.output;
238     do {
239         p--;
240     } while (p < lx_console.wnd_start && p != buffer->wr_pos &&
241              ((char*)buffer->data)[p] != '\n');
242
243     if (p > lx_console.wnd_start) {
244         return 0;
245     }
246     return p + 1;
247 }
248
249 void
250 console_view_up()
251 {
252     struct fifo_buf* buffer = &lx_console.output;
253     mutex_lock(&buffer->lock);
254     fifo_set_rdptr(buffer, __find_prev_line(buffer->rd_pos));
255     buffer->flags |= FIFO_DIRTY;
256     mutex_unlock(&buffer->lock);
257
258     console_flush();
259 }
260
261 void
262 console_view_down()
263 {
264     struct fifo_buf* buffer = &lx_console.output;
265     mutex_lock(&buffer->lock);
266
267     size_t wnd = lx_console.wnd_start;
268     size_t p = __find_next_line(buffer->rd_pos);
269     fifo_set_rdptr(buffer, p > wnd ? wnd : p);
270     buffer->flags |= FIFO_DIRTY;
271     mutex_unlock(&buffer->lock);
272
273     console_flush();
274 }
275
276 void
277 console_flush()
278 {
279     if (mutex_on_hold(&lx_console.output.lock)) {
280         return;
281     }
282     if (!(lx_console.output.flags & FIFO_DIRTY)) {
283         return;
284     }
285
286     size_t rdpos_save = lx_console.output.rd_pos;
287     tty_flush_buffer(&lx_console.output);
288     fifo_set_rdptr(&lx_console.output, rdpos_save);
289
290     lx_console.output.flags &= ~FIFO_DIRTY;
291 }
292
293 void
294 console_write(struct console* console, u8_t* data, size_t size)
295 {
296     struct fifo_buf* fbuf = &console->output;
297     mutex_lock(&console->output.lock);
298     fifo_set_rdptr(fbuf, console->wnd_start);
299
300     u8_t* buffer = fbuf->data;
301     ptr_t ptr = fbuf->wr_pos;
302     ptr_t rd_ptr = fbuf->rd_pos;
303
304     char c;
305     for (size_t i = 0; i < size; i++) {
306         c = data[i];
307         if (!c) {
308             continue;
309         }
310         if (c == '\n') {
311             console->lines++;
312         } else if (c == '\x08') {
313             ptr = ptr ? ptr - 1 : fbuf->size - 1;
314             continue;
315         }
316         buffer[ptr] = c;
317         ptr = (ptr + 1) % fbuf->size;
318     }
319
320     fifo_set_wrptr(fbuf, ptr);
321
322     while (console->lines >= TTY_HEIGHT) {
323         rd_ptr = __find_next_line(rd_ptr);
324         console->lines--;
325     }
326
327     fifo_set_rdptr(&lx_console.output, rd_ptr);
328     console->wnd_start = rd_ptr;
329     fbuf->flags |= FIFO_DIRTY;
330     mutex_unlock(&fbuf->lock);
331
332     if (!lx_console.flush_timer) {
333         console_flush();
334     }
335 }
336
337 void
338 console_write_str(char* str)
339 {
340     console_write(&lx_console, (u8_t*)str, strlen(str));
341 }
342
343 void
344 console_write_char(char str)
345 {
346     console_write(&lx_console, (u8_t*)&str, 1);
347 }
348
349 void
350 console_start_flushing()
351 {
352     struct lx_timer* timer =
353       timer_run_ms(20, console_flush, NULL, TIMER_MODE_PERIODIC);
354     lx_console.flush_timer = timer;
355 }