#include	"stddef.h"
#include	"string.h"
#include	"linux-asm-io.h"
#include	"etherboot.h"
#include	"start32.h"
#include	"elf_boot.h"

#ifndef	FIRST32DOS
#define	FIRST32LINUX	1
#endif

#ifdef	FIRST32LINUX
#define SERIAL_CONSOLE 0
/*
 * 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; either version 2, or (at
 * your option) any later version.
 */

/*

Memory layout assumed by mknbi and this program

0x07C00-0x07FFF   0.5 kB	floppy boot sector if loaded from floppy
0x0F???-0x0FFFF     ? kB	large Etherboot data buffers (deprecated)
0x10000-0x8FFFF 512.0 kB	kernel (from tagged image)
0x90000-0x901FF	  0.5 kB	Linux floppy boot sector (from Linux image)
0x90200-0x921FF	  8.0 kB	kernel setup (from Linux image)
0x92200-0x923FF	  0.5 kB	tagged image header ("directory")
0x92400-0x927FF	  1.0 kB	kernel parameters (generated by mknbi)
0x92800-0x93FFF	  6.0 kB	this program (generated by mknbi)
0x94000-0x9FFFF	 48.0 kB	Etherboot (top few kB may be used by BIOS)
				Normally Etherboot starts at 0x94000
0x100000-			kernel (if bzImage) (from tagged image)
after bzImage kernel		ramdisk (optional) (from tagged image)
				moved to below top of memory by this program
				but not higher than 896kB or what the
				limit in setup.S says

*/

#define		PARAMSIZE	512

extern void printf(const char *, ...);
extern int sprintf(char *, const char *, ...);
extern void xstartlinux(unsigned long);
extern void exit(int);

#ifdef	FIRST32ELF
static Elf32_Phdr	*seg[S_END] = { 0 };
#else
static struct segment	*seg[S_END] = { 0 };
#endif
static unsigned char 	*ip, *op;
static short		*vgamode;
static struct bootp_t	*bp;
static unsigned char	*vendortags;
unsigned long		top_of_initrd = 0;
static enum { RD_TOP, RD_ASIS, RD_HEXADDR } rdmode = RD_TOP;
static unsigned long	rdaddr;


#if SERIAL_CONSOLE
/* Base Address */
#define TTYS0 0x3f8
/* Data */
#define TTYS0_RBR (TTYS0+0x00)
#define TTYS0_TBR (TTYS0+0x00)
/* Control */
#define TTYS0_IER (TTYS0+0x01)
#define TTYS0_IIR (TTYS0+0x02)
#define TTYS0_FCR (TTYS0+0x02)
#define TTYS0_LCR (TTYS0+0x03)
#define TTYS0_MCR (TTYS0+0x04)

#define TTYS0_DLL (TTYS0+0x00)
#define TTYS0_DLM (TTYS0+0x01)
/* Status */
#define TTYS0_LSR (TTYS0+0x05)
#define TTYS0_MSR (TTYS0+0x06)
#define TTYS0_SCR (TTYS0+0x07)

static void ttys0_tx_byte(unsigned byte)
{
	while((inb(TTYS0_LSR) & 0x20) == 0)
		;
	outb(byte, TTYS0_TBR);
}

#endif
void putchar(int c)
{
	if (c == '\n')
		putchar('\r');
#if SERIAL_CONSOLE
	ttys0_tx_byte(c);
#endif
	console_putc(c);
}

static inline void quit(void)
{
	printf("Bad argument\n");
	exit(0);
}

static void nomem(void)
{
	printf("Out of parameter space\n");
	exit(0);
}

static inline void checkvendor(void)
{
	union {
		unsigned long	l;
		unsigned char	c[4];
	} u;

	memcpy(u.c, vendortags, sizeof(u));
	if (u.l == RFC_1048 || u.l == VEND_CMU || u.l == VEND_STAN)
		vendortags += 4;
	else
		vendortags = 0;
}

#ifdef	FIRST32ELF
static inline void locate_segs(union infoblock *header)
{
	int		i;
	Elf32_Phdr	*s;

	s = (Elf32_Phdr *)((char *)header + header->ehdr.e_phoff);
	for (i = 0; i < S_END && i < header->ehdr.e_phnum; i++, s++) {
		seg[i] = s;
#if	DEBUG > 1
		printf("%d %#X\n", i, s->p_paddr);
#endif
	}
}

#else

static inline void locate_segs(union infoblock *header)
{
	int		i;
	struct segment	*s;

	s = (struct segment *)((char *)header + sizeof(struct imgheader)
		+ ((header->img.length & 0xF0) >> 2));
	for (i = 0; i < S_END; i++, s++) {
		seg[i] = s;
#if	DEBUG > 1
		printf("%d %#X\n", i, s->p_paddr);
#endif
		if (s->flags & F_FINAL)
			break;
		s = (struct segment *)((char *)s + ((s->lengths & 0xF0) >> 2));
	}
}
#endif	/* !FIRST32ELF */

/*
 *	Find DHCP vendor tag, return pointer to tag length
 */
static unsigned char *gettag(unsigned int tag)
{
	unsigned char		*p;
	unsigned char		c;
	static unsigned char	emptytag[] = { 0, 0 };

	if (vendortags == 0)
		return (emptytag);
	for (p = vendortags; (c = *p) != RFC1533_END; ) {
		if (c == RFC1533_PAD)
			p++;
		else if (c == tag)
			return (p + 1);
		else
			p += p[1] + 2;
	}
	return (emptytag);
}

static void outtag(unsigned char *value)
{
	int			len;

	len = *value++;
	if (op + len > ip)
		nomem();
	while (len-- > 0)
		*op++ = *value++;
}

/* Return 1 if s2 is a prefix of s1 */
static int strprefix(const unsigned char *s1, const unsigned char *s2)
{
	while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
		;
	/* Have we reached the end of s2? */
	return (*s2 == '\0');
}

enum keyword { K_VGA, K_NFSROOT, K_IP, K_RDBASE, K_MEM };

static inline int match_keyword(const unsigned char *start)
{
	if (strprefix(start, "vga"))
		return (K_VGA);
	if (strprefix(start, "nfsroot"))
		return (K_NFSROOT);
	if (strprefix(start, "ip"))
		return (K_IP);
	if (strprefix(start, "rdbase"))
		return (K_RDBASE);
	if (strprefix(start, "mem"))
		return (K_MEM);
	return (-1);
}

#define	isws(c)	((c) == ' ' || (c) == '\t')

static inline int copy_and_match(void)
{
	int			c;
	unsigned char		*start;

	start = ip;
	/* Stop copying at = if it exists */
	while ((c = *ip) != '\0' && !isws(c) && c != '=') {
		*op++ = *ip++;
	}
	if (c == '=') {
		ip++;
		*op++ = '=';
		return (match_keyword(start));
	}
	return (-1);
}

static unsigned long gethex(const unsigned char *p)
{
	unsigned long		value = 0;

	for (;;) {
		int c = *p++;
		if (c >= '0' && c <= '9')
			c -= '0';
		else if (c >= 'a' && c <= 'f')
			c -= 'a' - 10;
		else if (c >= 'A' && c <= 'F')
			c -= 'A' - 10;
		else
			break;
		value <<= 4;
		value |= c;
	}
	return (value);
}

static int getdec(const unsigned char *p)
{
	int			value = 0, sign = 0;

	if (*p == '-') {
		sign = 1;
		p++;
	}
	for (;;) {
		int c = *p++;
		if (c >= '0' && c <= '9')
			c -= '0';
		else
			break;
		value *= 10;
		value += c;
	}
	return (sign ? -value : value);
}

static void copy_nonws(void)
{
	int			c;

	/* Copy up to next whitespace */
	while ((c = *ip) != '\0' && !isws(c))
		*op++ = *ip++;
}

static void discard_arg(void)
{
	int			c;

	/* Discard up to next whitespace */
	while ((c = *ip) != '\0' && !isws(c))
		ip++;
}

static int outip(const unsigned char *p)
{
	long		ip;

	if (*p == 0)
		return (0);
	memcpy(&ip, p + 1, sizeof(ip));
	return sprintf(op, "%@", ip);
}

static inline void subst_value(int kwindex)
{
	int			c;
	unsigned char		*p;

	if (kwindex == K_VGA) {
		/* backup over "vga=" */
		op -= sizeof("vga=") - 1;
		if (strprefix(ip, "ask"))
			c = -3;
		else if (strprefix(ip, "extended"))
			c = -2;
		else if (strprefix(ip, "normal"))
			c = -1;
		else if (strprefix(ip, "0x"))
			c = gethex(ip+2);
		else	/* assume decimal mode number */
			c = getdec(ip);
		*vgamode = c;
		discard_arg();
	} else if (kwindex == K_NFSROOT && strprefix(ip, "rom") &&
		(ip[3] == '\0' || isws(ip[3]))) {
		outtag(gettag(RFC1533_ROOTPATH));
		discard_arg();
	} else if (kwindex == K_IP && strprefix(ip, "rom") &&
		(ip[3] == '\0' || isws(ip[3]))) {
		long		ip;
		op += sprintf(op, "%@:%@:", bp->bp_yiaddr, bp->bp_siaddr);
		p = gettag(RFC1533_GATEWAY);
		op += outip(p);
		*op++ = ':';
		p = gettag(RFC1533_NETMASK);
		op += outip(p);
		*op++ = ':';
		outtag(gettag(RFC1533_HOSTNAME));
		p = gettag(RFC1533_VENDOR_ETHDEV);
		if (*p)
			*op++ = ':';
		outtag(p);
		discard_arg();
	} else if (kwindex == K_RDBASE) {
		if (strprefix(ip, "top"))
			rdmode = RD_TOP;
		else if (strprefix(ip, "asis"))
			rdmode = RD_ASIS;
		else if (strprefix(ip, "0x")) {
			rdmode = RD_HEXADDR;
			rdaddr = gethex(ip+2);
		}
		discard_arg();
	} else if (kwindex == K_MEM) {
		unsigned char 	*p;
		unsigned long	memsize;
		memsize = getdec(p = ip);
		while (*p >= '0' && *p <= '9')
			++p;
		if (*p == 'G')
			memsize <<= 30;
		else if (*p == 'M')
			memsize <<= 20;
		else if (*p == 'K')
			memsize <<= 10;
		top_of_initrd = memsize;
		copy_nonws();
	} else
		copy_nonws();
}

static inline int skipws(void)
{
	int			c;

	while ((c = *ip) != '\0' && isws(c))
		ip++;
	return (c);
}

/*
 *	The parameters are copied from the input area to the output
 *	area, looking out for keyword=value pairs while doing so.
 *	If a possible keyword is found, indicated by an =,
 *	it is matched against a small list.
 *	If it matches none of the keywords on the list,
 *	the value is copied unchanged.
 *	If it matches a keyword, then the appropriate substitutions
 *	are made.
 *	While doing the substitution, a check is made that the output
 *	pointer doesn't overrun the input pointer. This is the only
 *	place it could happen, as the substitution may be longer than
 *	the original.
 */
static inline void process_params(void)
{
	int			i;

	while (skipws() != '\0') {
		if ((i = copy_and_match()) >= 0)
			subst_value(i);
		else
			copy_nonws();
		*op++ = ' ';
	}
	/* There may be a space after the last arg, probably does not matter
           but this is a reminder */
	*op = '\0';
}

/*
 * String is not null terminated, count of chars following is in first element
 * If there are 6 colons, returns char position after 6th colon
 * Else returns one position after string
 * which forces the length calculated below to be negative
 * Length of 7th argument can be calculated by subtracting the
 * length of the string preceding from the total length.
 */
static inline unsigned char *skip6colons(unsigned char *p)
{
	int	len, coloncount;

	for (len = *p++, coloncount = 6; len > 0 && coloncount > 0; p++, len--)
		if (*p == ':')
			coloncount--;
	return (p + (coloncount > 0));
}

static void parse_elf_boot_notes(
	void *notes, union infoblock **rheader, struct bootp_t **rbootp)
{
	unsigned char *note, *end;
	Elf_Bhdr *bhdr;
	Elf_Nhdr *hdr;

	bhdr = notes;
	if (bhdr->b_signature != ELF_BHDR_MAGIC) {
		return;
	}

	note = ((char *)bhdr) + sizeof(*bhdr);
	end  = ((char *)bhdr) + bhdr->b_size;
	while (note < end) {
		unsigned char *n_name, *n_desc, *next;
		hdr = (Elf_Nhdr *)note;
		n_name = note + sizeof(*hdr);
		n_desc = n_name + ((hdr->n_namesz + 3) & ~3);
		next = n_desc + ((hdr->n_descsz + 3) & ~3);
		if (next > end) 
			break;
#if 0
		printf("n_type: %x n_name(%d): n_desc(%d): \n", 
			hdr->n_type, hdr->n_namesz, hdr->n_descsz);
#endif

		if ((hdr->n_namesz == 10) &&
			(memcmp(n_name, "Etherboot", 10) == 0)) {
			switch(hdr->n_type) {
			case EB_BOOTP_DATA:
				*rbootp = *((void **)n_desc);
				break;
			case EB_HEADER:
				*rheader = *((void **)n_desc);
				break;
			default:
				break;
			}
		}
		note = next;
	}
}

int first(struct ebinfo *eb, union infoblock *header, struct bootp_t *bootp)
{
	int			i;
	unsigned char		*p, *q, *params;
	struct bootblock	*boot;
	struct setupblock	*setup;
	union {
		unsigned long	l;
		unsigned char	c[4];
	} u;

#if DEBUG > 1
	printf("&eb = %#X\n", &eb);
#endif
	printf(MKNBI_VERSION "/" __FILE__
#ifdef	FIRST32ELF
		" (ELF)"
#endif
		" (GPL)\n");
#if DEBUG > 1
	printf("eb = %#X, header = %#X, bootp = %#X\n", eb, header, bootp);
#endif
	/* Sanity checks */
#ifdef	FIRST32ELF
	parse_elf_boot_notes(eb, &header, &bootp);
	if (header->img.magic != ELF_MAGIC
#else
	if (header->img.magic != TAG_MAGIC
#endif
		|| bootp->bp_op != BOOTP_REPLY)
		quit();
	bp = bootp;
	vendortags = (unsigned char *)bootp->bp_vend;
	checkvendor();
	locate_segs(header);
	/* Locate boot block */
	boot = (struct bootblock *)seg[S_BOOT]->p_paddr;
	/* Point to word to alter if vga=... specified */
	vgamode = &boot->vgamode;
	/* Locate setup block */
	setup = (struct setupblock *)seg[S_SETUP]->p_paddr;
	/* Adjust loader type byte */
	setup->su_type = SU_MY_LOADER_TYPE;
	/* If setup version >= 0x202, use new command line protocol.
	   This frees setup.S from being tied to 0x90000 */
	if (setup->su_version >= 0x202)
		setup->su_cmd_line_ptr = params = (unsigned char *)seg[S_PARAMS]->p_paddr;
	else {	/* Use old protocol */
		/* Adjust boot block pointers to point to command line */
		boot->cl_magic = CL_MAGIC;
		boot->cl_offset = (params = (unsigned char *)seg[S_PARAMS]->p_paddr) - ((unsigned char *)boot);
	}
	p = params + (i = strlen(params) + 1);
	/* Append T129 if present */
	q = gettag(RFC1533_VENDOR_MAGIC);
	/* Check T128 present and correct */
	if (*q == 6 && (memcpy(u.c, q + 1, sizeof(u)), u.l == VEND_EB)) {
		q = gettag(RFC1533_VENDOR_ADDPARM);
		i = PARAMSIZE - 1 - i;		/*  +1 for SPACE */
		if (i > *q)			/* enough space? */
			i = *q;
		q++;
		if (i > 0)
			p[-1] = ' ';		/* NUL -> space */
		while (i-- > 0)
			*p++ = *q++;
		*p++ = '\0';			/* position past NUL */
		if (*(q = gettag(RFC1533_VENDOR_SELECTION)) == 1
		  && *(q = gettag(q[1])) > 0) {
			unsigned char	*r = skip6colons(q);
			/* If we have an argument and enough space, copy it */
			if ((i = *q - (r - q) + 1) > 0
			  && i < PARAMSIZE - 2 - strlen(params)) {
			/* +2 for SPACE and final NUL */
				p[-1] = ' ';
				while (i-- > 0) {
					/* escapes: ~b -> \\, ~c -> : */
					if (i > 0 && r[0] == '~' && r[1] == 'b')
						*p++ = '\\', r += 2, --i;
					else if (i > 0 && r[0] == '~' && r[1] == 'c')
						*p++ = ':', r += 2, --i;
					else
						*p++ = *r++;
				}
				*p++ = '\0';
			}
		}
	}
	/* Move parameters to end of parameter area,
	   tail first to avoid overwriting */
	q = params + PARAMSIZE;
	/* At least 1 byte is copied, the NUL */
	do {
		*--q = *--p;
	} while (p != params);
	ip = q;
	op = params;
#ifdef	DEBUG
	printf("Parameters: %s\n", p);
#endif
	/* mem= param affects top_of_initrd */
	process_params();
	if (seg[S_RAMDISK] != 0) {
		unsigned long		max;

		get_memsizes();
		max = (setup->su_version >= 0x203) ? setup->ramdisk_max : 0x37FFFFFF;
		/* compute top of initrd only if user has not overridden it */
		if (top_of_initrd == 0) {
			struct e820entry	*e;
			/* look for highest E820_RAM that is under ramdisk_max
			   strictly speaking we should also check that
			   we have room for the ramdisk in the memory segment */
			for (i = 0; i < meminfo.map_count; i++) {
				e = &meminfo.map[i];
				if (e->type == E820_RAM
					&& e->addr < max
					&& (e->addr + e->size) > top_of_initrd)
					top_of_initrd = e->addr + e->size;
			}
			/*2004/05/08: memdisk needs 64k between initrd and the computed top */
			top_of_initrd -= 0x10000;
		}
		if (top_of_initrd > max)
			top_of_initrd = max;
		/* Round down to next lower 4k boundary */
		top_of_initrd &= ~0xFFF;
		printf("Top of ramdisk is %#X\n", top_of_initrd);
		if (rdmode == RD_TOP || rdmode == RD_HEXADDR) {
			long			*dp, *sp;

                       /*2004/05/08: now we have a true size rd, hence
                               need to align tail pointer to 4k boundary up */
			sp = (long *)((p = (unsigned char *)seg[S_RAMDISK]->p_paddr) +
				(i = ((seg[S_RAMDISK]->p_filesz + 0xfff) & ~0xfff)));
			/*
			 * If user specified address, align dest tail pointer to 4k boundary below
			 */
			if (rdmode == RD_HEXADDR)
				dp = (long *)((rdaddr & ~0xfff) + i);
			else
				dp = (long *)top_of_initrd;
			/* Copy to destination by longwords, tail first;
				seg[] might get overwritten, save rd size first */
			i = seg[S_RAMDISK]->p_filesz;
			while (sp > (long *)p)
				*--dp = *--sp;
			printf("Ramdisk at %#X, size %#X\n",
				(setup->su_ramdisk_start = (unsigned long)dp),
				(setup->su_ramdisk_size = i));
		} else {	/* leave ramdisk as loaded, just report */
			printf("Ramdisk at %#X, size %#X\n",
				(setup->su_ramdisk_start = (unsigned long)seg[S_RAMDISK]->p_paddr),
				(setup->su_ramdisk_size = seg[S_RAMDISK]->p_filesz));
		}
	}
#ifdef	DEBUG
	printf("Ready\n");
#endif
#if DEBUG > 3
	/* Delay so we can read display */
	for (i = 0; i < 0x7ffffff; i++)
		;
#endif
	xstartlinux((unsigned long)setup);
	return (0);
}
#endif	/* FIRST32LINUX */

#ifdef	FIRST32DOS
extern void printf(const char *, ...);
extern void xstart(unsigned long, union infoblock *, struct bootp_t *);
extern void exit(int);

struct bootp_t	bpcopy;

void putchar(int c)
{
	if (c == '\n')
		putchar('\r');
	console_putc(c);
}

static inline void quit(void)
{
	printf("Bad argument\n");
	exit(0);
}

static void parse_elf_boot_notes(
	void *notes, union infoblock **rheader, struct bootp_t **rbootp)
{
	unsigned char *note, *end;
	Elf_Bhdr *bhdr;
	Elf_Nhdr *hdr;

	bhdr = notes;
	if (bhdr->b_signature != ELF_BHDR_MAGIC) {
		return;
	}

	note = ((char *)bhdr) + sizeof(*bhdr);
	end  = ((char *)bhdr) + bhdr->b_size;
	while (note < end) {
		unsigned char *n_name, *n_desc, *next;
		hdr = (Elf_Nhdr *)note;
		n_name = note + sizeof(*hdr);
		n_desc = n_name + ((hdr->n_namesz + 3) & ~3);
		next = n_desc + ((hdr->n_descsz + 3) & ~3);
		if (next > end) 
			break;
#if 0
		printf("n_type: %x n_name(%d): n_desc(%d): \n", 
			hdr->n_type, hdr->n_namesz, hdr->n_descsz);
#endif

		if ((hdr->n_namesz == 10) &&
			(memcmp(n_name, "Etherboot", 10) == 0)) {
			switch(hdr->n_type) {
			case EB_BOOTP_DATA:
				*rbootp = *((void **)n_desc);
				break;
			case EB_HEADER:
				*rheader = *((void **)n_desc);
				break;
			default:
				break;
			}
		}
		note = next;
	}
}

int first(struct ebinfo *eb, union infoblock *header, struct bootp_t *bootp)
{
#if DEBUG > 1
	printf("&eb = %#X\n", &eb);
#endif
	printf(MKNBI_VERSION "/" __FILE__ " (ELF)" " (GPL)\n");
#if DEBUG > 1
	printf("eb = %#X, header = %#X, bootp = %#X\n", eb, header, bootp);
#endif
	/* Sanity checks */
	parse_elf_boot_notes(eb, &header, &bootp);
	if (header->img.magic != ELF_MAGIC || bootp->bp_op != BOOTP_REPLY)
		quit();
	memcpy(&bpcopy, bootp, sizeof(bpcopy));
	xstart(RELOC - 0x1000, header, &bpcopy);
	return (0);
}
#endif	/* FIRST32DOS */


syntax highlighted by Code2HTML, v. 0.9.1