| 1 |
/* ============================================================================ |
| 2 |
* Pactor64 — Interactive Shell (x86-64) |
| 3 |
* Serial-based line editor with built-in commands |
| 4 |
* Includes "run" command for executing ELF64 userspace programs |
| 5 |
* ============================================================================ */ |
| 6 |
#include "../include/pactor64.h" |
| 7 |
#include "ata.h" |
| 8 |
/* Forward declaration */ |
| 9 |
static void cmd_run_elf(const unsigned char *elf_data, unsigned int elf_len, const char *name); |
| 10 |
#include "hello_elf.h" |
| 11 |
#include "pactor64_llc_bin.h" |
| 12 |
#include "raw_test_bin.h" |
| 13 |
#include "raw_stack_test_bin.h" |
| 14 |
#include "hello_v2_bin.h" |
| 15 |
#include "test_prog_bin.h" |
| 16 |
#include "app_binaries.h" |
| 17 |
#include "ext2.h" |
| 18 |
#include "elf.h" |
| 19 |
|
| 20 |
/* Embedded hello.elf binary (generated by xxd) */ |
| 21 |
|
| 22 |
/* Assembly functions for userspace transitions */ |
| 23 |
extern void enter_userspace(uint64_t user_rip, uint64_t user_rsp); |
| 24 |
extern uint64_t kernel_stack_top; |
| 25 |
|
| 26 |
#define CMD_BUF_SIZE 256 |
| 27 |
|
| 28 |
/* === Page table constants === */ |
| 29 |
#define PAGE_PRESENT 0x01 |
| 30 |
#define PAGE_WRITE 0x02 |
| 31 |
#define PAGE_USER 0x04 /* U/S bit: 1 = user accessible */ |
| 32 |
#define PAGE_SIZE_FLAG 0x80 /* PS bit for 2MB pages */ |
| 33 |
|
| 34 |
/* User stack: 64KB at virtual address 0x10000000 (256MB, well above kernel/heap) */ |
| 35 |
#define USER_STACK_BASE 0x10000000 |
| 36 |
#define USER_STACK_SIZE 0x10000 /* 64KB */ |
| 37 |
#define USER_STACK_TOP (USER_STACK_BASE + USER_STACK_SIZE) |
| 38 |
|
| 39 |
/* ============================================================================ |
| 40 |
* Page table helpers — make pages user-accessible for ring 3 execution |
| 41 |
* The kernel uses 2MB pages with identity mapping (first 1GB). |
| 42 |
* We need to set the U/S bit on PML4, PDPT, and specific PD entries. |
| 43 |
* ============================================================================ */ |
| 44 |
|
| 45 |
/* Set the U/S bit on a 2MB PD entry covering a given virtual address */ |
| 46 |
static void pt_make_user_accessible(uint64_t vaddr) { |
| 47 |
uint64_t cr3; |
| 48 |
__asm__ volatile ("mov %%cr3, %0" : "=r"(cr3)); |
| 49 |
|
| 50 |
/* PML4 index: bits [47:39] of vaddr */ |
| 51 |
uint64_t pml4_idx = (vaddr >> 39) & 0x1FF; |
| 52 |
/* PDPT index: bits [38:30] */ |
| 53 |
uint64_t pdpt_idx = (vaddr >> 30) & 0x1FF; |
| 54 |
/* PD index: bits [29:21] */ |
| 55 |
uint64_t pd_idx = (vaddr >> 21) & 0x1FF; |
| 56 |
|
| 57 |
uint64_t *pml4 = (uint64_t *)(cr3 & ~0xFFFULL); |
| 58 |
|
| 59 |
/* Set U/S on PML4 entry */ |
| 60 |
pml4[pml4_idx] |= PAGE_USER; |
| 61 |
|
| 62 |
/* Get PDPT and set U/S */ |
| 63 |
uint64_t *pdpt = (uint64_t *)(pml4[pml4_idx] & ~0xFFFULL); |
| 64 |
pdpt[pdpt_idx] |= PAGE_USER; |
| 65 |
|
| 66 |
/* Get PD and set U/S on the 2MB page entry */ |
| 67 |
uint64_t *pd = (uint64_t *)(pdpt[pdpt_idx] & ~0xFFFULL); |
| 68 |
pd[pd_idx] |= PAGE_USER; |
| 69 |
|
| 70 |
kprintf(" PD[%d] for vaddr 0x%x → flags 0x%x\n", pd_idx, vaddr, pd[pd_idx]); |
| 71 |
} |
| 72 |
|
| 73 |
/* Flush the TLB by reloading CR3 */ |
| 74 |
static void tlb_flush(void) { |
| 75 |
uint64_t cr3; |
| 76 |
__asm__ volatile ("mov %%cr3, %0" : "=r"(cr3)); |
| 77 |
__asm__ volatile ("mov %0, %%cr3" : : "r"(cr3) : "memory"); |
| 78 |
} |
| 79 |
|
| 80 |
/* === Built-in Commands === */ |
| 81 |
static void cmd_ata_test(void) { |
| 82 |
uint8_t buf[512]; |
| 83 |
kprintf(" Reading sector 0 (MBR)...\n"); |
| 84 |
if (ata_read_sectors(0, 1, buf) != 0) { |
| 85 |
kprintf(" ERROR: Failed to read sector 0\n"); |
| 86 |
return; |
| 87 |
} |
| 88 |
kprintf(" First 32 bytes of sector 0:\n "); |
| 89 |
for (int i = 0; i < 32; i++) { |
| 90 |
if (buf[i] < 0x10) serial_putc('0'); |
| 91 |
kprintf("%x ", buf[i]); |
| 92 |
if ((i & 15) == 15 && i < 31) kprintf("\n "); |
| 93 |
} |
| 94 |
kprintf("\n"); |
| 95 |
kprintf(" MBR signature: 0x%x 0x%x\n", buf[510], buf[511]); |
| 96 |
} |
| 97 |
|
| 98 |
static void cmd_diskinfo(void) { |
| 99 |
if (!ata_is_present()) { |
| 100 |
kprintf(" No ATA drive detected.\n"); |
| 101 |
return; |
| 102 |
} |
| 103 |
kprintf(" ATA Drive Info:\n"); |
| 104 |
kprintf(" Model: %s\n", ata_get_model()); |
| 105 |
kprintf(" Serial: %s\n", ata_get_serial()); |
| 106 |
kprintf(" Sectors: %d\n", ata_get_sector_count()); |
| 107 |
kprintf(" Capacity: %d MB\n", ata_get_sector_count() * 512 / (1024 * 1024)); |
| 108 |
} |
| 109 |
|
| 110 |
/* Directory listing callback for ext2 */ |
| 111 |
static void dir_list_cb(const char *name, uint8_t name_len, |
| 112 |
uint32_t inode, uint8_t file_type, void *ctx) { |
| 113 |
(void)ctx; |
| 114 |
const char *type_str = "?"; |
| 115 |
switch (file_type) { |
| 116 |
case EXT2_FT_REG_FILE: type_str = "F"; break; |
| 117 |
case EXT2_FT_DIR: type_str = "D"; break; |
| 118 |
case EXT2_FT_SYMLINK: type_str = "L"; break; |
| 119 |
} |
| 120 |
/* kprintf doesn't support %.*s, so null-terminate into a temp buffer */ |
| 121 |
char name_buf[256]; |
| 122 |
kmemcpy(name_buf, name, name_len); |
| 123 |
name_buf[name_len] = '\0'; |
| 124 |
kprintf(" [%s] inode=%d %s\n", type_str, inode, name_buf); |
| 125 |
} |
| 126 |
|
| 127 |
static void cmd_mount(void) { |
| 128 |
static ext2_fs_t fs; |
| 129 |
/* MBR partition 1 starts at sector 2048 (default in mkdisk.sh) */ |
| 130 |
if (ext2_mount(&fs, 2048) != 0) { |
| 131 |
kprintf(" Failed to mount ext2\n"); |
| 132 |
return; |
| 133 |
} |
| 134 |
kprintf(" ext2 mounted successfully!\n"); |
| 135 |
kprintf(" Volume: %.16s\n", fs.sb.s_volume_name); |
| 136 |
|
| 137 |
/* Try to list root directory */ |
| 138 |
kprintf(" Root directory:\n"); |
| 139 |
ext2_readdir(&fs, EXT2_ROOT_INO, dir_list_cb, NULL); |
| 140 |
} |
| 141 |
|
| 142 |
static void cmd_help(void) { |
| 143 |
kprintf(" Available commands:\n"); |
| 144 |
kprintf(" help - Show this help\n"); |
| 145 |
kprintf(" run - Run hello.elf in userspace (ring 3)\n"); |
| 146 |
kprintf(" meminfo - Memory information\n"); |
| 147 |
kprintf(" ticks - Timer tick count\n"); |
| 148 |
kprintf(" ata - ATA drive info\n"); |
| 149 |
kprintf(" ata_test - Read sector 0 (MBR)\n"); |
| 150 |
kprintf(" mount - Mount ext2 filesystem\n"); |
| 151 |
kprintf(" reboot - Reboot the system\n"); |
| 152 |
kprintf(" halt - Halt the system\n"); |
| 153 |
} |
| 154 |
|
| 155 |
static void cmd_meminfo(void) { |
| 156 |
uint64_t total = pmm_get_total_pages(); |
| 157 |
uint64_t free = pmm_get_free_pages(); |
| 158 |
uint64_t used = total - free; |
| 159 |
kprintf(" Physical Memory:\n"); |
| 160 |
kprintf(" Total pages: %d (%d MB)\n", total, total * 4 / 1024); |
| 161 |
kprintf(" Free pages: %d (%d MB)\n", free, free * 4 / 1024); |
| 162 |
kprintf(" Used pages: %d (%d MB)\n", used, used * 4 / 1024); |
| 163 |
} |
| 164 |
|
| 165 |
static void cmd_ticks(void) { |
| 166 |
kprintf(" Timer ticks: %d\n", timer_get_ticks()); |
| 167 |
kprintf(" Uptime (sec): %d\n", timer_get_seconds()); |
| 168 |
} |
| 169 |
|
| 170 |
static void cmd_reboot(void) { |
| 171 |
kprintf(" Rebooting...\n"); |
| 172 |
/* Triple fault: load an invalid IDT then trigger an interrupt */ |
| 173 |
struct idtr bad = { 0, 0 }; |
| 174 |
idt_load(&bad); |
| 175 |
__asm__ volatile ("int $0x03"); |
| 176 |
/* Fallback: keyboard controller reset */ |
| 177 |
outb(0x64, 0xFE); |
| 178 |
for (;;) hlt(); |
| 179 |
} |
| 180 |
|
| 181 |
static void cmd_halt(void) { |
| 182 |
kprintf(" System halted.\n"); |
| 183 |
cli(); |
| 184 |
for (;;) hlt(); |
| 185 |
} |
| 186 |
|
| 187 |
/* ============================================================================ |
| 188 |
* cmd_run — Load and execute hello.elf in userspace (ring 3) |
| 189 |
* |
| 190 |
* Steps: |
| 191 |
* 1. Parse and load ELF segments |
| 192 |
* 2. Set up page table U/S bits for user code + stack |
| 193 |
* 3. Zero user stack pages |
| 194 |
* 4. Enter ring 3 via IRETQ |
| 195 |
* 5. When program calls exit(), return_to_kernel() brings us back |
| 196 |
* ============================================================================ */ |
| 197 |
static void cmd_run(void) { cmd_run_elf(hello_elf_data, hello_elf_data_len, "hello"); } |
| 198 |
static void cmd_run_elf(const unsigned char *elf_data, unsigned int elf_len, const char *name) { |
| 199 |
kprintf(" Loading ELF64 binary: %s\n", name); |
| 200 |
|
| 201 |
/* Step 1: Load ELF */ |
| 202 |
elf64_load_result_t result = elf64_load(elf_data, elf_len); |
| 203 |
if (!result.valid) { |
| 204 |
kprintf(" ERROR: Failed to load ELF\n"); |
| 205 |
return; |
| 206 |
} |
| 207 |
|
| 208 |
kprintf(" Entry: 0x%x, Load: 0x%x-0x%x\n", |
| 209 |
result.entry, result.load_base, result.load_end); |
| 210 |
|
| 211 |
/* Step 2: Set up page table U/S bits for user pages */ |
| 212 |
kprintf(" Setting up page tables for ring 3...\n"); |
| 213 |
|
| 214 |
/* Make code pages user-accessible. |
| 215 |
* The ELF loads at 0x400000 which is in PD[2] (covers 4MB-6MB). */ |
| 216 |
pt_make_user_accessible(result.load_base); |
| 217 |
|
| 218 |
/* Also make pages around the code user-accessible (in case it spans 2MB boundaries) */ |
| 219 |
if (result.load_end > (result.load_base & ~0x1FFFFF) + 0x200000) { |
| 220 |
pt_make_user_accessible(result.load_end); |
| 221 |
} |
| 222 |
|
| 223 |
/* Make user stack pages user-accessible. |
| 224 |
* Stack at 0x10000000 is in PD[128] (covers 256MB-258MB). */ |
| 225 |
pt_make_user_accessible(USER_STACK_BASE); |
| 226 |
|
| 227 |
/* Flush TLB so page table changes take effect */ |
| 228 |
tlb_flush(); |
| 229 |
|
| 230 |
/* Step 3: Zero user stack area */ |
| 231 |
kmemset((void *)USER_STACK_BASE, 0, USER_STACK_SIZE); |
| 232 |
kprintf(" User stack: 0x%x - 0x%x (64KB)\n", |
| 233 |
USER_STACK_BASE, USER_STACK_TOP); |
| 234 |
|
| 235 |
/* Step 4: Update TSS RSP0 for SYSCALL entry from user mode */ |
| 236 |
/* (Already set in entry.asm, but update to be safe) */ |
| 237 |
extern char tss_struct[]; |
| 238 |
*(uint64_t *)(tss_struct + 4) = (uint64_t)&kernel_stack_top; |
| 239 |
|
| 240 |
kprintf(" Entering userspace at RIP=0x%x RSP=0x%x...\n", |
| 241 |
result.entry, USER_STACK_TOP); |
| 242 |
kprintf(" ----------------------------------------\n"); |
| 243 |
|
| 244 |
/* Step 5: Jump to ring 3! |
| 245 |
* enter_userspace saves kernel RSP and does IRETQ. |
| 246 |
* When the program calls exit(), return_to_kernel() will |
| 247 |
* restore the kernel stack and we'll return here. */ |
| 248 |
outb(0x21, 0xFD); /* Mask timer during userspace */ |
| 249 |
enter_userspace(result.entry, USER_STACK_TOP); |
| 250 |
/* DEBUG: serial marker to confirm return */ |
| 251 |
|
| 252 |
/* We get here when return_to_kernel() is called from sys_exit */ |
| 253 |
kprintf("\n ----------------------------------------\n"); |
| 254 |
kprintf(" Returned to kernel shell.\n"); |
| 255 |
outb(0x21, 0xFC); /* Re-enable timer */ |
| 256 |
} |
| 257 |
|
| 258 |
/* === Execute a command === */ |
| 259 |
static void execute_command(const char *cmd) { |
| 260 |
while (*cmd == ' ') cmd++; |
| 261 |
if (*cmd == '\0') return; |
| 262 |
|
| 263 |
if (kstrncmp(cmd, "execute ", 8) == 0) { |
| 264 |
cmd_execute(cmd + 8); |
| 265 |
} else if (kstrcmp(cmd, "apps") == 0) { |
| 266 |
cmd_apps(); |
| 267 |
} else if (kstrcmp(cmd, "help") == 0) { |
| 268 |
cmd_help(); |
| 269 |
} else if (kstrcmp(cmd, "run") == 0) { |
| 270 |
cmd_run(); |
| 271 |
} else if (kstrcmp(cmd, "meminfo") == 0) { |
| 272 |
cmd_meminfo(); |
| 273 |
} else if (kstrcmp(cmd, "ticks") == 0) { |
| 274 |
cmd_ticks(); |
| 275 |
} else if (kstrcmp(cmd, "ata") == 0) { |
| 276 |
cmd_diskinfo(); |
| 277 |
} else if (kstrcmp(cmd, "ata_test") == 0) { |
| 278 |
cmd_ata_test(); |
| 279 |
} else if (kstrcmp(cmd, "mount") == 0) { |
| 280 |
cmd_mount(); |
| 281 |
} else if (kstrcmp(cmd, "reboot") == 0) { |
| 282 |
cmd_reboot(); |
| 283 |
} else if (kstrcmp(cmd, "halt") == 0) { |
| 284 |
cmd_halt(); |
| 285 |
} else { |
| 286 |
kprintf(" Unknown: '%s'. Type 'apps' for applications.\n", cmd); |
| 287 |
} |
| 288 |
} |
| 289 |
|
| 290 |
/* === Initialize shell === */ |
| 291 |
void shell_init(void) { |
| 292 |
kprintf("\n ========================================\n"); |
| 293 |
kprintf(" Pactor64 Shell Ready\n"); |
| 294 |
kprintf(" Type 'help' for available commands\n"); |
| 295 |
kprintf(" Type 'run' to execute hello.elf\n"); |
| 296 |
kprintf(" ========================================\n\n"); |
| 297 |
} |
| 298 |
|
| 299 |
/* === Run the shell (main loop) === */ |
| 300 |
void shell_run(void) { |
| 301 |
char buf[CMD_BUF_SIZE]; |
| 302 |
int pos = 0; |
| 303 |
|
| 304 |
kprintf("pactor64> "); |
| 305 |
|
| 306 |
for (;;) { |
| 307 |
/* Poll serial for input */ |
| 308 |
int c = serial_getc(); |
| 309 |
if (c < 0) { |
| 310 |
/* Also check keyboard (PS/2) */ |
| 311 |
c = keyboard_getchar(); |
| 312 |
if (c < 0) { |
| 313 |
__asm__ volatile ("hlt"); |
| 314 |
continue; |
| 315 |
} |
| 316 |
} |
| 317 |
|
| 318 |
char ch = (char)c; |
| 319 |
|
| 320 |
if (ch == '\n' || ch == '\r') { |
| 321 |
/* Enter: execute command */ |
| 322 |
serial_puts("\r\n"); |
| 323 |
buf[pos] = '\0'; |
| 324 |
execute_command(buf); |
| 325 |
pos = 0; |
| 326 |
kprintf("pactor64> "); |
| 327 |
} else if (ch == '\b' || ch == 127) { |
| 328 |
/* Backspace */ |
| 329 |
if (pos > 0) { |
| 330 |
pos--; |
| 331 |
serial_puts("\b \b"); /* erase character on terminal */ |
| 332 |
} |
| 333 |
} else if (ch >= ' ' && ch < 127) { |
| 334 |
/* Printable character */ |
| 335 |
if (pos < CMD_BUF_SIZE - 1) { |
| 336 |
buf[pos++] = ch; |
| 337 |
serial_putc(ch); /* echo */ |
| 338 |
} |
| 339 |
} |
| 340 |
/* Ignore other control characters */ |
| 341 |
} |
| 342 |
} |
| 343 |
|