limine: Add support for multiple terminals and properly document it

This commit is contained in:
mintsuki 2022-04-01 10:00:36 +02:00
parent 15f111f5c3
commit fb175747df
9 changed files with 281 additions and 30 deletions

View File

@ -19,6 +19,8 @@ languages.
All pointers are 64-bit wide. All pointers point to the object with the All pointers are 64-bit wide. All pointers point to the object with the
higher half direct map offset already added to them, unless otherwise noted. higher half direct map offset already added to them, unless otherwise noted.
The calling convention matches the SysV C ABI for the specific architecture.
### Executable formats ### Executable formats
The Limine protocol does not enforce any specific executable format, but The Limine protocol does not enforce any specific executable format, but
@ -306,7 +308,7 @@ ID:
Request: Request:
```c ```c
typedef void (*limine_terminal_callback)(uint64_t, uint64_t, uint64_t, uint64_t); typedef void (*limine_terminal_callback)(struct limine_terminal *, uint64_t, uint64_t, uint64_t, uint64_t);
struct limine_terminal_request { struct limine_terminal_request {
uint64_t id[4]; uint64_t id[4];
@ -320,22 +322,201 @@ struct limine_terminal_request {
Response: Response:
```c ```c
typedef void (*limine_terminal_write)(const char *, uint64_t);
struct limine_terminal_response { struct limine_terminal_response {
uint64_t revision; uint64_t revision;
uint64_t terminal_count;
struct limine_terminal **terminals;
};
```
* `terminal_count` - How many terminals are present.
* `terminals` - Pointer to an array of `terminal_count` pointers to
`struct limine_terminal` structures.
```c
typedef void (*limine_terminal_write)(const char *, uint64_t);
struct limine_terminal {
uint32_t columns; uint32_t columns;
uint32_t rows; uint32_t rows;
struct limine_framebuffer *framebuffer;
limine_terminal_write write; limine_terminal_write write;
}; };
``` ```
* `columns` and `rows` - Columns and rows provided by the terminal. * `columns` and `rows` - Columns and rows provided by the terminal.
* `framebuffer` - The framebuffer associated with this terminal.
* `write` - Physical pointer to the terminal write() function. * `write` - Physical pointer to the terminal write() function.
The function is not thread-safe, nor reentrant, per-terminal.
This means multiple terminals may be called simultaneously, and multiple
callbacks may be handled simultaneously.
Note: Omitting this request will cause the bootloader to not initialise Note: Omitting this request will cause the bootloader to not initialise
the terminal service. The terminal is further documented in the stivale2 the terminal service.
specification.
#### Terminal callback
The callback is a function that is part of the kernel, which is called by the
terminal during a `write()` call whenever an event or escape sequence cannot
be handled by the bootloader's terminal alone, and the kernel may want to be
notified in order to handle it itself.
Returning from the callback will resume the `write()` call which will return
to its caller normally.
Not returning from a callback may leave the terminal in an undefined state
and cause issues.
The callback function has the following prototype:
```c
void callback(struct limine_terminal *terminal, uint64_t type, uint64_t, uint64_t, uint64_t);
```
The `terminal` argument is a pointer to the Limine terminal structure which
has the `write()` call that caused the callback.
The purpose of the last 3 arguments changes depending on the `type` argument.
The callback types are as follows:
* `LIMINE_TERMINAL_CB_DEC` - (type value: `10`)
This callback is triggered whenever a DEC Private Mode (DECSET/DECRST)
sequence is encountered that the terminal cannot handle alone. The arguments
to this callback are: `terminal`, `type`, `values_count`, `values`, `final`.
`values_count` is a count of how many values are in the array pointed to by
`values`. `values` is a pointer to an array of `uint32_t` values, which are
the values passed to the DEC private escape.
`final` is the final character in the DEC private escape sequence (typically
`l` or `h`).
* `LIMINE_TERMINAL_CB_BELL` - (type value: `20`)
This callback is triggered whenever a bell event is determined to be
necessary (such as when a bell character `\a` is encountered). The arguments
to this callback are: `terminal`, `type`, `unused1`, `unused2`, `unused3`.
* `LIMINE_TERMINAL_CB_PRIVATE_ID` - (type value: `30`)
This callback is triggered whenever the kernel has to respond to a DEC
private identification request. The arguments to this callback are:
`terminal`, `type`, `unused1`, `unused2`, `unused3`.
* `LIMINE_TERMINAL_CB_STATUS_REPORT` - (type value `40`)
This callback is triggered whenever the kernel has to respond to a ECMA-48
status report request. The arguments to this callback are: `terminal`,
`type`, `unused1`, `unused2`, `unused3`.
* `LIMINE_TERMINAL_CB_POS_REPORT` - (type value `50`)
This callback is triggered whenever the kernel has to respond to a ECMA-48
cursor position report request. The arguments to this callback are:
`terminal`, `type`, `x`, `y`, `unused3`. Where `x` and `y` represent the
cursor position at the time the callback is triggered.
* `LIMINE_TERMINAL_CB_KBD_LEDS` - (type value `60`)
This callback is triggered whenever the kernel has to respond to a keyboard
LED state change request. The arguments to this callback are: `terminal`,
`type`, `led_state`, `unused2`, `unused3`. `led_state` can have one of the
following values: `0, 1, 2, or 3`. These values mean: clear all LEDs, set
scroll lock, set num lock, and set caps lock LED, respectively.
* `LIMINE_TERMINAL_CB_MODE` - (type value: `70`)
This callback is triggered whenever an ECMA-48 Mode Switch sequence
is encountered that the terminal cannot handle alone. The arguments to this
callback are: `terminal`, `type`, `values_count`, `values`, `final`.
`values_count` is a count of how many values are in the array pointed to by
`values`. `values` is a pointer to an array of `uint32_t` values, which are
the values passed to the mode switch escape.
`final` is the final character in the mode switch escape sequence (typically
`l` or `h`).
* `LIMINE_TERMINAL_CB_LINUX` - (type value `80`)
This callback is triggered whenever a private Linux escape sequence
is encountered that the terminal cannot handle alone. The arguments to this
callback are: `terminal`, `type`, `values_count`, `values`, `unused3`.
`values_count` is a count of how many values are in the array pointed to by
`values`. `values` is a pointer to an array of `uint32_t` values, which are
the values passed to the Linux private escape.
#### Terminal context control
The `write()` function can additionally be used to set and restore terminal
context, and refresh the terminal fully.
In order to achieve this, special values for the `length` argument are
passed. These values are:
```c
#define LIMINE_TERMINAL_CTX_SIZE ((uint64_t)(-1))
#define LIMINE_TERMINAL_CTX_SAVE ((uint64_t)(-2))
#define LIMINE_TERMINAL_CTX_RESTORE ((uint64_t)(-3))
#define LIMINE_TERMINAL_FULL_REFRESH ((uint64_t)(-4))
```
For `CTX_SIZE`, the `ptr` variable has to point to a location to which the
terminal will *write* a single `uint64_t` which contains the size of the
terminal context.
For `CTX_SAVE` and `CTX_RESTORE`, the `ptr` variable has to point to a
location to which the terminal will *save* or *restore* its context from,
respectively.
This location must have a size congruent to the value received from
`CTX_SIZE`.
For `FULL_REFRESH`, the `ptr` variable is unused. This routine is to be used
after control of the framebuffer is taken over and the bootloader's terminal
has to *fully* repaint the framebuffer to avoid inconsistencies.
#### x86_64
Additionally, the kernel must ensure, when calling `write()`, that:
* Either the GDT provided by the bootloader is still properly loaded, or a
custom GDT is loaded with at least the following descriptors in this specific
order:
- Null descriptor
- 16-bit code descriptor. Base = `0`, limit = `0xffff`. Readable.
- 16-bit data descriptor. Base = `0`, limit = `0xffff`. Writable.
- 32-bit code descriptor. Base = `0`, limit = `0xffffffff`. Readable.
- 32-bit data descriptor. Base = `0`, limit = `0xffffffff`. Writable.
- 64-bit code descriptor. Base and limit irrelevant. Readable.
- 64-bit data descriptor. Base and limit irrelevant. Writable.
* The currently loaded virtual address space is still the one provided at
entry by the bootloader, or a custom virtual address space is loaded which
identity maps the framebuffer memory region associated with the terminal, and
all the bootloader reclaimable memory regions, with read, write, and execute
permissions.
* The routine is called *by its physical address* (the value of the function
pointer is already physical), which should be identity mapped.
* Bootloader-reclaimable memory entries are left untouched until after the
kernel is done utilising bootloader-provided facilities (this terminal being
one of them).
Notes regarding segment registers and FPU:
The values of the FS and GS segments are guaranteed preserved across the
call. All other segment registers may have their "hidden" portion
overwritten, but Limine guarantees that the "visible" portion is going to
be restored to the one used at the time of call before returning.
No registers other than the segment registers and general purpose registers
are going to be used. Especially, this means that there is no need to save
and restore FPU, SSE, or AVX state when calling the terminal write function.
#### Terminal characteristics
The terminal should strive for Linux console compatibility.
### Framebuffer Feature ### Framebuffer Feature

View File

@ -292,7 +292,8 @@ void (*term_context_save)(uint64_t ptr);
void (*term_context_restore)(uint64_t ptr); void (*term_context_restore)(uint64_t ptr);
void (*term_full_refresh)(void); void (*term_full_refresh)(void);
void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t) = NULL; uint64_t term_arg = 0;
void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t) = NULL;
struct term_context term_context; struct term_context term_context;
@ -669,7 +670,11 @@ static void dec_private_parse(uint8_t c) {
} }
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c);
} else {
term_callback(TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c, 0);
}
} }
} }
@ -679,7 +684,11 @@ static void linux_private_parse(void) {
} }
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0);
} else {
term_callback(TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0, 0);
}
} }
} }
@ -705,7 +714,11 @@ static void mode_toggle(uint8_t c) {
} }
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c);
} else {
term_callback(TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c, 0);
}
} }
} }
@ -819,7 +832,11 @@ static void control_sequence_parse(uint8_t c) {
break; break;
case 'c': case 'c':
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_PRIVATE_ID, 0, 0, 0);
} else {
term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0, 0);
}
} }
break; break;
case 'd': case 'd':
@ -849,19 +866,31 @@ static void control_sequence_parse(uint8_t c) {
switch (esc_values[0]) { switch (esc_values[0]) {
case 5: case 5:
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_STATUS_REPORT, 0, 0, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_STATUS_REPORT, 0, 0, 0);
} else {
term_callback(TERM_CB_STATUS_REPORT, 0, 0, 0, 0);
}
} }
break; break;
case 6: case 6:
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_POS_REPORT, x + 1, y + 1, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_POS_REPORT, x + 1, y + 1, 0);
} else {
term_callback(TERM_CB_POS_REPORT, x + 1, y + 1, 0, 0);
}
} }
break; break;
} }
break; break;
case 'q': case 'q':
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_KBD_LEDS, esc_values[0], 0, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_KBD_LEDS, esc_values[0], 0, 0);
} else {
term_callback(TERM_CB_KBD_LEDS, esc_values[0], 0, 0, 0);
}
} }
break; break;
case 'J': case 'J':
@ -1066,7 +1095,11 @@ is_csi:
break; break;
case 'Z': case 'Z':
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_PRIVATE_ID, 0, 0, 0);
} else {
term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0, 0);
}
} }
break; break;
case '(': case '(':
@ -1185,7 +1218,11 @@ void term_putchar(uint8_t c) {
case '\a': case '\a':
// The bell is handled by the kernel // The bell is handled by the kernel
if (term_callback != NULL) { if (term_callback != NULL) {
term_callback(TERM_CB_BELL, 0, 0, 0); if (term_arg != 0) {
term_callback(term_arg, TERM_CB_BELL, 0, 0, 0);
} else {
term_callback(TERM_CB_BELL, 0, 0, 0, 0);
}
} }
return; return;
case 14: case 14:

View File

@ -103,7 +103,8 @@ extern void (*term_full_refresh)(void);
#define TERM_CTX_RESTORE ((uint64_t)(-3)) #define TERM_CTX_RESTORE ((uint64_t)(-3))
#define TERM_FULL_REFRESH ((uint64_t)(-4)) #define TERM_FULL_REFRESH ((uint64_t)(-4))
extern void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t); extern uint64_t term_arg;
extern void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
extern bool term_autoflush; extern bool term_autoflush;

View File

@ -108,7 +108,7 @@ static void *_get_request(uint64_t id[4]) {
extern symbol stivale2_term_write_entry; extern symbol stivale2_term_write_entry;
extern void *stivale2_rt_stack; extern void *stivale2_rt_stack;
extern uint64_t stivale2_term_callback_ptr; extern uint64_t stivale2_term_callback_ptr;
void stivale2_term_callback(uint64_t, uint64_t, uint64_t, uint64_t); void stivale2_term_callback(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
#endif #endif
bool limine_load(char *config, char *cmdline) { bool limine_load(char *config, char *cmdline) {
@ -552,6 +552,7 @@ FEAT_END
struct fb_info fb; struct fb_info fb;
// Terminal feature // Terminal feature
uint64_t *term_fb_ptr = NULL;
FEAT_START FEAT_START
struct limine_terminal_request *terminal_request = get_request(LIMINE_TERMINAL_REQUEST); struct limine_terminal_request *terminal_request = get_request(LIMINE_TERMINAL_REQUEST);
if (terminal_request == NULL) { if (terminal_request == NULL) {
@ -561,6 +562,8 @@ FEAT_START
struct limine_terminal_response *terminal_response = struct limine_terminal_response *terminal_response =
ext_mem_alloc(sizeof(struct limine_terminal_response)); ext_mem_alloc(sizeof(struct limine_terminal_response));
struct limine_terminal *terminal = ext_mem_alloc(sizeof(struct limine_terminal));
quiet = false; quiet = false;
serial = false; serial = false;
@ -579,18 +582,28 @@ FEAT_START
term_callback = (void *)terminal_request->callback; term_callback = (void *)terminal_request->callback;
#endif #endif
term_arg = reported_addr(terminal);
#if defined (__i386__) #if defined (__i386__)
if (stivale2_rt_stack == NULL) { if (stivale2_rt_stack == NULL) {
stivale2_rt_stack = ext_mem_alloc(16384) + 16384; stivale2_rt_stack = ext_mem_alloc(16384) + 16384;
} }
terminal_response->write = (uintptr_t)(void *)stivale2_term_write_entry; terminal->write = (uintptr_t)(void *)stivale2_term_write_entry;
#elif defined (__x86_64__) #elif defined (__x86_64__)
terminal_response->write = (uintptr_t)term_write; terminal->write = (uintptr_t)term_write;
#endif #endif
terminal_response->columns = term_cols; term_fb_ptr = &terminal->framebuffer;
terminal_response->rows = term_rows;
terminal->columns = term_cols;
terminal->rows = term_rows;
uint64_t *term_list = ext_mem_alloc(1 * sizeof(uint64_t));
term_list[0] = reported_addr(terminal);
terminal_response->terminal_count = 1;
terminal_response->terminals = reported_addr(term_list);
terminal_request->response = reported_addr(terminal_response); terminal_request->response = reported_addr(terminal_response);
@ -621,6 +634,10 @@ skip_fb_init:;
// For now we only support 1 framebuffer // For now we only support 1 framebuffer
struct limine_framebuffer *fbp = ext_mem_alloc(sizeof(struct limine_framebuffer)); struct limine_framebuffer *fbp = ext_mem_alloc(sizeof(struct limine_framebuffer));
if (term_fb_ptr != NULL) {
*term_fb_ptr = reported_addr(fbp);
}
struct edid_info_struct *edid_info = get_edid_info(); struct edid_info_struct *edid_info = get_edid_info();
if (edid_info != NULL) { if (edid_info != NULL) {
fbp->edid_size = sizeof(struct edid_info_struct); fbp->edid_size = sizeof(struct edid_info_struct);

View File

@ -73,7 +73,7 @@
extern symbol stivale2_term_write_entry; extern symbol stivale2_term_write_entry;
void *stivale2_rt_stack = NULL; void *stivale2_rt_stack = NULL;
uint64_t stivale2_term_callback_ptr = 0; uint64_t stivale2_term_callback_ptr = 0;
void stivale2_term_callback(uint64_t, uint64_t, uint64_t, uint64_t); void stivale2_term_callback(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
#endif #endif
bool stivale2_load(char *config, char *cmdline) { bool stivale2_load(char *config, char *cmdline) {

View File

@ -43,6 +43,7 @@ bits 64
mov rsi, [rbp + 16] mov rsi, [rbp + 16]
mov rdx, [rbp + 24] mov rdx, [rbp + 24]
mov rcx, [rbp + 32] mov rcx, [rbp + 32]
mov r8, [rbp + 40]
call .get_got call .get_got
.get_got: .get_got:

View File

@ -39,6 +39,7 @@ bits 64
mov rsi, [rbp + 16] mov rsi, [rbp + 16]
mov rdx, [rbp + 24] mov rdx, [rbp + 24]
mov rcx, [rbp + 32] mov rcx, [rbp + 32]
mov r8, [rbp + 40]
mov rbx, rsp mov rbx, rsp
mov rsp, [user_stack] mov rsp, [user_stack]

View File

@ -169,14 +169,22 @@ struct limine_framebuffer_request {
#define LIMINE_TERMINAL_CTX_RESTORE ((uint64_t)(-3)) #define LIMINE_TERMINAL_CTX_RESTORE ((uint64_t)(-3))
#define LIMINE_TERMINAL_FULL_REFRESH ((uint64_t)(-4)) #define LIMINE_TERMINAL_FULL_REFRESH ((uint64_t)(-4))
struct limine_terminal;
typedef void (*limine_terminal_write)(const char *, uint64_t); typedef void (*limine_terminal_write)(const char *, uint64_t);
typedef void (*limine_terminal_callback)(uint64_t, uint64_t, uint64_t, uint64_t); typedef void (*limine_terminal_callback)(struct limine_terminal *, uint64_t, uint64_t, uint64_t, uint64_t);
struct limine_terminal {
uint32_t columns;
uint32_t rows;
LIMINE_PTR(struct limine_framebuffer *) framebuffer;
LIMINE_PTR(limine_terminal_write) write;
};
struct limine_terminal_response { struct limine_terminal_response {
uint64_t revision; uint64_t revision;
uint32_t columns; uint64_t terminal_count;
uint32_t rows; LIMINE_PTR(struct limine_terminal **) terminals;
LIMINE_PTR(limine_terminal_write) write;
}; };
struct limine_terminal_request { struct limine_terminal_request {

View File

@ -138,7 +138,7 @@ extern char kernel_start[];
static void limine_main(void) { static void limine_main(void) {
if (_terminal_request.response) { if (_terminal_request.response) {
stivale2_print = _terminal_request.response->write; stivale2_print = _terminal_request.response->terminals[0]->write;
} }
e9_printf("\nWe're alive"); e9_printf("\nWe're alive");
@ -323,9 +323,14 @@ FEAT_START
} }
struct limine_terminal_response *term_response = _terminal_request.response; struct limine_terminal_response *term_response = _terminal_request.response;
e9_printf("Terminal feature, revision %d", term_response->revision); e9_printf("Terminal feature, revision %d", term_response->revision);
e9_printf("Columns: %d", term_response->columns); e9_printf("%d terminal(s)", term_response->terminal_count);
e9_printf("Rows: %d", term_response->rows); for (size_t i = 0; i < term_response->terminal_count; i++) {
e9_printf("Write function at: %x", term_response->write); struct limine_terminal *terminal = term_response->terminals[i];
e9_printf("Columns: %d", terminal->columns);
e9_printf("Rows: %d", terminal->rows);
e9_printf("Using framebuffer: %x", terminal->framebuffer);
e9_printf("Write function at: %x", terminal->write);
}
FEAT_END FEAT_END
for (;;); for (;;);