feat: disconnect Bluetooth devices, flush disks on shutdown/reboot, display progress in login.elf window
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 57
|
#define MONTAUK_BUILD_NUMBER 64
|
||||||
|
|||||||
@@ -13,8 +13,28 @@
|
|||||||
#include <ACPI/AcpiShutdown.hpp>
|
#include <ACPI/AcpiShutdown.hpp>
|
||||||
#include <ACPI/AcpiSleep.hpp>
|
#include <ACPI/AcpiSleep.hpp>
|
||||||
|
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
namespace Montauk {
|
namespace Montauk {
|
||||||
|
|
||||||
|
// Pending graceful power-off request, set by the desktop and consumed by
|
||||||
|
// login.elf. Kernel-global IPC channel (one outstanding request system-wide).
|
||||||
|
// `inline` so the single definition is shared across translation units.
|
||||||
|
inline int g_pendingPowerAction = POWER_REQ_QUERY;
|
||||||
|
|
||||||
|
// SYS_POWER_REQUEST. action == POWER_REQ_QUERY reads and clears the pending
|
||||||
|
// action; any other value records it as the pending action. Returns the
|
||||||
|
// pending action for queries, or 0 when recording one.
|
||||||
|
static int64_t Sys_PowerRequest(int action) {
|
||||||
|
if (action == POWER_REQ_QUERY) {
|
||||||
|
int pending = g_pendingPowerAction;
|
||||||
|
g_pendingPowerAction = POWER_REQ_QUERY;
|
||||||
|
return (int64_t)pending;
|
||||||
|
}
|
||||||
|
g_pendingPowerAction = action;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void Sys_Reset() {
|
static void Sys_Reset() {
|
||||||
if (Efi::g_ResetSystem) {
|
if (Efi::g_ResetSystem) {
|
||||||
/* Switch to kernel PML4 which has identity-mapped UEFI runtime regions */
|
/* Switch to kernel PML4 which has identity-mapped UEFI runtime regions */
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ namespace Montauk {
|
|||||||
return (int64_t)Fs::FsProbe::MountPartition(partIndex, driveNum);
|
return (int64_t)Fs::FsProbe::MountPartition(partIndex, driveNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush all block-device write caches and cleanly unmount disk-backed
|
||||||
|
// volumes ahead of power-off. Returns the number of volumes unmounted.
|
||||||
|
static int64_t Sys_FsSync() {
|
||||||
|
return (int64_t)Fs::FsProbe::SyncAndUnmountAll();
|
||||||
|
}
|
||||||
|
|
||||||
// Format a partition with a filesystem. Returns 0 on success, -1 on error.
|
// Format a partition with a filesystem. Returns 0 on success, -1 on error.
|
||||||
static int64_t Sys_FsFormat(const FsFormatParams* params) {
|
static int64_t Sys_FsFormat(const FsFormatParams* params) {
|
||||||
if (params == nullptr) return -1;
|
if (params == nullptr) return -1;
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ namespace Montauk {
|
|||||||
case SYS_SHUTDOWN:
|
case SYS_SHUTDOWN:
|
||||||
Sys_Shutdown();
|
Sys_Shutdown();
|
||||||
return 0;
|
return 0;
|
||||||
|
case SYS_POWER_REQUEST:
|
||||||
|
return Sys_PowerRequest((int)frame->arg1);
|
||||||
case SYS_GETTIME:
|
case SYS_GETTIME:
|
||||||
if (!UserMemory::Writable<DateTime>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<DateTime>(frame->arg1)) return -1;
|
||||||
Sys_GetTime((DateTime*)frame->arg1);
|
Sys_GetTime((DateTime*)frame->arg1);
|
||||||
@@ -322,6 +324,8 @@ namespace Montauk {
|
|||||||
return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1);
|
return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1);
|
||||||
case SYS_FSMOUNT:
|
case SYS_FSMOUNT:
|
||||||
return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2);
|
||||||
|
case SYS_FS_SYNC:
|
||||||
|
return Sys_FsSync();
|
||||||
case SYS_FSFORMAT:
|
case SYS_FSFORMAT:
|
||||||
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return -1;
|
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return -1;
|
||||||
return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1);
|
return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1);
|
||||||
|
|||||||
@@ -249,6 +249,21 @@ namespace Montauk {
|
|||||||
static constexpr uint64_t SYS_THREAD_JOIN = 132;
|
static constexpr uint64_t SYS_THREAD_JOIN = 132;
|
||||||
static constexpr uint64_t SYS_THREAD_SELF = 133;
|
static constexpr uint64_t SYS_THREAD_SELF = 133;
|
||||||
|
|
||||||
|
/* Storage.hpp -- flush + unmount persistent volumes for power-off */
|
||||||
|
static constexpr uint64_t SYS_FS_SYNC = 134;
|
||||||
|
|
||||||
|
/* Power.hpp -- cross-process graceful power-off request channel */
|
||||||
|
static constexpr uint64_t SYS_POWER_REQUEST = 135;
|
||||||
|
|
||||||
|
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
|
||||||
|
// a pending action and exits; login.elf reads it, runs the shutdown stages,
|
||||||
|
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
|
||||||
|
enum PowerRequestAction : int {
|
||||||
|
POWER_REQ_QUERY = 0, // read-and-clear the pending action
|
||||||
|
POWER_REQ_SHUTDOWN = 1,
|
||||||
|
POWER_REQ_REBOOT = 2,
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
||||||
|
|
||||||
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
|
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
|
||||||
|
|||||||
@@ -745,6 +745,9 @@ namespace Drivers::Storage::Ahci {
|
|||||||
bdev.WriteSectors = [](void* ctx, uint64_t lba, uint32_t count, const void* buffer) -> bool {
|
bdev.WriteSectors = [](void* ctx, uint64_t lba, uint32_t count, const void* buffer) -> bool {
|
||||||
return WriteSectors((int)(uintptr_t)ctx, lba, count, buffer);
|
return WriteSectors((int)(uintptr_t)ctx, lba, count, buffer);
|
||||||
};
|
};
|
||||||
|
bdev.Flush = [](void* ctx) -> bool {
|
||||||
|
return FlushCache((int)(uintptr_t)ctx);
|
||||||
|
};
|
||||||
bdev.Ctx = (void*)(uintptr_t)i;
|
bdev.Ctx = (void*)(uintptr_t)i;
|
||||||
bdev.SectorCount = g_ports[i].SectorCount;
|
bdev.SectorCount = g_ports[i].SectorCount;
|
||||||
bdev.SectorSize = g_ports[i].SectorSizeLog;
|
bdev.SectorSize = g_ports[i].SectorSizeLog;
|
||||||
@@ -826,6 +829,37 @@ namespace Drivers::Storage::Ahci {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FlushCache(int port) {
|
||||||
|
if (!g_initialized || port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int slot = FindFreeSlot(port);
|
||||||
|
if (slot < 0) {
|
||||||
|
KernelLogStream(ERROR, "AHCI") << "FlushCache: no free command slot";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandHeader* hdr = &g_ports[port].CmdList[slot];
|
||||||
|
CommandTable* tbl = g_ports[port].CmdTables[slot];
|
||||||
|
|
||||||
|
// FLUSH CACHE EXT is a non-data command: no PRDT, no transfer direction.
|
||||||
|
memset(tbl, 0, sizeof(CommandTable) + sizeof(PrdtEntry) * MAX_PRDT_ENTRIES);
|
||||||
|
|
||||||
|
FisRegH2D* fis = (FisRegH2D*)tbl->CommandFis;
|
||||||
|
fis->FisType = (uint8_t)FisType::RegH2D;
|
||||||
|
fis->CmdCtl = 1; // Command
|
||||||
|
fis->Command = ATA_CMD_FLUSH_CACHE_EX;
|
||||||
|
fis->Device = (1 << 6); // LBA mode
|
||||||
|
|
||||||
|
hdr->CflPmpA = 5; // CFL = FIS length in dwords (FisRegH2D = 20 bytes)
|
||||||
|
hdr->Flags = 0; // read direction (no data either way)
|
||||||
|
hdr->PrdtLength = 0;
|
||||||
|
hdr->PrdByteCount = 0;
|
||||||
|
|
||||||
|
return IssueCommand(port, slot);
|
||||||
|
}
|
||||||
|
|
||||||
bool WriteSectors(int port, uint64_t lba, uint32_t count, const void* buffer) {
|
bool WriteSectors(int port, uint64_t lba, uint32_t count, const void* buffer) {
|
||||||
if (!g_initialized || port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
if (!g_initialized || port < 0 || port >= MAX_PORTS || !g_ports[port].Active) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -167,9 +167,10 @@ namespace Drivers::Storage::Ahci {
|
|||||||
// ATA commands
|
// ATA commands
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
constexpr uint8_t ATA_CMD_IDENTIFY = 0xEC;
|
constexpr uint8_t ATA_CMD_IDENTIFY = 0xEC;
|
||||||
constexpr uint8_t ATA_CMD_READ_DMA_EX = 0x25; // READ DMA EXT (48-bit LBA)
|
constexpr uint8_t ATA_CMD_READ_DMA_EX = 0x25; // READ DMA EXT (48-bit LBA)
|
||||||
constexpr uint8_t ATA_CMD_WRITE_DMA_EX = 0x35; // WRITE DMA EXT (48-bit LBA)
|
constexpr uint8_t ATA_CMD_WRITE_DMA_EX = 0x35; // WRITE DMA EXT (48-bit LBA)
|
||||||
|
constexpr uint8_t ATA_CMD_FLUSH_CACHE_EX = 0xEA; // FLUSH CACHE EXT (48-bit)
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Constants
|
// Constants
|
||||||
@@ -252,6 +253,10 @@ namespace Drivers::Storage::Ahci {
|
|||||||
// Write sectors to a SATA device
|
// Write sectors to a SATA device
|
||||||
bool WriteSectors(int port, uint64_t lba, uint32_t count, const void* buffer);
|
bool WriteSectors(int port, uint64_t lba, uint32_t count, const void* buffer);
|
||||||
|
|
||||||
|
// Flush the device's write cache to media (FLUSH CACHE EXT). Returns true on
|
||||||
|
// success. Used during graceful shutdown to make pending writes durable.
|
||||||
|
bool FlushCache(int port);
|
||||||
|
|
||||||
// Get info about a specific port
|
// Get info about a specific port
|
||||||
const PortInfo* GetPortInfo(int port);
|
const PortInfo* GetPortInfo(int port);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ namespace Drivers::Storage {
|
|||||||
struct BlockDevice {
|
struct BlockDevice {
|
||||||
bool (*ReadSectors)(void* ctx, uint64_t lba, uint32_t count, void* buffer);
|
bool (*ReadSectors)(void* ctx, uint64_t lba, uint32_t count, void* buffer);
|
||||||
bool (*WriteSectors)(void* ctx, uint64_t lba, uint32_t count, const void* buffer);
|
bool (*WriteSectors)(void* ctx, uint64_t lba, uint32_t count, const void* buffer);
|
||||||
|
// Flush the device's volatile write cache to non-volatile media. Optional:
|
||||||
|
// a null pointer means the device has no write-back cache to flush (so a
|
||||||
|
// completed WriteSectors is already durable). Returns true on success.
|
||||||
|
bool (*Flush)(void* ctx);
|
||||||
void* Ctx;
|
void* Ctx;
|
||||||
uint64_t SectorCount;
|
uint64_t SectorCount;
|
||||||
uint16_t SectorSize;
|
uint16_t SectorSize;
|
||||||
|
|||||||
@@ -665,6 +665,9 @@ namespace Drivers::Storage::Nvme {
|
|||||||
bdev.WriteSectors = [](void* ctx, uint64_t lba, uint32_t count, const void* buffer) -> bool {
|
bdev.WriteSectors = [](void* ctx, uint64_t lba, uint32_t count, const void* buffer) -> bool {
|
||||||
return WriteSectors((int)(uintptr_t)ctx, lba, count, buffer);
|
return WriteSectors((int)(uintptr_t)ctx, lba, count, buffer);
|
||||||
};
|
};
|
||||||
|
bdev.Flush = [](void* ctx) -> bool {
|
||||||
|
return Flush((int)(uintptr_t)ctx);
|
||||||
|
};
|
||||||
bdev.Ctx = (void*)(uintptr_t)i;
|
bdev.Ctx = (void*)(uintptr_t)i;
|
||||||
bdev.SectorCount = g_namespaces[i].SectorCount;
|
bdev.SectorCount = g_namespaces[i].SectorCount;
|
||||||
bdev.SectorSize = (uint16_t)g_namespaces[i].SectorSize;
|
bdev.SectorSize = (uint16_t)g_namespaces[i].SectorSize;
|
||||||
@@ -770,6 +773,22 @@ namespace Drivers::Storage::Nvme {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Flush(int ns) {
|
||||||
|
if (!g_initialized || ns < 0 || ns >= g_nsCount || !g_namespaces[ns].Active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NVM Flush: commits the namespace's volatile write cache to media.
|
||||||
|
SqEntry cmd = {};
|
||||||
|
cmd.Opcode = IO_CMD_FLUSH;
|
||||||
|
cmd.Nsid = g_namespaces[ns].Nsid;
|
||||||
|
|
||||||
|
SubmitIoCommand(cmd);
|
||||||
|
|
||||||
|
CqEntry cqe;
|
||||||
|
return WaitIoCompletion(cqe);
|
||||||
|
}
|
||||||
|
|
||||||
bool WriteSectors(int ns, uint64_t lba, uint32_t count, const void* buffer) {
|
bool WriteSectors(int ns, uint64_t lba, uint32_t count, const void* buffer) {
|
||||||
if (!g_initialized || ns < 0 || ns >= g_nsCount || !g_namespaces[ns].Active) {
|
if (!g_initialized || ns < 0 || ns >= g_nsCount || !g_namespaces[ns].Active) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ namespace Drivers::Storage::Nvme {
|
|||||||
// NVM I/O opcodes
|
// NVM I/O opcodes
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
|
constexpr uint8_t IO_CMD_FLUSH = 0x00;
|
||||||
constexpr uint8_t IO_CMD_READ = 0x02;
|
constexpr uint8_t IO_CMD_READ = 0x02;
|
||||||
constexpr uint8_t IO_CMD_WRITE = 0x01;
|
constexpr uint8_t IO_CMD_WRITE = 0x01;
|
||||||
|
|
||||||
@@ -187,6 +188,10 @@ namespace Drivers::Storage::Nvme {
|
|||||||
// Write sectors to an NVMe namespace
|
// Write sectors to an NVMe namespace
|
||||||
bool WriteSectors(int ns, uint64_t lba, uint32_t count, const void* buffer);
|
bool WriteSectors(int ns, uint64_t lba, uint32_t count, const void* buffer);
|
||||||
|
|
||||||
|
// Flush the namespace's volatile write cache to media (NVM Flush command).
|
||||||
|
// Returns true on success. Used during graceful shutdown.
|
||||||
|
bool Flush(int ns);
|
||||||
|
|
||||||
// Get info about a specific namespace
|
// Get info about a specific namespace
|
||||||
const NamespaceInfo* GetNamespaceInfo(int ns);
|
const NamespaceInfo* GetNamespaceInfo(int ns);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "FsProbe.hpp"
|
#include "FsProbe.hpp"
|
||||||
#include <Drivers/Storage/Gpt.hpp>
|
#include <Drivers/Storage/Gpt.hpp>
|
||||||
|
#include <Drivers/Storage/BlockDevice.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
|
|
||||||
@@ -195,4 +196,43 @@ namespace Fs::FsProbe {
|
|||||||
return unmounted;
|
return unmounted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SyncAndUnmountAll() {
|
||||||
|
// 1. Flush every block device's volatile write cache to media. FAT32 and
|
||||||
|
// ext2 are write-through, so file data already reached the device; the
|
||||||
|
// flush makes the device commit it to non-volatile storage.
|
||||||
|
int devCount = Drivers::Storage::GetBlockDeviceCount();
|
||||||
|
for (int i = 0; i < devCount; i++) {
|
||||||
|
const auto* dev = Drivers::Storage::GetBlockDevice(i);
|
||||||
|
if (dev && dev->Flush) {
|
||||||
|
dev->Flush(dev->Ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Cleanly unmount every disk-backed VFS drive. The ramdisk has no
|
||||||
|
// backing block device (GetBlockDeviceForDrive < 0) and is left
|
||||||
|
// mounted so the rest of userspace keeps running until power-off.
|
||||||
|
int drives[Vfs::MaxDrives];
|
||||||
|
int n = Vfs::VfsDriveList(drives, Vfs::MaxDrives);
|
||||||
|
int unmounted = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int driveNum = drives[i];
|
||||||
|
if (GetBlockDeviceForDrive(driveNum) < 0) continue;
|
||||||
|
|
||||||
|
if (Vfs::UnregisterDrive(driveNum) == 0) {
|
||||||
|
unmounted++;
|
||||||
|
for (int p = 0; p < Drivers::Storage::Gpt::MaxPartitions; p++) {
|
||||||
|
if (g_mounted[p] && g_driveForPart[p] == driveNum) {
|
||||||
|
g_mounted[p] = false;
|
||||||
|
g_driveForPart[p] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kt::KernelLogStream(Kt::OK, "FsProbe") << "Synced and unmounted "
|
||||||
|
<< unmounted << " disk-backed volume(s) for power-off";
|
||||||
|
return unmounted;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,4 +38,10 @@ namespace Fs::FsProbe {
|
|||||||
// drive isn't backed by a partition (e.g. ramdisk).
|
// drive isn't backed by a partition (e.g. ramdisk).
|
||||||
int GetBlockDeviceForDrive(int driveNum);
|
int GetBlockDeviceForDrive(int driveNum);
|
||||||
|
|
||||||
|
// Prepare persistent storage for power-off: flush every block device's
|
||||||
|
// write cache to media, then cleanly unmount all disk-backed VFS drives.
|
||||||
|
// The ramdisk (which has no backing block device) is left mounted. Returns
|
||||||
|
// the number of disk-backed volumes that were unmounted.
|
||||||
|
int SyncAndUnmountAll();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -173,6 +173,21 @@ namespace Montauk {
|
|||||||
static constexpr uint64_t SYS_THREAD_JOIN = 132;
|
static constexpr uint64_t SYS_THREAD_JOIN = 132;
|
||||||
static constexpr uint64_t SYS_THREAD_SELF = 133;
|
static constexpr uint64_t SYS_THREAD_SELF = 133;
|
||||||
|
|
||||||
|
// Flush + unmount persistent volumes for power-off
|
||||||
|
static constexpr uint64_t SYS_FS_SYNC = 134;
|
||||||
|
|
||||||
|
// Cross-process graceful power-off request channel
|
||||||
|
static constexpr uint64_t SYS_POWER_REQUEST = 135;
|
||||||
|
|
||||||
|
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
|
||||||
|
// a pending action and exits; login.elf reads it, runs the shutdown stages,
|
||||||
|
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
|
||||||
|
enum PowerRequestAction : int {
|
||||||
|
POWER_REQ_QUERY = 0, // read-and-clear the pending action
|
||||||
|
POWER_REQ_SHUTDOWN = 1,
|
||||||
|
POWER_REQ_REBOOT = 2,
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
||||||
|
|
||||||
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
|
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
|
||||||
|
|||||||
@@ -369,6 +369,15 @@ namespace montauk {
|
|||||||
return (int)syscall0(Montauk::SYS_SUSPEND);
|
return (int)syscall0(Montauk::SYS_SUSPEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Graceful power-off request channel. The desktop posts a pending action
|
||||||
|
// (Montauk::POWER_REQ_SHUTDOWN / POWER_REQ_REBOOT) then exits; login.elf
|
||||||
|
// reads it with POWER_REQ_QUERY (read-and-clear), runs the shutdown stages,
|
||||||
|
// and finally calls shutdown()/reset(). Returns the pending action for a
|
||||||
|
// query, or 0 when posting one.
|
||||||
|
inline int power_request(int action) {
|
||||||
|
return (int)syscall1(Montauk::SYS_POWER_REQUEST, (uint64_t)(int64_t)action);
|
||||||
|
}
|
||||||
|
|
||||||
// Mouse
|
// Mouse
|
||||||
inline void mouse_state(Montauk::MouseState* out) { syscall1(Montauk::SYS_MOUSESTATE, (uint64_t)out); }
|
inline void mouse_state(Montauk::MouseState* out) { syscall1(Montauk::SYS_MOUSESTATE, (uint64_t)out); }
|
||||||
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
|
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
|
||||||
@@ -436,6 +445,11 @@ namespace montauk {
|
|||||||
inline int fs_mount(int partIndex, int driveNum) {
|
inline int fs_mount(int partIndex, int driveNum) {
|
||||||
return (int)syscall2(Montauk::SYS_FSMOUNT, (uint64_t)partIndex, (uint64_t)driveNum);
|
return (int)syscall2(Montauk::SYS_FSMOUNT, (uint64_t)partIndex, (uint64_t)driveNum);
|
||||||
}
|
}
|
||||||
|
// Flush all block-device write caches and cleanly unmount disk-backed
|
||||||
|
// volumes ahead of power-off. Returns the number of volumes unmounted.
|
||||||
|
inline int fs_sync() {
|
||||||
|
return (int)syscall0(Montauk::SYS_FS_SYNC);
|
||||||
|
}
|
||||||
inline int fs_format(const Montauk::FsFormatParams* params) {
|
inline int fs_format(const Montauk::FsFormatParams* params) {
|
||||||
return (int)syscall1(Montauk::SYS_FSFORMAT, (uint64_t)params);
|
return (int)syscall1(Montauk::SYS_FSFORMAT, (uint64_t)params);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -226,4 +226,13 @@ void open_reboot_dialog(DesktopState* ds);
|
|||||||
void open_wordprocessor(DesktopState* ds);
|
void open_wordprocessor(DesktopState* ds);
|
||||||
void open_shutdown_dialog(DesktopState* ds);
|
void open_shutdown_dialog(DesktopState* ds);
|
||||||
void open_sleep_dialog(DesktopState* ds);
|
void open_sleep_dialog(DesktopState* ds);
|
||||||
|
|
||||||
|
// Hand a graceful power-off request to login.elf and exit the desktop so the
|
||||||
|
// user returns to the login screen, where the shutdown stages run (Bluetooth
|
||||||
|
// teardown, filesystem flush) before the final ACPI power-off / reset. Pass
|
||||||
|
// Montauk::POWER_REQ_SHUTDOWN or Montauk::POWER_REQ_REBOOT.
|
||||||
|
[[noreturn]] inline void desktop_request_power(int action) {
|
||||||
|
montauk::power_request(action);
|
||||||
|
montauk::exit(0);
|
||||||
|
}
|
||||||
bool desktop_poll_external_windows(DesktopState* ds);
|
bool desktop_poll_external_windows(DesktopState* ds);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ static void reboot_dialog_on_mouse(Window* win, MouseEvent& ev) {
|
|||||||
rs->hover_cancel = cb.contains(lx, ly);
|
rs->hover_cancel = cb.contains(lx, ly);
|
||||||
|
|
||||||
if (ev.left_pressed()) {
|
if (ev.left_pressed()) {
|
||||||
if (rs->hover_reboot) montauk::reset();
|
if (rs->hover_reboot) desktop_request_power(Montauk::POWER_REQ_REBOOT);
|
||||||
if (rs->hover_cancel) {
|
if (rs->hover_cancel) {
|
||||||
for (int i = 0; i < rs->ds->window_count; i++) {
|
for (int i = 0; i < rs->ds->window_count; i++) {
|
||||||
if (rs->ds->windows[i].app_data == rs) {
|
if (rs->ds->windows[i].app_data == rs) {
|
||||||
@@ -86,7 +86,7 @@ static void reboot_dialog_on_key(Window* win, const Montauk::KeyEvent& key) {
|
|||||||
if (!rs || !key.pressed) return;
|
if (!rs || !key.pressed) return;
|
||||||
|
|
||||||
if (key.ascii == '\n' || key.ascii == '\r') {
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||||
montauk::reset();
|
desktop_request_power(Montauk::POWER_REQ_REBOOT);
|
||||||
}
|
}
|
||||||
if (key.scancode == 0x01) { // Escape
|
if (key.scancode == 0x01) { // Escape
|
||||||
for (int i = 0; i < rs->ds->window_count; i++) {
|
for (int i = 0; i < rs->ds->window_count; i++) {
|
||||||
@@ -181,7 +181,7 @@ static void shutdown_dialog_on_mouse(Window* win, MouseEvent& ev) {
|
|||||||
ss->hover_cancel = cb.contains(lx, ly);
|
ss->hover_cancel = cb.contains(lx, ly);
|
||||||
|
|
||||||
if (ev.left_pressed()) {
|
if (ev.left_pressed()) {
|
||||||
if (ss->hover_shutdown) montauk::shutdown();
|
if (ss->hover_shutdown) desktop_request_power(Montauk::POWER_REQ_SHUTDOWN);
|
||||||
if (ss->hover_cancel) {
|
if (ss->hover_cancel) {
|
||||||
for (int i = 0; i < ss->ds->window_count; i++) {
|
for (int i = 0; i < ss->ds->window_count; i++) {
|
||||||
if (ss->ds->windows[i].app_data == ss) {
|
if (ss->ds->windows[i].app_data == ss) {
|
||||||
@@ -198,7 +198,7 @@ static void shutdown_dialog_on_key(Window* win, const Montauk::KeyEvent& key) {
|
|||||||
if (!ss || !key.pressed) return;
|
if (!ss || !key.pressed) return;
|
||||||
|
|
||||||
if (key.ascii == '\n' || key.ascii == '\r') {
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||||
montauk::shutdown();
|
desktop_request_power(Montauk::POWER_REQ_SHUTDOWN);
|
||||||
}
|
}
|
||||||
if (key.scancode == 0x01) { // Escape
|
if (key.scancode == 0x01) { // Escape
|
||||||
for (int i = 0; i < ss->ds->window_count; i++) {
|
for (int i = 0; i < ss->ds->window_count; i++) {
|
||||||
|
|||||||
@@ -339,11 +339,11 @@ static void launcher_activate_selected(DesktopState* ds) {
|
|||||||
break;
|
break;
|
||||||
case LAUNCHER_ITEM_COMMAND_REBOOT:
|
case LAUNCHER_ITEM_COMMAND_REBOOT:
|
||||||
desktop_close_launcher(ds);
|
desktop_close_launcher(ds);
|
||||||
montauk::reset();
|
desktop_request_power(Montauk::POWER_REQ_REBOOT);
|
||||||
return;
|
return;
|
||||||
case LAUNCHER_ITEM_COMMAND_SHUTDOWN:
|
case LAUNCHER_ITEM_COMMAND_SHUTDOWN:
|
||||||
desktop_close_launcher(ds);
|
desktop_close_launcher(ds);
|
||||||
montauk::shutdown();
|
desktop_request_power(Montauk::POWER_REQ_SHUTDOWN);
|
||||||
return;
|
return;
|
||||||
case LAUNCHER_ITEM_SPECIAL_FOLDER:
|
case LAUNCHER_ITEM_SPECIAL_FOLDER:
|
||||||
if (item->path[0] != '\0') {
|
if (item->path[0] != '\0') {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ SRCS := \
|
|||||||
login_wallpaper.cpp \
|
login_wallpaper.cpp \
|
||||||
login_render.cpp \
|
login_render.cpp \
|
||||||
login_input.cpp \
|
login_input.cpp \
|
||||||
|
login_shutdown.cpp \
|
||||||
stb_truetype_impl.cpp \
|
stb_truetype_impl.cpp \
|
||||||
font_data.cpp
|
font_data.cpp
|
||||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||||
|
|||||||
@@ -146,3 +146,10 @@ bool try_first_boot_submit(LoginState* ls);
|
|||||||
bool try_login(LoginState* ls);
|
bool try_login(LoginState* ls);
|
||||||
void handle_key(LoginState* ls, const Montauk::KeyEvent& key);
|
void handle_key(LoginState* ls, const Montauk::KeyEvent& key);
|
||||||
void handle_mouse(LoginState* ls);
|
void handle_mouse(LoginState* ls);
|
||||||
|
|
||||||
|
// Graceful shutdown view (login_shutdown.cpp). draw_shutdown_screen paints the
|
||||||
|
// status card; perform_graceful_shutdown runs the staged power-off (Bluetooth
|
||||||
|
// teardown, filesystem flush) and finally issues the ACPI power-off / reset, so
|
||||||
|
// it never returns. `action` is Montauk::POWER_REQ_SHUTDOWN or POWER_REQ_REBOOT.
|
||||||
|
void draw_shutdown_screen(LoginState* ls, const char* heading, const char* status);
|
||||||
|
[[noreturn]] void perform_graceful_shutdown(LoginState* ls, int action);
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ void launch_desktop_session(LoginState* ls) {
|
|||||||
montauk::setuser(pid, ls->username);
|
montauk::setuser(pid, ls->username);
|
||||||
montauk::waitpid(pid);
|
montauk::waitpid(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The desktop hands off a graceful power-off by posting a request and
|
||||||
|
// exiting (see desktop_request_power). Pick it up now that waitpid has
|
||||||
|
// returned and run the shutdown stages; perform_graceful_shutdown never
|
||||||
|
// returns. A normal logout leaves no request, so we fall through.
|
||||||
|
int req = montauk::power_request(Montauk::POWER_REQ_QUERY);
|
||||||
|
if (req == Montauk::POWER_REQ_SHUTDOWN || req == Montauk::POWER_REQ_REBOOT) {
|
||||||
|
perform_graceful_shutdown(ls, req);
|
||||||
|
}
|
||||||
|
|
||||||
finish_desktop_session(ls);
|
finish_desktop_session(ls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +220,10 @@ void handle_mouse(LoginState* ls) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (lo.shutdown_button.contains(mx, my)) {
|
} else if (lo.shutdown_button.contains(mx, my)) {
|
||||||
montauk::shutdown();
|
perform_graceful_shutdown(ls, Montauk::POWER_REQ_SHUTDOWN);
|
||||||
return;
|
return;
|
||||||
} else if (lo.reboot_button.contains(mx, my)) {
|
} else if (lo.reboot_button.contains(mx, my)) {
|
||||||
montauk::reset();
|
perform_graceful_shutdown(ls, Montauk::POWER_REQ_REBOOT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* login_shutdown.cpp
|
||||||
|
* Graceful shutdown view and staged power-off for the MontaukOS login screen
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "login.hpp"
|
||||||
|
#include <montauk/thread.h>
|
||||||
|
|
||||||
|
using namespace gui;
|
||||||
|
|
||||||
|
// Minimum time each shutdown stage stays on screen, so the status message is
|
||||||
|
// readable even when the underlying work completes near-instantly.
|
||||||
|
static constexpr uint64_t STAGE_MIN_VISIBLE_MS = 600;
|
||||||
|
|
||||||
|
// Per-stage watchdog budgets. Each stage's blocking work runs on a disposable
|
||||||
|
// worker thread; if it overruns its budget we abandon the worker and move on,
|
||||||
|
// so an unresponsive device can never wedge the power-off.
|
||||||
|
// - Bluetooth: a disconnect is normally sub-second; 4 s tolerates a slow
|
||||||
|
// controller. Bailing here is safe -- it does not risk data.
|
||||||
|
// - Filesystem flush: kept generous (10 s) so a legitimately slow flush on
|
||||||
|
// real hardware is never cut short and writes are not lost; only a truly
|
||||||
|
// wedged storage controller hits this bound.
|
||||||
|
static constexpr uint64_t BT_STAGE_TIMEOUT_MS = 4000;
|
||||||
|
static constexpr uint64_t FS_STAGE_TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
|
void draw_shutdown_screen(LoginState* ls, const char* heading, const char* status) {
|
||||||
|
Framebuffer& fb = ls->fb;
|
||||||
|
int sw = ls->screen_w;
|
||||||
|
int sh = ls->screen_h;
|
||||||
|
|
||||||
|
// Same packed/scratch handling as draw_login_screen: Canvas assumes a
|
||||||
|
// tightly packed buffer, so compose into the scratch buffer when the
|
||||||
|
// framebuffer pitch is not width*4.
|
||||||
|
bool packed = fb.pitch() == sw * (int)sizeof(uint32_t);
|
||||||
|
uint32_t* target = packed ? fb.buffer() : ls->compose;
|
||||||
|
if (!target) return;
|
||||||
|
Canvas c(target, sw, sh);
|
||||||
|
|
||||||
|
const mtk::Theme& th = ls->theme;
|
||||||
|
int sfh = system_font_height();
|
||||||
|
|
||||||
|
// ==== Layout (mirrors the login/setup card chrome, minus the footer) ====
|
||||||
|
// Titlebar holds the app title; the body holds a heading + a status line,
|
||||||
|
// matching the login/setup heading + subtitle. The card is sized to fit the
|
||||||
|
// content with symmetric padding -- no empty footer band.
|
||||||
|
const char* window_title = "MontaukOS";
|
||||||
|
int content_x = CONTENT_PAD_X;
|
||||||
|
|
||||||
|
int y = TITLEBAR_H + CONTENT_TOP_PAD;
|
||||||
|
int heading_y = y;
|
||||||
|
y += sfh + 6;
|
||||||
|
int status_y = y;
|
||||||
|
y += sfh;
|
||||||
|
|
||||||
|
int card_h = y + CONTENT_TOP_PAD + 10;
|
||||||
|
int card_x = (sw - CARD_W) / 2;
|
||||||
|
int card_y = (sh - card_h) / 2;
|
||||||
|
|
||||||
|
Rect card = {card_x, card_y, CARD_W, card_h};
|
||||||
|
Rect titlebar = {card_x, card_y, CARD_W, TITLEBAR_H};
|
||||||
|
|
||||||
|
// ==== Background ====
|
||||||
|
if (ls->has_wallpaper && ls->bg_wallpaper) {
|
||||||
|
montauk::memcpy(target, ls->bg_wallpaper,
|
||||||
|
(uint64_t)sw * sh * sizeof(uint32_t));
|
||||||
|
} else {
|
||||||
|
c.fill(BG_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Card ====
|
||||||
|
int so = 4; // drop-shadow offset
|
||||||
|
c.fill_rect_alpha(card.x + so, card.y + card.h, card.w, so, colors::SHADOW);
|
||||||
|
c.fill_rect_alpha(card.x + card.w, card.y + so, so, card.h, colors::SHADOW);
|
||||||
|
c.fill_rect_alpha(card.x + card.w, card.y + card.h, so, so, colors::SHADOW);
|
||||||
|
|
||||||
|
c.fill_rect(card.x, card.y, card.w, card.h, CARD_BG);
|
||||||
|
c.fill_rect(titlebar.x, titlebar.y, titlebar.w, titlebar.h, TITLEBAR_BG);
|
||||||
|
c.rect(card.x, card.y, card.w, card.h, CARD_BORDER);
|
||||||
|
c.hline(titlebar.x, titlebar.y + titlebar.h - 1, titlebar.w, CARD_BORDER);
|
||||||
|
|
||||||
|
// ==== Title + heading + status ====
|
||||||
|
int window_tw = text_width(window_title);
|
||||||
|
c.text(titlebar.x + (titlebar.w - window_tw) / 2,
|
||||||
|
titlebar.y + (TITLEBAR_H - sfh) / 2, window_title, th.text);
|
||||||
|
c.text(card_x + content_x, card_y + heading_y, heading, th.text);
|
||||||
|
c.text(card_x + content_x, card_y + status_y, status, th.text_subtle);
|
||||||
|
|
||||||
|
// ==== Present ====
|
||||||
|
if (!packed) fb.copy_from(ls->compose, sw * (int)sizeof(uint32_t));
|
||||||
|
fb.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a stage message, hold it on screen for a moment, then return so the
|
||||||
|
// caller can perform the stage's work.
|
||||||
|
static void show_stage(LoginState* ls, const char* heading, const char* status) {
|
||||||
|
draw_shutdown_screen(ls, heading, status);
|
||||||
|
montauk::sleep_ms(STAGE_MIN_VISIBLE_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// A unit of shutdown work plus a flag the worker sets when it finishes. `done`
|
||||||
|
// is polled across threads, so all access goes through atomics.
|
||||||
|
struct StageWork {
|
||||||
|
void (*fn)();
|
||||||
|
volatile bool done;
|
||||||
|
};
|
||||||
|
|
||||||
|
int stage_worker(void* arg) {
|
||||||
|
StageWork* w = (StageWork*)arg;
|
||||||
|
w->fn();
|
||||||
|
__atomic_store_n(&w->done, true, __ATOMIC_RELEASE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run fn() on a worker thread, waiting up to timeout_ms for it to finish. If a
|
||||||
|
// device wedges, only the disposable worker blocks (in its kernel syscall); the
|
||||||
|
// shutdown flow keeps moving. Returns true if fn() completed in time, false if
|
||||||
|
// it was abandoned. A timed-out worker is intentionally left running and never
|
||||||
|
// joined (joining would re-block us) -- power-off reclaims it in moments.
|
||||||
|
bool run_stage(void (*fn)(), uint64_t timeout_ms) {
|
||||||
|
auto* w = (StageWork*)montauk::malloc(sizeof(StageWork));
|
||||||
|
if (!w) { fn(); return true; } // no memory: best-effort inline
|
||||||
|
w->fn = fn;
|
||||||
|
w->done = false;
|
||||||
|
|
||||||
|
int tid = montauk::thread_spawn(stage_worker, w);
|
||||||
|
if (tid < 0) { fn(); montauk::mfree(w); return true; } // can't isolate: inline
|
||||||
|
|
||||||
|
const uint64_t step = 50;
|
||||||
|
uint64_t waited = 0;
|
||||||
|
while (!__atomic_load_n(&w->done, __ATOMIC_ACQUIRE) && waited < timeout_ms) {
|
||||||
|
montauk::sleep_ms(step);
|
||||||
|
waited += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__atomic_load_n(&w->done, __ATOMIC_ACQUIRE)) {
|
||||||
|
montauk::thread_join(tid, nullptr); // reclaim the worker's stack
|
||||||
|
montauk::mfree(w);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage bodies -- no captured state, so they double as bare thread entries.
|
||||||
|
void stage_disconnect_bluetooth() {
|
||||||
|
Montauk::BtDevInfo devs[8];
|
||||||
|
int n = montauk::bt_list(devs, 8);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (devs[i].connected) {
|
||||||
|
montauk::bt_disconnect(devs[i].bdAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stage_flush_filesystems() {
|
||||||
|
montauk::fs_sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void perform_graceful_shutdown(LoginState* ls, int action) {
|
||||||
|
const bool rebooting = (action == Montauk::POWER_REQ_REBOOT);
|
||||||
|
const char* heading = rebooting ? "Restarting" : "Shutting Down";
|
||||||
|
|
||||||
|
// ==== Stage 1: disconnect connected Bluetooth devices ====
|
||||||
|
// Bounded so an unresponsive controller cannot block the (critical)
|
||||||
|
// filesystem flush that follows.
|
||||||
|
show_stage(ls, heading, "Disconnecting Bluetooth devices...");
|
||||||
|
if (!run_stage(stage_disconnect_bluetooth, BT_STAGE_TIMEOUT_MS)) {
|
||||||
|
show_stage(ls, heading, "Bluetooth is unresponsive, continuing...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Stage 2: flush writes and unmount filesystems ====
|
||||||
|
show_stage(ls, heading, "Flushing file systems...");
|
||||||
|
if (!run_stage(stage_flush_filesystems, FS_STAGE_TIMEOUT_MS)) {
|
||||||
|
show_stage(ls, heading, "Storage is unresponsive, continuing...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Stage 3: dispatch the ACPI power-off / reset ====
|
||||||
|
show_stage(ls, heading, rebooting ? "Restarting now..." : "Powering off...");
|
||||||
|
if (rebooting) {
|
||||||
|
montauk::reset();
|
||||||
|
} else {
|
||||||
|
montauk::shutdown();
|
||||||
|
}
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
@@ -8,5 +8,8 @@
|
|||||||
|
|
||||||
extern "C" void _start() {
|
extern "C" void _start() {
|
||||||
montauk::print("Shutting down...\n");
|
montauk::print("Shutting down...\n");
|
||||||
|
// This low-level utility bypasses the login graceful-shutdown view, so flush
|
||||||
|
// pending writes and unmount disk-backed volumes here before powering off.
|
||||||
|
montauk::fs_sync();
|
||||||
montauk::shutdown();
|
montauk::shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user