blob: 607af542f949b57d5ab16fe1388242039b37843f [file] [log] [blame]
// Copyright 2021 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 <endian.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/fake-i2c/fake-i2c.h>
#include <lib/fzl/vmo-mapper.h>
#include <array>
#include <zxtest/zxtest.h>
#include "gt92xx.h"
#include "src/devices/gpio/testing/fake-gpio/fake-gpio.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace {
bool enable_load_firmware = false;
bool corrupt_firmware_checksum = false;
} // namespace
// Must not be in a namespace in order to override the weak implementation in fake_ddk.
zx_status_t load_firmware_from_driver(zx_driver_t* drv, zx_device_t* device, const char* path,
zx_handle_t* fw, size_t* size) {
constexpr uint8_t kFirmwareTestData[] = {0x52, 0xc0, 0xb3, 0x37, 0x84, 0x2c, 0xf0, 0xbc,
0x88, 0xe7, 0xca, 0x28, 0x93, 0x9f, 0xed, 0x86,
0xd6, 0x06, 0x4b, 0xb1, 0x72, 0x65, 0x45, 0x48,
0x6d, 0xcf, 0x06, 0x86, 0xe7, 0xac, 0x39, 0x6f};
constexpr uint8_t kFirmwareHeader[] = {
0x00, 0x01, 0x60, 0x00, // Firmware size excluding header
'9', '2', '9', '3', '\0', '\0', '\0', '\0', // Product ID string
0x61, 0x05, // Firmware version number
};
constexpr size_t kFirmwareSize = sizeof(kFirmwareHeader) + 0x16000;
if (!enable_load_firmware) {
return ZX_ERR_NOT_FOUND;
}
zx::vmo firmware_vmo;
fzl::VmoMapper firmware_mapper;
zx_status_t status = firmware_mapper.CreateAndMap(
kFirmwareSize, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &firmware_vmo);
if (status != ZX_OK) {
return status;
}
uint8_t* firmware = reinterpret_cast<uint8_t*>(firmware_mapper.start());
memcpy(firmware, kFirmwareHeader, sizeof(kFirmwareHeader));
constexpr size_t kSectionOffsets[] = {
0x0000, 0x2000, 0x4000, 0x6000, // SS51
0xa800, 0xc800, 0xe800, 0x10800, // Gwake
0x15000, // DSP ISP
};
for (size_t offset : kSectionOffsets) {
memcpy(firmware + sizeof(kFirmwareHeader) + offset, kFirmwareTestData,
sizeof(kFirmwareTestData));
}
uint16_t checksum = 0;
// Skip the first uint16 and put the checksum there after.
for (size_t i = 1; i < (kFirmwareSize - sizeof(kFirmwareHeader)) / sizeof(checksum); i++) {
checksum += be16toh(reinterpret_cast<uint16_t*>(firmware + sizeof(kFirmwareHeader))[i]);
}
checksum = htobe16(-checksum);
if (corrupt_firmware_checksum) {
checksum++;
}
memcpy(firmware + sizeof(kFirmwareHeader), &checksum, sizeof(checksum));
*fw = firmware_vmo.release();
*size = kFirmwareSize;
return ZX_OK;
}
namespace goodix {
class FakeTouchDevice : public fake_i2c::FakeI2c {
private:
uint8_t product_info_[sizeof(uint32_t) + sizeof(uint16_t)] = {'9', '2', '9', '3', 0x04, 0x61};
public:
enum ControllerState {
kIdle,
kReadingDspIsp,
kReadingGwake,
kReadingSs51,
kReadingDsp,
kReadingBoot,
kReadingBootIsp,
kReadingLink,
kReadingFirstSs51Section,
kReady,
};
void SetFirmwareMessageInvalid() { firmware_message_ = 0; }
void SetProductInfo(std::array<uint8_t, sizeof(product_info_)> product_info) {
memcpy(product_info_, product_info.data(), sizeof(product_info_));
}
void SetCorruptSectionRead() { corrupt_section_read_ = true; }
bool FirmwareWritten() const { return firmware_written_; }
ControllerState CurrentState() const { return current_state_; }
protected:
zx_status_t Transact(const uint8_t* write_buffer, size_t write_buffer_size, uint8_t* read_buffer,
size_t* read_buffer_size) override {
if (write_buffer_size < 2) {
return ZX_ERR_NOT_SUPPORTED;
}
const uint16_t address = (write_buffer[0] << 8) | write_buffer[1];
write_buffer += 2;
write_buffer_size -= 2;
ControllerState next_state = current_state_;
if (address == GT_REG_SW_RESET) {
if (write_buffer_size == 0) {
read_buffer[0] = sw_reset_;
*read_buffer_size = 1;
} else {
sw_reset_ = write_buffer[0];
}
} else if (address == GT_REG_CPU_RESET && write_buffer_size == 1) {
next_state = kReadingDspIsp;
} else if (address == GT_REG_BOOT_CONTROL) {
if (write_buffer_size == 0) {
read_buffer[0] = 0; // Always not busy
*read_buffer_size = 1;
} else if (write_buffer[0] == 0x00 && current_state_ == kReadingDspIsp) {
// This assumes that Gwake is always written, even though it is technically optional.
next_state = kReadingGwake;
} else if (write_buffer[0] == 0xd && current_state_ == kReadingGwake) {
next_state = kReadingSs51;
} else if (write_buffer[0] == 0x4 && current_state_ == kReadingSs51) {
next_state = kReadingDsp;
} else if (write_buffer[0] == 0x5 && current_state_ == kReadingDsp) {
next_state = kReadingBoot;
} else if (write_buffer[0] == 0x6 && current_state_ == kReadingBoot) {
next_state = kReadingBootIsp;
} else if (write_buffer[0] == 0x7 && current_state_ == kReadingBootIsp) {
next_state = kReadingLink;
} else if (write_buffer[0] == 0x9 && current_state_ == kReadingLink) {
next_state = kReadingFirstSs51Section;
} else if (write_buffer[0] == 0x1) {
// Boot ISP and Link are optional, but the process always ends with writing the first SS51
// section.
if (current_state_ == kReadingFirstSs51Section || current_state_ == kReadingBootIsp ||
current_state_ == kReadingLink) {
next_state = kReady;
}
}
} else if (address == GT_REG_FIRMWARE && write_buffer_size == 0) {
read_buffer[0] = firmware_message_;
*read_buffer_size = 1;
} else if (address == GT_REG_HW_INFO && write_buffer_size == 0) {
memset(read_buffer, 0, sizeof(uint32_t));
*read_buffer_size = sizeof(uint32_t);
} else if (address == GT_REG_PRODUCT_INFO && write_buffer_size == 0) {
memcpy(read_buffer, product_info_, sizeof(product_info_));
*read_buffer_size = sizeof(product_info_);
} else if ((address >= 0x9000 && address < 0xb000) || (address >= 0xc000)) {
// This needs to be reset for the config check, at this point in the firmware download the
// message value is no longer used.
firmware_message_ = GT_FIRMWARE_MAGIC;
// Map [0x9000, 0xb000) to [0x8000, 0xa000) -- that way the top bits can masked off to
// get an offset.
const uint16_t offset = (address ^ (address < 0xb000 ? 0x1000 : 0)) & 0x1fff;
const size_t remaining_size = std::min<size_t>(sizeof(section_) - offset, 256);
if (write_buffer_size == 0) {
memcpy(read_buffer, &section_[offset], remaining_size);
*read_buffer_size = remaining_size;
} else if (write_buffer_size <= remaining_size) {
firmware_written_ = true;
memcpy(&section_[offset], write_buffer, write_buffer_size);
if (corrupt_section_read_) {
section_[offset]++;
}
} else {
return ZX_ERR_IO;
}
} else if (write_buffer_size == 0) {
*read_buffer = 0;
*read_buffer_size = 1;
}
current_state_ = next_state;
return ZX_OK;
}
private:
uint8_t sw_reset_ = 0;
uint8_t section_[0x2000];
uint8_t firmware_message_ = GT_FIRMWARE_MAGIC;
bool corrupt_section_read_ = false;
bool firmware_written_ = false;
ControllerState current_state_ = kIdle;
};
class Gt92xxTestDevice : public Gt92xxDevice {
public:
Gt92xxTestDevice(async_dispatcher_t* dispatcher, ddk::I2cChannel i2c,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> intr,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> reset, zx_device_t* parent)
: Gt92xxDevice(parent, dispatcher, std::move(i2c), std::move(intr), std::move(reset)) {}
void Running(bool run) { Gt92xxDevice::running_.store(run); }
zx_status_t Init() { return Gt92xxDevice::Init(); }
void Trigger() { irq_.trigger(0, zx::time()); }
zx_status_t StartThread() {
EXPECT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq_));
auto thunk = [](void* arg) -> int {
return reinterpret_cast<Gt92xxTestDevice*>(arg)->Thread();
};
Running(true);
int ret = thrd_create_with_name(&test_thread_, thunk, this, "gt92xx-test-thread");
return (ret == thrd_success) ? ZX_OK : ZX_ERR_BAD_STATE;
}
zx_status_t StopThread() {
Running(false);
irq_.trigger(0, zx::time());
int ret = thrd_join(test_thread_, nullptr);
return (ret == thrd_success) ? ZX_OK : ZX_ERR_BAD_STATE;
}
thrd_t test_thread_;
};
class GoodixTest : public zxtest::Test {
public:
void SetUp() override {
ASSERT_OK(fidl_servers_loop_.StartThread("fidl-servers"));
enable_load_firmware = true;
fidl::ClientEnd reset_gpio_client = reset_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
fidl::ClientEnd intr_gpio_client = intr_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
auto endpoints = fidl::Endpoints<fuchsia_hardware_i2c::Device>::Create();
fidl::BindServer(fidl_servers_loop_.dispatcher(), std::move(endpoints.server), &i2c_);
fake_parent_ = MockDevice::FakeRootParent();
device_.emplace(nullptr, std::move(endpoints.client), std::move(intr_gpio_client),
std::move(reset_gpio_client), fake_parent_.get());
}
void TearDown() override {
enable_load_firmware = false;
corrupt_firmware_checksum = false;
fidl_servers_loop_.Shutdown();
}
void VerifyInitialReset() {
std::vector reset_states = reset_gpio_.SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_GE(reset_states.size(), 2);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0}, reset_states[0].sub_state);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 1}, reset_states[1].sub_state);
std::vector intr_states = intr_gpio_.SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_GE(intr_states.size(), 2);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0}, intr_states[0].sub_state);
ASSERT_EQ(fake_gpio::ReadSubState{.flags = fuchsia_hardware_gpio::GpioFlags::kPullUp},
intr_states[1].sub_state);
}
void VerifyEnteringUpdateMode(size_t reset_state_log_offset, size_t intr_state_log_offset) {
std::vector reset_states = reset_gpio_.SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_GE(reset_states.size(), reset_state_log_offset + 2);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0}, reset_states[reset_state_log_offset].sub_state);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 1},
reset_states[reset_state_log_offset + 1].sub_state);
std::vector intr_states = intr_gpio_.SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_GE(intr_states.size(), intr_state_log_offset + 1);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0}, intr_states[intr_state_log_offset].sub_state);
}
void VerifyLeavingUpdateMode(size_t reset_state_log_offset, size_t intr_state_log_offset) {
std::vector reset_states = reset_gpio_.SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_GE(reset_states.size(), reset_state_log_offset + 3);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0}, reset_states[reset_state_log_offset].sub_state);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 1},
reset_states[reset_state_log_offset + 1].sub_state);
ASSERT_EQ(fake_gpio::ReadSubState{.flags = fuchsia_hardware_gpio::GpioFlags::kPullDown},
reset_states[reset_state_log_offset + 2].sub_state);
std::vector intr_states = intr_gpio_.SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_GE(intr_states.size(), intr_state_log_offset + 4);
ASSERT_EQ(fake_gpio::ReadSubState{.flags = fuchsia_hardware_gpio::GpioFlags::kPullUp},
intr_states[intr_state_log_offset].sub_state);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0},
intr_states[intr_state_log_offset + 1].sub_state);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0},
intr_states[intr_state_log_offset + 2].sub_state);
ASSERT_EQ(fake_gpio::ReadSubState{.flags = fuchsia_hardware_gpio::GpioFlags::kPullUp},
intr_states[intr_state_log_offset + 3].sub_state);
}
void VerifyUpdateMode(size_t reset_state_log_offset, size_t intr_state_log_offset) {
VerifyEnteringUpdateMode(reset_state_log_offset, intr_state_log_offset);
VerifyLeavingUpdateMode(reset_state_log_offset + 2, intr_state_log_offset + 1);
}
async::Loop fidl_servers_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio> reset_gpio_{
fidl_servers_loop_.dispatcher(), std::in_place};
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio> intr_gpio_{
fidl_servers_loop_.dispatcher(), std::in_place};
FakeTouchDevice i2c_;
std::optional<Gt92xxTestDevice> device_;
std::shared_ptr<MockDevice> fake_parent_;
};
TEST_F(GoodixTest, FirmwareTest) {
EXPECT_OK(device_->Init());
VerifyInitialReset();
VerifyUpdateMode(2, 2);
EXPECT_TRUE(i2c_.FirmwareWritten());
EXPECT_EQ(i2c_.CurrentState(), FakeTouchDevice::kReady);
}
TEST_F(GoodixTest, FirmwareCurrent) {
i2c_.SetProductInfo({'9', '2', '9', '3', 0x05, 0x61});
EXPECT_OK(device_->Init());
VerifyInitialReset();
EXPECT_FALSE(i2c_.FirmwareWritten());
EXPECT_EQ(i2c_.CurrentState(), FakeTouchDevice::kIdle);
}
TEST_F(GoodixTest, FirmwareNotApplicable) {
// Wrong product ID
i2c_.SetProductInfo({'9', '2', '9', '5', 0x04, 0x61});
EXPECT_OK(device_->Init());
VerifyInitialReset();
EXPECT_FALSE(i2c_.FirmwareWritten());
EXPECT_EQ(i2c_.CurrentState(), FakeTouchDevice::kIdle);
}
TEST_F(GoodixTest, ForceFirmwareUpdate) {
// Wrong product ID
i2c_.SetProductInfo({'9', '2', '9', '5', 0x04, 0x61});
// Send an unknown firmware message so that the product ID/version check is skipped
i2c_.SetFirmwareMessageInvalid();
EXPECT_OK(device_->Init());
VerifyInitialReset();
VerifyUpdateMode(2, 2);
EXPECT_TRUE(i2c_.FirmwareWritten());
EXPECT_EQ(i2c_.CurrentState(), FakeTouchDevice::kReady);
}
TEST_F(GoodixTest, BadFirmwareChecksum) {
corrupt_firmware_checksum = true;
EXPECT_NOT_OK(device_->Init());
VerifyInitialReset();
EXPECT_FALSE(i2c_.FirmwareWritten());
EXPECT_EQ(i2c_.CurrentState(), FakeTouchDevice::kIdle);
}
TEST_F(GoodixTest, ReadbackCheckFail) {
i2c_.SetCorruptSectionRead();
EXPECT_NOT_OK(device_->Init());
VerifyInitialReset();
VerifyEnteringUpdateMode(2, 2);
EXPECT_TRUE(i2c_.FirmwareWritten());
EXPECT_EQ(i2c_.CurrentState(), FakeTouchDevice::kReadingDspIsp);
}
} // namespace goodix