blob: b61c4696bb96050992d1544e4f1cf65e04bdc1c3 [file] [log] [blame]
// 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_