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