Skip to content

Data provisioning

Overview

Welma enables native management of product provisioning data (such as serial numbers, hardware revisions, MAC addresses, etc.) through data-store, a data provisioning implementation..

Data provisioning is a management system that handles structured data blocks across different storage backends (such as eMMC, EEPROM, files or block devices) while providing data integrity features.

Features:

  • Small storage footprint (minimum size 17 bytes)
  • Make it read-only after provisioning completed
  • Protection against hardware reset (data does not get corrupted)
  • Several values can be set atomically
  • Values are octet strings, with optional integer conversion
  • D-Bus interface

Runtime architecture

On the embedded Linux system, the provisioning data is accessible via the dsctl tool provided by the data-store package. This tool can be used in two different modes:

  • Base mode: Compiled into dsctl.base binary. It Provides direct access to the backend via the access management library (libdsio.so). Welma usese this mode in the initramfs context to setup the machine-id.

  • D-Bus mode: Compiled into dsctl.dbus binary. It provides acces to the backend via a dedicated D-Bus server allows all access to the backend to be centralized in a single process, which protects the backend from concurrent access. This mode is available in the root filesystem by default.

Yocto configuration

The definition of the data blocks layout and the variables contained therein is managed during compilation using a YAML file. To customize the layout, simply provide a suitable description file.

Description file

The description file defines data block structure and storage configuration. It must contain at least one block. Each block must have a unique name can be configured using the following options:

  • type: The block type. Only static is supported
  • backend: Defines the runtime device backend path
  • backend-offset: Defines the offset where Data store is located inside the backend
  • backend-offset-copy (optional): Defines the offset of block copy inside the backend
  • options (optional): Defines the verification options. Only crc and none are supported. Default to crc
  • data: Defines the variables inside the current block as a list of <name>: <size> with sizes specified in bytes

Naming convention

Blocks must be defined under the appropriate blocks section. Neither block names nor variable names may contain spaces, periods, colons, quotation marks, equal signs, backslashes, or null bytes. Variable sizes are restricted to decimal and hexadecimal formats.

Additionally, environment variables can be used within the data mapping file to provide flexibility in configuration.

The default description file in welma is defined as follows:

blocks:
  - welma:
    type: static
    backend: '${BOOT_DEV_PATH}'
    backend-offset: 0x200000
    options: 'crc'
    data:
      machine-id: 32

Note

To ensure the machine ID is managed by data-store within the initramfs, Welma defines a default welma block that includes the machine-id variable. If you intend to customize this setup, please make sure to update the initramfs with the corresponding block and variable

Block Structure

Each static block stored in the backend follows a defined structure, which is organized as follows:

Offset Size Field Description
0x00 8 Magic Header "DSHEADER" (ASCII)
0x08 1 Closed Flag 0x49 = closed, other = open
0x09 2 Data store size Total size block variables (N)
0x0b 1 Reserved Not used
0x0c N Variable Data Sequential variable storage
N+0x0c 4 CRC32 Checksum If enabled

Usage

The data-store implementation of data provisioning includes the dsctl tool, which allows interaction with data stored in the backends.

dsctl API

Usage:
        build/dsctl.base COMMAND [OPTIONS] [BLOCK[.VARIABLE[=VALUE]]] [ARGUMENTS]

Commands:
        init  <block> [--force|-f] [--backend|-b <backend>]
        info  [<block>[.<variable>]] [--backend|-b <backend>]
        get   <block>.<variable>[:int] [--backend|-b <backend>]
        set   <block>.<variable>[:int]=<value> [--backend|-b <backend>] [--force|-f]
        close <block> [--backend|-b <backend>]

Arguments:
        -b | --backend <path> : Specify the backend device to use. Only on base mode
        -f | --force          : Force write operation even if the block is corrupted
        -h | --help           : Show usage information

Commands

  • init: Initialize an empty block in the backend (values set to 0). If the block contains valid (uncorrupted) data, the --force flag must be used to overwrite the block, otherwise, DSIO_UNAUTHORIZED_OPERATION error is returned

  • info: Display information about blocks and variables

  • get: Retrieve the value of a variable in a block. It supports reading only one variable at a time. The :int keyword could be used to read a stored integer value (Only for data store sizes up to 8 bytes).

  • set: Set the value of a variable in a block. Several variables within the same block can be written in a single command. If the block is corrupted, the --force flag must be used, otherwise, DSIO_UNAUTHORIZED_OPERATION error is returned

  • close: Close a specific block. No writing (set and init) will be allowed. Used only for blocks with valid (uncorrupted) content.

D-Bus API

The data-store daemon exposes a D-Bus API, described as follow:

node /com/witekio/datastore {
      interface com.witekio.datastore.Manager {                                                                                         
    methods:                                                      
      Close(in  s block);                                         
      Init(in  s block,                                           
           in  i force);                                          
      Get(in  s block,                                            
          in  s variable,                                         
          out ay value);                                          
      Set(in  s block,                                            
          in  a(siay) variables,                                  
          in  i force);                                           
      GetBlocks(out as blocks);                                   
      Info(in  s block,                                           
           out s name,                                            
           out s backend,                                         
           out a(st) data,                                        
           out t offset,                                          
           out t offset_copy,                                     
           out i data_count,                                      
           out i type,                                            
           out i mode,                                            
           out i options);                                        
      GetVarInfo(in  s block,                                     
                 in  s variable,                                  
                 out t size);                                     
    signals:                                                      
    properties:                                                   
  };
}

Note

the --backend option is only supported by the base mode. D-Bus implementation always uses the default backend listed the YAML description file

Usage example

In this example, we define a read-only block named config, which contains both a serial number and a numeric identifier. To do this, we follow these steps:

  • Add the new block under the blocks section in the YAML description file

    blocks:
      - welma:
        type: static
        backend: '${BOOT_DEV_PATH}'
        backend-offset: 0x200000
        options: 'crc'
        data:
          machine-id: 32
    
      - config:
        type: static
        backend: /dev/mmcblk0
        backend-offset: 0x200
        options: 'crc'
        data:
          serial-number: 32
          num-id: 4
    

  • Build and install the new you image

On the running image:

  • check the available blocks

    $ dsctl info
    Available blocks:
        - welma
        - config
    

  • Initialize the config block in the backend

    $ dsctl init config
    

  • In this step, verify that the config block is defined as a read-write mode block

    $ dsctl info config
     - config
        Backend: /dev/mmcblk0
        Mode: read-write
        Type: static
        Backend Offset: 0x200
        Backend Offset Copy: N/A
        Option Flags: CRC
        Variables:
            serial-number: 32
            num-id: 4
    

  • Set a value for each variable

    $ dsctl set config.serial-number="SN123456" config.num-id:int=0x01ABCDEF
    

  • Close the block to make it read-only

    $ dsctl close config
    
    $ dsctl info config
     - config
        Backend: /dev/mmcblk0
        Mode: read-only
        Type: static
        Backend Offset: 0x200
        Backend Offset Copy: N/A
        Option Flags: CRC
        Variables:
            serial-number: 32
            num-id: 4
    

  • Applications can use D-Bus API or dsctl tool to read config variables

    $ dsctl get config.serial-number
    SN123456
    
    $ dsctl get config.num-id:int
    28036591
    

Once the config block is closed, no further modifications can be made to it through data-store tools.