view loader/loader/loader_c.c @ 683:4fa3b09faa14

loader: do not duplicate ccw and orb definitions Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Tue, 06 Aug 2019 10:28:29 -0400
parents 45bec12c305f
children 9de638d13941
line wrap: on
line source

/*
 * Copyright (c) 2007-2019 Josef 'Jeff' Sipek
 */

#include "loader.h"
#include <channel.h>
#include <binfmt_elf.h>

static unsigned char seek_data[6];
static unsigned char search_data[5];
static struct ccw ccw[4];

struct orb ORB = {
	.param		= 0x12345678,
	.f		= 1,
	.lpm		= 0xff,
	.addr		= 0xffffffff,
};

static u8 *buf = (u8*) (16 * 1024);
static u32 *ptrbuf = (u32*) (20 * 1024);

/*
 * halt the cpu
 *
 * NOTE: we don't care about not clobbering registers as when this
 * code executes, the CPU will be stopped.
 */
static inline void die(void)
{
	asm volatile(
		"SR	%r1, %r1	# not used, but should be zero\n"
		"SR	%r3, %r3 	# CPU Address\n"
		"SIGP	%r1, %r3, 0x05	# Signal, order 0x05\n"
	);

	/*
	 * Just in case SIGP fails
	 */
	for(;;);
}

/*
 * determine amount of storage
 */
static u64 sense_memsize(void)
{
	volatile u64 *pgm_psw = (void *) 0x1d0;
	u64 size;
	int cc;

#define SKIP_SIZE	(1024*1024ULL)

	/* set new PGM psw */
	pgm_psw[0] = 0x0000000180000000ULL;
	pgm_psw[1] = (u64) &PGMHANDLER;

	for(size = 0; size < ((u64)~SKIP_SIZE)-1; size += SKIP_SIZE) {
		asm volatile(
			"lg	%%r1,%1\n"
			"tprot	0(%%r1),0\n"
			"ipm	%0\n"
			"srl    %0,28\n"
		: /* output */
		  "=d" (cc)
		: /* input */
		  "m" (size)
		: /* clobber */
		  "cc", "r1"
		);

		/*
		 * we cheat here a little...if we try to tprot a location
		 * that isn't part of the configuration, a program exception
		 * fires off, but our handler sets the CC to 3, and resumes
		 * execution
		 */
		if (cc == 3)
			break;
	}

	/* invalidate new PGM psw */
	memset((void *) pgm_psw, 0, 16);

	return size;
}

static void read_blk(void *ptr, u32 lba)
{
	u16 cc, hh, r;

	if (lba < 1)
		die();

	memset(ccw, 0, sizeof(ccw));

	lba--;
	cc = lba / RECORDS_PER_CYL;
	hh = (lba % RECORDS_PER_CYL) / RECORDS_PER_TRACK;
	r = (lba % RECORDS_PER_CYL) % RECORDS_PER_TRACK;
	r++;

	ORB.addr = ADDR31(ccw);

	/* SEEK */
	ccw[0].cmd = 0x07;
	ccw[0].flags = CCW_FLAG_CC | CCW_FLAG_SLI;
	ccw[0].count = 6;
	ccw[0].addr = ADDR31(seek_data);

	seek_data[0] = 0;		/* zero */
	seek_data[1] = 0;		/* zero */
	seek_data[2] = cc >> 8;		/* Cc */
	seek_data[3] = cc & 0xff;	/* cC */
	seek_data[4] = hh >> 8;		/* Hh */
	seek_data[5] = hh & 0xff;	/* hH */

	/* SEARCH */
	ccw[1].cmd = 0x31;
	ccw[1].flags = CCW_FLAG_CC | CCW_FLAG_SLI;
	ccw[1].count = 5;
	ccw[1].addr = ADDR31(search_data);

	search_data[0] = cc >> 8;
	search_data[1] = cc & 0xff;
	search_data[2] = hh >> 8;
	search_data[3] = hh & 0xff;
	search_data[4] = r;

	/* TIC */
	ccw[2].cmd = 0x08;
	ccw[2].flags = 0;
	ccw[2].count = 0;
	ccw[2].addr = ADDR31(&ccw[1]);

	/* READ DATA */
	ccw[3].cmd = 0x86;
	ccw[3].flags = 0;
	ccw[3].count = 4096;
	ccw[3].addr = ADDR31(ptr);

	/*
	 * issue IO
	 */
	__do_io();
}

/*
 * read the entire nucleus into TEMP_BASE
 */
static inline void readnucleus(void)
{
	struct ADT *ADT = (struct ADT*) buf;
	struct FST *FST = (struct FST*) buf;
	struct FST fst;
	int i, found;
	u32 nfst;

	read_blk(buf, EDF_LABEL_BLOCK_NO);

	if ((ADT->IDENT != __ADTIDENT) ||
	    (ADT->DBSIZ != EDF_SUPPORTED_BLOCK_SIZE) ||
	    (ADT->OFFST != 0) ||
	    (ADT->FSTSZ != sizeof(struct FST)))
		die();

	nfst = ADT->NFST;

	read_blk(buf, ADT->DOP);

	if (FST->NLVL != 0)
	       die(); // FIXME

	for(i=0,found=0; i<nfst; i++) {
		if ((!memcmp(FST[i].FNAME, CP_FN, 8)) &&
		    (!memcmp(FST[i].FTYPE, CP_FT, 8))) {
			memcpy(&fst, &FST[i], sizeof(struct FST));
			found = 1;
			break;
		}
	}

	if (!found)
		die();

	if (fst.PTRSZ != 4 ||
	    fst.LRECL != 4096 ||
	    fst.RECFM != FSTDFIX)
		die();

	/* Don't allow more than 3MB to be read */
	if ((FST->AIC * FST->LRECL) > (3ULL * 1024 * 1024))
		die();

	/* Since we're assuming that NLVL==1, there's only 1 pointer block */
	read_blk(ptrbuf, fst.FOP);

	/* Read all the blocks pointed to by the ptr block */
	for(i=0; i<fst.AIC; i++)
		read_blk(TEMP_BASE + (4096 * i), ptrbuf[i]);
}

void load_nucleus(void)
{
	/*
	 * These are all stored in registers
	 */
	register u32 iplsch;
	register int i, j;
	register Elf64_Ehdr *nucleus_elf;
	register Elf64_Shdr *section, *link_section, *tmp_section;
	register Elf64_Phdr *segment;
	register Elf64_Ehdr *elfcpy;
	register unsigned char *symtab;
	register void (*start_sym)(u64, u32, void*);

	/* Save the IPL device subchannel id */
	iplsch = *((u32*) 0xb8);

	/*
	 * Read entire ELF to temporary location
	 */
	readnucleus();

	nucleus_elf = (Elf64_Ehdr*) TEMP_BASE;

	/*
	 * Check that this looks like a valid ELF
	 */
	if (nucleus_elf->e_ident[0] != '\x7f' ||
	    nucleus_elf->e_ident[1] != 'E' ||
	    nucleus_elf->e_ident[2] != 'L' ||
	    nucleus_elf->e_ident[3] != 'F' ||
	    nucleus_elf->e_ident[EI_CLASS] != ELFCLASS64 ||
	    nucleus_elf->e_ident[EI_DATA] != ELFDATA2MSB ||
	    nucleus_elf->e_ident[EI_VERSION] != EV_CURRENT ||
	    nucleus_elf->e_type != ET_EXEC ||
	    nucleus_elf->e_machine != 0x16 || // FIXME: find the name for the #define
	    nucleus_elf->e_version != EV_CURRENT)
		die();

	/* Iterate through each program header, and copy all PT_LOAD
	 * segments to the final destinations.
	 */

	for(i=0; i<nucleus_elf->e_phnum; i++) {
		segment = (Elf64_Phdr*) (TEMP_BASE +
					 nucleus_elf->e_phoff +
					 nucleus_elf->e_phentsize * i);

		if (segment->p_type != PT_LOAD)
			continue;

		if (segment->p_filesz)
			memcpy((void*) segment->p_vaddr,
			       TEMP_BASE + segment->p_offset,
			       segment->p_filesz);

		if (segment->p_filesz != segment->p_memsz)
			memset((void*) (segment->p_vaddr + segment->p_filesz),
			       0, segment->p_memsz - segment->p_filesz);
	}

	/*
	 * Copy over the ELF header & tweak it.
	 */
	symtab = SYMTAB_BASE;
	elfcpy = (Elf64_Ehdr*) symtab;
	memcpy(elfcpy, nucleus_elf, nucleus_elf->e_ehsize);
	symtab += nucleus_elf->e_ehsize;

	/* the program headers start right after */
	elfcpy->e_phoff = symtab - SYMTAB_BASE;

	/* copy over all the program headers */
	memcpy(symtab, TEMP_BASE + nucleus_elf->e_phoff,
	       nucleus_elf->e_phentsize * nucleus_elf->e_phnum);
	symtab += nucleus_elf->e_phentsize * nucleus_elf->e_phnum;

	/* the section headers start right after */
	elfcpy->e_shoff = symtab - SYMTAB_BASE;

	/*
	 * Iterate through each section to find the .symtab & .strtab (as
	 * well as the null section), and copy the the headers over
	 */
	elfcpy->e_shnum = 0;

	for(i=0; i<nucleus_elf->e_shnum; i++) {
		section = (Elf64_Shdr*) (TEMP_BASE +
					 nucleus_elf->e_shoff +
					 nucleus_elf->e_shentsize * i);

		switch (section->sh_type) {
			case SHT_STRTAB:
				if (i == nucleus_elf->e_shstrndx)
					elfcpy->e_shstrndx = elfcpy->e_shnum;

			case SHT_NULL:
			case SHT_SYMTAB:
				memcpy(symtab, section, nucleus_elf->e_shentsize);
				elfcpy->e_shnum++;
				symtab += nucleus_elf->e_shentsize;
				break;
			default:
				/* Ignoring */
				break;
		}
	}

	for(i=0; i<elfcpy->e_shnum; i++) {
		section = (Elf64_Shdr*) (SYMTAB_BASE +
					 elfcpy->e_shoff +
					 elfcpy->e_shentsize * i);

		if (!section->sh_link)
			continue;

		tmp_section = (Elf64_Shdr*) (TEMP_BASE +
					 nucleus_elf->e_shoff +
					 nucleus_elf->e_shentsize *
					 section->sh_link);

		for(j=0; j<elfcpy->e_shnum; j++) {
			link_section = (Elf64_Shdr*) (SYMTAB_BASE +
						      elfcpy->e_shoff +
						      elfcpy->e_shentsize * j);

			if (memcmp(tmp_section, link_section, elfcpy->e_shentsize))
				continue;

			section->sh_link = j;
			break;
		}
	}

	/*
	 * Now that we know what sections and where, let's copy those over
	 */
	for (i=1; i<elfcpy->e_shnum; i++) {
		section = (Elf64_Shdr*) (SYMTAB_BASE +
					 elfcpy->e_shoff +
					 elfcpy->e_shentsize * i);

		memcpy(symtab, TEMP_BASE + section->sh_offset,
		       section->sh_size);
		section->sh_offset = symtab - SYMTAB_BASE;
		symtab += section->sh_size;
	}

	/*
	 * Now, jump to the nucleus entry point
	 */
	start_sym = (void*) nucleus_elf->e_entry;
	start_sym(sense_memsize(), iplsch, SYMTAB_BASE);
}