blob: 88d6a80da8b75daa9870a33a5a6891f102c58060 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <inttypes.h>
#include <lib/virtio/backends/pci.h>
#include <lib/virtio/device.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <zircon/status.h>
#include <memory>
#include <utility>
#include <ddk/debug.h>
#include <hw/inout.h>
#include <pretty/hexdump.h>
namespace virtio {
Device::Device(zx_device_t* bus_device, zx::bti bti, std::unique_ptr<Backend> backend)
: bti_(std::move(bti)), backend_(std::move(backend)), bus_device_(bus_device) {
zxlogf(TRACE, "%s: entry", __func__);
device_ops_.version = DEVICE_OPS_VERSION;
}
Device::~Device() { zxlogf(TRACE, "%s: exit", __func__); }
void Device::Unbind(ddk::UnbindTxn txn) { txn.Reply(); }
void Device::Release() {
irq_thread_should_exit_.store(true, std::memory_order_release);
thrd_join(irq_thread_, nullptr);
backend_.reset();
}
void Device::IrqWorker() {
const auto irq_mode = backend_->InterruptMode();
ZX_DEBUG_ASSERT(irq_mode == PCI_IRQ_MODE_LEGACY || irq_mode == PCI_IRQ_MODE_MSI_X);
zxlogf(DEBUG, "%s: starting %s irq worker", tag(),
(irq_mode == PCI_IRQ_MODE_LEGACY) ? "legacy" : "msi-x");
while (backend_->InterruptValid() == ZX_OK) {
auto result = backend_->WaitForInterrupt();
if (!result.is_ok()) {
// Timeouts are fine, but need to continue because there's nothing to ack.
if (result.status_value() == ZX_ERR_TIMED_OUT) {
continue;
}
zxlogf(DEBUG, "%s: error while waiting for interrupt: %s", tag(), result.status_string());
break;
}
// Ack the interrupt we saw based on the key returned from the port. For legacy interrupts
// this will always be 0, but MSI-X will depend on the number of vectors configured.
auto key = result.value();
backend_->InterruptAck(key);
// Read the status before completing the interrupt in case
// another interrupt fires and changes the status.
if (irq_mode == PCI_IRQ_MODE_LEGACY) {
uint32_t irq_status = IsrStatus();
zxlogf(TRACE, "%s: irq_status: %#x\n", __func__, irq_status);
// Since we handle both interrupt types here it's possible for a
// spurious interrupt if they come in sequence and we check IsrStatus
// after both have been triggered.
if (irq_status) {
if (irq_status & VIRTIO_ISR_QUEUE_INT) { /* used ring update */
IrqRingUpdate();
}
if (irq_status & VIRTIO_ISR_DEV_CFG_INT) { /* config change */
IrqConfigChange();
}
}
} else {
// MSI-X
zxlogf(TRACE, "%s: irq key: %u\n", __func__, key);
switch (key) {
case PciBackend::kMsiConfigVector:
IrqConfigChange();
break;
case PciBackend::kMsiQueueVector:
IrqRingUpdate();
break;
}
}
if (irq_thread_should_exit_.load(std::memory_order_relaxed)) {
break;
}
}
}
int Device::IrqThreadEntry(void* arg) {
Device* d = static_cast<Device*>(arg);
d->IrqWorker();
return 0;
}
void Device::StartIrqThread() {
thrd_create_with_name(&irq_thread_, IrqThreadEntry, this, "virtio-irq-thread");
}
zx_status_t Device::CopyDeviceConfig(void* _buf, size_t len) const {
assert(_buf);
for (uint16_t i = 0; i < len; i++) {
backend_->ReadDeviceConfig(i, static_cast<uint8_t*>(_buf) + i);
}
return ZX_OK;
}
} // namespace virtio