diff options
author | Pavel V. Shatov (Meister) <meisterpaul1@yandex.ru> | 2021-09-13 11:54:00 +0300 |
---|---|---|
committer | Pavel V. Shatov (Meister) <meisterpaul1@yandex.ru> | 2021-09-13 11:54:00 +0300 |
commit | c71f1f58e0d9ab8eae86604acd721c1e0abdcd3c (patch) | |
tree | aa71d045c0b01cf5f21dad2d280bf9d2d675c1ef /stm-ice40mkm.c | |
parent | d1624b391113f777f4aa9d70cc26ff6a9fe27ed2 (diff) |
This adds two routines, ice40mkm_init() and ice40mkm_configure(). The former
should be called once during startup. The latter configures the iCE40 chip
with a bitstream stored in the very end of the main FPGA's configuration
memory. It should be called after startup and potentially after MKM bitstream
is upgraded.
Diffstat (limited to 'stm-ice40mkm.c')
-rw-r--r-- | stm-ice40mkm.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/stm-ice40mkm.c b/stm-ice40mkm.c new file mode 100644 index 0000000..5a03525 --- /dev/null +++ b/stm-ice40mkm.c @@ -0,0 +1,201 @@ +/* + * stm-ice40mkm.c + * -------------- + * Functions for configuring the Lattice iCE40-based master key memory. + * + * Copyright 2021 The Commons Conservancy Cryptech Project + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "stm-ice40mkm.h" +#include "stm-init.h" +#include "stm-fpgacfg.h" + +/* handle to SPI3 */ +static SPI_HandleTypeDef hspi_ice40mkm; + +/* + * initialize SPI3 peripheral and assert reset signal + */ +void ice40mkm_init(void) +{ + /* initialize GPIO pins */ + ICE40MKM_GPIO_INIT(); + + /* + * datasheet says up to 45 MHz is fine, but that's apparently for master mode + * slave speed not explicitly defined, but TN-02001-3.2 says only up to 25 MHz + * 4x prescaler is used for 22.5 MHz (2x works fine, but might be out of specs) + */ + + /* SPI3 (iCE40 slave SPI configuration interface) init function */ + hspi_ice40mkm.Instance = SPI3; + hspi_ice40mkm.Init.Mode = SPI_MODE_MASTER; + hspi_ice40mkm.Init.Direction = SPI_DIRECTION_2LINES; + hspi_ice40mkm.Init.DataSize = SPI_DATASIZE_8BIT; + hspi_ice40mkm.Init.CLKPolarity = SPI_POLARITY_HIGH; + hspi_ice40mkm.Init.CLKPhase = SPI_PHASE_2EDGE; + hspi_ice40mkm.Init.NSS = SPI_NSS_SOFT; + hspi_ice40mkm.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; + hspi_ice40mkm.Init.FirstBit = SPI_FIRSTBIT_MSB; + hspi_ice40mkm.Init.TIMode = SPI_TIMODE_DISABLE; + hspi_ice40mkm.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + hspi_ice40mkm.Init.CRCPolynomial = 10; + HAL_SPI_Init(&hspi_ice40mkm); + + /* assert iCE40 reset */ + _ice40mkm_reset_assert(); +} + +/* + * force iCE40 into slave SPI mode + * read bitstream from the end of the main FPGA's config memory + * send the bitstream to iCE40 + * check, that "config done" went high + * + * return codes: + * 0 = success + * 1 = serial transfer timeout + * 2 = bad magic marker (image missing?) + * 3 = config done did not go high (crc error, etc) + */ +int ice40mkm_configure(void) +{ + HAL_StatusTypeDef prom_ok; + + size_t c; + uint8_t prom_buf[N25Q128_PAGE_SIZE]; + uint8_t prom_marker[] = ICE40_MAGIC_MARKER; + int prom_marker_bad = 0, cdone_state; + + uint32_t prom_addr, prom_offset = N25Q128_SECTOR_SIZE * + (N25Q128_NUM_SECTORS - ICE40_BITSTREAM_SECTORS); + + uint32_t page, num_pages = ICE40_BITSTREAM_SECTORS * + (N25Q128_SECTOR_SIZE / N25Q128_PAGE_SIZE); + + /* + * assuming ice40mkm_init() has already been called (reset + * has already been asserted), activate chip-select and + * de-assert reset to force device into slave mode, then + * wait for 2 ms to allow internal "housekeeping" to finish + */ + + _ice40mkm_chip_select(); // + HAL_Delay(1); // minimum reset duration is 200 ns + _ice40mkm_reset_deassert(); // + HAL_Delay(2); // minimum wait time is 1200 us + + /* + * send 8 dummy clock ticks when chip select is high, + * then revert to low chip select + */ + _ice40mkm_chip_deselect(); + prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, 1, N25Q128_SPI_TIMEOUT); + if (prom_ok != HAL_OK) return 1; + _ice40mkm_chip_select(); + + /* + * device is now ready to receive the bitstream + * + * the bitstream is stored in the last two sectors of the main + * FPGA configuration memory + * + * the very first page of the bitstream starts with a magic marker, + * which can be used to detect whether the bitstream is present + */ + + /* claim access to FPGA config storage */ + fpgacfg_access_control(ALLOW_ARM); + + /* + * read the bitstream page-by-page and send it over SPI3 + * note, that bitstream is slightly smaller than two sectors, so we're + * sending some 0xFF's after the binary image, which is harmless + * (just some dummy clock cycles) + */ + prom_addr = prom_offset; + for (page=0; page<num_pages; page++) + { + /* read next page */ + prom_ok = fpgacfg_read_data(prom_addr, prom_buf, N25Q128_PAGE_SIZE); + if (prom_ok != HAL_OK) break; + + /* check magic marker */ + if (page == 0) { + for (c=0; c<sizeof prom_marker; c++) + if (prom_buf[c] != prom_marker[c]) { + prom_marker_bad = 1; + break; + } + } + + /* send page to iCE40 */ + prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, N25Q128_PAGE_SIZE, N25Q128_SPI_TIMEOUT); + if (prom_ok != HAL_OK) break; + + /* increment address */ + prom_addr += N25Q128_PAGE_SIZE; + } + + /* relinquish access to FPGA config storage */ + fpgacfg_access_control(ALLOW_FPGA); + + /* check, that magic marker was correct */ + /* check, that all the pages were written */ + if (prom_marker_bad) return 2; + else if (page != num_pages) return 1; + + /* clear chip-select */ + _ice40mkm_chip_deselect(); + + /* + * we should wait for at least 100 clock ticks before sampling + * the CDONE pin, we can only wait for multiples of 8, so let's + * wait for 128 (16 bytes) to be on the safe side + */ + prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, 16, N25Q128_SPI_TIMEOUT); + if (prom_ok != HAL_OK) return 3; + + /* now check config done pin */ + cdone_state = _ice40mkm_cdone(); + if (!cdone_state) return 3; + + /* + * we should wait for at least 49 clock ticks after CDONE turns high to + * release SPI pins for user application, we're not using them anyway, but + * let's still follow vendor's recommendations just in case; we can only wait + * for multiples of 8, so let's wait for 64 ticks (8 bytes) instead of 49. + */ + prom_ok = HAL_SPI_Transmit(&hspi_ice40mkm, prom_buf, 8, N25Q128_SPI_TIMEOUT); + if (prom_ok != HAL_OK) return 1; + + /* cool, everything went fine, zero code means success */ + return 0; +} |