/*
 * s390-tools/zipl/src/boot.c
 *   Functions to handle the boot loader data.
 *
 * Copyright (C) 2001-2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Author(s): Carsten Otte <cotte@de.ibm.com>
 *            Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
 */

#include "boot.h"

#include <stdint.h>
#include <string.h>

#include "bootmap.h"
#include "error.h"
#include "misc.h"

/* Include boot data */
#include "../boot/data.c"


/* Check sizes of internal objects. Return 0 if everything is correct,
 * non-zero otherwise. */
int
boot_check_data(void)
{
	if (sizeof(boot_data_fba0) !=
	    sizeof(struct boot_fba_stage0)) {
		error_reason("Size mismatch of stage 0 loader for disks with "
			     "FBA layout");
		return -1;
	}
	if (sizeof(boot_data_eckd0) !=
	    sizeof(struct boot_eckd_stage0)) {
		error_reason("Size mismatch of stage 0 loader for disks with "
			     "ECKD layout");
		return -1;
	}
	if (sizeof(boot_data_eckd1a) !=
	    sizeof(struct boot_eckd_classic_stage1)) {
		error_reason("Size mismatch of stage 1 loader for disks with "
			     "ECKD classic layout");
		return -1;
	}
	if (sizeof(boot_data_eckd1b) !=
	    sizeof(struct boot_eckd_compatible_stage1)) {
		error_reason("Size mismatch of stage 1 loader for disks with "
			     "ECKD compatible layout");
		return -1;
	}
	return 0;
}


/* Stage 3 bootloader parameter structure */
struct stage3_params {
	uint64_t parm_addr;
	uint64_t initrd_addr;
	uint64_t initrd_len;
	uint64_t load_psw;
};

/* Create a stage 3 loader in memory.
 * Upon success, return 0 and set BUFFER to point to the data buffer and set
 * BYTECOUNT to contain the loader size in bytes. Return non-zero otherwise. */
int
boot_get_stage3(void** buffer, size_t* bytecount, address_t parm_addr,
		address_t initrd_addr, size_t initrd_len, address_t image_addr)
{
	struct stage3_params params;
	void* data;

	if (image_addr != (image_addr & PSW_ADDRESS_MASK)) {
		error_reason("Kernel image load address to high (31 bit "
			     "addressing mode)");
		return -1;
	}
	/* Get memory */
	data = misc_malloc(sizeof(boot_data_stage3));
	if (data == NULL)
		return -1;
	/* Prepare params section */
	params.parm_addr = (uint64_t) parm_addr;
	params.initrd_addr = (uint64_t) initrd_addr;
	params.initrd_len = (uint64_t) initrd_len;
	params.load_psw = (uint64_t) (image_addr | PSW_LOAD);
	/* Initialize buffer */
	memcpy(data, boot_data_stage3, sizeof(boot_data_stage3));
	memcpy(data, &params, sizeof(struct stage3_params));
	*buffer = data;
	*bytecount = sizeof(boot_data_stage3);
	return 0;
}


#define FBA_FLAG_CC 0x0000000040000000LL

int
boot_init_fba_stage0(struct boot_fba_stage0* stage0,
		     disk_blockptr_t* program_table,
		     disk_blockptr_t* stage2_list, blocknum_t stage2_count,
		     struct disk_info* info)
{
	blocknum_t i;

	/* Initialize stage 0 data */
	memcpy(stage0, boot_data_fba0, sizeof(struct boot_fba_stage0));
	/* Fill in blocklist for stage 2 loader */
	if (stage2_count > 8) {
		error_reason("Not enough room for FBA stage 2 loader "
			     "(try larger block size)");
		return -1;
	}
	for (i=0; i < stage2_count; i++) {
		stage0->locdata[i].blocknr =
			(uint32_t) stage2_list[i].linear.block;
	}
	/* Terminate CCW chain */
	stage0->locread[i - 1].read &= ~FBA_FLAG_CC;
	/* Store program table pointer in stage 0 loader */
	bootmap_store_blockptr(&stage0->param1, program_table, info);
	return 0;
}


void
boot_init_eckd_classic_stage0(struct boot_eckd_stage0* stage0)
{
	memcpy(stage0, boot_data_eckd0, sizeof(struct boot_eckd_stage0));
	/* Fill in size of stage 1 loader */
	stage0->read.count = sizeof(struct boot_eckd_classic_stage1);
}


void
boot_init_eckd_compatible_stage0(struct boot_eckd_stage0* stage0)
{
	memcpy(stage0, boot_data_eckd0, sizeof(struct boot_eckd_stage0));
	/* Fill in size of stage 1 loader */
	stage0->read.count = sizeof(struct boot_eckd_compatible_stage1);
}


#define ECKD_CCW_FLAG_CC	0x40
#define ECKD_CCW_FLAG_SLI	0x20

int
boot_init_eckd_classic_stage1(struct boot_eckd_classic_stage1* stage1,
		     disk_blockptr_t* stage2_list, blocknum_t stage2_count,
		     struct disk_info* info)
{
	blocknum_t i;

	memcpy(stage1, boot_data_eckd1a,
	       sizeof(struct boot_eckd_classic_stage1));
	/* Fill in blocklist for stage 2 loader */
	if (stage2_count > 8) {
		error_reason("Not enough room for ECKD stage 2 loader "
			     "(try larger block size)");
		return -1;
	}
	for (i=0; i < stage2_count; i++) {
		stage1->ssrt[i].read.count 	= stage2_list[i].chs.size;
		stage1->seek[i].cyl		= stage2_list[i].chs.cyl;
		stage1->seek[i].head		= stage2_list[i].chs.head;
		stage1->seek[i].sec		= stage2_list[i].chs.sec;
		stage1->ssrt[i].read.address_lo	= ZIPL_STAGE2_LOAD_ADDRESS +
						  i * stage2_list[i].chs.size;
		stage1->ssrt[i].read.flags	= ECKD_CCW_FLAG_CC |
						  ECKD_CCW_FLAG_SLI;
	}
	/* Terminate CCW chain */
	stage1->ssrt[i - 1].read.flags &= ~ECKD_CCW_FLAG_CC;
	return 0;
}


int
boot_init_eckd_classic_dump_stage1(struct boot_eckd_classic_stage1* stage1,
				   struct disk_info* info)
{
	blocknum_t blocks;
	blocknum_t i;

	memcpy(stage1, boot_data_eckd1a,
	       sizeof(struct boot_eckd_classic_stage1));
	blocks = (sizeof(boot_data_eckd2dump) +
			info->phy_block_size - 1) / info->phy_block_size;
	if (blocks > 8) {
		error_reason("Not enough room for ECKD stage 2 dump record "
			     "(try larger block size)");
		return -1;
	}
	/* Fill in block list of stage 2 dump record */
	for (i=0; i < blocks; i++) {
		stage1->ssrt[i].read.count = info->phy_block_size;
		stage1->ssrt[i].read.address_lo = ZIPL_STAGE2_LOAD_ADDRESS +
						  i * info->phy_block_size;
		stage1->seek[i].cyl =
			disk_cyl_from_blocknum(i + info->geo.start, info);
		stage1->seek[i].head =
			disk_head_from_blocknum(i + info->geo.start, info);
		stage1->seek[i].sec =
			disk_sec_from_blocknum(i + info->geo.start, info);
	}
	/* Terminate CCW chain */
	stage1->ssrt[i - 1].read.flags &= ~ECKD_CCW_FLAG_CC;
	return 0;
}


int
boot_init_eckd_compatible_stage1(struct boot_eckd_compatible_stage1* stage1,
				 struct disk_info* info)
{
	blocknum_t blocks;
	blocknum_t i;

	memcpy(stage1, boot_data_eckd1b,
	       sizeof(struct boot_eckd_compatible_stage1));
	/* Fill in blocklist for stage 2 loader */
	blocks = (sizeof(boot_data_eckd2) + info->phy_block_size - 1) /
		 info->phy_block_size;
	if (blocks > 12) {
		error_reason("Not enough room for ECKD stage 2 loader "
			     "(try larger block size)");
		return -1;
	}
	for (i=0; i < blocks; i++) {
		stage1->read[i].count		= info->phy_block_size;
		stage1->read[i].address_lo	= ZIPL_STAGE2_LOAD_ADDRESS +
						  i * info->phy_block_size;
	}
	/* Terminate CCW chain */
	stage1->read[i - 1].flags &= ~ECKD_CCW_FLAG_CC;
	return 0;
}


int
boot_init_eckd_compatible_dump_stage1(
				struct boot_eckd_compatible_stage1* stage1,
				struct disk_info* info)
{
	blocknum_t blocks;
	blocknum_t i;

	memcpy(stage1, boot_data_eckd1b,
	       sizeof(struct boot_eckd_compatible_stage1));
	blocks = (sizeof(boot_data_eckd2dump) +
			info->phy_block_size - 1) / info->phy_block_size;
	if (blocks > 12) {
		error_reason("Not enough room for ECKD stage 2 dump record "
			     "(try larger block size)");
		return -1;
	}
	/* Fill in block list of stage 2 dump record */
	for (i = 0; i < blocks; i++) {
		stage1->read[i].count = info->phy_block_size;
		stage1->read[i].address_lo = ZIPL_STAGE2_LOAD_ADDRESS +
					     i * info->phy_block_size;
	}
	/* Terminate CCW chain */
	stage1->read[i - 1].flags &= ~ECKD_CCW_FLAG_CC;
	return 0;
}


int
boot_get_fba_stage2(void** data, size_t* size)
{
	void* buffer;

	buffer = misc_malloc(sizeof(boot_data_fba2));
	if (buffer == NULL)
		return -1;
	memcpy(buffer, boot_data_fba2, sizeof(boot_data_fba2));
	*data = buffer;
	*size = sizeof(boot_data_fba2);
	return 0;
}


int
boot_get_eckd_stage2(void** data, size_t* size)
{
	void* buffer;

	buffer = misc_malloc(sizeof(boot_data_eckd2));
	if (buffer == NULL)
		return -1;
	memcpy(buffer, boot_data_eckd2, sizeof(boot_data_eckd2));
	*data = buffer;
	*size = sizeof(boot_data_eckd2);
	return 0;
}


int
boot_get_tape_dump(void** data, size_t* size)
{
	void* buffer;

	buffer = misc_malloc(sizeof(boot_data_tapedump));
	if (buffer == NULL)
		return -1;
	memcpy(buffer, boot_data_tapedump, sizeof(boot_data_tapedump));
	*data = buffer;
	*size = sizeof(boot_data_tapedump);
	return 0;
}


int
boot_get_eckd_dump_stage2(void** data, size_t* size)
{
	void* buffer;

	buffer = misc_malloc(sizeof(boot_data_eckd2dump));
	if (buffer == NULL)
		return -1;
	memcpy(buffer, boot_data_eckd2dump,
	       sizeof(boot_data_eckd2dump));
	*data = buffer;
	*size = sizeof(boot_data_eckd2dump);
	return 0;
}
