blob: f2e6f968010fc7ecb87ac3794083ce491584764b [file] [log] [blame]
// Copyright 2017 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/platform/platform-proxy.h"
#include <lib/zircon-internal/align.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <utility>
#include <ddk/debug.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include "src/devices/bus/drivers/platform/platform-bus-bind.h"
namespace platform_bus {
void PlatformProxy::DdkRelease() { delete this; }
zx_status_t PlatformProxy::PDevGetMmio(uint32_t index, pdev_mmio_t* out_mmio) {
if (index >= mmios_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
const Mmio& mmio = mmios_[index];
const zx_paddr_t vmo_base = ZX_ROUNDDOWN(mmio.base, ZX_PAGE_SIZE);
const size_t vmo_size = ZX_ROUNDUP(mmio.base + mmio.length - vmo_base, ZX_PAGE_SIZE);
zx::vmo vmo;
zx_status_t status = zx::vmo::create_physical(mmio.resource, vmo_base, vmo_size, &vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "%s %s: creating vmo failed %d", name_, __FUNCTION__, status);
return status;
}
char name[32];
snprintf(name, sizeof(name), "%s mmio %u", name_, index);
status = vmo.set_property(ZX_PROP_NAME, name, sizeof(name));
if (status != ZX_OK) {
zxlogf(ERROR, "%s %s: setting vmo name failed %d", name_, __FUNCTION__, status);
return status;
}
out_mmio->offset = mmio.base - vmo_base;
out_mmio->vmo = vmo.release();
out_mmio->size = mmio.length;
return ZX_OK;
}
zx_status_t PlatformProxy::PDevGetInterrupt(uint32_t index, uint32_t flags,
zx::interrupt* out_irq) {
if (index >= irqs_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
Irq* irq = &irqs_[index];
if (flags == 0) {
flags = irq->mode;
}
zx_status_t status = zx::interrupt::create(irq->resource, irq->irq, flags, out_irq);
if (status != ZX_OK) {
zxlogf(ERROR, "%s %s: creating interrupt failed: %d", name_, __FUNCTION__, status);
return status;
}
return ZX_OK;
}
zx_status_t PlatformProxy::PDevGetBti(uint32_t index, zx::bti* out_bti) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
req.header.op = PDEV_GET_BTI;
req.index = index;
return Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp), nullptr, 0,
out_bti->reset_and_get_address(), 1, nullptr);
}
zx_status_t PlatformProxy::PDevGetSmc(uint32_t index, zx::resource* out_resource) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
req.header.op = PDEV_GET_SMC;
req.index = index;
return Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp), nullptr, 0,
out_resource->reset_and_get_address(), 1, nullptr);
}
zx_status_t PlatformProxy::PDevGetDeviceInfo(pdev_device_info_t* out_info) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
req.header.op = PDEV_GET_DEVICE_INFO;
auto status = Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
if (status != ZX_OK) {
return status;
}
memcpy(out_info, &resp.device_info, sizeof(*out_info));
return ZX_OK;
}
zx_status_t PlatformProxy::PDevGetBoardInfo(pdev_board_info_t* out_info) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
req.header.op = PDEV_GET_BOARD_INFO;
auto status = Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
if (status != ZX_OK) {
return status;
}
memcpy(out_info, &resp.board_info, sizeof(*out_info));
return ZX_OK;
}
zx_status_t PlatformProxy::Rpc(const platform_proxy_req_t* req, size_t req_length,
platform_proxy_rsp_t* resp, size_t resp_length,
const zx_handle_t* in_handles, size_t in_handle_count,
zx_handle_t* out_handles, size_t out_handle_count,
size_t* out_actual) {
uint32_t resp_size, handle_count;
zx_channel_call_args_t args = {
.wr_bytes = req,
.wr_handles = in_handles,
.rd_bytes = resp,
.rd_handles = out_handles,
.wr_num_bytes = static_cast<uint32_t>(req_length),
.wr_num_handles = static_cast<uint32_t>(in_handle_count),
.rd_num_bytes = static_cast<uint32_t>(resp_length),
.rd_num_handles = static_cast<uint32_t>(out_handle_count),
};
auto status = rpc_channel_.call(0, zx::time::infinite(), &args, &resp_size, &handle_count);
if (status != ZX_OK) {
// This is a fairly serious error; subsequent requests are very likely
// to also fail.
//
// TODO(fxbug.dev/33622): Make this less likely and/or handle differently.
zxlogf(ERROR, "PlatformProxy::Rpc rpc_channel_.call failed - status: %d", status);
return status;
}
status = resp->status;
if (status == ZX_OK && resp_size < sizeof(*resp)) {
zxlogf(ERROR, "PlatformProxy::Rpc resp_size too short: %u", resp_size);
status = ZX_ERR_INTERNAL;
goto fail;
} else if (status == ZX_OK && handle_count != out_handle_count) {
zxlogf(ERROR, "PlatformProxy::Rpc handle count %u expected %zu", handle_count,
out_handle_count);
status = ZX_ERR_INTERNAL;
goto fail;
}
if (out_actual) {
*out_actual = resp_size;
}
fail:
if (status != ZX_OK) {
for (uint32_t i = 0; i < handle_count; i++) {
zx_handle_close(out_handles[i]);
}
}
return status;
}
zx_status_t PlatformProxy::Create(void* ctx, zx_device_t* parent, const char* name,
const char* args, zx_handle_t rpc_channel) {
fbl::AllocChecker ac;
auto proxy = fbl::make_unique_checked<PlatformProxy>(&ac, parent, rpc_channel);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = proxy->Init(parent);
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = proxy.release();
return ZX_OK;
}
zx_status_t PlatformProxy::Init(zx_device_t* parent) {
pdev_device_info_t info;
auto status = PDevGetDeviceInfo(&info);
if (status != ZX_OK) {
return status;
}
memcpy(name_, info.name, sizeof(name_));
metadata_count_ = info.metadata_count;
fbl::AllocChecker ac;
for (uint32_t i = 0; i < info.mmio_count; i++) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
zx_handle_t rsrc_handle;
req.header.op = PDEV_GET_MMIO;
req.index = i;
status =
Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp), NULL, 0, &rsrc_handle, 1, NULL);
if (status != ZX_OK) {
return status;
}
Mmio mmio;
mmio.base = resp.paddr;
mmio.length = resp.length;
mmio.resource.reset(rsrc_handle);
mmios_.push_back(std::move(mmio), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zxlogf(TRACE, "%s: received MMIO %u (base %#lx length %#lx handle %#x)", name_, i, mmio.base,
mmio.length, mmio.resource.get());
}
for (uint32_t i = 0; i < info.irq_count; i++) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
zx_handle_t rsrc_handle;
req.header.op = PDEV_GET_INTERRUPT;
req.index = i;
status =
Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp), NULL, 0, &rsrc_handle, 1, NULL);
if (status != ZX_OK) {
return status;
}
Irq irq;
irq.irq = resp.irq;
irq.mode = resp.mode;
irq.resource.reset(rsrc_handle);
irqs_.push_back(std::move(irq), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zxlogf(TRACE, "%s: received IRQ %u (irq %#x handle %#x)", name_, i, irq.irq,
irq.resource.get());
}
return DdkAdd(name_);
}
static constexpr zx_driver_ops_t proxy_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.create = PlatformProxy::Create;
return ops;
}();
} // namespace platform_bus
ZIRCON_DRIVER(platform_bus_proxy, platform_bus::proxy_driver_ops, "zircon", "0.1");