Tizen 2.0 Release
[framework/multimedia/gst-plugins-bad0.10.git] / sys / dvb / camtransport.c
1 /*
2  * camtransport.c - GStreamer CAM (EN50221) transport layer
3  * Copyright (C) 2007 Alessandro Decina
4  * 
5  * Authors:
6  *   Alessandro Decina <alessandro@nnva.org>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "camtransport.h"
25 #include <sys/select.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <errno.h>
30
31 #define GST_CAT_DEFAULT cam_debug_cat
32 #define READ_TIMEOUT_SEC 2
33 #define READ_TIMEOUT_USEC 0
34
35 #define POLL_INTERVAL 0.300
36
37 #define TAG_SB 0x80
38 #define TAG_RCV 0x81
39 #define TAG_CREATE_T_C 0x82
40 #define TAG_C_T_C_REPLY 0x83
41 #define TAG_DELETE_T_C 0x84
42 #define TAG_D_T_C_REPLY 0x85
43 #define TAG_REQUEST_T_C 0x86
44 #define TAG_NEW_T_C 0x87
45 #define TAG_T_C_ERROR 0x88
46 #define TAG_DATA_MORE 0xA1
47 #define TAG_DATA_LAST 0xA0
48
49 /* utility struct used to store the state of the connections in cam_tl_read_next
50  */
51 typedef struct
52 {
53   GList *active;
54   GList *idle;
55 } CamTLConnectionsStatus;
56
57 void cam_gst_util_dump_mem (const guchar * mem, guint size);
58
59 static CamTLConnection *
60 cam_tl_connection_new (CamTL * tl, guint8 id)
61 {
62   CamTLConnection *connection;
63
64   connection = g_new0 (CamTLConnection, 1);
65   connection->tl = tl;
66   connection->id = id;
67   connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
68   connection->has_data = FALSE;
69
70   return connection;
71 }
72
73 static void
74 cam_tl_connection_destroy (CamTLConnection * connection)
75 {
76   if (connection->last_poll)
77     g_timer_destroy (connection->last_poll);
78
79   g_free (connection);
80 }
81
82 CamTL *
83 cam_tl_new (int fd)
84 {
85   CamTL *tl;
86
87   tl = g_new0 (CamTL, 1);
88   tl->fd = fd;
89   tl->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
90       NULL, (GDestroyNotify) cam_tl_connection_destroy);
91
92   return tl;
93 }
94
95 void
96 cam_tl_destroy (CamTL * tl)
97 {
98   g_hash_table_destroy (tl->connections);
99
100   g_free (tl);
101 }
102
103 /* read data from the module without blocking indefinitely */
104 static CamReturn
105 cam_tl_read_timeout (CamTL * tl, struct timeval *timeout)
106 {
107   fd_set read_fd;
108   int sret;
109
110   FD_ZERO (&read_fd);
111   FD_SET (tl->fd, &read_fd);
112
113   sret = select (tl->fd + 1, &read_fd, NULL, NULL, timeout);
114   if (sret == 0) {
115     GST_DEBUG ("read timeout");
116     return CAM_RETURN_TRANSPORT_TIMEOUT;
117   }
118
119   tl->buffer_size = read (tl->fd, &tl->buffer, HOST_BUFFER_SIZE);
120   if (tl->buffer_size == -1) {
121     GST_ERROR ("error reading tpdu: %s", g_strerror (errno));
122     return CAM_RETURN_TRANSPORT_ERROR;
123   }
124
125   return CAM_RETURN_OK;
126 }
127
128 /* read data from the module using the default timeout */
129 static CamReturn
130 cam_tl_read (CamTL * tl)
131 {
132   struct timeval timeout;
133
134   timeout.tv_sec = READ_TIMEOUT_SEC;
135   timeout.tv_usec = READ_TIMEOUT_USEC;
136
137   return cam_tl_read_timeout (tl, &timeout);
138 }
139
140 /* get the number of bytes to allocate for a TPDU with a body of body_length
141  * bytes. Also get the offset from the beginning of the buffer that marks the
142  * position of the first byte of the TPDU body */
143 void
144 cam_tl_calc_buffer_size (CamTL * tl, guint body_length,
145     guint * buffer_size, guint * offset)
146 {
147   guint length_field_len;
148
149   /* the size of a TPDU is:
150    * 1 byte slot number
151    * 1 byte connection id 
152    * length_field_len bytes length field 
153    * 1 byte connection id
154    * body_length bytes body
155    */
156
157   /* get the length of the lenght_field block */
158   length_field_len = cam_calc_length_field_size (body_length);
159
160   *offset = 3 + length_field_len + 1;
161   *buffer_size = *offset + body_length;
162 }
163
164 /* write the header of a TPDU
165  * NOTE: this function assumes that the buffer is large enough to contain the
166  * complete TPDU (see cam_tl_calc_buffer_size ()) and that enough space has been
167  * left from the beginning of the buffer to write the TPDU header.
168  */
169 static CamReturn
170 cam_tl_connection_write_tpdu (CamTLConnection * connection,
171     guint8 tag, guint8 * buffer, guint buffer_size, guint body_length)
172 {
173   int sret;
174   CamTL *tl = connection->tl;
175   guint8 length_field_len;
176
177   /* slot number */
178   buffer[0] = connection->slot;
179   /* connection number */
180   buffer[1] = connection->id;
181   /* tag */
182   buffer[2] = tag;
183   /* length can take 1 to 4 bytes */
184   length_field_len = cam_write_length_field (&buffer[3], body_length);
185   buffer[3 + length_field_len] = connection->id;
186
187   GST_DEBUG ("writing TPDU %x connection %d", buffer[2], connection->id);
188
189   //cam_gst_util_dump_mem (buffer, buffer_size);
190
191   sret = write (tl->fd, buffer, buffer_size);
192   if (sret == -1) {
193     GST_ERROR ("error witing TPDU (%d): %s", errno, g_strerror (errno));
194     return CAM_RETURN_TRANSPORT_ERROR;
195   }
196
197   tl->expected_tpdus += 1;
198
199   return CAM_RETURN_OK;
200 }
201
202 /* convenience function to write control TPDUs (TPDUs having a single-byte body)
203  */
204 static CamReturn
205 cam_tl_connection_write_control_tpdu (CamTLConnection * connection, guint8 tag)
206 {
207   guint8 tpdu[5];
208
209   /* TPDU layout (5 bytes):
210    *
211    * slot number (1 byte)
212    * connection id (1 byte)
213    * tag (1 byte)
214    * length (1 byte)
215    * connection id (1 byte)
216    */
217
218   return cam_tl_connection_write_tpdu (connection, tag, tpdu, 5, 1);
219 }
220
221 /* read the next TPDU from the CAM */
222 static CamReturn
223 cam_tl_read_tpdu_next (CamTL * tl, CamTLConnection ** out_connection)
224 {
225   CamReturn ret;
226   CamTLConnection *connection;
227   guint8 connection_id;
228   guint8 *tpdu;
229   guint8 length_field_len;
230   guint8 status;
231
232   ret = cam_tl_read (tl);
233   if (CAM_FAILED (ret))
234     return ret;
235
236   tpdu = tl->buffer;
237
238   /* must hold at least slot, connection_id, 1byte length_field, connection_id
239    */
240   if (tl->buffer_size < 4) {
241     GST_ERROR ("invalid TPDU length %d", tl->buffer_size);
242     return CAM_RETURN_TRANSPORT_ERROR;
243   }
244
245   /* LPDU slot */
246   /* slot = tpdu[0]; */
247   /* LPDU connection id */
248   connection_id = tpdu[1];
249
250   connection = g_hash_table_lookup (tl->connections,
251       GINT_TO_POINTER ((guint) connection_id));
252   if (connection == NULL) {
253     /* WHAT? */
254     GST_ERROR ("CAM sent a TPDU on an unknown connection: %d", connection_id);
255     return CAM_RETURN_TRANSPORT_ERROR;
256   }
257
258   /* read the length_field () */
259   length_field_len = cam_read_length_field (&tpdu[3], &tl->body_length);
260
261   if (tl->body_length + 3 > tl->buffer_size) {
262     GST_ERROR ("invalid TPDU length_field (%d) exceeds "
263         "the size of the buffer (%d)", tl->body_length, tl->buffer_size);
264     return CAM_RETURN_TRANSPORT_ERROR;
265   }
266
267   /* skip slot + connection id + tag + lenght_field () + connection id */
268   tl->body = tpdu + 4 + length_field_len;
269   /* do not count the connection id byte as part of the body */
270   tl->body_length -= 1;
271
272   if (tl->buffer[tl->buffer_size - 4] != TAG_SB) {
273     GST_ERROR ("no TAG_SB appended to TPDU");
274     return CAM_RETURN_TRANSPORT_ERROR;
275   }
276
277   status = tl->buffer[tl->buffer_size - 1];
278   if (status & 0x80) {
279     connection->has_data = TRUE;
280   } else {
281     connection->has_data = FALSE;
282   }
283
284   GST_DEBUG ("received TPDU %x more data %d", tpdu[2], connection->has_data);
285   tl->expected_tpdus -= 1;
286
287   *out_connection = connection;
288
289   return CAM_RETURN_OK;
290 }
291
292 /* create a connection with the module */
293 CamReturn
294 cam_tl_create_connection (CamTL * tl, guint8 slot,
295     CamTLConnection ** connection)
296 {
297   CamReturn ret;
298   CamTLConnection *conn = NULL;
299
300   if (tl->connection_ids == 255)
301     return CAM_RETURN_TRANSPORT_TOO_MANY_CONNECTIONS;
302
303   conn = cam_tl_connection_new (tl, ++tl->connection_ids);
304
305   /* send a TAG_CREATE_T_C TPDU */
306   ret = cam_tl_connection_write_control_tpdu (conn, TAG_CREATE_T_C);
307   if (CAM_FAILED (ret))
308     goto error;
309
310   g_hash_table_insert (tl->connections, GINT_TO_POINTER (conn->id), conn);
311
312   *connection = conn;
313
314   return CAM_RETURN_OK;
315
316 error:
317   if (conn)
318     cam_tl_connection_destroy (conn);
319
320   return ret;
321 }
322
323 CamReturn
324 cam_tl_connection_delete (CamTLConnection * connection)
325 {
326   CamReturn ret;
327
328   ret = cam_tl_connection_write_control_tpdu (connection, TAG_DELETE_T_C);
329   if (CAM_FAILED (ret))
330     return ret;
331
332   connection->state = CAM_TL_CONNECTION_STATE_IN_DELETION;
333
334   return CAM_RETURN_OK;
335 }
336
337 static CamReturn
338 handle_control_tpdu (CamTL * tl, CamTLConnection * connection)
339 {
340   if (tl->body_length != 0) {
341     GST_ERROR ("got control tpdu of invalid length: %d", tl->body_length);
342     return CAM_RETURN_TRANSPORT_ERROR;
343   }
344
345   switch (tl->buffer[2]) {
346       /* create transport connection reply */
347     case TAG_C_T_C_REPLY:
348       /* a connection might be closed before it's acknowledged */
349       if (connection->state != CAM_TL_CONNECTION_STATE_IN_DELETION) {
350         GST_DEBUG ("connection created %d", connection->id);
351         connection->state = CAM_TL_CONNECTION_STATE_OPEN;
352
353         if (tl->connection_created)
354           tl->connection_created (tl, connection);
355       }
356       break;
357       /* delete transport connection reply */
358     case TAG_D_T_C_REPLY:
359       connection->state = CAM_TL_CONNECTION_STATE_CLOSED;
360       GST_DEBUG ("connection closed %d", connection->id);
361
362       if (tl->connection_deleted)
363         tl->connection_deleted (tl, connection);
364
365       g_hash_table_remove (tl->connections,
366           GINT_TO_POINTER ((guint) connection->id));
367       break;
368   }
369
370   return CAM_RETURN_OK;
371 }
372
373 static CamReturn
374 handle_data_tpdu (CamTL * tl, CamTLConnection * connection)
375 {
376   if (tl->body_length == 0) {
377     /* FIXME: figure out why this seems to happen from time to time with the
378      * predator cam */
379     GST_WARNING ("Empty data TPDU received");
380     return CAM_RETURN_OK;
381   }
382
383   if (tl->connection_data)
384     return tl->connection_data (tl, connection, tl->body, tl->body_length);
385
386   return CAM_RETURN_OK;
387 }
388
389 static void
390 foreach_connection_get (gpointer key, gpointer value, gpointer user_data)
391 {
392   GList **lst = (GList **) user_data;
393
394   *lst = g_list_append (*lst, value);
395 }
396
397 CamReturn
398 cam_tl_connection_poll (CamTLConnection * connection, gboolean force)
399 {
400   CamReturn ret;
401
402   if (connection->last_poll == NULL) {
403     connection->last_poll = g_timer_new ();
404   } else if (!force &&
405       g_timer_elapsed (connection->last_poll, NULL) < POLL_INTERVAL) {
406     return CAM_RETURN_TRANSPORT_POLL;
407   }
408
409   GST_DEBUG ("polling connection %d", connection->id);
410   ret = cam_tl_connection_write_control_tpdu (connection, TAG_DATA_LAST);
411   if (CAM_FAILED (ret))
412     return ret;
413
414   g_timer_start (connection->last_poll);
415
416   return CAM_RETURN_OK;
417 }
418
419 /* read all the queued TPDUs */
420 CamReturn
421 cam_tl_read_all (CamTL * tl, gboolean poll)
422 {
423   CamReturn ret = CAM_RETURN_OK;
424   CamTLConnection *connection;
425   GList *connections = NULL;
426   GList *walk;
427   gboolean done = FALSE;
428
429   while (!done) {
430     while (tl->expected_tpdus) {
431       /* read the next TPDU from the connection */
432       ret = cam_tl_read_tpdu_next (tl, &connection);
433       if (CAM_FAILED (ret)) {
434         GST_ERROR ("error reading TPDU from module: %d", ret);
435         goto out;
436       }
437
438       switch (tl->buffer[2]) {
439         case TAG_C_T_C_REPLY:
440         case TAG_D_T_C_REPLY:
441           connection->empty_data = 0;
442           ret = handle_control_tpdu (tl, connection);
443           break;
444         case TAG_DATA_MORE:
445         case TAG_DATA_LAST:
446           connection->empty_data = 0;
447           ret = handle_data_tpdu (tl, connection);
448           break;
449         case TAG_SB:
450           /* this is handled by tpdu_next */
451           break;
452       }
453
454       if (CAM_FAILED (ret))
455         goto out;
456     }
457
458     done = TRUE;
459
460     connections = NULL;
461     g_hash_table_foreach (tl->connections,
462         foreach_connection_get, &connections);
463
464     for (walk = connections; walk; walk = walk->next) {
465       CamTLConnection *connection = CAM_TL_CONNECTION (walk->data);
466
467       if (connection->has_data == TRUE && connection->empty_data < 10) {
468         ret = cam_tl_connection_write_control_tpdu (connection, TAG_RCV);
469         if (CAM_FAILED (ret)) {
470           g_list_free (connections);
471           goto out;
472         }
473         /* increment the empty_data counter. If we get data, this will be reset
474          * to 0 */
475         connection->empty_data++;
476         done = FALSE;
477       } else if (poll) {
478         ret = cam_tl_connection_poll (connection, FALSE);
479         if (ret == CAM_RETURN_TRANSPORT_POLL)
480           continue;
481
482         if (CAM_FAILED (ret)) {
483           g_list_free (connections);
484           goto out;
485         }
486
487         done = FALSE;
488       }
489     }
490
491     g_list_free (connections);
492   }
493
494 out:
495   return ret;
496 }
497
498 CamReturn
499 cam_tl_connection_write (CamTLConnection * connection,
500     guint8 * buffer, guint buffer_size, guint body_length)
501 {
502   return cam_tl_connection_write_tpdu (connection,
503       TAG_DATA_LAST, buffer, buffer_size, 1 + body_length);
504 }