| // Copyright 2017 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. |
| |
| #include "eeprom_device.h" |
| #include "utilities.h" |
| |
| constexpr unsigned int kLogiEepromDeviceVersionDataSize = 2; |
| constexpr uint16_t kLogiEepromAddressSignatureByte0 = 0x00; |
| constexpr uint16_t kLogiEepromAddressSignatureByte1 = 0x01; |
| constexpr uint16_t kLogiEepromAdressMajorVersion = 0x10; |
| constexpr uint16_t kLogiEepromAddressMinorVersion = 0x0f; |
| constexpr unsigned int kLogiEepromMaxAttemptSendBlockData = 3; |
| constexpr unsigned char kLogiUvcXuDevInfoCsEepromVersion = 3; |
| constexpr unsigned char kLogiEepromUvcXuTestDbgCsEepromAddress = 3; |
| constexpr unsigned char kLogiEepromUvcXuTestDbgCsEepromAccess = 4; |
| constexpr unsigned int kLogiEepromImageVerificationMinSize = 6; |
| |
| EepromDevice::EepromDevice(std::string pid) |
| : VideoDevice(pid, kLogiDeviceEeprom) {} |
| |
| EepromDevice::~EepromDevice() { |
| closeDevice(); |
| } |
| |
| int EepromDevice::readDeviceVersion(std::string* deviceVersion) { |
| if (!isOpen) |
| return kLogiErrorDeviceNotOpen; |
| |
| std::vector<unsigned char> outputData; |
| int unitId = GetUnitID(kLogiGuidDeviceInfo); |
| int error = getXuControl(unitId, |
| kLogiUvcXuDevInfoCsEepromVersion, |
| &outputData); |
| if (error) |
| return error; |
| if (outputData.size() < kLogiEepromDeviceVersionDataSize) |
| return kLogiErrorInvalidDeviceVersionDataSize; |
| |
| // device version is valid |
| int major = (int)outputData[1]; |
| int minor = (int)outputData[0]; |
| *deviceVersion = GetDeviceStringVersion(major, minor); |
| return kLogiErrorNoError; |
| } |
| |
| int EepromDevice::getImageVersion(std::vector<char> buffer, |
| std::string* imageVersion) { |
| if (buffer.empty()) |
| return kLogiErrorImageBufferReadFailed; |
| |
| uint8_t major = 0; |
| uint8_t minor = 0; |
| std::vector<S19Block> imageBlocks; |
| int error = parseS19(buffer, &imageBlocks); |
| if (error) |
| return error; |
| error = readImageByte(imageBlocks, kLogiEepromAdressMajorVersion, &major); |
| if (error) |
| return error; |
| error = readImageByte(imageBlocks, kLogiEepromAddressMinorVersion, &minor); |
| if (error) |
| return error; |
| |
| std::string version = GetDeviceStringVersion(major, minor); |
| *imageVersion = version; |
| return kLogiErrorNoError; |
| } |
| |
| int EepromDevice::verifyImage(std::vector<char> buffer) { |
| if (buffer.empty() || buffer.size() < kLogiEepromImageVerificationMinSize) |
| return kLogiErrorImageBufferReadFailed; |
| |
| std::vector<S19Block> imageBlocks; |
| int error = parseS19(buffer, &imageBlocks); |
| if (error) |
| return error; |
| |
| // perform some sanity checks |
| uint8_t signatureAA = 0x00; |
| uint8_t signature55 = 0x00; |
| error = readImageByte(imageBlocks, |
| kLogiEepromAddressSignatureByte0, |
| &signatureAA); |
| if (error) |
| return error; |
| error = readImageByte(imageBlocks, |
| kLogiEepromAddressSignatureByte1, |
| &signature55); |
| if (error) |
| return error; |
| |
| if (signatureAA != 0xAA || signature55 != 0x55) |
| return kLogiErrorImageVerificationFailed; |
| |
| return kLogiErrorNoError; |
| } |
| |
| int EepromDevice::parseS19(std::vector<char> buffer, |
| std::vector<S19Block>* imageBlocks) { |
| if (buffer.empty()) |
| return kLogiErrorImageBufferReadFailed; |
| |
| // Split the binary file into lines |
| std::vector<std::string> lines = splitBinaryIntoLines(buffer); |
| if (lines.empty()) |
| return kLogiErrorParseS19BinaryFailed; |
| |
| std::vector<S19Block> resultBlocks; |
| S19Block block; |
| |
| for (const auto& line : lines) { |
| if (line[0] != 'S') |
| return kLogiErrorParseS19BinaryFailed; |
| |
| int addressBytes = 0; |
| const auto commandString = line[1]; |
| switch (commandString) { |
| case '0': // Block header |
| block = S19Block(); |
| addressBytes = 2; |
| break; |
| case '1': // Data sequence (2-byte address) |
| case '9': // End of block (2-byte address) |
| addressBytes = 2; |
| break; |
| default: // Others are not supported by this parser |
| return kLogiErrorParseS19BinaryFailed; |
| } |
| |
| const unsigned int byteCount = stoi(line.substr(2, 2), NULL, 16); |
| const uint16_t address = |
| (uint16_t)stoi(line.substr(4, addressBytes * 2), NULL, 16); |
| uint8_t runningCheckSum = |
| (uint8_t)(byteCount + (address >> 8) + (address & 0x00ff)); |
| |
| const auto payloadString = line.substr(1 + 1 + 2 + addressBytes * 2); |
| if (payloadString.size() != 2 * (byteCount - addressBytes)) |
| return kLogiErrorParseS19BinaryFailed; |
| std::vector<uint8_t> data; |
| auto it = begin(payloadString); |
| uint8_t expectedCheckSum = 0; |
| for (;;) { |
| uint8_t byte; |
| // Most-significant nibble |
| char c = (char)(*it++); |
| if (!ConvertHexCharToUnsignedInt(c, &byte)) |
| return kLogiErrorParseS19BinaryFailed; |
| byte <<= 4; |
| |
| // Least-significant nibble |
| c = (char)(*it++); |
| uint8_t leastSignificantByte; |
| if (!ConvertHexCharToUnsignedInt(c, &leastSignificantByte)) |
| return kLogiErrorParseS19BinaryFailed; |
| byte |= leastSignificantByte; |
| |
| if (it != end(payloadString)) { |
| // Data byte |
| data.push_back(byte); |
| runningCheckSum += byte; |
| } else { |
| // Check sum (last byte) |
| expectedCheckSum = byte; |
| break; |
| } |
| } |
| if ((uint8_t)~runningCheckSum != expectedCheckSum) |
| return kLogiErrorParseS19BinaryFailed; |
| |
| switch (commandString) { |
| case '0': // Block header |
| block.Header = make_pair(address, data); |
| break; |
| case '1': // Data sequence (2-byte address) |
| block.Data.push_back(make_pair(address, data)); |
| break; |
| case '9': // End of block (2-byte address) |
| resultBlocks.push_back(block); |
| break; |
| default: // Others are not supported by this parser |
| return kLogiErrorParseS19BinaryFailed; |
| } |
| } |
| |
| *imageBlocks = resultBlocks; |
| return kLogiErrorNoError; |
| } |
| |
| int EepromDevice::sendImage(std::vector<S19Block> imageBlocks) { |
| if (imageBlocks.empty()) |
| return kLogiErrorReadS19ImageByteFailed; |
| |
| int bytesWritten = 0; |
| for (const auto& block : imageBlocks) { |
| for (const auto& dataPointer : block.Data) { |
| const auto baseAddress = dataPointer.first; |
| const auto& data = dataPointer.second; |
| for (uint16_t offset = 0; offset < data.size(); offset++) { |
| int attempts = kLogiEepromMaxAttemptSendBlockData; |
| while (attempts-- > 0) { |
| int error = writeEepromByte(baseAddress + offset, data[offset]); |
| if (error) |
| continue; // Failed this attempt, retry to send image. |
| break; |
| } |
| |
| if (attempts <= 0) |
| return kLogiErrorSendImageFailed; |
| } |
| bytesWritten += data.size(); |
| } |
| } |
| return kLogiErrorNoError; |
| } |
| |
| int EepromDevice::performUpdate(std::vector<char> buffer) { |
| if (!isOpen) |
| return kLogiErrorDeviceNotOpen; |
| |
| std::string deviceVersion; |
| std::string imageVersion; |
| int error = getDeviceVersion(&deviceVersion); |
| if (error) |
| return error; |
| |
| error = verifyImage(buffer); |
| if (error) |
| return error; |
| |
| error = getImageVersion(buffer, &imageVersion); |
| if (error) |
| return error; |
| |
| // check if update available |
| int compareVersion = CompareVersions(deviceVersion, imageVersion); |
| if (compareVersion < 0) { |
| std::vector<S19Block> imageBlocks; |
| error = parseS19(buffer, &imageBlocks); |
| if (error) |
| return error; |
| error = sendImage(imageBlocks); |
| } |
| return error; |
| } |
| |
| std::vector<std::string> EepromDevice::splitBinaryIntoLines( |
| std::vector<char> buffer) { |
| std::vector<std::string> lines; |
| std::string line; |
| for (const auto byte : buffer) { |
| if (isalnum(byte)) { |
| line.push_back(byte); |
| } else if (byte == '\r' || byte == '\n') { |
| if (!line.empty()) { |
| lines.push_back(line); |
| line.clear(); |
| } |
| } else { |
| return std::vector<std::string>(); |
| } |
| } |
| return lines; |
| } |
| |
| int EepromDevice::readImageByte(std::vector<S19Block> imageBlocks, |
| uint16_t address, |
| uint8_t* value) { |
| if (imageBlocks.empty()) |
| return kLogiErrorReadS19ImageByteFailed; |
| for (const auto& block : imageBlocks) { |
| for (const auto& p : block.Data) { |
| const auto baseAddress = p.first; |
| const auto& data = p.second; |
| |
| const auto blockSize = data.size(); |
| if (address >= baseAddress && address < (baseAddress + blockSize)) { |
| const auto offset = address - baseAddress; // Offset within this block |
| |
| *value = data[offset]; |
| return kLogiErrorNoError; |
| } |
| } |
| } |
| return kLogiErrorReadS19ImageByteFailed; |
| } |
| |
| int EepromDevice::countImageBytes(std::vector<S19Block> imageBlocks) { |
| if (imageBlocks.empty()) |
| return kLogiErrorReadS19ImageByteFailed; |
| int byteCount = 0; |
| for (const auto& block : imageBlocks) { |
| for (const auto& data : block.Data) |
| byteCount += data.second.size(); |
| } |
| return byteCount; |
| } |
| |
| int EepromDevice::writeEepromByte(uint16_t address, uint8_t byte) { |
| unsigned char firstByte = uint8_t(address & 0xff); |
| unsigned char secondByte = uint8_t(address >> 8); |
| std::vector<unsigned char> addressData; |
| addressData.push_back(firstByte); |
| addressData.push_back(secondByte); |
| int unitID = GetUnitID(kLogiGuidTestDebug); |
| int error = setXuControl(unitID, |
| kLogiEepromUvcXuTestDbgCsEepromAddress, |
| addressData); |
| if (error) |
| return error; |
| |
| std::vector<unsigned char> byteData; |
| byteData.push_back(byte); |
| return setXuControl(unitID, kLogiEepromUvcXuTestDbgCsEepromAccess, byteData); |
| } |