/* * Copyright (c) 2006 Josef Cejka * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup console * @{ */ /** @file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "console.h" #include "gcons.h" #define MAX_KEYREQUESTS_BUFFERED 32 #define NAME "console" /** Index of currently used virtual console. */ int active_console = 0; int prev_console = 0; /** Information about framebuffer */ struct { int phone; /**< Framebuffer phone */ ipcarg_t rows; /**< Framebuffer rows */ ipcarg_t cols; /**< Framebuffer columns */ } fb_info; typedef struct { keybuffer_t keybuffer; /**< Buffer for incoming keys. */ /** Buffer for unsatisfied request for keys. */ FIFO_CREATE_STATIC(keyrequests, ipc_callid_t, MAX_KEYREQUESTS_BUFFERED); int keyrequest_counter; /**< Number of requests in buffer. */ int client_phone; /**< Phone to connected client. */ int used; /**< 1 if this virtual console is * connected to some client.*/ screenbuffer_t screenbuffer; /**< Screenbuffer for saving screen * contents and related settings. */ } connection_t; static connection_t connections[CONSOLE_COUNT]; /**< Array of data for virtual * consoles */ static keyfield_t *interbuffer = NULL; /**< Pointer to memory shared * with framebufer used for * faster virtual console * switching */ /** Information on row-span yet unsent to FB driver. */ struct { int row; /**< Row where the span lies. */ int col; /**< Leftmost column of the span. */ int n; /**< Width of the span. */ } fb_pending; /** Size of cwrite_buf. */ #define CWRITE_BUF_SIZE 256 /** Buffer for receiving data via the CONSOLE_WRITE call from the client. */ static char cwrite_buf[CWRITE_BUF_SIZE]; static void fb_putchar(wchar_t c, int row, int col); /** Find unused virtual console. * */ static int find_free_connection(void) { int i; for (i = 0; i < CONSOLE_COUNT; i++) { if (!connections[i].used) return i; } return -1; } static void clrscr(void) { async_msg_0(fb_info.phone, FB_CLEAR); } static void curs_visibility(bool visible) { async_msg_1(fb_info.phone, FB_CURSOR_VISIBILITY, visible); } static void curs_hide_sync(void) { ipc_call_sync_1_0(fb_info.phone, FB_CURSOR_VISIBILITY, false); } static void curs_goto(int row, int col) { async_msg_2(fb_info.phone, FB_CURSOR_GOTO, row, col); } static void set_style(int style) { async_msg_1(fb_info.phone, FB_SET_STYLE, style); } static void set_color(int fgcolor, int bgcolor, int flags) { async_msg_3(fb_info.phone, FB_SET_COLOR, fgcolor, bgcolor, flags); } static void set_rgb_color(int fgcolor, int bgcolor) { async_msg_2(fb_info.phone, FB_SET_RGB_COLOR, fgcolor, bgcolor); } static void set_attrs(attrs_t *attrs) { switch (attrs->t) { case at_style: set_style(attrs->a.s.style); break; case at_idx: set_color(attrs->a.i.fg_color, attrs->a.i.bg_color, attrs->a.i.flags); break; case at_rgb: set_rgb_color(attrs->a.r.fg_color, attrs->a.r.bg_color); break; } } /** Send an area of screenbuffer to the FB driver. */ static void fb_update_area(connection_t *conn, int x, int y, int w, int h) { int i, j; int rc; attrs_t *attrs; keyfield_t *field; if (interbuffer) { for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { interbuffer[i + j * w] = *get_field_at(&conn->screenbuffer, x + i, y + j); } } rc = async_req_4_0(fb_info.phone, FB_DRAW_TEXT_DATA, x, y, w, h); } else { rc = ENOTSUP; } if (rc != 0) { /* attrs = &conn->screenbuffer.attrs; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { field = get_field_at(&conn->screenbuffer, x + i, y + j); if (!attrs_same(*attrs, field->attrs)) set_attrs(&field->attrs); attrs = &field->attrs; fb_putchar(field->character, y + j, x + i); } }*/ } } /** Flush pending cells to FB. */ static void fb_pending_flush(void) { screenbuffer_t *scr; scr = &(connections[active_console].screenbuffer); if (fb_pending.n > 0) { fb_update_area(&connections[active_console], fb_pending.col, fb_pending.row, fb_pending.n, 1); fb_pending.n = 0; } } /** Mark a character cell as changed. * * This adds the cell to the pending rowspan if possible. Otherwise * the old span is flushed first. */ static void cell_mark_changed(int row, int col) { if (fb_pending.n != 0) { if (row != fb_pending.row || col != fb_pending.col + fb_pending.n) { fb_pending_flush(); } } if (fb_pending.n == 0) { fb_pending.row = row; fb_pending.col = col; } ++fb_pending.n; } /** Print a character to the active VC with buffering. */ static void fb_putchar(wchar_t c, int row, int col) { async_msg_3(fb_info.phone, FB_PUTCHAR, c, row, col); } /** Process a character from the client (TTY emulation). */ static void write_char(int console, wchar_t ch) { bool flush_cursor = false; screenbuffer_t *scr = &(connections[console].screenbuffer); switch (ch) { case '\n': fb_pending_flush(); flush_cursor = true; scr->position_y++; scr->position_x = 0; break; case '\r': break; case '\t': scr->position_x += 8; scr->position_x -= scr->position_x % 8; break; case '\b': if (scr->position_x == 0) break; scr->position_x--; if (console == active_console) cell_mark_changed(scr->position_y, scr->position_x); screenbuffer_putchar(scr, ' '); break; default: if (console == active_console) cell_mark_changed(scr->position_y, scr->position_x); screenbuffer_putchar(scr, ch); scr->position_x++; } if (scr->position_x >= scr->size_x) { flush_cursor = true; scr->position_y++; } if (scr->position_y >= scr->size_y) { fb_pending_flush(); scr->position_y = scr->size_y - 1; screenbuffer_clear_line(scr, scr->top_line); scr->top_line = (scr->top_line + 1) % scr->size_y; if (console == active_console) async_msg_1(fb_info.phone, FB_SCROLL, 1); } scr->position_x = scr->position_x % scr->size_x; if (console == active_console && flush_cursor) curs_goto(scr->position_y, scr->position_x); } /** Switch to new console */ static void change_console(int newcons) { connection_t *conn; int i, j, rc; keyfield_t *field; attrs_t *attrs; if (newcons == active_console) return; fb_pending_flush(); if (newcons == KERNEL_CONSOLE) { async_serialize_start(); curs_hide_sync(); gcons_in_kernel(); async_serialize_end(); if (__SYSCALL0(SYS_DEBUG_ENABLE_CONSOLE)) { prev_console = active_console; active_console = KERNEL_CONSOLE; } else newcons = active_console; } if (newcons != KERNEL_CONSOLE) { async_serialize_start(); if (active_console == KERNEL_CONSOLE) gcons_redraw_console(); active_console = newcons; gcons_change_console(newcons); conn = &connections[active_console]; set_attrs(&conn->screenbuffer.attrs); curs_visibility(false); if (interbuffer) { for (j = 0; j < conn->screenbuffer.size_y; j++) { for (i = 0; i < conn->screenbuffer.size_x; i++) { unsigned int size_x; size_x = conn->screenbuffer.size_x; interbuffer[j * size_x + i] = *get_field_at(&conn->screenbuffer, i, j); } } /* This call can preempt, but we are already at the end */ rc = async_req_4_0(fb_info.phone, FB_DRAW_TEXT_DATA, 0, 0, conn->screenbuffer.size_x, conn->screenbuffer.size_y); } if ((!interbuffer) || (rc != 0)) { set_attrs(&conn->screenbuffer.attrs); clrscr(); attrs = &conn->screenbuffer.attrs; for (j = 0; j < conn->screenbuffer.size_y; j++) for (i = 0; i < conn->screenbuffer.size_x; i++) { field = get_field_at(&conn->screenbuffer, i, j); if (!attrs_same(*attrs, field->attrs)) set_attrs(&field->attrs); attrs = &field->attrs; if ((field->character == ' ') && (attrs_same(field->attrs, conn->screenbuffer.attrs))) continue; fb_putchar(field->character, j, i); } } curs_goto(conn->screenbuffer.position_y, conn->screenbuffer.position_x); curs_visibility(conn->screenbuffer.is_cursor_visible); async_serialize_end(); } } /** Handler for keyboard */ static void keyboard_events(ipc_callid_t iid, ipc_call_t *icall) { ipc_callid_t callid; ipc_call_t call; int retval; kbd_event_t ev; connection_t *conn; int newcon; /* Ignore parameters, the connection is alread opened */ while (1) { callid = async_get_call(&call); switch (IPC_GET_METHOD(call)) { case IPC_M_PHONE_HUNGUP: /* TODO: Handle hangup */ return; case KBD_MS_LEFT: newcon = gcons_mouse_btn(IPC_GET_ARG1(call)); if (newcon != -1) change_console(newcon); retval = 0; break; case KBD_MS_MOVE: gcons_mouse_move(IPC_GET_ARG1(call), IPC_GET_ARG2(call)); retval = 0; break; case KBD_EVENT: /* Got event from keyboard driver. */ retval = 0; ev.type = IPC_GET_ARG1(call); ev.key = IPC_GET_ARG2(call); ev.mods = IPC_GET_ARG3(call); ev.c = IPC_GET_ARG4(call); /* switch to another virtual console */ conn = &connections[active_console]; if ((ev.key >= KC_F1) && (ev.key < KC_F1 + CONSOLE_COUNT) && ((ev.mods & KM_CTRL) == 0)) { if (ev.key == KC_F12) change_console(KERNEL_CONSOLE); else change_console(ev.key - KC_F1); break; } /* if client is awaiting key, send it */ if (conn->keyrequest_counter > 0) { conn->keyrequest_counter--; ipc_answer_4(fifo_pop(conn->keyrequests), EOK, ev.type, ev.key, ev.mods, ev.c); break; } keybuffer_push(&conn->keybuffer, &ev); retval = 0; break; default: retval = ENOENT; } ipc_answer_0(callid, retval); } } /** Handle CONSOLE_WRITE call. */ static void cons_write(int consnum, ipc_callid_t rid, ipc_call_t *request) { ipc_callid_t callid; size_t size; wchar_t ch; size_t off; if (!ipc_data_write_receive(&callid, &size)) { ipc_answer_0(callid, EINVAL); ipc_answer_0(rid, EINVAL); } if (size > CWRITE_BUF_SIZE) size = CWRITE_BUF_SIZE; (void) ipc_data_write_finalize(callid, cwrite_buf, size); off = 0; while (off < size) { ch = str_decode(cwrite_buf, &off, size); write_char(consnum, ch); } gcons_notify_char(consnum); ipc_answer_1(rid, EOK, size); } /** Default thread for new connections */ static void client_connection(ipc_callid_t iid, ipc_call_t *icall) { ipc_callid_t callid; ipc_call_t call; int consnum; ipcarg_t arg1, arg2, arg3, arg4; connection_t *conn; screenbuffer_t *scr; if ((consnum = find_free_connection()) == -1) { ipc_answer_0(iid, ELIMIT); return; } conn = &connections[consnum]; conn->used = 1; async_serialize_start(); gcons_notify_connect(consnum); conn->client_phone = IPC_GET_ARG5(*icall); screenbuffer_clear(&conn->screenbuffer); /* Accept the connection */ ipc_answer_0(iid, EOK); while (1) { async_serialize_end(); callid = async_get_call(&call); async_serialize_start(); arg1 = 0; arg2 = 0; arg3 = 0; arg4 = 0; switch (IPC_GET_METHOD(call)) { case IPC_M_PHONE_HUNGUP: gcons_notify_disconnect(consnum); /* Answer all pending requests */ while (conn->keyrequest_counter > 0) { conn->keyrequest_counter--; ipc_answer_0(fifo_pop(conn->keyrequests), ENOENT); break; } conn->used = 0; return; case CONSOLE_PUTCHAR: write_char(consnum, IPC_GET_ARG1(call)); gcons_notify_char(consnum); break; case CONSOLE_WRITE: cons_write(consnum, callid, &call); continue; case CONSOLE_CLEAR: /* Send message to fb */ if (consnum == active_console) { async_msg_0(fb_info.phone, FB_CLEAR); } screenbuffer_clear(&conn->screenbuffer); break; case CONSOLE_GOTO: screenbuffer_goto(&conn->screenbuffer, IPC_GET_ARG2(call), IPC_GET_ARG1(call)); if (consnum == active_console) curs_goto(IPC_GET_ARG1(call), IPC_GET_ARG2(call)); break; case CONSOLE_GETSIZE: arg1 = fb_info.rows; arg2 = fb_info.cols; break; case CONSOLE_FLUSH: fb_pending_flush(); if (consnum == active_console) { async_req_0_0(fb_info.phone, FB_FLUSH); scr = &(connections[consnum].screenbuffer); curs_goto(scr->position_y, scr->position_x); } break; case CONSOLE_SET_STYLE: fb_pending_flush(); arg1 = IPC_GET_ARG1(call); screenbuffer_set_style(&conn->screenbuffer, arg1); if (consnum == active_console) set_style(arg1); break; case CONSOLE_SET_COLOR: fb_pending_flush(); arg1 = IPC_GET_ARG1(call); arg2 = IPC_GET_ARG2(call); arg3 = IPC_GET_ARG3(call); screenbuffer_set_color(&conn->screenbuffer, arg1, arg2, arg3); if (consnum == active_console) set_color(arg1, arg2, arg3); break; case CONSOLE_SET_RGB_COLOR: fb_pending_flush(); arg1 = IPC_GET_ARG1(call); arg2 = IPC_GET_ARG2(call); screenbuffer_set_rgb_color(&conn->screenbuffer, arg1, arg2); if (consnum == active_console) set_rgb_color(arg1, arg2); break; case CONSOLE_CURSOR_VISIBILITY: fb_pending_flush(); arg1 = IPC_GET_ARG1(call); conn->screenbuffer.is_cursor_visible = arg1; if (consnum == active_console) curs_visibility(arg1); break; case CONSOLE_GETKEY: if (keybuffer_empty(&conn->keybuffer)) { /* buffer is empty -> store request */ if (conn->keyrequest_counter < MAX_KEYREQUESTS_BUFFERED) { fifo_push(conn->keyrequests, callid); conn->keyrequest_counter++; } else { /* * No key available and too many * requests => fail. */ ipc_answer_0(callid, ELIMIT); } continue; } kbd_event_t ev; keybuffer_pop(&conn->keybuffer, &ev); arg1 = ev.type; arg2 = ev.key; arg3 = ev.mods; arg4 = ev.c; break; case CONSOLE_KCON_ENABLE: change_console(KERNEL_CONSOLE); break; } ipc_answer_4(callid, EOK, arg1, arg2, arg3, arg4); } } static void interrupt_received(ipc_callid_t callid, ipc_call_t *call) { change_console(prev_console); } int main(int argc, char *argv[]) { printf(NAME ": HelenOS Console service\n"); ipcarg_t phonehash; int kbd_phone; size_t ib_size; int i; async_set_client_connection(client_connection); /* Connect to keyboard driver */ kbd_phone = ipc_connect_me_to_blocking(PHONE_NS, SERVICE_KEYBOARD, 0, 0); if (kbd_phone < 0) { printf(NAME ": Failed to connect to keyboard service\n"); return -1; } if (ipc_connect_to_me(kbd_phone, SERVICE_CONSOLE, 0, 0, &phonehash) != 0) { printf(NAME ": Failed to create callback from keyboard service\n"); return -1; } async_new_connection(phonehash, 0, NULL, keyboard_events); /* Connect to framebuffer driver */ fb_info.phone = ipc_connect_me_to_blocking(PHONE_NS, SERVICE_VIDEO, 0, 0); if (fb_info.phone < 0) { printf(NAME ": Failed to connect to video service\n"); return -1; } /* Disable kernel output to the console */ __SYSCALL0(SYS_DEBUG_DISABLE_CONSOLE); /* Initialize gcons */ gcons_init(fb_info.phone); /* Synchronize, the gcons can have something in queue */ async_req_0_0(fb_info.phone, FB_FLUSH); async_req_0_2(fb_info.phone, FB_GET_CSIZE, &fb_info.rows, &fb_info.cols); set_rgb_color(DEFAULT_FOREGROUND, DEFAULT_BACKGROUND); clrscr(); /* Init virtual consoles */ for (i = 0; i < CONSOLE_COUNT; i++) { connections[i].used = 0; keybuffer_init(&connections[i].keybuffer); connections[i].keyrequests.head = 0; connections[i].keyrequests.tail = 0; connections[i].keyrequests.items = MAX_KEYREQUESTS_BUFFERED; connections[i].keyrequest_counter = 0; if (screenbuffer_init(&connections[i].screenbuffer, fb_info.cols, fb_info.rows) == NULL) { /* FIXME: handle error */ return -1; } } connections[KERNEL_CONSOLE].used = 1; /* Set up shared memory buffer. */ ib_size = sizeof(keyfield_t) * fb_info.cols * fb_info.rows; interbuffer = as_get_mappable_page(ib_size); fb_pending.n = 0; if (as_area_create(interbuffer, ib_size, AS_AREA_READ | AS_AREA_WRITE | AS_AREA_CACHEABLE) != interbuffer) { interbuffer = NULL; } if (interbuffer) { if (ipc_share_out_start(fb_info.phone, interbuffer, AS_AREA_READ) != EOK) { as_area_destroy(interbuffer); interbuffer = NULL; } } curs_goto(0, 0); curs_visibility( connections[active_console].screenbuffer.is_cursor_visible); /* Register at NS */ if (ipc_connect_to_me(PHONE_NS, SERVICE_CONSOLE, 0, 0, &phonehash) != 0) return -1; /* Receive kernel notifications */ if (event_subscribe(EVENT_KCONSOLE, 0) != EOK) printf(NAME ": Error registering kconsole notifications\n"); async_set_interrupt_received(interrupt_received); // FIXME: avoid connectiong to itself, keep using klog // printf(NAME ": Accepting connections\n"); async_manager(); return 0; } /** @} */