| // 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 <flasher/device.hpp> |
| #include <flasher/modutil.hpp> |
| #include <flasher/util.hpp> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <format> |
| #include <stdexcept> |
| #include <utility> |
| |
| namespace flasher |
| { |
| |
| void Device::writeAtExact(std::span<const std::byte> data, size_t offset) |
| { |
| opAtExact("Device writeAtExact", &Device::writeAt, *this, data, offset); |
| } |
| |
| Device::Type Device::toType(std::string_view str) |
| { |
| if (str == "nor") |
| { |
| return Type::Nor; |
| } |
| if (str == "simple") |
| { |
| return Type::Simple; |
| } |
| throw std::invalid_argument(std::format("Not a device type: {}", str)); |
| } |
| |
| Device::Device(DeviceInfo&& info) : info(std::move(info)) |
| { |
| if (info.erase_size != 0 && info.size % info.erase_size != 0) |
| { |
| throw std::invalid_argument( |
| std::format("Flash size {} is not divisible by erase size {}", |
| info.size, info.erase_size)); |
| } |
| switch (info.type) |
| { |
| case Type::Nor: |
| if (info.erase_size == 0) |
| { |
| throw std::invalid_argument( |
| "Nor flashes can't have 0 erase size"); |
| } |
| break; |
| case Type::Simple: |
| if (info.erase_size != 0) |
| { |
| throw std::invalid_argument("Simple flashes can't erase"); |
| } |
| break; |
| } |
| } |
| |
| size_t Device::recommendedStride() const |
| { |
| return info.erase_size == 0 ? 8192 : info.erase_size; |
| } |
| |
| size_t Device::getSize() const |
| { |
| return info.size; |
| } |
| |
| size_t Device::eraseAlignStart(size_t offset) const |
| { |
| if (info.erase_size == 0) |
| return offset; |
| return offset / info.erase_size * info.erase_size; |
| } |
| |
| size_t Device::eraseAlignEnd(size_t offset) const |
| { |
| if (info.erase_size == 0) |
| return offset; |
| return eraseAlignStart(offset + info.erase_size - 1); |
| } |
| |
| bool Device::needsErase(std::span<const std::byte> flash_data, |
| std::span<const std::byte> new_data) const |
| { |
| if (new_data.size() > flash_data.size()) |
| { |
| throw std::invalid_argument("New data bigger than MTD data"); |
| } |
| switch (info.type) |
| { |
| case Type::Nor: |
| for (size_t i = 0; i < new_data.size(); ++i) |
| { |
| if ((flash_data[i] & new_data[i]) != new_data[i]) |
| { |
| return true; |
| } |
| } |
| return false; |
| case Type::Simple: |
| return false; |
| } |
| throw std::invalid_argument("Don't know how to erase Device"); |
| } |
| |
| template <typename Iter> |
| static size_t shrinkWrite(Device::Type type, Iter flash_data_begin, |
| Iter new_data_begin, Iter new_data_end) |
| { |
| switch (type) |
| { |
| case Device::Type::Nor: |
| for (auto itn = new_data_begin, itf = flash_data_begin; |
| new_data_end != itn; itn++, itf++) |
| { |
| if ((*itn & *itf) != *itf) |
| { |
| return itn - new_data_begin; |
| } |
| } |
| return new_data_end - new_data_begin; |
| case Device::Type::Simple: |
| for (auto itn = new_data_begin, itf = flash_data_begin; |
| new_data_end != itn; itn++, itf++) |
| { |
| if (*itn != *itf) |
| { |
| return itn - new_data_begin; |
| } |
| } |
| return new_data_end - new_data_begin; |
| } |
| throw std::invalid_argument("Don't know how to shrink write data"); |
| } |
| |
| size_t Device::shrinkWritePre(std::span<const std::byte> flash_data, |
| std::span<const std::byte> new_data) const |
| { |
| return shrinkWrite(info.type, flash_data.begin(), new_data.begin(), |
| new_data.begin() + |
| std::min(flash_data.size(), new_data.size())); |
| } |
| |
| size_t Device::shrinkWritePost(std::span<const std::byte> flash_data, |
| std::span<const std::byte> new_data) const |
| { |
| auto max = std::min(flash_data.size(), new_data.size()); |
| return max - |
| shrinkWrite(info.type, flash_data.rbegin() + flash_data.size() - max, |
| new_data.rbegin() + new_data.size() - max, |
| new_data.rend()); |
| } |
| |
| void Device::mockErase(std::span<std::byte> data) const |
| { |
| switch (info.type) |
| { |
| case Type::Nor: |
| memset(data.data(), 0xff, data.size()); |
| return; |
| case Type::Simple: |
| return; |
| } |
| throw std::invalid_argument("Don't know how to erase Device"); |
| } |
| |
| ModTypeMap<DeviceType>& getDeviceTypes() noexcept |
| { |
| static ModTypeMap<DeviceType> deviceTypes; |
| return deviceTypes; |
| } |
| |
| std::unique_ptr<Device> openDevice(const ModArgs& args) |
| { |
| return openMod<Device>(getDeviceTypes(), args); |
| } |
| |
| void registerDeviceType(std::string_view name, |
| std::unique_ptr<DeviceType>&& type) noexcept |
| { |
| getDeviceTypes().emplace(name, std::move(type)); |
| } |
| |
| void unregisterDeviceType(std::string_view name) noexcept |
| { |
| getDeviceTypes().erase(name); |
| } |
| |
| } // namespace flasher |