From 184bc83718963636297d230d8f3846d025942a72 Mon Sep 17 00:00:00 2001 From: Liu Aleaxander Date: Thu, 6 Aug 2009 15:17:48 +0800 Subject: [PATCH] Core:PXELINUX: try to merge pxelinux the others Syslinux derivative disabled for now --- com32/include/com32.h | 12 +- core/Makefile | 6 +- core/bios.inc | 1 + core/cache.c | 11 +- core/dhcp_option.c | 259 +++++++ core/disk.c | 39 - core/diskio.c | 40 + core/dnsresolv.inc | 4 + core/extern.inc | 3 + core/fs.c | 50 +- core/include/disk.h | 1 + core/include/fs.h | 2 +- core/include/pxe.h | 370 +++++++++ core/parsecmd.inc | 1 + core/pxe.c | 1551 ++++++++++++++++++++++++++++++++++++++ core/pxelinux.asm | 1993 ++----------------------------------------------- 16 files changed, 2301 insertions(+), 2042 deletions(-) create mode 100644 core/dhcp_option.c delete mode 100644 core/disk.c create mode 100644 core/include/pxe.h create mode 100644 core/pxe.c diff --git a/com32/include/com32.h b/com32/include/com32.h index c003f7c..50485ae 100644 --- a/com32/include/com32.h +++ b/com32/include/com32.h @@ -153,21 +153,27 @@ static inline void *MK_PTR(uint16_t __seg, uint16_t __offs) /* Some tools to handle 16:16 far pointers in memory */ struct __far_ptr { - uint32_t __ptr; + union { + uint32_t ptr; + struct { + uint16_t offs, seg; + }; + }; } __attribute__ ((packed)); typedef struct __far_ptr far_ptr_t; static inline void *GET_PTR(far_ptr_t __fptr) { - return MK_PTR(__fptr.__ptr >> 16, __fptr.__ptr); + return MK_PTR(__fptr.seg, __fptr.offs); } static inline far_ptr_t FAR_PTR(void *__ptr) { far_ptr_t __fptr; - __fptr.__ptr = (SEG(__ptr) << 16) + OFFS(__ptr); + __fptr.offs = OFFS(__ptr); + __fptr.seg = SEG(__ptr); return __fptr; } diff --git a/core/Makefile b/core/Makefile index 1cb1170..b690ef3 100644 --- a/core/Makefile +++ b/core/Makefile @@ -32,10 +32,10 @@ INCLUDES = -I./include -I$(com32)/include CODEPAGE = cp865 # The targets to build in this directory... -BTARGET = kwdhash.gen \ - extlinux.bin extlinux.bss extlinux.sys \ +BTARGET = kwdhash.gen pxelinux.0 + #extlinux.bin extlinux.bss extlinux.sys \ ldlinux.bss ldlinux.sys ldlinux.bin \ - isolinux.bin isolinux-debug.bin + pxelinux.0 isolinux.bin isolinux-debug.bin # All primary source files for the main syslinux files NASMSRC := $(wildcard *.asm) diff --git a/core/bios.inc b/core/bios.inc index 987a216..4c6c5b5 100644 --- a/core/bios.inc +++ b/core/bios.inc @@ -18,6 +18,7 @@ %ifndef _BIOS_INC %define _BIOS_INC + global BIOS_fbm, BIOS_timer absolute 4*1Eh ; In the interrupt table fdctab equ $ diff --git a/core/cache.c b/core/cache.c index b0fbee7..4be99bb 100644 --- a/core/cache.c +++ b/core/cache.c @@ -73,11 +73,11 @@ void cache_init(struct device *dev, int block_size_shift) */ struct cache_struct* get_cache_block(struct device *dev, block_t block) { + struct cache_struct *head = (struct cache_struct *)dev->cache_head; + struct cache_struct *last = head->prev; /* let's find it from the end, 'cause the endest is the freshest */ - struct cache_struct *cs = dev->cache_head->prev; - struct cache_struct *head, *last; + struct cache_struct *cs = head->prev; int i; - static int total_read; static int missed; @@ -104,7 +104,7 @@ struct cache_struct* get_cache_block(struct device *dev, block_t block) /* missed, so we need to load it */ if (i == dev->cache_entries) { /* store it at the head of real cache */ - cs = dev->cache_head->next; + cs = head->next; cs->block = block; getoneblk(cs->data, block, dev->cache_block_size); @@ -116,9 +116,6 @@ struct cache_struct* get_cache_block(struct device *dev, block_t block) cs->next->prev = cs->prev; /* add to just before head node */ - last = dev->cache_head->prev; - head = dev->cache_head; - last->next = cs; cs->prev = last; head->prev = cs; diff --git a/core/dhcp_option.c b/core/dhcp_option.c new file mode 100644 index 0000000..f9ff4f2 --- /dev/null +++ b/core/dhcp_option.c @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include + +void subnet_mask(void *data, int opt_len) +{ + if (opt_len != 4) + return; + Netmask = *(uint32_t *)data; +} + +void router(void *data, int opt_len) +{ + if (opt_len != 4) + return; + Gateway = *(uint32_t *)data; +} + +void dns_servers(void *data, int opt_len) +{ + int num = opt_len >> 2; + int i; + + if (num > DNS_MAX_SERVERS) + num = DNS_MAX_SERVERS; + + for (i = 0; i < num; i++) { + DNSServers[i] = *(uint32_t *)data; + data += 4; + } + + /* NOT SURE FOR NOW */ + LastDNSServer = OFFS_WRT(&DNSServers[num - 1], 0); +} + +void local_domain(void *data, int opt_len) +{ + com32sys_t regs; + char *p = (char *)data + opt_len; + char end = *p; + + memset(®s, 0, sizeof regs); + *p = '\0'; /* Zero-terminate option */ + regs.esi.w[0] = OFFS_WRT(data, 0); + regs.edi.w[0] = OFFS_WRT(LocalDomain, 0); + call16(dns_mangle, ®s, NULL); + *p = end; /* Resotre ending byte */ +} + +void vendor_encaps(void *data, int opt_len) +{ + /* Only recongnize PXELINUX options */ + parse_dhcp_options(data, opt_len, 208); +} + +void option_overload(void *data, int opt_len) +{ + if (opt_len != 1) + return; + OverLoad = *(uint8_t *)data; +} + + +void server(void *data, int opt_len) +{ + uint32_t ip; + + if (opt_len != 4) + return; + + if (ServerIP) + return; + + ip = *(uint32_t *)data; + if (ip_ok(ip)) + ServerIP = ip; +} + +void client_identifier(void *data, int opt_len) +{ + if (opt_len > MAC_MAX || opt_len < 2 || + MACLen != (opt_len >> 8) || + *(uint8_t *)data != MACType) + return; + + opt_len --; + MACLen = opt_len & 0xff; + memcpy(MAC, data+1, opt_len); + MAC[opt_len] = 0; +} + +void bootfile_name(void *data, int opt_len) +{ + strncpy(BootFile, data, opt_len); + BootFile[opt_len] = 0; +} + +void uuid_client_identifier(void *data, int opt_len) +{ + int type = *(uint8_t *)data; + if (opt_len != 17 || + (type | HaveUUID)) + return; + + HaveUUID = 1; + UUIDType = type; + memcpy(UUID, data+1, 16); + UUID[16] = 0; +} + +void pxelinux_configfile(void *data, int opt_len) +{ + DHCPMagic |= 2; + strncpy(ConfigName, data, opt_len); + ConfigName[opt_len] = 0; +} + +void pxelinux_pathprefix(void *data,int opt_len) +{ + DHCPMagic |= 4; + strncpy(PathPrefix, data, opt_len); + PathPrefix[opt_len] = 0; +} + +void pxelinux_reboottime(void *data, int opt_len) +{ + if ((opt_len && 0xff) != 4) + return ; + + RebootTime = ntohl(*(uint32_t *)data); + DHCPMagic |= 8; /* Got reboot time */ +} + + +struct dhcp_options { + int opt_num; + void (*fun) (void *, int); +}; + +struct dhcp_options dhcp_opts[] = { + {1, subnet_mask}, + {3, router}, + {6, dns_servers}, + {15, local_domain}, + {43, vendor_encaps}, + {52, option_overload}, + {54, server}, + {61, client_identifier}, + {67, bootfile_name}, + {97, uuid_client_identifier}, + {209, pxelinux_configfile}, + {210, pxelinux_pathprefix}, + {211, pxelinux_reboottime} +}; + +/* + * Parse a sequence of DHCP options, pointed to by _option_; + * -- some DHCP servers leave option fields unterminated + * in violation of the spec. + * + * filter contains the minimum value for the option to recognize + * -- this is used to restrict parsing to PXELINUX-specific options only. + */ +void parse_dhcp_options(void *option, int size, int filter) +{ + uint8_t opt_num; + uint8_t opt_len; + uint8_t opt_filter = filter == 208 ? 208 : 0; + int opt_entries = sizeof(dhcp_opts) / sizeof(dhcp_opts[0]); + int i = 0; + char *p = option; + struct dhcp_options *opt; + + if (opt_filter) + printf("***NOTE!:*** we hit a pxelinux-specific options\n"); + + while (size --) { + opt_num = *p++; + + if (!size) + break; + if (opt_num == 0) + continue; + if (opt_num == 0xff) + break; + + /* Anything else will have a lenght filed */ + opt_len = *p++; /* c <- option lenght */ + size = size - opt_len - 1; + if (size < 0) + break; + if (opt_num < opt_filter) { /* Is the option value valid */ + option += opt_len; /* Try next */ + continue; + } + + opt = dhcp_opts; + for (i = 0; i < opt_entries; i++) { + if (opt_num == opt->opt_num) { + opt->fun(p, opt_len); + break; + } + opt ++; + } + + /* parse next */ + p += opt_len; + } +} + +/* + * + ; + ; parse_dhcp + ; + ; Parse a DHCP packet. This includes dealing with "overloaded" + ; option fields (see RFC 2132, section 9.3) + ; + ; This should fill in the following global variables, if the + ; information is present: + ; + ; MyIP - client IP address + ; ServerIP - boot server IP address + ; Netmask - network mask + ; Gateway - default gateway router IP + ; BootFile - boot file name + ; DNSServers - DNS server IPs + ; LocalDomain - Local domain name + ; MACLen, MAC - Client identifier, if MACLen == 0 + ; + ; This assumes the DHCP packet is in "trackbuf". + ; +*/ +void parse_dhcp(int pkt_len) +{ + struct bootp_t *dhcp = (struct bootp_t *)trackbuf; + int opt_len; + + OverLoad = 0; + if (ip_ok(dhcp->yip)) + MyIP = dhcp->yip; + + if (ip_ok(dhcp->sip)) + ServerIP = dhcp->sip; + + opt_len = (char *)dhcp + pkt_len - (char *)&dhcp->options; + if (opt_len && (dhcp->option_magic == BOOTP_OPTION_MAGIC)) + parse_dhcp_options(&dhcp->options, opt_len, 0); + + if (OverLoad & 1) + parse_dhcp_options(&dhcp->bootfile, 128, 0); + else if (dhcp->bootfile[0]) + strcpy(BootFile, dhcp->bootfile); + + if (OverLoad & 2) + parse_dhcp_options(dhcp->sname, 64, 0); +} diff --git a/core/disk.c b/core/disk.c deleted file mode 100644 index 69a9c0b..0000000 --- a/core/disk.c +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include "core.h" -#include "disk.h" - -void read_sectors(char *buf, sector_t sector_num, int sectors) -{ - com32sys_t regs; - //static __lowmem char low_buf[65536]; - /* for safe, we use buf + (sectors << SECTOR_SHIFT) here */ - int high_addr = (buf + (sectors << SECTOR_SHIFT)) > (char *)0x100000; - - memset(®s, 0, sizeof regs); - regs.eax.l = sector_num; - regs.ebp.l = sectors; - - if (high_addr) { - regs.es = SEG(core_xfer_buf); - regs.ebx.w[0] = OFFS(core_xfer_buf); - } else { - regs.es = SEG(buf); - regs.ebx.w[0] = OFFS(core_xfer_buf); - } - - call16(getlinsec, ®s, NULL); - - if (high_addr) - memcpy(buf, core_xfer_buf, sectors << SECTOR_SHIFT); -} - - -void getoneblk(char *buf, block_t block, int block_size) -{ - int sec_per_block = block_size >> SECTOR_SHIFT; - - read_sectors(buf, block * sec_per_block, sec_per_block); -} - - diff --git a/core/diskio.c b/core/diskio.c index bf22220..274157b 100644 --- a/core/diskio.c +++ b/core/diskio.c @@ -303,3 +303,43 @@ struct disk *disk_init(uint8_t devno, bool cdrom, sector_t part_start, #endif return &disk; } + + +/* + * initialize the device structure + */ +struct device * device_init(uint8_t devno, bool cdrom, sector_t part_start, + uint16_t bsHeads, uint16_t bsSecPerTrack) +{ + static struct device dev; + + dev.disk = disk_init(devno, cdrom, part_start, bsHeads, bsSecPerTrack); + + /* for now, isolinux doesn't use cache */ + if (!cdrom) { + /* + * FIX!! I can't use __lowmem here, 'cause it will cause the error: + * "auxseg/lowmem region collides with xfer_buf_seg". + * + * static __lowmem char cache_buf[65536]; + */ + dev.cache_data = core_cache_buf; + dev.cache_size = sizeof core_cache_buf; + } else + dev.cache_data = NULL; + + return &dev; +} + + +/* debug function */ +void dump_dev(struct device *dev) +{ + printf("device type:%s\n", dev->disk->type ? "EDD" : "CHS"); + printf("drive number: 0x%x\n", dev->disk->disk_number); + printf("cache_data: %p\n", dev->cache_data); + printf("cache_head: %p\n", dev->cache_head); + printf("cache_block_size: %d\n", dev->cache_block_size); + printf("cache_entries: %d\n", dev->cache_entries); + printf("cache_size: %d\n", dev->cache_size); +} diff --git a/core/dnsresolv.inc b/core/dnsresolv.inc index 7b63237..9733282 100644 --- a/core/dnsresolv.inc +++ b/core/dnsresolv.inc @@ -33,6 +33,7 @@ DNS_MAX_SERVERS equ 4 ; Max no of DNS servers ; ; On return, DX contains the number of dots encountered. ; + global dns_mangle dns_mangle: push ax push bx @@ -151,6 +152,7 @@ dns_skiplabel: endstruc section .bss16 + global LocalDomain, DNSServers alignb 2 DNSSendBuf resb DNS_MAX_PACKET DNSRecvBuf resb DNS_MAX_PACKET @@ -176,6 +178,7 @@ pxe_udp_read_pkt_dns: .buffersize: dw DNS_MAX_PACKET ; Max packet size .buffer: dw DNSRecvBuf, 0 ; off, seg of buffer + global LastDNSServer LastDNSServer dw DNSServers ; Actual resolver function @@ -186,6 +189,7 @@ LastDNSServer dw DNSServers ; No segment assumptions permitted. ; section .text16 + global dns_resolv dns_resolv: push ds push es diff --git a/core/extern.inc b/core/extern.inc index 2e6dcd0..a820c9a 100644 --- a/core/extern.inc +++ b/core/extern.inc @@ -18,4 +18,7 @@ ; fat.c extern alloc_fill_dir, readdir + ; pxe.c + extern gendotquad + %endif ; EXTERN_INC diff --git a/core/fs.c b/core/fs.c index 165d07e..7dc3b2a 100644 --- a/core/fs.c +++ b/core/fs.c @@ -2,7 +2,7 @@ #include #include #include "fs.h" -#include "cache.h" +//#include "cache.h" /* The this fs pointer */ @@ -75,45 +75,6 @@ void searchdir(com32sys_t *regs) } -/* - * initialize the device structure - */ -struct device * device_init(uint8_t devno, bool cdrom, sector_t part_start, - uint16_t bsHeads, uint16_t bsSecPerTrack) -{ - static struct device dev; - - dev.disk = disk_init(devno, cdrom, part_start, bsHeads, bsSecPerTrack); - - /* for now, isolinux doesn't use cache */ - if (!cdrom) { - /* - * FIX!! I can't use __lowmem here, 'cause it will cause the error: - * "auxseg/lowmem region collides with xfer_buf_seg". - * - * static __lowmem char cache_buf[65536]; - */ - dev.cache_data = core_cache_buf; - dev.cache_size = sizeof core_cache_buf; - } else - dev.cache_data = NULL; - - return &dev; -} - - -/* debug function */ -void dump_dev(struct device *dev) -{ - printf("device type:%s\n", dev->disk->type ? "EDD" : "CHS"); - printf("drive number: 0x%x\n", dev->disk->disk_number); - printf("cache_data: %p\n", dev->cache_data); - printf("cache_head: %p\n", dev->cache_head); - printf("cache_block_size: %d\n", dev->cache_block_size); - printf("cache_entries: %d\n", dev->cache_entries); - printf("cache_size: %d\n", dev->cache_size); -} - /* * it will do: * set up the vfs fs structure; @@ -130,7 +91,10 @@ void fs_init(com32sys_t *regs) /* set up the fs stucture */ fs.fs_name = ops->fs_name; fs.fs_ops = ops; - fs.fs_dev = device_init(regs->edx.b[0], regs->edx.b[1], regs->ecx.l, + if (1)//! strcmp(fs.fs_name, "pxe")) + fs.fs_dev = NULL; + else + fs.fs_dev = device_init(regs->edx.b[0], regs->edx.b[1], regs->ecx.l, \ regs->esi.w[0], regs->edi.w[0]); this_fs = &fs; @@ -138,6 +102,6 @@ void fs_init(com32sys_t *regs) blk_shift = fs.fs_ops->fs_init(&fs); /* initialize the cache */ - if (fs.fs_dev->cache_data) - cache_init(fs.fs_dev, blk_shift); + //if (fs.fs_dev && fs.fs_dev->cache_data) + // cache_init(fs.fs_dev, blk_shift); } diff --git a/core/include/disk.h b/core/include/disk.h index 386f4f7..0d8702d 100644 --- a/core/include/disk.h +++ b/core/include/disk.h @@ -35,5 +35,6 @@ extern void getoneblk(char *, block_t, int); /* diskio.c */ struct disk *disk_init(uint8_t, bool, sector_t, uint16_t, uint16_t); +struct device *device_init(uint8_t, bool, sector_t, uint16_t, uint16_t); #endif /* DISK_H */ diff --git a/core/include/fs.h b/core/include/fs.h index b5128d8..163808e 100644 --- a/core/include/fs.h +++ b/core/include/fs.h @@ -44,7 +44,7 @@ struct device { /* the cache stuff */ char* cache_data; - struct cache_struct* cache_head; + void* cache_head; uint16_t cache_block_size; uint16_t cache_entries; uint32_t cache_size; diff --git a/core/include/pxe.h b/core/include/pxe.h new file mode 100644 index 0000000..b9c71df --- /dev/null +++ b/core/include/pxe.h @@ -0,0 +1,370 @@ + +/** +* ----------------------------------------------------------------------- +* +* Copyright 1999-2008 H. Peter Anvin - All Rights Reserved +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, Inc., 53 Temple Place Ste 330, +* Boston MA 02111-1307, USA; either version 2 of the License, or +* (at your option) any later version; incorporated herein by reference. +* +* ----------------------------------------------------------------------- + +* +* pxe.inc +* +* PXE opcodes +* +*/ +#ifndef PXE_H +#define PXE_H + + +#define PXENV_TFTP_OPEN 0x0020 +#define PXENV_TFTP_CLOSE 0x0021 +#define PXENV_TFTP_READ 0x0022 +#define PXENV_TFTP_READ_FILE 0x0023 +#define PXENV_TFTP_READ_FILE_PMODE 0x0024 +#define PXENV_TFTP_GET_FSIZE 0x0025 + +#define PXENV_UDP_OPEN 0x0030 +#define PXENV_UDP_CLOSE 0x0031 +#define PXENV_UDP_READ 0x0032 +#define PXENV_UDP_WRITE 0x0033 + +#define PXENV_START_UNDI 0x0000 +#define PXENV_UNDI_STARTUP 0x0001 +#define PXENV_UNDI_CLEANUP 0x0002 +#define PXENV_UNDI_INITIALIZE 0x0003 +#define PXENV_UNDI_RESET_NIC 0x0004 +#define PXENV_UNDI_SHUTDOWN 0x0005 +#define PXENV_UNDI_OPEN 0x0006 +#define PXENV_UNDI_CLOSE 0x0007 +#define PXENV_UNDI_TRANSMIT 0x0008 +#define PXENV_UNDI_SET_MCAST_ADDR 0x0009 +#define PXENV_UNDI_SET_STATION_ADDR 0x000A +#define PXENV_UNDI_SET_PACKET_FILTER 0x000B +#define PXENV_UNDI_GET_INFORMATION 0x000C +#define PXENV_UNDI_GET_STATISTICS 0x000D +#define PXENV_UNDI_CLEAR_STATISTICS 0x000E +#define PXENV_UNDI_INITIATE_DIAGS 0x000F +#define PXENV_UNDI_FORCE_INTERRUPT 0x0010 +#define PXENV_UNDI_GET_MCAST_ADDR 0x0011 +#define PXENV_UNDI_GET_NIC_TYPE 0x0012 +#define PXENV_UNDI_GET_IFACE_INFO 0x0013 +#define PXENV_UNDI_ISR 0x0014 +#define PXENV_STOP_UNDI 0x0015 +#define PXENV_UNDI_GET_STATE 0x0015 + +#define PXENV_UNLOAD_STACK 0x0070 +#define PXENV_GET_CACHED_INFO 0x0071 +#define PXENV_RESTART_DHCP 0x0072 +#define PXENV_RESTART_TFTP 0x0073 +#define PXENV_MODE_SWITCH 0x0074 +#define PXENV_START_BASE 0x0075 +#define PXENV_STOP_BASE 0x0076 + +/* gPXE extensions... */ +#define PXENV_FILE_OPEN 0x00e0 +#define PXENV_FILE_CLOSE 0x00e1 +#define PXENV_FILE_SELECT 0x00e2 +#define PXENV_FILE_READ 0x00e3 +#define PXENV_GET_FILE_SIZE 0x00e4 +#define PXENV_FILE_EXEC 0x00e5 +#define PXENV_FILE_API_CHECK 0x00e6 + +/* Exit codes */ +#define PXENV_EXIT_SUCCESS 0x0000 +#define PXENV_EXIT_FAILURE 0x0001 + +/* Status codes */ +#define PXENV_STATUS_SUCCESS 0x00 +#define PXENV_STATUS_FAILURE 0x01 +#define PXENV_STATUS_BAD_FUNC 0x02 +#define PXENV_STATUS_UNSUPPORTED 0x03 +#define PXENV_STATUS_KEEP_UNDI 0x04 +#define PXENV_STATUS_KEEP_ALL 0x05 +#define PXENV_STATUS_OUT_OF_RESOURCES 0x06 +#define PXENV_STATUS_ARP_TIMEOUT 0x11 +#define PXENV_STATUS_UDP_CLOSED 0x18 +#define PXENV_STATUS_UDP_OPEN 0x19 +#define PXENV_STATUS_TFTP_CLOSED 0x1a +#define PXENV_STATUS_TFTP_OPEN 0x1b +#define PXENV_STATUS_MCOPY_PROBLEM 0x20 +#define PXENV_STATUS_BIS_INTEGRITY_FAILURE 0x21 +#define PXENV_STATUS_BIS_VALIDATE_FAILURE 0x22 +#define PXENV_STATUS_BIS_INIT_FAILURE 0x23 +#define PXENV_STATUS_BIS_SHUTDOWN_FAILURE 0x24 +#define PXENV_STATUS_BIS_GBOA_FAILURE 0x25 +#define PXENV_STATUS_BIS_FREE_FAILURE 0x26 +#define PXENV_STATUS_BIS_GSI_FAILURE 0x27 +#define PXENV_STATUS_BIS_BAD_CKSUM 0x28 +#define PXENV_STATUS_TFTP_CANNOT_ARP_ADDRESS 0x30 +#define PXENV_STATUS_TFTP_OPEN_TIMEOUT 0x32 + +#define PXENV_STATUS_TFTP_UNKNOWN_OPCODE 0x33 +#define PXENV_STATUS_TFTP_READ_TIMEOUT 0x35 +#define PXENV_STATUS_TFTP_ERROR_OPCODE 0x36 +#define PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION 0x38 +#define PXENV_STATUS_TFTP_CANNOT_READ_FROM_CONNECTION 0x39 +#define PXENV_STATUS_TFTP_TOO_MANY_PACKAGES 0x3a +#define PXENV_STATUS_TFTP_FILE_NOT_FOUND 0x3b +#define PXENV_STATUS_TFTP_ACCESS_VIOLATION 0x3c +#define PXENV_STATUS_TFTP_NO_MCAST_ADDRESS 0x3d +#define PXENV_STATUS_TFTP_NO_FILESIZE 0x3e +#define PXENV_STATUS_TFTP_INVALID_PACKET_SIZE 0x3f +#define PXENV_STATUS_DHCP_TIMEOUT 0x51 +#define PXENV_STATUS_DHCP_NO_IP_ADDRESS 0x52 +#define PXENV_STATUS_DHCP_NO_BOOTFILE_NAME 0x53 +#define PXENV_STATUS_DHCP_BAD_IP_ADDRESS 0x54 +#define PXENV_STATUS_UNDI_INVALID_FUNCTION 0x60 +#define PXENV_STATUS_UNDI_MEDIATEST_FAILED 0x61 +#define PXENV_STATUS_UNDI_CANNOT_INIT_NIC_FOR_MCAST 0x62 +#define PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC 0x63 +#define PXENV_STATUS_UNDI_CANNOT_INITIALIZE_PHY 0x64 +#define PXENV_STATUS_UNDI_CANNOT_READ_CONFIG_DATA 0x65 +#define PXENV_STATUS_UNDI_CANNOT_READ_INIT_DATA 0x66 +#define PXENV_STATUS_UNDI_BAD_MAC_ADDRESS 0x67 +#define PXENV_STATUS_UNDI_BAD_EEPROM_CHECKSUM 0x68 +#define PXENV_STATUS_UNDI_ERROR_SETTING_ISR 0x69 +#define PXENV_STATUS_UNDI_INVALID_STATE 0x6a +#define PXENV_STATUS_UNDI_TRANSMIT_ERROR 0x6b +#define PXENV_STATUS_UNDI_INVALID_PARAMETER 0x6c +#define PXENV_STATUS_BSTRAP_PROMPT_MENU 0x74 +#define PXENV_STATUS_BSTRAP_MCAST_ADDR 0x76 +#define PXENV_STATUS_BSTRAP_MISSING_LIST 0x77 +#define PXENV_STATUS_BSTRAP_NO_RESPONSE 0x78 +#define PXENV_STATUS_BSTRAP_FILE_TOO_BIG 0x79 +#define PXENV_STATUS_BINL_CANCELED_BY_KEYSTROKE 0xa0 +#define PXENV_STATUS_BINL_NO_PXE_SERVER 0xa1 +#define PXENV_STATUS_NOT_AVAILABLE_IN_PMODE 0xa2 +#define PXENV_STATUS_NOT_AVAILABLE_IN_RMODE 0xa3 +#define PXENV_STATUS_BUSD_DEVICE_NOT_SUPPORTED 0xb0 +#define PXENV_STATUS_LOADER_NO_FREE_BASE_MEMORY 0xc0 +#define PXENV_STATUS_LOADER_NO_BC_ROMID 0xc1 +#define PXENV_STATUS_LOADER_BAD_BC_ROMID 0xc2 +#define PXENV_STATUS_LOADER_BAD_BC_RUNTIME_IMAGE 0xc3 +#define PXENV_STATUS_LOADER_NO_UNDI_ROMID 0xc4 +#define PXENV_STATUS_LOADER_BAD_UNDI_ROMID 0xc5 +#define PXENV_STATUS_LOADER_BAD_UNDI_DRIVER_IMAGE 0xc6 +#define PXENV_STATUS_LOADER_NO_PXE_STRUCT 0xc8 +#define PXENV_STATUS_LOADER_NO_PXENV_STRUCT 0xc9 +#define PXENV_STATUS_LOADER_UNDI_START 0xca +#define PXENV_STATUS_LOADER_BC_START 0xcb + + + + +/* + * some other defines + */ +#define PKTBUF_SIZE (65536 / MAX_OPEN) + +#define TFTP_BLOCKSIZE_LG2 9 +#define TFTP_BLOCKSIZE (1 << TFTP_BLOCKSIZE_LG2) +#define PKTBUF_SEG 0x4000 +#define DNS_MAX_SERVERS 4 + +#define is_digit(c) (((c) >= '0') && ((c) <= '9')) +#define htons(x) ( ( ((x) & 0xff) << 8) + ( ((x) &0xff00) >> 8) ) +#define ntohs(x) htons(x) +#define htonl(x) ( ( ((x) & 0xff) << 24) + ( ((x) & 0xff00) << 8 ) + \ + ( ((x) & 0xff0000) >> 8 ) + ( ((x) & 0xff000000) >> 24) ) +#define ntohl(x) htonl(x) + +/* + * TFTP operation codes + */ +#define TFTP_RRQ htons(1) // Read rest +#define TFTP_WRQ htons(2) // Write rest +#define TFTP_DATA htons(3) // Data packet +#define TFTP_ACK htons(4) // ACK packet +#define TFTP_ERROR htons(5) // ERROR packet +#define TFTP_OACK htons(6) // OACK packet + +/* + * TFTP error codes + */ +#define TFTP_EUNDEF htons(0) // Unspecified error +#define TFTP_ENOTFOUND htons(1) // File not found +#define TFTP_EACCESS htons(2) // Access violation +#define TFTP_ENOSPACE htons(3) // Disk full +#define TFTP_EBADOP htons(4) // Invalid TFTP operation +#define TFTP_EBADID htons(5) // Unknown transfer +#define TFTP_EEXISTS htons(6) // File exists +#define TFTP_ENOUSER htons(7) // No such user +#define TFTP_EOPTNEG htons(8) // Option negotiation failure + +#define BOOTP_OPTION_MAGIC htonl(0x63825363) + + +/* + * structures + */ +struct bootp_t { + uint8_t opcode; /* BOOTP/DHCP "opcode" */ + uint8_t hardware; /* ARP hreadware type */ + uint8_t hardlen; /* Hardware address length */ + uint8_t gatehops; /* Used by forwarders */ + uint32_t ident; /* Transaction ID */ + uint16_t seconds; /* Seconds elapsed */ + uint16_t flags; /* Broadcast flags */ + uint32_t cip; /* Cient IP */ + uint32_t yip; /* "Your" IP */ + uint32_t sip; /* Next Server IP */ + uint32_t gip; /* Relay agent IP */ + uint8_t macaddr[16]; /* Client MAC address */ + uint8_t sname[64]; /* Server name (optional) */ + char bootfile[128]; /* Boot file name */ + uint32_t option_magic; /* Vendor option magic cookie */ + uint8_t options[1260]; /* Vendor options */ +} __attribute__ ((packed)); + +struct open_file_t { + uint16_t tftp_localport; /* Local port number (0=not in us)*/ + uint16_t tftp_remoteport; /* Remote port number */ + uint32_t tftp_remoteip; /* Remote IP address */ + uint32_t tftp_filepos; /* bytes downloaded (includeing buffer) */ + uint32_t tftp_filesize; /* Total file size(*) */ + uint32_t tftp_blksize; /* Block size for this connection(*) */ + uint16_t tftp_bytesleft; /* Unclaimed data bytes */ + uint16_t tftp_lastpkt; /* Sequence number of last packet (NBO) */ + uint16_t tftp_dataptr; /* Pointer to available data */ + uint8_t tftp_goteof; /* 1 if the EOF packet received */ + uint8_t tftp_unused[3]; /* Currently unused */ + uint16_t tftp_pktbuf; /* Packet buffer offset */ +} __attribute__ ((packed)); +extern char Files[]; + +struct pxe_udp_write_pkt { + uint16_t status; + uint32_t sip; + uint32_t gip; + uint16_t lport; + uint16_t rport; + uint16_t buffersize; + uint16_t buffer[2]; +} __attribute__ ((packed)); + +struct pxe_udp_read_pkt { + uint16_t status; + uint32_t sip; + uint32_t dip; + uint16_t rport; + uint16_t lport; + uint16_t buffersize; + uint16_t buffer[2]; +} __attribute__ ((packed)); + +struct pxe_bootp_query_pkt { + uint16_t status; + uint16_t packettype; + uint16_t buffersize; + uint16_t buffer[2]; + uint16_t bufferlimit; +} __attribute__ ((packed)); + +struct pxe_udp_open_pkt { + uint16_t status; + uint32_t sip; +} __attribute__ ((packed)); + +struct gpxe_file_api_check { + uint16_t status; + uint16_t size; + uint32_t magic; + uint32_t provider; + uint32_t apimask; + uint32_t flags; +} __attribute__ ((packed)); + +struct gpxe_file_open { + uint16_t status; + uint16_t filehandle; + uint16_t filename[2]; + uint32_t reserved; +} __attribute__ ((packed)); + +struct gpxe_get_file_size { + uint16_t status; + uint16_t filehandle; + uint32_t filesize; +} __attribute__ ((packed)); + +struct gpxe_file_read { + uint16_t status; + uint16_t filehandle; + uint16_t buffersize; + uint16_t buffer[2]; +} __attribute__ ((packed)); + +/* + * externs + */ +extern uint32_t ServerIP; +extern uint32_t MyIP; +extern uint32_t Netmask; +extern uint32_t Gateway; +extern uint32_t ServerPort; + +#define MAC_MAX 32 +extern char MACStr[]; /* MAC address as a string */ +extern char MAC[]; /* Actual MAC address */ +extern char BOOTIFStr[]; /* Space for "BOOTIF=" */ +extern uint8_t MACLen; /* MAC address len */ +extern uint8_t MACType; /* MAC address type */ + + +extern uint8_t DHCPMagic; +extern uint8_t OverLoad; +extern uint32_t RebootTime; + + +/* TFTP ACK packet */ +extern uint16_t ack_packet_buf[]; + +extern void kaboom(void); +extern void dns_mangle(void); +extern char trackbuf[]; +extern char BootFile[]; +extern char PathPrefix[]; +extern char CurrentDirName[]; +extern char LocalDomain[]; + +extern char packet_buf[]; + +extern char IPOption[]; +extern char DotQuadBuf[]; + + +extern uint32_t DNSServers[]; +extern uint16_t LastDNSServer; + +extern char ConfigName[]; + +extern uint16_t RealBaseMem; +extern uint16_t APIVer; +extern far_ptr_t PXEEntry; + +extern far_ptr_t InitStack; + +extern int HaveUUID; +extern uint8_t UUIDType; +extern char UUID[]; + +extern volatile uint16_t BIOS_timer; + + +/* + * functions + */ +int ip_ok(uint32_t); +void parse_dhcp(int); +void parse_dhcp_options(void *, int, int); + + + + +#endif /* pxe.h */ diff --git a/core/parsecmd.inc b/core/parsecmd.inc index 94627b8..7e0ac5c 100644 --- a/core/parsecmd.inc +++ b/core/parsecmd.inc @@ -111,6 +111,7 @@ err_badcfg db 'Unknown keyword in configuration file: ',0 err_noparm db 'Missing parameter in configuration file. Keyword: ',0 section .uibss + global KernelName alignb 4 vk_size equ (vk_end + 3) & ~3 VKernelBuf: resb vk_size ; "Current" vkernel diff --git a/core/pxe.c b/core/pxe.c new file mode 100644 index 0000000..1a61d96 --- /dev/null +++ b/core/pxe.c @@ -0,0 +1,1551 @@ +#include +#include +#include +#include +#include +#include + +#define MAX_OPEN_LG2 5 +#define MAX_OPEN (1 << MAX_OPEN_LG2) +#define FILENAME_MAX_LG2 7 +#define FILENAME_MAX (1 << FILENAME_MAX_LG2) + +#define MAX(a,b) (a > b ? a : b) + +#define GPXE 1 +#define USE_PXE_PROVIDED_STACK 0 + +char *err_nopxe = "No !PXE or PXENV+ API found; we're dead...\n"; +char *err_pxefailed = "PXE API call failed, error "; +char *err_udpinit = "Failed to initialize UDP stack\n"; +char *err_noconfig = "Unable to locate configuration file\n"; +char *err_damage = "TFTP server sent an incomprehesible reply\n"; + +char *tftpprefix_msg = "TFTP prefix: "; +char *get_packet_msg = "Getting cached packet "; + +uint16_t NextSocket = 49152; + +int has_gpxe; +int HaveUUID = 0; +uint8_t UUIDType; +uint8_t uuid_dashes[] = {4, 2, 2, 2, 6, 0}; + +uint16_t StructPtr[2]; + +static const uint8_t TimeoutTable[] = { + 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44, 53, 64, 77, + 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0 +}; + +char *mode = "octet"; +char *tsize_str = "tsize"; +int tsize_len = 6; /* We should include the final null here */ + +char *blksize_str = "blksize"; +int blksize_len = 8; + +char *asciidec = "1408"; + + + +/* + * Allocate a local UDP port structure. + * return the socket pointer if success, or null if failure + * + */ +struct open_file_t* allocate_socket() +{ + extern uint16_t NextSocket; + uint16_t i = MAX_OPEN; + struct open_file_t *socket = (struct open_file_t *)&Files; + + for (; i > 0; i--) { + if (*(uint16_t*)socket == 0) + break; + socket ++; + } + + /* Not found */ + if (i == 0) + return NULL; + + /* + * Allocate a socket number. Socket numbers are made guaranteed unique + * by including the socket slot number(inverted, because we use the loop + * counter cx; add a counter value to keep the numbers from being likely + * to get immediately reused. + * + * The NextSocket variable also contains the top two bits set. This + * generates a value in the range 49152 to 57343. + * + */ + i--; + NextSocket = ((NextSocket + 1) & ((1 << (13-MAX_OPEN_LG2))-1)) | 0xc000; + i <<= 13-MAX_OPEN_LG2; + i += NextSocket; + i = ntohs(i) ; /* convet to network byte order, little to big */ + *(uint16_t*)socket = i; /* socket in use */ + + return socket; +} + +/* + * free socket, socket in SI; return SI = 0, ZF = 1 for convenience + */ +void free_socket(struct open_file_t *file) +{ + /* tftp_pktbuf is not cleared */ + memset(file, 0, sizeof(struct open_file_t) - 2); +} + +/** + * Take a nubmer of bytes in memory and convert to lower-case hxeadecimal + * + * @param: dst, output buffer + * @param: src, input buffer + * @param: count, number of bytes + * + */ +void lchexbytes(char *dst, const void *src, int count) +{ + uint8_t half; + uint8_t c; + const uint8_t *s = src; + + for(; count > 0; count--) { + c = *s++; + half = ((c >> 4) & 0x0f) + '0'; + *dst++ = half > '9' ? (half + 'a' - '9' - 1) : half; + + half = (c & 0x0f) + '0'; + *dst++ = half > '9' ? (half + 'a' - '9' - 1) : half; + } +} + +/** + * just like the lchexbytes, except to upper-case + * + */ +void uchexbytes(char *dst, const void *src, int count) +{ + uint8_t half; + uint8_t c; + const uint8_t *s = src; + + for(; count > 0; count--) { + c = *s++; + half = ((c >> 4) & 0x0f) + '0'; + *dst++ = half > '9' ? (half + 'A' - '9' - 1) : half; + + half = (c & 0x0f) + '0'; + *dst++ = half > '9' ? (half + 'A' - '9' - 1) : half; + } +} + + + + +/* + * + * Tests an IP address in EAX for validity; return with 0 for bad, 1 for good. + * We used to refuse class E, but class E addresses are likely to become + * assignable unicast addresses in the near future. + * + */ +int ip_ok(uint32_t ip) +{ + if (ip == -1) /* Refuse the all-one address */ + goto bad; + if ((ip & 0xff) == 0) /* Refuse network zero */ + goto bad; + if ((ip & 0xff) == 0xff) /* Refuse loopback */ + goto bad; + if ((ip & 0xf0) == 0xe0) /* Refuse class D */ + goto bad; + + return 1; + + bad: + return 0; +} + + +/********************************************************************* +; +; gendotquad +; +; Take an IP address (in network byte order) in EAX and +; output a dotted quad string to ES:DI. +; DI points to terminal null at end of string on exit. +; + *********************************************************************/ +/** + * @param: dst, to store the converted ip string + * @param: ip, the ip address that needed convert. + * + * @return: the ip string length + */ +int gendotquad(char *dst, uint32_t ip) +{ + int part; + int i = 0, j; + char temp[4]; + char *p = dst; + + for (; i < 4; i++) { + j = 0; + part = ip & 0xff; + do { + temp[j++] = (part % 10) + '0'; + }while(part /= 10); + for (; j > 0; j--) + *p++ = temp[j-1]; + *p++ = '.'; + + ip >>= 8; + } + /* drop the last dot '.' and zero-terminate string*/ + *(--p) = 0; + + return p - dst; +} + +/* + * parse the ip_str and return the ip address with *res. + * return the the string address after the ip string + * + */ +char *parse_dotquad(char *ip_str, uint32_t *res) +{ + char *p = ip_str; + int i = 0; + uint8_t part = 0; + uint32_t ip = 0; + + for (; i < 4; i++) { + while (is_digit(*p)) { + part = part * 10 + *p - '0'; + p++; + } + if (i != 3 && *p != '.') + return NULL; + + ip = (ip << 8) | part; + part = 0; + p++; + } + p --; + + *res = ip; + return p; +} + +/* + * the ASM pxenv function wrapper, return 1 if error, or 0 + * + */ +int pxe_call(int opcode, void *data) +{ + extern void pxenv(void); + com32sys_t in_regs, out_regs; + +#if 0 + printf("pxe_call op %04x data %p\n", opcode, data); +#endif + + memset(&in_regs, 0, sizeof in_regs); + + in_regs.ebx.w[0] = opcode; + in_regs.es = SEG(data); + in_regs.edi.w[0] = OFFS(data); + call16(pxenv, &in_regs, &out_regs); + + return out_regs.eflags.l & EFLAGS_CF; /* CF SET for fail */ +} + +/** + * + * Send ACK packet. This is a common operation and so is worth canning. + * + * @param: file, TFTP block pointer + * @param: ack_num, Packet # to ack (network byte order) + * + */ +void ack_packet(struct open_file_t *file, uint16_t ack_num) +{ + int err; + static __lowmem struct pxe_udp_write_pkt uw_pkt; + + /* Packet number to ack */ + ack_packet_buf[1] = ack_num; + uw_pkt.lport = file->tftp_localport; + uw_pkt.rport = file->tftp_remoteport; + uw_pkt.sip = file->tftp_remoteip; + uw_pkt.gip = ((uw_pkt.sip ^ MyIP) & Netmask) ? Gateway : 0; + uw_pkt.buffer[0] = OFFS_WRT(ack_packet_buf, 0); + uw_pkt.buffer[1] = 0; /* seems SEG and OFFS stuff doesn't work here */ + uw_pkt.buffersize = 4; + + err = pxe_call(PXENV_UDP_WRITE, &uw_pkt); +#if 0 + printf("sent %s\n", err ? "FAILED" : "OK"); +#endif +} + + +/** + * Get a DHCP packet from the PXE stack into the trackbuf + * + * @param: type, packet type + * @return: buffer size + * + */ +int pxe_get_cached_info(int type) +{ + int err; + static __lowmem struct pxe_bootp_query_pkt bq_pkt; + printf(" %02x", type); + + bq_pkt.status = 0; + bq_pkt.packettype = type; + bq_pkt.buffersize = 8192; + bq_pkt.buffer[0] = OFFS_WRT(trackbuf, 0); + bq_pkt.buffer[1] = 0; + + err = pxe_call(PXENV_GET_CACHED_INFO, &bq_pkt); + if (err) { + printf("%s %04x\n", err_pxefailed, err); + call16(kaboom, NULL, NULL); + } + + return bq_pkt.buffersize; +} + + + +#if GPXE + +/* + * Return 1 if and only if the buffer pointed to by + * url is a URL (contains ://) + * + */ +int is_url(char *url) +{ + + while (*url) { + if(! strncmp(url, "://", 3)) + return 1; + + url++; + } + return 0; +} + + +/* + * Return CF=0 if and only if the buffer pointed to by DS:SI is a URL + * (contains ://) *and* the gPXE extensions API is available. No + * registers modified. + */ +int is_gpxe(char *url) +{ + int err; + static __lowmem struct gpxe_file_api_check ac; + char *gpxe_warning_msg = "URL syntax, but gPXE extensions not detected, tring plain TFTP...\n"; + + if (! is_url(url)) + return 0; + + ac.size = 20; + ac.magic = 0x91d447b2; + /* If has_gpxe is greater than one, means the gpxe status is unknow */ + while (has_gpxe > 1) { + err = pxe_call(PXENV_FILE_API_CHECK, &ac); + if (err || ac.magic != 0xe9c17b20) + printf("%s\n", gpxe_warning_msg); + else + has_gpxe = (~ac.provider & 0xffff) & 0x4b ? 0 : 1; + + if (!has_gpxe) + printf("%s\n", gpxe_warning_msg); + } + + if (has_gpxe == 1) + return 1; + else + return 0; +} + +/** + * Get a fresh packet from a gPXE socket + * @param: file -> socket structure + * + */ +void get_packet_gpxe(struct open_file_t *file) +{ + static __lowmem struct gpxe_file_read fr; + int err; + + while (1) { + fr.filehandle = file->tftp_remoteport; + fr.buffer[0] = file->tftp_pktbuf; + fr.buffer[1] = PKTBUF_SEG; + fr.buffersize = PKTBUF_SIZE; + err = pxe_call(PXENV_FILE_READ, &fr); + if (!err) /* successed */ + break; + + if (fr.status == PXENV_STATUS_TFTP_OPEN) + continue; + call16(kaboom, NULL, NULL); + } + + file->tftp_bytesleft = fr.buffersize; + file->tftp_filepos += fr.buffersize; + + if (file->tftp_bytesleft == 0) + file->tftp_filesize = file->tftp_filepos; + + /* if we're done here, close the file */ + if (file->tftp_filesize > file->tftp_filepos) + return; + + /* Got EOF, close it */ + file->tftp_goteof = 1; + pxe_call(PXENV_FILE_CLOSE, &fr); +} +#endif /* GPXE */ + + +/* + * mangle a filename pointed to by _src_ into a buffer pointed + * to by _dst_; ends on encountering any whitespace. + * + * The first four bytes of the manged name is the IP address of + * the download host, 0 for no host, or -1 for a gPXE URL. + * + */ +void pxe_mangle_name(char *dst, char *src) +{ + char *p = src; + uint32_t ip; + int i = 0; + +#if GPXE + if (is_url(src)) + goto prefix_done; +#endif + + ip = ServerIP; + if (*p == 0) + goto noip; + if (! strncmp(p, "::", 2)) + goto gotprefix; + else { + while (*p && strncmp(p, "::", 2)) + p ++; + if (! *p) + goto noip; + /* + * we have a :: prefix of ip sort, it could be either a DNS + * name or dot-quad IP address. Try the dot-quad first. + */ + p = src; + if ((p = parse_dotquad(p, &ip)) && !strncmp(p, "::", 2)) + goto gotprefix; + else { +#if 0 + extern void dns_resolv(void); + call16(dns_resolv, regs, regs); + p = (char *)MK_PTR(regs->ds, regs->esi.w[0]); + ip = regs->eax.l; + if (! strncmp(p, "::", 2)) + if (ip) + goto gotprefix; +#endif + } + } + noip: + p = src; + ip = 0; + goto prefix_done; + + gotprefix: + p += 2; /* skip double colon */ + + prefix_done: + *(uint32_t *)dst = ip; + dst += 4; + i = FILENAME_MAX - 5; + + do { + if (*p <= ' ') + break; + *dst++ = *p++; + }while (i--); + + i ++; + while (i) { + *dst++ = 0; + i --; + } + +#if 0 + printf("the name before mangling: "); + dump16(src); + printf("the name after mangling: "); + dump16((char *)MK_PTR(regs->ds, regs->edi.w[0])); +#endif +} + + +/* + * + ; + ; Get a fresh packet if the buffer is drained, and we haven't hit + ; EOF yet. The buffer should be filled immediately after draining! + ; + ; expects fs -> pktbuf_seg and ds:si -> socket structure + ; +*/ +void fill_buffer(struct open_file_t *file) +{ + int err; + int last_pkt; + const uint8_t *timeout_ptr = TimeoutTable; + uint8_t timeout; + uint16_t buffersize; + uint16_t old_time; + void *data = NULL; + static __lowmem struct pxe_udp_read_pkt pkt; + + if (file->tftp_bytesleft || file->tftp_goteof) + return; + +#if GPXE + if (file->tftp_localport == 0xffff) { + get_packet_gpxe(file); + return; + } +#endif + + + /* + * Start by ACKing the previous packet; this should cause + * the next packet to be sent. + */ + ack_again: + ack_packet(file, file->tftp_lastpkt); + + timeout_ptr = TimeoutTable; + timeout = *timeout_ptr++; + old_time = BIOS_timer; + while (timeout) { + pkt.buffer[0] = file->tftp_pktbuf; + pkt.buffer[1] = PKTBUF_SEG; + pkt.buffersize = PKTBUF_SIZE; + pkt.sip = file->tftp_remoteip; + pkt.dip = MyIP; + pkt.rport = file->tftp_remoteport; + pkt.lport = file->tftp_localport; + err = pxe_call(PXENV_UDP_READ, &pkt); + if (err) { + if (BIOS_timer == old_time) { + printf("."); + continue; + } + printf("+"); + + timeout--; /* decrease one timer tick */ + if (!timeout) { + timeout = *timeout_ptr++; + if (!timeout) + break; + } + continue; + } + + if (pkt.buffersize < 4) /* Bad size for a DATA packet */ + continue; + + data = MK_PTR(PKTBUF_SEG, file->tftp_pktbuf); + if (*(uint16_t *)data != TFTP_DATA) /* Not a data packet */ + continue; + + /* If goes here, recevie OK, break */ + break; + } + + /* time runs out */ + if (timeout == 0) + call16(kaboom, NULL, NULL); + + last_pkt = file->tftp_lastpkt; + last_pkt = ntohs(last_pkt); /* Host byte order */ + last_pkt++; + last_pkt = htons(last_pkt); /* Network byte order */ + if (*(uint16_t *)(data + 2) != last_pkt) { + /* + * Wrong packet, ACK the packet and try again. + * This is presumably because the ACK got lost, + * so the server just resent the previous packet. + */ +#if 0 + printf("Wrong packet, wanted %04x, got %04x\n", htons(last_pkt), htons(*(uint16_t *)(data+2))); +#endif + goto ack_again; + } + + /* It's the packet we want. We're also EOF if the size < blocksize */ + file->tftp_lastpkt = last_pkt; /* Update last packet number */ + buffersize = pkt.buffersize - 4; /* Skip TFTP header */ + file->tftp_dataptr = file->tftp_pktbuf + 4; + file->tftp_filepos += buffersize; + file->tftp_bytesleft = buffersize; + if (buffersize < file->tftp_blksize) { + /* it's the last block, ACK packet immediately */ + ack_packet(file, *(uint16_t *)(data + 2)); + + /* Make sure we know we are at end of file */ + file->tftp_filesize = file->tftp_filepos; + file->tftp_goteof = 1; + } +} + + +/** + * getfssec: Get multiple clusters from a file, given the starting cluster. + * In this case, get multiple blocks from a specific TCP connection. + * + * @param: fs, the fs_info structure address, in pxe, we don't use this. + * @param: buf, buffer to store the read data + * @param: openfile, TFTP socket pointer + * @param: blocks, 512-byte block count; 0FFFFh = until end of file + * + * @return: the bytes read + * + */ +uint32_t pxe_getfssec(struct fs_info *fs, char *buf, + void *open_file, int blocks, int *have_more) +{ + struct open_file_t *file = (struct open_file_t *)open_file; + int count = blocks; + int chunk; + int bytes_read = 0; + + sti(); + fs = NULL; /* drop the compile warning message */ + + count <<= TFTP_BLOCKSIZE_LG2; + while (count) { + fill_buffer(file); /* If we have no 'fresh' buffer, get it */ + if (! file->tftp_bytesleft) + break; + + chunk = count; + if (chunk > file->tftp_bytesleft) + chunk = file->tftp_bytesleft; + file->tftp_bytesleft -= chunk; + memcpy(buf, MK_PTR(PKTBUF_SEG, file->tftp_dataptr), chunk); + file->tftp_dataptr += chunk; + buf += chunk; + bytes_read += chunk; + count -= chunk; + } + + + if (file->tftp_bytesleft || (file->tftp_filepos < file->tftp_filesize)) { + fill_buffer(file); + *have_more = 1; + } else if (file->tftp_goteof) { + /* + * The socket is closed and the buffer dranined + * close socket structure and re-init for next user + */ + free_socket(file); + *have_more = 0; + } + + return bytes_read; + } + + + +/* + * Fill the packet tail with the tftp informations then retures the lenght + */ +int fill_tail(char *dst) +{ + char *p = dst; + strcpy(p, mode); + p += strlen(mode) + 1; + + strcpy(p, tsize_str); + p += strlen(tsize_str) + 1; + + strcpy(p, "0"); + p += 2; + + strcpy(p, blksize_str); + p += strlen(blksize_str) + 1; + + strcpy(p, asciidec); + p += strlen(asciidec) + 1; + + return p - dst; +} + + +struct tftp_options { + char *str_ptr; /* string pointer */ + int str_len; /* string lenght */ + int offset; /* offset into socket structre */ +}; +struct tftp_options tftp_options[2]; + +inline void init_options() +{ + tftp_options[0].str_ptr = tsize_str; + tftp_options[0].str_len = tsize_len; + tftp_options[0].offset = (int)&((struct open_file_t *)0)->tftp_filesize; + + tftp_options[1].str_ptr = blksize_str; + tftp_options[1].str_len = blksize_len; + tftp_options[1].offset = (int)&((struct open_file_t *)0)->tftp_blksize; +} + +/* + * + * + * searchdir: + * + * Open a TFTP connection to the server + * + * On entry: + * DS:DI = mangled filename + * If successful: + * ZF clear + * SI = socket pointer + * EAX = file length in bytes, or -1 if unknown + * If unsuccessful + * ZF set + * + */ +void pxe_searchdir(char *filename, struct file *file) +{ + char *buf = packet_buf; + char *p = filename; + char *options; + char *src, *dst; + char *data; + struct open_file_t *open_file; + static __lowmem struct pxe_udp_write_pkt uw_pkt; + static __lowmem struct pxe_udp_read_pkt ur_pkt; + static __lowmem struct gpxe_file_open fo; + static __lowmem struct gpxe_get_file_size gs; + struct tftp_options *tftp_opt = tftp_options; + int i = 0; + int tftp_opts = sizeof tftp_options / sizeof tftp_options[0]; + int err; + int buffersize; + const uint8_t *timeout_ptr; + uint8_t timeout; + uint16_t oldtime; + uint16_t tid; + uint16_t opcode; + uint16_t blk_num; + uint32_t ip; + uint32_t filesize = 0; + uint32_t *data_ptr; + + init_options(); + open_file = allocate_socket(); + if (!open_file) + goto done; + + timeout_ptr = TimeoutTable; /* Reset timeout */ + + sendreq: + uw_pkt.buffer[0] = OFFS_WRT(buf, 0); + uw_pkt.buffer[1] = 0; + *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */ + buf += 2; + + ip = *(uint32_t *)p; /* ip <- server override (if any) */ + p += 4; + if (ip == 0) { + /* Have prefix */ + strcpy(buf, PathPrefix); + buf += strlen(PathPrefix); + ip = ServerIP; /* Get the default server */ + } + + strcpy(buf, p); /* Copy the filename */ + buf += strlen(p) + 1; /* advance the pointer, null char included */ + +#if GPXE + if (is_gpxe(packet_buf + 2)) { + fo.status = PXENV_STATUS_BAD_FUNC; + fo.filename[0] = OFFS_WRT(packet_buf + 2, 0); + fo.filename[1] = 0; + err = pxe_call(PXENV_FILE_OPEN, &fo); + if (err) + goto done; + + open_file->tftp_localport = -1; + open_file->tftp_remoteport = fo.filehandle; + gs.filehandle = fo.filehandle; + +#if 0 + err = pxe_call(PXENV_GET_FILE_SIZE, &gs); + if (!err) + filesize = gs.filesize; + else +#endif + filesize = -1; + open_file->tftp_filesize = -1; + goto done; + } +#endif /* GPXE */ + + open_file->tftp_remoteip = ip; + tid = open_file->tftp_localport; /* TID(local port No) */ + uw_pkt.sip = ip; + uw_pkt.gip = ((uw_pkt.sip ^ MyIP) & Netmask) ? Gateway : 0; + uw_pkt.lport = tid; + uw_pkt.rport = ServerPort; + buf += fill_tail(buf); + uw_pkt.buffersize = buf - packet_buf; + err = pxe_call(PXENV_UDP_WRITE, &uw_pkt); + if (err || uw_pkt.status != 0) + goto failure; /* In fact, the 'failure' target will not do a failure thing; + it will move on to the next timeout, then tries again until + _real_ time out */ + + /* + * Danger, Will Robinson! We need to support tiemout + * and retry lest we just lost a packet ... + */ + + /* Packet transmitted OK, now we need to receive */ + timeout = *timeout_ptr++; + oldtime = BIOS_timer; + while (timeout) { + buf = packet_buf; + ur_pkt.buffer[0] = OFFS_WRT(buf, 0); + ur_pkt.buffer[1] = 0; + ur_pkt.buffersize = 2048; + ur_pkt.dip = MyIP; + ur_pkt.lport = tid; + err = pxe_call(PXENV_UDP_READ, &ur_pkt); + if (err) { + if (oldtime == BIOS_timer) + continue; + timeout --; /* Decrease one timer tick */ + if (!timeout) + goto failure; + } + + /* Make sure the packet actually came from the server */ + if (ur_pkt.sip == open_file->tftp_remoteip) + break; + } + + /* Got packet; reset timeout */ + timeout_ptr = TimeoutTable; + open_file->tftp_remoteport = ur_pkt.rport; + + /* filesize <- -1 == unknow */ + open_file->tftp_filesize = -1; + /* Default blksize unless blksize option negotiated */ + open_file->tftp_blksize = TFTP_BLOCKSIZE; + buffersize = ur_pkt.buffersize - 2; /* bytes after opcode */ + if (buffersize < 0) + goto failure; /* Garbled reply */ + + /* + * Get the opcode type, and parse it + */ + opcode = *(uint16_t *)packet_buf; + if (opcode == TFTP_ERROR) { + filesize = 0; + open_file = NULL; + goto done; /* ERROR reply; don't try again */ + } else if (opcode == TFTP_DATA) { + /* + * If the server doesn't support any options, we'll get a + * DATA reply instead of OACK. Stash the data in the file + * buffer and go with the default value for all options... + * + * We got a DATA packet, meaning no options are + * suported. Save the data away and consider the + * length undefined, *unless* this is the only + * data packet... + */ + buffersize -= 2; + if (buffersize < 0) + goto failure; + data = packet_buf + 2; + blk_num = *(uint16_t *)data; + data += 2; + if (blk_num != htons(1)) + goto failure; + open_file->tftp_lastpkt = blk_num; + if (buffersize > TFTP_BLOCKSIZE) + goto err_reply; /* Corrupt */ + else if (buffersize < TFTP_BLOCKSIZE) { + /* + * This is the final EOF packet, already... + * We know the filesize, but we also want to + * ack the packet and set the EOF flag. + */ + open_file->tftp_filesize = buffersize; + open_file->tftp_goteof = 1; + ack_packet(open_file, blk_num); + } + + open_file->tftp_bytesleft = buffersize; + open_file->tftp_dataptr = open_file->tftp_pktbuf; + memcpy(MK_PTR(PKTBUF_SEG, open_file->tftp_pktbuf), data, buffersize); + goto done; + + } else if (opcode == TFTP_OACK) { + /* + * Now we need to parse the OACK packet to get the transfer + * and packet sizes. + */ + if (!buffersize) + goto done; /* No options acked */ + + /* + * If we find an option which starts with a NUL byte, + * (a null option), we're either seeing garbage that some + * TFTP servers add to the end of the packet, or we have + * no clue how to parse the rest of the packet (what is + * an option name and what is a value?) In either case, + * discard the rest. + */ + options = packet_buf + 2; + if (*options == 0) + goto done; + + dst = src = options; + while (buffersize--) { + if (*src == 0) + break; /* found a final null */ + *dst++ = *src++ | 0x20; + if (!buffersize) + goto done; /* found no final null */ + } + + /* + * Parse option pointed to by options; guaranteed to be null-terminated + */ + p = options; + do { + tftp_opt = tftp_options; + for (i = 0; i < tftp_opts; i++) { + if (!strncmp(p, tftp_opt->str_ptr,tftp_opt->str_len)) + break; + tftp_opt++; + } + if (i == tftp_opts) + goto err_reply; /* Non-negotitated option returned, no idea what it means ...*/ + + p += tftp_opt->str_len; + + /* get the address of the filed that we want to write on */ + data_ptr = (uint32_t *)((char *)file + tftp_opt->offset); + *data_ptr = 0; + + /* do convert a number-string to decimal number, just like atoi */ + while (buffersize--) { + if (*p == 0) + break; /* found a final null */ + if (*p > '9') + goto err_reply; /* Not a decimal digit */ + *data_ptr = *data_ptr * 10 + *p++ - '0'; + } + + }while (buffersize); + + } else { + extern char tftp_proto_err[]; + err_reply: + + uw_pkt.rport = open_file->tftp_remoteport; + uw_pkt.buffer[0] = OFFS_WRT(tftp_proto_err, 0); + uw_pkt.buffer[1] = 0; + uw_pkt.buffersize = 24; + pxe_call(PXENV_UDP_WRITE, &uw_pkt); + printf("TFTP server sent an incomprehesible reply\n"); + call16(kaboom, NULL, NULL); + } + + filesize = open_file->tftp_filesize; + + done: + if (!filesize) + free_socket(open_file); + file->file_len = filesize; + file->open_file = (void *)open_file; + return; + + failure: + timeout_ptr++; + if (*timeout_ptr) + goto sendreq; /* Try again */ +} + + +/* + * Store standard filename prefix + */ +void get_prefix(void) +{ + int len; + char *p; + char c; + + if (DHCPMagic & 0x04) /* Did we get a path prefix option */ + goto got_prefix; + + strcpy(PathPrefix, BootFile); + len = strlen(PathPrefix); + p = &PathPrefix[len - 1]; + + while (len--) { + c = *p--; + c |= 0x20; + + c = (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c == '.' || c == '-'); + if (!c) + break; + }; + + if (len < 0) + p --; + + *(p + 2) = 0; /* Zero-terminate after delimiter */ + + got_prefix: + printf("%s%s\n", tftpprefix_msg, PathPrefix); + strcpy(CurrentDirName, PathPrefix); +} + + /** + * try to load a config file, if found, return 1, or return 0 + * + */ +int try_load(com32sys_t *regs) +{ + extern char KernelName[]; + char *config_name = (char *)MK_PTR(regs->ds, regs->edi.w[0]); + + get_prefix(); + + printf("Trying to load: %-50s ", config_name); + pxe_mangle_name(KernelName, config_name); + + regs->edi.w[0] = OFFS_WRT(KernelName, 0); + call16(core_open, regs, regs); + if (regs->eflags.l & EFLAGS_ZF) { + printf(" [FAILED]\n"); + return 0; + } else { + printf(" [ OK ]\n"); + return 1; + } +} + + + /* + * load configuration file + * + */ +void pxe_load_config(com32sys_t *regs) +{ + extern void no_config(void); + char *cfgprefix = "pxelinux.cfg/"; + char *default_str = "default"; + char *config_file; /* Pointer to the variable suffix */ + char *p; + + uint8_t *uuid_ptr; + + int tries = 8; + char *last; + + if (DHCPMagic & 0x02) { + /* We got a DHCP option, try it first */ + if (try_load(regs)) + return; + } + + memcpy(ConfigName, cfgprefix, strlen(cfgprefix)); + config_file = ConfigName + strlen(cfgprefix); + /* + * Have to guess config file name ... + */ + + /* Try loading by UUID */ + if (HaveUUID) { + uuid_ptr = uuid_dashes; + p = config_file; + while (*uuid_ptr) { + int len = *uuid_ptr; + char *src = UUID; + + lchexbytes(p, src, len); + p += len * 2; + src += len; + uuid_ptr++; + *p++ = '-'; + } + /* Remove last dash and zero-terminate */ + *--p = '\0'; + regs->edi.w[0] = OFFS_WRT(ConfigName, 0); + if (try_load(regs)) + return; + } + + /* Try loading by MAC address */ + strcpy(config_file, MACStr); + regs->edi.w[0] = OFFS_WRT(ConfigName, 0); + if (try_load(regs)) + return; + +#if 0 + printf("MY IP: %p(%X)\n", MyIP, *(uint32_t *)MyIP); + #endif + + /* Nope, try hexadecimal IP prefixes... */ + uchexbytes(config_file, (uint8_t *)&MyIP, 4); /* Convet to hex string */ + last = &config_file[8]; + while (tries) { + *last = '\0'; /* Zero-terminate string */ + regs->edi.w[0] = OFFS_WRT(ConfigName, 0); + if (try_load(regs)) + return; + last--; /* Drop one character */ + tries--; + }; + + /* Final attempt: "default" string */ + strcpy(config_file, default_str); + regs->edi.w[0] = OFFS_WRT(ConfigName, 0); + if (try_load(regs)) + return; + + /* call16(no_config, NULL, NULL); */ +} + + + +/* + * Generate the botif string, and the hardware-based config string + */ +void make_bootif_string(void) +{ + char *bootif_str = "BOOTIF="; + uint8_t *src = &MACType; /* MACType just followed by MAC */ + char *dst; + int i = MACLen + 1; /* MACType included */ + + strcpy(BOOTIFStr, bootif_str); + dst = strchr(BOOTIFStr, '\0'); + for (; i > 0; i--) { + lchexbytes(dst, src, 1); + dst += 2; + src += 1; + *dst++ = '-'; + } + *(dst - 1) = 0; /* Drop the last '-' and null-terminate string */ + +#if 0 + printf("%s\n", BOOTIFStr); +#endif +} +/* + ; + ; genipopt + ; + ; Generate an ip=::: + ; option into IPOption based on a DHCP packet in trackbuf. + ; Assumes CS == DS == ES. + ; +*/ +void genipopt(void) +{ + char *p = IPOption; + int ip_len; + + strcpy(p, "ip="); + p += 3; + + ip_len = gendotquad(p, MyIP); + p += ip_len; + *p++ = ':'; + + ip_len = gendotquad(p, ServerIP); + p += ip_len; + *p++ = ':'; + + ip_len = gendotquad(p, Gateway); + p += ip_len; + *p++ = ':'; + + ip_len = gendotquad(p, Netmask); +} + + +/* Generate ip= option and print the ip adress */ +void ip_init(void) +{ + int ip = MyIP; + char *myipaddr_msg = "My IP address seems to be "; + + genipopt(); + + gendotquad(DotQuadBuf, ip); + + ip = ntohl(ip); + printf("%s %08X %s\n", myipaddr_msg, ip, DotQuadBuf); + + printf("%s\n", IPOption); +} + +/* + * Validity check on possible !PXE structure in buf + * return 1 for success, 0 for failure. + * + */ +int is_pxe(char *buf) +{ + int i = buf[4]; + uint8_t sum = 0; + + if (memcmp(buf, "!PXE", 4) || i < 0x58) + return 0; + + while (i--) + sum += *buf++; + + if (sum == 0) + return 1; + else + return 0; +} + +/** + * Just like is_pxe, it checks PXENV+ structure + * + */ +int is_pxenv(char *buf) +{ + int i = buf[8]; + uint8_t sum = 0; + + if (memcmp(buf, "PXENV+", 6) || i < 0x28) + return 0; + + while (i--) + sum += *buf++; + + if (sum == 0) + return 1; + else + return 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. +; +; On exit, if found: +; ZF = 1, ES:BX -> !PXE structure +; Otherwise: +; ZF = 0 +; +; Assumes DS == CS +; Clobbers AX, BX, CX, DX, SI, ES +; + ********************************************************************/ + +inline int memory_scan(uint16_t seg, int (*func)(char *)) +{ + while (seg < 0xA000) { + if (func(MK_PTR(seg, 0))) + return 1; /* found it */ + seg++; + } + return 0; +} + +int memory_scan_for_pxe_struct() +{ + extern uint16_t BIOS_fbm; /* Starting segment */ + uint16_t seg = BIOS_fbm << (10 - 4); + + return memory_scan(seg, is_pxe); +} + +int memory_scan_for_pxenv_struct() +{ + uint16_t seg = 0x1000; + + return memory_scan(seg, 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 + * + */ +void pxe_init() +{ + char plan = 'A'; + uint16_t seg, off; + uint16_t code_seg, code_len; + uint16_t data_seg, data_len; + char *base = GET_PTR(InitStack); + + /* Assume API version 2.1 */ + APIVer = 0x201; + + /* Plan A: !PXE structure as SS:[SP + 4] */ + off = *(uint16_t *)(base + 48); + seg = *(uint16_t *)(base + 50); + if (is_pxe(MK_PTR(seg, off))) + goto have_pxe; + + /* Plan B: PXENV+ structure at [ES:BX] */ + plan++; + off = *(uint16_t *)(base + 24); /* Original BX */ + seg = *(uint16_t *)(base + 4); /* Original ES */ + if (is_pxenv(MK_PTR(seg, off))) + goto have_pxenv; + + /* + * Plan C: PXENV+ structure via INT 1Ah AX=5650h + * + * for now, we just skip it since it must be run in Real mode + */ + extern void plan_c(void); + plan++; +#if 0 + goto plan_D; + call16(plan_c, regs, regs); + if (((regs->eflags.l & EFLAGS_CF) == 0) && (regs->eax.w[0] == 0x564e)) { + seg = regs->es; + off = regs->ebx.w[0]; + if (is_pxenv(MK_PTR(seg, off))) + goto have_pxenv; + } + + plan_D: +#endif + /* Plan D: !PXE memory scan */ + plan++; + if (memory_scan_for_pxe_struct()) + goto have_pxe; + + /* Plan E: PXENV+ memory scan */ + plan++; + if (memory_scan_for_pxenv_struct()) + goto have_pxenv; + + /* Found nothing at all !! */ + printf("%s\n", err_nopxe); + call16(kaboom, NULL, NULL); + + have_pxenv: + StructPtr[0] = off; + StructPtr[1] = seg; + base = MK_PTR(seg, off); + APIVer = *(uint16_t *)(base + 6); + 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 (*(char *)(base + 8) < 0x2c) { /* Length of PXENV+ structure in bytes */ + if (memory_scan_for_pxe_struct()) + goto have_pxe; + } + + off = *(uint16_t *)(base + 0x28); + seg = *(uint16_t *)(base + 0x2a); + if (is_pxe(MK_PTR(seg, off))) + goto have_pxe; + /* + * Nope, !PXE structuremissing despite API 2.1+, or at least + * the pointer is missing. Do a last-ditch attempt to find it + */ + if (memory_scan_for_pxe_struct()) + goto have_pxe; + } + + /* Otherwise, no dice, use PXENV+ structure */ + data_len = *(uint16_t *)(base + 0x22); /* UNDI data len */ + data_seg = *(uint16_t *)(base + 0x20); /* UNDI data seg */ + code_len = *(uint16_t *)(base + 0x26); /* UNDI code len */ + code_seg = *(uint16_t *)(base + 0x24); /* UNDI code seg */ + PXEEntry = *(far_ptr_t *)(base + 0x0a); /* PXENV+ entry point */ + printf("PXENV+ entry point found (we hope) at "); + goto have_entrypoint; + + have_pxe: + StructPtr[0] = off; + StructPtr[1] = seg; + base = MK_PTR(seg, off); + + data_len = *(uint16_t *)(base + 0x2e); /* UNDI data len */ + data_seg = *(uint16_t *)(base + 0x28); /* UNDI data seg */ + code_len = *(uint16_t *)(base + 0x36); /* UNDI code len */ + code_seg = *(uint16_t *)(base + 0x30); /* UNDI code seg */ + PXEEntry = *(far_ptr_t *)(base + 0x10); /* !PXE entry point */ + printf("!PXE entry point found (we hope) at "); + + have_entrypoint: + printf("%04X:%04X via plan %c\n", PXEEntry.seg, PXEEntry.offs, plan); + printf("UNDI code segment at %04X len %04X\n", code_seg, code_len); + code_seg = code_seg + ((code_len + 15) >> 4); + + printf("UNDI data segment at %04X len %04X\n", data_seg, data_len); + data_seg = data_seg + ((data_len + 15) >> 4); + + + RealBaseMem = MAX(code_seg,data_seg) >> 6; /* Convert to kilobytes */ + //regs->es = regs->ds; /* Restore the es segment */ +} + +/* + * Initialize UDP stack + * + */ +void udp_init(void) +{ + int err; + static __lowmem struct pxe_udp_open_pkt uo_pkt; + uo_pkt.sip = MyIP; + err = pxe_call(PXENV_UDP_OPEN, &uo_pkt); + if (err || uo_pkt.status) { + printf("%s", err_udpinit); + printf("%d\n", uo_pkt.status); + call16(kaboom, NULL, NULL); + } +} + + +/* + * Network-specific initialization + */ +void network_init() +{ + struct bootp_t *bp = (struct bootp_t *)trackbuf; + int pkt_len; + + *LocalDomain = 0; /* No LocalDomain received */ + + /* + * Get the DHCP client identifiers (query info 1) + */ + printf("%s", get_packet_msg); + pkt_len = pxe_get_cached_info(1); + parse_dhcp(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); + parse_dhcp(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 + */ + MACLen = bp->hardlen > 16 ? 0 : bp->hardlen; + MACType = bp->hardware; + memcpy(MAC, bp->macaddr, MACLen); + + + /* + * Get the boot file and other info. This lives in the CACHED_REPLY + * packet (query info 3) + */ + pkt_len = pxe_get_cached_info(3); + parse_dhcp(pkt_len); + + printf("\n"); + + + + make_bootif_string(); + ip_init(); + + /* + * Check to see if we got any PXELINUX-specific DHCP options; in particular, + * if we didn't get the magic enable, do not recognize any other options. + */ + if ((DHCPMagic & 1) == 0) + DHCPMagic = 0; + + udp_init(); +} + +/* + * Initialize pxe fs + * + */ +int pxe_fs_init(struct fs_info *fs) +{ + fs = NULL; /* drop the compile warning message */ + + /* do the pxe initialize */ + pxe_init(); + + /* Network-specific initialization */ + network_init(); + + return 0; +} + +const struct fs_ops pxe_fs_ops = { + .fs_name = "pxe", + .fs_init = pxe_fs_init, + .searchdir = pxe_searchdir, + .getfssec = pxe_getfssec, + .mangle_name = pxe_mangle_name, + .unmangle_name = NULL, + .load_config = pxe_load_config +}; diff --git a/core/pxelinux.asm b/core/pxelinux.asm index b866369..6c00958 100644 --- a/core/pxelinux.asm +++ b/core/pxelinux.asm @@ -161,23 +161,31 @@ tftp_pktbuf resw 1 ; Packet buffer offset ; Memory below this point is reserved for the BIOS and the MBR ; section .earlybss + global trackbuf trackbufsize equ 8192 trackbuf resb trackbufsize ; Track buffer goes here ; ends at 2800h + ; These fields save information from before the time + ; .bss is zeroed... must be in .earlybss + global InitStack +InitStack resd 1 + section .bss16 + global Files alignb open_file_t_size Files resb MAX_OPEN*open_file_t_size alignb FILENAME_MAX + global BootFile, PathPrefix, DotQuadBuf, IPOption BootFile resb 256 ; Boot file from DHCP packet PathPrefix resb 256 ; Path prefix derived from boot file DotQuadBuf resb 16 ; Buffer for dotted-quad IP address IPOption resb 80 ; ip= option buffer -InitStack resd 1 ; Pointer to reset stack (SS:SP) PXEStack resd 1 ; Saved stack during PXE call alignb 4 + global DHCPMagic, OverLoad, RebootTime, APIVer, RealBaseMem RebootTime resd 1 ; Reboot timeout, if set by option StrucPtr resd 1 ; Pointer to PXENV+ or !PXE structure APIVer resw 1 ; PXE API version found @@ -187,6 +195,7 @@ OverLoad resb 1 ; Set if DHCP packet uses "overloading" DHCPMagic resb 1 ; PXELINUX magic flags ; The relative position of these fields matter! + global MACStr, MACLen, MACType, MAC, BOOTIFStr MAC_MAX equ 32 ; Handle hardware addresses this long MACLen resb 1 ; MAC address len MACType resb 1 ; MAC address type @@ -195,6 +204,7 @@ BOOTIFStr resb 7 ; Space for "BOOTIF=" MACStr resb 3*(MAC_MAX+1) ; MAC address as a string ; The relative position of these fields matter! + global UUID UUIDType resb 1 ; Type byte from DHCP option UUID resb 16 ; UUID, from the PXE stack UUIDNull resb 1 ; dhcp_copyoption zero-terminates @@ -212,6 +222,7 @@ pxe_unload_stack_pkt_len equ $-pxe_unload_stack_pkt ; BOOTP/DHCP packet buffer section .bss16 + global packet_buf alignb 16 packet_buf resb 2048 ; Transfer packet packet_buf_size equ $-packet_buf @@ -277,42 +288,20 @@ _start1: call writestr_early ; -; Assume API version 2.1, in case we find the !PXE structure without -; finding the PXENV+ structure. This should really look at the Base -; Code ROM ID structure in have_pxe, but this is adequate for now -- -; if we have !PXE, we have to be 2.1 or higher, and we don't care -; about higher versions than that. +; do fs initialize ; - mov word [APIVer],0201h + extern pxe_fs_ops + mov eax,pxe_fs_ops + pm_call fs_init + jmp next ; -; Now we need to 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=5650h -> PXENV+ -; d. Search memory for !PXE -; e. Search memory for PXENV+ +; Plan C, because it must be in real mode, we set it here ; -; If we find a PXENV+ structure, we try to find a !PXE structure from -; it if the API version is 2.1 or later. -; - ; Plan A: !PXE structure as SS:[SP+4] - lgs bp,[InitStack] ; GS:BP -> original stack - les bx,[gs:bp+48] - call is_pxe - je have_pxe - - ; Plan B: PXENV+ structure at [ES:BX] - inc byte [plan] - mov bx,[gs:bp+24] ; Original BX - mov es,[gs:bp+4] ; Original ES - call is_pxenv - je have_pxenv - + global plan_c +plan_c: + ; Plan C: PXENV+ structure via INT 1Ah AX=5650h - inc byte [plan] mov ax, 5650h %if USE_PXE_PROVIDED_STACK == 0 lss sp,[InitStack] @@ -322,285 +311,10 @@ _start1: lss esp,[BaseStack] %endif sti ; Work around Etherboot bug + ret - jc no_int1a - cmp ax,564Eh - jne no_int1a - - call is_pxenv - je have_pxenv - -no_int1a: - ; Plan D: !PXE memory scan - inc byte [plan] - call memory_scan_for_pxe_struct ; !PXE scan - je have_pxe - - ; Plan E: PXENV+ memory scan - inc byte [plan] - call memory_scan_for_pxenv_struct ; PXENV+ scan - je have_pxenv - - ; Found nothing at all!! -no_pxe: - mov si,err_nopxe - call writestr_early - jmp kaboom - -have_pxenv: - mov [StrucPtr],bx - mov [StrucPtr+2],es - - mov si,found_pxenv - call writestr_early - - mov si,apiver_str - call writestr_early - mov ax,[es:bx+6] - mov [APIVer],ax - call writehex4 - call crlf - - cmp ax,0201h ; API version 2.1 or higher - jb .old_api - cmp byte [es:bx+8],2Ch ; Space for !PXE pointer? - jb .pxescan - les bx,[es:bx+28h] ; !PXE structure pointer - call is_pxe - je 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. -.pxescan: - call memory_scan_for_pxe_struct - je have_pxe - - ; Otherwise, no dice, use PXENV+ structure -.old_api: - les bx,[StrucPtr] - push word [es:bx+22h] ; UNDI data len - push word [es:bx+20h] ; UNDI data seg - push word [es:bx+26h] ; UNDI code len - push word [es:bx+24h] ; UNDI code seg - push dword [es:bx+0Ah] ; PXENV+ entry point - - mov si,pxenventry_msg - jmp have_entrypoint - -have_pxe: - mov [StrucPtr],bx - mov [StrucPtr+2],es - - push word [es:bx+2Eh] ; UNDI data len - push word [es:bx+28h] ; UNDI data seg - push word [es:bx+36h] ; UNDI code len - push word [es:bx+30h] ; UNDI code seg - push dword [es:bx+10h] ; !PXE entry point - - mov si,pxeentry_msg - -have_entrypoint: - push cs - pop es ; Restore CS == DS == ES - - call writestr_early ; !PXE or PXENV+ entry found - - pop dword [PXEEntry] - mov ax,[PXEEntry+2] - call writehex4 - mov al,':' - call writechr - mov ax,[PXEEntry] - call writehex4 - - mov si,viaplan_msg - call writestr_early - - mov si,undi_code_msg - call writestr_early - pop ax ; UNDI code segment - call writehex4 - xchg dx,ax - mov si,len_msg - call writestr_early - pop ax ; UNDI code length - call writehex4 - call crlf - add ax,15 - shr ax,4 - add dx,ax ; DX = seg of end of UNDI code - - mov si,undi_data_msg - call writestr_early - pop ax ; UNDI data segment - call writehex4 - xchg bx,ax - mov si,len_msg - call writestr_early - pop ax ; UNDI data length - call writehex4 - call crlf - add ax,15 - shr ax,4 - add ax,bx ; AX = seg of end of UNDI data - - cmp ax,dx - ja .data_on_top - xchg ax,dx -.data_on_top: - ; Could we safely add 63 here before the shift? - shr ax,6 ; Convert to kilobytes - mov [RealBaseMem],ax - - -; -; Network-specific initialization -; - xor ax,ax - mov [LocalDomain],al ; No LocalDomain received - -; -; The DHCP client identifiers are best gotten from the DHCPREQUEST -; packet (query info 1). -; -query_bootp_1: - mov si,get_packet_msg - call writestr_early - - mov dl,1 - call pxe_get_cached_info - call parse_dhcp - - ; 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. - mov byte [DHCPMagic],1 - -; -; Now attempt to get the BOOTP/DHCP packet that brought us life (and an IP -; address). This lives in the DHCPACK packet (query info 2). -; -query_bootp_2: - mov dl,2 - call pxe_get_cached_info - call parse_dhcp ; Parse DHCP packet -; -; 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.) -; -.save_mac: - movzx cx,byte [trackbuf+bootp.hardlen] - cmp cx,16 - jna .mac_ok - xor cx,cx ; Bad hardware address length -.mac_ok: - mov [MACLen],cl - mov al,[trackbuf+bootp.hardware] - mov [MACType],al - mov si,trackbuf+bootp.macaddr - mov di,MAC - rep movsb - -; Enable this if we really need to zero-pad this field... -; mov cx,MAC+MAC_MAX+1 -; sub cx,di -; xor ax,ax -; rep stosb - -; -; Now, get the boot file and other info. This lives in the CACHED_REPLY -; packet (query info 3). -; -query_bootp_3: - mov dl,3 - call pxe_get_cached_info - call parse_dhcp ; Parse DHCP packet - call crlf - -; -; Generate the bootif string, and the hardware-based config string. -; -make_bootif_string: - mov si,bootif_str - mov di,BOOTIFStr - mov cx,bootif_str_len - rep movsb - - movzx cx,byte [MACLen] - mov si,MACType - inc cx -.hexify_mac: - push cx - mov cl,1 ; CH == 0 already - call lchexbytes - mov al,'-' - stosb - pop cx - loop .hexify_mac - mov [di-1],cl ; Null-terminate and strip final dash -; -; Generate ip= option -; - call genipopt - -; -; Print IP address -; - mov eax,[MyIP] - mov di,DotQuadBuf - push di - call gendotquad ; This takes network byte order input - - xchg ah,al ; Convert to host byte order - ror eax,16 ; (BSWAP doesn't work on 386) - xchg ah,al - - mov si,myipaddr_msg - call writestr_early - call writehex8 - mov al,' ' - call writechr - pop si ; DotQuadBuf - call writestr_early - call crlf - - mov si,IPOption - call writestr_early - call crlf - -; -; Check to see if we got any PXELINUX-specific DHCP options; in particular, -; if we didn't get the magic enable, do not recognize any other options. -; -check_dhcp_magic: - test byte [DHCPMagic], 1 ; If we didn't get the magic enable... - jnz .got_magic - mov byte [DHCPMagic], 0 ; If not, kill all other options -.got_magic: - - -; -; Initialize UDP stack -; -udp_init: - mov eax,[MyIP] - mov [pxe_udp_open_pkt.sip],eax - mov di,pxe_udp_open_pkt - mov bx,PXENV_UDP_OPEN - call pxenv - jc .failed - cmp word [pxe_udp_open_pkt.status], byte 0 - je .success -.failed: mov si,err_udpinit - call writestr_early - jmp kaboom -.success: +next: ; ; Common initialization code ; @@ -627,145 +341,9 @@ udp_init: ; ; -; Store standard filename prefix -; -prefix: test byte [DHCPMagic], 04h ; Did we get a path prefix option - jnz .got_prefix - mov si,BootFile - mov di,PathPrefix - cld - call strcpy - mov cx,di - sub cx,PathPrefix+1 - std - lea si,[di-2] ; Skip final null! -.find_alnum: lodsb - or al,20h - cmp al,'.' ; Count . or - as alphanum - je .alnum - cmp al,'-' - je .alnum - cmp al,'0' - jb .notalnum - cmp al,'9' - jbe .alnum - cmp al,'a' - jb .notalnum - cmp al,'z' - ja .notalnum -.alnum: loop .find_alnum - dec si -.notalnum: mov byte [si+2],0 ; Zero-terminate after delimiter - cld -.got_prefix: - mov si,tftpprefix_msg - call writestr_early - mov si,PathPrefix - call writestr_early - call crlf - - ; Set CurrentDirName - push di - mov si,PathPrefix - mov di,CurrentDirName - call strcpy - pop di - -; ; Load configuration file ; -find_config: - -; -; Begin looking for configuration file -; -config_scan: - test byte [DHCPMagic], 02h - jz .no_option - - ; We got a DHCP option, try it first - call .try - jnz .success - -.no_option: - mov di,ConfigName - mov si,cfgprefix - mov cx,cfgprefix_len - rep movsb - - ; Have to guess config file name... - - ; Try loading by UUID. - cmp byte [HaveUUID],0 - je .no_uuid - - push di - mov bx,uuid_dashes - mov si,UUID -.gen_uuid: - movzx cx,byte [bx] - jcxz .done_uuid - inc bx - call lchexbytes - mov al,'-' - stosb - jmp .gen_uuid -.done_uuid: - mov [di-1],cl ; Remove last dash and zero-terminate - pop di - call .try - jnz .success -.no_uuid: - - ; Try loading by MAC address - push di - mov si,MACStr - call strcpy - pop di - call .try - jnz .success - - ; Nope, try hexadecimal IP prefixes... -.scan_ip: - mov cx,4 - mov si,MyIP - call uchexbytes ; Convert to hex string - - mov cx,8 ; Up to 8 attempts -.tryagain: - mov byte [di],0 ; Zero-terminate string - call .try - jnz .success - dec di ; Drop one character - loop .tryagain - - ; Final attempt: "default" string - mov si,default_str ; "default" string - call strcpy - call .try - jnz .success - - mov si,err_noconfig - call writestr_early - jmp kaboom - -.try: - pusha - mov si,trying_msg - call writestr_early - mov di,ConfigName - mov si,di - call writestr_early - call crlf - mov si,di - mov di,KernelName ; Borrow this buffer for mangled name - call mangle_name - call open - popa - ret - - -.success: + pm_call load_config ; ; Linux kernel loading code is common. However, we need to define @@ -814,6 +392,11 @@ local_boot: ; kaboom: write a message and bail out. Wait for quite a while, ; or a user keypress, then do a hard reboot. ; + global no_config, kaboom +; set the no_config kaboom here +no_config: + mov si, err_noconfig + call writestr_early kaboom: RESET_STACK_AND_SEGS AX .patch: mov si,bailmsg @@ -848,718 +431,6 @@ kaboom: mov word [BIOS_magic],0 ; Cold reboot jmp 0F000h:0FFF0h ; Reset vector address -; -; 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. -; -; On exit, if found: -; ZF = 1, ES:BX -> !PXE structure -; Otherwise: -; ZF = 0 -; -; Assumes DS == CS -; Clobbers AX, BX, CX, DX, SI, ES -; -memory_scan_for_pxe_struct: - mov dx,is_pxe - mov ax,[BIOS_fbm] ; Starting segment - shl ax,(10-4) ; Kilobytes -> paragraphs - jmp memory_scan_common - -memory_scan_for_pxenv_struct: - mov ax,1000h ; Starting segment - mov dx,is_pxenv - ; fall through - -memory_scan_common: - dec ax ; To skip inc ax -.mismatch: - inc ax - cmp ax,0A000h-1 ; End of memory - ja .not_found ; ZF = 0 on not found - mov es,ax - xor bx,bx - call dx - jne .mismatch -.not_found: - ret - -; -; is_pxe: -; Validity check on possible !PXE structure in ES:BX -; is_pxenv: -; Validity check on possible PXENV+ structure in ES:BX -; -; Return ZF = 1 on success -; -; Clobbers CX and SI -; -is_struc: -.pxe: - cmp dword [es:bx],'!PXE' - jne .bad - movzx cx,byte [es:bx+4] - cmp cx,58h - jae .checksum - ret -.pxenv: - cmp dword [es:bx],'PXEN' - jne .bad - cmp word [es:bx+4],'V+' - jne .bad - movzx cx,[es:bx+8] - cmp cx,28h - jb .bad -.checksum: - push ax - mov si,bx - xor ax,ax -.loop: - es lodsb - add ah,al - loop .loop - pop ax -.bad: - ret - -is_pxe equ is_struc.pxe -is_pxenv equ is_struc.pxenv - -; -; close_file: -; Deallocates a file structure (pointer in SI) -; Assumes CS == DS. -; -; XXX: We should check to see if this file is still open on the server -; side and send a courtesy ERROR packet to the server. -; -close_file: - and si,si - jz .closed - mov word [si],0 ; Not in use -.closed: ret - -; -; searchdir: -; -; Open a TFTP connection to the server -; -; On entry: -; DS:DI = mangled filename -; If successful: -; ZF clear -; SI = socket pointer -; EAX = file length in bytes, or -1 if unknown -; If unsuccessful -; ZF set -; - -searchdir: - push es - push bx - push cx - mov ax,ds - mov es,ax - mov si,di - push bp - mov bp,sp - - call allocate_socket - jz .ret - - mov ax,TimeoutTable ; Reset timeout - -.sendreq: push ax ; [bp-2] - Timeout pointer - push si ; [bp-4] - File name - - mov di,packet_buf - mov [pxe_udp_write_pkt.buffer],di - - mov ax,TFTP_RRQ ; TFTP opcode - stosw - - lodsd ; EAX <- server override (if any) - and eax,eax - jnz .noprefix ; No prefix, and we have the server - - push si ; Add common prefix - mov si,PathPrefix - call strcpy - dec di - pop si - - mov eax,[ServerIP] ; Get default server - -.noprefix: - call strcpy ; Filename -%if GPXE - mov si,packet_buf+2 - call is_gpxe - jnc .gpxe -%endif - - mov [bx+tftp_remoteip],eax - - push bx ; [bp-6] - TFTP block - mov bx,[bx] - push bx ; [bp-8] - TID (local port no) - - mov [pxe_udp_write_pkt.sip],eax - ; Now figure out the gateway - xor eax,[MyIP] - and eax,[Netmask] - jz .nogwneeded - mov eax,[Gateway] -.nogwneeded: - mov [pxe_udp_write_pkt.gip],eax - mov [pxe_udp_write_pkt.lport],bx - mov ax,[ServerPort] - mov [pxe_udp_write_pkt.rport],ax - mov si,tftp_tail - mov cx,tftp_tail_len - rep movsb - sub di,packet_buf ; Get packet size - mov [pxe_udp_write_pkt.buffersize],di - - mov di,pxe_udp_write_pkt - mov bx,PXENV_UDP_WRITE - call pxenv - jc .failure - cmp word [pxe_udp_write_pkt.status],byte 0 - jne .failure - - ; - ; Danger, Will Robinson! We need to support timeout - ; and retry lest we just lost a packet... - ; - - ; Packet transmitted OK, now we need to receive -.getpacket: mov bx,[bp-2] - movzx bx,byte [bx] - push bx ; [bp-10] - timeout in ticks - push word [BIOS_timer] ; [bp-12] - -.pkt_loop: mov bx,[bp-8] ; TID - mov di,packet_buf - mov [pxe_udp_read_pkt.buffer],di - mov [pxe_udp_read_pkt.buffer+2],ds - mov word [pxe_udp_read_pkt.buffersize],packet_buf_size - mov eax,[MyIP] - mov [pxe_udp_read_pkt.dip],eax - mov [pxe_udp_read_pkt.lport],bx - mov di,pxe_udp_read_pkt - mov bx,PXENV_UDP_READ - call pxenv - jnc .got_packet ; Wait for packet -.no_packet: - mov dx,[BIOS_timer] - cmp dx,[bp-12] - je .pkt_loop - mov [bp-12],dx - dec word [bp-10] - jnz .pkt_loop - pop ax ; Adjust stack - pop ax - jmp .failure - -.got_packet: - mov si,[bp-6] ; TFTP pointer - mov bx,[bp-8] ; TID - - ; Make sure the packet actually came from the server - ; This is technically not to the TFTP spec? - mov eax,[si+tftp_remoteip] - cmp [pxe_udp_read_pkt.sip],eax - jne .no_packet - - ; Got packet - reset timeout - mov word [bp-2],TimeoutTable - - pop ax ; Adjust stack - pop ax - - mov ax,[pxe_udp_read_pkt.rport] - mov [si+tftp_remoteport],ax - - ; filesize <- -1 == unknown - mov dword [si+tftp_filesize], -1 - ; Default blksize unless blksize option negotiated - mov word [si+tftp_blksize], TFTP_BLOCKSIZE - - movzx ecx,word [pxe_udp_read_pkt.buffersize] - sub cx,2 ; CX <- bytes after opcode - jb .failure ; Garbled reply - - mov si,packet_buf - lodsw - - cmp ax, TFTP_ERROR - je .bailnow ; ERROR reply: don't try again - - ; If the server doesn't support any options, we'll get - ; a DATA reply instead of OACK. Stash the data in - ; the file buffer and go with the default value for - ; all options... - cmp ax, TFTP_DATA - je .no_oack - - cmp ax, TFTP_OACK - jne .err_reply ; Unknown packet type - - ; Now we need to parse the OACK packet to get the transfer - ; and packet sizes. - ; SI -> first byte of options; [E]CX -> byte count -.parse_oack: - jcxz .done_pkt ; No options acked -.get_opt_name: - mov di,si - mov bx,si -.opt_name_loop: lodsb - and al,al - jz .got_opt_name - or al,20h ; Convert to lowercase - stosb - loop .opt_name_loop - ; We ran out, and no final null - jmp .err_reply -.got_opt_name: ; si -> option value - dec cx ; bytes left in pkt - jz .err_reply ; Option w/o value - - ; Parse option pointed to by bx; guaranteed to be - ; null-terminated. - push cx - push si - mov si,bx ; -> option name - mov bx,tftp_opt_table - mov cx,tftp_opts -.opt_loop: - push cx - push si - mov di,[bx] ; Option pointer - mov cx,[bx+2] ; Option len - repe cmpsb - pop si - pop cx - je .get_value ; OK, known option - add bx,6 - loop .opt_loop - - pop si - pop cx - jmp .err_reply ; Non-negotiated option returned - -.get_value: pop si ; si -> option value - pop cx ; cx -> bytes left in pkt - mov bx,[bx+4] ; Pointer to data target - add bx,[bp-6] ; TFTP socket pointer - xor eax,eax - xor edx,edx -.value_loop: lodsb - and al,al - jz .got_value - sub al,'0' - cmp al, 9 - ja .err_reply ; Not a decimal digit - imul edx,10 - add edx,eax - mov [bx],edx - loop .value_loop - ; Ran out before final null, accept anyway - jmp short .done_pkt - -.got_value: - dec cx - jnz .get_opt_name ; Not end of packet - - ; ZF == 1 - - ; Success, done! -.done_pkt: - pop si ; Junk - pop si ; We want the packet ptr in SI - - mov eax,[si+tftp_filesize] -.got_file: ; SI->socket structure, EAX = size - and eax,eax ; Set ZF depending on file size - jz .error_si ; ZF = 1 need to free the socket -.ret: - leave ; SP <- BP, POP BP - pop cx - pop bx - pop es - ret - - -.no_oack: ; We got a DATA packet, meaning no options are - ; suported. Save the data away and consider the length - ; undefined, *unless* this is the only data packet... - mov bx,[bp-6] ; File pointer - sub cx,2 ; Too short? - jb .failure - lodsw ; Block number - cmp ax,htons(1) - jne .failure - mov [bx+tftp_lastpkt],ax - cmp cx,TFTP_BLOCKSIZE - ja .err_reply ; Corrupt... - je .not_eof - ; This was the final EOF packet, already... - ; We know the filesize, but we also want to ack the - ; packet and set the EOF flag. - mov [bx+tftp_filesize],ecx - mov byte [bx+tftp_goteof],1 - push si - mov si,bx - ; AX = htons(1) already - call ack_packet - pop si -.not_eof: - mov [bx+tftp_bytesleft],cx - mov ax,pktbuf_seg - push es - mov es,ax - mov di,tftp_pktbuf - mov [bx+tftp_dataptr],di - add cx,3 - shr cx,2 - rep movsd - pop es - jmp .done_pkt - -.err_reply: ; Option negotiation error. Send ERROR reply. - ; ServerIP and gateway are already programmed in - mov si,[bp-6] - mov ax,[si+tftp_remoteport] - mov word [pxe_udp_write_pkt.rport],ax - mov word [pxe_udp_write_pkt.buffer],tftp_opt_err - mov word [pxe_udp_write_pkt.buffersize],tftp_opt_err_len - mov di,pxe_udp_write_pkt - mov bx,PXENV_UDP_WRITE - call pxenv - - ; Write an error message and explode - mov si,err_damage - call writestr_early - jmp kaboom - -.bailnow: - ; Immediate error - no retry - mov word [bp-2],TimeoutTableEnd-1 - -.failure: pop bx ; Junk - pop bx - pop si - pop ax - inc ax - cmp ax,TimeoutTableEnd - jb .sendreq ; Try again - -.error: mov si,bx ; Socket pointer -.error_si: ; Socket pointer already in SI - call free_socket ; ZF <- 1, SI <- 0 - jmp .ret - - -%if GPXE -.gpxe: - push bx ; Socket pointer - mov di,gpxe_file_open - mov word [di],2 ; PXENV_STATUS_BAD_FUNC - mov word [di+4],packet_buf+2 ; Completed URL - mov [di+6],ds - mov bx,PXENV_FILE_OPEN - call pxenv - pop si ; Socket pointer in SI - jc .error_si - - mov ax,[di+2] - mov word [si+tftp_localport],-1 ; gPXE URL - mov [si+tftp_remoteport],ax - mov di,gpxe_get_file_size - mov [di+2],ax - -%if 0 - ; Disable this for now since gPXE doesn't always - ; return valid information in PXENV_GET_FILE_SIZE - mov bx,PXENV_GET_FILE_SIZE - call pxenv - mov eax,[di+4] ; File size - jnc .oksize -%endif - or eax,-1 ; Size unknown -.oksize: - mov [si+tftp_filesize],eax - jmp .got_file -%endif ; GPXE - -; -; allocate_socket: Allocate a local UDP port structure -; -; If successful: -; ZF set -; BX = socket pointer -; If unsuccessful: -; ZF clear -; -allocate_socket: - push cx - mov bx,Files - mov cx,MAX_OPEN -.check: cmp word [bx], byte 0 - je .found - add bx,open_file_t_size - loop .check - xor cx,cx ; ZF = 1 - pop cx - ret - ; Allocate a socket number. Socket numbers are made - ; guaranteed unique by including the socket slot number - ; (inverted, because we use the loop counter cx); add a - ; counter value to keep the numbers from being likely to - ; get immediately reused. - ; - ; The NextSocket variable also contains the top two bits - ; set. This generates a value in the range 49152 to - ; 57343. -.found: - dec cx - push ax - mov ax,[NextSocket] - inc ax - and ax,((1 << (13-MAX_OPEN_LG2))-1) | 0xC000 - mov [NextSocket],ax - shl cx,13-MAX_OPEN_LG2 - add cx,ax ; ZF = 0 - xchg ch,cl ; Convert to network byte order - mov [bx],cx ; Socket in use - pop ax - pop cx - ret - -; -; Free socket: socket in SI; return SI = 0, ZF = 1 for convenience -; -free_socket: - push es - pusha - xor ax,ax - mov es,ax - mov di,si - mov cx,tftp_pktbuf >> 1 ; tftp_pktbuf is not cleared - rep stosw - popa - pop es - xor si,si - ret - -; -; parse_dotquad: -; Read a dot-quad pathname in DS:SI and output an IP -; address in EAX, with SI pointing to the first -; nonmatching character. -; -; Return CF=1 on error. -; -; No segment assumptions permitted. -; -parse_dotquad: - push cx - mov cx,4 - xor eax,eax -.parseloop: - mov ch,ah - mov ah,al - lodsb - sub al,'0' - jb .notnumeric - cmp al,9 - ja .notnumeric - aad ; AL += 10 * AH; AH = 0; - xchg ah,ch - jmp .parseloop -.notnumeric: - cmp al,'.'-'0' - pushf - mov al,ah - mov ah,ch - xor ch,ch - ror eax,8 - popf - jne .error - loop .parseloop - jmp .done -.error: - loop .realerror ; If CX := 1 then we're done - clc - jmp .done -.realerror: - stc -.done: - dec si ; CF unchanged! - pop cx - ret - -; -; is_url: Return CF=0 if and only if the buffer pointed to by -; DS:SI is a URL (contains ://). No registers modified. -; -%if GPXE -is_url: - push si - push eax -.loop: - mov eax,[si] - inc si - and al,al - jz .not_url - and eax,0FFFFFFh - cmp eax,'://' - jne .loop -.done: - ; CF=0 here - pop eax - pop si - ret -.not_url: - stc - jmp .done - -; -; is_gpxe: Return CF=0 if and only if the buffer pointed to by -; DS:SI is a URL (contains ://) *and* the gPXE extensions -; API is available. No registers modified. -; -is_gpxe: - call is_url - jc .ret ; Not a URL, don't bother -.again: - cmp byte [HasGPXE],1 - ja .unknown - ; CF=1 if not available (0), - ; CF=0 if known available (1). -.ret: ret - -.unknown: - ; If we get here, the gPXE status is unknown. - push es - pushad - push ds - pop es - mov di,gpxe_file_api_check - mov bx,PXENV_FILE_API_CHECK ; BH = 0 - call pxenv - jc .nogood - cmp dword [di+4],0xe9c17b20 - jne .nogood - mov ax,[di+12] ; Don't care about the upper half... - not ax ; Set bits of *missing* functions... - and ax,01001011b ; The functions we care about - setz bh - jz .done -.nogood: - mov si,gpxe_warning_msg - call writestr_early -.done: - mov [HasGPXE],bh - popad - pop es - jmp .again - - section .data16 -gpxe_warning_msg: - db 'URL syntax, but gPXE extensions not detected, ' - db 'trying plain TFTP...', CR, LF, 0 -HasGPXE db -1 ; Unknown - section .text16 - -%endif - -; -; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed -; to by ES:DI; ends on encountering any whitespace. -; DI is preserved. -; -; This verifies that a filename is < FILENAME_MAX characters -; and doesn't contain whitespace, and zero-pads the output buffer, -; so "repe cmpsb" can do a compare. -; -; The first four bytes of the manged name is the IP address of -; the download host, 0 for no host, or -1 for a gPXE URL. -; -; No segment assumptions permitted. -; -mangle_name: - push di -%if GPXE - call is_url - jc .not_url - or eax,-1 ; It's a URL - jmp .prefix_done -.not_url: -%endif ; GPXE - push si - mov eax,[cs:ServerIP] - cmp byte [si],0 - je .noip ; Null filename?!?! - cmp word [si],'::' ; Leading ::? - je .gotprefix - -.more: - inc si - cmp byte [si],0 - je .noip - cmp word [si],'::' - jne .more - - ; We have a :: prefix of some sort, it could be either - ; a DNS name or a dot-quad IP address. Try the dot-quad - ; first... -.here: - pop si - push si - call parse_dotquad - jc .notdq - cmp word [si],'::' - je .gotprefix -.notdq: - pop si - push si - call dns_resolv - cmp word [si],'::' - jne .noip - and eax,eax - jnz .gotprefix - -.noip: - pop si - xor eax,eax - jmp .prefix_done - -.gotprefix: - pop cx ; Adjust stack - inc si ; Skip double colon - inc si - -.prefix_done: - stosd ; Save IP address prefix - mov cx,FILENAME_MAX-5 - -.mn_loop: - lodsb - cmp al,' ' ; If control or space, end - jna .mn_end - stosb - loop .mn_loop -.mn_end: - inc cx ; At least one null byte - xor ax,ax ; Zero-fill name - rep stosb ; Doesn't do anything if CX=0 - pop di - ret ; Done ; ; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled @@ -1582,7 +453,7 @@ unmangle_name: jz .noip cmp eax,-1 jz .noip ; URL - call gendotquad + pm_call gendotquad mov ax,'::' stosw .noip: @@ -1602,6 +473,7 @@ unmangle_name: ; ; While we're at it, save and restore all registers. ; + global pxenv pxenv: pushfd pushad @@ -1636,250 +508,13 @@ pxenv: ret ; Must be after function def due to NASM bug + global PXEEntry PXEEntry equ pxenv.jump+1 section .bss16 alignb 2 PXEStatus resb 2 - section .text16 - -; -; getfssec: Get multiple clusters from a file, given the starting cluster. -; -; In this case, get multiple blocks from a specific TCP connection. -; -; On entry: -; ES:BX -> Buffer -; SI -> TFTP socket pointer -; CX -> 512-byte block count; 0FFFFh = until end of file -; On exit: -; SI -> TFTP socket pointer (or 0 on EOF) -; CF = 1 -> Hit EOF -; ECX -> number of bytes actually read -; -getfssec: - push eax - push edi - push bx - push si - push fs - mov di,bx - mov ax,pktbuf_seg - mov fs,ax - - xor eax,eax - movzx ecx,cx - shl ecx,TFTP_BLOCKSIZE_LG2 ; Convert to bytes - push ecx ; Initial request size - jz .hit_eof ; Nothing to do? - -.need_more: - call fill_buffer - movzx eax,word [si+tftp_bytesleft] - and ax,ax - jz .hit_eof - - push ecx - cmp ecx,eax - jna .ok_size - mov ecx,eax -.ok_size: - mov ax,cx ; EAX<31:16> == ECX<31:16> == 0 - mov bx,[si+tftp_dataptr] - sub [si+tftp_bytesleft],cx - xchg si,bx - fs rep movsb ; Copy from packet buffer - xchg si,bx - mov [si+tftp_dataptr],bx - - pop ecx - sub ecx,eax - jnz .need_more - -.hit_eof: - call fill_buffer - - pop eax ; Initial request amount - xchg eax,ecx - sub ecx,eax ; ... minus anything not gotten - - pop fs - pop si - - ; Is there anything left of this? - mov eax,[si+tftp_filesize] - sub eax,[si+tftp_filepos] - jnz .bytes_left - - cmp [si+tftp_bytesleft],ax ; AX == 0 - jne .bytes_left - - cmp byte [si+tftp_goteof],0 - je .done - ; I'm 99% sure this can't happen, but... - call fill_buffer ; Receive/ACK the EOF packet -.done: - ; The socket is closed and the buffer drained - ; Close socket structure and re-init for next user - call free_socket - stc - jmp .ret -.bytes_left: - clc -.ret: - pop bx - pop edi - pop eax - ret - -; -; Get a fresh packet if the buffer is drained, and we haven't hit -; EOF yet. The buffer should be filled immediately after draining! -; -; expects fs -> pktbuf_seg and ds:si -> socket structure -; -fill_buffer: - cmp word [si+tftp_bytesleft],0 - je .empty - ret ; Otherwise, nothing to do - -.empty: - push es - pushad - mov ax,ds - mov es,ax - - ; Note: getting the EOF packet is not the same thing - ; as tftp_filepos == tftp_filesize; if the EOF packet - ; is empty the latter condition can be true without - ; having gotten the official EOF. - cmp byte [si+tftp_goteof],0 - jne .ret ; Already EOF - -%if GPXE - cmp word [si+tftp_localport], -1 - jne .get_packet_tftp - call get_packet_gpxe - jmp .ret -.get_packet_tftp: -%endif ; GPXE - - ; TFTP code... -.packet_loop: - ; Start by ACKing the previous packet; this should cause the - ; next packet to be sent. - mov bx,TimeoutTable - -.send_ack: push bx ; Retry pointer - movzx cx,byte [bx] ; Timeout - - mov ax,[si+tftp_lastpkt] - call ack_packet ; Send ACK - - ; We used to test the error code here, but sometimes - ; PXE would return negative status even though we really - ; did send the ACK. Now, just treat a failed send as - ; a normally lost packet, and let it time out in due - ; course of events. - -.send_ok: ; Now wait for packet. - mov dx,[BIOS_timer] ; Get current time - -.wait_data: push cx ; Timeout - push dx ; Old time - - mov bx,[si+tftp_pktbuf] - mov [pxe_udp_read_pkt.buffer],bx - mov [pxe_udp_read_pkt.buffer+2],fs - mov [pxe_udp_read_pkt.buffersize],word PKTBUF_SIZE - mov eax,[si+tftp_remoteip] - mov [pxe_udp_read_pkt.sip],eax - mov eax,[MyIP] - mov [pxe_udp_read_pkt.dip],eax - mov ax,[si+tftp_remoteport] - mov [pxe_udp_read_pkt.rport],ax - mov ax,[si+tftp_localport] - mov [pxe_udp_read_pkt.lport],ax - mov di,pxe_udp_read_pkt - mov bx,PXENV_UDP_READ - call pxenv - jnc .recv_ok - - ; No packet, or receive failure - mov dx,[BIOS_timer] - pop ax ; Old time - pop cx ; Timeout - cmp ax,dx ; Same time -> don't advance timeout - je .wait_data ; Same clock tick - loop .wait_data ; Decrease timeout - - pop bx ; Didn't get any, send another ACK - inc bx - cmp bx,TimeoutTableEnd - jb .send_ack - jmp kaboom ; Forget it... - -.recv_ok: pop dx ; - pop cx ; - - cmp word [pxe_udp_read_pkt.buffersize],byte 4 - jb .wait_data ; Bad size for a DATA packet - - mov bx,[si+tftp_pktbuf] - cmp word [fs:bx],TFTP_DATA ; Not a data packet? - jne .wait_data ; Then wait for something else - - mov ax,[si+tftp_lastpkt] - xchg ah,al ; Host byte order - inc ax ; Which packet are we waiting for? - xchg ah,al ; Network byte order - cmp [fs:bx+2],ax - je .right_packet - - ; Wrong packet, ACK the packet and then try again - ; This is presumably because the ACK got lost, - ; so the server just resent the previous packet - mov ax,[fs:bx+2] - call ack_packet - jmp .send_ok ; Reset timeout - -.right_packet: ; It's the packet we want. We're also EOF if the - ; size < blocksize - - pop cx ; Don't need the retry count anymore - - mov [si+tftp_lastpkt],ax ; Update last packet number - - movzx ecx,word [pxe_udp_read_pkt.buffersize] - sub cx,byte 4 ; Skip TFTP header - - ; Set pointer to data block - lea ax,[bx+4] ; Data past TFTP header - mov [si+tftp_dataptr],ax - - add [si+tftp_filepos],ecx - mov [si+tftp_bytesleft],cx - - cmp cx,[si+tftp_blksize] ; Is it a full block? - jb .last_block ; If not, it's EOF - -.ret: - popad - pop es - ret - - -.last_block: ; Last block - ACK packet immediately - mov ax,[fs:bx+2] - call ack_packet - - ; Make sure we know we are at end of file - mov eax,[si+tftp_filepos] - mov [si+tftp_filesize],eax - mov byte [si+tftp_goteof],1 - - jmp .ret ; ; TimeoutTable: list of timeouts (in 18.2 Hz timer ticks) @@ -1894,96 +529,7 @@ TimeoutTable: TimeoutTableEnd equ $ section .text16 -; -; ack_packet: -; -; Send ACK packet. This is a common operation and so is worth canning. -; -; Entry: -; SI = TFTP block -; AX = Packet # to ack (network byte order) -; Exit: -; All registers preserved -; -; This function uses the pxe_udp_write_pkt but not the packet_buf. -; -ack_packet: - pushad - mov [ack_packet_buf+2],ax ; Packet number to ack - mov ax,[si] - mov [pxe_udp_write_pkt.lport],ax - mov ax,[si+tftp_remoteport] - mov [pxe_udp_write_pkt.rport],ax - mov eax,[si+tftp_remoteip] - mov [pxe_udp_write_pkt.sip],eax - xor eax,[MyIP] - and eax,[Netmask] - jz .nogw - mov eax,[Gateway] -.nogw: - mov [pxe_udp_write_pkt.gip],eax - mov [pxe_udp_write_pkt.buffer],word ack_packet_buf - mov [pxe_udp_write_pkt.buffersize], word 4 - mov di,pxe_udp_write_pkt - mov bx,PXENV_UDP_WRITE - call pxenv - popad - ret - -%if GPXE -; -; Get a fresh packet from a gPXE socket; expects fs -> pktbuf_seg -; and ds:si -> socket structure -; -; Assumes CS == DS == ES. -; -get_packet_gpxe: - mov di,gpxe_file_read - - mov ax,[si+tftp_remoteport] ; gPXE filehandle - mov [di+2],ax - mov ax,[si+tftp_pktbuf] - mov [di+6],ax - mov [si+tftp_dataptr],ax - mov [di+8],fs - -.again: - mov word [di+4],PKTBUF_SIZE - mov bx,PXENV_FILE_READ - call pxenv - jnc .ok ; Got data or EOF - cmp word [di],PXENV_STATUS_TFTP_OPEN ; == EWOULDBLOCK - je .again - jmp kaboom ; Otherwise error... - -.ok: - movzx eax,word [di+4] ; Bytes read - mov [si+tftp_bytesleft],ax ; Bytes in buffer - add [si+tftp_filepos],eax ; Position in file - - and ax,ax ; EOF? - mov eax,[si+tftp_filepos] - - jnz .got_stuff - - ; We got EOF here, make sure the upper layers know - mov [si+tftp_filesize],eax -.got_stuff: - ; If we're done here, close the file - cmp [si+tftp_filesize],eax - ja .done ; Not EOF, there is still data... - - ; Reuse the previous [es:di] structure since the - ; relevant fields are all the same - mov byte [si+tftp_goteof],1 - - mov bx,PXENV_FILE_CLOSE - call pxenv - ; Ignore return... -.done: - ret -%endif ; GPXE ; ; unload_pxe: @@ -2082,460 +628,7 @@ reset_pxe: pop es ret -; -; gendotquad -; -; Take an IP address (in network byte order) in EAX and -; output a dotted quad string to ES:DI. -; DI points to terminal null at end of string on exit. -; -gendotquad: - push eax - push cx - mov cx,4 -.genchar: - push eax - cmp al,10 ; < 10? - jb .lt10 ; If so, skip first 2 digits - - cmp al,100 ; < 100 - jb .lt100 ; If so, skip first digit - - aam 100 - ; Now AH = 100-digit; AL = remainder - add ah,'0' - mov [es:di],ah - inc di - -.lt100: - aam 10 - ; Now AH = 10-digit; AL = remainder - add ah,'0' - mov [es:di],ah - inc di - -.lt10: - add al,'0' - stosb - mov al,'.' - stosb - pop eax - ror eax,8 ; Move next char into LSB - loop .genchar - dec di - mov [es:di], byte 0 - pop cx - pop eax - ret -; -; uchexbytes/lchexbytes -; -; Take a number of bytes in memory and convert to upper/lower-case -; hexadecimal -; -; Input: -; DS:SI = input bytes -; ES:DI = output buffer -; CX = number of bytes -; Output: -; DS:SI = first byte after -; ES:DI = first byte after -; CX = 0 -; -; Trashes AX, DX -; - -lchexbytes: - mov dl,'a'-'9'-1 - jmp xchexbytes -uchexbytes: - mov dl,'A'-'9'-1 -xchexbytes: -.loop: - lodsb - mov ah,al - shr al,4 - call .outchar - mov al,ah - call .outchar - loop .loop - ret -.outchar: - and al,0Fh - add al,'0' - cmp al,'9' - jna .done - add al,dl -.done: - stosb - ret - -; -; pxe_get_cached_info -; -; Get a DHCP packet from the PXE stack into the trackbuf. -; -; Input: -; DL = packet type -; Output: -; CX = buffer size -; -; Assumes CS == DS == ES. -; -pxe_get_cached_info: - pushad - mov al,' ' - call writechr - mov al,dl - call writehex2 - mov di,pxe_bootp_query_pkt - push di - xor ax,ax - stosw ; Status - movzx ax,dl - stosw ; Packet type - mov ax,trackbufsize - stosw ; Buffer size - mov ax,trackbuf - stosw ; Buffer offset - xor ax,ax - stosw ; Buffer segment - pop di ; DI -> parameter set - mov bx,PXENV_GET_CACHED_INFO - call pxenv - jc .err - - popad - mov cx,[pxe_bootp_query_pkt.buffersize] - ret - -.err: - mov si,err_pxefailed - call writestr_early - call writehex4 - call crlf - jmp kaboom - - section .data16 -get_packet_msg db 'Getting cached packet', 0 - - section .text16 -; -; ip_ok -; -; Tests an IP address in EAX for validity; return with ZF=1 for bad. -; We used to refuse class E, but class E addresses are likely to become -; assignable unicast addresses in the near future. -; -ip_ok: - push ax - cmp eax,-1 ; Refuse the all-ones address - jz .out - and al,al ; Refuse network zero - jz .out - cmp al,127 ; Refuse loopback - jz .out - and al,0F0h - cmp al,224 ; Refuse class D -.out: - pop ax - ret - -; -; parse_dhcp -; -; Parse a DHCP packet. This includes dealing with "overloaded" -; option fields (see RFC 2132, section 9.3) -; -; This should fill in the following global variables, if the -; information is present: -; -; MyIP - client IP address -; ServerIP - boot server IP address -; Netmask - network mask -; Gateway - default gateway router IP -; BootFile - boot file name -; DNSServers - DNS server IPs -; LocalDomain - Local domain name -; MACLen, MAC - Client identifier, if MACLen == 0 -; -; This assumes the DHCP packet is in "trackbuf" and the length -; of the packet in in CX on entry. -; - -parse_dhcp: - mov byte [OverLoad],0 ; Assume no overload - mov eax, [trackbuf+bootp.yip] - call ip_ok - jz .noyip - mov [MyIP], eax -.noyip: - mov eax, [trackbuf+bootp.sip] - and eax, eax - call ip_ok - jz .nosip - mov [ServerIP], eax -.nosip: - sub cx, bootp.options - jbe .nooptions - mov si, trackbuf+bootp.option_magic - lodsd - cmp eax, BOOTP_OPTION_MAGIC - jne .nooptions - call parse_dhcp_options -.nooptions: - mov si, trackbuf+bootp.bootfile - test byte [OverLoad],1 - jz .nofileoverload - mov cx,128 - call parse_dhcp_options - jmp short .parsed_file -.nofileoverload: - cmp byte [si], 0 - jz .parsed_file ; No bootfile name - mov di,BootFile - mov cx,32 - rep movsd - xor al,al - stosb ; Null-terminate -.parsed_file: - mov si, trackbuf+bootp.sname - test byte [OverLoad],2 - jz .nosnameoverload - mov cx,64 - call parse_dhcp_options -.nosnameoverload: - ret - -; -; Parse a sequence of DHCP options, pointed to by DS:SI; the field -; size is CX -- some DHCP servers leave option fields unterminated -; in violation of the spec. -; -; For parse_some_dhcp_options, DH contains the minimum value for -; the option to recognize -- this is used to restrict parsing to -; PXELINUX-specific options only. -; -parse_dhcp_options: - xor dx,dx - -parse_some_dhcp_options: -.loop: - and cx,cx - jz .done - - lodsb - dec cx - jz .done ; Last byte; must be PAD, END or malformed - cmp al, 0 ; PAD option - je .loop - cmp al,255 ; END option - je .done - - ; Anything else will have a length field - mov dl,al ; DL <- option number - xor ax,ax - lodsb ; AX <- option length - dec cx - sub cx,ax ; Decrement bytes left counter - jb .done ; Malformed option: length > field size - - cmp dl,dh ; Is the option value valid? - jb .opt_done - - mov bx,dhcp_option_list -.find_option: - cmp bx,dhcp_option_list_end - jae .opt_done - cmp dl,[bx] - je .found_option - add bx,3 - jmp .find_option -.found_option: - pushad - call [bx+1] - popad - -; Fall through - ; Unknown option. Skip to the next one. -.opt_done: - add si,ax - jmp .loop -.done: - ret - - section .data16 -dhcp_option_list: - section .text16 - -%macro dopt 2 - section .data16 - db %1 - dw dopt_%2 - section .text16 -dopt_%2: -%endmacro - -; -; Parse individual DHCP options. SI points to the option data and -; AX to the option length. DL contains the option number. -; All registers are saved around the routine. -; - dopt 1, subnet_mask - mov ebx,[si] - mov [Netmask],ebx - ret - - dopt 3, router - mov ebx,[si] - mov [Gateway],ebx - ret - - dopt 6, dns_servers - mov cx,ax - shr cx,2 - cmp cl,DNS_MAX_SERVERS - jna .oklen - mov cl,DNS_MAX_SERVERS -.oklen: - mov di,DNSServers - rep movsd - mov [LastDNSServer],di - ret - - dopt 15, local_domain - mov bx,si - add bx,ax - xor ax,ax - xchg [bx],al ; Zero-terminate option - mov di,LocalDomain - call dns_mangle ; Convert to DNS label set - mov [bx],al ; Restore ending byte - ret - - dopt 43, vendor_encaps - mov dh,208 ; Only recognize PXELINUX options - mov cx,ax ; Length of option = max bytes to parse - call parse_some_dhcp_options ; Parse recursive structure - ret - - dopt 52, option_overload - mov bl,[si] - mov [OverLoad],bl - ret - - dopt 54, server - mov eax,[si] - cmp dword [ServerIP],0 - jne .skip ; Already have a next server IP - call ip_ok - jz .skip - mov [ServerIP],eax -.skip: ret - - dopt 61, client_identifier - cmp ax,MAC_MAX ; Too long? - ja .skip - cmp ax,2 ; Too short? - jb .skip - cmp [MACLen],ah ; Only do this if MACLen == 0 - jne .skip - push ax - lodsb ; Client identifier type - cmp al,[MACType] - pop ax - jne .skip ; Client identifier is not a MAC - dec ax - mov [MACLen],al - mov di,MAC - jmp dhcp_copyoption -.skip: ret - - dopt 67, bootfile_name - mov di,BootFile - jmp dhcp_copyoption - - dopt 97, uuid_client_identifier - cmp ax,17 ; type byte + 16 bytes UUID - jne .skip - mov dl,[si] ; Must have type 0 == UUID - or dl,[HaveUUID] ; Capture only the first instance - jnz .skip - mov byte [HaveUUID],1 ; Got UUID - mov di,UUIDType - jmp dhcp_copyoption -.skip: ret - - dopt 209, pxelinux_configfile - mov di,ConfigName - or byte [DHCPMagic],2 ; Got config file - jmp dhcp_copyoption - - dopt 210, pxelinux_pathprefix - mov di,PathPrefix - or byte [DHCPMagic],4 ; Got path prefix - jmp dhcp_copyoption - - dopt 211, pxelinux_reboottime - cmp al,4 - jne .done - mov ebx,[si] - xchg bl,bh ; Convert to host byte order - rol ebx,16 - xchg bl,bh - mov [RebootTime],ebx - or byte [DHCPMagic],8 ; Got RebootTime -.done: ret - - ; Common code for copying an option verbatim - ; Copies the option into ES:DI and null-terminates it. - ; Returns with AX=0 and SI past the option. -dhcp_copyoption: - xchg cx,ax ; CX <- option length - rep movsb - xchg cx,ax ; AX <- 0 - stosb ; Null-terminate - ret - - section .data16 -dhcp_option_list_end: - section .text16 - - section .data16 -HaveUUID db 0 -uuid_dashes db 4,2,2,2,6,0 ; Bytes per UUID dashed section - section .text16 - -; -; genipopt -; -; Generate an ip=::: -; option into IPOption based on a DHCP packet in trackbuf. -; Assumes CS == DS == ES. -; -genipopt: - pushad - mov di,IPOption - mov eax,'ip=' - stosd - dec di - mov eax,[MyIP] - call gendotquad - mov al,':' - stosb - mov eax,[ServerIP] - call gendotquad - mov al,':' - stosb - mov eax,[Gateway] - call gendotquad - mov al,':' - stosb - mov eax,[Netmask] - call gendotquad ; Zero-terminates its output - popad - ret ; ----------------------------------------------------------------------------- ; Common modules @@ -2627,6 +720,8 @@ old_api_unload: ; PXE query packets partially filled in ; section .bss16 + global pxe_bootp_query_pkt, pxe_udp_write_pkt + global pxe_udp_open_pkt, pxe_udp_read_pkt pxe_bootp_query_pkt: .status: resw 1 ; Status .packettype: resw 1 ; Boot server packet type @@ -2662,7 +757,7 @@ pxe_udp_read_pkt: %if GPXE section .data16 - + global gpxe_file_api_check gpxe_file_api_check: .status: dw 0 ; Status .size: dw 20 ; Size in bytes @@ -2672,6 +767,8 @@ gpxe_file_api_check: .flags: dd 0 section .bss16 + global gpxe_file_read, gpxe_get_file_size + global gpxe_file_open gpxe_file_open: .status: resw 1 ; Status @@ -2698,9 +795,9 @@ gpxe_file_read: section .data16 alignz 4 + global BaseStack BaseStack dd StackTop ; ESP of base stack dw 0 ; SS of base stack -NextSocket dw 49152 ; Counter for allocating socket numbers KeepPXE db 0 ; Should PXE be kept around? ; @@ -2727,18 +824,22 @@ tftp_opt_table: tftp_opts equ ($-tftp_opt_table)/6 ; -; Error packet to return on options negotiation error +; Error packet to return on TFTP protocol error +; Most of our errors are OACK parsing errors, so use that error code ; -tftp_opt_err dw TFTP_ERROR ; ERROR packet - dw TFTP_EOPTNEG ; ERROR 8: bad options - db 'tsize option required', 0 ; Error message -tftp_opt_err_len equ ($-tftp_opt_err) + global tftp_proto_err, tftp_proto_err_len +tftp_proto_err dw TFTP_ERROR ; ERROR packet + dw TFTP_EOPTNEG ; ERROR 8: OACK error + db 'TFTP protocol error', 0 ; Error message +tftp_proto_err_len equ ($-tftp_proto_err) alignz 4 + global ack_packet_buf ack_packet_buf: dw TFTP_ACK, 0 ; TFTP ACK packet ; ; IP information (initialized to "unknown" values) + global MyIP, ServerIP, Netmask, Gateway, ServerPort MyIP dd 0 ; My IP address ServerIP dd 0 ; IP address of boot server Netmask dd 0 ; Netmask of this subnet -- 2.7.4