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 __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 #include #include +#include +#include #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 #include #include +#include +#include +#include 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 #include #include +#include +#include +#include // 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 #include #include +#include +#include -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 +#include +#include +#include +#include + +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 + +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 +#include +#include +#include +#include +#include +#include + +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 +#include + +#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