blob: 2f4c1eb85396f967e4ac1c142c7d3773521abc0d [file] [log] [blame]
// 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 <fuchsia/hardware/platform/device/c/banjo.h>
#include <fuchsia/hardware/platform/device/cpp/banjo.h>
#include <fuchsia/hardware/sysmem/c/banjo.h>
#include <fuchsia/hardware/sysmem/cpp/banjo.h>
#include <lib/async/cpp/task.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/fdf/cpp/dispatcher.h>
#include <lib/fdf/internal.h>
#include <lib/fpromise/result.h>
#include <lib/sync/cpp/completion.h>
#include <lib/zx/interrupt.h>
#include <lib/zx/resource.h>
#include <zircon/limits.h>
#include <fbl/array.h>
#include <zxtest/zxtest.h>
#include "device.h"
#include "src/devices/bus/testing/fake-pdev/fake-pdev.h"
struct Context {
std::shared_ptr<amlogic_secure_mem::AmlogicSecureMemDevice> dev;
};
class Binder : public fake_ddk::Bind {
zx_status_t DeviceRemove(zx_device_t* dev) override {
Context* context = reinterpret_cast<Context*>(dev);
if (context->dev != nullptr) {
context->dev->DdkRelease();
}
context->dev = nullptr;
return ZX_OK;
}
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) override {
*out = parent;
Context* context = reinterpret_cast<Context*>(parent);
context->dev.reset(reinterpret_cast<amlogic_secure_mem::AmlogicSecureMemDevice*>(args->ctx));
if (args && args->ops) {
if (args->ops->message) {
zx_status_t status;
if ((status = fidl_.SetMessageOp(args->ctx, args->ops->message)) < 0) {
return status;
}
}
}
return ZX_OK;
}
};
class FakeSysmem : public ddk::SysmemProtocol<FakeSysmem> {
public:
FakeSysmem() : proto_({&sysmem_protocol_ops_, this}) {}
const sysmem_protocol_t* proto() const { return &proto_; }
zx_status_t SysmemConnect(zx::channel allocator2_request) {
// Currently, do nothing
return ZX_OK;
}
zx_status_t SysmemRegisterHeap(uint64_t heap, zx::channel heap_connection) {
// Currently, do nothing
return ZX_OK;
}
zx_status_t SysmemRegisterSecureMem(zx::channel tee_connection) {
// Stash the tee_connection_ so the channel can stay open long enough to avoid a potentially
// confusing error message during the test.
tee_connection_ = std::move(tee_connection);
return ZX_OK;
}
zx_status_t SysmemUnregisterSecureMem() {
// Currently, do nothing
return ZX_OK;
}
private:
sysmem_protocol_t proto_;
zx::channel tee_connection_;
};
// We cover the code involved in supporting non-VDEC secure memory and VDEC secure memory in
// sysmem-test, so this fake doesn't really need to do much yet.
class FakeTee : public ddk::TeeProtocol<FakeTee> {
public:
FakeTee() : proto_({&tee_protocol_ops_, this}) {}
const tee_protocol_t* proto() const { return &proto_; }
zx_status_t TeeConnectToApplication(const uuid_t* application_uuid, zx::channel tee_app_request,
zx::channel service_provider) {
// Currently, do nothing
//
// We don't rely on the tee_app_request channel sticking around for these tests. See
// sysmem-test for a test that exercises the tee_app_request channel.
return ZX_OK;
}
private:
tee_protocol_t proto_;
};
class AmlogicSecureMemTest : public zxtest::Test {
protected:
AmlogicSecureMemTest() {
pdev_.UseFakeBti();
static constexpr size_t kNumBindFragments = 3;
fbl::Array<fake_ddk::FragmentEntry> fragments(new fake_ddk::FragmentEntry[kNumBindFragments],
kNumBindFragments);
fragments[0] = pdev_.fragment();
fragments[1].name = "sysmem";
fragments[1].protocols.emplace_back(fake_ddk::ProtocolEntry{
ZX_PROTOCOL_SYSMEM, *reinterpret_cast<const fake_ddk::Protocol*>(sysmem_.proto())});
fragments[2].name = "tee";
fragments[2].protocols.emplace_back(fake_ddk::ProtocolEntry{
ZX_PROTOCOL_TEE, *reinterpret_cast<const fake_ddk::Protocol*>(tee_.proto())});
ddk_.SetFragments(std::move(fragments));
// We initialize this in a dispatcher thread so that fdf_dispatcher_get_current_dispatcher
// works. This dispatcher isn't actually used in the test.
fdf_internal_push_driver(reinterpret_cast<void*>(0x12345678));
auto dispatcher =
fdf::Dispatcher::Create(0, fit::bind_member(this, &AmlogicSecureMemTest::ShutdownHandler));
fdf_internal_pop_driver();
ASSERT_OK(dispatcher.status_value());
dispatcher_ = *std::move(dispatcher);
libsync::Completion completion;
async::PostTask(dispatcher_.async_dispatcher(), [&]() {
ASSERT_OK(amlogic_secure_mem::AmlogicSecureMemDevice::Create(nullptr, parent()));
completion.Signal();
});
completion.Wait();
}
void TearDown() override {
// For now, we use DdkSuspend(mexec) partly to cover DdkSuspend(mexec) handling, and
// partly because it's the only way of cleaning up safely that we've implemented so far, as
// aml-securemem doesn't yet implement DdkUnbind() - and arguably it doesn't really need to
// given what aml-securemem is.
ddk::SuspendTxn txn(dev()->zxdev(), DEV_POWER_STATE_D3COLD, false, DEVICE_SUSPEND_REASON_MEXEC);
libsync::Completion completion;
async::PostTask(dispatcher_.async_dispatcher(), [&]() {
dev()->DdkSuspend(std::move(txn));
completion.Signal();
});
completion.Wait();
dispatcher_.ShutdownAsync();
ASSERT_OK(shutdown_completion_.Wait());
}
void ShutdownHandler(fdf_dispatcher_t* dispatcher) {
ASSERT_EQ(dispatcher, dispatcher_.get());
shutdown_completion_.Signal();
}
zx_device_t* parent() { return reinterpret_cast<zx_device_t*>(&ctx_); }
amlogic_secure_mem::AmlogicSecureMemDevice* dev() { return ctx_.dev.get(); }
private:
Binder ddk_;
fake_pdev::FakePDev pdev_;
FakeSysmem sysmem_;
FakeTee tee_;
Context ctx_ = {};
fdf::Dispatcher dispatcher_;
libsync::Completion shutdown_completion_;
};
TEST_F(AmlogicSecureMemTest, GetSecureMemoryPhysicalAddressBadVmo) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
ASSERT_TRUE(dev()->GetSecureMemoryPhysicalAddress(std::move(vmo)).is_error());
}