blob: 4eed2a7368bf792fdb78a0706d8015c5dac18458 [file] [log] [blame]
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <unistd.h>
#include <flasher/file.hpp>
#include <flasher/mutate.hpp>
#include <flasher/ops.hpp>
#include <flashupdate/args.hpp>
#include <flashupdate/flash.hpp>
#include <flashupdate/logging.hpp>
#include <charconv>
#include <filesystem>
#include <format>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
namespace flashupdate
{
namespace flash
{
using stdplus::fd::OpenAccess;
using stdplus::fd::OpenFlag;
using stdplus::fd::OpenFlags;
std::string FlashHelper::readMtdFileText(const std::string& filename)
{
LOG(LogLevel::Debug, "Reading Mtd File {}\n", filename);
auto argReadFile = flasher::ModArgs(filename);
auto readFile = openFile(argReadFile, OpenFlags(OpenAccess::ReadOnly));
std::filesystem::path file(filename.data());
auto size = std::filesystem::file_size(file);
std::vector<std::byte> fileIn(size);
readFile->readAt(fileIn, 0);
// Find new line and remove the data after it.
auto newLinePos =
std::find(fileIn.begin(), fileIn.end(), static_cast<std::byte>('\n'));
if (newLinePos == fileIn.end())
{
throw std::runtime_error("not able to find newline in the mtd file");
}
size = newLinePos - fileIn.begin();
std::string output(size, ' ');
std::memcpy(output.data(), fileIn.data(), size);
return output;
}
std::string FlashHelper::findMtdDevice(const std::string& name)
{
for (const auto& entry :
std::filesystem::directory_iterator("/sys/class/mtd/"))
{
try
{
auto mtdName = this->readMtdFileText(
std::format("{}/name", entry.path().c_str()));
if (name == mtdName)
{
std::string_view mtd = entry.path().c_str();
return mtd.substr(mtd.find_last_of('/') + 1).data();
}
}
catch (const std::exception& e)
{
LOG(LogLevel::Debug, "failed to check mtd name: err {}\n",
e.what());
}
}
return std::string();
}
Flash::Flash()
{
helperPtr = std::make_unique<FlashHelper>();
helper = helperPtr.get();
}
Flash::Flash(Config config, bool keepMux) : Flash()
{
this->config = config;
this->keepMux = keepMux;
}
Flash::~Flash()
{
cleanup();
}
void bindDriver(const Config& config, bool bindValue)
{
auto argFile = flasher::ModArgs(std::format("{}/{}", config.flash.driver,
bindValue ? "bind" : "unbind"));
auto file = openFile(argFile, OpenFlags(OpenAccess::WriteOnly));
auto fileOut = std::vector<std::byte>(config.flash.deviceId.size());
memcpy(fileOut.data(), config.flash.deviceId.data(),
config.flash.deviceId.size());
file->writeAtExact(fileOut, 0);
}
inline std::optional<std::pair<std::string, uint32_t>>
Flash::getFlash(bool primary, std::optional<size_t> expectedSize)
{
return primary ? getFlash(std::nullopt, expectedSize) :
// Use the staging index from the metadata instead of the
// targeted one.
getFlash(static_cast<std::optional<uint8_t>>(
config.flash.stagingIndex),
expectedSize);
}
std::optional<std::pair<std::string, uint32_t>> Flash::getFlash(
std::optional<uint8_t> secondary, std::optional<size_t> expectedSize)
{
auto partition = secondary.has_value() ? config.flash.secondary[*secondary]
: config.flash.primary;
std::string_view location = partition.location;
auto index = location.find_last_of(',');
if (index == std::string::npos)
{
return std::nullopt;
}
auto name = location.substr(index + 1);
if (!location.starts_with("mtd"))
{
// non-mtd device path is expected to be in the format of
// fake,type=simple,erase=0,fake.img
// Last element is the image
try
{
// Create a file if the expectedSize is valid
if (!std::filesystem::exists(name) && expectedSize)
{
LOG(LogLevel::Info, "Creating the stage file.");
auto argFile = flasher::ModArgs(name);
auto file = openFile(argFile, OpenFlags(OpenAccess::WriteOnly)
.set(OpenFlag::Create)
.set(OpenFlag::Trunc));
file->truncate(*expectedSize);
}
std::filesystem::path path(name);
uint32_t size = std::filesystem::file_size(path);
return std::make_pair(partition.location, size);
}
catch (const std::filesystem::filesystem_error& e)
{
LOG(LogLevel::Error, "failed find the partition: {}", e.what());
return std::nullopt;
}
}
#ifndef DEV_WORKFLOW
// Check if the driver for the SPI flash is already in use.
bool spiDriverExists =
access(std::format("{}/{}", config.flash.driver, config.flash.deviceId)
.c_str(),
F_OK) == 0;
// Only setup Flash Driver if it is needed.
// Cleaning up before the driver is ready can cause the kernel to crash.
// If the flash is not used, the cleanup might happen too soon and cause the
// issue.
if (partition.muxSelect)
{
std::string gpio =
std::format("/sys/class/gpio/gpio{}/", *partition.muxSelect);
LOG(LogLevel::Info, "Select the MUX with {}", gpio);
// Expose the GPIO if it does not exists
if (access(gpio.c_str(), F_OK) == -1)
{
auto argFile = flasher::ModArgs("/sys/class/gpio/export");
auto file = openFile(argFile, OpenFlags(OpenAccess::WriteOnly)
.set(OpenFlag::Create)
.set(OpenFlag::Trunc));
std::string data = std::to_string(*config.flash.primary.muxSelect);
std::vector<std::byte> file_out(data.size());
std::memcpy(file_out.data(), data.data(), data.size());
file->writeAtExact(file_out, 0);
}
if (spiDriverExists)
{
// Get MUX GPIO Value and reset the spi driver if the MUX is not set
// before the Driver
auto argFile = flasher::ModArgs(std::format(
"/sys/class/gpio/gpio{}/value", *partition.muxSelect));
auto gpioValue = openFile(argFile, OpenFlags(OpenAccess::ReadOnly));
std::vector<std::byte> value(1);
gpioValue->readAt(value, 0);
// The MUX is not set before the driver so unbind the driver.
if (value[0] == std::byte('0'))
{
LOG(LogLevel::Info, "Reset SPI Driver fist. GPIO was not set");
bindDriver(config, /*bindValue=*/false);
spiDriverExists = false;
}
}
LOG(LogLevel::Info,
"Select the MUX with gpio{} to enable the firmware flash",
*partition.muxSelect);
auto argFile = flasher::ModArgs(std::format(
"/sys/class/gpio/gpio{}/direction", *partition.muxSelect));
auto file = openFile(argFile, OpenFlags(OpenAccess::WriteOnly)
.set(OpenFlag::Create)
.set(OpenFlag::Trunc));
std::string data = "high";
std::vector<std::byte> file_out(data.size());
std::memcpy(file_out.data(), data.data(), data.size());
file->writeAtExact(file_out, 0);
resetGPIOs.emplace(*partition.muxSelect);
}
// Bind driver if it doesn't already exist.
if (!spiDriverExists)
{
bindDriver(config, /*bindValue=*/true);
LOG(LogLevel::Info, "bound {} to {}", config.flash.deviceId,
config.flash.driver);
}
// After successfully setting the mux, setting the flag to prepare for
// cleanup.
needMuxReset = true;
#endif
// Mtd dev path is expected to be in the format of
// mtd,bios-primary
// Last element is the label of the mtd device
std::string mtd = helper->findMtdDevice(name.data());
if (mtd.empty())
{
LOG(LogLevel::Info, "failed to find the mtd device with label of {}",
name);
return std::nullopt;
}
std::string sizeStr =
helper->readMtdFileText(std::format("/sys/class/mtd/{}/size", mtd));
uint32_t size;
auto [ptr, ec]{
std::from_chars(sizeStr.data(), sizeStr.data() + sizeStr.size(), size)};
if (ec != std::errc())
{
throw std::runtime_error(
std::format("failed to convert string to uint32_t: {}",
std::make_error_code(ec).message()));
}
if (ptr != sizeStr.data() + sizeStr.size())
{
throw std::runtime_error("converted invalid characters");
}
LOG(LogLevel::Info, "using {} as the firmware flash with size of {}", name,
size);
if (expectedSize && size != expectedSize)
{
throw std::runtime_error(
std::format("Device size does not match expected, Want {}, got {}",
*expectedSize, size));
}
return std::make_pair(std::format("mtd,/dev/{}", mtd), size);
}
void Flash::cleanup()
{
if (!needMuxReset || keepMux)
{
return;
}
#ifndef DEV_WORKFLOW
LOG(LogLevel::Info, "Cleanup the MUX");
LOG(LogLevel::Info, "unbind {} to {}", config.flash.deviceId,
config.flash.driver);
// Check if the driver for the SPI flash is already removed.
if (access(std::format("{}/{}", config.flash.driver, config.flash.deviceId)
.c_str(),
F_OK) != -1)
{
bindDriver(config, /*bindValue=*/false);
}
// Switch mux to host
// It is possible that there are multiple MUX selected.
for (const auto& gpio : resetGPIOs)
{
LOG(LogLevel::Info, "set gpio{} to low", gpio);
auto argFile = flasher::ModArgs(
std::format("/sys/class/gpio/gpio{}/direction", gpio));
auto file = openFile(argFile, OpenFlags(OpenAccess::WriteOnly)
.set(OpenFlag::Create)
.set(OpenFlag::Trunc));
std::string data = "low";
std::vector<std::byte> file_out = std::vector<std::byte>(data.size());
memcpy(file_out.data(), data.data(), data.size());
file->writeAtExact(file_out, 0);
}
#endif
}
void Flash::setFlashHelper(FlashHelper* helper)
{
if (helper == nullptr)
{
return;
}
this->helper = helper;
}
} // namespace flash
} // namespace flashupdate