diff --git a/subsys/lorawan/services/CMakeLists.txt b/subsys/lorawan/services/CMakeLists.txt index 2f3aeeeaca2..258cdd51d50 100644 --- a/subsys/lorawan/services/CMakeLists.txt +++ b/subsys/lorawan/services/CMakeLists.txt @@ -15,6 +15,11 @@ if(CONFIG_LORAWAN_SERVICES) frag_transport.c ) + zephyr_library_sources_ifdef( + CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM + frag_decoder_lowmem.c + ) + zephyr_library_sources_ifdef( CONFIG_LORAWAN_REMOTE_MULTICAST multicast.c diff --git a/subsys/lorawan/services/Kconfig b/subsys/lorawan/services/Kconfig index 591ba3ad60a..d21831cd908 100644 --- a/subsys/lorawan/services/Kconfig +++ b/subsys/lorawan/services/Kconfig @@ -75,6 +75,11 @@ config LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH help The default decoder implementation from LoRaMAC-node. +config LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM + bool "Low Memory Footprint Algorithm" + help + Alternate implementation with reduced RAM footprint. + endchoice # LORAWAN_FRAG_TRANSPORT_DECODER DT_CHOSEN_Z_CODE_PARTITION := zephyr,code-partition diff --git a/subsys/lorawan/services/frag_decoder_lowmem.c b/subsys/lorawan/services/frag_decoder_lowmem.c new file mode 100644 index 00000000000..14a6c1569a2 --- /dev/null +++ b/subsys/lorawan/services/frag_decoder_lowmem.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2024 A Labs GmbH + * Copyright (c) 2024 tado GmbH + * Copyright (c) 2022 Jiapeng Li + * + * Based on: https://github.com/JiapengLi/LoRaWANFragmentedDataBlockTransportAlgorithm + * Original algorithm: http://www.inference.org.uk/mackay/gallager/papers/ldpc.pdf + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "frag_decoder_lowmem.h" +#include "frag_flash.h" + +#include +#include +#include + +LOG_MODULE_REGISTER(lorawan_frag_dec, CONFIG_LORAWAN_SERVICES_LOG_LEVEL); + +SYS_BITARRAY_DEFINE_STATIC(lost_frames, FRAG_MAX_NB); +SYS_BITARRAY_DEFINE_STATIC(lost_frames_matrix, (FRAG_TOLERANCE * (FRAG_TOLERANCE + 1) / 2)); +SYS_BITARRAY_DEFINE_STATIC(matched_lost_frm_bm0, FRAG_TOLERANCE); +SYS_BITARRAY_DEFINE_STATIC(matched_lost_frm_bm1, FRAG_TOLERANCE); +SYS_BITARRAY_DEFINE_STATIC(matrix_line_bm, FRAG_MAX_NB); + + +static inline size_t matrix_location_to_index(size_t x, size_t y, size_t m) +{ + /* We only store the top half of the matrix because it is triangular, + * but that means when mapping the coordinates into the flat representation + * we need to account for that + */ + return (y + 1) * (m + m - y) / 2 - (m - x); +} + +static bool triangular_matrix_get_entry(struct sys_bitarray *m2tbm, size_t x, size_t y, size_t m) +{ + /* We are dealing with triangular matrices, so we don't expect actions in the lower half */ + __ASSERT(x >= y, "x: %d, y: %d, m: %d", x, y, m); + size_t bit; + int ret; + + ret = sys_bitarray_test_bit(m2tbm, matrix_location_to_index(x, y, m), &bit); + __ASSERT_NO_MSG(ret == 0); + + return bit != 0; +} + +static void triangular_matrix_set_entry(struct sys_bitarray *m2tbm, size_t x, size_t y, size_t m) +{ + /* We are dealing with triangular matrices, so we don't expect actions in the lower half */ + __ASSERT(x >= y, "x: %d, y: %d, m: %d", x, y, m); + int ret; + + ret = sys_bitarray_set_bit(m2tbm, matrix_location_to_index(x, y, m)); + __ASSERT_NO_MSG(ret == 0); +} + +static void triangular_matrix_clear_entry(struct sys_bitarray *m2tbm, size_t x, size_t y, size_t m) +{ + /* We are dealing with triangular matrices, so we don't expect actions in the lower half */ + __ASSERT(x >= y, "x: %d, y: %d, m: %d", x, y, m); + + int ret; + + ret = sys_bitarray_clear_bit(m2tbm, matrix_location_to_index(x, y, m)); + __ASSERT_NO_MSG(ret == 0); +} + +static inline bool bit_get(struct sys_bitarray *bitmap, size_t index) +{ + int bit, ret; + + ret = sys_bitarray_test_bit(bitmap, index, &bit); + __ASSERT_NO_MSG(ret == 0); + return bit != 0; +} + +static inline void bit_set(struct sys_bitarray *bitmap, size_t index) +{ + int ret; + + ret = sys_bitarray_set_bit(bitmap, index); + __ASSERT_NO_MSG(ret == 0); +} + +static inline void bit_clear(struct sys_bitarray *bitmap, size_t index) +{ + int ret; + + ret = sys_bitarray_clear_bit(bitmap, index); + __ASSERT_NO_MSG(ret == 0); +} + +static inline size_t bit_count_ones(struct sys_bitarray *bitmap, size_t index) +{ + size_t count; + int ret; + + ret = sys_bitarray_popcount_region(bitmap, index + 1, 0, &count); + __ASSERT_NO_MSG(ret == 0); + return count; +} + +static inline void bit_xor(struct sys_bitarray *des, struct sys_bitarray *src, size_t size) +{ + int ret; + + ret = sys_bitarray_xor(des, src, size, 0); + __ASSERT_NO_MSG(ret == 0); +} + +static inline void bit_clear_all(struct sys_bitarray *bitmap, size_t size) +{ + int ret; + + ret = sys_bitarray_clear_region(bitmap, size, 0); + __ASSERT_NO_MSG(ret == 0); +} + +/** + * Generate a 23bit Pseudorandom Binary Sequence (PRBS) + * + * @param previous Previous value in the sequence + * + * @returns Next value in the pseudorandom sequence + */ +static int32_t prbs23(int32_t previous) +{ + int32_t b0 = previous & 1; + int32_t b1 = (previous & 32) / 32; + + return (previous / 2) + ((b0 ^ b1) << 22); +} + +/** + * Generate vector for coded fragment n of the MxN parity matrix + * + * @param m Total number of uncoded fragments (M) + * @param n Coded fragment number (starting at 1 and not 0) + * @param vec Output vector (buffer size must be greater than m) + */ +static void frag_dec_parity_matrix_vector(size_t m, size_t n, struct sys_bitarray *vec) +{ + size_t mm, r; + int32_t x; + int ret; + + ret = sys_bitarray_clear_region(vec, m, 0); + __ASSERT_NO_MSG(ret == 0); + + /* + * Powers of 2 must be treated differently to make sure matrix content is close + * to random. Powers of 2 tend to generate patterns. + */ + if (is_power_of_two(m)) { + mm = m + 1; + } else { + mm = m; + } + + x = 1 + (1001 * n); + + for (size_t nb_coeff = 0; nb_coeff < (m / 2); nb_coeff++) { + r = (1 << 16); + while (r >= m) { + x = prbs23(x); + r = x % mm; + } + ret = sys_bitarray_set_bit(vec, r); + __ASSERT_NO_MSG(ret == 0); + } +} + +void frag_dec_init(struct frag_decoder *decoder, size_t nb_frag, size_t frag_size) +{ + decoder->nb_frag = nb_frag; + decoder->frag_size = frag_size; + /* Set all frames lost, from 0 to nb_frag-1 */ + decoder->lost_frame_count = decoder->nb_frag; + sys_bitarray_set_region(&lost_frames, decoder->nb_frag, 0); + + sys_bitarray_clear_region(&lost_frames_matrix, (FRAG_TOLERANCE * (FRAG_TOLERANCE + 1) / 2), + 0); + + decoder->filled_lost_frame_count = 0; + decoder->status = FRAG_DEC_STA_UNCODED; +} + +void frag_dec_frame_received(struct frag_decoder *decoder, uint16_t index) +{ + int ret, was_set; + + ret = sys_bitarray_test_and_clear_bit(&lost_frames, index, &was_set); + __ASSERT_NO_MSG(ret == 0); + + if (was_set != 0) { + decoder->lost_frame_count--; + } +} + +static void frag_dec_write_vector(struct sys_bitarray *matrix, uint16_t line_index, + struct sys_bitarray *vector, size_t len) +{ + for (size_t i = line_index; i < len; i++) { + if (bit_get(vector, i)) { + triangular_matrix_set_entry(matrix, i, line_index, len); + } else { + triangular_matrix_clear_entry(matrix, i, line_index, len); + } + } +} + +static void frag_dec_read_vector(struct sys_bitarray *matrix, uint16_t line_index, + struct sys_bitarray *vector, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (i >= line_index && triangular_matrix_get_entry(matrix, i, line_index, len)) { + bit_set(vector, i); + } else { + bit_clear(vector, i); + } + } +} + +int frag_dec(struct frag_decoder *decoder, uint16_t frag_counter, const uint8_t *buf, size_t len) +{ + int ret; + int i, j; + size_t unmatched_frame_count; + size_t lost_frame_index, frame_index; + static uint8_t row_data_buf[FRAG_MAX_SIZE]; + static uint8_t xor_row_data_buf[FRAG_MAX_SIZE]; + + if (decoder->status == FRAG_DEC_STA_DONE) { + return decoder->lost_frame_count; + } + + if (len != decoder->frag_size) { + return FRAG_DEC_ERR_INVALID_FRAME; + } + + __ASSERT_NO_MSG(frag_counter > 0); + + if (frag_counter <= decoder->nb_frag && decoder->status == FRAG_DEC_STA_UNCODED) { + /* Mark new received frame */ + frag_dec_frame_received(decoder, frag_counter - 1); + /* Save data to flash */ + frag_flash_write((frag_counter - 1) * decoder->frag_size, (uint8_t *)buf, + decoder->frag_size); + /* If no frame was lost, we are already done */ + if (decoder->lost_frame_count == 0) { + decoder->status = FRAG_DEC_STA_DONE; + return decoder->lost_frame_count; + } + return FRAG_DEC_ONGOING; + } + /* At least one frame was lost, start recovering frames */ + decoder->status = FRAG_DEC_STA_CODED; + + /* Clear all temporary bm and buf */ + bit_clear_all(&matched_lost_frm_bm0, decoder->lost_frame_count); + bit_clear_all(&matched_lost_frm_bm1, decoder->lost_frame_count); + + /* Copy data buffer because we need to manipulate it */ + memcpy(xor_row_data_buf, buf, decoder->frag_size); + + if (decoder->lost_frame_count > FRAG_TOLERANCE) { + return FRAG_DEC_ERR_TOO_MANY_FRAMES_LOST; + } + + unmatched_frame_count = 0; + /* Build parity matrix vector for current line */ + frag_dec_parity_matrix_vector(decoder->nb_frag, frag_counter - decoder->nb_frag, + &matrix_line_bm); + for (i = 0; i < decoder->nb_frag; i++) { + if (!bit_get(&matrix_line_bm, i)) { + /* This frame is not part of the recovery matrix for the current fragment */ + continue; + } + if (bit_get(&lost_frames, i)) { + /* No match for this coded frame in the uncoded frames. + * Check which lost frame we are processing by checking how many have been + * lost between the start and the current coded fragment. + */ + bit_set(&matched_lost_frm_bm0, bit_count_ones(&lost_frames, i) - 1); + unmatched_frame_count++; + } else { + /* Restore frame by XORing with already received frame */ + /* Load previously received data into buffer */ + frag_flash_read(i * decoder->frag_size, row_data_buf, decoder->frag_size); + /* XOR previously received data with data for current frame */ + mem_xor_n(xor_row_data_buf, xor_row_data_buf, row_data_buf, + decoder->frag_size); + } + } + if (unmatched_frame_count == 0) { + return FRAG_DEC_ONGOING; + } + + /* &matched_lost_frm_bm0 now contains new coded frame which excludes all received + * frames content start to diagonal &matched_lost_frm_bm0 + */ + do { + ret = sys_bitarray_find_nth_set(&matched_lost_frm_bm0, 1, decoder->lost_frame_count, + 0, &lost_frame_index); + if (ret == 1) { + /* Not found */ + break; + } + if (ret != 0) { + return FRAG_DEC_ERR; + } + /* We know which one is the next lost frame, try to find it in the lost frame bitmap + */ + ret = sys_bitarray_find_nth_set(&lost_frames, lost_frame_index + 1, + decoder->nb_frag, 0, &frame_index); + if (ret == 1) { + /* Not found */ + break; + } + if (ret != 0) { + return FRAG_DEC_ERR; + } + /* If current frame contains new information, save it */ + if (!triangular_matrix_get_entry(&lost_frames_matrix, lost_frame_index, + lost_frame_index, decoder->lost_frame_count)) { + frag_dec_write_vector(&lost_frames_matrix, lost_frame_index, + &matched_lost_frm_bm0, decoder->lost_frame_count); + frag_flash_write(frame_index * decoder->frag_size, xor_row_data_buf, + decoder->frag_size); + decoder->filled_lost_frame_count++; + break; + } + + frag_dec_read_vector(&lost_frames_matrix, lost_frame_index, &matched_lost_frm_bm1, + decoder->lost_frame_count); + bit_xor(&matched_lost_frm_bm0, &matched_lost_frm_bm1, decoder->lost_frame_count); + frag_flash_read(frame_index * decoder->frag_size, row_data_buf, decoder->frag_size); + mem_xor_n(xor_row_data_buf, xor_row_data_buf, row_data_buf, decoder->frag_size); + } while (!sys_bitarray_is_region_cleared(&matched_lost_frm_bm0, decoder->lost_frame_count, + 0)); + + if (decoder->filled_lost_frame_count != decoder->lost_frame_count) { + return FRAG_DEC_ONGOING; + } + + if (decoder->lost_frame_count < 2) { + decoder->status = FRAG_DEC_STA_DONE; + return decoder->lost_frame_count; + } + + /* All frame content is received, now to reconstruct the whole frame */ + for (i = (decoder->lost_frame_count - 2); i >= 0; i--) { + ret = sys_bitarray_find_nth_set(&lost_frames, i + 1, decoder->nb_frag, 0, + &frame_index); + if (ret != 0) { + return FRAG_DEC_ERR; + } + frag_flash_read(frame_index * decoder->frag_size, xor_row_data_buf, + decoder->frag_size); + frag_dec_read_vector(&lost_frames_matrix, i, &matched_lost_frm_bm1, + decoder->lost_frame_count); + for (j = (decoder->lost_frame_count - 1); j > i; j--) { + if (!bit_get(&matched_lost_frm_bm1, j)) { + continue; + } + ret = sys_bitarray_find_nth_set(&lost_frames, j + 1, decoder->nb_frag, 0, + &lost_frame_index); + if (ret != 0) { + return FRAG_DEC_ERR; + } + frag_dec_read_vector(&lost_frames_matrix, j, &matched_lost_frm_bm0, + decoder->lost_frame_count); + bit_xor(&matched_lost_frm_bm1, &matched_lost_frm_bm0, + decoder->lost_frame_count); + frag_flash_read(lost_frame_index * decoder->frag_size, row_data_buf, + decoder->frag_size); + mem_xor_n(xor_row_data_buf, xor_row_data_buf, row_data_buf, + decoder->frag_size); + frag_dec_write_vector(&lost_frames_matrix, i, &matched_lost_frm_bm1, + decoder->lost_frame_count); + } + frag_flash_write(frame_index * decoder->frag_size, xor_row_data_buf, + decoder->frag_size); + } + decoder->status = FRAG_DEC_STA_DONE; + return decoder->lost_frame_count; +} diff --git a/subsys/lorawan/services/frag_decoder_lowmem.h b/subsys/lorawan/services/frag_decoder_lowmem.h new file mode 100644 index 00000000000..4fc6159a1b7 --- /dev/null +++ b/subsys/lorawan/services/frag_decoder_lowmem.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 A Labs GmbH + * Copyright (c) 2024 tado GmbH + * Copyright (c) 2022 Jiapeng Li + * + * Based on: https://github.com/JiapengLi/LoRaWANFragmentedDataBlockTransportAlgorithm + * Original algorithm: http://www.inference.org.uk/mackay/gallager/papers/ldpc.pdf + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef FRAG_DEC_H_ +#define FRAG_DEC_H_ + +#include +#include + +#define FRAG_MAX_NB \ + (CONFIG_LORAWAN_FRAG_TRANSPORT_IMAGE_SIZE / CONFIG_LORAWAN_FRAG_TRANSPORT_MIN_FRAG_SIZE + \ + 1U) +#define FRAG_MAX_SIZE (CONFIG_LORAWAN_FRAG_TRANSPORT_MAX_FRAG_SIZE) +#define FRAG_TOLERANCE (FRAG_MAX_NB * CONFIG_LORAWAN_FRAG_TRANSPORT_MAX_REDUNDANCY / 100U) + +#define FRAG_DEC_ONGOING (-1) +#define FRAG_DEC_ERR_INVALID_FRAME (-2) +#define FRAG_DEC_ERR_TOO_MANY_FRAMES_LOST (-3) +#define FRAG_DEC_ERR (-4) + +enum frag_decoder_status { + /** Processing uncoded fragments */ + FRAG_DEC_STA_UNCODED, + /** Processing coded fragments and restoring data with the help of other fragments */ + FRAG_DEC_STA_CODED, + /** All fragments received and/or restored */ + FRAG_DEC_STA_DONE, +}; + +struct frag_decoder { + /** Current decoder status */ + enum frag_decoder_status status; + + /** Number of fragments */ + uint16_t nb_frag; + /** Size of individual fragment */ + uint8_t frag_size; + + /** Number of frames lost in this session */ + uint16_t lost_frame_count; + /** Number of frames recovered in this session */ + uint16_t filled_lost_frame_count; +}; + +void frag_dec_init(struct frag_decoder *decoder, size_t nb_frag, size_t frag_size); +int frag_dec(struct frag_decoder *decoder, uint16_t frag_counter, const uint8_t *buf, size_t len); + +#endif /* FRAG_DEC_H_ */ diff --git a/subsys/lorawan/services/frag_transport.c b/subsys/lorawan/services/frag_transport.c index 2efe5213992..24541e30de0 100644 --- a/subsys/lorawan/services/frag_transport.c +++ b/subsys/lorawan/services/frag_transport.c @@ -15,6 +15,8 @@ #include #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH #include +#elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM) +#include "frag_decoder_lowmem.h" #endif #include @@ -85,6 +87,8 @@ struct frag_transport_context { #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH /* variables required for FragDecoder.h */ FragDecoderCallbacks_t decoder_callbacks; +#elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM) + struct frag_decoder decoder; #endif }; @@ -219,6 +223,8 @@ static void frag_transport_package_callback(uint8_t port, bool data_pending, int ctx.decoder_callbacks.FragDecoderWrite = frag_flash_write; ctx.decoder_callbacks.FragDecoderRead = frag_flash_read; +#elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM) + frag_dec_init(&ctx.decoder, ctx.nb_frag, ctx.frag_size); #endif frag_flash_init(ctx.frag_size); ctx.is_active = true; @@ -269,6 +275,9 @@ static void frag_transport_package_callback(uint8_t port, bool data_pending, int #ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH decoder_process_status = FragDecoderProcess( frag_counter, (uint8_t *)&rx_buf[rx_pos]); +#elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM) + decoder_process_status = frag_dec( + &ctx.decoder, frag_counter, &rx_buf[rx_pos], ctx.frag_size); #endif LOG_INF("DataFragment %u of %u (%u lost), session: %u, decoder result: %d",