feat: input device subsystem to resolve race condition on polling input
[lunaix-os.git] / lunaix-os / kernel / lxconsole.c
1 #include <klibc/string.h>
2 #include <lunaix/device.h>
3 #include <lunaix/input.h>
4 #include <lunaix/keyboard.h>
5 #include <lunaix/lxconsole.h>
6 #include <lunaix/mm/pmm.h>
7 #include <lunaix/mm/valloc.h>
8 #include <lunaix/mm/vmm.h>
9 #include <lunaix/sched.h>
10 #include <lunaix/tty/console.h>
11 #include <lunaix/tty/tty.h>
12
13 static struct console lx_console;
14
15 int
16 __tty_write(struct device* dev, void* buf, size_t offset, size_t len);
17
18 int
19 __tty_read(struct device* dev, void* buf, size_t offset, size_t len);
20
21 static waitq_t lx_reader;
22 static volatile char key;
23
24 int
25 __lxconsole_listener(struct input_device* dev)
26 {
27     uint32_t keycode = dev->current_pkt.sys_code;
28     uint32_t type = dev->current_pkt.pkt_type;
29     if (type == PKT_PRESS) {
30         if (keycode == KEY_UP) {
31             console_view_up();
32         } else if (keycode == KEY_DOWN) {
33             console_view_down();
34         }
35         goto done;
36     }
37     if ((keycode & 0xff00) > KEYPAD) {
38         goto done;
39     }
40
41     key = (char)(keycode & 0x00ff);
42
43     pwake_all(&lx_reader);
44
45 done:
46     return INPUT_EVT_NEXT;
47 }
48
49 void
50 lxconsole_init()
51 {
52     memset(&lx_console, 0, sizeof(lx_console));
53     fifo_init(&lx_console.output, VGA_BUFFER_VADDR + 0x1000, 8192, 0);
54     fifo_init(&lx_console.input, valloc(4096), 4096, 0);
55
56     // FIXME use valloc to allocate console buffer.
57     // In doing this, the console buffer can only be accessed from kernel mode
58     //  any direct write to this buffer from user land should be purged!
59
60     // 分配控制台缓存
61     for (size_t i = 0; i < PG_ALIGN(lx_console.output.size); i += PG_SIZE) {
62         uintptr_t pa = pmm_alloc_page(KERNEL_PID, 0);
63         vmm_set_mapping(PD_REFERENCED,
64                         (uintptr_t)lx_console.output.data + i,
65                         pa,
66                         PG_PREM_URW,
67                         0);
68     }
69
70     lx_console.flush_timer = NULL;
71
72     struct device* tty_dev = device_addseq(NULL, &lx_console, "tty");
73     tty_dev->write = __tty_write;
74     tty_dev->read = __tty_read;
75
76     waitq_init(&lx_reader);
77     input_add_listener(__lxconsole_listener);
78 }
79
80 int
81 __tty_write(struct device* dev, void* buf, size_t offset, size_t len)
82 {
83     struct console* console = (struct console*)dev->underlay;
84     console_write(console, buf, len);
85 }
86
87 int
88 __tty_read(struct device* dev, void* buf, size_t offset, size_t len)
89 {
90     struct console* console = (struct console*)dev->underlay;
91
92     size_t count = fifo_read(&console->input, buf, len);
93     if (count > 0 && ((char*)buf)[count - 1] == '\n') {
94         return count;
95     }
96
97     while (count < len) {
98         pwait(&lx_reader);
99
100         if (key == 0x08) {
101             if (fifo_backone(&console->input)) {
102                 console_write_char(key);
103             }
104             continue;
105         }
106         console_write_char(key);
107         if (!fifo_putone(&console->input, key) || key == '\n') {
108             break;
109         }
110     }
111     return count + fifo_read(&console->input, buf + count, len - count);
112 }
113
114 void
115 console_schedule_flush()
116 {
117     // TODO make the flush on-demand rather than periodic
118 }
119
120 void
121 console_view_up()
122 {
123     struct fifo_buf* buffer = &lx_console.output;
124     mutex_lock(&buffer->lock);
125     size_t p = lx_console.erd_pos - 2;
126     while (p < lx_console.erd_pos && p != buffer->wr_pos &&
127            ((char*)buffer->data)[p] != '\n') {
128         p--;
129     }
130     p++;
131
132     if (p > lx_console.erd_pos) {
133         p = 0;
134     }
135
136     buffer->flags |= FIFO_DIRTY;
137     lx_console.erd_pos = p;
138     mutex_unlock(&buffer->lock);
139 }
140
141 size_t
142 __find_next_line(size_t start)
143 {
144     size_t p = start;
145     while (p != lx_console.output.wr_pos &&
146            ((char*)lx_console.output.data)[p] != '\n') {
147         p = (p + 1) % lx_console.output.size;
148     }
149     return p + 1;
150 }
151
152 void
153 console_view_down()
154 {
155     struct fifo_buf* buffer = &lx_console.output;
156     mutex_lock(&buffer->lock);
157
158     lx_console.erd_pos = __find_next_line(lx_console.erd_pos);
159     buffer->flags |= FIFO_DIRTY;
160     mutex_unlock(&buffer->lock);
161 }
162
163 void
164 console_flush()
165 {
166     if (mutex_on_hold(&lx_console.output.lock)) {
167         return;
168     }
169     if (!(lx_console.output.flags & FIFO_DIRTY)) {
170         return;
171     }
172
173     tty_flush_buffer(lx_console.output.data,
174                      lx_console.erd_pos,
175                      lx_console.output.wr_pos,
176                      lx_console.output.size);
177     lx_console.output.flags &= ~FIFO_DIRTY;
178 }
179
180 void
181 console_write(struct console* console, uint8_t* data, size_t size)
182 {
183     mutex_lock(&console->output.lock);
184     uint8_t* buffer = console->output.data;
185     uintptr_t ptr = console->output.wr_pos;
186     uintptr_t rd_ptr = console->output.rd_pos;
187
188     char c;
189     int lines = 0;
190     int j = 0;
191     for (size_t i = 0; i < size; i++) {
192         c = data[i];
193         if (!c) {
194             continue;
195         }
196         buffer[(ptr + j) % console->output.size] = c;
197         lines += (c == '\n');
198         j++;
199     }
200
201     size = j;
202
203     uintptr_t new_ptr = (ptr + size) % console->output.size;
204     console->output.wr_pos = new_ptr;
205
206     if (console->lines > TTY_HEIGHT && lines > 0) {
207         console->output.rd_pos =
208           __find_next_line((size + rd_ptr) % console->output.size);
209     }
210
211     if (new_ptr < ptr + size && new_ptr > rd_ptr) {
212         console->output.rd_pos = new_ptr;
213     }
214
215     console->lines += lines;
216     console->erd_pos = console->output.rd_pos;
217     console->output.flags |= FIFO_DIRTY;
218     mutex_unlock(&console->output.lock);
219 }
220
221 void
222 console_write_str(char* str)
223 {
224     console_write(&lx_console, str, strlen(str));
225 }
226
227 void
228 console_write_char(char str)
229 {
230     console_write(&lx_console, &str, 1);
231 }
232
233 void
234 console_start_flushing()
235 {
236     struct lx_timer* timer =
237       timer_run_ms(20, console_flush, NULL, TIMER_MODE_PERIODIC);
238     lx_console.flush_timer = timer;
239 }