MCU Manager Guide

Management Library for 32-bit MCUs

March 4, 2024 (1mo ago)


MCU Manager (mcumgr) is a device management subsystem supported within the Zephyr OS.

mcumgr, by design is hardware and os agnostic, allowing it to be switched to a new target running a different RTOS.

Listed are the management operations support by mcumgr:

  • Image management
  • File System management
  • OS management
  • Settings (config) management
  • Shell management
  • Statistic management
  • Zephyr management

The goal of mcumgr is to define a common management infrastructure for 32-bit MCUs.

Architecture

Transports

The transport layer defines the medium in which transfers occur. Bluetooth and Shell are some of the transports supported within mcumgr.

Transfer Encodings

Data transfered between devices have a standard encoding for proper processingt wihtin the managment layer. mcumgr uses the Simple Management Protocol (SMP) to encode data over the transport.

With this encoded transfers can be referred to as SMP requests packets and SMP response pakcets.

Management:

The Management layer processes all SMP requests/responses packets and hands the data to the corresponding Command Handler

Command Handler:

mcumgr managment groups all have command handlers for potential commands received in a SMP request.

The command handler is called by the managmant layer and is repsonsible for the completion of the command action and generating response fields for the SMP response packet.

Flow

This section details the code flow for an OTA DFU utilzing mcumgr.

Setup

Before main mcumgr logic begins, a few important things get intialized during system intialization.

static int smp_init(void)
{
#ifdef CONFIG_SMP_CLIENT
sys_slist_init(&smp_transport_clients);
#endif

k_work_queue_init(&smp_work_queue);

k_work_queue_start(&smp_work_queue, smp_work_queue_stack,
K_THREAD_STACK_SIZEOF(smp_work_queue_stack),
CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_THREAD_PRIO, &smp_work_queue_config);

return 0;
}

Within smp_init a work queue is created. A dedicated work thread will process work items submitted to the smp_work_queue. How it ties into the mcumgr flow will be covered later on.

static void smp_bt_setup(void)
{
/*...*/

rc = smp_transport_init(&smp_bt_transport);

if (rc == 0) {
rc = smp_bt_register();
}

/*...*/
}

Within smp_bt_setup two important intializations occurs allowing for Bluetooth transport. First, smp_transport_init intializes the SMP transport structure. This includes initializtion of a work object and fifo...

k_work_init(&smpt->work, smp_handle_reqs);
k_fifo_init(&smpt->fifo);

and also the population of the callback API.

smp_bt_transport.functions.output = smp_bt_tx_pkt;
smp_bt_transport.functions.get_mtu = smp_bt_get_mtu;
smp_bt_transport.functions.ud_copy = smp_bt_ud_copy;
smp_bt_transport.functions.ud_free = smp_bt_ud_free;
smp_bt_transport.functions.query_valid_check = smp_bt_query_valid_check;

Second, smp_bt_register registers the SMP Service. This will be discussed more next.

int smp_bt_register(void)
{
return bt_gatt_service_register(&smp_bt_svc);
}

Bluetooth Transport

The Zeyphr Bluetooth Stack enables mcumgr to use Bluetooth transport. Enable CONFIG_MCUMGR_TRANSPORT_BT in proj.conf to compile in the supporting code.

mcumgr defines a SMP Profile that consists of a Service, Characteristic, and Descriptor.

static struct bt_gatt_attr smp_bt_attrs[] = {
/* SMP Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&smp_bt_svc_uuid),


BT_GATT_CHARACTERISTIC(&smp_bt_chr_uuid.uuid,
BT_GATT_CHRC_WRITE_WITHOUT_RESP |
BT_GATT_CHRC_NOTIFY,
#ifdef CONFIG_MCUMGR_TRANSPORT_BT_AUTHEN
BT_GATT_PERM_WRITE_AUTHEN,
#else
BT_GATT_PERM_WRITE,
#endif
NULL, smp_bt_chr_write, NULL),
BT_GATT_CCC(smp_bt_ccc_changed,
#ifdef CONFIG_MCUMGR_TRANSPORT_BT_AUTHEN
BT_GATT_PERM_READ_AUTHEN |
BT_GATT_PERM_WRITE_AUTHEN),
#else
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
#endif
};

smp_bt_chr_write is the handler for writes to the characteristic and will be the entry point to the mcumgr code flow.

Within the smp_bt_chr_write routine, smp_rx_req is called enqueuing the incoming SMP request for processing.

smp_rx_req(struct smp_transport *smpt, struct net_buf *nb)
{
net_buf_put(&smpt->fifo, nb);
k_work_submit_to_queue(&smp_work_queue, &smpt->work);
}

Here the network buffer, which contains the SMP request, is put into the fifo dicussed in the setup phase. Also the SMP transport structure work object is submitted to the SMP work queue. From this point the Bluetooth transport logic is done. The flow will continue on once the work thread begins work on the submitted work item.

Management

The management and SMP layer seem to be closely intertwineed or at least do not have a clear seperation. For simplicty the flow will combine them into one section.

Recalling the work object initialized during setup, smp_handle_reqs was the handler. When the work thread gets CPU time it will deque the work item submitted to the queue and process the work object hander.

static void smp_handle_reqs(struct k_work *work)
{
struct smp_transport *smpt;
struct net_buf *nb;


smpt = (void *)work;


/* Read and handle received messages */
while ((nb = net_buf_get(&smpt->fifo, K_NO_WAIT)) != NULL) {
smp_process_packet(smpt, nb);
}
}

Within the work item handler corresponding SMP transport structures and network buffers are dequeued and passed into smp_process_packet which processes a single SMP packet and send the corresponding response(s).

A new structure is also introduced witin smp_process_packet

struct smp_streamer {
struct smp_transport *smpt;
struct cbor_nb_reader *reader;
struct cbor_nb_writer *writer;
};

The smp_streamer structure Decodes, Encoded, and transmits SMP packets. This is used to update request and response buffers for an associated request. It also contains Bluetooth callbacks for SMP responses.

Following the flow smp_process_request_packet is called, where it loops through the contents of the SMP request packets and processes each individual request.

static int smp_handle_single_req(struct smp_streamer *streamer, const struct smp_hdr *req_hdr,
const char **rsn)
{
struct smp_hdr rsp_hdr;
struct cbor_nb_writer *nbw = streamer->writer;
zcbor_state_t *zsp = nbw->zs;
int rc;


/*...*/


/* Process the request and write the response payload. */
rc = smp_handle_single_payload(streamer, req_hdr);
if (rc != 0) {
*rsn = MGMT_CTXT_RC_RSN(streamer);
return rc;
}


/*...*/


smp_make_rsp_hdr(req_hdr, &rsp_hdr,
zsp->payload_mut - nbw->nb->data - MGMT_HDR_SIZE);
nbw->nb->len = zsp->payload_mut - nbw->nb->data;
smp_write_hdr(streamer, &rsp_hdr);


return 0;
}

Looping through each request smp_handle_single_req is called which processes each requests and writes the response. Majority of the logic occurs within smp_handle_single_payload.

Within smp_handle_single_payload the corresponding management group and handler to the request payload is determined and executed

Command Handler

DFU is under the Image Management Group. And the appropriate handler is the write operation for IMG_MGMT_ID_UPLOAD.

static const struct mgmt_handler img_mgmt_handlers[] = {
[IMG_MGMT_ID_STATE] = {
.mh_read = img_mgmt_state_read,
#ifdef CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP
.mh_write = NULL
#else
.mh_write = img_mgmt_state_write,
#endif
},
[IMG_MGMT_ID_UPLOAD] = {
.mh_read = NULL,
.mh_write = img_mgmt_upload
},
[IMG_MGMT_ID_ERASE] = {
.mh_read = NULL,
.mh_write = img_mgmt_erase
},
};

Within img_mgmt_upload the request payload is decoded and utilizing specific APIs Zepyhr RTOS provides, mcumgr will write the new image to the secondary flash partition slot. At this point mcumgr has no more work to do for DFU. The rest is up to the bootloader to upgrade and swap the images.

Back to Bluetooth

From here we flow all the way back up, to now sending the reponse.

/* Send the response. */
rc = streamer->smpt->functions.output(rsp);

Recall the callback APIs populates during setup in the SMP transport structure. Here is allow for the the SMP response to be sent via notification.