/******************************************************************************* * Copyright (C) 2025 MINRES Technologies GmbH * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. 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. * * 3. 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. * * Contributors: * eyck@minres.com - initial implementation ******************************************************************************/ #include "memory_if.h" #include "iss/arch/riscv_hart_common.h" #include "iss/vm_types.h" #include namespace iss { namespace mem { struct clic_config { uint64_t clic_base{0xc0000000}; unsigned clic_int_ctl_bits{4}; unsigned clic_num_irq{16}; unsigned clic_num_trigger{0}; bool nmode{false}; }; inline void read_reg_with_offset(uint32_t reg, uint8_t offs, uint8_t* const data, unsigned length) { auto reg_ptr = reinterpret_cast(®); switch(offs) { default: for(auto i = 0U; i < length; ++i) *(data + i) = *(reg_ptr + i); break; case 1: for(auto i = 0U; i < length; ++i) *(data + i) = *(reg_ptr + 1 + i); break; case 2: for(auto i = 0U; i < length; ++i) *(data + i) = *(reg_ptr + 2 + i); break; case 3: *data = *(reg_ptr + 3); break; } } inline void write_reg_with_offset(uint32_t& reg, uint8_t offs, const uint8_t* const data, unsigned length) { auto reg_ptr = reinterpret_cast(®); switch(offs) { default: for(auto i = 0U; i < length; ++i) *(reg_ptr + i) = *(data + i); break; case 1: for(auto i = 0U; i < length; ++i) *(reg_ptr + 1 + i) = *(data + i); break; case 2: for(auto i = 0U; i < length; ++i) *(reg_ptr + 2 + i) = *(data + i); break; case 3: *(reg_ptr + 3) = *data; break; } } template struct clic : public memory_elem { using this_class = clic; using reg_t = WORD_TYPE; constexpr static unsigned WORD_LEN = sizeof(WORD_TYPE) * 8; clic(arch::priv_if hart_if, clic_config cfg) : hart_if(hart_if) , cfg(cfg) { clic_int_reg.resize(cfg.clic_num_irq, clic_int_reg_t{.raw = 0}); clic_cfg_reg = 0x30; clic_mact_lvl = clic_mprev_lvl = (1 << (cfg.clic_int_ctl_bits)) - 1; clic_uact_lvl = clic_uprev_lvl = (1 << (cfg.clic_int_ctl_bits)) - 1; hart_if.csr_rd_cb[arch::mtvt] = MK_CSR_RD_CB(read_plain); hart_if.csr_wr_cb[arch::mtvt] = MK_CSR_WR_CB(write_xtvt); // hart_if.csr_rd_cb[mxnti] = MK_CSR_RD_CB(read_plain(a,r);}; // hart_if.csr_wr_cb[mxnti] = MK_CSR_WR_CB(write_plain(a,r);}; hart_if.csr_rd_cb[arch::mintstatus] = MK_CSR_RD_CB(read_intstatus); hart_if.csr_wr_cb[arch::mintstatus] = MK_CSR_WR_CB(write_null); // hart_if.csr_rd_cb[mscratchcsw] = MK_CSR_RD_CB(read_plain(a,r);}; // hart_if.csr_wr_cb[mscratchcsw] = MK_CSR_WR_CB(write_plain(a,r);}; // hart_if.csr_rd_cb[mscratchcswl] = MK_CSR_RD_CB(read_plain(a,r);}; // hart_if.csr_wr_cb[mscratchcswl] = MK_CSR_WR_CB(write_plain(a,r);}; hart_if.csr_rd_cb[arch::mintthresh] = MK_CSR_RD_CB(read_plain); hart_if.csr_wr_cb[arch::mintthresh] = MK_CSR_WR_CB(write_intthresh); if(cfg.nmode) { hart_if.csr_rd_cb[arch::utvt] = MK_CSR_RD_CB(read_plain); hart_if.csr_wr_cb[arch::utvt] = MK_CSR_WR_CB(write_xtvt); hart_if.csr_rd_cb[arch::uintstatus] = MK_CSR_RD_CB(read_intstatus); hart_if.csr_wr_cb[arch::uintstatus] = MK_CSR_WR_CB(write_null); hart_if.csr_rd_cb[arch::uintthresh] = MK_CSR_RD_CB(read_plain); hart_if.csr_wr_cb[arch::uintthresh] = MK_CSR_WR_CB(write_intthresh); } hart_if.csr[arch::mintthresh] = (1 << (cfg.clic_int_ctl_bits)) - 1; hart_if.csr[arch::uintthresh] = (1 << (cfg.clic_int_ctl_bits)) - 1; } ~clic() = default; memory_if get_mem_if() override { return memory_if{.rd_mem{util::delegate::from(this)}, .wr_mem{util::delegate::from(this)}}; } void set_next(memory_if mem) override { down_stream_mem = mem; } std::tuple get_range() override { return {cfg.clic_base, cfg.clic_base + 0x7fff}; } private: iss::status read_mem(iss::access_type access, uint64_t addr, unsigned length, uint8_t* data) { if(addr >= cfg.clic_base && (addr + length) < (cfg.clic_base + 0x8000)) return read_clic(addr, length, data); return down_stream_mem.rd_mem(access, addr, length, data); } iss::status write_mem(iss::access_type access, uint64_t addr, unsigned length, uint8_t const* data) { if(addr >= cfg.clic_base && (addr + length) < (cfg.clic_base + 0x8000)) return write_clic(addr, length, data); return down_stream_mem.wr_mem(access, addr, length, data); } iss::status read_clic(uint64_t addr, unsigned length, uint8_t* data); iss::status write_clic(uint64_t addr, unsigned length, uint8_t const* data); iss::status write_null(unsigned addr, reg_t val) { return iss::status::Ok; } iss::status read_plain(unsigned addr, reg_t& val) { val = hart_if.csr[addr]; return iss::Ok; } iss::status write_xtvt(unsigned addr, reg_t val) { hart_if.csr[addr] = val & ~0x3fULL; return iss::Ok; } iss::status read_cause(unsigned addr, reg_t& val); iss::status write_cause(unsigned addr, reg_t val); iss::status read_intstatus(unsigned addr, reg_t& val); iss::status write_intthresh(unsigned addr, reg_t val); protected: arch::priv_if hart_if; memory_if down_stream_mem; clic_config cfg; uint8_t clic_cfg_reg{0}; std::array clic_inttrig_reg; union clic_int_reg_t { struct { uint8_t ip; uint8_t ie; uint8_t attr; uint8_t ctl; }; uint32_t raw; }; std::vector clic_int_reg; uint8_t clic_mprev_lvl{0}, clic_uprev_lvl{0}; uint8_t clic_mact_lvl{0}, clic_uact_lvl{0}; }; template iss::status clic::read_clic(uint64_t addr, unsigned length, uint8_t* const data) { if(addr == cfg.clic_base) { // cliccfg *data = clic_cfg_reg; for(auto i = 1; i < length; ++i) *(data + i) = 0; } else if(addr >= (cfg.clic_base + 0x40) && (addr + length) <= (cfg.clic_base + 0x40 + cfg.clic_num_trigger * 4)) { // clicinttrig auto offset = ((addr & 0x7fff) - 0x40) / 4; read_reg_with_offset(clic_inttrig_reg[offset], addr & 0x3, data, length); } else if(addr >= (cfg.clic_base + 0x1000) && (addr + length) <= (cfg.clic_base + 0x1000 + cfg.clic_num_irq * 4)) { // clicintip/clicintie/clicintattr/clicintctl auto offset = ((addr & 0x7fff) - 0x1000) / 4; read_reg_with_offset(clic_int_reg[offset].raw, addr & 0x3, data, length); } else { for(auto i = 0U; i < length; ++i) *(data + i) = 0; } return iss::Ok; } template iss::status clic::write_clic(uint64_t addr, unsigned length, const uint8_t* const data) { if(addr == cfg.clic_base) { // cliccfg clic_cfg_reg = (clic_cfg_reg & ~0x1e) | (*data & 0x1e); } else if(addr >= (cfg.clic_base + 0x40) && (addr + length) <= (cfg.clic_base + 0x40 + cfg.clic_num_trigger * 4)) { // clicinttrig auto offset = ((addr & 0x7fff) - 0x40) / 4; write_reg_with_offset(clic_inttrig_reg[offset], addr & 0x3, data, length); } else if(addr >= (cfg.clic_base + 0x1000) && (addr + length) <= (cfg.clic_base + 0x1000 + cfg.clic_num_irq * 4)) { // clicintip/clicintie/clicintattr/clicintctl auto offset = ((addr & 0x7fff) - 0x1000) / 4; write_reg_with_offset(clic_int_reg[offset].raw, addr & 0x3, data, length); clic_int_reg[offset].raw &= 0xf0c70101; // clicIntCtlBits->0xf0, clicintattr->0xc7, clicintie->0x1, clicintip->0x1 } return iss::Ok; } template iss::status clic::read_cause(unsigned addr, reg_t& val) { if((hart_if.csr[arch::mtvec] & 0x3) == 3) { val = hart_if.csr[addr] & (1UL << (sizeof(reg_t) * 8) | (hart_if.mcause_max_irq - 1) | (0xfUL << 16)); auto mode = (addr >> 8) & 0x3; switch(mode) { case 0: val |= clic_uprev_lvl << 16; val |= hart_if.state.mstatus.UPIE << 27; break; default: val |= clic_mprev_lvl << 16; val |= hart_if.state.mstatus.MPIE << 27; val |= hart_if.state.mstatus.MPP << 28; break; } } else val = hart_if.csr[addr] & ((1UL << (sizeof(WORD_TYPE) * 8 - 1)) | (hart_if.mcause_max_irq - 1)); return iss::Ok; } template iss::status clic::write_cause(unsigned addr, reg_t val) { if((hart_if.csr[arch::mtvec] & 0x3) == 3) { auto mask = ((1UL << (sizeof(WORD_TYPE) * 8 - 1)) | (hart_if.mcause_max_irq - 1) | (0xfUL << 16)); hart_if.csr[addr] = (val & mask) | (hart_if.csr[addr] & ~mask); auto mode = (addr >> 8) & 0x3; switch(mode) { case 0: clic_uprev_lvl = ((val >> 16) & 0xff) | (1 << (8 - cfg.clic_int_ctl_bits)) - 1; hart_if.state.mstatus.UPIE = (val >> 27) & 0x1; break; default: clic_mprev_lvl = ((val >> 16) & 0xff) | (1 << (8 - cfg.clic_int_ctl_bits)) - 1; hart_if.state.mstatus.MPIE = (val >> 27) & 0x1; hart_if.state.mstatus.MPP = (val >> 28) & 0x3; break; } } else { auto mask = ((1UL << (sizeof(WORD_TYPE) * 8 - 1)) | (hart_if.mcause_max_irq - 1)); hart_if.csr[addr] = (val & mask) | (hart_if.csr[addr] & ~mask); } return iss::Ok; } template iss::status clic::read_intstatus(unsigned addr, reg_t& val) { auto mode = (addr >> 8) & 0x3; val = clic_uact_lvl & 0xff; if(mode == 0x3) val += (clic_mact_lvl & 0xff) << 24; return iss::Ok; } template iss::status clic::write_intthresh(unsigned addr, reg_t val) { hart_if.csr[addr] = (val & 0xff) | (1 << (cfg.clic_int_ctl_bits)) - 1; return iss::Ok; } } // namespace mem } // namespace iss