Skip to content

RFC: Implement IEEE802 MAC SAP interface (MLME/MCPS) #11324

@jia200x

Description

@jia200x

Description

While working with different MAC layers (e.g LoRaWAN, TSCH, etc) I've noticed most of them share a very convenient interface for controlling the MAC layer and sending/receiving data.
This is the Service Access Point interface defined by the IEEE (as seen in the IEEE802.15.4-2006 document)

interface
(picture taken from here)

This MAC has the following characteristics:

  • It's defined by the Mac Common Part Sublayer (MCPS) and Mac (sub)-Layer Management Entity (MLME). MCPS is in charge of data operations and MLME of all kind of MAC management (join, get/set, associate, change channel, etc).
  • The MAC user sees 2 Service Access Points (SAP) in order to control the MAC layer operation: MCPS-SAP and MLME-SAP.
    Inside the MLME component there's a Physical layer Information Base (PIB) and sometimes a MAC layer Information Base (MIB) that stores internal variables (channel, modes, etc). It's accessed via the MLME-SAP.
  • Each Service Access Point has 4 primitives for MAC user <=> MAC communication: Request/Confirm and Indication/Response.
  • Request/Confirm: The upper layer Requests an operation (send data, change datarate, join a network) and receives a synchronous or asynchronous Confirm with status and/or useful data from the request.
    • E.g joining a network might take several seconds. The Confirm will be asynchronous (on either timeout or if received an Acknowledgement from a coordinator, for instance). On the other side, changing channel or getting a value from the MAC can be done synchronously
  • Indication/Response: The MAC layer can indicate the MAC user about an event (received data, lost connection, a beacon was received) using the Indication primitive. The upper layer might send back a Response to that indication (although it seems to be used rarely).

The actions of Confirm and Indication are defined by the user via callbacks and/or data structures, so the same MAC layer can be used by different users (gnrc_netif, a dedicated thread, a test, etc).

PD-SAP and PLME-SAP are service access points for the transceiver, but this will be out of the scope for now.

I see some pros with this MAC:

  • It's standard (see IEEE802.15.4-2006 document, the original document is not for free but there's a summary here). I've seen it at least in Semtech LoRaMAC, NXP IEEE802.15.4 stack and Nordic IEEE802.15.4 API
  • It helps with testing. To test a MAC layer, just define some test-specific Confirm and Indication.
  • We could unify most of MAC code (Semtech LoRaMAC, GNRC LoRaWAN (gnrc_lorawan: add initial support for GNRC based LoRaWAN stack (v2) #11022 ), TSCH, etc).
    • There are some devices that include the MAC layer (see e.g RN2483 or CA-8210), a.k.a FullMAC devices. Since we already have some software defined MACs (SoftMAC), these SAP interfaces could be used for both transparently.
  • The asynchronous/synchronous nature is quite convenient for connection-oriented MACs (PPP, LoRaWAN, Wifi, etc). E.g just send a Request primitive and wait for a Confirm primitive with either SUCCESS or FAIL.

See the LoRaMAC reference for a real use case of this type of MAC.

Integration to RIOT

int

  • The MAC user (gnrc_netif, a test or a dedicated thread) talks directly with the MAC using SAP primitives. Since the MAC user implements Confirm and Indication, primitives can have different behaviors (e.g pass a packet to an upper layer, assertion in case of tests, etc).
  • The MAC could eventually interact with the lower layer (transceiver) using the netdev interface or a PD-SAP/PLME-SAP (not described here, but part of the IEEE standard). I drew this part with dotted lines because it could be optional for us...)
  • If the transceiver implements the MAC layer, the MCPS/MLME SAP interface should be defined in the driver. That way, the MAC user will always see the same interface.

A (very) simple POC API

main.c

/* Here goes the MAC user code. Note there can be multiple implementations of
confirm/indication primitives depending on the expected behavior, and these functions are
included in the mac descriptor (mac->mlme_confirm, mac->mcps_confirm and mac->mcps_indication */

int main(void)
{
    /* Request the mac layer to scan */
    sap_request_t request;
    request.type = MLME_SCAN;

    /* struct to hold confirm information */
    sap_confirm_t confirm;
    mac->mlme_request(&request, &confirm);
    /* confirm->status will be SAP_DEFERRED, since the result of the scan command is asynchronous */
    
    request.type = MLME_GET;
    request.param.get.netopt = NETOPT_FOO;
    /* ... */
    mac->mlme_request(&request, &confirm);
    /* confirm->status will be SAP_SUCCESS, since getting a variable is synchronous.
       confirm->param.get.data contains the value */

    request.type = MCPS_DATA;
    request.param.data_req.channel = my_channel;
    request.param.data_req.iolist = my_iolist;
    request.param.data_req.ack_requested = true;
    mac->mcps_request(&request, &confirm);
    /* confirm->status will be SAP_DEFERRED if everything went OK */
}

/* main.c mlme_confirm implementation */
void mlme_confirm(sap_confirm_t *confirm)
{
    switch(confirm->type) {
        case MLME_SCAN:
            if(confirm->status == SAP_SUCCESS) {
                puts("Scan went successfully!");
            }
            break;
    }
}
/* main.c mcps_confirm implementation */
void mcps_confirm(sap_confirm_t *confirm)
{
    switch(confirm->type) {
        case MCPS_DATA:
            if(confirm->status == SAP_SUCCESS) {
                puts("ACK was received successfully. Transmission was OK");
            }
            break;
    }
}

/* main.c mcps_indication implementation */
void mcps_indication(sap_indication_t *indication)
{
    switch(confirm->type) {
        case MCPS_DATA:
            puts("Received packet with payload:");
            print_packet(indication.msdu);
            break;
    }
}

mac.c

/* Here goes the MAC code */
/* associated to a descriptor (e.g mac->mlme_request) */
void mlme_request(sap_request_t *request, sap_confirm_t *confirm)
{
    switch(sap->type) {
        case MLME_GET:
            /* Get a MAC internal variable. This is synchronous */
            netdev->driver->get(netdev, request->param.get.netopt, request->param.get.data, request->param.get.data_len);
            confirm->status = SAP_SUCCESS;
            break;
        case MLME_SCAN:
            /* Scan is asynchronous. Set confirm status to DEFERRED in order to indicate the user the MAC will call the Confirm callback when ready */
           start_scan();
           confirm->status = SAP_DEFERRED;
           break;
    }
}
/* Called when scan finishes */
void _isr_scan_finished(void *arg)
{
    sap_mac_t *mac = arg;
    sap_confirm_t confirm;
    /* Populate confirm with scan data */
    confirm.data = get_scan_data();
    if(scan_status == 0) {
        confirm.status = SAP_SUCCESS;
    }
    else {
        confirm.status = SAP_FAIL;
    }
        mac->mlme_confirm(&confirm);
}

/* associated to a descriptor (e.g mac->mcps_request) */
void mcps_request(sap_request_t *request, sap_confirm_t *confirm)
{
    switch(sap->type) {
        case MCPS_DATA:
            /* Ask the MLME whether the MAC is connected or not */
            if(mlme_mac_is_disconnected()) {
                /* MAC is disconnected from medium. Confirm with error */
                confirm->status = SAP_FAIL;
                return;
            }
            netdev->driver->send(netdev, request->param.data_req.iolist, ...);
            /* When an ACK is received, the MAC layer will call the Confirm callback */
            confirm->status = SAP_DEFERRED;
            break;
    }
}

/* Called by the radio when received an ACK */
_isr_received_ack(void *arg)
{
    sap_mac_t *mac = arg;
    sap_confirm_t confirm;
    confirm.type = MCPS_DATA;
    confirm.status = SAP_SUCCESS;
     mac->mcps_confirm(&confirm);
}

/* Called when received a packet */
_isr_packet_received(void *arg)
{
    sap_mac_t *mac = arg;
    sap_indication_t indication;
    /* ... */
    indication.msdu = get_packet();
    indication.rssi = get_rssi();
    indication.channel = get_rx_channel();

    /* Indicate the upper layer we just received a packet */
    mac->mcps_indication(&indication)
}

I already implemented this mechanism in #11022, but it's not that decoupled from GNRC LoRaWAN.

Does this idea make sense? All comments would be very helpful

Metadata

Metadata

Assignees

No one assigned

    Labels

    Discussion: RFCThe issue/PR is used as a discussion starting point about the item of the issue/PRState: archivedState: The PR has been archived for possible future re-adaptationType: new featureThe issue requests / The PR implemements a new feature for RIOT

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions