Skip to content

Commit 7e0665c

Browse files
modules: openthread: Fix LQI metrics in nRF radio
In the radio nRF5 OpenThread port implementation there is a bug that causes wrong LQI metrics results. Signed-off-by: Arkadiusz Balys <[email protected]>
1 parent c00c2b2 commit 7e0665c

File tree

1 file changed

+173
-41
lines changed

1 file changed

+173
-41
lines changed

modules/openthread/platform/radio_nrf5.c

Lines changed: 173 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_OPENTHREAD_PLATFORM_LOG_LEVEL);
8686
#define NRF5_BROADCAST_ADDRESS 0xffff
8787
#define NRF5_NO_SHORT_ADDRESS_ASSIGNED 0xfffe
8888

89+
/* IE constants */
90+
#define IE_VENDOR_SIZE_MIN 3 /* Minimum vendor OUI size in bytes */
91+
8992
#if defined(CONFIG_COOP_ENABLED)
9093
#define OT_WORKER_PRIORITY K_PRIO_COOP(CONFIG_OPENTHREAD_THREAD_PRIORITY)
9194
#else
@@ -98,6 +101,9 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_OPENTHREAD_PLATFORM_LOG_LEVEL);
98101
#define PSDU_LENGTH(psdu) ((psdu)[0])
99102
#define PSDU_DATA(psdu) ((psdu) + 1)
100103

104+
#define CSL_IE_SIZE (6) /* Buffer for CSL IE: 2 bytes header + 4 bytes content */
105+
#define LM_IE_SIZE (32) /* Buffer for LM IE: 2 bytes header + 30 bytes content */
106+
101107
enum nrf5_pending_events {
102108
PENDING_EVENT_FRAME_RECEIVED, /* Radio has received new frame */
103109
PENDING_EVENT_RX_FAILED, /* The RX failed */
@@ -125,6 +131,7 @@ struct nrf5_header_ie_csl_reduced {
125131

126132
struct nrf5_header_ie_link_metrics {
127133
uint8_t vendor_oui[IE_VENDOR_SIZE_MIN];
134+
uint8_t subtype; /* Vendor-specific subtype/probing ID */
128135
uint8_t lqi_token;
129136
uint8_t link_margin_token;
130137
uint8_t rssi_token;
@@ -348,9 +355,20 @@ static int nrf5_set_tx_power(uint16_t channel)
348355
}
349356

350357
#if defined(CONFIG_OPENTHREAD_CSL_RECEIVER) || defined(CONFIG_OPENTHREAD_LINK_METRICS_SUBJECT)
358+
/**
359+
* @brief Set ACK data for both short and extended addresses
360+
*
361+
* This function handles the address conversion and calls the nRF driver
362+
* to set ACK data for both address types.
363+
*
364+
* @param short_addr Short address
365+
* @param ext_addr Extended address
366+
* @param header_ie_buf Buffer containing the IE data
367+
* @param ie_length Length of the IE data in bytes
368+
* @return 0 on success, negative error code on failure
369+
*/
351370
static int nrf5_ack_data_set(uint16_t short_addr, const otExtAddress *ext_addr,
352-
const struct nrf5_header_ie *header_ie)
353-
371+
const uint8_t *header_ie_buf, size_t ie_length)
354372
{
355373
uint8_t ext_addr_le[EXTENDED_ADDRESS_SIZE];
356374
uint8_t short_addr_le[SHORT_ADDRESS_SIZE];
@@ -363,17 +381,15 @@ static int nrf5_ack_data_set(uint16_t short_addr, const otExtAddress *ext_addr,
363381
sys_memcpy_swap(ext_addr_le, ext_addr->m8, EXTENDED_ADDRESS_SIZE);
364382

365383
if (short_addr != NRF5_NO_SHORT_ADDRESS_ASSIGNED) {
366-
nrf_802154_ack_data_set(short_addr_le, false, header_ie,
367-
header_ie->length + IE_HEADER_SIZE, NRF_802154_ACK_DATA_IE);
384+
nrf_802154_ack_data_set(short_addr_le, false, header_ie_buf, ie_length,
385+
NRF_802154_ACK_DATA_IE);
368386
}
369-
nrf_802154_ack_data_set(ext_addr_le, true, header_ie, header_ie->length + IE_HEADER_SIZE,
387+
nrf_802154_ack_data_set(ext_addr_le, true, header_ie_buf, ie_length,
370388
NRF_802154_ACK_DATA_IE);
371389

372390
return 0;
373391
}
374-
#endif
375392

376-
#if defined(CONFIG_OPENTHREAD_CSL_RECEIVER)
377393
static int nrf5_ack_data_clear(uint16_t short_addr, const otExtAddress *ext_addr)
378394
{
379395
uint8_t ext_addr_le[EXTENDED_ADDRESS_SIZE];
@@ -395,6 +411,42 @@ static int nrf5_ack_data_clear(uint16_t short_addr, const otExtAddress *ext_addr
395411
}
396412
#endif
397413

414+
#if defined(CONFIG_OPENTHREAD_CSL_RECEIVER)
415+
/**
416+
* @brief Create CSL IE using structured format with bit fields
417+
*
418+
* Creates a CSL Information Element using the defined structs with proper IEEE 802.15.4 format
419+
*
420+
* @param csl_period CSL period value
421+
* @param ie_buffer Buffer to store the IE (must be at least sizeof(struct nrf5_header_ie) bytes)
422+
* @return Length of the created IE in bytes
423+
*/
424+
static size_t create_csl_ie(uint32_t csl_period, uint8_t *ie_buffer)
425+
{
426+
if (ie_buffer == NULL) {
427+
return 0;
428+
}
429+
430+
struct nrf5_header_ie csl_ie;
431+
const uint8_t csl_element_id = NRF5_HEADER_IE_ELEMENT_ID_CSL_IE;
432+
433+
memset(&csl_ie, 0, sizeof(csl_ie));
434+
435+
csl_ie.length = sizeof(struct nrf5_header_ie_csl_reduced);
436+
csl_ie.element_id_low = csl_element_id & 0x01;
437+
csl_ie.element_id_high = (csl_element_id >> 1) & 0x7f;
438+
csl_ie.type = NRF5_IE_TYPE_HEADER;
439+
csl_ie.content.csl_reduced.csl_phase = sys_cpu_to_le16(0);
440+
csl_ie.content.csl_reduced.csl_period = sys_cpu_to_le16(csl_period);
441+
442+
size_t csl_ie_size = sizeof(uint16_t) + sizeof(struct nrf5_header_ie_csl_reduced);
443+
444+
memcpy(ie_buffer, &csl_ie, csl_ie_size);
445+
446+
return csl_ie_size;
447+
}
448+
#endif
449+
398450
static void nrf5_get_eui64(uint8_t *mac)
399451
{
400452
__ASSERT(mac != NULL, "nrf5_get_eui64: mac is NULL");
@@ -644,7 +696,6 @@ static bool nrf5_tx_csma_ca(otRadioFrame *frame, uint8_t *payload)
644696
.use_metadata_value = true,
645697
.power = get_transmit_power_for_channel(frame->mChannel),
646698
},
647-
648699
};
649700

650701
nrf_802154_csma_ca_max_backoffs_set(frame->mInfo.mTxInfo.mMaxCsmaBackoffs);
@@ -1440,17 +1491,6 @@ otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, otShort
14401491

14411492
ARG_UNUSED(aInstance);
14421493

1443-
const struct nrf5_header_ie header_ie = {
1444-
.length = sizeof(struct nrf5_header_ie_csl_reduced),
1445-
.element_id_high = (NRF5_HEADER_IE_ELEMENT_ID_CSL_IE) >> 1U,
1446-
.element_id_low = (NRF5_HEADER_IE_ELEMENT_ID_CSL_IE) & 0x01,
1447-
.type = NRF5_IE_TYPE_HEADER,
1448-
.content.csl_reduced = {
1449-
.csl_phase = 0,
1450-
.csl_period = aCslPeriod,
1451-
},
1452-
};
1453-
14541494
nrf_802154_csl_writer_period_set(aCslPeriod);
14551495
#if defined(CONFIG_NRF_802154_SER_HOST)
14561496
nrf5_data.csl.period = aCslPeriod;
@@ -1459,7 +1499,10 @@ otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, otShort
14591499
if (aCslPeriod == 0) {
14601500
result = nrf5_ack_data_clear(aShortAddr, aExtAddr);
14611501
} else {
1462-
result = nrf5_ack_data_set(aShortAddr, aExtAddr, &header_ie);
1502+
uint8_t csl_ie_buf[CSL_IE_SIZE];
1503+
size_t ie_length = create_csl_ie(aCslPeriod, csl_ie_buf);
1504+
1505+
result = nrf5_ack_data_set(aShortAddr, aExtAddr, csl_ie_buf, ie_length);
14631506
}
14641507

14651508
return result ? OT_ERROR_FAILED : OT_ERROR_NONE;
@@ -1542,34 +1585,123 @@ otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aCh
15421585
}
15431586

15441587
#if defined(CONFIG_OPENTHREAD_LINK_METRICS_SUBJECT)
1588+
1589+
/**
1590+
* @brief Set vendor IE header for link metrics using structured format
1591+
*
1592+
* This function creates a properly formatted IEEE 802.15.4 vendor-specific header IE
1593+
* for OpenThread link metrics probing
1594+
*
1595+
* +---------------------------------+----------------------+
1596+
* | Length | Element ID | Type=0 | Vendor OUI |
1597+
* +-----------+------------+--------+----------------------+
1598+
* | Bytes: 0-1 | 2-4 |
1599+
* +-----------+---------------------+----------------------+
1600+
* | Bits: 0-6 | 7-14 | 15 | IE_VENDOR_THREAD_OUI |
1601+
* +-----------+------------+--------+----------------------|
1602+
*
1603+
* Thread v1.2.1 Spec., 4.11.3.4.4.6
1604+
* +---------------------------------+-------------------+------------------+
1605+
* | Vendor Specific Information |
1606+
* +---------------------------------+-------------------+------------------+
1607+
* | 5 | 6 | 7 (optional) |
1608+
* +---------------------------------+-------------------+------------------+
1609+
* | IE_VENDOR_THREAD_ACK_PROBING_ID | LINK_METRIC_TOKEN | LINK_METRIC_TOKEN|
1610+
* |---------------------------------|-------------------|------------------|
1611+
*
1612+
* @param lqi Include LQI metric
1613+
* @param link_margin Include link margin metric
1614+
* @param rssi Include RSSI metric
1615+
* @param ie_header Buffer to store the IE header
1616+
*/
1617+
static void set_vendor_ie_header_lm(bool lqi, bool link_margin, bool rssi, uint8_t *ie_header)
1618+
{
1619+
/* OpenThread vendor-specific constants */
1620+
const uint8_t ie_vendor_id = NRF5_HEADER_IE_ELEMENT_ID_VENDOR_SPECIFIC_IE;
1621+
const uint8_t ie_vendor_thread_ack_probing_id = 0x00;
1622+
const uint32_t ie_vendor_thread_oui = 0xeab89b;
1623+
const uint8_t ie_vendor_thread_rssi_token = 0x01;
1624+
const uint8_t ie_vendor_thread_margin_token = 0x02;
1625+
const uint8_t ie_vendor_thread_lqi_token = 0x03;
1626+
1627+
struct nrf5_header_ie vendor_ie;
1628+
uint8_t link_metrics_data_len = (uint8_t)lqi + (uint8_t)link_margin + (uint8_t)rssi;
1629+
uint8_t token_offset;
1630+
1631+
__ASSERT(link_metrics_data_len <= 2, "Thread limits to 2 metrics at most");
1632+
__ASSERT(ie_header, "Invalid argument");
1633+
1634+
if (link_metrics_data_len == 0) {
1635+
ie_header[0] = 0;
1636+
return;
1637+
}
1638+
1639+
/* Clear the structure */
1640+
memset(&vendor_ie, 0, sizeof(vendor_ie));
1641+
1642+
vendor_ie.length = 4 + link_metrics_data_len;
1643+
vendor_ie.element_id_low = ie_vendor_id & 0x01;
1644+
vendor_ie.element_id_high = (ie_vendor_id >> 1) & 0x7f;
1645+
vendor_ie.type = NRF5_IE_TYPE_HEADER;
1646+
vendor_ie.content.link_metrics.vendor_oui[0] = (ie_vendor_thread_oui) & 0xff;
1647+
vendor_ie.content.link_metrics.vendor_oui[1] = (ie_vendor_thread_oui >> 8) & 0xff;
1648+
vendor_ie.content.link_metrics.vendor_oui[2] = (ie_vendor_thread_oui >> 16) & 0xff;
1649+
vendor_ie.content.link_metrics.subtype = ie_vendor_thread_ack_probing_id;
1650+
1651+
memcpy(ie_header, &vendor_ie, 2);
1652+
1653+
uint8_t *content_ptr = ie_header + 2;
1654+
1655+
content_ptr[0] = (ie_vendor_thread_oui) & 0xff;
1656+
content_ptr[1] = (ie_vendor_thread_oui >> 8) & 0xff;
1657+
content_ptr[2] = (ie_vendor_thread_oui >> 16) & 0xff;
1658+
content_ptr[3] = ie_vendor_thread_ack_probing_id;
1659+
1660+
token_offset = 4;
1661+
1662+
if (lqi) {
1663+
content_ptr[token_offset++] = ie_vendor_thread_lqi_token;
1664+
}
1665+
1666+
if (link_margin) {
1667+
content_ptr[token_offset++] = ie_vendor_thread_margin_token;
1668+
}
1669+
1670+
if (rssi) {
1671+
content_ptr[token_offset++] = ie_vendor_thread_rssi_token;
1672+
}
1673+
}
1674+
15451675
otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, otLinkMetrics aLinkMetrics,
1546-
otShortAddress aShortAddress,
1676+
const otShortAddress aShortAddress,
15471677
const otExtAddress *aExtAddress)
15481678
{
1549-
int result;
1550-
15511679
ARG_UNUSED(aInstance);
15521680

1553-
const struct nrf5_header_ie header_ie = {
1554-
.length = sizeof(struct nrf5_header_ie_link_metrics),
1555-
.element_id_high = (NRF5_HEADER_IE_ELEMENT_ID_VENDOR_SPECIFIC_IE) >> 1U,
1556-
.element_id_low = (NRF5_HEADER_IE_ELEMENT_ID_VENDOR_SPECIFIC_IE) & 0x01,
1557-
.type = NRF5_IE_TYPE_HEADER,
1558-
.content.link_metrics = {
1559-
.vendor_oui[0] = (IE_VENDOR_THREAD_OUI >> 0) & 0xff,
1560-
.vendor_oui[1] = (IE_VENDOR_THREAD_OUI >> 8) & 0xff,
1561-
.vendor_oui[2] = (IE_VENDOR_THREAD_OUI >> 16) & 0xff,
1562-
.lqi_token = aLinkMetrics.mLqi ? IE_VENDOR_THREAD_LQI_TOKEN : 0,
1563-
.link_margin_token = aLinkMetrics.mLinkMargin
1564-
? IE_VENDOR_THREAD_MARGIN_TOKEN
1565-
: 0,
1566-
.rssi_token = aLinkMetrics.mRssi ? IE_VENDOR_THREAD_RSSI_TOKEN : 0,
1567-
},
1568-
};
1681+
/* Use the proper IEEE 802.15.4 driver configure interface */
1682+
uint8_t header_ie_buf[LM_IE_SIZE];
15691683

1570-
result = nrf5_ack_data_set(aShortAddress, aExtAddress, &header_ie);
1684+
/* Validate addresses */
1685+
if (aShortAddress == NRF5_BROADCAST_ADDRESS || aExtAddress == NULL) {
1686+
return OT_ERROR_INVALID_ARGS;
1687+
}
15711688

1572-
return result ? OT_ERROR_FAILED : OT_ERROR_NONE;
1689+
/* Create the vendor-specific IE header */
1690+
set_vendor_ie_header_lm(aLinkMetrics.mLqi, aLinkMetrics.mLinkMargin, aLinkMetrics.mRssi,
1691+
header_ie_buf);
1692+
1693+
/* If no metrics requested, clear the ACK data using the helper function */
1694+
if (header_ie_buf[0] == 0) {
1695+
return nrf5_ack_data_clear(aShortAddress, aExtAddress) ? OT_ERROR_FAILED
1696+
: OT_ERROR_NONE;
1697+
}
1698+
1699+
/* Calculate IE length: first byte contains length field */
1700+
uint8_t ie_length = (header_ie_buf[0] & 0x7f) + 2; /* +2 for the header itself */
1701+
1702+
return nrf5_ack_data_set(aShortAddress, aExtAddress, header_ie_buf, ie_length)
1703+
? OT_ERROR_FAILED
1704+
: OT_ERROR_NONE;
15731705
}
15741706
#endif
15751707

0 commit comments

Comments
 (0)