|  | // 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 |