| // Copyright 2022 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 <fuchsia/hardware/pci/c/banjo.h> |
| #include <fuchsia/hardware/pci/cpp/banjo.h> |
| #include <lib/ddk/binding.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/zx/bti.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/interrupt.h> |
| #include <lib/zx/status.h> |
| #include <lib/zx/vmo.h> |
| #include <string.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| // TODO(fxbug.dev/33713): Stop depending on the types in this file. |
| #include <zircon/syscalls/pci.h> |
| #include <zircon/types.h> |
| |
| #include <bind/fuchsia/acpi/cpp/bind.h> |
| #include <ddktl/device.h> |
| #include <fbl/alloc_checker.h> |
| |
| #include "src/devices/bus/drivers/pci/common.h" |
| #include "src/devices/bus/drivers/pci/device.h" |
| #include "src/devices/bus/drivers/pci/proxy_rpc.h" |
| |
| #define LOG_STATUS(level, status, format, ...) \ |
| ({ \ |
| zx_status_t _status = (status); \ |
| zxlogf(level, "[%s] %s(" format ") = %s", device()->config()->addr(), \ |
| __func__ __VA_OPT__(, ) __VA_ARGS__, zx_status_get_string(_status)); \ |
| _status; \ |
| }) |
| |
| namespace pci { |
| |
| zx::status<> BanjoDevice::Create(zx_device_t* parent, pci::Device* device) { |
| fbl::AllocChecker ac; |
| std::unique_ptr<BanjoDevice> banjo_dev(new (&ac) BanjoDevice(parent, std::move(device))); |
| if (!ac.check()) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| auto pci_dev = banjo_dev->device(); |
| |
| auto pci_bind_topo = static_cast<uint32_t>( |
| BIND_PCI_TOPO_PACK(pci_dev->bus_id(), pci_dev->dev_id(), pci_dev->func_id())); |
| // clang-format off |
| zx_device_prop_t pci_device_props[] = { |
| {BIND_PROTOCOL, 0, ZX_PROTOCOL_PCI}, |
| {BIND_PCI_VID, 0, pci_dev->vendor_id()}, |
| {BIND_PCI_DID, 0, pci_dev->device_id()}, |
| {BIND_PCI_CLASS, 0, pci_dev->class_id()}, |
| {BIND_PCI_SUBCLASS, 0, pci_dev->subclass()}, |
| {BIND_PCI_INTERFACE, 0, pci_dev->prog_if()}, |
| {BIND_PCI_REVISION, 0, pci_dev->rev_id()}, |
| {BIND_PCI_TOPO, 0, pci_bind_topo}, |
| }; |
| // clang-format on |
| |
| // Create an isolated devhost to load the proxy pci driver containing the PciProxy |
| // instance which will talk to this device. |
| zx_status_t status = banjo_dev->DdkAdd(ddk::DeviceAddArgs(pci_dev->config()->addr()) |
| .set_props(pci_device_props) |
| .set_proto_id(ZX_PROTOCOL_PCI) |
| .set_flags(DEVICE_ADD_MUST_ISOLATE)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[%s] Failed to create pci banjo fragment: %s", pci_dev->config()->addr(), |
| zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| |
| auto banjo_dev_unowned = banjo_dev.release(); |
| // TODO(fxbug.dev/93333): Remove this once DFv2 is stabilised. |
| bool is_dfv2 = device_is_dfv2(banjo_dev_unowned->zxdev()); |
| if (is_dfv2) { |
| return zx::ok(); |
| } |
| |
| const zx_bind_inst_t pci_fragment_match[] = { |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI), |
| BI_ABORT_IF(NE, BIND_PCI_VID, pci_dev->vendor_id()), |
| BI_ABORT_IF(NE, BIND_PCI_DID, pci_dev->device_id()), |
| BI_ABORT_IF(NE, BIND_PCI_CLASS, pci_dev->class_id()), |
| BI_ABORT_IF(NE, BIND_PCI_SUBCLASS, pci_dev->subclass()), |
| BI_ABORT_IF(NE, BIND_PCI_INTERFACE, pci_dev->prog_if()), |
| BI_ABORT_IF(NE, BIND_PCI_REVISION, pci_dev->rev_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 sysmem_match[] = { |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_SYSMEM), |
| }; |
| |
| const device_fragment_part_t sysmem_fragment[] = { |
| {std::size(sysmem_match), sysmem_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}, |
| }; |
| |
| // These are laid out so that ACPI can be optionally included via the number |
| // of fragments specified. |
| const device_fragment_t fragments[] = { |
| {"pci", std::size(pci_fragment), pci_fragment}, |
| {"sysmem", std::size(sysmem_fragment), sysmem_fragment}, |
| {"acpi", std::size(acpi_fragment), acpi_fragment}, |
| }; |
| |
| composite_device_desc_t composite_desc = { |
| .props = pci_device_props, |
| .props_count = std::size(pci_device_props), |
| .fragments = fragments, |
| .fragments_count = (pci_dev->has_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", pci_dev->config()->addr()); |
| status = banjo_dev_unowned->DdkAddComposite(composite_name, &composite_desc); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[%s] Failed to create pci banjo composite: %s", pci_dev->config()->addr(), |
| zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx_status_t BanjoDevice::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; |
| } |
| |
| zx_status_t BanjoDevice::PciReadConfig8(uint16_t offset, uint8_t* out_value) { |
| zx::status<uint8_t> result = device_->ReadConfig<uint8_t, PciReg8>(offset); |
| if (result.is_ok()) { |
| *out_value = result.value(); |
| } |
| return LOG_STATUS(TRACE, result.status_value(), "%#x", offset); |
| } |
| |
| zx_status_t BanjoDevice::PciReadConfig16(uint16_t offset, uint16_t* out_value) { |
| zx::status<uint16_t> result = device_->ReadConfig<uint16_t, PciReg16>(offset); |
| if (result.is_ok()) { |
| *out_value = result.value(); |
| } |
| return LOG_STATUS(TRACE, result.status_value(), "%#x", offset); |
| } |
| |
| zx_status_t BanjoDevice::PciReadConfig32(uint16_t offset, uint32_t* out_value) { |
| zx::status<uint32_t> result = device_->ReadConfig<uint32_t, PciReg32>(offset); |
| if (result.is_ok()) { |
| *out_value = result.value(); |
| } |
| return LOG_STATUS(TRACE, result.status_value(), "%#x", offset); |
| } |
| |
| zx_status_t BanjoDevice::PciWriteConfig8(uint16_t offset, uint8_t value) { |
| zx_status_t status = device_->WriteConfig<uint8_t, PciReg8>(offset, value); |
| return LOG_STATUS(TRACE, status, "%#x, %#x", offset, value); |
| } |
| |
| zx_status_t BanjoDevice::PciWriteConfig16(uint16_t offset, uint16_t value) { |
| zx_status_t status = device_->WriteConfig<uint16_t, PciReg16>(offset, value); |
| return LOG_STATUS(TRACE, status, "%#x, %#x", offset, value); |
| } |
| |
| zx_status_t BanjoDevice::PciWriteConfig32(uint16_t offset, uint32_t value) { |
| zx_status_t status = device_->WriteConfig<uint32_t, PciReg32>(offset, value); |
| return LOG_STATUS(TRACE, status, "%#x, %#x", offset, value); |
| } |
| |
| zx_status_t BanjoDevice::PciSetBusMastering(bool enable) { |
| fbl::AutoLock dev_lock(device_->dev_lock()); |
| zx_status_t status = device_->SetBusMastering(enable); |
| return LOG_STATUS(DEBUG, status, "%d", enable); |
| } |
| |
| zx_status_t BanjoDevice::PciGetBar(uint32_t bar_id, pci_bar_t* out_bar) { |
| zx_status_t status = ZX_OK; |
| fbl::AutoLock dev_lock(device_->dev_lock()); |
| if (bar_id >= device_->bar_count()) { |
| return LOG_STATUS(DEBUG, ZX_ERR_INVALID_ARGS, "%u", bar_id); |
| } |
| |
| // Don't return bars corresponding to unused bars or the upper half |
| // of a 64 bit bar. |
| auto& bar = device_->bars()[bar_id]; |
| if (!bar) { |
| return LOG_STATUS(DEBUG, ZX_ERR_NOT_FOUND, "%u", bar_id); |
| } |
| |
| size_t bar_size = bar->size; |
| #ifdef ENABLE_MSIX |
| // If this device shares BAR data with either of the MSI-X tables then we need |
| // to determine what portions of the BAR the driver can be permitted to |
| // access. |
| if (device_->capabilities().msix) { |
| zx::status<size_t> result = device_->capabilities().msix->GetBarDataSize(*bar); |
| if (result.is_error()) { |
| return LOG_STATUS(DEBUG, result.status_value(), "%u", bar_id); |
| } |
| bar_size = result.value(); |
| } |
| #endif |
| |
| out_bar->bar_id = bar_id; |
| out_bar->size = bar_size; |
| out_bar->type = (bar->is_mmio) ? PCI_BAR_TYPE_MMIO : PCI_BAR_TYPE_IO; |
| |
| // MMIO Bars have an associated VMO for the driver to map, whereas IO bars |
| // have a Resource corresponding to an IO range for the driver to access. |
| // These are mutually exclusive, so only one handle is ever needed. |
| zx::status<zx::handle> result; |
| if (bar->is_mmio) { |
| result = bar->allocation->CreateVmo(); |
| if (result.is_ok()) { |
| out_bar->result.vmo = result.value().release(); |
| } |
| } else { // Bar using IOports |
| result = bar->allocation->CreateResource(); |
| if (result.is_ok()) { |
| out_bar->result.io.resource = result.value().release(); |
| out_bar->result.io.address = bar->address; |
| } |
| } |
| |
| if (result.is_error()) { |
| zxlogf(ERROR, "[%s] Failed to create %s for BAR %u (type = %s, range = [%#lx, %#lx)): %s", |
| device_->config()->addr(), (bar->is_mmio) ? "VMO" : "resource", bar_id, |
| (bar->is_mmio) ? "MMIO" : "IO", bar->address, bar->address + bar->size, |
| zx_status_get_string(status)); |
| } |
| return LOG_STATUS(DEBUG, result.status_value(), "%u", bar_id); |
| } |
| |
| zx_status_t BanjoDevice::PciGetBti(uint32_t index, zx::bti* out_bti) { |
| fbl::AutoLock dev_lock(device_->dev_lock()); |
| zx_status_t status = device_->bdi()->GetBti(device_, index, out_bti); |
| return LOG_STATUS(DEBUG, status, "%u", index); |
| } |
| |
| zx_status_t BanjoDevice::PciGetDeviceInfo(pci_device_info_t* out_info) { |
| 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 = device_->bus_id(); |
| out_info->dev_id = device_->dev_id(); |
| out_info->func_id = device_->func_id(); |
| return LOG_STATUS(DEBUG, ZX_OK, ""); |
| } |
| |
| namespace { |
| |
| // Capabilities and Extended Capabilities only differ by what list they're in along with the |
| // size of their entries. We can offload most of the work into a templated work function. |
| template <class T, class L> |
| zx_status_t GetFirstOrNextCapability(const L& list, T cap_id, bool is_first, |
| std::optional<T> scan_offset, T* out_offset) { |
| // Scan for the capability type requested, returning the first capability |
| // found after we've seen the capability owning the previous offset. We |
| // can't scan entirely based on offset being >= than a given base because |
| // capabilities pointers can point backwards in config space as long as the |
| // structures are valid. |
| bool found_prev = is_first; |
| |
| for (auto& cap : list) { |
| if (found_prev) { |
| if (cap.id() == cap_id) { |
| *out_offset = cap.base(); |
| return ZX_OK; |
| } |
| } else { |
| if (cap.base() == scan_offset) { |
| found_prev = true; |
| } |
| } |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| } // namespace |
| |
| zx_status_t BanjoDevice::PciGetFirstCapability(uint8_t cap_id, uint8_t* out_offset) { |
| zx_status_t status = GetFirstOrNextCapability<uint8_t, CapabilityList>( |
| device_->capabilities().list, cap_id, /*is_first=*/true, std::nullopt, out_offset); |
| return LOG_STATUS(DEBUG, status, "%#x", cap_id); |
| } |
| |
| zx_status_t BanjoDevice::PciGetNextCapability(uint8_t cap_id, uint8_t offset, uint8_t* out_offset) { |
| zx_status_t status = |
| GetFirstOrNextCapability<uint8_t, CapabilityList>(device_->capabilities().list, cap_id, |
| /*is_first=*/false, offset, out_offset); |
| return LOG_STATUS(DEBUG, status, "%#x, %#x", cap_id, offset); |
| } |
| |
| zx_status_t BanjoDevice::PciGetFirstExtendedCapability(uint16_t cap_id, uint16_t* out_offset) { |
| zx_status_t status = GetFirstOrNextCapability<uint16_t, ExtCapabilityList>( |
| device_->capabilities().ext_list, cap_id, true, std::nullopt, out_offset); |
| return LOG_STATUS(DEBUG, status, "%#x", cap_id); |
| } |
| |
| zx_status_t BanjoDevice::PciGetNextExtendedCapability(uint16_t cap_id, uint16_t offset, |
| uint16_t* out_offset) { |
| zx_status_t status = GetFirstOrNextCapability<uint16_t, ExtCapabilityList>( |
| device_->capabilities().ext_list, cap_id, false, offset, out_offset); |
| return LOG_STATUS(DEBUG, status, "%#x, %#x", cap_id, offset); |
| } |
| |
| void BanjoDevice::PciGetInterruptModes(pci_interrupt_modes_t* modes) { |
| *modes = device_->GetInterruptModes(); |
| } |
| |
| zx_status_t BanjoDevice::PciSetInterruptMode(pci_interrupt_mode_t mode, |
| uint32_t requested_irq_count) { |
| zx_status_t status = device_->SetIrqMode(mode, requested_irq_count); |
| return LOG_STATUS(DEBUG, status, "%u, %u", mode, requested_irq_count); |
| } |
| |
| zx_status_t BanjoDevice::PciMapInterrupt(uint32_t which_irq, zx::interrupt* out_handle) { |
| zx::status<zx::interrupt> result = device_->MapInterrupt(which_irq); |
| if (result.is_ok()) { |
| *out_handle = std::move(result.value()); |
| } |
| |
| return LOG_STATUS(DEBUG, result.status_value(), "%u", which_irq); |
| } |
| |
| zx_status_t BanjoDevice::PciAckInterrupt() { |
| fbl::AutoLock dev_lock(device_->dev_lock()); |
| return device_->AckLegacyIrq(); |
| } |
| |
| zx_status_t BanjoDevice::PciResetDevice() { return LOG_STATUS(DEBUG, ZX_ERR_NOT_SUPPORTED, ""); } |
| |
| } // namespace pci |