Commit cad56e80 authored by Lucas Romero's avatar Lucas Romero Committed by Johan Hedberg
Browse files

lorawan: services: frag decoder: add lowmem implementation



that utilizes the fact that the ldpc recovery matrix is triangular and only
stores the half with non-zero values.

This implementation is hopefully going to make forward error correction
usable on lower memory devices.

Signed-off-by: default avatarLucas Romero <luqasn@gmail.com>
parent 69c5ef96
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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
+5 −0
Original line number Diff line number Diff line
@@ -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
+390 −0
Original line number Diff line number Diff line
/*
 * 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 <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/bitarray.h>

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;
}
+56 −0
Original line number Diff line number Diff line
/*
 * 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 <stdint.h>
#include <stddef.h>

#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_ */
+9 −0
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
#include <LoRaMac.h>
#ifdef CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_SEMTECH
#include <FragDecoder.h>
#elif defined(CONFIG_LORAWAN_FRAG_TRANSPORT_DECODER_LOWMEM)
#include "frag_decoder_lowmem.h"
#endif

#include <zephyr/lorawan/lorawan.h>
@@ -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",