blob: 393d568e18d7f7b0d824f195994a78b70d7b14f5 [file] [log] [blame]
// Copyright 2018 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 "optee-controller.h"
#include <inttypes.h>
#include <lib/fidl-utils/bind.h>
#include <string.h>
#include <limits>
#include <memory>
#include <utility>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/io-buffer.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/composite.h>
#include <fbl/auto_lock.h>
#include <tee-client-api/tee-client-types.h>
#include "ddktl/suspend-txn.h"
#include "optee-client.h"
#include "optee-device-info.h"
#include "optee-util.h"
namespace optee {
namespace fuchsia_tee = ::llcpp::fuchsia::tee;
constexpr TEEC_UUID kOpteeOsUuid = {
0x486178E0, 0xE7F8, 0x11E3, {0xBC, 0x5E, 0x00, 0x02, 0xA5, 0xD5, 0xC5, 0x1B}};
static bool IsOpteeApi(const tee_smc::TrustedOsCallUidResult& returned_uid) {
return returned_uid.uid_0_3 == kOpteeApiUid_0 && returned_uid.uid_4_7 == kOpteeApiUid_1 &&
returned_uid.uid_8_11 == kOpteeApiUid_2 && returned_uid.uid_12_15 == kOpteeApiUid_3;
}
static bool IsOpteeApiRevisionSupported(const tee_smc::TrustedOsCallRevisionResult& returned_rev) {
// The cast is unfortunately necessary to mute a compiler warning about an unsigned expression
// always being greater than 0.
ZX_DEBUG_ASSERT(returned_rev.minor <= static_cast<uint32_t>(std::numeric_limits<int32_t>::max()));
return returned_rev.major == kOpteeApiRevisionMajor &&
static_cast<int32_t>(returned_rev.minor) >= static_cast<int32_t>(kOpteeApiRevisionMinor);
}
zx_status_t OpteeController::ValidateApiUid() const {
static const zx_smc_parameters_t kGetApiFuncCall =
tee_smc::CreateSmcFunctionCall(tee_smc::kTrustedOsCallUidFuncId);
union {
zx_smc_result_t raw;
tee_smc::TrustedOsCallUidResult uid;
} result;
zx_status_t status = zx_smc_call(secure_monitor_.get(), &kGetApiFuncCall, &result.raw);
return status == ZX_OK ? IsOpteeApi(result.uid) ? ZX_OK : ZX_ERR_NOT_FOUND : status;
}
zx_status_t OpteeController::ValidateApiRevision() const {
static const zx_smc_parameters_t kGetApiRevisionFuncCall =
tee_smc::CreateSmcFunctionCall(tee_smc::kTrustedOsCallRevisionFuncId);
union {
zx_smc_result_t raw;
tee_smc::TrustedOsCallRevisionResult revision;
} result;
zx_status_t status = zx_smc_call(secure_monitor_.get(), &kGetApiRevisionFuncCall, &result.raw);
return status == ZX_OK
? IsOpteeApiRevisionSupported(result.revision) ? ZX_OK : ZX_ERR_NOT_SUPPORTED
: status;
}
zx_status_t OpteeController::GetOsRevision() {
static const zx_smc_parameters_t kGetOsRevisionFuncCall =
tee_smc::CreateSmcFunctionCall(kGetOsRevisionFuncId);
union {
zx_smc_result_t raw;
GetOsRevisionResult revision;
} result;
zx_status_t status = zx_smc_call(secure_monitor_.get(), &kGetOsRevisionFuncCall, &result.raw);
if (status != ZX_OK) {
return status;
}
os_revision_ = result.revision;
return ZX_OK;
}
zx_status_t OpteeController::ExchangeCapabilities() {
uint64_t nonsecure_world_capabilities = 0;
if (zx_system_get_num_cpus() == 1) {
nonsecure_world_capabilities |= kNonSecureCapUniprocessor;
}
const zx_smc_parameters_t func_call =
tee_smc::CreateSmcFunctionCall(kExchangeCapabilitiesFuncId, nonsecure_world_capabilities);
union {
zx_smc_result_t raw;
ExchangeCapabilitiesResult response;
} result;
zx_status_t status = zx_smc_call(secure_monitor_.get(), &func_call, &result.raw);
if (status != ZX_OK) {
return status;
}
if (result.response.status != kReturnOk) {
return ZX_ERR_INTERNAL;
}
secure_world_capabilities_ = result.response.secure_world_capabilities;
return ZX_OK;
}
zx_status_t OpteeController::InitializeSharedMemory() {
// The Trusted OS and Rich OS share a dedicated portion of RAM to send messages back and forth. To
// discover the memory region to use, we ask the platform device for a MMIO representing the TEE's
// entire dedicated memory region and query the TEE to discover which section of that should be
// used as the shared memory. The rest of the TEE's memory region is secure.
static constexpr uint32_t kTeeBtiIndex = 0;
zx_status_t status = pdev_.GetBti(kTeeBtiIndex, &bti_);
if (status != ZX_OK) {
LOG(ERROR, "unable to get bti");
return status;
}
// The TEE BTI will be pinned to get the physical address of the shared memory region between the
// Rich OS and the Trusted OS. This memory region is not used for DMA and only used for message
// exchange between the two "worlds." As the TEE is not distinct hardware, but rather the CPU
// operating in a different EL, it cannot be accessing the shared memory region at this time. The
// Trusted OS can never execute any code unless we explicitly call into it via SMC, and it can
// only run code during that SMC call. Once the call returns, the Trusted OS is no longer
// executing any code and will not until the next time we explicitly call into it. The physical
// addresses acquired from the BTI pinning are only used within the context of the OP-TEE
// CallWithArgs SMC calls.
//
// As the Trusted OS cannot be actively accessing this memory region, it is safe to release from
// quarantine.
status = bti_.release_quarantine();
if (status != ZX_OK) {
LOG(ERROR, "could not release quarantine bti - %d", status);
return status;
}
// The Secure World memory is located at a fixed physical address in RAM, so we have to request
// the platform device map the physical vmo for us.
static constexpr uint32_t kSecureWorldMemoryMmioIndex = 0;
pdev_mmio_t mmio_dev;
status = pdev_.GetMmio(kSecureWorldMemoryMmioIndex, &mmio_dev);
if (status != ZX_OK) {
LOG(ERROR, "unable to get secure world mmio");
return status;
}
// Briefly pin the first page of this VMO to determine the secure world's base physical address.
zx_paddr_t mmio_vmo_paddr;
zx::pmt pmt;
status = bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, *zx::unowned_vmo(mmio_dev.vmo),
/*offset=*/0, ZX_PAGE_SIZE, &mmio_vmo_paddr, /*num_addrs=*/1, &pmt);
if (status != ZX_OK) {
LOG(ERROR, "unable to pin secure world memory");
return status;
}
status = pmt.unpin();
ZX_DEBUG_ASSERT(status == ZX_OK);
zx_paddr_t secure_world_paddr = mmio_vmo_paddr + mmio_dev.offset;
size_t secure_world_size = mmio_dev.size;
// Now that we have the TEE's entire memory range, query the TEE to see which region of it we
// should use.
zx_paddr_t shared_mem_paddr;
size_t shared_mem_size;
status = DiscoverSharedMemoryConfig(&shared_mem_paddr, &shared_mem_size);
if (status != ZX_OK) {
LOG(ERROR, "unable to discover shared memory configuration");
return status;
}
if (shared_mem_paddr < secure_world_paddr ||
(shared_mem_paddr + shared_mem_size) > (secure_world_paddr + secure_world_size)) {
LOG(ERROR, "shared memory outside of secure world range");
return ZX_ERR_OUT_OF_RANGE;
}
// Map and pin the just the shared memory region of the secure world memory.
mmio_buffer_t mmio;
zx_off_t shared_mem_offset = shared_mem_paddr - mmio_vmo_paddr;
status = mmio_buffer_init(&mmio, shared_mem_offset, shared_mem_size, mmio_dev.vmo,
ZX_CACHE_POLICY_CACHED);
if (status != ZX_OK) {
LOG(ERROR, "unable to map secure world memory");
return status;
}
mmio_pinned_buffer_t pinned_mmio;
status = mmio_buffer_pin(&mmio, bti_.get(), &pinned_mmio);
if (status != ZX_OK) {
LOG(ERROR, "unable to pin secure world memory: %d", status);
return status;
}
// Take ownership of the PMT so that we can explicitly unpin.
pmt_ = zx::pmt(pinned_mmio.pmt);
status = SharedMemoryManager::Create(ddk::MmioBuffer(mmio), pinned_mmio.paddr,
&shared_memory_manager_);
if (status != ZX_OK) {
LOG(ERROR, "unable to initialize SharedMemoryManager");
return status;
}
return status;
}
zx_status_t OpteeController::DiscoverSharedMemoryConfig(zx_paddr_t* out_start_addr,
size_t* out_size) {
static const zx_smc_parameters_t func_call =
tee_smc::CreateSmcFunctionCall(kGetSharedMemConfigFuncId);
union {
zx_smc_result_t raw;
GetSharedMemConfigResult response;
} result;
zx_status_t status = zx_smc_call(secure_monitor_.get(), &func_call, &result.raw);
if (status != ZX_OK) {
return status;
}
if (result.response.status != kReturnOk) {
return ZX_ERR_INTERNAL;
}
*out_start_addr = result.response.start;
*out_size = result.response.size;
return status;
}
zx_status_t OpteeController::Create(void* ctx, zx_device_t* parent) {
auto tee = std::make_unique<OpteeController>(parent);
auto status = tee->Bind();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for tee
__UNUSED auto ptr = tee.release();
}
return status;
}
zx_status_t OpteeController::Bind() {
zx_status_t status = ZX_ERR_INTERNAL;
ddk::CompositeProtocolClient composite(parent());
if (!composite.is_valid()) {
LOG(ERROR, "unable to get composite protocol");
return ZX_ERR_NO_RESOURCES;
}
pdev_ = ddk::PDev(composite);
if (!pdev_.is_valid()) {
LOG(ERROR, "unable to get pdev protocol");
return ZX_ERR_NO_RESOURCES;
}
sysmem_ = ddk::SysmemProtocolClient(composite, "sysmem");
if (!sysmem_.is_valid()) {
LOG(ERROR, "unable to get sysmem protocol");
return ZX_ERR_NO_RESOURCES;
}
// Optional protocol
rpmb_protocol_client_ = ddk::RpmbProtocolClient(composite, "rpmb");
static constexpr uint32_t kTrustedOsSmcIndex = 0;
status = pdev_.GetSmc(kTrustedOsSmcIndex, &secure_monitor_);
if (status != ZX_OK) {
LOG(ERROR, "unable to get secure monitor handle");
return status;
}
// TODO(fxbug.dev/13426): Remove this once we have a tee core driver that will discover the TEE OS
status = ValidateApiUid();
if (status != ZX_OK) {
LOG(ERROR, "API UID does not match");
return status;
}
status = ValidateApiRevision();
if (status != ZX_OK) {
LOG(ERROR, "API revision not supported");
return status;
}
status = GetOsRevision();
if (status != ZX_OK) {
LOG(ERROR, "unable to get Trusted OS revision");
return status;
}
status = ExchangeCapabilities();
if (status != ZX_OK) {
LOG(ERROR, "could not exchange capabilities");
return status;
}
status = InitializeSharedMemory();
if (status != ZX_OK) {
LOG(ERROR, "could not initialize shared memory");
return status;
}
status = DdkAdd(kDeviceName.data(), DEVICE_ADD_ALLOW_MULTI_COMPOSITE);
if (status != ZX_OK) {
LOG(ERROR, "failed to add device");
return status;
}
return ZX_OK;
}
zx_status_t OpteeController::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
fuchsia_hardware_tee::DeviceConnector::Dispatch(this, msg, &transaction);
return transaction.Status();
}
zx_status_t OpteeController::DdkOpen(zx_device_t** out_dev, uint32_t flags) {
// Do not set out_dev because this Controller will handle the FIDL messages
return ZX_OK;
}
void OpteeController::DdkSuspend(ddk::SuspendTxn txn) {
// All operations should have been halted when the child devices were suspended.
shared_memory_manager_ = nullptr;
zx_status_t status = pmt_.unpin();
ZX_DEBUG_ASSERT(status == ZX_OK);
txn.Reply(ZX_OK, txn.requested_state());
}
void OpteeController::DdkUnbind(ddk::UnbindTxn txn) {
// Initiate the removal of this device and all of its children.
txn.Reply();
}
void OpteeController::DdkRelease() {
// devmgr has given up ownership, so we must clean ourself up.
delete this;
}
zx_status_t OpteeController::TeeConnect(zx::channel tee_device_request,
zx::channel service_provider) {
ZX_DEBUG_ASSERT(tee_device_request.is_valid());
// Create a new `OpteeClient` device and hand off client communication to it.
auto client =
std::make_unique<OpteeClient>(this, std::move(service_provider),
std::nullopt /* application_uuid */, true /* use_old_api */);
// Add a child `OpteeClient` device instance and have it immediately start serving
// `tee_device_request`
zx_status_t status = client->DdkAdd(ddk::DeviceAddArgs("optee-client")
.set_flags(DEVICE_ADD_INSTANCE)
.set_client_remote(std::move(tee_device_request)));
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the memory for `client`
[[maybe_unused]] auto client_ptr = client.release();
return ZX_OK;
}
void OpteeController::ConnectTee(zx::channel service_provider, zx::channel tee_request,
[[maybe_unused]] ConnectTeeCompleter::Sync& _completer) {
TeeConnect(std::move(tee_request), std::move(service_provider));
}
void OpteeController::ConnectToDeviceInfo(
zx::channel device_info_request,
[[maybe_unused]] ConnectToDeviceInfoCompleter::Sync& _completer) {
ZX_DEBUG_ASSERT(device_info_request.is_valid());
// Create a new `OpteeDeviceInfo` device and hand off client communication to it.
auto device_info = std::make_unique<OpteeDeviceInfo>(this);
// Add a child `OpteeDeviceInfo` instance device and have it immediately start serving
// `device_info_request`.
zx_status_t status = device_info->DdkAdd(ddk::DeviceAddArgs("optee-client")
.set_flags(DEVICE_ADD_INSTANCE)
.set_client_remote(std::move(device_info_request)));
if (status != ZX_OK) {
LOG(ERROR, "failed to create device info child");
return;
}
// devmgr is now in charge of the memory for `device_info`
[[maybe_unused]] auto device_info_ptr = device_info.release();
}
void OpteeController::ConnectToApplication(
llcpp::fuchsia::tee::Uuid application_uuid, zx::channel service_provider,
zx::channel application_request,
[[maybe_unused]] ConnectToApplicationCompleter::Sync& _completer) {
ZX_DEBUG_ASSERT(application_request.is_valid());
// Create a new `OpteeClient` device and hand off client communication to it.
auto client = std::make_unique<OpteeClient>(this, std::move(service_provider),
Uuid(application_uuid), false /* use_old_api */);
// Add a child `OpteeClient` device instance and have it immediately start serving
// `device_request`
zx_status_t status = client->DdkAdd(ddk::DeviceAddArgs("optee-client")
.set_flags(DEVICE_ADD_INSTANCE)
.set_client_remote(std::move(application_request)));
if (status != ZX_OK) {
LOG(ERROR, "failed to create device info child (status: %d)", status);
return;
}
// devmgr is now in charge of the memory for `client`
[[maybe_unused]] auto client_ptr = client.release();
}
OsInfo OpteeController::GetOsInfo() const {
fuchsia_tee::Uuid uuid;
uuid.time_low = kOpteeOsUuid.timeLow;
uuid.time_mid = kOpteeOsUuid.timeMid;
uuid.time_hi_and_version = kOpteeOsUuid.timeHiAndVersion;
std::memcpy(uuid.clock_seq_and_node.data(), kOpteeOsUuid.clockSeqAndNode,
sizeof(uuid.clock_seq_and_node));
OsRevision os_revision;
os_revision.set_major(os_revision_.major);
os_revision.set_minor(os_revision_.minor);
OsInfo os_info;
os_info.set_uuid(uuid);
os_info.set_revision(std::move(os_revision));
os_info.set_is_global_platform_compliant(true);
return os_info;
}
uint32_t OpteeController::CallWithMessage(const optee::Message& message, RpcHandler rpc_handler) {
uint32_t return_value = tee_smc::kSmc32ReturnUnknownFunction;
union {
zx_smc_parameters_t params;
RpcFunctionResult rpc_result;
} func_call;
func_call.params = tee_smc::CreateSmcFunctionCall(optee::kCallWithArgFuncId,
static_cast<uint32_t>(message.paddr() >> 32),
static_cast<uint32_t>(message.paddr()));
while (true) {
union {
zx_smc_result_t raw;
CallWithArgResult response;
RpcFunctionArgs rpc_args;
} result;
zx_status_t status = zx_smc_call(secure_monitor_.get(), &func_call.params, &result.raw);
if (status != ZX_OK) {
LOG(ERROR, "unable to invoke SMC");
return return_value;
}
if (result.response.status == kReturnEThreadLimit) {
// TODO(rjascani): This should actually block until a thread is available. For now,
// just quit.
LOG(ERROR, "hit thread limit, need to fix this");
break;
} else if (optee::IsReturnRpc(result.response.status)) {
rpc_handler(result.rpc_args, &func_call.rpc_result);
} else {
return_value = result.response.status;
break;
}
}
return return_value;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = OpteeController::Create;
return ops;
}();
} // namespace optee
// clang-format off
ZIRCON_DRIVER_BEGIN(optee, optee::driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_OPTEE),
ZIRCON_DRIVER_END(optee)
// clang-format on