StorageServer

Overview

The StorageServer is a component that enables multiple client components to access a storage device.

Concepts

TRENTOS has a very generic concept of “storage”. Access to storage is defined by function pointers which can perform read/write/etc. operations on said storage. It is up to the provider of a storage interface to provide its implementation, which can be generic (e.g. a RamDisk) or platform-specific (e.g. an SPI flash driver).

Implementation

In order to allow multiple components to access a single “piece of storage”, storage is divided into different segments which can then be accessed by the clients of the StorageServer.

For each client interface an offset/size pair is configured, which limits the access for this client to the storage address range offset to offset+size-1. On the interface from the clients to the StorageServer the visible address range is 0 to size-1. The Storage server performs the address translation internally.

Info: It is possible to set the offset values such that client storage spaces overlap. However, this is not advisable if the StorageServer’s clients do not implement a mechanism to prevent race conditions when simultaneously using the same underlying storage.

The StorageServer implements the if_OS_Storage interface for its clients and uses the same interface for its own access to the underlying storage.

The StorageServer supports up to 8 parallel clients.

Usage

This is how the component can be instantiated in the system.

Declaration of the Component in CMake

The StorageServer can be declared via a simple macro in the CMakeLists.txt file:

StorageServer_DeclareCAmkESComponent(
    <NameOfComponent>
)

Instantiation and Configuration in CAmkES

In order to wire the StorageServer to a set of clients, it has to be declared, instantiated, connected and configured in the main CAmkES composition of the system.

Defining the Component

The component is simply declared by using its respective macro:

#include "StorageServer/camkes/StorageServer.camkes"
StorageServer_COMPONENT_DEFINE(
    <NameOfComponent>
)

Instantiating and Connecting the Component

The following macro allows to create an instance of the component and connect it to the storage provider (e.g., RamDisk or Flash):

component   <NameOfComponent> <nameOfInstance>;

StorageServer_INSTANCE_CONNECT(
    <nameOfInstance>
    <storage>.<storage_rpc>, <storage>.<storage_port>
)

The clients have to use the if_OS_Storage interface and provide a dataport for transactions with the StorageServer.  The following macro accepts a list of clients, as long as they are given in pairs with their RPC interface and dataport:

StorageServer_INSTANCE_CONNECT_CLIENTS(
    <nameOfInstance>
    <client1>.<nameOfInterface>, <client1>.<nameOfDataport>,
    <client2>.<nameOfInterface>, <client2>.<nameOfDataport>,
    ...
)

Configuring the Instance

Every instance of the StorageServer has to be configured. For every client listed above, we need to pass two values:

  • offset: indicates where (from the beginning of the underlying storage) the storage of that client starts (in bytes)

  • size: this parameter indicates how big the storage is (in bytes)

The following macro can be used:

StorageServer_INSTANCE_CONFIGURE_CLIENTS(
    <nameOfInstance>,
    <offset1>, <size1>,
    <offset2>, <size2>,
    ...
)

Please note that the order of the configuration here must correspond to the order in which the clients are listed in the StorageServer_INSTANCE_CONNECT_CLIENTS() macro.

Assigning Clients’ Badges

The StorageServer uses the “badge” assigned by seL4 to each client’s RPC endpoint to map the client to its respective storage configuration; badges can be assigned via this macro:

StorageServer_CLIENT_ASSIGN_BADGES(
    <client1>.<nameOfInterface>,
    <client2>.<nameOfInterface>,
    ...
)

Please note that the order of clients needs to correspond to the order StorageServer_INSTANCE_CONFIGURE_CLIENTS() and StorageServer_INSTANCE_CONNECT_CLIENTS() macros. The StorageServer can handle up to 8 clients.

Example

Here is an example configuration of two clients using the StorageServer to partition a RamDisk of 2 MiB into two storages of 1 MiB for each client.

Instantiation of the Component in CMake

The component is added to the build as MyStorageServer:

StorageServer_DeclareCAmkESComponent(
    MyStorageServer
)

Instantiation and Configuration in CAmkES

Defining the Component

The component is declared as MyStorageServer in the main CAmkES file:

#include "StorageServer/camkes/StorageServer.camkes"
StorageServer_COMPONENT_DEFINE(
    MyStorageServer
)

Instantiating and Connecting the Component

The following instantiates a RamDisk component, the StorageServer instance and connects them together.

// Instantiate RamDisk for storage and a StorageServer
component RamDisk           storage;
component MyStorageServer   myStorageServer;
// Instantiate two clients
component Client            client1;
component Client            client2;

// Connect interfaces USED by StorageServer
StorageServer_INSTANCE_CONNECT(
    myStorageServer,
    storage.storage_rpc,    storage.storage_port
)
// Connect interfaces PROVIDED by StorageServer
StorageServer_INSTANCE_CONNECT_CLIENTS(
    myStorageServer,
    client1.storage_rpc, client1.storage_port,
    client2.storage_rpc, client2.storage_port
)

Please note that in the beginning, we connect the StorageServer to a RamDisk via the if_OS_Storage interface, whereas the second macro connects two clients to the StorageServer, again via an instance of if_OS_Storage (and a dataport).

Configuring the Instance

Here we see that for each entry in the StorageServer_INSTANCE_CONNECT_CLIENTS() macro, we have a matching line which contains an offset and a size value. As we can see, each client gets 1 MiB of storage, but the storage of the second client begins at the end of the first client’s storage.

StorageServer_INSTANCE_CONFIGURE_CLIENTS(
    myStorageServer,
    0,         1024*1024,
    1024*1024, 1024*1024
)

Assigning Clients’ Badges

We assign the badges to the client’s RPC endpoints via this macro:

StorageServer_CLIENT_ASSIGN_BADGES(
    client1.myStorageServer_rpc,
    client2.myStorageServer_rpc
)

Using the Component’s Interfaces in C

The StorageServer can be used by including the camkes.h header and calling the RPC interface in the following way:

// For the CAmkES generated interface
#include <camkes.h>

// For wrapped access to the interface
#include <OS_Dataport.h>
#include <if_OS_Storage.h>

static const if_OS_Storage_t storage =
    IF_OS_STORAGE_ASSIGN(
        myStorageServer_rpc,
        myStorageServer_port);

static const OS_Dataport_t port =
    OS_DATAPORT_ASSIGN(
        myStorageServer_port);

...

int run() {
    const off_t   desiredOffset = 13;
    const size_t  dataSz        = 42;
    const uint8_t writePattern  = 0xA5;

    void* const dataPort = OS_Dataport_getBuf(port);

    CHECK_PTR_NOT_NULL(dataPort);

    // Verify in the production code that there is no data port overflow.
    memset(dataPort, writePattern, dataSz);

    size_t bytesWritten = 0U;
    if(OS_SUCCESS != storage.write(
        desiredOffset,
        dataSz,
        &bytesWritten))
    {
        // Handle the write error.
    }

    if(dataSz != bytesWritten)
    {
        // Handle the write error.
    }

    // Clearing the data port for sanity.
    memset(dataPort, 0xFF, dataSz);

    size_t bytesRead = 0U;
    if(OS_SUCCESS != storage.read(
        desiredOffset,
        dataSz,
        &bytesRead))
    {
        // Handle the read error.
    }

    if(dataSz != bytesRead)
    {
        // Handle the read error.
    }

    // Verifying the read content:
    for(size_t i = 0; i < dataSz; ++i)
    {
        if(writePattern != dataPort[i])
        {
            // Shall never happen!
        }
    }

    ...
}