kernel.early-console is used to specify which output device the kernel boot log should be written to. the first thing the kernel does on boot after initialising the bootstrap processor is initialise the early console, making it useful for debugging problems that occur early in the boot process. this arg accepts a list of hard-coded values for output devices, such as tty0 for the display or ttyS0 for the serial port. the exact values supported will depend on the platform. once all drivers are loaded, the kernel switches to the device specified by kernel.console for output. unlike kernel.early-console, this arg specifies the name of a tty device in /dev/tty. this means that, not only are more devices supported (any device provided by a tty driver), but the kernel can also get input from the user using this console too (not used by the kernel itself, but will be used by the user to interact with userspace programs, like the shell).
163 lines
3.8 KiB
C
163 lines
3.8 KiB
C
#include <socks/tty.h>
|
|
#include <socks/device.h>
|
|
#include <socks/console.h>
|
|
#include <socks/input.h>
|
|
#include <socks/libc/stdio.h>
|
|
|
|
static struct char_device_ops tty_ops = {
|
|
.read = tty_read,
|
|
.write = tty_write,
|
|
};
|
|
|
|
static spin_lock_t foreground_lock = SPIN_LOCK_INIT;
|
|
static struct device *foreground = NULL;
|
|
static struct device *foreground_input = NULL;
|
|
|
|
static spin_lock_t kernel_console_tty_lock = SPIN_LOCK_INIT;
|
|
static struct device *kernel_console_tty = NULL;
|
|
|
|
static void tty_input_hook_callback(struct device *dev, struct input_event *ev, enum input_event_hook_flags *flags, void *arg)
|
|
{
|
|
struct device *fg_tty = foreground;
|
|
|
|
if (!fg_tty) {
|
|
return;
|
|
}
|
|
|
|
tty_report_event(fg_tty, ev);
|
|
*flags = 0;
|
|
}
|
|
|
|
static struct input_event_hook foreground_input_hook = {
|
|
.hook_callback = tty_input_hook_callback,
|
|
};
|
|
|
|
static void tty_console_write(struct console *con, const char *s, unsigned int len)
|
|
{
|
|
if (kernel_console_tty) {
|
|
size_t nr_written;
|
|
tty_write(kernel_console_tty, s, 0, len, &nr_written, 0);
|
|
}
|
|
}
|
|
|
|
static struct console tty_console = {
|
|
.c_name = "tty",
|
|
.c_write = tty_console_write,
|
|
};
|
|
|
|
void register_tty_console(void)
|
|
{
|
|
console_register(&tty_console);
|
|
}
|
|
|
|
void redirect_printk_to_tty(struct device *dest)
|
|
{
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&kernel_console_tty_lock, &flags);
|
|
|
|
if (kernel_console_tty) {
|
|
device_deref(kernel_console_tty);
|
|
kernel_console_tty = NULL;
|
|
}
|
|
|
|
kernel_console_tty = device_ref(dest);
|
|
|
|
spin_unlock_irqrestore(&kernel_console_tty_lock, flags);
|
|
}
|
|
|
|
struct device *tty_device_create(void)
|
|
{
|
|
struct char_device *cdev = char_device_create();
|
|
if (!cdev) {
|
|
return NULL;
|
|
}
|
|
|
|
struct tty_device *tty_dev = kmalloc(sizeof *tty_dev, VM_NORMAL);
|
|
if (!tty_dev) {
|
|
object_deref(char_device_object(cdev));
|
|
return NULL;
|
|
}
|
|
|
|
kern_status_t status = ringbuffer_init(&tty_dev->tty_input, TTY_INPUT_QUEUE_SIZE * sizeof(char));
|
|
if (status != KERN_OK) {
|
|
kfree(tty_dev);
|
|
object_deref(char_device_object(cdev));
|
|
return NULL;
|
|
}
|
|
|
|
tty_dev->tty_ldisc = tty_default_line_discipline();
|
|
|
|
cdev->c_ops = &tty_ops;
|
|
cdev->c_tty = tty_dev;
|
|
|
|
return char_device_base(cdev);
|
|
}
|
|
|
|
static kern_status_t generate_name(struct tty_driver *owner, struct device *dev, char out[DEV_NAME_MAX])
|
|
{
|
|
/* minor numbers start at 1. subtract 1 to start at 0 instead */
|
|
snprintf(out, DEV_NAME_MAX, "%s%u", owner->tty_name, dev->dev_minor - 1);
|
|
return KERN_OK;
|
|
}
|
|
|
|
kern_status_t tty_device_register(struct device *dev, struct tty_driver *owner, struct device *parent)
|
|
{
|
|
kern_status_t status = device_register(dev, tty_driver_base(owner), parent);
|
|
if (status != KERN_OK) {
|
|
return status;
|
|
}
|
|
|
|
char link_name[DEV_NAME_MAX];
|
|
generate_name(owner, dev, link_name);
|
|
char link_path[OBJECT_PATH_MAX];
|
|
snprintf(link_path, sizeof link_path, "/dev/tty/%s", link_name);
|
|
|
|
return object_namespace_create_link(global_namespace(), link_path, &dev->dev_base);
|
|
}
|
|
|
|
void tty_set_foreground(struct device *tty)
|
|
{
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&foreground_lock, &flags);
|
|
|
|
if (foreground) {
|
|
device_deref(foreground);
|
|
foreground = NULL;
|
|
}
|
|
|
|
foreground = device_ref(tty);
|
|
|
|
spin_unlock_irqrestore(&foreground_lock, flags);
|
|
}
|
|
|
|
kern_status_t tty_connect_foreground_input_device(struct device *input)
|
|
{
|
|
struct input_device *inputdev = INPUT_DEVICE(input);
|
|
if (!inputdev) {
|
|
return KERN_INVALID_ARGUMENT;
|
|
}
|
|
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&foreground_lock, &flags);
|
|
|
|
if (foreground_input) {
|
|
struct device *prev = foreground_input;
|
|
foreground_input = NULL;
|
|
|
|
device_lock(prev);
|
|
input_device_remove_hook(prev, &foreground_input_hook);
|
|
device_unlock(prev);
|
|
device_deref(prev);
|
|
object_deref(&prev->dev_base);
|
|
}
|
|
|
|
foreground_input = device_ref(input);
|
|
device_lock(input);
|
|
input_device_add_hook(input, &foreground_input_hook);
|
|
device_unlock(input);
|
|
|
|
spin_unlock_irqrestore(&foreground_lock, flags);
|
|
|
|
return KERN_OK;
|
|
}
|