diff options
Diffstat (limited to 'core/fs/pxe/bios.c')
-rw-r--r-- | core/fs/pxe/bios.c | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/core/fs/pxe/bios.c b/core/fs/pxe/bios.c new file mode 100644 index 00000000..81aa715c --- /dev/null +++ b/core/fs/pxe/bios.c @@ -0,0 +1,403 @@ +#include <syslinux/firmware.h> +#include <core.h> +#include "pxe.h" +#include <net.h> +#include <minmax.h> +#include <bios.h> + +static uint16_t real_base_mem; /* Amount of DOS memory after freeing */ + +static bool has_gpxe; +static uint32_t gpxe_funcs; + +/* + * Validity check on possible !PXE structure in buf + * return 1 for success, 0 for failure. + * + */ +static int is_pxe(const void *buf) +{ + const struct pxe_t *pxe = buf; + const uint8_t *p = buf; + int i = pxe->structlength; + uint8_t sum = 0; + + if (i < sizeof(struct pxe_t) || + memcmp(pxe->signature, "!PXE", 4)) + return 0; + + while (i--) + sum += *p++; + + return sum == 0; +} + +/* + * Just like is_pxe, it checks PXENV+ structure + * + */ +static int is_pxenv(const void *buf) +{ + const struct pxenv_t *pxenv = buf; + const uint8_t *p = buf; + int i = pxenv->length; + uint8_t sum = 0; + + /* The pxeptr field isn't present in old versions */ + if (i < offsetof(struct pxenv_t, pxeptr) || + memcmp(pxenv->signature, "PXENV+", 6)) + return 0; + + while (i--) + sum += *p++; + + return sum == 0; +} + +/* + * memory_scan_for_pxe_struct: + * memory_scan_for_pxenv_struct: + * + * If none of the standard methods find the !PXE/PXENV+ structure, + * look for it by scanning memory. + * + * return the corresponding pxe structure if found, or NULL; + */ +static const void *memory_scan(uintptr_t start, int (*func)(const void *)) +{ + const char *ptr; + + /* Scan each 16 bytes of conventional memory before the VGA region */ + for (ptr = (const char *)start; ptr < (const char *)0xA0000; ptr += 16) { + if (func(ptr)) + return ptr; /* found it! */ + ptr += 16; + } + return NULL; +} + +static const struct pxe_t *memory_scan_for_pxe_struct(void) +{ + uint16_t start = bios_fbm(); /* Starting segment */ + + return memory_scan(start << 10, is_pxe); +} + +static const struct pxenv_t *memory_scan_for_pxenv_struct(void) +{ + return memory_scan(0x10000, is_pxenv); +} + +/* + * Find the !PXE structure; we search for the following, in order: + * + * a. !PXE structure as SS:[SP + 4] + * b. PXENV+ structure at [ES:BX] + * c. INT 1Ah AX=0x5650 -> PXENV+ + * d. Search memory for !PXE + * e. Search memory for PXENV+ + * + * If we find a PXENV+ structure, we try to find a !PXE structure from + * if if the API version is 2.1 or later + * + */ +int pxe_init(bool quiet) +{ + extern void pxe_int1a(void); + char plan = 'A'; + uint16_t seg, off; + uint16_t code_seg, code_len; + uint16_t data_seg, data_len; + const char *base = GET_PTR(InitStack); + com32sys_t regs; + const char *type; + const struct pxenv_t *pxenv; + const struct pxe_t *pxe; + + /* Assume API version 2.1 */ + APIVer = 0x201; + + /* Plan A: !PXE structure as SS:[SP + 4] */ + off = *(const uint16_t *)(base + 48); + seg = *(const uint16_t *)(base + 50); + pxe = MK_PTR(seg, off); + if (is_pxe(pxe)) + goto have_pxe; + + /* Plan B: PXENV+ structure at [ES:BX] */ + plan++; + off = *(const uint16_t *)(base + 24); /* Original BX */ + seg = *(const uint16_t *)(base + 4); /* Original ES */ + pxenv = MK_PTR(seg, off); + if (is_pxenv(pxenv)) + goto have_pxenv; + + /* Plan C: PXENV+ structure via INT 1Ah AX=5650h */ + plan++; + memset(®s, 0, sizeof regs); + regs.eax.w[0] = 0x5650; + call16(pxe_int1a, ®s, ®s); + if (!(regs.eflags.l & EFLAGS_CF) && (regs.eax.w[0] == 0x564e)) { + pxenv = MK_PTR(regs.es, regs.ebx.w[0]); + if (is_pxenv(pxenv)) + goto have_pxenv; + } + + /* Plan D: !PXE memory scan */ + plan++; + if ((pxe = memory_scan_for_pxe_struct())) + goto have_pxe; + + /* Plan E: PXENV+ memory scan */ + plan++; + if ((pxenv = memory_scan_for_pxenv_struct())) + goto have_pxenv; + + /* Found nothing at all !! */ + if (!quiet) + printf("No !PXE or PXENV+ API found; we're dead...\n"); + return -1; + + have_pxenv: + APIVer = pxenv->version; + if (!quiet) + printf("Found PXENV+ structure\nPXE API version is %04x\n", APIVer); + + /* if the API version number is 0x0201 or higher, use the !PXE structure */ + if (APIVer >= 0x201) { + if (pxenv->length >= sizeof(struct pxenv_t)) { + pxe = GET_PTR(pxenv->pxeptr); + if (is_pxe(pxe)) + goto have_pxe; + /* + * Nope, !PXE structure missing despite API 2.1+, or at least + * the pointer is missing. Do a last-ditch attempt to find it + */ + if ((pxe = memory_scan_for_pxe_struct())) + goto have_pxe; + } + APIVer = 0x200; /* PXENV+ only, assume version 2.00 */ + } + + /* Otherwise, no dice, use PXENV+ structure */ + data_len = pxenv->undidatasize; + data_seg = pxenv->undidataseg; + code_len = pxenv->undicodesize; + code_seg = pxenv->undicodeseg; + PXEEntry = pxenv->rmentry; + type = "PXENV+"; + goto have_entrypoint; + + have_pxe: + data_len = pxe->seg[PXE_Seg_UNDIData].size; + data_seg = pxe->seg[PXE_Seg_UNDIData].sel; + code_len = pxe->seg[PXE_Seg_UNDICode].size; + code_seg = pxe->seg[PXE_Seg_UNDICode].sel; + PXEEntry = pxe->entrypointsp; + type = "!PXE"; + + have_entrypoint: + if (!quiet) { + printf("%s entry point found (we hope) at %04X:%04X via plan %c\n", + type, PXEEntry.seg, PXEEntry.offs, plan); + printf("UNDI code segment at %04X len %04X\n", code_seg, code_len); + printf("UNDI data segment at %04X len %04X\n", data_seg, data_len); + } + + code_seg = code_seg + ((code_len + 15) >> 4); + data_seg = data_seg + ((data_len + 15) >> 4); + + real_base_mem = max(code_seg, data_seg) >> 6; /* Convert to kilobytes */ + + probe_undi(); + + return 0; +} + +/* + * See if we have gPXE + */ +void gpxe_init(void) +{ + int err; + static __lowmem struct s_PXENV_FILE_API_CHECK api_check; + + if (APIVer >= 0x201) { + api_check.Size = sizeof api_check; + api_check.Magic = 0x91d447b2; + err = pxe_call(PXENV_FILE_API_CHECK, &api_check); + if (!err && api_check.Magic == 0xe9c17b20) + gpxe_funcs = api_check.APIMask; + } + + /* Necessary functions for us to use the gPXE file API */ + has_gpxe = (~gpxe_funcs & 0x4b) == 0; +} + + +/** + * Get a DHCP packet from the PXE stack into a lowmem buffer + * + * @param: type, packet type + * @return: buffer size + * + */ +static int pxe_get_cached_info(int type, void *buf, size_t bufsiz) +{ + int err; + static __lowmem struct s_PXENV_GET_CACHED_INFO get_cached_info; + printf(" %02x", type); + + memset(&get_cached_info, 0, sizeof get_cached_info); + get_cached_info.PacketType = type; + get_cached_info.BufferSize = bufsiz; + get_cached_info.Buffer = FAR_PTR(buf); + err = pxe_call(PXENV_GET_CACHED_INFO, &get_cached_info); + if (err) { + printf("PXE API call failed, error %04x\n", err); + kaboom(); + } + + return get_cached_info.BufferSize; +} + +/* + * This function unloads the PXE and UNDI stacks and + * unclaims the memory. + */ +__export void unload_pxe(uint16_t flags) +{ + /* PXE unload sequences */ + /* + * iPXE does: + * UNDI_SHUTDOWN, UNDI_CLEANUP, STOP_UNDI + * Older Syslinux did: + * UDP_CLOSE, UNDI_SHUTDOWN, UNLOAD_STACK, STOP_UNDI/UNDI_CLEANUP + */ + static const uint8_t new_api_unload[] = { + PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_STOP_UNDI, 0 + }; + static const uint8_t old_api_unload[] = { + PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_UNDI_CLEANUP, 0 + }; + + unsigned int api; + const uint8_t *api_ptr; + int err; + size_t int_addr; + static __lowmem union { + struct s_PXENV_UNDI_SHUTDOWN undi_shutdown; + struct s_PXENV_UNLOAD_STACK unload_stack; + struct s_PXENV_STOP_UNDI stop_undi; + struct s_PXENV_UNDI_CLEANUP undi_cleanup; + uint16_t Status; /* All calls have this as the first member */ + } unload_call; + + dprintf("Called unload_pxe()...\n"); + dprintf("FBM before unload = %d\n", bios_fbm()); + + err = reset_pxe(); + + dprintf("FBM after reset_pxe = %d, err = %d\n", bios_fbm(), err); + + /* If we want to keep PXE around, we still need to reset it */ + if (flags || err) + return; + + dprintf("APIVer = %04x\n", APIVer); + + api_ptr = APIVer >= 0x0200 ? new_api_unload : old_api_unload; + while((api = *api_ptr++)) { + dprintf("PXE call %04x\n", api); + memset(&unload_call, 0, sizeof unload_call); + err = pxe_call(api, &unload_call); + if (err || unload_call.Status != PXENV_STATUS_SUCCESS) { + printf("PXE unload API call %04x failed: 0x%x\n", + api, unload_call.Status); + goto cant_free; + } + } + + api = 0xff00; + if (real_base_mem <= bios_fbm()) { /* Sanity check */ + dprintf("FBM %d < real_base_mem %d\n", bios_fbm(), real_base_mem); + goto cant_free; + } + api++; + + /* Check that PXE actually unhooked the INT 0x1A chain */ + int_addr = (size_t)GET_PTR(*(far_ptr_t *)(4 * 0x1a)); + int_addr >>= 10; + if (int_addr >= real_base_mem || int_addr < bios_fbm()) { + set_bios_fbm(real_base_mem); + dprintf("FBM after unload_pxe = %d\n", bios_fbm()); + return; + } + + dprintf("Can't free FBM, real_base_mem = %d, " + "FBM = %d, INT 1A = %08x (%d)\n", + real_base_mem, bios_fbm(), + *(uint32_t *)(4 * 0x1a), int_addr); + +cant_free: + printf("Failed to free base memory error %04x-%08x (%d/%dK)\n", + api, *(uint32_t *)(4 * 0x1a), bios_fbm(), real_base_mem); + return; +} + +void net_parse_dhcp(void) +{ + int pkt_len; + struct bootp_t *bp; + const size_t dhcp_max_packet = 4096; + + bp = lmalloc(dhcp_max_packet); + if (!bp) { + printf("Out of low memory\n"); + kaboom(); + } + + *LocalDomain = 0; /* No LocalDomain received */ + + /* + * Get the DHCP client identifiers (query info 1) + */ + printf("Getting cached packet "); + pkt_len = pxe_get_cached_info(1, bp, dhcp_max_packet); + parse_dhcp(bp, pkt_len); + /* + * We don't use flags from the request packet, so + * this is a good time to initialize DHCPMagic... + * Initialize it to 1 meaning we will accept options found; + * in earlier versions of PXELINUX bit 0 was used to indicate + * we have found option 208 with the appropriate magic number; + * we no longer require that, but MAY want to re-introduce + * it in the future for vendor encapsulated options. + */ + *(char *)&DHCPMagic = 1; + + /* + * Get the BOOTP/DHCP packet that brought us file (and an IP + * address). This lives in the DHCPACK packet (query info 2) + */ + pkt_len = pxe_get_cached_info(2, bp, dhcp_max_packet); + parse_dhcp(bp, pkt_len); + /* + * Save away MAC address (assume this is in query info 2. If this + * turns out to be problematic it might be better getting it from + * the query info 1 packet + */ + MAC_len = bp->hardlen > 16 ? 0 : bp->hardlen; + MAC_type = bp->hardware; + memcpy(MAC, bp->macaddr, MAC_len); + + /* + * Get the boot file and other info. This lives in the CACHED_REPLY + * packet (query info 3) + */ + pkt_len = pxe_get_cached_info(3, bp, dhcp_max_packet); + parse_dhcp(bp, pkt_len); + printf("\n"); + + lfree(bp); +} |