1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "platform-protocol-device.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include <ddk/binding.h>
12#include <ddk/debug.h>
13#include <ddk/device.h>
14#include <ddk/driver.h>
15#include <ddk/metadata.h>
16#include <ddk/protocol/platform-defs.h>
17#include <fbl/function.h>
18#include <zircon/syscalls/resource.h>
19#include <lib/zx/vmar.h>
20#include <lib/zx/vmo.h>
21
22#include "platform-bus.h"
23
24namespace platform_bus {
25
26zx_status_t ProtocolDevice::Create(const pbus_dev_t* pdev, zx_device_t* parent, PlatformBus* bus,
27                                   fbl::unique_ptr<platform_bus::ProtocolDevice>* out) {
28    fbl::AllocChecker ac;
29    fbl::unique_ptr<platform_bus::ProtocolDevice> dev(new (&ac)
30                                  platform_bus::ProtocolDevice(parent, bus, pdev));
31    if (!ac.check()) {
32        return ZX_ERR_NO_MEMORY;
33    }
34    auto status = dev->Init(pdev);
35    if (status != ZX_OK) {
36        return status;
37    }
38    out->swap(dev);
39    return ZX_OK;
40}
41
42ProtocolDevice::ProtocolDevice(zx_device_t* parent, PlatformBus* bus, const pbus_dev_t* pdev)
43    : ProtocolDeviceType(parent), bus_(bus), vid_(pdev->vid), pid_(pdev->pid),
44      did_(pdev->did), resources_(ROOT_DEVICE_ID) {
45    strlcpy(name_, pdev->name, sizeof(name_));
46}
47
48zx_status_t ProtocolDevice::Init(const pbus_dev_t* pdev) {
49    auto status = resources_.Init(pdev);
50    if (status != ZX_OK) {
51        return status;
52    }
53
54    platform_bus_protocol_t pbus;
55    status = device_get_protocol(parent(), ZX_PROTOCOL_PLATFORM_BUS, &pbus);
56    if (status != ZX_OK) {
57        return status;
58    }
59
60    pbus_ctx_ = pbus.ctx;
61    // Make a copy of the platform bus protocol so we can replace some methods.
62    pbus_ops_ = *pbus.ops;
63
64    // Do not allow calling device_add and protocol_device_add.
65    // Only the board driver should be calling those.
66    pbus_ops_.device_add = [](void* ctx, const pbus_dev_t* dev) { return ZX_ERR_NOT_SUPPORTED; };
67    pbus_ops_.protocol_device_add = [](void* ctx, uint32_t proto_id, const pbus_dev_t* dev)
68                                    { return ZX_ERR_NOT_SUPPORTED; };
69    return ZX_OK;
70}
71
72zx_status_t ProtocolDevice::GetMmio(uint32_t index, pdev_mmio_t* out_mmio) {
73    if (index >= resources_.mmio_count()) {
74        return ZX_ERR_OUT_OF_RANGE;
75    }
76
77    const pbus_mmio_t& mmio = resources_.mmio(index);
78    const zx_paddr_t vmo_base = ROUNDDOWN(mmio.base, PAGE_SIZE);
79    const size_t vmo_size = ROUNDUP(mmio.base + mmio.length - vmo_base, PAGE_SIZE);
80    zx::vmo vmo;
81
82    zx_status_t status = zx_vmo_create_physical(bus_->GetResource(), vmo_base, vmo_size,
83                                                vmo.reset_and_get_address());
84    if (status != ZX_OK) {
85        zxlogf(ERROR, "%s: creating vmo failed %d\n", __FUNCTION__, status);
86        return status;
87    }
88
89    char name[32];
90    snprintf(name, sizeof(name), "mmio %u", index);
91    status = vmo.set_property(ZX_PROP_NAME, name, sizeof(name));
92    if (status != ZX_OK) {
93        zxlogf(ERROR, "%s: setting vmo name failed %d\n", __FUNCTION__, status);
94        return status;
95    }
96
97    out_mmio->offset = mmio.base - vmo_base;
98    out_mmio->vmo = vmo.release();
99    out_mmio->size = mmio.length;
100    return ZX_OK;
101}
102
103// TODO(surajmalhotra): Remove after migrating all clients off.
104zx_status_t ProtocolDevice::MapMmio(uint32_t index, uint32_t cache_policy, void** out_vaddr,
105                                    size_t* out_size, zx_paddr_t* out_paddr,
106                                    zx_handle_t* out_handle) {
107    if (index >= resources_.mmio_count()) {
108        return ZX_ERR_OUT_OF_RANGE;
109    }
110
111    const pbus_mmio_t& mmio = resources_.mmio(index);
112    const zx_paddr_t vmo_base = ROUNDDOWN(mmio.base, PAGE_SIZE);
113    const size_t vmo_size = ROUNDUP(mmio.base + mmio.length - vmo_base, PAGE_SIZE);
114    zx::vmo vmo;
115    zx_status_t status = zx_vmo_create_physical(bus_->GetResource(), vmo_base, vmo_size,
116                                                vmo.reset_and_get_address());
117    if (status != ZX_OK) {
118        zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_create_physical failed %d\n", status);
119        return status;
120    }
121
122    char name[32];
123    snprintf(name, sizeof(name), "mmio %u", index);
124    status = vmo.set_property(ZX_PROP_NAME, name, sizeof(name));
125    if (status != ZX_OK) {
126        zxlogf(ERROR, "%s: setting vmo name failed %d\n", __FUNCTION__, status);
127        return status;
128    }
129
130    status = vmo.set_cache_policy(cache_policy);
131    if (status != ZX_OK) {
132        zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_set_cache_policy failed %d\n", status);
133        return status;
134    }
135
136    uintptr_t virt;
137    status = zx::vmar::root_self()->map(0, vmo, 0, vmo_size, ZX_VM_PERM_READ |
138                                        ZX_VM_PERM_WRITE | ZX_VM_MAP_RANGE, &virt);
139    if (status != ZX_OK) {
140        zxlogf(ERROR, "platform_dev_map_mmio: zx_vmar_map failed %d\n", status);
141        return status;
142    }
143
144    *out_size = mmio.length;
145    *out_handle = vmo.release();
146    if (out_paddr) {
147        *out_paddr = vmo_base;
148    }
149    *out_vaddr = reinterpret_cast<void*>(virt + (mmio.base - vmo_base));
150    return ZX_OK;
151}
152
153zx_status_t ProtocolDevice::MapInterrupt(uint32_t index, uint32_t flags, zx_handle_t* out_handle) {
154    if (index >= resources_.irq_count()) {
155        return ZX_ERR_OUT_OF_RANGE;
156    }
157    if (out_handle == nullptr) {
158        return ZX_ERR_INVALID_ARGS;
159    }
160
161    const pbus_irq_t& irq = resources_.irq(index);
162    if (flags == 0) {
163        flags = irq.mode;
164    }
165    zx_status_t status = zx_interrupt_create(bus_->GetResource(), irq.irq, flags, out_handle);
166    if (status != ZX_OK) {
167        zxlogf(ERROR, "platform_dev_map_interrupt: zx_interrupt_create failed %d\n", status);
168        return status;
169    }
170    return status;
171}
172
173zx_status_t ProtocolDevice::GetBti(uint32_t index, zx_handle_t* out_handle) {
174    if (index >= resources_.bti_count()) {
175        return ZX_ERR_OUT_OF_RANGE;
176    }
177    if (out_handle == nullptr) {
178        return ZX_ERR_INVALID_ARGS;
179    }
180
181    const pbus_bti_t& bti = resources_.bti(index);
182
183    return bus_->GetBti(bti.iommu_index, bti.bti_id, out_handle);
184}
185
186zx_status_t ProtocolDevice::GetDeviceInfo(pdev_device_info_t* out_info) {
187    pdev_device_info_t info = {
188        .vid = vid_,
189        .pid = pid_,
190        .did = did_,
191        .mmio_count = static_cast<uint32_t>(resources_.mmio_count()),
192        .irq_count = static_cast<uint32_t>(resources_.irq_count()),
193        .gpio_count = static_cast<uint32_t>(resources_.gpio_count()),
194        .i2c_channel_count = static_cast<uint32_t>(resources_.i2c_channel_count()),
195        .clk_count = static_cast<uint32_t>(resources_.clk_count()),
196        .bti_count = static_cast<uint32_t>(resources_.bti_count()),
197        .metadata_count = static_cast<uint32_t>(resources_.metadata_count()),
198        .reserved = {},
199        .name = {},
200    };
201    static_assert(sizeof(info.name) == sizeof(name_), "");
202    memcpy(info.name, name_, sizeof(out_info->name));
203    memcpy(out_info, &info, sizeof(info));
204
205    return ZX_OK;
206}
207
208zx_status_t ProtocolDevice::GetBoardInfo(pdev_board_info_t* out_info) {
209    return bus_->GetBoardInfo(out_info);
210}
211
212zx_status_t ProtocolDevice::DeviceAdd(uint32_t index, device_add_args_t* args, zx_device_t** out) {
213    return ZX_ERR_NOT_SUPPORTED;
214}
215
216zx_status_t ProtocolDevice::GetProtocol(uint32_t proto_id, uint32_t index, void* out_protocol) {
217    // Pass through to DdkGetProtocol if index is zero
218    if (index != 0) {
219        return ZX_ERR_OUT_OF_RANGE;
220    }
221    return DdkGetProtocol(proto_id, out_protocol);
222}
223
224zx_status_t ProtocolDevice::DdkGetProtocol(uint32_t proto_id, void* out) {
225    if (proto_id == ZX_PROTOCOL_PLATFORM_DEV) {
226        auto proto = static_cast<platform_device_protocol_t*>(out);
227        proto->ops = &pdev_proto_ops_;
228        proto->ctx = this;
229        return ZX_OK;
230    } else if (proto_id == ZX_PROTOCOL_PLATFORM_BUS) {
231        // Protocol implementation drivers get a restricted subset of the platform bus protocol
232        auto proto = static_cast<platform_bus_protocol_t*>(out);
233        proto->ops = &pbus_ops_;
234        proto->ctx = pbus_ctx_;
235        return ZX_OK;
236    } else {
237        return bus_->DdkGetProtocol(proto_id, out);
238    }
239}
240
241void ProtocolDevice::DdkRelease() {
242    delete this;
243}
244
245zx_status_t ProtocolDevice::Start() {
246    zx_device_prop_t props[] = {
247        {BIND_PLATFORM_DEV_VID, 0, vid_},
248        {BIND_PLATFORM_DEV_PID, 0, pid_},
249        {BIND_PLATFORM_DEV_DID, 0, did_},
250    };
251
252    char name[ZX_DEVICE_NAME_MAX];
253    if (vid_ == PDEV_VID_GENERIC && pid_ == PDEV_PID_GENERIC && did_ == PDEV_DID_KPCI) {
254        strlcpy(name, "pci", sizeof(name));
255    } else {
256        snprintf(name, sizeof(name), "%02x:%02x:%01x", vid_, pid_, did_);
257    }
258
259    // Protocol devices run in our devhost.
260    uint32_t device_add_flags = 0;
261
262    const size_t metadata_count = resources_.metadata_count();
263    const size_t boot_metadata_count = resources_.boot_metadata_count();
264    if (metadata_count > 0 || boot_metadata_count > 0) {
265        // Keep device invisible until after we add its metadata.
266        device_add_flags |= DEVICE_ADD_INVISIBLE;
267    }
268
269    auto status = DdkAdd(name, device_add_flags, props, fbl::count_of(props));
270    if (status != ZX_OK) {
271        return status;
272    }
273
274    if (metadata_count > 0 || boot_metadata_count > 0) {
275        for (size_t i = 0; i < metadata_count; i++) {
276            const auto& metadata = resources_.metadata(i);
277            status = DdkAddMetadata(metadata.type, metadata.data, metadata.len);
278            if (status != ZX_OK) {
279                DdkRemove();
280                return status;
281            }
282        }
283
284        for (size_t i = 0; i < boot_metadata_count; i++) {
285            const auto& metadata = resources_.boot_metadata(i);
286            const void* data;
287            uint32_t length;
288            status = bus_->GetZbiMetadata(metadata.zbi_type, metadata.zbi_extra, &data, &length);
289            if (status == ZX_OK) {
290                status = DdkAddMetadata(metadata.zbi_type, data, length);
291            }
292            if (status != ZX_OK) {
293                DdkRemove();
294                return status;
295            }
296        }
297
298        DdkMakeVisible();
299    }
300
301    return ZX_OK;
302}
303
304} // namespace platform_bus
305