/* a.out */
struct exec {
	unsigned long      a_midmag;	/* flags<<26 | mid<<16 | magic */
	unsigned long      a_text;	/* text segment size */
	unsigned long      a_data;	/* initialized data size */
	unsigned long      a_bss;	/* uninitialized data size */
	unsigned long      a_syms;	/* symbol table size */
	unsigned long      a_entry;	/* entry point */
	unsigned long      a_trsize;	/* text relocation size */
	unsigned long      a_drsize;	/* data relocation size */
};

struct aout_state {
	struct exec head;
	unsigned long curaddr;
	int segment;			/* current segment number, -1 for none */
	unsigned long loc;		/* start offset of current block */
	unsigned long skip;		/* padding to be skipped to current segment */
	unsigned long toread;		/* remaining data to be read in the segment */
};

static struct aout_state astate;

static sector_t aout_download(unsigned char *data, unsigned int len, int eof);
static inline os_download_t aout_probe(unsigned char *data, unsigned int len)
{
	unsigned long start, mid, end, istart, iend;
	if (len < sizeof(astate.head)) {
		return 0;
	}
	memcpy(&astate.head, data, sizeof(astate.head));
	if ((astate.head.a_midmag & 0xffff) != 0x010BL) {
		return 0;
	}
	
	printf("(a.out");
	aout_freebsd_probe();
	printf(")... ");
	/* Check the aout image */
	start  = astate.head.a_entry;
	mid    = (((start + astate.head.a_text) + 4095) & ~4095) + astate.head.a_data;
	end    = ((mid + 4095) & ~4095) + astate.head.a_bss;
	istart = 4096;
	iend   = istart + (mid - start);
	if (!prep_segment(start, mid, end, istart, iend))
		return dead_download;
	astate.segment = -1;
	astate.loc = 0;
	astate.skip = 0;
	astate.toread = 0;
	return aout_download;
}

static sector_t aout_download(unsigned char *data, unsigned int len, int eof)
{
	unsigned int offset;	/* working offset in the current data block */

	offset = 0;

#ifdef AOUT_LYNX_KDI
	astate.segment++;
	if (astate.segment == 0) {
		astate.curaddr = 0x100000;
		astate.head.a_entry = astate.curaddr + 0x20;
	}
	memcpy(phys_to_virt(astate.curaddr), data, len);
	astate.curaddr += len;
	return 0;
#endif

	do {
		if (astate.segment != -1) {
			if (astate.skip) {
				if (astate.skip >= len - offset) {
					astate.skip -= len - offset;
					break;
				}
				offset += astate.skip;
				astate.skip = 0;
			}

			if (astate.toread) {
				if (astate.toread >= len - offset) {
					memcpy(phys_to_virt(astate.curaddr), data+offset,
						len - offset);
					astate.curaddr += len - offset;
					astate.toread -= len - offset;
					break;
				}
				memcpy(phys_to_virt(astate.curaddr), data+offset, astate.toread);
				offset += astate.toread;
				astate.toread = 0;
			}
		}

		/* Data left, but current segment finished - look for the next
		 * segment.  This is quite simple for a.out files.  */
		astate.segment++;
		switch (astate.segment) {
		case 0:
			/* read text */
			astate.curaddr = astate.head.a_entry;
			astate.skip = 4096;
			astate.toread = astate.head.a_text;
			break;
		case 1:
			/* read data */
			/* skip and curaddr may be wrong, but I couldn't find
			 * examples where this failed.  There is no reasonable
			 * documentation for a.out available.  */
			astate.skip = ((astate.curaddr + 4095) & ~4095) - astate.curaddr;
			astate.curaddr = (astate.curaddr + 4095) & ~4095;
			astate.toread = astate.head.a_data;
			break;
		case 2:
			/* initialize bss and start kernel */
			astate.curaddr = (astate.curaddr + 4095) & ~4095;
			astate.skip = 0;
			astate.toread = 0;
			memset(phys_to_virt(astate.curaddr), '\0', astate.head.a_bss);
			goto aout_startkernel;
		default:
			break;
		}
	} while (offset < len);

	astate.loc += len;

	if (eof) {
		unsigned long entry;

aout_startkernel:
		entry = astate.head.a_entry;
		done(1);

		aout_freebsd_boot();
#ifdef AOUT_LYNX_KDI
		xstart32(entry);
#endif
		printf("unexpected a.out variant\n");
		longjmp(restart_etherboot, -2);
	}
	return 0;
}
