summaryrefslogtreecommitdiff
path: root/src/soc/samsung/exynos5420/alternate_cbfs.c
blob: d19098b94826983f99c548cf8e7c068dda95ba4a (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
/*
 * This file is part of the coreboot project.
 *
 * Copyright 2013 Google Inc.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */


#include <assert.h>
#include <cbfs.h>  /* This driver serves as a CBFS media source. */
#include <stdlib.h>
#include <string.h>
#include <arch/cache.h>
#include <console/console.h>
#include "alternate_cbfs.h"
#include "power.h"
#include "spi.h"

/* This allows USB A-A firmware upload from a compatible host in four parts:
 * The first two are the bare BL1 and the Coreboot boot block, which are just
 * written to their respective loading addresses. These transfers are initiated
 * by the IROM / BL1, so this code has nothing to do with them.
 *
 * The third transfer is a valid CBFS image that contains only the romstage,
 * and must be small enough to fit into alternate_cbfs_size[__BOOT_BLOCK__] in
 * IRAM. It is loaded when this function gets called in the boot block, and
 * the normal CBFS code extracts the romstage from it.
 *
 * The fourth transfer is also a CBFS image, but can be of arbitrary size and
 * should contain all available stages/payloads/etc. It is loaded when this
 * function is called a second time at the end of the romstage, and copied to
 * alternate_cbfs_buffer[!__BOOT_BLOCK__] in DRAM. It will reside there for the
 * rest of the firmware's lifetime and all subsequent stages (which will not
 * have __PRE_RAM__ defined) can just directly reference it there.
 */
static int usb_cbfs_open(struct cbfs_media *media)
{
#ifdef __PRE_RAM__
	static int first_run = 1;
	int (*irom_load_usb)(void) = *irom_load_image_from_usb_ptr;

	if (!first_run)
		return 0;

	dcache_mmu_disable();
	if (!irom_load_usb()) {
		dcache_mmu_enable();
		printk(BIOS_EMERG, "Unable to load CBFS image via USB!\n");
		return -1;
	}
	dcache_mmu_enable();

	/*
	 * We need to trust the host/irom to copy the image to our
	 * alternate_cbfs_buffer address... there is no way to control or even
	 * check the transfer size or target address from our side.
	 */

	printk(BIOS_DEBUG, "USB A-A transfer successful, CBFS image should now"
		" be at %p\n", alternate_cbfs_buffer);
	first_run = 0;
#endif
	return 0;
}

/*
 * SDMMC works very similar to USB A-A: we copy the CBFS image into memory
 * and read it from there. While SDMMC would also allow direct block by block
 * on-demand reading, we might run into problems if we call back into the IROM
 * in very late boot stages (e.g. after initializing/changing MMC clocks)... so
 * this seems like a safer approach. It also makes it easy to pass our image
 * down to payloads.
 */
static int sdmmc_cbfs_open(struct cbfs_media *media)
{
#ifdef __PRE_RAM__
	/*
	 * In the bootblock, we just copy the small part that fits in the buffer
	 * and hope that it's enough (since the romstage is currently always the
	 * first component in the image, this should work out). In the romstage,
	 * we copy until our buffer is full (currently 12M) to avoid the pain of
	 * figuring out the true image size from in here. Since this is mainly a
	 * developer/debug boot mode, those shortcomings should be bearable.
	 */
	const u32 count = alternate_cbfs_size / 512;
	static int first_run = 1;
	int (*irom_load_sdmmc)(u32 start, u32 count, void *dst) =
		*irom_sdmmc_read_blocks_ptr;

	if (!first_run)
		return 0;

	dcache_mmu_disable();
	if (!irom_load_sdmmc(1, count, alternate_cbfs_buffer)) {
		dcache_mmu_enable();
		printk(BIOS_EMERG, "Unable to load CBFS image from SDMMC!\n");
		return -1;
	}
	dcache_mmu_enable();

	printk(BIOS_DEBUG, "SDMMC read successful, CBFS image should now be"
		" at %p\n", alternate_cbfs_buffer);
	first_run = 0;
#endif
	return 0;
}

static int alternate_cbfs_close(struct cbfs_media *media) { return 0; }

static size_t alternate_cbfs_read(struct cbfs_media *media, void *dest,
				  size_t offset, size_t count)
{
	ASSERT(offset + count < alternate_cbfs_size);
	memcpy(dest, alternate_cbfs_buffer + offset, count);
	return count;
}

static void *alternate_cbfs_map(struct cbfs_media *media, size_t offset,
				   size_t count)
{
	ASSERT(offset + count < alternate_cbfs_size);
	return alternate_cbfs_buffer + offset;
}

static void *alternate_cbfs_unmap(struct cbfs_media *media,
				  const void *buffer) { return 0; }

static int initialize_exynos_sdmmc_cbfs_media(struct cbfs_media *media)
{
	printk(BIOS_DEBUG, "Using Exynos alternate boot mode SDMMC\n");

	media->open = sdmmc_cbfs_open;
	media->close = alternate_cbfs_close;
	media->read = alternate_cbfs_read;
	media->map = alternate_cbfs_map;
	media->unmap = alternate_cbfs_unmap;

	return 0;
}

static int initialize_exynos_usb_cbfs_media(struct cbfs_media *media)
{
	printk(BIOS_DEBUG, "Using Exynos alternate boot mode USB A-A\n");

	media->open = usb_cbfs_open;
	media->close = alternate_cbfs_close;
	media->read = alternate_cbfs_read;
	media->map = alternate_cbfs_map;
	media->unmap = alternate_cbfs_unmap;

	return 0;
}

int init_default_cbfs_media(struct cbfs_media *media)
{
	if (*iram_secondary_base == SECONDARY_BASE_BOOT_USB)
		return initialize_exynos_usb_cbfs_media(media);

	switch (exynos_power->om_stat & OM_STAT_MASK) {
	case OM_STAT_SDMMC:
		return initialize_exynos_sdmmc_cbfs_media(media);
	case OM_STAT_SPI:
		return initialize_exynos_spi_cbfs_media(media,
			(void*)CONFIG_CBFS_CACHE_ADDRESS,
			CONFIG_CBFS_CACHE_SIZE);
	default:
		printk(BIOS_EMERG, "Exynos OM_STAT value 0x%x not supported!\n",
			exynos_power->om_stat);
		return 0;
	}
}