blob: 464e57119aaf5a3aa2ae3d2914b9b81d773ab467 [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 "../include/lib/virtio/backends/pci.h"
#include <assert.h>
#include <lib/ddk/debug.h>
#include <lib/device-protocol/pci.h>
#include <lib/zx/handle.h>
#include <lib/zx/interrupt.h>
#include <lib/zx/port.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls/port.h>
#include <algorithm>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <virtio/virtio.h>
namespace virtio {
namespace fpci = fuchsia_hardware_pci;
PciBackend::PciBackend(ddk::Pci pci, fuchsia_hardware_pci::wire::DeviceInfo info)
: pci_(std::move(pci)), info_(info) {
snprintf(tag_, sizeof(tag_), "pci[%02x:%02x.%1x]", info_.bus_id, info_.dev_id, info_.func_id);
}
zx_status_t PciBackend::Bind() {
zx::interrupt interrupt;
zx_status_t status = zx::port::create(/*options=*/ZX_PORT_BIND_TO_INTERRUPT, &wait_port_);
if (status != ZX_OK) {
zxlogf(ERROR, "cannot create wait port: %s", zx_status_get_string(status));
return status;
}
// enable bus mastering
status = pci().SetBusMastering(true);
if (status != ZX_OK) {
zxlogf(ERROR, "cannot enable bus master: %s", zx_status_get_string(status));
return status;
}
status = ConfigureInterruptMode();
if (status != ZX_OK) {
zxlogf(ERROR, "cannot configure IRQs: %s", zx_status_get_string(status));
return status;
}
return Init();
}
// Virtio supports both a legacy INTx IRQ as well as MSI-X. In the former case,
// a driver is required to read the ISR_STATUS register to determine what sort
// of event has happened. This can be an expensive operation depending on the
// hypervisor / emulation environment. For MSI-X a device 'should' support 2 or
// more vector table entries, but is not required to. Since we only have one IRQ
// worker in the backends at this time it's not that important that we allocate
// a vector per ring, so for now the ideal is roughly two vectors, one being for
// config changes and the other for rings.
zx_status_t PciBackend::ConfigureInterruptMode() {
// This looks a lot like something ConfigureInterruptMode was designed for, but
// since we have a specific requirement to use MSI-X if and only if we have 2
// vectors it means rolling it by hand.
fpci::InterruptMode mode = fpci::InterruptMode::kMsiX;
uint32_t irq_cnt = 2;
zx_status_t status = pci().SetInterruptMode(mode, irq_cnt);
if (status != ZX_OK) {
mode = fpci::InterruptMode::kLegacy;
irq_cnt = 1;
status = pci().SetInterruptMode(mode, irq_cnt);
if (status != ZX_OK) {
irq_cnt = 0;
}
}
if (irq_cnt == 0) {
zxlogf(ERROR, "Failed to configure a virtio IRQ mode: %s", zx_status_get_string(status));
return status;
}
// Legacy only supports 1 IRQ, but for MSI-X we only need 2
for (uint32_t i = 0; i < irq_cnt; i++) {
zx::interrupt interrupt = {};
status = pci().MapInterrupt(i, &interrupt);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to map interrupt %u: %s", i, zx_status_get_string(status));
return status;
}
// Use the interrupt index as the key so we can ack the correct interrupt after
// a port wait.
status = interrupt.bind(wait_port_, /*key=*/i, /*options=*/0);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to bind interrupt %u: %s", i, zx_status_get_string(status));
return status;
}
irq_handles().push_back(std::move(interrupt));
}
irq_mode() = mode;
zxlogf(DEBUG, "using %s IRQ mode (irq_cnt = %u)",
(irq_mode() == fpci::InterruptMode::kMsiX ? "MSI-X" : "legacy"), irq_cnt);
return ZX_OK;
}
zx::result<uint32_t> PciBackend::WaitForInterrupt() {
zx_port_packet packet;
zx_status_t status = wait_port_.wait(zx::deadline_after(zx::msec(100)), &packet);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(packet.key);
}
void PciBackend::InterruptAck(uint32_t key) {
ZX_DEBUG_ASSERT(key < irq_handles().size());
irq_handles()[key].ack();
if (irq_mode() == fpci::InterruptMode::kLegacy) {
pci().AckInterrupt();
}
}
} // namespace virtio