:: commit e7ae1953044d77cdb88fbe8e3df46d1fe25d6da6

Matteo Semenzato <mattew8898@gmail.com> — 2020-11-05 00:37

parents: 10bd26962a

Add pxe support

diff --git a/.gitignore b/.gitignore
index 7fcab45d..ec9d1bd5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@
 .vscode
 /limine-install
 !/limine.bin
+!/limine-pxe.bin
diff --git a/Makefile b/Makefile
index d921cbfe..cc5beb67 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,7 @@ PATH := $(shell pwd)/toolchain/bin:$(PATH)
 all: stage2 decompressor
 	gzip -n -9 < stage2/stage2.bin > stage2/stage2.bin.gz
 	cd bootsect && nasm bootsect.asm -fbin -o ../limine.bin
+	cd pxeboot && nasm bootsect.asm -fbin -o ../limine-pxe.bin
 
 clean: stage2-clean decompressor-clean test-clean
 	rm -f stage2/stage2.bin.gz
diff --git a/decompressor/main.c b/decompressor/main.c
index 7c221ec0..07db966f 100644
--- a/decompressor/main.c
+++ b/decompressor/main.c
@@ -3,14 +3,14 @@
 #include <gzip/tinf.h>
 
 __attribute__((noreturn))
-void entry(uint8_t *compressed_stage2, size_t stage2_size, uint8_t boot_drive) {
+void entry(uint8_t *compressed_stage2, size_t stage2_size, uint8_t boot_drive, int pxe) {
     // The decompressor should decompress compressed_stage2 to address 0x8000.
     uint8_t *dest = (uint8_t *)0x8000;
 
     tinf_gzip_uncompress(dest, compressed_stage2, stage2_size);
 
     __attribute__((noreturn))
-    void (*stage2)(uint8_t boot_drive) = (void *)dest;
+    void (*stage2)(uint8_t boot_drive, int pxe) = (void *)dest;
 
-    stage2(boot_drive);
+    stage2(boot_drive, pxe);
 }
diff --git a/limine-pxe.bin b/limine-pxe.bin
new file mode 100644
index 00000000..8afef2a9
Binary files /dev/null and b/limine-pxe.bin differ
diff --git a/limine.bin b/limine.bin
index bcc5e41d..cb47f437 100644
Binary files a/limine.bin and b/limine.bin differ
diff --git a/pxeboot/bootsect.asm b/pxeboot/bootsect.asm
new file mode 100644
index 00000000..a082d441
--- /dev/null
+++ b/pxeboot/bootsect.asm
@@ -0,0 +1,64 @@
+org 0x7c00
+bits 16
+
+start:
+    jmp 0x0000:.initialise_cs
+  .initialise_cs:
+    xor ax, ax
+    mov ds, ax
+    mov es, ax
+    mov ss, ax
+    mov sp, 0x7c00
+    sti
+    call load_gdt
+
+    cli
+
+    mov eax, cr0
+    bts ax, 0
+    mov cr0, eax
+
+    jmp 0x18:.mode32
+    bits 32
+  .mode32:
+    mov ax, 0x20
+    mov ds, ax
+    mov es, ax
+    mov fs, ax
+    mov gs, ax
+    mov ss, ax
+
+    push 0x1
+    and edx, 0xff
+    push edx
+
+    push stage2.size
+    push (stage2 - decompressor) + 0x70000
+
+    mov esi, decompressor
+    mov edi, 0x70000
+    mov ecx, stage2.fullsize
+    rep movsb
+
+    call 0x70000
+
+bits 16
+
+err:
+    hlt
+    jmp err
+
+; Includes
+
+%include '../bootsect/gdt.inc'
+
+; ********************* Stage 2 *********************
+
+decompressor:
+incbin '../decompressor/decompressor.bin'
+
+align 16
+stage2:
+incbin '../stage2/stage2.bin.gz'
+.size: equ $ - stage2
+.fullsize: equ $ - decompressor
diff --git a/stage2/lib/config.c b/stage2/lib/config.c
index 051a473c..871f076c 100644
--- a/stage2/lib/config.c
+++ b/stage2/lib/config.c
@@ -5,6 +5,8 @@
 #include <lib/blib.h>
 #include <mm/pmm.h>
 #include <fs/file.h>
+#include <lib/print.h>
+#include <pxe/tftp.h>
 
 #define SEPARATOR '\n'
 
@@ -12,7 +14,7 @@ bool config_ready = false;
 
 static char *config_addr;
 
-int init_config(struct part *part) {
+int init_config_disk(struct part *part) {
     struct file_handle f;
 
     if (fopen(&f, part, "/limine.cfg")) {
@@ -26,6 +28,23 @@ int init_config(struct part *part) {
 
     fread(&f, config_addr, 0, f.size);
 
+    return init_config(config_size);
+}
+
+int init_config_pxe(void) {
+    struct tftp_file_handle cfg;
+    if (tftp_open(&cfg, 0, 69, "limine.cfg")) {
+        return -1;
+    }
+    config_addr = conv_mem_alloc(cfg.file_size);
+    tftp_read(&cfg, config_addr, 0, cfg.file_size);
+
+    print("\nconfig: %s\n", config_addr);
+
+    return init_config(cfg.file_size);
+}
+
+int init_config(size_t config_size) {
     // remove windows carriage returns, if any
     for (size_t i = 0; i < config_size; i++) {
         if (config_addr[i] == '\r') {
diff --git a/stage2/lib/config.h b/stage2/lib/config.h
index 959d0a67..26fd561e 100644
--- a/stage2/lib/config.h
+++ b/stage2/lib/config.h
@@ -7,7 +7,9 @@
 
 extern bool config_ready;
 
-int init_config(struct part *part);
+int init_config_disk(struct part *part);
+int init_config_pxe(void);
+int init_config(size_t config_size);
 int config_get_entry_name(char *ret, size_t index, size_t limit);
 int config_set_entry(size_t index);
 char *config_get_value(char *buf, size_t index, size_t limit, const char *key);
diff --git a/stage2/lib/libc.c b/stage2/lib/libc.c
index c11d7b9e..cb5844e4 100644
--- a/stage2/lib/libc.c
+++ b/stage2/lib/libc.c
@@ -1,6 +1,9 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <lib/libc.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <lib/blib.h>
 
 int toupper(int c) {
     if (c >= 'a' && c <= 'z') {
@@ -67,3 +70,24 @@ size_t strlen(const char *str) {
 
     return len;
 }
+
+int inet_pton(const char *src, void *dst) {
+    uint8_t array[4] = {};
+    const char *current = src;
+
+    for (int i = 0; i < 4; i++) {
+       long int value = strtoui(current, 0, 10);
+       if (value > 255)
+           return -1;
+       for (int j = 0; j < 3; j++) {
+           if (*current != '\0' && *current != '.')
+                current++;
+           else
+               break;
+       }
+       current++;
+       array[i] = value;
+    }
+    memcpy(dst, array, 4);
+    return 0;
+}
diff --git a/stage2/lib/libc.h b/stage2/lib/libc.h
index bffe94a1..43bdd5ae 100644
--- a/stage2/lib/libc.h
+++ b/stage2/lib/libc.h
@@ -16,5 +16,6 @@ char *strncpy(char *, const char *, size_t);
 size_t strlen(const char *);
 int strcmp(const char *, const char *);
 int strncmp(const char *, const char *, size_t);
+int inet_pton(const char *src, void *dst);
 
 #endif
diff --git a/stage2/lib/pxe.asm b/stage2/lib/pxe.asm
new file mode 100644
index 00000000..70b4545b
--- /dev/null
+++ b/stage2/lib/pxe.asm
@@ -0,0 +1,85 @@
+section .realmode
+
+global pxe_call
+global set_pxe_fp
+
+set_pxe_fp:
+    mov eax, [esp + 4]
+    mov [pxe_call.pxe_fp], eax
+    ret
+
+pxe_call:
+   ; Save GDT in case BIOS overwrites it
+    sgdt [.gdt]
+
+    ; Save non-scratch GPRs
+    push ebx
+    push esi
+    push edi
+    push ebp
+
+    mov ebx, eax
+    
+    ; Jump to real mode
+    jmp 0x08:.bits16
+  .bits16:
+    bits 16
+    mov ax, 0x10
+    mov ds, ax
+    mov es, ax
+    mov fs, ax
+    mov gs, ax
+    mov ss, ax
+    mov eax, cr0
+    and al, 0xfe
+    mov cr0, eax
+    jmp 0x00:.cszero
+  .cszero:
+    xor ax, ax
+    mov ss, ax
+    mov ds, ax
+    mov es, ax
+    mov fs, ax
+    mov gs, ax
+
+    sti
+
+    push dx
+    push cx
+    push bx
+    call far [.pxe_fp]
+    add sp, 6
+    mov bx, ax
+
+    cli
+   ; Restore GDT
+    lgdt [ss:.gdt]
+
+    ; Jump back to pmode
+    mov eax, cr0
+    or al, 1
+    mov cr0, eax
+    jmp 0x18:.bits32
+  .bits32:
+    bits 32
+    mov ax, 0x20
+    mov ds, ax
+    mov es, ax
+    mov fs, ax
+    mov gs, ax
+    mov ss, ax
+
+    mov eax, ebx
+    ; Restore non-scratch GPRs
+    pop ebp
+    pop edi
+    pop esi
+    pop ebx
+
+    ; Exit
+    ret
+
+align 16
+  .pxe_fp:   dd 0
+  .esp:      dd 0
+  .gdt:      dq 0
diff --git a/stage2/lib/uri.c b/stage2/lib/uri.c
index ed8173d4..bd83d867 100644
--- a/stage2/lib/uri.c
+++ b/stage2/lib/uri.c
@@ -5,6 +5,9 @@
 #include <lib/part.h>
 #include <lib/libc.h>
 #include <fs/file.h>
+#include <mm/pmm.h>
+#include <lib/print.h>
+#include <pxe/tftp.h>
 
 // A URI takes the form of: resource://root/path
 // The following function splits up a URI into its componenets
@@ -109,6 +112,28 @@ static bool uri_guid_dispatch(struct file_handle *fd, char *guid_str, char *path
     return true;
 }
 
+static bool uri_tftp_dispatch(struct file_handle *fd, char *root, char *path) {
+    uint32_t ip;
+    if (!strcmp(root, "")) {
+        ip = 0;
+    } else {
+        if (inet_pton(root, &ip)) {
+            panic("invalid ipv4 address: %s", root);
+        }
+        print("\nip: %x\n", ip);
+    }
+
+    struct tftp_file_handle *cfg = conv_mem_alloc(sizeof(struct tftp_file_handle));
+    if(tftp_open(cfg, ip, 69, path)) {
+        return false;
+    }
+
+    fd->fd = cfg;
+    fd->read = tftp_read;
+    fd->size = cfg->file_size;
+    return true;
+}
+
 bool uri_open(struct file_handle *fd, char *uri) {
     char *resource, *root, *path;
     uri_resolve(uri, &resource, &root, &path);
@@ -117,6 +142,8 @@ bool uri_open(struct file_handle *fd, char *uri) {
         return uri_bios_dispatch(fd, root, path);
     } else if (!strcmp(resource, "guid")) {
         return uri_guid_dispatch(fd, root, path);
+    } else if (!strcmp(resource, "tftp")) {
+        return uri_tftp_dispatch(fd, root, path);
     } else {
         panic("Resource `%s` not valid.", resource);
     }
diff --git a/stage2/main.c b/stage2/main.c
index 34f33bbc..4cb664bd 100644
--- a/stage2/main.c
+++ b/stage2/main.c
@@ -17,8 +17,10 @@
 #include <protos/linux.h>
 #include <protos/chainload.h>
 #include <menu.h>
+#include <pxe/pxe.h>
+#include <pxe/tftp.h>
 
-void entry(uint8_t _boot_drive) {
+void entry(uint8_t _boot_drive, int pxe_boot) {
     boot_drive = _boot_drive;
 
     mtrr_save();
@@ -30,32 +32,39 @@ void entry(uint8_t _boot_drive) {
     if (!a20_enable())
         panic("Could not enable A20 line");
 
-    print("Boot drive: %x\n", boot_drive);
-
     part_create_index();
-
-    // Look for config file.
-    print("Searching for config file...\n");
-    struct part parts[4];
-    for (int i = 0; ; i++) {
-        if (i == 4) {
-            panic("Config file not found.");
+    init_e820();
+    init_memmap();
+    
+    if (pxe_boot) {
+        pxe_init();
+        if(init_config_pxe()) {
+            panic("failed to load config file");
         }
-        print("Checking partition %d...\n", i);
-        int ret = part_get(&parts[i], boot_drive, i);
-        if (ret) {
-            print("Partition not found.\n");
-        } else {
-            print("Partition found.\n");
-            if (!init_config(&parts[i])) {
-                print("Config file found and loaded.\n");
-                break;
+        print("config loaded");
+    } else {
+        print("Boot drive: %x\n", boot_drive);
+        // Look for config file.
+        print("Searching for config file...\n");
+        struct part parts[4];
+        for (int i = 0; ; i++) {
+            if (i == 4) {
+                panic("Config file not found.");
+            }
+            print("Checking partition %d...\n", i);
+            int ret = part_get(&parts[i], boot_drive, i);
+            if (ret) {
+                print("Partition not found.\n");
+            } else {
+                print("Partition found.\n");
+                if (!init_config_disk(&parts[i])) {
+                    print("Config file found and loaded.\n");
+                    break;
+                }
             }
         }
     }
 
-    init_e820();
-    init_memmap();
 
     char *cmdline = menu();
 
diff --git a/stage2/pxe/pxe.c b/stage2/pxe/pxe.c
new file mode 100644
index 00000000..6f46fe44
--- /dev/null
+++ b/stage2/pxe/pxe.c
@@ -0,0 +1,43 @@
+#include <lib/print.h>
+#include <lib/real.h>
+#include <pxe/pxe.h>
+#include <lib/libc.h>
+#include <lib/blib.h>
+
+void set_pxe_fp(uint32_t fp);
+
+void pxe_init(void) {
+    //pxe installation check
+    struct rm_regs r = { 0 };
+    r.ebx = 0;
+    r.ecx = 0;
+    r.eax = 0x5650;
+    r.es = 0;
+
+    rm_int(0x1a, &r, &r);
+    if ((r.eax & 0xffff) != 0x564e) {
+        panic("PXE installation check failed");
+    }
+
+    struct pxenv* pxenv = { 0 };
+
+    pxenv = (struct pxenv*)((r.es << 4) + (r.ebx & 0xffff));
+    if (memcmp(pxenv->signature, PXE_SIGNATURE, sizeof(pxenv->signature)) != 0) {
+        panic("PXENV structure signature corrupted");
+    }
+
+    if (pxenv->version < 0x201) {
+        //we won't support pxe < 2.1, grub does this too and it seems to work fine
+        panic("\npxe version too old");
+    }
+
+    struct bangpxe* bangpxe = (struct bangpxe*)((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4) + (pxenv->pxe_ptr & 0xffff));
+
+    if (memcmp(bangpxe->signature, PXE_BANGPXE_SIGNATURE,
+            sizeof(bangpxe->signature))
+        != 0) {
+        panic("!pxe signature corrupted");
+    }
+    set_pxe_fp(bangpxe->rm_entry);
+    print("Successfully initialized pxe");
+}
diff --git a/stage2/pxe/pxe.h b/stage2/pxe/pxe.h
new file mode 100644
index 00000000..a1a9ab65
--- /dev/null
+++ b/stage2/pxe/pxe.h
@@ -0,0 +1,95 @@
+#ifndef PXE_H
+#define PXE_H
+
+#include <stdint.h>
+
+void pxe_init(void);
+int pxe_call(uint16_t opcode, uint16_t buf_seg, uint16_t buf_off) __attribute__((regparm(3)));
+
+#define MAC_ADDR_LEN 16
+typedef uint8_t MAC_ADDR_t[MAC_ADDR_LEN];
+
+struct bootph {
+    uint8_t opcode;
+    uint8_t Hardware;
+    uint8_t Hardlen;
+    uint8_t Gatehops;
+    uint32_t ident;
+    uint16_t seconds;
+    uint16_t Flags;
+    uint32_t cip;
+    uint32_t yip;
+    uint32_t sip;
+    uint32_t gip;
+    MAC_ADDR_t CAddr;
+    uint8_t Sname[64];
+    uint8_t bootfile[128];
+    union bootph_vendor {
+        uint8_t d[1024];
+        struct bootph_vendor_v {
+            uint8_t magic[4];
+            uint32_t flags;
+            uint8_t pad[56];
+        } v;
+    } vendor;
+};
+
+ struct PXENV_UNDI_GET_INFORMATION {
+    uint16_t Status;
+    uint16_t BaseIo;
+    uint16_t IntNumber;
+    uint16_t MaxTranUnit;
+    uint16_t HwType;
+    uint16_t HwAddrLen;
+    uint8_t CurrentNodeAddress[16];
+    uint8_t PermNodeAddress[16];
+    uint16_t ROMAddress;
+    uint16_t RxBufCt;
+    uint16_t TxBufCt;
+ };
+
+#define PXE_SIGNATURE "PXENV+"
+struct pxenv {
+    uint8_t signature[6];
+    uint16_t version;
+    uint8_t length;
+    uint8_t checksum;
+    uint32_t rm_entry;
+    uint32_t pm_offset;
+    uint16_t pm_selector; 
+    uint16_t stack_seg; 
+    uint16_t stack_size; 
+    uint16_t bc_code_seg; 
+    uint16_t bc_code_size; 
+    uint16_t bc_data_seg; 
+    uint16_t bc_data_size; 
+    uint16_t undi_data_seg; 
+    uint16_t undi_data_size; 
+    uint16_t undi_code_seg; 
+    uint16_t undi_code_size; 
+    uint32_t pxe_ptr; 
+} __attribute__((packed));
+
+#define PXE_BANGPXE_SIGNATURE "!PXE"
+struct bangpxe {
+    uint8_t signature[4];
+    uint8_t length;
+    uint8_t chksum;
+    uint8_t rev;
+    uint8_t reserved;
+    uint32_t undiromid;
+    uint32_t baseromid;
+    uint32_t rm_entry;
+    uint32_t pm_entry;
+} __attribute__((packed));
+
+#define PXENV_GET_CACHED_INFO 0x0071
+struct pxenv_get_cached_info {
+    uint16_t status;
+    uint16_t packet_type;
+    uint16_t buffer_size;
+    uint32_t buffer;
+    uint16_t buffer_limit;
+} __attribute__((packed));
+
+#endif
diff --git a/stage2/pxe/tftp.c b/stage2/pxe/tftp.c
new file mode 100644
index 00000000..9e546599
--- /dev/null
+++ b/stage2/pxe/tftp.c
@@ -0,0 +1,97 @@
+#include <pxe/tftp.h>
+#include <pxe/pxe.h>
+#include <lib/real.h>
+#include <lib/print.h>
+#include <lib/libc.h>
+#include <mm/pmm.h>
+#include <lib/blib.h>
+
+int tftp_open(struct tftp_file_handle* handle, uint32_t server_ip, uint16_t server_port, const char* name) {
+    int ret = 0;
+    if (!server_ip) {
+        struct pxenv_get_cached_info cachedinfo = { 0 };
+        cachedinfo.packet_type = 2;
+        pxe_call(PXENV_GET_CACHED_INFO, ((uint16_t)rm_seg(&cachedinfo)), (uint16_t)rm_off(&cachedinfo));
+        struct bootph *ph = (struct bootph*)(void *) (((((uint32_t)cachedinfo.buffer) >> 16) << 4) + (((uint32_t)cachedinfo.buffer) & 0xFFFF));
+        server_ip = ph->sip;
+    }
+  
+    struct PXENV_UNDI_GET_INFORMATION undi_info = { 0 };
+    ret = pxe_call(UNDI_GET_INFORMATION, ((uint16_t)rm_seg(&undi_info)), (uint16_t)rm_off(&undi_info));
+    if (ret) {
+        return -1;
+    }
+   
+    //TODO figure out a more proper way to do this.
+    uint16_t mtu = undi_info.MaxTranUnit - 48;
+
+    handle->server_ip = server_ip;
+    handle->server_port = server_port;
+    handle->packet_size = mtu;
+
+    struct pxenv_get_file_size fsize = {
+        .status = 0,
+        .sip = server_ip,
+    };
+    strcpy((char*)fsize.name, name);
+    ret = pxe_call(TFTP_GET_FILE_SIZE, ((uint16_t)rm_seg(&fsize)), (uint16_t)rm_off(&fsize));
+    if (ret) {
+        return -1;
+    }
+
+    handle->file_size = fsize.file_size;
+
+    volatile struct pxenv_open open = {
+        .status = 0,
+        .sip = server_ip,
+        .port = (server_port) << 8,
+        .packet_size = mtu
+    };
+    strcpy((char*)open.name, name);
+    ret = pxe_call(TFTP_OPEN, ((uint16_t)rm_seg(&open)), (uint16_t)rm_off(&open));
+    if (ret) {
+        print("failed to open file %x or bad packet size", open.status);
+        return -1;
+    }
+    mtu = open.packet_size;
+
+    uint8_t *buf = conv_mem_alloc(mtu);
+    handle->data = ext_mem_alloc(handle->file_size);
+    memset(handle->data, 0, handle->file_size);
+    size_t to_transfer = handle->file_size;
+    size_t progress = 0;
+
+    while (to_transfer > 0) {
+        volatile struct pxenv_read read = {
+            .boff = ((uint16_t)rm_off(buf)),
+            .bseg = ((uint16_t)rm_seg(buf)),
+        };
+        ret = pxe_call(TFTP_READ, ((uint16_t)rm_seg(&read)), (uint16_t)rm_off(&read));
+        if (ret) {
+            panic("failed reading");
+        }
+        memcpy(handle->data + progress, buf, read.bsize);
+
+        if (read.bsize < mtu) {
+            break;
+        }
+        to_transfer -= read.bsize;
+        progress += read.bsize;
+    }
+
+    uint16_t close = 0;
+    ret = pxe_call(TFTP_CLOSE, ((uint16_t)rm_seg(&close)), (uint16_t)rm_off(&close));
+    if (ret) {
+        panic("close failed");
+    }
+    return 0;
+}
+
+int tftp_read(void* fd, void *buf, uint64_t loc, uint64_t count) {
+    struct tftp_file_handle *handle = (struct tftp_file_handle*)fd;
+    if ((loc + count) > handle->file_size) {
+        return -1;
+    }
+    memcpy(buf, handle->data + loc, count);
+    return 0;
+}
diff --git a/stage2/pxe/tftp.h b/stage2/pxe/tftp.h
new file mode 100644
index 00000000..fa248cb1
--- /dev/null
+++ b/stage2/pxe/tftp.h
@@ -0,0 +1,51 @@
+#ifndef TFTP_H
+#define TFTP_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#define UNDI_GET_INFORMATION 0xC
+
+struct tftp_file_handle {
+    uint32_t server_ip;
+    uint16_t server_port;
+    uint16_t packet_size;
+    size_t file_size;
+    void *data;
+};
+
+#define TFTP_OPEN 0x0020
+struct pxenv_open {
+    uint16_t status;
+    uint32_t sip;
+    uint32_t gip;
+    uint8_t name[128];
+    uint16_t port;
+    uint16_t packet_size;
+ } __attribute__((packed));
+
+#define TFTP_READ 0x22
+struct pxenv_read {
+    uint16_t status;
+    uint16_t pn;
+    uint16_t bsize;
+    uint16_t boff;
+    uint16_t bseg;
+} __attribute__((packed));
+
+#define TFTP_GET_FILE_SIZE 0x25
+struct pxenv_get_file_size {
+    uint16_t status;
+    uint32_t sip;
+    uint32_t gip;
+    uint8_t name[128];
+    uint32_t file_size;
+} __attribute__((packed));
+
+#define TFTP_CLOSE 0x21
+
+//server_ip and server_port can be 0 for default
+int tftp_open(struct tftp_file_handle* handle, uint32_t server_ip, uint16_t server_port, const char* name);
+int tftp_read(void *fd, void *buf, uint64_t loc, uint64_t count);
+
+#endif
tab: 248 wrap: offon