| // 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 |
| |
| #include <err.h> |
| #include <platform.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <trace.h> |
| |
| #include <dev/interrupt.h> |
| #include <lib/pci/pio.h> |
| #include <lib/user_copy/user_ptr.h> |
| #include <object/handle.h> |
| #include <object/process_dispatcher.h> |
| #include <object/resources.h> |
| #include <object/vm_object_dispatcher.h> |
| #include <vm/vm_object_physical.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/limits.h> |
| #include <fbl/ref_ptr.h> |
| #include <fbl/unique_free_ptr.h> |
| #include <zircon/syscalls/pci.h> |
| |
| #include "priv.h" |
| |
| #define LOCAL_TRACE 0 |
| |
| #if WITH_LIB_GFXCONSOLE |
| // If we were built with the GFX console, make sure that it is un-bound when |
| // user mode takes control of PCI. Note: there should probably be a cleaner way |
| // of doing this. Not all system have PCI, and (eventually) not all systems |
| // will attempt to initialize PCI. Someday, there should be a different way of |
| // handing off from early/BSOD kernel mode graphics to user mode. |
| #include <lib/gfxconsole.h> |
| static inline void shutdown_early_init_console() { |
| gfxconsole_bind_display(nullptr, nullptr); |
| } |
| #else |
| static inline void shutdown_early_init_console() {} |
| #endif |
| |
| #if WITH_DEV_PCIE |
| #include <dev/pcie_bus_driver.h> |
| #include <dev/pcie_root.h> |
| #include <object/pci_device_dispatcher.h> |
| |
| // Implementation of a PcieRoot with a look-up table based legacy IRQ swizzler |
| // suitable for use with ACPI style swizzle definitions. |
| class PcieRootLUTSwizzle : public PcieRoot { |
| public: |
| static fbl::RefPtr<PcieRoot> Create(PcieBusDriver& bus_drv, |
| uint managed_bus_id, |
| const zx_pci_irq_swizzle_lut_t& lut) { |
| fbl::AllocChecker ac; |
| auto root = fbl::AdoptRef(new (&ac) PcieRootLUTSwizzle(bus_drv, |
| managed_bus_id, |
| lut)); |
| if (!ac.check()) { |
| TRACEF("Out of memory attemping to create PCIe root to manage bus ID 0x%02x\n", |
| managed_bus_id); |
| return nullptr; |
| } |
| |
| return root; |
| } |
| |
| zx_status_t Swizzle(uint dev_id, uint func_id, uint pin, uint* irq) override { |
| if ((irq == nullptr) || |
| (dev_id >= fbl::count_of(lut_)) || |
| (func_id >= fbl::count_of(lut_[dev_id])) || |
| (pin >= fbl::count_of(lut_[dev_id][func_id]))) |
| return ZX_ERR_INVALID_ARGS; |
| |
| *irq = lut_[dev_id][func_id][pin]; |
| return (*irq == ZX_PCI_NO_IRQ_MAPPING) ? ZX_ERR_NOT_FOUND : ZX_OK; |
| } |
| |
| private: |
| PcieRootLUTSwizzle(PcieBusDriver& bus_drv, |
| uint managed_bus_id, |
| const zx_pci_irq_swizzle_lut_t& lut) |
| : PcieRoot(bus_drv, managed_bus_id) { |
| ::memcpy(&lut_, &lut, sizeof(lut_)); |
| } |
| |
| zx_pci_irq_swizzle_lut_t lut_; |
| }; |
| |
| // Scan |lut| for entries mapping to |irq|, and replace them with |
| // ZX_PCI_NO_IRQ_MAPPING. |
| static void pci_irq_swizzle_lut_remove_irq(zx_pci_irq_swizzle_lut_t* lut, uint32_t irq) { |
| for (size_t dev = 0; dev < fbl::count_of(*lut); ++dev) { |
| for (size_t func = 0; func < fbl::count_of((*lut)[dev]); ++func) { |
| for (size_t pin = 0; pin < fbl::count_of((*lut)[dev][func]); ++pin) { |
| uint32_t* assigned_irq = &(*lut)[dev][func][pin]; |
| if (*assigned_irq == irq) { |
| *assigned_irq = ZX_PCI_NO_IRQ_MAPPING; |
| } |
| } |
| } |
| } |
| } |
| |
| zx_status_t sys_pci_add_subtract_io_range(zx_handle_t handle, bool mmio, uint64_t base, uint64_t len, bool add) { |
| |
| LTRACEF("handle %x mmio %d base %#" PRIx64 " len %#" PRIx64 " add %d\n", handle, mmio, base, len, add); |
| |
| // TODO(ZX-971): finer grained validation |
| // TODO(security): Add additional access checks |
| zx_status_t status; |
| if ((status = validate_resource(handle, ZX_RSRC_KIND_ROOT)) < 0) { |
| return status; |
| } |
| |
| auto pcie = PcieBusDriver::GetDriver(); |
| if (pcie == nullptr) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| PciAddrSpace addr_space = mmio ? PciAddrSpace::MMIO : PciAddrSpace::PIO; |
| |
| if (add) { |
| return pcie->AddBusRegion(base, len, addr_space); |
| } else { |
| return pcie->SubtractBusRegion(base, len, addr_space); |
| } |
| } |
| |
| zx_status_t sys_pci_init(zx_handle_t handle, user_in_ptr<const zx_pci_init_arg_t> _init_buf, uint32_t len) { |
| // TODO(ZX-971): finer grained validation |
| // TODO(security): Add additional access checks |
| zx_status_t status; |
| if ((status = validate_resource(handle, ZX_RSRC_KIND_ROOT)) < 0) { |
| return status; |
| } |
| |
| fbl::unique_free_ptr<zx_pci_init_arg_t> arg; |
| |
| if (len < sizeof(*arg) || len > ZX_PCI_INIT_ARG_MAX_SIZE) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto pcie = PcieBusDriver::GetDriver(); |
| if (pcie == nullptr) |
| return ZX_ERR_BAD_STATE; |
| |
| // we have to malloc instead of new since this is a variable-sized structure |
| arg.reset(static_cast<zx_pci_init_arg_t*>(malloc(len))); |
| if (!arg) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| { |
| status = _init_buf.reinterpret<const void>().copy_array_from_user( |
| arg.get(), len); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| if (LOCAL_TRACE) { |
| TRACEF("%u address window%s found in init arg\n", arg->addr_window_count, |
| (arg->addr_window_count == 1) ? "" : "s"); |
| for (uint32_t i = 0; i < arg->addr_window_count; i++) { |
| printf("[%u]\n\tis_mmio: %d\n\thas_ecam: %d\n\tbase: %#" PRIxPTR "\n" |
| "\tsize: %zu\n\tbus_start: %u\n\tbus_end: %u\n", |
| i, |
| arg->addr_windows[i].is_mmio, arg->addr_windows[i].has_ecam, |
| arg->addr_windows[i].base, arg->addr_windows[i].size, |
| arg->addr_windows[i].bus_start, arg->addr_windows[i].bus_end); |
| } |
| } |
| |
| const uint32_t win_count = arg->addr_window_count; |
| if (len != sizeof(*arg) + sizeof(arg->addr_windows[0]) * win_count) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (arg->num_irqs > fbl::count_of(arg->irqs)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Configure interrupts |
| for (unsigned int i = 0; i < arg->num_irqs; ++i) { |
| uint32_t irq = arg->irqs[i].global_irq; |
| if (!is_valid_interrupt(irq, 0)) { |
| // If the interrupt isn't valid, mask it out of the IRQ swizzle table |
| // and don't activate it. Attempts to use legacy IRQs for the device |
| // will fail later. |
| arg->irqs[i].global_irq = ZX_PCI_NO_IRQ_MAPPING; |
| pci_irq_swizzle_lut_remove_irq(&arg->dev_pin_to_global_irq, irq); |
| continue; |
| } |
| |
| enum interrupt_trigger_mode tm = IRQ_TRIGGER_MODE_EDGE; |
| if (arg->irqs[i].level_triggered) { |
| tm = IRQ_TRIGGER_MODE_LEVEL; |
| } |
| enum interrupt_polarity pol = IRQ_POLARITY_ACTIVE_LOW; |
| if (arg->irqs[i].active_high) { |
| pol = IRQ_POLARITY_ACTIVE_HIGH; |
| } |
| |
| zx_status_t status = configure_interrupt(irq, tm, pol); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| // TODO(teisenbe): For now assume there is only one ECAM |
| if (win_count != 1) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (arg->addr_windows[0].bus_start != 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (arg->addr_windows[0].bus_start > arg->addr_windows[0].bus_end) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| #if ARCH_X86 |
| // Check for a quirk that we've seen. Some systems will report overly large |
| // PCIe config regions that collide with architectural registers. |
| unsigned int num_buses = arg->addr_windows[0].bus_end - |
| arg->addr_windows[0].bus_start + 1; |
| paddr_t end = arg->addr_windows[0].base + |
| num_buses * PCIE_ECAM_BYTE_PER_BUS; |
| const paddr_t high_limit = 0xfec00000ULL; |
| if (end > high_limit) { |
| TRACEF("PCIe config space collides with arch devices, truncating\n"); |
| end = high_limit; |
| if (end < arg->addr_windows[0].base) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| arg->addr_windows[0].size = ROUNDDOWN(end - arg->addr_windows[0].base, |
| PCIE_ECAM_BYTE_PER_BUS); |
| uint64_t new_bus_end = (arg->addr_windows[0].size / PCIE_ECAM_BYTE_PER_BUS) + |
| arg->addr_windows[0].bus_start - 1; |
| if (new_bus_end >= PCIE_MAX_BUSSES) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| arg->addr_windows[0].bus_end = static_cast<uint8_t>(new_bus_end); |
| } |
| #endif |
| |
| if (arg->addr_windows[0].is_mmio) { |
| if (arg->addr_windows[0].size < PCIE_ECAM_BYTE_PER_BUS) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (arg->addr_windows[0].size / PCIE_ECAM_BYTE_PER_BUS > |
| PCIE_MAX_BUSSES - arg->addr_windows[0].bus_start) { |
| |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // TODO(johngro): Update the syscall to pass a paddr_t for base instead of a uint64_t |
| ASSERT(arg->addr_windows[0].base < fbl::numeric_limits<paddr_t>::max()); |
| |
| // TODO(johngro): Do not limit this to a single range. Instead, fetch all |
| // of the ECAM ranges from ACPI, as well as the appropriate bus start/end |
| // ranges. |
| const PcieBusDriver::EcamRegion ecam = { |
| .phys_base = static_cast<paddr_t>(arg->addr_windows[0].base), |
| .size = arg->addr_windows[0].size, |
| .bus_start = 0x00, |
| .bus_end = static_cast<uint8_t>((arg->addr_windows[0].size / PCIE_ECAM_BYTE_PER_BUS) - 1), |
| }; |
| |
| zx_status_t ret = pcie->AddEcamRegion(ecam); |
| if (ret != ZX_OK) { |
| TRACEF("Failed to add ECAM region to PCIe bus driver! (ret %d)\n", ret); |
| return ret; |
| } |
| } |
| |
| // TODO(johngro): Change the user-mode and devmgr behavior to add all of the |
| // roots in the system. Do not assume that there is a single root, nor that |
| // it manages bus ID 0. |
| auto root = PcieRootLUTSwizzle::Create(*pcie, 0, arg->dev_pin_to_global_irq); |
| if (root == nullptr) |
| return ZX_ERR_NO_MEMORY; |
| |
| // Enable PIO config space if the address window was not MMIO |
| pcie->EnablePIOWorkaround(!arg->addr_windows[0].is_mmio); |
| |
| zx_status_t ret = pcie->AddRoot(fbl::move(root)); |
| if (ret != ZX_OK) { |
| TRACEF("Failed to add root complex to PCIe bus driver! (ret %d)\n", ret); |
| return ret; |
| } |
| |
| ret = pcie->StartBusDriver(); |
| if (ret != ZX_OK) { |
| TRACEF("Failed to start PCIe bus driver! (ret %d)\n", ret); |
| return ret; |
| } |
| |
| shutdown_early_init_console(); |
| return ZX_OK; |
| } |
| |
| zx_status_t sys_pci_get_nth_device(zx_handle_t hrsrc, |
| uint32_t index, |
| user_out_ptr<zx_pcie_device_info_t> out_info, |
| user_out_handle* out_handle) { |
| /** |
| * Returns the pci config of a device. |
| * @param index Device index |
| * @param out_info Device info (BDF address, vendor id, etc...) |
| */ |
| LTRACEF("handle %x index %u\n", hrsrc, index); |
| |
| // TODO(ZX-971): finer grained validation |
| zx_status_t status; |
| if ((status = validate_resource(hrsrc, ZX_RSRC_KIND_ROOT)) < 0) { |
| return status; |
| } |
| |
| if (!out_info) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::RefPtr<Dispatcher> dispatcher; |
| zx_rights_t rights; |
| zx_pcie_device_info_t info; |
| status = PciDeviceDispatcher::Create(index, &info, &dispatcher, &rights); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // If everything is successful add the handle to the process |
| status = out_info.copy_to_user(info); |
| if (status != ZX_OK) |
| return status; |
| |
| return out_handle->make(fbl::move(dispatcher), rights); |
| } |
| |
| zx_status_t sys_pci_config_read(zx_handle_t handle, uint16_t offset, size_t width, |
| user_out_ptr<uint32_t> out_val) { |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| fbl::RefPtr<Dispatcher> dispatcher; |
| |
| // Get the PciDeviceDispatcher from the handle passed in via the pci protocol |
| auto up = ProcessDispatcher::GetCurrent(); |
| zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ | ZX_RIGHT_WRITE, |
| &pci_device); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| auto device = pci_device->device(); |
| auto cfg_size = device->is_pcie() ? PCIE_EXTENDED_CONFIG_SIZE : PCIE_BASE_CONFIG_SIZE; |
| if (out_val.get() == nullptr || offset + width > cfg_size) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Based on the width passed in we can use the type safety of the PciConfig layer |
| // to ensure we're getting correctly sized data back and return errors in the PIO |
| // cases. |
| auto config = device->config(); |
| switch (width) { |
| case 1u: |
| return out_val.copy_to_user(static_cast<uint32_t>(config->Read(PciReg8(offset)))); |
| case 2u: |
| return out_val.copy_to_user(static_cast<uint32_t>(config->Read(PciReg16(offset)))); |
| case 4u: |
| return out_val.copy_to_user(config->Read(PciReg32(offset))); |
| } |
| |
| // If we reached this point then the width was invalid. |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t sys_pci_config_write(zx_handle_t handle, uint16_t offset, size_t width, uint32_t val) { |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| fbl::RefPtr<Dispatcher> dispatcher; |
| |
| // Get the PciDeviceDispatcher from the handle passed in via the pci protocol |
| auto up = ProcessDispatcher::GetCurrent(); |
| zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ | ZX_RIGHT_WRITE, |
| &pci_device); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Writes to the PCI header or outside of the device's config space are not allowed. |
| auto device = pci_device->device(); |
| auto cfg_size = device->is_pcie() ? PCIE_EXTENDED_CONFIG_SIZE : PCIE_BASE_CONFIG_SIZE; |
| if (offset < ZX_PCI_STANDARD_CONFIG_HDR_SIZE || offset + width > cfg_size) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto config = device->config(); |
| switch (width) { |
| case 1u: |
| config->Write(PciReg8(offset), static_cast<uint8_t>(val & UINT8_MAX)); |
| break; |
| case 2u: |
| config->Write(PciReg16(offset), static_cast<uint16_t>(val & UINT16_MAX)); |
| break; |
| case 4u: |
| config->Write(PciReg32(offset), val); |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| return ZX_OK; |
| } |
| /* This is a transitional method to bootstrap legacy PIO access before |
| * PCI moves to userspace. |
| */ |
| zx_status_t sys_pci_cfg_pio_rw(zx_handle_t handle, uint8_t bus, uint8_t dev, uint8_t func, |
| uint8_t offset, user_inout_ptr<uint32_t> val, size_t width, bool write) { |
| #if ARCH_X86 |
| uint32_t val_; |
| zx_status_t status = validate_resource(handle, ZX_RSRC_KIND_ROOT); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (write) { |
| val.copy_from_user(&val_); |
| status = Pci::PioCfgWrite(bus, dev, func, offset, val_, width); |
| } else { |
| status = Pci::PioCfgRead(bus, dev, func, offset, &val_, width); |
| if (status == ZX_OK) { |
| val.copy_to_user(val_); |
| } |
| } |
| |
| return status; |
| #else |
| return ZX_ERR_NOT_SUPPORTED; |
| #endif |
| } |
| |
| zx_status_t sys_pci_enable_bus_master(zx_handle_t dev_handle, bool enable) { |
| /** |
| * Enables or disables bus mastering for the PCI device associated with the handle. |
| * @param handle Handle associated with a PCI device |
| * @param enable true if bus mastering should be enabled. |
| */ |
| LTRACEF("handle %x\n", dev_handle); |
| |
| auto up = ProcessDispatcher::GetCurrent(); |
| |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_WRITE, &pci_device); |
| if (status != ZX_OK) |
| return status; |
| |
| return pci_device->EnableBusMaster(enable); |
| } |
| |
| zx_status_t sys_pci_reset_device(zx_handle_t dev_handle) { |
| /** |
| * Resets the PCI device associated with the handle. |
| * @param handle Handle associated with a PCI device |
| */ |
| LTRACEF("handle %x\n", dev_handle); |
| |
| auto up = ProcessDispatcher::GetCurrent(); |
| |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_WRITE, &pci_device); |
| if (status != ZX_OK) |
| return status; |
| |
| return pci_device->ResetDevice(); |
| } |
| |
| zx_status_t sys_pci_get_bar(zx_handle_t dev_handle, |
| uint32_t bar_num, |
| user_out_ptr<zx_pci_bar_t> out_bar, |
| user_out_handle* out_handle) { |
| if (dev_handle == ZX_HANDLE_INVALID || |
| !out_bar || |
| bar_num >= PCIE_MAX_BAR_REGS) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Grab the PCI device object |
| auto up = ProcessDispatcher::GetCurrent(); |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| zx_status_t status = up->GetDispatcherWithRights(dev_handle, |
| ZX_RIGHT_READ | ZX_RIGHT_WRITE, &pci_device); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Get bar info from the device via the dispatcher and make sure it makes sense |
| const pcie_bar_info_t* info = pci_device->GetBar(bar_num); |
| if (info == nullptr || info->size == 0) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // A bar can be MMIO, PIO, or unused. In the MMIO case it can be passed |
| // back to the caller as a VMO. |
| zx_pci_bar_t bar = {}; |
| bar.size = info->size; |
| bar.type = (info->is_mmio) ? PCI_BAR_TYPE_MMIO : PCI_BAR_TYPE_PIO; |
| |
| // MMIO based bars are passed back using a VMO. If we end up creating one here |
| // without errors then later a handle will be passed back to the caller. |
| fbl::RefPtr<Dispatcher> dispatcher; |
| fbl::RefPtr<VmObject> vmo; |
| zx_rights_t rights; |
| if (info->is_mmio) { |
| // Create a VMO mapping to the address / size of the mmio region this bar |
| // was allocated at |
| status = VmObjectPhysical::Create(info->bus_addr, |
| fbl::max<uint64_t>(info->size, PAGE_SIZE), &vmo); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Set the name of the vmo for tracking |
| char name[32]; |
| auto dev = pci_device->device(); |
| snprintf(name, sizeof(name), "pci-%02x:%02x.%1x-bar%u", |
| dev->bus_id(), dev->dev_id(), dev->func_id(), bar_num); |
| vmo->set_name(name, sizeof(name)); |
| |
| // Now that the vmo has been created for the bar, create a handle to |
| // the appropriate dispatcher for the caller |
| status = VmObjectDispatcher::Create(vmo, &dispatcher, &rights); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| pci_device->EnableMmio(true); |
| } else { |
| DEBUG_ASSERT(info->bus_addr != 0); |
| bar.addr = info->bus_addr; |
| pci_device->EnablePio(true); |
| } |
| |
| // Metadata has been sorted out, so copy back the structure to userspace |
| // and then account for the vmo handle if one was created. |
| status = out_bar.copy_to_user(bar); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (vmo) { |
| return out_handle->make(fbl::move(dispatcher), rights); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t sys_pci_map_interrupt(zx_handle_t dev_handle, |
| int32_t which_irq, |
| user_out_handle* out_handle) { |
| /** |
| * Returns a handle that can be waited on. |
| * @param handle Handle associated with a PCI device |
| * @param which_irq Identifier for an IRQ, returned in sys_pci_get_nth_device, or -1 for legacy |
| * interrupts |
| * @param out_handle pointer to a handle to associate with the interrupt mapping |
| */ |
| LTRACEF("handle %x\n", dev_handle); |
| |
| auto up = ProcessDispatcher::GetCurrent(); |
| |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| zx_status_t status = |
| up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_READ, &pci_device); |
| if (status != ZX_OK) |
| return status; |
| |
| fbl::RefPtr<Dispatcher> interrupt_dispatcher; |
| zx_rights_t rights; |
| zx_status_t result = pci_device->MapInterrupt(which_irq, &interrupt_dispatcher, &rights); |
| if (result != ZX_OK) |
| return result; |
| |
| return out_handle->make(fbl::move(interrupt_dispatcher), rights); |
| } |
| |
| /** |
| * Gets info about the capabilities of a PCI device's IRQ modes. |
| * @param handle Handle associated with a PCI device. |
| * @param mode The IRQ mode whose capabilities are to be queried. |
| * @param out_len Out param which will hold the maximum number of IRQs supported by the mode. |
| */ |
| zx_status_t sys_pci_query_irq_mode(zx_handle_t dev_handle, |
| uint32_t mode, |
| user_out_ptr<uint32_t> out_max_irqs) { |
| LTRACEF("handle %x\n", dev_handle); |
| |
| auto up = ProcessDispatcher::GetCurrent(); |
| |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_READ, &pci_device); |
| if (status != ZX_OK) |
| return status; |
| |
| uint32_t max_irqs; |
| zx_status_t result = pci_device->QueryIrqModeCaps((zx_pci_irq_mode_t)mode, &max_irqs); |
| if (result != ZX_OK) |
| return result; |
| |
| status = out_max_irqs.copy_to_user(max_irqs); |
| if (status != ZX_OK) |
| return status; |
| |
| return result; |
| } |
| |
| /** |
| * Selects an IRQ mode for a PCI device. |
| * @param handle Handle associated with a PCI device. |
| * @param mode The IRQ mode to select. |
| * @param requested_irq_count The number of IRQs to select request for the given mode. |
| */ |
| zx_status_t sys_pci_set_irq_mode(zx_handle_t dev_handle, |
| uint32_t mode, |
| uint32_t requested_irq_count) { |
| LTRACEF("handle %x\n", dev_handle); |
| |
| auto up = ProcessDispatcher::GetCurrent(); |
| |
| fbl::RefPtr<PciDeviceDispatcher> pci_device; |
| zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_WRITE, &pci_device); |
| if (status != ZX_OK) |
| return status; |
| |
| return pci_device->SetIrqMode((zx_pci_irq_mode_t)mode, requested_irq_count); |
| } |
| #else // WITH_DEV_PCIE |
| zx_status_t sys_pci_init(zx_handle_t, user_in_ptr<const zx_pci_init_arg_t>, uint32_t) { |
| shutdown_early_init_console(); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_add_subtract_io_range(zx_handle_t handle, bool mmio, uint64_t base, uint64_t len, bool add) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_config_read(zx_handle_t handle, uint16_t offset, size_t width, |
| user_out_ptr<uint32_t> out_val) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_config_write(zx_handle_t handle, uint16_t offset, size_t width, uint32_t val) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_cfg_pio_rw(zx_handle_t handle, uint8_t bus, uint8_t dev, uint8_t func, |
| uint8_t offset, user_inout_ptr<uint32_t> val, size_t width, bool write) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_get_nth_device(zx_handle_t, uint32_t, user_inout_ptr<zx_pcie_device_info_t>, |
| user_out_handle*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_enable_bus_master(zx_handle_t, bool) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_reset_device(zx_handle_t) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_get_bar(zx_handle_t, uint32_t, pci_resource_t**, user_out_handle*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_map_interrupt(zx_handle_t, int32_t, user_out_handle*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_query_irq_mode(zx_handle_t, uint32_t, user_out_ptr<uint32_t>) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t sys_pci_set_irq_mode(zx_handle_t, uint32_t, uint32_t) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| #endif // WITH_DEV_PCIE |