blob: c849b78a1d66ebf3e6a9c90b9fca9bc1e17b92cf [file] [log] [blame]
// Copyright 2019 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 "pci-bus.h"
#include <endian.h>
#include <lib/driver/component/cpp/driver_base.h>
namespace ahci {
PciBus::~PciBus() {}
zx_status_t PciBus::RegRead(size_t offset, uint32_t* val_out) {
*val_out = le32toh(mmio_->Read32(offset));
return ZX_OK;
}
zx_status_t PciBus::RegWrite(size_t offset, uint32_t val) {
mmio_->Write32(htole32(val), offset);
return ZX_OK;
}
zx_status_t PciBus::Configure() {
if (!pci_.is_valid()) {
FDF_LOG(ERROR, "Invalid client to PCI device service.");
return ZX_ERR_NOT_SUPPORTED;
}
// Map register window.
{
const auto result = pci_->GetBar(5);
if (!result.ok()) {
FDF_LOG(ERROR, "Call to GetBar failed: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "GetBar failed: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
if (!result->value()->result.result.is_vmo()) {
FDF_LOG(ERROR, "PCI BAR is not an MMIO BAR.");
return ZX_ERR_WRONG_TYPE;
}
auto mmio = fdf::MmioBuffer::Create(0, result->value()->result.size,
std::move(result->value()->result.result.vmo()),
ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (mmio.is_error()) {
FDF_LOG(ERROR, "Failed to map PCI register window: %s", mmio.status_string());
return mmio.status_value();
}
mmio_ = *std::move(mmio);
}
fuchsia_hardware_pci::wire::DeviceInfo config;
{
const auto result = pci_->GetDeviceInfo();
if (!result.ok()) {
FDF_LOG(ERROR, "Call to GetDeviceInfo failed: %s", result.status_string());
return result.status();
}
config = result->info;
}
// TODO: move this to SATA.
if (config.sub_class != 0x06 && config.base_class == 0x01) { // SATA
FDF_LOG(ERROR, "Device class 0x%x unsupported", config.sub_class);
return ZX_ERR_NOT_SUPPORTED;
}
// FIXME intel devices need to set SATA port enable at config + 0x92
// ahci controller is bus master
{
const auto result = pci_->SetBusMastering(true);
if (!result.ok()) {
FDF_LOG(ERROR, "Call to SetBusMastering failed: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "SetBusMastering failed: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
}
// Request 1 interrupt of any mode.
{
const auto result = pci_->GetInterruptModes();
if (!result.ok()) {
FDF_LOG(ERROR, "Call to GetInterruptModes failed: %s", result.status_string());
return result.status();
}
if (result->modes.msix_count > 0) {
irq_mode_ = fuchsia_hardware_pci::InterruptMode::kMsiX;
} else if (result->modes.msi_count > 0) {
irq_mode_ = fuchsia_hardware_pci::InterruptMode::kMsi;
} else if (result->modes.has_legacy) {
irq_mode_ = fuchsia_hardware_pci::InterruptMode::kLegacy;
} else {
FDF_LOG(ERROR, "No interrupt modes are supported.");
return ZX_ERR_NOT_SUPPORTED;
}
}
{
const auto result = pci_->SetInterruptMode(irq_mode_, 1);
if (!result.ok()) {
FDF_LOG(ERROR, "Call to SetInterruptMode failed: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "SetInterruptMode failed: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
}
// Get bti handle.
{
const auto result = pci_->GetBti(0);
if (!result.ok()) {
FDF_LOG(ERROR, "Call to GetBti failed: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "GetBti failed: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
bti_ = std::move(result->value()->bti);
}
// Get irq handle.
{
const auto result = pci_->MapInterrupt(0);
if (!result.ok()) {
FDF_LOG(ERROR, "Call to MapInterrupt failed: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "MapInterrupt failed: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
irq_ = std::move(result->value()->interrupt);
}
return ZX_OK;
}
zx_status_t PciBus::DmaBufferInit(std::unique_ptr<dma_buffer::ContiguousBuffer>* buffer_out,
size_t size, zx_paddr_t* phys_out, void** virt_out) {
// Allocate memory for the command list, FIS receive area, command table and PRDT.
const size_t buffer_size = fbl::round_up(size, zx_system_get_page_size());
auto buffer_factory = dma_buffer::CreateBufferFactory();
zx_status_t status = buffer_factory->CreateContiguous(bti_, buffer_size, 0, buffer_out);
if (status != ZX_OK) {
return status;
}
*phys_out = (*buffer_out)->phys();
*virt_out = (*buffer_out)->virt();
return ZX_OK;
}
zx_status_t PciBus::BtiPin(uint32_t options, const zx::unowned_vmo& vmo, uint64_t offset,
uint64_t size, zx_paddr_t* addrs, size_t addrs_count, zx::pmt* pmt_out) {
zx_handle_t pmt;
zx_status_t status =
zx_bti_pin(bti_.get(), options, vmo->get(), offset, size, addrs, addrs_count, &pmt);
if (status == ZX_OK) {
*pmt_out = zx::pmt(pmt);
}
return status;
}
zx_status_t PciBus::InterruptWait() {
if (irq_mode_ == fuchsia_hardware_pci::InterruptMode::kLegacy) {
const auto result = pci_->AckInterrupt();
if (!result.ok()) {
FDF_LOG(ERROR, "Call to AckInterrupt failed: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
FDF_LOG(ERROR, "AckInterrupt failed: %s", zx_status_get_string(result->error_value()));
return result->error_value();
}
}
return irq_.wait(nullptr);
}
void PciBus::InterruptCancel() { irq_.destroy(); }
} // namespace ahci