blob: dc2851263fcb5838e98ab2010eff3792a92658bd [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-protocol-device.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/protocol/platform-defs.h>
#include <fbl/function.h>
#include <zircon/syscalls/resource.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include "platform-bus.h"
namespace platform_bus {
zx_status_t ProtocolDevice::Create(const pbus_dev_t* pdev, zx_device_t* parent, PlatformBus* bus,
fbl::unique_ptr<platform_bus::ProtocolDevice>* out) {
fbl::AllocChecker ac;
fbl::unique_ptr<platform_bus::ProtocolDevice> dev(new (&ac)
platform_bus::ProtocolDevice(parent, bus, pdev));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->Init(pdev);
if (status != ZX_OK) {
return status;
}
out->swap(dev);
return ZX_OK;
}
ProtocolDevice::ProtocolDevice(zx_device_t* parent, PlatformBus* bus, const pbus_dev_t* pdev)
: ProtocolDeviceType(parent), bus_(bus), vid_(pdev->vid), pid_(pdev->pid),
did_(pdev->did), resources_(ROOT_DEVICE_ID) {
strlcpy(name_, pdev->name, sizeof(name_));
}
zx_status_t ProtocolDevice::Init(const pbus_dev_t* pdev) {
auto status = resources_.Init(pdev);
if (status != ZX_OK) {
return status;
}
platform_bus_protocol_t pbus;
status = device_get_protocol(parent(), ZX_PROTOCOL_PLATFORM_BUS, &pbus);
if (status != ZX_OK) {
return status;
}
pbus_ctx_ = pbus.ctx;
// Make a copy of the platform bus protocol so we can replace some methods.
pbus_ops_ = *pbus.ops;
// Do not allow calling device_add and protocol_device_add.
// Only the board driver should be calling those.
pbus_ops_.device_add = [](void* ctx, const pbus_dev_t* dev) { return ZX_ERR_NOT_SUPPORTED; };
pbus_ops_.protocol_device_add = [](void* ctx, uint32_t proto_id, const pbus_dev_t* dev)
{ return ZX_ERR_NOT_SUPPORTED; };
return ZX_OK;
}
zx_status_t ProtocolDevice::MapMmio(uint32_t index, uint32_t cache_policy, void** out_vaddr,
size_t* out_size, zx_paddr_t* out_paddr,
zx_handle_t* out_handle) {
if (index >= resources_.mmio_count()) {
return ZX_ERR_OUT_OF_RANGE;
}
const pbus_mmio_t& mmio = resources_.mmio(index);
const zx_paddr_t vmo_base = ROUNDDOWN(mmio.base, PAGE_SIZE);
const size_t vmo_size = ROUNDUP(mmio.base + mmio.length - vmo_base, PAGE_SIZE);
zx::vmo vmo;
zx_status_t status = zx_vmo_create_physical(bus_->GetResource(), vmo_base, vmo_size,
vmo.reset_and_get_address());
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_create_physical failed %d\n", status);
return status;
}
status = vmo.set_cache_policy(cache_policy);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_set_cache_policy failed %d\n", status);
return status;
}
uintptr_t virt;
status = zx::vmar::root_self()->map(0, vmo, 0, vmo_size, ZX_VM_PERM_READ |
ZX_VM_PERM_WRITE | ZX_VM_MAP_RANGE, &virt);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmar_map failed %d\n", status);
return status;
}
*out_size = mmio.length;
*out_handle = vmo.release();
if (out_paddr) {
*out_paddr = vmo_base;
}
*out_vaddr = reinterpret_cast<void*>(virt + (mmio.base - vmo_base));
return ZX_OK;
}
zx_status_t ProtocolDevice::MapInterrupt(uint32_t index, uint32_t flags, zx_handle_t* out_handle) {
if (index >= resources_.irq_count()) {
return ZX_ERR_OUT_OF_RANGE;
}
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
const pbus_irq_t& irq = resources_.irq(index);
if (flags == 0) {
flags = irq.mode;
}
zx_status_t status = zx_interrupt_create(bus_->GetResource(), irq.irq, flags, out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_interrupt: zx_interrupt_create failed %d\n", status);
return status;
}
return status;
}
zx_status_t ProtocolDevice::GetBti(uint32_t index, zx_handle_t* out_handle) {
if (index >= resources_.bti_count()) {
return ZX_ERR_OUT_OF_RANGE;
}
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
const pbus_bti_t& bti = resources_.bti(index);
return bus_->IommuGetBti(bti.iommu_index, bti.bti_id, out_handle);
}
zx_status_t ProtocolDevice::GetDeviceInfo(pdev_device_info_t* out_info) {
pdev_device_info_t info = {
.vid = vid_,
.pid = pid_,
.did = did_,
.mmio_count = static_cast<uint32_t>(resources_.mmio_count()),
.irq_count = static_cast<uint32_t>(resources_.irq_count()),
.gpio_count = static_cast<uint32_t>(resources_.gpio_count()),
.i2c_channel_count = static_cast<uint32_t>(resources_.i2c_channel_count()),
.clk_count = static_cast<uint32_t>(resources_.clk_count()),
.bti_count = static_cast<uint32_t>(resources_.bti_count()),
.metadata_count = static_cast<uint32_t>(resources_.metadata_count()),
.reserved = {},
.name = {},
};
static_assert(sizeof(info.name) == sizeof(name_), "");
memcpy(info.name, name_, sizeof(out_info->name));
memcpy(out_info, &info, sizeof(info));
return ZX_OK;
}
zx_status_t ProtocolDevice::GetBoardInfo(pdev_board_info_t* out_info) {
return bus_->GetBoardInfo(out_info);
}
zx_status_t ProtocolDevice::DeviceAdd(uint32_t index, device_add_args_t* args, zx_device_t** out) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ProtocolDevice::DdkGetProtocol(uint32_t proto_id, void* out) {
if (proto_id == ZX_PROTOCOL_PLATFORM_DEV) {
auto proto = static_cast<platform_device_protocol_t*>(out);
proto->ops = &pdev_proto_ops_;
proto->ctx = this;
return ZX_OK;
} else if (proto_id == ZX_PROTOCOL_PLATFORM_BUS) {
// Protocol implementation drivers get a restricted subset of the platform bus protocol
auto proto = static_cast<platform_bus_protocol_t*>(out);
proto->ops = &pbus_ops_;
proto->ctx = pbus_ctx_;
return ZX_OK;
} else {
return bus_->DdkGetProtocol(proto_id, out);
}
}
void ProtocolDevice::DdkRelease() {
delete this;
}
zx_status_t ProtocolDevice::Start() {
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, vid_},
{BIND_PLATFORM_DEV_PID, 0, pid_},
{BIND_PLATFORM_DEV_DID, 0, did_},
};
char name[ZX_DEVICE_NAME_MAX];
if (vid_ == PDEV_VID_GENERIC && pid_ == PDEV_PID_GENERIC && did_ == PDEV_DID_KPCI) {
strlcpy(name, "pci", sizeof(name));
} else {
snprintf(name, sizeof(name), "%02x:%02x:%01x", vid_, pid_, did_);
}
// Protocol devices run in our devhost.
uint32_t device_add_flags = 0;
const size_t metadata_count = resources_.metadata_count();
const size_t boot_metadata_count = resources_.boot_metadata_count();
if (metadata_count > 0 || boot_metadata_count > 0) {
// Keep device invisible until after we add its metadata.
device_add_flags |= DEVICE_ADD_INVISIBLE;
}
auto status = DdkAdd(name, device_add_flags, props, fbl::count_of(props));
if (status != ZX_OK) {
return status;
}
if (metadata_count > 0 || boot_metadata_count > 0) {
for (size_t i = 0; i < metadata_count; i++) {
const auto& metadata = resources_.metadata(i);
status = DdkAddMetadata(metadata.type, metadata.data, metadata.len);
if (status != ZX_OK) {
DdkRemove();
return status;
}
}
for (size_t i = 0; i < boot_metadata_count; i++) {
const auto& metadata = resources_.boot_metadata(i);
const void* data;
uint32_t length;
status = bus_->GetZbiMetadata(metadata.zbi_type, metadata.zbi_extra, &data, &length);
if (status == ZX_OK) {
status = DdkAddMetadata(metadata.zbi_type, data, length);
}
if (status != ZX_OK) {
DdkRemove();
return status;
}
}
DdkMakeVisible();
}
return ZX_OK;
}
} // namespace platform_bus