From df7fb1667d94dab98daf286220c7e255c97f235d Mon Sep 17 00:00:00 2001 From: David Saada Date: Thu, 26 Apr 2018 19:27:23 +0300 Subject: [PATCH] Implement BufferedBlockDevice Block device allowing smaller read and program sizes for the underlying BD, using a cache. --- .../filesystem/buffered_block_device/main.cpp | 139 +++++++++++ .../filesystem/bd/BufferedBlockDevice.cpp | 228 ++++++++++++++++++ features/filesystem/bd/BufferedBlockDevice.h | 166 +++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 features/TESTS/filesystem/buffered_block_device/main.cpp create mode 100644 features/filesystem/bd/BufferedBlockDevice.cpp create mode 100644 features/filesystem/bd/BufferedBlockDevice.h diff --git a/features/TESTS/filesystem/buffered_block_device/main.cpp b/features/TESTS/filesystem/buffered_block_device/main.cpp new file mode 100644 index 00000000000..fcf385701de --- /dev/null +++ b/features/TESTS/filesystem/buffered_block_device/main.cpp @@ -0,0 +1,139 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * 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 "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" + +#include "BufferedBlockDevice.h" +#include "HeapBlockDevice.h" +#include + +using namespace utest::v1; + +static const bd_size_t heap_erase_size = 512; +static const bd_size_t heap_prog_size = heap_erase_size; +static const bd_size_t heap_read_size = 256; +static const bd_size_t num_blocks = 4; + +void functionality_test() +{ + HeapBlockDevice heap_bd(num_blocks * heap_erase_size, heap_read_size, heap_prog_size, heap_erase_size); + BufferedBlockDevice bd(&heap_bd); + + int err = bd.init(); + TEST_ASSERT_EQUAL(0, err); + + uint8_t *read_buf, *write_buf; + read_buf = new uint8_t[heap_prog_size]; + write_buf = new uint8_t[heap_prog_size]; + + TEST_ASSERT_EQUAL(1, bd.get_read_size()); + TEST_ASSERT_EQUAL(1, bd.get_program_size()); + TEST_ASSERT_EQUAL(heap_erase_size, bd.get_erase_size()); + + for (bd_size_t i = 0; i < num_blocks; i++) { + memset(write_buf, i, heap_prog_size); + err = heap_bd.program(write_buf, i * heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + } + + err = bd.read(read_buf, heap_prog_size + heap_prog_size / 2, 1); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL(1, read_buf[0]); + + err = bd.read(read_buf, 2 * heap_prog_size + heap_prog_size / 2, 4); + TEST_ASSERT_EQUAL(0, err); + memset(write_buf, 2, 4); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, 4); + + memset(write_buf, 1, heap_prog_size); + memset(write_buf + 64, 0x5A, 8); + memset(write_buf + 72, 0xA5, 8); + err = bd.program(write_buf + 64, heap_prog_size + 64, 8); + TEST_ASSERT_EQUAL(0, err); + err = bd.program(write_buf + 72, heap_prog_size + 72, 8); + TEST_ASSERT_EQUAL(0, err); + err = bd.read(read_buf, heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + memset(write_buf, 1, heap_prog_size); + // Underlying BD should not be updated before sync + err = heap_bd.read(read_buf, heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + err = bd.sync(); + TEST_ASSERT_EQUAL(0, err); + memset(write_buf + 64, 0x5A, 8); + memset(write_buf + 72, 0xA5, 8); + // Should be updated now + err = bd.read(read_buf, heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + err = heap_bd.read(read_buf, heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + + memset(write_buf, 0xAA, 16); + memset(write_buf + 16, 0xBB, 16); + err = bd.program(write_buf, 3 * heap_prog_size - 16, 32); + TEST_ASSERT_EQUAL(0, err); + // First block should sync, but second still shouldn't + memset(write_buf, 2, heap_prog_size - 16); + memset(write_buf + heap_prog_size - 16, 0xAA, 16); + err = heap_bd.read(read_buf, 2 * heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + memset(write_buf, 3, heap_prog_size); + err = heap_bd.read(read_buf, 3 * heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + memset(write_buf, 0xBB, 16); + err = bd.read(read_buf, 3 * heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + // Moving to another block should automatically sync + err = bd.read(read_buf, 15, 1); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL(0, read_buf[0]); + err = heap_bd.read(read_buf, 3 * heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + err = bd.read(read_buf, 3 * heap_prog_size, heap_prog_size); + TEST_ASSERT_EQUAL(0, err); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_buf, read_buf, heap_prog_size); + + delete[] read_buf; + delete[] write_buf; +} + +// Test setup +utest::v1::status_t test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(30, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("BufferedBlockDevice functionality test", functionality_test), +}; + +Specification specification(test_setup, cases); + +int main() +{ + return !Harness::run(specification); +} + diff --git a/features/filesystem/bd/BufferedBlockDevice.cpp b/features/filesystem/bd/BufferedBlockDevice.cpp new file mode 100644 index 00000000000..7fba7c7f7e8 --- /dev/null +++ b/features/filesystem/bd/BufferedBlockDevice.cpp @@ -0,0 +1,228 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * 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 "BufferedBlockDevice.h" +#include "mbed_assert.h" +#include +#include + +static inline uint32_t align_down(bd_size_t val, bd_size_t size) +{ + return val / size * size; +} + +BufferedBlockDevice::BufferedBlockDevice(BlockDevice *bd) + : _bd(bd), _bd_program_size(0), _curr_aligned_addr(0), _flushed(true), _cache(0) +{ +} + +BufferedBlockDevice::~BufferedBlockDevice() +{ + deinit(); +} + +int BufferedBlockDevice::init() +{ + int err = _bd->init(); + if (err) { + return err; + } + + _bd_program_size = _bd->get_program_size(); + + if (!_cache) { + _cache = new uint8_t[_bd_program_size]; + } + + _curr_aligned_addr = _bd->size(); + _flushed = true; + + return 0; +} + +int BufferedBlockDevice::deinit() +{ + delete[] _cache; + _cache = 0; + return _bd->deinit(); +} + +int BufferedBlockDevice::flush() +{ + if (!_flushed) { + int ret = _bd->program(_cache, _curr_aligned_addr, _bd_program_size); + if (ret) { + return ret; + } + _flushed = true; + } + return 0; +} + +int BufferedBlockDevice::sync() +{ + int ret = flush(); + if (ret) { + return ret; + } + return _bd->sync(); +} + +int BufferedBlockDevice::read(void *b, bd_addr_t addr, bd_size_t size) +{ + MBED_ASSERT(_cache); + bool moved_unit = false; + + bd_addr_t aligned_addr = align_down(addr, _bd_program_size); + + uint8_t *buf = static_cast (b); + + if (aligned_addr != _curr_aligned_addr) { + // Need to flush if moved to another program unit + flush(); + _curr_aligned_addr = aligned_addr; + moved_unit = true; + } + + while (size) { + _curr_aligned_addr = align_down(addr, _bd_program_size); + if (moved_unit) { + int ret = _bd->read(_cache, _curr_aligned_addr, _bd_program_size); + if (ret) { + return ret; + } + } + bd_addr_t offs_in_buf = addr - _curr_aligned_addr; + bd_size_t chunk = std::min(_bd_program_size - offs_in_buf, size); + memcpy(buf, _cache + offs_in_buf, chunk); + moved_unit = true; + buf += chunk; + addr += chunk; + size -= chunk; + } + + return 0; +} + +int BufferedBlockDevice::program(const void *b, bd_addr_t addr, bd_size_t size) +{ + MBED_ASSERT(_cache); + int ret; + bool moved_unit = false; + + bd_addr_t aligned_addr = align_down(addr, _bd_program_size); + + const uint8_t *buf = static_cast (b); + + // Need to flush if moved to another program unit + if (aligned_addr != _curr_aligned_addr) { + flush(); + _curr_aligned_addr = aligned_addr; + moved_unit = true; + } + + while (size) { + _curr_aligned_addr = align_down(addr, _bd_program_size); + bd_addr_t offs_in_buf = addr - _curr_aligned_addr; + bd_size_t chunk = std::min(_bd_program_size - offs_in_buf, size); + const uint8_t *prog_buf; + if (chunk < _bd_program_size) { + // If moved a unit, and program doesn't cover entire unit, it means we don't have the entire + // program unit cached - need to complete it from underlying BD + if (moved_unit) { + ret = _bd->read(_cache, _curr_aligned_addr, _bd_program_size); + if (ret) { + return ret; + } + } + memcpy(_cache + offs_in_buf, buf, chunk); + prog_buf = _cache; + } else { + // No need to copy data to our cache on each iteration. Just make sure it's updated + // on the last iteration, when size is not greater than program size (can't be smaller, as + // this is covered in the previous condition). + prog_buf = buf; + if (size == _bd_program_size) { + memcpy(_cache, buf, _bd_program_size); + } + } + + // Don't flush on the last iteration, just on all preceding ones. + if (size > chunk) { + ret = _bd->program(prog_buf, _curr_aligned_addr, _bd_program_size); + if (ret) { + return ret; + } + _bd->sync(); + } else { + _flushed = false; + } + + moved_unit = true; + buf += chunk; + addr += chunk; + size -= chunk; + } + + return 0; +} + +int BufferedBlockDevice::erase(bd_addr_t addr, bd_size_t size) +{ + MBED_ASSERT(is_valid_erase(addr, size)); + return _bd->erase(addr, size); +} + +int BufferedBlockDevice::trim(bd_addr_t addr, bd_size_t size) +{ + MBED_ASSERT(is_valid_erase(addr, size)); + + if ((_curr_aligned_addr >= addr) && (_curr_aligned_addr <= addr + size)) { + _flushed = true; + _curr_aligned_addr = _bd->size(); + } + return _bd->trim(addr, size); +} + +bd_size_t BufferedBlockDevice::get_read_size() const +{ + return 1; +} + +bd_size_t BufferedBlockDevice::get_program_size() const +{ + return 1; +} + +bd_size_t BufferedBlockDevice::get_erase_size() const +{ + return _bd->get_erase_size(); +} + +bd_size_t BufferedBlockDevice::get_erase_size(bd_addr_t addr) const +{ + return _bd->get_erase_size(addr); +} + +int BufferedBlockDevice::get_erase_value() const +{ + return _bd->get_erase_value(); +} + +bd_size_t BufferedBlockDevice::size() const +{ + return _bd->size(); +} diff --git a/features/filesystem/bd/BufferedBlockDevice.h b/features/filesystem/bd/BufferedBlockDevice.h new file mode 100644 index 00000000000..85aa1176623 --- /dev/null +++ b/features/filesystem/bd/BufferedBlockDevice.h @@ -0,0 +1,166 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_BUFFERED_BLOCK_DEVICE_H +#define MBED_BUFFERED_BLOCK_DEVICE_H + +#include "BlockDevice.h" + + +/** Block device for allowing minimal read and program sizes (of 1) for the underlying BD, + * using a buffer on the heap. + */ +class BufferedBlockDevice : public BlockDevice { +public: + /** Lifetime of the memory block device + * + * @param bd Block device to back the BufferedBlockDevice + */ + BufferedBlockDevice(BlockDevice *bd); + + /** Lifetime of a block device + */ + virtual ~BufferedBlockDevice(); + + /** Initialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int init(); + + /** Deinitialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int deinit(); + + /** Ensure data on storage is in sync with the driver + * + * @return 0 on success or a negative error code on failure + */ + virtual int sync(); + + /** Read blocks from a block device + * + * @param buffer Buffer to read blocks into + * @param addr Address of block to begin reading from + * @param size Size to read in bytes, must be a multiple of read block size + * @return 0 on success, negative error code on failure + */ + virtual int read(void *buffer, bd_addr_t addr, bd_size_t size); + + /** Program blocks to a block device + * + * The blocks must have been erased prior to being programmed + * + * @param buffer Buffer of data to write to blocks + * @param addr Address of block to begin writing to + * @param size Size to write in bytes, must be a multiple of program block size + * @return 0 on success, negative error code on failure + */ + virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size); + + /** Erase blocks on a block device + * + * The state of an erased block is undefined until it has been programmed, + * unless get_erase_value returns a non-negative byte value + * + * @param addr Address of block to begin erasing + * @param size Size to erase in bytes, must be a multiple of erase block size + * @return 0 on success, negative error code on failure + */ + virtual int erase(bd_addr_t addr, bd_size_t size); + + /** Mark blocks as no longer in use + * + * This function provides a hint to the underlying block device that a region of blocks + * is no longer in use and may be erased without side effects. Erase must still be called + * before programming, but trimming allows flash-translation-layers to schedule erases when + * the device is not busy. + * + * @param addr Address of block to mark as unused + * @param size Size to mark as unused in bytes, must be a multiple of erase block size + * @return 0 on success, negative error code on failure + */ + virtual int trim(bd_addr_t addr, bd_size_t size); + + /** Get the size of a readable block + * + * @return Size of a readable block in bytes + */ + virtual bd_size_t get_read_size() const; + + /** Get the size of a programmable block + * + * @return Size of a programmable block in bytes + * @note Must be a multiple of the read size + */ + virtual bd_size_t get_program_size() const; + + /** Get the size of an erasable block + * + * @return Size of an erasable block in bytes + * @note Must be a multiple of the program size + */ + virtual bd_size_t get_erase_size() const; + + /** Get the size of an erasable block given address + * + * @param addr Address within the erasable block + * @return Size of an erasable block in bytes + * @note Must be a multiple of the program size + */ + virtual bd_size_t get_erase_size(bd_addr_t addr) const; + + /** Get the value of storage when erased + * + * If get_erase_value returns a non-negative byte value, the underlying + * storage is set to that value when erased, and storage containing + * that value can be programmed without another erase. + * + * @return The value of storage when erased, or -1 if you can't + * rely on the value of erased storage + */ + virtual int get_erase_value() const; + + /** Get the total size of the underlying device + * + * @return Size of the underlying device in bytes + */ + virtual bd_size_t size() const; + +protected: + BlockDevice *_bd; + bd_size_t _bd_program_size; + bd_size_t _curr_aligned_addr; + bool _flushed; + uint8_t *_cache; + + /** Flush data in cache + * + * @return 0 on success or a negative error code on failure + */ + int flush(); + +}; + + +#endif