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