Files
LithosAnanake/src/starkernel/hal/console.c
T
Robert Allan JamesandClaude Sonnet 5 eefe765f00 starkernel: item 4.4e -- fix console_putc() forwarding \n to vt100_putc() without \r
console_putc() mirrored raw '\n' to vt100_putc() while raw_putc() (serial
path) auto-injects '\r' before '\n'. vt100_putc()'s '\n' handler only
increments the row, never resets the column, so every line after the first
started at whatever column the previous line ended on instead of column 0 --
confirmed by screendump as diagonal text scatter across the framebuffer.
Latent since this code was written; invisible until 4.4c turned the
framebuffer console on for the first time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 08:35:48 -04:00

307 lines
8.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* console.c - Serial console implementation (UART 16550)
* Supports amd64 via I/O ports
*/
#include "console.h"
#include "framebuffer.h"
#include "vt100.h"
#include "arch.h"
#if defined(__x86_64__) || defined(__i386__)
#define SERIAL_SUPPORTED 1
/* UART 16550 I/O ports (COM1) */
#define SERIAL_PORT_BASE 0x3F8
#define SERIAL_DATA_PORT (SERIAL_PORT_BASE + 0)
#define SERIAL_INT_ENABLE_PORT (SERIAL_PORT_BASE + 1)
#define SERIAL_FIFO_CTRL_PORT (SERIAL_PORT_BASE + 2)
#define SERIAL_LINE_CTRL_PORT (SERIAL_PORT_BASE + 3)
#define SERIAL_MODEM_CTRL_PORT (SERIAL_PORT_BASE + 4)
#define SERIAL_LINE_STATUS_PORT (SERIAL_PORT_BASE + 5)
/* Line Status Register bits */
#define SERIAL_LSR_DATA_READY (1 << 0)
#define SERIAL_LSR_THR_EMPTY (1 << 5)
static inline void outb(uint16_t port, uint8_t val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
#elif defined(__aarch64__)
#define SERIAL_SUPPORTED 1
/* PL011 UART — QEMU virt machine UART0 at 0x09000000 */
#define PL011_BASE ((volatile unsigned int *)0x09000000UL)
#define PL011_DR (PL011_BASE + 0) /* Data Register (offset 0x000) */
#define PL011_FR (PL011_BASE + 6) /* Flag Register (offset 0x018) */
#define PL011_FR_TXFF (1u << 5) /* TX FIFO full */
static inline void pl011_putc(char c)
{
while (*PL011_FR & PL011_FR_TXFF) { }
*PL011_DR = (unsigned int)(unsigned char)c;
}
#elif defined(__riscv)
#define SERIAL_SUPPORTED 1
/* NS16550 UART — QEMU virt machine UART0 at 0x10000000 (MMIO, byte-wide) */
#define NS16550_BASE ((volatile unsigned char *)0x10000000UL)
#define NS16550_THR (NS16550_BASE + 0) /* Transmit Holding Register */
#define NS16550_LSR (NS16550_BASE + 5) /* Line Status Register */
#define NS16550_LSR_THRE (1u << 5) /* Transmit Holding Register Empty */
#define NS16550_LSR_DR (1u << 0) /* Data Ready */
static inline void ns16550_putc(char c)
{
while (!(*NS16550_LSR & NS16550_LSR_THRE)) { }
*NS16550_THR = (unsigned char)c;
}
#else
#define SERIAL_SUPPORTED 0
/* Stubs for other architectures */
static inline void outb(uint16_t port, uint8_t val) {
(void)port;
(void)val;
}
static inline uint8_t inb(uint16_t port) {
(void)port;
return 0;
}
#endif
/**
* Initialize serial console
* x86: programs 16550 UART.
* aarch64: PL011 is already initialized by UEFI firmware on QEMU virt.
*/
void console_init(void) {
#if defined(__x86_64__) || defined(__i386__)
outb(SERIAL_INT_ENABLE_PORT, 0x00);
outb(SERIAL_LINE_CTRL_PORT, 0x80);
outb(SERIAL_DATA_PORT, 0x01);
outb(SERIAL_INT_ENABLE_PORT, 0x00);
outb(SERIAL_LINE_CTRL_PORT, 0x03);
outb(SERIAL_FIFO_CTRL_PORT, 0xC7);
outb(SERIAL_MODEM_CTRL_PORT, 0x0B);
outb(SERIAL_MODEM_CTRL_PORT, 0x1E);
outb(SERIAL_DATA_PORT, 0xAE);
if (inb(SERIAL_DATA_PORT) != 0xAE) {
return;
}
outb(SERIAL_MODEM_CTRL_PORT, 0x0F);
#elif defined(__aarch64__)
/* PL011 already enabled by AAVMF; nothing to do */
(void)0;
#elif defined(__riscv)
/* NS16550 already enabled by UEFI firmware on QEMU virt; nothing to do */
(void)0;
#else
(void)SERIAL_SUPPORTED;
#endif
}
#if defined(__x86_64__) || defined(__i386__)
static int serial_transmit_empty(void) {
return inb(SERIAL_LINE_STATUS_PORT) & SERIAL_LSR_THR_EMPTY;
}
#endif
/* Active VM name for [Name] line prefix; NULL = no prefix */
static const char *g_active_vm_name = (void *)0;
static int g_line_start = 1;
void console_set_vm_name(const char *name) {
g_active_vm_name = name;
}
const char *console_get_vm_name(void) {
return g_active_vm_name;
}
/* Raw single-character write — no prefix logic, called by emit_prefix() */
static void raw_putc(char c) {
#if defined(__aarch64__)
if (c == '\n') pl011_putc('\r');
pl011_putc(c);
#elif defined(__riscv)
if (c == '\n') ns16550_putc('\r');
ns16550_putc(c);
#elif defined(__x86_64__) || defined(__i386__)
if (c == '\n') {
while (!serial_transmit_empty()) { arch_relax(); }
outb(SERIAL_DATA_PORT, '\r');
}
while (!serial_transmit_empty()) { arch_relax(); }
outb(SERIAL_DATA_PORT, (uint8_t)c);
#else
(void)c;
#endif
}
/* Emit "[VMName] " to both serial (raw_putc) and, when available, the
* framebuffer (vt100_putc) -- mirrors console_putc()'s own serial/framebuffer
* split so the prefix reaches both outputs, not serial only. No recursion
* into console_putc itself (would re-trigger the line-start prefix check). */
static void emit_prefix(void) {
const char *p;
int fb = fb_is_available();
raw_putc('[');
if (fb) vt100_putc('[');
for (p = g_active_vm_name; *p; p++) {
raw_putc(*p);
if (fb) vt100_putc(*p);
}
raw_putc(']');
if (fb) vt100_putc(']');
raw_putc(' ');
if (fb) vt100_putc(' ');
}
/**
* Write a single character to serial console.
* Emits "[VMName] " at the start of each new line when a VM name is set.
* Also mirrors output to the framebuffer VT100 terminal when available.
* Serial output is ALWAYS active regardless of framebuffer state.
*/
void console_putc(char c) {
/* --- serial UART path (always on) --- */
if (g_active_vm_name && g_line_start && c != '\n') {
emit_prefix();
g_line_start = 0;
}
raw_putc(c);
if (c == '\n') {
g_line_start = 1;
}
/* --- framebuffer VT100 path (when available) --- */
if (fb_is_available()) {
if (c == '\n') vt100_putc('\r');
vt100_putc(c);
}
}
/**
* Write a null-terminated string to serial console
*/
void console_puts(const char *s) {
if (!s) return;
while (*s) {
console_putc(*s++);
}
}
/**
* Write a string with newline to serial console
*/
void console_println(const char *s) {
console_puts(s);
console_putc('\n');
}
/**
* Check if data is available to read
*/
int console_poll(void) {
#if defined(__x86_64__) || defined(__i386__)
return inb(SERIAL_LINE_STATUS_PORT) & SERIAL_LSR_DATA_READY;
#elif defined(__aarch64__)
/* PL011_FR bit 4 = RXFE (RX FIFO empty); data available when RXFE == 0 */
#define PL011_FR_RXFE (1u << 4)
return !(*PL011_FR & PL011_FR_RXFE);
#elif defined(__riscv)
return *NS16550_LSR & NS16550_LSR_DR;
#else
return 0;
#endif
}
/**
* Initialize the framebuffer VT100 terminal and attach it to the console.
* Safe to call with info == NULL or when the framebuffer base is zero (no-op).
* After this call every console_putc / console_puts output is mirrored to
* the screen; the serial UART continues to function concurrently.
*/
void console_fb_init(const FramebufferInfo *info, FbPixelFormat fmt)
{
if (!info || !info->base) return;
fb_init(info, fmt);
if (fb_is_available()) {
vt100_init();
}
}
/**
* Read a single character from serial console (non-blocking)
* Returns -1 if no character available
*/
int console_getc(void) {
#if defined(__x86_64__) || defined(__i386__)
if (!console_poll()) return -1;
return (int)(unsigned char)inb(SERIAL_DATA_PORT);
#elif defined(__aarch64__)
if (!console_poll()) return -1;
return (int)(*PL011_DR & 0xFF);
#elif defined(__riscv)
if (!console_poll()) return -1;
return (int)(*NS16550_THR & 0xFF);
#else
return -1;
#endif
}