gstreamer/sys/dvb/camtransport.c
2011-07-12 18:15:14 -04:00

504 lines
13 KiB
C

/*
* camtransport.c - GStreamer CAM (EN50221) transport layer
* Copyright (C) 2007 Alessandro Decina
*
* Authors:
* Alessandro Decina <alessandro@nnva.org>
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "camtransport.h"
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#define GST_CAT_DEFAULT cam_debug_cat
#define READ_TIMEOUT_SEC 2
#define READ_TIMEOUT_USEC 0
#define POLL_INTERVAL 0.300
#define TAG_SB 0x80
#define TAG_RCV 0x81
#define TAG_CREATE_T_C 0x82
#define TAG_C_T_C_REPLY 0x83
#define TAG_DELETE_T_C 0x84
#define TAG_D_T_C_REPLY 0x85
#define TAG_REQUEST_T_C 0x86
#define TAG_NEW_T_C 0x87
#define TAG_T_C_ERROR 0x88
#define TAG_DATA_MORE 0xA1
#define TAG_DATA_LAST 0xA0
/* utility struct used to store the state of the connections in cam_tl_read_next
*/
typedef struct
{
GList *active;
GList *idle;
} CamTLConnectionsStatus;
void cam_gst_util_dump_mem (const guchar * mem, guint size);
static CamTLConnection *
cam_tl_connection_new (CamTL * tl, guint8 id)
{
CamTLConnection *connection;
connection = g_new0 (CamTLConnection, 1);
connection->tl = tl;
connection->id = id;
connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
connection->has_data = FALSE;
return connection;
}
static void
cam_tl_connection_destroy (CamTLConnection * connection)
{
if (connection->last_poll)
g_timer_destroy (connection->last_poll);
g_free (connection);
}
CamTL *
cam_tl_new (int fd)
{
CamTL *tl;
tl = g_new0 (CamTL, 1);
tl->fd = fd;
tl->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) cam_tl_connection_destroy);
return tl;
}
void
cam_tl_destroy (CamTL * tl)
{
g_hash_table_destroy (tl->connections);
g_free (tl);
}
/* read data from the module without blocking indefinitely */
static CamReturn
cam_tl_read_timeout (CamTL * tl, struct timeval *timeout)
{
fd_set read_fd;
int sret;
FD_ZERO (&read_fd);
FD_SET (tl->fd, &read_fd);
sret = select (tl->fd + 1, &read_fd, NULL, NULL, timeout);
if (sret == 0) {
GST_DEBUG ("read timeout");
return CAM_RETURN_TRANSPORT_TIMEOUT;
}
tl->buffer_size = read (tl->fd, &tl->buffer, HOST_BUFFER_SIZE);
if (tl->buffer_size == -1) {
GST_ERROR ("error reading tpdu: %s", g_strerror (errno));
return CAM_RETURN_TRANSPORT_ERROR;
}
return CAM_RETURN_OK;
}
/* read data from the module using the default timeout */
static CamReturn
cam_tl_read (CamTL * tl)
{
struct timeval timeout;
timeout.tv_sec = READ_TIMEOUT_SEC;
timeout.tv_usec = READ_TIMEOUT_USEC;
return cam_tl_read_timeout (tl, &timeout);
}
/* get the number of bytes to allocate for a TPDU with a body of body_length
* bytes. Also get the offset from the beginning of the buffer that marks the
* position of the first byte of the TPDU body */
void
cam_tl_calc_buffer_size (CamTL * tl, guint body_length,
guint * buffer_size, guint * offset)
{
guint length_field_len;
/* the size of a TPDU is:
* 1 byte slot number
* 1 byte connection id
* length_field_len bytes length field
* 1 byte connection id
* body_length bytes body
*/
/* get the length of the lenght_field block */
length_field_len = cam_calc_length_field_size (body_length);
*offset = 3 + length_field_len + 1;
*buffer_size = *offset + body_length;
}
/* write the header of a TPDU
* NOTE: this function assumes that the buffer is large enough to contain the
* complete TPDU (see cam_tl_calc_buffer_size ()) and that enough space has been
* left from the beginning of the buffer to write the TPDU header.
*/
static CamReturn
cam_tl_connection_write_tpdu (CamTLConnection * connection,
guint8 tag, guint8 * buffer, guint buffer_size, guint body_length)
{
int sret;
CamTL *tl = connection->tl;
guint8 length_field_len;
/* slot number */
buffer[0] = connection->slot;
/* connection number */
buffer[1] = connection->id;
/* tag */
buffer[2] = tag;
/* length can take 1 to 4 bytes */
length_field_len = cam_write_length_field (&buffer[3], body_length);
buffer[3 + length_field_len] = connection->id;
GST_DEBUG ("writing TPDU %x connection %d", buffer[2], connection->id);
//cam_gst_util_dump_mem (buffer, buffer_size);
sret = write (tl->fd, buffer, buffer_size);
if (sret == -1) {
GST_ERROR ("error witing TPDU (%d): %s", errno, g_strerror (errno));
return CAM_RETURN_TRANSPORT_ERROR;
}
tl->expected_tpdus += 1;
return CAM_RETURN_OK;
}
/* convenience function to write control TPDUs (TPDUs having a single-byte body)
*/
static CamReturn
cam_tl_connection_write_control_tpdu (CamTLConnection * connection, guint8 tag)
{
guint8 tpdu[5];
/* TPDU layout (5 bytes):
*
* slot number (1 byte)
* connection id (1 byte)
* tag (1 byte)
* length (1 byte)
* connection id (1 byte)
*/
return cam_tl_connection_write_tpdu (connection, tag, tpdu, 5, 1);
}
/* read the next TPDU from the CAM */
static CamReturn
cam_tl_read_tpdu_next (CamTL * tl, CamTLConnection ** out_connection)
{
CamReturn ret;
CamTLConnection *connection;
guint8 connection_id;
guint8 *tpdu;
guint8 length_field_len;
guint8 status;
ret = cam_tl_read (tl);
if (CAM_FAILED (ret))
return ret;
tpdu = tl->buffer;
/* must hold at least slot, connection_id, 1byte length_field, connection_id
*/
if (tl->buffer_size < 4) {
GST_ERROR ("invalid TPDU length %d", tl->buffer_size);
return CAM_RETURN_TRANSPORT_ERROR;
}
/* LPDU slot */
/* slot = tpdu[0]; */
/* LPDU connection id */
connection_id = tpdu[1];
connection = g_hash_table_lookup (tl->connections,
GINT_TO_POINTER ((guint) connection_id));
if (connection == NULL) {
/* WHAT? */
GST_ERROR ("CAM sent a TPDU on an unknown connection: %d", connection_id);
return CAM_RETURN_TRANSPORT_ERROR;
}
/* read the length_field () */
length_field_len = cam_read_length_field (&tpdu[3], &tl->body_length);
if (tl->body_length + 3 > tl->buffer_size) {
GST_ERROR ("invalid TPDU length_field (%d) exceeds "
"the size of the buffer (%d)", tl->body_length, tl->buffer_size);
return CAM_RETURN_TRANSPORT_ERROR;
}
/* skip slot + connection id + tag + lenght_field () + connection id */
tl->body = tpdu + 4 + length_field_len;
/* do not count the connection id byte as part of the body */
tl->body_length -= 1;
if (tl->buffer[tl->buffer_size - 4] != TAG_SB) {
GST_ERROR ("no TAG_SB appended to TPDU");
return CAM_RETURN_TRANSPORT_ERROR;
}
status = tl->buffer[tl->buffer_size - 1];
if (status & 0x80) {
connection->has_data = TRUE;
} else {
connection->has_data = FALSE;
}
GST_DEBUG ("received TPDU %x more data %d", tpdu[2], connection->has_data);
tl->expected_tpdus -= 1;
*out_connection = connection;
return CAM_RETURN_OK;
}
/* create a connection with the module */
CamReturn
cam_tl_create_connection (CamTL * tl, guint8 slot,
CamTLConnection ** connection)
{
CamReturn ret;
CamTLConnection *conn = NULL;
if (tl->connection_ids == 255)
return CAM_RETURN_TRANSPORT_TOO_MANY_CONNECTIONS;
conn = cam_tl_connection_new (tl, ++tl->connection_ids);
/* send a TAG_CREATE_T_C TPDU */
ret = cam_tl_connection_write_control_tpdu (conn, TAG_CREATE_T_C);
if (CAM_FAILED (ret))
goto error;
g_hash_table_insert (tl->connections, GINT_TO_POINTER (conn->id), conn);
*connection = conn;
return CAM_RETURN_OK;
error:
if (conn)
cam_tl_connection_destroy (conn);
return ret;
}
CamReturn
cam_tl_connection_delete (CamTLConnection * connection)
{
CamReturn ret;
ret = cam_tl_connection_write_control_tpdu (connection, TAG_DELETE_T_C);
if (CAM_FAILED (ret))
return ret;
connection->state = CAM_TL_CONNECTION_STATE_IN_DELETION;
return CAM_RETURN_OK;
}
static CamReturn
handle_control_tpdu (CamTL * tl, CamTLConnection * connection)
{
if (tl->body_length != 0) {
GST_ERROR ("got control tpdu of invalid length: %d", tl->body_length);
return CAM_RETURN_TRANSPORT_ERROR;
}
switch (tl->buffer[2]) {
/* create transport connection reply */
case TAG_C_T_C_REPLY:
/* a connection might be closed before it's acknowledged */
if (connection->state != CAM_TL_CONNECTION_STATE_IN_DELETION) {
GST_DEBUG ("connection created %d", connection->id);
connection->state = CAM_TL_CONNECTION_STATE_OPEN;
if (tl->connection_created)
tl->connection_created (tl, connection);
}
break;
/* delete transport connection reply */
case TAG_D_T_C_REPLY:
connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
GST_DEBUG ("connection closed %d", connection->id);
if (tl->connection_deleted)
tl->connection_deleted (tl, connection);
g_hash_table_remove (tl->connections,
GINT_TO_POINTER ((guint) connection->id));
break;
}
return CAM_RETURN_OK;
}
static CamReturn
handle_data_tpdu (CamTL * tl, CamTLConnection * connection)
{
if (tl->body_length == 0) {
/* FIXME: figure out why this seems to happen from time to time with the
* predator cam */
GST_WARNING ("Empty data TPDU received");
return CAM_RETURN_OK;
}
if (tl->connection_data)
return tl->connection_data (tl, connection, tl->body, tl->body_length);
return CAM_RETURN_OK;
}
static void
foreach_connection_get (gpointer key, gpointer value, gpointer user_data)
{
GList **lst = (GList **) user_data;
*lst = g_list_append (*lst, value);
}
CamReturn
cam_tl_connection_poll (CamTLConnection * connection, gboolean force)
{
CamReturn ret;
if (connection->last_poll == NULL) {
connection->last_poll = g_timer_new ();
} else if (!force &&
g_timer_elapsed (connection->last_poll, NULL) < POLL_INTERVAL) {
return CAM_RETURN_TRANSPORT_POLL;
}
GST_DEBUG ("polling connection %d", connection->id);
ret = cam_tl_connection_write_control_tpdu (connection, TAG_DATA_LAST);
if (CAM_FAILED (ret))
return ret;
g_timer_start (connection->last_poll);
return CAM_RETURN_OK;
}
/* read all the queued TPDUs */
CamReturn
cam_tl_read_all (CamTL * tl, gboolean poll)
{
CamReturn ret = CAM_RETURN_OK;
CamTLConnection *connection;
GList *connections = NULL;
GList *walk;
gboolean done = FALSE;
while (!done) {
while (tl->expected_tpdus) {
/* read the next TPDU from the connection */
ret = cam_tl_read_tpdu_next (tl, &connection);
if (CAM_FAILED (ret)) {
GST_ERROR ("error reading TPDU from module: %d", ret);
goto out;
}
switch (tl->buffer[2]) {
case TAG_C_T_C_REPLY:
case TAG_D_T_C_REPLY:
connection->empty_data = 0;
ret = handle_control_tpdu (tl, connection);
break;
case TAG_DATA_MORE:
case TAG_DATA_LAST:
connection->empty_data = 0;
ret = handle_data_tpdu (tl, connection);
break;
case TAG_SB:
/* this is handled by tpdu_next */
break;
}
if (CAM_FAILED (ret))
goto out;
}
done = TRUE;
connections = NULL;
g_hash_table_foreach (tl->connections,
foreach_connection_get, &connections);
for (walk = connections; walk; walk = walk->next) {
CamTLConnection *connection = CAM_TL_CONNECTION (walk->data);
if (connection->has_data == TRUE && connection->empty_data < 10) {
ret = cam_tl_connection_write_control_tpdu (connection, TAG_RCV);
if (CAM_FAILED (ret)) {
g_list_free (connections);
goto out;
}
/* increment the empty_data counter. If we get data, this will be reset
* to 0 */
connection->empty_data++;
done = FALSE;
} else if (poll) {
ret = cam_tl_connection_poll (connection, FALSE);
if (ret == CAM_RETURN_TRANSPORT_POLL)
continue;
if (CAM_FAILED (ret)) {
g_list_free (connections);
goto out;
}
done = FALSE;
}
}
g_list_free (connections);
}
out:
return ret;
}
CamReturn
cam_tl_connection_write (CamTLConnection * connection,
guint8 * buffer, guint buffer_size, guint body_length)
{
return cam_tl_connection_write_tpdu (connection,
TAG_DATA_LAST, buffer, buffer_size, 1 + body_length);
}