root/src/add-ons/kernel/drivers/common/console.cpp
/*
 * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */


#include <Drivers.h>
#include <KernelExport.h>

#include <console.h>
#include <lock.h>

#include <string.h>
#include <stdio.h>
#include <termios.h>


#define DEVICE_NAME "console"

#define TAB_SIZE 8
#define TAB_MASK 7

#define FMASK 0x0f
#define BMASK 0x70

typedef enum {
        CONSOLE_STATE_NORMAL = 0,
        CONSOLE_STATE_GOT_ESCAPE,
        CONSOLE_STATE_SEEN_BRACKET,
        CONSOLE_STATE_NEW_ARG,
        CONSOLE_STATE_PARSING_ARG,
} console_state;

typedef enum {
        SCREEN_ERASE_WHOLE,
        SCREEN_ERASE_UP,
        SCREEN_ERASE_DOWN
} erase_screen_mode;

typedef enum {
        LINE_ERASE_WHOLE,
        LINE_ERASE_LEFT,
        LINE_ERASE_RIGHT
} erase_line_mode;

#define MAX_ARGS 8

static struct console_desc {
        mutex   lock;

        int32   lines;
        int32   columns;

        uint8   attr;
        uint8   saved_attr;
        bool    bright_attr;
        bool    reverse_attr;

        int32   x;                                              /* current x coordinate */
        int32   y;                                              /* current y coordinate */
        int32   saved_x;                                /* used to save x and y */
        int32   saved_y;

        int32   scroll_top;     /* top of the scroll region */
        int32   scroll_bottom;  /* bottom of the scroll region */

        /* state machine */
        console_state state;
        int32   arg_count;
        int32   args[MAX_ARGS];

        char    module_name[B_FILE_NAME_LENGTH];
        console_module_info *module;
} sConsole;

int32 api_version = B_CUR_DRIVER_API_VERSION;

static int32 sOpenMask;


static inline void
update_cursor(struct console_desc *console, int x, int y)
{
        console->module->move_cursor(x, y);
}


static void
gotoxy(struct console_desc *console, int newX, int newY)
{
        if (newX >= console->columns)
                newX = console->columns - 1;
        if (newX < 0)
                newX = 0;
        if (newY >= console->lines)
                newY = console->lines - 1;
        if (newY < 0)
                newY = 0;

        console->x = newX;
        console->y = newY;
}


static void
reset_console(struct console_desc *console)
{
        console->attr = 0x0f;
        console->scroll_top = 0;
        console->scroll_bottom = console->lines - 1;
        console->bright_attr = true;
        console->reverse_attr = false;
}


/*!     Scroll from the cursor line up to the top of the scroll region up one line.
*/
static void
scrup(struct console_desc *console)
{
        // see if cursor is outside of scroll region
        if (console->y < console->scroll_top || console->y > console->scroll_bottom)
                return;

        if (console->y - console->scroll_top > 1) {
                // move the screen up one
                console->module->blit(0, console->scroll_top + 1, console->columns,
                        console->y - console->scroll_top, 0, console->scroll_top);
        }

        // clear the bottom line
        console->module->fill_glyph(0, console->y, console->columns, 1, ' ',
                console->attr);
}


/*!     Scroll from the cursor line down to the bottom of the scroll region down
        one line.
*/
static void
scrdown(struct console_desc *console)
{
        // see if cursor is outside of scroll region
        if (console->y < console->scroll_top || console->y > console->scroll_bottom)
                return;

        if (console->scroll_bottom - console->y > 1) {
                // move the screen down one
                console->module->blit(0, console->y, console->columns,
                        console->scroll_bottom - console->y, 0, console->y + 1);
        }

        // clear the top line
        console->module->fill_glyph(0, console->y, console->columns, 1, ' ',
                console->attr);
}


static void
lf(struct console_desc *console)
{
        //dprintf("lf: y %d x %d scroll_top %d scoll_bottom %d\n", console->y, console->x, console->scroll_top, console->scroll_bottom);

        if (console->y == console->scroll_bottom ) {
                // we hit the bottom of our scroll region
                scrup(console);
        } else if(console->y < console->scroll_bottom) {
                console->y++;
        }
}


static void
rlf(struct console_desc *console)
{
        if (console->y == console->scroll_top) {
                // we hit the top of our scroll region
                scrdown(console);
        } else if (console->y > console->scroll_top) {
                console->y--;
        }
}


static void
cr(struct console_desc *console)
{
        console->x = 0;
}


static void
del(struct console_desc *console)
{
        if (console->x > 0) {
                console->x--;
        } else if (console->y > 0) {
        console->y--;
        console->x = console->columns - 1;
    } else {
        //This doesn't work...
        //scrdown(console);
        //console->y--;
        //console->x = console->columns - 1;
        return;
    }
        console->module->put_glyph(console->x, console->y, ' ', console->attr);
}


static void
erase_line(struct console_desc *console, erase_line_mode mode)
{
        switch (mode) {
                case LINE_ERASE_WHOLE:
                        console->module->fill_glyph(0, console->y, console->columns, 1, ' ',
                                console->attr);
                        break;
                case LINE_ERASE_LEFT:
                        console->module->fill_glyph(0, console->y, console->x + 1, 1, ' ',
                                console->attr);
                        break;
                case LINE_ERASE_RIGHT:
                        console->module->fill_glyph(console->x, console->y,
                                console->columns - console->x, 1, ' ', console->attr);
                        break;
                default:
                        return;
        }
}


static void
erase_screen(struct console_desc *console, erase_screen_mode mode)
{
        switch (mode) {
                case SCREEN_ERASE_WHOLE:
                        console->module->clear(console->attr);
                        break;
                case SCREEN_ERASE_UP:
                        console->module->fill_glyph(0, 0, console->columns, console->y + 1,
                                ' ', console->attr);
                        break;
                case SCREEN_ERASE_DOWN:
                        console->module->fill_glyph(console->y, 0, console->columns,
                                console->lines - console->y, ' ', console->attr);
                        break;
                default:
                        return;
        }
}


static void
save_cur(struct console_desc *console, bool saveAttrs)
{
        console->saved_x = console->x;
        console->saved_y = console->y;

        if (saveAttrs)
                console->saved_attr = console->attr;
}


static void
restore_cur(struct console_desc *console, bool restoreAttrs)
{
        console->x = console->saved_x;
        console->y = console->saved_y;

        if (restoreAttrs)
                console->attr = console->saved_attr;
}


static char
console_putch(struct console_desc *console, const char c)
{
        if (++console->x >= console->columns) {
                cr(console);
                lf(console);
        }
        console->module->put_glyph(console->x - 1, console->y, c, console->attr);
        return c;
}


static void
tab(struct console_desc *console)
{
        console->x = (console->x + TAB_SIZE) & ~TAB_MASK;
        if (console->x >= console->columns) {
                console->x -= console->columns;
                lf(console);
        }
}


static void
set_scroll_region(struct console_desc *console, int top, int bottom)
{
        if (top < 0)
                top = 0;
        if (bottom >= console->lines)
                bottom = console->lines - 1;
        if (top > bottom)
                return;

        console->scroll_top = top;
        console->scroll_bottom = bottom;
}


static void
set_vt100_attributes(struct console_desc *console, int32 *args, int32 argCount)
{
        if (argCount == 0) {
                // that's the default (attributes off)
                argCount++;
                args[0] = 0;
        }

        for (int32 i = 0; i < argCount; i++) {
                //dprintf("set_vt100_attributes: %ld\n", args[i]);
                switch (args[i]) {
                        case 0: // reset
                                console->attr = 0x0f;
                                console->bright_attr = true;
                                console->reverse_attr = false;
                                break;
                        case 1: // bright
                                console->bright_attr = true;
                                console->attr |= 0x08; // set the bright bit
                                break;
                        case 2: // dim
                                console->bright_attr = false;
                                console->attr &= ~0x08; // unset the bright bit
                                break;
                        case 4: // underscore we can't do
                                break;
                        case 5: // blink
                                console->attr |= 0x80; // set the blink bit
                                break;
                        case 7: // reverse
                                console->reverse_attr = true;
                                console->attr = ((console->attr & BMASK) >> 4)
                                        | ((console->attr & FMASK) << 4);
                                if (console->bright_attr)
                                        console->attr |= 0x08;
                                break;
                        case 8: // hidden?
                                break;

                        /* foreground colors */
                        case 30: console->attr = (console->attr & ~FMASK) | 0 | (console->bright_attr ? 0x08 : 0); break; // black
                        case 31: console->attr = (console->attr & ~FMASK) | 4 | (console->bright_attr ? 0x08 : 0); break; // red
                        case 32: console->attr = (console->attr & ~FMASK) | 2 | (console->bright_attr ? 0x08 : 0); break; // green
                        case 33: console->attr = (console->attr & ~FMASK) | 6 | (console->bright_attr ? 0x08 : 0); break; // yellow
                        case 34: console->attr = (console->attr & ~FMASK) | 1 | (console->bright_attr ? 0x08 : 0); break; // blue
                        case 35: console->attr = (console->attr & ~FMASK) | 5 | (console->bright_attr ? 0x08 : 0); break; // magenta
                        case 36: console->attr = (console->attr & ~FMASK) | 3 | (console->bright_attr ? 0x08 : 0); break; // cyan
                        case 37: console->attr = (console->attr & ~FMASK) | 7 | (console->bright_attr ? 0x08 : 0); break; // white

                        /* background colors */
                        case 40: console->attr = (console->attr & ~BMASK) | (0 << 4); break; // black
                        case 41: console->attr = (console->attr & ~BMASK) | (4 << 4); break; // red
                        case 42: console->attr = (console->attr & ~BMASK) | (2 << 4); break; // green
                        case 43: console->attr = (console->attr & ~BMASK) | (6 << 4); break; // yellow
                        case 44: console->attr = (console->attr & ~BMASK) | (1 << 4); break; // blue
                        case 45: console->attr = (console->attr & ~BMASK) | (5 << 4); break; // magenta
                        case 46: console->attr = (console->attr & ~BMASK) | (3 << 4); break; // cyan
                        case 47: console->attr = (console->attr & ~BMASK) | (7 << 4); break; // white
                }
        }
}


static bool
process_vt100_command(struct console_desc *console, const char c,
        bool seenBracket, int32 *args, int32 argCount)
{
        bool ret = true;

        //dprintf("process_vt100_command: c '%c', argCount %ld, arg[0] %ld, arg[1] %ld, seenBracket %d\n",
        //      c, argCount, args[0], args[1], seenBracket);

        if (seenBracket) {
                switch(c) {
                        case 'H': // set cursor position
                        case 'f':
                        {
                                int32 row = argCount > 0 ? args[0] : 1;
                                int32 col = argCount > 1 ? args[1] : 1;
                                if (row > 0)
                                        row--;
                                if (col > 0)
                                        col--;
                                gotoxy(console, col, row);
                                break;
                        }
                        case 'A':       // move up
                        {
                                int32 deltaY = argCount > 0 ? -args[0] : -1;
                                if (deltaY == 0)
                                        deltaY = -1;
                                gotoxy(console, console->x, console->y + deltaY);
                                break;
                        }
                        case 'e':
                        case 'B':       // move down
                        {
                                int32 deltaY = argCount > 0 ? args[0] : 1;
                                if (deltaY == 0)
                                        deltaY = 1;
                                gotoxy(console, console->x, console->y + deltaY);
                                break;
                        }
                        case 'D':       // move left
                        {
                                int32 deltaX = argCount > 0 ? -args[0] : -1;
                                if (deltaX == 0)
                                        deltaX = -1;
                                gotoxy(console, console->x + deltaX, console->y);
                                break;
                        }
                        case 'a':
                        case 'C':       // move right
                        {
                                int32 deltaX = argCount > 0 ? args[0] : 1;
                                if (deltaX == 0)
                                        deltaX = 1;
                                gotoxy(console, console->x + deltaX, console->y);
                                break;
                        }
                        case '`':
                        case 'G':       // set X position
                        {
                                int32 newX = argCount > 0 ? args[0] : 1;
                                if (newX > 0)
                                        newX--;
                                gotoxy(console, newX, console->y);
                                break;
                        }
                        case 'd':       // set y position
                        {
                                int32 newY = argCount > 0 ? args[0] : 1;
                                if (newY > 0)
                                        newY--;
                                gotoxy(console, console->x, newY);
                                break;
                        }
                        case 's':       // save current cursor
                                save_cur(console, false);
                                break;
                        case 'u':       // restore cursor
                                restore_cur(console, false);
                                break;
                        case 'r':       // set scroll region
                        {
                                int32 low = argCount > 0 ? args[0] : 1;
                                int32 high = argCount > 1 ? args[1] : console->lines;
                                if (low <= high)
                                        set_scroll_region(console, low - 1, high - 1);
                                break;
                        }
                        case 'L':       // scroll virtual down at cursor
                        {
                                int32 lines = argCount > 0 ? args[0] : 1;
                                while (lines > 0) {
                                        scrdown(console);
                                        lines--;
                                }
                                break;
                        }
                        case 'M':       // scroll virtual up at cursor
                        {
                                int32 lines = argCount > 0 ? args[0] : 1;
                                while (lines > 0) {
                                        scrup(console);
                                        lines--;
                                }
                                break;
                        }
                        case 'K':
                                if (argCount == 0 || args[0] == 0) {
                                        // erase to end of line
                                        erase_line(console, LINE_ERASE_RIGHT);
                                } else if (argCount > 0) {
                                        if (args[0] == 1)
                                                erase_line(console, LINE_ERASE_LEFT);
                                        else if (args[0] == 2)
                                                erase_line(console, LINE_ERASE_WHOLE);
                                }
                                break;
                        case 'J':
                                if (argCount == 0 || args[0] == 0) {
                                        // erase to end of screen
                                        erase_screen(console, SCREEN_ERASE_DOWN);
                                } else {
                                        if (args[0] == 1)
                                                erase_screen(console, SCREEN_ERASE_UP);
                                        else if (args[0] == 2)
                                                erase_screen(console, SCREEN_ERASE_WHOLE);
                                }
                                break;
                        case 'm':
                                if (argCount >= 0)
                                        set_vt100_attributes(console, args, argCount);
                                break;
                        default:
                                ret = false;
                }
        } else {
                switch (c) {
                        case 'c':
                                reset_console(console);
                                break;
                        case 'D':
                                rlf(console);
                                break;
                        case 'M':
                                lf(console);
                                break;
                        case '7':
                                save_cur(console, true);
                                break;
                        case '8':
                                restore_cur(console, true);
                                break;
                        default:
                                ret = false;
                }
        }

        return ret;
}


static ssize_t
_console_write(struct console_desc *console, const void *buffer, size_t length)
{
        const char *c;
        size_t pos = 0;

        while (pos < length) {
                c = &((const char *)buffer)[pos++];

                switch (console->state) {
                        case CONSOLE_STATE_NORMAL:
                                // just output the stuff
                                switch (*c) {
                                        case '\n':
                                                lf(console);
                                                break;
                                        case '\r':
                                                cr(console);
                                                break;
                                        case 0x8: // backspace
                                                del(console);
                                                break;
                                        case '\t':
                                                tab(console);
                                                break;
                                        case '\a':
                                                // beep
                                                dprintf("<BEEP>\n");
                                                break;
                                        case '\0':
                                                break;
                                        case 0x1b:
                                                // escape character
                                                console->arg_count = 0;
                                                console->state = CONSOLE_STATE_GOT_ESCAPE;
                                                break;
                                        default:
                                                console_putch(console, *c);
                                }
                                break;
                        case CONSOLE_STATE_GOT_ESCAPE:
                                // Look for either commands with no argument, or the '['
                                // character
                                switch (*c) {
                                        case '[':
                                                console->state = CONSOLE_STATE_SEEN_BRACKET;
                                                break;
                                        default:
                                                console->args[0] = 0;
                                                process_vt100_command(console, *c, false, console->args,
                                                        0);
                                                console->state = CONSOLE_STATE_NORMAL;
                                                break;
                                }
                                break;
                        case CONSOLE_STATE_SEEN_BRACKET:
                                switch (*c) {
                                        case '0'...'9':
                                                console->arg_count = 0;
                                                console->args[console->arg_count] = *c - '0';
                                                console->state = CONSOLE_STATE_PARSING_ARG;
                                                break;
                                        case '?':
                                                // Private DEC mode parameter follows - we ignore those
                                                // anyway
                                                // TODO: check if it was really used in combination with
                                                // a mode command
                                                break;
                                        default:
                                                process_vt100_command(console, *c, true, console->args,
                                                        0);
                                                console->state = CONSOLE_STATE_NORMAL;
                                                break;
                                }
                                break;
                        case CONSOLE_STATE_NEW_ARG:
                                switch (*c) {
                                        case '0'...'9':
                                                console->arg_count++;
                                                if (console->arg_count == MAX_ARGS) {
                                                        console->state = CONSOLE_STATE_NORMAL;
                                                        break;
                                                }
                                                console->args[console->arg_count] = *c - '0';
                                                console->state = CONSOLE_STATE_PARSING_ARG;
                                                break;
                                        default:
                                                process_vt100_command(console, *c, true, console->args,
                                                        console->arg_count + 1);
                                                console->state = CONSOLE_STATE_NORMAL;
                                                break;
                                }
                                break;
                        case CONSOLE_STATE_PARSING_ARG:
                                // parse args
                                switch (*c) {
                                        case '0'...'9':
                                                console->args[console->arg_count] *= 10;
                                                console->args[console->arg_count] += *c - '0';
                                                break;
                                        case ';':
                                                console->state = CONSOLE_STATE_NEW_ARG;
                                                break;
                                        default:
                                                process_vt100_command(console, *c, true, console->args,
                                                        console->arg_count + 1);
                                                console->state = CONSOLE_STATE_NORMAL;
                                                break;
                                }
                }
        }

        return pos;
}


//      #pragma mark -


static status_t
console_open(const char *name, uint32 flags, void **cookie)
{
        if (atomic_or(&sOpenMask, 1) == 1)
                return B_BUSY;

        *cookie = &sConsole;

        status_t status = get_module(sConsole.module_name, (module_info **)&sConsole.module);
        if (status == B_OK)
                sConsole.module->clear(0x0f);

        return status;
}


static status_t
console_freecookie(void *cookie)
{
        if (sConsole.module != NULL) {
                put_module(sConsole.module_name);
                sConsole.module = NULL;
        }

        atomic_and(&sOpenMask, ~1);

        return B_OK;
}


static status_t
console_close(void *cookie)
{
//      dprintf("console_close: entry\n");

        return 0;
}


static status_t
console_read(void *cookie, off_t pos, void *buffer, size_t *_length)
{
        return B_NOT_ALLOWED;
}


static status_t
console_write(void *cookie, off_t pos, const void *buffer, size_t *_length)
{
        struct console_desc *console = (struct console_desc *)cookie;
        ssize_t written = 0;
        status_t status = B_OK;

#if 0
{
        const char *input = (const char *)buffer;
        dprintf("console_write (%lu bytes): \"", *_length);
        for (uint32 i = 0; i < *_length; i++) {
                if (input[i] < ' ')
                        dprintf("(%d:0x%x)", input[i], input[i]);
                else
                        dprintf("%c", input[i]);
        }
        dprintf("\"\n");
}
#endif

        mutex_lock(&console->lock);

        update_cursor(console, -1, -1); // hide it

        size_t bytesLeft = *_length;
        const char *str = (const char*)buffer;
        while (bytesLeft > 0) {
                char localBuffer[512];
                size_t chunkSize = min_c(sizeof(localBuffer), bytesLeft);
                if (user_memcpy(localBuffer, str, chunkSize) < B_OK) {
                        status = B_BAD_ADDRESS;
                        break;
                }
                written += _console_write(console, localBuffer, chunkSize);
                str += chunkSize;
                bytesLeft -= chunkSize;
        }
        update_cursor(console, console->x, console->y);

        mutex_unlock(&console->lock);

        if (status == B_OK)
                *_length = written;
        return status;
}


static status_t
console_ioctl(void *cookie, uint32 op, void *buffer, size_t length)
{
        struct console_desc *console = (struct console_desc *)cookie;

        if (op == TIOCGWINSZ) {
                struct winsize size;
                size.ws_xpixel = size.ws_col = console->columns;
                size.ws_ypixel = size.ws_row = console->lines;

                return user_memcpy(buffer, &size, sizeof(struct winsize));
        }

        return B_BAD_VALUE;
}


//      #pragma mark -


status_t
init_hardware(void)
{
        // iterate through the list of console modules until we find one that accepts the job
        void *cookie = open_module_list("console");
        if (cookie == NULL)
                return B_ERROR;

        bool found = false;

        char buffer[B_FILE_NAME_LENGTH];
        size_t bufferSize = sizeof(buffer);

        while (read_next_module_name(cookie, buffer, &bufferSize) == B_OK) {
                dprintf("con_init: trying module %s\n", buffer);
                if (get_module(buffer, (module_info **)&sConsole.module) == B_OK) {
                        strlcpy(sConsole.module_name, buffer, sizeof(sConsole.module_name));
                        put_module(buffer);
                        found = true;
                        break;
                }

                bufferSize = sizeof(buffer);
        }

        if (found) {
                // set up the console structure
                mutex_init(&sConsole.lock, "console lock");
                sConsole.module->get_size(&sConsole.columns, &sConsole.lines);

                reset_console(&sConsole);
                gotoxy(&sConsole, 0, 0);
                save_cur(&sConsole, true);
        }

        close_module_list(cookie);
        return found ? B_OK : B_ERROR;
}


const char **
publish_devices(void)
{
        static const char *devices[] = {
                DEVICE_NAME,
                NULL
        };

        return devices;
}


device_hooks *
find_device(const char *name)
{
        static device_hooks hooks = {
                &console_open,
                &console_close,
                &console_freecookie,
                &console_ioctl,
                &console_read,
                &console_write,
                /* Leave select/deselect/readv/writev undefined. The kernel will
                 * use its own default implementation. The basic hooks above this
                 * line MUST be defined, however. */
                NULL,
                NULL,
                NULL,
                NULL
        };

        if (!strcmp(name, DEVICE_NAME))
                return &hooks;

        return NULL;
}


status_t
init_driver(void)
{
        return B_OK;
}


void
uninit_driver(void)
{
}