| // Copyright 2024 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. |
| |
| #ifndef THIRD_PARTY_MPACT_RISCV_RISCV_PLIC_H_ |
| #define THIRD_PARTY_MPACT_RISCV_RISCV_PLIC_H_ |
| |
| #include <cstdint> |
| |
| #include "absl/container/btree_map.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/string_view.h" |
| #include "mpact/sim/generic/data_buffer.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/ref_count.h" |
| #include "mpact/sim/util/memory/memory_interface.h" |
| |
| // This file implements the RiscV PLIC (Platform Level Interrupt Controller). |
| // It has a memory mapped register interface that is used to control the |
| // interrupts to one or more contexts across one or more RiscV cores. |
| // |
| // The interrupt targets (contexts) are notified using the |
| // RiscVPlicIrqInterface interface. Therefore each target much register its |
| // own instance of this interface with the plic using the SetContext method. |
| // |
| // Interrupt sources communicate with the PLIC using the SetInterrupt method. |
| // |
| // The PLIC supports both level and edge triggered interrupts. |
| |
| namespace mpact { |
| namespace sim { |
| namespace riscv { |
| |
| using ::mpact::sim::generic::DataBuffer; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::generic::ReferenceCount; |
| |
| using ::mpact::sim::util::MemoryInterface; |
| |
| class RiscVPlicIrqInterface { |
| public: |
| virtual ~RiscVPlicIrqInterface() = default; |
| virtual void SetIrq(bool irq_value) = 0; |
| }; |
| |
| class RiscVPlic : public MemoryInterface { |
| public: |
| // The constructor takes the number of interrupt sources and the number of |
| // contexts that the PLIC can send interrupts to. A core usually has multiple |
| // contexts, one for each privilege level (machine, supervisor, etc.) that is |
| // capable of receiving and handling interrupts. |
| RiscVPlic(int num_sources, int num_contexts); |
| RiscVPlic() = delete; |
| RiscVPlic(const RiscVPlic &) = delete; |
| RiscVPlic &operator=(const RiscVPlic &) = delete; |
| ~RiscVPlic() override; |
| |
| // Configure the PLIC state according to the source and context configuration |
| // strings. The source configuration string is a semicolon separated list of |
| // <source>=<priority> items, where <source> is the interrupt source number |
| // and <priority> is an non-negative integer priority value. Higher values |
| // have higher priorities. A value of zero disables the source. Any source |
| // that is not configured is disabled by default. However, MMR writes can |
| // change the configured values. |
| // |
| // The context configuration string is a semicolon separated list of |
| // <context>=<threshold>,(<source>,<enable>)+ items, where <context> is the |
| // context number, <source> is the source number, <threshold> is a non |
| // negative integer that is the priority threshold for that context, and |
| // <enable> is a 0 or 1 value that indicates whether the interrupt source is |
| // enabled for that context. Multiple lines for the same context with |
| // different sources is allowed. Any context not configured is assumed to |
| // disable all sources with a zero priority threshold. |
| absl::Status Configure(absl::string_view source_cfg, |
| absl::string_view context_cfg); |
| // Interrupt request from the given interrupt source. |
| void SetInterrupt(int source, bool value, bool is_level); |
| |
| // MemoryInterface overrides. |
| // Non-vector load method. |
| void Load(uint64_t address, DataBuffer *db, Instruction *inst, |
| ReferenceCount *context) override; |
| // Vector load method - this is stubbed out. |
| void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size, |
| DataBuffer *db, Instruction *inst, |
| ReferenceCount *context) override; |
| // Non-vector store method. |
| void Store(uint64_t address, DataBuffer *db) override; |
| // Vector store method - this is stubbed out. |
| void Store(DataBuffer *address, DataBuffer *mask, int el_size, |
| DataBuffer *db) override; |
| |
| void SetContext(int context_no, RiscVPlicIrqInterface *context_if); |
| |
| private: |
| struct GatewayInfo { |
| // The gateway is able to accept the interrupt and send it to the plic core. |
| bool ready; |
| // Pending bit in gateway. Only gets set if the interrupt is level based. |
| bool pending; |
| GatewayInfo() : ready(true), pending(false) {} |
| }; |
| |
| // MMR read/write methods. |
| uint32_t Read(uint32_t offset); |
| void Write(uint32_t offset, uint32_t value); |
| // Interrupt claim. |
| uint32_t ClaimInterrupt(int context); |
| // Signal interrupt completion for the given context and interrupt id. |
| void CompleteInterrupt(int context, uint32_t id); |
| // Set plic core pending interrupt bit and trigger interrupt to context as |
| // needed. |
| void SetPlicPendingInterrupt(int source); |
| // Handling pending bits. |
| void SetPending(int source, bool value); |
| bool IsPending(int source); |
| |
| int num_sources_; |
| int num_contexts_; |
| // Interface to call to write the IRQ line for a context. |
| RiscVPlicIrqInterface **context_if_; |
| // Last value written to the IRQ line for a context. |
| bool *context_irq_ = nullptr; |
| // Source gateway info. |
| GatewayInfo *gateway_info_ = nullptr; |
| // Interrupt priorities by source. |
| uint32_t *interrupt_priority_ = nullptr; |
| // Pending interrupts by source - 32 bits per word. |
| uint32_t *interrupt_pending_ = nullptr; |
| // Enable bits per context per source - 32 bits per word. |
| // Array is organized as interrupt_enabled_[context][source / 32]. |
| uint32_t **interrupt_enabled_ = nullptr; |
| // Priority threshold by context. |
| uint32_t *priority_threshold_ = nullptr; |
| // Interrupt claim/complete register by context. |
| uint32_t *interrupt_claim_complete_ = nullptr; |
| // Map from source to context that has the source enabled. This must be |
| // updated whenever an an enable bit is changed for a context. |
| absl::btree_multimap<int, int> source_to_context_; |
| // Map from context to source for which the context has the source enabled. |
| absl::btree_multimap<int, int> context_to_source_; |
| }; |
| |
| class RiscVPlicSourceInterface : public RiscVPlicIrqInterface { |
| public: |
| RiscVPlicSourceInterface(RiscVPlic *plic, int source, bool is_level); |
| RiscVPlicSourceInterface() = delete; |
| ~RiscVPlicSourceInterface() override = default; |
| void SetIrq(bool irq_value) override { |
| if (plic_ != nullptr) plic_->SetInterrupt(source_, irq_value, is_level_); |
| }; |
| |
| private: |
| RiscVPlic *plic_ = nullptr; |
| int source_ = 0; |
| bool is_level_ = false; |
| }; |
| |
| } // namespace riscv |
| } // namespace sim |
| } // namespace mpact |
| |
| #endif // THIRD_PARTY_MPACT_RISCV_RISCV_PLIC_H_ |