fix: kernel concurrency, interrupt context, and user VA safety
This commit is contained in:
@@ -192,6 +192,7 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_SDR_READ = 146; // (handle, buf, len) -> bytes
|
||||
static constexpr uint64_t SYS_SDR_SETPARAM = 147; // (handle, param, value)
|
||||
static constexpr uint64_t SYS_SDR_GETPARAM = 148; // (handle, param) -> value
|
||||
static constexpr uint64_t SYS_FBFLIP = 150;
|
||||
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
||||
static constexpr uint64_t SYS_DISPLAYINFO = 154;
|
||||
static constexpr uint64_t SYS_DISPLAYMODES = 155;
|
||||
|
||||
@@ -8,16 +8,21 @@
|
||||
#include <cstdint>
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/config.h>
|
||||
#include "gui/gui.hpp"
|
||||
|
||||
namespace gui {
|
||||
|
||||
class Framebuffer {
|
||||
uint32_t* hw_fb;
|
||||
uint32_t* hw_fb2; // second scanout buffer (page flip), nullptr if unsupported
|
||||
uint32_t* hw_direct; // buffer known to be live after page-flip fallback
|
||||
uint32_t* hw_mirror; // second possible live buffer when SURFLIVE is unavailable
|
||||
uint32_t* back_buf;
|
||||
int fb_width;
|
||||
int fb_height;
|
||||
int fb_pitch; // in bytes
|
||||
int hw_next; // scanout buffer the next flip() will present (page flip only)
|
||||
|
||||
static inline void fill_pixels(uint32_t* dst, int count, uint32_t pixel) {
|
||||
if (!dst || count <= 0) return;
|
||||
@@ -59,8 +64,24 @@ class Framebuffer {
|
||||
return 0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
||||
}
|
||||
|
||||
static bool prefer_hardware_page_flip() {
|
||||
char user[64] = {};
|
||||
if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) {
|
||||
auto doc = montauk::config::load_user(user, "display");
|
||||
bool enabled = doc.get_bool("graphics.tear_free", false);
|
||||
doc.destroy();
|
||||
return enabled;
|
||||
}
|
||||
auto doc = montauk::config::load("display");
|
||||
bool enabled = doc.get_bool("graphics.tear_free", false);
|
||||
doc.destroy();
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public:
|
||||
Framebuffer() : hw_fb(nullptr), back_buf(nullptr), fb_width(0), fb_height(0), fb_pitch(0) {
|
||||
Framebuffer() : hw_fb(nullptr), hw_fb2(nullptr), hw_direct(nullptr), hw_mirror(nullptr),
|
||||
back_buf(nullptr),
|
||||
fb_width(0), fb_height(0), fb_pitch(0), hw_next(1) {
|
||||
montauk::abi::FbInfo info;
|
||||
montauk::fb_info(&info);
|
||||
|
||||
@@ -69,7 +90,40 @@ public:
|
||||
fb_pitch = (int)info.pitch;
|
||||
|
||||
hw_fb = (uint32_t*)montauk::fb_map();
|
||||
hw_direct = hw_fb;
|
||||
back_buf = (uint32_t*)montauk::alloc((uint64_t)fb_height * fb_pitch);
|
||||
|
||||
// Hardware page flipping: fb_map() maps the second scanout buffer
|
||||
// directly after the first when the kernel supports flipping. Acquire
|
||||
// the scanout before choosing an off-screen buffer: another fullscreen
|
||||
// process (notably login.elf) may have left either buffer live.
|
||||
bool flip_supported = hw_fb && montauk::fb_flip(-1, 0) == 1;
|
||||
uint64_t pages = ((uint64_t)fb_height * fb_pitch + 0xFFF) / 0x1000;
|
||||
uint32_t* second = flip_supported
|
||||
? (uint32_t*)((uint8_t*)hw_fb + pages * 0x1000) : nullptr;
|
||||
if (flip_supported && prefer_hardware_page_flip()) {
|
||||
int64_t front = montauk::fb_flip(-2, 0);
|
||||
if (front == 0 || front == 1) {
|
||||
hw_fb2 = second;
|
||||
hw_next = (int)front ^ 1;
|
||||
} else {
|
||||
// If the live register cannot be trusted even at startup,
|
||||
// avoid flipping and update both possible scanout surfaces.
|
||||
hw_mirror = second;
|
||||
}
|
||||
} else if (flip_supported) {
|
||||
// A direct-copy client must make buffer 0 live before it starts
|
||||
// drawing there; the previous fullscreen client may have left
|
||||
// buffer 1 scanning out.
|
||||
if (montauk::fb_flip(0, 1) != 0) {
|
||||
int64_t front = montauk::fb_flip(-2, 0);
|
||||
if (front == 1) {
|
||||
hw_direct = second;
|
||||
} else if (front != 0) {
|
||||
hw_mirror = second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int width() const { return fb_width; }
|
||||
@@ -250,12 +304,63 @@ public:
|
||||
inline void flip() {
|
||||
if (!hw_fb || !back_buf) return;
|
||||
|
||||
// With hardware page flipping, copy into the scanout buffer that is
|
||||
// currently OFF screen, then ask the display engine to present it at
|
||||
// the next vblank (tear-free). Without it, fall back to copying
|
||||
// straight into the visible framebuffer.
|
||||
uint32_t* dst_fb = hw_direct;
|
||||
if (hw_fb2) dst_fb = (hw_next == 1) ? hw_fb2 : hw_fb;
|
||||
|
||||
// Copy back buffer to hardware framebuffer, row by row (pitch may differ)
|
||||
uint64_t row_bytes = (uint64_t)fb_width * sizeof(uint32_t);
|
||||
for (int y = 0; y < fb_height; y++) {
|
||||
uint32_t* src = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
|
||||
uint32_t* dst = (uint32_t*)((uint8_t*)hw_fb + y * fb_pitch);
|
||||
uint32_t* dst = (uint32_t*)((uint8_t*)dst_fb + y * fb_pitch);
|
||||
montauk::memcpy(dst, src, row_bytes);
|
||||
if (hw_mirror) {
|
||||
uint32_t* mirror = (uint32_t*)((uint8_t*)hw_mirror + y * fb_pitch);
|
||||
montauk::memcpy(mirror, src, row_bytes);
|
||||
}
|
||||
}
|
||||
if (hw_mirror) asm volatile("sfence" ::: "memory");
|
||||
|
||||
if (hw_fb2) {
|
||||
// Wait for the latch (vsync): the buffer we just left becomes
|
||||
// safe to draw into only once the new one is actually scanning.
|
||||
asm volatile("sfence" ::: "memory");
|
||||
int64_t presented = montauk::fb_flip(hw_next, 1);
|
||||
if (presented == hw_next) {
|
||||
hw_next ^= 1;
|
||||
} else {
|
||||
// Do not retry a faulty page-flip path forever: that turns a
|
||||
// transient latch failure into an apparently frozen desktop.
|
||||
// Keep drawing directly into whichever buffer hardware says
|
||||
// is live for the rest of this process. If SURFLIVE itself is
|
||||
// unavailable, request buffer 0 without another blocking wait.
|
||||
int64_t front = montauk::fb_flip(-2, 0);
|
||||
if (front == 0 || front == 1) {
|
||||
hw_direct = (front == 1) ? hw_fb2 : hw_fb;
|
||||
} else {
|
||||
(void)montauk::fb_flip(0, 0);
|
||||
hw_direct = hw_fb;
|
||||
hw_mirror = hw_fb2;
|
||||
}
|
||||
hw_fb2 = nullptr;
|
||||
|
||||
// The frame above was copied to the requested (possibly
|
||||
// off-screen) buffer. Publish it once more to the live direct
|
||||
// buffer so the cursor and desktop resume immediately.
|
||||
for (int y = 0; y < fb_height; y++) {
|
||||
uint32_t* src = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
|
||||
uint32_t* dst = (uint32_t*)((uint8_t*)hw_direct + y * fb_pitch);
|
||||
montauk::memcpy(dst, src, row_bytes);
|
||||
if (hw_mirror) {
|
||||
uint32_t* mirror = (uint32_t*)((uint8_t*)hw_mirror + y * fb_pitch);
|
||||
montauk::memcpy(mirror, src, row_bytes);
|
||||
}
|
||||
}
|
||||
asm volatile("sfence" ::: "memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -326,6 +326,11 @@ namespace montauk {
|
||||
// Framebuffer
|
||||
inline void fb_info(montauk::abi::FbInfo* info) { syscall1(montauk::abi::SYS_FBINFO, (uint64_t)info); }
|
||||
inline void* fb_map() { return (void*)syscall0(montauk::abi::SYS_FBMAP); }
|
||||
// Page flip between two scanout buffers. index -1 queries support;
|
||||
// index -2 acquires ownership and returns the live front buffer.
|
||||
inline int64_t fb_flip(int64_t index, uint64_t flags) {
|
||||
return syscall2(montauk::abi::SYS_FBFLIP, (uint64_t)index, flags);
|
||||
}
|
||||
inline int display_info(montauk::abi::DisplayInfo* out) {
|
||||
return (int)syscall1(montauk::abi::SYS_DISPLAYINFO, (uint64_t)out);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user