summaryrefslogtreecommitdiffstats
path: root/arch/s390/kernel/machine_kexec_file.c
blob: d9b4f9d23e9ff6a9deb2c222a409a413b31aafac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// SPDX-License-Identifier: GPL-2.0
/*
 * s390 code for kexec_file_load system call
 *
 * Copyright IBM Corp. 2018
 *
 * Author(s): Philipp Rudo <prudo@linux.vnet.ibm.com>
 */

#include <linux/elf.h>
#include <linux/kexec.h>
#include <asm/setup.h>

const struct kexec_file_ops * const kexec_file_loaders[] = {
	NULL,
};

/*
 * The kernel is loaded to a fixed location. Turn off kexec_locate_mem_hole
 * and provide kbuf->mem by hand.
 */
int arch_kexec_walk_mem(struct kexec_buf *kbuf,
			int (*func)(struct resource *, void *))
{
	return 1;
}

int arch_kexec_apply_relocations_add(struct purgatory_info *pi,
				     Elf_Shdr *section,
				     const Elf_Shdr *relsec,
				     const Elf_Shdr *symtab)
{
	Elf_Rela *relas;
	int i;

	relas = (void *)pi->ehdr + relsec->sh_offset;

	for (i = 0; i < relsec->sh_size / sizeof(*relas); i++) {
		const Elf_Sym *sym;	/* symbol to relocate */
		unsigned long addr;	/* final location after relocation */
		unsigned long val;	/* relocated symbol value */
		void *loc;		/* tmp location to modify */

		sym = (void *)pi->ehdr + symtab->sh_offset;
		sym += ELF64_R_SYM(relas[i].r_info);

		if (sym->st_shndx == SHN_UNDEF)
			return -ENOEXEC;

		if (sym->st_shndx == SHN_COMMON)
			return -ENOEXEC;

		if (sym->st_shndx >= pi->ehdr->e_shnum &&
		    sym->st_shndx != SHN_ABS)
			return -ENOEXEC;

		loc = pi->purgatory_buf;
		loc += section->sh_offset;
		loc += relas[i].r_offset;

		val = sym->st_value;
		if (sym->st_shndx != SHN_ABS)
			val += pi->sechdrs[sym->st_shndx].sh_addr;
		val += relas[i].r_addend;

		addr = section->sh_addr + relas[i].r_offset;

		switch (ELF64_R_TYPE(relas[i].r_info)) {
		case R_390_8:		/* Direct 8 bit.   */
			*(u8 *)loc = val;
			break;
		case R_390_12:		/* Direct 12 bit.  */
			*(u16 *)loc &= 0xf000;
			*(u16 *)loc |= val & 0xfff;
			break;
		case R_390_16:		/* Direct 16 bit.  */
			*(u16 *)loc = val;
			break;
		case R_390_20:		/* Direct 20 bit.  */
			*(u32 *)loc &= 0xf00000ff;
			*(u32 *)loc |= (val & 0xfff) << 16;	/* DL */
			*(u32 *)loc |= (val & 0xff000) >> 4;	/* DH */
			break;
		case R_390_32:		/* Direct 32 bit.  */
			*(u32 *)loc = val;
			break;
		case R_390_64:		/* Direct 64 bit.  */
			*(u64 *)loc = val;
			break;
		case R_390_PC16:	/* PC relative 16 bit.	*/
			*(u16 *)loc = (val - addr);
			break;
		case R_390_PC16DBL:	/* PC relative 16 bit shifted by 1.  */
			*(u16 *)loc = (val - addr) >> 1;
			break;
		case R_390_PC32DBL:	/* PC relative 32 bit shifted by 1.  */
			*(u32 *)loc = (val - addr) >> 1;
			break;
		case R_390_PC32:	/* PC relative 32 bit.	*/
			*(u32 *)loc = (val - addr);
			break;
		case R_390_PC64:	/* PC relative 64 bit.	*/
			*(u64 *)loc = (val - addr);
			break;
		default:
			break;
		}
	}
	return 0;
}

int arch_kexec_kernel_image_probe(struct kimage *image, void *buf,
				  unsigned long buf_len)
{
	/* A kernel must be at least large enough to contain head.S. During
	 * load memory in head.S will be accessed, e.g. to register the next
	 * command line. If the next kernel were smaller the current kernel
	 * will panic at load.
	 *
	 * 0x11000 = sizeof(head.S)
	 */
	if (buf_len < 0x11000)
		return -ENOEXEC;

	return kexec_image_probe_default(image, buf, buf_len);
}
OpenPOWER on IntegriCloud