Co-Processor Communication#

Overview#

The purpose of the Co-Processor Communication (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 other words, CPC allows 2 processors to communicate using a serial link. CPC involves code running on a primary device and on a secondary device. Currently, only Linux-Host are supported as Primary device, also known as CPC daemon (CPCd). The secondary device can only be a EFR(M) Device.

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 secondary device interacts by default with the host 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.

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 blockCPCD Building block

CPCD DiagramCPCD 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:

  • SL_CPC_DEBUG_SYSTEM_VIEW_LOG_CORE_EVENT Enable events that happen at the core level

  • SL_CPC_DEBUG_SYSTEM_VIEW_LOG_ENDPOINT_EVENT Enable events that happen on an endpoint

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:

  • SL_CPC_DEBUG_CORE_EVENT_COUNTERS Enable counters for events that happen at the core level

  • SL_CPC_DEBUG_ENDPOINT_EVENT_COUNTERS Enable counters for events that happen on an endpoint

  • SL_CPC_DEBUG_MEMORY_ALLOCATOR_COUNTERS Enable counters for memory allocation

Assertions#

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