blob: df8d6480404134c46ec7bb452408b94bc87a0da3 [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/ktrace.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:
zx_status_t Init() { return bitmap_.Reset(kNumBits); }
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:
zx_status_t Init() {
Guard<SpinLock, IrqSave> lock{&lock_};
return bitmap_.Init();
}
// 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); }
// Waits for an interrupt.
zx_status_t Wait(zx_time_t deadline, StateInvalidator* invalidator) {
if (invalidator != nullptr) {
invalidator->Invalidate();
}
ktrace_vcpu(TAG_VCPU_BLOCK, VCPU_INTERRUPT);
do {
zx_status_t status = event_.Wait(Deadline::no_slack(deadline));
if (status == ZX_ERR_TIMED_OUT) {
break;
} else if (status != ZX_OK) {
ktrace_vcpu(TAG_VCPU_UNBLOCK, VCPU_INTERRUPT);
return ZX_ERR_CANCELED;
}
} while (!Pending());
ktrace_vcpu(TAG_VCPU_UNBLOCK, VCPU_INTERRUPT);
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_