#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
#include "console.h"
#include "spinlock.h"
#include "uart_console.h"
#include "hal/uart.h"
#include "hal/cpu.h"
#include "vga_console.h"
#include "keyboard.h"
static spinlock_t g_console_lock = {0};
static int g_console_uart_enabled = 1;
static int g_console_vga_enabled = 0;
/* ---- Kernel log ring buffer (like Linux __log_buf) ---- */
#define KLOG_BUF_SIZE 16384
static char klog_buf[KLOG_BUF_SIZE];
static size_t klog_head = 0; // next write position
static size_t klog_count = 0; // total bytes stored (capped at KLOG_BUF_SIZE)
static spinlock_t klog_lock = {0};
static volatile int klog_suppress_flag = 0;
static void klog_append(const char* s, size_t len) {
for (size_t i = 0; i < len; i++) {
klog_buf[klog_head] = s[i];
klog_head = (klog_head + 1) % KLOG_BUF_SIZE;
}
klog_count += len;
if (klog_count > KLOG_BUF_SIZE) {
klog_count = KLOG_BUF_SIZE;
}
}
void console_init(void) {
spinlock_init(&g_console_lock);
g_console_uart_enabled = 1;
/* If no UART hardware is present, auto-enable VGA so boot messages
* are visible instead of being silently dropped. */
if (!hal_uart_is_present()) {
g_console_vga_enabled = 1;
}
}
void console_enable_uart(int enabled) {
uintptr_t flags = spin_lock_irqsave(&g_console_lock);
g_console_uart_enabled = enabled ? 1 : 0;
spin_unlock_irqrestore(&g_console_lock, flags);
}
void console_enable_vga(int enabled) {
uintptr_t flags = spin_lock_irqsave(&g_console_lock);
g_console_vga_enabled = enabled ? 1 : 0;
spin_unlock_irqrestore(&g_console_lock, flags);
}
void console_write(const char* s) {
if (!s) return;
uintptr_t flags = spin_lock_irqsave(&g_console_lock);
if (g_console_uart_enabled) {
uart_print(s);
}
if (g_console_vga_enabled) {
vga_print(s);
}
spin_unlock_irqrestore(&g_console_lock, flags);
}
void console_write_buf(const char* buf, uint32_t len) {
if (!buf || len == 0) return;
uintptr_t flags = spin_lock_irqsave(&g_console_lock);
if (g_console_uart_enabled) {
for (uint32_t i = 0; i < len; i++) {
hal_uart_putc(buf[i]);
}
}
if (g_console_vga_enabled) {
vga_write_buf(buf, len);
}
spin_unlock_irqrestore(&g_console_lock, flags);
}
void console_put_char(char c) {
uintptr_t flags = spin_lock_irqsave(&g_console_lock);
if (g_console_uart_enabled) {
hal_uart_putc(c);
}
if (g_console_vga_enabled) {
vga_put_char(c);
}
spin_unlock_irqrestore(&g_console_lock, flags);
}
static void out_char(char** dst, size_t* rem, int* total, char c) {
(*total)++;
if (*rem == 0) return;
**dst = c;
(*dst)++;
(*rem)--;
}
static void out_str(char** dst, size_t* rem, int* total, const char* s) {
if (!s) s = "(null)";
for (size_t i = 0; s[i] != 0; i++) {
out_char(dst, rem, total, s[i]);
}
}
static void out_uint_base_u32(char** dst, size_t* rem, int* total, uint32_t v, unsigned base, int upper) {
char tmp[32];
size_t n = 0;
if (base < 2 || base > 16) base = 10;
if (v == 0) {
tmp[n++] = '0';
} else {
while (v != 0 && n < sizeof(tmp)) {
unsigned d = (unsigned)(v % (uint32_t)base);
if (d < 10) tmp[n++] = (char)('0' + d);
else tmp[n++] = (char)((upper ? 'A' : 'a') + (d - 10));
v /= (uint32_t)base;
}
}
while (n > 0) {
out_char(dst, rem, total, tmp[--n]);
}
}
static void out_uint_hex_fixed_u32(char** dst, size_t* rem, int* total, uint32_t v, int digits, int upper) {
const char* hex = upper ? "0123456789ABCDEF" : "0123456789abcdef";
for (int i = digits - 1; i >= 0; i--) {
unsigned nibble = (unsigned)((v >> (i * 4)) & 0xF);
out_char(dst, rem, total, hex[nibble]);
}
}
static void out_int_i32(char** dst, size_t* rem, int* total, int32_t v) {
if (v < 0) {
out_char(dst, rem, total, '-');
out_uint_base_u32(dst, rem, total, (uint32_t)(-v), 10, 0);
} else {
out_uint_base_u32(dst, rem, total, (uint32_t)v, 10, 0);
}
}
int kvsnprintf(char* out, size_t out_size, const char* fmt, va_list ap) {
if (!out || out_size == 0) return 0;
if (!fmt) {
out[0] = 0;
return 0;
}
char* dst = out;
size_t rem = out_size - 1;
int total = 0;
for (size_t i = 0; fmt[i] != 0; i++) {
char c = fmt[i];
if (c != '%') {
out_char(&dst, &rem, &total, c);
continue;
}
char spec = fmt[++i];
if (spec == 0) break;
switch (spec) {
case '%':
out_char(&dst, &rem, &total, '%');
break;
case 'c': {
int v = va_arg(ap, int);
out_char(&dst, &rem, &total, (char)v);
break;
}
case 's': {
const char* s = va_arg(ap, const char*);
out_str(&dst, &rem, &total, s);
break;
}
case 'd':
case 'i': {
int v = va_arg(ap, int);
out_int_i32(&dst, &rem, &total, (int32_t)v);
break;
}
case 'u': {
unsigned v = va_arg(ap, unsigned);
out_uint_base_u32(&dst, &rem, &total, (uint32_t)v, 10, 0);
break;
}
case 'x': {
unsigned v = va_arg(ap, unsigned);
out_uint_base_u32(&dst, &rem, &total, (uint32_t)v, 16, 0);
break;
}
case 'X': {
unsigned v = va_arg(ap, unsigned);
out_uint_base_u32(&dst, &rem, &total, (uint32_t)v, 16, 1);
break;
}
case 'p': {
uintptr_t v = (uintptr_t)va_arg(ap, void*);
out_str(&dst, &rem, &total, "0x");
out_uint_hex_fixed_u32(&dst, &rem, &total, (uint32_t)v, 8, 1);
break;
}
default:
out_char(&dst, &rem, &total, '%');
out_char(&dst, &rem, &total, spec);
break;
}
}
*dst = 0;
return total;
}
int ksnprintf(char* out, size_t out_size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int r = kvsnprintf(out, out_size, fmt, ap);
va_end(ap);
return r;
}
void kprintf(const char* fmt, ...) {
char buf[512];
va_list ap;
va_start(ap, fmt);
int len = kvsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (len > 0 && !klog_suppress_flag) {
size_t slen = (size_t)len;
if (slen >= sizeof(buf)) slen = sizeof(buf) - 1;
uintptr_t lf = spin_lock_irqsave(&klog_lock);
klog_append(buf, slen);
spin_unlock_irqrestore(&klog_lock, lf);
}
console_write(buf);
}
void klog_set_suppress(int suppress) {
klog_suppress_flag = suppress ? 1 : 0;
}
int kgetc(void) {
for (;;) {
char c = 0;
int rd = keyboard_read_nonblock(&c, 1);
if (rd > 0) return (int)(unsigned char)c;
int sc = hal_uart_try_getc();
if (sc >= 0) return sc;
hal_cpu_idle();
}
}
size_t klog_read(char* out, size_t out_size) {
if (!out || out_size == 0) return 0;
uintptr_t flags = spin_lock_irqsave(&klog_lock);
size_t avail = klog_count;
if (avail > out_size - 1) avail = out_size - 1;
size_t start;
if (klog_count >= KLOG_BUF_SIZE) {
start = klog_head; // oldest byte is right after head
} else {
start = (klog_head + KLOG_BUF_SIZE - klog_count) % KLOG_BUF_SIZE;
}
// skip oldest bytes if avail < klog_count
size_t skip = klog_count - avail;
start = (start + skip) % KLOG_BUF_SIZE;
for (size_t i = 0; i < avail; i++) {
out[i] = klog_buf[(start + i) % KLOG_BUF_SIZE];
}
out[avail] = '\0';
spin_unlock_irqrestore(&klog_lock, flags);
return avail;
}