:: commit fb175747df63944bf9e49804f9ac5a71c36e5783

mintsuki <mintsuki@protonmail.com> — 2022-04-01 08:00

parents: 15f111f5c3

limine: Add support for multiple terminals and properly document it

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,16 +169,24 @@ 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_response {
-    uint64_t revision;
+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;
+    uint64_t terminal_count;
+    LIMINE_PTR(struct limine_terminal **) terminals;
+};
+
 struct limine_terminal_request {
     uint64_t id[4];
     uint64_t revision;
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 (;;);
tab: 248 wrap: offon