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