Programming Guide
Getting started
Installation
The xtcp component can be found at:
To install follow the instructions found on the community wiki at
Compilation and Demo Applications
The standalone XMOS TCP/IP package is supplied with several demo applications. These all start with the prefix app_ and are described in the package README. This can be built in the XDE or from the command line by executing the command xmake all.
An example of the two-thread TCP plus Ethernet implementation can be found in the test_2_thread_example.
Source code structure
All the files for the stack are contains in the module_xtcp directory. The important header files that are used by applications are:
File |
Description |
---|---|
uip_server.h |
Header file containing prototype for uip_server(). |
uip_single_server.h |
Header file containing prototype for uipSingleServer(). |
xtcp_client.h |
Header file containing the client API described in Client API. |
An XTCP application (tutorial)
This tutorial walks through the a simple webserver application that uses the XMOS TCP/IP component. This can be found in the app_simple_webserver directory.
The toplevel main
The toplevel main of the application sets up the different components running on different threads on the device. It can be found in the file main.xc.
First the ethernet MAC is run on core 2. Details of the ethernet component can be found in [XEth10].
on stdcore[2]: { int mac_address[2]; ethernet_getmac_otp(otp_data, otp_addr, otp_ctrl, (mac_address, char[])); phy_init(clk_smi, #ifdef PORT_ETH_RST_N p_mii_resetn, #else null, #endif smi, mii); ethernet_server(mii, mac_address, mac_rx, 1, mac_tx, 1, smi, connect_status); }
The TCP/IP server is run using the uip_server() function.
on stdcore[3]: uip_server(mac_rx[0], mac_tx[0], xtcp, 1, ipconfig, connect_status);
Finally, the client to the TCP/IP server is run on a separate thread and connected to the TCP/IP server via the first element xtcp channel array. The function xhttpd implements the web server.
on stdcore[0]: xhttpd(xtcp[0]);
The webserver mainloop
The webserver is implemented in the xhttpd function in xhttpd.xc. This function implements a simple loop that just responds to events from the TCP/IP server. When an event occurs it is passed onto the httpd_handle_event handler.
void xhttpd(chanend tcp_svr) { xtcp_connection_t conn; // Initiate the HTTP state httpd_init(tcp_svr); // Loop forever processing TCP events while(1) { select { case xtcp_event(tcp_svr, conn): httpd_handle_event(tcp_svr, conn); break; } } }
The webserver event handler
The event handler is implemented in httpd.c and contains the main logic of the web server. The server can handle several connections at once. However, events for each connection may be interleaved so the handler needs to store separate state for each one. The httpd_state_t structures holds this state:
typedef struct httpd_state_t { int active; //< Whether this state structure is being used // for a connection int conn_id; //< The connection id char *dptr; //< Pointer to the remaining data to send int dlen; //< The length of remaining data to send char *prev_dptr; //< Pointer to the previously sent item of data } httpd_state_t; httpd_state_t connection_states[NUM_HTTPD_CONNECTIONS];
The http_init function is called at the start of the application. It initializes the connection state array and makes a request to accept incoming new TCP connections on port 80 (using the xtcp_listen() function):
void httpd_init(chanend tcp_svr) { int i; // Listen on the http port xtcp_listen(tcp_svr, 80, XTCP_PROTOCOL_TCP); for ( i = 0; i < NUM_HTTPD_CONNECTIONS; i++ ) { connection_states[i].active = 0; connection_states[i].dptr = NULL; } }
When an event occurs the httpd_handle_event function is called. The behaviour of this function depends on the event type. Firstly, link status events are ignored:
void httpd_handle_event(chanend tcp_svr, xtcp_connection_t *conn) { // We have received an event from the TCP stack, so respond // appropriately // Ignore events that are not directly relevant to http switch (conn->event) { case XTCP_IFUP: case XTCP_IFDOWN: case XTCP_ALREADY_HANDLED: return; default: break; }
For other events, we first check that the connection is definitely a http connection (is directed at port 80) and then call one of several event handlers for each type of event. The is a separate function for new connections, receiving data, sending data and closing connections:
if (conn->local_port == 80) { switch (conn->event) { case XTCP_NEW_CONNECTION: httpd_init_state(tcp_svr, conn); break; case XTCP_RECV_DATA: httpd_recv(tcp_svr, conn); break; case XTCP_SENT_DATA: case XTCP_REQUEST_DATA: case XTCP_RESEND_DATA: httpd_send(tcp_svr, conn); break; case XTCP_TIMED_OUT: case XTCP_ABORTED: case XTCP_CLOSED: httpd_free_state(conn); break; default: // Ignore anything else break; } conn->event = XTCP_ALREADY_HANDLED; }
The following sections describe the four handler functions.
Handling Connections
When a XTCP_NEW_CONNECTION event occurs we need to associate some state with the connection. So the connection_states array is searched for a free state structure.
void httpd_init_state(chanend tcp_svr, xtcp_connection_t *conn) { int i; // Try and find an empty connection slot for (i=0;i<NUM_HTTPD_CONNECTIONS;i++) { if (!connection_states[i].active) break; }
If we don’t find a free state we cannot handle the connection so xtcp_abort`() is called to abort the connection.
if ( i == NUM_HTTPD_CONNECTIONS ) { xtcp_abort(tcp_svr, conn); }
If we can allocate the state structure then the elements of the structure are initialized. The function xtcp_set_connection_appstate() is then called to associate the state with the connection. This means when a subsequent event is signalled on this connection the state can be recovered.
else { connection_states[i].active = 1; connection_states[i].conn_id = conn->id; connection_states[i].dptr = NULL; xtcp_set_connection_appstate( tcp_svr, conn, (xtcp_appstate_t) &connection_states[i]);
When a XTCP_TIMED_OUT, XTCP_ABORTED or XTCP_CLOSED event is received then the state associated with the connection can be freed up. This is done in the httpd_free_state function:
void httpd_free_state(xtcp_connection_t *conn) { int i; for ( i = 0; i < NUM_HTTPD_CONNECTIONS; i++ ) { if (connection_states[i].conn_id == conn->id) { connection_states[i].active = 0; } } }
Receiving Data
When a XTCP_RECV_DATA event occurs the httpd_recv function is called. The first thing this function does is call the xtcp_recv() function to place the received data in the data array. (Note that all TCP/IP clients must call xtcp_recv() directly after receiving this kind of event).
void httpd_recv(chanend tcp_svr, xtcp_connection_t *conn) { struct httpd_state_t *hs = (struct httpd_state_t *) conn->appstate; char data[XTCP_CLIENT_BUF_SIZE]; int len; // Receive the data from the TCP stack len = xtcp_recv(tcp_svr, data);
The hs variable points to the connection state. This was recovered from the appstate member of the connection structure which was previously associated with application state when the connection was set up. As a safety check we only proceed if this state has been set up and the hs variable is non-null.
if ( hs == NULL || hs->dptr != NULL) { return; }
Now the connection state is known and the incoming data buffer filled. To keep things simple, this server makes the assumption that a single tcp packet gives us enough information to parse the http request. However, many applications will need to concatenate each tcp packet to a different buffer and handle data after several tcp packets have come in. The next step in the code is to call the parse_http_request function:
parse_http_request(hs, &data[0], len);
This function examines the incoming packet and checks if it is a GET request. If so, then it always serves the same page. We signal that a page is ready to the callee by setting the data pointer (dptr) and data length (dlen) members of the connection state.
void parse_http_request(httpd_state_t *hs, char *data, int len) { // Return if we have data already if (hs->dptr != NULL) { return; } // Test if we received a HTTP GET request if (strncmp(data, "GET ", 4) == 0) { // Assign the default page character array as the data to send hs->dptr = &page[0]; hs->dlen = strlen(&page[0]); } else { // We did not receive a get request, so do nothing } }
The final part of the receive handler checks if the parse_http_request function set the dptr data pointer. If so, then it signals to the tcp/ip server that we wish to send some data on this connection. The actual sending of data is handled when an XTCP_REQUEST_DATA event is signalled by the tcp/ip server.
if (hs->dptr != NULL) { // Initate a send request with the TCP stack. // It will then reply with event XTCP_REQUEST_DATA // when it's ready to send xtcp_init_send(tcp_svr, conn); }
Sending Data
To send data the connection state keeps track of three variables:
Name |
Description |
---|---|
dptr |
A pointer to the next piece of data to send |
dlen |
The amount of data left to send |
prev_dptr |
The previous value of dptr before the last send |
We keep the previous value of dptr in case the tcp/ip server asks for a resend.
On receiving a XTCP_REQUEST_DATA, XTCP_SENT_DATA or XTCP_RESEND_DATA event the function httpd_send is called.
The first thing the function does is check whether we have been asked to resend data. In this case it sends the previous amount of data using the prev_dptr pointer.
if (conn->event == XTCP_RESEND_DATA) { xtcp_send(tcp_svr, hs->prev_dptr, (hs->dptr - hs->prev_dptr)); return; }
If the request is for the next piece of data, then the function first checks that we have data left to send. If not, the function xtcp_complete_send() is called to finish the send transaction and then the connection is closed down with xtcp_close() (since HTTP only does one transfer per connection).
if (hs->dlen == 0 || hs->dptr == NULL) { // Terminates the send process xtcp_complete_send(tcp_svr); // Close the connection xtcp_close(tcp_svr, conn); }
If we have data to send, then first the amount of data to send is calculated. This is based on the amount of data we have left (hs->dlen) and the maximum we can send (conn->mss). Having calculated this length, the data is sent using the xtcp_send() function.
Once the data is sent, all that is left to do is update the dptr, dlen and prev_dptr variables in the connection state.
else { int len = hs->dlen; if (len > conn->mss) len = conn->mss; xtcp_send(tcp_svr, hs->dptr, len); hs->prev_dptr = hs->dptr; hs->dptr += len; hs->dlen -= len; }
Converting to use the two thread version of the stack
In order to convert the application to use the two threaded version, the following changes would be made.
First, add the UIP_USE_SINGLE_THREADED_ETHERNET constant to the xtcp_client_config.h file found in the application’s source code.
- ::
#define UIP_USE_SINGLE_THREADED_ETHERNET
Next, replace the 5-thread ethernet server, and the TCP/IP server, with a single call to the 2 thread stack.
- ::
on stdcore[0]: {
char mac_address[6];
ethernet_getmac_otp(otp_ports, mac_address);
// Bring PHY out of reset p_reset <: 0x2;
// Start server uipSingleServer(clk_smi, null, smi, mii, xtcp, 1, ipconfig, mac_address); }
All other parts of the system will remain the same, as the client-server interface between the XTCP server and the application remains the same.