source: mainline/uspace/srv/console/console.c@ d2cc7e1

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since d2cc7e1 was d2cc7e1, checked in by Jiri Svoboda <jirik.svoboda@…>, 16 years ago

Buffer console output with line granularity. Makes esp. msim/ski console faster. EGA-fb needs fixing.

  • Property mode set to 100644
File size: 17.5 KB
Line 
1/*
2 * Copyright (c) 2006 Josef Cejka
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup console
30 * @{
31 */
32/** @file
33 */
34
35#include <libc.h>
36#include <fb.h>
37#include <ipc/ipc.h>
38#include <kbd.h>
39#include <kbd/keycode.h>
40#include <ipc/fb.h>
41#include <ipc/services.h>
42#include <errno.h>
43#include <key_buffer.h>
44#include <ipc/console.h>
45#include <unistd.h>
46#include <async.h>
47#include <libadt/fifo.h>
48#include <screenbuffer.h>
49#include <sys/mman.h>
50#include <stdio.h>
51#include <sysinfo.h>
52
53#include "console.h"
54#include "gcons.h"
55
56#define MAX_KEYREQUESTS_BUFFERED 32
57
58#define NAME "console"
59
60/** Index of currently used virtual console.
61 */
62int active_console = 0;
63int prev_console = 0;
64
65/** Information about framebuffer
66 */
67struct {
68 int phone; /**< Framebuffer phone */
69 ipcarg_t rows; /**< Framebuffer rows */
70 ipcarg_t cols; /**< Framebuffer columns */
71} fb_info;
72
73typedef struct {
74 keybuffer_t keybuffer; /**< Buffer for incoming keys. */
75 /** Buffer for unsatisfied request for keys. */
76 FIFO_CREATE_STATIC(keyrequests, ipc_callid_t,
77 MAX_KEYREQUESTS_BUFFERED);
78 int keyrequest_counter; /**< Number of requests in buffer. */
79 int client_phone; /**< Phone to connected client. */
80 int used; /**< 1 if this virtual console is
81 * connected to some client.*/
82 screenbuffer_t screenbuffer; /**< Screenbuffer for saving screen
83 * contents and related settings. */
84} connection_t;
85
86static connection_t connections[CONSOLE_COUNT]; /**< Array of data for virtual
87 * consoles */
88static keyfield_t *interbuffer = NULL; /**< Pointer to memory shared
89 * with framebufer used for
90 * faster virtual console
91 * switching */
92/** Size of fb_buf. */
93#define FB_BUF_SIZE 256
94
95/** Buffer for sending characters to FB driver. */
96static char fb_buf[FB_BUF_SIZE];
97
98/* Properties of fb_buf data. */
99static int fb_buf_row; /**< Row where fb_buf data belong. */
100static int fb_buf_col; /**< Column where fb_buf data start. */
101static int fb_console; /**< VC to which fb_buf data belong. */
102int fb_bi = 0; /**< Number of valid chars in fb_buf. */
103
104/** Size of cwrite_buf. */
105#define CWRITE_BUF_SIZE 256
106
107/** Buffer for receiving data via the CONSOLE_WRITE call from the client. */
108static char cwrite_buf[CWRITE_BUF_SIZE];
109
110
111/** Find unused virtual console.
112 *
113 */
114static int find_free_connection(void)
115{
116 int i;
117
118 for (i = 0; i < CONSOLE_COUNT; i++) {
119 if (!connections[i].used)
120 return i;
121 }
122 return -1;
123}
124
125static void clrscr(void)
126{
127 async_msg_0(fb_info.phone, FB_CLEAR);
128}
129
130static void curs_visibility(bool visible)
131{
132 async_msg_1(fb_info.phone, FB_CURSOR_VISIBILITY, visible);
133}
134
135static void curs_hide_sync(void)
136{
137 ipc_call_sync_1_0(fb_info.phone, FB_CURSOR_VISIBILITY, false);
138}
139
140static void curs_goto(int row, int col)
141{
142 async_msg_2(fb_info.phone, FB_CURSOR_GOTO, row, col);
143}
144
145static void set_style(int style)
146{
147 async_msg_1(fb_info.phone, FB_SET_STYLE, style);
148}
149
150static void set_color(int fgcolor, int bgcolor, int flags)
151{
152 async_msg_3(fb_info.phone, FB_SET_COLOR, fgcolor, bgcolor, flags);
153}
154
155static void set_rgb_color(int fgcolor, int bgcolor)
156{
157 async_msg_2(fb_info.phone, FB_SET_RGB_COLOR, fgcolor, bgcolor);
158}
159
160static void set_attrs(attrs_t *attrs)
161{
162 switch (attrs->t) {
163 case at_style:
164 set_style(attrs->a.s.style);
165 break;
166
167 case at_idx:
168 set_color(attrs->a.i.fg_color, attrs->a.i.bg_color,
169 attrs->a.i.flags);
170 break;
171
172 case at_rgb:
173 set_rgb_color(attrs->a.r.fg_color, attrs->a.r.bg_color);
174 break;
175 }
176}
177
178/** Write a character vector to FB driver via IPC. */
179static ssize_t fb_write(const char *buf, size_t nbyte, int row, int col)
180{
181 ipcarg_t rc;
182 ipc_call_t answer;
183 aid_t req;
184
185 async_serialize_start();
186
187 req = async_send_2(fb_info.phone, FB_WRITE, row, col, &answer);
188 rc = ipc_data_write_start(fb_info.phone, (void *) buf, nbyte);
189
190 if (rc != EOK) {
191 async_wait_for(req, NULL);
192 async_serialize_end();
193 return (ssize_t) rc;
194 }
195
196 async_wait_for(req, &rc);
197 async_serialize_end();
198
199 if (rc == EOK)
200 return (ssize_t) IPC_GET_ARG1(answer);
201 else
202 return -1;
203}
204
205/** Flush buffered characters to FB. */
206static void fb_buf_flush(void)
207{
208 screenbuffer_t *scr;
209 int i;
210
211 scr = &(connections[fb_console].screenbuffer);
212
213 if (fb_bi > 0) {
214 if (fb_write(fb_buf, fb_bi, fb_buf_row, fb_buf_col) < 0) {
215 /* Try falling back to char-by-char. */
216 for (i = 0; i < fb_bi; i++) {
217 async_msg_3(fb_info.phone, FB_PUTCHAR, fb_buf[i],
218 fb_buf_row, fb_buf_col + i);
219 }
220 }
221 fb_bi = 0;
222 }
223}
224
225/** Print a character to the active VC with buffering. */
226static void prtchr(char c, int row, int col)
227{
228 if (fb_bi >= FB_BUF_SIZE)
229 fb_buf_flush();
230
231 if (fb_bi == 0) {
232 fb_buf_row = row;
233 fb_buf_col = col;
234 fb_console = active_console;
235 }
236
237 fb_buf[fb_bi++] = c;
238}
239
240/** Check key and process special keys.
241 *
242 *
243 */
244static void write_char(int console, char key)
245{
246 bool flush_cursor = false;
247 screenbuffer_t *scr = &(connections[console].screenbuffer);
248
249 switch (key) {
250 case '\n':
251 fb_buf_flush();
252 flush_cursor = true;
253 scr->position_y++;
254 scr->position_x = 0;
255 break;
256 case '\r':
257 fb_buf_flush();
258 break;
259 case '\t':
260 fb_buf_flush();
261 scr->position_x += 8;
262 scr->position_x -= scr->position_x % 8;
263 break;
264 case '\b':
265 fb_buf_flush();
266 if (scr->position_x == 0)
267 break;
268 scr->position_x--;
269 if (console == active_console)
270 prtchr(' ', scr->position_y, scr->position_x);
271 screenbuffer_putchar(scr, ' ');
272 break;
273 default:
274 if (console == active_console)
275 prtchr(key, scr->position_y, scr->position_x);
276
277 screenbuffer_putchar(scr, key);
278 scr->position_x++;
279 }
280
281 if (scr->position_x >= scr->size_x) {
282 fb_buf_flush();
283 flush_cursor = true;
284 scr->position_y++;
285 }
286
287 if (scr->position_y >= scr->size_y) {
288 scr->position_y = scr->size_y - 1;
289 screenbuffer_clear_line(scr, scr->top_line);
290 scr->top_line = (scr->top_line + 1) % scr->size_y;
291 if (console == active_console)
292 async_msg_1(fb_info.phone, FB_SCROLL, 1);
293 }
294
295 scr->position_x = scr->position_x % scr->size_x;
296
297 if (console == active_console && flush_cursor)
298 curs_goto(scr->position_y, scr->position_x);
299}
300
301/** Switch to new console */
302static void change_console(int newcons)
303{
304 connection_t *conn;
305 int i, j, rc;
306 keyfield_t *field;
307 attrs_t *attrs;
308
309 if (newcons == active_console)
310 return;
311
312 fb_buf_flush();
313
314 if (newcons == KERNEL_CONSOLE) {
315 async_serialize_start();
316 curs_hide_sync();
317 gcons_in_kernel();
318 async_serialize_end();
319
320 if (__SYSCALL0(SYS_DEBUG_ENABLE_CONSOLE)) {
321 prev_console = active_console;
322 active_console = KERNEL_CONSOLE;
323 } else
324 newcons = active_console;
325 }
326
327 if (newcons != KERNEL_CONSOLE) {
328 async_serialize_start();
329
330 if (active_console == KERNEL_CONSOLE)
331 gcons_redraw_console();
332
333 active_console = newcons;
334 gcons_change_console(newcons);
335 conn = &connections[active_console];
336
337 set_attrs(&conn->screenbuffer.attrs);
338 curs_visibility(false);
339 if (interbuffer) {
340 for (i = 0; i < conn->screenbuffer.size_x; i++)
341 for (j = 0; j < conn->screenbuffer.size_y; j++) {
342 unsigned int size_x;
343
344 size_x = conn->screenbuffer.size_x;
345 interbuffer[i + j * size_x] =
346 *get_field_at(&conn->screenbuffer, i, j);
347 }
348 /* This call can preempt, but we are already at the end */
349 rc = async_req_0_0(fb_info.phone, FB_DRAW_TEXT_DATA);
350 }
351
352 if ((!interbuffer) || (rc != 0)) {
353 set_attrs(&conn->screenbuffer.attrs);
354 clrscr();
355 attrs = &conn->screenbuffer.attrs;
356
357 for (j = 0; j < conn->screenbuffer.size_y; j++)
358 for (i = 0; i < conn->screenbuffer.size_x; i++) {
359 field = get_field_at(&conn->screenbuffer, i, j);
360 if (!attrs_same(*attrs, field->attrs))
361 set_attrs(&field->attrs);
362 attrs = &field->attrs;
363 if ((field->character == ' ') &&
364 (attrs_same(field->attrs,
365 conn->screenbuffer.attrs)))
366 continue;
367
368 prtchr(field->character, j, i);
369 }
370 }
371
372 curs_goto(conn->screenbuffer.position_y,
373 conn->screenbuffer.position_x);
374 curs_visibility(conn->screenbuffer.is_cursor_visible);
375
376 async_serialize_end();
377 }
378}
379
380/** Handler for keyboard */
381static void keyboard_events(ipc_callid_t iid, ipc_call_t *icall)
382{
383 ipc_callid_t callid;
384 ipc_call_t call;
385 int retval;
386 kbd_event_t ev;
387 connection_t *conn;
388 int newcon;
389
390 /* Ignore parameters, the connection is alread opened */
391 while (1) {
392 callid = async_get_call(&call);
393 switch (IPC_GET_METHOD(call)) {
394 case IPC_M_PHONE_HUNGUP:
395 /* TODO: Handle hangup */
396 return;
397 case KBD_MS_LEFT:
398 newcon = gcons_mouse_btn(IPC_GET_ARG1(call));
399 if (newcon != -1)
400 change_console(newcon);
401 retval = 0;
402 break;
403 case KBD_MS_MOVE:
404 gcons_mouse_move(IPC_GET_ARG1(call),
405 IPC_GET_ARG2(call));
406 retval = 0;
407 break;
408 case KBD_EVENT:
409 /* Got event from keyboard driver. */
410 retval = 0;
411 ev.type = IPC_GET_ARG1(call);
412 ev.key = IPC_GET_ARG2(call);
413 ev.mods = IPC_GET_ARG3(call);
414 ev.c = IPC_GET_ARG4(call);
415
416 /* switch to another virtual console */
417
418 conn = &connections[active_console];
419
420 if ((ev.key >= KC_F1) && (ev.key < KC_F1 +
421 CONSOLE_COUNT)) {
422 if (ev.key == KC_F12)
423 change_console(KERNEL_CONSOLE);
424 else
425 change_console(ev.key - KC_F1);
426 break;
427 }
428
429 /* if client is awaiting key, send it */
430 if (conn->keyrequest_counter > 0) {
431 conn->keyrequest_counter--;
432 ipc_answer_4(fifo_pop(conn->keyrequests), EOK,
433 ev.type, ev.key, ev.mods, ev.c);
434 break;
435 }
436
437 keybuffer_push(&conn->keybuffer, &ev);
438 retval = 0;
439
440 break;
441 default:
442 retval = ENOENT;
443 }
444 ipc_answer_0(callid, retval);
445 }
446}
447
448/** Handle CONSOLE_WRITE call. */
449static void cons_write(int consnum, ipc_callid_t rid, ipc_call_t *request)
450{
451 ipc_callid_t callid;
452 size_t len;
453 size_t i;
454
455 if (!ipc_data_write_receive(&callid, &len)) {
456 ipc_answer_0(callid, EINVAL);
457 ipc_answer_0(rid, EINVAL);
458 }
459
460 if (len > CWRITE_BUF_SIZE)
461 len = CWRITE_BUF_SIZE;
462
463 (void) ipc_data_write_finalize(callid, cwrite_buf, len);
464
465 for (i = 0; i < len; i++) {
466 write_char(consnum, cwrite_buf[i]);
467 }
468
469 gcons_notify_char(consnum);
470 ipc_answer_1(rid, EOK, len);
471}
472
473/** Default thread for new connections */
474static void client_connection(ipc_callid_t iid, ipc_call_t *icall)
475{
476 ipc_callid_t callid;
477 ipc_call_t call;
478 int consnum;
479 ipcarg_t arg1, arg2, arg3, arg4;
480 connection_t *conn;
481
482 if ((consnum = find_free_connection()) == -1) {
483 ipc_answer_0(iid, ELIMIT);
484 return;
485 }
486 conn = &connections[consnum];
487 conn->used = 1;
488
489 async_serialize_start();
490 gcons_notify_connect(consnum);
491 conn->client_phone = IPC_GET_ARG5(*icall);
492 screenbuffer_clear(&conn->screenbuffer);
493
494 /* Accept the connection */
495 ipc_answer_0(iid, EOK);
496
497 while (1) {
498 async_serialize_end();
499 callid = async_get_call(&call);
500 async_serialize_start();
501
502 arg1 = 0;
503 arg2 = 0;
504 arg3 = 0;
505 arg4 = 0;
506
507 switch (IPC_GET_METHOD(call)) {
508 case IPC_M_PHONE_HUNGUP:
509 gcons_notify_disconnect(consnum);
510
511 /* Answer all pending requests */
512 while (conn->keyrequest_counter > 0) {
513 conn->keyrequest_counter--;
514 ipc_answer_0(fifo_pop(conn->keyrequests),
515 ENOENT);
516 break;
517 }
518 conn->used = 0;
519 return;
520 case CONSOLE_PUTCHAR:
521 write_char(consnum, IPC_GET_ARG1(call));
522 gcons_notify_char(consnum);
523 break;
524 case CONSOLE_WRITE:
525 cons_write(consnum, callid, &call);
526 continue;
527 case CONSOLE_CLEAR:
528 /* Send message to fb */
529 if (consnum == active_console) {
530 async_msg_0(fb_info.phone, FB_CLEAR);
531 }
532
533 screenbuffer_clear(&conn->screenbuffer);
534
535 break;
536 case CONSOLE_GOTO:
537 fb_buf_flush();
538 screenbuffer_goto(&conn->screenbuffer,
539 IPC_GET_ARG2(call), IPC_GET_ARG1(call));
540 if (consnum == active_console)
541 curs_goto(IPC_GET_ARG1(call),
542 IPC_GET_ARG2(call));
543 break;
544 case CONSOLE_GETSIZE:
545 arg1 = fb_info.rows;
546 arg2 = fb_info.cols;
547 break;
548 case CONSOLE_FLUSH:
549 fb_buf_flush();
550 if (consnum == active_console)
551 async_req_0_0(fb_info.phone, FB_FLUSH);
552 break;
553 case CONSOLE_SET_STYLE:
554 fb_buf_flush();
555 arg1 = IPC_GET_ARG1(call);
556 screenbuffer_set_style(&conn->screenbuffer, arg1);
557 if (consnum == active_console)
558 set_style(arg1);
559 break;
560 case CONSOLE_SET_COLOR:
561 fb_buf_flush();
562 arg1 = IPC_GET_ARG1(call);
563 arg2 = IPC_GET_ARG2(call);
564 arg3 = IPC_GET_ARG3(call);
565 screenbuffer_set_color(&conn->screenbuffer, arg1,
566 arg2, arg3);
567 if (consnum == active_console)
568 set_color(arg1, arg2, arg3);
569 break;
570 case CONSOLE_SET_RGB_COLOR:
571 fb_buf_flush();
572 arg1 = IPC_GET_ARG1(call);
573 arg2 = IPC_GET_ARG2(call);
574 screenbuffer_set_rgb_color(&conn->screenbuffer, arg1,
575 arg2);
576 if (consnum == active_console)
577 set_rgb_color(arg1, arg2);
578 break;
579 case CONSOLE_CURSOR_VISIBILITY:
580 fb_buf_flush();
581 arg1 = IPC_GET_ARG1(call);
582 conn->screenbuffer.is_cursor_visible = arg1;
583 if (consnum == active_console)
584 curs_visibility(arg1);
585 break;
586 case CONSOLE_GETKEY:
587 if (keybuffer_empty(&conn->keybuffer)) {
588 /* buffer is empty -> store request */
589 if (conn->keyrequest_counter <
590 MAX_KEYREQUESTS_BUFFERED) {
591 fifo_push(conn->keyrequests, callid);
592 conn->keyrequest_counter++;
593 } else {
594 /*
595 * No key available and too many
596 * requests => fail.
597 */
598 ipc_answer_0(callid, ELIMIT);
599 }
600 continue;
601 }
602 kbd_event_t ev;
603 keybuffer_pop(&conn->keybuffer, &ev);
604 arg1 = ev.type;
605 arg2 = ev.key;
606 arg3 = ev.mods;
607 arg4 = ev.c;
608 break;
609 }
610 ipc_answer_4(callid, EOK, arg1, arg2, arg3, arg4);
611 }
612}
613
614static void interrupt_received(ipc_callid_t callid, ipc_call_t *call)
615{
616 change_console(prev_console);
617}
618
619int main(int argc, char *argv[])
620{
621 printf(NAME ": HelenOS Console service\n");
622
623 ipcarg_t phonehash;
624 int kbd_phone;
625 size_t ib_size;
626 int i;
627
628 async_set_client_connection(client_connection);
629
630 /* Connect to keyboard driver */
631 kbd_phone = ipc_connect_me_to_blocking(PHONE_NS, SERVICE_KEYBOARD, 0, 0);
632 if (kbd_phone < 0) {
633 printf(NAME ": Failed to connect to keyboard service\n");
634 return -1;
635 }
636
637 if (ipc_connect_to_me(kbd_phone, SERVICE_CONSOLE, 0, 0, &phonehash) != 0) {
638 printf(NAME ": Failed to create callback from keyboard service\n");
639 return -1;
640 }
641
642 async_new_connection(phonehash, 0, NULL, keyboard_events);
643
644 /* Connect to framebuffer driver */
645 fb_info.phone = ipc_connect_me_to_blocking(PHONE_NS, SERVICE_VIDEO, 0, 0);
646 if (fb_info.phone < 0) {
647 printf(NAME ": Failed to connect to video service\n");
648 return -1;
649 }
650
651 /* Disable kernel output to the console */
652 __SYSCALL0(SYS_DEBUG_DISABLE_CONSOLE);
653
654 /* Initialize gcons */
655 gcons_init(fb_info.phone);
656 /* Synchronize, the gcons can have something in queue */
657 async_req_0_0(fb_info.phone, FB_FLUSH);
658
659 async_req_0_2(fb_info.phone, FB_GET_CSIZE, &fb_info.rows,
660 &fb_info.cols);
661 set_rgb_color(DEFAULT_FOREGROUND, DEFAULT_BACKGROUND);
662 clrscr();
663
664 /* Init virtual consoles */
665 for (i = 0; i < CONSOLE_COUNT; i++) {
666 connections[i].used = 0;
667 keybuffer_init(&connections[i].keybuffer);
668
669 connections[i].keyrequests.head = 0;
670 connections[i].keyrequests.tail = 0;
671 connections[i].keyrequests.items = MAX_KEYREQUESTS_BUFFERED;
672 connections[i].keyrequest_counter = 0;
673
674 if (screenbuffer_init(&connections[i].screenbuffer,
675 fb_info.cols, fb_info.rows) == NULL) {
676 /* FIXME: handle error */
677 return -1;
678 }
679 }
680 connections[KERNEL_CONSOLE].used = 1;
681
682 /* Set up shared memory buffer. */
683 ib_size = sizeof(keyfield_t) * fb_info.cols * fb_info.rows;
684 interbuffer = as_get_mappable_page(ib_size);
685
686 if (as_area_create(interbuffer, ib_size, AS_AREA_READ |
687 AS_AREA_WRITE | AS_AREA_CACHEABLE) != interbuffer) {
688 interbuffer = NULL;
689 }
690
691 if (interbuffer) {
692 if (ipc_share_out_start(fb_info.phone, interbuffer,
693 AS_AREA_READ) != EOK) {
694 as_area_destroy(interbuffer);
695 interbuffer = NULL;
696 }
697 }
698
699 curs_goto(0, 0);
700 curs_visibility(
701 connections[active_console].screenbuffer.is_cursor_visible);
702
703 /* Register at NS */
704 if (ipc_connect_to_me(PHONE_NS, SERVICE_CONSOLE, 0, 0, &phonehash) != 0)
705 return -1;
706
707 /* Receive kernel notifications */
708// if (sysinfo_value("kconsole.present")) {
709// int inr = sysinfo_value("kconsole.inr");
710// if (ipc_register_irq(inr, device_assign_devno(), 0, NULL) != EOK)
711// printf(NAME ": Error registering kconsole notifications\n");
712//
713// async_set_interrupt_received(interrupt_received);
714// }
715
716 // FIXME: avoid connectiong to itself, keep using klog
717 // printf(NAME ": Accepting connections\n");
718 async_manager();
719
720 return 0;
721}
722
723/** @}
724 */
Note: See TracBrowser for help on using the repository browser.