/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2008 rPath, Inc. - 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., 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.
 *
 * ----------------------------------------------------------------------- */

/*
 * nbi.c
 *
 * Take a linked list of segments and output it as an NBI image
 */

#include "wraplinux.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "nbi.h"
#include "segment.h"
#include "le.h"

static inline int is_real_seg(struct segment *s)
{
	return (s->sh_type == SHT_PROGBITS) &&
		(s->sh_flags & SHF_ALLOC);
}

int output_nbi(struct segment *segs, addr_t entry, FILE *out)
{
	struct nbi_header nhdr;
	struct nbi_image_header ihdr;
	uint32_t offset;
	struct segment *s;
	addr_t base;
	addr_t address, length;

	segs = sort_segments(segs);

	wrle32(NBI_MAGIC, &nhdr.magic);
	wrle32(NBI_HFLAG_PROTMODE + (sizeof nhdr >> 2), &nhdr.flags);
	wrle32(entry, &nhdr.entry);

	/* NBI wants a 512-byte area to load its header into low memory.
	   The spec says it should be between 0x10000 and 0x94000.
	   Try our best to find such an area; if we totally fail, then
	   fall back to the classical boot sector address and hope the
	   loader can cope. */
	base = 0x10000;
	for (s = segs; s; s = s->next) {
		if (is_real_seg(s)) {
			if (s->address >= base+512)
				break;	/* Found a safe area */

			base = (s->address + s->length + 511) & ~511;
		}
	}

	if (s && base <= 0x93800) {
		wrle16(0, &nhdr.header_off);
		wrle16(base >> 4, &nhdr.header_seg);
	} else {
		/* Last resort: classical boot sector location */
		wrle16(0x7c00, &nhdr.header_off);
		wrle16(0, &nhdr.header_seg);
	}

	offset = c_fwrite(&nhdr, sizeof nhdr, out);

	for (s = segs; s; s = s->next) {
		if (is_real_seg(s)) {
			ihdr.lengths = sizeof ihdr >> 2;
			ihdr.tags = 0;
			ihdr.resv = 0;
			/* The semantics of NBI memsz > filesz is
			   unclear.  It might be desirable to actually
			   allow generation of NOBITS segments. */

			address = s->address;
			length  = s->length;

			while (s->next && is_real_seg(s->next) &&
			       s->next->align <= s->align &&
			       s->next->address ==
			       align_up(address+length, s->next->align)) {
				/* Merge sections */
				s = s->next;
				length += padsize(address+length, s->align);
				length += s->length;
			}

			ihdr.load_addr = address;
			ihdr.filesz    = length;
			ihdr.memsz     = length;
			ihdr.flags     = s->next ? 0 : NBI_IFLAG_LAST;

			offset += c_fwrite(&ihdr, sizeof ihdr, out);
		}
	}

	if (offset <= NBI_HEADER_SIZE)
		offset += c_writezero(NBI_HEADER_SIZE-offset, out);

	if (offset == NBI_HEADER_SIZE)
		offset += c_fwrite("\x55", 1, out);
	if (offset == NBI_HEADER_SIZE+1)
		offset += c_fwrite("\xaa", 1, out);

	for (s = segs; s; s = s->next) {
		if (is_real_seg(s)) {
			address = s->address;
			address += c_fwrite(s->data, s->length, out);

			while (s->next && is_real_seg(s->next) &&
			       s->next->align <= s->align &&
			       s->next->address ==
			       align_up(address, s->next->align)) {
				/* Merge sections */
				s = s->next;
				address +=
					c_writezero(padsize(address, s->align),
						    out);
				address += c_fwrite(s->data, s->length, out);
			}
		}
	}

	return 0;
}
