Add com(4) terminal and console support.

Implement com(4) interrupt support for receiving data.

Add termios terminal support to the com(4) driver with tcsetattr(2)
support for configuring the hardware serial settings.

Add kernel(7) --console option which can be used to initialize the
com(4) driver early for the kernel console with the specified serial
settings and window size.

Add kernel(7) --text option for overriding the TERM environment variable.

Add kernel(7) --disable-logo option for disabling writing the the
operating system logo to the console on boot.

Add advanced bootloader option to select booting to a serial console on
com1. Add bootloader variables and hooks to customize this behavior.

Add tix-iso-bootconfig(8) --console, --grub-serial, --serial, and
--serial-console options as a convenince to opt into booting to the
serial console by default.

Add tix-iso-bootconfig(8) --kernel-options option for forwarding
additional options to the kernel.

The logterminal driver used for tty1 currently assumes that it controls
the kernel log, which is not true when com(4) is used as the console,
therefore the tty1 driver is currently disabled if a serial console is
selected, as a temporary workaround.
This commit is contained in:
Jonas 'Sortie' Termansen 2023-07-05 22:53:12 +02:00
parent 47c55f31c2
commit e57c84c5e6
18 changed files with 896 additions and 147 deletions

View file

@ -153,6 +153,16 @@ echo "require sshd optional" > boot/grub/init/local-sshd
exec > boot/grub/grub.cfg
cat << EOF
function hook_initialize_terminal {
insmod all_video
if loadfont unicode; then
insmod gfxterm
terminal_output gfxterm
fi
}
EOF
for hook in \
advanced_menu_post \
advanced_menu_pre \
@ -198,12 +208,6 @@ find . | grep -Eq '\.gz$' && echo "insmod gzio"
find . | grep -Eq '\.xz$' && echo "insmod xzio"
cat << EOF
insmod all_video
if loadfont unicode; then
insmod gfxterm
terminal_output gfxterm
fi
set version="$version"
set machine="$machine"
set base_menu_title="Sortix \$version for \$machine"
@ -213,6 +217,7 @@ set title_sysinstall='new installation'
set title_sysupgrade='upgrade existing installation'
set timeout=10
set default="0"
set console=
if [ -e /boot/random.seed ]; then
no_random_seed=
else
@ -224,6 +229,8 @@ set enable_dhclient=true
set enable_gui=true
set enable_ntpd=false
set enable_sshd=false
set kernel_options=
set serial_console=--console=com1
export version
export machine
@ -234,6 +241,7 @@ export title_sysinstall
export title_sysupgrade
export timeout
export default
export console
export no_random_seed
export enable_src
export enable_network_drivers
@ -241,6 +249,8 @@ export enable_dhclient
export enable_gui
export enable_ntpd
export enable_sshd
export kernel_options
export serial_console
EOF
if [ -n "$ports" ]; then
@ -327,7 +337,7 @@ esac
cat << EOF
hook_kernel_pre
echo -n "Loading /$kernel ($(human_size $kernel)) ... "
multiboot /$kernel --firmware=\$grub_platform \$no_random_seed \$enable_network_drivers "\$@"
multiboot /$kernel \$console --firmware=\$grub_platform \$no_random_seed \$enable_network_drivers \$kernel_options "\$@"
echo done
hook_kernel_post
if ! \$enable_dhclient; then
@ -418,6 +428,8 @@ if [ -e /boot/grub/hooks.cfg ]; then
. /boot/grub/hooks.cfg
fi
hook_initialize_terminal
. /boot/grub/main.cfg
EOF
@ -484,6 +496,19 @@ else
}
fi
if [ "\$console" = "\$serial_console" ]; then
menuentry "Disable serial console" {
console=
configfile /boot/grub/advanced.cfg
}
else
menuentry "Enable serial console" {
console="$serial_console"
enable_gui=false
configfile /boot/grub/advanced.cfg
}
fi
if "\$enable_src"; then
menuentry "Disable loading source code" {
enable_src=false

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2012, 2014-2016, 2023-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -17,10 +17,16 @@
* Handles communication to COM serial ports.
*/
#include <sys/ioctl.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <sortix/fcntl.h>
#include <sortix/stat.h>
@ -38,6 +44,7 @@
#include <sortix/kernel/thread.h>
#include "com.h"
#include "tty.h"
extern "C" unsigned char nullpage[4096];
@ -90,6 +97,10 @@ static const uint8_t IER_LOW_POWER = 1 << 5;
static const unsigned BASE_BAUD = 1843200 / 16;
static const speed_t DEFAULT_SPEED = B38400;
static const size_t DEFAULT_COLUMNS = 80;
static const size_t DEFAULT_ROWS = 25;
static const unsigned int UART_8250 = 1;
static const unsigned int UART_16450 = 2;
static const unsigned int UART_16550 = 3;
@ -136,17 +147,78 @@ static inline bool CanWriteByte(uint16_t port)
return inport8(port + LSR) & LSR_THRE;
}
class DevCOMPort : public AbstractInode
static bool IsValidSpeed(speed_t speed)
{
return speed && speed <= 115200 && !(115200 % speed);
}
static void ConfigurePort(uint16_t port, const struct termios* tio,
bool enable_interrupts)
{
uint16_t divisor = 115200 / tio->c_ispeed;
outport8(port + FCR, 0);
outport8(port + LCR, LCR_DLAB);
outport8(port + DLL, divisor & 0xFF);
outport8(port + DLM, divisor >> 8);
uint8_t lcr = 0;
if ( (tio->c_cflag & CSIZE) == CS5 )
lcr |= LCR_WLEN5;
else if ( (tio->c_cflag & CSIZE) == CS6 )
lcr |= LCR_WLEN6;
else if ( (tio->c_cflag & CSIZE) == CS7 )
lcr |= LCR_WLEN7;
else if ( (tio->c_cflag & CSIZE) == CS8 )
lcr |= LCR_WLEN8;
if ( tio->c_cflag & CSTOPB )
lcr |= LCR_STOP;
if ( tio->c_cflag & PARENB )
lcr |= LCR_PARITY;
if ( tio->c_cflag & PARENB )
{
lcr |= LCR_PARITY;
if ( !(tio->c_cflag & PARODD) )
lcr |= LCR_EPAR;
}
outport8(port + LCR, lcr);
uint8_t mcr = 0x2 /* RTS */;
if ( tio->c_cflag & CREAD )
mcr |= 0x1 /* DTR */;
outport8(port + MCR, mcr);
uint8_t ier = enable_interrupts ? 1 : 0;
outport8(port + IER, ier);
}
class DevCOMPort : public TTY
{
public:
DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, uint16_t port);
DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, uint16_t port,
const char* name);
virtual ~DevCOMPort();
virtual int ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
virtual int sync(ioctx_t* ctx);
virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count);
virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count);
virtual void tty_output(const unsigned char* buffer, size_t length);
virtual bool Reconfigure(const struct termios* new_tio);
public:
void ImportConsole(const struct termios* console_tio,
const struct winsize* console_size);
bool Initialize(int interrupt);
bool EmergencyIsImpaired();
bool EmergencyRecoup();
void EmergencyReset();
private:
static void InterruptHandler(struct interrupt_context*, void*);
static void InterruptWorkHandler(void* context);
void OnInterrupt();
void InterruptWork();
private:
kthread_mutex_t port_lock;
kthread_mutex_t reconfigure_lock;
struct interrupt_handler irq_registration;
struct interrupt_work interrupt_work;
struct winsize ws;
uint16_t port;
uint8_t pending_input_byte;
bool has_pending_input_byte;
@ -154,24 +226,98 @@ private:
};
DevCOMPort::DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode,
uint16_t port)
uint16_t port, const char* name) : TTY(dev, 0, mode,
owner, group, name)
{
inode_type = INODE_TYPE_STREAM;
this->port = port;
this->port_lock = KTHREAD_MUTEX_INITIALIZER;
this->stat_uid = owner;
this->stat_gid = group;
this->type = S_IFCHR;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->dev = dev;
this->ino = (ino_t) this;
this->reconfigure_lock = KTHREAD_MUTEX_INITIALIZER;
this->has_pending_input_byte = false;
tio.c_ispeed = DEFAULT_SPEED;
tio.c_ospeed = DEFAULT_SPEED;
memset(&ws, 0, sizeof(ws));
ws.ws_col = DEFAULT_COLUMNS;
ws.ws_row = DEFAULT_ROWS;
interrupt_work.handler = InterruptWorkHandler;
interrupt_work.context = this;
}
DevCOMPort::~DevCOMPort()
{
}
void DevCOMPort::ImportConsole(const struct termios* console_tio,
const struct winsize* console_size)
{
tio.c_cflag = console_tio->c_cflag;
tio.c_ispeed = console_tio->c_ispeed;
tio.c_ospeed = console_tio->c_ospeed;
ws = *console_size;
}
bool DevCOMPort::Initialize(int interrupt)
{
ConfigurePort(port, &tio, true);
irq_registration.handler = DevCOMPort::InterruptHandler;
irq_registration.context = this;
Interrupt::RegisterHandler(interrupt, &irq_registration);
return true;
}
void DevCOMPort::InterruptHandler(struct interrupt_context*, void* user)
{
((DevCOMPort*) user)->OnInterrupt();
}
void DevCOMPort::OnInterrupt()
{
if ( !IsLineReady(port) )
return;
Interrupt::ScheduleWork(&interrupt_work);
}
void DevCOMPort::InterruptWorkHandler(void* context)
{
((DevCOMPort*) context)->InterruptWork();
}
void DevCOMPort::InterruptWork()
{
ScopedLock lock1(&termlock);
ScopedLock lock2(&port_lock);
while ( IsLineReady(port) )
{
unsigned char byte = inport8(port + RXR);
if ( tio.c_cflag & CREAD )
ProcessByte(byte);
}
}
int DevCOMPort::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( cmd == TIOCGWINSZ )
{
struct winsize* user_ws = (struct winsize*) arg;
if ( !ctx->copy_to_dest(user_ws, &ws, sizeof(ws)) )
return -1;
return 0;
}
else if ( cmd == TIOCSWINSZ )
{
const struct winsize* user_ws = (const struct winsize*) arg;
if ( !ctx->copy_from_src(&ws, user_ws, sizeof(ws)) )
return -1;
winch();
return 0;
}
lock.Reset();
return TTY::ioctl(ctx, cmd, arg);
}
int DevCOMPort::sync(ioctx_t* /*ctx*/)
{
ScopedLock lock(&port_lock);
@ -179,57 +325,9 @@ int DevCOMPort::sync(ioctx_t* /*ctx*/)
return 0;
}
ssize_t DevCOMPort::read(ioctx_t* ctx, uint8_t* dest, size_t count)
void DevCOMPort::tty_output(const unsigned char* buffer, size_t length)
{
ScopedLock lock(&port_lock);
for ( size_t i = 0; i < count; i++ )
{
unsigned long attempt = 0;
while ( !has_pending_input_byte && !IsLineReady(port) )
{
attempt++;
if ( attempt <= 10 )
continue;
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
{
kthread_mutex_unlock(&port_lock);
kthread_yield();
kthread_mutex_lock(&port_lock);
continue;
}
if ( i )
return (ssize_t) i;
if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1;
if ( Signal::IsPending() )
return errno = EINTR, -1;
kthread_mutex_unlock(&port_lock);
kthread_yield();
kthread_mutex_lock(&port_lock);
}
uint8_t value = has_pending_input_byte ?
pending_input_byte :
inport8(port + RXR);
if ( !ctx->copy_to_dest(dest + i, &value, sizeof(value)) )
{
has_pending_input_byte = true;
pending_input_byte = value;
return i ? (ssize_t) i : -1;
}
has_pending_input_byte = false;
}
return (ssize_t) count;
}
ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
{
ScopedLock lock(&port_lock);
for ( size_t i = 0; i < count; i++ )
for ( size_t i = 0; i < length; i++ )
{
unsigned long attempt = 0;
while ( !CanWriteByte(port) )
@ -237,7 +335,7 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
attempt++;
if ( attempt <= 10 )
continue;
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
if ( attempt <= 15 )
{
kthread_mutex_unlock(&port_lock);
kthread_yield();
@ -245,24 +343,299 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
continue;
}
if ( i )
return (ssize_t) i;
if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1;
return;
// TODO: This is problematic.
if ( Signal::IsPending() )
return errno = EINTR, -1;
{
errno = EINTR;
return;
}
}
uint8_t value;
if ( !ctx->copy_from_src(&value, src + i, sizeof(value)) )
return i ? (ssize_t) i : -1;
outport8(port + TXR, value);
outport8(port + TXR, buffer[i]);
}
return (ssize_t) count;
}
bool DevCOMPort::Reconfigure(const struct termios* new_tio) // termlock held
{
if ( !IsValidSpeed(new_tio->c_ispeed) || !IsValidSpeed(new_tio->c_ospeed) )
return errno = EINVAL, false;
if ( new_tio->c_ispeed != new_tio->c_ospeed )
return errno = EINVAL, false;
if ( tio.c_ispeed != new_tio->c_ispeed ||
tio.c_ospeed != new_tio->c_ospeed ||
tio.c_cflag != new_tio->c_cflag )
{
// Detect if a panic happens midway.
ScopedLock lock(&reconfigure_lock);
ConfigurePort(port, new_tio, true);
}
return true;
}
bool DevCOMPort::EmergencyIsImpaired()
{
if ( !kthread_mutex_trylock(&termlock) )
return true;
kthread_mutex_unlock(&termlock);
if ( !kthread_mutex_trylock(&port_lock) )
return true;
kthread_mutex_unlock(&port_lock);
if ( !kthread_mutex_trylock(&reconfigure_lock) )
return true;
kthread_mutex_unlock(&reconfigure_lock);
return false;
}
bool DevCOMPort::EmergencyRecoup()
{
kthread_mutex_trylock(&termlock);
kthread_mutex_unlock(&termlock);
kthread_mutex_trylock(&port_lock);
kthread_mutex_unlock(&port_lock);
kthread_mutex_trylock(&reconfigure_lock);
kthread_mutex_unlock(&reconfigure_lock);
if ( !kthread_mutex_trylock(&reconfigure_lock) )
return false;
kthread_mutex_unlock(&reconfigure_lock);
return true;
}
void DevCOMPort::EmergencyReset()
{
kthread_mutex_trylock(&termlock);
kthread_mutex_unlock(&termlock);
kthread_mutex_trylock(&port_lock);
kthread_mutex_unlock(&port_lock);
kthread_mutex_trylock(&reconfigure_lock);
kthread_mutex_unlock(&reconfigure_lock);
ConfigurePort(port, &tio, false);
}
static size_t console_device;
static struct termios console_tio;
static uint16_t console_port;
static kthread_mutex_t console_lock = KTHREAD_MUTEX_INITIALIZER;
static bool console_imported;
static struct winsize console_size;
static Ref<DevCOMPort> com_devices[1 + NUM_COM_PORTS];
static void ConsoleWriteByte(unsigned char byte)
{
size_t attempt = 0;
while ( !CanWriteByte(console_port) )
{
attempt++;
if ( attempt <= 10 )
continue;
if ( attempt <= 15 )
{
kthread_mutex_unlock(&console_lock);
kthread_yield();
kthread_mutex_lock(&console_lock);
continue;
}
}
outport8(console_port + TXR, byte);
}
static size_t ConsoleWrite(void* /*ctx*/, const char* buf, size_t len)
{
ScopedLock lock(&console_lock);
if ( console_imported )
{
ioctx_t ctx; SetupKernelIOCtx(&ctx);
Ref<DevCOMPort> com = com_devices[console_device];
const uint8_t* buffer = (const uint8_t*) buf;
size_t done = 0;
while ( done < len )
{
ssize_t amount = com->write(&ctx, buffer + done, len - done);
if ( amount < 0 )
break; // TODO: Block all signals.
done += amount;
}
return done;
}
for ( size_t i = 0; i < len; i++ )
{
if ( buf[i] == '\n' )
ConsoleWriteByte('\r');
ConsoleWriteByte(buf[i]);
}
return len;
}
static size_t ConsoleWidth(void* /*ctx*/)
{
if ( console_imported )
{
ioctx_t ctx; SetupKernelIOCtx(&ctx);
Ref<DevCOMPort> com = com_devices[console_device];
struct winsize ws;
com->ioctl(&ctx, TIOCGWINSZ, (uintptr_t) &ws);
return ws.ws_col;
}
return console_size.ws_col;
}
static size_t ConsoleHeight(void* /*ctx*/)
{
if ( console_imported )
{
ioctx_t ctx; SetupKernelIOCtx(&ctx);
Ref<DevCOMPort> com = com_devices[console_device];
struct winsize ws;
com->ioctl(&ctx, TIOCGWINSZ, (uintptr_t) &ws);
return ws.ws_row;
}
return console_size.ws_row;
}
static void ConsoleGetCursor(void* /*ctx*/, size_t* column, size_t* row)
{
// TODO: Conceptually this does not make sense.
*column = 0;
*row = 0;
}
static bool ConsoleSync(void* /*ctx*/)
{
ScopedLock lock(&console_lock);
return true;
}
static void ConsoleInvalidate(void* /*ctx*/)
{
ScopedLock lock(&console_lock);
}
bool ConsoleEmergencyIsImpaired(void* /*ctx*/)
{
if ( !kthread_mutex_trylock(&console_lock) )
return true;
kthread_mutex_unlock(&console_lock);
if ( console_imported )
{
Ref<DevCOMPort> com = com_devices[console_device];
return com->EmergencyIsImpaired();
}
return false;
}
bool ConsoleEmergencyRecoup(void* /*ctx*/)
{
kthread_mutex_trylock(&console_lock);
kthread_mutex_unlock(&console_lock);
if ( console_imported )
{
Ref<DevCOMPort> com = com_devices[console_device];
return com->EmergencyRecoup();
}
return true;
}
void ConsoleEmergencyReset(void* /*ctx*/)
{
kthread_mutex_trylock(&console_lock);
kthread_mutex_unlock(&console_lock);
if ( console_imported )
{
Ref<DevCOMPort> com = com_devices[console_device];
com->EmergencyReset();
}
}
void InitializeConsole(const char* console)
{
assert(!strncmp(console, "com", 3));
assert(isdigit((unsigned char) console[3]));
char* end;
unsigned long device = strtoul(console + 3, &end, 10);
if ( device < 1 || NUM_COM_PORTS < device )
PanicF("Invalid console: %s", console);
console_device = device;
struct termios tio;
memset(&tio, 0, sizeof(tio));
console_tio.c_ispeed = DEFAULT_SPEED;
console_tio.c_cflag = CS8;
memset(&console_size, 0, sizeof(console_size));
console_size.ws_col = DEFAULT_COLUMNS;
console_size.ws_row = DEFAULT_ROWS;
if ( *end == ',' )
{
end++;
if ( *end != ',' )
{
unsigned long value = strtoul(end, &end, 10);
if ( !IsValidSpeed(value) )
PanicF("Invalid console options: %s", console);
console_tio.c_ispeed = value;
console_tio.c_cflag = 0;
if ( *end == 'o' )
console_tio.c_cflag |= PARENB | PARODD;
else if ( *end == 'e' )
console_tio.c_cflag |= PARENB;
else if ( *end != 'n' )
PanicF("Invalid console options: %s", console);
end++;
if ( *end == '5' )
console_tio.c_cflag |= CS5;
else if ( *end == '6' )
console_tio.c_cflag |= CS6;
else if ( *end == '7' )
console_tio.c_cflag |= CS7;
else if ( *end == '8' )
console_tio.c_cflag |= CS8;
else
PanicF("Invalid console options: %s", console);
end++;
}
if ( *end == ',' )
{
end++;
unsigned long width = strtoul(end, &end, 10);
if ( !width || *end != 'x' )
PanicF("Invalid console options: %s", console);
end++;
unsigned long height = strtoul(end, &end, 10);
if ( !height || *end )
PanicF("Invalid console options: %s", console);
console_size.ws_col = width;
console_size.ws_row = height;
}
}
else if ( *end )
PanicF("Invalid console: %s", console);
console_tio.c_ospeed = console_tio.c_ispeed;
const uint16_t* bioscom_ports = (const uint16_t*) (nullpage + 0x400);
if ( !(console_port = bioscom_ports[device - 1]) )
PanicF("No such hardware device detected: %s", console);
outport8(console_port + IER, 0x0);
ConfigurePort(console_port, &console_tio, false);
Log::fallback_framebuffer = NULL;
Log::device_callback = ConsoleWrite;
Log::device_writeraw = ConsoleWrite;
Log::device_width = ConsoleWidth;
Log::device_height = ConsoleHeight;
Log::device_get_cursor = ConsoleGetCursor;
Log::device_sync = ConsoleSync;
Log::device_invalidate = ConsoleInvalidate;
Log::emergency_device_is_impaired = ConsoleEmergencyIsImpaired;
Log::emergency_device_recoup = ConsoleEmergencyRecoup;
Log::emergency_device_reset = ConsoleEmergencyReset;
Log::emergency_device_callback = ConsoleWrite;
Log::emergency_device_writeraw = ConsoleWrite;
Log::emergency_device_width = ConsoleWidth;
Log::emergency_device_height = ConsoleHeight;
Log::emergency_device_get_cursor = ConsoleGetCursor;
Log::emergency_device_sync = ConsoleSync;
snprintf(Log::console_tty, sizeof(Log::console_tty), "/dev/com%lu", device);
}
void Init(const char* devpath, Ref<Descriptor> slashdev)
{
uint16_t com_ports[1 + NUM_COM_PORTS];
@ -282,21 +655,6 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
ioctx_t ctx; SetupKernelIOCtx(&ctx);
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
{
uint16_t port = com_ports[i];
if ( !port )
continue;
uint8_t interrupts = 0;
outport8(port + FCR, 0);
outport8(port + LCR, 0x80);
outport8(port + DLL, 0xC);
outport8(port + DLM, 0x0);
outport8(port + LCR, 0x3); // 8n1
outport8(port + MCR, 0x3); // DTR + RTS
outport8(port + IER, interrupts);
}
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
{
if ( !com_ports[i] )
@ -304,13 +662,27 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
com_devices[i] = Ref<DevCOMPort>();
continue;
}
com_devices[i] = Ref<DevCOMPort>(new DevCOMPort(slashdev->dev, 0, 0, 0660, com_ports[i]));
if ( !com_devices[i] )
PanicF("Unable to allocate device for COM port %zu", i);
char name[3 + sizeof(size_t) * 3];
snprintf(name, sizeof(name), "com%zu", i);
if ( LinkInodeInDir(&ctx, slashdev, name, com_devices[i]) != 0 )
PanicF("Unable to link %s/%s to COM port driver.", devpath, name);
char ttyname[TTY_NAME_MAX+1];
snprintf(ttyname, sizeof(ttyname), "com%zu", i);
Ref<DevCOMPort> com(
new DevCOMPort(slashdev->dev, 0, 0, 0660, com_ports[i], ttyname));
if ( !com )
PanicF("Unable to allocate device for %s", ttyname);
com_devices[i] = com;
if ( i == console_device )
{
ScopedLock lock(&console_lock);
com->ImportConsole(&console_tio, &console_size);
}
int interrupt = i == 1 || i == 3 ? Interrupt::IRQ4 : Interrupt::IRQ3;
com->Initialize(interrupt);
if ( i == console_device )
{
ScopedLock lock(&console_lock);
console_imported = true;
}
if ( LinkInodeInDir(&ctx, slashdev, ttyname, com) != 0 )
PanicF("Unable to link %s/%s.", devpath, ttyname);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 2014, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -26,6 +26,7 @@
namespace Sortix {
namespace COM {
void InitializeConsole(const char* console);
void Init(const char* devpath, Ref<Descriptor> slashdev);
} // namespace COM

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -26,6 +26,8 @@
#include <stdint.h>
#include <string.h>
#include <sortix/limits.h>
typedef struct multiboot_info multiboot_info_t;
namespace Sortix {
@ -38,6 +40,8 @@ class TextBufferHandle;
namespace Sortix {
namespace Log {
extern char console_tty[TTY_NAME_MAX+1];
extern uint8_t* fallback_framebuffer;
extern size_t fallback_framebuffer_bpp;
extern size_t fallback_framebuffer_pitch;
@ -130,6 +134,7 @@ void Center(const char* string);
void BeginReplace();
void CancelReplace();
void FinishReplace(TextBuffer* textbuf);
void InitializeConsole(const char* console);
} // namespace Log
} // namespace Sortix

View file

@ -117,8 +117,10 @@ static void SystemIdleThread(void* user);
static int argc;
static char** argv;
static multiboot_info_t* bootinfo;
static const char* console = "tty1";
static bool enable_em = true;
static bool enable_network_drivers = true;
static char* term = (char*) "TERM=sortix";
static char* cmdline_tokenize(char** saved)
{
@ -198,10 +200,6 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
// Initialize the kernel log.
Log::Init(bootinfo);
// Display the logo.
Log::PrintF("\e[37;41m\e[2J");
Log::Center(BRAND_LOGO);
char* cmdline = NULL;
if ( bootinfo->flags & MULTIBOOT_INFO_CMDLINE && bootinfo->cmdline )
{
@ -265,6 +263,7 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
argv[argc] = NULL;
// Add new once-only options to sysinstall's normalize_kernel_options.
bool enable_logo = true;
bool no_random_seed = false;
for ( int i = 0; i < argc; i++ )
{
@ -285,10 +284,16 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
HaltKernel();
}
}
else if ( !strncmp(arg, "--console=", strlen("--console=")) )
console = arg + strlen("--console=");
else if ( !strcmp(arg, "--disable-em") )
enable_em = false;
else if ( !strcmp(arg, "--enable-em") )
enable_em = true;
else if ( !strcmp(arg, "--disable-logo") )
enable_logo = false;
else if ( !strcmp(arg, "--enable-logo") )
enable_logo = true;
else if ( !strcmp(arg, "--disable-network-drivers") )
enable_network_drivers = false;
else if ( !strcmp(arg, "--enable-network-drivers") )
@ -309,6 +314,12 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
PanicF("Unsupported firmware option: %s", firmware);
}
}
else if ( !strncmp(arg, "--term=", strlen("--term=")) )
{
const char* value = arg + strlen("--term=");
if ( asprintf(&term, "TERM=%s", value) < 0 )
Panic("malloc");
}
else
{
Log::PrintF("\r\e[J");
@ -325,6 +336,16 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
argv[argc] = NULL;
}
// Initialize the console.
Log::InitializeConsole(console);
// Display the logo.
if ( enable_logo )
{
Log::PrintF("\e[37;41m\e[J");
Log::Center(BRAND_LOGO);
}
// Initialize the interrupt handler table and enable interrupts.
Interrupt::Init();
@ -556,12 +577,15 @@ static void BootThread(void* /*user*/)
Panic("Could not symlink /dev/ptmx -> pts/ptmx");
// Register the kernel terminal as /dev/tty1.
Ref<Inode> tty1(new LogTerminal(slashdev->dev, 0666, 0, 0,
keyboard, kblayout, "tty1"));
if ( !tty1 )
Panic("Could not allocate a kernel terminal");
if ( LinkInodeInDir(&ctx, slashdev, "tty1", tty1) != 0 )
Panic("Unable to link /dev/tty1 to kernel terminal.");
if ( !strcmp(Log::console_tty, "/dev/tty1") )
{
Ref<Inode> tty1(new LogTerminal(slashdev->dev, 0666, 0, 0,
keyboard, kblayout, "tty1"));
if ( !tty1 )
Panic("Could not allocate a kernel terminal");
if ( LinkInodeInDir(&ctx, slashdev, "tty1", tty1) != 0 )
Panic("Unable to link /dev/tty1 to kernel terminal.");
}
// Register the mouse as /dev/mouse.
Ref<Inode> mousedev(new PS2MouseDevice(slashdev->dev, 0666, 0, 0, mouse));
@ -737,12 +761,12 @@ static void InitThread(void* /*user*/)
Ref<DescriptorTable> dtable = process->GetDTable();
Ref<Descriptor> tty1 = root->open(&ctx, "/dev/tty1", O_READ | O_WRITE);
if ( !tty1 )
PanicF("/dev/tty1: %m");
if ( tty1->ioctl(&ctx, TIOCSCTTY, 0) < 0 )
PanicF("/dev/tty1: ioctl: TIOCSCTTY: %m");
tty1.Reset();
Ref<Descriptor> tty = root->open(&ctx, Log::console_tty, O_READ | O_WRITE);
if ( !tty )
PanicF("%s: %m", Log::console_tty);
if ( tty->ioctl(&ctx, TIOCSCTTY, 0) < 0 )
PanicF("%s: ioctl: TIOCSCTTY: %m", Log::console_tty);
tty.Reset();
Ref<Descriptor> tty_stdin = root->open(&ctx, "/dev/tty", O_READ);
if ( !tty_stdin || dtable->Allocate(tty_stdin, 0) != 0 )
@ -787,7 +811,7 @@ static void InitThread(void* /*user*/)
Log::PrintF("\r\e[m\e[J");
int envc = 1;
const char* envp[] = { "TERM=sortix", NULL };
const char* envp[] = { term, NULL };
struct thread_registers regs;
assert((((uintptr_t) &regs) & (alignof(regs)-1)) == 0);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2017, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,6 +18,7 @@
*/
#include <assert.h>
#include <ctype.h>
#include <stddef.h>
#include <string.h>
@ -29,6 +30,7 @@
#include <sortix/kernel/pci-mmio.h>
#include <sortix/kernel/textbuffer.h>
#include "com.h"
#include "lfbtextbuffer.h"
#include "multiboot.h"
#include "textterminal.h"
@ -37,6 +39,8 @@
namespace Sortix {
namespace Log {
char console_tty[TTY_NAME_MAX+1];
uint8_t* fallback_framebuffer = NULL;
size_t fallback_framebuffer_bpp = 0;
size_t fallback_framebuffer_pitch = 0;
@ -262,5 +266,16 @@ void FinishReplace(TextBuffer* textbuf)
((TextTerminal*) Log::device_pointer)->FinishReplace(textbuf);
}
void InitializeConsole(const char* console)
{
if ( !strcmp(console, "tty1") )
snprintf(console_tty, sizeof(console_tty), "/dev/%s", console);
else if ( !strncmp(console, "com", 3) &&
isdigit((unsigned char) console[3]) )
COM::InitializeConsole(console);
else
PanicF("Unknown console: %s", console);
}
} // namespace Log
} // namespace Sortix

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2021 Jonas 'Sortie' Termansen.
* Copyright (c) 2012-2016, 2021, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -157,6 +157,12 @@ void LogTerminal::tty_output(const unsigned char* buffer, size_t length)
}
}
bool LogTerminal::Reconfigure(const struct termios* new_tio) // termlock held
{
(void) new_tio;
return true;
}
int LogTerminal::sync(ioctx_t* /*ctx*/)
{
ScopedLock lock(&termlock);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2012, 2014, 2015, 2016, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -44,6 +44,7 @@ public:
protected:
virtual void tty_output(const unsigned char* buffer, size_t length);
virtual bool Reconfigure(const struct termios* new_tio);
private:
void ProcessKeystroke(int kbkey);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, 2021, 2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016, 2021, 2022, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -465,6 +465,7 @@ public:
protected:
virtual void tty_output(const unsigned char* buffer, size_t length);
virtual bool Reconfigure(const struct termios* new_tio);
private:
short PollMasterEventStatus();
@ -594,6 +595,12 @@ void PTY::tty_output(const unsigned char* buffer, size_t length) // termlock hel
}
}
bool PTY::Reconfigure(const struct termios* new_tio) // termlock held
{
(void) new_tio;
return true;
}
short PTY::PollMasterEventStatus()
{
short status = 0;

View file

@ -230,13 +230,17 @@ int TTY::settermmode(ioctx_t* /*ctx*/, unsigned int termmode)
new_oflag |= OCRNL;
else
new_oflag &= ~OCRNL;
bool oldnoutf8 = old_lflag & ISORTIX_32BIT;
bool newnoutf8 = new_lflag & ISORTIX_32BIT;
if ( oldnoutf8 != newnoutf8 )
bool old_no_utf8 = old_lflag & ISORTIX_32BIT;
bool new_no_utf8 = new_lflag & ISORTIX_32BIT;
struct termios new_tio = tio;
new_tio.c_cflag = new_cflag;
new_tio.c_lflag = new_lflag;
new_tio.c_oflag = new_oflag;
if ( !Reconfigure(&new_tio) )
return -1;
tio = new_tio;
if ( old_no_utf8 != new_no_utf8 )
memset(&read_ps, 0, sizeof(read_ps));
tio.c_cflag = new_cflag;
tio.c_lflag = new_lflag;
tio.c_oflag = new_oflag;
if ( !(tio.c_lflag & ICANON) )
CommitLineBuffer();
return 0;
@ -822,7 +826,7 @@ int TTY::tcsetattr(ioctx_t* ctx, int actions, const struct termios* io_tio)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
return -1;
if ( !RequireForeground(SIGTTOU) )
return -1;
switch ( actions )
@ -830,15 +834,17 @@ int TTY::tcsetattr(ioctx_t* ctx, int actions, const struct termios* io_tio)
case TCSANOW: break;
case TCSADRAIN: break;
case TCSAFLUSH: linebuffer.Flush(); break;
default: return errno = EINVAL, -1;
default: return -1;
}
tcflag_t old_lflag = tio.c_lflag;
if ( !ctx->copy_from_src(&tio, io_tio, sizeof(tio)) )
struct termios new_tio;
if ( !ctx->copy_from_src(&new_tio, io_tio, sizeof(new_tio)) )
return -1;
tcflag_t new_lflag = tio.c_lflag;
bool oldnoutf8 = old_lflag & ISORTIX_32BIT;
bool newnoutf8 = new_lflag & ISORTIX_32BIT;
if ( oldnoutf8 != newnoutf8 )
bool old_no_utf8 = tio.c_lflag & ISORTIX_32BIT;
bool new_no_utf8 = new_tio.c_lflag & ISORTIX_32BIT;
if ( !Reconfigure(&new_tio) )
return -1;
tio = new_tio;
if ( old_no_utf8 != new_no_utf8 )
memset(&read_ps, 0, sizeof(read_ps));
if ( !(tio.c_lflag & ICANON) )
CommitLineBuffer();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2012, 2014, 2015, 2016, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -86,6 +86,7 @@ protected:
tty_output((const unsigned char*) str, strlen(str));
}
virtual void tty_output(const unsigned char* buffer, size_t length) = 0;
virtual bool Reconfigure(const struct termios* new_tio) = 0;
protected:
void ProcessUnicode(uint32_t unicode);

23
share/man/man4/com.4 Normal file
View file

@ -0,0 +1,23 @@
.Dd December 16, 2024
.Dt COM 4
.Os
.Sh NAME
.Nm com
.Nd uart 8250 serial port terminal driver
.Sh SYNOPSIS
.Nm /dev/com Ns Ar X
.Sh DESCRIPTION
.Nm
is a terminal driver for the UART 8250 serial port and compatible devices.
.Pp
The serial ports are detected using the BIOS Data Area.
.Pp
.Nm
supports being the
.Xr kernel 7
console as described in its
.Fl \-console
option.
.Sh SEE ALSO
.Xr tty 4 ,
.Xr kernel 7

View file

@ -166,6 +166,46 @@ which will instead boot to a plain
.Pa /dev/tty1
terminal.
.Pp
The kernel console can be changed to the primary serial port terminal by
selecting
.Sy Enable serial console .
You can use the
.Xr release-iso-modification 7
procedure to customize the serial port settings (speed, bits, parity), window
size, and the
.Ev TERM
environment variable ahead of time.
Otherwise it will default to a speed of 38400 with 8 bits and no parity, with a
80x25 window size, and the default
.Ev TERM
variable.
You can change the settings appropriately after boot using the
.Xr stty 7
.Sy ispeed , ospeed , cols ,
and
.Sy rows
commands, followed by
.Sy export TERM=terminal
to select your terminal.
The installation remembers the
.Xr kernel 7
.Fl \-console
and
.Fl \-term
options it was booted with.
If you wish for the installed GRUB bootloader to use the serial line, choose
the
.Sy chroot
option at the end of the installation, and then set the
.Sy GRUB_SERIAL_COMMAND
and
.Sy GRUB_TERMINAL
variables appropriately per the GRUB documentation in the
.Pa /etc/grub
file and run the
.Xr update-grub 8
command.
.Pp
The network drivers can be disabled by navigating to the advanced menu and
selecting
.Sy Disable network drivers .

View file

@ -6,12 +6,16 @@
.Nd operating system kernel
.Sh SYNOPSIS
.Pa /boot/sortix.bin
.Op Fl \-console Ns = Ns Ar terminal
.Op Fl \-disable-em
.Op Fl \-enable-em
.Op Fl \-disable-network-drivers
.Op Fl \-disable-logo
.Op Fl \-enable-em
.Op Fl \-enable-network-drivers
.Op Fl \-enable-logo
.Op Fl \-firmware Ns = Ns Oo Sy bios "|" Sy efi "|" pc Oc
.Op Fl \-no-random-seed
.Op Fl \-term Ns = Ns Ar terminal
.Op Fl \-
.Op Ar init ...
.Sh DESCRIPTION
@ -51,10 +55,57 @@ otherwise.
.Pp
The options are as follows:
.Bl -tag -width "12345678"
.It Fl \-console Ns = Ns Ar terminal
Use the
.Ar terminal
as the console and the session for
.Xr init 8
along with options for initializing the terminal.
The choices are:
.Bl -tag -width "12345678"
.It Sy com Ns Ar n Ns Oo , Ns Oo BBBBPN Oc Ns Oo Ns , Ns Ar columns Ns Sy x Ns Ar rows Oc Oc
Use the
.Xr com 4
serial line; optionally initializing it with the speed
.Ar BBBB ,
with the parity
.Ar P
being either
.Sy n
for no parity,
.Sy o
for odd parity,
or
.Sy e
for even parity, and
.Ar N
is the number of bits per character;
and optionally initializing the terminal window size to the selected
.Ar columns
and
.Ar rows
or 80x25 by default.
The
.Fl \-term
option is useful to select the
.Ev TERM
environment variable.
.Pp
This choice currently disables the
.Sy tty1
terminal because its logterminal driver needs a major restructuring.
.It Sy tty1
Use the
.Sy tty1
virtual terminal as the console.
This terminal is the default console.
.El
.It Fl \-disable-em
Don't initialize the
.Xr em 4
driver.
.It Fl \-disable-logo
Don't display the operating system boot on the console during boot.
.It Fl \-disable-network-drivers
Don't initialize any network drivers.
This option ensures the booted system is not networked.
@ -62,6 +113,9 @@ This option ensures the booted system is not networked.
Do initialize the
.Xr em 4
driver.
.It Fl \-enable-logo
Display the operating system boot on the console during boot.
This is the default behavior.
.It Fl \-enable-network-drivers
Do initialize network drivers.
This is the default behavior.
@ -77,6 +131,17 @@ value is a synonym for
Don't warn if no random seed file was loaded by the bootloader (usually from
.Pa /boot/random.seed ) .
This option is useful for live environments where this situation is unavoidable.
.It Fl \-term Ns = Ns Ar terminal
Set the
.Ev TERM
environment variable to the selected
.Ar terminal .
This option is useful in combination with selecting a serial line as the
.Fl \-console.
The default
.Ev TERM
is
.Sy sortix .
.El
.Pp
The kernel accepts multiboot modules from the bootloader, which are processed

View file

@ -67,6 +67,10 @@ Secondly, it loads
(if it exists)
which can take any actions it wants and customize the default configuration.
.Pp
Third, the
.Sy hook_initialize_terminal
hook is run to initialize the bootloader input and output.
.Pp
Finally, it loads
.Pa /boot/grub/main.cfg
to display the menu menu.
@ -256,6 +260,13 @@ The main menu uses this title verbatim, while the other menus will append
" when constructing
.Sy menu_title.
(Default: "Sortix $version for $machine")
.It Sy console
Optionally contains
.Fl \-console
and
.Fl \-term
options to forward to the
.Xr kernel 7 .
.It Sy default
Select this bootloader menu option number by default (counting from 0).
If the selected menu option itself is a submenu, it can be appended with a
@ -289,6 +300,9 @@ Whether to start the
.Xr sshd 8
daemon.
(Default: false)
.It Sy kernel_options
Optionally contains additional options for the
.Xr kernel 7 .
.It Sy machine
The machine type this release was built for.
.It Sy menu_title
@ -312,6 +326,14 @@ and not if set to
.Sy false .
(Default:
.Sy true )
.It Sy serial_console
When the serial console is enabled in the advanced menu, this variable contains
the
.Fl \-console
and
.Fl \-term
options to forward to the
.Xr kernel 7 .
.It Sy timeout
The time in seconds before the default menu entry (according to the
.Sy default
@ -453,6 +475,9 @@ The following hooks are run by the GRUB bootloader configuration:
After the advanced menu entries have been emitted.
.It Sy hook_advanced_menu_pre
Before the advanced menu entries are emitted.
.It Sy hook_initialize_terminal
After having loaded the hook configuration file, when it's time to configure
the bootloader input and output.
.It Sy hook_initrd_post
After the initrd is loaded.
.It Sy hook_kernel_post

View file

@ -536,6 +536,22 @@ tix-iso-liveconfig --autoinstall=autoupgrade.conf liveconfig
tix-iso-bootconfig --liveconfig=liveconfig --default=2 bootconfig
tix-iso-add sortix.iso bootconfig
.Ed
.Ss Boot to Console Instead of GUI By Default
To customize a release so it boots to a console instead of the GUI:
.Bd -literal
tix-iso-bootconfig --disable-gui bootconfig
tix-iso-add sortix.iso bootconfig
.Ed
.Ss Boot to Serial Console using Qemu
To boot to a serial terminal with a custom serial settings and a custom terminal
window size of 118 colums by 256 rows and a specific
.Ev TERM
variable inside the qemu virtual machine:
.Bd -literal
tix-iso-bootconfig --serial="com1,9600n8,118x56 --term=xterm-256color" bootconfig
tix-iso-add sortix.iso bootconfig
qemu-system-x86_64 -serial mon:stdio -cdrom sortix.iso
.Ed
.Sh SEE ALSO
.Xr xorriso 1 ,
.Xr development 7 ,

View file

@ -19,6 +19,7 @@
set -e
append_title="modified by $(id -un)@$(hostname)"
console=
default=
directory=
enable_append_title=true
@ -28,8 +29,12 @@ enable_network_drivers=
enable_ntpd=
enable_src=
enable_sshd=
grub_serial=
kernel_options=
init_target=
liveconfig=
serial=
serial_console=
operand=1
random_seed=false
timeout=
@ -53,6 +58,8 @@ for argument do
--) dashdash=yes ;;
--append-title=*) append_title=$parameter ;;
--append-title) previous_option=append_title ;;
--console=*) console=$parameter ;;
--console) previous_option=console ;;
--default=*) default=$parameter ;;
--default) previous_option=default ;;
--disable-append-title) enable_append_title=false ;;
@ -69,11 +76,19 @@ for argument do
--enable-ntpd) enable_ntpd=true ;;
--enable-src) enable_src=true ;;
--enable-sshd) enable_sshd=true ;;
--grub-serial=*) grub_serial=$parameter ;;
--grub-serial) previous_option=grub_serial ;;
--kernel-options=*) kernel_options=$parameter ;;
--kernel-options) previous_option=kernel_options ;;
--init-target=*) init_target=$parameter ;;
--init-target) previous_option=init_target ;;
--liveconfig=*) liveconfig=$parameter ;;
--liveconfig) previous_option=liveconfig ;;
--random-seed) random_seed=true ;;
--serial=*) serial=$parameter ;;
--serial) previous_option=serial ;;
--serial-console=*) serial_console=$parameter ;;
--serial-console) previous_option=serial_console ;;
--timeout=*) timeout=$parameter ;;
--timeout) previous_option=timeout ;;
-*) echo "$0: unrecognized option $argument" >&2
@ -95,6 +110,13 @@ if test -n "$previous_option"; then
exit 1
fi
if test -n "$serial"; then
enable_gui=false
console="$serial"
grub_serial="$serial"
serial_console="$serial"
fi
if test -z "$directory"; then
echo "$0: No directory was specified" >&2
exit 1
@ -107,17 +129,17 @@ human_size() {
print_enable_default() {
if [ "$1" = true ]; then
printf " enable_%s=--enable-%s\n" "$2" "$3"
printf "enable_%s=--enable-%s\n" "$2" "$3"
elif [ "$1" = false ]; then
printf " enable_%s=--disable-%s\n" "$2" "$3"
printf "enable_%s=--disable-%s\n" "$2" "$3"
fi
}
print_enable_default_bool() {
if [ "$1" = true ]; then
printf " enable_%s=true\n" "$2"
printf "enable_%s=true\n" "$2"
elif [ "$1" = false ]; then
printf " enable_%s=false\n" "$2"
printf "enable_%s=false\n" "$2"
fi
}
@ -152,9 +174,15 @@ mkdir -p -- "$directory/boot/grub"
(if [ -e "$directory/boot/liveconfig.tar.xz" ]; then
printf 'insmod xzio\n'
fi
if [ -n "$console" ]; then
printf 'console="--console=%s"\n' "$console"
fi
if [ -n "$default" ]; then
printf 'default="%s"\n' "$default"
fi
if [ -n "$serial_console" ]; then
printf 'serial_console="--console=%s"\n' "$serial_console"
fi
if [ -n "$timeout" ]; then
printf 'timeout="%s"\n' "$timeout"
fi
@ -170,10 +198,30 @@ mkdir -p -- "$directory/boot/grub"
print_enable_default_bool "$enable_src" src src
print_enable_default_bool "$enable_sshd" sshd sshd
print_enable_default_bool "$enable_ntpd" ntpd ntpd
if [ -n "$kernel_options" ]; then
printf 'kernel_options="%s"\n' "$kernel_options"
fi
if $enable_append_title; then
printf "base_menu_title=\"\$base_menu_title - \"'%s'\n" \
"$(printf '%s\n' "$append_title" | sed "s/'/'\\\\''/g")"
fi
if [ -n "$grub_serial" ]; then
device=$(expr "X$grub_serial" : 'Xcom\([0-9]\+\).*' || true)
device=$(expr "$device" - 1 || true)
settings=$(expr "X$grub_serial" : 'Xcom[0-9]\+,\([^,]*\)\(,.*\)\?' || true)
speed=$(expr "X$settings" : 'X\([0-9]*\)[noe][5678]' || true)
parity=$(expr "X$settings" : 'X[0-9]*\([noe]\)[5678]' || true)
case "$parity" in n) parity=no;; o) parity=odd;; e) parity=even;; esac
bits=$(expr "X$settings" : 'X[0-9]*[noe]\([5678]\)' || true)
cat << EOF
function hook_initialize_terminal {
serial --unit=$device ${speed+--speed=$speed} ${parity+--parity=$parity} \
${bits+--word=$bits}
terminal_input serial
terminal_output serial
}
EOF
fi
if [ -n "$init_target" ]; then
printf 'function hook_menu_pre {\n'
printf ' menuentry "Sortix (%s)" {\n' "$init_target"

View file

@ -7,6 +7,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl \-append-title Ns = Ns Ar text
.Op Fl \-console Ns = Ns Ar terminal
.Op Fl \-default Ns = Ns Ar default-boot-menu-option
.Op Fl \-disable-append-title
.Op Fl \-disable-dhclient
@ -22,9 +23,13 @@
.Op Fl \-enable-ntpd
.Op Fl \-enable-src
.Op Fl \-enable-sshd
.Op Fl \-grub-serial Ns = Ns Ar terminal
.Op Fl \-kernel-options Ns = Ns Ar options
.Op Fl \-init-target Ns = Ns Ar target
.Op Fl \-liveconfig Ns = Ns Ar liveconfig-directory
.Op Fl \-random-seed
.Op Fl \-serial Ns = Ns Ar terminal
.Op Fl \-serial-console Ns = Ns Ar terminal
.Op Fl \-timeout Ns = Ns Ar boot-menu-timeout
.Ar output-directory
.Sh DESCRIPTION
@ -83,6 +88,20 @@ The bootloader menu title is appended to by default, but can be disabled with
and re-enabled with
.Fl \-enable-append-title ,
whichever comes last takes precedence.
.It Fl \-console Ns = Ns Ar terminal
Forward the
.Fl \-console
option to the
.Xr kernel 7
to select the
.Ar terminal
to use as the console and the settings to initialize it.
.Pp
The
.Ev TERM
variable can be overridden by suffixing the quoted argument with a space
followed by
.Fl \-term Ns = Ns Ar terminal .
.It Fl \-default Ns = Ns Ar default-boot-menu-option
Select bootloader menu option number
.Ar default-boot-menu-option
@ -205,6 +224,18 @@ GRUB variable to
causing the bootloader to load additional configuration that turns on the
.Xr sshd 8
daemon on boot.
.It Fl \-grub-serial Ns = Ns Ar terminal
Use the serial
.Ar terminal
for GRUB's menu in the format of the
.Xr kernel 7
.Fl \-console
option.
.It Fl \-kernel-options Ns = Ns Ar options
Forward these additional
.Ar options
to the the
.Xr kernel 7 .
.It Fl \-init-target Ns = Ns Ar target
Add a new first menu entry that boots the
.Ar target
@ -277,6 +308,34 @@ If a release .iso has been made from
it should be securely erased when no longer useful.
If a release .iso has been burned to a physical media, it should be securely
erased when no longer useful.
.It Fl \-serial Ns = Ns Ar terminal
Boot entirely using the serial
.Xr terminal
as specified in the format of the
.Xr kernel 7
.Fl \-console option.
This option is a shorthand equivalent to setting the
.Fl \-console ,
.Fl \-grub-console ,
.Fl \-serial-console ,
and
.Fl \-disable-gui
options.
.It Fl \-serial-console Ns = Ns Ar terminal
Whenever the serial console is enabled in the advanced bootloader menu, forward
this
.Fl \-console
option to the
.Xr kernel 7
to select the
.Ar terminal
to use as the console and the settings to initialize it.
.Pp
The
.Ev TERM
variable can be overridden by suffixing the quoted argument with a space
followed by
.Fl \-term Ns = Ns Ar terminal .
.It Fl \-timeout Ns = Ns Ar boot-menu-timeout
Pick the default bootloader menu option after
.Ar boot-menu-timeout
@ -378,6 +437,16 @@ To customize a release so it boots to a console instead of the GUI:
tix-iso-bootconfig --disable-gui bootconfig
tix-iso-add sortix.iso bootconfig
.Ed
.Ss Boot to Serial Console using Qemu
To boot to a serial terminal with a custom serial settings and a custom terminal
window size of 118 colums by 256 rows and a specific
.Ev TERM
variable inside the qemu virtual machine:
.Bd -literal
tix-iso-bootconfig --serial="com1,9600n8,118x56 --term=xterm-256color" bootconfig
tix-iso-add sortix.iso bootconfig
qemu-system-x86_64 -serial mon:stdio -cdrom sortix.iso
.Ed
.Sh SEE ALSO
.Xr xorriso 1 ,
.Xr kernel 7 ,