gapk/src/procqueue.c

243 lines
6.0 KiB
C

/* Processing Queue Management */
/*
* This file is part of gapk (GSM Audio Pocket Knife).
*
* gapk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gapk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gapk. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <talloc.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gapk/procqueue.h>
#include <osmocom/gapk/logging.h>
/* Internal root talloc context */
extern TALLOC_CTX *gapk_root_ctx;
/* crate a new (empty) processing queue */
struct osmo_gapk_pq *
osmo_gapk_pq_create(const char *name)
{
struct osmo_gapk_pq *pq;
/* Allocate memory for a new processing queue */
pq = talloc_zero(gapk_root_ctx, struct osmo_gapk_pq);
if (!pq)
return NULL;
if (name != NULL) {
/* Rename talloc context */
talloc_set_name(pq, "struct osmo_gapk_pq '%s'", name);
/* Set queue name */
pq->name = name;
}
/* Init its list of items */
INIT_LLIST_HEAD(&pq->items);
return pq;
}
/*! destroy a processing queue, calls exit() callback of each item
* \param[in] pq Processing Queue to be destroyed */
void
osmo_gapk_pq_destroy(struct osmo_gapk_pq *pq)
{
struct osmo_gapk_pq_item *item, *item_next;
if (!pq)
return;
/* Iterate over all items in queue */
llist_for_each_entry_safe(item, item_next, &pq->items, list) {
/* Free output buffer memory */
talloc_free(item->buf);
/* Call exit handler if preset */
if (item->exit)
item->exit(item->state);
/* Delete an item from list */
llist_del(&item->list);
talloc_free(item);
}
talloc_free(pq);
}
/*! allocate + add an item to a processing queue; return new item
* \param[in] pq Processing Queue to which item is added
* \returns new PQ item; NULL on error */
struct osmo_gapk_pq_item *
osmo_gapk_pq_add_item(struct osmo_gapk_pq *pq)
{
struct osmo_gapk_pq_item *item;
/* Allocate memory for a new item */
item = talloc_zero(pq, struct osmo_gapk_pq_item);
if (!item)
return NULL;
/* Add one to the end of a queue */
llist_add_tail(&item->list, &pq->items);
/* Increase the items count */
pq->n_items++;
return item;
}
/*! check a processing queue; make sure I/O data lengths are equal
* \param[in] pq Make sure both source and sink are preset
* \param[in] strict Processing Queue to be checked
* \returns 0 on succcess; negative on error */
int
osmo_gapk_pq_check(struct osmo_gapk_pq *pq, int strict)
{
struct osmo_gapk_pq_item *item_prev = NULL;
struct osmo_gapk_pq_item *item;
/* Make sure I/O data lengths are equal */
llist_for_each_entry(item, &pq->items, list) {
if (item_prev && item->len_in) {
if (item->len_in != item_prev->len_out) {
LOGPGAPK(LOGL_ERROR, "PQ '%s': item '%s/%s' requires "
"input size %u, but previous '%s/%s' has %u\n",
pq->name, item->cat_name, item->sub_name,
item->len_in, item_prev->cat_name,
item_prev->sub_name, item_prev->len_out);
return -EINVAL;
}
}
/* Save pointer to the previous item */
item_prev = item;
}
if (strict) {
/* Make sure the first item is a source */
item = llist_first_entry(&pq->items,
struct osmo_gapk_pq_item, list);
if (item->type != OSMO_GAPK_ITEM_TYPE_SOURCE)
goto src_sink_err;
/* Make sure the last item is a sink */
item = llist_last_entry(&pq->items,
struct osmo_gapk_pq_item, list);
if (item->type != OSMO_GAPK_ITEM_TYPE_SINK)
goto src_sink_err;
}
return 0;
src_sink_err:
LOGPGAPK(LOGL_ERROR, "PQ '%s': the first item should be a source, "
"and the last one should be a sink\n", pq->name);
return -EINVAL;
}
/*! prepare a processing queue; allocates buffers
* \param[in] pq Processing Queue to be prepared
* \returns 0 on succcess; negative on error */
int
osmo_gapk_pq_prepare(struct osmo_gapk_pq *pq)
{
struct osmo_gapk_pq_item *item;
unsigned int buf_size;
/* Iterate over all items in queue */
llist_for_each_entry(item, &pq->items, list) {
/* The sink item doesn't require an output buffer */
if (item->type == OSMO_GAPK_ITEM_TYPE_SINK)
continue;
buf_size = item->len_out;
/**
* Use maximum known buffer size
* for variable-length codec output
*/
if (!buf_size)
buf_size = VAR_BUF_SIZE;
/* Allocate memory for an output buffer */
item->buf = talloc_named_const(item, buf_size, ".buffer");
if (!item->buf)
return -ENOMEM;
}
return 0;
}
/*! execute a processing queue; iterate over processing elements
* \param[in] pq Processing Queue to be executed
* \returns 0 on success; negative on error (if any item returns negative) */
int
osmo_gapk_pq_execute(struct osmo_gapk_pq *pq)
{
struct osmo_gapk_pq_item *item;
unsigned int len_prev = 0;
uint8_t *buf_prev = NULL;
int rv;
/* Iterate over all items in queue */
llist_for_each_entry(item, &pq->items, list) {
/* Call item's processing handler */
rv = item->proc(item->state, item->buf, buf_prev, len_prev);
if (rv < 0) {
LOGPGAPK(LOGL_ERROR, "PQ '%s': execution aborted: "
"item '%s/%s' returned %d\n", pq->name,
item->cat_name, item->sub_name, rv);
return rv;
}
buf_prev = item->buf;
len_prev = rv;
}
return 0;
}
char *
osmo_gapk_pq_describe(struct osmo_gapk_pq *pq)
{
struct osmo_gapk_pq_item *item;
char *result = NULL;
int i = 0;
/* Nothing to describe */
if (!pq->n_items)
return NULL;
/* Iterate over all items in queue */
llist_for_each_entry(item, &pq->items, list) {
result = talloc_asprintf_append(result, "%s/%s%s",
item->cat_name, item->sub_name,
++i < pq->n_items ? " -> " : "");
}
/* Change talloc context name */
talloc_set_name_const(result, ".description");
/* Change parent talloc context to pq */
talloc_steal(pq, result);
return result;
}