blob: df23b2334a3decfa29f4e47e3625d6071985b8d7 [file] [log] [blame]
/**
* SPDX-License-Identifier: GPL-2.0 only
*
* Copyright (c) 2025 Sandisk Corporation or its affiliates.
*/
/**
* Two-phase circular buffer implementation for producer/consumer separation.
*
* This header defines the data structures and inline functions for a two-phase
* circular buffer, allowing staged writes and explicit commit of data batches.
* Useful for double-buffered systems or scenarios requiring controlled visibility
* of produced data to consumers.
*/
#ifndef PHASE_CIRCULAR_BUFFER_H
#define PHASE_CIRCULAR_BUFFER_H
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
/**
* struct pc_buf - Two-phase circular buffer.
* @commit_head: Index of the next committed element in the buffer (visible to consumer).
* @staging_head: Index of the next staged (but not yet committed) element (written by producer).
* @read_tail: Index of the next element to be read by the consumer.
* @capacity: Total capacity of the buffer (number of elements).
* @buffer: Buffer data.
*
* This structure implements a two-phase circular buffer, where data is first staged
* by advancing @staging_head, and only becomes visible to the consumer when @commit_head
* is explicitly updated. This allows for controlled commit of data batches, useful in
* double-buffered systems or producer/consumer separation.
*/
struct pc_buf {
uint64_t commit_head;
uint64_t staging_head;
uint64_t read_tail;
uint64_t capacity;
uint64_t buffer[];
};
/**
* pcb_alloc - Allocate and initialize buffer.
* @capacity: Number of elements the buffer can hold.
*
* Returns a pointer to the allocated buffer, or NULL on failure.
*/
static inline struct pc_buf *pcb_alloc(uint64_t capacity)
{
size_t size = sizeof(struct pc_buf) + sizeof(uint64_t) * capacity;
struct pc_buf *cb = (struct pc_buf *)malloc(size);
if (!cb)
return NULL;
cb->commit_head = 0;
cb->staging_head = 0;
cb->read_tail = 0;
cb->capacity = capacity;
return cb;
}
/**
* pcb_is_empty - Check if the buffer is empty.
* @cb: pointer to the pc_buf structure.
*
* Returns true if the buffer has no committed data.
*/
static inline bool pcb_is_empty(const struct pc_buf *cb)
{
return cb->read_tail == cb->commit_head;
}
/**
* pcb_is_full - Check if the buffer is full.
* @cb: pointer to the pc_buf structure.
*
* Returns true if the buffer cannot accept more staged data.
*/
static inline bool pcb_is_full(const struct pc_buf *cb)
{
return ((cb->staging_head + 1) % cb->capacity) == cb->read_tail;
}
/**
* pcb_push_staged - Push a value into the staged buffer.
* @cb: pointer to the pc_buf structure.
* @value: value to be staged.
*
* Returns true if the value was successfully staged, false if the buffer is full.
*/
static inline bool pcb_push_staged(struct pc_buf *cb, uint64_t value)
{
if (pcb_is_full(cb))
return false;
cb->buffer[cb->staging_head] = value;
cb->staging_head = (cb->staging_head + 1) % cb->capacity;
return true;
}
/**
* pcb_commit - Commit the staged data to make it visible to consumers.
* @cb: pointer to the pc_buf structure.
*
* Updates the commit head to the current staging head, making
* all staged data visible to consumers. It should be called after staging data.
*/
static inline void pcb_commit(struct pc_buf *cb)
{
cb->commit_head = cb->staging_head;
}
/**
* pcb_pop - Pop a value from the committed buffer.
* @cb: pointer to the pc_buf structure.
* @out: pointer to the variable to store the popped value.
*
* Returns true if a value was successfully popped, false if the buffer is empty.
*/
static inline bool pcb_pop(struct pc_buf *cb, uint64_t *out)
{
if (pcb_is_empty(cb))
return false;
*out = cb->buffer[cb->read_tail];
cb->read_tail = (cb->read_tail + 1) % cb->capacity;
return true;
}
/**
* pcb_print_committed - Print the contents of the committed buffer.
* @cb: pointer to the pc_buf structure.
*
* This function prints all committed data in the buffer.
*/
static inline void pcb_print_committed(const struct pc_buf *cb)
{
uint64_t i = cb->read_tail;
printf("Committed buffer: ");
while (i != cb->commit_head) {
printf("%" PRIu64 " ", cb->buffer[i]);
i = (i + 1) % cb->capacity;
}
printf("\n");
}
/**
* pcb_print_staged - Print the contents of the staged buffer.
* @cb: pointer to the pc_buf structure.
*
* This function prints all staged data that has not yet been committed.
*/
static inline void pcb_print_staged(const struct pc_buf *cb)
{
uint64_t i = cb->commit_head;
printf("Staged (not visible yet): ");
while (i != cb->staging_head) {
printf("%" PRIu64 " ", cb->buffer[i]);
i = (i + 1) % cb->capacity;
}
printf("\n");
}
/**
* pcb_committed_size - Get the size of committed data in the buffer.
* @cb: pointer to the pc_buf structure.
*
* Returns the number of elements that have been committed and are visible to consumers.
*/
static inline uint64_t pcb_committed_size(const struct pc_buf *cb)
{
if (cb->commit_head >= cb->read_tail)
return cb->commit_head - cb->read_tail;
else
return cb->capacity - cb->read_tail + cb->commit_head;
}
/**
* pcb_staged_size - Get the size of staged data in the buffer.
* @cb: pointer to the pc_buf structure.
*
* Returns the number of elements that have been staged but not yet committed.
*/
static inline uint64_t pcb_staged_size(const struct pc_buf *cb)
{
if (cb->staging_head >= cb->commit_head)
return cb->staging_head - cb->commit_head;
else
return cb->capacity - cb->commit_head + cb->staging_head;
}
/**
* pcb_space_available - Check if there is space available for staging.
* @cb: pointer to the pc_buf structure.
*
* Returns true if there is space available for staging new data, false if the buffer is full.
*/
static inline bool pcb_space_available(const struct pc_buf *cb)
{
uint64_t used = pcb_committed_size(cb) + pcb_staged_size(cb);
/* keep 1 slot reserved to distinguish full from empty */
return used < (cb->capacity - 1);
}
#endif /* PHASE_CIRCULAR_BUFFER_H */