/* * Copyright (c) 2023-2024 Ian Marco Moffett and the Osmora Team. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of Hyra nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. */ #include #include #include #include #include #include /* * Default cursor color. * * TODO: The cursor color should * be the invert of the color * of whatever it is on top of. */ #define DEFAULT_CURSOR_BG 0x808080 #define CURSOR_WIDTH FONT_WIDTH #define CURSOR_HEIGHT FONT_HEIGHT __KERNEL_META("$Hyra$: tty.c, Ian Marco Moffett, " "Core TTY implementation"); /* List of attached TTYs */ static TAILQ_HEAD(, tty) tty_list; /* * Renders a char onto the TTY specified by `tty`. */ static void tty_draw_char(struct tty *tty, char c, uint32_t fg, uint32_t bg) { uint32_t *fb_ptr; uint32_t x, y; size_t idx; const uint8_t *glyph; /* Get a pointer to framebuffer memory */ fb_ptr = tty->fbdev.mem; /* Get the specific glyph of `c` */ glyph = &DEFAULT_FONT_DATA[(int)c*16]; x = tty->chpos_x; y = tty->chpos_y; for (uint32_t cy = 0; cy < FONT_HEIGHT; ++cy) { for (uint32_t cx = 0; cx < FONT_WIDTH; ++cx) { idx = fbdev_get_index(&tty->fbdev, x+FONT_WIDTH-cx, y+cy); fb_ptr[idx] = __TEST(glyph[cy], __BIT(cx)) ? fg : bg; } } } /* * Draws a cursor onto the screen. * * Call with TTY locked. */ static void tty_draw_cursor(struct tty *tty, bool hide) { uint32_t x, y, idx; uint32_t *fb_ptr; uint32_t color; fb_ptr = tty->fbdev.mem; color = hide ? tty->bg : DEFAULT_CURSOR_BG; for (size_t cy = 0; cy < CURSOR_HEIGHT; ++cy) { for (size_t cx = 0; cx < CURSOR_WIDTH; ++cx) { x = tty->curspos_x + cx; y = tty->curspos_y + cy; idx = fbdev_get_index(&tty->fbdev, x, y); fb_ptr[idx] = color; } } } static void tty_scroll_single(struct tty *tty) { uint32_t *fb_ptr; size_t line_size, dest_idx, src_idx; fb_ptr = tty->fbdev.mem; line_size = tty->fbdev.pitch/4; /* Copy each line up */ for (size_t y = FONT_HEIGHT; y < tty->t_ws_ypixel; y += FONT_HEIGHT) { dest_idx = fbdev_get_index(&tty->fbdev, 0, y - FONT_HEIGHT); src_idx = fbdev_get_index(&tty->fbdev, 0, y); memcpy32(&fb_ptr[dest_idx], &fb_ptr[src_idx], FONT_HEIGHT*line_size); } /* * Ensure we start at X position 0 * after we scrolled down. */ tty->chpos_x = 0; tty->curspos_x = 0; } /* * Handles a newline. * * Call with TTY locked. */ static inline void tty_newline(struct tty *tty) { uint32_t ypos; const uint32_t MAX_YPOS = tty->t_ws_ypixel - (CURSOR_HEIGHT*2); /* Hide the cursor */ tty_draw_cursor(tty, true); /* Reset X positions */ tty->chpos_x = 0; tty->curspos_x = 0; /* * Get the value closest to end * of the screen. */ ypos = __MAX(tty->chpos_y, tty->curspos_y); /* * Check if we need to scroll * instead of incrementing * Y positions. */ if (ypos < MAX_YPOS) { tty->chpos_y += FONT_HEIGHT; tty->curspos_y += FONT_HEIGHT; } else { tty_scroll_single(tty); } /* Redraw the cursor */ tty_draw_cursor(tty, false); } /* * Appends a character to the TTY specified * by `tty` as well as incrementing tty->chpos_x * and making newlines as needed. * * Call with TTY locked. */ static inline void tty_append_char(struct tty *tty, int c) { const uint32_t MAX_XPOS = tty->t_ws_xpixel - FONT_WIDTH; /* Hide the cursor */ tty_draw_cursor(tty, true); tty_draw_char(tty, c, tty->fg, tty->bg); tty->chpos_x += FONT_WIDTH; tty->curspos_x += FONT_WIDTH; if (tty->chpos_x >= MAX_XPOS) { tty_newline(tty); } /* Redraw the cursor */ tty_draw_cursor(tty, false); } /* * Writes out a tab as `tty->tab_width` * spaces. * * Call with TTY locked. */ static void tty_expand_tab(struct tty *tty) { for (size_t i = 0; i < tty->tab_width; ++i) { tty_append_char(tty, ' '); } } /* * Writes a char to the TTY. * * Returns 0 on success; non-zero value * is a character to be resent. * * Call with TTY locked. */ static int tty_putch(struct tty *tty, int c) { if (!__TEST(tty->t_oflag, OPOST)) { /* * Just write out the char with * no processing. */ tty_append_char(tty, c); return 0; } switch (c) { case ASCII_HT: /* Tab */ tty_expand_tab(tty); break; case ASCII_LF: /* Line feed ('\n') */ tty_newline(tty); break; default: tty_append_char(tty, c); break; } return 0; } /* * Flushes a TTY specified by `tty`. * * Call with TTY locked. */ void tty_flush(struct tty *tty) { struct tty_ring *ring; ring = &tty->ring; /* * Write each byte from the buffer * to the screen with output processing * if possible. * * XXX: Perhaps there could be a faster * way of doing this instead of * byte by byte? */ for (size_t i = 0; i < ring->len; ++i) { tty_putch(tty, ring->buf[i]); } ring->len = 0; } /* * Writes to a TTY specified by `tty`. * * @buf: Buffer to write. * @len: Length of buffer. * * Returns number of bytes written, and * EXIT_FAILURE on error. */ ssize_t tty_write(struct tty *tty, const char *buf, size_t len) { if (len == 0) { /* Bad value, don't even try */ return EXIT_FAILURE; } TTY_LOCK(tty); for (size_t i = 0; i < len; ++i) { tty_push_char(tty, buf[i]); /* * If we have a newline and we are * buffering bytes, flush the ring. */ if (buf[i] == '\n' && __TEST(tty->t_oflag, ORBUF)) { tty_flush(tty); } } /* * If we aren't buffering bytes, don't * keep the bytes within the ring and * flush it right away per `tty_write()` * call. */ if (!__TEST(tty->t_oflag, ORBUF)) { tty_flush(tty); } TTY_UNLOCK(tty); return len; } /* * Sets TTY fields to their defaults. */ void tty_set_defaults(struct tty *tty) { /* Ensure everything is initially zero */ memset(tty, 0, sizeof(*tty)); /* * Now, initialize everything to their defaults. * * Some notes about the default framebuffer device: * ------------------------------------------------ * The default framebuffer device should be the * front buffer. Later on during boot, all attached * TTYs shall have their fbdev swapped out with a * backbuffer to improve performace as reading directly * from video memory is going to be slow. * * XXX: At some point we should be allocating a backbuffer * instead when it's time for *all* TTYs to have them. * * A good idea would be to only allocate a backbuffer * *if* we switched to some TTY and deallocate * that backbuffer when switching away from that TTY. * * The first thing that comes to mind when thinking * about this idea is loosing our text when we switch * back out. To rectify this, we could buffer chars * which would take less memory than keeping the whole * backbuffer (holds pixels i.e uint32_t). * * This can perhaps be done by some internal flag which * indicates that kmalloc() is usable and chars can * be buffered. Once we switch back, just allocate * a new backbuffer and copy the chars back. */ tty->fbdev = fbdev_get_front(); tty->t_oflag = (OPOST | ORBUF); tty->tab_width = DEFAULT_TAB_WIDTH; tty->fg = 0x808080; tty->bg = 0x000000; tty->t_ws_xpixel = tty->fbdev.width; tty->t_ws_ypixel = tty->fbdev.height; tty->t_ws_row = tty->fbdev.height / FONT_HEIGHT; tty->t_ws_col = tty->fbdev.width / FONT_WIDTH; } void tty_attach(struct tty *tty) { TAILQ_INSERT_TAIL(&tty_list, tty, link); tty_draw_cursor(tty, false); } void tty_init(void) { TAILQ_INIT(&tty_list); /* Ensure the TTY list is usable */ }