Include file '/docs/CHANGELOG.md' not found or reading it failed

Include file ‘/docs/CHANGELOG.md’ not found or reading it failed

Getting started may be the most challenging part of every new library.
This guide is describing how to start with the library quickly and effectively

Library is primarly hosted on `Github <https://github.com/MaJerle/lwrb>`_.

Downloading latest release from `releases area <https://github.com/MaJerle/lwrb/releases>`_ on Github

All releases are available on Github `releases area <https://github.com/MaJerle/lwrb/releases>`_.

This is used when you do not have yet local copy on your machine.

Open console and navigate to path in the system to clone repository to. Use command ``cd your_path``

Open console and navigate to path in the system to clone repository to. Use command 

Clone repository with one of available options below

* Run ``git clone --recurse-submodules https://github.com/MaJerle/lwrb`` command to clone entire repository, including submodules
* Run ``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwrb`` to clone `development` branch, including submodules
* Run ``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwrb`` to clone `latest stable` branch, including submodules

Run ``git clone --recurse-submodules https://github.com/MaJerle/lwrb`` command to clone entire repository, including submodules

``git clone --recurse-submodules https://github.com/MaJerle/lwrb``

Run ``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwrb`` to clone `development` branch, including submodules

``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwrb``

git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwrb

Run ``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwrb`` to clone `latest stable` branch, including submodules

``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwrb``

git clone --recurse-submodules --branch main https://github.com/MaJerle/lwrb

Open console and navigate to path in the system where your repository is located. Use command ``cd your_path``

Open console and navigate to path in the system where your repository is located. Use command 

Run ``git pull origin main`` command to get latest changes on ``main`` branch

Run ``git pull origin develop`` command to get latest changes on ``develop`` branch

Run ``git submodule update --init --remote`` to update submodules to latest version

This is preferred option to use when you want to evaluate library and run prepared examples.
Repository consists of multiple submodules which can be automatically downloaded when cloning and pulling changes from root repository.

At this point it is assumed that you have successfully download library, either with ``git clone`` command or with manual download from the library releases page.
Next step is to add the library to the project, by means of source files to compiler inputs and header files in search path.

At this point it is assumed that you have successfully download library, either with 

 command or with manual download from the library releases page.

*CMake* is the main supported build system. Package comes with the ``CMakeLists.txt`` and ``library.cmake`` files, both located in the ``lwrb`` directory:

``library.cmake``: It is a fully configured set of variables and with library definition. User can include this file to the project file with ``include(path/to/library.cmake)`` and then manually use the variables provided by the file, such as list of source files, include paths or necessary compiler definitions. It is up to the user to properly use the this file on its own.

: It is a fully configured set of variables and with library definition. User can include this file to the project file with 

 and then manually use the variables provided by the file, such as list of source files, include paths or necessary compiler definitions. It is up to the user to properly use the this file on its own.

``CMakeLists.txt``: It is a wrapper-only file and includes ``library.cmake`` file. It is used for when user wants to include the library to the main project by simply calling *CMake* ``add_subdirectory`` command, followed by ``target_link_libraries`` to link external library to the final project.

 file. It is used for when user wants to include the library to the main project by simply calling 

Open ``library.cmake`` and analyze the provided information. Among variables, you can also find list of all possible exposed libraries for the user.

 and analyze the provided information. Among variables, you can also find list of all possible exposed libraries for the user.

Add ``lwrb/src/include`` folder to `include path` of your toolchain. This is where `C/C++` compiler can find the files during compilation process. Usually using ``-I`` flag

 compiler can find the files during compilation process. Usually using 

Add source files from ``lwrb/src/`` folder to toolchain build. These files are built by `C/C++` compilery

To verify proper library setup, minimal example has been prepared.
Run it in your main application file to verify its proper execution

#include "lwrb/lwrb.h"

/* Declare rb instance & raw data */
lwrb_t buff;
uint8_t buff_data[8];

/* Application code ... */
lwrb_init(&buff, buff_data, sizeof(buff_data)); /* Initialize buffer */

/* Write 4 bytes of data */
lwrb_write(&buff, "0123", 4);

/* Print number of bytes in buffer */
printf("Bytes in buffer: %d\r\n", (int)lwrb_get_full(&buff));
/* Will print "4" */

/* Now let's read */
uint8_t data[8]; /* Application working data */
size_t len;

/* Read from buffer, will return number of bytes read */
len = lwrb_read(&buff, data, sizeof(data));
printf("Number of bytes read: %d\r\n", (int)len);

/* Data is now available in the "data" variable */

LwRB is a generic *FIFO* (First In; First Out) buffer library optimized for embedded systems.

 (First In; First Out) buffer library optimized for embedded systems.

:ref:`download_library` :ref:`getting_started` `Open Github <https://github.com/MaJerle/lwrb>`_ `Donate <https://paypal.me/tilz0R>`_

Written in C (C11), compatible with ``size_t`` for size data types

Uses optimized memory copy instead of loops to read/write data from/to memory

Thread safe when used as pipe with single write and single read entries

Interrupt safe when used as pipe with single write and single read entries

Suitable for DMA transfers from and to memory with zero-copy overhead between buffer and application memory

Fresh contributions are always welcome. Simple instructions to proceed:

Respect `C style & coding rules <https://github.com/MaJerle/c-code-style>`_ used by the library

`C style & coding rules <https://github.com/MaJerle/c-code-style>`_

Create a pull request to ``develop`` branch with new features or bug fixes

/* Application variables */
uint8_t data[2];

/* Try to read buffer */
/* len holds number of bytes read */
/* Read until len == 0, when buffer is empty */
while ((len = lwrb_read(&buff, data, sizeof(data))) > 0) {
    printf("Successfully read %d bytes\r\n", (int)len);
}

Include file '/docs/lwrb/lwrb-develop-docs/AUTHORS' not found or reading it failed

Include file ‘/docs/lwrb/lwrb-develop-docs/AUTHORS’ not found or reading it failed

Include file '/docs/lwrb/lwrb-develop-docs/CHANGELOG.md' not found or reading it failed

Include file ‘/docs/lwrb/lwrb-develop-docs/CHANGELOG.md’ not found or reading it failed

Include file '/docs/lwrb/lwrb-develop-docs/LICENSE' not found or reading it failed

Include file ‘/docs/lwrb/lwrb-develop-docs/LICENSE’ not found or reading it failed

Buffer size shall always be ``1`` byte bigger than anticipated data size.

When application uses buffer for some data block ``N`` times, it is advised to set buffer size to ``1`` byte more than ``N * block_size`` is.
This is due to ``R`` and ``W`` pointers alignment.

/* Number of data blocks to write */
#define N          3

/* Create custom data structure */
/* Data is array of 2 32-bit words, 8-bytes */
uint32_t d[2];

/* Create buffer structures */
lwrb_t buff_1;
lwrb_t buff_2;

/* Create data for buffers. Use sizeof structure,
   multiplied by N (for N instances) */
/* Buffer with + 1 bytes bigger memory */
uint8_t buff_data_1[sizeof(d) * N + 1];
/* Buffer without + 1 at the end */
uint8_t buff_data_2[sizeof(d) * N];

/* Write result values */
size_t len_1;
size_t len_2;

/* Initialize buffers */
lwrb_init(&buff_1, buff_data_1, sizeof(buff_data_1));
lwrb_init(&buff_2, buff_data_2, sizeof(buff_data_2));

/* Write data to buffer */
for (size_t i = 0; i < N; ++i) {
    /* Prepare data */
    d.a = i;
    d.b = i * 2;

    /* Write data to both buffers, memory copy from d to buffer */
    len_1 = lwrb_write(&buff_1, d, sizeof(d));
    len_2 = lwrb_write(&buff_2, d, sizeof(d));

    /* Print results */
    printf("Write buffer 1: %d/%d bytes; buffer 2: %d/%d\r\n",
        (int)len_1, (int)sizeof(d),
        (int)len_2, (int)sizeof(d));
}

Write: buffer 1: 8/8; buffer 2: 8/8
Write: buffer 1: 8/8; buffer 2: 7/8 <-- See here -->

When using LwRB in the application, it may be useful to get notification on different events,
such as info when something has been written or read to/from buffer.

Library has support for events that get called each time there has been a modification
in the buffer data, that means on every read or write operation.

Notify application layer that LwRB operation has been executed and send debug message

Unlock semaphore when sufficient amount of bytes have been written/read from/to buffer when application uses operating system

Write notification to message queue at operating system level to wakeup another task

Every operation that modified `read` or `write` internal pointers,
is considered as read or write operation. An exception is *reset* event that sets
both internal pointers to `0`

 internal pointers,
is considered as read or write operation. An exception is 

/**
 * \brief           Buffer event function
 */
void
my_buff_evt_fn(lwrb_t* buff, lwrb_evt_type_t type, size_t len) {
    switch (type) {
        case LWRB_EVT_RESET:
            printf("[EVT] Buffer reset event!\r\n");
            break;
        case LWRB_EVT_READ:
            printf("[EVT] Buffer read event: %d byte(s)!\r\n", (int)len);
            break;
        case LWRB_EVT_WRITE:
            printf("[EVT] Buffer write event: %d byte(s)!\r\n", (int)len);
            break;
        default: break;
    }
}

/* Later in the code... */

/* Init buffer and set event function */
lwrb_init(&buff, buff_data, sizeof(buff_data));
lwrb_set_evt_fn(&buff, my_buff_evt_fn);

This section shows different buffer corner cases and provides basic understanding how data are managed internally.

.. figure:: ../static/images/buff_cases.svg
    :align: center
    :alt: Different buffer corner cases

    Different buffer corner cases

``R`` represents `Read` pointer. Read on read/write operations. Modified on read operation only

 pointer. Read on read/write operations. Modified on read operation only

``W`` represents `Write` pointer. Read on read/write operations. Modified on write operation only

 pointer. Read on read/write operations. Modified on write operation only

``S`` represents `Size` of buffer. Used on all operations, never modified (atomic value)

  * Valid number of ``W`` and ``R`` pointers are between ``0`` and ``S - 1``

 of buffer. Used on all operations, never modified (atomic value)

* Valid number of ``W`` and ``R`` pointers are between ``0`` and ``S - 1``

Valid number of ``W`` and ``R`` pointers are between ``0`` and ``S - 1``

Buffer size is ``S = 8``, thus valid number range for ``W`` and ``R`` pointers is ``0 - 7``.

  * ``R`` and ``W`` numbers overflow at ``S``, thus valid range is always ``0, 1, 2, 3, ..., S - 2, S - 1, 0, 1, 2, 3, ..., S - 2, S - 1, 0, ...``
  * Example ``S = 4``: ``0, 1, 2, 3, 0, 1, 2, 3, 0, 1, ...``

* ``R`` and ``W`` numbers overflow at ``S``, thus valid range is always ``0, 1, 2, 3, ..., S - 2, S - 1, 0, 1, 2, 3, ..., S - 2, S - 1, 0, ...``
* Example ``S = 4``: ``0, 1, 2, 3, 0, 1, 2, 3, 0, 1, ...``

``R`` and ``W`` numbers overflow at ``S``, thus valid range is always ``0, 1, 2, 3, ..., S - 2, S - 1, 0, 1, 2, 3, ..., S - 2, S - 1, 0, ...``

``0, 1, 2, 3, ..., S - 2, S - 1, 0, 1, 2, 3, ..., S - 2, S - 1, 0, ...``

0, 1, 2, 3, ..., S - 2, S - 1, 0, 1, 2, 3, ..., S - 2, S - 1, 0, ...

Maximal number of bytes buffer can hold is always ``S - 1``, thus example buffer can hold up to ``7`` bytes

``R`` and ``W`` pointers always point to the next read/write operation

When ``W == R - 1``, buffer is considered full.

  * ``W == R - 1`` is valid only if ``W`` and ``R`` overflow at buffer size ``S``.
  * Always add ``S`` to calculated number and then use modulus ``S`` to get final value

* ``W == R - 1`` is valid only if ``W`` and ``R`` overflow at buffer size ``S``.
* Always add ``S`` to calculated number and then use modulus ``S`` to get final value

``W == R - 1`` is valid only if ``W`` and ``R`` overflow at buffer size ``S``.

Always add ``S`` to calculated number and then use modulus ``S`` to get final value

Example 1, add ``2`` numbers: ``2 + 3 = (3 + 2 + S) % S = (3 + 2 + 4) % 4 = (5 + 4) % 4 = 1``

Example 2, subtract ``2`` numbers: ``2 - 3 = (2 - 3 + S) % S = (2 - 3 + 4) % 4 = (-1 + 4) % 4 = 3``

``2 - 3 = (2 - 3 + S) % S = (2 - 3 + 4) % 4 = (-1 + 4) % 4 = 3``

Case **B**: Buffer holds ``W - R = 4 - 0 = 4`` bytes as ``W > R``

Case **C**: Buffer is full as ``W == R - 1`` or ``7 == 0 - 1`` or ``7 = (0 - 1 + S) % S = (0 - 1 + 8) % 8 = (-1 + 8) % 8 = 7``

  * ``R`` and ``W`` can hold ``S`` different values, from ``0`` to ``S - 1``, that is modulus of ``S``
  * Buffer holds ``W - R = 7 - 0 = 7`` bytes as ``W > R``

* ``R`` and ``W`` can hold ``S`` different values, from ``0`` to ``S - 1``, that is modulus of ``S``
* Buffer holds ``W - R = 7 - 0 = 7`` bytes as ``W > R``

``R`` and ``W`` can hold ``S`` different values, from ``0`` to ``S - 1``, that is modulus of ``S``

Case **D**: Buffer holds ``S - (R - W) = 8 - (5 - 3) = 6`` bytes as ``R > W``

Case **E**: Buffer is full as ``W == R - 1`` (``4 = 5 - 1``) and holds ``S - (R - W) = 8 - (5 - 4) ) = 7`` bytes

One of the key features of LwRB library is that it can be seamlessly integrated with DMA controllers on embedded systems.

DMA stands for *Direct Memory Access* controller and is usually used to off-load CPU.
More about DMA is available on `Wikipedia <https://en.wikipedia.org/wiki/Direct_memory_access>`_.

 controller and is usually used to off-load CPU.
More about DMA is available on 

DMA controllers normally use source and destination memory addresses to transfer data in-between.
This features, together with LwRB, allows seamless integration and zero-copy of application data at interrupts after DMA transfer has been completed.
Some manual work is necessary to be handled, but this is very minor in comparison of writing byte-by-byte to buffer at (for example) each received character.

DMA transfers data from LwRB memory to (usually) some hardware IP

This describes how to pass LwRB output memory address as pointer to DMA (or any other processing function).
After data is successfully processed, application can skip processed data and mark buffer as free for new data being written to it.

.. figure:: ../static/images/buff_lin_read_skip.svg
    :alt: Data transfer from memory to hardware IP

    Data transfer from memory to hardware IP

Case **B**: State after skipping ``R`` pointer for ``3`` bytes. Buffer now holds ``4`` remaining bytes

Case **C**: Buffer is empty, no more memory available for read operation

uint8_t* data;

/* Initialize buffer, use buff_data as data array */

/* Use write, read operations, process data */
/* ... */

/* IMAGE PART A */

/* At this stage, we have buffer as on image above */
/* R = 5, W = 4, buffer is considered full */

/* Get length of linear memory at read pointer */
/* Function returns 3 as we can read 3 bytes from buffer in sequence */
/* When function returns 0, there is no memory available in the buffer for read anymore */
if ((len = lwrb_get_linear_block_read_length(&buff)) > 0) {
    /* Get pointer to first element in linear block at read address */
    /* Function returns &buff_data[5] */
    data = lwrb_get_linear_block_read_address(&buff);

    /* Send data via DMA and wait to finish (for sake of example) */
    send_data(data, len);

    /* Now skip sent bytes from buffer = move read pointer */
    lwrb_skip(&buff, len);

    /* Now R points to top of buffer, R = 0 */
    /* At this point, we are at image part B */
}

/* IMAGE PART B */

/* Function returns 4 as we can read 4 bytes from buffer in sequence */
    /* Function returns &buff_data[0] */

    /* Read pointer is moved for len bytes */

    /* Now R points to 4, that is R == W and buffer is now empty */
    /* At this point, we are at image part C */
}

/* IMAGE PART C */

/* Buffer is considered empty as R == W */

Part **A** on image clearly shows that not all data bytes are linked in single contiguous block of memory.
To send all bytes from lwrb, it might be necessary to repeat procedure multiple times

 on image clearly shows that not all data bytes are linked in single contiguous block of memory.

/* Initialization part skipped */

/* When function returns 0, there is no memory
   available in the buffer for read anymore */
while ((len = lwrb_get_linear_block_read_length(&buff)) > 0) {

    /* If max length needs to be considered */
    /* simply decrease it and use smaller len on skip function */
    if (len > max_len) {
        len = max_len;
    }

}

Similar to reading data from buffer with zero-copy overhead, it is possible to write to lwrb with zero-copy overhead too.
Only difference is that application now needs pointer to write memory address and length of maximal number of bytes to directly copy into buffer.
After successful processing, buffer advance operation is necessary to manually increase write pointer and to increase number of bytes in buffer.

.. figure:: ../static/images/buff_lin_write_advance.svg

Case **A**: Initial state, buffer is empty as ``R == W``

  * Based on ``W`` pointer position, application could write ``4`` bytes to contiguous block of memory

* Based on ``W`` pointer position, application could write ``4`` bytes to contiguous block of memory

Based on ``W`` pointer position, application could write ``4`` bytes to contiguous block of memory

Case **B**: State after advancing `W` pointer for `4` bytes. Buffer now holds `4` bytes and has ``3`` remaining available

Case **C**: Buffer is full, no more free memory available for write operation

: Buffer is full, no more free memory available for write operation

/* ... */

/* R = 4, W = 4, buffer is considered empty */

/* Get length of linear memory at write pointer */
/* Function returns 4 as we can write 4 bytes to buffer in sequence */
/* When function returns 0, there is no memory available in the buffer for write anymore */
if ((len = lwrb_get_linear_block_write_length(&buff)) > 0) {
    /* Get pointer to first element in linear block at write address */
    /* Function returns &buff_data[4] */
    data = lwrb_get_linear_block_write_address(&buff);

    /* Receive data via DMA and wait to finish (for sake of example) */
    /* Any other hardware may directly write to data array */
    /* Data array has len bytes length */
    /* Or use memcpy(data, my_array, len); */
    receive_data(data, len);

    /* Now advance buffer for written bytes to buffer = move write pointer */
    /* Write pointer is moved for len bytes */
    lwrb_advance(&buff, len);

    /* Now W points to top of buffer, W = 0 */
}

/* Function returns 3 as we can write 3 bytes to buffer in sequence */

    /* Now W points to 3, R points to 4, that is R == W + 1 and buffer is now full */
}

/* Buffer is considered full as R == W + 1 */

This is an example showing pseudo code for implementing data transfer using DMA with zero-copy overhead.
For read operation purposes, application gets direct access to LwRB read pointer and length of contiguous memory.

It is assumed that after DMA transfer completes, interrupt is generated (embedded system) and buffer is skipped in the interrupt.

Buffer skip operation is used to mark sent data as processed and to free memory for new writes to buffer

/* Working data length */
volatile size_t len;

/* Send data function */
void send_data(void);

int
main(void) {
    /* Initialize buffer */
    lwrb_init(&buff, buff_data, sizeof(buff_data));

    /* Write 4 bytes of data */
    lwrb_write(&buff, "0123", 4);

    /* Send data over DMA */
    send_data();

    while (1);
}

/* Send data over DMA */
void
send_data(void) {
    /* If len > 0, DMA transfer is on-going */
    if (len > 0) {
        return;
    }

    /* Get maximal length of buffer to read data as linear memory */
    len = lwrb_get_linear_block_read_length(&buff);
        /* Get pointer to read memory */
        uint8_t* data = lwrb_get_linear_block_read_address(&buff);

        /* Start DMA transfer */
        start_dma_transfer(data, len);
    }

    /* Function does not wait for transfer to finish */
}

/* Interrupt handler */
/* Called on DMA transfer finish */
void
DMA_Interrupt_handler(void) {
    /* Transfer finished */
        /* Now skip the data (move read pointer) as they were successfully transferred over DMA */
        lwrb_skip(&buff, len);

        /* Reset length = DMA is not active */
        len = 0;

        /* Try to send more */
        send_data();
    }
}

Check `STM32 UART DMA TX RX Github <https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx>`_ repository for use cases.

`STM32 UART DMA TX RX Github <https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx>`_

Ring buffers are effectively used in embedded systems with or without operating systems.
Common problem most of implementations have, is linked to multi-thread environment (when using OS) or reading/writing from/to interrupts.
Question becomes *What happens if I write to buffer while another thread is reading from it?*

Question becomes 

*What happens if I write to buffer while another thread is reading from it?*

What happens if I write to buffer while another thread is reading from it?

One of the main requirements (beside being lightweight) of *LwRB* was to allow *read-while-write* or *write-while-read* operations.
This is  achieved only when there is single write entry point and single read exit point.

 operations.

.. figure:: ../static/images/buff_thread_safety_single_read_write.svg
    :alt: Write and read operation with single entry and exit points

    Write and read operation with single entry and exit points

Often called and used as *pipe* to write (for example) raw data to the buffer allowing another task
to process the data from another thread.

 to write (for example) raw data to the buffer allowing another task

No race-condition is introduced when application uses LwRB with single write entry and single read exit point.
LwRB uses *C11* standard ``stdatomic.h`` library to ensure read and write operations are race-free for any platform supporting C11 and its respected atomic library.

LwRB uses 

 library to ensure read and write operations are race-free for any platform supporting C11 and its respected atomic library.

Thread (or interrupt) safety, with one entry and one exit points, is achieved by storing actual buffer read and write pointer variables to the local ones before performing any calculation.
Therefore multiple *conditional* checks are guaranteed to be performed on the same local variables, even if actual buffer pointers get modified.

Therefore multiple 

 checks are guaranteed to be performed on the same local variables, even if actual buffer pointers get modified.

Read pointer could get changed by interrupt or another thread when application tries to write to buffer

Write pointer could get changed by interrupt or another thread when application ties to read from buffer

Even single entry and single exit points may introduce race condition, especially on smaller system, such as 8-bit or 16-bit system, or in general,
where arbitrary type (normaly `size_t`) is `sizeof(type) > architecture_size`.
This is solved by C11 atomic library, that ensures atomic reads and writes to key structure members.

where arbitrary type (normaly 

.

Thread safety gets completely broken when application does one of the following:

Uses multiple read/write exit/entry points to the same LwRB instance

.. figure:: ../static/images/buff_thread_safety_2_thread_write.svg
    :alt: Write operation to same LwRB instance from 2 threads

    Write operation to same LwRB instance from 2 threads.
    Write protection is necessary to ensure thread safety.

Write operation to same LwRB instance from 2 threads.
Write protection is necessary to ensure thread safety.

.. figure:: ../static/images/buff_thread_safety_2_main_irq_write.svg
    :alt: Write operation to same LwRB instance from main loop and interrupt context

    Write operation to same LwRB instance from main loop and interrupt context.

Write operation to same LwRB instance from main loop and interrupt context

Write operation to same LwRB instance from main loop and interrupt context.

.. figure:: ../static/images/buff_thread_safety_2_thread_read.svg
    :alt: Read operation from same LwRB instance from 2 threads

    Read operation from same LwRB instance from 2 threads.
    Read protection is necessary to ensure thread safety.

Read operation from same LwRB instance from 2 threads.
Read protection is necessary to ensure thread safety.

.. figure:: ../static/images/buff_thread_safety_2_thread_read_write.svg
    :alt: Read from and write to operations using LwRB instance from multiple threads

    Read and write operations are executed from multiple threads.
    Both, read and write, operations require exclusive access.

Read from and write to operations using LwRB instance from multiple threads

Read and write operations are executed from multiple threads.
Both, read and write, operations require exclusive access.

Above use cases are examples when thread safety gets broken. Application must ensure
exclusive access only to the part in *dashed-red* rectangle.

exclusive access only to the part in 

/* Declare variables */
lwrb_t rb;

/* 2 mutexes, one for write operations,
    one for read operations */
mutex_t m_w, m_r;

/* 4 threads below, 2 for write, 2 for read */
void
thread_write_1(void* arg) {
    /* Use write mutex */
    while (1) {
        mutex_get(&m_w);
        lwrb_write(&rb, ...);
        mutex_give(&m_w);
    }
}

void
thread_write_2(void* arg) {
    while (1) {
    }
}

void
thread_read_1(void* arg) {
    /* Use read mutex */
    while (1) {
        mutex_get(&m_r);
        lwrb_read(&rb, ...);
        mutex_give(&m_r);
    }
}

void
thread_read_2(void* arg) {
    while (1) {
    }
}

Read and write operations can be used simultaneously hence it is perfectly valid if
access is granted to *read* operation while *write* operation from one thread takes place.

access is granted to 

``2`` different mutexes are used for read and write due to the implementation,
allowing application to use buffer in *read-while-write* and *write-while-read* mode.
Mutexes are used to prevent *write-while-write* and *read-while-read* operations respectively

 different mutexes are used for read and write due to the implementation,
allowing application to use buffer in 

For *multi-entry-point-single-exit-point* use case, *read* mutex is not necessary.
For *single-entry-point-multi-exit-point* use case, *write* mutex is not necessary.

Functions considered as *read* operation are ``read``, ``skip``, ``peek`` and ``linear read``.
Functions considered as *write* operation are ``write``, ``advance`` and ``linear write``.

While thread-safety concepts are very important, depending on the system architecture and variable sizes (and hardware cache),
application must also ensure that all the writes and reads to the internal variables are executed in atomic manner.

Especially critical case is when read/write from/to variable isn't ``1`` cycle on specific architecture (for instance 32-bit variable on 8-bit CPU).

Especially critical case is when read/write from/to variable isn’t 

 cycle on specific architecture (for instance 32-bit variable on 8-bit CPU).

Library (in its default configuration) uses ``stdatomic`` feature from *C11* language, and relies on a compiler to properly
generate necessary calls to make sure, all reads and writes are atomic.

 language, and relies on a compiler to properly

Atomicity is required even if ring buffer is configured in *fifo* mode, with single write point and single read point.

You can disable atomic operations in the library, by defining ``LWRB_DISABLE_ATOMIC`` global macro (typically with ``-D`` compiler option).
It is then up to the developer to make sure architecture properly handles atomic operations.

 compiler option).