blob: 7ca9be5b8774e7ea62d5e30c3231afaf43846baf [file] [log] [blame]
/****************************************************************************
*
* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved
*
* Maxwell Software Mailbox Emulation shared data definitions.
*
* Ref: SC-506707-DD - Structure version 2
*
****************************************************************************/
/**
* Circular buffer backed packet stream (Implementation)
*
* To allow easy distinction between full and empty buffers, there
* is one slot reserved in the available space. This means that the following
* conditions can be used to easily test the buffer's status without tracking
* the used size explicitly:
* - read_index == write_index : Buffer is empty
* - write_index + 1 == read_index : Buffer is full
* Otherwise if read_index == write_index then the buffer could be either
* empty or full.
*/
/* Implements */
#include "cpacket_buffer.h"
/* Uses */
#include <scsc/scsc_logring.h>
#include <linux/module.h>
#include "miframman.h"
/**
* Advances the read/write index by the given amount, wrapping around if this
* exceeds the buffer length.
*/
static inline void cpacketbuffer_advance_index(uint32_t *idx, uint32_t amount, uint32_t buffer_size)
{
*idx = (*idx + amount) % buffer_size;
}
/**
* Converts a buffer address to a read/write index.
*
* The address must be at the start of a packet.
*/
static inline uint32_t cpacketbuffer_address_to_index(struct cpacketbuffer *buffer, const uint8_t *address)
{
ptrdiff_t offset = address - (uint8_t *)buffer->buffer;
return (offset / buffer->packet_size) % buffer->num_packets;
}
/**
* Converts a buffer read/write index to an address.
*/
static inline uint8_t *cpacketbuffer_index_to_address(struct cpacketbuffer *buffer, uint32_t *idx)
{
return (uint8_t *)buffer->buffer + (*idx % buffer->num_packets) * buffer->packet_size;
}
/** Returns the current read index of the buffer */
static inline uint32_t cpacketbuffer_read_index(const struct cpacketbuffer *buffer)
{
return *buffer->read_index;
}
/** Returns the current write index of the buffer */
static inline uint32_t cpacketbuffer_write_index(const struct cpacketbuffer *buffer)
{
return *buffer->write_index;
}
/** Writes a set of whole packets to the buffer */
static bool cpacketbuffer_write_block(struct cpacketbuffer *buffer, const void *buf, uint32_t num_bytes)
{
uint32_t num_packets = (num_bytes + buffer->packet_size - 1) / buffer->packet_size;
const uint8_t *source_data;
uint32_t start_write_index;
uint32_t end_write_index;
if (num_packets > cpacketbuffer_free_space(buffer)) {
/* Not enough free packets to write this block */
SCSC_TAG_ERR(CPKTBUFF, "Not enough free packets in buffer, requested %u free_space %u\n", num_packets, cpacketbuffer_free_space(buffer));
return false;
}
source_data = (const uint8_t *)buf;
start_write_index = cpacketbuffer_write_index(buffer);
end_write_index = start_write_index;
cpacketbuffer_advance_index(&end_write_index, num_packets - 1, buffer->num_packets);
if (end_write_index < start_write_index) {
/* Writes wrap around the buffer, split the write in two */
uint32_t initial_write_size = (buffer->num_packets - start_write_index) * buffer->packet_size;
memcpy(cpacketbuffer_index_to_address(buffer, buffer->write_index), source_data, initial_write_size);
memcpy(buffer->buffer, source_data + initial_write_size, num_bytes - initial_write_size);
} else
memcpy(cpacketbuffer_index_to_address(buffer, buffer->write_index), source_data, num_bytes);
/* CPU memory barrier */
smp_wmb();
cpacketbuffer_advance_index(buffer->write_index, num_packets, buffer->num_packets);
return true;
}
/** Log buffer configuration at DEBUG level */
static void mxcbufconf_print(const struct mxcbufconf *buf_conf)
{
SCSC_TAG_DBG4(CPKTBUFF, "mxcbufconf\n\tbuffer_loc: 0x%x\n\tnum_packets: %d\n\tpacket_size: %d\n\treadix: 0x%x\n\twriteix: 0x%x\n",
buf_conf->buffer_loc,
buf_conf->num_packets,
buf_conf->packet_size,
buf_conf->read_index_loc,
buf_conf->write_index_loc
);
}
/** Externally visible functions */
int cpacketbuffer_init(struct cpacketbuffer *buffer, uint32_t num_packets, uint32_t packet_size, struct scsc_mx *mx)
{
struct miframman *miframman;
uint32_t *ridx;
uint32_t *widx;
void *mem;
buffer->mx = mx;
miframman = scsc_mx_get_ramman(mx);
mem = miframman_alloc(miframman, num_packets * packet_size, 4, MIFRAMMAN_OWNER_COMMON);
if (!mem)
return -ENOMEM;
ridx = miframman_alloc(miframman, sizeof(uint32_t), 4, MIFRAMMAN_OWNER_COMMON);
if (!ridx) {
miframman_free(miframman, mem);
return -ENOMEM;
}
widx = miframman_alloc(miframman, sizeof(uint32_t), 4, MIFRAMMAN_OWNER_COMMON);
if (!widx) {
miframman_free(miframman, ridx);
miframman_free(miframman, mem);
return -ENOMEM;
}
buffer->buffer = mem;
buffer->num_packets = num_packets;
buffer->packet_size = packet_size;
buffer->read_index = ridx;
buffer->write_index = widx;
*buffer->read_index = 0;
*buffer->write_index = 0;
return 0;
}
void cpacketbuffer_release(struct cpacketbuffer *buffer)
{
struct miframman *miframman;
miframman = scsc_mx_get_ramman(buffer->mx);
miframman_free(miframman, buffer->read_index);
miframman_free(miframman, buffer->write_index);
miframman_free(miframman, buffer->buffer);
}
bool cpacketbuffer_write(struct cpacketbuffer *buffer, const void *buf, uint32_t num_bytes)
{
uint32_t start_write_index;
if (buf == NULL || num_bytes == 0) {
SCSC_TAG_ERR(CPKTBUFF, "Error buf %p num_bytes %u\n", buf, num_bytes);
return false;
}
SCSC_TAG_DBG4(CPKTBUFF, "Before: *buffer->read_index=0x%x *buffer->write_index=0x%x\n",
*buffer->read_index, *buffer->write_index);
start_write_index = cpacketbuffer_write_index(buffer);
if (!cpacketbuffer_write_block(buffer, buf, num_bytes)) {
SCSC_TAG_ERR(CPKTBUFF, "Error writing cpacketbuffer_write_block %u bytes\n", num_bytes);
return false;
}
/* CPU memory barrier */
smp_wmb();
SCSC_TAG_DBG4(CPKTBUFF, "After: *buffer->read_index=0x%x *buffer->write_index=0x%x\n",
*buffer->read_index, *buffer->write_index);
return true;
}
bool cpacketbuffer_write_gather(struct cpacketbuffer *buffer, const void **bufs, uint32_t *num_bytes, uint32_t num_bufs)
{
uint32_t start_write_index;
uint32_t i;
if (bufs == NULL || num_bytes == 0 || num_bufs == 0) {
SCSC_TAG_ERR(CPKTBUFF, "Error bufs %p num_bufs %u\n", bufs, num_bufs);
return false;
}
start_write_index = cpacketbuffer_write_index(buffer);
for (i = 0; i < num_bufs; ++i) {
/* Write all the whole packets from this buffer */
uint32_t partial_packet_len = num_bytes[i] % buffer->packet_size;
uint32_t whole_packet_len = num_bytes[i] - partial_packet_len;
if (whole_packet_len > 0 &&
!cpacketbuffer_write_block(buffer, bufs[i], whole_packet_len)) {
SCSC_TAG_ERR(CPKTBUFF, "Error writing cpacketbuffer_write_block partial %u whole %u\n", partial_packet_len, whole_packet_len);
SCSC_TAG_ERR(CPKTBUFF, "num_buf %u index %u num_bytes[i] %u buffer->packet_size %u\n",
num_bufs, i, num_bytes[i], buffer->packet_size);
return false;
}
if (partial_packet_len != 0) {
/* Partial packet present - write this and enough from the next data block(s) to fill this packet
* before continuing */
uint32_t needed_bytes;
uint8_t *write_ptr = cpacketbuffer_index_to_address(buffer, buffer->write_index);
memcpy(write_ptr, (const uint8_t *)bufs[i] + whole_packet_len, partial_packet_len);
write_ptr += partial_packet_len;
needed_bytes = buffer->packet_size - partial_packet_len;
while (i + 1 < num_bufs && needed_bytes > 0) {
uint32_t num_bytes_to_take = num_bytes[i + 1] >= needed_bytes ? needed_bytes : num_bytes[i + 1];
memcpy(write_ptr, bufs[i + 1], num_bytes_to_take);
bufs[i + 1] = (const uint8_t *)bufs[i + 1] + num_bytes_to_take;
num_bytes[i + 1] -= num_bytes_to_take;
write_ptr += num_bytes_to_take;
needed_bytes -= num_bytes_to_take;
if (num_bytes[i + 1] == 0)
/* This buffer has been consumed entirely, move to the next */
++i;
}
/* CPU memory barrier */
smp_wmb();
cpacketbuffer_advance_index(buffer->write_index, 1, buffer->num_packets);
}
}
/* CPU memory barrier */
smp_wmb();
return true;
}
uint32_t cpacketbuffer_read(struct cpacketbuffer *buffer, void *buf, uint32_t num_bytes)
{
uint8_t *read_start;
uint32_t num_packets;
uint32_t num_available_packets;
if (buf == NULL || cpacketbuffer_is_empty(buffer))
return 0;
/* Work out where we're reading from */
read_start = cpacketbuffer_index_to_address(buffer, buffer->read_index);
num_packets = num_bytes / buffer->packet_size;
if (num_bytes % buffer->packet_size != 0)
/* Partial data packet read requested, this means we remove the whole thing */
++num_packets;
/* Ensure we have enough actual data to satisfy the read request, otherwise
* truncate the read request to the amount of data available. */
num_available_packets = buffer->num_packets - (cpacketbuffer_free_space(buffer) + 1);
if (num_packets > num_available_packets) {
num_packets = num_available_packets;
num_bytes = num_packets * buffer->packet_size;
}
if (cpacketbuffer_read_index(buffer) + num_packets > buffer->num_packets) {
/* The read wraps around the end of the buffer, do it in two parts */
uint32_t initial_read_size = (buffer->num_packets - cpacketbuffer_read_index(buffer)) * buffer->packet_size;
memcpy(buf, read_start, initial_read_size);
memcpy((uint8_t *)buf + initial_read_size, buffer->buffer, num_bytes - initial_read_size);
} else
memcpy(buf, read_start, num_bytes);
/* CPU memory barrier */
smp_wmb();
/* Update the read index with how many packets we pulled out of the stream */
cpacketbuffer_advance_index(buffer->read_index, num_packets, buffer->num_packets);
/* CPU memory barrier */
smp_wmb();
return num_bytes;
}
const void *cpacketbuffer_peek(struct cpacketbuffer *buffer, const void *current_packet)
{
uint32_t next_packet_index;
SCSC_TAG_DBG4(CPKTBUFF, "*buffer->read_index=0x%x *buffer->write_index=0x%x\n",
*buffer->read_index, *buffer->write_index);
if (current_packet == NULL)
/* Reading the first available packet */
next_packet_index = cpacketbuffer_read_index(buffer);
else
/* Reading the next available packet past the current value of current_packet */
next_packet_index = cpacketbuffer_address_to_index(buffer,
(const uint8_t *)current_packet + buffer->packet_size);
if (next_packet_index == cpacketbuffer_write_index(buffer))
/* No more packets available */
return NULL;
return cpacketbuffer_index_to_address(buffer, &next_packet_index);
}
void cpacketbuffer_peek_complete(struct cpacketbuffer *buffer, const void *current_packet)
{
if (current_packet == NULL)
return;
/* The address we're given is the last packet read, so the new read index is for the next one */
*buffer->read_index = cpacketbuffer_address_to_index(buffer,
(const uint8_t *)current_packet + buffer->packet_size);
/* CPU memory barrier */
smp_wmb();
}
bool cpacketbuffer_is_empty(const struct cpacketbuffer *buffer)
{
return cpacketbuffer_read_index(buffer) == cpacketbuffer_write_index(buffer);
}
bool cpacketbuffer_is_full(const struct cpacketbuffer *buffer)
{
return (cpacketbuffer_write_index(buffer) + 1) % buffer->num_packets == cpacketbuffer_read_index(buffer);
}
uint32_t cpacketbuffer_free_space(const struct cpacketbuffer *buffer)
{
uint32_t base_free_space = cpacketbuffer_write_index(buffer) >= cpacketbuffer_read_index(buffer) ?
cpacketbuffer_read_index(buffer) + buffer->num_packets - cpacketbuffer_write_index(buffer) :
cpacketbuffer_read_index(buffer) - cpacketbuffer_write_index(buffer);
/* Subtract the full/empty identification reserved slot from the free space */
return base_free_space - 1;
}
uint32_t cpacketbuffer_packet_size(const struct cpacketbuffer *buffer)
{
return buffer->packet_size;
}
void cpacketbuffer_config_serialise(const struct cpacketbuffer *buffer, struct mxcbufconf *buf_conf)
{
scsc_mifram_ref mifram_ref;
struct scsc_mif_abs *mif;
mif = scsc_mx_get_mif_abs(buffer->mx);
mif->get_mifram_ref(mif, buffer->buffer, &mifram_ref);
buf_conf->buffer_loc = mifram_ref;
buf_conf->num_packets = buffer->num_packets;
buf_conf->packet_size = buffer->packet_size;
mif->get_mifram_ref(mif, buffer->read_index, &mifram_ref);
buf_conf->read_index_loc = mifram_ref;
mif->get_mifram_ref(mif, buffer->write_index, &mifram_ref);
buf_conf->write_index_loc = mifram_ref;
mxcbufconf_print(buf_conf);
}
void cpacketbuffer_log(const struct cpacketbuffer *buffer, enum scsc_log_level log_level)
{
const uint8_t *read_start = cpacketbuffer_index_to_address((struct cpacketbuffer *)buffer, buffer->read_index);
SCSC_TAG_LVL((CPKTBUFF), log_level,
"read_index=0x%x write_index=0x%x, read_start[0]=0x%08x\n",
*buffer->read_index, *buffer->write_index,
*(uint32_t *)read_start);
}