/*
 * Main program for stage3b bootloader
 *
 * Copyright IBM Corp. 2020
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include "libc.h"
#include "stage3b.h"

#include "lib/zt_common.h"
#include "boot/psw.h"
#include "boot/error.h"
#include "boot/s390.h"
#include "boot/linux_layout.h"
#include "boot/loaders_layout.h"
#include "sclp.h"


static volatile struct stage3b_args __section(".loader_parms") loader_parms;

static inline void __noreturn load_psw(struct psw_t psw)
{
	asm volatile("lpswe %0" : : "Q"(psw) : "cc");

	while (1)
		;
}

static unsigned long get_kernel_cmdline_size(void)
{
	unsigned long size = *(volatile unsigned long *)MAX_COMMAND_LINE_SIZE;

	if (size != 0)
		return size;

	return LEGACY_COMMAND_LINE_SIZE;
}

void __noreturn start(void)
{
	volatile struct stage3b_args *args = &loader_parms;
	volatile struct memblob *kernel = &args->kernel;
	volatile struct memblob *cmdline = &args->cmdline;
	volatile struct memblob *initrd = &args->initrd;
	struct psw_t psw = args->psw;

	/* set up ASCII and line-mode */
	sclp_setup(SCLP_LINE_ASCII_INIT);

	if (kernel->size < IMAGE_LOAD_ADDRESS)
		panic(EINTERNAL, "Invalid kernel\n");

	/* move the kernel and cut the kernel header */
	memmove((void *)IMAGE_LOAD_ADDRESS,
		(void *)(kernel->src + IMAGE_LOAD_ADDRESS),
		kernel->size - IMAGE_LOAD_ADDRESS);

	if (cmdline->size > get_kernel_cmdline_size())
		panic(EINTERNAL, "Command line is too large\n");

	if (cmdline->size > 0) {
		/* make sure the cmdline is a null-terminated string */
		if (((char *)cmdline->src)[cmdline->size - 1] != '\0')
			panic(EINTERNAL, "Command line needs to be null-terminated\n");

		/* move the kernel cmdline */
		memmove((void *)COMMAND_LINE, (void *)cmdline->src, cmdline->size);
	}
	/* the initrd does not need to be moved */

	if (initrd->size > 0) {
		/* copy initrd start address and size into new kernel space */
		*(unsigned long long *)INITRD_START = initrd->src;
		*(unsigned long long *)INITRD_SIZE = initrd->size;
	}

	/* disable ASCII and line-mode */
	sclp_setup(SCLP_DISABLE);

	/* use lpswe instead of diag308 as a I/O subsystem reset is not
	 * needed as this was already done by the diag308 subcode 10 call
	 * in stage3a
	 */
	load_psw(psw);
}

void panic_notify(unsigned long UNUSED(rc))
{
}
