The ONL NPR Tutorial

NPR Tutorial >> Writing A Plugin TOC

New Window?

Tour of a Simple Plugin

Contents

Useful Files

The source code for all of the predefined plugins are in subdirectories of ~onl/npr/plugins/ in the ONL filesystem. For example, ~onl/npr/plugins/mycount/ is the directory for the mycount plugin, and ~onl/npr/plugins/mycount/mycount.c is the source file for the plugin. These files are important to the plugin developer because most developers start from the source code for a plugin that serves as the starting point for the code. All of the source code have the same base, and the null plugin is the required plugin code stripped of all functionality other than packet forwarding. Every plugin directory has a README file that describes the plugin and the degree to which the plugin has been tested. Most of these plugins are summarized in the table in the NPR Tutorial page " NPR Tutorial => Router Plugins => Predefined Plugins".

As discussed later, the files in ~onl/npr/ may also be worthwhile: the .h files in the subdirectory pluginFramework/ contain the functions that make up the plugin API. Also, reading the code may give you a better understanding of how plugins work. And sometimes, you can get copy the code and modify it for your own use.

 

A Few Caveats

 

General Structure of a Simple Plugin

The source code of most predefined plugins is organized into the following segments:

We now go through the mycount plugin source code.
 

The mycount Plugin

[[ mycount-2npr.png Figure ]]

We installed the mycount plugin at NPR 1 (left NPR) and directed all packets coming into port 1.1 to go to the plugin. The plugin counts the packet by incrementing two counters and then forwards the packet onto the Queue Manager. It also displays a debug message whenever it gets a packet and responds to control messages from the RLI. It counts packets in two ways: 1) by incrementing PluginCounter 0 which can be easily displayed in a chart; and 2) by incrementing a normal variable whose value can be displayed by sending the plugin a control message requesting the value of the counter.

[[ mycount-charts-etc-resize.png Figure ]]

The figure (above) shows the effect of sending 10 ping packets from n1p1 to n2p1. The chart shows PluginCounter 0 increasing from 0 to 10 and was obtained through the NPR 1 => Monitoring => Plugin Counter menu item. The Plugin Command Log window shows the value of the variable pkt_count which was displayed when we sent a get command to the plugin. The xterm window shows the debug messages that were sent from the plugin a meta-packet was handled by the handle_pkt() routine. These messages were stored in the file debug.log after configuring the debugging by selecting NPR 1 => Plugin Debugging. Note that the packet count is displayed as a hexadecimal number (e.g., 'pkt xa' is referring to packet 10 decimal).

 

The Plugin API and Helper Functions

At the beginning of the mycount plugin code (past the initial comment lines), we see the following two lines:

#include "plugin_api.h"
#include "./plugin_helpers.h"
The file ~onl/npr/pluginFramework/plugin_api.h also includes the file ~onl/npr/pluginFramework/plugin_dl.h. These files together define functions for reading meta-packets from the plugin input rings as well as writing meta-packets to the input rings for other components like the Queue Manager. They also contain functions for converting from unsigned long to hexadecimal and decimal characters which is the format used in debug messages and control messages.

The file ./plugin_helpers.h contain three helper functions:

This file primarily exists because the plugin API does not have these functions as of the writing of this tutorial. In all likelihood, these functions will be included as part of the API at some future date. The ./plugin_helpers.h can be found at ~onl/npr/pluginFramework/plugin_helpers.h. The two functions helper_ultohex_lmem() and helper_ultodec_lmem() follow the naming convention of appending the strings "lmem", "sram" and "dram" to indicate that the parameters are stored in local memory, SRAM and DRAM respectively.
 

Thread-Specific Global Variables

The next group of lines we see are the thread-specific global variable declarations and should not be modified:

__declspec(gp_reg) int dlNextBlock;  // where to send packets to next
__declspec(gp_reg) int dlFromBlock;  // where to get packets from
__declspec(gp_reg) int msgNextBlock; // where to send control messages to next
__declspec(gp_reg) int msgFromBlock; // where to get control messages from

// see ring_formats.h for struct definitions
volatile __declspec(gp_reg) plc_plugin_data ring_in;  // ring data from PLC
volatile __declspec(gp_reg) plugin_out_data ring_out; // ring data to next block

__declspec(local_mem) unsigned int timeout; // time (cycles) between callbacks
__declspec(gp_reg) unsigned int pluginId;   // plugin id (0...7)
The special qualifiers that appear above have the following semantics: If you do not include a __declspec() qualifier, the variable is assumed to be a normal variable to be allocated to SRAM.

Note that the above variables appear to be global (outside the scope of any of the functions) and they are. But also, they will actually be replicated for each thread when the plugin is loaded ... unless they have the shared attribute (which they don't here). The variables are used in the ways summarized in the following table:

Variable Usage
dlNextBlock Where to send the next meta-packet
dlFromBlock Where to get the next meta-packet
msgNextBlock Where to get the next control message
msgFromBlock Where to send the next control message
ring_in Incoming data from the PLC block
ring_out Data to be sent to the next block (QM)
timeout Callback timeout (not used here)
pluginId The plugin ME number, a number between 0 and 4

For example, dlNextBlock is usually set to the value of QM. QM is a constant that is defined in ~onl/npr/pluginFramework/plugin_dl.h to be 0 and stands for the Queue Manager block.

 

Main

Note that every thread is loaded with and executes a main() function. The main function does initialization and then executes a code segment based on the hardware context returned by the ctx() call.

	void main()
	{
1	    int c;
	
2	    plugin_init();
3	    dl_sink_init();
4	    dl_source_init();
	
5	    c = ctx();	// get the thread's hardware context# (0-7)
	
6	    if(c >= FIRST_PACKET_THREAD && c <= LAST_PACKET_THREAD)
7	    {
8	    	while(1)	{ handle_pkt(); }
9	    }
10	#ifdef MESSAGE_THREAD
11	    else if(c == MESSAGE_THREAD)
12	    {
13	    	while(1)	{ handle_msg(); }
14	    }
15	#endif
16	#ifdef CALLBACK_THREAD
17	    else if(c == CALLBACK_THREAD)
18	    {
19		while(1)	{ callback(); }
20	    }
21	#endif
	}
 

Plugin Initialization

The function plugin_init() contains the plugin initialization code that is common to all plugins. It initializes global variables (e.g., pluginId) and then calls plugin_init_user() to allow users to do their own initialization processing.

	void plugin_init()
	{
1	  timeout = 10000;
2	  dlNextBlock = QM;
3	  switch(__ME())
4	  {
5	    case 0x7:
6	      pluginId = 0;
7	      dlFromBlock  = PACKET_IN_RING_0;
8	      msgFromBlock = MESSAGE_IN_RING_0;
9	      msgNextBlock = MESSAGE_OUT_RING_0;
10	      break;
11	    case 0x10:
12	      pluginId = 1;
13	      dlFromBlock  = PACKET_IN_RING_1;
14	      msgFromBlock = MESSAGE_IN_RING_1;
15	      msgNextBlock = MESSAGE_OUT_RING_1;
16	      break;
17	   ... Cases 0x11, 0x12, 0x13, default ...
18	  }
19	  plugin_init_user();	// user hook
	}

The function plugin_init_user() contains the plugin initialization code that is specific to the plugin.

	void plugin_init_user()
	{
1	    if(ctx() == 0)
2	    {
3		pkt_count = 0;
4		debug_on = 1;
5		sleep(timeout);
6		helper_plugin_cntr_zero( 0 );
7	    }
	}
 

Handling a Packet

The function handle_pkt() is called from main() if the thread context is one of the packet handling threads.

	void handle_pkt()
	{
1	    dl_source_packet(dlFromBlock);
2	    default_format_out_data(dlNextBlock);
3	    handle_pkt_user();
4	    dl_sink_packet(dlNextBlock);
	}

The function handle_pkt_user() does the actual packet processing.

	void handle_pkt_user() {
1	    __declspec(local_mem) char dbgmsg[28]	= "got pkt x";
2	    __declspec(local_mem) char pkt_count_str[9];
3	    ++pkt_count;
4	    if( debug_on ) {
5		onl_api_int2str( pkt_count, pkt_count_str );
6		strcat_lmem(dbgmsg, pkt_count_str);
7		onl_api_debug_message(msgNextBlock, dbgmsg);
8	    }
9	    onl_api_plugin_cntr_inc(pluginId, 0);
	}
 

Handling a Control Message

The function handle_msg() is called from main() if the thread context is the message handling thread. Some plugins call handle_msg_user() to do the plugin-specific processing of control messages, but mycount does not.

	void handle_msg()
	{
1	    __declspec(gp_reg)	  unsigned int message[8];
2	    __declspec(local_mem) char in_msgstr[28];
3-7	    ... other declarations ...
8
9	    for( i=0; i<8; ++i ) { message[i] = 0; }
10	    dl_source_message( msgFromBlock, message );
11
12-14	    ... make a copy of the request message header ...
15
16	    onl_api_intarr2str( &message[1], in_msgstr );
17-31	    ... process the request which should be "z", "d" or "g" ...
32
33-37	    ... form the control message response header ...
38	    dl_sink_message(msgNextBlock, message);
	}

We see the general structure of handle_msg(). Line 10 inputs the message in raw form, and line 38 outputs the response message.

We now discuss the detailed parts of handle_msg(). We present the declarations without much discussion since they will be discussed when we describe the code that uses them.
1	    __declspec(gp_reg)	  unsigned int message[8];
2	    __declspec(local_mem) char in_msgstr[28];
3	    __declspec(local_mem) char pkt_count_str[28];
4	    __declspec(gp_reg)	  onl_api_ctrl_msg_hdr hdr;
5	    __declspec(gp_reg)	  int i;
6	    __declspec(local_mem) char out_msgstr[28];
7	    __declspec(local_mem) char BAD_op_err[8]	= "BAD OP";

However, note that the __declspec() portions of the declarations indicate the variables are to be either in general-purpose registers or local memory. Also, note that the type onl_api_ctrl_msg_hdr is defined as a union between an int and several bit fields. The definition of onl_api_ctrl_msg_hdr is in ~onl/npr/pluginFramework/plugin_dl.h.

Now we discuss the processing that precedes the command processing code. In most cases, these lines should never be changed.

9	    for( i=0; i<8; ++i ) { message[i] = 0; }
10	    dl_source_message( msgFromBlock, message );
11
12	    hdr.value = message[0];
13	    if( hdr.type != CM_CONTROLMSG )	{ return; }
14	    if( hdr.response_requested != 1 )	{ return; }

Now we discuss the command processing part. If we look only at the control structure of the code, it is obvious that the code is looking to see if the first character in the message payload is one of the three characters 'z', 'd' or 'g' These characters represent the commands zero counters, toggle debug flag, and get counts. There is currently no standard for the format of control messages other than that they are a sequence of strings. The mycount plugin uses the simplest form of command. The delay plugin uses much longer command names (e.g., "=counts", "delay=", "=delay", "reset") and uses SRAM instead of local memory for processing command names. The one difficulty with using local memory is that there is only 4,096 words of local memory.

17	    if( in_msgstr[0] == 'z' ) {
18		pkt_count = 0;
19		helper_plugin_cntr_zero( 0 );
20	    } else if( in_msgstr[0] == 'd' ) {
21		debug_on = (debug_on+1) % 2;
22	    } else if( in_msgstr[0] == 'g' ) {
23		helper_ultodec_lmem( pkt_count, pkt_count_str );
24		strcpy_lmem( out_msgstr, pkt_count_str );
25		if( onl_api_str2intarr(out_msgstr, &message[1]) < 0 )
26		    { return; } 
27	    } else {
28		strcpy_lmem( out_msgstr, BAD_op_err );
29		if( onl_api_str2intarr(out_msgstr, &message[1]) < 0 )
30		    { return; } 
31	    }

The code that sets up the header of the reply message is straightforward, and you should not have to modify these lines.

33	    hdr.type = CM_CONTROLMSGRSP;
34	    hdr.response_requested = 0;
35	    hdr.num_words = 7;
36	    message[0] = hdr.value;

You should now have a better appreciation of how to write a simple plugin in which the packet handling involves getting a meta-packet, doing some simple counting, and then sending the meta-packet onto the Queue Manager.

 

Some Comments on the Makefile

There are a number of issues that we have glossed over or just ommitted. We explain the meaning of the lines in the file Makefile shown below to clarify one such issue.

	# set spill based on your spilling needs
	#   8 = spill to Local Memory then SRAM
	#   7 = no spill
	#   6 = spill to SRAM only
	#   5 = spill to Local Memory then Next Neighbor regs
	#   4 = spill to Next Neighbor regs then Local Memory
	#   3 = spill to Local Memory only
	#   2 = spill to Next Neighbor regs only
	#   1 = spill to Next Neighbor regs then Local Memory then SRAM
	#   0 = spill to Local Memory then Next Neighbor regs then SRAM
	SPILL=3

All but the last line are comments. The mycount Makefile has chosen SPILL=3 which means that when the compiler runs out of registers when attempting to allocate registers to variables, it will try to use local memory. And if local memory runs out of space, the compiler will output an error message and quit. This choice can be significant if you were trying to write a larger plugin because the local memory in each microengine contains only 640 32-bit words. Luckily, this is not a problem with the mycount plugin.

One reason for choosing the SPILL=3 option is that the plugin will not use additional SRAM. Later, we will see that the delay plugin does use SRAM, and we will have to change the Makefile to avoid SRAM reference conflicts if we plan to load the plugin onto more than one ME. Because the mycount plugin doesn't use additional SRAM, we could load the mycount plugin into any or all of the five plugin MEs without worrying about SRAM usage by the compiler.

 

What's Left To Learn?

We now need to cover three topics to give a more complete view of NPR plugins:

The pages to follow discuss these topics.

 Revised:  Thu, Nov 13, 2008 

  
  

NPR Tutorial >> Writing A Plugin TOC