blob: ac9e1d8eee630df4dbcd24e472ae01574b6a90a8 [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This is a implementation of IAP firmware updater for STM32-based touchpads.
#include <cstdio>
#include <iomanip>
#include <map>
#include <string>
#include <vector>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <base/logging.h>
#include <base/command_line.h>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/flag_helper.h>
#define GPIO_SYS "/sys/class/gpio/"
#define PULSE_WIDTH 100000 // 100ms reset pulse width, we need at least 300ns.
constexpr int kMaxI2CDevicePathLen = 150;
// Per-board specific configurations.
struct BoardConfig {
uint8_t i2cBusMin;
uint8_t i2cBusMax;
uint8_t i2cSlaveAddress;
uint32_t flash_size;
uint32_t flash_address;
uint32_t fwInfoAddress;
int32_t bootGPIO;
int32_t resetGPIO;
};
// Board specific configurations.
static std::map<std::string, struct BoardConfig> boardConfigs = {
{
"eve",
{
.i2cBusMin = 7,
.i2cBusMax = 8,
.i2cSlaveAddress = 0x46,
.flash_size = 512 * 1024,
.flash_address = 0x08000000,
.fwInfoAddress = 0x80001c4,
.bootGPIO = 432,
.resetGPIO = 433,
},
},
};
// Global config instance.
static struct BoardConfig config;
enum StatusCode {
ACK = 0x79,
NACK = 0x1f,
BUSY = 0x76,
};
class IAPFirmwareUpdater {
public:
IAPFirmwareUpdater(uint8_t i2cBusMin,
uint8_t u2cBusMax,
uint8_t slaveAddress);
void Flash(uint32_t address, std::string path, bool verify);
void ReadFirmware(std::string filename);
void GetFirmwareVersion();
bool EnterBootloader();
void LeaveBootLoader();
private:
int i2c_fd_;
// High level functions.
void WriteFlash(uint32_t address, std::vector<uint8_t> data);
std::vector<uint8_t> ReadFlash(uint32_t address, uint32_t length);
// IAP I2C command wrapper functions.
bool PollForAck(uint32_t timeoutMs = 5000);
uint8_t GetVersion();
int GetId();
void Erase();
bool WriteFlashChunk(uint32_t address, uint8_t length, uint8_t* data);
bool ReadFlashChunk(uint32_t address, uint8_t length, uint8_t* data);
// Helper functions.
std::vector<uint8_t> LoadFirmware(std::string path);
bool WriteCommand(uint8_t command);
uint8_t ReadOneByte();
bool WriteToFile(std::string path, std::string value);
};
IAPFirmwareUpdater::IAPFirmwareUpdater(uint8_t i2cBusMin,
uint8_t i2cBusMax,
uint8_t slaveAddress) {
char dev_path[kMaxI2CDevicePathLen];
for (uint8_t i = i2cBusMax; i >= i2cBusMin; i--) {
snprintf(dev_path, kMaxI2CDevicePathLen, "/dev/i2c-%d", i);
LOG(INFO) << "Attempting to probe <" << dev_path << "> for a touchpad";
i2c_fd_ = open(dev_path, O_RDWR);
if (i2c_fd_ < 0) {
PLOG(INFO) << "Failed to open device";
continue;
} else {
LOG(INFO) << "Successfully opened device, attempting to communicate";
}
if (ioctl(i2c_fd_, I2C_SLAVE_FORCE, slaveAddress)) {
PLOG(INFO) << "Failed to set slave address 0x" << std::hex
<< static_cast<int>(slaveAddress);
close(i2c_fd_);
continue;
}
if (!EnterBootloader()) {
close(i2c_fd_);
} else {
LOG(INFO) << "Successfully entered bootloader mode, this is the one!";
return;
}
}
}
bool IAPFirmwareUpdater::PollForAck(uint32_t timeoutMs) {
struct timespec delay = {.tv_sec = 0, .tv_nsec = 250000000};
uint8_t byte;
uint32_t elapseMs = 0;
struct timeval start, current;
gettimeofday(&start, NULL);
while (true) {
switch ((byte = ReadOneByte())) {
case ACK:
return true;
case NACK:
return false;
case BUSY:
nanosleep(&delay, NULL);
break;
default:
LOG(FATAL) << "PollForAck: unexpected byte 0x" << std::hex
<< static_cast<int>(byte);
}
gettimeofday(&current, NULL);
elapseMs = ((current.tv_sec - start.tv_sec) * 1000 +
(current.tv_usec - start.tv_usec) / 1000);
if (elapseMs > timeoutMs) {
LOG(ERROR) << "PollForAck: timed out waiting for ACK";
return false;
}
}
}
void IAPFirmwareUpdater::Flash(uint32_t address,
std::string path,
bool verify) {
int id = GetId();
LOG(INFO) << "BootLoader version: 0x" << std::hex
<< static_cast<int>(GetVersion());
LOG(INFO) << "Chip ID: 0x" << std::hex << id;
// Begin flashing
std::vector<uint8_t> firmware = LoadFirmware(path);
Erase();
WriteFlash(address, firmware);
std::vector<uint8_t> readFirmware = ReadFlash(address, firmware.size());
if (verify) {
LOG(INFO) << "Verifing firmware ...";
for (unsigned i = 0; i < firmware.size(); i++) {
if (firmware[i] != readFirmware[i]) {
LOG(FATAL) << "Verification failed at 0x" << std::setfill('0')
<< std::setw(8) << address + i;
}
}
LOG(INFO) << "Firmware verified.";
}
LeaveBootLoader();
}
void IAPFirmwareUpdater::GetFirmwareVersion() {
std::vector<uint8_t> version_bytes(4, 0);
ReadFlashChunk(config.fwInfoAddress, version_bytes.size(),
version_bytes.data());
uint32_t version = *reinterpret_cast<uint32_t*>(version_bytes.data());
printf("0x%x\n", version);
LeaveBootLoader();
}
void IAPFirmwareUpdater::ReadFirmware(std::string filename) {
std::vector<uint8_t> firmware =
ReadFlash(config.flash_address, config.flash_size);
WriteToFile(filename, std::string(firmware.begin(), firmware.end()));
LOG(INFO) << "Firmware saved to " << filename;
LeaveBootLoader();
}
bool IAPFirmwareUpdater::EnterBootloader() {
// Bootloader (IAP mode) can be entered by first asserting the BOOT pin
// then toggle the reset pin.
const std::string bootGPIO = base::NumberToString(config.bootGPIO);
const std::string resetGPIO = base::NumberToString(config.resetGPIO);
CHECK(WriteToFile(GPIO_SYS "export", bootGPIO));
CHECK(WriteToFile(GPIO_SYS "export", resetGPIO));
CHECK(WriteToFile(GPIO_SYS "gpio" + bootGPIO + "/direction", "out"));
CHECK(WriteToFile(GPIO_SYS "gpio" + resetGPIO + "/direction", "out"));
CHECK(WriteToFile(GPIO_SYS "gpio" + bootGPIO + "/value", "1"));
CHECK(WriteToFile(GPIO_SYS "gpio" + resetGPIO + "/value", "1"));
usleep(PULSE_WIDTH);
CHECK(WriteToFile(GPIO_SYS "gpio" + resetGPIO + "/value", "0"));
usleep(PULSE_WIDTH);
CHECK(WriteToFile(GPIO_SYS "gpio" + bootGPIO + "/value", "0"));
CHECK(WriteToFile(GPIO_SYS "unexport", bootGPIO));
CHECK(WriteToFile(GPIO_SYS "unexport", resetGPIO));
int id = GetId();
if (id == -1) {
LOG(INFO) << "Failed to enter bootloader mode.";
return false;
}
return true;
}
void IAPFirmwareUpdater::LeaveBootLoader() {
const std::string bootGPIO = base::NumberToString(config.bootGPIO);
const std::string resetGPIO = base::NumberToString(config.resetGPIO);
CHECK(WriteToFile(GPIO_SYS "export", bootGPIO));
CHECK(WriteToFile(GPIO_SYS "export", resetGPIO));
CHECK(WriteToFile(GPIO_SYS "gpio" + bootGPIO + "/direction", "out"));
CHECK(WriteToFile(GPIO_SYS "gpio" + resetGPIO + "/direction", "out"));
CHECK(WriteToFile(GPIO_SYS "gpio" + bootGPIO + "/value", "0"));
CHECK(WriteToFile(GPIO_SYS "gpio" + resetGPIO + "/value", "1"));
usleep(PULSE_WIDTH);
CHECK(WriteToFile(GPIO_SYS "gpio" + resetGPIO + "/value", "0"));
CHECK(WriteToFile(GPIO_SYS "unexport", bootGPIO));
CHECK(WriteToFile(GPIO_SYS "unexport", resetGPIO));
}
void IAPFirmwareUpdater::WriteFlash(uint32_t address,
std::vector<uint8_t> data) {
const uint32_t chunkSize = 200;
uint32_t length = data.size();
uint32_t sent = 0, to_send = 0;
uint8_t* buffer = data.data();
LOG(INFO) << "Writing flash ";
while (sent < length) {
printf(".");
fflush(stdout);
to_send = std::min(chunkSize, length - sent);
if (!WriteFlashChunk(address + sent, to_send, buffer + sent)) {
LOG(FATAL) << "WriteFlashChunk failed at 0x" << std::hex
<< std::setfill('0') << std::setw(8) << sent;
}
sent += to_send;
}
printf("\n");
}
std::vector<uint8_t> IAPFirmwareUpdater::ReadFlash(uint32_t address,
uint32_t length) {
const uint32_t chunkSize = 200;
std::vector<uint8_t> data(length, 0);
uint8_t* buffer = data.data();
uint32_t read = 0, to_read = 0;
LOG(INFO) << "Reading flash ";
while (read < length) {
printf(".");
fflush(stdout);
to_read = std::min(chunkSize, length - read);
if (!ReadFlashChunk(address + read, to_read, buffer + read)) {
LOG(FATAL) << "ReadFlashChunk failed at 0x" << std::hex
<< std::setfill('0') << std::setw(8) << read;
}
read += to_read;
}
printf("\n");
return data;
}
uint8_t IAPFirmwareUpdater::GetVersion() {
CHECK(WriteCommand(0x01));
CHECK(PollForAck());
uint8_t version = ReadOneByte();
CHECK(PollForAck());
return version;
}
int IAPFirmwareUpdater::GetId() {
uint8_t idBuf[3];
if (!WriteCommand(0x02)) {
return -1;
}
if (!PollForAck()) {
return -1;
}
if (read(i2c_fd_, idBuf, 3) != 3) {
return -1;
}
if (!PollForAck()) {
return -1;
}
return idBuf[1] << 8 | idBuf[2];
}
void IAPFirmwareUpdater::Erase() {
LOG(INFO) << "Starting full erase ... ";
fflush(stdout);
CHECK(WriteCommand(0x45));
CHECK(PollForAck());
uint8_t idBuf[3] = {0xff, 0xff, 0x00};
CHECK_EQ(write(i2c_fd_, idBuf, 3), 3);
CHECK(PollForAck(60000));
LOG(INFO) << "done";
}
bool IAPFirmwareUpdater::WriteFlashChunk(uint32_t address,
uint8_t length,
uint8_t* data) {
CHECK(WriteCommand(0x31));
CHECK(PollForAck());
uint8_t addr[5];
uint8_t checksum = 0;
for (int i = 0; i < 4; i++) {
addr[i] = (address >> (24 - i * 8)) & 0xff;
checksum ^= addr[i];
}
addr[4] = checksum;
CHECK_EQ(write(i2c_fd_, addr, 5), 5);
CHECK(PollForAck());
uint8_t buffer[300] = {0};
int offset = 0;
buffer[offset++] = length - 1;
checksum = length - 1;
for (int i = 0; i < length; i++) {
buffer[offset++] = data[i];
checksum ^= data[i];
}
buffer[offset++] = checksum;
CHECK_EQ(write(i2c_fd_, buffer, offset), offset);
CHECK(PollForAck());
return true;
}
bool IAPFirmwareUpdater::ReadFlashChunk(uint32_t address,
uint8_t length,
uint8_t* data) {
CHECK(WriteCommand(0x11));
CHECK(PollForAck());
uint8_t addr[5];
uint8_t checksum = 0;
for (int i = 0; i < 4; i++) {
addr[i] = (address >> (24 - i * 8)) & 0xff;
checksum ^= addr[i];
}
addr[4] = checksum;
CHECK_EQ(write(i2c_fd_, addr, 5), 5);
CHECK(PollForAck());
uint8_t size = length - 1;
uint8_t len[2] = {size, static_cast<uint8_t>(~size)};
CHECK_EQ(write(i2c_fd_, len, 2), 2);
CHECK(PollForAck());
CHECK_EQ(read(i2c_fd_, data, length), length);
return true;
}
std::vector<uint8_t> IAPFirmwareUpdater::LoadFirmware(std::string path) {
std::string buf;
CHECK(base::ReadFileToString(base::FilePath(path), &buf))
<< "Failed to load firmware: " << path;
return std::vector<uint8_t>(buf.begin(), buf.end());
}
bool IAPFirmwareUpdater::WriteCommand(uint8_t command) {
uint8_t buffer[2] = {command, static_cast<uint8_t>(~command)};
return write(i2c_fd_, buffer, 2) == 2;
}
uint8_t IAPFirmwareUpdater::ReadOneByte() {
uint8_t byte;
CHECK_EQ(read(i2c_fd_, &byte, 1), 1);
return byte;
}
bool IAPFirmwareUpdater::WriteToFile(std::string path, std::string value) {
return (base::WriteFile(base::FilePath(path), value.c_str(), value.size()) ==
value.size());
}
int main(int argc, const char* argv[]) {
DEFINE_string(board, "", "Target board to update");
DEFINE_string(read, "", "Read current firmware content to file");
DEFINE_bool(fw_version, false, "Get current firmware version");
DEFINE_bool(enter_bootloader, false, "Enter bootloader mode");
DEFINE_bool(leave_bootloader, false, "Leave bootloader mode");
brillo::FlagHelper::Init(argc, argv, "STM32 IAP firmware updater");
if (!FLAGS_board.length()) {
LOG(FATAL) << "No board specified";
}
if (boardConfigs.find(FLAGS_board) == boardConfigs.end()) {
LOG(FATAL) << "Unsupported board " << FLAGS_board;
}
config = boardConfigs[FLAGS_board];
auto updater = IAPFirmwareUpdater(config.i2cBusMin, config.i2cBusMax,
config.i2cSlaveAddress);
if (FLAGS_fw_version) {
updater.GetFirmwareVersion();
} else if (FLAGS_read.length()) {
updater.ReadFirmware(FLAGS_read);
} else if (FLAGS_enter_bootloader) {
// Do nothing as the constructor already enters bootloader
} else if (FLAGS_leave_bootloader) {
updater.LeaveBootLoader();
} else {
auto commandline = base::CommandLine::ForCurrentProcess();
auto args = commandline->GetArgs();
if (args.size() >= 1) {
updater.Flash(config.flash_address, args[0], true);
}
}
return 0;
}