Add file_block_store
To help with FWU debug and test, a file_block_store is added,
enabling a standard file containing a disk image to be accessed
directly by an application. This will be used to verify decoding
and configuration using of a boot image file generated by the
firmware build system.
Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I85a00dd0536810ce10510abf05cdcfa9ad0c7621
diff --git a/components/service/block_storage/block_store/device/file/component.cmake b/components/service/block_storage/block_store/device/file/component.cmake
new file mode 100644
index 0000000..b330197
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+ message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/file_block_store.c"
+ )
diff --git a/components/service/block_storage/block_store/device/file/file_block_store.c b/components/service/block_storage/block_store/device/file/file_block_store.c
new file mode 100644
index 0000000..1d8d542
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/file_block_store.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include "file_block_store.h"
+
+#define ERASED_DATA_VAL (0xff)
+
+ssize_t file_length(FILE *fp)
+{
+ ssize_t size = -1;
+
+ if (!fseek(fp, 0, SEEK_END))
+ size = ftell(fp);
+
+ return size;
+}
+
+static psa_status_t seek(FILE *fp, ssize_t pos)
+{
+ if (fseek(fp, pos, SEEK_SET))
+ return PSA_ERROR_BAD_STATE;
+
+ return PSA_SUCCESS;
+}
+
+static psa_status_t prepare_for_read(
+ const struct file_block_store *this_instance,
+ uint32_t lba, size_t offset,
+ size_t requested_read_len,
+ size_t *adjusted_read_len)
+{
+ assert(this_instance);
+
+ psa_status_t status = PSA_ERROR_BAD_STATE;
+
+ const struct storage_partition *storage_partition =
+ &this_instance->base_block_device.storage_partition;
+
+ ssize_t read_pos = lba * storage_partition->block_size + offset;
+ ssize_t file_len = file_length(this_instance->file_handle);
+
+ if (file_len >= 0) {
+
+ /* File exists so attempt to seek the read position to the requested LBA + offset */
+ if (read_pos <= file_len) {
+
+ size_t bytes_until_end_of_file = (size_t)(file_len - read_pos);
+ size_t bytes_until_end_of_block = storage_partition->block_size - offset;
+
+ size_t read_limit = (bytes_until_end_of_file < bytes_until_end_of_block) ?
+ bytes_until_end_of_file :
+ bytes_until_end_of_block;
+
+ *adjusted_read_len = (requested_read_len < read_limit) ?
+ requested_read_len :
+ read_limit;
+
+ status = seek(this_instance->file_handle, read_pos);
+ } else {
+
+ /* Requested block is beyond the end of the file */
+ status = PSA_ERROR_INVALID_ARGUMENT;
+ }
+ }
+
+ return status;
+}
+
+static psa_status_t write_erased(
+ const struct file_block_store *this_instance,
+ size_t pos,
+ size_t len)
+{
+ psa_status_t status = PSA_ERROR_BAD_STATE;
+ size_t remaining_len = len;
+
+ assert(this_instance);
+
+ status = seek(this_instance->file_handle, pos);
+
+ while ((status == PSA_SUCCESS) && (remaining_len > 0)) {
+
+ size_t erase_len = (remaining_len < sizeof(this_instance->erase_buf)) ?
+ remaining_len :
+ sizeof(this_instance->erase_buf);
+
+ size_t write_len = fwrite(this_instance->erase_buf, 1,
+ erase_len, this_instance->file_handle);
+
+ if (write_len != erase_len)
+ status = PSA_ERROR_BAD_STATE;
+
+ remaining_len -= erase_len;
+ }
+
+ return status;
+}
+
+static psa_status_t prepare_for_write(
+ const struct file_block_store *this_instance,
+ uint32_t lba, size_t offset,
+ size_t requested_write_len,
+ size_t *adjusted_write_len)
+{
+ assert(this_instance);
+
+ psa_status_t status = PSA_ERROR_BAD_STATE;
+
+ const struct storage_partition *storage_partition =
+ &this_instance->base_block_device.storage_partition;
+
+ size_t bytes_until_end_of_block = storage_partition->block_size - offset;
+ *adjusted_write_len = (requested_write_len < bytes_until_end_of_block) ?
+ requested_write_len :
+ bytes_until_end_of_block;
+
+ ssize_t write_pos = lba * storage_partition->block_size + offset;
+ ssize_t file_len = file_length(this_instance->file_handle);
+
+ if (file_len >= 0) {
+
+ if (write_pos > file_len) {
+
+ /* Writing beyond the current end-of-file so extend the file */
+ status = write_erased(this_instance, file_len, write_pos - file_len);
+ } else {
+
+ /* Writing over existing data */
+ status = seek(this_instance->file_handle, write_pos);
+ }
+ }
+
+ return status;
+}
+
+static psa_status_t file_block_store_get_partition_info(void *context,
+ const struct uuid_octets *partition_guid,
+ struct storage_partition_info *info)
+{
+ struct file_block_store *this_instance = (struct file_block_store *)context;
+
+ return block_device_get_partition_info(
+ &this_instance->base_block_device, partition_guid, info);
+}
+
+static psa_status_t file_block_store_open(void *context,
+ uint32_t client_id,
+ const struct uuid_octets *partition_guid,
+ storage_partition_handle_t *handle)
+{
+ struct file_block_store *this_instance = (struct file_block_store *)context;
+ psa_status_t status = PSA_ERROR_BAD_STATE;
+
+ if (this_instance->file_handle > 0) {
+
+ status = block_device_open(
+ &this_instance->base_block_device,
+ client_id, partition_guid, handle);
+ }
+
+ return status;
+}
+
+static psa_status_t file_block_store_close(void *context,
+ uint32_t client_id,
+ storage_partition_handle_t handle)
+{
+ struct file_block_store *this_instance = (struct file_block_store *)context;
+
+ return block_device_close(
+ &this_instance->base_block_device, client_id, handle);
+}
+
+static psa_status_t file_block_store_read(void *context,
+ uint32_t client_id,
+ storage_partition_handle_t handle,
+ uint32_t lba,
+ size_t offset,
+ size_t buffer_size,
+ uint8_t *buffer,
+ size_t *data_len)
+{
+ const struct file_block_store *this_instance =
+ (struct file_block_store *)context;
+
+ psa_status_t status = block_device_check_access_permitted(
+ &this_instance->base_block_device, client_id, handle);
+
+ *data_len = 0;
+
+ if (status == PSA_SUCCESS) {
+
+ const struct storage_partition *storage_partition =
+ &this_instance->base_block_device.storage_partition;
+
+ if (storage_partition_is_lba_legal(storage_partition, lba) &&
+ (offset < storage_partition->block_size)) {
+
+ size_t read_len = 0;
+
+ status = prepare_for_read(
+ this_instance,
+ lba, offset,
+ buffer_size,
+ &read_len);
+
+ if (status == PSA_SUCCESS) {
+
+ *data_len = fread(buffer, 1,
+ read_len, this_instance->file_handle);
+
+ if (*data_len != read_len)
+ status = PSA_ERROR_BAD_STATE;
+ }
+ } else
+ /* Block or offset outside of configured limits */
+ status = PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ return status;
+}
+
+static psa_status_t file_block_store_write(void *context,
+ uint32_t client_id,
+ storage_partition_handle_t handle,
+ uint32_t lba,
+ size_t offset,
+ const uint8_t *data,
+ size_t data_len,
+ size_t *num_written)
+{
+ struct file_block_store *this_instance = (struct file_block_store *)context;
+
+ psa_status_t status = block_device_check_access_permitted(
+ &this_instance->base_block_device, client_id, handle);
+
+ *num_written = 0;
+
+ if (status == PSA_SUCCESS) {
+
+ const struct storage_partition *storage_partition =
+ &this_instance->base_block_device.storage_partition;
+
+ if (storage_partition_is_lba_legal(storage_partition, lba) &&
+ (offset < storage_partition->block_size)) {
+
+ size_t adjusted_len = 0;
+
+ status = prepare_for_write(
+ this_instance,
+ lba, offset,
+ data_len,
+ &adjusted_len);
+
+ if (status == PSA_SUCCESS) {
+
+ *num_written = fwrite(data, 1,
+ adjusted_len, this_instance->file_handle);
+
+ if (*num_written != adjusted_len)
+ status = PSA_ERROR_BAD_STATE;
+ }
+ } else
+ /* Block or offset outside of configured limits */
+ status = PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ return status;
+}
+
+static psa_status_t file_block_store_erase(void *context,
+ uint32_t client_id,
+ storage_partition_handle_t handle,
+ uint32_t begin_lba,
+ size_t num_blocks)
+{
+ struct file_block_store *this_instance = (struct file_block_store *)context;
+ const struct storage_partition *storage_partition =
+ &this_instance->base_block_device.storage_partition;
+
+ psa_status_t status = block_device_check_access_permitted(
+ &this_instance->base_block_device, client_id, handle);
+
+ /* Sanitize the range of LBAs to erase */
+ if ((status == PSA_SUCCESS) &&
+ !storage_partition_is_lba_legal(storage_partition, begin_lba))
+ status = PSA_ERROR_INVALID_ARGUMENT;
+
+ if (status == PSA_SUCCESS) {
+
+ size_t blocks_remaining = storage_partition->num_blocks - begin_lba;
+ size_t blocks_to_erase = (num_blocks < blocks_remaining) ?
+ num_blocks : blocks_remaining;
+
+ ssize_t file_len = file_length(this_instance->file_handle);
+
+ if (file_len >= 0) {
+
+ /* File exists. If erased block falls within the limits of the file,
+ * explicitly set blocks to the erased state. If erased block is
+ * beyond EOF, there's nothing to do.
+ */
+ ssize_t block_pos = begin_lba * storage_partition->block_size;
+
+ if (block_pos < file_len)
+ status = write_erased(this_instance,
+ block_pos,
+ blocks_to_erase * storage_partition->block_size);
+
+ } else {
+ status = PSA_ERROR_BAD_STATE;
+ }
+ }
+
+ return status;
+}
+
+struct block_store *file_block_store_init(
+ struct file_block_store *this_instance,
+ const char *filename,
+ size_t block_size)
+{
+ struct block_store *block_store = NULL;
+ size_t num_blocks = 0;
+
+ assert(this_instance);
+
+ /* Define concrete block store interface */
+ static const struct block_store_interface interface = {
+ file_block_store_get_partition_info,
+ file_block_store_open,
+ file_block_store_close,
+ file_block_store_read,
+ file_block_store_write,
+ file_block_store_erase
+ };
+
+ /* Initialize base block_store */
+ this_instance->base_block_device.base_block_store.context = this_instance;
+ this_instance->base_block_device.base_block_store.interface = &interface;
+
+ /* Initialize buffer used for erase operations */
+ memset(this_instance->erase_buf, ERASED_DATA_VAL, sizeof(this_instance->erase_buf));
+
+ /* Open the file */
+ this_instance->file_handle = fopen(filename, "r+b");
+
+ if (this_instance->file_handle) {
+
+ /* File already exists so initialise the view of the number of blocks */
+ ssize_t file_len = file_length(this_instance->file_handle);
+
+ if (file_len >= 0)
+ num_blocks = (size_t)file_len / block_size;
+
+ } else {
+
+ /* File doesn't exist so create an empty one */
+ this_instance->file_handle = fopen(filename, "w+b");
+ }
+
+ if (this_instance->file_handle)
+ block_store = block_device_init(
+ &this_instance->base_block_device, NULL, num_blocks, block_size);
+
+ return block_store;
+}
+
+void file_block_store_deinit(
+ struct file_block_store *this_instance)
+{
+ assert(this_instance);
+
+ if (this_instance->file_handle) {
+
+ fclose(this_instance->file_handle);
+ this_instance->file_handle = NULL;
+ }
+
+ block_device_deinit(&this_instance->base_block_device);
+}
+
+psa_status_t file_block_store_configure(
+ struct file_block_store *this_instance,
+ const struct uuid_octets *disk_guid,
+ size_t num_blocks,
+ size_t block_size)
+{
+ assert(this_instance);
+
+ block_device_configure(&this_instance->base_block_device,
+ disk_guid, num_blocks, block_size);
+
+ return PSA_SUCCESS;
+}
diff --git a/components/service/block_storage/block_store/device/file/file_block_store.h b/components/service/block_storage/block_store/device/file/file_block_store.h
new file mode 100644
index 0000000..cb66b9b
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/file_block_store.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FILE_BLOCK_STORE_H
+#define FILE_BLOCK_STORE_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include "service/block_storage/block_store/device/block_device.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief file_block_store structure
+ *
+ * A file_block_store is a block_device that uses a file for storage.
+ * The file represents a real storage device organized as a series of
+ * consecutive blocks. The file_block_store can be used for accessing disk
+ * image files in a Posix environment.
+ */
+struct file_block_store
+{
+ struct block_device base_block_device;
+ FILE *file_handle;
+ uint8_t erase_buf[256];
+};
+
+/**
+ * \brief Initialize a file_block_store
+ *
+ * \param[in] file_block_store The subject file_block_store
+ * \param[in] filename The host filename used for storage
+ * \param[in] block_size The storage block size
+ *
+ * \return Pointer to block_store or NULL on failure
+ */
+struct block_store *file_block_store_init(
+ struct file_block_store *file_block_store,
+ const char *filename,
+ size_t block_size);
+
+/**
+ * \brief De-initialize a file_block_store
+ *
+ * \param[in] file_block_store The subject file_block_store
+ */
+void file_block_store_deinit(
+ struct file_block_store *file_block_store);
+
+/**
+ * \brief Configure the file_block_store
+ *
+ * \param[in] file_block_store The subject file_block_store
+ * \param[in] disk_guid The disk GUID (nil uuid for any)
+ * \param[in] num_blocks The number of contiguous blocks
+ * \param[in] block_size Block size in bytes
+ *
+ * \return PSA_SUCCESS if successful
+ */
+psa_status_t file_block_store_configure(
+ struct file_block_store *file_block_store,
+ const struct uuid_octets *disk_guid,
+ size_t num_blocks,
+ size_t block_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FILE_BLOCK_STORE_H */
diff --git a/components/service/block_storage/block_store/device/file/test/component.cmake b/components/service/block_storage/block_store/device/file/test/component.cmake
new file mode 100644
index 0000000..a587b5a
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/test/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+ message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/file_block_store_tests.cpp"
+ )
diff --git a/components/service/block_storage/block_store/device/file/test/file_block_store_tests.cpp b/components/service/block_storage/block_store/device/file/test/file_block_store_tests.cpp
new file mode 100644
index 0000000..34ea57c
--- /dev/null
+++ b/components/service/block_storage/block_store/device/file/test/file_block_store_tests.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstring>
+#include <cstdio>
+#include <string>
+#include <stdint.h>
+#include "common/uuid/uuid.h"
+#include "service/block_storage/block_store/device/file/file_block_store.h"
+#include "CppUTest/TestHarness.h"
+
+TEST_GROUP(FileBlockStoreTests)
+{
+ void setup()
+ {
+ m_filename = std::string("file_block_store.tmp");
+ memset(m_disk_guid.octets, 0, sizeof(m_disk_guid.octets));
+
+ struct block_store *block_store = file_block_store_init(
+ &m_file_block_store,
+ m_filename.c_str(),
+ BLOCK_SIZE);
+
+ CHECK_TRUE(block_store);
+
+ psa_status_t status = file_block_store_configure(
+ &m_file_block_store,
+ &m_disk_guid,
+ NUM_BLOCKS, BLOCK_SIZE);
+
+ LONGS_EQUAL(PSA_SUCCESS, status);
+
+ status = block_store_open(
+ &m_file_block_store.base_block_device.base_block_store,
+ CLIENT_ID,
+ &m_disk_guid,
+ &m_partition_handle);
+
+ LONGS_EQUAL(PSA_SUCCESS, status);
+ }
+
+ void teardown()
+ {
+ block_store_close(
+ &m_file_block_store.base_block_device.base_block_store,
+ CLIENT_ID,
+ m_partition_handle);
+
+ file_block_store_deinit(&m_file_block_store);
+ remove(m_filename.c_str());
+ }
+
+ void set_block(
+ size_t lba, size_t offset,
+ size_t len, uint8_t val,
+ size_t *num_written)
+ {
+ struct block_store *bs = &m_file_block_store.base_block_device.base_block_store;
+ uint8_t write_buf[len];
+
+ memset(write_buf, val, len);
+ *num_written = 0;
+
+ psa_status_t status = block_store_write(
+ bs, CLIENT_ID, m_partition_handle,
+ lba, offset,
+ write_buf, len, num_written);
+
+ LONGS_EQUAL(PSA_SUCCESS, status);
+ }
+
+ void check_block(
+ size_t lba, size_t offset,
+ size_t len, uint8_t expected_val)
+ {
+ struct block_store *bs = &m_file_block_store.base_block_device.base_block_store;
+ uint8_t read_buf[len];
+ size_t num_read = 0;
+
+ psa_status_t status = block_store_read(
+ bs, CLIENT_ID, m_partition_handle,
+ lba, offset,
+ len, read_buf,
+ &num_read);
+
+ LONGS_EQUAL(PSA_SUCCESS, status);
+ UNSIGNED_LONGS_EQUAL(len, num_read);
+
+ for (size_t i = 0; i < len; i++)
+ BYTES_EQUAL(expected_val, read_buf[i]);
+ }
+
+ void erase_blocks(
+ uint32_t begin_lba,
+ size_t num_blocks)
+ {
+ struct block_store *bs = &m_file_block_store.base_block_device.base_block_store;
+
+ psa_status_t status = block_store_erase(
+ bs, CLIENT_ID, m_partition_handle,
+ begin_lba, num_blocks);
+
+ LONGS_EQUAL(PSA_SUCCESS, status);
+ }
+
+ static const size_t NUM_BLOCKS = 100;
+ static const size_t BLOCK_SIZE = 512;
+ static const uint32_t CLIENT_ID = 27;
+
+ std::string m_filename;
+ struct uuid_octets m_disk_guid;
+ struct file_block_store m_file_block_store;
+ storage_partition_handle_t m_partition_handle;
+};
+
+/*
+ * Check a sequence of whole block writes and reads.
+ */
+TEST(FileBlockStoreTests, wholeBlockRw)
+{
+ size_t num_written = 0;
+
+ set_block(7, 0, BLOCK_SIZE, 'a', &num_written);
+ UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+ set_block(6, 0, BLOCK_SIZE, 'b', &num_written);
+ UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+ set_block(1, 0, BLOCK_SIZE, 'c', &num_written);
+ UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+ set_block(9, 0, BLOCK_SIZE, 'd', &num_written);
+ UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+ /* Check written blocks are as expected */
+ check_block(7, 0, BLOCK_SIZE, 'a');
+ check_block(6, 0, BLOCK_SIZE, 'b');
+ check_block(1, 0, BLOCK_SIZE, 'c');
+ check_block(9, 0, BLOCK_SIZE, 'd');
+
+ /* Erase all the written blocks */
+ erase_blocks(9, 1);
+ erase_blocks(1, 1);
+ erase_blocks(7, 1);
+ erase_blocks(6, 1);
+}
+
+/*
+ * Check state when initialised with existing disk image file
+ */
+TEST(FileBlockStoreTests, initWithExistingDiskImage)
+{
+ /* Write to last block to expand disk image file to entire size */
+ size_t num_written = 0;
+
+ set_block(NUM_BLOCKS - 1, 0, BLOCK_SIZE, 'a', &num_written);
+ UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, num_written);
+
+ /* Close the block_store opened during setup. This will have created a disk image file */
+ block_store_close(
+ &m_file_block_store.base_block_device.base_block_store,
+ CLIENT_ID,
+ m_partition_handle);
+
+ file_block_store_deinit(&m_file_block_store);
+
+ /* Re-initialise and open */
+ struct block_store *block_store = file_block_store_init(
+ &m_file_block_store,
+ m_filename.c_str(),
+ BLOCK_SIZE);
+
+ CHECK_TRUE(block_store);
+
+ psa_status_t status = block_store_open(
+ block_store,
+ CLIENT_ID,
+ &m_disk_guid,
+ &m_partition_handle);
+
+ LONGS_EQUAL(PSA_SUCCESS, status);
+
+ /* Expect disk partition size to reflect existing disk file */
+ struct storage_partition_info disk_info;
+
+ status = block_store_get_partition_info(block_store, &m_disk_guid, &disk_info);
+ LONGS_EQUAL(PSA_SUCCESS, status);
+
+ UNSIGNED_LONGS_EQUAL(NUM_BLOCKS, disk_info.num_blocks);
+ UNSIGNED_LONGS_EQUAL(BLOCK_SIZE, disk_info.block_size);
+}
diff --git a/components/service/block_storage/factory/file/block_store_factory.c b/components/service/block_storage/factory/file/block_store_factory.c
new file mode 100644
index 0000000..d879332
--- /dev/null
+++ b/components/service/block_storage/factory/file/block_store_factory.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <trace.h>
+#include "block_store_factory.h"
+#include "service/block_storage/block_store/device/file/file_block_store.h"
+#include "service/block_storage/block_store/partitioned/partitioned_block_store.h"
+#include "service/block_storage/config/gpt/gpt_partition_configurator.h"
+#include "media/volume/index/volume_index.h"
+#include "media/volume/block_volume/block_volume.h"
+
+#ifndef FILE_BLOCK_SIZE
+/* Default to most common block size for UEFI volumes */
+#define FILE_BLOCK_SIZE (512)
+#endif
+
+static char disk_img_filename[256];
+
+struct block_store_assembly
+{
+ struct file_block_store file_block_store;
+ struct partitioned_block_store partitioned_block_store;
+ struct block_volume volume;
+};
+
+static void tear_down_assembly(struct block_store_assembly *assembly)
+{
+ volume_index_clear();
+
+ if (assembly) {
+ partitioned_block_store_deinit(&assembly->partitioned_block_store);
+ file_block_store_deinit(&assembly->file_block_store);
+ block_volume_deinit(&assembly->volume);
+
+ free(assembly);
+ }
+}
+
+struct block_store *file_block_store_factory_create(void)
+{
+ struct block_store *product = NULL;
+ struct block_store_assembly *assembly =
+ (struct block_store_assembly*)malloc(sizeof(struct block_store_assembly));
+
+ if (assembly) {
+
+ struct uuid_octets disk_guid;
+ memset(&disk_guid, 0, sizeof(disk_guid));
+
+ volume_index_init();
+
+ /* Ensure disk image filename is set */
+ if (disk_img_filename[0] == '\0')
+ file_block_store_factory_set_filename("secure-flash.img");
+
+ /* Initialise a file_block_store to provide underlying storage */
+ struct block_store *secure_flash = file_block_store_init(
+ &assembly->file_block_store,
+ disk_img_filename,
+ FILE_BLOCK_SIZE);
+
+ if (secure_flash) {
+
+ /* Secure flash successfully initialized so create a block_volume
+ * associated with secure flash block store to enable it to be
+ * accessed as a storage volume.
+ */
+ struct volume *volume = NULL;
+
+ int result = block_volume_init(&assembly->volume,
+ secure_flash, &disk_guid,
+ &volume);
+
+ if (result == 0) {
+
+ volume_index_add(VOLUME_ID_SECURE_FLASH, volume);
+
+ /* Stack a partitioned_block_store over the back store */
+ product = partitioned_block_store_init(
+ &assembly->partitioned_block_store,
+ 0,
+ &disk_guid,
+ secure_flash,
+ NULL);
+
+ if (product) {
+
+ /* Successfully created the block store stack so configure the
+ * partitions if there are any described in the GPT. No GPT
+ * is a valid configuration option so it's deliberately not
+ * treated as an error. To help a platform integrator who
+ * intended to use a GPT, a message is output if partitions
+ * weren't configured.
+ * */
+ bool is_configured = gpt_partition_configure(
+ &assembly->partitioned_block_store,
+ VOLUME_ID_SECURE_FLASH);
+
+ if (!is_configured)
+ IMSG("No GPT detected in %s", disk_img_filename);
+
+ } else
+ EMSG("Failed to init partitioned_block_store");
+ }
+ } else
+ EMSG("Failed to init file_block_store: %s", disk_img_filename);
+
+ if (!product) {
+
+ /* Something went wrong! */
+ tear_down_assembly(assembly);
+ }
+ } else
+ EMSG("Failed to alloc file_block_store assembly");
+
+ return product;
+}
+
+void file_block_store_factory_destroy(struct block_store *block_store)
+{
+ if (block_store) {
+
+ size_t offset_into_assembly =
+ offsetof(struct block_store_assembly, partitioned_block_store) +
+ offsetof(struct partitioned_block_store, base_block_store);
+
+ struct block_store_assembly *assembly = (struct block_store_assembly*)
+ ((uint8_t*)block_store - offset_into_assembly);
+
+ tear_down_assembly(assembly);
+ }
+}
+
+void file_block_store_factory_set_filename(const char *filename)
+{
+ strncpy(disk_img_filename, filename, sizeof(disk_img_filename));
+
+ /* Ensure that filename is null terminated */
+ disk_img_filename[sizeof(disk_img_filename) - 1] = '\0';
+}
\ No newline at end of file
diff --git a/components/service/block_storage/factory/file/block_store_factory.h b/components/service/block_storage/factory/file/block_store_factory.h
new file mode 100644
index 0000000..647a4df
--- /dev/null
+++ b/components/service/block_storage/factory/file/block_store_factory.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FILE_BLOCK_STORE_FACTORY_H
+#define FILE_BLOCK_STORE_FACTORY_H
+
+#include "service/block_storage/block_store/block_store.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A block store factory that constructs a partitioned block store backed
+ * by a file block device that provides access to a disk image file.
+ * If the disk image contains a GPT, it will be used to configure a set
+ * of storage partitions.
+ *
+ * This factory is intended for deployments where Posix filesystem
+ * access is possible with access to a a disk image file.
+ */
+
+/**
+ * \brief Factory method to create a block_store
+ *
+ * \return A pointer to the constructed block_store (NULL on failure)
+ */
+struct block_store *file_block_store_factory_create(void);
+
+/**
+ * \brief Destroys a block_store created with block_store_factory_create
+ *
+ * \param[in] block_store The block store to destroy
+ */
+void file_block_store_factory_destroy(struct block_store *block_store);
+
+/**
+ * \brief Set the filename for the disk image file
+ *
+ * \param[in] filename Disk image filename
+ */
+void file_block_store_factory_set_filename(const char *filename);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FILE_BLOCK_STORE_FACTORY_H */
diff --git a/components/service/block_storage/factory/file/component.cmake b/components/service/block_storage/factory/file/component.cmake
new file mode 100644
index 0000000..644f039
--- /dev/null
+++ b/components/service/block_storage/factory/file/component.cmake
@@ -0,0 +1,20 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+ message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/block_store_factory.c"
+ )
+
+# If none already defined, make this the default factory for the deployment
+if (NOT DEFINED TS_BLOCK_STORE_FACTORY)
+ set(TS_BLOCK_STORE_FACTORY "file_block_store_factory")
+ target_compile_definitions(${TGT} PRIVATE
+ CONCRETE_BLOCK_STORE_FACTORY=${TS_BLOCK_STORE_FACTORY})
+endif()
\ No newline at end of file
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index f2087d9..474ecde 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -96,6 +96,8 @@
"components/service/block_storage/block_store/device/ram"
"components/service/block_storage/block_store/device/ram/test"
"components/service/block_storage/block_store/device/null"
+ "components/service/block_storage/block_store/device/file"
+ "components/service/block_storage/block_store/device/file/test"
"components/service/block_storage/block_store/client"
"components/service/block_storage/block_store/partitioned"
"components/service/block_storage/block_store/partitioned/test"