| // Copyright 2017 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #ifndef ZIRCON_KERNEL_HYPERVISOR_INCLUDE_HYPERVISOR_INTERRUPT_TRACKER_H_ |
| #define ZIRCON_KERNEL_HYPERVISOR_INCLUDE_HYPERVISOR_INTERRUPT_TRACKER_H_ |
| |
| #include <lib/fit/defer.h> |
| #include <lib/ktrace.h> |
| #include <lib/zx/status.h> |
| |
| #include <bitmap/raw-bitmap.h> |
| #include <bitmap/storage.h> |
| #include <hypervisor/ktrace.h> |
| #include <hypervisor/state_invalidator.h> |
| #include <kernel/auto_lock.h> |
| #include <kernel/event.h> |
| |
| namespace hypervisor { |
| |
| // InterruptBitmap relies on these precise enum values, do not modify without adjusting below. |
| enum class InterruptType : uint8_t { |
| INACTIVE = 0, |
| VIRTUAL = 1, |
| PHYSICAL = 2, |
| }; |
| |
| template <uint32_t N> |
| class InterruptBitmap { |
| public: |
| InterruptBitmap() { |
| zx_status_t result = bitmap_.Reset(kNumBits); |
| // `bitmap_` uses static storage, so `Reset` cannot fail. |
| DEBUG_ASSERT(result == ZX_OK); |
| } |
| |
| InterruptType Get(uint32_t vector) const { |
| if (vector >= N) { |
| DEBUG_ASSERT(false); |
| return InterruptType::INACTIVE; |
| } |
| size_t bitoff = vector * 2; |
| size_t first; |
| bool inactive = bitmap_.Scan(bitoff, bitoff + 2, false, &first); |
| if (inactive) { |
| return InterruptType::INACTIVE; |
| } |
| return bitoff == first ? InterruptType::VIRTUAL : InterruptType::PHYSICAL; |
| } |
| |
| void Set(uint32_t vector, InterruptType type) { |
| if (vector >= N) { |
| DEBUG_ASSERT(false); |
| return; |
| } |
| size_t bitoff = vector * 2; |
| bitmap_.Clear(bitoff, bitoff + 2); |
| if (type != InterruptType::INACTIVE) { |
| auto state_bit = static_cast<size_t>(type) - 1; |
| bitmap_.SetOne(bitoff + state_bit); |
| } |
| } |
| |
| void Clear(uint32_t min, uint32_t max) { |
| if (max < min || max >= N) { |
| DEBUG_ASSERT(false); |
| return; |
| } |
| bitmap_.Clear(min * 2, max * 2); |
| } |
| |
| InterruptType Scan(uint32_t* vector) { |
| size_t bitoff; |
| #if ARCH_ARM64 |
| bool is_empty = bitmap_.Scan(0, kNumBits, false, &bitoff); |
| #elif ARCH_X86 |
| bool is_empty = bitmap_.ReverseScan(0, kNumBits, false, &bitoff); |
| #endif |
| if (is_empty) { |
| return InterruptType::INACTIVE; |
| } |
| *vector = static_cast<uint32_t>(bitoff / 2); |
| if (bitoff % 2 == 0) { |
| return InterruptType::VIRTUAL; |
| } else { |
| return InterruptType::PHYSICAL; |
| } |
| } |
| |
| private: |
| static constexpr uint32_t kNumBits = N * 2; |
| bitmap::RawBitmapGeneric<bitmap::FixedStorage<kNumBits>> bitmap_; |
| }; |
| |
| // |N| is the maximum number of interrupts to be tracked. |
| template <uint32_t N> |
| class InterruptTracker { |
| public: |
| // Returns whether there are pending interrupts. |
| bool Pending() { |
| uint32_t vector; |
| Guard<SpinLock, IrqSave> lock{&lock_}; |
| return bitmap_.Scan(&vector) != InterruptType::INACTIVE; |
| } |
| |
| // Clears all vectors in the range [min, max). |
| void Clear(uint32_t min, uint32_t max) { |
| Guard<SpinLock, IrqSave> lock{&lock_}; |
| bitmap_.Clear(min, max); |
| } |
| |
| // Pops the specified vector, if it is pending. |
| InterruptType TryPop(uint32_t vector) { |
| Guard<SpinLock, IrqSave> lock{&lock_}; |
| InterruptType type = bitmap_.Get(vector); |
| if (type != InterruptType::INACTIVE) { |
| bitmap_.Set(vector, InterruptType::INACTIVE); |
| } |
| return type; |
| } |
| |
| // Pops the highest priority interrupt. |
| InterruptType Pop(uint32_t* vector) { |
| Guard<SpinLock, IrqSave> lock{&lock_}; |
| InterruptType type = bitmap_.Scan(vector); |
| if (type != InterruptType::INACTIVE) { |
| bitmap_.Set(*vector, InterruptType::INACTIVE); |
| } |
| return type; |
| } |
| |
| // Tracks the given interrupt. |
| void Track(uint32_t vector, InterruptType type) { |
| Guard<SpinLock, IrqSave> lock{&lock_}; |
| bitmap_.Set(vector, type); |
| } |
| |
| // Tracks the given interrupt, and signals any waiters. |
| void Interrupt(uint32_t vector, InterruptType type) { |
| Track(vector, type); |
| event_.Signal(); |
| } |
| |
| // Tracks the given virtual interrupt, and signals any waiters. |
| void VirtualInterrupt(uint32_t vector) { Interrupt(vector, hypervisor::InterruptType::VIRTUAL); } |
| |
| // Cancels a wait for an interrupt. |
| // |
| // We signal `ZX_ERR_INTERNAL_INTR_RETRY`, so that if the status is propagated |
| // to the syscall-layer, we will retry the syscall. |
| void Cancel() { event_.Signal(ZX_ERR_INTERNAL_INTR_RETRY); } |
| |
| // Waits for an interrupt. |
| zx::status<> Wait(zx_time_t deadline, StateInvalidator* invalidator = nullptr) { |
| if (invalidator != nullptr) { |
| invalidator->Invalidate(); |
| } |
| ktrace_vcpu(TAG_VCPU_BLOCK, VCPU_INTERRUPT); |
| auto defer = fit::defer([] { ktrace_vcpu(TAG_VCPU_UNBLOCK, VCPU_INTERRUPT); }); |
| do { |
| zx_status_t status = event_.Wait(Deadline::no_slack(deadline)); |
| switch (status) { |
| case ZX_OK: |
| continue; |
| case ZX_ERR_TIMED_OUT: |
| // If the event timed out, return ZX_OK to resume the VCPU. |
| return zx::ok(); |
| default: |
| // Otherwise, return the status. |
| return zx::error(status); |
| } |
| } while (!Pending()); |
| return zx::ok(); |
| } |
| |
| private: |
| AutounsignalEvent event_; |
| DECLARE_SPINLOCK(InterruptTracker) lock_; |
| InterruptBitmap<N> bitmap_ TA_GUARDED(lock_); |
| }; |
| |
| } // namespace hypervisor |
| |
| #endif // ZIRCON_KERNEL_HYPERVISOR_INCLUDE_HYPERVISOR_INTERRUPT_TRACKER_H_ |