-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
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)
(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).
- 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
- 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