blob: 978cfcc5c91a70dbff664ccc35c4686a791e86fc [file] [log] [blame]
// 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 <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/fidl.h>
#include <zircon/process.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/protocol/pci.h>
#include <ddk/protocol/sysmem.h>
#include <hw/pci.h>
#include "kpci-private.h"
zx_status_t pci_rpc_request(kpci_device_t* dev, uint32_t op, zx_handle_t* handle, pci_msg_t* req,
pci_msg_t* resp) {
if (dev->pciroot_rpcch == ZX_HANDLE_INVALID) {
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t in_handle_cnt = (op == PCI_OP_CONNECT_SYSMEM) ? 1 : 0;
uint32_t handle_cnt = 0;
if (handle) {
// Since only the caller knows if they expected a valid handle back, make
// sure the handle reads INVALID if we didn't get one.
*handle = ZX_HANDLE_INVALID;
handle_cnt = 1;
}
req->hdr.ordinal = op;
zx_channel_call_args_t cc_args = {
.wr_bytes = req,
.rd_bytes = resp,
.rd_handles = handle,
.wr_num_bytes = sizeof(*req),
.rd_num_bytes = sizeof(*resp),
.rd_num_handles = handle_cnt,
.wr_handles = in_handle_cnt ? &req->handle : NULL,
.wr_num_handles = in_handle_cnt,
};
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t st = zx_channel_call(dev->pciroot_rpcch, 0, ZX_TIME_INFINITE, &cc_args, &actual_bytes,
&actual_handles);
if (st != ZX_OK) {
return st;
}
if (actual_bytes != sizeof(*resp)) {
return ZX_ERR_INTERNAL;
}
return (zx_status_t)resp->hdr.ordinal;
}
// pci_op_* methods are called by the proxy devhost. For each PCI
// protocol method there is generally a pci_op_* method for the proxy
// devhost and a corresponding kpci_* method in the top devhost that the
// protocol request is handled by.
// Enables or disables bus mastering for a particular device.
static zx_status_t pci_op_enable_bus_master(void* ctx, bool enable) {
kpci_device_t* dev = ctx;
pci_msg_t req = {
.enable = enable,
};
pci_msg_t resp = {};
return pci_rpc_request(dev, PCI_OP_ENABLE_BUS_MASTER, NULL, &req, &resp);
}
// Resets the device.
static zx_status_t pci_op_reset_device(void* ctx) {
kpci_device_t* dev = ctx;
pci_msg_t req = {};
pci_msg_t resp = {};
return pci_rpc_request(dev, PCI_OP_RESET_DEVICE, NULL, &req, &resp);
}
// These reads are proxied directly over to the device's PciConfig object so the validity of the
// widths and offsets will be validated on that end and then trickle back to this level of the
// protocol.
static zx_status_t pci_op_config_read(void* ctx, uint16_t offset, size_t width, uint32_t* val) {
kpci_device_t* dev = ctx;
if (width > sizeof(uint32_t) || val == NULL) {
return ZX_ERR_INVALID_ARGS;
}
pci_msg_t req = {
.cfg =
{
.offset = offset,
.width = (uint16_t)width,
},
};
pci_msg_t resp = {};
zx_status_t st = pci_rpc_request(dev, PCI_OP_CONFIG_READ, NULL, &req, &resp);
if (st == ZX_OK) {
*val = resp.cfg.value;
}
return st;
}
static zx_status_t pci_op_config_read8(void* ctx, uint16_t offset, uint8_t* val) {
uint32_t tmp;
zx_status_t st = pci_op_config_read(ctx, offset, sizeof(*val), &tmp);
*val = tmp & UINT8_MAX;
return st;
}
static zx_status_t pci_op_config_read16(void* ctx, uint16_t offset, uint16_t* val) {
uint32_t tmp;
zx_status_t st = pci_op_config_read(ctx, offset, sizeof(*val), &tmp);
*val = tmp & UINT16_MAX;
return st;
}
static zx_status_t pci_op_config_read32(void* ctx, uint16_t offset, uint32_t* val) {
return pci_op_config_read(ctx, offset, sizeof(uint32_t), val);
}
// These reads are proxied directly over to the device's PciConfig object so the validity of the
// widths and offsets will be validated on that end and then trickle back to this level of the
// protocol.
static zx_status_t pci_op_config_write(void* ctx, uint16_t offset, size_t width, uint32_t val) {
kpci_device_t* dev = ctx;
if (width > sizeof(uint32_t)) {
return ZX_ERR_INVALID_ARGS;
}
pci_msg_t req = {
.cfg =
{
.offset = offset,
.width = (uint16_t)width,
.value = val,
},
};
pci_msg_t resp = {};
return pci_rpc_request(dev, PCI_OP_CONFIG_WRITE, NULL, &req, &resp);
}
static zx_status_t pci_op_config_write8(void* ctx, uint16_t offset, uint8_t val) {
return pci_op_config_write(ctx, offset, sizeof(uint8_t), val);
}
static zx_status_t pci_op_config_write16(void* ctx, uint16_t offset, uint16_t val) {
return pci_op_config_write(ctx, offset, sizeof(uint16_t), val);
}
static zx_status_t pci_op_config_write32(void* ctx, uint16_t offset, uint32_t val) {
return pci_op_config_write(ctx, offset, sizeof(uint32_t), val);
}
static zx_status_t pci_op_get_next_capability(void* ctx, uint8_t type, uint8_t in_offset,
uint8_t* out_offset) {
uint32_t cap_offset = 0;
pci_op_config_read(ctx, in_offset + 1, sizeof(uint8_t), &cap_offset);
uint8_t limit = 64;
zx_status_t st;
// Walk the capability list looking for the type requested, starting at the offset
// passed in. limit acts as a barrier in case of an invalid capability pointer list
// that causes us to iterate forever otherwise.
while (cap_offset != 0 && limit--) {
uint32_t type_id = 0;
if ((st = pci_op_config_read(ctx, (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 == type) {
*out_offset = (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);
return ZX_ERR_BAD_STATE;
}
if ((st = pci_op_config_read(ctx, (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);
return ZX_ERR_BAD_STATE;
}
}
// No more entries are in the list
return ZX_ERR_NOT_FOUND;
}
static zx_status_t pci_op_get_first_capability(void* ctx, uint8_t type, uint8_t* out_offset) {
// the next_capability method will always look at the second byte next
// pointer to fetch the next capability. By offsetting the CapPtr field
// by -1 we can pretend we're working with a normal capability entry
return pci_op_get_next_capability(ctx, type, PCI_CFG_CAPABILITIES_PTR - 1u, out_offset);
}
static zx_status_t pci_op_get_bar(void* ctx, uint32_t bar_id, zx_pci_bar_t* out_bar) {
kpci_device_t* dev = ctx;
pci_msg_t req = {
.bar.id = bar_id,
};
pci_msg_t resp = {};
zx_handle_t handle;
zx_status_t st = pci_rpc_request(dev, PCI_OP_GET_BAR, &handle, &req, &resp);
if (st == ZX_OK) {
// Grab the payload and copy the handle over if one was passed back to us
*out_bar = resp.bar;
if (out_bar->type == ZX_PCI_BAR_TYPE_PIO) {
#if __x86_64__
// x86 PIO space access requires permission in the I/O bitmap
// TODO: this is the last remaining use of get_root_resource in pci
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
st =
zx_ioports_request(get_root_resource(), (uint16_t)out_bar->addr, (uint16_t)out_bar->size);
if (st != ZX_OK) {
zxlogf(ERROR, "Failed to map IO window for bar into process: %d", st);
return st;
}
#else
zxlogf(INFO,
"%s: PIO bars may not be supported correctly on this arch. "
"Please have someone check this!\n",
__func__);
#endif
} else {
out_bar->handle = handle;
}
}
return st;
}
static zx_status_t pci_op_map_interrupt(void* ctx, uint32_t which_irq, zx_handle_t* out_handle) {
if (!out_handle) {
return ZX_ERR_INVALID_ARGS;
}
kpci_device_t* dev = ctx;
pci_msg_t req = {
.irq.which_irq = which_irq,
};
pci_msg_t resp = {};
zx_handle_t handle;
zx_status_t st = pci_rpc_request(dev, PCI_OP_MAP_INTERRUPT, &handle, &req, &resp);
if (st == ZX_OK) {
*out_handle = handle;
}
return st;
}
static zx_status_t pci_op_get_bti(void* ctx, uint32_t index, zx_handle_t* out_handle) {
if (!out_handle) {
return ZX_ERR_INVALID_ARGS;
}
kpci_device_t* dev = ctx;
pci_msg_t req = {.bti_index = index};
pci_msg_t resp = {};
zx_handle_t handle;
zx_status_t st = pci_rpc_request(dev, PCI_OP_GET_BTI, &handle, &req, &resp);
if (st == ZX_OK) {
*out_handle = handle;
}
return st;
}
static zx_status_t pci_op_query_irq_mode(void* ctx, zx_pci_irq_mode_t mode,
uint32_t* out_max_irqs) {
kpci_device_t* dev = ctx;
pci_msg_t req = {
.irq.mode = mode,
};
pci_msg_t resp = {};
zx_status_t st = pci_rpc_request(dev, PCI_OP_QUERY_IRQ_MODE, NULL, &req, &resp);
if (st == ZX_OK) {
*out_max_irqs = resp.irq.max_irqs;
}
return st;
}
static zx_status_t pci_op_set_irq_mode(void* ctx, zx_pci_irq_mode_t mode,
uint32_t requested_irq_count) {
kpci_device_t* dev = ctx;
pci_msg_t req = {
.irq =
{
.mode = mode,
.requested_irqs = requested_irq_count,
},
};
pci_msg_t resp = {};
return pci_rpc_request(dev, PCI_OP_SET_IRQ_MODE, NULL, &req, &resp);
}
static zx_status_t pci_op_configure_irq_mode(void* ctx, uint32_t requested_irq_count) {
kpci_device_t* dev = ctx;
pci_msg_t req = {
.irq =
{
.requested_irqs = requested_irq_count,
},
};
pci_msg_t resp = {};
return pci_rpc_request(dev, PCI_OP_CONFIGURE_IRQ_MODE, NULL, &req, &resp);
}
static zx_status_t pci_op_get_device_info(void* ctx, zx_pcie_device_info_t* out_info) {
kpci_device_t* dev = ctx;
pci_msg_t req = {};
pci_msg_t resp = {};
zx_status_t st = pci_rpc_request(dev, PCI_OP_GET_DEVICE_INFO, NULL, &req, &resp);
if (st == ZX_OK) {
*out_info = resp.info;
}
return st;
}
static pci_protocol_ops_t _pci_protocol = {
.enable_bus_master = pci_op_enable_bus_master,
.reset_device = pci_op_reset_device,
.get_bar = pci_op_get_bar,
.map_interrupt = pci_op_map_interrupt,
.configure_irq_mode = pci_op_configure_irq_mode,
.query_irq_mode = pci_op_query_irq_mode,
.set_irq_mode = pci_op_set_irq_mode,
.get_device_info = pci_op_get_device_info,
.config_read8 = pci_op_config_read8,
.config_read16 = pci_op_config_read16,
.config_read32 = pci_op_config_read32,
.config_write8 = pci_op_config_write8,
.config_write16 = pci_op_config_write16,
.config_write32 = pci_op_config_write32,
.get_next_capability = pci_op_get_next_capability,
.get_first_capability = pci_op_get_first_capability,
.get_bti = pci_op_get_bti,
};
static zx_status_t pci_sysmem_connect(void* ctx, zx_handle_t handle) {
kpci_device_t* dev = ctx;
pci_msg_t req = {
.handle = handle,
};
pci_msg_t resp = {};
return pci_rpc_request(dev, PCI_OP_CONNECT_SYSMEM, NULL, &req, &resp);
};
static sysmem_protocol_ops_t sysmem_protocol = {
.connect = pci_sysmem_connect,
};
static zx_status_t get_protocol(void* ctx, uint32_t proto_id, void* protocol) {
if (proto_id == ZX_PROTOCOL_SYSMEM) {
sysmem_protocol_t* proto = protocol;
proto->ctx = ctx;
proto->ops = &sysmem_protocol;
return ZX_OK;
} else if (proto_id == ZX_PROTOCOL_PCI) {
pci_protocol_t* proto = protocol;
proto->ctx = ctx;
proto->ops = &_pci_protocol;
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
// A device ops structure appears to be required still, but does not need
// to have any of the methods implemented. All of the proxy's work is done
// in its protocol methods.
static zx_protocol_device_t device_ops = {
.version = DEVICE_OPS_VERSION,
.get_protocol = get_protocol,
};
static zx_status_t pci_proxy_create(void* ctx, zx_device_t* parent, const char* name,
const char* args, zx_handle_t rpcch) {
if (!parent || !args) {
return ZX_ERR_BAD_STATE;
}
unsigned long index = strtoul(args, NULL, 10);
zx_pcie_device_info_t info;
kpci_device_t* device = calloc(1, sizeof(kpci_device_t));
if (!device) {
return ZX_ERR_NO_MEMORY;
}
if (index > UINT32_MAX) {
return ZX_ERR_INVALID_ARGS;
}
// The channel and index are all we need to make this protocol call and the
// upper devhost is already fully initialized at this point so we can get
// our bind information from it.
device->index = (uint32_t)index;
device->pciroot_rpcch = rpcch;
zx_status_t st = pci_op_get_device_info(device, &info);
if (st != ZX_OK) {
free(device);
return st;
}
char devname[20];
snprintf(devname, sizeof(devname), "%02x:%02x.%1x", info.bus_id, info.dev_id, info.func_id);
device_add_args_t device_args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = devname,
.ctx = device,
.ops = &device_ops,
.proto_id = ZX_PROTOCOL_PCI,
.proto_ops = &_pci_protocol,
};
st = device_add(parent, &device_args, &device->zxdev);
if (st != ZX_OK) {
free(device);
}
return st;
}
static zx_driver_ops_t kpci_driver_ops = {
.version = DRIVER_OPS_VERSION,
.create = pci_proxy_create,
};
// clang-format off
ZIRCON_DRIVER_BEGIN(pci_proxy, kpci_driver_ops, "zircon", "0.1", 1)
BI_ABORT_IF_AUTOBIND,
ZIRCON_DRIVER_END(pci_proxy)
// clang-format on