From: Jasmin Jessich Date: Sun, 12 Nov 2017 16:04:02 +0000 (+0100) Subject: Added DD CI support into new file X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=132af3fba821e5637396292c6f8d73657bb613a3;p=thirdparty%2Ftvheadend.git Added DD CI support into new file - 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 --- diff --git a/Makefile b/Makefile index 64a37fd7e..52c58e43d 100644 --- 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 \ diff --git a/configure b/configure index 2e9f8b290..71c0c64b3 100755 --- 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 index 000000000..fd930c361 --- /dev/null +++ b/src/input/mpegts/linuxdvb/linuxdvb_ddci.c @@ -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 . + */ + +#include "tvheadend.h" +#include "linuxdvb_private.h" +#include "tvhpoll.h" +#include "input/mpegts/tsdemux.h" + +#include + +/* 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; +} diff --git a/src/input/mpegts/linuxdvb/linuxdvb_private.h b/src/input/mpegts/linuxdvb/linuxdvb_private.h index 5ed4a8a5a..ee9d7bbb0 100644 --- a/src/input/mpegts/linuxdvb/linuxdvb_private.h +++ b/src/input/mpegts/linuxdvb/linuxdvb_private.h @@ -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 */ diff --git a/src/tvhlog.c b/src/tvhlog.c index f21f67593..a6d11c26b 100644 --- a/src/tvhlog.c +++ b/src/tvhlog.c @@ -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); diff --git a/src/tvhlog.h b/src/tvhlog.h index 3708ff17b..d11143500 100644 --- a/src/tvhlog.h +++ b/src/tvhlog.h @@ -195,6 +195,9 @@ enum { LS_TSDEBUG, LS_CODEC, LS_VAAPI, +#if ENABLE_DDCI + LS_DDCI, +#endif LS_LAST /* keep this last */ };