1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
17045: sys/coding: add XOR based coding module r=benpicco a=benpicco



19243: cpu/gd32v: add periph_gpio_ll and periph_gpio_ll_irq support r=benpicco a=gschorcht

### Contribution description

This PR provides the `periph_gpio_ll` and `periph_gpio_ll_irq` support for GD32VF103. Level triggered interrupts are emulated.

`periph_gpio_ll_irq` could be split off from this PR as a separate PR if necessary.

### Testing procedure

Use any GD32V board and connect PA0 -> PB0 and PA1 -> PB1 where PA is the output port and PB the input port. With these connections `tests/periph_gpio_ll` should work.
```
BOARD=sipeed-longan-nano make -j8 -C tests/periph_gpio_ll flash term
```

If necessary, change the input and output pins by setting the environment variables and connect the corresponding pins, for example for `seeedstudio-gd32` PA1 -> PB8 and PA8 -> PB9:
```
PIN_OUT_0=1 PIN_OUT_1=8  PIN_IN_0=8 PIN_IN_1=9 BOARD=seedstudio-gd32 make -j8 -C tests/periph_gpio_ll flash term
```

### Issues/PRs references


Co-authored-by: Benjamin Valentin <benjamin.valentin@ml-pa.com>
Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
This commit is contained in:
bors[bot] 2023-02-07 17:39:20 +00:00 committed by GitHub
commit f341ad6c9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1071 additions and 3 deletions

View File

@ -13,6 +13,10 @@ config CPU_FAM_GD32V
select HAS_PERIPH_CLIC
select HAS_PERIPH_GPIO
select HAS_PERIPH_GPIO_IRQ
select HAS_PERIPH_GPIO_LL
select HAS_PERIPH_GPIO_LL_IRQ
select HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_HIGH
select HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_LOW
select HAS_PERIPH_FLASHPAGE
select HAS_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE
select HAS_PERIPH_FLASHPAGE_PAGEWISE

View File

@ -4,6 +4,10 @@ FEATURES_PROVIDED += arch_nuclei
FEATURES_PROVIDED += periph_clic
FEATURES_PROVIDED += periph_gpio
FEATURES_PROVIDED += periph_gpio_irq
FEATURES_PROVIDED += periph_gpio_ll
FEATURES_PROVIDED += periph_gpio_ll_irq
FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high
FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low
FEATURES_PROVIDED += periph_rtc
FEATURES_PROVIDED += periph_rtc_mem
FEATURES_PROVIDED += periph_rtt

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_gd32v
* @ingroup drivers_periph_gpio_ll
* @{
*
* @file
* @brief GPIO Low-level API implementation for the GD32V GPIO peripheral
*
* @author Gunar Schorcht <gunar@schorcht.net>
*/
#ifndef GPIO_LL_ARCH_H
#define GPIO_LL_ARCH_H
#include "architecture.h"
#include "periph/gpio_ll.h"
#include "periph_cpu.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef DOXYGEN /* hide implementation specific details from Doxygen */
/**
* @brief Number of ports available on GD32VF103
*/
#define GPIO_PORT_NUMOF 5
/**
* @brief Get a GPIO port by number
*/
#define GPIO_PORT(num) (GPIOA_BASE + ((num) << 10))
/**
* @brief Get a GPIO port number by gpio_t value
*/
#define GPIO_PORT_NUM(port) (((port) - GPIOA_BASE) >> 10)
static inline uword_t gpio_ll_read(gpio_port_t port)
{
return ((GPIO_Type *)port)->ISTAT;
}
static inline uword_t gpio_ll_read_output(gpio_port_t port)
{
return ((GPIO_Type *)port)->OCTL;
}
static inline void gpio_ll_set(gpio_port_t port, uword_t mask)
{
((GPIO_Type *)port)->BOP = mask;
}
static inline void gpio_ll_clear(gpio_port_t port, uword_t mask)
{
((GPIO_Type *)port)->BOP = mask << 16;
}
static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask)
{
unsigned irq_state = irq_disable();
((GPIO_Type *)port)->OCTL ^= mask;
irq_restore(irq_state);
}
static inline void gpio_ll_write(gpio_port_t port, uword_t value)
{
((GPIO_Type *)port)->OCTL = value;
}
static inline gpio_port_t gpio_get_port(gpio_t pin)
{
return pin & 0xfffffff0UL;
}
static inline uint8_t gpio_get_pin_num(gpio_t pin)
{
return pin & 0xfUL;
}
static inline gpio_port_t gpio_port_pack_addr(void *addr)
{
return (gpio_port_t)addr;
}
static inline void * gpio_port_unpack_addr(gpio_port_t port)
{
if (port < GPIOA_BASE) {
return (void *)port;
}
return NULL;
}
static inline bool is_gpio_port_num_valid(uint_fast8_t num)
{
return num < GPIO_PORT_NUMOF;
}
#endif /* DOXYGEN */
#ifdef __cplusplus
}
#endif
#endif /* GPIO_LL_ARCH_H */
/** @} */

View File

@ -183,6 +183,36 @@ void gpio_init_af(gpio_t pin, gpio_af_t af);
*/
void gpio_init_analog(gpio_t pin);
/* Hide this from Doxygen to avoid merging implementation details into
* public view on type */
#ifndef DOXYGEN
#define HAVE_GPIO_PULL_STRENGTH_T
typedef enum {
GPIO_PULL_WEAKEST = 0,
GPIO_PULL_WEAK = 0,
GPIO_PULL_STRONG = 0,
GPIO_PULL_STRONGEST = 0
} gpio_pull_strength_t;
#define HAVE_GPIO_DRIVE_STRENGTH_T
typedef enum {
GPIO_DRIVE_WEAKEST = 0,
GPIO_DRIVE_WEAK = 0,
GPIO_DRIVE_STRONG = 0,
GPIO_DRIVE_STRONGEST = 0
} gpio_drive_strength_t;
#define HAVE_GPIO_SLEW_T
typedef enum {
GPIO_SLEW_SLOWEST = 0,
GPIO_SLEW_SLOW = 1,
GPIO_SLEW_FAST = 2,
GPIO_SLEW_FASTEST = 2,
} gpio_slew_t;
#endif /* !DOXYGEN */
/**
* @brief Available number of ADC devices
*/

154
cpu/gd32v/periph/gpio_ll.c Normal file
View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_gd32v
* @ingroup drivers_periph_gpio_ll
* @{
*
* @file
* @brief GPIO Low-level API implementation for the GD32V GPIO peripheral
*
* @author Gunar Schorcht <gunar@schorcht.net>
* @}
*/
#include <errno.h>
#include <string.h>
#include "cpu.h"
#include "bitarithm.h"
#include "periph/gpio_ll.h"
#define ENABLE_DEBUG 0
#include "debug.h"
uint16_t pin_used[GPIO_PORT_NUMOF] = {};
int gpio_ll_init(gpio_port_t port, uint8_t pin, const gpio_conf_t *conf)
{
if ((conf->pull == GPIO_PULL_KEEP) ||
(conf->state == GPIO_OUTPUT_OPEN_SOURCE) ||
((conf->state != GPIO_INPUT) && (conf->pull != GPIO_FLOATING))) {
return -ENOTSUP;
}
unsigned state = irq_disable();
periph_clk_en(APB2, (RCU_APB2EN_PAEN_Msk << GPIO_PORT_NUM(port)));
volatile uint32_t *ctrl = (pin < 8) ? &((GPIO_Type *)port)->CTL0
: &((GPIO_Type *)port)->CTL1;
volatile uint32_t *octl = &((GPIO_Type *)port)->OCTL;
unsigned pos = ((pin % 8) * 4);
/* reset configuration CTLx[1:0], MDx[1:0] (analogue, input mode) */
*ctrl &= ~(0xf << pos);
switch (conf->state) {
case GPIO_DISCONNECT:
*ctrl |= 0x1 << (pos + 2);
pin_used[GPIO_PORT_NUM(port)] &= ~(1 << pin);
if (pin_used[GPIO_PORT_NUM(port)] == 0) {
periph_clk_dis(APB2, (RCU_APB2EN_PAEN_Msk << GPIO_PORT_NUM(port)));
}
break;
case GPIO_INPUT:
pin_used[GPIO_PORT_NUM(port)] |= 1 << pin;
if (conf->pull == GPIO_FLOATING) {
*ctrl |= 0x1 << (pos + 2);
}
else {
*ctrl |= 0x2 << (pos + 2);
if (conf->pull == GPIO_PULL_UP) {
*octl |= 1 << pin;
}
else {
*octl &= ~(1 << pin);
}
}
break;
case GPIO_OUTPUT_PUSH_PULL:
case GPIO_OUTPUT_OPEN_DRAIN:
pin_used[GPIO_PORT_NUM(port)] |= 1 << pin;
*ctrl |= (conf->slew_rate + 1) << pos;
*ctrl |= (conf->state == GPIO_OUTPUT_OPEN_DRAIN ? 0x1 : 0x0) << (pos + 2);
if (conf->initial_value) {
gpio_ll_set(port, 1UL << pin);
}
else {
gpio_ll_clear(port, 1UL << pin);
}
break;
default:
irq_restore(state);
return -ENOTSUP;
}
irq_restore(state);
return 0;
}
void gpio_ll_query_conf(gpio_conf_t *dest, gpio_port_t port, uint8_t pin)
{
assert(dest);
unsigned state = irq_disable();
memset(dest, 0, sizeof(*dest));
volatile uint32_t *ctrl_reg = (pin < 8) ? &((GPIO_Type *)port)->CTL0
: &((GPIO_Type *)port)->CTL1;
unsigned pos = ((pin % 8) * 4);
uint32_t mode = (*ctrl_reg >> pos) & 0x3;
uint32_t ctrl = (*ctrl_reg >> (pos + 2)) & 0x3;
if (mode == 0) {
dest->state = GPIO_INPUT;
switch (ctrl) {
case 0:
dest->state = GPIO_USED_BY_PERIPHERAL;
break;
case 1:
dest->pull = GPIO_FLOATING;
break;
case 2:
dest->pull = (((GPIO_Type *)port)->OCTL & (1UL << pin)) ? GPIO_PULL_UP
: GPIO_PULL_DOWN;
break;
default:
break;
}
}
else {
dest->slew_rate = mode - 1;
dest->pull = GPIO_FLOATING;
switch (ctrl) {
case 0:
dest->state = GPIO_OUTPUT_PUSH_PULL;
break;
case 1:
dest->state = GPIO_OUTPUT_OPEN_DRAIN;
break;
default:
dest->state = GPIO_USED_BY_PERIPHERAL;
break;
}
}
if (dest->state == GPIO_INPUT) {
dest->initial_value = (gpio_ll_read(port) >> pin) & 1UL;
}
else {
dest->initial_value = (gpio_ll_read_output(port) >> pin) & 1UL;
}
irq_restore(state);
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_gd32v
* @ingroup drivers_periph_gpio_ll_irq
* @{
*
* @file
* @brief IRQ implementation of the GPIO Low-Level API for GD32V
*
* @author Gunar Schorcht <gunar@schorcht.net>
* @}
*/
#include <errno.h>
#include "cpu.h"
#include "bitarithm.h"
#include "periph/gpio_ll_irq.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @brief Number of available external interrupt lines
*/
#define GPIO_ISR_CHAN_NUMOF (16U)
#define GPIO_ISR_CHAN_MASK (0xFFFF)
struct _gpio_isr_ctx {
gpio_ll_cb_t cb;
void *arg;
};
/**
* @brief Allocate memory for one callback and argument per EXTI channel
*/
static struct _gpio_isr_ctx _exti_ctx[GPIO_ISR_CHAN_NUMOF];
/* keep the state of level triggered interrupt activation using 32-bit values
* since the registers require 32-bit access */
static uint32_t _h_level_triggered = 0;
static uint32_t _l_level_triggered = 0;
void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin)
{
(void)port;
EXTI->INTEN &= ~(1 << pin);
}
void gpio_ll_irq_unmask_and_clear(gpio_port_t port, uint8_t pin)
{
(void)port;
EXTI->PD = (1 << pin);
EXTI->INTEN |= (1 << pin);
}
static inline unsigned _irq_num(unsigned pin)
{
if (pin < 5) {
return EXTI0_IRQn + pin;
}
if (pin < 10) {
return EXTI5_9_IRQn;
}
return EXTI10_15_IRQn;
}
/* Forward declaration of ISR */
static void _gpio_isr(unsigned irqn);
int gpio_ll_irq(gpio_port_t port, uint8_t pin, gpio_irq_trig_t trig, gpio_ll_cb_t cb, void *arg)
{
unsigned irq_state = irq_disable();
int port_num = GPIO_PORT_NUM(port);
/* set callback */
_exti_ctx[pin].cb = cb;
_exti_ctx[pin].arg = arg;
/* enable alternate function clock for the GPIO module */
periph_clk_en(APB2, RCU_APB2EN_AFEN_Msk);
/* configure the EXTI channel */
volatile uint32_t *afio_exti_ss = &AFIO->EXTISS0 + (pin >> 2);
*afio_exti_ss &= ~(0xfUL << ((pin & 0x03) * 4));
*afio_exti_ss |= (uint32_t)port_num << ((pin & 0x03) * 4);
uint32_t pin_mask = 1UL << pin;
_h_level_triggered &= ~pin_mask;
_l_level_triggered &= ~pin_mask;
EXTI->RTEN &= ~pin_mask;
EXTI->FTEN &= ~pin_mask;
/* configure the active flank, level interrupts are emulated */
switch (trig) {
case GPIO_TRIGGER_EDGE_RISING:
EXTI->RTEN |= pin_mask;
break;
case GPIO_TRIGGER_EDGE_FALLING:
EXTI->FTEN |= pin_mask;
break;
case GPIO_TRIGGER_EDGE_BOTH:
EXTI->RTEN |= pin_mask;
EXTI->FTEN |= pin_mask;
break;
case GPIO_TRIGGER_LEVEL_HIGH:
EXTI->RTEN |= pin_mask;
_h_level_triggered |= pin_mask;
break;
case GPIO_TRIGGER_LEVEL_LOW:
EXTI->FTEN |= pin_mask;
_l_level_triggered |= pin_mask;
break;
}
/* clear pending interrupts */
EXTI->PD = pin_mask;
/* enable global pin interrupt */
unsigned irqn = _irq_num(pin);
clic_set_handler(irqn, _gpio_isr);
clic_enable_interrupt(irqn, CPU_DEFAULT_IRQ_PRIO);
/* unmask the pins interrupt channel */
EXTI->INTEN |= pin_mask;
/* emulate a level interrupt if the pin already has the level */
uint32_t level = gpio_ll_read(port) & pin_mask;
if ((trig == GPIO_TRIGGER_LEVEL_HIGH) && level) {
EXTI->SWIEV |= pin_mask;
}
else if ((trig == GPIO_TRIGGER_LEVEL_LOW) && !level) {
EXTI->SWIEV |= pin_mask;
}
irq_restore(irq_state);
return 0;
}
static void _gpio_isr(unsigned irqn)
{
(void)irqn;
/* read pending interrupts */
uint32_t pending = (EXTI->PD & GPIO_ISR_CHAN_MASK);
/* clear pending interrupts */
EXTI->PD = pending;
/* generate soft interrupts for lines which have their interrupt enabled */
pending &= EXTI->INTEN;
/* iterate over all set bits */
uint8_t pin = 0;
while (pending) {
/* get next pin with interrupt */
pending = bitarithm_test_and_clear(pending, &pin);
/* call registered callball function */
_exti_ctx[pin].cb(_exti_ctx[pin].arg);
/* emulate level triggered IRQs by asserting the IRQ again in software */
uint32_t pin_mask = 1UL << pin;
if ((_h_level_triggered & pin_mask) || (_l_level_triggered & pin_mask)) {
/* determine the port of triggered interrupt */
volatile uint32_t *afio_exti_ss = &AFIO->EXTISS0 + (pin >> 2);
gpio_port_t port = GPIO_PORT(((*afio_exti_ss >> ((pin & 0x03) * 4)) & 0xfUL));
/* trigger software interrupt if the pin has the according level */
uint32_t level = gpio_ll_read(port) & pin_mask;
if ((_h_level_triggered & pin_mask) && level) {
EXTI->SWIEV |= pin_mask;
}
else if ((_l_level_triggered & pin_mask) && !level) {
EXTI->SWIEV |= pin_mask;
}
}
}
}

View File

@ -454,7 +454,7 @@ static inline bool is_gpio_port_num_valid(uint_fast8_t num);
* value will be chosen instead and `0` is returned.
* @warning Note that hardware GPIO peripherals may have shared building
* blocks. Those *SHOULD* be handed out by the implementation in
* first-come-fist-served fashion. (E.g. if there is only one pull up
* first-come-first-served fashion. (E.g. if there is only one pull up
* resistor per port that can be connected to any pin of that port,
* typically the first pin on the port configured as pull up will
* succeed and subsequent configuration as pull ups for other pins on

View File

@ -117,9 +117,10 @@ void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin);
/**
* @brief Unmask IRQs on the given GPIO pin
*
* Same as @ref gpio_ll_irq_unmask_and_clear except that IRQs that came in during
* Same as @ref gpio_ll_irq_unmask_and_clear except that IRQs that came in while
* masked are not lost.
*
* @warning On same MCUs (most notably STM32) this is impossible to implement.
* @warning On some MCUs (most notably STM32) this is impossible to implement.
* The feature `periph_gpio_ll_irq_unmask` is provided, if this
* function is available.
*

View File

@ -33,6 +33,10 @@ ifneq (,$(filter auto_init_sock_dns,$(USEMODULE)))
endif
endif
ifneq (,$(filter coding,$(USEMODULE)))
USEMODULE += bitfield
endif
ifneq (,$(filter congure_%,$(USEMODULE)))
USEMODULE += congure
endif

1
sys/coding/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

16
sys/coding/doc.txt Normal file
View File

@ -0,0 +1,16 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_coding Error correction codes
* @ingroup sys
* @brief Error correction function libraries
*
* This module provides functions to generate redundancy data for error
* correction.
*/

203
sys/coding/xor.c Normal file
View File

@ -0,0 +1,203 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup sys_coding_xor
* @brief XOR coding algorithm
*
* @{
*
* @file
* @brief XOR coding implementation
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#include <string.h>
#include "bitfield.h"
#include "coding/xor.h"
#define ENABLE_DEBUG 0
#include "debug.h"
static void _gen_parity(const void *data, size_t len, uint8_t *out)
{
const uint8_t *in = data;
memset(out, 0, CODING_XOR_PARITY_LEN(len));
for (unsigned i = 0; i < len; ++i) {
out[i / CONFIG_CODING_XOR_CHECK_BYTES] ^= in[i];
}
}
static inline size_t _transpose_idx(size_t i, size_t width, size_t height)
{
size_t x = i % width;
size_t y = i / width;
return x * height + y;
}
static inline void _swap(uint8_t *a, uint8_t *b)
{
uint8_t tmp = *a;
*a = *b;
*b = tmp;
}
/* https://www.geeksforgeeks.org/inplace-m-x-n-size-matrix-transpose/ */
static void _transpose(uint8_t *data, size_t len, size_t height)
{
BITFIELD(visited, len);
memset(visited, 0, sizeof(visited));
/* A[0] and A[size-1] won't move */
size_t i = 1;
size_t last = len -1;
bf_set(visited, 0);
bf_set(visited, last);
while (i < last) {
size_t cycle_begin = i;
uint8_t tmp = data[i];
do {
/* Input matrix [r x c]
* Output matrix
* i_new = (i*r)%(N-1)
*/
size_t next = (i * height) % last;
_swap(&data[next], &tmp);
bf_set(visited, i);
i = next;
} while (i != cycle_begin);
/* Get Next Move (what about querying random location?) */
i = bf_find_first_unset(visited, len);
}
}
static inline void _mix(void *data, size_t len)
{
_transpose(data, len, len / CONFIG_CODING_XOR_CHECK_BYTES);
}
static inline void _unmix(void *data, size_t len)
{
_transpose(data, len, CONFIG_CODING_XOR_CHECK_BYTES);
}
static bool _recover_byte(const uint8_t *in, size_t width, uint8_t height,
const uint8_t *parity, size_t idx, uint8_t *bitfield,
size_t block_size, uint8_t *out)
{
uint8_t res = parity[idx / CONFIG_CODING_XOR_CHECK_BYTES];
size_t start = idx - idx % CONFIG_CODING_XOR_CHECK_BYTES;
for (unsigned i = start; i < start + CONFIG_CODING_XOR_CHECK_BYTES; ++i) {
/* skip the lost byte */
if (i == idx) {
continue;
}
/* get index of neighbor byte in transposed matrix */
size_t idx_in = _transpose_idx(i, height, width);
if (!bf_isset(bitfield, idx_in / block_size)) {
DEBUG("missing chunk %u\n", idx_in / block_size);
return false;
}
res ^= in[idx_in];
}
*out = res;
return true;
}
static bool _recover_blocks(void *data, size_t len, const uint8_t *parity,
uint8_t *bitfield, size_t block_size)
{
uint8_t *in = data;
const uint8_t height = CONFIG_CODING_XOR_CHECK_BYTES;
const size_t width = len / height;
const uint8_t num_data_blocks = len / block_size;
bool success = true;
for (size_t i = 0; i < len; i += block_size) {
/* block is present, nothing to do */
if (bf_isset(bitfield, i / block_size)) {
continue;
}
DEBUG("try to recover chunk %u / %u\n", i / block_size, num_data_blocks);
for (size_t j = i; j < i + block_size; ++j) {
/* get original byte position */
size_t idx = _transpose_idx(j, width, height);
/* we can only recover the byte if we have the matching parity block */
size_t parity_block = idx / (CONFIG_CODING_XOR_CHECK_BYTES * block_size);
if (!bf_isset(bitfield, num_data_blocks + parity_block)) {
DEBUG("missing parity block %u\n", parity_block);
success = false;
goto next_block;
}
/* try to recover lost byte from parity */
if (!_recover_byte(in, width, height, parity, idx,
bitfield, block_size, &in[j])) {
success = false;
goto next_block;
}
}
bf_set(bitfield, i / block_size);
next_block:
/* try to recover another block */ ;
}
return success;
}
void coding_xor_generate(void *data, size_t len, uint8_t *parity)
{
_gen_parity(data, len, parity);
_mix(data, len);
}
bool coding_xor_recover(void *data, size_t len, uint8_t *parity,
uint8_t *bitfield, size_t block_size, bool recover_parity)
{
size_t num_data_chunks = len / block_size;
size_t num_parity_chunks = CODING_XOR_PARITY_LEN(len) / block_size;
if (!_recover_blocks(data, len, parity, bitfield, block_size)) {
return false;
}
_unmix(data, len);
if (!recover_parity) {
return true;
}
/* recover lost parity blocks */
for (size_t i = 0; i < num_parity_chunks; ++i) {
if (bf_isset(bitfield, num_data_chunks + i)) {
continue;
}
DEBUG("regenerate parity block %u\n", i);
size_t data_len = block_size * CONFIG_CODING_XOR_CHECK_BYTES;
_gen_parity((uint8_t *)data + i * data_len,
data_len, parity + i * block_size);
}
return true;
}
/** @} */

93
sys/include/coding/xor.h Normal file
View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_coding_xor XOR code
* @ingroup sys_coding
* @brief Simple XOR based coding algorithms
*
* @experimental This is a very basic implementation, it can only recover
* 1 lost block in 3 and only has a 33% chance of recovering
* two consecutive lost blocks.
* API / Algorithm might change if that means we can do better.
*
* @{
*
* @file
* @brief XOR coding definitions
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#ifndef CODING_XOR_H
#define CODING_XOR_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Number of payload bytes per parity byte
*/
#ifndef CONFIG_CODING_XOR_CHECK_BYTES
#define CONFIG_CODING_XOR_CHECK_BYTES 3U
#endif
/**
* @brief Get the size of the needed parity buffer for a given payload size
*
* @param in Payload length
*/
#define CODING_XOR_PARITY_LEN(in) (((in) + CONFIG_CODING_XOR_CHECK_BYTES - 1) \
/ CONFIG_CODING_XOR_CHECK_BYTES)
/**
* @brief Generate parity and mix data buffer
*
* This generates parity data to recover one in @ref CONFIG_CODING_XOR_CHECK_BYTES
* bytes.
* The data buffer is then mixed to distribute bytes amongst transfer blocks, so
* that the chance for consecutive bytes to be in the same block is lowered.
*
* @param[in,out] data The data buffer to be processed
* @param[in] len Size of the data buffer
* @param[out] parity Buffer to hold parity data.
* Must be at least `CODING_XOR_PARITY_LEN(len)` bytes
*/
void coding_xor_generate(void *data, size_t len, uint8_t *parity);
/**
* @brief Restore and unmix buffer
*
*
* @param[in, out] data The data buffer to be restored
* @param[in] len Size of the data buffer
* @param[in,out] parity Buffer with parity data.
* Must be at least `CODING_XOR_PARITY_LEN(len)` bytes
* @param[in,out] blocks Bitfieled to indicate which blocks were received.
* This indicates the presence of both data and parity
* blocks. Parity blocks are appended after the last
* data block.
* If a block was restored it's bit will be set.
* @param[in] block_size Size of a data/payload block
* @param[in] recover_parity If true, missing parity blocks will be re-generated
* from data blocks.
*
* @return True if all data blocks were recovered
*/
bool coding_xor_recover(void *data, size_t len, uint8_t *parity,
uint8_t *blocks, size_t block_size, bool recover_parity);
#ifdef __cplusplus
}
#endif
#endif /* CODING_XOR_H */
/** @} */

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1 @@
USEMODULE += coding

View File

@ -0,0 +1,190 @@
/*
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "embUnit/embUnit.h"
#include "bitfield.h"
#include "coding/xor.h"
#include "tests-coding.h"
static void test_coding_xor_building_blocks(void)
{
char string[] = "0123456789AB";
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
coding_xor_generate(string, sizeof(string) - 1, parity);
TEST_ASSERT_EQUAL_INT(0x33, parity[0]);
TEST_ASSERT_EQUAL_INT(0x32, parity[1]);
TEST_ASSERT_EQUAL_INT(0x39, parity[2]);
TEST_ASSERT_EQUAL_INT(0x3A, parity[3]);
TEST_ASSERT_EQUAL_STRING("0369147A258B", string);
uint8_t blocks = 0x80;
bool success = coding_xor_recover(string, sizeof(string) - 1, parity,
&blocks, sizeof(string) - 1, false);
TEST_ASSERT(success);
TEST_ASSERT_EQUAL_STRING("0123456789AB", string);
}
static void test_coding_xor_recovery(void)
{
char string[] = "0123456789AB";
char string_rx[sizeof(string)];
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
const uint8_t chunk_size = 4;
const uint8_t num_chunks = sizeof(string) / chunk_size;
const uint8_t num_parity_chunks = sizeof(parity) / chunk_size;
BITFIELD(chunks, num_chunks + num_parity_chunks);
memset(chunks, 0, sizeof(chunks));
memset(string_rx, 0, sizeof(string_rx));
coding_xor_generate(string, sizeof(string) - 1, parity);
for (unsigned i = 0; i < num_chunks; ++i) {
/* lose a single chunk / packet */
if (i == 1) {
continue;
}
memcpy(string_rx + i * chunk_size,
string + i * chunk_size,
chunk_size);
bf_set(chunks, i);
}
/* we have all parity chunks */
for (unsigned i = 0; i < num_parity_chunks; ++i) {
bf_set(chunks, num_chunks + i);
}
bool success = coding_xor_recover(string_rx, sizeof(string_rx) - 1,
parity, chunks, chunk_size, false);
TEST_ASSERT(success);
TEST_ASSERT_EQUAL_STRING("0123456789AB", string_rx);
}
static void test_coding_xor_recovery_failed(void)
{
char string[] = "0123456789AB";
char string_rx[sizeof(string)];
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
const uint8_t chunk_size = 4;
const uint8_t num_chunks = sizeof(string) / chunk_size;
const uint8_t num_parity_chunks = sizeof(parity) / chunk_size;
BITFIELD(chunks, num_chunks + num_parity_chunks);
memset(chunks, 0, sizeof(chunks));
memset(string_rx, 0, sizeof(string_rx));
coding_xor_generate(string, sizeof(string) - 1, parity);
for (unsigned i = 0; i < num_chunks; ++i) {
/* lose two chunks / packets */
if (i == 1 || i == 2) {
continue;
}
memcpy(string_rx + i * chunk_size,
string + i * chunk_size,
chunk_size);
bf_set(chunks, i);
}
/* we have all parity chunks */
for (unsigned i = 0; i < num_parity_chunks; ++i) {
bf_set(chunks, num_chunks + i);
}
bool success = coding_xor_recover(string_rx, sizeof(string_rx) - 1,
parity, chunks, chunk_size, false);
TEST_ASSERT(!success);
}
static void test_coding_xor_recovery_parity(void)
{
char string[] = "0123456789AB";
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
uint8_t parity_rx[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
const uint8_t chunk_size = 4;
const uint8_t num_chunks = sizeof(string) / chunk_size;
const uint8_t num_parity_chunks = sizeof(parity) / chunk_size;
BITFIELD(chunks, num_chunks + num_parity_chunks);
memset(chunks, 0, sizeof(chunks));
coding_xor_generate(string, sizeof(string) - 1, parity);
/* we have all data chunks */
for (unsigned i = 0; i < num_chunks; ++i) {
bf_set(chunks, i);
}
bool success = coding_xor_recover(string, sizeof(string) - 1, parity_rx,
chunks, chunk_size, true);
TEST_ASSERT(success);
TEST_ASSERT_EQUAL_STRING("0123456789AB", string);
TEST_ASSERT(memcmp(parity, parity_rx, sizeof(parity)) == 0);
}
static void test_coding_xor_recovery_large(void)
{
uint8_t txbuf[1024];
const size_t data_len = 768;
const size_t parity_len = CODING_XOR_PARITY_LEN(data_len);
uint8_t *parity = &txbuf[data_len];
const uint8_t chunk_size = 64;
const size_t num_chunks = (data_len + parity_len) / chunk_size;
BITFIELD(chunks, num_chunks);
memset(chunks, 0, sizeof(chunks));
TEST_ASSERT_EQUAL_INT(sizeof(txbuf), data_len + parity_len);
/* fill TX buffer with known pattern */
for (unsigned i = 0; i < data_len; ++i) {
txbuf[i] = i & 0xFF;
}
coding_xor_generate(txbuf, data_len, parity);
for (unsigned i = 0; i < num_chunks; ++i) {
uint8_t *data = &txbuf[i * chunk_size];
/* lose some chunks */
if (i == 3 || i == 10 || i == 13) {
memset(data, 0, chunk_size);
} else {
bf_set(chunks, i);
}
}
bool success = coding_xor_recover(txbuf, data_len, parity, chunks,
chunk_size, true);
TEST_ASSERT(success);
for (unsigned i = 0; i < data_len; ++i) {
TEST_ASSERT_EQUAL_INT(i & 0xFF, txbuf[i]);
}
}
Test *tests_coding_xor_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_coding_xor_building_blocks),
new_TestFixture(test_coding_xor_recovery),
new_TestFixture(test_coding_xor_recovery_failed),
new_TestFixture(test_coding_xor_recovery_parity),
new_TestFixture(test_coding_xor_recovery_large),
};
EMB_UNIT_TESTCALLER(coding_xor_tests, NULL, NULL, fixtures);
return (Test *)&coding_xor_tests;
}

View File

@ -0,0 +1,14 @@
/*
* Copyright 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#include "tests-coding.h"
void tests_coding(void)
{
TESTS_RUN(tests_coding_xor_tests());
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @addtogroup unittests
* @{
*
* @file
* @brief Unittests for the ``coding`` module
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#ifndef TESTS_CODING_H
#define TESTS_CODING_H
#include "embUnit.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The entry point of this test suite.
*/
void tests_coding(void);
/**
* @brief Generates tests for coding/xor.h
*
* @return embUnit tests if successful, NULL if not.
*/
Test *tests_coding_xor_tests(void);
#ifdef __cplusplus
}
#endif
#endif /* TESTS_CODING_H */
/** @} */