blob: bddf41faf5f8745c0fb0bf969bdeedf1daaf3acb [file] [log] [blame]
// Copyright 2019 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 "biod/cros_fp_firmware.h"
#include <string>
#include <base/check.h>
#include <base/logging.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/memory_mapped_file.h>
#include <base/notreached.h>
#include <chromeos/ec/ec_commands.h>
#include <fmap.h>
namespace {
constexpr char kFmapRoIdLabel[] = "RO_FRID";
constexpr char kFmapRwIdLabel[] = "RW_FWID";
bool DecodeVersionFromArea(const base::MemoryMappedFile& image,
const struct fmap* fmap,
const char* area_name,
std::string* ver) {
DCHECK(fmap != nullptr);
DCHECK(area_name != nullptr);
DCHECK(ver != nullptr);
const auto area = fmap_find_area(fmap, area_name);
if (area == nullptr) {
LOG(ERROR) << "Failed to find FMAP area " << area_name << ".";
return false;
}
if ((area->offset + area->size) > fmap->size) {
LOG(ERROR) << "FMAP area " << area_name << " has offset " << area->offset
<< " with size " << area->size
<< ", which spans outside the firmware image, of size "
<< fmap->size << ".";
return false;
}
// area->size can be larger than the printable characters in the buffer
// std::string(char*, size_t) constructor will not stop at first \0
const char* str =
reinterpret_cast<const char*>(&(image.data()[area->offset]));
*ver = std::string(str, strnlen(str, area->size));
return true;
}
} // namespace
namespace biod {
CrosFpFirmware::CrosFpFirmware(const base::FilePath& image_path)
: path_(image_path) {
DecodeVersionFromFile();
}
base::FilePath CrosFpFirmware::GetPath() const {
return path_;
}
bool CrosFpFirmware::IsValid() const {
return status_ == Status::kOk;
}
CrosFpFirmware::Status CrosFpFirmware::GetStatus() const {
return status_;
}
std::string CrosFpFirmware::GetStatusString() const {
return StatusToString(GetStatus());
}
const CrosFpFirmware::ImageVersion& CrosFpFirmware::GetVersion() const {
return version_;
}
std::string CrosFpFirmware::StatusToString(Status status) {
switch (status) {
case CrosFpFirmware::Status::kUninitialized:
return "Not initialized";
case CrosFpFirmware::Status::kNotFound:
return "Firmware file does not exist.";
case CrosFpFirmware::Status::kBadFmap:
return "Firmware file has bad FMAP.";
case CrosFpFirmware::Status::kOpenError:
return "Failed to open firmware file version.";
case CrosFpFirmware::Status::kOk:
return "Firmware file is okay.";
}
NOTREACHED();
return "";
}
void CrosFpFirmware::DecodeVersionFromFile() {
if (!base::PathExists(path_) || base::DirectoryExists(path_)) {
LOG(ERROR) << "Failed to find firmware file '" << path_.value() << "'.";
status_ = Status::kNotFound;
return;
}
base::MemoryMappedFile image;
if (!image.Initialize(path_) || !image.IsValid()) {
PLOG(ERROR) << "Failed to open firmware file '" << path_.value() << "'.";
status_ = Status::kOpenError;
return;
}
auto fmap_offset = fmap_find(image.data(), image.length());
if (fmap_offset < 0 || fmap_offset >= image.length()) {
LOG(ERROR) << "Failed to find FMAP inside firmware file '" << path_.value()
<< "'.";
status_ = Status::kBadFmap;
return;
}
DLOG(INFO) << "FMAP signature found at offset " << fmap_offset << ".";
base::MemoryMappedFile image_fmap_aligned;
const base::MemoryMappedFile::Region image_fmap_aligned_region = {
.offset = fmap_offset,
.size = image.length() - fmap_offset,
};
// MemoryMappedFile doesn't allow sharing the same open file with another
// MemoryMappedFile nor does it allow mapping multiple file regions,
// so we need to open the same file again.
if (!image_fmap_aligned.Initialize(
base::File(path_, base::File::FLAG_OPEN | base::File::FLAG_READ),
image_fmap_aligned_region) ||
!image_fmap_aligned.IsValid()) {
PLOG(ERROR) << "Failed to open firmware file's fmap region '"
<< path_.value() << "'.";
status_ = Status::kOpenError;
return;
}
const auto fmap =
reinterpret_cast<const struct fmap*>(image_fmap_aligned.data());
// The firmware file's self reported size should not be larger
// than the file size.
if (fmap->size > image.length()) {
LOG(ERROR) << "FMAP reported an image size of " << fmap->size
<< ", which is larger than the entire file size, "
<< image.length() << ", for '" << path_.value() << "'.";
status_ = Status::kBadFmap;
return;
}
if (!DecodeVersionFromArea(image, fmap, kFmapRoIdLabel,
&version_.ro_version)) {
status_ = Status::kBadFmap;
return;
}
if (!DecodeVersionFromArea(image, fmap, kFmapRwIdLabel,
&version_.rw_version)) {
status_ = Status::kBadFmap;
return;
}
status_ = Status::kOk;
}
} // namespace biod