...
Table of Content Zone | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ConceptsRCE code is divided into three layers:
Each layer's code is stored independently somewhere on the RCE, e.g., in configuration flash. The low-level interface to an RCE's protocol plug-ins uses abstractions called ports, virtual channels, frames, frame buffers, conduits and factories. A virtual channel is the RCE end of a two-way communications link similar in concept to a BSD socket. They are globally visible but not MT-safe; at a given time at most one thread may be waiting for data from, receiving data from or delivering data to any given virtual channel. Virtual channels receive and transfer data packaged in frames. The content of a frame depends on the protocol being used. One frame corresponds to one I/O message and is delivered in a single frame buffer. In other words all virtual channels implement datagram rather than byte-stream protocols. It's up to higher-level software such as a TCP stack to provide any operations that cross frame boundaries. Each virtual channel uses two queues of frames; one for frames which have arrived and have not yet been consumed by the application, the other for frames generated by the application but which have not yet been transmitted. In addition to a frame, a frame buffer contains administrative information that is private to the implementation of the plug-in interface; the user gives and takes only A port represents a hardware I/O engine capable of DMA to and from system RAM, i.e., a protocol plug-in of a particular type. An RCE has at most eight ports (which limit derives from the limit on the number of plugins). Each port may connect to pins leading out of the FPGA, though some may simply offer access to FPGA resources such as DSPs. No pin may be used by more than one port. Each port has its own virtual channel space where each virtual channel is identified by a 16-bit unsigned integer. Each virtual channel represents a different source and/or sink of data such as a UDP port or a section of Petacache memory. The size of the virtual channel number space on a port depends on the port type and may be as low as one. Every virtual channel object created is allocated a virtual channel number from its port's space but no two virtual channels of a port may have the same virtual channel number. Each incoming frame must contain information from which a virtual channel number can be derived. If it doesn't, or if it specifies a virtual channel number that is invalid or not allocated to a virtual channel object, then the port's lost-frame count is incremented and the frame is recycled. The FPGA is connected to the larger system by data paths called conduits. Each conduit connects to one or more pins on the FPGA; no pin may belong to more than one conduit. At boot time each port that uses pins will be matched to the conduit that connects to exactly the same set of pins. If the resulting mapping is not one-to-one then the boot process fails. Each conduit has associated type and version numbers which the software for the matching port checks for validity at boot time. If the software rejects the conduit then again booting will fail. Each type of port has both an official (unsigned) number and an official short name such as "eth" or "config". Ethernet ports that differ only in the number of pins they use, e.g., 10 Gb/s ethernet (4) and a slower ethernet (1) have the same port type and short name. PPI-handling software is held in relocatable modules recorded on the RCE. A module for plugin type FOO holds:
Each module is loaded, relocated and bound to the system core before its first use. The core code knows only the abstract base classes Factory and Port, not the derived classes specific to plugin type. Both PPI hardware and the port factory modules have version numbers which will allow some measure of compatibility checking at boot time. Any incompatibility detected causes the boot to fail. InterfaceThe C++ class declarations given here contain only those members intended for the user. Whether a method is virtual is not specified, nor are friend declarations shown; these are considered implementation details. NamespacesConfiguration informationRCE boot code discovers the set of protocol plugins and conduits available using a set of read-only configuration registers. These registers have their own address space, the "configuration space". For each plugin the configuration space registers yield plugin type, version number and the set of FPGA pins connected to it. For each conduit they yield conduit type, version number and its set of FPGA pins. Access to configuration registers is via an object which given an abstract register number returns the register contents. Another object uses the abstract register layer to provide all the configuration info for a plugin or a conduit given its index number (or supplies an indication that the given entity doesn't exist). Differences between Gen I and Gen IIGen IIThe configuration space registers are hardware memory locations filled with information by the ICMI. I/O buffers are allocated and deallocated by firmware, which also controls the size and content of frame headers and the maximum payload size. All the client gets to see in each buffer is its payload. Gen IThere is no IPMI and there are no configuration registers. The low-level configuration information is burned into a configuration flash container. At boot time the container is read and its contents fed into an object which simulates the Gen II configuration register space. Above that layer the handling of the information is just like that in Gen II. Protocol plugins are assembled from Protocol Interface Core (PIC) blocks which have no counterpart in Gen II. We want to make the configuration information handling like that in Gen II, so rather than extending it with PIC block assignments we sweep those under the rug by embedding them in the plugin software modules. There is no memory management for I/O buffers in firmware; software must allocate the buffer pools. Frame headers are fully exposed to the client who must know for a given plugin the header size, the maximum payload size and the recommeded number of buffers for a given plugin. This information can also be embedded in the plugin-handling module at the cost of having to fit the module to the RCE application. C++ application interfaceThe C++ class declarations given in this section contain only those members intended for use by the application after booting is complete and all plugins are on-line. Whether a method is virtual is not specified, nor are friend declarations shown; these are considered implementation details. Classes and their responsibilities
NamespacesAll the public All the public declarations for the interface are in namespace
Header filesAll the header files for the public interface are the top level of package
Note that in the repository both Universalconstantsconstants ( |
Code Block | ||||
---|---|---|---|---|
| ||||
static const unsigned MAX_PLUGINS = 8;
|
Port-type enumeration (PortTypes.hh
)
The numbers are members of an enumeration assigned by the DAQ project.
Code Block | ||||
---|---|---|---|---|
| ||||
enum PortType { CONFIG_FLASH, ETHERNET, PGP1, PGP2, etc., INVALID_PORTTYPE } PortType; |
The header file also contains a specialization of the template RCE::service::EnumInfo
which allows one to use the function templates emin<>()
, emax<>()
, ecount<>()
, evalid<>()
, enext<>()
, eprev<>()
and estr<>()
:
Code Block | ||||
---|---|---|---|---|
| ||||
emin<PortType>() == CONFIG_FLASH emax<PortType>() == PortType(INVALID_PORTTYPE - 1) ecount<PortType>() == int(INVALID_PORTTYPE) evalid(x) is true for all from emin() to emax() inclusive, else false enext(emax()) == eprev(emin()) == INVALID_PORTTYPE enext(CONFIG_FLASH) == ETHERNET, etc. eprev(ETHERNET) == CONFIG_FLASH, etc. estr(CONFIG_FLASH) == "CONFIG_FLASH", etc. estr(x) == "**INVALID**" if and only if evalid(x) is false |
ecount<>()
can't be used as a dimension for static arrays since the compiler considers it to be non-constant; in that case use EnumInfo<PortType>::count
.
Conduit-type enumeration (TBD)
When specified this enumeration will be set up like the port type.
Port list
Port list (PortList.hh
)
This class is a Borg-type singleton; the constructor makes a stateless object whose member functions access the true (shared) state defined elsewhere. The destructor destroys these stateless objects but does not touch the true state information. You can therefore just use the constructor whenever you need to access the One True List, e.g., PortList().head()
.
You can get a count of the number of ports or the first port on the list (the list can't be empty). The report()
member function will print informational messages in the system log which show the contents of the port list in brief form, one line per port.
Note |
---|
The location and form of the system log depends on how the system logging package was initialized at application startup. Client code making log entries is not aware of this initialization. |
A particular port may be looked up in several different ways:
- By its global ID index number, assigned in sequence starting from zero as ports are created.
- By its type number and the creation sequence index number within the type, e.g.,
(ETHERNET,0)
,(ETHERNET,1)
, etc. - By the number of the pipe conduit the port is connected to.
Lookup methods return the null pointer if the search fails.
Code Block | ||||
---|---|---|---|---|
| ||||
class PortList { public: PortList() {} ~PortList() {} int numChannels() const; Port* head() const; Port* lookup(PortType type, unsigned typeSeq) const; Port* lookup(unsigned idindex) const; Port* lookupByPipelookupByConduit(unsigned pipe) const; void report() const; ); |
Port (Port.hh
)
A port object represents a particular instance of a protocol plug-in. Each port object is created at system startup. Port objects live until system shutdown and may not be copied or assigned.
Each port creates and destroys VirtualChannel objects on demand. During its lifetime each VirtualChannel object has exclusive use of one of the ports port's virtual channel numbers; the virtual channel number becomes available again once the VirtualChannel object is deallocated. The client code may request a specific, unused virtual channel number for the type of port, e.g., a well-known TCP port number. The client may also allow the port to assign a number not currently in use by any VirtualChannel.
Every port object is a member of the linked list accessed though class PortList and may not be removed from the list. Use the next()
member function to iterate over the list.
A short name for the type and a short description of the port are also provided.
A "lost" counter is provided which counts the number of incoming frames that were discarded, for whatever reason, instead of being queued in a virtual channel.
Other information provided:
- The ID index number of the pipe associated with the port.
- The hardware version number of the associated plugin.
- The software version number of the associated plugin module.
The report()
member function produces detailed multi-line description of the port in the system log, including all platform-specific information.
Code Block | ||||
---|---|---|---|---|
| ||||
class Port { public: VirtualChannel* allocate(int portNum); VirtualChannel* allocate(); void deallocate(VirtualChannel *); unsigned lost() const; unsigned idindex() const; unsigned type() const; const char* name() const; unsigned typeSequencetypeIndex() const; unsigned pipeconduit() const; unsigned versionHard() const; unsigned versionSoft() const; Port* next() const; const char* description() const; void report() const; }; |
Virtual channel
Each VirtualChannel object is created by a port and is assigned a unique ID in the port's virtual channel number space.
Incoming frames on the associated port may be waited for and retrieved using the wait()
member function. Client code will normally keep a frame for a short time then give it back to the virtual channel they got it from using the virtual channel's giveBack()
member function. It's an error to try to give back a frame to a virtual channel which didn't produce it; the results of doing so will be unpredictable.
A virtual channel takes frames given to its send()
member function and queues them for output. The frame buffer is NOT reclaimed after sending nor does the plugin interface tell you when it's safe to re-use the buffer; that's up to the message protocol. For example, the PGP1 protocol used with Petacache flash memory allows the client to put an arbitrary transaction ID into every message sent, and every message produces a response incorporating the original transaction ID. The client can then track pending transactions and the send buffers associated with them.
Code Block | ||||
---|---|---|---|---|
| ||||
class VirtualChannel { public: unsigned idindex() const; void* wait(); void giveBack(void *frame); void send(void *frame); }; |
C++ interface for RCE boot code
Implementation
General
Bit numbering: Bit zero is the least significant bit of a value (contrary to the convention in PowerPC documents).
Namespaces
The namespace RCE::ppi
also contains implementation declarations but these don't appear in public header files but in package private headers (see below). Some names are declared in the same way for both Gen I and Gen II, others look different depending on the platform and some may not appear at all on a given platform.
Header files
All implementation code (including inline function definitions) appears somewhere under the directory rce/ppi/src/
. Generic code appears directly in src/
while platform specific code will appear in subdirectories gen1/
, gen2/
, and possibly i86-linux/
. Unit test code is in rce/ppi/test/
which has the same substructure as src/
. Some implementation is partly generic and partly platform specific so a filename may appear in several places.
Code Block | ||||
---|---|---|---|---|
| ||||
release/ rce/ ppi/ src/ Conduit.hh Conduit-inl.hh Conduit.cc ConduitType.hh ConduitList.hh ConduitList-inl.hh ConduitList.cc Port-inl.hh Port.cc PortList-inl.hh PortList.cc Configuration.hh ConfigurationSpace.hh Factory.hh VirtualChannel-inl.hh VirtualChannel.cc gen1/ PortList.cc Configuration.cc ConfigurationData.hh Factory.cc etc. gen2/ etc. etc. test/ testPpi.cc gen1/ testPpi.cc etc. |
Conduit list
All the instances of ConduitList
created by the default constructor are empty; the non-static member functions all use the static head-of-list pointer and never use this
. The constructor that takes a Configuration\&
argument actually builds the list before returning the empty object.
Code Block | ||||
---|---|---|---|---|
| ||||
class ConduitList { public: ConduitList(); explicit ConduitList(const Configuration&); Conduit* head() const; unsigned numConduits() const; Conduit* lookup(unsigned long long pins); private: static Conduit* m_head; static int m_Conduits; }; |
Conduit
Code Block | ||||
---|---|---|---|---|
| ||||
class Conduit { public: unsigned id() const; ConduitType type() const; unsigned version() const; unsigned long long pins() const; Conduit* next() const; protected: friend class Configuration; Conduit(unsigned id, ConduitType, unsigned version, unsigned long long pins); ~Conduit(); void next(Conduit*); }; |
Port list
All the instances of PortList
}} created by the default constructor are empty; the non-static member functions all use the static head-of-list pointer and never use this
. The constructor that takes a Configuration\&
argument actually builds the list before returning the empty object.
Code Block | ||||
---|---|---|---|---|
| ||||
class PortList { public: PortList(const Configuration&); private: static Port* m_head; static int m_ports; }; |
Port
The protected constructor takes a PPI definition object plus other information supplied by the factory code and the system startup code. There are also protected member functions to increment the "lost" count and to set the "next" pointer (even though PortList
is a friend we prefer to use an accessor for the "next" pointer).
Code Block | ||||
---|---|---|---|---|
| ||||
class Plugin;
class Port {
public:
virtual unsigned lookup(void*);
protected:
friend class Configuration;
Port(
unsigned id,
PortType type,
unsigned long long pins,
unsigned versionHard);
virtual ~Port() = 0;
void incLost();
void next(Port*);
private:
Port(const Port &);
Port& operator=(const Port &);
};
|
The name and description string pointers must refer to non-automatic storage since the object must remain valid until shutdown.
Virtual channel
The first private constructor stores the given Port* and virtual channel number.
Code Block | ||||
---|---|---|---|---|
| ||||
class VirtualChannel { private: friend class Port; VirtualChannel(Port*, unsigned portNum); ~VirtualChannel(); VirtualChannel(const VirtualChannel&); VirtualChannel& operator=(const VirtualChannel&); private: Port* m_port; unsigned m_id; }; |
Port factory
The abstract base class for objects that manufacture Port instances. Port factories are created during system startup and last until system shutdown. Assignment or copying of instances is forbidden.
The derived classes use the protected constructor to save the port type name, software version number, recommended number of reception buffers per port and the port description string.
Code Block | ||||
---|---|---|---|---|
| ||||
class Factory { public: PortType type() const; const char* name() const; unsigned versionSoft() const; unsigned numBuffersAdvised() const; const char* description() const; VirtualChannel* createChannel( Plugin*, unsigned channelId, unsigned pipeId); ) = 0; protected: Factory( unsigned type, const char* name, unsigned versionSoft, unsigned numBuffersAdvised, const char *description ); virtual ~Factory() = 0; }; |
Plugin software modules
Each module's entry point is named rce_appmain
; this symbol is recognized by the module build system which places its value in the transfer address slot of the module's ELF header. The prototype of the entry point function is
Code Block | ||||
---|---|---|---|---|
| ||||
extern "C" Factory* rce_appmain(); |
The system init code uses placement new to construct an instance of PluginModule atop the image of the module then calls the run()
member function to obtain the factory object.
Code Block | ||||
---|---|---|---|---|
| ||||
class PluginModule: public RCE::ELF::Module { public: typedef Factory* (*EntryPoint)(); Factory* run(); }; |
The module's entry point is run directly without creating a new thread
(otherwise we'd need synchronization in order to wait for the factory
to be produced).
Configuration table access
The same declaration is used for both Gen I and Gen II since it uses only references to classes Pipe
and Plugin
. The implementations are quite different, however.
Code Block | ||||
---|---|---|---|---|
| ||||
static unsigned EMPTY = 0xffffffff; class Configuration { public: Configuration(); ~Configuration(); void plugin(unsigned defNum, Plugin&); void pipe(unsigned lane, Pipe&); }; |
Gen I
Frames are still divided into header and payload sections. Each port manages its own pool of reception (import) buffers.
Location and format of the configuration tables
Configuration container zero in the configuration flash contains tables of information about the hardware and firmware; this information can't be gotten directly from the hardware and firmware.
To make alignment easier we use 32-bit fields wherever possible, even for 16-bit quantities such as virtual channel numbers.
The plugin table will come first, at the beginning of the container, immediately followed by the pipe table and the network address table.
Plugin table
The plugin table will have eight array elements, one for each possible PPI in the system. An element is considered unused if the type
field contains 0xffffffff
(EMPTY
) in which case all the other members of that element are zero.
Field name | Type | Description |
---|---|---|
type | uint32_t | The type number of the resulting Port object as given in the header ChannelTypes.hh |
lanes | uint32_t | Lane usage bitmask; bit N is set to indicate that lane N is used by this PPI |
version | uint32_t | The version number for the PPI firmware |
maxPorts | uint32_t | The size of the virtual channel number space |
pibs | uint32_t | PIB usage bitmask |
pebs | uint32_t | PEB usage bitmask |
ecbs | uint32_t | ECB usage bitmask |
flbs | uint32_t | FLB usage bitmask |
importPayloadSize | uint32_t | The maximum size in bytes of an import frame payload |
importHeaderSize | uint32_t | The size of an import frame header |
exportPayloadSize | uint32_t | The maximum size in bytes of an export frame payload |
exportHeaderSize | uint32_t | The size of an export frame header |
Pipe table
In order to discover which pipe a PPI uses will you take the lowest lane number used by the PPI and use it to index the pipe table, which has 32 array elements. A pipe ID of EMPTY
indicates an unused element, otherwise the ID of the pipe is given. A pipe ID may contain subfields telling what the pipe is connected to or it may be a simple index into a further table that gives such information.
Field name | Type | Description |
---|---|---|
pipeId | uint32_t | Pipe ID number |
Network address table
Ethernet plugins for Gen I RCEs need to have their MAC and IP addresses supplied to them. The table has eight array elements with each unused element having a MAC address of 0xffffffff ffffffff
. The information is read from configuration flash and stored in main memory from whence an API yet to be defined will retrieve it.
Field name | Type | Description |
---|---|---|
mac | uint64_t | Ethernet MAC address, right justified |
ip | uint32_t | IPv4 address in network byte order |
pipeId | uint32_t | Pipe ID number |
Storage of plugin software modules
Only a few different types of protocol plugins are found on Gen I systems so each type is assigned to a fixed Configuration container:
Plugin type | Container name |
---|---|
ETHERNET | 1 |
PGP1 | 2 |
PGP2 | 3 |
future expansion | 4-10 |
Later a container may be used for CONFIG_FLASH, if we ever manage to make a usable wrapper for the FCI package makes it look like another plugin.
Class Buffer
Frames buffers for imports will be allocated by the system startup code; those for exports will be allocated by the client software. Before a frame buffer may be used for either import or export an instance of class Buffer must be created at the beginning of the buffer using placement new.
A Buffer contains regions of fixed size used in the management of the frame itself and its buffer:
- The firmware's in-memory descriptor. The firmware TDE for the frame points to the descriptor.
- Status. The firmware writes operation completion status here.
- One or more links used to make singly-linked lists.
Immediately following the end of the Buffer we have first the frame's header and then its payload.
The links, descriptor and status areas have the same sizes for all port types.
The descriptor must begin on a 64-byte boundary; for ease of layout the entire buffer also has that alignment. The buffer should also begin on a cache line boundary in order to reduce cache thrashing but since a cache line is only 32 bytes long this requirement is already met.
Buffer instances may not be copied or assigned.
Code Block | ||||
---|---|---|---|---|
| ||||
class Buffer { public: Buffer(void *bufferAddress); Buffer* link(unsigned linkNum) const; const Descriptor& descriptor() const; const OpStatus& status() const; char* header() const; char* payload() const; enum {NUM_LINKS = 4}; private: Descriptor m_descr; OpStatus m_status; Buffer* m_link[NUM_LINKS]; }; |
Use case: System startup
- Boot code:
- Loads and starts the system core.
- System core
- Initializes the CPU.
- Initializes RTEMS.
- Initializes any extra C++ support.
- Sets up the MMU's TLB and enables the MMU.
- Creates the default instance of the dynamic linker.
- Reads Configuration container zero.
- Saves the ethernet MAC table for later use.
- Reads, links and runs the required plugin modules, creating the Factory objects.
- Creates the import buffer pools for all plugins (using information supplied by the factories).
- Uses the Factories to create all required Port objects (the Factories know how to find the buffer pools).
- Initializes the network (uses the network address table).
- Initializes each ethernet port.
- Initializes IP, UDP, TCP and BSD sockets.
- Gets any required DHCP leases.
- Loads and links the application code using the default dynamic linker.
- Calls the application entry point.
Use case: Frame import
TBD.
Use case: Frame export
TBD.
Gen II
Location of the configuration tables
The PpiDefs and PipeDefs table entries will be obtained directly from the hardware via DCR registers.
Protocol plug-ins.
Protocol plug-ins will no longer be implemented using PIC blocks.
Frames
The old division of frames into header and payload will no longer be made. All input frame buffers for all channels will be held in a single free-list maintained by the firmware; when asked for a buffer of a certain size it allocates the one whose size is the best match.