Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Offical no

.

"Catch-all channel"

The Factories table:

Table of Content Zone
locationtop
typelist

Concepts

The low-level interface to an RCE's protocol plug-ins uses abstractions called ports, frames, channels, lanes, pipes and factories.

A port is the RCE end of a communications link similar in concept to a BSD socket. Ports are globally visible but not MT-safe; at a given time at most one task may be waiting for data or receiving data from any given port.

Ports deliver data as frames. The exact content of a frame depends on the transmission protocol being used but the port recognizes a broad division into header and payload. One frame corresponds to one message on the I/O medium and is delivered in a single buffer. In other words all ports 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 port contains a queue of frames which have arrived and not yet been consumed by the application.

A channel is a hardware I/O engine capable of DMA to and from system RAM, i.e., a protocol plug-in. An RCE has at most eight channels. Most channels will make use of one or more of the Multi-Gigabit Tranceiver modules (lanes) available in firmware, though some may simply offer access to on-board resources such as DSPs. Each channel has its own port space where each port is identified by a 16-bit unsigned integer. Each port represents a different source of incoming data such as a UDP port or a Petacache flash lane. The actual number of ports available on a channel depends on the channel type and may be as low as one.

With one exception there is a one-to-one correspondence between (channel, port no.) and port objectss. The exception is a "catch-all" port, which receives data that doesn't "belong" to any other port in the system. There is a catch-all pseudo-channel available with a limit of one port; creating a port on this channel will create a catch-all port. If no catch-all port exists then an orphan frame is dropped and an error message is placed in the system log.

All the lanes (if any) of a given channel go to one the outputs (pipes) of the rear transition module. This mapping is fixed by hardware.

Each type of channel has both an offical (unsigned) number and an official short name such as "eth" or "config". Either may be used to look up the corresponding Channel object after system startup. Channels that differ only in the number of lanes they use, e.g., 10 Gb/s ethernet (4) and a slower ethernet (1) will have the same channel type, in this case "eth".

The factory code that creates the right kind of Channel and Frame objects for a given type of channel may be already part of the system core or it may be in a container in configuration flash. In the latter case the code must be loaded, relocated and bound to the system core before its first use. An entry point is called in each such loaded factory code module which will register it in a central table using a function exported by the core for this purpose. Pre-loaded code must also be registered using this function. Factory code returns values of Channel* or Frame* though the actual objects pointed to are tailored for the specific channel type.

The information needed to initialize the channels is found in configuration flash and/or by probing the hardware.

Channel information in configuration flash

Data container zero in the configuration flash contains tables providing all the information needed to make all channels ready. This includes references to type-specific factory code but not the code itself; other containers will hold that. In addition the tables provide some extra information not actually needed for setup but required to print a summary of what the protocol plug-ins provide.

The tables are called Channels, Channel Types, Factories, Data Paths, Buffers and Strings. Except for Strings each table is an array of plain-old-data structs with the first field being a key value normally equal to the array index. The keys are there to allow tables to refer to one another's entries. A key equal to 0xffffffff signifies the end of the table in which case none of the other fields in the struct have any guaranteed values and should never be read. Thus you'll generate end-of-table sentinels automatically when you erase a flash block before writing tables into it, provided you leave at least one 32-bit word of unwritten space at the end of each table. If you write all the tables in one go then you must provide the end-of-table markers explicitly.

The Strings table is like an ELF string table; NUL-terminated ASCII strings laid end to end. Certain standard strings such as the short-names of channels are at the front of the table with only one instance of each string present. The other tables refer to a string by giving the offset of its first character in the Strings table.

General layout of the container contents

The first words of the container are the 32-bit offsets in the container to the starts of the tables in the following order:

Offset of Channels

Offset of Channel TypesOffset of Factories

Offset of Data Paths

Offset of Buffers

Offset of Strings

Each of the offsets should be divisible by four and if the corresponding table is present must be greater than zero.Only the Factories and Buffers tables are needed for Virtex-5,6 RCEs so the other offsets are zero.

After the offsets come the tables themselves. No particular order is required, though since String table entries have variable length it's most convenient to place it last.

To make alignment easier we use 32-bit fields wherever possible, even for 16-bit quantities such as port numbers. All of the declarations for the configuration tables are in namespace RCE::config.

Code Block
none
none
typedef uint32_t StringOff; // Offset within the Strings table.

The Channels

table (Virtex-4)

table

Most of this table will specify the firware resources used by the channel. The type and number of ports supported are also given.

Code Block
none
none
struct Channel {
    uint32_t  key;
    unit32_t  typeKeyofficialType;   // The official channel  // Ref. to Channel Types tabletype number.
    unit32_t  lanesUsed;      // Bit-mask of lanes used.
    uint32_t  blocksUsedpibsUsed;       // Bit-mask of Pending PICImport blocksBlocks used.
    uint32_t  numPortspebsUsed;       // SizeBit-mask of port-number spacePending Export Blocks used.
    StringOff description;
};

The Channel Types table (Virtex-4)

Code Block
nonenone

struct ChannelType {
    uint32_t  ecbsUsed;       // Bit-mask of Event Completion Blocks used.
    uint32_t  key;flbsUsed;       // Bit-mask of Free List Blocks used.
    uint32_t  officialNumber;
numPorts;        StringOff officialName;// Size of port-number space.
    StringOff description;
};

The Factories table

(Virtex-4,5,6)

In In the Factories table a container name of 0xffffffff marks pre-loaded factory code; otherwise the name is used to find the required container as specified in the RCE document.

Code Block
none
none
struct Factory {
    uint32_t  key;
    uint32_t  channelTypeKey;
    uint32_t  containerName;
    StringOff description;
};

The Data Paths table

(Virtex-4)

The lanes (MGTs) allocated to a channel will all feed a particular output (pipe) on the backplane. Those channels that have no lanes will not appear in this table.

Code Block
none
none
struct DataPath {
    uint32_t  key;
    uint32_t  channelKey;
    uint32_t  pipeNum;    // Where the channel's signals "come out."
};

The Buffers table

(Virtex-4,5,6)

Each channel comes with a recommendation for the number and size (in bytes) of buffers to be allocated in non-cached memory. Those recommendations are in this table. Note that these are only recommendations ; the system initialization procedure needs to consider all the recommendations together along with the amount of memory actually available.

Code Block
none
none
struct Buffer {
    uint32_t key;
    uint32_t channelKey;
    uint32_t bufferCount;
    uint32_t bufferSize;
};

Example configuration tables

Here's what the tables would look like for an Virtex-4 RCE that defines nothing but an ethernet LAN and configuration flash, which are standard for all RCEs. We assume that the ethernet uses lanes 0-3, PIB 0, PEB 0, ECB 0, FLB0 and feeds pipe zero.

The Channels table:

KeyType key

Official type no.

Lanes

PIBs

PEBs

ECBs

FLBs

Ports

Firmware version

Description

0

0

0xf

1

1

1

1

65536

1

"LAN"

1

1

0

0

0

0

0

1

1

"Configuration flash"

2

2

0

10

1

0

0

0

1

1

"Catch-all channel"

Ethernet allows 65536 ports so as not to restrict the port space of UDP or TCP.

The Channel Types table:

Key

Official name

Description

0

0

"eth"

"ethernet"

1

1

"config"

"Configuration flash"

2

2

"catch-all"

Key

Type key

Container name

Description

0

0

<name1>

"Virtex-4 ethernet"

1

1

0xffffffff

"Virtex-4 configuration flash"

2

2

0xffffffff"

"Virtex-4 catch-all channel"

The Data Paths table:

Key

Channel key

Pipe no.

0

0

0

The Buffers table:

Key

Channel key

No. of buffers

Buffer size

0

0

32

1518

1

1

16

2048

Here we assume that the firmware delivers all ethernet framing bytes as well as the payload and that jumbo frames are not allowed. We assume that the formware delivers configuration flash data in units of pages and that a page is 2K bytes long.

How the tables appear in RAM

We copy the tables without alteration and set the members of the following structure:

Code Block
none
none
struct Tables {
    unsigned numChannels;
    Channel *channel;      // Pointer to first element of table.
    unsigned numChannelTypesnumFactories;
    ChannelTypeFactory *channelType;
    unsigned numFactories;
    Factory *factoryfactory;
    unsigned numDataPaths;
    DataPath *dataPath;
    unsigned numBuffers;
    Buffer *buffer;
};

Use case: System startup

for Virtex-4

  1. Boot code
    1. Loads and starts the system core.
  2. System core
    1. Initializes the CPU.
    2. Initializes RTEMS.
    3. Initializes any extra C++ support.
    4. Sets up the MMU's TLB and enables the MMU.
    5. Registers the factories for configuration flash and catch-all.
    6. Creates the configuration flash channel.
    7. Allocates uncached RAM for config. flash pages.
    8. Enables the configuration flash channel.
    9. Copies the Channels table and related info from configuration flash into RAM.
    10. Creates the default instance of the dynamic linker.
    11. Loads and links the factory code marked as loadable in the Factories table.
    12. Calls all factory code entry points so that all factories are registered.
    13. Walks the Channels table and creates all Channel objects.
    14. Walks the Buffers table and allocates uncached memory for I/O buffers.
    15. Enables all Channel objects not already enabled.
    16. Loads and links the application code using the default dynamic linker.
    17. Initializes the network.
      1. Initializes each ethernet channel.
      2. Initializes IP, UDP, TCP and BSD sockets.
      3. Gets a DHCP lease if required.
    18. Calls the application entry point.
  3. Factory code entry point.
    1. Registers with the core two functions that create the right kind of Channel and Factory objects.

Use case: Frame import

Prior to this the code wishing to use the port has associated a consumer task with it. That task is blocked or idling waiting for new frames.

  1. Plug-in
    1. Writes the frame via DMA.
    2. Causes a frame-arrival interrupt.
  2. ISR
    1. Reads the cause-of-interrupt register and passes its value to the I/O dispatcher task.
  3. I/O dispatcher task
    1. Finds the Channel objects based on cause-of-interrupt
    2. Channel object
      1. Gets the descriptor for the frame from the right firmware queue.
      2. Builds a Frame object of the right kind using the descriptor.
    3. Frame object
      1. Examines the frame data to determine the right port.
    4. Port object
      1. Enqueues the frame.
      2. Wakes up the consumer task if necessary.
  4. Consumer task
    1. Consumes at least one of the enqueued frames.
    2. Yields or blocks.

Running the Channel, Frame and Port object code in a dispatcher task rather than the ISR keeps the latter simple; it doesn't have to know about system data structures, just hardware. Simplicity should translate to speed of response. The dispatcher task can be normal C++ code that runs with the MMU on, which an ISR by default doesn't.

Sub-case: Full cooperative multitasking

We assign the same priority to the I/O dispatcher task and all the consumer tasks. All of these tasks remain in the ready queue; each puts itself at the back of ready queue (yields) when it finds that its input queue is empty or after it has completed a certain amount of work. The consumer tasks and the I/O dispatcher task don't need to synchronize their communication because no one of them can preempt another.

The communication between the ISR and the I/O dispatcher task needs some synchronization since the ISR can preempt the dispatcher task at any time. We need not resort to semaphores or other locks, though; there are relatively simple lock-free algorithms we can use. The dispatcher task, once it comes to the front of the ready queue, loops until it manages to read its input queue without detecting interference from the ISR, then either yields immediately if the queue was empty or after performing one or more dispatches. As an alternative the dispatcher can disable interrupts for the short time it takes to check its queue.

Sub-case: Preemptive multitasking

If the I/O dispatcher is given a higher priority than consumer threads then it must synchronize its communication with them. It also won't just be able to yield when it has nothing to do since it will go onto what amounts to a different ready queue from the consumer tasks, one that is examined first. The dispatcher task would always run, starving the consumers. The dispatcher task would have to actually block itself and the ISR would have to unblock it.

If the consumer tasks don't all have the same priority then they too will have to block themselves and be unblocked by the dispatcher task.

If we use time-slicing then we don't have to be so careful in deciding when each task should yield but then all inter-task communication will require synchronization. If priority assignment is non-uniform then we need to use explicit blocking and unblocking instead of yielding.

Use case: Frame export

Low-level API

Official RCE channel type numbers and names

These are given in a header file made available to both core and application code. The numbers are members of an enumeration and the names are given in a static array.

Code Blocknonenone

namespace RCE::channel {

    enum {
        ETHERNET,
        CONFIG_FLASH,
        CATCH_ALL,
        ...
    } Numbers;

    const char *names = {
        "eth",
        "config",
        "catch-all"
        ...
    };
}

TBD.

Use case: Frame export

TBD.

Low-level API

Official RCE channel type numbers and names

These are given in a header file made available to both core and application code. The numbers are members of an enumeration and the names are given in a static array.

Code Block
none
none

namespace RCE::chantype {

    enum {
        ETHERNET,
        CONFIG_FLASH,
        CATCH_ALL,
        ...
    } Numbers;

    const char *name = {
        "eth",
        "config",
        "catch-all"
        ...
    };

    const char *description = {
        "ethernet",
        "configuration flash",
        "catch-all channel",
        ...
    };
}

Channel factory

The abstract base class for objects that manufacture Channel instances. Channel factories are created during system startup and last until system shutdown. Assignment or copying of instances is forbidden.

Code Block
none
none

class ChannelFactory {
public:
    ChannelFactory();
    virtual Channel* create(
        unsigned ident,              // Assigned by init. code.
        unsigned channelsTableKey,
        RCE::config::Tables&) = 0;
    virtual ~ChannelFactory();
};

Channel type registry

An instance of this class will be exported by the core code using some design pattern such as Singleton or functional equivalent. This instance is created during system startup and lasts until system shutdown. Assignment or copying of instances is forbidden.

Code Block
none
none

class ChanneTypeRegistry {

    ChannelTypeRegistry();

    // Register a Channel factory using its official RCE type number.
    void register(
        unsigned officialNumber,
        ChannelFactory &,
    );

    ChannelFactory& channelFactory(unsigned officialNumber);

    unsigned officialNumber(const char *officialName);
);

Channel

A channel object manages a set of I/O buffers. At the beginning of each buffer the channel will build a SuperFrame object. Each channel object is created at system startup, initially in a disabled state. Afterward the system startup code will allocate the buffers, feed them to the channel objects and then enable them. Channel objects live until system shutdown.

Each channel creates and destroys Port objects on demand. Each port is assigned a unique ID number. The client code may request a particular ID in the legal range for the type of channel, e.g., a well-known TCP port number. The client may also allow the channel to assign an ID not currently in use by any port.

A channel knows how to derive the ID number of the Port that will receive a frame from the frame header.

Channel and its derived classes do not allow copying or assignment of their instances.

Code Block
none
none

class Channel {
public:
  // Set the system's channel ID anf the typer number. Leaves
  // the Channel in the disabled state.
  Channel(unsigned systemAssignedId, unsigned typeNum);

  unsigned id() const;

  unsigned typeNum() const;

  // Create a Port with a specified port number.
  // Returns 0 if the number is already taken.
  virtual Port* createPort(int portNum) = 0;

  // Create a Port with a number assigned by the channel.
  // Returns 0 if no port number is free.
  virtual Port* createPort() = 0;

  virtual void destroyPort(Port *) = 0

Channel and Frame factories

These are the abstract base classes.

Code Blocknonenone

class ChannelFactory {
public:
    ChannelFactory();
    virtual Channel* create(
        unsigned ident,   // Assigned by init. code.
        unsigned channelsTableKey,
        RCE::config::Tables&) = 0;
    virtual ~ChannelFactory();
};

Channel type registry

Code Blocknonenone

class ChanneTypeRegistry {

    ChannelTypeRegistry();

    // Register a Channel factory using its official RCE type number.
    void register(
        unsigned officialNumber,
        ChannelFactory &,
    );

    ChannelFactory& channelFactory(unsigned officialNumber);

    unsigned officialNumber(const char *officialName);
);

An instance of this class will be exported by the core code using some design pattern such as Singleton or functional equivalent.

Class Frame

  • Instance responsibilities.
  1. Ownership of a frame buffer. Once created a Frame instance must be kept alive as long as the frame buffer is in use by the client code. The destructor of the Frame subclass will return buffers to the I/O pool. For Virtex-4 each Channel instance has its own I/O pool; for Virtex-5,6 the pool is global.
  2. Furnish on request the locations and sizes of the following sections of the frame buffer:
    1. Links.
    2. Descriptor.
    3. Status.
    4. Header.
    5. Payload.
  3. Calculate a channel port number from the header contents.

Frame instances are created by Channel instances.

Channel

Code Block
nonenone

class Channel {
  // Class responsibilities
  // ----------------------
  // Maintain the following mappings:
  // 1) PIC block number to Channel (1-to-1).
  // 2) Global Channel ID to Channel (1-to-1).
  // 3) ChannelClass to Channel (1-to-many).
  //
  // Instance responsibilities
  // -------------------------
  // Create and destroy Ports. Allocate and free port numbers. Maintain
  // the following mappings:
  // 1) port number to Port (1-to-1).
  // Give out the minimum frame buffer size and the recommended number of
  // frames for the channel.
public:
  // Install a new channel of the given class and assign it the next available
  // ID number. Associated with the class are two DCR addresses, one for
  // imported frames and one for exported frames. Presumably these are the
  // DCRs used to dequeue and enqueue frame descriptors, respectively.
  // Using this constructor automatically makes the object findable
  // by any of the static member functions that perform searches.
  Channel(const ChannelClass &ctype,
          unsigned importDcr,
          unsigned exportDcr);

  // Return the ID assigned by the constructor.
  unsigned id() const;

  // Return the DCR address associated with importing frames.
  unsigned importDcr() const;

  // Return the DCR address associated with exporting frames.
  unsigned exportDcr() const;

  // Return the class of the channel.
  const ChannelClass &channelClass() const;

  // Create a new Port object for this Channel, assigning its
  // global and port nos.
  void createPort();

  // Destroy a Port. It is an error to give a predefined Port or
  // a Port not belonging to this Channel (throws std::logic_error).
  void destroyPort(Port *);

  // Return the Port associated with the port number, or return 0.
  Port *findPort(unsigned) const;

  // Examine the Frame header to see which of this Channel's Ports
  // the Frame should belong to. If such a Port is found then
  // enqueue the frame on it and return true. If no matching Port
  // can be found then return false.
  virtual bool enqueueFrame(Frame*) const;

  // The minimum Frame buffer size for this channel.
  virtual unsigned frameSize() const;

  // The recommended number of Frame buffers to allocate for this channel.
  virtual unsigned numberOfFrames() const;

  // Return 0 or the ChannelPort whichwith has the given ID, or return 0number.
  staticPort Channel* findChannelfindPort(unsigned channelIdportNum) const;

  // ReturnManage thea m'th Channel that belonging to the given class,
  // assuming that Channels are tested in ascending order by IDnew buffer.
  void addBuffer(void *);

  // numbers. The first matching Channel corresponds to m=0, etc. Return 0Reclaim a buffer that was given out by one of the Ports
  // ifcreated noby suchthis channel exists.
  static Channel* findChannel(const ChannelClass &ctype,
                              unsigned m);

void reclaimBuffer(void *);

  void enable();

  void isEnabled() const;

  // Return -1 or the number of the port to which the frame should go,
  // Returnassuming the frame Channelbelongs to withthis thechannel.
 given DCRvirtual address, or return 0.int getPortNumber(void *frameHeader) const;

  staticvirtual Channel*unsigned findChannelByDcrAddressframeHeaderSize(unsigned) const;

privateprotected:

  // Does nothing because Channels last until the next system shutdown.
  virtual ~Channel();

};


class Port {
  // Class responsibilities
  // ----------------------
  // Maintain a mapping of Port IDs to Ports (1-to-1).
  //
  // Instance responsibilities
  // -------------------------
  // Maintain a queue of Frames. Manage a consumer task, letting it
  // put itself to sleep if the queue is empty and waking it up if
  // needed when new Frames are queued.
public:

  // Return local number local to the Channel owning this Port.
  unsigned localPortNum() const;

  // Return the Channel that owns this Port.
  Channel *channel() const;

  // Put Frames on the queue maintained by this Port. Wake up
  // any consumer task if needed. The argument is the first
  // member of a list of Frames to enqueue.
  void enqueueFrames(Frame*);

  // Return a pointer to a Frame which is the first in a list
  // of Frames removed from the queue. If no Frames are available
  // then block set the consumer task ID and block until at least one
  // Frame is ready.
  // TDB: Needs a way to indicate that the Port has been
  // destroyed (a special Frame instance? Frame flags?)
  Frame *dequeueFrames();

  // Destroy this Port by calling upon the Channel that owns it.
  void destroy();

  // Return the Port with the given global ID, or return 0.
  Port *findPort(unsigned);

private:
  friend class Channel;

  // Create a port for the given Channel and port. The next available
  // global Port ID number is automatically assigned.
  Port(Channel *owner, unsigned localPortNum);

  ~Port();

};

Frames
 system shutdown.
  virtual ~Channel();

};

Class Port

Remembers the Channel that created it.

Allows client code to wait for new frames.

Reclaims frames that the client code has finished using AND that were provided by this particular port.

Code Block
none
none

class Port {
public:
  Port(Channel &, unsigned portNum);
  Channel& channel() const;
  unsigned portNum() const;
  virtual void* wait() const; // Returns a pointer to the frame header.
  void reclaim(void *frameHeader) const;
};

API implementation classes

These classes are not exposed to the client but are usde internally by the API.

Class SuperFrame

A SuperFrame instance is placed at the beginning of a buffer that will also hold the frame proper immediately after the SuperFrame. The instances are created at system startup and live until system shutdown.

A SuperFrame contains regions of fixed size used in the management of the frame itself and its buffer:

  • One or more links used to make singly-linked lists.
  • The firmware's in-memory descriptor. The firmware TDE for the frame points to the descriptor.
  • Status. The firmware writes operation completion status here.

Immediately following the end of the SuperFrame buffer in we have first the frame's header and then its payload.

The links, descriptor and status areas have the same sizes for all channel 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.

SuperFrame is used internally by the API; no client code is ever given any of its instances.

Code Block
none
none

class SuperFrame {
public:
    SuperFrame(void *bufferAddress);
    SuperFrame* link(unsigned linkNum) const;
    const Descriptor& descriptor() const;
    const OpStatus& status() const;
    char* header() const;
    char* payload() const;
private:
    SuperFrame* link[N];
    Descriptor descr;
    OpStatus status;
    // The frame header starts here.
};