blob: 85dd8f9b9521e97bbf01ccdce7f63fb095773d16 [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 <assert.h>
#include <stdint.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/protocol/platform-defs.h>
#include <zircon/process.h>
#include <zircon/syscalls/iommu.h>
#include "platform-bus.h"
static zx_status_t platform_bus_get_bti(void* ctx, uint32_t iommu_index, uint32_t bti_id,
zx_handle_t* out_handle) {
platform_bus_t* bus = ctx;
if (iommu_index != 0) {
return ZX_ERR_OUT_OF_RANGE;
}
return zx_bti_create(bus->dummy_iommu_handle, 0, bti_id, out_handle);
}
// default IOMMU protocol to use if the board driver does not set one via pbus_set_protocol()
static iommu_protocol_ops_t platform_bus_default_iommu_ops = {
.get_bti = platform_bus_get_bti,
};
static zx_status_t platform_bus_set_protocol(void* ctx, uint32_t proto_id, void* protocol) {
if (!protocol) {
return ZX_ERR_INVALID_ARGS;
}
platform_bus_t* bus = ctx;
switch (proto_id) {
case ZX_PROTOCOL_USB_MODE_SWITCH:
memcpy(&bus->ums, protocol, sizeof(bus->ums));
break;
case ZX_PROTOCOL_GPIO:
memcpy(&bus->gpio, protocol, sizeof(bus->gpio));
break;
case ZX_PROTOCOL_I2C_IMPL: {
zx_status_t status = platform_i2c_init(bus, (i2c_impl_protocol_t *)protocol);
if (status != ZX_OK) {
return status;
}
memcpy(&bus->i2c, protocol, sizeof(bus->i2c));
break;
}
case ZX_PROTOCOL_CLK:
memcpy(&bus->clk, protocol, sizeof(bus->clk));
break;
case ZX_PROTOCOL_IOMMU:
memcpy(&bus->iommu, protocol, sizeof(bus->iommu));
break;
case ZX_PROTOCOL_MAILBOX:
memcpy(&bus->mailbox, protocol, sizeof(bus->mailbox));
break;
case ZX_PROTOCOL_SCPI:
memcpy(&bus->scpi, protocol, sizeof(bus->scpi));
break;
case ZX_PROTOCOL_CANVAS:
memcpy(&bus->canvas, protocol, sizeof(bus->canvas));
break;
default:
// TODO(voydanoff) consider having a registry of arbitrary protocols
return ZX_ERR_NOT_SUPPORTED;
}
completion_signal(&bus->proto_completion);
return ZX_OK;
}
static zx_status_t platform_bus_wait_protocol(void* ctx, uint32_t proto_id) {
platform_bus_t* bus = ctx;
platform_bus_protocol_t dummy;
while (platform_bus_get_protocol(bus, proto_id, &dummy) == ZX_ERR_NOT_SUPPORTED) {
completion_reset(&bus->proto_completion);
zx_status_t status = completion_wait(&bus->proto_completion, ZX_TIME_INFINITE);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
static zx_status_t platform_bus_device_add(void* ctx, const pbus_dev_t* dev, uint32_t flags) {
platform_bus_t* bus = ctx;
return platform_device_add(bus, dev, flags);
}
static zx_status_t platform_bus_device_enable(void* ctx, uint32_t vid, uint32_t pid, uint32_t did,
bool enable) {
platform_bus_t* bus = ctx;
platform_dev_t* dev;
list_for_every_entry(&bus->devices, dev, platform_dev_t, node) {
if (dev->vid == vid && dev->pid == pid && dev->did == did) {
return platform_device_enable(dev, enable);
}
}
return ZX_ERR_NOT_FOUND;
}
static const char* platform_bus_get_board_name(void* ctx) {
platform_bus_t* bus = ctx;
return bus->platform_id.board_name;
}
static platform_bus_protocol_ops_t platform_bus_proto_ops = {
.set_protocol = platform_bus_set_protocol,
.wait_protocol = platform_bus_wait_protocol,
.device_add = platform_bus_device_add,
.device_enable = platform_bus_device_enable,
.get_board_name = platform_bus_get_board_name,
};
// not static so we can access from platform_dev_get_protocol()
zx_status_t platform_bus_get_protocol(void* ctx, uint32_t proto_id, void* protocol) {
platform_bus_t* bus = ctx;
switch (proto_id) {
case ZX_PROTOCOL_PLATFORM_BUS: {
platform_bus_protocol_t* proto = protocol;
proto->ops = &platform_bus_proto_ops;
proto->ctx = bus;
return ZX_OK;
}
case ZX_PROTOCOL_USB_MODE_SWITCH:
if (bus->ums.ops) {
memcpy(protocol, &bus->ums, sizeof(bus->ums));
return ZX_OK;
}
break;
case ZX_PROTOCOL_GPIO:
if (bus->gpio.ops) {
memcpy(protocol, &bus->gpio, sizeof(bus->gpio));
return ZX_OK;
}
break;
case ZX_PROTOCOL_I2C_IMPL:
if (bus->i2c.ops) {
memcpy(protocol, &bus->i2c, sizeof(bus->i2c));
return ZX_OK;
}
break;
case ZX_PROTOCOL_CLK:
if (bus->clk.ops) {
memcpy(protocol, &bus->clk, sizeof(bus->clk));
return ZX_OK;
}
break;
case ZX_PROTOCOL_IOMMU:
if (bus->iommu.ops) {
memcpy(protocol, &bus->iommu, sizeof(bus->iommu));
return ZX_OK;
}
break;
case ZX_PROTOCOL_MAILBOX:
if (bus->mailbox.ops) {
memcpy(protocol, &bus->mailbox, sizeof(bus->mailbox));
return ZX_OK;
}
break;
case ZX_PROTOCOL_SCPI:
if (bus->scpi.ops) {
memcpy(protocol, &bus->scpi, sizeof(bus->scpi));
return ZX_OK;
}
break;
case ZX_PROTOCOL_CANVAS:
if (bus->canvas.ops) {
memcpy(protocol, &bus->canvas, sizeof(bus->canvas));
return ZX_OK;
}
break;
default:
// TODO(voydanoff) consider having a registry of arbitrary protocols
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_ERR_NOT_SUPPORTED;
}
static void platform_bus_release(void* ctx) {
platform_bus_t* bus = ctx;
platform_dev_t* dev;
list_for_every_entry(&bus->devices, dev, platform_dev_t, node) {
platform_dev_free(dev);
}
zx_handle_close(bus->dummy_iommu_handle);
free(bus->metadata);
free(bus);
}
static zx_protocol_device_t platform_bus_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol = platform_bus_get_protocol,
.release = platform_bus_release,
};
static zx_status_t platform_bus_suspend(void* ctx, uint32_t flags) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_protocol_device_t sys_device_proto = {
.version = DEVICE_OPS_VERSION,
.suspend = platform_bus_suspend,
};
static zx_status_t platform_bus_read_zbi(platform_bus_t* bus, zx_handle_t vmo) {
zbi_header_t header;
zx_status_t status = zx_vmo_read(vmo, &header, 0, sizeof(header));
if (status != ZX_OK) {
return status;
}
if ((header.type != ZBI_TYPE_CONTAINER) || (header.extra != ZBI_CONTAINER_MAGIC)) {
zxlogf(ERROR, "platform_bus: ZBI VMO not contain ZBI container\n");
return ZX_ERR_INTERNAL;
}
size_t zbi_length = header.length;
// compute size of ZBI records we need to save for metadata
size_t metadata_size = 0;
size_t len = zbi_length;
size_t off = sizeof(header);
while (len > sizeof(header)) {
zx_status_t status = zx_vmo_read(vmo, &header, off, sizeof(header));
if (status < 0) {
zxlogf(ERROR, "zx_vmo_read failed: %d\n", status);
return status;
}
size_t itemlen = ZBI_ALIGN(sizeof(zbi_header_t) + header.length);
if (itemlen > len) {
zxlogf(ERROR, "platform_bus: ZBI item too large (%zd > %zd)\n", itemlen, len);
break;
}
if (ZBI_TYPE_DRV_METADATA(header.type)) {
metadata_size += itemlen;
}
off += itemlen;
len -= itemlen;
}
if (metadata_size) {
bus->metadata = malloc(metadata_size);
if (!bus->metadata) {
return ZX_ERR_NO_MEMORY;
}
}
bool got_platform_id = false;
zx_off_t metadata_offset = 0;
uint8_t* metadata = (uint8_t*)bus->metadata;
len = zbi_length;
off = sizeof(header);
// find platform ID record and copy metadata records
while (len > sizeof(header)) {
zx_status_t status = zx_vmo_read(vmo, &header, off, sizeof(header));
if (status < 0) {
break;
}
size_t itemlen = ZBI_ALIGN(sizeof(zbi_header_t) + header.length);
if (itemlen > len) {
zxlogf(ERROR, "platform_bus: ZBI item too large (%zd > %zd)\n", itemlen, len);
break;
}
if (header.type == ZBI_TYPE_PLATFORM_ID) {
status = zx_vmo_read(vmo, &bus->platform_id, off + sizeof(zbi_header_t),
sizeof(bus->platform_id));
if (status != ZX_OK) {
zxlogf(ERROR, "zx_vmo_read failed: %d\n", status);
return status;
}
got_platform_id = true;
} else if (ZBI_TYPE_DRV_METADATA(header.type)) {
status = zx_vmo_read(vmo, metadata + metadata_offset, off, itemlen);
if (status != ZX_OK) {
zxlogf(ERROR, "zx_vmo_read failed: %d\n", status);
return status;
}
metadata_offset += itemlen;
}
off += itemlen;
len -= itemlen;
}
bus->metadata_size = metadata_size;
if (!got_platform_id) {
zxlogf(ERROR, "platform_bus: ZBI_TYPE_PLATFORM_ID not found\n");
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
static zx_status_t platform_bus_create(void* ctx, zx_device_t* parent, const char* name,
const char* args, zx_handle_t zbi_vmo) {
if (!args) {
zxlogf(ERROR, "platform_bus_create: args missing\n");
return ZX_ERR_NOT_SUPPORTED;
}
platform_bus_t* bus = calloc(1, sizeof(platform_bus_t));
if (!bus) {
return ZX_ERR_NO_MEMORY;
}
completion_reset(&bus->proto_completion);
bus->resource = get_root_resource();
zx_status_t status = platform_bus_read_zbi(bus, zbi_vmo);
zx_handle_close(zbi_vmo);
if (status != ZX_OK) {
free(bus);
return status;
}
// set up a dummy IOMMU protocol to use in the case where our board driver does not
// set a real one.
zx_iommu_desc_dummy_t desc;
status = zx_iommu_create(bus->resource, ZX_IOMMU_TYPE_DUMMY, &desc, sizeof(desc),
&bus->dummy_iommu_handle);
if (status != ZX_OK) {
free(bus);
return status;
}
bus->iommu.ops = &platform_bus_default_iommu_ops;
bus->iommu.ctx = bus;
// This creates the "sys" device
device_add_args_t self_args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = name,
.ops = &sys_device_proto,
.flags = DEVICE_ADD_NON_BINDABLE,
};
status = device_add(parent, &self_args, &parent);
if (status != ZX_OK) {
zx_handle_close(bus->dummy_iommu_handle);
free(bus);
return status;
}
// Then we attach the platform-bus device below it
list_initialize(&bus->devices);
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, bus->platform_id.vid},
{BIND_PLATFORM_DEV_PID, 0, bus->platform_id.pid},
};
device_add_args_t add_args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "platform",
.ctx = bus,
.ops = &platform_bus_proto,
.proto_id = ZX_PROTOCOL_PLATFORM_BUS,
.proto_ops = &platform_bus_proto_ops,
.props = props,
.prop_count = countof(props),
};
return device_add(parent, &add_args, &bus->zxdev);
}
static zx_driver_ops_t platform_bus_driver_ops = {
.version = DRIVER_OPS_VERSION,
.create = platform_bus_create,
};
ZIRCON_DRIVER_BEGIN(platform_bus, platform_bus_driver_ops, "zircon", "0.1", 1)
// devmgr loads us directly, so we need no binding information here
BI_ABORT_IF_AUTOBIND,
ZIRCON_DRIVER_END(platform_bus)