diff options
Diffstat (limited to 'contrib/syslinux-4.02/core/fs/pxe')
-rw-r--r-- | contrib/syslinux-4.02/core/fs/pxe/dhcp_option.c | 258 | ||||
-rw-r--r-- | contrib/syslinux-4.02/core/fs/pxe/dnsresolv.c | 349 | ||||
-rw-r--r-- | contrib/syslinux-4.02/core/fs/pxe/idle.c | 112 | ||||
-rw-r--r-- | contrib/syslinux-4.02/core/fs/pxe/portnum.c | 68 | ||||
-rw-r--r-- | contrib/syslinux-4.02/core/fs/pxe/pxe.c | 1731 | ||||
-rw-r--r-- | contrib/syslinux-4.02/core/fs/pxe/pxe.h | 252 |
6 files changed, 2770 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/core/fs/pxe/dhcp_option.c b/contrib/syslinux-4.02/core/fs/pxe/dhcp_option.c new file mode 100644 index 0000000..50f2de0 --- /dev/null +++ b/contrib/syslinux-4.02/core/fs/pxe/dhcp_option.c @@ -0,0 +1,258 @@ +#include <stdio.h> +#include <string.h> +#include <core.h> +#include <sys/cpu.h> +#include "pxe.h" + +char LocalDomain[256]; + +int over_load; +uint8_t uuid_type; +uint8_t uuid[16]; + +static void parse_dhcp_options(const void *, int, uint8_t); + +static void subnet_mask(const void *data, int opt_len) +{ + if (opt_len != 4) + return; + IPInfo.netmask = *(const uint32_t *)data; +} + +static void router(const void *data, int opt_len) +{ + if (opt_len != 4) + return; + IPInfo.gateway = *(const uint32_t *)data; +} + +static void dns_servers(const void *data, int opt_len) +{ + const uint32_t *dp = data; + int num = 0; + + while (num < DNS_MAX_SERVERS) { + uint32_t ip; + + if (opt_len < 4) + break; + + opt_len -= 4; + ip = *dp++; + if (ip_ok(ip)) + dns_server[num++] = ip; + } + while (num < DNS_MAX_SERVERS) + dns_server[num++] = 0; +} + +static void local_domain(const void *data, int opt_len) +{ + char buffer[256]; + char *ld = LocalDomain; + + memcpy(buffer, data, opt_len); + buffer[opt_len] = 0; + + dns_mangle(&ld, buffer); +} + +static void vendor_encaps(const void *data, int opt_len) +{ + /* Only recognize PXELINUX options */ + parse_dhcp_options(data, opt_len, 208); +} + +static void option_overload(const void *data, int opt_len) +{ + if (opt_len != 1) + return; + over_load = *(uint8_t *)data; +} + +static void server(const void *data, int opt_len) +{ + uint32_t ip; + + if (opt_len != 4) + return; + + if (IPInfo.serverip) + return; + + ip = *(uint32_t *)data; + if (ip_ok(ip)) + IPInfo.serverip = ip; +} + +static void client_identifier(const void *data, int opt_len) +{ + if (opt_len > MAC_MAX || opt_len < 2 || + MAC_len != (opt_len >> 8) || + *(uint8_t *)data != MAC_type) + return; + + opt_len --; + MAC_len = opt_len & 0xff; + memcpy(MAC, data+1, opt_len); + MAC[opt_len] = 0; +} + +static void bootfile_name(const void *data, int opt_len) +{ + memcpy(boot_file, data, opt_len); + boot_file[opt_len] = 0; +} + +static void uuid_client_identifier(const void *data, int opt_len) +{ + int type = *(const uint8_t *)data; + if (opt_len != 17 || type != 0 || have_uuid) + return; + + have_uuid = true; + uuid_type = type; + memcpy(uuid, data+1, 16); +} + +static void pxelinux_configfile(const void *data, int opt_len) +{ + DHCPMagic |= 2; + memcpy(ConfigName, data, opt_len); + ConfigName[opt_len] = 0; +} + +static void pxelinux_pathprefix(const void *data, int opt_len) +{ + DHCPMagic |= 4; + memcpy(path_prefix, data, opt_len); + path_prefix[opt_len] = 0; +} + +static void pxelinux_reboottime(const void *data, int opt_len) +{ + if (opt_len != 4) + return; + + RebootTime = ntohl(*(const uint32_t *)data); + DHCPMagic |= 8; /* Got reboot time */ +} + + +struct dhcp_options { + int opt_num; + void (*fun)(const void *, int); +}; + +static const 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. + */ +static void parse_dhcp_options(const void *option, int size, uint8_t opt_filter) +{ + int opt_num; + int opt_len; + const int opt_entries = sizeof(dhcp_opts) / sizeof(dhcp_opts[0]); + int i = 0; + const uint8_t *p = option; + const struct dhcp_options *opt; + + /* The only 1-byte options are 00 and FF, neither of which matter */ + while (size >= 2) { + opt_num = *p++; + size--; + + if (opt_num == 0) + continue; + if (opt_num == 0xff) + break; + + /* Anything else will have a length field */ + opt_len = *p++; /* c <- option lenght */ + size -= opt_len + 1; + if (size < 0) + break; + + if (opt_num >= opt_filter) { + 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 + * server_ip - boot server IP address + * net_mask - network mask + * gate_way - default gateway router IP + * boot_file - boot file name + * DNSServers - DNS server IPs + * LocalDomain - Local domain name + * MAC_len, MAC - Client identifier, if MAC_len == 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; + + IPInfo.ipv4 = 4; /* This is IPv4 only for now... */ + + over_load = 0; + if (ip_ok(dhcp->yip)) + IPInfo.myip = dhcp->yip; + + if (ip_ok(dhcp->sip)) + IPInfo.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 (over_load & 1) + parse_dhcp_options(&dhcp->bootfile, 128, 0); + else if (dhcp->bootfile[0]) + strcpy(boot_file, dhcp->bootfile); + + if (over_load & 2) + parse_dhcp_options(dhcp->sname, 64, 0); +} diff --git a/contrib/syslinux-4.02/core/fs/pxe/dnsresolv.c b/contrib/syslinux-4.02/core/fs/pxe/dnsresolv.c new file mode 100644 index 0000000..2b263fa --- /dev/null +++ b/contrib/syslinux-4.02/core/fs/pxe/dnsresolv.c @@ -0,0 +1,349 @@ +#include <stdio.h> +#include <string.h> +#include <core.h> +#include "pxe.h" + +/* DNS CLASS values we care about */ +#define CLASS_IN 1 + +/* DNS TYPE values we care about */ +#define TYPE_A 1 +#define TYPE_CNAME 5 + +/* + * The DNS header structure + */ +struct dnshdr { + uint16_t id; + uint16_t flags; + /* number of entries in the question section */ + uint16_t qdcount; + /* number of resource records in the answer section */ + uint16_t ancount; + /* number of name server resource records in the authority records section*/ + uint16_t nscount; + /* number of resource records in the additional records section */ + uint16_t arcount; +} __attribute__ ((packed)); + +/* + * The DNS query structure + */ +struct dnsquery { + uint16_t qtype; + uint16_t qclass; +} __attribute__ ((packed)); + +/* + * The DNS Resource recodes structure + */ +struct dnsrr { + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t rdlength; /* The lenght of this rr data */ + char rdata[]; +} __attribute__ ((packed)); + + +uint32_t dns_server[DNS_MAX_SERVERS] = {0, }; + + +/* + * Turn a string in _src_ into a DNS "label set" in _dst_; returns the + * number of dots encountered. On return, *dst is updated. + */ +int dns_mangle(char **dst, const char *p) +{ + char *q = *dst; + char *count_ptr; + char c; + int dots = 0; + + count_ptr = q; + *q++ = 0; + + while (1) { + c = *p++; + if (c == 0 || c == ':' || c == '/') + break; + if (c == '.') { + dots++; + count_ptr = q; + *q++ = 0; + continue; + } + + *count_ptr += 1; + *q++ = c; + } + + if (*count_ptr) + *q++ = 0; + + /* update the strings */ + *dst = q; + return dots; +} + + +/* + * Compare two sets of DNS labels, in _s1_ and _s2_; the one in _s2_ + * is allowed pointers relative to a packet in buf. + * + */ +static bool dns_compare(const void *s1, const void *s2, const void *buf) +{ + const uint8_t *q = s1; + const uint8_t *p = s2; + unsigned int c0, c1; + + while (1) { + c0 = p[0]; + if (c0 >= 0xc0) { + /* Follow pointer */ + c1 = p[1]; + p = (const uint8_t *)buf + ((c0 - 0xc0) << 8) + c1; + } else if (c0) { + c0++; /* Include the length byte */ + if (memcmp(q, p, c0)) + return false; + q += c0; + p += c0; + } else { + return *q == 0; + } + } +} + +/* + * Copy a DNS label into a buffer, considering the possibility that we might + * have to follow pointers relative to "buf". + * Returns a pointer to the first free byte *after* the terminal null. + */ +static void *dns_copylabel(void *dst, const void *src, const void *buf) +{ + uint8_t *q = dst; + const uint8_t *p = src; + unsigned int c0, c1; + + while (1) { + c0 = p[0]; + if (c0 >= 0xc0) { + /* Follow pointer */ + c1 = p[1]; + p = (const uint8_t *)buf + ((c0 - 0xc0) << 8) + c1; + } else if (c0) { + c0++; /* Include the length byte */ + memcpy(q, p, c0); + p += c0; + q += c0; + } else { + *q++ = 0; + return q; + } + } +} + +/* + * Skip past a DNS label set in DS:SI + */ +static char *dns_skiplabel(char *label) +{ + uint8_t c; + + while (1) { + c = *label++; + if (c >= 0xc0) + return ++label; /* pointer is two bytes */ + if (c == 0) + return label; + label += c; + } +} + +/* + * Actual resolver function + * Points to a null-terminated or :-terminated string in _name_ + * and returns the ip addr in _ip_ if it exists and can be found. + * If _ip_ = 0 on exit, the lookup failed. _name_ will be updated + * + * XXX: probably need some caching here. + */ +uint32_t dns_resolv(const char *name) +{ + static char __lowmem DNSSendBuf[PKTBUF_SIZE]; + static char __lowmem DNSRecvBuf[PKTBUF_SIZE]; + char *p; + int err; + int dots; + int same; + int rd_len; + int ques, reps; /* number of questions and replies */ + uint8_t timeout; + const uint8_t *timeout_ptr = TimeoutTable; + uint32_t oldtime; + uint32_t srv; + uint32_t *srv_ptr; + struct dnshdr *hd1 = (struct dnshdr *)DNSSendBuf; + struct dnshdr *hd2 = (struct dnshdr *)DNSRecvBuf; + struct dnsquery *query; + struct dnsrr *rr; + static __lowmem struct s_PXENV_UDP_WRITE udp_write; + static __lowmem struct s_PXENV_UDP_READ udp_read; + uint16_t local_port; + uint32_t result = 0; + + /* Make sure we have at least one valid DNS server */ + if (!dns_server[0]) + return 0; + + /* Get a local port number */ + local_port = get_port(); + + /* First, fill the DNS header struct */ + hd1->id++; /* New query ID */ + hd1->flags = htons(0x0100); /* Recursion requested */ + hd1->qdcount = htons(1); /* One question */ + hd1->ancount = 0; /* No answers */ + hd1->nscount = 0; /* No NS */ + hd1->arcount = 0; /* No AR */ + + p = DNSSendBuf + sizeof(struct dnshdr); + dots = dns_mangle(&p, name); /* store the CNAME */ + + if (!dots) { + p--; /* Remove final null */ + /* Uncompressed DNS label set so it ends in null */ + strcpy(p, LocalDomain); + } + + /* Fill the DNS query packet */ + query = (struct dnsquery *)p; + query->qtype = htons(TYPE_A); + query->qclass = htons(CLASS_IN); + p += sizeof(struct dnsquery); + + /* Now send it to name server */ + timeout_ptr = TimeoutTable; + timeout = *timeout_ptr++; + srv_ptr = dns_server; + while (timeout) { + srv = *srv_ptr++; + if (!srv) { + srv_ptr = dns_server; + srv = *srv_ptr++; + } + + udp_write.status = 0; + udp_write.ip = srv; + udp_write.gw = gateway(srv); + udp_write.src_port = local_port; + udp_write.dst_port = DNS_PORT; + udp_write.buffer_size = p - DNSSendBuf; + udp_write.buffer = FAR_PTR(DNSSendBuf); + err = pxe_call(PXENV_UDP_WRITE, &udp_write); + if (err || udp_write.status) + continue; + + oldtime = jiffies(); + do { + if (jiffies() - oldtime >= timeout) + goto again; + + udp_read.status = 0; + udp_read.src_ip = srv; + udp_read.dest_ip = IPInfo.myip; + udp_read.s_port = DNS_PORT; + udp_read.d_port = local_port; + udp_read.buffer_size = PKTBUF_SIZE; + udp_read.buffer = FAR_PTR(DNSRecvBuf); + err = pxe_call(PXENV_UDP_READ, &udp_read); + } while (err || udp_read.status || hd2->id != hd1->id); + + if ((hd2->flags ^ 0x80) & htons(0xf80f)) + goto badness; + + ques = htons(hd2->qdcount); /* Questions */ + reps = htons(hd2->ancount); /* Replies */ + p = DNSRecvBuf + sizeof(struct dnshdr); + while (ques--) { + p = dns_skiplabel(p); /* Skip name */ + p += 4; /* Skip question trailer */ + } + + /* Parse the replies */ + while (reps--) { + same = dns_compare(DNSSendBuf + sizeof(struct dnshdr), + p, DNSRecvBuf); + p = dns_skiplabel(p); + rr = (struct dnsrr *)p; + rd_len = ntohs(rr->rdlength); + if (same && ntohs(rr->class) == CLASS_IN) { + switch (ntohs(rr->type)) { + case TYPE_A: + if (rd_len == 4) { + result = *(uint32_t *)rr->rdata; + goto done; + } + break; + case TYPE_CNAME: + dns_copylabel(DNSSendBuf + sizeof(struct dnshdr), + rr->rdata, DNSRecvBuf); + /* + * We should probably rescan the packet from the top + * here, and technically we might have to send a whole + * new request here... + */ + break; + default: + break; + } + } + + /* not the one we want, try next */ + p += sizeof(struct dnsrr) + rd_len; + } + + badness: + /* + * + ; We got back no data from this server. + ; Unfortunately, for a recursive, non-authoritative + ; query there is no such thing as an NXDOMAIN reply, + ; which technically means we can't draw any + ; conclusions. However, in practice that means the + ; domain doesn't exist. If this turns out to be a + ; problem, we may want to add code to go through all + ; the servers before giving up. + + ; If the DNS server wasn't capable of recursion, and + ; isn't capable of giving us an authoritative reply + ; (i.e. neither AA or RA set), then at least try a + ; different setver... + */ + if (hd2->flags == htons(0x480)) + continue; + + break; /* failed */ + + again: + continue; + } + +done: + free_port(local_port); /* Return port number to the free pool */ + + return result; +} + + +/* + * the one should be called from ASM file + */ +void pxe_dns_resolv(com32sys_t *regs) +{ + const char *name = MK_PTR(regs->ds, regs->esi.w[0]); + + regs->eax.l = dns_resolv(name); +} diff --git a/contrib/syslinux-4.02/core/fs/pxe/idle.c b/contrib/syslinux-4.02/core/fs/pxe/idle.c new file mode 100644 index 0000000..52a87c3 --- /dev/null +++ b/contrib/syslinux-4.02/core/fs/pxe/idle.c @@ -0,0 +1,112 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2008 H. Peter Anvin - All Rights Reserved + * Copyright 2009 Intel Corporation; author: H. Peter Anvin + * + * 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., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include <stdio.h> +#include <string.h> +#include <core.h> +#include <fs.h> +#include <minmax.h> +#include <sys/cpu.h> +#include "pxe.h" + +static int pxe_idle_poll(void) +{ + static __lowmem char junk_pkt[PKTBUF_SIZE]; + static __lowmem t_PXENV_UDP_READ read_buf; + + memset(&read_buf, 0, sizeof read_buf); + + read_buf.src_ip = 0; /* Any destination */ + read_buf.dest_ip = IPInfo.myip; + read_buf.s_port = 0; /* Any source port */ + read_buf.d_port = htons(9); /* Discard port (not used...) */ + read_buf.buffer_size = sizeof junk_pkt; + read_buf.buffer = FAR_PTR(junk_pkt); + + pxe_call(PXENV_UDP_READ, &read_buf); + + return 0; +} + +static uint32_t pxe_detect_nic_type(void) +{ + static __lowmem t_PXENV_UNDI_GET_NIC_TYPE nic_type; + + if (pxe_call(PXENV_UNDI_GET_NIC_TYPE, &nic_type)) + return -1; /* Unknown NIC */ + + if (nic_type.NicType != PCI_NIC && nic_type.NicType != CardBus_NIC) + return -1; /* Not a PCI NIC */ + + /* + * Return VID:DID as a single number, with the VID in the high word + * -- this is opposite from the usual order, but it makes it easier to + * enforce that the table is sorted. + */ + return (nic_type.info.pci.Vendor_ID << 16) + nic_type.info.pci.Dev_ID; +} + +#define PCI_DEV(vid, did) (((vid) << 16) + (did)) + +/* This array should be sorted!! */ +static const uint32_t pxe_need_idle_drain[] = +{ + /* + * Older Broadcom NICs: they need receive calls on idle to avoid + * FIFO stalls. + */ + PCI_DEV(0x14e4, 0x1659), /* BCM5721 */ + PCI_DEV(0x14e4, 0x165a), /* BCM5722 */ + PCI_DEV(0x14e4, 0x165b), /* BCM5723 */ + PCI_DEV(0x14e4, 0x1668), /* BCM5714 */ + PCI_DEV(0x14e4, 0x1669), /* BCM5714S */ + PCI_DEV(0x14e4, 0x166a), /* BCM5780 */ + PCI_DEV(0x14e4, 0x1673), /* BCM5755M */ + PCI_DEV(0x14e4, 0x1674), /* BCM5756ME */ + PCI_DEV(0x14e4, 0x1678), /* BCM5715 */ + PCI_DEV(0x14e4, 0x1679), /* BCM5715S */ + PCI_DEV(0x14e4, 0x167b), /* BCM5755 */ +}; + +void pxe_idle_init(void) +{ + uint32_t dev_id = pxe_detect_nic_type(); + int l, h; + bool found; + + l = 0; + h = sizeof pxe_need_idle_drain / sizeof pxe_need_idle_drain[0] - 1; + + found = false; + while (h >= l) { + int x = (l+h) >> 1; + uint32_t id = pxe_need_idle_drain[x]; + + if (id == dev_id) { + found = true; + break; + } else if (id < dev_id) { + l = x+1; + } else { + h = x-1; + } + } + + if (found) + idle_hook_func = pxe_idle_poll; +} + +void pxe_idle_cleanup(void) +{ + idle_hook_func = NULL; +} diff --git a/contrib/syslinux-4.02/core/fs/pxe/portnum.c b/contrib/syslinux-4.02/core/fs/pxe/portnum.c new file mode 100644 index 0000000..19af0cd --- /dev/null +++ b/contrib/syslinux-4.02/core/fs/pxe/portnum.c @@ -0,0 +1,68 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2010 Intel Corporation; author: H. Peter Anvin + * + * 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., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include <stdint.h> +#include <stdbool.h> +#include <netinet/in.h> +#include "pxe.h" + +/* Port number bitmap - port numbers 49152 (0xc000) to 57343 (0xefff) */ +#define PORT_NUMBER_BASE 49152 +#define PORT_NUMBER_COUNT 8192 /* Power of 2, please */ +static uint32_t port_number_bitmap[PORT_NUMBER_COUNT/32]; +static uint16_t first_port_number /* = 0 */; + +/* + * Bitmap functions + */ +static bool test_bit(const uint32_t *bitmap, int32_t index) +{ + uint8_t st; + asm("btl %2,%1 ; setc %0" : "=qm" (st) : "m" (*bitmap), "r" (index)); + return st; +} + +static void set_bit(uint32_t *bitmap, int32_t index) +{ + asm volatile("btsl %1,%0" : "+m" (*bitmap) : "r" (index) : "memory"); +} + +static void clr_bit(uint32_t *bitmap, int32_t index) +{ + asm volatile("btcl %1,%0" : "+m" (*bitmap) : "r" (index) : "memory"); +} + +/* + * Get and free a port number (host byte order) + */ +uint16_t get_port(void) +{ + uint16_t port; + + do { + port = first_port_number++; + first_port_number &= PORT_NUMBER_COUNT - 1; + } while (test_bit(port_number_bitmap, port)); + + set_bit(port_number_bitmap, port); + return htons(port + PORT_NUMBER_BASE); +} + +void free_port(uint16_t port) +{ + port = ntohs(port) - PORT_NUMBER_BASE; + + if (port >= PORT_NUMBER_COUNT) + return; + + clr_bit(port_number_bitmap, port); +} diff --git a/contrib/syslinux-4.02/core/fs/pxe/pxe.c b/contrib/syslinux-4.02/core/fs/pxe/pxe.c new file mode 100644 index 0000000..0238ed4 --- /dev/null +++ b/contrib/syslinux-4.02/core/fs/pxe/pxe.c @@ -0,0 +1,1731 @@ +#include <dprintf.h> +#include <stdio.h> +#include <string.h> +#include <core.h> +#include <fs.h> +#include <minmax.h> +#include <sys/cpu.h> +#include "pxe.h" + +#define GPXE 1 + +static uint16_t real_base_mem; /* Amount of DOS memory after freeing */ + +uint8_t MAC[MAC_MAX]; /* Actual MAC address */ +uint8_t MAC_len; /* MAC address len */ +uint8_t MAC_type; /* MAC address type */ + +char __bss16 BOOTIFStr[7+3*(MAC_MAX+1)]; +#define MAC_str (BOOTIFStr+7) /* The actual hardware address */ +char __bss16 SYSUUIDStr[8+32+5]; +#define UUID_str (SYSUUIDStr+8) /* The actual UUID */ + +char boot_file[256]; /* From DHCP */ +char path_prefix[256]; /* From DHCP */ +char dot_quad_buf[16]; + +static bool has_gpxe; +static uint32_t gpxe_funcs; +bool have_uuid = false; + +/* Common receive buffer */ +static __lowmem char packet_buf[PKTBUF_SIZE] __aligned(16); + +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 +}; + +struct tftp_options { + const char *str_ptr; /* string pointer */ + size_t offset; /* offset into socket structre */ +}; + +#define IFIELD(x) offsetof(struct inode, x) +#define PFIELD(x) (offsetof(struct inode, pvt) + \ + offsetof(struct pxe_pvt_inode, x)) + +static const struct tftp_options tftp_options[] = +{ + { "tsize", IFIELD(size) }, + { "blksize", PFIELD(tftp_blksize) }, +}; +static const int tftp_nopts = sizeof tftp_options / sizeof tftp_options[0]; + +static void tftp_error(struct inode *file, uint16_t errnum, + const char *errstr); + +/* + * Allocate a local UDP port structure and assign it a local port number. + * Return the inode pointer if success, or null if failure + */ +static struct inode *allocate_socket(struct fs_info *fs) +{ + struct inode *inode = alloc_inode(fs, 0, sizeof(struct pxe_pvt_inode)); + + if (!inode) { + malloc_error("socket structure"); + } else { + struct pxe_pvt_inode *socket = PVT(inode); + socket->tftp_localport = get_port(); + inode->mode = DT_REG; /* No other types relevant for PXE */ + } + + return inode; +} + +static void free_socket(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + + free_port(socket->tftp_localport); + free_inode(inode); +} + +#if GPXE +static void gpxe_close_file(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + static __lowmem struct s_PXENV_FILE_CLOSE file_close; + + file_close.FileHandle = socket->tftp_remoteport; + pxe_call(PXENV_FILE_CLOSE, &file_close); +} +#endif + +static void pxe_close_file(struct file *file) +{ + struct inode *inode = file->inode; + struct pxe_pvt_inode *socket = PVT(inode); + + if (!socket->tftp_goteof) { +#if GPXE + if (socket->tftp_localport == 0xffff) { + gpxe_close_file(inode); + } else +#endif + if (socket->tftp_localport != 0) { + tftp_error(inode, 0, "No error, file close"); + } + } + + free_socket(inode); +} + +/** + * 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 + * + */ +static 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 + * + */ +static 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; + } +} + +/* + * Parse a single hexadecimal byte, which must be complete (two + * digits). This is used in URL parsing. + */ +static int hexbyte(const char *p) +{ + if (!is_hex(p[0]) || !is_hex(p[1])) + return -1; + else + return (hexval(p[0]) << 4) + hexval(p[1]); +} + +/* + * Tests an IP address in _ip_ 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. + * + */ +bool ip_ok(uint32_t ip) +{ + uint8_t ip_hi = (uint8_t)ip; /* First octet of the ip address */ + + if (ip == 0xffffffff || /* Refuse the all-ones address */ + ip_hi == 0 || /* Refuse network zero */ + ip_hi == 127 || /* Refuse the loopback network */ + (ip_hi & 240) == 224) /* Refuse class D */ + return false; + + return true; +} + + +/* + * Take an IP address (in network byte order) in _ip_ and + * output a dotted quad string to _dst_, returns the length + * of the dotted quad ip string. + * + */ +static 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 + * + */ +static const char *parse_dotquad(const char *ip_str, uint32_t *res) +{ + const char *p = ip_str; + uint8_t part = 0; + uint32_t ip = 0; + int i; + + for (i = 0; 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 = htonl(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 regs; + +#if 0 + printf("pxe_call op %04x data %p\n", opcode, data); +#endif + + memset(®s, 0, sizeof regs); + regs.ebx.w[0] = opcode; + regs.es = SEG(data); + regs.edi.w[0] = OFFS(data); + call16(pxenv, ®s, ®s); + + return regs.eflags.l & EFLAGS_CF; /* CF SET if fail */ +} + +/** + * Send an ERROR packet. This is used to terminate a connection. + * + * @inode: Inode structure + * @errnum: Error number (network byte order) + * @errstr: Error string (included in packet) + */ +static void tftp_error(struct inode *inode, uint16_t errnum, + const char *errstr) +{ + static __lowmem struct { + uint16_t err_op; + uint16_t err_num; + char err_msg[64]; + } __packed err_buf; + static __lowmem struct s_PXENV_UDP_WRITE udp_write; + int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1); + struct pxe_pvt_inode *socket = PVT(inode); + + err_buf.err_op = TFTP_ERROR; + err_buf.err_num = errnum; + memcpy(err_buf.err_msg, errstr, len); + err_buf.err_msg[len] = '\0'; + + udp_write.src_port = socket->tftp_localport; + udp_write.dst_port = socket->tftp_remoteport; + udp_write.ip = socket->tftp_remoteip; + udp_write.gw = gateway(udp_write.ip); + udp_write.buffer = FAR_PTR(&err_buf); + udp_write.buffer_size = 4 + len + 1; + + /* If something goes wrong, there is nothing we can do, anyway... */ + pxe_call(PXENV_UDP_WRITE, &udp_write); +} + + +/** + * Send ACK packet. This is a common operation and so is worth canning. + * + * @param: inode, Inode pointer + * @param: ack_num, Packet # to ack (network byte order) + * + */ +static void ack_packet(struct inode *inode, uint16_t ack_num) +{ + int err; + static __lowmem uint16_t ack_packet_buf[2]; + static __lowmem struct s_PXENV_UDP_WRITE udp_write; + struct pxe_pvt_inode *socket = PVT(inode); + + /* Packet number to ack */ + ack_packet_buf[0] = TFTP_ACK; + ack_packet_buf[1] = ack_num; + udp_write.src_port = socket->tftp_localport; + udp_write.dst_port = socket->tftp_remoteport; + udp_write.ip = socket->tftp_remoteip; + udp_write.gw = gateway(udp_write.ip); + udp_write.buffer = FAR_PTR(ack_packet_buf); + udp_write.buffer_size = 4; + + err = pxe_call(PXENV_UDP_WRITE, &udp_write); +#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 + * + */ +static int pxe_get_cached_info(int type) +{ + int err; + static __lowmem struct s_PXENV_GET_CACHED_INFO get_cached_info; + printf(" %02x", type); + + get_cached_info.Status = 0; + get_cached_info.PacketType = type; + get_cached_info.BufferSize = 8192; + get_cached_info.Buffer = FAR_PTR(trackbuf); + err = pxe_call(PXENV_GET_CACHED_INFO, &get_cached_info); + if (err) { + printf("PXE API call failed, error %04x\n", err); + kaboom(); + } + + return get_cached_info.BufferSize; +} + + +/* + * Return the type of pathname passed. + */ +enum pxe_path_type { + PXE_RELATIVE, /* No :: or URL */ + PXE_HOMESERVER, /* Starting with :: */ + PXE_TFTP, /* host:: */ + PXE_URL_TFTP, /* tftp:// */ + PXE_URL, /* Absolute URL syntax */ +}; + +static enum pxe_path_type pxe_path_type(const char *str) +{ + const char *p; + + p = str; + + while (1) { + switch (*p) { + case ':': + if (p[1] == ':') { + if (p == str) + return PXE_HOMESERVER; + else + return PXE_TFTP; + } else if (p > str && p[1] == '/' && p[2] == '/') { + if (!strncasecmp(str, "tftp://", 7)) + return PXE_URL_TFTP; + else + return PXE_URL; + } + + /* else fall through */ + case '/': case '!': case '@': case '#': case '%': + case '^': case '&': case '*': case '(': case ')': + case '[': case ']': case '{': case '}': case '\\': + case '|': case '=': case '`': case '~': case '\'': + case '\"': case ';': case '>': case '<': case '?': + case '\0': + /* Any of these characters terminate the colon search */ + return PXE_RELATIVE; + default: + break; + } + p++; + } +} + +#if GPXE + +/** + * Get a fresh packet from a gPXE socket + * @param: inode -> Inode pointer + * + */ +static void get_packet_gpxe(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + static __lowmem struct s_PXENV_FILE_READ file_read; + int err; + + while (1) { + file_read.FileHandle = socket->tftp_remoteport; + file_read.Buffer = FAR_PTR(packet_buf); + file_read.BufferSize = PKTBUF_SIZE; + err = pxe_call(PXENV_FILE_READ, &file_read); + if (!err) /* successed */ + break; + + if (file_read.Status != PXENV_STATUS_TFTP_OPEN) + kaboom(); + } + + memcpy(socket->tftp_pktbuf, packet_buf, file_read.BufferSize); + + socket->tftp_dataptr = socket->tftp_pktbuf; + socket->tftp_bytesleft = file_read.BufferSize; + socket->tftp_filepos += file_read.BufferSize; + + if (socket->tftp_bytesleft == 0) + inode->size = socket->tftp_filepos; + + /* if we're done here, close the file */ + if (inode->size > socket->tftp_filepos) + return; + + /* Got EOF, close it */ + socket->tftp_goteof = 1; + gpxe_close_file(inode); +} +#endif /* GPXE */ + + +/* + * mangle a filename pointed to by _src_ into a buffer pointed + * to by _dst_; ends on encountering any whitespace. + * + */ +static void pxe_mangle_name(char *dst, const char *src) +{ + size_t len = FILENAME_MAX-1; + + while (len-- && not_whitespace(*src)) + *dst++ = *src++; + + *dst = '\0'; +} + +/* + * Get a fresh packet if the buffer is drained, and we haven't hit + * EOF yet. The buffer should be filled immediately after draining! + */ +static void fill_buffer(struct inode *inode) +{ + int err; + int last_pkt; + const uint8_t *timeout_ptr; + uint8_t timeout; + uint16_t buffersize; + uint32_t oldtime; + void *data = NULL; + static __lowmem struct s_PXENV_UDP_READ udp_read; + struct pxe_pvt_inode *socket = PVT(inode); + + if (socket->tftp_bytesleft || socket->tftp_goteof) + return; + +#if GPXE + if (socket->tftp_localport == 0xffff) { + get_packet_gpxe(inode); + return; + } +#endif + + /* + * Start by ACKing the previous packet; this should cause + * the next packet to be sent. + */ + timeout_ptr = TimeoutTable; + timeout = *timeout_ptr++; + oldtime = jiffies(); + + ack_again: + ack_packet(inode, socket->tftp_lastpkt); + + while (timeout) { + udp_read.buffer = FAR_PTR(packet_buf); + udp_read.buffer_size = PKTBUF_SIZE; + udp_read.src_ip = socket->tftp_remoteip; + udp_read.dest_ip = IPInfo.myip; + udp_read.s_port = socket->tftp_remoteport; + udp_read.d_port = socket->tftp_localport; + err = pxe_call(PXENV_UDP_READ, &udp_read); + if (err) { + uint32_t now = jiffies(); + + if (now-oldtime >= timeout) { + oldtime = now; + timeout = *timeout_ptr++; + if (!timeout) + break; + } + continue; + } + + if (udp_read.buffer_size < 4) /* Bad size for a DATA packet */ + continue; + + data = packet_buf; + if (*(uint16_t *)data != TFTP_DATA) /* Not a data packet */ + continue; + + /* If goes here, recevie OK, break */ + break; + } + + /* time runs out */ + if (timeout == 0) + kaboom(); + + last_pkt = socket->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 */ + socket->tftp_lastpkt = last_pkt; /* Update last packet number */ + buffersize = udp_read.buffer_size - 4; /* Skip TFTP header */ + memcpy(socket->tftp_pktbuf, packet_buf + 4, buffersize); + socket->tftp_dataptr = socket->tftp_pktbuf; + socket->tftp_filepos += buffersize; + socket->tftp_bytesleft = buffersize; + if (buffersize < socket->tftp_blksize) { + /* it's the last block, ACK packet immediately */ + ack_packet(inode, *(uint16_t *)(data + 2)); + + /* Make sure we know we are at end of file */ + inode->size = socket->tftp_filepos; + socket->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 + * + */ +static uint32_t pxe_getfssec(struct file *file, char *buf, + int blocks, bool *have_more) +{ + struct inode *inode = file->inode; + struct pxe_pvt_inode *socket = PVT(inode); + int count = blocks; + int chunk; + int bytes_read = 0; + + count <<= TFTP_BLOCKSIZE_LG2; + while (count) { + fill_buffer(inode); /* If we have no 'fresh' buffer, get it */ + if (!socket->tftp_bytesleft) + break; + + chunk = count; + if (chunk > socket->tftp_bytesleft) + chunk = socket->tftp_bytesleft; + socket->tftp_bytesleft -= chunk; + memcpy(buf, socket->tftp_dataptr, chunk); + socket->tftp_dataptr += chunk; + buf += chunk; + bytes_read += chunk; + count -= chunk; + } + + + if (socket->tftp_bytesleft || (socket->tftp_filepos < inode->size)) { + fill_buffer(inode); + *have_more = 1; + } else if (socket->tftp_goteof) { + /* + * The socket is closed and the buffer drained; the caller will + * call close_file and therefore free the socket. + */ + *have_more = 0; + } + + return bytes_read; +} + +/** + * Open a TFTP connection to the server + * + * @param:filename, the file we wanna open + * + * @out: open_file_t structure, stores in file->open_file + * @out: the lenght of this file, stores in file->file_len + * + */ +static void pxe_searchdir(const char *filename, struct file *file) +{ + struct fs_info *fs = file->fs; + struct inode *inode; + struct pxe_pvt_inode *socket; + char *buf; + const char *np; + char *p; + char *options; + char *data; + static __lowmem struct s_PXENV_UDP_WRITE udp_write; + static __lowmem struct s_PXENV_UDP_READ udp_read; + static __lowmem struct s_PXENV_FILE_OPEN file_open; + static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408"; + static __lowmem char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail]; + const struct tftp_options *tftp_opt; + int i = 0; + int err; + int buffersize; + int rrq_len; + const uint8_t *timeout_ptr; + uint32_t timeout; + uint32_t oldtime; + uint16_t tid; + uint16_t opcode; + uint16_t blk_num; + uint32_t ip = 0; + uint32_t opdata, *opdata_ptr; + enum pxe_path_type path_type; + char fullpath[2*FILENAME_MAX]; + uint16_t server_port = TFTP_PORT; /* TFTP server port */ + + inode = file->inode = NULL; + + buf = rrq_packet_buf; + *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */ + buf += 2; + + path_type = pxe_path_type(filename); + if (path_type == PXE_RELATIVE) { + snprintf(fullpath, sizeof fullpath, "%s%s", fs->cwd_name, filename); + path_type = pxe_path_type(filename = fullpath); + } + + switch (path_type) { + case PXE_RELATIVE: /* Really shouldn't happen... */ + case PXE_URL: + buf = stpcpy(buf, filename); + ip = IPInfo.serverip; /* Default server */ + break; + + case PXE_HOMESERVER: + buf = stpcpy(buf, filename+2); + ip = IPInfo.serverip; + break; + + case PXE_TFTP: + np = strchr(filename, ':'); + buf = stpcpy(buf, np+2); + if (parse_dotquad(filename, &ip) != np) + ip = dns_resolv(filename); + break; + + case PXE_URL_TFTP: + np = filename + 7; + while (*np && *np != '/' && *np != ':') + np++; + if (np > filename + 7) { + if (parse_dotquad(filename + 7, &ip) != np) + ip = dns_resolv(filename + 7); + } + if (*np == ':') { + np++; + server_port = 0; + while (*np >= '0' && *np <= '9') + server_port = server_port * 10 + *np++ - '0'; + server_port = server_port ? htons(server_port) : TFTP_PORT; + } + if (*np == '/') + np++; /* Do *NOT* eat more than one slash here... */ + /* + * The ; is because of a quirk in the TFTP URI spec (RFC + * 3617); it is to be followed by TFTP modes, which we just ignore. + */ + while (*np && *np != ';') { + int v; + if (*np == '%' && (v = hexbyte(np+1)) > 0) { + *buf++ = v; + np += 3; + } else { + *buf++ = *np++; + } + } + *buf = '\0'; + break; + } + + if (!ip) + return; /* No server */ + + buf++; /* Point *past* the final NULL */ + memcpy(buf, rrq_tail, sizeof rrq_tail); + buf += sizeof rrq_tail; + + rrq_len = buf - rrq_packet_buf; + + inode = allocate_socket(fs); + if (!inode) + return; /* Allocation failure */ + socket = PVT(inode); + +#if GPXE + if (path_type == PXE_URL) { + if (has_gpxe) { + file_open.Status = PXENV_STATUS_BAD_FUNC; + file_open.FileName = FAR_PTR(rrq_packet_buf + 2); + err = pxe_call(PXENV_FILE_OPEN, &file_open); + if (err) + goto done; + + socket->tftp_localport = -1; + socket->tftp_remoteport = file_open.FileHandle; + inode->size = -1; + goto done; + } else { + static bool already = false; + if (!already) { + printf("URL syntax, but gPXE extensions not detected, " + "trying plain TFTP...\n"); + already = true; + } + } + } +#endif /* GPXE */ + + timeout_ptr = TimeoutTable; /* Reset timeout */ + +sendreq: + timeout = *timeout_ptr++; + if (!timeout) + return; /* No file available... */ + oldtime = jiffies(); + + socket->tftp_remoteip = ip; + tid = socket->tftp_localport; /* TID(local port No) */ + udp_write.buffer = FAR_PTR(rrq_packet_buf); + udp_write.ip = ip; + udp_write.gw = gateway(udp_write.ip); + udp_write.src_port = tid; + udp_write.dst_port = server_port; + udp_write.buffer_size = rrq_len; + pxe_call(PXENV_UDP_WRITE, &udp_write); + + /* If the WRITE call fails, we let the timeout take care of it... */ + +wait_pkt: + for (;;) { + buf = packet_buf; + udp_read.status = 0; + udp_read.buffer = FAR_PTR(buf); + udp_read.buffer_size = PKTBUF_SIZE; + udp_read.dest_ip = IPInfo.myip; + udp_read.d_port = tid; + err = pxe_call(PXENV_UDP_READ, &udp_read); + if (err || udp_read.status) { + uint32_t now = jiffies(); + if (now - oldtime >= timeout) + goto sendreq; + } else { + /* Make sure the packet actually came from the server */ + if (udp_read.src_ip == socket->tftp_remoteip) + break; + } + } + + socket->tftp_remoteport = udp_read.s_port; + + /* filesize <- -1 == unknown */ + inode->size = -1; + /* Default blksize unless blksize option negotiated */ + socket->tftp_blksize = TFTP_BLOCKSIZE; + buffersize = udp_read.buffer_size - 2; /* bytes after opcode */ + if (buffersize < 0) + goto wait_pkt; /* Garbled reply */ + + /* + * Get the opcode type, and parse it + */ + opcode = *(uint16_t *)packet_buf; + switch (opcode) { + case TFTP_ERROR: + inode->size = 0; + break; /* ERROR reply; don't try again */ + + case 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 wait_pkt; + data = packet_buf + 2; + blk_num = *(uint16_t *)data; + data += 2; + if (blk_num != htons(1)) + goto wait_pkt; + socket->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. + */ + inode->size = buffersize; + socket->tftp_goteof = 1; + ack_packet(inode, blk_num); + } + + socket->tftp_bytesleft = buffersize; + socket->tftp_dataptr = socket->tftp_pktbuf; + memcpy(socket->tftp_pktbuf, data, buffersize); + break; + + case TFTP_OACK: + /* + * Now we need to parse the OACK packet to get the transfer + * and packet sizes. + */ + + options = packet_buf + 2; + p = options; + + while (buffersize) { + const char *opt = p; + + /* + * 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. + */ + if (!*opt) + goto done; + + while (buffersize) { + if (!*p) + break; /* Found a final null */ + *p++ |= 0x20; + buffersize--; + } + if (!buffersize) + break; /* Unterminated option */ + + /* Consume the terminal null */ + p++; + buffersize--; + + if (!buffersize) + break; /* No option data */ + + /* + * Parse option pointed to by options; guaranteed to be + * null-terminated + */ + tftp_opt = tftp_options; + for (i = 0; i < tftp_nopts; i++) { + if (!strcmp(opt, tftp_opt->str_ptr)) + break; + tftp_opt++; + } + if (i == tftp_nopts) + goto err_reply; /* Non-negotitated option returned, + no idea what it means ...*/ + + /* get the address of the filed that we want to write on */ + opdata_ptr = (uint32_t *)((char *)inode + tftp_opt->offset); + opdata = 0; + + /* do convert a number-string to decimal number, just like atoi */ + while (buffersize--) { + uint8_t d = *p++; + if (d == '\0') + break; /* found a final null */ + d -= '0'; + if (d > 9) + goto err_reply; /* Not a decimal digit */ + opdata = opdata*10 + d; + } + *opdata_ptr = opdata; + } + break; + + default: + printf("TFTP unknown opcode %d\n", ntohs(opcode)); + goto err_reply; + } + +done: + if (!inode->size) { + free_socket(inode); + return; + } + file->inode = inode; + return; + +err_reply: + /* Build the TFTP error packet */ + tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error"); + printf("TFTP server sent an incomprehesible reply\n"); + kaboom(); +} + + +/* + * Store standard filename prefix + */ +static void get_prefix(void) +{ + int len; + char *p; + char c; + + if (!(DHCPMagic & 0x04)) { + /* No path prefix option, derive from boot file */ + + strlcpy(path_prefix, boot_file, sizeof path_prefix); + len = strlen(path_prefix); + p = &path_prefix[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 */ + } + + printf("TFTP prefix: %s\n", path_prefix); + chdir(path_prefix); +} + +/* + * realpath for PXE + */ +static size_t pxe_realpath(struct fs_info *fs, char *dst, const char *src, + size_t bufsize) +{ + enum pxe_path_type path_type = pxe_path_type(src); + + return snprintf(dst, bufsize, "%s%s", + path_type == PXE_RELATIVE ? fs->cwd_name : "", src); +} + +/* + * chdir for PXE + */ +static int pxe_chdir(struct fs_info *fs, const char *src) +{ + /* The cwd for PXE is just a text prefix */ + enum pxe_path_type path_type = pxe_path_type(src); + + if (path_type == PXE_RELATIVE) + strlcat(fs->cwd_name, src, sizeof fs->cwd_name); + else + strlcpy(fs->cwd_name, src, sizeof fs->cwd_name); + + dprintf("cwd = \"%s\"\n", fs->cwd_name); + return 0; +} + + /* + * try to load a config file, if found, return 1, or return 0 + * + */ +static int try_load(char *config_name) +{ + com32sys_t regs; + + printf("Trying to load: %-50s ", config_name); + pxe_mangle_name(KernelName, config_name); + + memset(®s, 0, sizeof regs); + regs.edi.w[0] = OFFS_WRT(KernelName, 0); + call16(core_open, ®s, ®s); + if (regs.eflags.l & EFLAGS_ZF) { + strcpy(ConfigName, KernelName); + printf("\r"); + return 0; + } else { + printf("ok\n"); + return 1; + } +} + + +/* Load the config file, return 1 if failed, or 0 */ +static int pxe_load_config(void) +{ + const char *cfgprefix = "pxelinux.cfg/"; + const char *default_str = "default"; + char *config_file; + char *last; + int tries = 8; + + get_prefix(); + if (DHCPMagic & 0x02) { + /* We got a DHCP option, try it first */ + if (try_load(ConfigName)) + return 0; + } + + /* + * Have to guess config file name ... + */ + config_file = stpcpy(ConfigName, cfgprefix); + + /* Try loading by UUID */ + if (have_uuid) { + strcpy(config_file, UUID_str); + if (try_load(ConfigName)) + return 0; + } + + /* Try loading by MAC address */ + strcpy(config_file, MAC_str); + if (try_load(ConfigName)) + return 0; + + /* Nope, try hexadecimal IP prefixes... */ + uchexbytes(config_file, (uint8_t *)&IPInfo.myip, 4); + last = &config_file[8]; + while (tries) { + *last = '\0'; /* Zero-terminate string */ + if (try_load(ConfigName)) + return 0; + last--; /* Drop one character */ + tries--; + }; + + /* Final attempt: "default" string */ + strcpy(config_file, default_str); + if (try_load(ConfigName)) + return 0; + + printf("%-68s\n", "Unable to locate configuration file"); + kaboom(); +} + +/* + * Generate the bootif string. + */ +static void make_bootif_string(void) +{ + const uint8_t *src; + char *dst = BOOTIFStr; + int i; + + dst += sprintf(dst, "BOOTIF=%02x", MAC_type); + src = MAC; + for (i = MAC_len; i; i--) + dst += sprintf(dst, "-%02x", *src++); +} +/* + * Generate the SYSUUID string, if we have one... + */ +static void make_sysuuid_string(void) +{ + static const uint8_t uuid_dashes[] = {4, 2, 2, 2, 6, 0}; + const uint8_t *src = uuid; + const uint8_t *uuid_ptr = uuid_dashes; + char *dst; + + SYSUUIDStr[0] = '\0'; /* If nothing there... */ + + /* Try loading by UUID */ + if (have_uuid) { + dst = stpcpy(SYSUUIDStr, "SYSUUID="); + + while (*uuid_ptr) { + int len = *uuid_ptr; + + lchexbytes(dst, src, len); + dst += len * 2; + src += len; + uuid_ptr++; + *dst++ = '-'; + } + /* Remove last dash and zero-terminate */ + *--dst = '\0'; + } +} + +/* + * Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask> + * option into IPOption based on a DHCP packet in trackbuf. + * + */ +char __bss16 IPOption[3+4*16]; + +static void genipopt(void) +{ + char *p = IPOption; + const uint32_t *v = &IPInfo.myip; + int i; + + p = stpcpy(p, "ip="); + + for (i = 0; i < 4; i++) { + p += gendotquad(p, *v++); + *p++ = ':'; + } + *--p = '\0'; +} + + +/* Generate ip= option and print the ip adress */ +static void ip_init(void) +{ + uint32_t ip = IPInfo.myip; + + genipopt(); + gendotquad(dot_quad_buf, ip); + + ip = ntohl(ip); + printf("My IP address seems to be %08X %s\n", ip, dot_quad_buf); +} + +/* + * Print the IPAPPEND strings, in order + */ +extern const uint16_t IPAppends[]; +extern const char numIPAppends[]; + +static void print_ipappend(void) +{ + size_t i; + + for (i = 0; i < (size_t)numIPAppends; i++) { + const char *p = (const char *)(size_t)IPAppends[i]; + if (*p) + printf("%s\n", p); + } +} + +/* + * Validity check on possible !PXE structure in buf + * return 1 for success, 0 for failure. + * + */ +static int is_pxe(const void *buf) +{ + const struct pxe_t *pxe = buf; + const uint8_t *p = buf; + int i = pxe->structlength; + uint8_t sum = 0; + + if (i < sizeof(struct pxe_t) || + memcmp(pxe->signature, "!PXE", 4)) + return 0; + + while (i--) + sum += *p++; + + return sum == 0; +} + +/* + * Just like is_pxe, it checks PXENV+ structure + * + */ +static int is_pxenv(const void *buf) +{ + const struct pxenv_t *pxenv = buf; + const uint8_t *p = buf; + int i = pxenv->length; + uint8_t sum = 0; + + /* The pxeptr field isn't present in old versions */ + if (i < offsetof(struct pxenv_t, pxeptr) || + memcmp(pxenv->signature, "PXENV+", 6)) + return 0; + + while (i--) + sum += *p++; + + return sum == 0; +} + + + +/* + * memory_scan_for_pxe_struct: + * memory_scan_for_pxenv_struct: + * + * If none of the standard methods find the !PXE/PXENV+ structure, + * look for it by scanning memory. + * + * return the corresponding pxe structure if found, or NULL; + */ +static const void *memory_scan(uintptr_t start, int (*func)(const void *)) +{ + const char *ptr; + + /* Scan each 16 bytes of conventional memory before the VGA region */ + for (ptr = (const char *)start; ptr < (const char *)0xA0000; ptr += 16) { + if (func(ptr)) + return ptr; /* found it! */ + ptr += 16; + } + return NULL; +} + +static const struct pxe_t *memory_scan_for_pxe_struct(void) +{ + extern uint16_t BIOS_fbm; /* Starting segment */ + + return memory_scan(BIOS_fbm << 10, is_pxe); +} + +static const struct pxenv_t *memory_scan_for_pxenv_struct(void) +{ + return memory_scan(0x10000, is_pxenv); +} + +/* + * Find the !PXE structure; we search for the following, in order: + * + * a. !PXE structure as SS:[SP + 4] + * b. PXENV+ structure at [ES:BX] + * c. INT 1Ah AX=0x5650 -> PXENV+ + * d. Search memory for !PXE + * e. Search memory for PXENV+ + * + * If we find a PXENV+ structure, we try to find a !PXE structure from + * if if the API version is 2.1 or later + * + */ +static int pxe_init(bool quiet) +{ + extern void pxe_int1a(void); + char plan = 'A'; + uint16_t seg, off; + uint16_t code_seg, code_len; + uint16_t data_seg, data_len; + const char *base = GET_PTR(InitStack); + com32sys_t regs; + const char *type; + const struct pxenv_t *pxenv; + const struct pxe_t *pxe; + + /* Assume API version 2.1 */ + APIVer = 0x201; + + /* Plan A: !PXE structure as SS:[SP + 4] */ + off = *(const uint16_t *)(base + 48); + seg = *(const uint16_t *)(base + 50); + pxe = MK_PTR(seg, off); + if (is_pxe(pxe)) + goto have_pxe; + + /* Plan B: PXENV+ structure at [ES:BX] */ + plan++; + off = *(const uint16_t *)(base + 24); /* Original BX */ + seg = *(const uint16_t *)(base + 4); /* Original ES */ + pxenv = MK_PTR(seg, off); + if (is_pxenv(pxenv)) + goto have_pxenv; + + /* Plan C: PXENV+ structure via INT 1Ah AX=5650h */ + plan++; + memset(®s, 0, sizeof regs); + regs.eax.w[0] = 0x5650; + call16(pxe_int1a, ®s, ®s); + if (!(regs.eflags.l & EFLAGS_CF) && (regs.eax.w[0] == 0x564e)) { + pxenv = MK_PTR(regs.es, regs.ebx.w[0]); + if (is_pxenv(pxenv)) + goto have_pxenv; + } + + /* Plan D: !PXE memory scan */ + plan++; + if ((pxe = memory_scan_for_pxe_struct())) + goto have_pxe; + + /* Plan E: PXENV+ memory scan */ + plan++; + if ((pxenv = memory_scan_for_pxenv_struct())) + goto have_pxenv; + + /* Found nothing at all !! */ + if (!quiet) + printf("No !PXE or PXENV+ API found; we're dead...\n"); + return -1; + + have_pxenv: + APIVer = pxenv->version; + if (!quiet) + printf("Found PXENV+ structure\nPXE API version is %04x\n", APIVer); + + /* if the API version number is 0x0201 or higher, use the !PXE structure */ + if (APIVer >= 0x201) { + if (pxenv->length >= sizeof(struct pxenv_t)) { + pxe = GET_PTR(pxenv->pxeptr); + if (is_pxe(pxe)) + goto have_pxe; + /* + * Nope, !PXE structure missing despite API 2.1+, or at least + * the pointer is missing. Do a last-ditch attempt to find it + */ + if ((pxe = memory_scan_for_pxe_struct())) + goto have_pxe; + } + APIVer = 0x200; /* PXENV+ only, assume version 2.00 */ + } + + /* Otherwise, no dice, use PXENV+ structure */ + data_len = pxenv->undidatasize; + data_seg = pxenv->undidataseg; + code_len = pxenv->undicodesize; + code_seg = pxenv->undicodeseg; + PXEEntry = pxenv->rmentry; + type = "PXENV+"; + goto have_entrypoint; + + have_pxe: + data_len = pxe->seg[PXE_Seg_UNDIData].size; + data_seg = pxe->seg[PXE_Seg_UNDIData].sel; + code_len = pxe->seg[PXE_Seg_UNDICode].size; + code_seg = pxe->seg[PXE_Seg_UNDICode].sel; + PXEEntry = pxe->entrypointsp; + type = "!PXE"; + + have_entrypoint: + if (!quiet) { + printf("%s entry point found (we hope) at %04X:%04X via plan %c\n", + type, PXEEntry.seg, PXEEntry.offs, plan); + printf("UNDI code segment at %04X len %04X\n", code_seg, code_len); + printf("UNDI data segment at %04X len %04X\n", data_seg, data_len); + } + + code_seg = code_seg + ((code_len + 15) >> 4); + data_seg = data_seg + ((data_len + 15) >> 4); + + real_base_mem = max(code_seg, data_seg) >> 6; /* Convert to kilobytes */ + + return 0; +} + +/* + * See if we have gPXE + */ +static void gpxe_init(void) +{ + int err; + static __lowmem struct s_PXENV_FILE_API_CHECK api_check; + + if (APIVer >= 0x201) { + api_check.Size = sizeof api_check; + api_check.Magic = 0x91d447b2; + err = pxe_call(PXENV_FILE_API_CHECK, &api_check); + if (!err && api_check.Magic == 0xe9c17b20) + gpxe_funcs = api_check.APIMask; + } + + /* Necessary functions for us to use the gPXE file API */ + has_gpxe = (~gpxe_funcs & 0x4b) == 0; +} + +/* + * Initialize UDP stack + * + */ +static void udp_init(void) +{ + int err; + static __lowmem struct s_PXENV_UDP_OPEN udp_open; + udp_open.src_ip = IPInfo.myip; + err = pxe_call(PXENV_UDP_OPEN, &udp_open); + if (err || udp_open.status) { + printf("Failed to initialize UDP stack "); + printf("%d\n", udp_open.status); + kaboom(); + } +} + + +/* + * Network-specific initialization + */ +static void network_init(void) +{ + 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("Getting cached packet "); + 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 + */ + MAC_len = bp->hardlen > 16 ? 0 : bp->hardlen; + MAC_type = bp->hardware; + memcpy(MAC, bp->macaddr, MAC_len); + + /* + * Get the boot file and other info. This lives in the CACHED_REPLY + * packet (query info 3) + */ + pkt_len = pxe_get_cached_info(3); + parse_dhcp(pkt_len); + printf("\n"); + + make_bootif_string(); + make_sysuuid_string(); + ip_init(); + print_ipappend(); + + /* + * 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 + * + */ +static int pxe_fs_init(struct fs_info *fs) +{ + (void)fs; /* drop the compile warning message */ + + /* This block size is actually arbitrary... */ + fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2; + fs->sector_size = fs->block_size = 1 << TFTP_BLOCKSIZE_LG2; + + /* This block size is actually arbitrary... */ + fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2; + fs->sector_size = fs->block_size = 1 << TFTP_BLOCKSIZE_LG2; + + /* Find the PXE stack */ + if (pxe_init(false)) + kaboom(); + + /* See if we also have a gPXE stack */ + gpxe_init(); + + /* Network-specific initialization */ + network_init(); + + /* Initialize network-card-specific idle handling */ + pxe_idle_init(); + + /* Our name for the root */ + strcpy(fs->cwd_name, "::"); + + return 0; +} + +/* + * Look to see if we are on an EFI CSM system. Some EFI + * CSM systems put the BEV stack in low memory, which means + * a return to the PXE stack will crash the system. However, + * INT 18h works reliably, so in that case hack the stack and + * point the "return address" to an INT 18h instruction. + * + * Hack the stack instead of the much simpler "just invoke INT 18h + * if we want to reset", so that chainloading other NBPs will work. + * + * This manipulates the real-mode InitStack directly. It relies on this + * *not* being a currently active stack, i.e. the former + * USE_PXE_PROVIDED_STACK no longer works. + */ +extern far_ptr_t InitStack; + +struct efi_struct { + uint32_t magic; + uint8_t csum; + uint8_t len; +} __attribute__((packed)); +#define EFI_MAGIC (('$' << 24)+('E' << 16)+('F' << 8)+'I') + +static inline bool is_efi(const struct efi_struct *efi) +{ + /* + * We don't verify the checksum, because it seems some CSMs leave + * it at zero, sigh... + */ + return (efi->magic == EFI_MAGIC) && (efi->len >= 83); +} + +static void install_efi_csm_hack(void) +{ + static const uint8_t efi_csm_hack[] = + { + 0xcd, 0x18, /* int $0x18 */ + 0xea, 0xf0, 0xff, 0x00, 0xf0, /* ljmpw $0xf000,$0xfff0 */ + 0xf4 /* hlt */ + }; + uint16_t *retcode; + + retcode = GET_PTR(*(far_ptr_t *)((char *)GET_PTR(InitStack) + 44)); + + /* Don't do this if the return already points to int $0x18 */ + if (*retcode != 0x18cd) { + uint32_t efi_ptr; + bool efi = false; + + for (efi_ptr = 0xe0000 ; efi_ptr < 0x100000 ; efi_ptr += 16) { + if (is_efi((const struct efi_struct *)efi_ptr)) { + efi = true; + break; + } + } + + if (efi) { + uint8_t *src = GET_PTR(InitStack); + uint8_t *dst = src - sizeof efi_csm_hack; + + memmove(dst, src, 52); + memcpy(dst+52, efi_csm_hack, sizeof efi_csm_hack); + InitStack.offs -= sizeof efi_csm_hack; + + /* Clobber the return address */ + *(uint16_t *)(dst+44) = OFFS_WRT(dst+52, InitStack.seg); + *(uint16_t *)(dst+46) = InitStack.seg; + } + } +} + +int reset_pxe(void) +{ + static __lowmem struct s_PXENV_UDP_CLOSE udp_close; + extern void gpxe_unload(void); + int err = 0; + + pxe_idle_cleanup(); + + pxe_call(PXENV_UDP_CLOSE, &udp_close); + + if (gpxe_funcs & 0x80) { + /* gPXE special unload implemented */ + call16(gpxe_unload, &zero_regs, NULL); + + /* Locate the actual vendor stack... */ + err = pxe_init(true); + } + + install_efi_csm_hack(); + return err; +} + +/* + * This function unloads the PXE and UNDI stacks and + * unclaims the memory. + */ +void unload_pxe(void) +{ + /* PXE unload sequences */ + static const uint8_t new_api_unload[] = { + PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_STOP_UNDI, 0 + }; + static const uint8_t old_api_unload[] = { + PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_UNDI_CLEANUP, 0 + }; + + unsigned int api; + const uint8_t *api_ptr; + int err; + size_t int_addr; + static __lowmem union { + struct s_PXENV_UNDI_SHUTDOWN undi_shutdown; + struct s_PXENV_UNLOAD_STACK unload_stack; + struct s_PXENV_STOP_UNDI stop_undi; + struct s_PXENV_UNDI_CLEANUP undi_cleanup; + uint16_t Status; /* All calls have this as the first member */ + } unload_call; + + dprintf("FBM before unload = %d\n", BIOS_fbm); + + err = reset_pxe(); + + dprintf("FBM after reset_pxe = %d, err = %d\n", BIOS_fbm, err); + + /* If we want to keep PXE around, we still need to reset it */ + if (KeepPXE || err) + return; + + dprintf("APIVer = %04x\n", APIVer); + + api_ptr = APIVer >= 0x0200 ? new_api_unload : old_api_unload; + while((api = *api_ptr++)) { + dprintf("PXE call %04x\n", api); + memset(&unload_call, 0, sizeof unload_call); + err = pxe_call(api, &unload_call); + if (err || unload_call.Status != PXENV_STATUS_SUCCESS) { + dprintf("PXE unload API call %04x failed\n", api); + goto cant_free; + } + } + + api = 0xff00; + if (real_base_mem <= BIOS_fbm) { /* Sanity check */ + dprintf("FBM %d < real_base_mem %d\n", BIOS_fbm, real_base_mem); + goto cant_free; + } + api++; + + /* Check that PXE actually unhooked the INT 0x1A chain */ + int_addr = (size_t)GET_PTR(*(far_ptr_t *)(4 * 0x1a)); + int_addr >>= 10; + if (int_addr >= real_base_mem || int_addr < BIOS_fbm) { + BIOS_fbm = real_base_mem; + dprintf("FBM after unload_pxe = %d\n", BIOS_fbm); + return; + } + + dprintf("Can't free FBM, real_base_mem = %d, " + "FBM = %d, INT 1A = %08x (%d)\n", + real_base_mem, BIOS_fbm, + *(uint32_t *)(4 * 0x1a), int_addr); + +cant_free: + printf("Failed to free base memory error %04x-%08x (%d/%dK)\n", + api, *(uint32_t *)(4 * 0x1a), BIOS_fbm, real_base_mem); + return; +} + +const struct fs_ops pxe_fs_ops = { + .fs_name = "pxe", + .fs_flags = FS_NODEV, + .fs_init = pxe_fs_init, + .searchdir = pxe_searchdir, + .chdir = pxe_chdir, + .realpath = pxe_realpath, + .getfssec = pxe_getfssec, + .close_file = pxe_close_file, + .mangle_name = pxe_mangle_name, + .load_config = pxe_load_config, +}; diff --git a/contrib/syslinux-4.02/core/fs/pxe/pxe.h b/contrib/syslinux-4.02/core/fs/pxe/pxe.h new file mode 100644 index 0000000..1e6fa76 --- /dev/null +++ b/contrib/syslinux-4.02/core/fs/pxe/pxe.h @@ -0,0 +1,252 @@ +/* ----------------------------------------------------------------------- + * + * Copyright 1999-2008 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin + * + * 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.h + * + * PXE opcodes + * + */ +#ifndef PXE_H +#define PXE_H + +#include <syslinux/pxe_api.h> +#include "fs.h" /* For MAX_OPEN, should go away */ + +/* + * Some basic defines... + */ +#define TFTP_PORT htons(69) /* Default TFTP port */ +#define TFTP_BLOCKSIZE_LG2 9 +#define TFTP_BLOCKSIZE (1 << TFTP_BLOCKSIZE_LG2) +#define PKTBUF_SIZE 2048 /* */ + +#define is_digit(c) (((c) >= '0') && ((c) <= '9')) + +static inline bool is_hex(char c) +{ + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} + +static inline int hexval(char c) +{ + return (c >= 'A') ? (c & ~0x20) - 'A' + 10 : (c - '0'); +} + +/* + * 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) +#define MAC_MAX 32 + +/* Defines for DNS */ +#define DNS_PORT htons(53) /* Default DNS port */ +#define DNS_MAX_PACKET 512 /* Defined by protocol */ +#define DNS_MAX_SERVERS 4 /* Max no of DNS servers */ + + +/* + * structures + */ + +struct pxenv_t { + uint8_t signature[6]; /* PXENV+ */ + uint16_t version; + uint8_t length; + uint8_t checksum; + segoff16_t rmentry; + uint32_t pmoffset; + uint16_t pmselector; + uint16_t stackseg; + uint16_t stacksize; + uint16_t bc_codeseg; + uint16_t bc_codesize; + uint16_t bc_dataseg; + uint16_t bc_datasize; + uint16_t undidataseg; + uint16_t undidatasize; + uint16_t undicodeseg; + uint16_t undicodesize; + segoff16_t pxeptr; +} __packed; + +struct pxe_t { + uint8_t signature[4]; /* !PXE */ + uint8_t structlength; + uint8_t structcksum; + uint8_t structrev; + uint8_t _pad1; + segoff16_t undiromid; + segoff16_t baseromid; + segoff16_t entrypointsp; + segoff16_t entrypointesp; + segoff16_t statuscallout; + uint8_t _pad2; + uint8_t segdesccnt; + uint16_t firstselector; + pxe_segdesc_t seg[7]; +} __packed; + +enum pxe_segments { + PXE_Seg_Stack = 0, + PXE_Seg_UNDIData = 1, + PXE_Seg_UNDICode = 2, + PXE_Seg_UNDICodeWrite = 3, + PXE_Seg_BC_Data = 4, + PXE_Seg_BC_Code = 5, + PXE_Seg_BC_CodeWrite = 6 +}; + +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)); + +/* + * Our inode private information -- this includes the packet buffer! + */ +struct pxe_pvt_inode { + 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_blksize; /* Block size for this connection(*) */ + uint16_t tftp_bytesleft; /* Unclaimed data bytes */ + uint16_t tftp_lastpkt; /* Sequence number of last packet (NBO) */ + char *tftp_dataptr; /* Pointer to available data */ + uint8_t tftp_goteof; /* 1 if the EOF packet received */ + uint8_t tftp_unused[3]; /* Currently unused */ + char tftp_pktbuf[PKTBUF_SIZE]; +} __attribute__ ((packed)); + +#define PVT(i) ((struct pxe_pvt_inode *)((i)->pvt)) + +/* + * Network boot information + */ +struct ip_info { + uint32_t ipv4; + uint32_t myip; + uint32_t serverip; + uint32_t gateway; + uint32_t netmask; +}; + +/* + * Variable externs + */ +extern struct ip_info IPInfo; + +extern uint8_t MAC[]; +extern char BOOTIFStr[]; +extern uint8_t MAC_len; +extern uint8_t MAC_type; + +extern uint8_t DHCPMagic; +extern uint32_t RebootTime; + +extern char boot_file[]; +extern char path_prefix[]; +extern char LocalDomain[]; + +extern char IPOption[]; +extern char dot_quad_buf[]; + +extern uint32_t dns_server[]; + +extern uint16_t APIVer; +extern far_ptr_t PXEEntry; +extern uint8_t KeepPXE; + +extern far_ptr_t InitStack; + +extern bool have_uuid; +extern uint8_t uuid_type; +extern uint8_t uuid[]; + +extern uint16_t BIOS_fbm; +extern const uint8_t TimeoutTable[]; + +/* + * Compute the suitable gateway for a specific route -- too many + * vendor PXE stacks don't do this correctly... + */ +static inline uint32_t gateway(uint32_t ip) +{ + if ((ip ^ IPInfo.myip) & IPInfo.netmask) + return IPInfo.gateway; + else + return 0; +} + +/* + * functions + */ + +/* pxe.c */ +bool ip_ok(uint32_t); +int pxe_call(int, void *); + +/* dhcp_options.c */ +void parse_dhcp(int); + +/* dnsresolv.c */ +int dns_mangle(char **, const char *); +uint32_t dns_resolv(const char *); + +/* idle.c */ +void pxe_idle_init(void); +void pxe_idle_cleanup(void); + +/* socknum.c */ +uint16_t get_port(void); +void free_port(uint16_t port); + +#endif /* pxe.h */ |