| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/devices/bus/drivers/pci/kpci.h" |
| |
| #include <fuchsia/hardware/pci/c/banjo.h> |
| #include <fuchsia/hardware/pci/cpp/banjo.h> |
| #include <fuchsia/hardware/pciroot/cpp/banjo.h> |
| #include <fuchsia/hardware/platform/device/cpp/banjo.h> |
| #include <fuchsia/hardware/sysmem/cpp/banjo.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/device.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <zircon/compiler.h> |
| #include <zircon/errors.h> |
| #include <zircon/fidl.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/pci.h> |
| #include <zircon/syscalls/resource.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| #include <bind/fuchsia/acpi/cpp/bind.h> |
| #include <ddktl/device.h> |
| |
| #include "src/devices/bus/drivers/pci/pci_bind.h" |
| #include "src/devices/bus/drivers/pci/proxy_rpc.h" |
| |
| namespace pci { |
| |
| static const zx_bind_inst_t sysmem_fragment_match[] = { |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_SYSMEM), |
| }; |
| |
| static const device_fragment_part_t sysmem_fragment[] = { |
| {std::size(sysmem_fragment_match), sysmem_fragment_match}, |
| }; |
| |
| zx_status_t KernelPci::CreateComposite(zx_device_t* parent, kpci_device device, bool uses_acpi) { |
| auto pci_bind_topo = static_cast<uint32_t>( |
| BIND_PCI_TOPO_PACK(device.info.bus_id, device.info.dev_id, device.info.func_id)); |
| zx_device_prop_t fragment_props[] = { |
| {BIND_PROTOCOL, 0, ZX_PROTOCOL_PCI}, |
| {BIND_PCI_VID, 0, device.info.vendor_id}, |
| {BIND_PCI_DID, 0, device.info.device_id}, |
| {BIND_PCI_CLASS, 0, device.info.base_class}, |
| {BIND_PCI_SUBCLASS, 0, device.info.sub_class}, |
| {BIND_PCI_INTERFACE, 0, device.info.program_interface}, |
| {BIND_PCI_REVISION, 0, device.info.revision_id}, |
| {BIND_PCI_TOPO, 0, pci_bind_topo}, |
| }; |
| |
| auto kpci = std::unique_ptr<KernelPci>(new KernelPci(parent, device)); |
| zx_status_t status = kpci->DdkAdd( |
| ddk::DeviceAddArgs(device.name).set_props(fragment_props).set_proto_id(ZX_PROTOCOL_PCI)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // TODO(fxbug.dev/93333): Remove this once DFv2 is stabilised. |
| bool is_dfv2 = device_is_dfv2(parent); |
| if (is_dfv2) { |
| static_cast<void>(kpci.release()); |
| return status; |
| return ZX_OK; |
| } |
| |
| // DFv2 does not support dynamic binding yet. |
| const zx_bind_inst_t pci_fragment_match[] = { |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI), |
| BI_ABORT_IF(NE, BIND_PCI_VID, device.info.vendor_id), |
| BI_ABORT_IF(NE, BIND_PCI_DID, device.info.device_id), |
| BI_ABORT_IF(NE, BIND_PCI_CLASS, device.info.base_class), |
| BI_ABORT_IF(NE, BIND_PCI_SUBCLASS, device.info.sub_class), |
| BI_ABORT_IF(NE, BIND_PCI_INTERFACE, device.info.program_interface), |
| BI_ABORT_IF(NE, BIND_PCI_REVISION, device.info.revision_id), |
| BI_ABORT_IF(EQ, BIND_COMPOSITE, 1), |
| BI_MATCH_IF(EQ, BIND_PCI_TOPO, pci_bind_topo), |
| }; |
| |
| const device_fragment_part_t pci_fragment[] = { |
| {std::size(pci_fragment_match), pci_fragment_match}, |
| }; |
| |
| const zx_bind_inst_t acpi_fragment_match[] = { |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI), |
| BI_ABORT_IF(NE, BIND_ACPI_BUS_TYPE, bind_fuchsia_acpi::BIND_ACPI_BUS_TYPE_PCI), |
| BI_MATCH_IF(EQ, BIND_PCI_TOPO, pci_bind_topo), |
| }; |
| |
| const device_fragment_part_t acpi_fragment[] = { |
| {std::size(acpi_fragment_match), acpi_fragment_match}, |
| }; |
| |
| const device_fragment_t fragments[] = { |
| {"sysmem", std::size(sysmem_fragment), sysmem_fragment}, |
| {"pci", std::size(pci_fragment), pci_fragment}, |
| {"acpi", std::size(acpi_fragment), acpi_fragment}, |
| }; |
| zx_device_prop_t composite_props[] = { |
| {BIND_PROTOCOL, 0, ZX_PROTOCOL_PCI}, |
| {BIND_PCI_VID, 0, device.info.vendor_id}, |
| {BIND_PCI_DID, 0, device.info.device_id}, |
| {BIND_PCI_CLASS, 0, device.info.base_class}, |
| {BIND_PCI_SUBCLASS, 0, device.info.sub_class}, |
| {BIND_PCI_INTERFACE, 0, device.info.program_interface}, |
| {BIND_PCI_REVISION, 0, device.info.revision_id}, |
| {BIND_PCI_TOPO, 0, pci_bind_topo}, |
| }; |
| |
| composite_device_desc_t composite_desc = { |
| .props = composite_props, |
| .props_count = std::size(composite_props), |
| .fragments = fragments, |
| .fragments_count = uses_acpi ? std::size(fragments) : std::size(fragments) - 1, |
| .primary_fragment = "pci", |
| .spawn_colocated = false, |
| }; |
| |
| char composite_name[ZX_DEVICE_NAME_MAX]; |
| snprintf(composite_name, sizeof(composite_name), "pci-%s", device.name); |
| auto kpci_composite = std::unique_ptr<KernelPci>(new KernelPci(parent, device)); |
| status = kpci_composite->DdkAddComposite(composite_name, &composite_desc); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| static_cast<void>(kpci_composite.release()); |
| static_cast<void>(kpci.release()); |
| return status; |
| } |
| |
| zx_status_t KernelPci::DdkGetProtocol(uint32_t proto_id, void* out) { |
| switch (proto_id) { |
| case ZX_PROTOCOL_PCI: { |
| auto proto = static_cast<pci_protocol_t*>(out); |
| proto->ctx = this; |
| proto->ops = &pci_protocol_ops_; |
| return ZX_OK; |
| } |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void KernelPci::DdkRelease() { |
| if (device_.handle != ZX_HANDLE_INVALID) { |
| zx_handle_close(device_.handle); |
| } |
| } |
| |
| zx_status_t KernelPci::PciGetBar(uint32_t bar_id, pci_bar_t* out_res) { |
| if (bar_id >= ZX_PCI_MAX_BAR_REGS) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_handle_t handle = ZX_HANDLE_INVALID; |
| zx_pci_bar_t bar{}; |
| zx_status_t st = zx_pci_get_bar(device_.handle, bar_id, &bar, &handle); |
| if (st == ZX_OK) { |
| out_res->bar_id = bar_id; |
| out_res->size = bar.size; |
| out_res->type = bar.type; |
| if (out_res->type == PCI_BAR_TYPE_IO) { |
| char name[] = "kPCI IO"; |
| st = zx_resource_create(get_root_resource(), ZX_RSRC_KIND_IOPORT, bar.addr, bar.size, name, |
| sizeof(name), &handle); |
| out_res->result.io.address = bar.addr; |
| out_res->result.io.resource = handle; |
| } else { |
| out_res->result.vmo = handle; |
| } |
| } |
| |
| return st; |
| } |
| |
| zx_status_t KernelPci::PciSetBusMastering(bool enable) { |
| return zx_pci_enable_bus_master(device_.handle, enable); |
| } |
| |
| zx_status_t KernelPci::PciResetDevice() { return zx_pci_reset_device(device_.handle); } |
| |
| zx_status_t KernelPci::PciAckInterrupt() { return ZX_OK; } |
| |
| zx_status_t KernelPci::PciMapInterrupt(uint32_t which_irq, zx::interrupt* out_handle) { |
| return zx_pci_map_interrupt(device_.handle, which_irq, out_handle->reset_and_get_address()); |
| } |
| |
| void KernelPci::PciGetInterruptModes(pci_interrupt_modes_t* out_modes) { |
| pci_interrupt_modes_t modes{}; |
| uint32_t count = 0; |
| zx_pci_query_irq_mode(device_.handle, PCI_INTERRUPT_MODE_LEGACY, &count); |
| modes.has_legacy = !!count; |
| zx_pci_query_irq_mode(device_.handle, PCI_INTERRUPT_MODE_MSI, &count); |
| modes.msi_count = static_cast<uint8_t>(count); |
| zx_pci_query_irq_mode(device_.handle, PCI_INTERRUPT_MODE_MSI_X, &count); |
| modes.msix_count = static_cast<uint16_t>(count); |
| *out_modes = modes; |
| } |
| |
| zx_status_t KernelPci::PciSetInterruptMode(pci_interrupt_mode_t mode, |
| uint32_t requested_irq_count) { |
| return zx_pci_set_irq_mode(device_.handle, mode, requested_irq_count); |
| } |
| |
| zx_status_t KernelPci::PciGetDeviceInfo(pci_device_info_t* out_info) { |
| memcpy(out_info, &device_.info, sizeof(*out_info)); |
| return ZX_OK; |
| } |
| |
| template <typename T> |
| zx_status_t ReadConfig(zx_handle_t device, uint16_t offset, T* out_value) { |
| uint32_t value; |
| zx_status_t st = zx_pci_config_read(device, offset, sizeof(T), &value); |
| if (st == ZX_OK) { |
| *out_value = static_cast<T>(value); |
| } |
| return st; |
| } |
| |
| zx_status_t KernelPci::PciReadConfig8(uint16_t offset, uint8_t* out_value) { |
| return ReadConfig(device_.handle, offset, out_value); |
| } |
| |
| zx_status_t KernelPci::PciReadConfig16(uint16_t offset, uint16_t* out_value) { |
| return ReadConfig(device_.handle, offset, out_value); |
| } |
| |
| zx_status_t KernelPci::PciReadConfig32(uint16_t offset, uint32_t* out_value) { |
| return ReadConfig(device_.handle, offset, out_value); |
| } |
| zx_status_t KernelPci::PciWriteConfig8(uint16_t offset, uint8_t value) { |
| return zx_pci_config_write(device_.handle, offset, sizeof(value), value); |
| } |
| |
| zx_status_t KernelPci::PciWriteConfig16(uint16_t offset, uint16_t value) { |
| return zx_pci_config_write(device_.handle, offset, sizeof(value), value); |
| } |
| |
| zx_status_t KernelPci::PciWriteConfig32(uint16_t offset, uint32_t value) { |
| return zx_pci_config_write(device_.handle, offset, sizeof(value), value); |
| } |
| |
| zx_status_t KernelPci::PciGetFirstCapability(uint8_t cap_id, uint8_t* out_offset) { |
| return PciGetNextCapability(cap_id, PCI_CONFIG_CAPABILITIES_PTR, out_offset); |
| } |
| |
| zx_status_t KernelPci::PciGetNextCapability(uint8_t cap_id, uint8_t offset, uint8_t* out_offset) { |
| // If we're looking for the first capability then we read from the offset |
| // since it contains 0x34 which ppints to the start of the list. Otherwise, we |
| // have an existing capability's offset and need to advance one byte to its |
| // next pointer. |
| if (offset != PCI_CONFIG_CAPABILITIES_PTR) { |
| offset++; |
| } |
| |
| // Walk the capability list looking for the type requested. limit acts as a |
| // barrier in case of an invalid capability pointer list that causes us to |
| // iterate forever otherwise. |
| uint8_t limit = 64; |
| uint32_t cap_offset = 0; |
| zx_pci_config_read(device_.handle, offset, sizeof(uint8_t), &cap_offset); |
| while (cap_offset != 0 && cap_offset != 0xFF && limit--) { |
| zx_status_t st; |
| uint32_t type_id = 0; |
| if ((st = zx_pci_config_read(device_.handle, static_cast<uint16_t>(cap_offset), sizeof(uint8_t), |
| &type_id)) != ZX_OK) { |
| zxlogf(ERROR, "%s: error reading type from cap offset %#x: %d", __func__, cap_offset, st); |
| return st; |
| } |
| |
| if (type_id == cap_id) { |
| *out_offset = static_cast<uint8_t>(cap_offset); |
| return ZX_OK; |
| } |
| |
| // We didn't find the right type, move on, but ensure we're still within the |
| // first 256 bytes of standard config space. |
| if (cap_offset >= UINT8_MAX) { |
| zxlogf(ERROR, "%s: %#x is an invalid capability offset!", __func__, cap_offset); |
| break; |
| } |
| if ((st = zx_pci_config_read(device_.handle, static_cast<uint16_t>(cap_offset + 1), |
| sizeof(uint8_t), &cap_offset)) != ZX_OK) { |
| zxlogf(ERROR, "%s: error reading next cap from cap offset %#x: %d", __func__, cap_offset + 1, |
| st); |
| break; |
| } |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t KernelPci::PciGetFirstExtendedCapability(uint16_t cap_id, uint16_t* out_offset) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t KernelPci::PciGetNextExtendedCapability(uint16_t cap_id, uint16_t offset, |
| uint16_t* out_offset) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t KernelPci::PciGetBti(uint32_t index, zx::bti* out_bti) { |
| zx_status_t st = ZX_ERR_NOT_SUPPORTED; |
| uint32_t bdf = (static_cast<uint32_t>(device_.info.bus_id) << 8) | |
| (static_cast<uint32_t>(device_.info.dev_id) << 3) | device_.info.func_id; |
| if (device_.pciroot.ops) { |
| st = pciroot_get_bti(&device_.pciroot, bdf, index, out_bti->reset_and_get_address()); |
| } else if (device_.pdev.ops) { |
| // TODO(teisenbe): This isn't quite right. We need to develop a way to |
| // resolve which BTI should go to downstream. However, we don't currently |
| // support any SMMUs for ARM, so this will work for now. |
| st = pdev_get_bti(&device_.pdev, 0, out_bti->reset_and_get_address()); |
| } |
| |
| return st; |
| } |
| |
| // Initializes the upper half of a pci / pci.proxy devhost pair. |
| static zx_status_t pci_init_child(zx_device_t* parent, uint32_t index, |
| pci_platform_info_t* plat_info) { |
| zx_pcie_device_info_t info; |
| zx_handle_t handle; |
| |
| if (!parent) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // This is a legacy function to get the 'nth' device on a bus. Please do not |
| // use get_root_resource() in new code. See fxbug.dev/31358. |
| zx_status_t status = zx_pci_get_nth_device(get_root_resource(), index, &info, &handle); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| kpci_device device{ |
| .handle = handle, |
| .index = index, |
| .info = {.vendor_id = info.vendor_id, |
| .device_id = info.device_id, |
| .base_class = info.base_class, |
| .sub_class = info.sub_class, |
| .program_interface = info.program_interface, |
| .revision_id = info.revision_id, |
| .bus_id = info.bus_id, |
| .dev_id = info.dev_id, |
| .func_id = info.func_id}, |
| }; |
| |
| // Store the PCIROOT protocol for use with get_bti in the pci protocol It is |
| // not fatal if this fails, but bti protocol methods will not work. |
| device_get_protocol(parent, ZX_PROTOCOL_PCIROOT, &device.pciroot); |
| device_get_protocol(parent, ZX_PROTOCOL_PDEV, &device.pdev); |
| |
| bool uses_acpi = false; |
| for (size_t i = 0; i < plat_info->acpi_bdfs_count; i++) { |
| const pci_bdf_t* bdf = &plat_info->acpi_bdfs_list[i]; |
| if (bdf->bus_id == device.info.bus_id && bdf->device_id == device.info.dev_id && |
| bdf->function_id == device.info.func_id) { |
| uses_acpi = true; |
| break; |
| } |
| } |
| |
| snprintf(device.name, sizeof(device.name), "%02x:%02x.%1x", device.info.bus_id, |
| device.info.dev_id, device.info.func_id); |
| status = KernelPci::CreateComposite(parent, device, uses_acpi); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "failed to create kPCI for %#02x:%#02x.%1x (%#04x:%#04x)", info.bus_id, |
| info.dev_id, info.func_id, info.vendor_id, info.device_id); |
| return status; |
| } |
| |
| return status; |
| } // namespace pci |
| |
| static zx_status_t pci_drv_bind(void* ctx, zx_device_t* parent) { |
| pci_platform_info_t platform_info{}; |
| pciroot_protocol_t pciroot; |
| zx_status_t result = device_get_protocol(parent, ZX_PROTOCOL_PCIROOT, &pciroot); |
| if (result == ZX_OK) { |
| result = pciroot_get_pci_platform_info(&pciroot, &platform_info); |
| } |
| // Walk PCI devices to create their upper half devices until we hit the end |
| for (uint32_t index = 0;; index++) { |
| if (pci_init_child(parent, index, &platform_info) != ZX_OK) { |
| break; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| } // namespace pci |
| |
| static zx_driver_ops_t kpci_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = pci::pci_drv_bind, |
| }; |
| |
| ZIRCON_DRIVER(pci, kpci_driver_ops, "zircon", "0.1"); |