| // Copyright 2019 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 "device.h" |
| |
| #include <lib/async/default.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls/object.h> |
| |
| #include <array> |
| #include <cinttypes> |
| #include <memory> |
| |
| #include <ddk/debug.h> |
| #include <ddk/platform-defs.h> |
| #include <ddktl/fidl.h> |
| #include <ddktl/protocol/composite.h> |
| #include <ddktl/protocol/platform/device.h> |
| #include <ddktl/protocol/sysmem.h> |
| |
| #include "log.h" |
| #include "src/devices/securemem/drivers/aml-securemem/aml-securemem-bind.h" |
| |
| namespace amlogic_secure_mem { |
| |
| zx_status_t AmlogicSecureMemDevice::Create(void* ctx, zx_device_t* parent) { |
| std::unique_ptr<AmlogicSecureMemDevice> sec_mem(new AmlogicSecureMemDevice(parent)); |
| |
| zx_status_t status = sec_mem->Bind(); |
| if (status == ZX_OK) { |
| // devmgr should now own the lifetime |
| __UNUSED auto ptr = sec_mem.release(); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t AmlogicSecureMemDevice::Bind() { |
| ddk_dispatcher_thread_ = thrd_current(); |
| ddk_loop_closure_queue_.SetDispatcher(async_get_default_dispatcher(), ddk_dispatcher_thread_); |
| |
| zx_status_t status = ZX_OK; |
| |
| ddk::CompositeProtocolClient composite(parent()); |
| if (!composite.is_valid()) { |
| LOG(ERROR, "Unable to get composite protocol"); |
| return status; |
| } |
| |
| status = ddk::PDevProtocolClient::CreateFromComposite( |
| composite, "ddk.protocol.platform.device.PDev", &pdev_proto_client_); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Unable to get pdev protocol - status: %d", status); |
| return status; |
| } |
| |
| status = |
| ddk::SysmemProtocolClient::CreateFromComposite(composite, "sysmem", &sysmem_proto_client_); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Unable to get sysmem protocol - status: %d", status); |
| return status; |
| } |
| |
| status = ddk::TeeProtocolClient::CreateFromComposite(composite, "tee", &tee_proto_client_); |
| if (status != ZX_OK) { |
| LOG(ERROR, "ddk::TeeProtocolClient::CreateFromDevice() failed - status: %d", status); |
| return status; |
| } |
| |
| // See note on the constraints of |bti_| in the header. |
| constexpr uint32_t kBtiIndex = 0; |
| status = pdev_proto_client_.GetBti(kBtiIndex, &bti_); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Unable to get bti handle - status: %d", status); |
| return status; |
| } |
| |
| status = CreateAndServeSysmemTee(); |
| if (status != ZX_OK) { |
| LOG(ERROR, "CreateAndServeSysmemTee() failed - status: %d", status); |
| return status; |
| } |
| |
| status = DdkAdd(kDeviceName); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to add device"); |
| return status; |
| } |
| |
| return status; |
| } |
| |
| zx_status_t AmlogicSecureMemDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) { |
| DdkTransaction transaction(txn); |
| llcpp::fuchsia::hardware::securemem::Device::Dispatch(this, msg, &transaction); |
| return transaction.Status(); |
| } |
| |
| // TODO(fxbug.dev/36888): Determine if we only ever use mexec to reboot from zedboot into a |
| // netboot(ed) image. Iff so, we could avoid some complexity here by not loading aml-securemem in |
| // zedboot, and not handling suspend(mexec) here, and not having UnregisterSecureMem(). |
| void AmlogicSecureMemDevice::DdkSuspend(ddk::SuspendTxn txn) { |
| LOG(DEBUG, "aml-securemem: begin DdkSuspend() - Suspend Reason: %d", txn.suspend_reason()); |
| |
| if ((txn.suspend_reason() & DEVICE_MASK_SUSPEND_REASON) != DEVICE_SUSPEND_REASON_MEXEC) { |
| // When a driver doesn't set a suspend function, the default impl returns |
| // ZX_OK. |
| txn.Reply(ZX_OK, txn.requested_state()); |
| return; |
| } |
| |
| // Sysmem loads first (by design, to maximize chance of getting contiguous |
| // memory), and aml-securemem depends on sysmem. This means aml-securemem |
| // will suspend before sysmem, so we have aml-securemem clean up secure memory |
| // during its suspend (instead of sysmem trying to call aml-securemem after |
| // aml-securemem has already suspended). |
| ZX_DEBUG_ASSERT((txn.suspend_reason() & DEVICE_MASK_SUSPEND_REASON) == |
| DEVICE_SUSPEND_REASON_MEXEC); |
| |
| if (sysmem_secure_mem_server_) { |
| is_suspend_mexec_ = true; |
| |
| // We'd like this to be able to suspend async, but instead since DdkSuspend() is a sync call, we |
| // have to pump the ddk_loop_closure_queue_ below (so far). |
| sysmem_secure_mem_server_->StopAsync(); |
| |
| // TODO(dustingreen): If DdkSuspend() becomes async, consider not running closures directly |
| // here. Or, if llcpp server binding permits unbind by an owner of the binding without |
| // requiring the whole dispatcher to shutdown, consider not running closures directly here. |
| while (sysmem_secure_mem_server_) { |
| ddk_loop_closure_queue_.RunOneHere(); |
| } |
| } |
| |
| LOG(DEBUG, "aml-securemem: end DdkSuspend()"); |
| txn.Reply(ZX_OK, txn.requested_state()); |
| } |
| |
| void AmlogicSecureMemDevice::GetSecureMemoryPhysicalAddress( |
| zx::vmo secure_mem, GetSecureMemoryPhysicalAddressCompleter::Sync& completer) { |
| auto result = GetSecureMemoryPhysicalAddress(std::move(secure_mem)); |
| if (result.is_error()) { |
| completer.Reply(result.error(), static_cast<zx_paddr_t>(0)); |
| } |
| |
| completer.Reply(ZX_OK, result.value()); |
| } |
| |
| fit::result<zx_paddr_t, zx_status_t> AmlogicSecureMemDevice::GetSecureMemoryPhysicalAddress( |
| zx::vmo secure_mem) { |
| ZX_DEBUG_ASSERT(secure_mem.is_valid()); |
| ZX_ASSERT(bti_.is_valid()); |
| |
| // Validate that the VMO handle passed meets additional constraints. |
| zx_info_vmo_t secure_mem_info; |
| zx_status_t status = secure_mem.get_info(ZX_INFO_VMO, reinterpret_cast<void*>(&secure_mem_info), |
| sizeof(secure_mem_info), nullptr, nullptr); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to get VMO info - status: %d", status); |
| return fit::error(status); |
| } |
| |
| // Only allow pinning on VMOs that are contiguous. |
| if ((secure_mem_info.flags & ZX_INFO_VMO_CONTIGUOUS) != ZX_INFO_VMO_CONTIGUOUS) { |
| LOG(ERROR, "Received non-contiguous VMO type to pin"); |
| return fit::error(ZX_ERR_WRONG_TYPE); |
| } |
| |
| // Pin the VMO to get the physical address. |
| zx_paddr_t paddr; |
| zx::pmt pmt; |
| status = bti_.pin(ZX_BTI_CONTIGUOUS | ZX_BTI_PERM_READ, secure_mem, 0 /* offset */, |
| secure_mem_info.size_bytes, &paddr, 1u, &pmt); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to pin memory - status: %d", status); |
| return fit::error(status); |
| } |
| |
| // Unpinning the PMT should never fail |
| status = pmt.unpin(); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| |
| return fit::ok(paddr); |
| } |
| |
| zx_status_t AmlogicSecureMemDevice::CreateAndServeSysmemTee() { |
| ZX_DEBUG_ASSERT(tee_proto_client_.is_valid()); |
| zx::channel tee_device_client; |
| zx::channel tee_device_server; |
| zx_status_t status = zx::channel::create(0, &tee_device_client, &tee_device_server); |
| if (status != ZX_OK) { |
| LOG(ERROR, "optee: failed to create fuchsia.tee.Device channels - status: %d", status); |
| return status; |
| } |
| constexpr zx_handle_t kNoServiceProvider = ZX_HANDLE_INVALID; |
| status = tee_proto_client_.Connect(std::move(tee_device_server), zx::channel(kNoServiceProvider)); |
| if (status != ZX_OK) { |
| LOG(ERROR, "optee: tee_client_.Connect() failed - status: %d", status); |
| return status; |
| } |
| sysmem_secure_mem_server_.emplace(ddk_dispatcher_thread_, std::move(tee_device_client)); |
| zx::channel sysmem_secure_mem_client; |
| zx::channel sysmem_secure_mem_server; |
| status = zx::channel::create(0, &sysmem_secure_mem_client, &sysmem_secure_mem_server); |
| if (status != ZX_OK) { |
| LOG(ERROR, "failed to create sysmem tee channels - status: %d", status); |
| return status; |
| } |
| status = sysmem_secure_mem_server_->BindAsync( |
| std::move(sysmem_secure_mem_server), &sysmem_secure_mem_server_thread_, |
| [this](bool is_success) { |
| ZX_DEBUG_ASSERT(thrd_current() == sysmem_secure_mem_server_thread_); |
| ddk_loop_closure_queue_.Enqueue([this, is_success] { |
| ZX_DEBUG_ASSERT(thrd_current() == ddk_dispatcher_thread_); |
| // Else the current lambda wouldn't be running. |
| ZX_DEBUG_ASSERT(sysmem_secure_mem_server_); |
| if (!is_success) { |
| // This unexpected loss of connection to sysmem should never happen. Complain if it |
| // does happen. |
| // |
| // TODO(dustingreen): Determine if there's a way to cause the aml-securemem's devhost |
| // to get re-started cleanly. Currently this is leaving the overall device in a state |
| // where DRM playback will likely be impossible (we should never get here). |
| // |
| // We may or may not see this message, depending on whether the sysmem failure causes a |
| // hard reboot first. |
| LOG(ERROR, "fuchsia::sysmem::Tee channel close !is_success - DRM playback will fail"); |
| } else { |
| // If is_success, that means the sysmem_secure_mem_server_ is being shut down |
| // intentionally before any channel close. So far, we only do this for suspend(mexec). |
| // In this case, tell sysmem that all is well, before the |
| // sysmem_secure_mem_server_.reset() below causes the channel to close (which sysmem |
| // would otherwise intentionally interpret as justifying a hard reboot). |
| ZX_DEBUG_ASSERT(is_suspend_mexec_); |
| LOG(DEBUG, "calling sysmem_proto_client_.UnregisterSecureMem()..."); |
| zx_status_t status = sysmem_proto_client_.UnregisterSecureMem(); |
| LOG(DEBUG, "sysmem_proto_client_.UnregisterSecureMem() returned"); |
| if (status != ZX_OK) { |
| // Ignore this failure here, but sysmem may panic if sysmem sees |
| // sysmem_secure_mem_server_ channel close without seeing UnregisterSecureMem() first. |
| LOG(ERROR, |
| "sysmem_proto_client_.UnregisterSecureMem() failed (ignoring here) - status: %d", |
| status); |
| } |
| } |
| |
| // Regardless of whether this is due to DdkSuspend() or unexpected channel closure, we |
| // won't be serving the fuchsia::sysmem::Tee channel any more. The ~SysmemSecureMemServer |
| // is designed to be called on the DDK thread. |
| // |
| // If DdkSuspend() is presently running, this lets it continue. |
| sysmem_secure_mem_server_.reset(); |
| LOG(DEBUG, "Done serving fuchsia::sysmem::Tee"); |
| // TODO(dustingreen): If DdkSuspend() were async, we could potentially finish the suspend |
| // here instead of pumping ddk_loop_closure_queue_ until !sysmem_secure_mem_server_. |
| // Similar for an async DdkUnbind() (assuming that ever needs to be handled in this |
| // driver). |
| }); |
| }); |
| if (status != ZX_OK) { |
| LOG(ERROR, "sysmem_secure_mem_server_->BindAsync() failed - status: %d", status); |
| // When BindAsync() fails, we don't call StopAsync(). |
| sysmem_secure_mem_server_.reset(); |
| return status; |
| } |
| |
| // Tell sysmem about the fidl::sysmem::Tee channel that sysmem will use (async) to configure |
| // secure memory ranges. Sysmem won't fidl call back during this banjo call. |
| LOG(DEBUG, "calling sysmem_proto_client_.RegisterSecureMem()..."); |
| status = sysmem_proto_client_.RegisterSecureMem(std::move(sysmem_secure_mem_client)); |
| if (status != ZX_OK) { |
| // In this case sysmem_secure_mem_server_ will get cleaned up when the channel close is noticed |
| // soon. |
| LOG(ERROR, "optee: Failed to RegisterSecureMem()"); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = AmlogicSecureMemDevice::Create; |
| return ops; |
| }(); |
| |
| } // namespace amlogic_secure_mem |
| |
| ZIRCON_DRIVER(amlogic_secure_mem, amlogic_secure_mem::driver_ops, "zircon", "0.1"); |