Files
zinnia/dev/video/fbcon/fbcon.c
T
2026-10-13 23:54:38 +02:00

206 lines
5.4 KiB
C

#include <zinnia/alloc.h>
#include <zinnia/assert.h>
#include <zinnia/console.h>
#include <zinnia/init.h>
#include <zinnia/pmap.h>
#include <zinnia/sched.h>
#include <zinnia/utils.h>
#include <zinnia/vma.h>
#include <stdint.h>
#include <string.h>
static const uint8_t fbcon_font[] = {
#embed "fbcon_font.bin"
};
constexpr size_t FONT_WIDTH = 8;
constexpr size_t FONT_HEIGHT = 12;
constexpr uint32_t FG_COLOR = 0xaaaaaa;
constexpr uint32_t BG_COLOR = 0x000000;
enum esc_state {
ESC_NONE,
ESC_START,
ESC_CSI,
};
static struct {
struct boot_framebuffer fb;
uint32_t* back;
uint32_t fg, bg;
size_t cols, rows;
size_t offset_x, offset_y;
size_t cx, cy;
enum esc_state esc;
} fbcon;
static inline uint8_t* fb_ptr() {
return HHDM_PTR(fbcon.fb.base);
}
static inline uint32_t* back_row(size_t y) {
return fbcon.back + y * fbcon.fb.width;
}
static void flush_rect(size_t x, size_t y, size_t w, size_t h) {
for (size_t j = 0; j < h; j++)
memcpy((uint32_t*)(fb_ptr() + (y + j) * fbcon.fb.pitch) + x, back_row(y + j) + x, w * sizeof(uint32_t));
}
static uint32_t pack_color(uint32_t rgb) {
const struct boot_framebuffer* fb = &fbcon.fb;
uint32_t r = (rgb >> 16) & 0xff;
uint32_t g = (rgb >> 8) & 0xff;
uint32_t b = rgb & 0xff;
return ((r >> (8 - fb->red.size)) << fb->red.shift) | ((g >> (8 - fb->green.size)) << fb->green.shift) |
((b >> (8 - fb->blue.size)) << fb->blue.shift);
}
static void fill_rect(size_t x, size_t y, size_t w, size_t h, uint32_t color) {
for (size_t j = 0; j < h; j++) {
uint32_t* row = back_row(y + j) + x;
for (size_t i = 0; i < w; i++)
row[i] = color;
}
flush_rect(x, y, w, h);
}
static void clear_screen() {
fill_rect(0, 0, fbcon.fb.width, fbcon.fb.height, fbcon.bg);
fbcon.cx = fbcon.cy = 0;
}
static void draw_glyph(size_t col, size_t row, uint8_t ch) {
const uint8_t* glyph = &fbcon_font[ch * FONT_HEIGHT];
size_t x = fbcon.offset_x + col * FONT_WIDTH;
size_t y = fbcon.offset_y + row * FONT_HEIGHT;
for (size_t j = 0; j < FONT_HEIGHT; j++) {
uint32_t* line = back_row(y + j) + x;
for (size_t i = 0; i < FONT_WIDTH; i++)
line[i] = (glyph[j] & (0x80 >> i)) ? fbcon.fg : fbcon.bg;
}
flush_rect(x, y, FONT_WIDTH, FONT_HEIGHT);
}
static void scroll_up() {
const size_t width = fbcon.fb.width;
const size_t text_y = fbcon.offset_y;
const size_t text_h = fbcon.rows * FONT_HEIGHT;
memmove(back_row(text_y), back_row(text_y + FONT_HEIGHT), (text_h - FONT_HEIGHT) * width * sizeof(uint32_t));
for (size_t j = text_y + text_h - FONT_HEIGHT; j < text_y + text_h; j++) {
uint32_t* row = back_row(j);
for (size_t i = 0; i < width; i++)
row[i] = fbcon.bg;
}
flush_rect(0, text_y, width, text_h);
}
static void newline() {
fbcon.cx = 0;
if (++fbcon.cy >= fbcon.rows) {
fbcon.cy = fbcon.rows - 1;
scroll_up();
}
}
static void put_char(uint8_t ch) {
switch (fbcon.esc) {
case ESC_START:
fbcon.esc = ch == '[' ? ESC_CSI : ESC_NONE;
if (ch == 'c')
clear_screen();
return;
case ESC_CSI:
if (ch >= 0x40 && ch <= 0x7e)
fbcon.esc = ESC_NONE;
return;
case ESC_NONE:
break;
}
switch (ch) {
case 0x1b:
fbcon.esc = ESC_START;
break;
case '\n':
newline();
break;
case '\r':
fbcon.cx = 0;
break;
case '\b':
if (fbcon.cx > 0)
fbcon.cx--;
break;
case '\t':
fbcon.cx = (fbcon.cx + 8) & ~(size_t)7;
if (fbcon.cx >= fbcon.cols)
newline();
break;
default:
if (fbcon.cx >= fbcon.cols)
newline();
draw_glyph(fbcon.cx++, fbcon.cy, ch);
break;
}
}
static void fbcon_write(struct console* con, const char* buf, size_t count) {
for (size_t i = 0; i < count; i++)
put_char((uint8_t)buf[i]);
}
static bool fbcon_setup(struct console* con) {
const struct boot_framebuffer* fb = &fbcon.fb;
if (fb->bytes_per_pixel != 4 || fb->red.size > 8 || fb->green.size > 8 || fb->blue.size > 8)
return false;
fbcon.fg = pack_color(FG_COLOR);
fbcon.bg = pack_color(BG_COLOR);
fbcon.cols = fb->width / FONT_WIDTH;
fbcon.rows = fb->height / FONT_HEIGHT;
fbcon.offset_x = (fb->width % FONT_WIDTH) / 2;
fbcon.offset_y = (fb->height % FONT_HEIGHT) / 2;
if (!fbcon.cols || !fbcon.rows)
return false;
clear_screen();
return true;
}
static struct console fbcon_con = {
.name = "fbcon",
.init = fbcon_setup,
.write = fbcon_write,
};
[[__init]]
void fbcon_init(struct boot_info* info) {
const struct boot_framebuffer* fb = &info->fb;
if (!fb->base)
return;
fbcon.fb = *fb;
const size_t pgsz = arch_mem_page_size();
const phys_t start = ALIGN_DOWN(fb->base, pgsz);
const phys_t end = ALIGN_UP(fb->base + fb->pitch * fb->height, pgsz);
for (phys_t p = start; p < end; p += pgsz) {
errno_t status =
pmap_map(&kernel_vma.pmap, (uintptr_t)HHDM_PTR(p), p, PTE_READ | PTE_WRITE, CACHE_WRITE_COMBINE);
if (status)
return;
}
fbcon.back = mem_alloc(fb->width * fb->height * sizeof(uint32_t), ALLOC_NOZERO);
if (!fbcon.back)
return;
console_register(&fbcon_con);
}
INIT_TASK(fbcon_stage, "core.fbcon", fbcon_init);
INIT_DEPENDS(fbcon_stage, sched_stage);