diff --git a/hw/top_earlgrey/data/ip/chip_spi_device_testplan.hjson b/hw/top_earlgrey/data/ip/chip_spi_device_testplan.hjson index 6e60909b3a7c82..5469584f5bf6e7 100644 --- a/hw/top_earlgrey/data/ip/chip_spi_device_testplan.hjson +++ b/hw/top_earlgrey/data/ip/chip_spi_device_testplan.hjson @@ -49,6 +49,7 @@ - Load a firmware image (bootstrap) through flash commands to the spi_device memory. - SW verifies the integrity of the image upon reception by reading the spi_device memory. - Ensure the image is executed correctly. + - Verify the LAST_READ_ADDR. ''' stage: V2 si_stage: SV3 @@ -58,9 +59,10 @@ "SPI_DEVICE.MODE.FLASH_EMULATION.COMMANDS", "SPI_DEVICE.MODE.FLASH_EMULATION.READ_COMMAND_PROCESSOR", "SPI_DEVICE.HW.FLASH_EMULATION_BLOCKS", + "SPI_DEVICE.HW.LAST_READ_ADDR", ] tests: ["rom_e2e_smoke"] - bazel: ["//sw/device/silicon_creator/rom/e2e:rom_e2e_smoke"] + bazel: ["//sw/device/silicon_creator/rom/e2e:rom_e2e_smoke", "//sw/device/tests:spi_device_flash_smoketest"] } { name: chip_sw_spi_device_pass_through @@ -72,7 +74,6 @@ - Send random flash commands over the SPI device interface (chip IOs) from the testbench. - Verify the flash commands which pass through spi_host0 are received on chip IOs. - - Verify the LAST_READ_ADDR. - Verify that only the payloads that are not filtered show up on the SPI host interface at chip IOs. - Verify spi_host1 doesn't send out any data from spi_device. @@ -93,7 +94,6 @@ "SPI_DEVICE.MODE.PASSTHROUGH", "SPI_DEVICE.HW.LANES", "SPI_DEVICE.MODE.PASSTHROUGH.CMD_FILTER", - "SPI_DEVICE.HW.LAST_READ_ADDR", ] tests: ["chip_sw_spi_device_pass_through"] bazel: [] diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index b7e2678b99b210..22ce7c0aacc688 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -3008,6 +3008,48 @@ opentitan_test( ], ) +opentitan_test( + name = "spi_device_flash_smoketest", + srcs = ["spi_device_flash_smoketest.c"], + cw310 = new_cw310_params( + test_cmd = """ + --bitstream="{bitstream}" + --bootstrap="{firmware}" + "{firmware:elf}" + """, + test_harness = "//sw/host/tests/chip/spi_device_flash_smoketest", + ), + exec_env = dicts.add( + EARLGREY_SILICON_OWNER_ROM_EXT_ENVS, + { + "//hw/top_earlgrey:fpga_cw310_sival": None, + "//hw/top_earlgrey:fpga_cw310_test_rom": None, + }, + ), + silicon = silicon_params( + test_cmd = """ + --bootstrap={firmware} + "{firmware:elf}" + """, + test_harness = "//sw/host/tests/chip/spi_device_flash_smoketest", + ), + deps = [ + "//hw/top_earlgrey/sw/autogen:top_earlgrey", + "//sw/device/lib/arch:device", + "//sw/device/lib/base:mmio", + "//sw/device/lib/dif:base", + "//sw/device/lib/dif:rv_plic", + "//sw/device/lib/dif:spi_device", + "//sw/device/lib/runtime:hart", + "//sw/device/lib/runtime:irq", + "//sw/device/lib/runtime:log", + "//sw/device/lib/testing:spi_device_testutils", + "//sw/device/lib/testing:spi_flash_testutils", + "//sw/device/lib/testing/test_framework:ottf_main", + "//sw/device/lib/testing/test_framework:status", + ], +) + cc_library( name = "spi_host_flash_test_impl", srcs = ["spi_host_flash_test_impl.c"], @@ -3856,6 +3898,7 @@ opentitan_test( ), exec_env = { "//hw/top_earlgrey:fpga_cw310_test_rom": None, + "//hw/top_earlgrey:fpga_cw310_sival": None, }, deps = [ "//sw/device/lib/arch:device", diff --git a/sw/device/tests/spi_device_flash_smoketest.c b/sw/device/tests/spi_device_flash_smoketest.c new file mode 100644 index 00000000000000..230cde308b499c --- /dev/null +++ b/sw/device/tests/spi_device_flash_smoketest.c @@ -0,0 +1,166 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "sw/device/lib/arch/device.h" +#include "sw/device/lib/base/status.h" +#include "sw/device/lib/dif/dif_spi_device.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/spi_device_testutils.h" +#include "sw/device/lib/testing/spi_flash_emulator.h" +#include "sw/device/lib/testing/spi_flash_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" + +OTTF_DEFINE_TEST_CONFIG(.enable_uart_flow_control = true); + +enum { + kSpiDeviceDatasetSize = 128, + kSpiDeviceFlashAddress = 0x00, +}; + +// A set of bytes to be send out by spi_device. +const uint8_t kSpiTxData[kSpiDeviceDatasetSize] = { + 0xe8, 0x50, 0xc6, 0xb4, 0xbe, 0x16, 0xed, 0x55, 0x16, 0x1d, 0xe6, 0x1c, + 0xde, 0x9f, 0xfd, 0x24, 0x89, 0x81, 0x4d, 0x0d, 0x1a, 0x12, 0x4f, 0x57, + 0xea, 0xd6, 0x6f, 0xc0, 0x7d, 0x46, 0xe7, 0x37, 0x81, 0xd3, 0x8e, 0x16, + 0xad, 0x7b, 0xd0, 0xe2, 0x4f, 0xff, 0x39, 0xe6, 0x71, 0x3c, 0x82, 0x04, + 0xec, 0x3a, 0x27, 0xcc, 0x3d, 0x58, 0x0e, 0x56, 0xd2, 0xd2, 0xb9, 0xa3, + 0xb5, 0x3d, 0xc0, 0x40, 0xba, 0x90, 0x16, 0xd8, 0xe3, 0xa4, 0x22, 0x74, + 0x80, 0xcb, 0x7b, 0xde, 0xd7, 0x3f, 0x4d, 0x93, 0x4d, 0x59, 0x79, 0x88, + 0x24, 0xe7, 0x68, 0x8b, 0x7a, 0x78, 0xb7, 0x07, 0x09, 0x26, 0xcf, 0x6b, + 0x52, 0xd9, 0x4c, 0xd3, 0x33, 0xdf, 0x2e, 0x0d, 0x3b, 0xab, 0x45, 0x85, + 0xc2, 0xc2, 0x19, 0xe5, 0xc7, 0x2b, 0xb0, 0xf6, 0xcb, 0x06, 0xf6, 0xe2, + 0xf5, 0xb1, 0xab, 0xef, 0x6f, 0xd8, 0x23, 0xfd, +}; + +static status_t configure_jedec_id(dif_spi_device_handle_t *spid) { + dif_spi_device_flash_id_t id = { + .device_id = 0x2298, + .manufacturer_id = 0x74, + .continuation_code = 0x17, + .num_continuation_code = 2, + }; + TRY(dif_spi_device_set_flash_id(spid, id)); + return OK_STATUS(); +} + +static status_t configure_flash_mode(dif_spi_device_handle_t *spid) { + dif_spi_device_config_t spi_device_config = { + .clock_polarity = kDifSpiDeviceEdgePositive, + .data_phase = kDifSpiDeviceEdgeNegative, + .tx_order = kDifSpiDeviceBitOrderMsbToLsb, + .rx_order = kDifSpiDeviceBitOrderMsbToLsb, + .device_mode = kDifSpiDeviceModeFlashEmulation, + }; + TRY(dif_spi_device_configure(spid, spi_device_config)); + + dif_spi_device_flash_command_t read_commands[] = { + { + // Slot 0: ReadStatus1 + .opcode = kSpiDeviceFlashOpReadStatus1, + .address_type = kDifSpiDeviceFlashAddrDisabled, + .dummy_cycles = 0, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = true, + }, + { + // Slot 1: ReadStatus2 + .opcode = kSpiDeviceFlashOpReadStatus2, + .address_type = kDifSpiDeviceFlashAddrDisabled, + .dummy_cycles = 0, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = true, + }, + { + // Slot 2: ReadStatus3 + .opcode = kSpiDeviceFlashOpReadStatus3, + .address_type = kDifSpiDeviceFlashAddrDisabled, + .dummy_cycles = 0, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = true, + }, + { + // Slot 3: ReadJedecID + .opcode = kSpiDeviceFlashOpReadJedec, + .address_type = kDifSpiDeviceFlashAddrDisabled, + .dummy_cycles = 0, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = true, + }, + { + // Slot 4: ReadSfdp + .opcode = kSpiDeviceFlashOpReadSfdp, + .address_type = kDifSpiDeviceFlashAddr3Byte, + .dummy_cycles = 8, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = true, + }, + { + // Slot 1: ReadNormal + .opcode = kSpiDeviceFlashOpReadNormal, + .address_type = kDifSpiDeviceFlashAddr3Byte, + .dummy_cycles = 0, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = true, + }, + }; + + for (uint8_t i = 0; i < ARRAYSIZE(read_commands); ++i) { + uint8_t slot = i + kSpiDeviceReadCommandSlotBase; + TRY(dif_spi_device_set_flash_command_slot(spid, slot, kDifToggleEnabled, + read_commands[i])); + } + + dif_spi_device_flash_command_t write_commands[] = { + { + // Slot 1: PageProgram + .opcode = kSpiDeviceFlashOpPageProgram, + .address_type = kDifSpiDeviceFlashAddrCfg, + .payload_io_type = kDifSpiDevicePayloadIoSingle, + .payload_dir_to_host = false, + .upload = true, + .set_busy_status = true, + }, + }; + + for (uint8_t i = 0; i < ARRAYSIZE(write_commands); ++i) { + uint8_t slot = i + (uint8_t)kSpiDeviceWriteCommandSlotBase; + TRY(dif_spi_device_set_flash_command_slot(spid, slot, kDifToggleEnabled, + write_commands[i])); + } + return OK_STATUS(); +} + +bool test_main(void) { + dif_spi_device_handle_t spid; + CHECK_DIF_OK(dif_spi_device_init_handle( + mmio_region_from_addr(TOP_EARLGREY_SPI_DEVICE_BASE_ADDR), &spid)); + + CHECK_STATUS_OK(configure_flash_mode(&spid)); + + LOG_INFO("SYNC: Waiting for write page"); + upload_info_t info; + CHECK_STATUS_OK(spi_device_testutils_wait_for_upload(&spid, &info)); + + CHECK_ARRAYS_EQ(info.data, kSpiTxData, ARRAYSIZE(kSpiTxData)); + CHECK_DIF_OK(dif_spi_device_set_flash_status_registers(&spid, 0x00)); + + CHECK_DIF_OK(dif_spi_device_write_flash_buffer( + &spid, kDifSpiDeviceFlashBufferTypeEFlash, 0, ARRAYSIZE(kSpiTxData), + kSpiTxData)); + + LOG_INFO("SYNC: Waiting for read"); + + busy_spin_micros(1000); + uint32_t address; + CHECK_DIF_OK(dif_spi_device_get_last_read_address(&spid, &address)); + CHECK(address == (kSpiDeviceFlashAddress + kSpiDeviceDatasetSize - 1)); + + return true; +} diff --git a/sw/host/tests/chip/spi_device_flash_smoketest/BUILD b/sw/host/tests/chip/spi_device_flash_smoketest/BUILD new file mode 100644 index 00000000000000..167e64092ebf97 --- /dev/null +++ b/sw/host/tests/chip/spi_device_flash_smoketest/BUILD @@ -0,0 +1,23 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_binary") + +package(default_visibility = ["//visibility:public"]) + +rust_binary( + name = "spi_device_flash_smoketest", + srcs = [ + "src/main.rs", + ], + deps = [ + "//sw/host/opentitanlib", + "//third_party/rust/crates:anyhow", + "//third_party/rust/crates:clap", + "//third_party/rust/crates:humantime", + "//third_party/rust/crates:log", + "//third_party/rust/crates:regex", + "@crate_index//:object", + ], +) diff --git a/sw/host/tests/chip/spi_device_flash_smoketest/src/main.rs b/sw/host/tests/chip/spi_device_flash_smoketest/src/main.rs new file mode 100644 index 00000000000000..3eb59bdb94c770 --- /dev/null +++ b/sw/host/tests/chip/spi_device_flash_smoketest/src/main.rs @@ -0,0 +1,112 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{bail, Result}; +use clap::Parser; +use regex::Regex; +use std::fs; +use std::path::PathBuf; +use std::time::Duration; + +use opentitanlib::app::TransportWrapper; +use opentitanlib::execute_test; +use opentitanlib::io::eeprom::AddressMode; +use opentitanlib::io::spi::{Target, Transfer}; +use opentitanlib::spiflash::sfdp::SectorErase; +use opentitanlib::spiflash::{EraseMode, ReadMode, Sfdp, SpiFlash}; +use opentitanlib::test_utils; +use opentitanlib::test_utils::init::InitializeTest; +use opentitanlib::test_utils::spi_passthru::{ + ConfigJedecId, SfdpData, SpiFlashEraseSector, SpiFlashReadSfdp, SpiFlashWrite, SpiMailboxMap, + SpiMailboxWrite, SpiPassthruSwapMap, StatusRegister, UploadInfo, +}; +use opentitanlib::transport::Capability; +use opentitanlib::uart::console::{ExitStatus, UartConsole}; + +#[derive(Debug, Parser)] +struct Opts { + #[command(flatten)] + init: InitializeTest, + + /// Console receive timeout. + #[arg(long, value_parser = humantime::parse_duration, default_value = "600s")] + timeout: Duration, + + /// Name of the SPI interface to connect to the OTTF console. + #[arg(long, default_value = "BOOTSTRAP")] + spi: String, + + /// Path to the firmware's ELF file, for querying symbol addresses. + #[arg(value_name = "FIRMWARE_ELF")] + firmware_elf: PathBuf, +} + +const SYNC_MSG: &str = r"SYNC:.*\r\n"; + +fn device_tx_rx_test( + opts: &Opts, + transport: &TransportWrapper, + data: &[u8], + address: u32, +) -> Result<()> { + let uart = transport.uart("console")?; + let spi = transport.spi(&opts.spi)?; + + let mut flash = SpiFlash { + // Double the flash size so we can test 3b and 4b addresses. + size: 32 * 1024 * 1024, + ..Default::default() + }; + + // Make sure we're in a mode appropriate for the address. + let mode = if address < 0x1000000 { + AddressMode::Mode3b + } else { + AddressMode::Mode4b + }; + flash.set_address_mode(&*spi, mode)?; + + /* Wait sync message. */ + let _ = UartConsole::wait_for(&*uart, SYNC_MSG, opts.timeout)?; + flash.program(&*spi, address, &data)?; + + /* Wait sync message. */ + let _ = UartConsole::wait_for(&*uart, SYNC_MSG, opts.timeout)?; + let mut buffer = vec![0xff; data.len()]; + flash.read(&*spi, address, &mut buffer)?; + + assert_eq!(buffer, data); + + Ok(()) +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + opts.init.init_logging(); + + /* Load the ELF binary and get the expect data.*/ + let elf_binary = fs::read(&opts.firmware_elf)?; + let object = object::File::parse(&*elf_binary)?; + + let tx_data = test_utils::object::symbol_data(&object, "kSpiTxData")?; + + let transport = opts.init.init_target()?; + execute_test!(device_tx_rx_test, &opts, &transport, &tx_data, 0x00); + + let uart = transport.uart("console")?; + let mut console = UartConsole { + timeout: Some(opts.timeout), + exit_success: Some(Regex::new(r"PASS!\r\n")?), + exit_failure: Some(Regex::new(r"FAIL:")?), + ..Default::default() + }; + + // Now watch the console for the exit conditions. + let result = console.interact(&*uart, None, Some(&mut std::io::stdout()))?; + if result != ExitStatus::ExitSuccess { + bail!("FAIL: {:?}", result); + }; + + Ok(()) +}