cpsw

Describing CPSW Hierarchies with YAML

This file is part of CPSW. It is subject to the license terms in the LICENSE.txt file found in the top-level directory of this distribution and here.

No part of CPSW, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE.txt file.

Introduction

This document describes how a CPSW hierarchy can be defined by writing a YAML file. Such a YAML file provides a convenient interface to the CPSW ‘builder’ API as it does not require writing any C++ code.

The document does not discuss YAML itself. Consult the YAML specification for details about the syntax and other features.

1. YAML Extensions

CPSW adds a few extensions to YAML:

1.1 YAML Include Files and Preprocessor

YAML provides no way to include one YAML file from another one. The YAML specification (and yaml-cpp implementation) makes it impossible to define file inclusion within YAML itself.

For this reason and because re-using snippets of YAML was deemed very desirable a simple preprocessor was implemented in CPSW.

The preprocessor recursively parses commented headers in YAML files for ‘include’ and ‘once’ directives and assembles a single stream of YAML which is then forwarded to the YAML parser library (yaml-cpp).

1.1.1 Preprocessor Header Format

The preprocessor scans an initial block of header lines:

    header_block =  { header_line } ;

    header_line  =  '#' , ( include_directive | once_directive | comment ) , ?EOL? ;

i.e., it scans until a line does not start with #.

1.1.3 #include Directive

The ‘include’ directive (a line starting with ‘#include ’ [notice the blank]):

    whitespace        = ' ' | ?TAB? | ?CR? ;
    nonwhite_char     = ?ANYCHAR? - whitespace ;
    nonbra_char       = nonwhite_char - '<' ;
    nonket_char       = nonwhite_char - '>' ;

    include_directive = 'include ', { whitespace }, include_filename, { whitespace } ;

    include_filename  = bracketed_name | nonbracketed_name ;

    nonbracketed_name = nonbra_char, { nonwhite_char } ;
    bracketed_name    = '<', nonket_char, { nonket_char }, '>' ;

directs the preprocessor to (recursively) continue with processing the file include_filename before resuming processing the current file.

include_filename currently cannot not contain any whitespace characters (escaping not implemented at this time) but it may contain path components.

All YAML files are assumed to reside in a single directory which defaults to the current-working directory but the preprocessor can be pointed to an alternate location.

1.1.4 #once Directive

If the preprocessor encounters a ‘once’ directive (a line starting with ‘#once ’ [notice the blank])

    once_directive := 'once ' , { whitespace } , once_tag ;
    once_tag       := nonwhite_char, { nonwhite_char } ;

then it reads the once_tag (any non-empty string following ‘#once ’) and checks if it has encountered the same once_tag already earlier. If this is the case then the preprocessor skips to the end of the file. Otherwise it records the tag and continues processing.

Thus, the #once directive serves as a simple multiple inclusion guard. However, unlike the familiar #ifdef, #once always skips to the end of the file.

E.g., if me.yaml contains:

    # This file includes itself but does not result in infinite recursion
    #
    # Note that here cannot be whitespace in '#include' nor '#once'
    #
    #once protect_me
    #
    #include me.yaml

    a_yaml_node: something

then the preprocessor will emit a_yaml_node: something just once.

1.2 YAML Merge Key — <<

1.2.1 Purpose of the Merge Key

With the standard YAML ‘alias’ feature and the preprocessor’s ‘#include’ directive there are powerful ways to re-use pieces of YAML at different locations.

Often, however, it is desirable to reuse most parts of an existing piece while being able to override select parts.

This cannot be achieved with standard YAML. As a result, the ‘Merge Key’ for YAML ‘maps’ has been proposed:.

1.2.2 The Semantics of the Merge Key

Assume we have a definition

    default: &default_tag
      setting_1: value_1
      setting_2: value_2

which we want to use in our map ‘mymap’:

    mymap: *default_tag

However, what if we want to override (just) setting_2 in mymap with let’s say my_value_2.

In standard YAML that would not be possible because a key must be present at most once in a map. Thus we might try:

    mymap: *default_tag
      setting_2: my_value_2

but this would not be valid YAML code. (mymap already has the ‘default’ node as its value and there is no way to add additional entries to that node. And even if there were, it would be illegal to use the setting_2 key again.)

This is where the merge key comes in:

mymap: «  : *default_tag setting_2: my_value_2

Thus ‘mymap has now two keys:

The idea of the merge key defines semantics for key lookup in a map:

    lookup_key:

      if ( map["key"] )
        return map["key"]
      else if ( map["<<"] )
        return map["<<"]["key"]

I.e.: look for the key in the map. If it is not found then check if there is a merge key and if there is then look for the original key in the merged node (value of the merge key).

NOTE:

1.2.3 Complex Merging

The idea of merging looks simple enough. However, as described above it works only across a single level. Imagine our default definition looks like this:

    default: &default_tag
      nested_map:
        setting_1: value_1
        setting_2: value_2

I.e., the default map contains nested maps and we want to override a setting located at a deeper level.

    mymap:
      <<       : *default_tag
      setting_2: my_value_2

no longer works, since setting_2 is not at the same hierarchy level as setting_2 inside nested_map.

What we mean is

    mymap:
      <<       : *default_tag
      nested_map:
        setting_2: my_value_2

but in this case the simple lookup algorithm presented above no longer works (setting_2 is not found in mymap["nested_map"] but there is also no merge key in mymap["nested_map"]).

As a result, the merging algorithm implemented by CPSW is more sophisticated and actually supports the notation just presented in the example.

2. CPSW Building Blocks

This section describes the building blocks made available by the CPSW framework which can be used to assemble hierarchies in YAML.

CPSW is extensible and user-defined classes which derive from the core classes may add additional YAML properties.

2.1 Basic Node

Every CPSW node described in YAML must be an entry in a map and is itself a YAML map. The key of the CPSW Node will become its name in the hierarchy:

    myname:
      property: value

will define a CPSW node called ‘myname’ and it’s map contains a key ‘property’. The exact properties which are supported and/or expected by a particular node depend in its underlying c++ class. All core classes shall be described in the following sections.

2.1.1 class Property

Every node must define a map entry with the key class. Its value must correspond to one of the classes registered with CPSW. CPSW will then dispatch interpreting the YAML node data to the factory method of this class which takes care of constructing the underlying CPSW object.

Thus, the basic node is defined by

    myname:
      class: <className>

The value of this key may also be a sequence of class names. In this case, CPSW tries to locate a class in the specified order. E.g.,

    myname:
      class:
        - myClass
        - MMIODev

CPSW would then try to locate ‘myClass’ first and if that fails ‘MMIODev’ would be used as a fallback.

The typical use case is a device for which a driver is initially not available. At least its registers could be made available by MMIODev.

2.1.1.1 Dynamic Class Loading

CPSW implements dynamic loading of classes. If it doesn’t find a class in its registry it tries to load “.so".

A properly written class contains initialization code which would register the class with CPSW so that the ensuing second attempt to find the class will succeed.

2.1.2 instantiate Property

Every node also supports an optional map entry with the key instantiate which has a boolean value (defaults to ‘true’). When set to ‘false’ then CPSW will skip instantiation of this node and any potential children.

This is useful (especially in combination with the merge key) to disable select nodes.

2.2 ‘Field’ Class

The lowest-level class which can actually be instantiated is a ‘Field’. ‘Field’ implements the ‘Stream’ user API.

It supports the following properties (in addition to ‘Basic Node’):

    myField:
        # class of this node
      class:       Field      # *mandatory*

        # brief description of the purpose and semantics of this Field
      description: <string>   # optional

        # suggested polling interval (not used by CPSW)
      pollSecs:    <double>   # optional

        # size (in bytes) of this field. Note that some subclasses
        # don't accept a size of zero (default).
      size:        <int>      # optional

        # hint to software whether this Field can be cached.
        # Legal values are:
        #   NOT_CACHEABLE
        #   WT_CACHEABLE  (write-through cacheable)
        #   WB_CACHEABLE  (write-back    cacheable)
        # If a Field is WB_CACHEABLE then the software may
        # e.g., combine transactions scheduled for this field
        # with others and/or reorder them.
      cacheable:   <Cacheable> # optional

        # definition of this property allows you to establish an
        # order in which Fields are dumped/saved to a configuration
        # file (ignored if a configuration 'template' is used).
        # Consult 'README.configData' for more information.
      configPrio:  <int>       # optional

The default value for cacheable is for leaves and `WT_CACHEABLE` for containers. During the build process settings are resolved by using the parent container's value. This makes it easier to manage a default (via the container) and override select Field(s) only.

2.3 ‘Dev’ Class

The Dev is the base class for all containers. It is derived from Field and inherits all of its properties (but the default value for cacheable is WB_CACHEABLE).

The Dev adds support for a map of children. The class entry must be ‘Dev’ or a subclass thereof.

    myDev:
      class:       Dev     # *mandatory*
      children:            # optional
        <child_entry>
          at:              # *mandatory*
            <child_address_entry>   # *mandatory*
        <child_entry>
          at:              # *mandatory*
            <child_address_entry>   # *mandatory*
        ...

After the children key all children are listed (as maps themselves).

Note that the Dev class also requires for each child a map with addressing information to be attached to the child. The properties in the <child_address_entry> are interpreted and used by the Dev class and not by the child itself. However, the information is specific to each child and is needed for communication between the Dev and its children.

E.g., in the case of a conventional memory-mapped device the addressing information contains an ‘offset’. In other cases it may be a ‘i2c’ address or a device filename etc.

Each Subclass of Dev supports a distinct subclass or set of subclasses of ‘Address’ which are usually implemented together with the container class.

2.3.1 The ‘Address’ Class

‘Address’ is the base class for addressing information which defines the connection of a child to its container.

The basic Address supports the following YAML properties (which must be defined in a map under the at key inside the child definition):

      # How many replicas of the child are to be
      # attached. This allows for the definition
      # of arrays.
      # The default value is '1'.
      # Note that not all subclasses of Address
      # may support values other than 1.
    nelms:    <int>        # optional

      # Associate a byte order with this address.
      # The default is 'unknown' in which case the
      # build process will propagate the container's
      # byte order here.
      #
      # Legal values are
      #  "BE"   (big-endian)
      #  "LE"   (little-endian)
      #  "UNKNOWN"
    byteOrder: <ByteOrder> # optional

The byte order may not make sense for all subclasses of ‘Address’ and is best left to be resolved by the container. It may occasionally required to be overridden, e.g., if select registers in a memory-mapped device appear swapped.

Here is an example for a little-endian memory map which has an ‘abnormal’ register presented in big-endian:

    register:  &aregister
      class:     IntField
      size:             4
      sizeBits:        32

    myDevice:
      class:      MMIODev
      byteOrder:  LE
      size:       0x10
      children:

        <<: *normal_children # assume to be defined elsewhere

        abnormal_register:
          <<: *aregister
          at:
            offset:    12
            byteOrder: BE

Regarding the nelms key it is important to realize that the definition of arrays of elements does not rest in the definition of the element itself but with the ‘address’ where the element is attached. (Similar to the ‘C’ language: an array is usually an ‘array of element-types’ and not an ‘array-of-element type’.

2.3.1 The ‘File-System Address’ Class

This class provides access to files (or devices) on the host computer via the operating system. The presence of fileName (inside at) triggers instantiation of this class, e.g.,:

    bytes:
      class:    IntField
      sizeBits: 8
      at:
        nelms:     1024
        #
        # Path to the file
        fileName:  <string>  # *mandatory*
        # indicate whether the
        # file is seekable. Some devices
        # are not...
        seekable:  <bool>    # optional
        # map the child entry at
        # an offset into the file.
        # This requires the file
        # to be seekable!
        offset:    <int>     # optional
        # optional timeout for *read*
        # operations (in micro-seconds).
        timeoutUS: <int>     # optional

2.4 The ‘SequenceCommand’ Class

The SequenceCommand class is derived from Field and is a ‘leaf’ element.

It implements the user-API ‘Command’ interface and can execute sequences of

In addition to Field’s properties SequenceCommand supports sequence. It’s value must be a YAML sequence of maps:

    mySequence:
      class:  SequenceCommand      # *mandatory*
        sequence:
          - entry: <string>
            value: <number>
          - entry: <string>
            value: <number>
          ...

The entry value strings identify a cpsw Path (starting at the sequence node’s parent) to which the numbers are written in the sequence defined by the list.

E.g., - entry: sibling value: 1234 - entry: usleep value: 10000 - entry: ../aunt/cousin value: 5678

would write 1234 to ‘sibling’ which resides in the same container as the SequenceCommand itself and 5678 to ‘cousin’ which resides in another container ‘aunt’ which is a sibling of the SequenceCommand’s parent.

The ‘usleep’ Path is treated special and will cause the sequence to delay for the specified number of microseconds. Note that this shadows any sibling named ‘usleep’.

The ‘system(cmds)’ Path is also treated special and will cause a process to be forked executing the cmds using the unix system() library call.

2.4.1 Choice of Multiple Sequences

The SequenceCommand also supports a ‘two-dimensional’ sequence of sequences to offer a choice between multiple commands. The user can then pick one of the sequences at run-time.

An enum menu may also be associated with the sequence command (more about ‘enum’ menus in the ‘IntField’ section.

This menu must specify exactly as many entries as there are sequences. In particular: if only a single sequence is defined then the menu can only contain a single entry (mapping to the integer ‘0’). This can be used to ‘label’ the command sequence.

A SequenceCommand with multiple sequences is not accessible by the user-API ‘Command’ interface but by ‘ScalVal_WO’.

Here is an example of a SequenceCommand with two choices:

    mySequence:
      class:  SequenceCommand      # *mandatory*
        sequence:
          -
            - entry: "system(echo running choice one)"
              value: 0             # ignored
          -
            - entry: "system(echo running choice two)"
              value: 0
        enums:
          - name:  "Choice One"
            value: 0
          - name:  "Choice Two"
            value: 1

2.5 The ‘IntField’ Class

IntField is derived from Field and is a ‘leaf’ class. It implements the ScalVal, ScalVal_RO, DoubleVal and DoubleVal_RO user-APIs.

IntField supports signed or unsigned integer numerical values of arbitrary length in bits (up to 64). IntField also supports arbitrary alignment within a byte, i.e., the bits that make up an IntField need not be byte-aligned (but the must be contiguous).

In addition, IntField supports single- or double-precision IEEE-754 floating-point numbers. Such numbers must have a length of exactly 32 or 64 bits and marked with the encoding property set to IEEE_754. Bit-shift and endianness-conversion etc. are handled the in the same way as for integers (‘enum’ mappings as described below are not supported for floating-point entities).

Note that if an entity has IEEE_754 encoding then it may only be accessed via the DoubleVal/DoubleVal_RO interfaces.

Thus, IntFields are ideal to represent registers or bits thereof.

Optionally, a list of ‘enum’ mappings can be associated with an IntField which allows mapping of numerical values to strings and back (e.g., a bit can be mapped to ‘True’/’False’).

IntField also takes care of byte-swapping numbers to host byte order and it can optionally word-swap (suitably aligned) sub-words to handle more obscure cases.

Depending on an IntField’s signed-ness numbers are sign-extended to the size requested by the user.

IntField can be configured to a ‘read-only’ mode (statically defined by YAML - cannot be switched later) in which only ScalVal_RO and DoubleVal_RO are supported and values cannot be changed.

An optional ‘encoding’ attribute can be defined for IntField.

This e.g., allows the user-application to properly decode numbers into strings if the underlying entity actually represents a character. The ‘encoding’ attribute is also used for registers containing floating-point numbers.

    myIntField:
      class: IntField           # *mandatory*

        # Whether to interpret the underlying bits
        # as a signed or unsigned number. Signed
        # numbers are sign-extended to the size
        # requested by the user. Defaults to 'false'.
      isSigned: <bool>          # optional

        # Size of the IntField in bits. The Field's
        # corresponding size in bytes is automatically
        # computed (YAML setting ignored).
        # Defaults to 32.
        # I.e.,: size = (sizeBits + lsBit + 7) / 8
      sizeBits: <int>           # optional

        # Alignment of the least-significant bit
        # within a byte. Legal values are 0..7.
        #
        # A number written is left-shifted by
        # this amount before being (merged and)
        # written to hardware. It is right-shifted
        # after being extracted from hardware and
        # before being handed to the user.
        # Defaults to 0.
      lsBit   : <int>           # optional

        # Access-mode of this IntField
        #  - "RW" (read-write; default)
        #  - "RO" (read-only)
        #  - "WO" (write-only -- *currently not supported*)
      mode    : <string>        # optional

        # Swap words of specified length (in bytes;
        # the IntField's bit-size *must* be a multiple
        # of the word-length!)
        #
        # Defaults to 0.
      wordSwap: <int>           # optional

        # If this IntField represents a character or a
		# floating-point number etc., then the encoding
        # used by this IntField can be specified.
        # An automatically generated GUI, for example,
        # could use the encoding to detect strings and
        # display them correctly.
        #
        # Legal values include "NONE", "ASCII", "UTF_8",
        # "ISO_8859_1" among others (consult the API header
        # for a complete list).
		# Floating-point numbers use e.g., "IEEE_754".
        #
        # Defaults to NONE.
      encoding: <int>           # optional

        # The base in which values are saved to a configuration
        # file. Legal values are 10 (decimal) or 16 (hex).
        # The only effect of this parameter is increasing
        # readability of saved configuration files.
        #
        # Defaults to 16.
      configBase: <int>         # optional

The wordSwap parameter can e.g., be used to handle the case of a 64-bit word in ‘mixed format’:

Assume a 32-bit system with little-endian byte-addressing where the most significant word is stored at a lower address, followed by the least significant word:

address 0 high_word 4 low_word

If the words are stored in little-endian format then the quad ‘0x0807060504030201’ would normally be stored (by increasing address):

      0x01, 0x02, 0x03, 0x04, 0x04, 0x06, 0x07, 0x08

If, however, (as it sometimes happens in ‘real-world’ devices) the above ‘mixed format’ is used then the data would be stored:

      0x04, 0x05, 0x06, 0x07, 0x01, 0x02, 0x03, 0x04

In this case an IntField with wordSwap set to 4 would read the correct value.

2.6 The ‘ConstIntField’ Class

ConstIntField is derived from IntField and is another ‘leaf’ class. It implements ScalVal_RO and DoubleVal_RO user-APIs.

The purpose of ConstIntField is providing run-time access to constant values which are defined in YAML. ConstIntField does never access real hardware.

ConstIntField can supply string-, double- or integer values. It inherits all properties from IntField but overrides some of them (sizeBits, lsBit, byteOrder, wordSwap, mode).

The type of the value retrieved from YAML is defined by the encoding property.

If encoding is set to ASCII then the value is interpreted as a string; otherwise, if encoding equals IEEE_754 then it is interpreted as a double else as a 64-bit integer (honoring isSigned).

A ‘Enum’ menu with a single entry which maps a string to the numerical value is always attached to ConstIntField (except if IEEE_754 encoding is used) but it is not necessary to define such a menu in YAML.

E.g., in the case of

    aString:
      class:    ConstIntField     # *mandatory*

        # inherited from IntField
      encoding: ASCII
        # The constant value
      value:    "Hello"           # *mandatory*

    aDouble:
      class:    ConstIntField     # *mandatory*
        # set to 'IEEE_754' if the value is to be interpreted
        # as a double.
      encoding: IEEE_754
      value:    3.141             # *mandatory*

aString’s menu will map the value 0 to the string “Hello” and aDouble’s menu will map the value 3 to the string “3.141”.

2.7 The ‘MMIODev’ Class

MMIODev is a subclass of Dev and implements a memory-mapped container.

The only parameter added to Dev is byteOrder which is just for convenience since it allows the user to define a byte-order for the entire container which sets the default for all of its children (each child’s Address may override this setting individually for the specific child).

    myMMIODev:
      class: MMIODev               # *mandatory*

        # NOTE: An MMIODev must specify a non-zero size
      size:  <int>

        # Set the default ByteOrder for the container.
        # As explained under 'Address' - if the ByteOrder
        # is unknown (default) then this MMIODev inherits
        # from the Address where it is attached.
        #
        # Note: At *some* level the byte-order which
        #       will be used by Fields for which this
        #       is relevant *must* be defined. Either
        #       by the Field itself or any of its parents
        #       in the hierarchy.
        # Legal values are
        #
        #  "BE"   (big-endian)
        #  "LE"   (little-endian)
        #  "UNKNOWN"

      byteOrder: <ByteOrder>

2.7.1 MMIOAddress

When a Field is attached to an MMIODev then there are two parameters in addition to nelms and byteOrder (which are inherited from Address):

      # The byte offset from the MMIODev's base address
      # where the child resides (if the child spans more than one
      # byte then this means the offset of the byte with the
      # lowest byte-address ).
      #
      # Default: <none>
    offset: <int>                   # *see note*

      # The 'stride' specifies by how many bytes the elements
      # of an array are separated/spaced.
      # The default (0) assumes 'dense' packing i.e., the stride
      # is automatically set to the size of an array element.
    stride: <int>                   # *optional*

Note that when the offset is omitted then an ordinary ‘Address’ is used which essentially bypasses the MMIODev and forwards all read/write operations to the parent.

2.7.2 MMIODev and IntField Example

Assume a (memory-mapped) device presents an ASCII string of 40 characters where each character is aligned on a 32-bit boundary. The string starts at offset 0x10.

    myMMIODev:
      class    :  MMIODev
      byteOrder:  LE
      size     :  0x1000
      children :
        myString:
          class:    IntField
          sizeBits: 8
          encoding: ASCII
          at:
            offset: 0x10
            stride:    4
            nelms:    40

2.8 The ‘NetIODev’ Class

NetIODev is – for most applications – the ‘root’ device which handles all communication with a remote endpoint.

The main property supported by NetIODev is the IP address of the peer:

    myNetIODev:
      class : NetIODev

        # CPSW does attempt to resolve DNS names.
        # IP addresses may be provided in 'numbers-
        # and-dot' notation or as names.

        # Note: omitting the peer's IP lets
        # CPSW pick 'INADDR_ANY' which on some systems
        # causes communication with the local host
        # (which may be useful for testing only).
      ipAddr:     <string>     # *optional*

        # Connect to 'ipAddr' via an 'rssi bridge'
        # proxy. This property defines the address
        # of the proxy (you must make sure such a
        # bridge is actually running!)
      rssiBridge: <string>

        # Connect to a TCP destination (either
        # a 'native' TCP destination or an 'rssi
        # bridge') via a SOCKS proxy (the most
        # common case being an SSH connection
        # with SOCKS support - see open-ssh's
        # '-D' option for details).
      socksProxy: <string>

Thus, for each peer with a different IP address a separate NetIODev instance is required. These could be attached to a dummy root Dev:

    myRoot:
      class:      Dev
      children:
        peer_1:
          class:  NetIODev
          ipAddr: 10.0.0.1
          at: # empty 'at' -- no addressing info needed
        peer_2:
          class:  NetIODev
          ipAddr: 10.0.0.2
          at: # empty 'at' -- no addressing info needed

In addition to the peer’s address the following settings help with tunneling/proxying connections over TCP and even SSH:

    myRoot:
      class:       NetIODev
      rssiBridge: <rssi_bridge_ip_or_name>
      socksProxy: <socks_proxy_ip_or_name>[:<socks_proxy_port>]

If the rssiBridge property is set then it points to a computer with direct connectivity to the target. On that computer a properly configured rssi_bridge must be running. Such a bridge serves as a proxy for RSSI/UDP communication:

    CPSW <-- TCP connection --> rssi_bridge <-- RSSI/UDP connection --> target

If rssiBridge is defined in YAML then all UDP modules are automatically converted into TCP.

In addition to rssiBridge the socksProxy property may be used for tunneling TCP connection(s) over SSH. Typically, an SSH connection is started with the -D option (“dynamic port forwarding”) letting the SOCKS protocol (which is supported by CPSW) taking care of the rest. Alternatively to socksProxy, the environment-variable SOCKS_PROXY may also be used for defining a SOCKS proxy.

It is often convenient to start an rssi_bridge via ssh:

    ssh -D1080 -tt user@host <path>/rssi_bridge -a <target_ip> -p 8198 -u 8197 <other ports>

In most cases socksProxy is simply set to localhost.

Note that the SOCKS proxy is ignored if no TCP is defined in the YAML (explicitly or implicitly due to rssiBridge).

2.8.1 NetIODev Address Objects

The true power of NetIODev lies in the Address objects it implements. These are the communication endpoints where children are attached.

Each endpoint instantiates a protocol stack or a part thereof which is configured with a variety of parameters that shall be listed below.

Note that the arrangement of the stack (‘stacking order’) is configured automatically and not subject to user choices.

The various protocol modules are presented in the order in which they are usually stacked:

    SRP_VirtualChannel -- (De)/Multiplexer of SRP transactions based on a tag
    SRP                -- 'Slac Register Protocol'. Encodes I/O Operations
    TDEST              -- (De)/Multiplexer based on TDEST tag
    Fragmentation      -- AKA 'Packetizer'; assembles/disassembles
                          larger frames from/into MTU-sized ones.
    RSSI               -- Reliability and flow-control
    UDP                -- UDP communication layer communicating with a single
                          port of the peer.

All modules above UDP are optional but some depend on each other: The TDEST (De)Muxer requires the Packetizer to be present and SRP_VC requires SRP.

Of course, the setup of these layers must match the peer’s configuration exactly.

Each protocol module is assigned its own map entry in the at map of the attachee. Since these entries are YAML maps the order of the protocol modules in YAML is arbitrary (the decision was made deliberately to use maps so that merge keys work across all levels).

    UDP:

        # The UDP port of the peer
        # NOTE: a port number of 0 *disables* UDP
        #
        # Default: 8192
      port:           <int>

        # Depth of the queue up to the next module
        # zero (default) picks a suitable value.
      outQueueDepth:  <int>

        # Number of RX threads to spawn for handling
        # zero (default) picks a suitable value.
      numRxThreads:   <int>
        #
        # Peers which do not implement ARP rely
        # on being contacted at regular intervals
        # so that they can record our UDP/IP/MAC
        # address.
        # A special thread periodically sends a short
        # message to the peer for this purpose.
        # This parameter defines the frequency of
        # this polling operation.
        #
        # A value of 0 disables polling altogether.
        # A value less than zero lets CPSW pick
        # a suitable default.
        #
        # Default: -1
      pollSecs:       <int>

        # Priority of the UDP RX threads. A number
        # bigger than zero must be a valid pthread
        # priority and tries to engage a real-time
        # scheduler. If this fails, CPSW falls back
        # to the default scheduler.
        #
        # Default: 0
      threadPriority: <int>

        # The presence of this key indicates
        # that RSSI shall be used. Its absence
        # that no RSSI is to be configured.
    RSSI:

        # Priority of the RSSI thread. A number
        # bigger than zero must be a valid pthread
        # priority and tries to engage a real-time
        # scheduler. If this fails, CPSW falls back
        # to the default scheduler.
        #
        # Default: 0
      threadPriority: <int>

        # log2 of the max. number of unacknowledged
        # segments the peer may send to us.
        # (See RSSI/RUDP spec for more information)
        #
        # Default: 4
      ldMaxUnackedSegs: <int>

        # Depth or RSS output queue.
        #
        # Default: 0 (a suitable value is chosen)
      outQueueDepth: <int>

        # Depth or RSS input queue.
        #
        # Default: 0 (a suitable value is chosen;
        # should be >= 2**ldMaxUnackedSegs)
      inpQueueDepth: <int>

        # Retransmission timeout (proposed to
        # negotiation process)
        #
        # (See RSSI/RUDP spec for more information)
        #
        # Default: 100000us
      retransmissionTimeoutUS: <int>

        # Cumulative ACK timeout (proposed to
        # negotiation process)
        #
        # (See RSSI/RUDP spec for more information)
        #
        # Default:  50000us (should be less than
        # retransmissionTimeoutUS)
      cumulativeAckTimeoutUS: <int>

        # NULL timeout (proposed to
        # negotiation process)
        #
        # (See RSSI/RUDP spec for more information)
        #
        # Default:  3000000us
      nullTimeoutUS: <int>

        # Max. number of retransmissions before
        # the connection is considered dead
        # (proposed to negotiation process).
        #
        # (See RSSI/RUDP spec for more information)
        #
        # Default:  15
      maxRetransmissions: <int>

        # Max. number of segments that are accepted
        # without sending ACK (``cumulative ACK'';
        # proposed to negotiation process).
        #
        # (See RSSI/RUDP spec for more information)
        #
        # Default:  2
      maxCumulativeAcks: <int>

        # Max. segment size we can receive
        # without sending ACK (``cumulative ACK'')
        #
        # (See RSSI/RUDP spec for more information)
        #
        # Default:  suitable value chosen by
        #           MTU discovery. When overriding
        #           the default, MAKE SURE NOT TO
        #           EXCEED YOUR CONNECTION'S MTU!
      maxSegmentSize: <int>

        # The presence of this key indicates
        # that 'depack' shall be used. Its absence
        # that no 'depack' is to be configured.
        # Obviously, configuring any parameter
        # for the depacketizer enables it as well.
        # The depacketizer is also enabled
        # automatically if the TDESTMux module
        # is enabled.
    depack:

        # Packetizer protocol version.
        # Either DEPACKETIZER_V0 or
        #        DEPACKETIZER_V2
        #
        # Note that  DEPACKETIZER_V2 which
        # supports TDEST interleaving is actually
        # implemented by the TDESTMux module, i.e.,
        # all parameters defined for the depacketizer
        # (such as threadPriority, queue sizes etc.)
        # are ignored; the TDESTMux configuration
        # is used instead!
        #
        # Default: DEPACKETIZER_V0
      protocolVersion:<DepackProtoVersion>

        # The output queue depth can in special
        # cases be tuned - if this module
        # is at the top of the stack and the
        # application requires more buffering
        # capacity inside CPSW.
      outQueueDepth:  <int>

        # For expert use only
      ldFrameWinSize: <int>

        # For expert use only
      ldFragWinSize:  <int>

        # Priority of the depacketizer thread. A number
        # bigger than zero must be a valid pthread
        # priority and tries to engage a real-time
        # scheduler. If this fails, CPSW falls back
        # to the default scheduler.
        #
        # Default: 0
      threadPriority: <int>

        # The TDEST (De)Muxer is enabled
        # by the presence of this key.
    TDESTMux:

        # TDEST to which the child connects
        # a value between 0 and 255;
        # Defaults to 0
      TDEST:          <int>

        # Whether to strip the Packetizer header
        # and footer prior to handing data up
        # (or adding header/footer in the opposite
        # direction).
        # The default (when this key is not present)
        # does not strip the header except if SRP
        # is layered on top.
      stripHeader:    <bool>

        # The output queue depth can in special
        # cases be tuned - if this module
        # is at the top of the stack and the
        # application requires more buffering
        # capacity inside CPSW.
      outQueueDepth:  <int>

        # The input queue depth can in special
        # cases be tuned - if this module
        # is at the top of the stack and the
        # application requires more buffering
        # capacity inside CPSW.
        #
        # Note: this parameter is only relevant
        # if the TDESTMux is operating in DEPACKETIZER_V2
        # mode. It is ignored otherwise.
      inpQueueDepth:  <int>

        # Priority of the demultiplexer thread. A number
        # bigger than zero must be a valid pthread
        # priority and tries to engage a real-time
        # scheduler. If this fails, CPSW falls back
        # to the default scheduler.
        #
        # Default: 0
      threadPriority: <int>

        # The presence of the SRP module is defined
        # by the value of protocolVersion.
        # NOTE: the default is SRP_UDP_V2 which enables
        #       SRP.
    SRP:

        # SRP Protocol version. To disable set explicitly
        # to SRP_UDP_NONE.
        # Legal values:
        #  SRP_UDP_NONE   No SRP
        #  SRP_UDP_V1     (early version in network-byte order
        #                  with extra header word)
        #  SRP_UDP_V2     first version in wider use
        #  SRP_UDP_V3     Current version
        # Consult respective confluence documentation for
        # details about SRP versions.
      protocolVersion:<SRPProtoVersion>

        # How long to wait for a SRP transaction to
        # complete (in micro-seconds)
        # zero (default) picks a suitable value
      timeoutUS:      <int>

        # Whether to automatically adjust the
        # retransmission timeout based on statistics.
        # Should be disabled if RSSI or TDESTMux
        # are being used (large frames to a different
        # TDEST going through the packetizer may stall
        # SRP)
        # Default: yes unless TDESTMux is configured
      dynTimeout:     <bool>

        # How many times to retry a failed SRP transaction
      retryCount:     <int>

        # The default write mode (might be overridden by
        # for individual operations if the API offers such
        # a feature). POSTED or SYNCHRONOUS (defaults to
        # POSTED).
      defaultWriteMode: <WriteMode>

        # The presence of this key enables the
        # SRP VirtualChannel (De)Muxer.
    SRPMux:

        # Virtual Channel number (see note) to use.
        # A number in the range 0..127.
        # Default: 0
      virtualChannel: <int>

        # Depth of the queue where asynchronous
        # replies are posted.
        # zero (default) picks a suitable value.
      outQueueDepth:  <int>

        # Priority of the demultiplexer thread. A number
        # bigger than zero must be a valid pthread
        # priority and tries to engage a real-time
        # scheduler. If this fails, CPSW falls back
        # to the default scheduler.
        #
        # Default: 0
      threadPriority: <int>
2.8.1.1 TCP Module

A TCP protocol module is also available. It is intended to substitute UDP and RSSI. This module is useful when contacting a server implemented in software (as opposed to firmware). In software, TCP is readily available and provides a reliable data stream without the need for RSSI.

The TCP module supports the following YAML properties:

    TCP:

        # The TCP port of the peer
        # NOTE: a port number of 0 *disables* TCP
        #
        # Default: 8192
      port:           <int>

        # Depth of the queue up to the next module
        # zero (default) picks a suitable value.
      outQueueDepth:  <int>

        # Priority of the TCP RX thread. A number
        # bigger than zero must be a valid pthread
        # priority and tries to engage a real-time
        # scheduler. If this fails, CPSW falls back
        # to the default scheduler.
        #
        # Default: 0
      threadPriority: <int>

2.8.2 Protocol Multiplexing

There are two layers of protocol-multiplexers available in the stack: messages may be routed based on TDEST information and in addition, SRP transactions may be multiplexed by virtual channel:

Items in brackets [] represent protocol modules, (*) are endpoints where children of NetIODev (itself in {}) are attached.

{   NetIODev Class                    }
                [       UDP       ]
                [      RSSI       ]
                [      depack     ]
                [ TDEST Mux-Demux ]
                /        |        \
               /         |         \
        TDEST_0       TDEST_1       TDEST_2
           |             |                |
          (*)           (*)            [ SRP ]
         Field         Field              |
                                    [ SRP VC Mux]
                                     /    |    \
                                   VC0   VC1   VC2
                                    |     |     |
                                   (*)   (*)   (*)
                               MMIODev MMIODev MMIODev

The diagram shows a configuration with a TDEST multiplexer which has three different TDESTs configured. TDEST_0 and TDEST_1 are used as raw (non-SRP) endpoints, e.g., to attach a ‘Stream’ing interface (implemented by ‘Field’).

TDEST_2 has an SRP module layered on top, followed by a SRP_VC multiplexer which has three endpoints (VC0, VC1 and VC2) configured.

In total, there are 5 children of the NetIODev.

The SRP VC multiplexer uses some bits in the SRP transaction ID for routing transactions. Its operation is entirely transparent to firmware and no extra firmware features are used.

The benefit is the following: all SRP transactions to a single VirtualChannel are strictly synchronous in order to guarantee a defined order in which transactions are executed.

However, if the user has multiple independent subdevices then these could all be attached to different VC channels and then be accessed in parallel.

From the above it is obvious that children of NetIODev can share parts of the protocol stack. It is necessary, however, that the common parts are configured identically. E.g., it is not possible to have two children talking to the same UDP port with one using RSSI and the other child not using RSSI.

Here is the YAML file describing the above configuration:

    commonConfig: &commonConfig
      UDP:
        port: 8192
      RSSI: ~
      TDESTMux: ~

    srpConfig: &srpConfig
      <<: *commonConfig
      SRP:
        protocolVersion: SRP_UDP_V3
      TDESTMux:
        TDEST: 2
      SRPMux: ~

    root:
      class:   NetIODev
      children:
        stream0:
          class: Field
          at:
            <<: *commonConfig
            TDESTMux:
              TDEST:  0
        stream1:
          class: Field
          at:
            <<: *commonConfig
            TDESTMux:
              TDEST:  1
        mmio0:
          class: MMIODev
          size:  0x1000
          at:
            <<: *srpConfig
            SRPMux:
              virtualChannel: 0
        mmio1:
          class: MMIODev
          size:  0x1000
          at:
            <<: *srpConfig
            SRPMux:
              virtualChannel: 1

        ... (mmio2 not shownN)