blob: 5d46f639778c1aba2b72f534672633ad0f15334b [file] [log] [blame]
// Copyright 2016 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
#if WITH_KERNEL_PCIE
#include "object/pci_device_dispatcher.h"
#include <assert.h>
#include <lib/counters.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <kernel/auto_lock.h>
#include <object/pci_interrupt_dispatcher.h>
#include <object/process_dispatcher.h>
KCOUNTER(dispatcher_pci_device_create_count, "dispatcher.pci_device.create")
KCOUNTER(dispatcher_pci_device_destroy_count, "dispatcher.pci_device.destroy")
zx_status_t PciDeviceDispatcher::Create(uint32_t index, zx_pcie_device_info_t* out_info,
KernelHandle<PciDeviceDispatcher>* out_handle,
zx_rights_t* out_rights) {
auto bus_drv = PcieBusDriver::GetDriver();
if (bus_drv == nullptr)
return ZX_ERR_BAD_STATE;
auto device = bus_drv->GetNthDevice(index);
if (device == nullptr)
return ZX_ERR_OUT_OF_RANGE;
fbl::AllocChecker ac;
KernelHandle new_handle(
fbl::AdoptRef(new (&ac) PciDeviceDispatcher(ktl::move(device), out_info)));
if (!ac.check())
return ZX_ERR_NO_MEMORY;
*out_handle = ktl::move(new_handle);
*out_rights = default_rights();
return ZX_OK;
}
PciDeviceDispatcher::PciDeviceDispatcher(fbl::RefPtr<PcieDevice> device,
zx_pcie_device_info_t* out_info)
: device_(device) {
kcounter_add(dispatcher_pci_device_create_count, 1);
out_info->vendor_id = device_->vendor_id();
out_info->device_id = device_->device_id();
out_info->base_class = device_->class_id();
out_info->sub_class = device_->subclass();
out_info->program_interface = device_->prog_if();
out_info->revision_id = device_->rev_id();
out_info->bus_id = static_cast<uint8_t>(device_->bus_id());
out_info->dev_id = static_cast<uint8_t>(device_->dev_id());
out_info->func_id = static_cast<uint8_t>(device_->func_id());
}
PciDeviceDispatcher::~PciDeviceDispatcher() {
// Bus mastering and IRQ configuration are two states that should be
// disabled when the driver using them has been unloaded.
DEBUG_ASSERT(device_);
kcounter_add(dispatcher_pci_device_destroy_count, 1);
zx_status_t s = EnableBusMaster(false);
if (s != ZX_OK) {
printf("Failed to disable bus mastering on %02x:%02x:%1x\n", device_->bus_id(),
device_->dev_id(), device_->func_id());
}
s = SetIrqMode(static_cast<zx_pci_irq_mode_t>(PCIE_IRQ_MODE_DISABLED), 0);
if (s != ZX_OK) {
printf("Failed to disable IRQs on %02x:%02x:%1x\n", device_->bus_id(), device_->dev_id(),
device_->func_id());
}
// Release our reference to the underlying PCI device state to indicate that
// we are now closed.
//
// Note: we should not need the lock at this point in time. We are
// destructing, if there are any other threads interacting with methods in
// this object, then we have a serious lifecycle management problem.
device_ = nullptr;
}
zx_status_t PciDeviceDispatcher::EnableBusMaster(bool enable) {
canary_.Assert();
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
device_->EnableBusMaster(enable);
return ZX_OK;
}
zx_status_t PciDeviceDispatcher::EnablePio(bool enable) {
canary_.Assert();
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
device_->EnablePio(enable);
return ZX_OK;
}
zx_status_t PciDeviceDispatcher::EnableMmio(bool enable) {
canary_.Assert();
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_ && device_);
device_->EnableMmio(enable);
return ZX_OK;
}
const pcie_bar_info_t* PciDeviceDispatcher::GetBar(uint32_t bar_num) {
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
return device_->GetBarInfo(bar_num);
}
zx_status_t PciDeviceDispatcher::GetConfig(pci_config_info_t* out) {
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
if (!out) {
return ZX_ERR_INVALID_ARGS;
}
auto cfg = device_->config();
out->size = (device_->is_pcie()) ? PCIE_EXTENDED_CONFIG_SIZE : PCIE_BASE_CONFIG_SIZE;
out->base_addr = cfg->base();
out->is_mmio = (cfg->addr_space() == PciAddrSpace::MMIO);
return ZX_OK;
}
zx_status_t PciDeviceDispatcher::ResetDevice() {
canary_.Assert();
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
return device_->DoFunctionLevelReset();
}
zx_status_t PciDeviceDispatcher::MapInterrupt(int32_t which_irq,
KernelHandle<InterruptDispatcher>* interrupt_handle,
zx_rights_t* rights) {
canary_.Assert();
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
if ((which_irq < 0) || (static_cast<uint32_t>(which_irq) >= irqs_avail_cnt_))
return ZX_ERR_INVALID_ARGS;
// Attempt to create the dispatcher. It will take care of things like checking for
// duplicate registration.
return PciInterruptDispatcher::Create(device_, which_irq, irqs_maskable_, rights,
interrupt_handle);
}
static_assert(static_cast<uint>(ZX_PCIE_IRQ_MODE_DISABLED) ==
static_cast<uint>(PCIE_IRQ_MODE_DISABLED),
"Mode mismatch, ZX_PCIE_IRQ_MODE_DISABLED != PCIE_IRQ_MODE_DISABLED");
static_assert(static_cast<uint>(ZX_PCIE_IRQ_MODE_LEGACY) == static_cast<uint>(PCIE_IRQ_MODE_LEGACY),
"Mode mismatch, ZX_PCIE_IRQ_MODE_LEGACY != PCIE_IRQ_MODE_LEGACY");
static_assert(static_cast<uint>(ZX_PCIE_IRQ_MODE_LEGACY_NOACK) ==
static_cast<uint>(PCIE_IRQ_MODE_LEGACY_NOACK),
"Mode mismatch, ZX_PCIE_IRQ_MODE_LEGACY != PCIE_IRQ_MODE_LEGACY");
static_assert(static_cast<uint>(ZX_PCIE_IRQ_MODE_MSI) == static_cast<uint>(PCIE_IRQ_MODE_MSI),
"Mode mismatch, ZX_PCIE_IRQ_MODE_MSI != PCIE_IRQ_MODE_MSI");
static_assert(static_cast<uint>(ZX_PCIE_IRQ_MODE_MSI_X) == static_cast<uint>(PCIE_IRQ_MODE_MSI_X),
"Mode mismatch, ZX_PCIE_IRQ_MODE_MSI_X != PCIE_IRQ_MODE_MSI_X");
zx_status_t PciDeviceDispatcher::QueryIrqModeCaps(zx_pci_irq_mode_t mode, uint32_t* out_max_irqs) {
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
pcie_irq_mode_caps_t caps;
zx_status_t ret = device_->QueryIrqModeCapabilities(static_cast<pcie_irq_mode_t>(mode), &caps);
*out_max_irqs = (ret == ZX_OK) ? caps.max_irqs : 0;
return ret;
}
zx_status_t PciDeviceDispatcher::SetIrqMode(zx_pci_irq_mode_t mode, uint32_t requested_irq_count) {
canary_.Assert();
Guard<Mutex> guard{&lock_};
DEBUG_ASSERT(device_);
if (mode == ZX_PCIE_IRQ_MODE_DISABLED)
requested_irq_count = 0;
zx_status_t ret;
ret = device_->SetIrqMode(static_cast<pcie_irq_mode_t>(mode), requested_irq_count);
if (ret == ZX_OK) {
pcie_irq_mode_caps_t caps;
ret = device_->QueryIrqModeCapabilities(static_cast<pcie_irq_mode_t>(mode), &caps);
// The only way for QueryIrqMode to fail at this point should be for the
// device to have become unplugged.
if (ret == ZX_OK) {
irqs_avail_cnt_ = requested_irq_count;
irqs_maskable_ = caps.per_vector_masking_supported;
} else {
device_->SetIrqMode(PCIE_IRQ_MODE_DISABLED, 0);
irqs_avail_cnt_ = 0;
irqs_maskable_ = false;
}
}
return ret;
}
#endif // if WITH_KERNEL_PCIE