]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Added DD CI support into new file
authorJasmin Jessich <jasmin@anw.at>
Sun, 12 Nov 2017 16:04:02 +0000 (17:04 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 16 Nov 2017 07:27:40 +0000 (08:27 +0100)
- New file linuxdvb_ddci.c
- DD CI related data are stored in a separate structure (struct
  linuxdvb_ddci).
- struct linuxdvb_ca stores a pointer to the new DD CI data structure
  which gets allocated, if a DD CI exists (see next commit).
- Added logging subsystem LS_DDCI.
- Compile linuxdvb_ddci.c if DD CI is enabled.
- Added config option ddci.

Signed-off-by: Jasmin Jessich <jasmin@anw.at>
Makefile
configure
src/input/mpegts/linuxdvb/linuxdvb_ddci.c [new file with mode: 0644]
src/input/mpegts/linuxdvb/linuxdvb_private.h
src/tvhlog.c
src/tvhlog.h

index 64a37fd7ebdf2877699c664b71051cc7f7852360..52c58e43d42414e937699d49d84d9341702d2c2c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -577,6 +577,11 @@ SRCS-DVBCAM = \
 SRCS-${CONFIG_LINUXDVB_CA} += $(SRCS-DVBCAM)
 I18N-C += $(SRCS-DVBCAM)
 
+SRCS-DDCI = \
+       src/input/mpegts/linuxdvb/linuxdvb_ddci.c
+SRCS-${CONFIG_DDCI} += $(SRCS-DDCI)
+I18N-C += $(SRCS-DDCI)
+
 # TSDEBUGCW
 SRCS-TSDEBUG = \
        src/input/mpegts/mpegts_tsdebug.c \
index 2e9f8b29072e30da6176b9bf1655c113ac8610c6..71c0c64b3f957adbc4883573a6470084abd44874 100755 (executable)
--- a/configure
+++ b/configure
@@ -75,6 +75,7 @@ OPTIONS=(
   "slow_memoryinfo:no"
   "libsystemd_daemon:no"
   "bintray_cache:yes"
+  "ddci:no"
 )
 
 #
@@ -651,6 +652,13 @@ if enabled linuxdvb; then
   fi
 fi
 
+# DD CI support
+if enabled ddci; then
+  if disabled linuxdvb_ca; then
+    die "DD CI requires linuxdvb_ca (linuxdvb libdvben50221) (use --disable-ddci)"
+  fi
+fi
+
 #
 # Icon caching
 #
diff --git a/src/input/mpegts/linuxdvb/linuxdvb_ddci.c b/src/input/mpegts/linuxdvb/linuxdvb_ddci.c
new file mode 100644 (file)
index 0000000..fd930c3
--- /dev/null
@@ -0,0 +1,882 @@
+ /*
+ *  Tvheadend - Linux DVB DDCI
+ *
+ *  Copyright (C) 2017 Jasmin Jessich
+ *
+ *  This program 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.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tvheadend.h"
+#include "linuxdvb_private.h"
+#include "tvhpoll.h"
+#include "input/mpegts/tsdemux.h"
+
+#include <fcntl.h>
+
+/* DD CI send Buffer size in number of 188 byte packages */
+// FIXME: make a config parameter
+#define LDDCI_SEND_BUF_NUM_DEF  1500
+#define LDDCI_RECV_BUF_NUM_DEF  1500
+
+#define LDDCI_SEND_BUFFER_POLL_TMO  150    /* ms */
+#define LDDCI_TS_SYNC_BYTE          0x47
+#define LDDCI_TS_SIZE               188
+#define LDDCI_TS_SCRAMBLING_CONTROL 0xC0
+
+#define LDDCI_TO_THREAD(_t)  (linuxdvb_ddci_thread_t *)(_t)
+
+#define LDDCI_WR_THREAD_STAT_TMO  10  /* sec */
+#define LDDCI_RD_THREAD_STAT_TMO  10  /* sec */
+
+#define LDDCI_MIN_TS_PKT (100 * LDDCI_TS_SIZE)
+#define LDDCI_MIN_TS_SYN (5 * LDDCI_TS_SIZE)
+
+typedef struct linuxdvb_ddci_thread
+{
+  linuxdvb_ddci_t          *lddci;
+  int                       lddci_thread_running;
+  int                       lddci_thread_stop;
+  pthread_t                 lddci_thread;
+  pthread_mutex_t           lddci_thread_lock;
+  tvh_cond_t                lddci_thread_cond;
+} linuxdvb_ddci_thread_t;
+
+typedef struct linuxdvb_ddci_send_packet
+{
+  TAILQ_ENTRY(linuxdvb_ddci_send_packet)  lddci_send_pkt_link;
+  size_t                                  lddci_send_pkt_len;
+  uint8_t                                 lddci_send_pkt_data[0];
+} linuxdvb_ddci_send_packet_t;
+
+typedef struct linuxdvb_ddci_send_buffer
+{
+  TAILQ_HEAD(,linuxdvb_ddci_send_packet)  lddci_send_buf_queue;
+  uint64_t                                lddci_send_buf_size_max;
+  uint64_t                                lddci_send_buf_size;
+  pthread_mutex_t                         lddci_send_buf_lock;
+  tvh_cond_t                              lddci_send_buf_cond;
+  tvhlog_limit_t                          lddci_send_buf_loglimit;
+  int                                     lddci_send_buf_pkgCntW;
+  int                                     lddci_send_buf_pkgCntR;
+  int                                     lddci_send_buf_pkgCntWL;
+  int                                     lddci_send_buf_pkgCntRL;
+} linuxdvb_ddci_send_buffer_t;
+
+typedef struct linuxdvb_ddci_wr_thread
+{
+  linuxdvb_ddci_thread_t;    /* have to be at first */
+  int                          lddci_cfg_send_buffer_sz; /* in TS packages */
+  linuxdvb_ddci_send_buffer_t  lddci_send_buffer;
+  mtimer_t                     lddci_send_buf_stat_tmo;
+} linuxdvb_ddci_wr_thread_t;
+
+typedef struct linuxdvb_ddci_rd_thread
+{
+  linuxdvb_ddci_thread_t;    /* have to be at first */
+  int                        lddci_cfg_recv_buffer_sz; /* in TS packages */
+  mtimer_t                   lddci_recv_stat_tmo;
+  int                        lddci_recv_pkgCntR;
+  int                        lddci_recv_pkgCntW;
+  int                        lddci_recv_pkgCntRL;
+  int                        lddci_recv_pkgCntWL;
+  int                        lddci_recv_pkgCntS;
+  int                        lddci_recv_pkgCntSL;
+} linuxdvb_ddci_rd_thread_t;
+
+struct linuxdvb_ddci
+{
+  linuxdvb_ca_t             *lca;    /* back link to the associated CA */
+  char                      *lddci_path;
+  char                       lddci_id[6];
+  int                        lddci_fdW;
+  int                        lddci_fdR;
+  linuxdvb_ddci_wr_thread_t  lddci_wr_thread;
+  linuxdvb_ddci_rd_thread_t  lddci_rd_thread;
+
+  /* currently we use a fix assignment to one single service */
+  service_t                 *t;   /* associated service */
+};
+
+
+/* When a DD CI is disabled on the WEB UI, the global lock is held
+ * when the threads are stopped. But this is not the case when THV is closed.
+ * So it is OK to check if the global lock is held and to omit the locking
+ * in this case. It can't happen, that the global lock is unlocked just after
+ * the lock check.
+ */
+static void
+linuxdvb_ddci_mtimer_disarm ( mtimer_t *mti )
+{
+  int locked;
+
+  locked = ! pthread_mutex_trylock(&global_lock);
+  mtimer_disarm(mti);
+  if (locked)
+    pthread_mutex_unlock(&global_lock);
+}
+
+/* When a DD CI is enabled on the WEB UI, the global lock is held when the
+ * threads are started. This is also the case when THV is started.
+ * So it is OK to check if the global lock is held and to omit the locking
+ * in this case. It can't happen, that the global lock is unlocked just after
+ * the lock check.
+ */
+static void
+linuxdvb_ddci_mtimer_arm_rel
+  ( mtimer_t *mti, mti_callback_t *callback, void *opaque, int64_t delta )
+{
+  int locked;
+
+  locked = ! pthread_mutex_trylock(&global_lock);
+  mtimer_arm_rel(mti, callback, opaque, delta);
+  if (locked)
+    pthread_mutex_unlock(&global_lock);
+}
+
+
+/*****************************************************************************
+ *
+ * DD CI Thread functions
+ *
+ *****************************************************************************/
+
+static void
+linuxdvb_ddci_thread_init
+  ( linuxdvb_ddci_t *lddci, linuxdvb_ddci_thread_t *ddci_thread )
+{
+  ddci_thread->lddci = lddci;
+  ddci_thread->lddci_thread_running = 0;
+  ddci_thread->lddci_thread_stop = 0;
+  pthread_mutex_init(&ddci_thread->lddci_thread_lock, NULL);
+  tvh_cond_init(&ddci_thread->lddci_thread_cond);
+}
+
+static inline int
+linuxdvb_ddci_thread_running ( linuxdvb_ddci_thread_t *ddci_thread )
+{
+  return ddci_thread->lddci_thread_running;
+}
+
+static inline void
+linuxdvb_ddci_thread_signal ( linuxdvb_ddci_thread_t *ddci_thread )
+{
+  tvh_cond_signal(&ddci_thread->lddci_thread_cond, 0);
+}
+
+static int
+linuxdvb_ddci_thread_start
+  ( linuxdvb_ddci_thread_t *ddci_thread, void *(*thread_routine) (void *),
+    void *arg, const char *name )
+{
+  int e = -1;
+
+  if (!linuxdvb_ddci_thread_running(ddci_thread)) {
+    pthread_mutex_lock(&ddci_thread->lddci_thread_lock);
+    tvhthread_create(&ddci_thread->lddci_thread, NULL, thread_routine, arg,
+                     name);
+    do {
+      e = tvh_cond_wait(&ddci_thread->lddci_thread_cond,
+                        &ddci_thread->lddci_thread_lock);
+      if (e == ETIMEDOUT) {
+        tvherror(LS_DDCI, "create thread %s error", name );
+        break;
+      }
+    } while (ERRNO_AGAIN(e));
+    pthread_mutex_unlock(&ddci_thread->lddci_thread_lock);
+  }
+
+  return e;
+}
+
+static void
+linuxdvb_ddci_thread_stop ( linuxdvb_ddci_thread_t *ddci_thread )
+{
+  if (linuxdvb_ddci_thread_running(ddci_thread)) {
+    ddci_thread->lddci_thread_stop = 1;
+    pthread_join(ddci_thread->lddci_thread, NULL);
+  }
+}
+
+
+/*****************************************************************************
+ *
+ * DD CI Send Buffer functions
+ *
+ *****************************************************************************/
+
+static void
+linuxdvb_ddci_send_buffer_init
+  ( linuxdvb_ddci_send_buffer_t *ddci_snd_buf, uint64_t ddci_snd_buf_max )
+{
+  TAILQ_INIT(&ddci_snd_buf->lddci_send_buf_queue);
+  ddci_snd_buf->lddci_send_buf_size_max = ddci_snd_buf_max;
+  ddci_snd_buf->lddci_send_buf_size = 0;
+  pthread_mutex_init(&ddci_snd_buf->lddci_send_buf_lock, NULL);
+  tvh_cond_init(&ddci_snd_buf->lddci_send_buf_cond);
+  tvhlog_limit_reset(&ddci_snd_buf->lddci_send_buf_loglimit);
+  ddci_snd_buf->lddci_send_buf_pkgCntW = 0;
+  ddci_snd_buf->lddci_send_buf_pkgCntR = 0;
+  ddci_snd_buf->lddci_send_buf_pkgCntWL = 0;
+  ddci_snd_buf->lddci_send_buf_pkgCntRL = 0;
+
+}
+
+/* must be called with locked mutex */
+static inline void
+linuxdvb_ddci_send_buffer_remove
+   ( linuxdvb_ddci_send_buffer_t *ddci_snd_buf, linuxdvb_ddci_send_packet_t *sp )
+{
+  if (sp) {
+    assert( ddci_snd_buf->lddci_send_buf_size >= sp->lddci_send_pkt_len);
+    ddci_snd_buf->lddci_send_buf_size -= sp->lddci_send_pkt_len;
+    // memoryinfo_free(&mpegts_input_queue_memoryinfo, sizeof(mpegts_packet_t) + mp->mp_len);
+    ddci_snd_buf->lddci_send_buf_pkgCntR += sp->lddci_send_pkt_len / LDDCI_TS_SIZE;
+    TAILQ_REMOVE(&ddci_snd_buf->lddci_send_buf_queue, sp, lddci_send_pkt_link);
+  }
+}
+
+static linuxdvb_ddci_send_packet_t *
+linuxdvb_ddci_send_buffer_get
+  ( linuxdvb_ddci_send_buffer_t *ddci_snd_buf, int64_t tmo )
+{
+  linuxdvb_ddci_send_packet_t  *sp;
+
+  pthread_mutex_lock(&ddci_snd_buf->lddci_send_buf_lock);
+
+  /* packet present? */
+  sp = TAILQ_FIRST(&ddci_snd_buf->lddci_send_buf_queue);
+  if (!sp) {
+    int r;
+
+    do {
+      int64_t mono = mclk() + ms2mono(tmo);
+
+      /* Wait for a packet */
+      r = tvh_cond_timedwait(&ddci_snd_buf->lddci_send_buf_cond,
+                             &ddci_snd_buf->lddci_send_buf_lock, mono);
+      if (r == ETIMEDOUT) {
+        break;
+      }
+    } while (ERRNO_AGAIN(r));
+
+    sp = TAILQ_FIRST(&ddci_snd_buf->lddci_send_buf_queue);
+  }
+
+  linuxdvb_ddci_send_buffer_remove(ddci_snd_buf, sp);
+
+  pthread_mutex_unlock(&ddci_snd_buf->lddci_send_buf_lock);
+
+  return sp;
+}
+
+static void
+linuxdvb_ddci_send_buffer_put
+  ( linuxdvb_ddci_send_buffer_t *ddci_snd_buf, const uint8_t *tsb, int len )
+{
+  pthread_mutex_lock(&ddci_snd_buf->lddci_send_buf_lock);
+
+  if (ddci_snd_buf->lddci_send_buf_size < ddci_snd_buf->lddci_send_buf_size_max) {
+    linuxdvb_ddci_send_packet_t  *sp;
+
+#if 0
+    /* Note: This debug output will work only for one DD CI instance! */
+    {
+      static uint8_t pid_seen[ 8192];
+      int pid, idx = 0;
+
+      while (idx < len) {
+        pid = (tsb[idx+1] & 0x1f) << 8 | tsb[idx+2];
+        if (!pid_seen[pid]) {
+          tvhtrace(LS_DDCI, "CAM PID %d", pid);
+          pid_seen[pid] = 1;
+        }
+        idx += LDDCI_TS_SIZE;
+      }
+    }
+#endif
+
+    sp = malloc(sizeof(linuxdvb_ddci_send_packet_t) + len);
+    sp->lddci_send_pkt_len = len;
+    memcpy(sp->lddci_send_pkt_data, tsb, len);
+    ddci_snd_buf->lddci_send_buf_size += len;
+    ddci_snd_buf->lddci_send_buf_pkgCntW += len / LDDCI_TS_SIZE;
+    // memoryinfo_alloc(&mpegts_input_queue_memoryinfo, sizeof(mpegts_packet_t) + len2);
+    TAILQ_INSERT_TAIL(&ddci_snd_buf->lddci_send_buf_queue, sp, lddci_send_pkt_link);
+    tvh_cond_signal(&ddci_snd_buf->lddci_send_buf_cond, 0);
+  } else {
+    if (tvhlog_limit(&ddci_snd_buf->lddci_send_buf_loglimit, 10))
+      tvhwarn(LS_DDCI, "too much queued output data in send buffer, discarding new");
+  }
+
+  pthread_mutex_unlock(&ddci_snd_buf->lddci_send_buf_lock);
+}
+
+static void
+linuxdvb_ddci_send_buffer_clear ( linuxdvb_ddci_send_buffer_t *ddci_snd_buf )
+{
+  linuxdvb_ddci_send_packet_t  *sp;
+
+  pthread_mutex_lock(&ddci_snd_buf->lddci_send_buf_lock);
+
+  while ((sp = TAILQ_FIRST(&ddci_snd_buf->lddci_send_buf_queue)))
+  {
+    linuxdvb_ddci_send_buffer_remove(ddci_snd_buf, sp);
+    free(sp);
+  }
+  ddci_snd_buf->lddci_send_buf_pkgCntW = 0;
+  ddci_snd_buf->lddci_send_buf_pkgCntR = 0;
+  ddci_snd_buf->lddci_send_buf_pkgCntWL = 0;
+  ddci_snd_buf->lddci_send_buf_pkgCntRL = 0;
+
+  pthread_mutex_unlock(&ddci_snd_buf->lddci_send_buf_lock);
+}
+
+static void
+linuxdvb_ddci_send_buffer_statistic
+  ( linuxdvb_ddci_send_buffer_t *ddci_snd_buf, char *ci_id )
+{
+  int   pkgCntR = ddci_snd_buf->lddci_send_buf_pkgCntR;
+  int   pkgCntW = ddci_snd_buf->lddci_send_buf_pkgCntW;
+
+  if ((pkgCntR != ddci_snd_buf->lddci_send_buf_pkgCntRL) ||
+      (pkgCntW != ddci_snd_buf->lddci_send_buf_pkgCntWL)) {
+    tvhtrace(LS_DDCI, "CAM %s send buff rd(-> CAM):%d, wr:%d",
+             ci_id, pkgCntR, pkgCntW);
+    ddci_snd_buf->lddci_send_buf_pkgCntRL = pkgCntR;
+    ddci_snd_buf->lddci_send_buf_pkgCntWL = pkgCntW;
+  }
+}
+
+
+/*****************************************************************************
+ *
+ * DD CI Writer Thread functions
+ *
+ *****************************************************************************/
+static void
+linuxdvb_ddci_wr_thread_statistic ( void *aux )
+{
+  linuxdvb_ddci_wr_thread_t *ddci_wr_thread = aux;
+  linuxdvb_ddci_thread_t    *ddci_thread = aux;
+  char                      *ci_id = ddci_thread->lddci->lddci_id;
+
+  /* timer callback is executed with global lock */
+  lock_assert(&global_lock);
+
+  linuxdvb_ddci_send_buffer_statistic(&ddci_wr_thread->lddci_send_buffer, ci_id);
+
+  mtimer_arm_rel(&ddci_wr_thread->lddci_send_buf_stat_tmo,
+                 linuxdvb_ddci_wr_thread_statistic, ddci_wr_thread,
+                 sec2mono(LDDCI_WR_THREAD_STAT_TMO));
+}
+
+static void
+linuxdvb_ddci_wr_thread_statistic_clr ( linuxdvb_ddci_wr_thread_t *ddci_wr_thread )
+{
+  linuxdvb_ddci_thread_t    *ddci_thread = (linuxdvb_ddci_thread_t *)ddci_wr_thread;
+  char                      *ci_id = ddci_thread->lddci->lddci_id;
+
+  linuxdvb_ddci_send_buffer_statistic(&ddci_wr_thread->lddci_send_buffer, ci_id);
+  linuxdvb_ddci_send_buffer_clear(&ddci_wr_thread->lddci_send_buffer);
+
+}
+
+static void *
+linuxdvb_ddci_write_thread ( void *arg )
+{
+  linuxdvb_ddci_wr_thread_t *ddci_wr_thread = arg;
+  linuxdvb_ddci_thread_t    *ddci_thread = arg;
+
+  int                        fd = ddci_thread->lddci->lddci_fdW;
+  char                      *ci_id = ddci_thread->lddci->lddci_id;
+
+  ddci_thread->lddci_thread_running = 1;
+  ddci_thread->lddci_thread_stop = 0;
+  linuxdvb_ddci_mtimer_arm_rel(&ddci_wr_thread->lddci_send_buf_stat_tmo,
+                               linuxdvb_ddci_wr_thread_statistic,
+                               ddci_wr_thread,
+                               sec2mono(LDDCI_WR_THREAD_STAT_TMO));
+  tvhtrace(LS_DDCI, "CAM %s write thread started", ci_id);
+  linuxdvb_ddci_thread_signal(ddci_thread);
+  while (tvheadend_is_running() && !ddci_thread->lddci_thread_stop) {
+    linuxdvb_ddci_send_packet_t *sp;
+
+    sp = linuxdvb_ddci_send_buffer_get(&ddci_wr_thread->lddci_send_buffer,
+                                       LDDCI_SEND_BUFFER_POLL_TMO);
+    if (sp) {
+      int r = tvh_write(fd, sp->lddci_send_pkt_data, sp->lddci_send_pkt_len);
+      if (r)
+        tvhwarn(LS_DDCI, "couldn't write to CAM %s:%m", ci_id);
+      free(sp);
+    }
+  }
+
+  linuxdvb_ddci_mtimer_disarm(&ddci_wr_thread->lddci_send_buf_stat_tmo);
+  tvhtrace(LS_DDCI, "CAM %s write thread finished", ci_id);
+  ddci_thread->lddci_thread_stop = 0;
+  ddci_thread->lddci_thread_running = 0;
+  return NULL;
+}
+
+static inline void
+linuxdvb_ddci_wr_thread_init ( linuxdvb_ddci_t *lddci )
+{
+  linuxdvb_ddci_thread_init(lddci, LDDCI_TO_THREAD(&lddci->lddci_wr_thread));
+}
+
+static int
+linuxdvb_ddci_wr_thread_start ( linuxdvb_ddci_wr_thread_t *ddci_wr_thread )
+{
+  int e;
+
+  // FIXME: Use a configuration parameter
+  ddci_wr_thread->lddci_cfg_send_buffer_sz = LDDCI_SEND_BUF_NUM_DEF * LDDCI_TS_SIZE;
+  linuxdvb_ddci_send_buffer_init(&ddci_wr_thread->lddci_send_buffer,
+                                 ddci_wr_thread->lddci_cfg_send_buffer_sz);
+  e = linuxdvb_ddci_thread_start(LDDCI_TO_THREAD(ddci_wr_thread),
+                                 linuxdvb_ddci_write_thread, ddci_wr_thread,
+                                 "lnx-ddci-wr");
+
+  return e;
+}
+
+static inline void
+linuxdvb_ddci_wr_thread_stop ( linuxdvb_ddci_wr_thread_t *ddci_wr_thread )
+{
+  /* See function linuxdvb_ddci_wr_thread_buffer_put why we lock here.
+   */
+
+  pthread_mutex_lock(&(LDDCI_TO_THREAD(ddci_wr_thread))->lddci_thread_lock);
+  linuxdvb_ddci_thread_stop(LDDCI_TO_THREAD(ddci_wr_thread));
+  linuxdvb_ddci_send_buffer_clear(&ddci_wr_thread->lddci_send_buffer);
+  pthread_mutex_unlock(&(LDDCI_TO_THREAD(ddci_wr_thread))->lddci_thread_lock);
+}
+
+static inline void
+linuxdvb_ddci_wr_thread_buffer_put
+  ( linuxdvb_ddci_wr_thread_t *ddci_wr_thread, const uint8_t *tsb, int len )
+{
+  /* We need to lock this function against linuxdvb_ddci_wr_thread_stop, because
+   * linuxdvb_ddci_wr_thread_buffer_put may be executed by another thread
+   * simultaneously, although the stop function is already running. Due to the
+   * race condition with the tread_running flag, it may happen, that the buffer
+   * is not empty after the stop function is finished. The next execution of
+   * linuxdvb_ddci_wr_thread_start will then re-init the queue and the wrongly
+   * stored data is lost -> memory leak.
+   */
+  pthread_mutex_lock(&(LDDCI_TO_THREAD(ddci_wr_thread))->lddci_thread_lock);
+  if (linuxdvb_ddci_thread_running(LDDCI_TO_THREAD(ddci_wr_thread)))
+    linuxdvb_ddci_send_buffer_put(&ddci_wr_thread->lddci_send_buffer, tsb, len );
+  pthread_mutex_unlock(&(LDDCI_TO_THREAD(ddci_wr_thread))->lddci_thread_lock);
+}
+
+
+/*****************************************************************************
+ *
+ * DD CI Reader Thread functions
+ *
+ *****************************************************************************/
+static void
+linuxdvb_ddci_rd_thread_statistic ( void *aux )
+{
+  linuxdvb_ddci_rd_thread_t *ddci_rd_thread = aux;
+  linuxdvb_ddci_thread_t    *ddci_thread = aux;
+  char                      *ci_id = ddci_thread->lddci->lddci_id;
+  int pkgCntR = ddci_rd_thread->lddci_recv_pkgCntR;
+  int pkgCntW = ddci_rd_thread->lddci_recv_pkgCntW;
+  int pkgCntS = ddci_rd_thread->lddci_recv_pkgCntS;
+
+  /* timer callback is executed with global lock */
+  lock_assert(&global_lock);
+
+  if ((pkgCntR != ddci_rd_thread->lddci_recv_pkgCntRL) ||
+      (pkgCntW != ddci_rd_thread->lddci_recv_pkgCntWL)) {
+    tvhtrace(LS_DDCI, "CAM %s recv rd(CAM ->):%d, wr:%d",
+             ci_id, pkgCntR, pkgCntW);
+    ddci_rd_thread->lddci_recv_pkgCntRL = pkgCntR;
+    ddci_rd_thread->lddci_recv_pkgCntWL = pkgCntW;
+  }
+  if ((pkgCntS != ddci_rd_thread->lddci_recv_pkgCntSL)) {
+    tvhtrace(LS_DDCI, "CAM %s got %d scrambled packets from CAM",
+             ci_id, pkgCntS);
+    ddci_rd_thread->lddci_recv_pkgCntSL = pkgCntS;
+  }
+
+  mtimer_arm_rel(&ddci_rd_thread->lddci_recv_stat_tmo,
+                 linuxdvb_ddci_rd_thread_statistic, ddci_rd_thread,
+                 sec2mono(LDDCI_RD_THREAD_STAT_TMO));
+}
+
+static void
+linuxdvb_ddci_rd_thread_statistic_clr ( linuxdvb_ddci_rd_thread_t *ddci_rd_thread )
+{
+  ddci_rd_thread->lddci_recv_pkgCntR = 0;
+  ddci_rd_thread->lddci_recv_pkgCntW = 0;
+  ddci_rd_thread->lddci_recv_pkgCntRL = 0;
+  ddci_rd_thread->lddci_recv_pkgCntWL = 0;
+  ddci_rd_thread->lddci_recv_pkgCntS = 0;
+  ddci_rd_thread->lddci_recv_pkgCntSL = 0;
+}
+
+static int inline
+ddci_ts_sync_count ( const uint8_t *tsb, int len )
+{
+  const uint8_t *start = tsb;
+
+#define LDDCI_TS_SIZE_10  (LDDCI_TS_SIZE * 10)
+
+  while (len >= LDDCI_TS_SIZE) {
+    if (len >= LDDCI_TS_SIZE_10 &&
+        tsb[0*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[1*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[2*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[3*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[4*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[5*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[6*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[7*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[8*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE &&
+        tsb[9*LDDCI_TS_SIZE] == LDDCI_TS_SYNC_BYTE) {
+      len -= LDDCI_TS_SIZE_10;
+      tsb += LDDCI_TS_SIZE_10;
+    } else if (*tsb == LDDCI_TS_SYNC_BYTE) {
+      len -= LDDCI_TS_SIZE;
+      tsb += LDDCI_TS_SIZE;
+    } else {
+      break;
+    }
+  }
+  return tsb - start;
+}
+
+static int
+ddci_ts_sync_search ( const uint8_t *tsb, int len )
+{
+  int skipped = 0;
+
+  while ((len > LDDCI_MIN_TS_SYN) &&
+         (ddci_ts_sync_count(tsb, len) < LDDCI_MIN_TS_SYN)) {
+    tsb++;
+    len--;
+    skipped++;
+  }
+  return skipped;
+}
+
+static inline int
+ddci_ts_sync ( const uint8_t *tsb, int len )
+{
+  /* it is enough to check the first byte for sync, because the data
+   * written into the CAM was completely in sync. In fact this check is
+   * required only for the first synchronization phase or when another
+   * stream has been tuned.
+   */
+  return *tsb == LDDCI_TS_SYNC_BYTE ? 0 : ddci_ts_sync_search(tsb, len);
+}
+
+static inline int
+ddci_is_scrambled(const uint8_t *tsb)
+{
+  return tsb[3] & LDDCI_TS_SCRAMBLING_CONTROL;
+}
+
+static void *
+linuxdvb_ddci_read_thread ( void *arg )
+{
+  linuxdvb_ddci_rd_thread_t *ddci_rd_thread = arg;
+  linuxdvb_ddci_thread_t    *ddci_thread = arg;
+  int                        fd = ddci_thread->lddci->lddci_fdR;
+  char                      *ci_id = ddci_thread->lddci->lddci_id;
+  tvhpoll_event_t ev[1];
+  tvhpoll_t *efd;
+  sbuf_t sb;
+
+  /* Setup poll */
+  efd = tvhpoll_create(1);
+  memset(ev, 0, sizeof(ev));
+  ev[0].events             = TVHPOLL_IN;
+  ev[0].fd = ev[0].data.fd = fd;
+  tvhpoll_add(efd, ev, 1);
+
+  /* Allocate memory */
+  sbuf_init_fixed(&sb, MINMAX(ddci_rd_thread->lddci_cfg_recv_buffer_sz,
+                              LDDCI_TS_SIZE * 100, LDDCI_TS_SIZE * 10000));
+
+  ddci_thread->lddci_thread_running = 1;
+  ddci_thread->lddci_thread_stop = 0;
+  linuxdvb_ddci_rd_thread_statistic_clr(ddci_rd_thread);
+  linuxdvb_ddci_mtimer_arm_rel(&ddci_rd_thread->lddci_recv_stat_tmo,
+                               linuxdvb_ddci_rd_thread_statistic, ddci_rd_thread,
+                               sec2mono(LDDCI_RD_THREAD_STAT_TMO));
+  tvhtrace(LS_DDCI, "CAM %s read thread started", ci_id);
+  linuxdvb_ddci_thread_signal(ddci_thread);
+  while (tvheadend_is_running() && !ddci_thread->lddci_thread_stop) {
+    service_t *t;
+    int nfds, num_pkg, clr_stat = 0, pkg_chk = 0, scrambled = 0;
+    ssize_t n;
+
+    nfds = tvhpoll_wait(efd, ev, 1, 150);
+    if (nfds <= 0) continue;
+    assert(ev[0].data.fd == fd);
+
+    /* Read */
+    errno = 0;
+    if ((n = sbuf_read(&sb, fd)) < 0) {
+      if (ERRNO_AGAIN(errno))
+        continue;
+      if (errno == EOVERFLOW)
+        tvhwarn(LS_DDCI, "read buffer overflow on CAM %s:%m", ci_id);
+      else
+        tvhwarn(LS_DDCI, "couldn't read from CAM %s:%m", ci_id);
+      continue;
+    }
+
+    if (sb.sb_ptr > 0) {
+      int len, skip;
+      uint8_t *tsb;
+
+      len = sb.sb_ptr;
+      if (len < LDDCI_MIN_TS_PKT)
+          continue;
+
+      tsb = sb.sb_data;
+      skip = ddci_ts_sync(tsb, len);
+      if (skip) {
+        tvhwarn(LS_DDCI, "CAM %s skipped %d bytes to sync on start of TS packet",
+                ci_id, skip);
+        sbuf_cut(&sb, skip);
+        len = sb.sb_ptr;
+      }
+
+      if (len < LDDCI_MIN_TS_SYN)
+          continue;
+
+      /* receive only whole packets */
+      len -= len % LDDCI_TS_SIZE;
+      num_pkg = len / LDDCI_TS_SIZE;
+      ddci_rd_thread->lddci_recv_pkgCntR += num_pkg;
+
+      /* FIXME: Once we implement CI+, this needs to be not executed, because
+       *        a CI+ CAM will use the scrambled bits for re-scrambling the TS
+       *        stream
+       */
+      while (pkg_chk < num_pkg) {
+        if (ddci_is_scrambled(tsb + (pkg_chk * LDDCI_TS_SIZE)))
+          ++scrambled;
+
+        ++pkg_chk;
+      }
+      ddci_rd_thread->lddci_recv_pkgCntS += scrambled;
+
+      /* FIXME: split the received packets according to the PID in different
+       *        buffers and deliver them
+       * FIXME: How to determine the right service pointer?
+       */
+      /* as a first step we send the data to the associated service */
+      t = ddci_thread->lddci->t;
+      if (t) {
+        pthread_mutex_lock(&t->s_stream_mutex);
+        if (t->s_status == SERVICE_RUNNING) {
+          ts_recv_packet2((mpegts_service_t *)t, tsb, len);
+          ddci_rd_thread->lddci_recv_pkgCntW += num_pkg;
+        }
+        pthread_mutex_unlock(&t->s_stream_mutex);
+        clr_stat = 1;
+      } else if (clr_stat) {
+        clr_stat = 0;
+
+        /* in case of MCD/MTD this needs to be re-thinked */
+        linuxdvb_ddci_rd_thread_statistic(ddci_rd_thread);
+        linuxdvb_ddci_rd_thread_statistic_clr(ddci_rd_thread);
+      }
+
+      /* handled */
+      sbuf_cut(&sb, len);
+    }
+  }
+
+  sbuf_free(&sb);
+  tvhpoll_destroy(efd);
+  linuxdvb_ddci_mtimer_disarm(&ddci_rd_thread->lddci_recv_stat_tmo);
+  tvhtrace(LS_DDCI, "CAM %s read thread finished", ci_id);
+  ddci_thread->lddci_thread_stop = 0;
+  ddci_thread->lddci_thread_running = 0;
+  return NULL;
+}
+
+static inline void
+linuxdvb_ddci_rd_thread_init ( linuxdvb_ddci_t *lddci )
+{
+  linuxdvb_ddci_thread_init(lddci, LDDCI_TO_THREAD(&lddci->lddci_rd_thread));
+}
+
+static int
+linuxdvb_ddci_rd_thread_start ( linuxdvb_ddci_rd_thread_t *ddci_rd_thread )
+{
+  int e;
+
+  // FIXME: Use a configuration parameter
+  ddci_rd_thread->lddci_cfg_recv_buffer_sz = LDDCI_RECV_BUF_NUM_DEF * LDDCI_TS_SIZE;
+  e = linuxdvb_ddci_thread_start(LDDCI_TO_THREAD(ddci_rd_thread),
+                                 linuxdvb_ddci_read_thread, ddci_rd_thread,
+                                 "ldvb-ddci-rd");
+
+  return e;
+}
+
+static inline void
+linuxdvb_ddci_rd_thread_stop ( linuxdvb_ddci_rd_thread_t *ddci_rd_thread )
+{
+  linuxdvb_ddci_thread_stop(LDDCI_TO_THREAD(ddci_rd_thread));
+}
+
+
+/*****************************************************************************
+ *
+ * DD CI API functions
+ *
+ *****************************************************************************/
+
+linuxdvb_ddci_t *
+linuxdvb_ddci_create ( linuxdvb_ca_t *lca, const char *ci_path)
+{
+  linuxdvb_ddci_t *lddci;
+
+  lddci = calloc(1, sizeof(*lddci));
+  lddci->lca = lca;
+  lddci->lddci_path  = strdup(ci_path);
+  snprintf(lddci->lddci_id, sizeof(lddci->lddci_id), "ci%u", lca->lca_number);
+  lddci->lddci_fdW = -1;
+  lddci->lddci_fdR = -1;
+  linuxdvb_ddci_wr_thread_init(lddci);
+  linuxdvb_ddci_rd_thread_init(lddci);
+
+  tvhtrace(LS_DDCI, "created %s %s", lddci->lddci_id, lddci->lddci_path);
+
+  return lddci;
+}
+
+void
+linuxdvb_ddci_close ( linuxdvb_ddci_t *lddci )
+{
+  int closed = 0;
+
+  if (lddci->lddci_fdW >= 0) {
+    tvhtrace(LS_DDCI, "closing write %s %s (fd %d)",
+             lddci->lddci_id, lddci->lddci_path, lddci->lddci_fdW);
+    linuxdvb_ddci_wr_thread_stop(&lddci->lddci_wr_thread);
+    close(lddci->lddci_fdW);
+    lddci->lddci_fdW = -1;
+    closed = 1;
+  }
+  if (lddci->lddci_fdR >= 0) {
+    tvhtrace(LS_DDCI, "closing read %s %s (fd %d)",
+             lddci->lddci_id, lddci->lddci_path, lddci->lddci_fdR);
+    linuxdvb_ddci_rd_thread_stop(&lddci->lddci_rd_thread);
+    close(lddci->lddci_fdR);
+    lddci->lddci_fdR = -1;
+    closed = 1;
+  }
+  if (closed)
+    tvhtrace(LS_DDCI, "CAM %s closed", lddci->lddci_id);
+}
+
+int
+linuxdvb_ddci_open ( linuxdvb_ddci_t *lddci )
+{
+  int ret = 0;
+
+  if (lddci->lddci_fdW < 0) {
+    lddci->lddci_fdW = tvh_open(lddci->lddci_path, O_WRONLY, 0);
+    tvhtrace(LS_DDCI, "opening %s %s for write (fd %d)",
+             lddci->lddci_id, lddci->lddci_path, lddci->lddci_fdW);
+    lddci->lddci_fdR = tvh_open(lddci->lddci_path, O_RDONLY | O_NONBLOCK, 0);
+    tvhtrace(LS_DDCI, "opening %s %s for read (fd %d)",
+             lddci->lddci_id, lddci->lddci_path, lddci->lddci_fdR);
+
+    if (lddci->lddci_fdW >= 0 && lddci->lddci_fdR >= 0) {
+      ret = linuxdvb_ddci_wr_thread_start(&lddci->lddci_wr_thread);
+      if (!ret)
+        ret = linuxdvb_ddci_rd_thread_start(&lddci->lddci_rd_thread);
+    }
+    else {
+      tvhtrace(LS_DDCI, "open write/read failed %s %s (fd-W %d, fd-R %d)",
+               lddci->lddci_id, lddci->lddci_path, lddci->lddci_fdW,
+               lddci->lddci_fdR);
+      ret = -1;
+    }
+  }
+
+  if (ret < 0)
+    linuxdvb_ddci_close(lddci);
+  else
+    tvhtrace(LS_DDCI, "CAM %s opened", lddci->lddci_id);
+
+  return ret;
+}
+
+void
+linuxdvb_ddci_put ( linuxdvb_ddci_t *lddci, const uint8_t *tsb, int len )
+{
+  linuxdvb_ddci_wr_thread_buffer_put(&lddci->lddci_wr_thread, tsb, len );
+}
+
+int
+linuxdvb_ddci_assign ( linuxdvb_ddci_t *lddci, service_t *t )
+{
+  int ret = 0;
+
+  if (lddci->t && t != NULL) {
+    tvhwarn(LS_DDCI, "active assignment at %s changed to %p",
+            lddci->lddci_id, t );
+    ret = 1;
+  }
+  else if (t)
+    tvhnotice(LS_DDCI, "CAM %s assigned to %p", lddci->lddci_id, t );
+  else if (lddci->t) {
+    tvhnotice(LS_DDCI, "CAM %s unassigned from %p", lddci->lddci_id, lddci->t );
+
+    /* in case of MCD/MTD this needs to be re-thinked */
+    linuxdvb_ddci_wr_thread_statistic_clr(&lddci->lddci_wr_thread);
+  }
+
+  lddci->t = t;
+  return ret;
+}
+
+int
+linuxdvb_ddci_is_assigned ( linuxdvb_ddci_t *lddci )
+{
+  return !!lddci->t;
+}
+
+int
+linuxdvb_ddci_require_descramble
+  ( service_t *t, int_fast16_t pid, elementary_stream_t *st )
+{
+  int ret = 0;
+
+  switch (pid) {
+    case DVB_CAT_PID:
+    case DVB_EIT_PID:
+      ++ret;
+      break;
+  }
+
+  /* DD CI requires all CA descriptor PIDs */
+  if (st && st->es_type == SCT_CA)
+    ++ret;
+
+  return ret;
+}
index 5ed4a8a5a9d108562fe77185109beeb104bd93a0..ee9d7bbb0ca5072154d1cab1a1db8055d42fd20c 100644 (file)
@@ -55,6 +55,9 @@ typedef struct linuxdvb_frontend    linuxdvb_frontend_t;
 typedef struct linuxdvb_ca          linuxdvb_ca_t;
 typedef struct linuxdvb_ca_capmt    linuxdvb_ca_capmt_t;
 #endif
+#if ENABLE_DDCI
+typedef struct linuxdvb_ddci        linuxdvb_ddci_t;
+#endif
 typedef struct linuxdvb_satconf     linuxdvb_satconf_t;
 typedef struct linuxdvb_satconf_ele linuxdvb_satconf_ele_t;
 typedef struct linuxdvb_diseqc      linuxdvb_diseqc_t;
@@ -215,6 +218,10 @@ struct linuxdvb_ca
   int                      lca_state;
   const char               *lca_state_str;
   linuxdvb_ca_capmt_queue_t lca_capmt_queue;
+#if ENABLE_DDCI
+  linuxdvb_ddci_t          *lddci;
+#endif
+
   /*
    * CAM module info
    */
@@ -438,6 +445,39 @@ linuxdvb_ca_enqueue_capmt(linuxdvb_ca_t *lca, uint8_t slot, const uint8_t *ptr,
 
 #endif
 
+#if ENABLE_DDCI
+
+linuxdvb_ddci_t *
+linuxdvb_ddci_create ( linuxdvb_ca_t *lca, const char *ci_path);
+int
+linuxdvb_ddci_open ( linuxdvb_ddci_t *lddci );
+void
+linuxdvb_ddci_close ( linuxdvb_ddci_t *lddci );
+void
+linuxdvb_ddci_put ( linuxdvb_ddci_t *lddci, const uint8_t *tsb, int len );
+/* Un/Assign the service to DD CI CAM.
+ * If t is NULL, the service is unassigned.
+ *
+ *  ret: 0 .. un/assigned
+ *       1 .. assigned, but it was already assigned to another service
+ */
+int
+linuxdvb_ddci_assign ( linuxdvb_ddci_t *lddci, service_t *t );
+int
+linuxdvb_ddci_is_assigned ( linuxdvb_ddci_t *lddci );
+/* Checks if the given PID needs to be sent to the CAM (descrambler).
+ * This will check for special PIDs only. Scrambled packets (scrambled bits set)
+ * data needs to be forwarded in any case to the CAM.
+ *
+ *  ret:  0 .. not required
+ *       >0 .. PID is required by the CAM
+ */
+int
+linuxdvb_ddci_require_descramble
+  ( service_t *t, int_fast16_t pid, elementary_stream_t *st );
+
+#endif
+
 /*
  * Diseqc gear
  */
index f21f67593708cf2b3c9833a05a0d90c653d79869..a6d11c26bcc0e9bdfa68623e6ebee02dd834775f 100644 (file)
@@ -171,6 +171,10 @@ tvhlog_subsys_t tvhlog_subsystems[] = {
   [LS_TSDEBUG]       = { "tsdebug",       N_("MPEG-TS Input Debug") },
   [LS_CODEC]         = { "codec",         N_("Codec") },
   [LS_VAAPI]         = { "vaapi",         N_("VA-API") },
+#if ENABLE_DDCI
+  [LS_DDCI]          = { "ddci",          N_("DD-CI") },
+#endif
+
 };
 
 static void
@@ -226,7 +230,7 @@ tvhlog_set_subsys ( bitops_ulong_t *c, const char *subsys )
           break;
         }
       if (i >= LS_LAST)
-        tvherror(LS_CONFIG, "uknown subsystem '%s'", t);
+        tvherror(LS_CONFIG, "unkown subsystem '%s'", t);
     }
 next:
     t = strtok_r(NULL, ",", &r);
index 3708ff17b391754dca5033c1273579703bf7fce0..d1114350022934344e791b0e6821b6c84229f1a6 100644 (file)
@@ -195,6 +195,9 @@ enum {
   LS_TSDEBUG,
   LS_CODEC,
   LS_VAAPI,
+#if ENABLE_DDCI
+  LS_DDCI,
+#endif
   LS_LAST     /* keep this last */
 };