blob: 6be9c36fd0dd703cbc877d64de06beaa6f785f51 [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 "optee-client.h"
#include "optee-device-info.h"
#include "optee-util.h"
namespace optee {
namespace fuchsia_tee = ::llcpp::fuchsia::tee;
enum {
kFragmentPdev,
kFragmentSysmem,
kFragmentCount,
};
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() {
zx_paddr_t shared_mem_start;
size_t shared_mem_size;
zx_status_t status = DiscoverSharedMemoryConfig(&shared_mem_start, &shared_mem_size);
if (status != ZX_OK) {
LOG(ERROR, "unable to discover shared memory configuration");
return status;
}
static constexpr uint32_t kTeeBtiIndex = 0;
zx::bti bti;
status = pdev_get_bti(&pdev_proto_, kTeeBtiIndex, bti.reset_and_get_address());
if (status != ZX_OK) {
LOG(ERROR, "unable to get bti");
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_get_mmio(&pdev_proto_, 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 sw_base_paddr = mmio_vmo_paddr + mmio_dev.offset;
size_t sw_size = mmio_dev.size;
if (shared_mem_start < sw_base_paddr ||
(shared_mem_start + shared_mem_size) > (sw_base_paddr + sw_size)) {
LOG(ERROR, "shared memory outside of secure world range");
return ZX_ERR_OUT_OF_RANGE;
}
mmio_buffer_t mmio;
size_t vmo_relative_offset = shared_mem_start - mmio_vmo_paddr;
status = mmio_buffer_init(&mmio, vmo_relative_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;
}
status = SharedMemoryManager::Create(shared_mem_start, shared_mem_size, ddk::MmioBuffer(mmio),
std::move(bti), &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;
composite_protocol_t composite;
status = device_get_protocol(parent(), ZX_PROTOCOL_COMPOSITE, &composite);
if (status != ZX_OK) {
LOG(ERROR, "unable to get composite protocol");
return status;
}
zx_device_t* fragments[kFragmentCount];
size_t actual;
composite_get_fragments(&composite, fragments, countof(fragments), &actual);
if (actual != countof(fragments)) {
LOG(ERROR, "unable to composite_get_fragments()");
return ZX_ERR_INTERNAL;
}
status = device_get_protocol(fragments[kFragmentPdev], ZX_PROTOCOL_PDEV, &pdev_proto_);
if (status != ZX_OK) {
LOG(ERROR, "unable to get pdev protocol");
return status;
}
status = device_get_protocol(fragments[kFragmentSysmem], ZX_PROTOCOL_SYSMEM, &sysmem_proto_);
if (status != ZX_OK) {
LOG(ERROR, "unable to get sysmem protocol");
return status;
}
static constexpr uint32_t kTrustedOsSmcIndex = 0;
status = pdev_get_smc(&pdev_proto_, kTrustedOsSmcIndex, secure_monitor_.reset_and_get_address());
if (status != ZX_OK) {
LOG(ERROR, "unable to get secure monitor handle");
return status;
}
// TODO(MTWN-140): 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_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::DdkUnbindNew(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("optee-client", // name
DEVICE_ADD_INSTANCE, // flags
nullptr, // props
0, // prop_count
0, // proto_id
nullptr, // proxy_args
tee_device_request.release() // client_remote
);
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("optee-client", // name
DEVICE_ADD_INSTANCE, // flags
nullptr, // props
0, // prop_count
0, // proto_id
nullptr, // proxy_args
device_info_request.release() // client_remote
);
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("optee-client", // name
DEVICE_ADD_INSTANCE, // flags
nullptr, // props
0, // prop_count
0, // proto_id
nullptr, // proxy_args
application_request.release() // client_remote
);
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