blob: 0fb12c261db472d5e060617e3492de661125c593 [file] [log] [blame]
// Copyright 2018 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-device.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 <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/i2c-lib.h>
#include <ddk/protocol/platform/bus.h>
#include <ddk/protocol/platform/device.h>
#include <fbl/auto_call.h>
#include <fbl/unique_ptr.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <utility>
#include "platform-proxy.h"
#include "proxy-protocol.h"
// The implementation of the platform bus protocol in this file is for use by
// drivers that exist in a proxy devhost and communicate with the platform bus
// over an RPC channel.
//
// More information can be found at the top of platform-device.cpp.
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(device_id_, &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(device_id_, &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(device_id_, &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(device_id_, &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(device_id_, &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(device_id_, &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(device_id_, &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(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyI2c::I2cGetMaxTransferSize(size_t* out_size) {
rpc_i2c_req_t req = {};
rpc_i2c_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_I2C;
req.header.op = I2C_GET_MAX_TRANSFER;
req.index = index_;
auto status = proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp));
if (status == ZX_OK) {
*out_size = resp.max_transfer;
}
return status;
}
zx_status_t ProxyI2c::I2cGetInterrupt(uint32_t flags, zx::interrupt* out_irq) {
return ZX_ERR_NOT_SUPPORTED;
}
void ProxyI2c::I2cTransact(const i2c_op_t* ops, size_t cnt, i2c_transact_callback transact_cb,
void* cookie) {
size_t writes_length = 0;
size_t reads_length = 0;
for (size_t i = 0; i < cnt; ++i) {
if (ops[i].is_read) {
reads_length += ops[i].data_size;
} else {
writes_length += ops[i].data_size;
}
}
if (!writes_length && !reads_length) {
transact_cb(cookie, ZX_ERR_INVALID_ARGS, nullptr, 0);
return;
}
size_t req_length = sizeof(rpc_i2c_req_t) + cnt * sizeof(i2c_rpc_op_t) + writes_length;
if (req_length >= PROXY_MAX_TRANSFER_SIZE) {
return transact_cb(cookie, ZX_ERR_INVALID_ARGS, nullptr, 0);
}
uint8_t req_buffer[PROXY_MAX_TRANSFER_SIZE];
auto req = reinterpret_cast<rpc_i2c_req_t*>(req_buffer);
req->header.proto_id = ZX_PROTOCOL_I2C;
req->header.op = I2C_TRANSACT;
req->index = index_;
req->cnt = cnt;
req->transact_cb = transact_cb;
req->cookie = cookie;
auto rpc_ops = reinterpret_cast<i2c_rpc_op_t*>(req + 1);
ZX_ASSERT(cnt < I2C_MAX_RW_OPS);
for (size_t i = 0; i < cnt; ++i) {
rpc_ops[i].length = ops[i].data_size;
rpc_ops[i].is_read = ops[i].is_read;
rpc_ops[i].stop = ops[i].stop;
}
uint8_t* p_writes = reinterpret_cast<uint8_t*>(rpc_ops) + cnt * sizeof(i2c_rpc_op_t);
for (size_t i = 0; i < cnt; ++i) {
if (!ops[i].is_read) {
memcpy(p_writes, ops[i].data_buffer, ops[i].data_size);
p_writes += ops[i].data_size;
}
}
const size_t resp_length = sizeof(rpc_i2c_rsp_t) + reads_length;
if (resp_length >= PROXY_MAX_TRANSFER_SIZE) {
transact_cb(cookie, ZX_ERR_INVALID_ARGS, nullptr, 0);
return;
}
uint8_t resp_buffer[PROXY_MAX_TRANSFER_SIZE];
rpc_i2c_rsp_t* rsp = reinterpret_cast<rpc_i2c_rsp_t*>(resp_buffer);
size_t actual;
auto status = proxy_->Rpc(device_id_, &req->header, static_cast<uint32_t>(req_length),
&rsp->header, static_cast<uint32_t>(resp_length), nullptr, 0, nullptr,
0, &actual);
if (status != ZX_OK) {
transact_cb(cookie, status, nullptr, 0);
return;
}
// TODO(voydanoff) This proxying code actually implements i2c_transact synchronously
// due to the fact that it is unsafe to respond asynchronously on the devmgr rxrpc channel.
// In the future we may want to redo the plumbing to allow this to be truly asynchronous.
if (actual != resp_length) {
status = ZX_ERR_INTERNAL;
} else {
status = rsp->header.status;
}
i2c_op_t read_ops[I2C_MAX_RW_OPS];
size_t read_ops_cnt = 0;
uint8_t* p_reads = reinterpret_cast<uint8_t*>(rsp + 1);
for (size_t i = 0; i < cnt; ++i) {
if (ops[i].is_read) {
read_ops[read_ops_cnt] = ops[i];
read_ops[read_ops_cnt].data_buffer = p_reads;
read_ops_cnt++;
p_reads += ops[i].data_size;
}
}
transact_cb(rsp->cookie, status, read_ops, read_ops_cnt);
return;
}
zx_status_t ProxyClock::ClockEnable(uint32_t index) {
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(device_id_, &req.header, sizeof(req), &resp, sizeof(resp));
}
zx_status_t ProxyClock::ClockDisable(uint32_t index) {
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(device_id_, &req.header, sizeof(req), &resp, sizeof(resp));
}
zx_status_t ProxyPower::PowerEnablePowerDomain() {
rpc_power_req_t req = {};
rpc_power_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_POWER;
req.header.op = POWER_ENABLE;
req.index = index_;
return proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyPower::PowerDisablePowerDomain() {
rpc_power_req_t req = {};
rpc_power_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_POWER;
req.header.op = POWER_DISABLE;
req.index = index_;
return proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyPower::PowerGetPowerDomainStatus(power_domain_status_t* out_status) {
rpc_power_req_t req = {};
rpc_power_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_POWER;
req.header.op = POWER_GET_STATUS;
req.index = index_;
zx_status_t status = proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header,
sizeof(resp));
if (status != ZX_OK) {
return status;
}
*out_status = resp.status;
return status;
}
zx_status_t ProxySysmem::SysmemConnect(zx::channel allocator2_request) {
platform_proxy_req_t req = {};
platform_proxy_rsp_t resp = {};
req.proto_id = ZX_PROTOCOL_SYSMEM;
req.op = SYSMEM_CONNECT;
zx_handle_t handle = allocator2_request.release();
return proxy_->Rpc(device_id_, &req, 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(device_id_, &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(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp));
}
zx_status_t ProxyDevice::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 ProxyDevice::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 ProxyDevice::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 proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp), nullptr, 0,
out_bti->reset_and_get_address(), 1, nullptr);
}
zx_status_t ProxyDevice::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 proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp), nullptr, 0,
out_resource->reset_and_get_address(), 1, nullptr);
}
zx_status_t ProxyDevice::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 = proxy_->Rpc(device_id_, &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 ProxyDevice::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 = proxy_->Rpc(device_id_, &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 ProxyDevice::PDevDeviceAdd(uint32_t index, const device_add_args_t* args,
zx_device_t** device) {
rpc_pdev_req_t req = {};
rpc_pdev_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_PDEV;
req.header.op = PDEV_DEVICE_ADD;
req.index = index;
auto status = proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.header, sizeof(resp));
if (status != ZX_OK) {
return status;
}
return CreateChild(zxdev(), resp.device_id, proxy_, args, device);
}
zx_status_t ProxyDevice::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_I2C) {
if (index >= i2cs_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
auto* proto = static_cast<i2c_protocol_t*>(out_protocol);
i2cs_[index].GetProtocol(proto);
return ZX_OK;
}
if (proto_id == ZX_PROTOCOL_POWER) {
if (index >= power_domains_.size()) {
return ZX_ERR_OUT_OF_RANGE;
}
auto* proto = static_cast<power_protocol_t*>(out_protocol);
power_domains_[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 ProxyDevice::CreateRoot(zx_device_t* parent, fbl::RefPtr<PlatformProxy> proxy) {
fbl::AllocChecker ac;
auto dev = fbl::make_unique_checked<ProxyDevice>(&ac,parent, ROOT_DEVICE_ID, proxy);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->InitRoot();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = dev.release();
return ZX_OK;
}
zx_status_t ProxyDevice::CreateChild(zx_device_t* parent, uint32_t device_id,
fbl::RefPtr<PlatformProxy> proxy,
const device_add_args_t* args,
zx_device_t** device) {
fbl::AllocChecker ac;
fbl::unique_ptr<ProxyDevice> dev(new (&ac) platform_bus::ProxyDevice(parent, device_id, proxy));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->InitChild(args, device);
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = dev.release();
return ZX_OK;
}
zx_status_t ProxyDevice::InitCommon() {
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 = proxy_->Rpc(device_id_, &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 = proxy_->Rpc(device_id_, &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(device_id_, i, proxy_);
gpios_.push_back(std::move(gpio), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
for (uint32_t i = 0; i < info.power_domain_count; i++) {
ProxyPower power_domain(device_id_, i, proxy_);
power_domains_.push_back(std::move(power_domain), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
for (uint32_t i = 0; i < info.i2c_channel_count; i++) {
ProxyI2c i2c(device_id_, i, proxy_);
i2cs_.push_back(std::move(i2c), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
return ZX_OK;
}
zx_status_t ProxyDevice::InitRoot() {
auto status = InitCommon();
if (status != ZX_OK) {
return status;
}
return DdkAdd(name_);
}
zx_status_t ProxyDevice::InitChild(const device_add_args_t* args, zx_device_t** device) {
auto status = InitCommon();
if (status != ZX_OK) {
return status;
}
ctx_ = args->ctx;
device_ops_ = args->ops;
proto_id_ = args->proto_id;
proto_ops_ = args->proto_ops;
device_add_args_t new_args = *args;
// Replace ctx and device protocol ops with ours so we can intercept device_get_protocol().
new_args.ctx = this;
new_args.ops = &ddk_device_proto_;
if (!device) {
device = &zxdev_;
}
if (metadata_count_ == 0) {
auto status = device_add(parent(), &new_args, device);
if (status == ZX_OK) zxdev_ = *device;
return status;
}
new_args.flags |= DEVICE_ADD_INVISIBLE;
status = device_add(parent(), &new_args, device);
if (status != ZX_OK) {
return status;
}
zxdev_ = *device;
// Remove ourselves from the devmgr if something goes wrong.
auto cleanup = fbl::MakeAutoCall([this]() { DdkRemove(); });
for (uint32_t i = 0; i < metadata_count_; i++) {
rpc_pdev_req_t req = {};
rpc_pdev_metadata_rsp_t resp = {};
req.header.proto_id = ZX_PROTOCOL_PDEV;
req.header.op = PDEV_GET_METADATA;
req.index = i;
status = proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp.pdev.header,
sizeof(resp));
if (status == ZX_OK) {
status = DdkAddMetadata(resp.pdev.metadata_type, resp.metadata,
resp.pdev.metadata_length);
}
if (status != ZX_OK) {
zxlogf(WARN, "%s failed to add metadata for new device\n", __func__);
}
}
cleanup.cancel();
// Make ourselves visible after all metadata has been added successfully.
DdkMakeVisible();
return ZX_OK;
}
zx_status_t ProxyDevice::DdkGetProtocol(uint32_t proto_id, void* out) {
auto* proto = static_cast<ddk::AnyProtocol*>(out);
// Try driver's get_protocol() first, if it is implemented.
if (device_ops_ && device_ops_->get_protocol) {
if (device_ops_->get_protocol(ctx_, proto_id, out) == ZX_OK) {
return ZX_OK;
}
}
// Next try driver's primary protocol.
if (proto_ops_ && proto_id_ == proto_id) {
proto->ops = proto_ops_;
proto->ctx = ctx_;
return ZX_OK;
}
// Finally, protocols provided by platform bus.
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_POWER: {
auto count = power_domains_.size();
if (count == 0) {
return ZX_ERR_NOT_SUPPORTED;
} else if (count > 1) {
zxlogf(ERROR, "%s: device has more than one power domain\n", __func__);
return ZX_ERR_BAD_STATE;
}
// Return zeroth power domain resource.
auto* proto = static_cast<power_protocol_t*>(out);
power_domains_[0].GetProtocol(proto);
return ZX_OK;
}
case ZX_PROTOCOL_I2C: {
auto count = i2cs_.size();
if (count == 0) {
return ZX_ERR_NOT_SUPPORTED;
} else if (count > 1) {
zxlogf(ERROR, "%s: device has more than one I2C channel\n", __func__);
return ZX_ERR_BAD_STATE;
}
// Return zeroth I2C resource.
auto* proto = static_cast<i2c_protocol_t*>(out);
i2cs_[0].GetProtocol(proto);
return ZX_OK;
}
case ZX_PROTOCOL_CLOCK: {
auto* proto = static_cast<clock_protocol_t*>(out);
clk_.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 proxy_->GetProtocol(proto_id, out);;
}
}
zx_status_t ProxyDevice::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
if (device_ops_ && device_ops_->open) {
return device_ops_->open(ctx_, dev_out, flags);
}
return ZX_OK;
}
zx_status_t ProxyDevice::DdkOpenAt(zx_device_t** dev_out, const char* path, uint32_t flags) {
if (device_ops_ && device_ops_->open_at) {
return device_ops_->open_at(ctx_, dev_out, path, flags);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProxyDevice::DdkClose(uint32_t flags) {
if (device_ops_ && device_ops_->close) {
return device_ops_->close(ctx_, flags);
}
return ZX_OK;
}
void ProxyDevice::DdkUnbind() {
if (device_ops_ && device_ops_->unbind) {
device_ops_->unbind(ctx_);
}
}
void ProxyDevice::DdkRelease() {
if (device_ops_ && device_ops_->release) {
device_ops_->release(ctx_);
}
delete this;
}
zx_status_t ProxyDevice::DdkRead(void* buf, size_t count, zx_off_t off, size_t* actual) {
if (device_ops_ && device_ops_->read) {
return device_ops_->read(ctx_, buf, count, off, actual);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProxyDevice::DdkWrite(const void* buf, size_t count, zx_off_t off, size_t* actual) {
if (device_ops_ && device_ops_->write) {
return device_ops_->write(ctx_, buf, count, off, actual);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_off_t ProxyDevice::DdkGetSize() {
if (device_ops_ && device_ops_->get_size) {
return device_ops_->get_size(ctx_);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProxyDevice::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
size_t out_len, size_t* actual) {
if (device_ops_ && device_ops_->ioctl) {
return device_ops_->ioctl(ctx_, op, in_buf, in_len, out_buf, out_len, actual);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProxyDevice::DdkSuspend(uint32_t flags) {
if (device_ops_ && device_ops_->suspend) {
return device_ops_->suspend(ctx_, flags);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProxyDevice::DdkResume(uint32_t flags) {
if (device_ops_ && device_ops_->resume) {
return device_ops_->resume(ctx_, flags);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProxyDevice::DdkRxrpc(zx_handle_t channel) {
if (device_ops_ && device_ops_->rxrpc) {
return device_ops_->rxrpc(ctx_, channel);
}
return ZX_ERR_NOT_SUPPORTED;
}
} // namespace platform_bus