Block Storage Service

Overview

The Block Storage service can be used to share a block-oriented storage device such as a QSPI flash between a set of independent secure world clients. A block storage service provider presents a block level interface for accessing an underlying block storage device. To allow multiple higher layer filesystems to share the same storage device, logical block addresses are partitioned, based on configuration data provided by a system integrator. The partition configuration data may be read from a GUID Partition Table (GPT) or from the block storage SP manifest. The configuration data restricts access to a storage partition to a defined owner. Each owner is allocated a maximum number of blocks and is given exclusive access to its own blocks, based on the client ID of the calling client.

The following diagram illustrates a firmware integration that uses a single block storage service provider to control access to a dedicated flash device. In this example StMM, OP-TEE, Update Agent and the Protected Storage SP act as clients of the service. Each client independently manages its own filesystem and is presented with its own logical partition, starting with a logical block address (LBA) of zero.

../_images/block-storage-example-usage.svg

Project Directories

Components within the Trusted Services project related to the Block Storage service are located under the following directories:

Directory

Contains

components/service/block_storage

Service specific code components.

deployments/block-storage

Build files and deployment specific code for building alternative configurations of the block storage service provider.

protocols/service/block_storage

Service access protocol definitions.

Service Interface

The Block Storage service supports a conventional set of block-level operations that can be adapted to different driver models by clients. The following table summarizes supported operations:

Operation

Description

Open

Open a session - take the required UniquePartitionGUID as a parameter. Returns a handle to be used as a qualifier for other requests made by a client.

Close

Close a previously opened session.

GetInfo

Returns information about the partition associated with an open session. Includes the block size and the total number of blocks assigned to the partition.

Read

Read data from the specified block.

Write

Write data to the specified block.

Erase

Erase a set of one or more blocks.

Protocol definitions live under: protocols/service/block_storage.

The service interface is realized by the block storage service provider. It delegates storage operations to a backend block_store. The block_store defines a common interface for components that realize block storage operations. Where an underlying storage technology does not support an explicit erase operation (e.g. RPMB), the corresponding concrete block_store should return success for a call to erase but perform no actual operation (if the partition is writable and the LBA falls within the limits of the partition).

Service Provider Configuration

A platform integrator must provide a set of configuration data to configure how the block storage service provider presents block storage to clients. Configuration data relates to the following:

  • Storage partition configuration - determines how storage is divided into separate partitions

  • Block device configuration - determines how the backed storage device is configured

Storage Partition Configuration

The block storage service allows a block storage device to be presented as a single storage partition or as a set of smaller storage partitions. The way that storage is presented is determined by configuration data prepared by a platform integrator. Each storage partition presented by a block storage service provider starts at LBA zero. The number of partitions and their size are defined by configuration data. Configuration data assigns partitions to owners to enable access to be controlled. If no partition configuration exists for a requesting client or if an attempt is made to access a block outside of the configured LBA range, access is denied. The set of storage partitions used for secure block storage will not necessarily span the entire underlying storage device. A platform integrator is free to limit the area used for secure block storage to allow the storage device to be used for other purposes e.g. as a boot source, read by the boot loader during early boot stages.

Two partition configuration methods will be supported; one where partition configuration data is read from an SP manifest and the other where configuration is defined by a GUID Partition Table. Both methods may be used in combination if necessary. Initial implementations will use the SP manifest configuration method.

Each partition configuration entry includes an attributes bitmap that conforms to the UEFI GPT Partition Entry attributes definition (see section 5.3 of the UEFI specification). Bits 48-63 are reserved for GUID specific use. For partitions labelled with the Secure Block Store GUID, bits will be defined for:

  • Read-only - write and erase operations are forbidden.

A GPT partition entry includes the PartitionName property which normally holds a human readable name for the partition. For secure block store partitions, the PartitionName property will hold the canonical UUID string identifying the owner. An empty string is interpreted as ‘no specific owner’ and any client will be granted access.

Configuration via SP Manifest

For an SP deployment, the partition configuration may be read from a device tree blob (DTB), passed as an initialization parameter. Per-partition configuration data comprises the following:

Config Value

Description

UniquePartitionGUID

GUID that is unique for a partition entry.

StartingLBA

The storage block address corresponding to LBA zero.

EndingLBA

The last storage block in the contiguous range of blocks.

Attributes

See UEFI specification

Owner

Holds canonical UUID string for owner.

The partition configuration is included as a sub-node of the block-dev node that includes configuration values related to the block device. The following is an example of how a block device and related partitions are defined within a DT based SP manifest:

block-dev {
  compatible = "tforg,ts-block-dev"
  disk-guid = "af9f72de-d71f-4492-b44b-a4b4d96000bf"

  partitions {
      compatible = "tforg,ts-block-partitions"

      fwu-meta {
          guid = "a6f99e90-7a75-4384-847a-29c9a86c6279"
          start-lba = <0x00000000>
          end-lba = <0x00000003>
          attr = <0x00000000>
          owner = "afb995cd-9354-4333-9ea2-bd62ccaedb22"
      };

      fip {
          guid = "1eccc9bc-9a5f-43d0-bcd3-466fd21c9a92"
          start-lba = <0x00000004>
          end-lba = <0x00040003>
          attr = <0x00000000>
          owner = "afb995cd-9354-4333-9ea2-bd62ccaedb22"
      };

      uefi-var {
          guid = "1022a92b-4b4a-47b4-94cb-35faf5a45dc2"
          start-lba = <0x00040004>
          end-lba = <0x00080003>
          attr = <0x00000000>
          owner = "ed32d533-99e6-4209-9cc0-2d72cdd998a7"
      };
  };
};

Configuration via GUID Partition Table (GPT)

The UEFI specification defines a standard layout for physical storage devices where storage partitions are described by partition entries within the GUID Partition Table. During initialization, the Block Storage SP will read the GPT and iterate over partition entries, identifying those with the secure block store partition type GUID. Each entry contains the following:

Offset

Length

contents

0

16 bytes

PartitionTypeGUID - Secure Block Store GUID

16

16 bytes

UniquePartitionGUID

32

8 bytes

Starting LBA

40

8 bytes

Ending LBA

48

8 bytes

Attributes (e.g. read-only)

56

72 bytes

PartitionName - Holds canonical UUID string for owner.

Design Description

The block storage service provider conforms to the same model as other service providers within the TS project. Service requests from clients are received by a service provider that is responsible for parameter deserialization/serialization and service level access control. Block storage operations are delegated to a backend block_store that provides block-level storage in some way. There is much flexibility to realize the backend block-level storage in different ways, allowing platform integrators to use alternative block_store realizations to provide storage solutions that meet specific product requirements.

The following class diagram illustrates the block storage service provider model:

'-------------------------------------------------------------------------------
' Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
'
' SPDX-License-Identifier: BSD-3-Clause
'
'-------------------------------------------------------------------------------

@startuml

class rpc_interface {
  +rpc_status_t {abstract}receive(call_req)
}

class service_provider {
  +void init(handlers, num_handlers)
  +void extend(service_provider)
  -rpc_status_t receive(call_req)
}

class block_storage_provider {
  +void init(block_store)
  +void regsiter_serializer(encoding, serializer)
  -rpc_status_t open_handler(call_req)
  -rpc_status_t close_handler(call_req)
  -rpc_status_t get_partition_info_handler(call_req)
  -rpc_status_t read_handler(call_req)
  -rpc_status_t write_handler(call_req)
  -rpc_status_t erase_handler(call_req)
}

class block_store {
  +int {abstract}open(client_id, uuid, *handle)
  +int {abstract}close(handle)
  +int {abstract}get_partition_info(uuid, *partition_info)
  +int {abstract}read(handle, lba, size, *buffer, *len)
  +int {abstract}write(handle, lba, *buffer, len)
  +int {abstract}erase(handle, *lba, num_blocks)
}

service_provider -up--|> rpc_interface
block_storage_provider -up--|> service_provider
block_storage_provider --> block_store
block_storage_provider --> block_storage_serializer

@enduml

Block Store

The block_store component defines a virtual interface for block IO operations. Alternative concrete block_store implementations are supported. Some block_store components are stackable over other block_store components to add features such as store partitioning or block authentication. Separation of functionality into stackable block_store components gives platform integrators the flexibility to create alternative storage solutions with different security/cost tradeoffs. The base block_store interface is defined in:

components/service/block_storage/block_store/block_store.h

Components that implement the block_store interface are located in subdirectories beneath components/service/block_storage/block_store. A block_device is class of block_store that actually provides block-level storage. In a stack of block_store components, a block_device will always live at the bottom. The following layer diagram illustrates a typical block storage deployment where storage is provided by a stack of block_store components:

../_images/block-storage-layers.svg

Some block devices supported in the TS project (located under: components/service/block_storage/block_store/block_device) are:

  • ram_block_store - stores blocks in RAM. Intended for test purposes.

  • null_block_store - a store with no real storage. Always accepts legal writes and returns zeros for reads.

  • fvb_block_store - an adapter that uses a UEFI firmware volume block driver to access storage. Can be used with drivers from the EDK2 project.

Other supported block_store components:

  • partitioned_block_store - a stackable block_store that presents an underlying block_store as a set of configurable storage partitions.

  • block_storage_client - communicates with a remote block storage service provider to provide storage.


Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.

SPDX-License-Identifier: BSD-3-Clause