blob: 96a5ba1e60ae1f7dc4a1979cf1118e4ac2a0a9e5 [file] [log] [blame]
// Copyright 2017 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 <inttypes.h>
#include <lib/ddk/debug.h>
#include <zircon/syscalls/types.h>
#include <fbl/auto_lock.h>
#include <virtio/virtio.h>
#include "../include/lib/virtio/backends/pci.h"
namespace virtio {
zx_status_t PciLegacyBackend::Init() {
fbl::AutoLock guard(&lock());
fidl::Arena arena;
fuchsia_hardware_pci::wire::Bar bar0;
zx_status_t status = pci().GetBar(arena, 0u, &bar0);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Couldn't get IO bar for device: %s", tag(), zx_status_get_string(status));
return status;
}
if (!bar0.result.is_io()) {
return ZX_ERR_WRONG_TYPE;
}
bar0_base_ =
static_cast<uint16_t>(bar0.result.io().address & std::numeric_limits<uint16_t>::max());
device_cfg_offset_ = bar0_base_ + ((irq_mode() == fuchsia_hardware_pci::InterruptMode::kMsiX)
? VIRTIO_PCI_CONFIG_OFFSET_MSIX
: VIRTIO_PCI_CONFIG_OFFSET_NOMSIX);
zxlogf(DEBUG, "%s: using legacy backend (io base = %#04x, io size = %#04zx, device base = %#04x)",
tag(), bar0_base_, bar0.size, device_cfg_offset_);
return ZX_OK;
}
// value pointers are used to maintain type safety with field width
void PciLegacyBackend::ReadDeviceConfig(uint16_t offset, uint8_t* value) {
fbl::AutoLock guard(&lock());
legacy_io_->Read(static_cast<uint16_t>(device_cfg_offset_ + offset), value);
}
void PciLegacyBackend::ReadDeviceConfig(uint16_t offset, uint16_t* value) {
fbl::AutoLock guard(&lock());
legacy_io_->Read(static_cast<uint16_t>(device_cfg_offset_ + offset), value);
}
void PciLegacyBackend::ReadDeviceConfig(uint16_t offset, uint32_t* value) {
fbl::AutoLock guard(&lock());
legacy_io_->Read(static_cast<uint16_t>(device_cfg_offset_ + offset), value);
}
void PciLegacyBackend::ReadDeviceConfig(uint16_t offset, uint64_t* value) {
fbl::AutoLock guard(&lock());
auto val = reinterpret_cast<uint32_t*>(value);
legacy_io_->Read(static_cast<uint16_t>(device_cfg_offset_ + offset), &val[0]);
legacy_io_->Read(static_cast<uint16_t>(device_cfg_offset_ + offset + sizeof(uint32_t)), &val[1]);
}
void PciLegacyBackend::WriteDeviceConfig(uint16_t offset, uint8_t value) {
fbl::AutoLock guard(&lock());
legacy_io_->Write(static_cast<uint16_t>(device_cfg_offset_ + offset), value);
}
void PciLegacyBackend::WriteDeviceConfig(uint16_t offset, uint16_t value) {
fbl::AutoLock guard(&lock());
legacy_io_->Write(static_cast<uint16_t>(device_cfg_offset_ + offset), value);
}
void PciLegacyBackend::WriteDeviceConfig(uint16_t offset, uint32_t value) {
fbl::AutoLock guard(&lock());
legacy_io_->Write(static_cast<uint16_t>(device_cfg_offset_ + offset), value);
}
void PciLegacyBackend::WriteDeviceConfig(uint16_t offset, uint64_t value) {
fbl::AutoLock guard(&lock());
auto words = reinterpret_cast<uint32_t*>(&value);
legacy_io_->Write(static_cast<uint16_t>(device_cfg_offset_ + offset), words[0]);
legacy_io_->Write(static_cast<uint16_t>(device_cfg_offset_ + offset + sizeof(uint32_t)),
words[1]);
}
// Get the ring size of a specific index
uint16_t PciLegacyBackend::GetRingSize(uint16_t index) {
fbl::AutoLock guard(&lock());
uint16_t val;
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_QUEUE_SELECT, index);
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_QUEUE_SIZE, &val);
zxlogf(TRACE, "%s: ring %u size = %u", tag(), index, val);
return val;
}
// Set up ring descriptors with the backend.
zx_status_t PciLegacyBackend::SetRing(uint16_t index, uint16_t count, zx_paddr_t pa_desc,
zx_paddr_t pa_avail, zx_paddr_t pa_used) {
fbl::AutoLock guard(&lock());
// Virtio 1.0 section 2.4.2
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_QUEUE_SELECT, index);
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_QUEUE_SIZE, count);
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_QUEUE_PFN, static_cast<uint32_t>(pa_desc / 4096));
// Virtio 1.0 section 4.1.4.8
if (irq_mode() == fuchsia_hardware_pci::InterruptMode::kMsiX) {
uint16_t vector = 0;
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_MSI_CONFIG_VECTOR, PciBackend::kMsiConfigVector);
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_MSI_CONFIG_VECTOR, &vector);
if (vector != PciBackend::kMsiConfigVector) {
zxlogf(ERROR, "MSI-X config vector in invalid state after write: %#x", vector);
return ZX_ERR_BAD_STATE;
}
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_MSI_QUEUE_VECTOR, PciBackend::kMsiQueueVector);
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_MSI_QUEUE_VECTOR, &vector);
if (vector != PciBackend::kMsiQueueVector) {
zxlogf(ERROR, "MSI-X queue vector in invalid state after write: %#x", vector);
return ZX_ERR_BAD_STATE;
}
}
zxlogf(TRACE, "%s: set ring %u (# = %u, addr = %#lx)", tag(), index, count, pa_desc);
return ZX_OK;
}
void PciLegacyBackend::RingKick(uint16_t ring_index) {
fbl::AutoLock guard(&lock());
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_QUEUE_NOTIFY, ring_index);
zxlogf(TRACE, "%s: kicked ring %u", tag(), ring_index);
}
uint64_t PciLegacyBackend::ReadFeatures() {
fbl::AutoLock guard(&lock());
uint32_t val;
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_DEVICE_FEATURES, &val);
// Legacy PCI back-end can only support one feature word.
return uint64_t{val};
}
void PciLegacyBackend::SetFeatures(uint64_t bitmap) {
// Legacy PCI back-end can only support one feature word.
const uint32_t truncated_bitmap = bitmap & UINT32_MAX;
fbl::AutoLock guard(&lock());
uint32_t val;
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_DRIVER_FEATURES, &val);
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_DRIVER_FEATURES, val | truncated_bitmap);
zxlogf(DEBUG, "%s: feature bits %08uh now set", tag(), truncated_bitmap);
}
// Virtio v0.9.5 does not support the FEATURES_OK negotiation so this should
// always succeed.
zx_status_t PciLegacyBackend::ConfirmFeatures() { return ZX_OK; }
void PciLegacyBackend::DeviceReset() {
fbl::AutoLock guard(&lock());
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_DEVICE_STATUS, 0u);
zxlogf(TRACE, "%s: device reset", tag());
}
void PciLegacyBackend::WaitForDeviceReset() {
fbl::AutoLock guard(&lock());
uint8_t status = 0xFF;
while (status != 0) {
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_DEVICE_STATUS, &status);
}
zxlogf(TRACE, "%s: device reset complete", tag());
}
void PciLegacyBackend::SetStatusBits(uint8_t bits) {
fbl::AutoLock guard(&lock());
uint8_t status;
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_DEVICE_STATUS, &status);
legacy_io_->Write(bar0_base_ + VIRTIO_PCI_DEVICE_STATUS, static_cast<uint8_t>(status | bits));
}
void PciLegacyBackend::DriverStatusAck() {
SetStatusBits(VIRTIO_STATUS_ACKNOWLEDGE | VIRTIO_STATUS_DRIVER);
zxlogf(TRACE, "%s: driver acknowledge", tag());
}
void PciLegacyBackend::DriverStatusOk() {
SetStatusBits(VIRTIO_STATUS_DRIVER_OK);
zxlogf(TRACE, "%s: driver ok", tag());
}
uint32_t PciLegacyBackend::IsrStatus() {
fbl::AutoLock guard(&lock());
uint8_t isr_status;
legacy_io_->Read(bar0_base_ + VIRTIO_PCI_ISR_STATUS, &isr_status);
return isr_status & (VIRTIO_ISR_QUEUE_INT | VIRTIO_ISR_DEV_CFG_INT);
}
} // namespace virtio