| /* |
| * Copyright 2023 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "sw/device/lib/spi_flash.h" |
| |
| #include <string.h> |
| |
| #include "hw/top_matcha/sw/autogen/top_matcha.h" |
| #include "sw/device/lib/arch/device.h" |
| #include "sw/device/lib/dif/dif_spi_host.h" |
| #include "sw/device/lib/eflash.h" |
| #include "sw/device/lib/tar.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| |
| static dif_spi_host_t spi_host0; |
| static const int kSpiFlashBytes = (64 * 1024 * 1024); |
| |
| dif_result_t spi_flash_read_page(uint32_t page, uint8_t* buf) { |
| /* clang-format off */ |
| dif_spi_host_segment_t segments[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = 0x13, // Page Read, 4b Addr |
| }, |
| {.type = kDifSpiHostSegmentTypeAddress, |
| .address = { |
| .width = kDifSpiHostWidthStandard, |
| .mode = kDifSpiHostAddrMode4b, |
| .address = page, |
| }}, |
| {.type = kDifSpiHostSegmentTypeRx, |
| .rx = {.width = kDifSpiHostWidthStandard, |
| .buf = buf, |
| .length = SPI_PAGE_SIZE >> 1}}, |
| }; |
| dif_spi_host_segment_t segments2[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = 0x13, // Page Read, 4b Addr |
| }, |
| {.type = kDifSpiHostSegmentTypeAddress, |
| .address = { |
| .width = kDifSpiHostWidthStandard, |
| .mode = kDifSpiHostAddrMode4b, |
| .address = page + (SPI_PAGE_SIZE >> 1), |
| }}, |
| {.type = kDifSpiHostSegmentTypeRx, |
| .rx = {.width = kDifSpiHostWidthStandard, |
| .buf = buf + (SPI_PAGE_SIZE >> 1), |
| .length = SPI_PAGE_SIZE >> 1}}, |
| }; |
| /* clang-format on */ |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, segments, |
| ARRAYSIZE(segments))); |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, segments2, |
| ARRAYSIZE(segments2))); |
| return kDifOk; |
| } |
| |
| dif_result_t spi_flash_init(void) { |
| CHECK_DIF_OK(dif_spi_host_init( |
| mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host0)); |
| dif_spi_host_config_t config = { |
| .spi_clock = kClockFreqSpiFlashHz, |
| .peripheral_clock_freq_hz = kClockFreqCpuHz, |
| }; |
| CHECK_DIF_OK(dif_spi_host_configure(&spi_host0, config)); |
| CHECK_DIF_OK(dif_spi_host_output_set_enabled(&spi_host0, /*enabled=*/true)); |
| CHECK_DIF_OK(eflash_init()); |
| return kDifOk; |
| } |
| |
| static int parse_octal(uint8_t* cursor) { |
| int size = 0; |
| while (*cursor) size = (size << 3) | ((*cursor++) - '0'); |
| return size; |
| } |
| |
| size_t str_size(const char* str) { |
| size_t size = 0; |
| char* ptr = (char*)str; |
| while (*ptr++) { |
| size++; |
| } |
| return size; |
| } |
| |
| // Returns the offset of the file(or prefix) in the tar. |
| // `size_out` will be filled with the size of the file. |
| static size_t find_file_in_tar(const char* filename, size_t start_cursor, |
| size_t* size_out, char* found_filename, |
| size_t filename_max_len) { |
| if (!size_out || !filename) { |
| return 0; |
| } |
| const char kTarMagic[] = "ustar"; |
| tar_header tar; |
| size_t cursor = start_cursor; |
| CHECK_DIF_OK(spi_flash_read_page(cursor, (uint8_t*)&tar)); |
| while (cursor < kSpiFlashBytes) { |
| // Check the tar header magic field to validate the header info. |
| if (memcmp((const char*)&tar.magic, kTarMagic, str_size(kTarMagic)) != 0) { |
| break; |
| } |
| int size = parse_octal((uint8_t*)&tar.size); |
| cursor += 512; |
| const char* tar_name = (const char*)&tar.name; |
| size_t tar_name_len = str_size(tar_name); |
| if (memcmp(tar_name, filename, str_size(filename)) == 0) { |
| if (size_out) { |
| *size_out = size; |
| } |
| if (found_filename) { |
| size_t copy_len = (tar_name_len < filename_max_len - 1) |
| ? tar_name_len |
| : filename_max_len - 1; |
| memcpy(found_filename, tar_name, copy_len); |
| found_filename[copy_len] = '\0'; |
| } |
| return cursor; |
| } |
| |
| cursor += (size + 511) & ~511; |
| CHECK_DIF_OK(spi_flash_read_page(cursor, (uint8_t*)&tar)); |
| } |
| |
| return 0; |
| } |
| |
| // Copies data from SPI flash to a memory region. |
| // If `addr` points to the region represented by the eFLASH, |
| // special methods will be used to populate it. |
| // Otherwise, `memcpy` is used. |
| static dif_result_t copy_flash_to_mem(size_t bin_offset, size_t size, |
| void* addr) { |
| if (!addr) { |
| return kDifBadArg; |
| } |
| if (bin_offset & (SPI_PAGE_SIZE - 1)) { |
| return kDifBadArg; |
| } |
| uint8_t page[SPI_PAGE_SIZE]; |
| size_t remaining = size; |
| bool eflash = false; |
| if ((uintptr_t)addr >= TOP_MATCHA_EFLASH_BASE_ADDR && |
| (uintptr_t)addr < |
| TOP_MATCHA_EFLASH_BASE_ADDR + TOP_MATCHA_EFLASH_SIZE_BYTES) { |
| eflash = true; |
| CHECK_DIF_OK(eflash_chip_erase()); |
| }; |
| while (remaining > 0) { |
| size_t page_used = remaining >= SPI_PAGE_SIZE ? SPI_PAGE_SIZE : remaining; |
| size_t offset = size - remaining; |
| CHECK_DIF_OK(spi_flash_read_page(bin_offset + offset, page)); |
| void* dst = (uint8_t*)addr + offset; |
| if (eflash) { |
| CHECK_DIF_OK(eflash_write_page(dst, page)); |
| CHECK_DIF_OK( |
| eflash_write_page(dst + EFLASH_PAGE_SIZE, page + EFLASH_PAGE_SIZE)); |
| } else { |
| if (memcpy(dst, page, page_used) != dst) { |
| return kDifError; |
| } |
| } |
| remaining -= page_used; |
| } |
| |
| return kDifOk; |
| } |
| |
| dif_result_t load_file_from_tar(const char* filename, void* addr, |
| size_t max_mem_addr) { |
| if (!filename) { |
| return kDifBadArg; |
| } |
| size_t size = 0; |
| size_t bin_offset = |
| find_file_in_tar(filename, /*start_cursor=*/0, &size, |
| /*found_filename=*/NULL, /*max_filename_len=*/0); |
| if (!bin_offset) return kDifError; |
| if ((size_t)addr + size > max_mem_addr) { |
| return kDifBadArg; |
| } |
| return copy_flash_to_mem(bin_offset, size, addr); |
| } |
| |
| dif_result_t load_file_prefix_from_tar(const char* file_prefix, void* addr, |
| size_t max_mem_addr, size_t* tar_offset, |
| char* filename, size_t filename_len) { |
| if (!tar_offset || !file_prefix || !filename) { |
| return kDifBadArg; |
| } |
| size_t size = 0; |
| size_t start_offset = *tar_offset; |
| size_t bin_offset = find_file_in_tar(file_prefix, start_offset, &size, |
| filename, filename_len); |
| if (!bin_offset) return kDifOutOfRange; |
| if ((size_t)addr + size > max_mem_addr) { |
| return kDifBadArg; |
| } |
| // Update the tar_offset for the future call. |
| *tar_offset = bin_offset + ((size + 511) & ~511); |
| return copy_flash_to_mem(bin_offset, size, addr); |
| } |