| // 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 |