diff --git a/PROTOCOL.md b/PROTOCOL.md index 16a65e54..0a79d272 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -19,6 +19,8 @@ languages. 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. +The calling convention matches the SysV C ABI for the specific architecture. + ### Executable formats The Limine protocol does not enforce any specific executable format, but @@ -306,7 +308,7 @@ ID: Request: ```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 { uint64_t id[4]; @@ -320,22 +322,201 @@ struct limine_terminal_request { Response: ```c -typedef void (*limine_terminal_write)(const char *, uint64_t); - struct limine_terminal_response { 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 rows; + struct limine_framebuffer *framebuffer; limine_terminal_write write; }; ``` * `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. +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 -the terminal service. The terminal is further documented in the stivale2 -specification. +the terminal service. + +#### 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 diff --git a/common/lib/term.c b/common/lib/term.c index 4778befe..717dedae 100644 --- a/common/lib/term.c +++ b/common/lib/term.c @@ -292,7 +292,8 @@ void (*term_context_save)(uint64_t ptr); void (*term_context_restore)(uint64_t ptr); 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; @@ -669,7 +670,11 @@ static void dec_private_parse(uint8_t c) { } 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) { - 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) { - 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; case 'c': 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; case 'd': @@ -849,19 +866,31 @@ static void control_sequence_parse(uint8_t c) { switch (esc_values[0]) { case 5: 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; case 6: 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; case 'q': 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; case 'J': @@ -1066,7 +1095,11 @@ is_csi: break; case 'Z': 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; case '(': @@ -1185,7 +1218,11 @@ void term_putchar(uint8_t c) { case '\a': // The bell is handled by the kernel 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; case 14: diff --git a/common/lib/term.h b/common/lib/term.h index c20547ba..bff8fc5d 100644 --- a/common/lib/term.h +++ b/common/lib/term.h @@ -103,7 +103,8 @@ extern void (*term_full_refresh)(void); #define TERM_CTX_RESTORE ((uint64_t)(-3)) #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; diff --git a/common/protos/limine.c b/common/protos/limine.c index d17160a0..0cd99fda 100644 --- a/common/protos/limine.c +++ b/common/protos/limine.c @@ -108,7 +108,7 @@ static void *_get_request(uint64_t id[4]) { extern symbol stivale2_term_write_entry; extern void *stivale2_rt_stack; 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 bool limine_load(char *config, char *cmdline) { @@ -552,6 +552,7 @@ FEAT_END struct fb_info fb; // Terminal feature + uint64_t *term_fb_ptr = NULL; FEAT_START struct limine_terminal_request *terminal_request = get_request(LIMINE_TERMINAL_REQUEST); if (terminal_request == NULL) { @@ -561,6 +562,8 @@ FEAT_START struct limine_terminal_response *terminal_response = ext_mem_alloc(sizeof(struct limine_terminal_response)); + struct limine_terminal *terminal = ext_mem_alloc(sizeof(struct limine_terminal)); + quiet = false; serial = false; @@ -579,18 +582,28 @@ FEAT_START term_callback = (void *)terminal_request->callback; #endif + term_arg = reported_addr(terminal); + #if defined (__i386__) if (stivale2_rt_stack == NULL) { 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__) - terminal_response->write = (uintptr_t)term_write; + terminal->write = (uintptr_t)term_write; #endif - terminal_response->columns = term_cols; - terminal_response->rows = term_rows; + term_fb_ptr = &terminal->framebuffer; + + 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); @@ -621,6 +634,10 @@ skip_fb_init:; // For now we only support 1 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(); if (edid_info != NULL) { fbp->edid_size = sizeof(struct edid_info_struct); diff --git a/common/protos/stivale2.c b/common/protos/stivale2.c index bc002f09..7fc8c3b8 100644 --- a/common/protos/stivale2.c +++ b/common/protos/stivale2.c @@ -73,7 +73,7 @@ extern symbol stivale2_term_write_entry; void *stivale2_rt_stack = NULL; 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 bool stivale2_load(char *config, char *cmdline) { diff --git a/common/protos/stivale2_rt.asm32u b/common/protos/stivale2_rt.asm32u index 723a92aa..a318f917 100644 --- a/common/protos/stivale2_rt.asm32u +++ b/common/protos/stivale2_rt.asm32u @@ -43,6 +43,7 @@ bits 64 mov rsi, [rbp + 16] mov rdx, [rbp + 24] mov rcx, [rbp + 32] + mov r8, [rbp + 40] call .get_got .get_got: diff --git a/common/protos/stivale2_rt.asmb b/common/protos/stivale2_rt.asmb index b5c05e8b..3f159c53 100644 --- a/common/protos/stivale2_rt.asmb +++ b/common/protos/stivale2_rt.asmb @@ -39,6 +39,7 @@ bits 64 mov rsi, [rbp + 16] mov rdx, [rbp + 24] mov rcx, [rbp + 32] + mov r8, [rbp + 40] mov rbx, rsp mov rsp, [user_stack] diff --git a/limine.h b/limine.h index fd5bff72..5ad7b182 100644 --- a/limine.h +++ b/limine.h @@ -169,14 +169,22 @@ struct limine_framebuffer_request { #define LIMINE_TERMINAL_CTX_RESTORE ((uint64_t)(-3)) #define LIMINE_TERMINAL_FULL_REFRESH ((uint64_t)(-4)) +struct limine_terminal; + 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 { uint64_t revision; - uint32_t columns; - uint32_t rows; - LIMINE_PTR(limine_terminal_write) write; + uint64_t terminal_count; + LIMINE_PTR(struct limine_terminal **) terminals; }; struct limine_terminal_request { diff --git a/test/limine.c b/test/limine.c index dc316e9c..ecdfc6a5 100644 --- a/test/limine.c +++ b/test/limine.c @@ -138,7 +138,7 @@ extern char kernel_start[]; static void limine_main(void) { if (_terminal_request.response) { - stivale2_print = _terminal_request.response->write; + stivale2_print = _terminal_request.response->terminals[0]->write; } e9_printf("\nWe're alive"); @@ -323,9 +323,14 @@ FEAT_START } struct limine_terminal_response *term_response = _terminal_request.response; e9_printf("Terminal feature, revision %d", term_response->revision); - e9_printf("Columns: %d", term_response->columns); - e9_printf("Rows: %d", term_response->rows); - e9_printf("Write function at: %x", term_response->write); + e9_printf("%d terminal(s)", term_response->terminal_count); + for (size_t i = 0; i < term_response->terminal_count; i++) { + 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 for (;;);