summaryrefslogtreecommitdiffstats
path: root/src/lib/romselfboot.c
blob: 416e797a174ff00d1950bdbcffdb1df3ed585749 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2003 Eric W. Biederman <ebiederm@xmission.com>
 * Copyright (C) 2009 Ron Minnich <rminnich@gmail.com>
 * Copyright (C) 2016 George Trudeau <george.trudeau@usherbrooke.ca>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <commonlib/compression.h>
#include <commonlib/endian.h>
#include <console/console.h>
#include <cpu/cpu.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <symbols.h>
#include <cbfs.h>
#include <lib.h>
#include <bootmem.h>
#include <program_loading.h>
#include <timestamp.h>

struct segment {
	struct segment *next;
	struct segment *prev;
	unsigned long s_dstaddr;
	unsigned long s_srcaddr;
	unsigned long s_memsz;
	unsigned long s_filesz;
	int compression;
};

/* Decode a serialized cbfs payload segment
 * from memory into native endianness.
 */
static void cbfs_decode_payload_segment(struct cbfs_payload_segment *segment,
					const struct cbfs_payload_segment *src)
{
	segment->type = read_be32(&src->type);
	segment->compression = read_be32(&src->compression);
	segment->offset = read_be32(&src->offset);
	segment->load_addr = read_be64(&src->load_addr);
	segment->len = read_be32(&src->len);
	segment->mem_len = read_be32(&src->mem_len);
}

static int
load_segment(struct segment *ptr)
{
	unsigned char *dest, *src, *end;
	size_t len, memsz;

	printk(BIOS_DEBUG,
	       "Loading Segment: addr: 0x%016lx memsz: 0x%016lx filesz: 0x%016lx\n",
	       ptr->s_dstaddr, ptr->s_memsz, ptr->s_filesz);
	/* Compute the boundaries of the segment */
	dest = (unsigned char *)(ptr->s_dstaddr);
	src = (unsigned char *)(ptr->s_srcaddr);
	len = ptr->s_filesz;
	memsz = ptr->s_memsz;
	end = dest + memsz;

	switch (ptr->compression) {
	case CBFS_COMPRESS_LZMA: {
		printk(BIOS_DEBUG, "using LZMA\n");
		timestamp_add_now(TS_START_ULZMA);
		len = ulzman(src, len, dest, memsz);
		timestamp_add_now(TS_END_ULZMA);
		if (!len) /* Decompression Error. */
			return 0;
		break;
	}
	case CBFS_COMPRESS_LZ4: {
		printk(BIOS_DEBUG, "using LZ4\n");
		timestamp_add_now(TS_START_ULZ4F);
		len = ulz4fn(src, len, dest, memsz);
		timestamp_add_now(TS_END_ULZ4F);
		if (!len) /* Decompression Error. */
			return 0;
		break;
	}
	case CBFS_COMPRESS_NONE: {
		printk(BIOS_DEBUG, "it's not compressed!\n");
		memcpy(dest, src, len);
		break;
	}
	default:
		printk(BIOS_INFO, "CBFS:  Unknown compression type %d\n",
		       ptr->compression);
		return -1;
	}
	return 0;
}

/* This loads the payload from a romstage.
 * This is different than the ramstage payload loader since we don't
 * check memory regions and we don't use malloc anywhere. It is most like
 * the LinuxBIOS v3 SELF loader.
 */
static int load_payload(
	struct segment *head,
	struct cbfs_payload *cbfs_payload, uintptr_t *entry)
{
	struct segment *new;
	struct cbfs_payload_segment *current_segment, *first_segment, segment;
	struct segment ptr;

	memset(head, 0, sizeof(*head));
	head->next = head->prev = head;

	first_segment = &cbfs_payload->segments;

	for (current_segment = first_segment;; ++current_segment) {
		printk(BIOS_DEBUG,
		       "Decoding segment from ROM address 0x%p\n",
		       current_segment);

		cbfs_decode_payload_segment(&segment, current_segment);

		switch (segment.type) {
		case PAYLOAD_SEGMENT_PARAMS:
			printk(BIOS_DEBUG, "  parameter section (skipped)\n");
			continue;

		case PAYLOAD_SEGMENT_CODE:
		case PAYLOAD_SEGMENT_DATA:
			printk(BIOS_DEBUG, "  %s (compression=%x)\n",
			       segment.type == PAYLOAD_SEGMENT_CODE
				       ? "code"
				       : "data",
			       segment.compression);

			new = &ptr;
			new->s_dstaddr = segment.load_addr;
			new->s_memsz = segment.mem_len;
			new->compression = segment.compression;
			new->s_srcaddr = (uintptr_t)((unsigned char *)first_segment)
					 + segment.offset;
			new->s_filesz = segment.len;

			printk(BIOS_DEBUG,
			       "  New segment dstaddr 0x%lx memsize 0x%lx srcaddr 0x%lx filesize 0x%lx\n",
			       new->s_dstaddr, new->s_memsz, new->s_srcaddr,
			       new->s_filesz);

			/* Clean up the values */
			if (new->s_filesz > new->s_memsz) {
				new->s_filesz = new->s_memsz;
				printk(BIOS_DEBUG,
				       "  cleaned up filesize 0x%lx\n",
				       new->s_filesz);
			}
			load_segment(new);
			break;

		case PAYLOAD_SEGMENT_BSS:
			printk(BIOS_DEBUG, "  BSS 0x%p (%d byte)\n", (void *)(intptr_t)segment.load_addr, segment.mem_len);

			new = &ptr;
			new->s_filesz = 0;
			new->s_srcaddr = (uintptr_t)((unsigned char *)first_segment)
					 + segment.offset;
			new->s_dstaddr = segment.load_addr;
			new->s_memsz = segment.mem_len;
			new->compression = CBFS_COMPRESS_NONE;
			load_segment(new);
			break;

		case PAYLOAD_SEGMENT_ENTRY:
			printk(BIOS_DEBUG, "  Entry Point 0x%p\n", (void *)(intptr_t)segment.load_addr);

			*entry = segment.load_addr;
			/* Per definition, a payload always has the entry point
			 * as last segment. Thus, we use the occurrence of the
			 * entry point as break condition for the loop.
			 * Can we actually just look at the number of section?
			 */
			return 1;

		default:
			/* We found something that we don't know about. Throw
			 * hands into the sky and run away!
			 */
			printk(BIOS_EMERG, "Bad segment type %x\n",
			       segment.type);
			return -1;
		}
	}

	return 1;
}

bool selfload(struct prog *payload, bool check_regions)
{
	uintptr_t entry = 0;
	struct segment head;
	void *data;

	data = rdev_mmap_full(prog_rdev(payload));
	if (data == NULL)
		return false;

	/* Load the segments */
	if (!load_payload(&head, data, &entry))
		return false;

	prog_set_entry(payload, (void *)entry, NULL);
	printk(BIOS_SPEW, "Loaded segments\n");
	return true;
}