mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
We now only allow per port preallocated buffers. We exchange the index into the array instead of passing the buffers around. We still use the refcount to track when a buffer can be reused. Improve API a little, allow passing the node as the first argument of the interface call. Implement alloc_buffer in v4l2 and improve the test.
1048 lines
26 KiB
C
1048 lines
26 KiB
C
/* Simple Plugin API
|
|
* Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <spa/control.h>
|
|
|
|
#if 0
|
|
#define SPA_DEBUG_CONTROL(format,args...) fprintf(stderr,format,##args)
|
|
#else
|
|
#define SPA_DEBUG_CONTROL(format,args...)
|
|
#endif
|
|
|
|
typedef struct {
|
|
uint32_t version;
|
|
uint32_t flags;
|
|
uint32_t length;
|
|
} SpaStackHeader;
|
|
|
|
typedef struct {
|
|
void *data;
|
|
size_t size;
|
|
size_t max_size;
|
|
void *free_data;
|
|
int *fds;
|
|
int n_fds;
|
|
int max_fds;
|
|
void *free_fds;
|
|
size_t magic;
|
|
} SpaStackControl;
|
|
|
|
#define SSC(c) ((SpaStackControl *) (c))
|
|
#define SSC_MAGIC ((size_t) 5493683301u)
|
|
#define is_valid_control(c) (c != NULL && \
|
|
SSC(c)->magic == SSC_MAGIC)
|
|
|
|
/**
|
|
* spa_control_init_data:
|
|
* @control: a #SpaControl
|
|
* @data: data
|
|
* @size: size of @data
|
|
* @fds: file descriptors
|
|
* @n_fds: number of file descriptors
|
|
*
|
|
* Initialize @control with @data and @size and @fds and @n_fds.
|
|
* The memory pointer to by @data and @fds becomes property of @control
|
|
* and should not be freed or modified until all references to the control
|
|
* are gone.
|
|
*/
|
|
SpaResult
|
|
spa_control_init_data (SpaControl *control,
|
|
void *data,
|
|
size_t size,
|
|
int *fds,
|
|
unsigned int n_fds)
|
|
{
|
|
SpaStackControl *sc = SSC (control);
|
|
|
|
SPA_DEBUG_CONTROL ("control %p: init", control);
|
|
|
|
sc->magic = SSC_MAGIC;
|
|
sc->data = data;
|
|
sc->size = size;
|
|
sc->max_size = size;
|
|
sc->free_data = NULL;
|
|
sc->fds = fds;
|
|
sc->n_fds = n_fds;
|
|
sc->max_fds = n_fds;
|
|
sc->free_fds = NULL;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* spa_control_get_version
|
|
* @control: a #SpaControl
|
|
*
|
|
* Get the control version
|
|
*
|
|
* Returns: the control version.
|
|
*/
|
|
uint32_t
|
|
spa_control_get_version (SpaControl *control)
|
|
{
|
|
SpaStackControl *sc = SSC (control);
|
|
SpaStackHeader *hdr;
|
|
|
|
if (!is_valid_control (control))
|
|
return -1;
|
|
|
|
hdr = sc->data;
|
|
return hdr->version;
|
|
}
|
|
|
|
/**
|
|
* spa_control_get_fd:
|
|
* @control: a #SpaControl
|
|
* @index: an index
|
|
* @steal: steal the fd
|
|
*
|
|
* Get the file descriptor at @index in @control.
|
|
*
|
|
* Returns: a file descriptor at @index in @control. The file descriptor
|
|
* is not duplicated in any way. -1 is returned on error.
|
|
*/
|
|
int
|
|
spa_control_get_fd (SpaControl *control,
|
|
unsigned int index,
|
|
bool close)
|
|
{
|
|
SpaStackControl *sc = SSC (control);
|
|
int fd;
|
|
|
|
if (!is_valid_control (control))
|
|
return -1;
|
|
|
|
if (sc->fds == NULL || sc->n_fds < index)
|
|
return -1;
|
|
|
|
fd = sc->fds[index];
|
|
if (fd < 0)
|
|
fd = -fd;
|
|
sc->fds[index] = close ? fd : -fd;
|
|
|
|
return fd;
|
|
}
|
|
|
|
SpaResult
|
|
spa_control_clear (SpaControl *control)
|
|
{
|
|
SpaStackControl *sc = SSC (control);
|
|
int i;
|
|
|
|
if (!is_valid_control (control))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
sc->magic = 0;
|
|
free (sc->free_data);
|
|
for (i = 0; i < sc->n_fds; i++) {
|
|
if (sc->fds[i] > 0) {
|
|
SPA_DEBUG_CONTROL ("%p: close %d %d", control, i, sc->fds[i]);
|
|
if (close (sc->fds[i]) < 0)
|
|
perror ("close");
|
|
}
|
|
}
|
|
free (sc->free_fds);
|
|
sc->n_fds = 0;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* SpaControlIter:
|
|
*
|
|
* #SpaControlIter is an opaque data structure and can only be accessed
|
|
* using the following functions.
|
|
*/
|
|
struct stack_iter {
|
|
size_t magic;
|
|
uint32_t version;
|
|
SpaStackControl *control;
|
|
size_t offset;
|
|
|
|
SpaControlCmd cmd;
|
|
size_t size;
|
|
void *data;
|
|
};
|
|
|
|
#define SCSI(i) ((struct stack_iter *) (i))
|
|
#define SCSI_MAGIC ((size_t) 6739527471u)
|
|
#define is_valid_iter(i) (i != NULL && \
|
|
SCSI(i)->magic == SCSI_MAGIC)
|
|
|
|
/**
|
|
* spa_control_iter_init:
|
|
* @iter: a #SpaControlIter
|
|
* @control: a #SpaControl
|
|
*
|
|
* Initialize @iter to iterate the packets in @control.
|
|
*/
|
|
SpaResult
|
|
spa_control_iter_init_full (SpaControlIter *iter,
|
|
SpaControl *control,
|
|
uint32_t version)
|
|
{
|
|
struct stack_iter *si = SCSI (iter);
|
|
|
|
if (iter == NULL || !is_valid_control (control))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
si->magic = SCSI_MAGIC;
|
|
si->version = version;
|
|
si->control = SSC (control);
|
|
si->offset = 0;
|
|
si->cmd = SPA_CONTROL_CMD_INVALID;
|
|
si->size = sizeof (SpaStackHeader);
|
|
si->data = NULL;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static bool
|
|
read_length (uint8_t * data, unsigned int size, size_t * length, size_t * skip)
|
|
{
|
|
size_t len, offset;
|
|
uint8_t b;
|
|
|
|
/* start reading the length, we need this to skip to the data later */
|
|
len = offset = 0;
|
|
do {
|
|
if (offset >= size)
|
|
return false;
|
|
|
|
b = data[offset++];
|
|
len = (len << 7) | (b & 0x7f);
|
|
} while (b & 0x80);
|
|
|
|
/* check remaining control size */
|
|
if (size - offset < len)
|
|
return false;
|
|
|
|
*length = len;
|
|
*skip = offset;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* spa_control_iter_next:
|
|
* @iter: a #SpaControlIter
|
|
*
|
|
* Move to the next packet in @iter.
|
|
*
|
|
* Returns: %SPA_RESULT_OK if more packets are available.
|
|
*/
|
|
SpaResult
|
|
spa_control_iter_next (SpaControlIter *iter)
|
|
{
|
|
struct stack_iter *si = SCSI (iter);
|
|
size_t len, size, skip;
|
|
uint8_t *data;
|
|
|
|
if (!is_valid_iter (iter))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
/* move to next packet */
|
|
si->offset += si->size;
|
|
|
|
/* now read packet */
|
|
data = si->control->data;
|
|
size = si->control->size;
|
|
if (si->offset >= size)
|
|
return SPA_RESULT_ERROR;
|
|
|
|
data += si->offset;
|
|
size -= si->offset;
|
|
|
|
if (size < 1)
|
|
return SPA_RESULT_ERROR;
|
|
|
|
si->cmd = *data;
|
|
|
|
data++;
|
|
size--;
|
|
|
|
if (!read_length (data, size, &len, &skip))
|
|
return SPA_RESULT_ERROR;
|
|
|
|
si->size = len;
|
|
si->data = data + skip;
|
|
si->offset += 1 + skip;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* spa_control_iter_done:
|
|
* @iter: a #SpaControlIter
|
|
*
|
|
* End iterations on @iter.
|
|
*/
|
|
SpaResult
|
|
spa_control_iter_end (SpaControlIter *iter)
|
|
{
|
|
struct stack_iter *si = SCSI (iter);
|
|
|
|
if (!is_valid_iter (iter))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
si->magic = 0;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
SpaControlCmd
|
|
spa_control_iter_get_cmd (SpaControlIter *iter)
|
|
{
|
|
struct stack_iter *si = SCSI (iter);
|
|
|
|
if (!is_valid_iter (iter))
|
|
return SPA_CONTROL_CMD_INVALID;
|
|
|
|
return si->cmd;
|
|
}
|
|
|
|
void *
|
|
spa_control_iter_get_data (SpaControlIter *iter, size_t *size)
|
|
{
|
|
struct stack_iter *si = SCSI (iter);
|
|
|
|
if (!is_valid_iter (iter))
|
|
return NULL;
|
|
|
|
if (size)
|
|
*size = si->size;
|
|
|
|
return si->data;
|
|
}
|
|
|
|
typedef struct {
|
|
SpaBuffer buffer;
|
|
SpaMeta metas[16];
|
|
SpaData datas[16];
|
|
int memid[16];
|
|
} MyBuffer;
|
|
|
|
static SpaResult
|
|
parse_add_buffer (struct stack_iter *si,
|
|
SpaControlCmdAddBuffer *command)
|
|
{
|
|
MyBuffer *b;
|
|
uint32_t *p = si->data;
|
|
unsigned int i;
|
|
|
|
command->port = *p++;
|
|
b = malloc (sizeof (MyBuffer));
|
|
b->buffer.refcount = 1;
|
|
b->buffer.notify = free;
|
|
b->buffer.id = *(uint32_t *)p++;
|
|
b->buffer.size = *(uint32_t *)p++;
|
|
b->buffer.n_metas = *(uint32_t *)p++;
|
|
b->buffer.metas = b->metas;
|
|
b->buffer.n_datas = *(uint32_t *)p++;
|
|
b->buffer.datas = b->datas;
|
|
|
|
for (i = 0; i < b->buffer.n_metas; i++) {
|
|
SpaMeta *m = &b->metas[i];
|
|
m->type = *p++;
|
|
m->size = *p++;
|
|
m->data = p;
|
|
p = p + m->size / 4;
|
|
}
|
|
for (i = 0; i < b->buffer.n_datas; i++) {
|
|
SpaData *d = &b->datas[i];
|
|
d->type = SPA_DATA_TYPE_MEMID;
|
|
d->ptr_type = NULL;
|
|
b->memid[i] = *p++;
|
|
d->ptr = &b->memid[i];
|
|
d->offset = *p++;
|
|
d->size = *p++;
|
|
d->stride = *p++;
|
|
}
|
|
command->buffer = &b->buffer;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
SpaResult
|
|
spa_control_iter_parse_cmd (SpaControlIter *iter,
|
|
void *command)
|
|
{
|
|
struct stack_iter *si = SCSI (iter);
|
|
SpaResult res = SPA_RESULT_OK;
|
|
|
|
if (!is_valid_iter (iter))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
switch (si->cmd) {
|
|
/* C -> S */
|
|
case SPA_CONTROL_CMD_NODE_UPDATE:
|
|
case SPA_CONTROL_CMD_PORT_UPDATE:
|
|
fprintf (stderr, "implement iter of %d\n", si->cmd);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_PORT_REMOVED:
|
|
if (si->size < sizeof (SpaControlCmdPortRemoved))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdPortRemoved));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_START_CONFIGURE:
|
|
case SPA_CONTROL_CMD_PORT_STATUS_CHANGE:
|
|
case SPA_CONTROL_CMD_START_ALLOC:
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_NEED_INPUT:
|
|
if (si->size < sizeof (SpaControlCmdNeedInput))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdNeedInput));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_HAVE_OUTPUT:
|
|
if (si->size < sizeof (SpaControlCmdHaveOutput))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdHaveOutput));
|
|
break;
|
|
|
|
/* S -> C */
|
|
case SPA_CONTROL_CMD_ADD_PORT:
|
|
if (si->size < sizeof (SpaControlCmdAddPort))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdAddPort));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REMOVE_PORT:
|
|
if (si->size < sizeof (SpaControlCmdRemovePort))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdRemovePort));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_SET_FORMAT:
|
|
{
|
|
SpaControlCmdSetFormat *cmd;
|
|
uint32_t *p = si->data;
|
|
|
|
cmd = command;
|
|
cmd->port = *p++;
|
|
cmd->str = (char *)p;
|
|
break;
|
|
}
|
|
|
|
case SPA_CONTROL_CMD_SET_PROPERTY:
|
|
fprintf (stderr, "implement iter of %d\n", si->cmd);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_END_CONFIGURE:
|
|
case SPA_CONTROL_CMD_PAUSE:
|
|
case SPA_CONTROL_CMD_START:
|
|
case SPA_CONTROL_CMD_STOP:
|
|
break;
|
|
|
|
/* bidirectional */
|
|
case SPA_CONTROL_CMD_ADD_MEM:
|
|
if (si->size < sizeof (SpaControlCmdAddMem))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdAddMem));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REMOVE_MEM:
|
|
if (si->size < sizeof (SpaControlCmdRemoveMem))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdRemoveMem));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_ADD_BUFFER:
|
|
res = parse_add_buffer (si, command);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REMOVE_BUFFER:
|
|
if (si->size < sizeof (SpaControlCmdRemoveBuffer))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdRemoveBuffer));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_PROCESS_BUFFER:
|
|
if (si->size < sizeof (SpaControlCmdProcessBuffer))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdProcessBuffer));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REUSE_BUFFER:
|
|
if (si->size < sizeof (SpaControlCmdReuseBuffer))
|
|
return SPA_RESULT_ERROR;
|
|
memcpy (command, si->data, sizeof (SpaControlCmdReuseBuffer));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_INVALID:
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
struct stack_builder {
|
|
size_t magic;
|
|
|
|
SpaStackHeader *sh;
|
|
SpaStackControl control;
|
|
|
|
SpaControlCmd cmd;
|
|
size_t offset;
|
|
};
|
|
|
|
#define SCSB(b) ((struct stack_builder *) (b))
|
|
#define SCSB_MAGIC ((size_t) 8103647428u)
|
|
#define is_valid_builder(b) (b != NULL && \
|
|
SCSB(b)->magic == SCSB_MAGIC)
|
|
|
|
|
|
/**
|
|
* spa_control_builder_init_full:
|
|
* @builder: a #SpaControlBuilder
|
|
* @version: a version
|
|
* @data: data to build into or %NULL to allocate
|
|
* @max_data: allocated size of @data
|
|
* @fds: memory for fds
|
|
* @max_fds: maximum number of fds in @fds
|
|
*
|
|
* Initialize a stack allocated @builder and set the @version.
|
|
*/
|
|
SpaResult
|
|
spa_control_builder_init_full (SpaControlBuilder *builder,
|
|
uint32_t version,
|
|
void *data,
|
|
size_t max_data,
|
|
int *fds,
|
|
unsigned int max_fds)
|
|
{
|
|
struct stack_builder *sb = SCSB (builder);
|
|
SpaStackHeader *sh;
|
|
|
|
if (builder == NULL)
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
sb->magic = SCSB_MAGIC;
|
|
|
|
if (max_data < sizeof (SpaStackHeader) || data == NULL) {
|
|
sb->control.max_size = sizeof (SpaStackHeader) + 128;
|
|
sb->control.data = malloc (sb->control.max_size);
|
|
sb->control.free_data = sb->control.data;
|
|
fprintf (stderr, "builder %p: realloc control memory %zd -> %zd\n",
|
|
builder, max_data, sb->control.max_size);
|
|
} else {
|
|
sb->control.max_size = max_data;
|
|
sb->control.data = data;
|
|
sb->control.free_data = NULL;
|
|
}
|
|
sb->control.size = sizeof (SpaStackHeader);
|
|
|
|
sb->control.fds = fds;
|
|
sb->control.max_fds = max_fds;
|
|
sb->control.n_fds = 0;
|
|
sb->control.free_fds = NULL;
|
|
|
|
sh = sb->sh = sb->control.data;
|
|
sh->version = version;
|
|
sh->flags = 0;
|
|
sh->length = 0;
|
|
|
|
sb->cmd = 0;
|
|
sb->offset = 0;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* spa_control_builder_clear:
|
|
* @builder: a #SpaControlBuilder
|
|
*
|
|
* Clear the memory used by @builder. This can be used to abort building the
|
|
* control.
|
|
*
|
|
* @builder becomes invalid after this function and can be reused with
|
|
* spa_control_builder_init()
|
|
*/
|
|
SpaResult
|
|
spa_control_builder_clear (SpaControlBuilder *builder)
|
|
{
|
|
struct stack_builder *sb = SCSB (builder);
|
|
|
|
if (!is_valid_builder (builder))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
sb->magic = 0;
|
|
free (sb->control.free_data);
|
|
free (sb->control.free_fds);
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* spa_control_builder_end:
|
|
* @builder: a #SpaControlBuilder
|
|
* @control: a #SpaControl
|
|
*
|
|
* Ends the building process and fills @control with the constructed
|
|
* #SpaControl.
|
|
*
|
|
* @builder becomes invalid after this function and can be reused with
|
|
* spa_control_builder_init()
|
|
*/
|
|
SpaResult
|
|
spa_control_builder_end (SpaControlBuilder *builder,
|
|
SpaControl *control)
|
|
{
|
|
struct stack_builder *sb = SCSB (builder);
|
|
SpaStackControl *sc = SSC (control);
|
|
|
|
if (!is_valid_builder (builder) || control == NULL)
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
sb->magic = 0;
|
|
sb->sh->length = sb->control.size - sizeof (SpaStackHeader);
|
|
|
|
sc->magic = SSC_MAGIC;
|
|
sc->data = sb->control.data;
|
|
sc->size = sb->control.size;
|
|
sc->max_size = sb->control.max_size;
|
|
sc->free_data = sb->control.free_data;
|
|
|
|
sc->fds = sb->control.fds;
|
|
sc->n_fds = sb->control.n_fds;
|
|
sc->max_fds = sb->control.max_fds;
|
|
sc->free_fds = sb->control.free_fds;
|
|
|
|
SPA_DEBUG_CONTROL ("builder %p: control %p init", builder, control);
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* spa_control_builder_add_fd:
|
|
* @builder: a #SpaControlBuilder
|
|
* @fd: a valid fd
|
|
*
|
|
* Add the file descriptor @fd to @builder.
|
|
*
|
|
* Returns: the index of the file descriptor in @builder.
|
|
*/
|
|
int
|
|
spa_control_builder_add_fd (SpaControlBuilder *builder,
|
|
int fd,
|
|
bool close)
|
|
{
|
|
struct stack_builder *sb = SCSB (builder);
|
|
int index;
|
|
|
|
if (!is_valid_builder (builder) || fd < 0)
|
|
return -1;
|
|
|
|
if (sb->control.n_fds >= sb->control.max_fds) {
|
|
int new_size = sb->control.max_fds + 8;
|
|
fprintf (stderr, "builder %p: realloc control fds %d -> %d\n",
|
|
builder, sb->control.max_fds, new_size);
|
|
sb->control.max_fds = new_size;
|
|
sb->control.free_fds = realloc (sb->control.free_fds, new_size * sizeof (int));
|
|
sb->control.fds = sb->control.free_fds;
|
|
}
|
|
index = sb->control.n_fds;
|
|
sb->control.fds[index] = close ? fd : -fd;
|
|
sb->control.n_fds++;
|
|
|
|
return index;
|
|
}
|
|
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
|
|
|
static void *
|
|
builder_ensure_size (struct stack_builder *sb, size_t size)
|
|
{
|
|
if (sb->control.size + size > sb->control.max_size) {
|
|
size_t new_size = sb->control.size + MAX (size, 1024);
|
|
fprintf (stderr, "builder %p: realloc control memory %zd -> %zd\n",
|
|
sb, sb->control.max_size, new_size);
|
|
sb->control.max_size = new_size;
|
|
sb->control.free_data = realloc (sb->control.free_data, new_size);
|
|
sb->sh = sb->control.data = sb->control.free_data;
|
|
}
|
|
return (uint8_t *) sb->control.data + sb->control.size;
|
|
}
|
|
|
|
static void *
|
|
builder_add_cmd (struct stack_builder *sb, SpaControlCmd cmd, size_t size)
|
|
{
|
|
uint8_t *p;
|
|
unsigned int plen;
|
|
|
|
plen = 1;
|
|
while (size >> (7 * plen))
|
|
plen++;
|
|
|
|
/* 1 for cmd, plen for size and size for payload */
|
|
p = builder_ensure_size (sb, 1 + plen + size);
|
|
|
|
sb->cmd = cmd;
|
|
sb->offset = sb->control.size;
|
|
sb->control.size += 1 + plen + size;
|
|
|
|
*p++ = cmd;
|
|
/* write length */
|
|
while (plen) {
|
|
plen--;
|
|
*p++ = ((plen > 0) ? 0x80 : 0) | ((size >> (7 * plen)) & 0x7f);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static SpaResult
|
|
build_add_buffer (struct stack_builder *sb,
|
|
SpaControlCmdAddBuffer *command)
|
|
{
|
|
unsigned int i;
|
|
size_t size;
|
|
SpaBuffer *b = command->buffer;
|
|
uint32_t *p;
|
|
|
|
/* port + id + size + n_metas + n_datas */
|
|
size = 4 + 4 + 4 + 4 + 4;
|
|
|
|
for (i = 0; i < b->n_metas; i++) {
|
|
SpaMeta *m = &b->metas[i];
|
|
/* type + size + data */
|
|
size += 4 + 4 + m->size;
|
|
}
|
|
for (i = 0; i < b->n_datas; i++) {
|
|
SpaData *d = &b->datas[i];
|
|
if (d->type != SPA_DATA_TYPE_MEMID)
|
|
continue;
|
|
/* memidx + offset + size + stride */
|
|
size += 4 + 4 + 4 + 4;
|
|
}
|
|
p = builder_add_cmd (sb, SPA_CONTROL_CMD_ADD_BUFFER, size);
|
|
|
|
*p++ = command->port;
|
|
*p++ = b->id;
|
|
*p++ = b->size;
|
|
*p++ = b->n_metas;
|
|
*p++ = b->n_datas;
|
|
|
|
for (i = 0; i < b->n_metas; i++) {
|
|
SpaMeta *m = &b->metas[i];
|
|
|
|
*p++ = m->type;
|
|
*p++ = m->size;
|
|
memcpy (p, m->data, m->size);
|
|
p = p + m->size / 4;
|
|
}
|
|
for (i = 0; i < b->n_datas; i++) {
|
|
SpaData *d = &b->datas[i];
|
|
if (d->type != SPA_DATA_TYPE_MEMID)
|
|
continue;
|
|
*p++ = *((uint32_t*)(d->ptr));
|
|
*p++ = d->offset;
|
|
*p++ = d->size;
|
|
*p++ = d->stride;
|
|
}
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* spa_control_builder_add_cmd:
|
|
* @builder: a #SpaControlBuilder
|
|
* @cmd: a #SpaControlCmd
|
|
* @command: a command
|
|
*
|
|
* Add a @cmd to @builder with data from @command.
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
SpaResult
|
|
spa_control_builder_add_cmd (SpaControlBuilder *builder,
|
|
SpaControlCmd cmd,
|
|
void *command)
|
|
{
|
|
struct stack_builder *sb = SCSB (builder);
|
|
void *p;
|
|
SpaResult res = SPA_RESULT_OK;
|
|
|
|
if (!is_valid_builder (builder))
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
|
|
switch (cmd) {
|
|
/* C -> S */
|
|
case SPA_CONTROL_CMD_NODE_UPDATE:
|
|
case SPA_CONTROL_CMD_PORT_UPDATE:
|
|
fprintf (stderr, "implement builder of %d\n", cmd);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_PORT_REMOVED:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdPortRemoved));
|
|
memcpy (p, command, sizeof (SpaControlCmdPortRemoved));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_START_CONFIGURE:
|
|
p = builder_add_cmd (sb, cmd, 0);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_PORT_STATUS_CHANGE:
|
|
case SPA_CONTROL_CMD_START_ALLOC:
|
|
p = builder_add_cmd (sb, cmd, 0);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_NEED_INPUT:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdNeedInput));
|
|
memcpy (p, command, sizeof (SpaControlCmdNeedInput));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_HAVE_OUTPUT:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdHaveOutput));
|
|
memcpy (p, command, sizeof (SpaControlCmdHaveOutput));
|
|
break;
|
|
|
|
/* S -> C */
|
|
case SPA_CONTROL_CMD_ADD_PORT:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdAddPort));
|
|
memcpy (p, command, sizeof (SpaControlCmdAddPort));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REMOVE_PORT:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdRemovePort));
|
|
memcpy (p, command, sizeof (SpaControlCmdRemovePort));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_SET_FORMAT:
|
|
{
|
|
size_t len, slen;
|
|
SpaControlCmdSetFormat *sf = command;
|
|
uint32_t *p;
|
|
|
|
slen = strlen (sf->str)+1;
|
|
/* port + string */
|
|
len = 4 + slen;
|
|
|
|
p = builder_add_cmd (sb, cmd, len);
|
|
*p++ = sf->port;
|
|
memcpy ((char*)p, sf->str, slen);
|
|
break;
|
|
}
|
|
|
|
case SPA_CONTROL_CMD_SET_PROPERTY:
|
|
fprintf (stderr, "implement builder of %d\n", cmd);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_END_CONFIGURE:
|
|
p = builder_add_cmd (sb, cmd, 0);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_PAUSE:
|
|
case SPA_CONTROL_CMD_START:
|
|
case SPA_CONTROL_CMD_STOP:
|
|
p = builder_add_cmd (sb, cmd, 0);
|
|
break;
|
|
|
|
/* bidirectional */
|
|
case SPA_CONTROL_CMD_ADD_MEM:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdAddMem));
|
|
memcpy (p, command, sizeof (SpaControlCmdAddMem));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REMOVE_MEM:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdRemoveMem));
|
|
memcpy (p, command, sizeof (SpaControlCmdRemoveMem));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_ADD_BUFFER:
|
|
res = build_add_buffer (sb, (SpaControlCmdAddBuffer *) command);
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REMOVE_BUFFER:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdRemoveBuffer));
|
|
memcpy (p, command, sizeof (SpaControlCmdRemoveBuffer));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_PROCESS_BUFFER:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdProcessBuffer));
|
|
memcpy (p, command, sizeof (SpaControlCmdProcessBuffer));
|
|
break;
|
|
|
|
case SPA_CONTROL_CMD_REUSE_BUFFER:
|
|
p = builder_add_cmd (sb, cmd, sizeof (SpaControlCmdReuseBuffer));
|
|
memcpy (p, command, sizeof (SpaControlCmdReuseBuffer));
|
|
break;
|
|
|
|
default:
|
|
case SPA_CONTROL_CMD_INVALID:
|
|
return SPA_RESULT_INVALID_ARGUMENTS;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
SpaResult
|
|
spa_control_read (SpaControl *control,
|
|
int fd,
|
|
void *data,
|
|
size_t max_data,
|
|
int *fds,
|
|
unsigned int max_fds)
|
|
{
|
|
ssize_t len;
|
|
SpaStackHeader *hdr;
|
|
SpaStackControl *sc = (SpaStackControl *) control;
|
|
size_t need;
|
|
struct cmsghdr *cmsg;
|
|
struct msghdr msg = {0};
|
|
struct iovec iov[1];
|
|
char cmsgbuf[CMSG_SPACE (max_fds * sizeof (int))];
|
|
|
|
sc->data = data;
|
|
sc->max_size = max_data;
|
|
sc->size = 0;
|
|
sc->free_data = NULL;
|
|
sc->fds = fds;
|
|
sc->max_fds = max_fds;
|
|
sc->n_fds = 0;
|
|
sc->free_fds = NULL;
|
|
hdr = sc->data;
|
|
|
|
/* read header and control messages first */
|
|
iov[0].iov_base = hdr;
|
|
iov[0].iov_len = sizeof (SpaStackHeader);;
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof (cmsgbuf);
|
|
msg.msg_flags = MSG_CMSG_CLOEXEC;
|
|
|
|
while (true) {
|
|
len = recvmsg (fd, &msg, msg.msg_flags);
|
|
if (len < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
else
|
|
goto recv_error;
|
|
}
|
|
break;
|
|
}
|
|
if (len != sizeof (SpaStackHeader))
|
|
return SPA_RESULT_ERROR;
|
|
|
|
/* now we know the total length */
|
|
need = sizeof (SpaStackHeader) + hdr->length;
|
|
|
|
if (sc->max_size < need) {
|
|
fprintf (stderr, "control: realloc receive memory %zd -> %zd\n", sc->max_size, need);
|
|
sc->max_size = need;
|
|
hdr = sc->data = sc->free_data = realloc (sc->free_data, need);
|
|
}
|
|
sc->size = need;
|
|
|
|
if (hdr->length > 0) {
|
|
/* read data */
|
|
while (true) {
|
|
len = recv (fd, (uint8_t *)sc->data + sizeof (SpaStackHeader), hdr->length, 0);
|
|
if (len < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
else
|
|
goto recv_error;
|
|
}
|
|
break;
|
|
}
|
|
if (len != hdr->length)
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
|
|
/* handle control messages */
|
|
for (cmsg = CMSG_FIRSTHDR (&msg); cmsg != NULL; cmsg = CMSG_NXTHDR (&msg, cmsg)) {
|
|
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
|
|
continue;
|
|
|
|
sc->n_fds = (cmsg->cmsg_len - ((char *)CMSG_DATA (cmsg) - (char *)cmsg)) / sizeof (int);
|
|
memcpy (sc->fds, CMSG_DATA (cmsg), sc->n_fds * sizeof (int));
|
|
}
|
|
sc->magic = SSC_MAGIC;
|
|
|
|
return SPA_RESULT_OK;
|
|
|
|
/* ERRORS */
|
|
recv_error:
|
|
{
|
|
fprintf (stderr, "could not recvmsg: %s\n", strerror (errno));
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
SpaResult
|
|
spa_control_write (SpaControl *control,
|
|
int fd)
|
|
{
|
|
SpaStackControl *sc = (SpaStackControl *) control;
|
|
ssize_t len;
|
|
struct msghdr msg = {0};
|
|
struct iovec iov[1];
|
|
struct cmsghdr *cmsg;
|
|
char cmsgbuf[CMSG_SPACE (sc->n_fds * sizeof (int))];
|
|
int fds_len = sc->n_fds * sizeof (int), *cm, i;
|
|
|
|
iov[0].iov_base = sc->data;
|
|
iov[0].iov_len = sc->size;
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = CMSG_SPACE (fds_len);
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN (fds_len);
|
|
cm = (int*)CMSG_DATA (cmsg);
|
|
for (i = 0; i < fds_len; i++)
|
|
cm[i] = sc->fds[i] > 0 ? sc->fds[i] : -sc->fds[i];
|
|
msg.msg_controllen = cmsg->cmsg_len;
|
|
|
|
while (true) {
|
|
len = sendmsg (fd, &msg, 0);
|
|
if (len < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
else
|
|
goto send_error;
|
|
}
|
|
break;
|
|
}
|
|
if (len != (ssize_t) sc->size)
|
|
return SPA_RESULT_ERROR;
|
|
|
|
return SPA_RESULT_OK;
|
|
|
|
/* ERRORS */
|
|
send_error:
|
|
{
|
|
fprintf (stderr, "could not sendmsg: %s\n", strerror (errno));
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
}
|