| /* |
| * 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 <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; |
| |
| if (!irom_load_usb()) { |
| printk(BIOS_EMERG, "Unable to load CBFS image via USB!\n"); |
| return -1; |
| } |
| |
| /* |
| * 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; |
| |
| if (!irom_load_sdmmc(1, count, alternate_cbfs_buffer)) { |
| printk(BIOS_EMERG, "Unable to load CBFS image from SDMMC!\n"); |
| return -1; |
| } |
| |
| 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; |
| } |
| } |