From 66d14e5874d904202dc959680a79bf6a303db850 Mon Sep 17 00:00:00 2001 From: Alwalid Salama Date: Thu, 26 Feb 2026 10:26:48 +0100 Subject: [PATCH] nor_flash_backend: add native SystemC NOR flash backend for QSPI Implement new QSPI SystemC model based on Micron Flash (MT25QU256ABA) The model handles the full NOR flash command set (READ, PP4, ERASE, READ_ID, READ_SFDP, status/config registers) via the biflow socket protocol. WEL is managed per-transaction using the last_write_fragment signal driven by the QSPI to support DMA multi-fragment writes. Signed-off-by: Marceron Thomas Signed-off-by: Alwalid Salama --- systemc-components/backends/CMakeLists.txt | 1 + .../backends/nor_flash_backend/CMakeLists.txt | 1 + .../backends/nor_flash_backend/README.md | 87 +++++ .../include/nor_flash_backend.h | 131 ++++++++ .../src/nor_flash_backend.cc | 312 ++++++++++++++++++ 5 files changed, 532 insertions(+) create mode 100644 systemc-components/backends/nor_flash_backend/CMakeLists.txt create mode 100644 systemc-components/backends/nor_flash_backend/README.md create mode 100644 systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h create mode 100644 systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc diff --git a/systemc-components/backends/CMakeLists.txt b/systemc-components/backends/CMakeLists.txt index 02fa885b..e97e47c7 100644 --- a/systemc-components/backends/CMakeLists.txt +++ b/systemc-components/backends/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(char_backend_socket) add_subdirectory(char_backend_stdio) add_subdirectory(loop_back_backend) add_subdirectory(char_backend_file) +add_subdirectory(nor_flash_backend) if (NOT WIN32) add_subdirectory(legacy_char_backend_stdio) endif() diff --git a/systemc-components/backends/nor_flash_backend/CMakeLists.txt b/systemc-components/backends/nor_flash_backend/CMakeLists.txt new file mode 100644 index 00000000..9549d048 --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/CMakeLists.txt @@ -0,0 +1 @@ +gs_create_dymod(nor_flash_backend) \ No newline at end of file diff --git a/systemc-components/backends/nor_flash_backend/README.md b/systemc-components/backends/nor_flash_backend/README.md new file mode 100644 index 00000000..22a9d311 --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/README.md @@ -0,0 +1,87 @@ +# nor_flash_backend + +Native SystemC/TLM NOR flash backend for the QSPI controller. + +## Architecture + +``` +┌─────────────────────┐ biflow_socket ┌──────────────────────┐ +│ QSPI module │ ◄──────────────────────────► │ nor_flash_backend │ +│ (quic::qspi) │ │ │ +│ │ last_write_fragment_signal │ m_binary_content[] │ +│ │ ─────────────────────────── ►│ m_sfdp_data[] │ +└─────────────────────┘ │ m_device_id[] │ + └──────────────────────┘ +``` + +The QSPI sends commands to the backend via `b_transport()`. The backend +processes them and enqueues response bytes back via `biflow_socket.enqueue()`. + +The `last_write_fragment_signal` is driven by the QSPI to indicate whether +the current DMA write fragment is the last one in the chain. The backend uses +this to clear WEL (Write Enable Latch) only after the final fragment, matching +real NOR flash behaviour for multi-fragment DMA writes. + +## Command set + +| Opcode | Command | Behaviour | +|--------|---------|-----------| +| `0x03` | READ | Read from `m_binary_content` | +| `0x13` | READ4 | Read (4-byte address mode) | +| `0x0C` | FAST_READ4 | Fast read (4-byte address) | +| `0x6C` | OUTPUT_FAST_READ4 | Quad output fast read | +| `0xEC` | QIOR4 | Quad I/O read | +| `0x12` | PP4 | Page program (AND write into `m_binary_content`) | +| `0x34` | PP4_QUAD | Quad page program | +| `0x21` | ERASE_4K | Erase 4 KB sector (fill with `0xFF`) | +| `0x20` | ERASE_4K (3B) | Erase 4 KB sector | +| `0x52` | ERASE_32K | Erase 32 KB sector | +| `0xD8` | ERASE_SECTOR | Erase 64 KB sector | +| `0xDC` | ERASE4_SECTOR | Erase 64 KB sector (4-byte address) | +| `0xC7/0x60` | BULK_ERASE | Erase entire flash | +| `0x06` | WRITE_ENABLE | Set WEL in status register | +| `0x04` | WRITE_DISABLE | Clear WEL | +| `0x05` | READ_STATUS | Return status register | +| `0x70` | READ_FLAG_STATUS | Return flag status register | +| `0x65` | READ_ENHANCED_VOL_CFG | Return enhanced volatile config | +| `0x85` | READ_VOLATILE_CFG | Return volatile config | +| `0x9F` | READ_ID | Return device ID bytes | +| `0x5A` | READ_SFDP | Return SFDP table bytes | +| `0x01` | WRITE_STATUS | Write status register | +| `0x61` | WRITE_ENHANCED_VOL_CFG | Write enhanced volatile config | +| `0x81` | WRITE_VOLATILE_CFG | Write volatile config | +| `0xB7` | ENTER_4B_ADDR | No-op (acknowledged) | +| `0xB9/0xAB` | ENTER/EXIT_DPD | No-op (acknowledged) | +| `0x66/0x99` | RESET_ENABLE/RESET | No-op (acknowledged) | +| `0x50` | CLEAR_FLAG_STATUS | Reset flag status register to `FSR_READY` | + +### Write semantics + +NOR flash is write-once per bit: `PP4` performs `memory[addr] &= data` (bits +can only be cleared, not set). Erase restores bits to `0xFF`. + +WEL is cleared automatically after each write or erase operation. For DMA +multi-fragment writes, WEL is only cleared after the **last** fragment +(signalled by `last_write_fragment_signal = true`). + +## Lua configuration + +Configured via `nor_flash_backend_func()` in `qup.lua`: + +```lua +nor_flash_backend_func({ + binary_path = "/path/to/singleimage.bin", + sfdp_data = "0x53,0x46,0x44,0x50,...", -- SFDP table bytes (hex, comma-separated) + device_id = "0x20,0xBB,0x19,0x00", -- JEDEC device ID +}) +``` + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `binary_path` | `""` | Path to the NOR flash binary image | +| `sfdp_data` | `""` | SFDP table content as comma-separated hex bytes | +| `device_id` | `"0x20,0xBB,0x19,0x00"` | JEDEC READ_ID response bytes | + +The binary image is loaded once at `end_of_elaboration()` and kept in a +`std::vector`. Writes and erases modify this in-memory buffer; the +file on disk is not modified. diff --git a/systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h b/systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h new file mode 100644 index 00000000..122f0e8d --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/include/nor_flash_backend.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef NOR_FLASH_BACKEND_H +#define NOR_FLASH_BACKEND_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief SystemC NOR Flash Backend for QSPI + * + * CCI Parameters: + * - binary_path: Path to the binary image file + * - sfdp_data: Comma-separated hex string of SFDP table bytes + * - device_id: Comma-separated hex string of device ID bytes + */ +class nor_flash_backend : public sc_core::sc_module +{ +public: + SCP_LOGGER(); + gs::biflow_socket biflow_socket; + TargetSignalSocket last_write_fragment_signal; + + cci::cci_param p_binary_path; + cci::cci_param p_sfdp_data; + cci::cci_param p_device_id; + + nor_flash_backend(sc_core::sc_module_name name); + + void end_of_elaboration() override; + void b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay); + +private: + // Flash command opcodes + enum FlashOpcode : uint8_t { + RESET_ENABLE_CMD = 0x66, + RESET_CMD = 0x99, + READ_STATUS_CMD = 0x05, + READ_STATUS_2_CMD = 0x3F, + WRITE_STATUS_2_CMD = 0x3E, + WRITE_ENABLE_CMD = 0x06, + WRITE_DISABLE_CMD = 0x04, + READ_ID_CMD = 0x9F, + READ_SFDP_CMD = 0x5A, + READ_CFG1_CMD = 0x35, + READ_FLAG_STATUS_CMD = 0x70, + READ_SECURITY_CMD = 0x2B, + WRITE_STATUS_CMD = 0x01, + ENTER_4B_ADDR_CMD = 0xB7, + READ_CFG_REG_CMD = 0x15, + READ_ENHANCED_VOL_CFG_CMD = 0x65, + ENTER_DPD = 0xB9, + EXIT_DPD = 0xAB, + WRITE_ENHANCED_VOL_CFG_CMD = 0x61, + WRITE_OCTAL_EN_STATUS_2_CMD = 0x31, + CLEAR_OCTAL_EN_STATUS_2_CMD = 0x3E, + ENABLE_8S_8S_8S_MODE_SEQ = 0xE8, + CLEAR_ERR_REGS = 0xB6, + CLEAR_FLAG_STATUS_REG = 0x50, + WRITE_CFG2_CMD = 0x72, + READ_VOLATILE_CFG_CMD = 0x85, + WRITE_VOLATILE_CFG_CMD = 0x81, + READ_CMD = 0x03, + READ4_CMD = 0x13, + FAST_READ4_CMD = 0x0C, + OUTPUT_FAST_READ4_CMD = 0x6C, + QIOR4_CMD = 0xEC, + ERASE4_SECTOR_CMD = 0xDC, + ERASE4_4K_CMD = 0x21, + ERASE_SECTOR_CMD = 0xD8, + ERASE_4K_CMD = 0x20, + ERASE_32K_CMD = 0x52, + BULK_ERASE_CMD = 0xC7, + BULK_ERASE_CMD2 = 0x60, + PP4_CMD = 0x12, + PP4_QUAD_CMD = 0x34 + }; + + std::vector m_binary_content; + std::vector m_sfdp_data; + std::vector m_device_id; + + uint8_t m_status_reg; ///< SR1 [7]=SRWD [6:3]=BP3:0 [1]=WEL [0]=WIP + uint8_t m_flag_status_reg; ///< FSR [7]=READY [6]=ERASE_SUSP [5]=ERASE_ERR [4]=PROG_ERR [3]=VPP_ERR [2]=PROG_SUSP + ///< [1]=PROT_ERR [0]=ADDR_MODE + uint8_t m_volatile_cfg; ///< VCR [7:4]=DUMMY_CYCLES [3]=XIP_DIS [2:1]=rsvd [0]=WRAP_DIS (default 0xFB) + uint8_t m_enhanced_vol_cfg; ///< EVCR [7]=DRV_STR [6]=HOLD_DIS [5]=QUAD_DIS [4]=DUAL_DIS [3:0]=rsvd (default 0xFF = + ///< SPI mode) + bool m_last_write_fragment = true; ///< true = current PP4 is the last DMA fragment → clear WEL after write + + static constexpr uint8_t SR_WEL = 0x02; ///< Status reg: Write Enable Latch (bit 1) + static constexpr uint8_t FSR_READY = 0x80; ///< Flag status: operation complete (bit 7) + static constexpr uint8_t FSR_ERASE_ERR = 0x20; ///< Flag status: erase error (bit 5) + static constexpr uint8_t FSR_PROG_ERR = 0x10; ///< Flag status: program error (bit 4) + static constexpr uint32_t SECTOR_SIZE_4K = 0x1000; ///< 4 KB subsector + static constexpr uint32_t SECTOR_SIZE_32K = 0x8000; ///< 32 KB subsector + static constexpr uint32_t SECTOR_SIZE_64K = 0x10000; ///< 64 KB sector + + bool load_binary(const std::string& path); + std::vector parse_hex_string(const std::string& hex_str); + void send_data_to_qspi(const uint8_t* data, uint32_t length); + void send_single_byte(uint8_t value); + + void handle_read_sfdp(uint64_t address, uint32_t tx_len); + void handle_read_id(uint32_t tx_len); + void handle_read_status(); + void handle_read_flag_status(); + void handle_read_enhanced_vol_cfg(); + void handle_read_volatile_cfg(); + void handle_read_memory(uint64_t address, uint32_t tx_len, uint8_t* out_buf); + void handle_write_memory(uint64_t address, uint8_t* data_ptr, uint32_t tx_len); +}; + +extern "C" void module_register(); + +#endif // NOR_FLASH_BACKEND_H \ No newline at end of file diff --git a/systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc b/systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc new file mode 100644 index 00000000..252fcf85 --- /dev/null +++ b/systemc-components/backends/nor_flash_backend/src/nor_flash_backend.cc @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "nor_flash_backend.h" + +nor_flash_backend::nor_flash_backend(sc_core::sc_module_name name) + : sc_core::sc_module(name) + , biflow_socket("biflow_socket") + , last_write_fragment_signal("last_write_fragment_signal") + , p_binary_path("binary_path", "", "Path to the NOR flash binary image file") + , p_sfdp_data("sfdp_data", "", "Comma-separated hex string of SFDP table bytes") + , p_device_id("device_id", "0x20,0xBB,0x19,0x00", "Comma-separated hex string of device ID bytes") + , m_status_reg(0x00) + , m_flag_status_reg(0x80) + , m_volatile_cfg(0xFB) + , m_enhanced_vol_cfg(0xFF) +{ + biflow_socket.register_b_transport(this, &nor_flash_backend::b_transport); + last_write_fragment_signal.register_value_changed_cb([&](bool value) { m_last_write_fragment = value; }); +} + +void nor_flash_backend::end_of_elaboration() +{ + std::string bin_path = p_binary_path.get_value(); + if (!bin_path.empty()) { + if (!load_binary(bin_path)) { + SCP_ERR(()) << "Failed to load binary: " << bin_path.c_str(); + } else { + SCP_INFO(()) << "Loaded binary '" << bin_path << "' (" << m_binary_content.size() << " bytes)"; + } + } else { + SCP_WARN(()) << "No binary_path configured"; + } + + m_sfdp_data = parse_hex_string(p_sfdp_data.get_value()); + m_device_id = parse_hex_string(p_device_id.get_value()); + + biflow_socket.can_receive_any(); +} + +bool nor_flash_backend::load_binary(const std::string& path) +{ + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) return false; + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + m_binary_content.resize(static_cast(size)); + return static_cast(file.read(reinterpret_cast(m_binary_content.data()), size)); +} + +std::vector nor_flash_backend::parse_hex_string(const std::string& hex_str) +{ + std::vector result; + std::stringstream ss(hex_str); + std::string token; + while (std::getline(ss, token, ',')) { + size_t start = token.find_first_not_of(" \t"); + if (start == std::string::npos) continue; + token = token.substr(start); + size_t end = token.find_last_not_of(" \t"); + if (end != std::string::npos) token = token.substr(0, end + 1); + unsigned long val = std::strtoul(token.c_str(), nullptr, 0); + result.push_back(static_cast(val)); + } + return result; +} + +void nor_flash_backend::send_data_to_qspi(const uint8_t* data, uint32_t length) +{ + /* + * Use enqueue() to send data back to the QSPI controller. + * We must NOT use force_send() here because b_transport is called from + * within the QSPI module's force_send() context — calling force_send() + * back would cause re-entrancy into the QSPI module's receive() method. + * enqueue() queues data for flow-controlled delivery, avoiding this issue. + */ + SCP_DEBUG(()) << "send_data_to_qspi: enqueueing " << length << " bytes"; + for (uint32_t i = 0; i < length; i++) { + biflow_socket.enqueue(data[i]); + } +} + +void nor_flash_backend::send_single_byte(uint8_t value) { send_data_to_qspi(&value, 1); } + +void nor_flash_backend::b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) +{ + uint8_t opcode = static_cast(trans.get_command()); + uint64_t address = trans.get_address(); + uint32_t tx_len = trans.get_data_length(); + uint8_t* data_ptr = trans.get_data_ptr(); + + SCP_DEBUG(()) << "b_transport: opcode=0x" << std::hex << static_cast(opcode) << " addr=0x" << address + << " len=" << std::dec << tx_len; + + switch (opcode) { + // ---- READ commands that return data ---- + case READ_SFDP_CMD: + SCP_DEBUG(()) << "READ_SFDP_CMD addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_sfdp(address, tx_len); + break; + case READ_ID_CMD: + SCP_DEBUG(()) << "READ_ID_CMD len=" << tx_len; + handle_read_id(tx_len); + break; + case READ_STATUS_CMD: + SCP_DEBUG(()) << "READ_STATUS_CMD status_reg=0x" << std::hex << static_cast(m_status_reg); + handle_read_status(); + break; + case READ_FLAG_STATUS_CMD: + SCP_DEBUG(()) << "READ_FLAG_STATUS_CMD flag=0x" << std::hex << static_cast(m_flag_status_reg); + handle_read_flag_status(); + break; + case READ_ENHANCED_VOL_CFG_CMD: + SCP_DEBUG(()) << "READ_ENHANCED_VOL_CFG_CMD val=0x" << std::hex << static_cast(m_enhanced_vol_cfg); + handle_read_enhanced_vol_cfg(); + break; + case READ_VOLATILE_CFG_CMD: + SCP_DEBUG(()) << "READ_VOLATILE_CFG_CMD val=0x" << std::hex << static_cast(m_volatile_cfg); + handle_read_volatile_cfg(); + break; + case READ_CMD: + SCP_DEBUG(()) << "READ_CMD addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_memory(address, tx_len, nullptr); + break; + + // ---- DMA read commands ---- + case READ4_CMD: + case FAST_READ4_CMD: + SCP_DEBUG(()) << "READ4/FAST_READ4 addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_memory(address, tx_len, data_ptr); + break; + + case OUTPUT_FAST_READ4_CMD: + case QIOR4_CMD: + SCP_DEBUG(()) << "OUTPUT_FAST_READ4/QIOR4 addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_read_memory(address, tx_len, data_ptr); + break; + + // ---- WRITE commands ---- + case WRITE_ENABLE_CMD: + SCP_DEBUG(()) << "WRITE_ENABLE_CMD"; + m_status_reg |= 0x02; // set WEL (bit 1) + break; + case WRITE_DISABLE_CMD: + SCP_DEBUG(()) << "WRITE_DISABLE_CMD"; + m_status_reg &= ~0x02; // clear WEL (bit 1) + break; + case WRITE_ENHANCED_VOL_CFG_CMD: + SCP_DEBUG(()) << "WRITE_ENHANCED_VOL_CFG_CMD val=0x" << std::hex + << (data_ptr ? static_cast(data_ptr[0]) : -1); + if (data_ptr) m_enhanced_vol_cfg = data_ptr[0]; + break; + case WRITE_VOLATILE_CFG_CMD: + SCP_DEBUG(()) << "WRITE_VOLATILE_CFG_CMD val=0x" << std::hex << (data_ptr ? static_cast(data_ptr[0]) : -1); + if (data_ptr) m_volatile_cfg = data_ptr[0]; + break; + case PP4_CMD: + case PP4_QUAD_CMD: + SCP_DEBUG(()) << "PP4_CMD addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + handle_write_memory(address, data_ptr, tx_len); + break; + + case WRITE_STATUS_CMD: + if (!data_ptr || !(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "WRITE_STATUS: WEL=0, ignoring"; + break; + } + m_status_reg = data_ptr[0] & ~(SR_WEL | 0x01); // WEL and WIP are read-only + m_status_reg &= ~SR_WEL; // HW clears WEL after operation + break; + + // ---- No-op commands (acknowledged but no action needed) ---- + case ENTER_4B_ADDR_CMD: + case RESET_ENABLE_CMD: + case RESET_CMD: + case ENTER_DPD: + case EXIT_DPD: + case READ_STATUS_2_CMD: + case READ_CFG1_CMD: + case READ_CFG_REG_CMD: + case WRITE_CFG2_CMD: + case READ_SECURITY_CMD: + case WRITE_OCTAL_EN_STATUS_2_CMD: + case ENABLE_8S_8S_8S_MODE_SEQ: + case CLEAR_ERR_REGS: + case CLEAR_FLAG_STATUS_REG: + SCP_DEBUG(()) << "CLEAR_FLAG_STATUS_REG"; + m_flag_status_reg = FSR_READY; + break; + + // ---- Erase commands ---- + case ERASE4_4K_CMD: + case ERASE4_SECTOR_CMD: + case ERASE_4K_CMD: + case ERASE_32K_CMD: + case ERASE_SECTOR_CMD: { + uint32_t erase_size; + if (opcode == ERASE4_4K_CMD || opcode == ERASE_4K_CMD) + erase_size = SECTOR_SIZE_4K; + else if (opcode == ERASE_32K_CMD) + erase_size = SECTOR_SIZE_32K; + else + erase_size = SECTOR_SIZE_64K; + SCP_DEBUG(()) << "ERASE addr=0x" << std::hex << address << " size=" << erase_size; + if (!(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "Erase: WEL=0, setting ERASE_ERR"; + m_flag_status_reg |= FSR_ERASE_ERR; + break; + } + if (!m_binary_content.empty() && address < m_binary_content.size()) { + uint32_t erase_end = std::min(static_cast(address + erase_size), + static_cast(m_binary_content.size())); + memset(&m_binary_content[address], 0xFF, erase_end - address); + } + m_status_reg &= ~SR_WEL; + break; + } + case BULK_ERASE_CMD: + case BULK_ERASE_CMD2: + SCP_DEBUG(()) << "BULK_ERASE"; + if (!(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "Bulk erase: WEL=0, setting ERASE_ERR"; + m_flag_status_reg |= FSR_ERASE_ERR; + break; + } + memset(m_binary_content.data(), 0xFF, m_binary_content.size()); + m_status_reg &= ~SR_WEL; + break; + + default: + SCP_WARN(()) << "Unknown opcode 0x" << std::hex << static_cast(opcode); + break; + } + trans.set_response_status(tlm::TLM_OK_RESPONSE); +} + +void nor_flash_backend::handle_read_sfdp(uint64_t address, uint32_t tx_len) +{ + if (m_sfdp_data.empty()) { + SCP_WARN(()) << "SFDP data not configured"; + return; + } + if (address >= m_sfdp_data.size() || address + tx_len > m_sfdp_data.size()) { + SCP_WARN(()) << "SFDP read out of range: addr=0x" << std::hex << address << " len=" << std::dec << tx_len + << " sfdp_size=" << m_sfdp_data.size(); + return; + } + send_data_to_qspi(&m_sfdp_data[address], tx_len); +} + +void nor_flash_backend::handle_read_id(uint32_t tx_len) +{ + uint32_t n = std::min(tx_len, static_cast(m_device_id.size())); + send_data_to_qspi(m_device_id.data(), n); +} + +void nor_flash_backend::handle_read_status() { send_single_byte(m_status_reg); } + +void nor_flash_backend::handle_read_flag_status() { send_single_byte(m_flag_status_reg); } + +void nor_flash_backend::handle_read_enhanced_vol_cfg() { send_single_byte(m_enhanced_vol_cfg); } + +void nor_flash_backend::handle_read_volatile_cfg() { send_single_byte(m_volatile_cfg); } + +void nor_flash_backend::handle_read_memory(uint64_t address, uint32_t tx_len, uint8_t* out_buf) +{ + if (m_binary_content.empty() || address + tx_len > m_binary_content.size()) { + SCP_WARN(()) << "read out of range: addr=0x" << std::hex << address << " len=" << std::dec << tx_len + << ", returning 0xFF"; + if (out_buf) { + memset(out_buf, 0xFF, tx_len); + } else { + std::vector ff_data(tx_len, 0xFF); + send_data_to_qspi(ff_data.data(), tx_len); + } + return; + } + SCP_DEBUG(()) << "read memory: addr=0x" << std::hex << address << " len=" << std::dec << tx_len; + if (out_buf) { + memcpy(out_buf, &m_binary_content[address], tx_len); + } else { + send_data_to_qspi(&m_binary_content[address], tx_len); + } +} + +void nor_flash_backend::handle_write_memory(uint64_t address, uint8_t* data_ptr, uint32_t tx_len) +{ + if (!data_ptr || m_binary_content.empty()) { + SCP_WARN(()) << "Write: no data or no binary loaded"; + return; + } + if (!(m_status_reg & SR_WEL)) { + SCP_WARN(()) << "Write: WEL=0, setting PROG_ERR"; + m_flag_status_reg |= FSR_PROG_ERR; + return; + } + if (address + tx_len > m_binary_content.size()) { + SCP_DEBUG(()) << "Write: expanding binary to " << std::hex << (address + tx_len); + m_binary_content.resize(static_cast(address + tx_len), 0xFF); + } + for (uint32_t i = 0; i < tx_len; i++) { + m_binary_content[address + i] &= data_ptr[i]; + } + if (m_last_write_fragment) { + m_status_reg &= ~SR_WEL; + } +} + +void module_register() { GSC_MODULE_REGISTER_C(nor_flash_backend); }