blob: 8476ee443b55331ae2bf0b41e8823f25eb106b2a [file] [log] [blame]
// Copyright 2021 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/cpp/banjo.h>
#include <lib/ddk/debug.h>
#include <lib/zx/status.h>
#include "src/devices/bus/drivers/pci/proxy_rpc.h"
#include "src/devices/internal/drivers/fragment/fragment-proxy.h"
#include "src/devices/internal/drivers/fragment/proxy-protocol.h"
namespace fragment {
zx_status_t FragmentProxy::PciRpc(pci::PciRpcOp op, zx_handle_t* rd_handle,
const zx_handle_t* wr_handle, PciRpcRequest* req,
PciRpcResponse* resp) {
ZX_DEBUG_ASSERT(req != nullptr);
ZX_DEBUG_ASSERT(resp != nullptr);
if (rpc_ == ZX_HANDLE_INVALID) {
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t rd_handle_cnt = 0;
if (rd_handle) {
// Since only the caller knows if they expected a valid handle back, make
// sure the handle is invalid if we didn't get one.
*rd_handle = ZX_HANDLE_INVALID;
rd_handle_cnt = 1;
}
uint32_t wr_handle_cnt = 0;
if (wr_handle && *wr_handle != ZX_HANDLE_INVALID) {
wr_handle_cnt = 1;
}
req->header.proto_id = ZX_PROTOCOL_PCI;
req->op = op;
zx_channel_call_args_t cc_args{};
cc_args.wr_bytes = req;
cc_args.wr_num_bytes = sizeof(*req);
cc_args.rd_bytes = resp;
cc_args.rd_num_bytes = sizeof(*resp);
cc_args.rd_handles = rd_handle;
cc_args.rd_num_handles = rd_handle_cnt;
cc_args.wr_handles = wr_handle;
cc_args.wr_num_handles = wr_handle_cnt;
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t st =
rpc_.call(0, zx::time(ZX_TIME_INFINITE), &cc_args, &actual_bytes, &actual_handles);
if (st != ZX_OK) {
zxlogf(ERROR, "rpc call failed: %s", zx_status_get_string(st));
return st;
}
if (actual_bytes != sizeof(*resp)) {
zxlogf(ERROR, "rpc payload mismatch (expected: %#lx, actual %#x)", sizeof(*resp), actual_bytes);
return ZX_ERR_INTERNAL;
}
return resp->header.status;
}
// TODO(fxbug.dev/33713): Convert this to using a better wire format when we no longer
// have to support the kernel driver.
zx_status_t FragmentProxy::PciGetBar(uint32_t bar_id, pci_bar_t* out_bar) {
PciRpcRequest req{};
PciRpcResponse resp{};
zx_handle_t handle;
req.bar.id = bar_id;
zx_status_t st =
PciRpc(pci::PCI_OP_GET_BAR, /*rd_handle=*/&handle, /*wr_handle=*/nullptr, &req, &resp);
// |st| is the channel operation status, |resp.ret| is the RPC status.
if (st != ZX_OK) {
return st;
}
if (resp.header.status != ZX_OK) {
return resp.header.status;
}
out_bar->bar_id = resp.bar.id;
out_bar->size = resp.bar.size;
out_bar->type = (resp.bar.is_mmio) ? PCI_BAR_TYPE_MMIO : PCI_BAR_TYPE_IO;
if (resp.bar.is_mmio) {
out_bar->result.vmo = handle;
} else {
// x86 PIO space access requires permission in the I/O bitmap. If an IO BAR
// is used then the handle returned corresponds to a resource with access to
// this range of IO space. On other platforms, like ARM, IO bars are still
// handled in MMIO space so this type will be unused.
out_bar->result.io.address = resp.bar.address;
st = zx_ioports_request(handle, static_cast<uint16_t>(resp.bar.address),
static_cast<uint32_t>(resp.bar.size));
if (st != ZX_OK) {
zxlogf(ERROR, "Failed to map IO window %#lx for bar into process: %d", resp.bar.address, st);
return st;
}
out_bar->result.io.resource = handle;
}
return ZX_OK;
}
zx_status_t FragmentProxy::PciSetBusMastering(bool enable) {
PciRpcRequest req{};
PciRpcResponse resp{};
req.enable = enable;
return PciRpc(pci::PCI_OP_ENABLE_BUS_MASTER, /*rd_handle=*/nullptr, /*wr_handle=*/nullptr, &req,
&resp);
}
zx_status_t FragmentProxy::PciResetDevice() { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t FragmentProxy::PciAckInterrupt() {
#ifdef ENABLE_USER_PCI
PciRpcRequest req{};
PciRpcResponse resp{};
return PciRpc(pci::PCI_OP_ACK_INTERRUPT, /*rd_handle=*/nullptr, /*wr_handle=*/nullptr,
/*req=*/&req, /*resp=*/&resp);
#else
return ZX_OK;
#endif
}
zx_status_t FragmentProxy::PciMapInterrupt(uint32_t which_irq, zx::interrupt* out_handle) {
PciRpcRequest req{};
PciRpcResponse resp{};
req.irq.which_irq = which_irq;
zx_handle_t irq_handle;
zx_status_t st = PciRpc(pci::PCI_OP_MAP_INTERRUPT, /*rd_handle=*/&irq_handle,
/*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
out_handle->reset(irq_handle);
}
return st;
}
void FragmentProxy::PciGetInterruptModes(pci_interrupt_modes_t* out_modes) {
PciRpcRequest req{};
PciRpcResponse resp{};
zx_status_t st = PciRpc(pci::PCI_OP_GET_INTERRUPT_MODES, /*rd_handle=*/nullptr,
/*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
*out_modes = resp.irq.modes;
}
}
zx_status_t FragmentProxy::PciSetInterruptMode(pci_interrupt_mode_t mode,
uint32_t requested_irq_count) {
PciRpcRequest req{};
PciRpcResponse resp{};
req.irq.mode = mode;
req.irq.requested_irqs = requested_irq_count;
return PciRpc(pci::PCI_OP_SET_INTERRUPT_MODE, /*rd_handle=*/nullptr, /*wr_handle=*/nullptr, &req,
&resp);
}
zx_status_t FragmentProxy::PciGetDeviceInfo(pci_device_info_t* out_info) {
PciRpcRequest req{};
PciRpcResponse resp{};
zx_status_t st = PciRpc(pci::PCI_OP_GET_DEVICE_INFO, /*rd_handle=*/nullptr,
/*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
*out_info = resp.info;
}
return st;
}
template <typename T>
zx_status_t FragmentProxy::PciReadConfig(uint16_t offset, T* out_value) {
PciRpcRequest req{};
PciRpcResponse resp{};
req.cfg.offset = offset;
req.cfg.width = static_cast<uint16_t>(sizeof(T));
zx_status_t st = PciRpc(pci::PCI_OP_CONFIG_READ, /*rd_handle=*/nullptr,
/*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
*out_value = static_cast<T>(resp.cfg.value);
}
return st;
}
zx_status_t FragmentProxy::PciReadConfig8(uint16_t offset, uint8_t* out_value) {
return PciReadConfig(offset, out_value);
}
zx_status_t FragmentProxy::PciReadConfig16(uint16_t offset, uint16_t* out_value) {
return PciReadConfig(offset, out_value);
}
zx_status_t FragmentProxy::PciReadConfig32(uint16_t offset, uint32_t* out_value) {
return PciReadConfig(offset, out_value);
}
template <typename T>
zx_status_t FragmentProxy::PciWriteConfig(uint16_t offset, T value) {
PciRpcRequest req{};
PciRpcResponse resp{};
req.cfg.offset = offset;
req.cfg.width = static_cast<uint16_t>(sizeof(T));
req.cfg.value = value;
return PciRpc(pci::PCI_OP_CONFIG_WRITE, /*rd_handle=*/nullptr, /*wr_handle=*/nullptr, &req,
&resp);
}
zx_status_t FragmentProxy::PciWriteConfig8(uint16_t offset, uint8_t value) {
return PciWriteConfig(offset, value);
}
zx_status_t FragmentProxy::PciWriteConfig16(uint16_t offset, uint16_t value) {
return PciWriteConfig(offset, value);
}
zx_status_t FragmentProxy::PciWriteConfig32(uint16_t offset, uint32_t value) {
return PciWriteConfig(offset, value);
}
zx_status_t FragmentProxy::PciGetFirstCapability(uint8_t cap_id, uint8_t* out_offset) {
return PciGetNextCapability(cap_id, pci::kPciCapOffsetFirst, out_offset);
}
zx_status_t FragmentProxy::PciGetNextCapability(uint8_t cap_id, uint8_t offset,
uint8_t* out_offset) {
if (!out_offset) {
return ZX_ERR_INVALID_ARGS;
}
PciRpcRequest req{};
req.cap.id = cap_id;
if (offset == pci::kPciCapOffsetFirst) {
req.cap.is_first = true;
req.cap.offset = 0;
} else {
req.cap.offset = offset;
}
PciRpcResponse resp{};
zx_status_t st = PciRpc(pci::PCI_OP_GET_NEXT_CAPABILITY, /*rd_handle=*/nullptr,
/*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
*out_offset = static_cast<uint8_t>(resp.cap.offset);
}
return st;
}
zx_status_t FragmentProxy::PciGetFirstExtendedCapability(uint16_t cap_id, uint16_t* out_offset) {
return PciGetNextExtendedCapability(cap_id, pci::kPciExtCapOffsetFirst, out_offset);
}
zx_status_t FragmentProxy::PciGetNextExtendedCapability(uint16_t cap_id, uint16_t offset,
uint16_t* out_offset) {
if (!out_offset) {
return ZX_ERR_INVALID_ARGS;
}
PciRpcRequest req{};
req.cap.id = cap_id;
if (offset == pci::kPciExtCapOffsetFirst) {
req.cap.is_first = true;
req.cap.offset = 0;
} else {
req.cap.offset = offset;
}
req.cap.is_extended = true;
PciRpcResponse resp{};
zx_status_t st = PciRpc(pci::PCI_OP_GET_NEXT_CAPABILITY, /*rd_handle=*/nullptr,
/*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
*out_offset = resp.cap.offset;
}
return st;
}
zx_status_t FragmentProxy::PciGetBti(uint32_t index, zx::bti* out_bti) {
PciRpcRequest req{};
PciRpcResponse resp{};
req.bti_index = index;
zx_handle_t handle;
zx_status_t st =
PciRpc(pci::PCI_OP_GET_BTI, /*rd_handle=*/&handle, /*wr_handle=*/nullptr, &req, &resp);
if (st == ZX_OK) {
out_bti->reset(handle);
}
return st;
}
} // namespace fragment