| // 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, zx_handle_t* out_handle) { |
| platform_dev_t* dev = ctx; |
| |
| if (index >= dev->irq_count || !out_handle) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| pbus_irq_t* irq = &dev->irqs[index]; |
| #if ENABLE_NEW_IRQ_API |
| zx_status_t status = zx_irq_create(dev->bus->resource, irq->irq, irq->mode, 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, irq->mode); |
| 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, |
| zx_handle_t* out_handle, uint32_t* out_handle_count) { |
| |
| zx_status_t status = platform_dev_map_interrupt(dev, index, 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_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, &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_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; |
| } |