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
