blob: 6794265ec564ea64201616a47511b78884e75556 [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 <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <ddk/protocol/platform-defs.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "platform-bus.h"
#include "platform-proxy.h"
static zx_status_t platform_dev_map_mmio(void* ctx, uint32_t index, uint32_t cache_policy,
void** vaddr, size_t* size, zx_handle_t* out_handle) {
platform_dev_t* dev = ctx;
if (index >= dev->mmio_count) {
return ZX_ERR_INVALID_ARGS;
}
pbus_mmio_t* mmio = &dev->mmios[index];
zx_paddr_t vmo_base = ROUNDDOWN(mmio->base, PAGE_SIZE);
size_t vmo_size = ROUNDUP(mmio->base + mmio->length - vmo_base, PAGE_SIZE);
zx_handle_t vmo_handle;
zx_status_t status = zx_vmo_create_physical(dev->bus->resource, vmo_base, vmo_size,
&vmo_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_create_physical failed %d\n", status);
return status;
}
status = zx_vmo_set_cache_policy(vmo_handle, cache_policy);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_set_cache_policy failed %d\n", status);
goto fail;
}
uintptr_t virt;
status = zx_vmar_map(zx_vmar_root_self(), 0, vmo_handle, 0, vmo_size,
ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE | ZX_VM_FLAG_MAP_RANGE,
&virt);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmar_map failed %d\n", status);
goto fail;
}
*size = mmio->length;
*out_handle = vmo_handle;
*vaddr = (void *)(virt + (mmio->base - vmo_base));
return ZX_OK;
fail:
zx_handle_close(vmo_handle);
return status;
}
static zx_status_t platform_dev_map_interrupt(void* ctx, uint32_t index,
uint32_t flags, zx_handle_t* out_handle) {
platform_dev_t* dev = ctx;
uint32_t flags_;
if (index >= dev->irq_count || !out_handle) {
return ZX_ERR_INVALID_ARGS;
}
pbus_irq_t* irq = &dev->irqs[index];
if (flags) {
flags_ = flags;
} else {
flags_ = irq->mode;
}
#if ENABLE_NEW_IRQ_API
zx_status_t status = zx_irq_create(dev->bus->resource, irq->irq, flags_, out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_interrupt: zx_irq_create failed %d\n", status);
return status;
}
#else
zx_status_t status = zx_interrupt_create(dev->bus->resource, 0, out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_interrupt: zx_interrupt_create failed %d\n", status);
return status;
}
status = zx_interrupt_bind(*out_handle, 0, dev->bus->resource, irq->irq, flags_);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_interrupt: zx_interrupt_bind failed %d\n", status);
zx_handle_close(*out_handle);
}
#endif
return status;
}
static zx_status_t platform_dev_get_bti(void* ctx, uint32_t index, zx_handle_t* out_handle) {
platform_dev_t* dev = ctx;
iommu_protocol_t* iommu = &dev->bus->iommu;
if (!iommu->ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->bti_count || !out_handle) {
return ZX_ERR_INVALID_ARGS;
}
pbus_bti_t* bti = &dev->btis[index];
return iommu_get_bti(iommu, bti->iommu_index, bti->bti_id, out_handle);
}
static zx_status_t platform_dev_get_device_info(void* ctx, pdev_device_info_t* out_info) {
platform_dev_t* dev = ctx;
memset(out_info, 0, sizeof(*out_info));
out_info->vid = dev->vid;
out_info->pid = dev->pid;
out_info->did = dev->did;
memcpy(&out_info->serial_port_info, &dev->serial_port_info, sizeof(out_info->serial_port_info));
out_info->mmio_count = dev->mmio_count;
out_info->irq_count = dev->irq_count;
out_info->gpio_count = dev->gpio_count;
out_info->i2c_channel_count = dev->i2c_channel_count;
out_info->clk_count = dev->clk_count;
out_info->bti_count = dev->bti_count;
return ZX_OK;
}
static platform_device_protocol_ops_t platform_dev_proto_ops = {
.map_mmio = platform_dev_map_mmio,
.map_interrupt = platform_dev_map_interrupt,
.get_bti = platform_dev_get_bti,
.get_device_info = platform_dev_get_device_info,
};
static zx_status_t pdev_rpc_get_mmio(platform_dev_t* dev, uint32_t index, zx_off_t* out_offset,
size_t *out_length, zx_handle_t* out_handle,
uint32_t* out_handle_count) {
if (index >= dev->mmio_count) {
return ZX_ERR_INVALID_ARGS;
}
pbus_mmio_t* mmio = &dev->mmios[index];
zx_paddr_t vmo_base = ROUNDDOWN(mmio->base, PAGE_SIZE);
size_t vmo_size = ROUNDUP(mmio->base + mmio->length - vmo_base, PAGE_SIZE);
zx_status_t status = zx_vmo_create_physical(dev->bus->resource, vmo_base, vmo_size,
out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "pdev_rpc_get_mmio: zx_vmo_create_physical failed %d\n", status);
return status;
}
*out_offset = mmio->base - vmo_base;
*out_length = mmio->length;
*out_handle_count = 1;
return ZX_OK;
}
static zx_status_t pdev_rpc_get_interrupt(platform_dev_t* dev, uint32_t index,
uint32_t flags,
zx_handle_t* out_handle, uint32_t* out_handle_count) {
zx_status_t status = platform_dev_map_interrupt(dev, index, flags, out_handle);
if (status == ZX_OK) {
*out_handle_count = 1;
}
return status;
}
static zx_status_t pdev_rpc_get_bti(platform_dev_t* dev, uint32_t index, zx_handle_t* out_handle,
uint32_t* out_handle_count) {
zx_status_t status = platform_dev_get_bti(dev, index, out_handle);
if (status == ZX_OK) {
*out_handle_count = 1;
}
return status;
}
static zx_status_t pdev_rpc_ums_get_initial_mode(platform_dev_t* dev, usb_mode_t* out_mode) {
platform_bus_t* bus = dev->bus;
if (!bus->ums.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
return usb_mode_switch_get_initial_mode(&bus->ums, out_mode);
}
static zx_status_t pdev_rpc_ums_set_mode(platform_dev_t* dev, usb_mode_t mode) {
platform_bus_t* bus = dev->bus;
if (!bus->ums.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
return usb_mode_switch_set_mode(&bus->ums, mode);
}
static zx_status_t pdev_rpc_gpio_config(platform_dev_t* dev, uint32_t index,
uint32_t flags) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_config(&bus->gpio, index, flags);
}
static zx_status_t pdev_rpc_gpio_set_alt_function(platform_dev_t* dev, uint32_t index,
uint32_t function) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_set_alt_function(&bus->gpio, index, function);
}
static zx_status_t pdev_rpc_gpio_read(platform_dev_t* dev, uint32_t index, uint8_t* out_value) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_read(&bus->gpio, index, out_value);
}
static zx_status_t pdev_rpc_gpio_write(platform_dev_t* dev, uint32_t index, uint8_t value) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_write(&bus->gpio, index, value);
}
static zx_status_t pdev_rpc_get_gpio_interrupt(platform_dev_t* dev, uint32_t index,
uint32_t flags,
zx_handle_t* out_handle,
uint32_t* out_handle_count) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
zx_status_t status = gpio_get_interrupt(&bus->gpio, index, flags, out_handle);
if (status == ZX_OK) {
*out_handle_count = 1;
}
return status;
}
static zx_status_t pdev_rpc_i2c_transact(platform_dev_t* dev, pdev_req_t* req, uint8_t* data,
zx_handle_t channel) {
platform_bus_t* bus = dev->bus;
if (!bus->i2c.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t index = req->index;
if (index >= dev->i2c_channel_count) {
return ZX_ERR_INVALID_ARGS;
}
pbus_i2c_channel_t* pdev_channel = &dev->i2c_channels[index];
return platform_i2c_transact(dev->bus, req, pdev_channel, data, channel);
}
static zx_status_t pdev_rpc_clk_enable(platform_dev_t* dev, uint32_t index) {
platform_bus_t* bus = dev->bus;
if (!bus->clk.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->clk_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->clks[index].clk;
return clk_enable(&bus->clk, index);
}
static zx_status_t pdev_rpc_clk_disable(platform_dev_t* dev, uint32_t index) {
platform_bus_t* bus = dev->bus;
if (!bus->clk.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->clk_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->clks[index].clk;
return clk_disable(&bus->clk, index);
}
static zx_status_t platform_dev_rxrpc(void* ctx, zx_handle_t channel) {
if (channel == ZX_HANDLE_INVALID) {
// proxy device has connected
return ZX_OK;
}
platform_dev_t* dev = ctx;
struct {
pdev_req_t req;
uint8_t data[PDEV_I2C_MAX_TRANSFER_SIZE];
} req_data;
pdev_req_t* req = &req_data.req;
pdev_resp_t resp;
uint32_t len = sizeof(req_data);
zx_status_t status = zx_channel_read(channel, 0, &req_data, NULL, len, 0, &len, NULL);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_rxrpc: zx_channel_read failed %d\n", status);
return status;
}
resp.txid = req->txid;
zx_handle_t handle = ZX_HANDLE_INVALID;
uint32_t handle_count = 0;
switch (req->op) {
case PDEV_GET_MMIO:
resp.status = pdev_rpc_get_mmio(dev, req->index, &resp.mmio.offset, &resp.mmio.length,
&handle, &handle_count);
break;
case PDEV_GET_INTERRUPT:
resp.status = pdev_rpc_get_interrupt(dev, req->index, req->flags, &handle, &handle_count);
break;
case PDEV_GET_BTI:
resp.status = pdev_rpc_get_bti(dev, req->index, &handle, &handle_count);
break;
case PDEV_GET_DEVICE_INFO:
resp.status = platform_dev_get_device_info(dev, &resp.info);
break;
case PDEV_UMS_GET_INITIAL_MODE:
resp.status = pdev_rpc_ums_get_initial_mode(dev, &resp.usb_mode);
break;
case PDEV_UMS_SET_MODE:
resp.status = pdev_rpc_ums_set_mode(dev, req->usb_mode);
break;
case PDEV_GPIO_CONFIG:
resp.status = pdev_rpc_gpio_config(dev, req->index, req->gpio_flags);
break;
case PDEV_GPIO_SET_ALT_FUNCTION:
resp.status = pdev_rpc_gpio_set_alt_function(dev, req->index, req->gpio_alt_function);
break;
case PDEV_GPIO_READ:
resp.status = pdev_rpc_gpio_read(dev, req->index, &resp.gpio_value);
break;
case PDEV_GPIO_WRITE:
resp.status = pdev_rpc_gpio_write(dev, req->index, req->gpio_value);
break;
case PDEV_GPIO_GET_INTERRUPT:
resp.status = pdev_rpc_get_gpio_interrupt(dev, req->index, req->flags, &handle, &handle_count);
break;
case PDEV_I2C_GET_MAX_TRANSFER:
resp.status = i2c_impl_get_max_transfer_size(&dev->bus->i2c, req->index,
&resp.i2c_max_transfer);
break;
case PDEV_I2C_TRANSACT:
resp.status = pdev_rpc_i2c_transact(dev, req, req_data.data, channel);
if (resp.status == ZX_OK) {
// If platform_i2c_transact succeeds, we return immmediately instead of calling
// zx_channel_write below. Instead we will respond in platform_i2c_complete().
return ZX_OK;
}
break;
case PDEV_CLK_ENABLE:
resp.status = pdev_rpc_clk_enable(dev, req->index);
break;
case PDEV_CLK_DISABLE:
resp.status = pdev_rpc_clk_disable(dev, req->index);
break;
default:
zxlogf(ERROR, "platform_dev_rxrpc: unknown op %u\n", req->op);
return ZX_ERR_INTERNAL;
}
// set op to match request so zx_channel_write will return our response
status = zx_channel_write(channel, 0, &resp, sizeof(resp), (handle_count == 1 ? &handle : NULL),
handle_count);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_rxrpc: zx_channel_write failed %d\n", status);
}
return status;
}
static zx_status_t platform_dev_get_protocol(void* ctx, uint32_t proto_id, void* protocol) {
platform_dev_t* dev = ctx;
if (proto_id == ZX_PROTOCOL_PLATFORM_DEV) {
platform_device_protocol_t* proto = protocol;
proto->ops = &platform_dev_proto_ops;
proto->ctx = dev;
return ZX_OK;
} else {
return platform_bus_get_protocol(dev->bus, proto_id, protocol);
}
}
void platform_dev_free(platform_dev_t* dev) {
free(dev->mmios);
free(dev->irqs);
free(dev->gpios);
free(dev->i2c_channels);
free(dev->clks);
free(dev->btis);
free(dev);
}
static zx_protocol_device_t platform_dev_proto = {
.version = DEVICE_OPS_VERSION,
.rxrpc = platform_dev_rxrpc,
.get_protocol = platform_dev_get_protocol,
// Note that we do not have a release callback here because we
// need to support re-adding platform devices when they are reenabled.
};
zx_status_t platform_device_add(platform_bus_t* bus, const pbus_dev_t* pdev, uint32_t flags) {
zx_status_t status = ZX_OK;
if (flags & ~(PDEV_ADD_DISABLED | PDEV_ADD_PBUS_DEVHOST)) {
return ZX_ERR_INVALID_ARGS;
}
platform_dev_t* dev = calloc(1, sizeof(platform_dev_t));
if (!dev) {
return ZX_ERR_NO_MEMORY;
}
if (pdev->mmio_count) {
size_t size = pdev->mmio_count * sizeof(*pdev->mmios);
dev->mmios = malloc(size);
if (!dev->mmios) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->mmios, pdev->mmios, size);
dev->mmio_count = pdev->mmio_count;
}
if (pdev->irq_count) {
size_t size = pdev->irq_count * sizeof(*pdev->irqs);
dev->irqs = malloc(size);
if (!dev->irqs) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->irqs, pdev->irqs, size);
dev->irq_count = pdev->irq_count;
}
if (pdev->gpio_count) {
size_t size = pdev->gpio_count * sizeof(*pdev->gpios);
dev->gpios = malloc(size);
if (!dev->gpios) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->gpios, pdev->gpios, size);
dev->gpio_count = pdev->gpio_count;
}
if (pdev->i2c_channel_count) {
size_t size = pdev->i2c_channel_count * sizeof(*pdev->i2c_channels);
dev->i2c_channels = malloc(size);
if (!dev->i2c_channels) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->i2c_channels, pdev->i2c_channels, size);
dev->i2c_channel_count = pdev->i2c_channel_count;
}
if (pdev->clk_count) {
const size_t sz = pdev->clk_count * sizeof(*pdev->clks);
dev->clks = malloc(sz);
if (!dev->clks) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->clks, pdev->clks, sz);
dev->clk_count = pdev->clk_count;
}
if (pdev->bti_count) {
const size_t sz = pdev->bti_count * sizeof(*pdev->btis);
dev->btis = malloc(sz);
if (!dev->btis) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->btis, pdev->btis, sz);
dev->bti_count = pdev->bti_count;
}
dev->bus = bus;
dev->flags = flags;
strlcpy(dev->name, pdev->name, sizeof(dev->name));
dev->vid = pdev->vid;
dev->pid = pdev->pid;
dev->did = pdev->did;
memcpy(&dev->serial_port_info, &pdev->serial_port_info, sizeof(dev->serial_port_info));
list_add_tail(&bus->devices, &dev->node);
if ((flags & PDEV_ADD_DISABLED) == 0) {
status = platform_device_enable(dev, true);
}
fail:
if (status != ZX_OK) {
platform_dev_free(dev);
}
return status;
}
zx_status_t platform_device_enable(platform_dev_t* dev, bool enable) {
zx_status_t status = ZX_OK;
if (enable && !dev->enabled) {
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, dev->vid},
{BIND_PLATFORM_DEV_PID, 0, dev->pid},
{BIND_PLATFORM_DEV_DID, 0, dev->did},
};
char namestr[ZX_DEVICE_NAME_MAX];
if (dev->vid == PDEV_VID_GENERIC && dev->pid == PDEV_PID_GENERIC &&
dev->did == PDEV_DID_KPCI) {
strlcpy(namestr, "pci", sizeof(namestr));
} else {
snprintf(namestr, sizeof(namestr), "%02x:%02x:%01x", dev->vid, dev->pid, dev->did);
}
char argstr[64];
snprintf(argstr, sizeof(argstr), "pdev:%s,", namestr);
bool new_devhost = !(dev->flags & PDEV_ADD_PBUS_DEVHOST);
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = namestr,
.ctx = dev,
.ops = &platform_dev_proto,
.proto_id = ZX_PROTOCOL_PLATFORM_DEV,
.props = props,
.prop_count = countof(props),
.proxy_args = (new_devhost ? argstr : NULL),
.flags = (new_devhost ? DEVICE_ADD_MUST_ISOLATE : 0),
};
// add PCI root at top level
zx_device_t* parent = dev->bus->zxdev;
if (dev->did == PDEV_DID_KPCI) {
parent = device_get_parent(parent);
}
status = device_add(parent, &args, &dev->zxdev);
} else if (!enable && dev->enabled) {
device_remove(dev->zxdev);
dev->zxdev = NULL;
}
if (status == ZX_OK) {
dev->enabled = enable;
}
return status;
}