blob: 4ad1ee20599dbb5e5362b3cb05c28c97f1185651 [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 "platform-proxy.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include <utility>
namespace platform_bus {
zx_status_t ProxyGpio::GpioConfigIn(uint32_t flags) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_CONFIG_IN;
req.index = index_;
req.flags = flags;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyGpio::GpioConfigOut(uint8_t initial_value) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_CONFIG_OUT;
req.index = index_;
req.value = initial_value;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyGpio::GpioSetAltFunction(uint64_t function) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_SET_ALT_FUNCTION;
req.index = index_;
req.alt_function = function;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyGpio::GpioGetInterrupt(uint32_t flags, zx::interrupt* out_irq) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_GET_INTERRUPT;
req.index = index_;
req.flags = flags;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp), nullptr, 0,
out_irq->reset_and_get_address(), 1, nullptr);
}
zx_status_t ProxyGpio::GpioSetPolarity(uint32_t polarity) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_SET_POLARITY;
req.index = index_;
req.polarity = polarity;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyGpio::GpioReleaseInterrupt() {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_RELEASE_INTERRUPT;
req.index = index_;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyGpio::GpioRead(uint8_t* out_value) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_READ;
req.index = index_;
auto status = proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
if (status != ZX_OK) {
return status;
}
*out_value = resp.value;
return ZX_OK;
}
zx_status_t ProxyGpio::GpioWrite(uint8_t value) {
rpc_gpio_req_t req = {};
rpc_gpio_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_GPIO;
req.header.op = GPIO_WRITE;
req.index = index_;
req.value = value;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyClock::ClockEnable() {
rpc_clk_req_t req = {};
platform_proxy_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_CLOCK;
req.header.op = CLK_ENABLE;
req.index = index_;
return proxy_->Rpc(&req.header, sizeof(req), &resp, sizeof(resp));
}
zx_status_t ProxyClock::ClockDisable() {
rpc_clk_req_t req = {};
platform_proxy_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_CLOCK;
req.header.op = CLK_DISABLE;
req.index = index_;
return proxy_->Rpc(&req.header, sizeof(req), &resp, sizeof(resp));
}
zx_status_t ProxySysmem::SysmemConnect(zx::channel allocator_request) {
rpc_sysmem_req_t req = {};
platform_proxy_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_SYSMEM;
req.header.op = SYSMEM_CONNECT;
zx_handle_t handle = allocator_request.release();
return proxy_->Rpc(&req.header, sizeof(req), &resp, sizeof(resp), &handle,
1, nullptr, 0, nullptr);
}
zx_status_t ProxySysmem::SysmemRegisterHeap(uint64_t heap,
zx::channel heap_connection) {
rpc_sysmem_req_t req = {};
platform_proxy_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_SYSMEM;
req.header.op = SYSMEM_REGISTER_HEAP;
req.heap = heap;
zx_handle_t handle = heap_connection.release();
return proxy_->Rpc(&req.header, sizeof(req), &resp, sizeof(resp), &handle,
1, nullptr, 0, nullptr);
}
zx_status_t ProxyAmlogicCanvas::AmlogicCanvasConfig(zx::vmo vmo, size_t offset,
const canvas_info_t* info,
uint8_t* out_canvas_idx) {
rpc_amlogic_canvas_req_t req = {};
rpc_amlogic_canvas_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_AMLOGIC_CANVAS;
req.header.op = AMLOGIC_CANVAS_CONFIG;
req.offset = offset;
req.info = *info;
zx_handle_t handle = vmo.release();
auto status = proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp),
&handle, 1, nullptr, 0, nullptr);
if (status != ZX_OK) {
return status;
}
*out_canvas_idx = resp.canvas_idx;
return ZX_OK;
}
zx_status_t ProxyAmlogicCanvas::AmlogicCanvasFree(uint8_t canvas_idx) {
rpc_amlogic_canvas_req_t req = {};
rpc_amlogic_canvas_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_AMLOGIC_CANVAS;
req.header.op = AMLOGIC_CANVAS_FREE;
req.canvas_idx = canvas_idx;
return proxy_->Rpc(&req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t PlatformProxy::DdkGetProtocol(uint32_t proto_id, void* out) {
auto* proto = static_cast<ddk::AnyProtocol*>(out);
switch (proto_id) {
case ZX_PROTOCOL_PDEV: {
proto->ops = &pdev_protocol_ops_;
proto->ctx = this;
return ZX_OK;
}
case ZX_PROTOCOL_GPIO: {
auto count = gpios_.size();
if (count == 0) {
return ZX_ERR_NOT_SUPPORTED;
} else if (count > 1) {
zxlogf(ERROR, "%s: device has more than one GPIO\n", __func__);
return ZX_ERR_BAD_STATE;
}
// Return zeroth GPIO resource.
auto* proto = static_cast<gpio_protocol_t*>(out);
gpios_[0].GetProtocol(proto);
return ZX_OK;
}
case ZX_PROTOCOL_CLOCK: {
auto count = clocks_.size();
if (count == 0) {
return ZX_ERR_NOT_SUPPORTED;
} else if (count > 1) {
zxlogf(ERROR, "%s: device has more than one clock\n", __func__);
return ZX_ERR_BAD_STATE;
}
// Return zeroth clock resource.
auto* proto = static_cast<clock_protocol_t*>(out);
clocks_[0].GetProtocol(proto);
return ZX_OK;
}
case ZX_PROTOCOL_SYSMEM: {
auto* proto = static_cast<sysmem_protocol_t*>(out);
sysmem_.GetProtocol(proto);
return ZX_OK;
}
case ZX_PROTOCOL_AMLOGIC_CANVAS: {
auto* proto = static_cast<amlogic_canvas_protocol_t*>(out);
canvas_.GetProtocol(proto);
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
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 = ROUNDDOWN(mmio.base, ZX_PAGE_SIZE);
const size_t vmo_size = 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\n", 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\n", 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\n", 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.proto_id = ZX_PROTOCOL_PDEV;
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.proto_id = ZX_PROTOCOL_PDEV;
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.proto_id = ZX_PROTOCOL_PDEV;
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.proto_id = ZX_PROTOCOL_PDEV;
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::PDevGetProtocol(uint32_t proto_id, uint32_t index, void* out_protocol,
size_t protocol_size, size_t* protocol_actual) {
if (protocol_size < sizeof(ddk::AnyProtocol)) {
return ZX_ERR_INVALID_ARGS;
}
*protocol_actual = sizeof(ddk::AnyProtocol);
// Return the GPIO protocol for the given index.
if (proto_id == ZX_PROTOCOL_GPIO) {
if (index >= gpios_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
auto* proto = static_cast<gpio_protocol_t*>(out_protocol);
gpios_[index].GetProtocol(proto);
return ZX_OK;
}
if (proto_id == ZX_PROTOCOL_CLOCK) {
if (index >= clocks_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
auto* proto = static_cast<clock_protocol_t*>(out_protocol);
clocks_[index].GetProtocol(proto);
return ZX_OK;
}
// For other protocols, fall through to DdkGetProtocol if index is zero
if (index != 0) {
return ZX_ERR_OUT_OF_RANGE;
}
return DdkGetProtocol(proto_id, out_protocol);
}
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(ZX-3833): Make this less likely and/or handle differently.
zxlogf(ERROR, "PlatformProxy::Rpc rpc_channel_.call failed - status: %d\n", status);
return status;
}
status = resp->status;
if (status == ZX_OK && resp_size < sizeof(*resp)) {
zxlogf(ERROR, "PlatformProxy::Rpc resp_size too short: %u\n", 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\n", 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.proto_id = ZX_PROTOCOL_PDEV;
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(SPEW, "%s: received MMIO %u (base %#lx length %#lx handle %#x)\n", 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.proto_id = ZX_PROTOCOL_PDEV;
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(SPEW, "%s: received IRQ %u (irq %#x handle %#x)\n", name_, i, irq.irq,
irq.resource.get());
}
for (uint32_t i = 0; i < info.gpio_count; i++) {
ProxyGpio gpio(i, this);
gpios_.push_back(std::move(gpio), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
for (uint32_t i = 0; i < info.clk_count; i++) {
ProxyClock clock(i, this);
clocks_.push_back(std::move(clock), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
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_BEGIN(platform_bus_proxy, platform_bus::proxy_driver_ops, "zircon", "0.1", 1)
BI_ABORT_IF_AUTOBIND,
ZIRCON_DRIVER_END(platform_bus_proxy)