Co-Processor Communication

Overview

The purpose of the Co-Processor Protocol (CPC) is to act as a serial link multiplexer that allows data sent from multiple applications to be transported over a secure shared physical link.

In CPC, data transfers between processors are segmented in sequential packets over endpoints. Transfers are guaranteed to be error-free and sent in order.

The CPC secondary interacts with the host's CPC daemon over an encrypted UART or SPI link.

Read and write operations with the CPC API are thread-safe and zero-copy. Care must be taken to only alter the passed buffer when alteration is allowed and to free any buffer provided by the CPC core to the user. It is worth mentioning that, in order to minimize memory allocation, when the write buffer is encrypted the original content is lost and replaced by its encrypted counterpart.

The CPC daemon (CPCd) supports the upgrade of the secondary's firmware in standalone bootloader mode via UART XMODEM and SPI EZSP. The upgrade image must be in Gecko Bootloader (.gbl) format. For this function to be available, the secondary must have the Gecko Bootloader installed, and the secondary application image must be generated with the component bootloader_interface.

CPCD Building block

CPCD Diagram

Secondary Application - User Endpoints

User Endpoints

A secondary application must first open a user endpoint before it can be opened by a host application.

On the secondary device, CPC operations are zero-copy. For write operations, this means that the application must wait for confirmation that a buffer has been transmitted before it re-uses the buffer. An on-write-completed callback can be registered on an endpoint for this purpose. For read operations, this means that the application is responsible for freeing the reception buffer by calling the function sl_cpc_free_rx_buffer().

A callback can be registered on an endpoint to notify the application that the host application has closed the endpoint. The secondary application must close and re-open the endpoint to re-establish the connection.

The endpoint IDs available for user endpoints are enumerated by sl_cpc_user_endpoint_id_t in sl_cpc.h.

Sample Code

#include <stdlib.h>
 // CPC API
#include "sl_cpc.h"

static bool endpoint_connected = false;
static bool write_completed = false;

// Callback triggered on endpoint error
static void on_endpoint_error(uint8_t endpoint_id,
                              void *arg)
{
  (void) arg;
  (void) endpoint_id;

  endpoint_connected = false;
}

// Callback triggered on write complete
static void on_write_completed(sl_cpc_user_endpoint_id_t endpoint_id,
                               void *buffer,
                               void *arg,
                               sl_status_t status)
{
  (void) arg;
  EFM_ASSERT(status == SL_STATUS_OK);

  // allocated buffer can be safely freed
  free(buffer);
}

void app(void)
{
  sl_status_t status;
  sl_cpc_endpoint_handle_t endpoint_handle;
  void *read_array;
  uint8_t *write_array;
  uint16_t size;

  while (1) {
    if (endpoint_connected == false) {
      // open user endpoint 0 
      status = sl_cpc_open_user_endpoint(&endpoint_handle, SL_CPC_ENDPOINT_USER_ID_0, SL_CPC_OPEN_ENDPOINT_FLAG_NONE, 1);
      EFM_ASSERT(status == SL_STATUS_OK);

      // register callback for write complete
      status = sl_cpc_set_endpoint_option(endpoint_handle, SL_CPC_ENDPOINT_ON_IFRAME_WRITE_COMPLETED,
                                          (void *)on_write_completed);
      EFM_ASSERT(status == SL_STATUS_OK);

      // register callback to re-open endpoint in case of error
      status = sl_cpc_set_endpoint_option(ep, SL_CPC_ENDPOINT_ON_ERROR,
                                    (void *)on_endpoint_error);
      EFM_ASSERT(status == SL_STATUS_OK);
    }

    // blocking read - wait for primary to connect to the endpoint and send data
    status = sl_cpc_read(&endpoint_handle, &read_array, &size, 0, 0u);
    EFM_ASSERT(status == SL_STATUS_OK);

    // copy data to write buffer
    write_array = (uint8_t *)malloc(size);
    EFM_ASSERT(write_array != NULL);
    memcpy(write_array, read_array, size);

    // return the buffer to CPC
    sl_cpc_free_rx_buffer(read_array);

    // echo the data back to the primary. Buffer will be freed in write complete callback
    status = sl_cpc_write(&endpoint_handle, write_array, size, 0u, NULL);
    EFM_ASSERT(status == SL_STATUS_OK);
  }
}

A more complete sample application can be found in the Example Projects & Demos tab of Simplicity Studio.

CPC Debugging Tools

SystemView

SEGGER SystemView is a real-time recording and visualization tool for embedded systems. SystemView events are available when the segger_systemview component is added to the project.

Two options are available:

Counters

Debug counters can be used to track the count of various CPC events. These counters are accessible via the debugger through the sl_cpc_core_debug structure.

Three options are available:

Assertions

When debugging, DEBUG_EFM should be defined from the compiler to enable the default internal assert handler.