summaryrefslogtreecommitdiffstats
path: root/kexec/arch/arm64/kexec-vmlinuz-arm64.c
blob: e291a34c97ade62ddeab04550927fa4c3b83576d (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
127
128
129
130
/*
 * ARM64 PE compressed Image (vmlinuz, ZBOOT) support.
 *
 * Several distros use 'make zinstall' rule inside
 * 'arch/arm64/boot/Makefile' to install the arm64
 * ZBOOT compressed file inside the boot destination
 * directory (for e.g. /boot).
 *
 * Currently we cannot use kexec_file_load() to load vmlinuz
 * PE images that self decompress.
 *
 * To support ZBOOT, we should:
 * a). Copy the compressed contents of vmlinuz to a temporary file.
 * b). Decompress (gunzip-decompress) the contents inside the
 *     temporary file.
 * c). Validate the resulting image and write it back to the
 *     temporary file.
 * d). Pass the 'fd' of the temporary file to the kernel space.
 *
 * Note this, module doesn't provide a _load() function instead
 * relying on image_arm64_load() to load the resulting decompressed
 * image.
 *
 * So basically the kernel space still gets a decompressed
 * kernel image to load via kexec-tools.
 */

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include "kexec-arm64.h"
#include <kexec-pe-zboot.h>
#include "arch/options.h"

static int kernel_fd = -1;
static off_t decompressed_size;

/* Returns:
 * -1 : in case of error/invalid format (not a valid PE+compressed ZBOOT format.
 */
int pez_arm64_probe(const char *kernel_buf, off_t kernel_size)
{
	int ret = -1;
	const struct arm64_image_header *h;
	char *buf;
	off_t buf_sz;

	buf = (char *)kernel_buf;
	buf_sz = kernel_size;
	if (!buf)
		return -1;
	h = (const struct arm64_image_header *)buf;

	dbgprintf("%s: PROBE.\n", __func__);
	if (buf_sz < sizeof(struct arm64_image_header)) {
		dbgprintf("%s: Not large enough to be a PE image.\n", __func__);
		return -1;
	}
	if (!arm64_header_check_pe_sig(h)) {
		dbgprintf("%s: Not an PE image.\n", __func__);
		return -1;
	}

	if (buf_sz < sizeof(struct arm64_image_header) + h->pe_header) {
		dbgprintf("%s: PE image offset larger than image.\n", __func__);
		return -1;
	}

	if (memcmp(&buf[h->pe_header],
		   arm64_pe_machtype, sizeof(arm64_pe_machtype))) {
		dbgprintf("%s: PE header doesn't match machine type.\n", __func__);
		return -1;
	}

	ret = pez_prepare(buf, buf_sz, &kernel_fd, &decompressed_size);

	if (!ret) {
	    /* validate the arm64 specific header */
	    struct arm64_image_header hdr_check;
	    if (read(kernel_fd, &hdr_check, sizeof(hdr_check)) != sizeof(hdr_check))
		goto bad_header;

	    lseek(kernel_fd, 0, SEEK_SET);

	    if (!arm64_header_check_magic(&hdr_check)) {
		dbgprintf("%s: Bad arm64 image header.\n", __func__);
		goto bad_header;
	    }
	}

	return ret;
bad_header:
	close(kernel_fd);
	free(buf);
	return -1;
}

int pez_arm64_load(int argc, char **argv, const char *buf, off_t len,
			struct kexec_info *info)
{
	if (kernel_fd > 0 && decompressed_size > 0) {
		char *kbuf;
		off_t nread;
		int fd;

		info->kernel_fd = kernel_fd;
		fd = dup(kernel_fd);
		if (fd < 0) {
			dbgprintf("%s: dup fd failed.\n", __func__);
			return -1;
		}
		kbuf = slurp_fd(fd, NULL, decompressed_size, &nread);
		if (!kbuf || nread != decompressed_size) {
			dbgprintf("%s: slurp_fd failed.\n", __func__);
			return -1;
		}
		return image_arm64_load(argc, argv, kbuf, decompressed_size, info);
	}

	dbgprintf("%s: wrong kernel file descriptor.\n", __func__);
	return -1;
}

void pez_arm64_usage(void)
{
	printf(
"     An ARM64 vmlinuz, PE image of a compressed, little endian.\n"
"     kernel, built with ZBOOT enabled.\n\n");
}