/*	$NetBSD: mkbootimage.c,v 1.11 2004/06/23 21:39:32 kleink Exp $	*/

/*-
 * Copyright (C) 1999, 2000 NONAKA Kimihiro (nonaka@NetBSD.org)
 * Copyright (C) 1996, 1997, 1998, 1999 Cort Dougan (cort@fsmlasb.com)
 * Copyright (C) 1996, 1997, 1998, 1999 Paul Mackeras (paulus@linuxcare.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Gary Thomas.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#include "../../sys/sys/bootblock.h"
#else
#include <sys/bootblock.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>

/* BFD ELF headers */
#include <elf/common.h>
#include <elf/external.h>

#include "byteorder.h"
#include "magic.h"

#ifndef	MBR_PTYPE_PREP
#define	MBR_PTYPE_PREP		0x41
#endif

#ifndef	MBR_PFLAG_ACTIVE
#define	MBR_PFLAG_ACTIVE	0x80
#endif

/*
 * Macros to get values from multi-byte ELF header fields.  These assume
 * a big-endian image.
 */
#define	ELFGET16(x)	(((x)[0] << 8) | (x)[1])

#define	ELFGET32(x)	(((x)[0] << 24) | ((x)[1] << 16) |		\
			 ((x)[2] <<  8) |  (x)[3])

int
main(argc, argv)
	int argc;
	char **argv;
{
	int i;
	int elf_fd, prep_fd, kern_fd;
	int elf_img_len = 0, kern_len = 0;
	struct stat elf_stat, kern_stat;
	unsigned char *elf_img = NULL, *kern_img = NULL;
	unsigned char mbr[512];
	unsigned long entry;
	unsigned long length;
	Elf32_External_Ehdr hdr;
	Elf32_External_Phdr phdr;
	struct mbr_partition *mbrp = (struct mbr_partition *)&mbr[MBR_PART_OFFSET];

	switch (argc) { 
	case 4:
		if ((kern_fd = open(argv[3], 0)) < 0) {
			fprintf(stderr, "Can't open kernel '%s': %s\n",
				argv[3], strerror(errno));
			exit(2);
		}
		fstat(kern_fd, &kern_stat);
		kern_len = kern_stat.st_size + MAGICSIZE + KERNLENSIZE;
		/* FALL THROUGH */
	case 3:
		if ((elf_fd = open(argv[1], 0)) < 0) {
			fprintf(stderr, "Can't open input '%s': %s\n",
				argv[1], strerror(errno));
			exit(2);
		}
		if ((prep_fd = creat(argv[2], 0644)) < 0) {
			fprintf(stderr, "Can't open output '%s': %s\n",
				argv[2], strerror(errno));
			exit(2);
		}

		break;

	default:
		fprintf(stderr, "usage: %s "
		    "<boot-prog> <boot-image> [<gzip'd kernel>]\n", argv[0]);
		exit(1);
		break;
	}

	/*
	 * ELF file operation
	 */
	if (read(elf_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
		fprintf(stderr, "Can't read input '%s': %s\n",
			argv[1], strerror(errno));
		exit(3);
	}
	if (hdr.e_ident[EI_MAG0] != ELFMAG0 ||
	    hdr.e_ident[EI_MAG1] != ELFMAG1 ||
	    hdr.e_ident[EI_MAG2] != ELFMAG2 ||
	    hdr.e_ident[EI_MAG3] != ELFMAG3 ||
	    hdr.e_ident[EI_CLASS] != ELFCLASS32) {
		fprintf(stderr, "input '%s' is not ELF32 format\n", argv[1]);
		exit(3);
	}
	if (hdr.e_ident[EI_DATA] != ELFDATA2MSB) {
		fprintf(stderr, "input '%s' is not big-endian\n", argv[1]);
		exit(3);
	}
	if (ELFGET16(hdr.e_machine) != EM_PPC) {
		fprintf(stderr, "input '%s' is not PowerPC exec binary\n",
			argv[1]);
		exit(3);
	}

	for (i = 0; i < ELFGET16(hdr.e_phnum); i++) {
		lseek(elf_fd, ELFGET32(hdr.e_phoff) + sizeof(phdr) * i,
			SEEK_SET);
		if (read(elf_fd, &phdr, sizeof(phdr)) != sizeof(phdr)) {
			fprintf(stderr, "Can't read input '%s' phdr : %s\n",
				argv[1], strerror(errno));
			exit(3);
                }

		if ((ELFGET32(phdr.p_type) != PT_LOAD) ||
		    !(ELFGET32(phdr.p_flags) & PF_X))
			continue;

		fstat(elf_fd, &elf_stat);
		elf_img_len = elf_stat.st_size - ELFGET32(phdr.p_offset);
		lseek(elf_fd, ELFGET32(phdr.p_offset), SEEK_SET);

		break;
	}

	memset(mbr, 0, sizeof(mbr));
 
	/* Set entry point and boot image size skipping over elf header */
	entry  = sa_htole32(0x400);
	length = sa_htole32(elf_stat.st_size - sizeof(hdr) + 0x400);

	/*
	 * Set magic number for msdos partition
	 */
	*(unsigned short *)&mbr[MBR_MAGIC_OFFSET] = sa_htole16(MBR_MAGIC);
  
	/*
	 * Build a "PReP" partition table entry in the boot record
	 *  - "PReP" may only look at the system_indicator
	 */
	mbrp->mbrp_flag = MBR_PFLAG_ACTIVE;
	mbrp->mbrp_type  = MBR_PTYPE_PREP;

	/*
	 * The first block of the diskette is used by this "boot record" which
	 * actually contains the partition table. (The first block of the
	 * partition contains the boot image, but I digress...)  We'll set up
	 * one partition on the diskette and it shall contain the rest of the
	 * diskette.
	 */
	mbrp->mbrp_shd   = 0;	/* zero-based			     */
	mbrp->mbrp_ssect = 2;	/* one-based			     */
	mbrp->mbrp_scyl  = 0;	/* zero-based			     */
	mbrp->mbrp_ehd   = 1;	/* assumes two heads		     */
	mbrp->mbrp_esect = 18;	/* assumes 18 sectors/track	     */
	mbrp->mbrp_ecyl  = 79;	/* assumes 80 cylinders/diskette     */

	/*
	 * The "PReP" software ignores the above fields and just looks at
	 * the next two.
	 *   - size of the diskette is (assumed to be)
	 *     (2 tracks/cylinder)(18 sectors/tracks)(80 cylinders/diskette)
	 *   - unlike the above sector numbers,
	 *     the beginning sector is zero-based!
	 */

	/* This has to be 0 on the PowerStack? */   
	mbrp->mbrp_start = sa_htole32(0);
	mbrp->mbrp_size  = sa_htole32(2 * 18 * 80 - 1);

	write(prep_fd, mbr, sizeof(mbr));
	write(prep_fd, &entry, sizeof(entry));
	write(prep_fd, &length, sizeof(length));  

	/* Set file position to 2nd sector where image will be written */
	lseek(prep_fd, 0x400, SEEK_SET);

	/* Copy boot image */
	elf_img = (unsigned char *)malloc(elf_img_len);
	if (!elf_img) {
		fprintf(stderr, "Can't malloc: %s\n", strerror(errno));
		exit(3);
	}
	if (read(elf_fd, elf_img, elf_img_len) != elf_img_len) {
	    	fprintf(stderr, "Can't read file '%s' : %s\n",
			argv[1], strerror(errno));
		free(elf_img);
		exit(3);
	}
	write(prep_fd, elf_img, elf_img_len);
	free(elf_img);

	/* Copy kernl */
	if (kern_len) {
		int tmp;
		kern_img = (unsigned char *)malloc(kern_stat.st_size);
		if (kern_img == NULL) {
			fprintf(stderr, "Can't malloc: %s\n", strerror(errno));
			exit(3);
		}
		if (read(kern_fd, (void *)kern_img, kern_stat.st_size) !=
		    kern_stat.st_size) {
			fprintf(stderr, "Can't read kernel '%s' : %s\n",
				argv[3], strerror(errno));
			exit(3);
		}
		write(prep_fd, (void *)magic, MAGICSIZE);
		tmp = sa_htobe32(kern_stat.st_size);
		write(prep_fd, (void *)&tmp, KERNLENSIZE);
		write(prep_fd, (void *)kern_img, kern_stat.st_size);

		length = sa_htole32(0x400 + elf_img_len + 8 +
		    kern_stat.st_size);
		lseek(prep_fd, sizeof(mbr) + 4, SEEK_SET);
		write(prep_fd, &length, sizeof(length));  

		free(kern_img);
		close(kern_fd);
	}

	close(prep_fd);
	close(elf_fd);

	return 0;
}
