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.
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.
CPSW adds a few extensions to YAML:
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).
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 #
.
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.
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.
<<
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:.
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:
<<
with the default node (itself a map) as its valuesetting_2
with my_value_2
as its valueThe 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:
the merge key ONLY works for maps, not sequences. I.e., a map must hold the merge key and another map must be attached to the merge key.
the merge key is not ‘natively’ supported by YAML. For it to work the user-code (CPSW in this case) must obey/implement its semantics during lookup operations.
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.
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.
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.
class
PropertyEvery 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.
CPSW implements dynamic loading of classes. If it doesn’t find
a class in its registry it tries to load “
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.
instantiate
PropertyEvery 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.
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
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.
‘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’.
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
The SequenceCommand class is derived from Field and is a ‘leaf’ element.
It implements the user-API ‘Command’ interface and can execute sequences of
system()
)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.
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
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.
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”.
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>
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.
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
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
).
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>
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>
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 TDEST
s 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)