]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
en50221: use own en50221 code which replaces liben50221 from dvb-apps, fixes #4738
authorJaroslav Kysela <perex@perex.cz>
Thu, 14 Dec 2017 07:09:49 +0000 (08:09 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 14 Dec 2017 07:09:49 +0000 (08:09 +0100)
24 files changed:
.doozer.json
Makefile
configure
debian/control
src/atomic.h
src/descrambler/capmt.c
src/descrambler/dvbcam.c
src/descrambler/dvbcam.h
src/input/mpegts/dvb.h
src/input/mpegts/dvb_psi.c
src/input/mpegts/en50221/en50221.c [new file with mode: 0644]
src/input/mpegts/en50221/en50221.h [new file with mode: 0644]
src/input/mpegts/en50221/en50221_apps.c [new file with mode: 0644]
src/input/mpegts/en50221/en50221_capmt.c [new file with mode: 0644]
src/input/mpegts/en50221/en50221_capmt.h [new file with mode: 0644]
src/input/mpegts/linuxdvb/linuxdvb_adapter.c
src/input/mpegts/linuxdvb/linuxdvb_ca.c
src/input/mpegts/linuxdvb/linuxdvb_ddci.c
src/input/mpegts/linuxdvb/linuxdvb_frontend.c
src/input/mpegts/linuxdvb/linuxdvb_private.h
src/input/mpegts/linuxdvb/linuxdvb_satconf.c
src/main.c
src/tvheadend.h
src/tvhlog.h

index 1c2244f57227f88f0e1762bafe2f6a1a61d70846..b66a437bdd6fc89bd80c6152dea6e611130e6665 100644 (file)
@@ -18,7 +18,6 @@
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache",
         "libx11-xcb-dev"
@@ -45,7 +44,6 @@
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
@@ -71,7 +69,6 @@
         "liburiparser-dev",
         "libpcre2-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
@@ -96,7 +93,6 @@
         "liburiparser-dev",
         "libpcre2-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre2-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre3-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre2-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
         "liburiparser-dev",
         "libpcre2-dev",
         "python",
-        "dvb-apps",
         "debhelper",
         "ccache"
       ],
index 03d1c9a01e0813476486c9a3ddf324e57146a8a3..9dedae33c1764184fa1385a1dac2ee81006bf89d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -572,6 +572,9 @@ I18N-C += $(SRCS-CONSTCW)
 
 # DVB CAM
 SRCS-DVBCAM = \
+        src/input/mpegts/en50221/en50221.c \
+        src/input/mpegts/en50221/en50221_apps.c \
+        src/input/mpegts/en50221/en50221_capmt.c \
        src/input/mpegts/linuxdvb/linuxdvb_ca.c \
        src/descrambler/dvbcam.c
 SRCS-${CONFIG_LINUXDVB_CA} += $(SRCS-DVBCAM)
index 71c0c64b3f957adbc4883573a6470084abd44874..8bf4056476175cb91d01e41f519ee67c42fed8bd 100755 (executable)
--- a/configure
+++ b/configure
@@ -66,7 +66,6 @@ OPTIONS=(
   "bundle:no"
   "pngquant:no"
   "dvbcsa:no"
-  "dvben50221:auto"
   "kqueue:no"
   "dbus_1:auto"
   "android:no"
@@ -178,6 +177,12 @@ uint64_t test(time_t *ptr){
 return __sync_fetch_and_add(ptr, 1);
 }'
 
+check_cc_snippet atomic_ptr '#include <stdint.h>
+#include <time.h>
+void *test(void * volatile *ptr){
+return __sync_fetch_and_add(ptr, (void *)1);
+}'
+
 check_cc_snippet bitops64 '#include <stdint.h>
 int test(void){
   int l = sizeof(long);
@@ -282,23 +287,6 @@ else
   printf "    ^ using build-in glibc iconv routines\n"
 fi
 
-if enabled_or_auto dvben50221; then
-  check_cc_snippet libdvben50221 '
-  #include <libdvben50221/en50221_session.h>
-  #define TEST test
-  int test(void)
-  {
-    struct en50221_transport_layer *tl = en50221_tl_create(5, 32);
-    return 0;
-  }
-  ' '-ldvben50221 -ldvbapi -lucsi'
-  if enabled libdvben50221; then
-    enable dvben50221
-  else
-    disable dvben50221
-  fi
-fi
-
 check_cc_snippet ifnames '
 #include <net/if.h>
 int test(void)
@@ -354,6 +342,7 @@ check_cc_header 'linux/dvb/version' linuxdvbapi
 if enabled_or_auto linuxdvb; then
   if enabled linuxdvbapi; then
     enable linuxdvb
+    enable linuxdvb_ca
   elif enabled linuxdvb; then
     die "Linux DVB API not found (use --disable-linuxdvb)"
   fi
@@ -642,23 +631,6 @@ if enabled cwc || enabled cccam || enabled capmt || enabled constcw; then
   fi
 fi
 
-#
-# libdvben50221
-#
-if enabled linuxdvb; then
-  if enabled libdvben50221; then
-    LDFLAGS="$LDFLAGS -ldvben50221 -ldvbapi -lucsi"
-    enable linuxdvb_ca
-  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
 #
index 8e0bfec91206ada9edc16e53830a384d183bd96a..5fba90dd24560ad91c3e4ffecd17b2841e79c590 100644 (file)
@@ -2,12 +2,12 @@ Source: tvheadend
 Section: video
 Priority: extra
 Maintainer: Adam Sutton <aps@tvheadend.org>
-Build-Depends: debhelper (>= 7.0.50), pkg-config, gettext, libavahi-client-dev, libssl-dev, zlib1g-dev, wget, bzip2, git-core, liburiparser-dev, python, ca-certificates, cmake, dvb-apps, libpcre2-dev | libpcre3-dev
+Build-Depends: debhelper (>= 7.0.50), pkg-config, gettext, libavahi-client-dev, libssl-dev, zlib1g-dev, wget, bzip2, git-core, liburiparser-dev, python, ca-certificates, cmake, libpcre2-dev | libpcre3-dev
 Standards-Version: 3.7.3
 
 Package: tvheadend
 Architecture: any
-Depends: ${shlibs:Depends}, libavahi-client3, zlib1g, liburiparser1, dvb-apps, bzip2
+Depends: ${shlibs:Depends}, libavahi-client3, zlib1g, liburiparser1, bzip2
 Recommends: xmltv-util
 Enhances: showtime
 Replaces: hts-tvheadend
index 402a2861e6697c5b14abe56e506f4684321c3ee1..08f4e01678bd1b4cdec81eb89a416cd5d5771c55 100644 (file)
@@ -21,6 +21,8 @@
 #include <stdint.h>
 #include <time.h>
 
+typedef void * volatile * atomic_refptr_t;
+
 extern pthread_mutex_t atomic_lock;
 
 /*
@@ -259,6 +261,21 @@ atomic_exchange_time_t(volatile time_t *ptr, time_t val)
 #endif
 }
 
+static inline void *
+atomic_exchange_ptr(atomic_refptr_t ptr, void *val)
+{
+#if ENABLE_ATOMIC_PTR
+  return  __sync_lock_test_and_set(ptr, val);
+#else
+  void *ret;
+  pthread_mutex_lock(&atomic_lock);
+  ret = *ptr;
+  *ptr = val;
+  pthread_mutex_unlock(&atomic_lock);
+  return ret;
+#endif
+}
+
 /*
  * Atomic get operation
  */
@@ -315,6 +332,12 @@ atomic_set_time_t(volatile time_t *ptr, time_t val)
   return atomic_exchange_time_t(ptr, val);
 }
 
+static inline void *
+atomic_set_ptr(atomic_refptr_t ptr, void *val)
+{
+  return atomic_exchange_ptr(ptr, val);
+}
+
 /*
  * Atomic set operation + peak (MAX)
  */
index 6f41263a9b1c71748241975b8c1a18bff02b89df..42bc850c3fa7b9d96d63bef9bb9cfe33c4a75598 100644 (file)
@@ -69,12 +69,12 @@ typedef struct dmx_filter {
 
 #define DVBAPI_PROTOCOL_VERSION     2
 
-#define CA_SET_DESCR       0x40106f86
+#define CA_SET_DESCR_      0x40106f86
 #define CA_SET_DESCR_X     0x866f1040
 #define CA_SET_DESCR_AES   0x40106f87
 #define CA_SET_DESCR_AES_X 0x876f1040
 #define CA_SET_DESCR_MODE  0x400c6f88
-#define CA_SET_PID         0x40086f87
+#define CA_SET_PID_        0x40086f87
 #define CA_SET_PID_X       0x876f0840
 #define DMX_STOP           0x00006f2a
 #define DMX_STOP_X         0x2a6f0000
@@ -1240,9 +1240,9 @@ capmt_msg_size(capmt_t *capmt, sbuf_t *sb, int offset)
     }
   }
   sb->sb_err = 1; /* "first seen" flag for the moment */
-  if (cmd == CA_SET_PID)
+  if (cmd == CA_SET_PID_)
     return 4 + 8 + adapter_byte;
-  else if (cmd == CA_SET_DESCR)
+  else if (cmd == CA_SET_DESCR_)
     return 4 + 16 + adapter_byte;
   else if (cmd == CA_SET_DESCR_AES)
     return 4 + 32 + adapter_byte;
@@ -1291,7 +1291,7 @@ capmt_peek_str(sbuf_t *sb, int *offset)
 static void
 capmt_analyze_cmd(capmt_t *capmt, uint32_t cmd, int adapter, sbuf_t *sb, int offset)
 {
-  if (cmd == CA_SET_PID) {
+  if (cmd == CA_SET_PID_) {
 
     uint32_t pid   = sbuf_peek_u32(sb, offset + 0);
     int32_t  index = sbuf_peek_s32(sb, offset + 4);
@@ -1330,7 +1330,7 @@ capmt_analyze_cmd(capmt_t *capmt, uint32_t cmd, int adapter, sbuf_t *sb, int off
       tvherror(LS_CAPMT, "%s: Invalid index %d in CA_SET_PID (%d) for adapter %d", capmt_name(capmt), index, MAX_INDEX, adapter);
     }
 
-  } else if (cmd == CA_SET_DESCR) {
+  } else if (cmd == CA_SET_DESCR_) {
 
     int32_t index  = sbuf_peek_s32(sb, offset + 0);
     int32_t parity = sbuf_peek_s32(sb, offset + 4);
index cce0cebf856dbefcd5eaf0be0358597965ef207d..bd0831838cf237c368b27dd8a310fbadf4471001 100644 (file)
@@ -27,6 +27,7 @@
 #include "dvbcam.h"
 #include "input/mpegts/tsdemux.h"
 #include "input/mpegts/linuxdvb/linuxdvb_private.h"
+#include "input/mpegts/en50221/en50221_capmt.h"
 
 #if ENABLE_LINUXDVB_CA
 
 
 typedef struct dvbcam_active_cam {
   TAILQ_ENTRY(dvbcam_active_cam) global_link;
-  uint16_t             caids[CAIDS_PER_CA_SLOT];
-  int                  num_caids;
-  linuxdvb_ca_t       *ca;
-  uint8_t              slot;
-  int                  active_programs;
-  int                  allocated_programs;
+  uint16_t              caids[CAIDS_PER_CA_SLOT];
+  int                   num_caids;
+  linuxdvb_ca_t        *ca;
+  uint8_t               slot;
+  int                   active_programs;
+  int                   allocated_programs;
 } dvbcam_active_cam_t;
 
 typedef struct dvbcam_active_service {
   th_descrambler_t;
   TAILQ_ENTRY(dvbcam_active_service) global_link;
   LIST_ENTRY(dvbcam_active_service)  dvbcam_link;
-  uint8_t             *last_pmt;
-  int                  last_pmt_len;
-  uint16_t             caid_list[32];
-  dvbcam_active_cam_t *ac;
-  mpegts_apids_t       ecm_pids;
-  uint8_t             *cat_data;
-  int                  cat_data_len;
-  mpegts_apids_t       cat_pids;
+  uint8_t              *last_pmt;
+  int                   last_pmt_len;
+  uint16_t              caid_list[32];
+  dvbcam_active_cam_t  *ac;
+  mpegts_apids_t        ecm_pids;
+  uint8_t              *cat_data;
+  int                   cat_data_len;
+  mpegts_apids_t        cat_pids;
 #if ENABLE_DDCI
-  int                  is_ddci;
+  linuxdvb_ddci_t      *lddci;
 #endif
 } dvbcam_active_service_t;
 
@@ -124,13 +125,19 @@ dvbcam_service_check_caid(dvbcam_active_service_t *as, uint16_t caid)
 static void
 dvbcam_unregister_ddci(dvbcam_active_cam_t *ac, dvbcam_active_service_t *as)
 {
-  if (ac && ac->ca->lddci) {
+  linuxdvb_ddci_t *lddci;
+
+  if (ac == NULL)
+    return;
+  lddci = as->lddci;
+  if (lddci) {
     th_descrambler_t *td = (th_descrambler_t *)as;
     service_t *t = td->td_service;
     th_descrambler_runtime_t *dr = t->s_descramble;
 
     /* unassign the service from the DD CI CAM */
-    linuxdvb_ddci_assign(ac->ca->lddci, NULL);
+    as->lddci = NULL;
+    linuxdvb_ddci_assign(lddci, NULL);
     if (dr) {
       dr->dr_descrambler = NULL;
       dr->dr_descramble = NULL;
@@ -141,16 +148,13 @@ dvbcam_unregister_ddci(dvbcam_active_cam_t *ac, dvbcam_active_service_t *as)
 int
 dvbcam_is_ddci(struct service *t)
 {
-  th_descrambler_runtime_t  *dr = t->s_descramble;
+  th_descrambler_runtime_t *dr = t->s_descramble;
   int ret = 0;
 
   if (dr) {
     dvbcam_active_service_t  *as = (dvbcam_active_service_t *)dr->dr_descrambler;
-
-    if (as && as->ac) {
-      linuxdvb_ddci_t        *lddci = as->ac->ca->lddci;
-      ret = lddci != NULL;
-    }
+    if (as && as->ac)
+      ret = as->lddci != NULL;
   }
   return ret;
 }
@@ -160,53 +164,60 @@ dvbcam_is_ddci(struct service *t)
  *
  */
 void
-dvbcam_register_cam(linuxdvb_ca_t * lca, uint8_t slot, uint16_t * caids,
+dvbcam_register_cam(linuxdvb_ca_t * lca, uint16_t * caids,
                     int num_caids)
 {
   dvbcam_active_cam_t *ac, *ac_first;
+  int registered = 0;
 
-  tvhtrace(LS_DVBCAM, "register cam ca %p slot %u num_caids %u",
-           lca, slot, num_caids);
+  tvhtrace(LS_DVBCAM, "register cam %p num_caids %u", lca->lca_name, num_caids);
 
-  num_caids = MIN(CAIDS_PER_CA_SLOT, num_caids);
+  pthread_mutex_lock(&dvbcam_mutex);
 
-  if ((ac = calloc(1, sizeof(*ac))) == NULL)
-       return;
+  TAILQ_FOREACH(ac, &dvbcam_active_cams, global_link) {
+    if (ac->ca == lca) {
+      registered = 1;
+      break;
+    }
+  }
+  if (ac == NULL) {
+    if ((ac = calloc(1, sizeof(*ac))) == NULL)
+      return;
+    ac->ca = lca;
+  }
+
+  num_caids = MIN(CAIDS_PER_CA_SLOT, num_caids);
 
-  ac->ca = lca;
-  ac->slot = slot;
   memcpy(ac->caids, caids, num_caids * sizeof(uint16_t));
   ac->num_caids = num_caids;
 
-  pthread_mutex_lock(&dvbcam_mutex);
-
   ac_first = TAILQ_FIRST(&dvbcam_active_cams);
-  TAILQ_INSERT_TAIL(&dvbcam_active_cams, ac, global_link);
+  if (!registered)
+    TAILQ_INSERT_TAIL(&dvbcam_active_cams, ac, global_link);
 
   if (ac_first == NULL)
     dvbcam_status_update();
 
   pthread_mutex_unlock(&dvbcam_mutex);
-
 }
 
 /*
  *
  */
 void
-dvbcam_unregister_cam(linuxdvb_ca_t * lca, uint8_t slot)
+dvbcam_unregister_cam(linuxdvb_ca_t *lca)
 {
   dvbcam_active_cam_t *ac, *ac_next;
   dvbcam_active_service_t *as;
 
-  tvhtrace(LS_DVBCAM, "unregister cam lca %p slot %u", lca, slot);
+  tvhtrace(LS_DVBCAM, "unregister cam %s", lca->lca_name);
 
   pthread_mutex_lock(&dvbcam_mutex);
 
   /* delete entry */
   for (ac = TAILQ_FIRST(&dvbcam_active_cams); ac != NULL; ac = ac_next) {
     ac_next = TAILQ_NEXT(ac, global_link);
-    if (ac->ca == lca && ac->slot == slot) {
+    if (ac->ca == lca) {
       TAILQ_REMOVE(&dvbcam_active_cams, ac, global_link);
       /* remove pointer to this CAM in all active services */
       TAILQ_FOREACH(as, &dvbcam_active_services, global_link)
@@ -234,6 +245,7 @@ dvbcam_ca_lookup(dvbcam_active_cam_t *ac, mpegts_input_t *input, uint16_t caid)
 {
   extern const idclass_t linuxdvb_frontend_class;
   linuxdvb_frontend_t *lfe = NULL;
+  linuxdvb_transport_t *lcat;
   int i;
 
   if (ac->ca == NULL)
@@ -242,10 +254,11 @@ dvbcam_ca_lookup(dvbcam_active_cam_t *ac, mpegts_input_t *input, uint16_t caid)
   if (idnode_is_instance(&input->ti_id, &linuxdvb_frontend_class))
     lfe = (linuxdvb_frontend_t*)input;
 
+  lcat = ac->ca->lca_transport;
 #if ENABLE_DDCI
-  if (!ac->ca->lddci)
+  if (!lcat->lddci)
 #endif
-  if (lfe == NULL || ac->ca->lca_adapter != lfe->lfe_adapter)
+  if (lfe == NULL || lcat->lcat_adapter != lfe->lfe_adapter)
     return 0;
 
   for (i = 0; i < ac->num_caids; i++)
@@ -263,8 +276,9 @@ dvbcam_pmt_data(mpegts_service_t *s, const uint8_t *ptr, int len)
 {
   dvbcam_active_cam_t *ac;
   dvbcam_active_service_t *as;
-  uint8_t list_mgmt;
-  int is_update = 0;
+  int bcmd, is_update = 0, r;
+  uint8_t *capmt;
+  size_t capmt_len;
 
   pthread_mutex_lock(&s->s_stream_mutex);
   pthread_mutex_lock(&dvbcam_mutex);
@@ -275,13 +289,13 @@ dvbcam_pmt_data(mpegts_service_t *s, const uint8_t *ptr, int len)
       break;
 
   if (as == NULL) {
-    tvhtrace(LS_DVBCAM, "cannot find active service entry");
+    tvhtrace(LS_DVBCAM, "%s: cannot find active service entry", s->s_nicename);
     goto done;
   }
 
   ac = as->ac;
   if (!ac) {
-    tvhtrace(LS_DVBCAM, "cannot find active cam entry");
+    tvhtrace(LS_DVBCAM, "%s: cannot find active cam entry", s->s_nicename);
     goto done;
   }
 
@@ -300,15 +314,25 @@ dvbcam_pmt_data(mpegts_service_t *s, const uint8_t *ptr, int len)
   /* if this is update just send updated CAPMT to CAM */
   if (is_update) {
     tvhtrace(LS_DVBCAM, "CAPMT sent to CAM (update)");
-    list_mgmt = CA_LIST_MANAGEMENT_UPDATE;
+    bcmd = EN50221_CAPMT_BUILD_UPDATE;
   } else {
-    list_mgmt = ac->active_programs ? CA_LIST_MANAGEMENT_ADD :
-                                      CA_LIST_MANAGEMENT_ONLY;
+    if (ac->active_programs)
+      bcmd = EN50221_CAPMT_BUILD_ADD;
+    else
+      bcmd = EN50221_CAPMT_BUILD_ONLY;
     ac->active_programs++;
   }
 
-  linuxdvb_ca_enqueue_capmt(ac->ca, ac->slot, as->last_pmt, as->last_pmt_len,
-                            list_mgmt, CA_PMT_CMD_ID_OK_DESCRAMBLING);
+  r = en50221_capmt_build(s, bcmd,
+                          s->s_dvb_service_id,
+                          as->last_pmt, as->last_pmt_len,
+                          &capmt, &capmt_len);
+  if (r >= 0) {
+    linuxdvb_ca_enqueue_capmt(ac->ca, capmt, capmt_len, 1);
+    free(capmt);
+  } else {
+    tvherror(LS_DVBCAM, "CAPMT unable to build");
+  }
 done:
   pthread_mutex_unlock(&dvbcam_mutex);
   pthread_mutex_unlock(&s->s_stream_mutex);
@@ -319,16 +343,27 @@ dvbcam_service_destroy(th_descrambler_t *td)
 {
   dvbcam_active_service_t *as = (dvbcam_active_service_t *)td;
   dvbcam_active_cam_t *ac;
-  int do_active_programs = 0;
+  mpegts_service_t *s;
+  int do_active_programs = 0, r;
+  uint8_t *capmt;
+  size_t capmt_len;
 
   pthread_mutex_lock(&dvbcam_mutex);
   ac = as->ac;
   if (as->last_pmt) {
-    if (ac)
-      linuxdvb_ca_enqueue_capmt(ac->ca, ac->slot, as->last_pmt,
-                                as->last_pmt_len,
-                                CA_LIST_MANAGEMENT_UPDATE,
-                                CA_PMT_CMD_ID_NOT_SELECTED);
+    if (ac) {
+      s = (mpegts_service_t *)td->td_service;
+      r = en50221_capmt_build(s, EN50221_CAPMT_BUILD_DELETE,
+                              s->s_dvb_service_id,
+                              as->last_pmt, as->last_pmt_len,
+                              &capmt, &capmt_len);
+      if (r >= 0) {
+        linuxdvb_ca_enqueue_capmt(ac->ca, capmt, capmt_len, 0);
+        free(capmt);
+      } else {
+        tvherror(LS_DVBCAM, "CAPMT unable to build (destroy)");
+      }
+    }
     free(as->last_pmt);
     do_active_programs = 1;
   }
@@ -348,11 +383,12 @@ dvbcam_service_destroy(th_descrambler_t *td)
       break;
     }
   }
+  pthread_mutex_unlock(&dvbcam_mutex);
   mpegts_pid_done(&as->ecm_pids);
   mpegts_pid_done(&as->cat_pids);
   free(as->cat_data);
+  free(as->td_nicename);
   free(as);
-  pthread_mutex_unlock(&dvbcam_mutex);
 }
 
 #if ENABLE_DDCI
@@ -363,7 +399,7 @@ dvbcam_descramble_ddci(service_t *t, elementary_stream_t *st, const uint8_t *tsb
   dvbcam_active_service_t   *as = (dvbcam_active_service_t *)dr->dr_descrambler;
 
   if (as->ac != NULL)
-    linuxdvb_ddci_put(as->ac->ca->lddci, tsb, len);
+    linuxdvb_ddci_put(as->ac->ca->lca_transport->lddci, tsb, len);
 
   return 1;
 }
@@ -392,6 +428,7 @@ dvbcam_service_start(caclient_t *cac, service_t *t)
   mpegts_apids_t ecm_pids;
   mpegts_apids_t ecm_to_open;
   mpegts_apids_t ecm_to_close;
+  linuxdvb_transport_t *lcat;
 #endif
 
   if (!cac->cac_enabled)
@@ -453,7 +490,8 @@ dvbcam_service_start(caclient_t *cac, service_t *t)
               continue;
   #if ENABLE_DDCI
             /* currently we allow only one service per DD CI */
-            if (ac->ca->lddci && linuxdvb_ddci_is_assigned(ac->ca->lddci))
+            lcat = ac->ca->lca_transport;
+            if (lcat->lddci && linuxdvb_ddci_is_assigned(lcat->lddci))
               continue;
   #endif
             tvhtrace(LS_DVBCAM, "%s/%p: match CAID %04X PID %d (%04X)",
@@ -526,7 +564,7 @@ end_of_search_for_cam:
 
   td = (th_descrambler_t *)as;
   snprintf(buf, sizeof(buf), "dvbcam-%i-%i-%04X",
-           ac->ca->lca_number, (int)ac->slot, (int)as->caid_list[0]);
+           ac->ca->lca_adapnum, ac->ca->lca_slotnum, (int)as->caid_list[0]);
   td->td_nicename = strdup(buf);
   td->td_service = t;
   td->td_stop = dvbcam_service_destroy;
@@ -534,11 +572,12 @@ end_of_search_for_cam:
   dr->dr_descrambler = td;
   dr->dr_descramble = descrambler_pass;
 #if ENABLE_DDCI
-  if (ac->ca->lddci) {
+  lcat = ac->ca->lca_transport;
+  if (lcat->lddci) {
     /* assign the service to the DD CI CAM */
-    linuxdvb_ddci_assign(ac->ca->lddci, t);
+    linuxdvb_ddci_assign(lcat->lddci, t);
     dr->dr_descramble = dvbcam_descramble_ddci;
-    as->is_ddci = 1;
+    as->lddci = lcat->lddci;
   }
 #endif
   descrambler_change_keystate(td, DS_READY, 0);
@@ -570,7 +609,7 @@ update_pid:
   pthread_mutex_unlock(&t->s_stream_mutex);
 
 #if ENABLE_DDCI
-  if (as && as->is_ddci) {
+  if (as && as->lddci) {
     mm = ((mpegts_service_t *)t)->s_dvb_mux;
     mi = mm->mm_active ? mm->mm_active->mmi_input : NULL;
     if (mi) {
@@ -643,7 +682,7 @@ dvbcam_cat_update(caclient_t *cac, mpegts_mux_t *mux, const uint8_t *data, int l
   pthread_mutex_lock(&dvbcam_mutex);
   /* look for CAID in all services */
   TAILQ_FOREACH(as, &dvbcam_active_services, global_link) {
-    if (!as->is_ddci) continue;
+    if (!as->lddci) continue;
     if (as->caid_list[0] == 0) continue;
     if (((mpegts_service_t *)as->td_service)->s_dvb_mux != mux) continue;
     if (len == as->cat_data_len &&
index 92548a32fe404e7d9f21ae3b92be87a0599a5942..abdffbf78e39524c0736667be81fb7d979cfd409 100644 (file)
@@ -27,8 +27,8 @@ struct elementary_stream;
 struct linuxdvb_ca;
 
 void dvbcam_init(void);
-void dvbcam_register_cam(struct linuxdvb_ca *lca, uint8_t slot, uint16_t * caids, int num_caids);
-void dvbcam_unregister_cam(struct linuxdvb_ca *lca, uint8_t slot);
+void dvbcam_register_cam(struct linuxdvb_ca *lca, uint16_t * caids, int num_caids);
+void dvbcam_unregister_cam(struct linuxdvb_ca *lca);
 void dvbcam_pmt_data(struct mpegts_service *s, const uint8_t *ptr, int len);
 
 #if ENABLE_DDCI
index cc0a92bb0c3e6dee6277c9878163cbcf80ca4df4..e38c1569d68abe35573b298e544284b2c97c97ca 100644 (file)
@@ -26,6 +26,9 @@
 #ifndef __TVH_DVB_SUPPORT_H__
 #define __TVH_DVB_SUPPORT_H__
 
+#include "queue.h"
+#include "redblack.h"
+
 struct mpegts_table;
 struct mpegts_table_state;
 struct mpegts_network;
index 2a8505ed876fa9f40c3cc088bf3bc1ebd24c512e..bdbdcf75c275b300ecd11a3c614b05d2ab888d43 100644 (file)
@@ -1135,8 +1135,7 @@ dvb_pmt_callback
     descrambler_caid_changed((service_t *)s);
 
 #if ENABLE_LINUXDVB_CA
-  /* DVBCAM requires full pmt data including header and crc */
-  dvbcam_pmt_data(s, ptr - 3, len + 3 + 4);
+  dvbcam_pmt_data(s, ptr, len);
 #endif
 
   /* Finish */
diff --git a/src/input/mpegts/en50221/en50221.c b/src/input/mpegts/en50221/en50221.c
new file mode 100644 (file)
index 0000000..295b3b7
--- /dev/null
@@ -0,0 +1,901 @@
+/*
+ *  Tvheadend - CI CAM (EN50221) generic interface
+ *  Copyright (C) 2017 Jaroslav Kysela
+ *
+ *  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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include "tvhlog.h"
+#include "queue.h"
+#include "sbuf.h"
+#include "en50221.h"
+
+/*
+ * Implementation note: For simplicity, the session is tied with
+ * the application structure and the connection is tied with the
+ * slot structure, because there is 1:1 mapping.
+ */
+
+static int en50221_slot_create
+  (en50221_transport_t *cit, uint8_t number, en50221_slot_t **cil);
+static void en50221_slot_destroy(en50221_slot_t *cil);
+static int en50221_slot_connection_initiated(en50221_slot_t *cil);
+static void en50221_slot_reset(en50221_slot_t *cil);
+static int en50221_slot_trigger_write_queue(en50221_slot_t *cil);
+#if 0
+static int en50221_slot_session_create
+  (en50221_slot_t *cil, uint32_t resource_id);
+#endif
+static int en50221_slot_spdu_recv
+  (en50221_slot_t *cil, const uint8_t *data, size_t datalen);
+static int en50221_slot_monitor(en50221_slot_t *cil, int64_t clock);
+static int en50221_create_app
+  (en50221_slot_t *cil, uint32_t resource_id,
+   uint16_t session_id, en50221_app_t **cia);
+static void en50221_app_destroy(en50221_app_t *cia);
+static int en50221_app_handle
+  (en50221_app_t *cia, uint8_t status, const uint8_t *data, size_t datalen);
+static int en50221_app_open(en50221_app_t *cia);
+static int en50221_app_monitor(en50221_app_t *cia, int64_t clock);
+
+/*
+ * Misc
+ */
+
+static inline uint32_t get32(const uint8_t *p)
+{
+  return (((uint32_t)p[0]) << 24) |
+         (((uint32_t)p[1]) << 16) |
+         (((uint32_t)p[2]) <<  8) |
+                     p[3];
+}
+
+static inline uint32_t getatag(const uint8_t *p, size_t l)
+{
+  if (l < 3)
+    return CICAM_AOT_NONE + l;
+  return (((uint32_t)p[0]) << 16) |
+         (((uint32_t)p[1]) <<  8) |
+                     p[2];
+}
+
+/*
+ * Transport layer
+ */
+
+#define CICAM_TDPU_SIZE      4096
+#define CICAM_TDPU_DATA_SIZE (CICAM_TDPU_SIZE-7)
+
+/* EN50221, Annex A.4.1.13 (pg70) */
+#define CICAM_T_SB           0x80  /* SB                             h<--m */
+#define CICAM_T_RCV          0x81  /* receive                        h-->m */
+#define CICAM_T_CREATE_TC    0x82  /* create transport connection    h-->m */
+#define CICAM_T_CTC_REPLY    0x83  /* ctc reply                      h<--m */
+#define CICAM_T_DELETE_TC    0x84  /* delete tc                      h<->m */
+#define CICAM_T_DTC_REPLY    0x85  /* delete tc reply                h<->m */
+#define CICAM_T_REQUEST_TC   0x86  /* request transport connection   h<--m */
+#define CICAM_T_NEW_TC       0x87  /* new tc (reply to request)      h-->m */
+#define CICAM_T_TC_ERROR     0x88  /* error creating tc              h-->m */
+#define CICAM_T_DATA_LAST    0xA0  /* data transfer                  h<->m */
+#define CICAM_T_DATA_MORE    0xA1  /* data transfer                  h<->m */
+
+/* Session tags */
+#define CICAM_ST_SESSION_NUMBER              0x90
+#define CICAM_ST_OPEN_SESSION_REQUEST        0x91
+#define CICAM_ST_OPEN_SESSION_RESPONSE       0x92
+#define CICAM_ST_CREATE_SESSION              0x93
+#define CICAM_ST_CREATE_SESSION_RESPONSE     0x94
+#define CICAM_ST_CLOSE_SESSION_REQUEST       0x95
+#define CICAM_ST_CLOSE_SESSION_RESPONSE      0x96
+
+/* Session state codes */
+#define CICAM_SS_OK                          0x00
+#define CICAM_SS_NOT_ALLOCATED               0xF0
+
+int en50221_create_transport
+  (en50221_ops_t *ops, void *ops_aux, int slots,
+   const char *name, en50221_transport_t **_cit)
+{
+  en50221_transport_t *cit;
+
+  cit = calloc(1, sizeof(*cit));
+  if (cit == NULL)
+    return -ENOMEM;
+  cit->cit_ops = ops;
+  cit->cit_ops_aux = ops_aux;
+  cit->cit_name = strdup(name);
+  cit->cit_slot_count = slots;
+  sbuf_init(&cit->cit_rmsg);
+  *_cit = cit;
+  return 0;
+}
+
+static void en50221_transport_destroy_slots(en50221_transport_t *cit)
+{
+  en50221_slot_t *slot;
+
+  while ((slot = LIST_FIRST(&cit->cit_slots)) != NULL)
+    en50221_slot_destroy(slot);
+}
+
+void en50221_transport_destroy(en50221_transport_t *cit)
+{
+  if (cit == NULL)
+    return;
+  en50221_transport_destroy_slots(cit);
+  sbuf_free(&cit->cit_rmsg);
+  free(cit->cit_name);
+  free(cit);
+}
+
+static int en50221_transport_build_slots(en50221_transport_t *cit)
+{
+  int i, r;
+  for (i = 0; i < cit->cit_slot_count; i++) {
+    r = en50221_slot_create(cit, i, NULL);
+    if (r < 0)
+      return r;
+  }
+  return 0;
+}
+
+int en50221_transport_reset(en50221_transport_t *cit)
+{
+  int r;
+
+  r = cit->cit_ops->cihw_reset(cit->cit_ops_aux);
+  if (r == 0) {
+    en50221_transport_destroy_slots(cit);
+    r = en50221_transport_build_slots(cit);
+    if (r < 0)
+      return r;
+    cit->cit_broken = 0;
+  }
+  return r;
+}
+
+static int
+en50221_transport_pdu_write(en50221_transport_t *cit,
+                            en50221_slot_t *cil, uint8_t tcnum,
+                            uint8_t tag, const uint8_t *data,
+                            size_t datalen)
+{
+  uint8_t *buf = alloca(6 + datalen);
+  size_t hlen;
+
+  buf[0] = tag;
+  datalen++;
+  if (datalen < 0x80) {
+    buf[1] = datalen & 0x7f;
+    hlen = 2;
+  } else if (datalen < 0x100) {
+    buf[1] = 0x82;
+    buf[2] = datalen & 0xff;
+    hlen = 3;
+  } else if (datalen < 0x10000) {
+    buf[1] = 0x83;
+    buf[2] = datalen >> 8;
+    buf[3] = datalen & 0xff;
+    hlen = 4;
+  } else {
+    tvherror(LS_EN50221, "%s: too much pdu data to write %zd",
+                         cit->cit_name, datalen);
+    return -EIO;
+  }
+  buf[hlen++] = tcnum;
+  if (datalen > 1)
+    memcpy(buf + hlen, data, datalen - 1);
+  return
+    cit->cit_ops->cihw_pdu_write(cit->cit_ops_aux, cil, tcnum,
+                                 buf, datalen + hlen - 1);
+}
+
+en50221_slot_t *
+en50221_transport_find_slot(en50221_transport_t *cit, uint8_t slotnum)
+{
+  en50221_slot_t *slot;
+
+  LIST_FOREACH(slot, &cit->cit_slots, cil_link)
+    if (slot->cil_number == slotnum)
+      return slot;
+  return NULL;
+}
+
+static int en50221_transport_broken(en50221_transport_t *cit,
+                                const uint8_t *data, size_t datalen)
+{
+  cit->cit_broken = 1;
+  tvhlog_hexdump(LS_EN50221, data, datalen);
+  return -ENXIO;
+}
+
+int en50221_transport_read(en50221_transport_t *cit,
+                           uint8_t slotnum, uint8_t tcnum,
+                           const uint8_t *data, size_t datalen)
+{
+  const uint8_t *p, *end;
+  uint8_t tag;
+  en50221_slot_t *slot;
+  size_t l;
+  int r;
+
+  if (cit->cit_broken)
+    return -ENXIO;
+
+  if (slotnum + 1 != tcnum) {
+    tvherror(LS_EN50221, "%s: invalid slot number 0x%02x and "
+                         "transport connection id (0x%02x)",
+                         cit->cit_name, slotnum, tcnum);
+    return en50221_transport_broken(cit, data, datalen);
+  }
+  slot = en50221_transport_find_slot(cit, slotnum);
+  if (slot == NULL) {
+    tvherror(LS_EN50221, "%s: invalid slot number 0x%02x",
+                         cit->cit_name, slotnum);
+    return en50221_transport_broken(cit, data, datalen);
+  }
+  if (datalen > 0)
+    slot->cil_monitor_read = mclk();
+
+  p = data;
+  end = data + datalen;
+  while (p + 3 <= end) {
+    tag = *p++;
+    r = en50221_extract_len(p, end - p, &p, &l, cit->cit_name, "tpdu");
+    if (r < 0)
+      return en50221_transport_broken(cit, data, datalen);
+    if (l == 0) {
+      tvherror(LS_EN50221, "%s: invalid zero length", cit->cit_name);
+      return en50221_transport_broken(cit, data, datalen);
+    }
+    if (end - p < l) {
+      tvhtrace(LS_EN50221, "%s: invalid data received (%zd)", cit->cit_name, (size_t)(end - p));
+      break;
+    }
+    switch (tag) {
+    case CICAM_T_SB:
+      if (slot->cil_closing) {
+        r = en50221_slot_trigger_write_queue(slot);
+        if (r < 0)
+          return en50221_transport_broken(cit, data, datalen);
+        break;
+      }
+      if (slot->cil_ready == 0) {
+        tvhwarn(LS_EN50221, "%s: received sb but slot is not ready", cit->cit_name);
+        break;
+      }
+      /* data are waiting? tell CAM that we can receive them or flush the write queue */
+      if (l == 2) {
+        if ((p[1] & 0x80) != 0) {
+          r = en50221_transport_pdu_write(cit, slot, tcnum, CICAM_T_RCV, NULL, 0);
+        } else {
+          r = en50221_slot_trigger_write_queue(slot);
+        }
+        if (r < 0)
+          return en50221_transport_broken(cit, data, datalen);
+      }
+      break;
+    case CICAM_T_CTC_REPLY:
+      if (l != 1) {
+        tvherror(LS_EN50221, "%s: invalid length %zd for tag 0x%02x", cit->cit_name, l, tag);
+        return en50221_transport_broken(cit, data, datalen);
+      }
+      slot->cil_ready = 1;
+      if (slot->cil_initiate_connection)
+        en50221_slot_connection_initiated(slot);
+      break;
+    case CICAM_T_DATA_MORE:
+    case CICAM_T_DATA_LAST:
+      if (slot->cil_ready == 0) {
+        tvhwarn(LS_EN50221, "%s: received data but slot is not ready", cit->cit_name);
+        break;
+      }
+      if (l > 1) {
+        p++; l--; /* what's this byte? tcnum? */
+        if (tag == CICAM_T_DATA_LAST) {
+          if (cit->cit_rmsg.sb_ptr == 0) {
+            r = en50221_slot_spdu_recv(slot, p, l);
+          } else {
+            sbuf_append(&cit->cit_rmsg, p, l);
+            r = en50221_slot_spdu_recv(slot, cit->cit_rmsg.sb_data,
+                                       cit->cit_rmsg.sb_ptr);
+            sbuf_reset(&cit->cit_rmsg, CICAM_TDPU_SIZE);
+          }
+          if (r < 0)
+            return en50221_transport_broken(cit, data, datalen);
+        } else {
+         sbuf_append(&cit->cit_rmsg, p, l);
+        }
+      }
+      break;
+    default:
+      tvherror(LS_EN50221, "%s: unknown transport tag 0x%02x", cit->cit_name, tag);
+      return en50221_transport_broken(cit, data, datalen);
+    }
+    p += l;
+  }
+  return 0;
+}
+
+int en50221_transport_monitor(en50221_transport_t *cit, int64_t clock)
+{
+  en50221_slot_t *slot;
+  int r;
+
+  if (LIST_EMPTY(&cit->cit_slots) && cit->cit_slot_count > 0) {
+    r = en50221_transport_build_slots(cit);
+    if (r < 0)
+      return r;
+  }
+  LIST_FOREACH(slot, &cit->cit_slots, cil_link) {
+    if (cit->cit_ops->cihw_cam_is_ready(cit->cit_ops_aux, slot) > 0) {
+      en50221_slot_enable(slot);
+      r = en50221_slot_monitor(slot, clock);
+      if (r < 0)
+        return r;
+    } else {
+      en50221_slot_disable(slot);
+    }
+  }
+  return 0;
+}
+
+/*
+ *  Slot/connection layer
+ */
+static int
+en50221_slot_create
+  (en50221_transport_t *cit, uint8_t number, en50221_slot_t **cil)
+{
+  en50221_slot_t *slot;
+  char buf[128];
+
+  slot = calloc(1, sizeof(*slot));
+  if (slot == NULL)
+    return -ENOMEM;
+  snprintf(buf, sizeof(buf), "%s-slot%d", cit->cit_name, number);
+  slot->cil_name = strdup(buf);
+  slot->cil_transport = cit;
+  slot->cil_number = number;
+  slot->cil_monitor_read = mclk();
+  TAILQ_INIT(&slot->cil_write_queue);
+  LIST_INSERT_HEAD(&cit->cit_slots, slot, cil_link);
+  if (cil)
+    *cil = slot;
+  return 0;
+}
+
+static void
+en50221_slot_applications_destroy(en50221_slot_t *cil)
+{
+  en50221_app_t *app;
+
+  while ((app = LIST_FIRST(&cil->cil_apps)) != NULL)
+    en50221_app_destroy(app);
+}
+
+static void
+en50221_slot_clear(en50221_slot_t *cil)
+{
+  en50221_slot_wmsg_t *wmsg;
+
+  if (cil == NULL)
+    return;
+  en50221_slot_applications_destroy(cil);
+  while ((wmsg = TAILQ_FIRST(&cil->cil_write_queue)) != NULL) {
+    TAILQ_REMOVE(&cil->cil_write_queue, wmsg, link);
+    free(wmsg);
+  }
+}
+
+static void
+en50221_slot_destroy(en50221_slot_t *cil)
+{
+  en50221_slot_clear(cil);
+  LIST_REMOVE(cil, cil_link);
+  free(cil->cil_name);
+  free(cil);
+}
+
+static void
+en50221_slot_reset(en50221_slot_t *cil)
+{
+  en50221_slot_clear(cil);
+  cil->cil_ready = 0;
+  cil->cil_initiate_connection = 0;
+  cil->cil_closing = 0;
+  cil->cil_tcnum = 0;
+}
+
+static inline int en50221_slot_pdu_send
+  (en50221_slot_t *cil, uint8_t tag, const uint8_t *data, size_t datalen,
+   int reply_is_expected)
+{
+  en50221_slot_wmsg_t *wmsg;
+
+  wmsg = malloc(sizeof(*wmsg) + datalen);
+  wmsg->tag = tag;
+  wmsg->len = datalen;
+  wmsg->reply_is_expected = reply_is_expected;
+  if (data && datalen > 0)
+    memcpy(wmsg->data, data, datalen);
+  TAILQ_INSERT_TAIL(&cil->cil_write_queue, wmsg, link);
+  return 0;
+}
+
+static int en50221_slot_trigger_write_queue(en50221_slot_t *cil)
+{
+  en50221_slot_wmsg_t *wmsg;
+  int r = 0;
+
+  wmsg = TAILQ_FIRST(&cil->cil_write_queue);
+  if (wmsg) {
+    TAILQ_REMOVE(&cil->cil_write_queue, wmsg, link);
+    r = en50221_transport_pdu_write(cil->cil_transport, cil, cil->cil_tcnum,
+                                    wmsg->tag, wmsg->data, wmsg->len);
+    free(wmsg);
+  } else {
+    if (cil->cil_ready) {
+      r = en50221_transport_pdu_write(cil->cil_transport, cil, cil->cil_tcnum,
+                                      CICAM_T_DATA_LAST, NULL, 0);
+    }
+  }
+  if (cil->cil_closing && TAILQ_EMPTY(&cil->cil_write_queue))
+    cil->cil_closing = 0;
+  return r;
+}
+
+static int
+en50221_slot_initiate_connection(en50221_slot_t *cil)
+{
+  en50221_app_t *app;
+  int r;
+
+  if (cil->cil_apdu_only) {
+    /* create dummy conditional access application */
+    r = en50221_create_app(cil, CICAM_RI_CONDITIONAL_ACCESS_SUPPORT, 1, &app);
+    if (r < 0)
+      return r;
+    r = en50221_app_pdu_send(app, CICAM_AOT_APPLICATION_INFO_ENQ, NULL, 0, 1);
+    if (r < 0)
+      return r;
+    return 0;
+  }
+  /* static connection ID allocation */
+  cil->cil_tcnum = cil->cil_number + 1;
+  r = en50221_slot_pdu_send(cil, CICAM_T_CREATE_TC, NULL, 0, 1);
+  if (r < 0)
+    return r;
+  cil->cil_initiate_connection = 1;
+  return en50221_slot_trigger_write_queue(cil);
+}
+
+int
+en50221_slot_enable(en50221_slot_t *cil)
+{
+  if (!cil->cil_enabled) {
+    en50221_slot_reset(cil);
+    cil->cil_enabled = 1;
+    return en50221_slot_initiate_connection(cil);
+  }
+  return 0;
+}
+
+int
+en50221_slot_disable(en50221_slot_t *cil)
+{
+  if (cil->cil_enabled) {
+    en50221_slot_reset(cil);
+    cil->cil_enabled = 0;
+    if (!cil->cil_apdu_only) {
+      en50221_slot_pdu_send(cil, CICAM_T_DELETE_TC, NULL, 0, 1);
+      cil->cil_closing = 1;
+      tvhtrace(LS_EN50221, "%s: closing", cil->cil_name);
+    }
+  }
+  return 0;
+}
+
+static int en50221_slot_connection_initiated(en50221_slot_t *cil)
+{
+  cil->cil_initiate_connection = 0;
+  return 0;
+}
+
+static en50221_app_t *
+en50221_slot_find_session(en50221_slot_t *cil, uint16_t session_id)
+{
+  en50221_app_t *cia;
+
+  LIST_FOREACH(cia, &cil->cil_apps, cia_link)
+    if (cia->cia_session_id == session_id)
+      return cia;
+  return NULL;
+}
+
+en50221_app_t *
+en50221_slot_find_application(en50221_slot_t *cil, uint32_t resource_id, uint32_t mask)
+{
+  en50221_app_t *cia;
+
+  resource_id &= mask;
+  LIST_FOREACH(cia, &cil->cil_apps, cia_link) {
+    if ((cia->cia_resource_id & mask) == resource_id)
+      return cia;
+  }
+  return NULL;
+}
+
+#if 0
+static int en50221_slot_session_create
+  (en50221_slot_t *cil, uint32_t resource_id)
+{
+  en50221_app_t *app;
+  uint16_t session;
+  uint8_t buf[8];
+  int r;
+
+  session = ++cil->cil_last_session_number;
+  if (session == 0)
+    session = ++cil->cil_last_session_number;
+  r = en50221_create_app(cil, resource_id, session, &app);
+  if (r < 0)
+    return r;
+  buf[0] = CICAM_ST_CREATE_SESSION;
+  buf[1] = 6;
+  buf[2] = resource_id >> 24;
+  buf[3] = resource_id >> 16;
+  buf[4] = resource_id >> 8;
+  buf[5] = resource_id;
+  buf[6] = session >> 8;
+  buf[7] = session;
+  return en50221_slot_pdu_send(cil, CICAM_T_DATA_LAST, buf, 8);
+}
+#endif
+
+static int en50221_slot_spdu_recv
+  (en50221_slot_t *cil, const uint8_t *data, size_t datalen)
+{
+  en50221_app_t *app;
+  uint16_t session;
+  uint32_t ri;
+  uint8_t buf[9];
+  int r;
+
+  switch (data[0]) {
+  case CICAM_ST_SESSION_NUMBER:
+    if (datalen <= 4) {
+      tvherror(LS_EN50221, "%s: unhandled session number length %zd",
+                           cil->cil_name, datalen);
+      return -EINVAL;
+    }
+    session = (data[2] << 8) | data[3];
+    app = en50221_slot_find_session(cil, session);
+    if (app == NULL) {
+      tvherror(LS_EN50221, "%s: unhandled session message length %zd "
+                           "session number 0x%04x",
+                           cil->cil_name, datalen, session);
+      return -EINVAL;
+    }
+    return en50221_app_handle(app, data[1], data + 4, datalen - 4);
+  case CICAM_ST_OPEN_SESSION_REQUEST:
+    if (datalen != 6 || data[1] != 4) {
+      tvherror(LS_EN50221, "%s: unhandled open session request length %zd "
+                           "data1 0x%02x", cil->cil_name, datalen, data[1]);
+      return -EINVAL;
+    }
+    ri = get32(data + 2);
+    app = en50221_slot_find_application(cil, ri, ~0);
+    if (app == NULL) {
+      session = ++cil->cil_last_session_number;
+      if (session == 0)
+        session = ++cil->cil_last_session_number;
+      r = en50221_create_app(cil, ri, session, &app);
+      if (r < 0)
+        return r;
+    } else {
+      session = app->cia_session_id;
+    }
+    buf[0] = CICAM_ST_OPEN_SESSION_RESPONSE;
+    buf[1] = 7;
+    buf[2] = app->cia_prop ? CICAM_SS_OK : CICAM_SS_NOT_ALLOCATED;
+    memcpy(buf + 3, data + 2, 4); /* ri */
+    buf[7] = session >> 8;
+    buf[8] = session & 0xff;
+    r = en50221_slot_pdu_send(cil, CICAM_T_DATA_LAST, buf, 9, 0);
+    if (r < 0)
+      return r;
+    return en50221_app_open(app);
+  case CICAM_ST_CREATE_SESSION_RESPONSE:
+    if (datalen != 9 || data[1] != 7) {
+      tvherror(LS_EN50221, "%s: unhandled create session response length %zd "
+                           "data1 0x%02x", cil->cil_name, datalen, data[1]);
+      return -EINVAL;
+    }
+    ri = get32(data + 3);
+    session = (data[7] << 8) | data[8];
+    if (data[2] != CICAM_SS_OK) {
+      tvherror(LS_EN50221, "%s: CAM rejected to create session 0x%04x",
+                           cil->cil_name, session);
+      return -EINVAL;
+    }
+    app = en50221_slot_find_session(cil, session);
+    if (app == NULL) {
+      r = en50221_create_app(cil, ri, session, &app);
+      if (r < 0)
+        return r;
+    }
+    break;
+  case CICAM_ST_CLOSE_SESSION_REQUEST:
+    if (datalen != 4 || data[1] != 2) {
+      tvherror(LS_EN50221, "%s: unhandled close session request length %zd "
+                           "data1 0x%02x", cil->cil_name, datalen, data[1]);
+      return -EINVAL;
+    }
+    session = (data[2] << 8) | data[3];
+    app = en50221_slot_find_session(cil, session);
+    if (app == NULL) {
+      tvherror(LS_EN50221, "%s: unhandled close session request, "
+                           "session number 0x%04x not found", cil->cil_name, session);
+      return -EINVAL;
+    }
+    en50221_app_destroy(app);
+    buf[0] = CICAM_ST_CLOSE_SESSION_RESPONSE;
+    buf[1] = 3;
+    buf[2] = CICAM_SS_OK;
+    buf[3] = session >> 8;
+    buf[4] = session & 0xff;
+    return en50221_slot_pdu_send(cil, CICAM_T_DATA_LAST, buf, 5, 0);
+  case CICAM_ST_CLOSE_SESSION_RESPONSE:
+    if (datalen != 5 || data[1] != 3 || data[2]) {
+      tvherror(LS_EN50221, "%s: unhandled close session response length %zd "
+                           "data1 0x%02x data2 0x%02x",
+                           cil->cil_name, datalen, data[1], data[2]);
+      return -EINVAL;
+    }
+    session = (data[3] << 8) | data[4];
+    app = en50221_slot_find_session(cil, session);
+    if (app == NULL) {
+      tvherror(LS_EN50221, "%s: unhandled close session response, "
+                           "session number 0x%04x not found",
+                           cil->cil_name, session);
+      return -EINVAL;
+    }
+    en50221_app_destroy(app);
+    break;
+  default:
+    tvherror(LS_EN50221, "%s: unknown session tag 0x%02x", cil->cil_name, data[0]);
+    return -EINVAL;
+  }
+  return 0;
+}
+
+static int
+en50221_slot_monitor(en50221_slot_t *cil, int64_t clock)
+{
+  en50221_app_t *cia;
+  int r;
+
+  LIST_FOREACH(cia, &cil->cil_apps, cia_link) {
+    if (cil->cil_monitor_read + ms2mono(500) < mclk()) {
+      tvherror(LS_EN50221, "%s: communication stalled for more than 500ms",
+               cil->cil_name);
+      return -ENXIO;
+    }
+    r = en50221_app_monitor(cia, clock);
+    if (r < 0)
+      return r;
+  }
+  return 0;
+}
+
+/*
+ *  Application/session layer
+ */
+static LIST_HEAD(, en50221_app_prop) en50221_app_props;
+
+void en50221_register_app(en50221_app_prop_t *prop)
+{
+  LIST_INSERT_HEAD(&en50221_app_props, prop, ciap_link);
+}
+
+static int en50221_create_app
+  (en50221_slot_t *cil, uint32_t resource_id,
+   uint16_t session_id, en50221_app_t **cia)
+{
+  en50221_app_prop_t *prop;
+  en50221_app_t *app;
+  char buf[128];
+
+  LIST_FOREACH(prop, &en50221_app_props, ciap_link)
+    if (prop->ciap_resource_id == resource_id)
+      break;
+  if (prop == NULL)
+    tvherror(LS_EN50221, "%s: unknown resource id %08x",
+                         cil->cil_name, resource_id);
+  app = calloc(1, prop->ciap_struct_size);
+  if (app == NULL)
+    return -ENOMEM;
+  snprintf(buf, sizeof(buf), "%s-app%08x/%04X", cil->cil_name,
+                             resource_id, session_id);
+  app->cia_name = strdup(buf);
+  app->cia_slot = cil;
+  app->cia_resource_id = resource_id;
+  app->cia_session_id = session_id;
+  app->cia_prop = prop;
+  LIST_INSERT_HEAD(&cil->cil_apps, app, cia_link);
+  if (cia)
+    *cia = app;
+  tvhtrace(LS_EN50221, "%s: registered (%s)", app->cia_name,
+                       prop ? prop->ciap_name : "???");
+  return 0;
+}
+
+static void
+en50221_app_destroy(en50221_app_t *cia)
+{
+  en50221_app_prop_t *prop;
+
+  if (cia == NULL)
+    return;
+  prop = cia->cia_prop;
+  LIST_REMOVE(cia, cia_link);
+  if (prop && prop->ciap_destroy)
+    prop->ciap_destroy(cia);
+  tvhtrace(LS_EN50221, "%s: destroyed (%s)", cia->cia_name,
+                       prop ? prop->ciap_name : "???");
+  free(cia->cia_name);
+  free(cia);
+}
+
+int en50221_app_pdu_send
+  (en50221_app_t *app, uint32_t atag, const uint8_t *data, size_t datalen,
+   int reply_is_expected)
+{
+  en50221_slot_t *slot = app->cia_slot;
+  en50221_transport_t *cit = slot->cil_transport;
+  uint8_t *buf = alloca(datalen + 10), *p, tag;
+  size_t hlen, buflen, tlen;
+  int r;
+
+  buf[0] = CICAM_ST_SESSION_NUMBER;
+  buf[1] = 2;
+  buf[2] = app->cia_session_id >> 8;
+  buf[3] = app->cia_session_id & 0xff;
+  buf[4] = atag >> 16;
+  buf[5] = atag >> 8;
+  buf[6] = atag & 0xff;
+  if (datalen < 0x80) {
+    buf[7] = datalen & 0x7f;
+    hlen = 8;
+  } else if (datalen < 0x100) {
+    buf[7] = 0x82;
+    buf[8] = datalen & 0xff;
+    hlen = 9;
+  } else if (datalen < 0x10000) {
+    buf[7] = 0x83;
+    buf[8] = datalen >> 8;
+    buf[9] = datalen & 0xff;
+    hlen = 10;
+  } else {
+    tvherror(LS_EN50221, "%s: too much apdu data to write %zd tag 0x%06x",
+                         app->cia_name, datalen, atag);
+    return -EIO;
+  }
+  if (datalen > 0)
+    memcpy(buf + hlen, data, datalen);
+  buflen = datalen + hlen;
+  if (slot->cil_apdu_only)
+    return cit->cit_ops->cihw_apdu_write(cit->cit_ops_aux, slot,
+                                         buf + 4, buflen - 4);
+  p = buf;
+  while (buflen > 0) {
+    if (buflen > CICAM_TDPU_DATA_SIZE) {
+      tlen = CICAM_TDPU_DATA_SIZE;
+      tag = CICAM_T_DATA_MORE;
+    } else {
+      tlen = buflen;
+      tag = CICAM_T_DATA_LAST;
+    }
+    r = en50221_slot_pdu_send(slot, tag, p, tlen, reply_is_expected);
+    if (r < 0)
+      return r;
+    buflen -= tlen;
+    p += tlen;
+  }
+  return 0;
+}
+
+static int
+en50221_app_handle
+  (en50221_app_t *cia, uint8_t status, const uint8_t *data, size_t datalen)
+{
+  en50221_app_prop_t *prop = cia->cia_prop;
+  uint32_t atag;
+  const uint8_t *p;
+  size_t l;
+  int r;
+
+  if (prop && prop->ciap_handle) {
+    if (datalen < 4)
+      return -ENXIO;
+    r = en50221_extract_len(data + 3, datalen - 3, &p, &l, cia->cia_name, "apdu");
+    if (r < 0)
+      return -ENXIO;
+    atag = getatag(data, datalen);
+    if (atag < 8) {
+      tvherror(LS_EN50221, "%s: invalid atag received 0x%06x", cia->cia_name, atag);
+      return -ENXIO;
+    }
+    return prop->ciap_handle(cia, status, atag, p, l);
+  }
+  return 0;
+}
+
+static int
+en50221_app_open(en50221_app_t *cia)
+{
+  en50221_app_prop_t *prop = cia->cia_prop;
+  if (prop && prop->ciap_open)
+    return prop->ciap_open(cia);
+  return 0;
+}
+
+static int
+en50221_app_monitor(en50221_app_t *cia, int64_t clock)
+{
+  if (cia->cia_prop && cia->cia_prop->ciap_monitor)
+    return cia->cia_prop->ciap_monitor(cia, clock);
+  return 0;
+}
+
+/*
+ *
+ */
+
+int
+en50221_extract_len
+  (const uint8_t *data, size_t datalen, const uint8_t **ptr, size_t *len,
+   const char *prefix, const char *pdu_name)
+{
+  const uint8_t *p, *end;
+  uint32_t j;
+  size_t l;
+
+  l = data[0];
+  p = data + 1;
+  if (l < 0x80) {
+  } else {
+    j = l & 0x7f;
+    end = data + datalen;
+    if (j > 3) {
+      tvherror(LS_EN50221, "%s: invalid %s length 0x%02zx", prefix, pdu_name, l);
+      return -ENXIO;
+    }
+    for (l = 0; j > 0 && p < end; j--) {
+      l <<= 8;
+      l |= *p++;
+    }
+    if (j > 0 || p >= end || l + (p - data) > datalen) {
+      tvherror(LS_EN50221, "%s: invalid %s length 0x%06zx", prefix, pdu_name, l);
+      return -ENXIO;
+    }
+  }
+  *ptr = p;
+  *len = l;
+  return 0;
+}
diff --git a/src/input/mpegts/en50221/en50221.h b/src/input/mpegts/en50221/en50221.h
new file mode 100644 (file)
index 0000000..911e41e
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ *  Tvheadend - CI CAM (EN50221) generic interface
+ *  Copyright (C) 2017 Jaroslav Kysela
+ *
+ *  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/>.
+ */
+
+#ifndef __EN50221_H__
+#define __EN50221_H__
+
+#include "queue.h"
+#include "htsmsg.h"
+#include "sbuf.h"
+
+/* Session resource IDs */
+#define CICAM_RI_RESOURCE_MANAGER            0x00010041
+#define CICAM_RI_APPLICATION_INFORMATION     0x00020041
+#define CICAM_RI_CONDITIONAL_ACCESS_SUPPORT  0x00030041
+#define CICAM_RI_HOST_CONTROL                0x00200041
+#define CICAM_RI_DATE_TIME                   0x00240041
+#define CICAM_RI_MMI                         0x00400041
+
+#define CICAM_RI_DUMMY_CAPMT                 0x00ffffff
+
+/* Application tags */
+#define CICAM_AOT_NONE                       0x000000
+#define CICAM_AOT_PROFILE_ENQ                0x9F8010
+#define CICAM_AOT_PROFILE                    0x9F8011
+#define CICAM_AOT_PROFILE_CHANGE             0x9F8012
+#define CICAM_AOT_APPLICATION_INFO_ENQ       0x9F8020
+#define CICAM_AOT_APPLICATION_INFO           0x9F8021
+#define CICAM_AOT_ENTER_MENU                 0x9F8022
+#define CICAM_AOT_PCMCIA_DATA_RATE           0x9F8024
+#define CICAM_AOT_CA_INFO_ENQ                0x9F8030
+#define CICAM_AOT_CA_INFO                    0x9F8031
+#define CICAM_AOT_CA_PMT                     0x9F8032
+#define CICAM_AOT_CA_PMT_REPLY               0x9F8033
+#define CICAM_AOT_CA_UPDATE                  0x9F8034
+#define CICAM_AOT_TUNE                       0x9F8400
+#define CICAM_AOT_REPLACE                    0x9F8401
+#define CICAM_AOT_CLEAR_REPLACE              0x9F8402
+#define CICAM_AOT_ASK_RELEASE                0x9F8403
+#define CICAM_AOT_DATE_TIME_ENQ              0x9F8440
+#define CICAM_AOT_DATE_TIME                  0x9F8441
+#define CICAM_AOT_CLOSE_MMI                  0x9F8800
+#define CICAM_AOT_DISPLAY_CONTROL            0x9F8801
+#define CICAM_AOT_DISPLAY_REPLY              0x9F8802
+#define CICAM_AOT_TEXT_LAST                  0x9F8803
+#define CICAM_AOT_TEXT_MORE                  0x9F8804
+#define CICAM_AOT_KEYPAD_CONTROL             0x9F8805
+#define CICAM_AOT_KEYPRESS                   0x9F8806
+#define CICAM_AOT_ENQ                        0x9F8807
+#define CICAM_AOT_ANSW                       0x9F8808
+#define CICAM_AOT_MENU_LAST                  0x9F8809
+#define CICAM_AOT_MENU_MORE                  0x9F880A
+#define CICAM_AOT_MENU_ANSW                  0x9F880B
+#define CICAM_AOT_LIST_LAST                  0x9F880C
+#define CICAM_AOT_LIST_MORE                  0x9F880D
+#define CICAM_AOT_SUBTITLE_SEGMENT_LAST      0x9F880E
+#define CICAM_AOT_SUBTITLE_SEGMENT_MORE      0x9F880F
+#define CICAM_AOT_DISPLAY_MESSAGE            0x9F8810
+#define CICAM_AOT_SCENE_END_MARK             0x9F8811
+#define CICAM_AOT_SCENE_DONE                 0x9F8812
+#define CICAM_AOT_SCENE_CONTROL              0x9F8813
+#define CICAM_AOT_SUBTITLE_DOWNLOAD_LAST     0x9F8814
+#define CICAM_AOT_SUBTITLE_DOWNLOAD_MORE     0x9F8815
+#define CICAM_AOT_FLUSH_DOWNLOAD             0x9F8816
+#define CICAM_AOT_DOWNLOAD_REPLY             0x9F8817
+#define CICAM_AOT_COMMS_CMD                  0x9F8C00
+#define CICAM_AOT_CONNECTION_DESCRIPTOR      0x9F8C01
+#define CICAM_AOT_COMMS_REPLY                0x9F8C02
+#define CICAM_AOT_COMMS_SEND_LAST            0x9F8C03
+#define CICAM_AOT_COMMS_SEND_MORE            0x9F8C04
+#define CICAM_AOT_COMMS_RCV_LAST             0x9F8C05
+#define CICAM_AOT_COMMS_RCV_MORE             0x9F8C06
+
+typedef struct en50221_app_prop en50221_app_prop_t;
+typedef struct en50221_app en50221_app_t;
+typedef struct en50221_slot en50221_slot_t;
+typedef struct en50221_slot_wmsg en50221_slot_wmsg_t;
+typedef struct en50221_transport en50221_transport_t;
+typedef struct en50221_ops en50221_ops_t;
+
+struct en50221_app_prop {
+  LIST_ENTRY(en50221_app_prop) ciap_link;
+  const char *ciap_name;
+  uint32_t ciap_resource_id;
+  size_t ciap_struct_size;
+  void (*ciap_destroy)(void *self);
+  int (*ciap_open)(void *self);
+  int (*ciap_handle)(void *self, uint8_t status, uint32_t atag,
+                     const uint8_t *data, size_t datalen);
+  int (*ciap_monitor)(void *self, int64_t clock);
+};
+
+struct en50221_app {
+  LIST_ENTRY(en50221_app) cia_link;
+  en50221_slot_t *cia_slot;
+  char *cia_name;
+  uint32_t cia_resource_id;
+  uint16_t cia_session_id;
+  en50221_app_prop_t *cia_prop;
+};
+
+struct en50221_slot_wmsg {
+  TAILQ_ENTRY(en50221_slot_wmsg) link;
+  uint8_t tag;
+  uint8_t reply_is_expected;
+  size_t len;
+  uint8_t data[0];
+};
+
+struct en50221_slot {
+  LIST_ENTRY(en50221_slot) cil_link;
+  en50221_transport_t *cil_transport;
+  char *cil_name;
+  uint8_t cil_number;
+  uint8_t cil_tcnum;
+  uint8_t cil_enabled;
+  uint8_t cil_ready;
+  uint8_t cil_caid_list;
+  uint8_t cil_apdu_only;
+  uint8_t cil_initiate_connection;
+  uint8_t cil_closing;
+  uint16_t cil_last_session_number;
+  int64_t cil_monitor_read;
+  TAILQ_HEAD(,en50221_slot_wmsg) cil_write_queue;
+  LIST_HEAD(, en50221_app) cil_apps;
+};
+
+struct en50221_transport {
+  LIST_HEAD(, en50221_slot) cit_slots;
+  en50221_ops_t *cit_ops;
+  void *cit_ops_aux;
+  uint8_t cit_broken;
+  int cit_slot_count;
+  char *cit_name;
+  sbuf_t cit_rmsg;
+};
+
+struct en50221_ops {
+  /* hw interface */
+  int (*cihw_reset)(void *aux);
+  int (*cihw_cam_is_ready)(void *aux, en50221_slot_t *slot);
+  int (*cihw_pdu_write)(void *aux, en50221_slot_t *slot, uint8_t tcnum,
+                        const uint8_t *data, size_t datalen);
+  int (*cihw_apdu_write)(void *aux, en50221_slot_t *slot,
+                         const uint8_t *data, size_t datalen);
+  /* software level ops */
+  int (*cisw_appinfo)(void *aux, en50221_slot_t *slot, uint8_t ver,
+                      char *name, uint8_t type, uint16_t manufacturer, uint16_t code);
+  int (*cisw_pcmcia_data_rate)(void *aux, en50221_slot_t *slot, uint8_t *rate);
+  int (*cisw_caids)(void *aux, en50221_slot_t *slot, uint16_t *list, int listcount);
+  int (*cisw_ca_close)(void *aux, en50221_slot_t *slot);
+  int (*cisw_menu)(void *aux, en50221_slot_t *slot, htsmsg_t *menu);
+  int (*cisw_enquiry)(void *aux, en50221_slot_t *slot, htsmsg_t *enq);
+  int (*cisw_close)(void *aux, en50221_slot_t *slot, int delay);
+};
+
+/*
+ *
+ */
+int en50221_create_transport(en50221_ops_t *ops, void *ops_aux, int slots,
+                             const char *name, en50221_transport_t **cit);
+void en50221_transport_destroy(en50221_transport_t *cit);
+en50221_slot_t *en50221_transport_find_slot(en50221_transport_t *cit, uint8_t slotnum);
+int en50221_transport_reset(en50221_transport_t *cit);
+int en50221_transport_monitor(en50221_transport_t *cit, int64_t clock);
+int en50221_transport_read(en50221_transport_t *cit,
+                           uint8_t slotnum, uint8_t tcnum,
+                           const uint8_t *data, size_t datalen);
+
+/*
+ *
+ */
+en50221_app_t *
+en50221_slot_find_application(en50221_slot_t *cil,
+                              uint32_t resource_id, uint32_t mask);
+int en50221_slot_enable(en50221_slot_t *cil);
+int en50221_slot_disable(en50221_slot_t *cil);
+
+/*
+ *
+ */
+void en50221_register_app(en50221_app_prop_t *prop);
+int en50221_app_pdu_send(en50221_app_t *app, uint32_t atag,
+                         const uint8_t *data, size_t datalen,
+                         int reply_is_expected);
+void en50221_register_apps(void);
+
+#define CICAM_CALL_APP_CB(app, name, ...) ({ \
+  en50221_slot_t *__s = app->cia_slot; \
+  en50221_transport_t *__t = __s->cil_transport; \
+  int __r = __t->cit_ops ? \
+    __t->cit_ops->name(__t->cit_ops_aux, __s, ##__VA_ARGS__) : 0; \
+  __r; \
+})
+
+/*
+ *
+ */
+int en50221_extract_len
+  (const uint8_t *data, size_t datalen, const uint8_t **ptr, size_t *len,
+   const char *prefix, const char *pdu_name);
+
+/*
+ * random public functions
+ */
+int en50221_send_capmt
+  (en50221_slot_t *slot, const uint8_t *capmt, uint8_t capmtlen);
+int en50221_pcmcia_data_rate(en50221_slot_t *slot, uint8_t rate);
+int en50221_mmi_answer
+  (en50221_slot_t *slot, const uint8_t *data, size_t datalen);
+int en50221_mmi_close(en50221_slot_t *slot);
+
+#endif /* EN50221_H */
diff --git a/src/input/mpegts/en50221/en50221_apps.c b/src/input/mpegts/en50221/en50221_apps.c
new file mode 100644 (file)
index 0000000..613c3ac
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ *  Tvheadend - CI CAM (EN50221) generic interface
+ *  Copyright (C) 2017 Jaroslav Kysela
+ *
+ *  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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "tvhlog.h"
+#include "en50221.h"
+#include "input/mpegts/dvb.h"
+#include "descrambler/caid.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+#define BYTE4BE(w) \
+  ((w >> 24) & 0xff), ((w >> 16) & 0xff), ((w >> 8) & 0xff), (w & 0xff)
+
+/*
+ *
+ */
+
+static inline uint8_t int2bcd(uint32_t v)
+{
+  return ((v / 10) << 4) + (v % 10);
+}
+
+static inline uint32_t getatag(const uint8_t *p, size_t l)
+{
+  if (l < 3)
+    return CICAM_AOT_NONE + l;
+  return (((uint32_t)p[0]) << 16) |
+         (((uint32_t)p[1]) <<  8) |
+                     p[2];
+}
+
+/*
+ * Resource manager
+ */
+
+typedef struct en50221_app_resman {
+  en50221_app_t;
+  uint8_t cia_profile_sent;
+} en50221_app_resman_t;
+
+static int
+en50221_app_resman_open(void *self)
+{
+  en50221_app_t *app = self;
+  return en50221_app_pdu_send(app, CICAM_AOT_PROFILE_ENQ, NULL, 0, 1);
+}
+
+static int
+en50221_app_resman_handle
+  (void *self, uint8_t status, uint32_t atag, const uint8_t *data, size_t datalen)
+{
+  en50221_app_resman_t *app = self;
+  static const uint8_t resources[] = {
+    BYTE4BE(CICAM_RI_RESOURCE_MANAGER),
+    BYTE4BE(CICAM_RI_APPLICATION_INFORMATION),
+    BYTE4BE(CICAM_RI_CONDITIONAL_ACCESS_SUPPORT),
+    BYTE4BE(CICAM_RI_DATE_TIME),
+    BYTE4BE(CICAM_RI_MMI)
+  };
+  if (atag == CICAM_AOT_PROFILE_ENQ) {
+    tvhtrace(LS_EN50221, "%s: profile enq reply sent", app->cia_name);
+    return en50221_app_pdu_send((en50221_app_t *)app, CICAM_AOT_PROFILE,
+                                resources, sizeof(resources), 0);
+  } else if (atag == CICAM_AOT_PROFILE) {
+    if (!app->cia_profile_sent) {
+      app->cia_profile_sent = 1;
+      tvhtrace(LS_EN50221, "%s: profile change sent", app->cia_name);
+      return en50221_app_pdu_send((en50221_app_t *)app, CICAM_AOT_PROFILE_CHANGE,
+                                  NULL, 0, 0);
+    }
+  } else {
+    tvherror(LS_EN50221, "%s: unknown atag for resman 0x%06x", app->cia_name, atag);
+  }
+  return 0;
+}
+
+static en50221_app_prop_t en50221_app_resman = {
+  .ciap_name = "resman",
+  .ciap_resource_id = CICAM_RI_RESOURCE_MANAGER,
+  .ciap_struct_size = sizeof(en50221_app_resman_t),
+  .ciap_open = en50221_app_resman_open,
+  .ciap_handle = en50221_app_resman_handle,
+};
+
+/*
+ * Conditional Access
+ */
+
+#define CICAM_CAEI_POSSIBLE                  0x01
+#define CICAM_CAEI_POSSIBLE_COND_PURCHASE    0x02
+#define CICAM_CAEI_POSSIBLE_COND_TECHNICAL   0x03
+#define CICAM_CAEI_NOT_POSSIBLE_ENTITLEMENT  0x71
+#define CICAM_CAEI_NOT_POSSIBLE_TECHNICAL    0x73
+
+#define CICAM_CA_ENABLE_FLAG                 0x80
+
+#define CICAM_CA_ENABLE(x) \
+  (((x) & CICAM_CA_ENABLE_FLAG) ? (x) & ~CICAM_CA_ENABLE_FLAG : 0)
+
+typedef struct en50221_app_ca {
+  en50221_app_t;
+  uint16_t cia_caids[64];
+  uint8_t cia_replies_to_query;
+  uint8_t cia_ca_possible;
+} en50221_app_ca_t;
+
+static void
+en50221_app_ca_destroy
+  (void *self)
+{
+  en50221_app_ca_t *app = self;
+  CICAM_CALL_APP_CB(app, cisw_ca_close);
+}
+
+static int
+en50221_app_ca_open(void *self)
+{
+  en50221_app_t *app = self;
+  return en50221_app_pdu_send(app, CICAM_AOT_CA_INFO_ENQ, NULL, 0, 1);
+}
+
+static int
+en50221_app_ca_handle
+  (void *self, uint8_t status, uint32_t atag, const uint8_t *data, size_t datalen)
+{
+  en50221_app_ca_t *app = self;
+  const uint8_t *p;
+  char buf[256];
+  int count, i, j;
+  uint16_t caid, u16;
+  size_t c;
+
+  if (atag == CICAM_AOT_CA_INFO) {
+    count = 0;
+    for (; count < sizeof(app->cia_caids) - 1 && datalen > 1; count++, datalen -= 2)
+      app->cia_caids[count] = (data[0] << 8) | data[1];
+    if (datalen) {
+      app->cia_caids[0] = 0;
+      tvherror(LS_EN50221, "wrong length for conditional access info");
+      return -EINVAL;
+    }
+    app->cia_caids[count] = 0;
+    buf[0] = buf[1] = '\0';
+    for (i = 0; i < count; ) {
+      for (j = 0, c = 0; j < 4 && i < count; i++, j++) {
+        caid = app->cia_caids[0];
+        tvh_strlcatf(buf, sizeof(buf), c, " %04X (%s)", caid, caid2name(caid));
+      }
+      tvhinfo(LS_EN50221, "%s: CAM supported CAIDs: %s", app->cia_name, buf + 1);
+    }
+    app->cia_slot->cil_caid_list = count > 0;
+    CICAM_CALL_APP_CB(app, cisw_caids, app->cia_caids, count);
+  } else if (atag == CICAM_AOT_CA_UPDATE) {
+    /* nothing */
+  } else if (atag == CICAM_AOT_CA_PMT_REPLY) {
+    c = 0;
+    app->cia_ca_possible = 0;
+    tvh_strlcatf(buf, sizeof(buf), c, "%s: PMT REPLY", app->cia_name);
+    if (datalen > 1) {
+      app->cia_replies_to_query = 1;
+      u16 = (data[0] << 8) | data[1];
+      i = datalen - 2;
+      p = data + 2;
+      tvh_strlcatf(buf, sizeof(buf), c, " %04X", u16);
+      if (i > 0) {
+        tvh_strlcatf(buf, sizeof(buf), c, " %02X", *p);
+        p++;
+        if (--i > 0) {
+          if ((i % 3) == 0 && i > 1) {
+            /* The EN50221 standard defines that the next byte is supposed
+               to be the CA_enable value at programme level. However, there are
+               CAMs (for instance the AlphaCrypt with firmware <= 3.05) that
+               insert a two byte length field here.
+               This is a workaround to skip this length field: */
+            u16 = (p[0] << 8) | p[1];
+            if (u16 == i - 2) {
+              p += 2;
+              i -= 2;
+            }
+          }
+          tvh_strlcatf(buf, sizeof(buf), c, " %02X", *p);
+          p++; i--;
+          app->cia_ca_possible = CICAM_CA_ENABLE(*p) == CICAM_CAEI_POSSIBLE;
+          while (i > 2) {
+            u16 = (p[0] << 8) | p[1];
+            if (CICAM_CA_ENABLE(p[2]) != CICAM_CAEI_POSSIBLE)
+              app->cia_ca_possible = 1;
+            tvh_strlcatf(buf, sizeof(buf), c, " %d=%02X", u16, p[2]);
+          }
+        }
+      }
+    }
+  } else {
+    tvherror(LS_EN50221, "unknown atag for conditional access 0x%06x", atag);
+  }
+  return 0;
+}
+
+int en50221_send_capmt
+  (en50221_slot_t *slot, const uint8_t *capmt, uint8_t capmtlen)
+{
+  en50221_app_t *app;
+  app = en50221_slot_find_application(slot, CICAM_RI_CONDITIONAL_ACCESS_SUPPORT, ~0);
+  if (app)
+    return en50221_app_pdu_send(app, CICAM_AOT_CA_PMT, capmt, capmtlen, 0);
+  return -ENXIO;
+}
+
+static en50221_app_prop_t en50221_app_ca = {
+  .ciap_name = "ca",
+  .ciap_resource_id = CICAM_RI_CONDITIONAL_ACCESS_SUPPORT,
+  .ciap_struct_size = sizeof(en50221_app_ca_t),
+  .ciap_destroy = en50221_app_ca_destroy,
+  .ciap_open = en50221_app_ca_open,
+  .ciap_handle = en50221_app_ca_handle,
+};
+
+/*
+ * DateTime
+ */
+
+typedef struct en50221_app_datetime {
+  en50221_app_t;
+  int64_t cia_update_interval;
+  int64_t cia_last_clock;
+} en50221_app_datetime_t;
+
+static int
+en50221_app_datetime_send(en50221_app_datetime_t *app, int64_t clock)
+{
+  time_t t = time(NULL);
+  struct tm tm_gmt;
+  struct tm tm_loc;
+  uint8_t buf[7];
+  int r = 0;
+
+  if (gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc)) {
+    int GMTOFF = tm_loc.tm_gmtoff / 60;
+    int Y      = tm_gmt.tm_year;
+    int M      = tm_gmt.tm_mon + 1;
+    int D      = tm_gmt.tm_mday;
+    int L      = (M == 1 || M == 2) ? 1 : 0;
+    int MJD    = 14956 + D + (int)((Y - L) * 365.25)
+                       + (int)((M + 1 + L * 12) * 30.6001);
+    buf[0] = MJD >> 8;
+    buf[1] = MJD & 0xff;
+    buf[2] = int2bcd(tm_gmt.tm_hour);
+    buf[3] = int2bcd(tm_gmt.tm_min);
+    buf[4] = int2bcd(tm_gmt.tm_sec);
+    buf[5] = GMTOFF >> 8;
+    buf[6] = GMTOFF & 0xff;
+
+    r = en50221_app_pdu_send((en50221_app_t *)app, CICAM_AOT_DATE_TIME, buf, 7, 0);
+    if (r >= 0) {
+      tvhtrace(LS_EN50221, "%s: updated datetime was sent", app->cia_name);
+      app->cia_last_clock = clock;
+    }
+  }
+  return r;
+}
+
+static int
+en50221_app_datetime_handle
+  (void *self, uint8_t status, uint32_t atag, const uint8_t *data, size_t datalen)
+{
+  int interval;
+  en50221_app_datetime_t *app = self;
+  if (atag == CICAM_AOT_DATE_TIME_ENQ) {
+    interval = 0;
+    if (datalen == 1)
+      interval = data[0];
+    app->cia_update_interval = sec2mono(interval);
+    tvhtrace(LS_EN50221, "%s: CAM requested datetime with interval %d", app->cia_name, interval);
+    en50221_app_datetime_send(app, mclk());
+  } else {
+    tvherror(LS_EN50221, "%s: unknown atag for datetime 0x%06x", app->cia_name, atag);
+  }
+  return 0;
+}
+
+static int
+en50221_app_datetime_monitor(void *self, int64_t clock)
+{
+  en50221_app_datetime_t *app = self;
+  if (app->cia_update_interval > 0 &&
+      app->cia_last_clock + app->cia_update_interval < clock)
+    return en50221_app_datetime_send(app, clock);
+  return 0;
+}
+
+static en50221_app_prop_t en50221_app_datetime = {
+  .ciap_name = "datetime",
+  .ciap_resource_id = CICAM_RI_DATE_TIME,
+  .ciap_struct_size = sizeof(en50221_app_datetime_t),
+  .ciap_handle = en50221_app_datetime_handle,
+  .ciap_monitor = en50221_app_datetime_monitor,
+};
+
+/*
+ * Application information
+ */
+
+typedef struct en50221_app_appinfo {
+  en50221_app_t;
+  uint8_t cia_info_version;
+  uint8_t cia_pcmcia_data_rate;
+} en50221_app_appinfo_t;
+
+static int
+en50221_app_appinfo_open(void *self)
+{
+  en50221_app_t *app = self;
+  return en50221_app_pdu_send(app, CICAM_AOT_APPLICATION_INFO_ENQ, NULL, 0, 1);
+}
+
+static int
+en50221_app_appinfo_handle
+  (void *self, uint8_t status, uint32_t atag, const uint8_t *data, size_t datalen)
+{
+  en50221_app_appinfo_t *app = self;
+  const uint8_t *p;
+  char *s;
+  size_t l;
+  uint16_t type, manufacturer, code;
+  uint8_t rate;
+  int r;
+
+  if (atag >= CICAM_AOT_APPLICATION_INFO &&
+      atag <= CICAM_AOT_APPLICATION_INFO+2) {
+    if (datalen < 6) {
+      tvherror(LS_EN50221, "%s: unknown length %zd for appinfo 0x%06x", app->cia_name, datalen, atag);
+      return -ENXIO;
+    }
+    type = data[0];
+    manufacturer = (data[1] << 8) | data[2];
+    code = (data[3] << 8) | data[4];
+    r = en50221_extract_len(data + 5, datalen - 5, &p, &l, app->cia_name, "appinfo");
+    if (r < 0)
+      return -ENXIO;
+    s = alloca(l * 2 + 1);
+    if (dvb_get_string(s, l * 2 + 1, p, l, NULL, NULL) < 0)
+      return -ENXIO;
+    tvhinfo(LS_EN50221, "%s: CAM INFO: %s, %02X, %04X, %04X", app->cia_name, s, type, manufacturer, code);
+    app->cia_info_version = atag & 0x3f;
+    CICAM_CALL_APP_CB(app, cisw_appinfo, atag & 0x3f, s, type, manufacturer, code);
+    if (app->cia_info_version >= 3) /* at least CI+ v1.3 */
+      if (CICAM_CALL_APP_CB(app, cisw_pcmcia_data_rate, &rate) >= 0)
+        return en50221_app_pdu_send((en50221_app_t *)app, CICAM_AOT_PCMCIA_DATA_RATE, &rate, 1, 0);
+  } else {
+    tvherror(LS_EN50221, "%s: unknown atag for appinfo 0x%06x", app->cia_name, atag);
+  }
+  return 0;
+}
+
+static en50221_app_prop_t en50221_app_appinfo1 = {
+  .ciap_name = "appinfo1", /* EN50221 */
+  .ciap_resource_id = CICAM_RI_APPLICATION_INFORMATION,
+  .ciap_struct_size = sizeof(en50221_app_appinfo_t),
+  .ciap_open = en50221_app_appinfo_open,
+  .ciap_handle = en50221_app_appinfo_handle,
+};
+
+static en50221_app_prop_t en50221_app_appinfo2 = {
+  .ciap_name = "appinfo2", /* TS101699 */
+  .ciap_resource_id = CICAM_RI_APPLICATION_INFORMATION+1,
+  .ciap_struct_size = sizeof(en50221_app_appinfo_t),
+  .ciap_open = en50221_app_appinfo_open,
+  .ciap_handle = en50221_app_appinfo_handle,
+};
+
+static en50221_app_prop_t en50221_app_appinfo3 = {
+  .ciap_name = "appinfo3", /* CIPLUS13 */
+  .ciap_resource_id = CICAM_RI_APPLICATION_INFORMATION+2,
+  .ciap_struct_size = sizeof(en50221_app_appinfo_t),
+  .ciap_open = en50221_app_appinfo_open,
+  .ciap_handle = en50221_app_appinfo_handle,
+};
+
+static en50221_app_prop_t en50221_app_appinfo5 = {
+  .ciap_name = "appinfo5", /* Bluebook A173-2 */
+  .ciap_resource_id = CICAM_RI_APPLICATION_INFORMATION+4,
+  .ciap_struct_size = sizeof(en50221_app_appinfo_t),
+  .ciap_open = en50221_app_appinfo_open,
+  .ciap_handle = en50221_app_appinfo_handle,
+};
+
+int en50221_pcmcia_data_rate(en50221_slot_t *slot, uint8_t rate)
+{
+  en50221_app_t *app;
+  app = en50221_slot_find_application(slot, CICAM_RI_APPLICATION_INFORMATION, ~0x3f);
+  if (app && ((en50221_app_appinfo_t *)app)->cia_info_version >= 3)
+    return en50221_app_pdu_send((en50221_app_t *)app, CICAM_AOT_PCMCIA_DATA_RATE, &rate, 1, 0);
+  return 0;
+}
+
+/*
+ * MMI
+ */
+
+/* Display Control Commands */
+#define MMI_DCC_SET_MMI_MODE                              0x01
+#define MMI_DCC_DISPLAY_CHARACTER_TABLE_LIST              0x02
+#define MMI_DCC_INPUT_CHARACTER_TABLE_LIST                0x03
+#define MMI_DCC_OVERLAY_GRAPHICS_CHARACTERISTICS          0x04
+#define MMI_DCC_FULL_SCREEN_GRAPHICS_CHARACTERISTICS      0x05
+
+/* MMI Modes */
+#define MMI_MM_HIGH_LEVEL                                 0x01
+#define MMI_MM_LOW_LEVEL_OVERLAY_GRAPHICS                 0x02
+#define MMI_MM_LOW_LEVEL_FULL_SCREEN_GRAPHICS             0x03
+
+/* Display Reply IDs */
+#define MMI_DRI_MMI_MODE_ACK                              0x01
+#define MMI_DRI_LIST_DISPLAY_CHARACTER_TABLES             0x02
+#define MMI_DRI_LIST_INPUT_CHARACTER_TABLES               0x03
+#define MMI_DRI_LIST_GRAPHIC_OVERLAY_CHARACTERISTICS      0x04
+#define MMI_DRI_LIST_FULL_SCREEN_GRAPHIC_CHARACTERISTICS  0x05
+#define MMI_DRI_UNKNOWN_DISPLAY_CONTROL_CMD               0xF0
+#define MMI_DRI_UNKNOWN_MMI_MODE                          0xF1
+#define MMI_DRI_UNKNOWN_CHARACTER_TABLE                   0xF2
+
+/* Enquiry Flags */
+#define MMI_EF_BLIND   0x01
+
+/* Answer IDs */
+#define MMI_AI_CANCEL  0x00
+#define MMI_AI_ANSWER  0x01
+
+typedef struct en50221_app_mi {
+  en50221_app_t;
+  htsmsg_t *cia_menu;
+  htsmsg_t *cia_enquiry;
+} en50221_app_mmi_t;
+
+static void
+en50221_app_mmi_destroy
+  (void *self)
+{
+  en50221_app_mmi_t *app = self;
+  htsmsg_destroy(app->cia_menu);
+  app->cia_menu = NULL;
+  htsmsg_destroy(app->cia_enquiry);
+  app->cia_enquiry = NULL;
+}
+
+static int
+getmenutext
+  (en50221_app_mmi_t *app, const char *key, int list, const uint8_t **_p, size_t *_l)
+{
+  const uint8_t *p = *_p, *p2;
+  size_t l = *_l, l2;
+  uint32_t tag;
+  char *txt;
+  htsmsg_t *c;
+  int r;
+
+  if (l == 0)
+    return 0;
+  if (l < 3) {
+    tvherror(LS_EN50221, "%s: unexpected tag length", app->cia_name);
+    return 0;
+  }
+  tag = getatag(p, l);
+  p += 3; l -= 3;
+  if (tag != CICAM_AOT_TEXT_LAST) {
+    tvherror(LS_EN50221, "%s: unexpected getmenutext tag %06X", app->cia_name, tag);
+    return -ENXIO;
+  }
+  r = en50221_extract_len(p, l, &p2, &l2, app->cia_name, "menutext");
+  if (r < 0)
+    return r;
+  if (l2 > 0) {
+    txt = alloca(l2 * 2 + 1);
+    if (dvb_get_string(txt, l2 * 2 + 1, p2, l2, NULL, NULL) > 0 && txt[0]) {
+      if (list) {
+        c = htsmsg_get_list(app->cia_menu, key);
+        if (c == NULL) {
+          c = htsmsg_create_list();
+          htsmsg_add_msg(app->cia_menu, key, c);
+        }
+        tvhtrace(LS_EN50221, "%s:  add to %s '%s'", app->cia_name, key, txt);
+        htsmsg_add_str(c, NULL, txt);
+      } else {
+        tvhtrace(LS_EN50221, "%s:  %s '%s'", app->cia_name, key, txt);
+        htsmsg_add_str(app->cia_menu, key, txt);
+      }
+    }
+  }
+  *_p = p2 + l2;
+  *_l = l - l2 - (p2 - p);
+  return 0;
+}
+
+static int
+en50221_app_mmi_handle
+  (void *self, uint8_t status, uint32_t atag, const uint8_t *data, size_t datalen)
+{
+  en50221_app_mmi_t *app = self;
+  const uint8_t *p;
+  char *txt;
+  size_t l;
+  int r, id, delay;
+
+  if (atag == CICAM_AOT_DISPLAY_CONTROL) {
+    tvhtrace(LS_EN50221, "%s: display control", app->cia_name);
+    if (datalen > 0) {
+      if (data[0] == MMI_DCC_SET_MMI_MODE) {
+        static uint8_t buf[] = { MMI_DRI_MMI_MODE_ACK, MMI_MM_HIGH_LEVEL };
+        tvhtrace(LS_EN50221, "%s: display control sending reply", app->cia_name);
+        return en50221_app_pdu_send((en50221_app_t *)app, CICAM_AOT_DISPLAY_REPLY,
+                                    buf, ARRAY_SIZE(buf), 0);
+      }
+    }
+  } else if (atag == CICAM_AOT_LIST_LAST || atag == CICAM_AOT_MENU_LAST) {
+    tvhtrace(LS_EN50221, "%s: %s last", app->cia_name,
+             atag == CICAM_AOT_LIST_LAST ? "list" : "menu");
+    htsmsg_destroy(app->cia_menu);
+    app->cia_menu = NULL;
+    if (datalen > 0) {
+      app->cia_menu = htsmsg_create_map();
+      if (atag == CICAM_AOT_LIST_LAST)
+        htsmsg_add_bool(app->cia_menu, "selectable", 1);
+      p = data + 1; l = datalen - 1; /* ignore choice_nb */
+      r = getmenutext(app, "title", 0, &p, &l);
+      if (!r) r = getmenutext(app, "subtitle", 0, &p, &l);
+      if (!r) r = getmenutext(app, "bottom", 0, &p, &l);
+      while (!r && l > 0)
+        r = getmenutext(app, "choices", 1, &p, &l);
+      if (!r)
+        CICAM_CALL_APP_CB(app, cisw_menu, app->cia_menu);
+      return r;
+    }
+  } else if (atag == CICAM_AOT_ENQ) {
+    tvhtrace(LS_EN50221, "%s: enquiry", app->cia_name);
+    htsmsg_destroy(app->cia_enquiry);
+    app->cia_enquiry = htsmsg_create_map();
+    if (datalen > 1) {
+      if (data[0] & MMI_EF_BLIND)
+        htsmsg_add_bool(app->cia_enquiry, "blind", 1);
+      if (data[1]) /* expected length */
+        htsmsg_add_s64(app->cia_enquiry, "explen", data[1]);
+      txt = alloca(datalen * 2 + 1);
+      if (dvb_get_string(txt, datalen * 2 + 1, data + 2, datalen - 2,
+                         NULL, NULL) > 0 && txt[0])
+        htsmsg_add_str(app->cia_enquiry, "text", txt);
+      tvhtrace(LS_EN50221, "%s: enquiry data %02x %02x '%s'",
+                           app->cia_name, data[0], data[1], txt);
+      CICAM_CALL_APP_CB(app, cisw_enquiry, app->cia_enquiry);
+    }
+  } else if (atag == CICAM_AOT_CLOSE_MMI) {
+    id = -1;
+    delay = 0;
+    if (datalen > 0) {
+      id = data[0];
+      if (datalen > 1)
+        delay = data[1];
+    }
+    tvhtrace(LS_EN50221, "%s: close mmi %d %d", app->cia_name, id, delay);
+    CICAM_CALL_APP_CB(app, cisw_close, delay);
+  } else {
+    tvherror(LS_EN50221, "%s: unknown atag for mmi 0x%06x", app->cia_name, atag);
+  }
+  return 0;
+}
+
+static en50221_app_prop_t en50221_app_mmi = {
+  .ciap_name = "mmi",
+  .ciap_resource_id = CICAM_RI_MMI,
+  .ciap_struct_size = sizeof(en50221_app_mmi_t),
+  .ciap_destroy = en50221_app_mmi_destroy,
+  .ciap_handle = en50221_app_mmi_handle,
+};
+
+static int en50221_mmi_basic_io
+  (en50221_slot_t *slot, uint32_t ri, const uint8_t *data, size_t datalen)
+{
+  en50221_app_t *app;
+  app = en50221_slot_find_application(slot, CICAM_RI_MMI, ~0);
+  if (app)
+    return en50221_app_pdu_send((en50221_app_t *)app, ri, data, datalen, 0);
+  return 0;
+}
+
+int en50221_mmi_answer
+  (en50221_slot_t *slot, const uint8_t *data, size_t datalen)
+{
+  return en50221_mmi_basic_io(slot, CICAM_AOT_ANSW, data, datalen);
+}
+
+int en50221_mmi_close(en50221_slot_t *slot)
+{
+  return en50221_mmi_basic_io(slot, CICAM_AOT_CLOSE_MMI, NULL, 0);
+}
+
+/*
+ *
+ */
+
+void en50221_register_apps(void)
+{
+  en50221_register_app(&en50221_app_resman);
+  en50221_register_app(&en50221_app_ca);
+  en50221_register_app(&en50221_app_datetime);
+  en50221_register_app(&en50221_app_appinfo1);
+  en50221_register_app(&en50221_app_appinfo2);
+  en50221_register_app(&en50221_app_appinfo3);
+  en50221_register_app(&en50221_app_appinfo5);
+  en50221_register_app(&en50221_app_mmi);
+}
diff --git a/src/input/mpegts/en50221/en50221_capmt.c b/src/input/mpegts/en50221/en50221_capmt.c
new file mode 100644 (file)
index 0000000..8cadca9
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ *  Tvheadend - CI CAM (EN50221) generic interface
+ *  Copyright (C) 2017 Jaroslav Kysela
+ *
+ *  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 "en50221_capmt.h"
+#include "input.h"
+
+#define EN50221_CAPMT_CMD_OK       1
+#define EN50221_CAPMT_CMD_OK_MMI   2
+#define EN50221_CAPMT_CMD_QUERY    3
+#define EN50221_CAPMT_CMD_NOTSEL   4
+
+#define EN50221_CAPMT_LM_MORE      0
+#define EN50221_CAPMT_LM_FIRST     1
+#define EN50221_CAPMT_LM_LAST      2
+#define EN50221_CAPMT_LM_ONLY      3
+#define EN50221_CAPMT_LM_ADD       4
+#define EN50221_CAPMT_LM_UPDATE    5
+
+/*
+ *
+ */
+
+static inline uint16_t
+extract_2byte(const uint8_t *ptr)
+{
+  return (((uint16_t)ptr[0]) << 8) | ptr[1];
+}
+
+static inline uint16_t
+extract_pid(const uint8_t *ptr)
+{
+  return ((ptr[0] & 0x1f) << 8) | ptr[1];
+}
+
+static inline uint16_t
+extract_len12(const uint8_t *ptr)
+{
+  return ((ptr[0] & 0xf) << 8) | ptr[1];
+}
+
+static inline void
+put_2byte(uint8_t *dst, uint16_t val)
+{
+  dst[0] = val >> 8;
+  dst[1] = val;
+}
+
+static inline void
+put_len12(uint8_t *dst, uint16_t val)
+{
+  dst[0] &= 0xf0;
+  dst[0] |= (val >> 8) & 0x0f;
+  dst[1] = val;
+}
+
+/*
+ *
+ */
+
+static int en50221_capmt_check_pid
+  (mpegts_service_t *s, uint16_t pid )
+{
+  elementary_stream_t *st;
+
+  TAILQ_FOREACH(st, &s->s_filt_components, es_link)
+    if (st->es_pid == pid) return 1;
+  return 0;
+}
+
+static int en50221_capmt_check_caid
+  (mpegts_service_t *s, uint16_t pid, uint16_t caid)
+{
+  elementary_stream_t *st;
+  caid_t *c;
+
+  TAILQ_FOREACH(st, &s->s_filt_components, es_link) {
+    if (st->es_type != SCT_CA) continue;
+    LIST_FOREACH(c, &st->es_caids, link) {
+      if (!c->use) continue;
+      if (c->caid == caid && c->pid == pid) return 1;
+    }
+  }
+  return 0;
+}
+
+/*
+ *
+ */
+
+int en50221_capmt_build
+  (mpegts_service_t *s, int bcmd, uint16_t svcid,
+   const uint8_t *pmt, size_t pmtlen,
+   uint8_t **capmt, size_t *capmtlen)
+{
+  uint8_t *d, *x, *y, dtag, dlen, cmd_id;
+  const uint8_t *p;
+  uint16_t l, caid, pid;
+  size_t tl;
+  int first = 0;
+
+  if (pmtlen < 9)
+    return -EINVAL;
+  d = malloc(pmtlen * 2);
+  if (d == NULL)
+    return -ENOMEM;
+
+  cmd_id = EN50221_CAPMT_CMD_OK;
+  switch (bcmd) {
+  case EN50221_CAPMT_BUILD_ONLY:   d[0] = 3; break;
+  case EN50221_CAPMT_BUILD_ADD:    d[0] = 4; break;
+  case EN50221_CAPMT_BUILD_DELETE:
+    cmd_id = EN50221_CAPMT_CMD_NOTSEL;
+    /* fallthru */
+  case EN50221_CAPMT_BUILD_UPDATE: d[0] = 5; break;
+  default:
+    goto reterr;
+  }
+
+  put_2byte(d + 1, svcid);
+  d[3] = pmt[2]; /* version + current_next_indicator */
+  d[4] = 0xf0;
+
+  /* common descriptors */
+  x = d + 6;
+  l = extract_len12(pmt + 7);
+  p  = pmt + 9;
+  tl = pmtlen - 9 - l;
+  first = 1;
+  if (9 + l > pmtlen)
+    goto reterr;
+  while (l > 1) {
+    dtag = p[0];
+    dlen = p[1];
+    if (dtag == DVB_DESC_CA && dlen >= 4) {
+      caid = extract_2byte(p + 2);
+      pid  = extract_pid(p + 4);
+      if (en50221_capmt_check_caid(s, pid, caid)) {
+        if (first) {
+          *x++ = cmd_id;
+          first = 0;
+        }
+        memcpy(x, p, dlen + 2);
+        x += dlen + 2;
+      }
+    }
+    p  += 2 + dlen;
+    l  -= 2 + dlen;
+  }
+  if (l)
+    goto reterr;
+  d[4] = 0xf0;
+  put_len12(d + 4, x - d - 6); /* update length */
+
+  while (tl >= 5) {
+    y = x;
+    l = extract_len12(p + 3);
+    if (l + 5 > tl)
+      goto reterr;
+    pid = extract_pid(p + 1);
+    memcpy(x, p, 3); /* stream type, PID */
+    p  += 5;
+    tl -= 5;
+    x  += 5;
+    if (en50221_capmt_check_pid(s, pid)) {
+      first = 1;
+      while (l > 1) {
+        dtag = p[0];
+        dlen = p[1];
+        if (dtag == DVB_DESC_CA && dlen >= 4) {
+          caid = extract_2byte(p + 2);
+          pid  = extract_pid(p + 4);
+          if (en50221_capmt_check_caid(s, pid, caid)) {
+            if (first) {
+              *x++ = cmd_id;
+              first = 0;
+            }
+            memcpy(x, p, dlen + 2);
+            x += dlen + 2;
+          }
+        }
+        p  += 2 + dlen;
+        l  -= 2 + dlen;
+        tl -= 2 + dlen;
+      }
+      if (l)
+        return -EINVAL;
+    } else {
+      p  += l;
+      tl -= l;
+    }
+    y[3] = 0xf0;
+    put_len12(y + 3, x - y - 5);
+  }
+
+  *capmt = d;
+  *capmtlen = x - d;
+  return 0;
+
+reterr:
+  free(d);
+  return -EINVAL;
+}
+
+/*
+ * rewrite capmt cmd_id - set it to 'query'
+ */
+
+int en50221_capmt_build_query
+  (const uint8_t *capmt, size_t capmtlen,
+   uint8_t **dst, size_t *dstlen)
+{
+  uint8_t *d, *x;
+  uint16_t l;
+  size_t tl;
+  const uint8_t *p, *n;
+
+  if (capmtlen < 6)
+    return -EINVAL;
+  l = extract_len12(capmt + 4);
+  if (l + 6 > capmtlen)
+    return -EINVAL;
+
+  d = malloc(capmtlen);
+  memcpy(d, capmt, capmtlen);
+  
+  p  = capmt + 6;
+  n  = p + l;
+  tl = capmtlen - l - 6;
+
+  if (l > 0) {
+    x = d + (p - capmt);
+    x[0] = EN50221_CAPMT_CMD_QUERY;
+  }
+
+  while (tl >= 5) {
+    l = extract_len12(n + 3);
+    if (l + 5 > tl)
+      return -EINVAL;
+    if (l > 0) {
+      if (l < 7)
+        goto reterr;
+      x = d + (n - capmt);
+      x[5] = EN50221_CAPMT_CMD_QUERY;
+    }
+    n  += 5 + l;
+    tl -= 5 + l;
+  }
+  if (tl)
+    return -EINVAL;
+  *dst = d;
+  *dstlen = capmtlen;
+  return 0;
+
+reterr:
+  free(d);
+  return -EINVAL;
+}
+
+/*
+ *
+ */
+
+void en50221_capmt_dump
+  (int subsys, const char *prefix, const uint8_t *capmt, size_t capmtlen)
+{
+  uint16_t l, caid, pid;
+  uint8_t dtag, dlen, stype;
+  const uint8_t *p;
+
+  if (capmtlen < 6) {
+    tvhtrace(subsys, "%s: CAPMT short length %zd", prefix, capmtlen);
+    return;
+  }
+  tvhtrace(subsys, "%s: CAPMT list_management %02x sid %04x ver %02x",
+                   prefix, capmt[0], ((int)capmt[1]) << 8 | capmt[2], capmt[3]);
+  l = extract_len12(capmt + 4);
+  p = capmt + 6;
+  capmt = p + l;
+  capmtlen -= l + 6;
+  if (l > 0) {
+    tvhtrace(subsys, "%s: CAPMT cmd_id %02x", prefix, p[0]);
+    p++;
+    l--;
+    while (l > 0) {
+      if (l < 6) {
+        tvhtrace(subsys, "%s:   CAPMT wrong CA descriptor length %d", prefix, l);
+        break;
+      }
+      dtag = p[0];
+      dlen = p[1];
+      if (dtag != DVB_DESC_CA) {
+        tvhtrace(subsys, "%s:   CAPMT ES wrong dtag %02x", prefix, dtag);
+      } else if (dlen < 4) {
+        tvhtrace(subsys, "%s:   CAPMT wrong CA descriptor length %d (2)", prefix, l);
+      } else {
+        caid = extract_2byte(p + 2);
+        pid  = extract_pid(p + 3);
+        tvhtrace(subsys, "%s:   CAPMT CA descriptor caid %04X pid %04x length %d", prefix, caid, pid, dlen);
+      }
+      p += dlen + 2;
+      l -= dlen + 2;
+    }
+  }
+  while (capmtlen >= 5) {
+    stype = capmt[0];
+    pid   = extract_pid(capmt + 1);
+    tvhtrace(subsys, "%s: CAPMT ES stype %02x pid %04x", prefix, stype, pid);
+    l     = extract_len12(capmt + 3);
+    p     = capmt + 5;
+    capmt += l + 5;
+    capmtlen -= l + 5;
+    if (l > 0) {
+      tvhtrace(subsys, "%s:   CAPMT ES cmd_id %02x", prefix, p[0]);
+      p++;
+      l--;
+      while (l > 2) {
+        dtag = p[0];
+        dlen = p[1];
+        if (dlen + 2 > l) {
+          tvhtrace(subsys, "%s:     CAPMT ES wrong tag length (%d)", prefix, dlen);
+        } else if (dtag != DVB_DESC_CA) {
+          tvhtrace(subsys, "%s:     CAPMT ES wrong dtag %02x", prefix, dtag);
+        } else if (dlen < 4) {
+          tvhtrace(subsys, "%s:     CAPMT ES wrong CA descriptor length %d (2)", prefix, l);
+        } else {
+          caid = extract_2byte(p + 2);
+          pid  = extract_pid(p + 3);
+          tvhtrace(subsys, "%s:     CAPMT ES CA descriptor caid %04X pid %04x length %d", prefix, caid, pid, dlen);
+        }
+        p += dlen + 2;
+        l -= dlen + 2;
+      }
+      if (l > 0)
+        tvhtrace(subsys, "%s:   CAPMT ES wrong length (%d)", prefix, l);
+    }
+  }
+  if (capmtlen)
+    tvhtrace(subsys, "%s: CAPMT wrong main length (%zd)", prefix, capmtlen);
+}
diff --git a/src/input/mpegts/en50221/en50221_capmt.h b/src/input/mpegts/en50221/en50221_capmt.h
new file mode 100644 (file)
index 0000000..1465799
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *  Tvheadend - CI CAM (EN50221) CAPMT interface
+ *  Copyright (C) 2017 Jaroslav Kysela
+ *
+ *  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/>.
+ */
+
+#ifndef __EN50221_CAPMT_H__
+#define __EN50221_CAPMT_H__
+
+#include "service.h"
+
+struct mpegts_service;
+
+#define EN50221_CAPMT_BUILD_DELETE       0
+#define EN50221_CAPMT_BUILD_ONLY         1
+#define EN50221_CAPMT_BUILD_ADD          2
+#define EN50221_CAPMT_BUILD_UPDATE       4
+
+int en50221_capmt_build
+  (struct mpegts_service *s,
+   int bcmd, uint16_t svcid, const uint8_t *pmt, size_t pmtlen,
+   uint8_t **capmt, size_t *capmtlen);
+
+int en50221_capmt_build_query(const uint8_t *capmt, size_t capmtlen,
+                              uint8_t **dst, size_t *dstlen);
+
+void en50221_capmt_dump
+  (int subsys, const char *prefix, const uint8_t *capmt, size_t capmtlen);
+
+#endif /* EN50221_CAPMT_H */
index 37f57d0935cfd193de9cf502075232a4c484bc56..91edf07723b9a0e181e8c1595214b978f88e0c02 100644 (file)
@@ -56,7 +56,7 @@ linuxdvb_adapter_class_save ( idnode_t *in, char *filename, size_t fsize )
   htsmsg_t *m, *l;
   linuxdvb_frontend_t *lfe;
 #if ENABLE_LINUXDVB_CA
-  linuxdvb_ca_t *lca;
+  linuxdvb_transport_t *lcat;
 #endif
   char ubuf[UUID_HEX_SIZE];
 
@@ -73,8 +73,8 @@ linuxdvb_adapter_class_save ( idnode_t *in, char *filename, size_t fsize )
     /* CAs */
   #if ENABLE_LINUXDVB_CA
     l = htsmsg_create_map();
-    LIST_FOREACH(lca, &la->la_ca_devices, lca_link)
-      linuxdvb_ca_save(lca, l);
+    LIST_FOREACH(lcat, &la->la_ca_transports, lcat_link)
+      linuxdvb_transport_save(lcat, l);
     htsmsg_add_msg(m, "ca_devices", l);
   #endif
 
@@ -90,6 +90,7 @@ linuxdvb_adapter_class_get_childs ( idnode_t *in )
 {
   linuxdvb_frontend_t *lfe;
 #if ENABLE_LINUXDVB_CA
+  linuxdvb_transport_t *lcat;
   linuxdvb_ca_t *lca;
 #endif
   linuxdvb_adapter_t *la = (linuxdvb_adapter_t*)in;
@@ -97,8 +98,9 @@ linuxdvb_adapter_class_get_childs ( idnode_t *in )
   LIST_FOREACH(lfe, &la->la_frontends, lfe_link)
     idnode_set_add(is, &lfe->ti_id, NULL, NULL);
 #if ENABLE_LINUXDVB_CA
-  LIST_FOREACH(lca, &la->la_ca_devices, lca_link)
-    idnode_set_add(is, &lca->lca_id, NULL, NULL);
+  LIST_FOREACH(lcat, &la->la_ca_transports, lcat_link)
+    LIST_FOREACH(lca, &lcat->lcat_slots, lca_link)
+      idnode_set_add(is, &lca->lca_id, NULL, NULL);
 #endif
   return is;
 }
@@ -116,18 +118,21 @@ linuxdvb_adapter_class_active_get ( void *obj )
 {
   static int active;
 #if ENABLE_LINUXDVB_CA
+  linuxdvb_transport_t *lcat;
   linuxdvb_ca_t *lca;
 #endif
   linuxdvb_adapter_t *la = (linuxdvb_adapter_t*)obj;
   active = la->la_is_enabled(la);
 #if ENABLE_LINUXDVB_CA
   if (!active) {
-    LIST_FOREACH(lca, &la->la_ca_devices, lca_link)
-      if (lca->lca_enabled) {
-        active = 1;
-        break;
-      }
+    LIST_FOREACH(lcat, &la->la_ca_transports, lcat_link)
+      LIST_FOREACH(lca, &lcat->lcat_slots, lca_link)
+        if (lca->lca_enabled) {
+          active = 1;
+          goto caok;
+        }
   }
+caok:
 #endif
   return &active;
 }
@@ -346,6 +351,7 @@ linuxdvb_adapter_add ( const char *path )
   int a, i, j, r, fd;
   char fe_path[512], dmx_path[512], dvr_path[512], name[132];
 #if ENABLE_LINUXDVB_CA
+  linuxdvb_transport_t *lcat;
   char ca_path[512];
   htsmsg_t *caconf = NULL;
   const char *ci_found = NULL;
@@ -356,6 +362,7 @@ linuxdvb_adapter_add ( const char *path )
 #endif
   linuxdvb_adapter_t *la = NULL;
   struct dvb_frontend_info dfi;
+  ca_caps_t cac;
   htsmsg_t *conf = NULL, *feconf = NULL;
   int save = 0;
   dvb_fe_type_t type;
@@ -523,12 +530,23 @@ linuxdvb_adapter_add ( const char *path )
       tvherror(LS_LINUXDVB, "unable to open %s", ca_path);
       continue;
     }
+    memset(&cac, 0, sizeof(cac));
     r = ioctl(fd, CA_RESET, NULL);
+    if (r == 0)
+      r = ioctl(fd, CA_GET_CAP, &cac);
     close(fd);
     if(r) {
       tvherror(LS_LINUXDVB, "unable to query %s", ca_path);
       continue;
     }
+    if(cac.slot_num == 0) {
+      tvherror(LS_LINUXDVB, "CAM %s has no slots", ca_path);
+      continue;
+    }
+    if((cac.slot_type & (CA_CI_LINK|CA_CI)) == 0) {
+      tvherror(LS_LINUXDVB, "unsupported CAM type %08x", cac.slot_type);
+      continue;
+    }
 
 #if ENABLE_DDCI
     /* check for DD CI only, if no frontend was found (stand alone mode) */
@@ -565,14 +583,18 @@ linuxdvb_adapter_add ( const char *path )
       la = linuxdvb_adapter_new(path, a, ca_path, name, &conf, &save);
       if (la == NULL) {
         tvherror(LS_LINUXDVB, "failed to create %s", path);
-        return; // Note: save to return here as global_lock is held
+        continue;
       }
     }
 
     if (conf)
       caconf = htsmsg_get_map(conf, "ca_devices");
 
-    linuxdvb_ca_create(caconf, la, i, ca_path, ci_found);
+    lcat = linuxdvb_transport_create(la, cac.slot_num, ca_path, ci_found);
+    if (lcat) {
+      for (j = 0; j < cac.slot_num; j++)
+        linuxdvb_ca_create(caconf, lcat, j);
+    }
     pthread_mutex_unlock(&global_lock);
   }
 #endif /* ENABLE_LINUXDVB_CA */
@@ -620,7 +642,10 @@ static void
 linuxdvb_adapter_del ( const char *path )
 {
   int a;
-  linuxdvb_frontend_t *lfe, *next;
+  linuxdvb_frontend_t *lfe, *lfe_next;
+#if ENABLE_LINUXDVB_CA
+  linuxdvb_transport_t *lcat, *lcat_next;
+#endif
   linuxdvb_adapter_t *la = NULL;
   tvh_hardware_t *th;
 
@@ -636,10 +661,18 @@ linuxdvb_adapter_del ( const char *path )
     idnode_save_check(&la->th_id, 0);
   
     /* Delete the frontends */
-    for (lfe = LIST_FIRST(&la->la_frontends); lfe != NULL; lfe = next) {
-      next = LIST_NEXT(lfe, lfe_link);
-      linuxdvb_frontend_delete(lfe);
+    for (lfe = LIST_FIRST(&la->la_frontends); lfe != NULL; lfe = lfe_next) {
+      lfe_next = LIST_NEXT(lfe, lfe_link);
+      linuxdvb_frontend_destroy(lfe);
+    }
+    
+#if ENABLE_LINUXDVB_CA
+    /* Delete the CA devices */
+    for (lcat = LIST_FIRST(&la->la_ca_transports); lcat != NULL; lcat = lcat_next) {
+      lcat_next = LIST_NEXT(lcat, lcat_link);
+      linuxdvb_transport_destroy(lcat);
     }
+#endif
 
     /* Free memory */
     free(la->la_rootpath);
@@ -762,6 +795,7 @@ linuxdvb_adapter_init ( void )
 
 #if ENABLE_LINUXDVB_CA
   idclass_register(&linuxdvb_ca_class);
+  linuxdvb_ca_init();
 #endif
 
   /* Install monitor on /dev */
@@ -792,5 +826,8 @@ linuxdvb_adapter_done ( void )
       linuxdvb_adapter_del(la->la_rootpath);
     }
   }
+#if ENABLE_LINUXDVB_CA
+  linuxdvb_ca_done();
+#endif
   pthread_mutex_unlock(&global_lock);
 }
index 41802342647c8dd4ea42383842b690de1087e2a6..cf7b3e9d56a0a99c1121f6aa80a1f00531acf6a6 100644 (file)
@@ -1,7 +1,9 @@
- /*
+/*
  *  Tvheadend - Linux DVB CA
  *
  *  Copyright (C) 2015 Damjan Marion
+ *  Copyright (C) 2017 Jaroslav Kysela
+ *
  *
  *  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
@@ -20,6 +22,8 @@
 #include "tvheadend.h"
 #include "linuxdvb_private.h"
 #include "notify.h"
+#include "tvhpoll.h"
+#include "htsmsg_json.h"
 
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <linux/dvb/frontend.h>
 #include <linux/dvb/ca.h>
 
+#include "../en50221/en50221_capmt.h"
 #include "descrambler/caid.h"
 #include "descrambler/dvbcam.h"
 
-static void linuxdvb_ca_monitor ( void *aux );
+#define TRANSPORT_RECOVERY 4 /* in seconds */
+
+static pthread_mutex_t linuxdvb_ca_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t linuxdvb_capmt_mutex = PTHREAD_MUTEX_INITIALIZER;
+static th_pipe_t linuxdvb_ca_pipe;
+static pthread_t linuxdvb_ca_threadid;
+static mtimer_t linuxdvb_ca_thread_join_timer;
+static LIST_HEAD(, linuxdvb_transport) linuxdvb_all_transports;
+static LIST_HEAD(, linuxdvb_ca) linuxdvb_all_cas;
+
+static int linuxdvb_transport_refresh(linuxdvb_transport_t *transport);
+
+/*
+ *
+ */
 
 typedef enum {
-  CA_SLOT_STATE_EMPTY = 0,
+  CA_SLOT_STATE_DISABLED = 0,
+  CA_SLOT_STATE_EMPTY,
   CA_SLOT_STATE_MODULE_PRESENT,
-  CA_SLOT_STATE_MODULE_READY
+  CA_SLOT_STATE_MODULE_INIT,
+  CA_SLOT_STATE_MODULE_CONNECTED,
+  CA_SLOT_STATE_MODULE_READY,
 } ca_slot_state_t;
 
-const static char *
+static const char *
 ca_slot_state2str(ca_slot_state_t v)
 {
   switch(v) {
-    case CA_SLOT_STATE_EMPTY:           return "slot empty";
-    case CA_SLOT_STATE_MODULE_PRESENT:  return "module present";
-    case CA_SLOT_STATE_MODULE_READY:    return "module ready";
+    case CA_SLOT_STATE_DISABLED:         return N_("slot disabled");
+    case CA_SLOT_STATE_EMPTY:            return N_("slot empty");
+    case CA_SLOT_STATE_MODULE_PRESENT:   return N_("module present");
+    case CA_SLOT_STATE_MODULE_INIT:      return N_("module init");
+    case CA_SLOT_STATE_MODULE_CONNECTED: return N_("module connected");
+    case CA_SLOT_STATE_MODULE_READY:     return N_("module ready");
   };
-  return "UNKNOWN";
-}
-
-const static char *
-ca_pmt_list_mgmt2str(uint8_t v)
-{
-  switch(v) {
-    case CA_LIST_MANAGEMENT_MORE:   return "more";
-    case CA_LIST_MANAGEMENT_FIRST:  return "first";
-    case CA_LIST_MANAGEMENT_LAST:   return "last";
-    case CA_LIST_MANAGEMENT_ONLY:   return "only";
-    case CA_LIST_MANAGEMENT_ADD:    return "add";
-    case CA_LIST_MANAGEMENT_UPDATE: return "update";
-  }
-  return "UNKNOWN";
+  return "???";
 }
 
-const static char *
-ca_pmt_cmd_id2str(uint8_t v)
-{
-  switch(v) {
-    case CA_PMT_CMD_ID_OK_DESCRAMBLING: return "ok_descrambling";
-    case CA_PMT_CMD_ID_OK_MMI:          return "ok_mmi";
-    case CA_PMT_CMD_ID_QUERY:           return "query";
-    case CA_PMT_CMD_ID_NOT_SELECTED:    return "not_selected";
-  }
-  return "UNKNOWN";
-}
-
-struct linuxdvb_ca_capmt {
-  TAILQ_ENTRY(linuxdvb_ca_capmt)  lcc_link;
+typedef enum {
+  CA_WRITE_CMD_SLOT_DISABLE = 0,
+  CA_WRITE_CMD_CAPMT,
+  CA_WRITE_CMD_CAPMT_QUERY,
+  CA_WRITE_CMD_PCMCIA_RATE,
+} ca_write_cmd_t;
+
+struct linuxdvb_ca_write {
+  TAILQ_ENTRY(linuxdvb_ca_write) lcw_link;
+  int      cmd;
   int      len;
-  uint8_t *data;
-  uint8_t  slot;
-  uint8_t  list_mgmt;
-  uint8_t  cmd_id;
+  uint8_t  data[0];
 };
 
 /*
- *  ts101699 and CI+ 1.3 definitions
+ * CA thread routines
  */
-#define TS101699_APP_AI_RESOURCEID  MKRID(2, 1, 2)
-#define CIPLUS13_APP_AI_RESOURCEID  MKRID(2, 1, 3)
 
-typedef enum {
-  CIPLUS13_DATA_RATE_72_MBPS = 0,
-  CIPLUS13_DATA_RATE_96_MBPS = 1,
-} ciplus13_data_rate_t;
+static int
+linuxdvb_ca_slot_change_state ( linuxdvb_ca_t *lca, int state )
+{
+  if (lca->lca_state != state) {
+    tvhnotice(LS_LINUXDVB, "%s: CAM slot %u status changed to %s",
+                           lca->lca_name, lca->lca_slotnum,
+                           ca_slot_state2str(state));
+    idnode_lnotify_title_changed(&lca->lca_id);
+    lca->lca_state = state;
+    return 1;
+  }
+  return 0;
+}
 
 static int
-ciplus13_app_ai_data_rate_info(linuxdvb_ca_t *lca, ciplus13_data_rate_t rate)
+linuxdvb_ca_slot_info( int fd, linuxdvb_transport_t *lcat, linuxdvb_ca_t *lca )
 {
-  uint8_t data[] = {0x9f, 0x80, 0x24, 0x01, (uint8_t) rate};
+  ca_slot_info_t csi;
+  en50221_slot_t *slot;
+  int state;
 
-  /* only version 3 (CI+ 1.3) supports data_rate_info */
-  if (lca->lca_ai_version != 3)
-    return 0;
+  memset(&csi, 0, sizeof(csi));
+  csi.num = lca->lca_slotnum;
 
-  tvhinfo(LS_EN50221, "%s: setting CI+ CAM data rate to %s Mbps", lca->lca_name, rate ? "96":"72");
+  if ((ioctl(fd, CA_GET_SLOT_INFO, &csi)) != 0) {
+    tvherror(LS_LINUXDVB, "%s: failed to get CAM slot %u info [e=%s]",
+                          lca->lca_name, csi.num, strerror(errno));
+    return -EIO;
+  }
+  if (csi.type & CA_CI_PHYS) {
+    lca->lca_phys_layer = 1;
+  } else if (csi.type & CA_CI_LINK) {
+    lca->lca_phys_layer = 0;
+  } else {
+    tvherror(LS_LINUXDVB, "%s: unable to communicated with slot type %08x",
+                          lca->lca_name, csi.type);
+    return -EIO;
+  }
+  if (csi.flags & CA_CI_MODULE_READY) {
+    if (lcat->lcat_fatal_time + sec2mono(TRANSPORT_RECOVERY) < mclk()) {
+      state = CA_SLOT_STATE_MODULE_INIT;
+      slot = en50221_transport_find_slot(lcat->lcat_transport, lca->lca_slotnum);
+      if (slot) {
+        if (slot->cil_ready)
+          state = CA_SLOT_STATE_MODULE_CONNECTED;
+        if (slot->cil_caid_list)
+          state = CA_SLOT_STATE_MODULE_READY;
+      }
+    } else {
+      state = CA_SLOT_STATE_MODULE_PRESENT;
+    }
+  } else if (csi.flags & CA_CI_MODULE_PRESENT)
+    state = CA_SLOT_STATE_MODULE_PRESENT;
+  else
+    state = CA_SLOT_STATE_EMPTY;
+
+  linuxdvb_ca_slot_change_state(lca, state);
 
-  return en50221_sl_send_data(lca->lca_sl, lca->lca_ai_session_number, data, sizeof(data));
+  return state >= CA_SLOT_STATE_MODULE_INIT;
 }
 
-static void
-linuxdvb_ca_class_changed ( idnode_t *in )
+static int
+linuxdvb_ca_open_fd( linuxdvb_transport_t *lcat )
 {
-  linuxdvb_adapter_t *la = ((linuxdvb_ca_t*)in)->lca_adapter;
-  linuxdvb_adapter_changed(la);
+  linuxdvb_ca_t *lca;
+  int fd, r;
+
+  fd = lcat->lcat_ca_fd;
+  if (fd > 0)
+    return 0;
+  if (lcat->lcat_fatal)
+    return -1;
+  fd = tvh_open(lcat->lcat_ca_path, O_RDWR | O_NONBLOCK, 0);
+  tvhtrace(LS_EN50221, "%s: opening %s (fd %d)",
+           lcat->lcat_name, lcat->lcat_ca_path, fd);
+  if (fd >= 0) {
+    LIST_FOREACH(lca, &lcat->lcat_slots, lca_link) {
+      lca->lca_capmt_blocked = 0;
+      if ((r = linuxdvb_ca_slot_info(fd, lcat, lca)) < 0) {
+        close(fd);
+        atomic_set(&lca->lca_enabled, 0);
+        linuxdvb_ca_slot_change_state(lca, CA_SLOT_STATE_DISABLED);
+        lcat->lcat_ca_fd = -1;
+        return r;
+      }
+    }
+    lcat->lcat_ca_fd = fd;
+#if ENABLE_DDCI
+    if (lcat->lddci)
+      linuxdvb_ddci_open(lcat->lddci);
+#endif
+  } else {
+    lcat->lcat_fatal = 1;
+    return -1;
+  }
+  return 0;
 }
 
 static void
-linuxdvb_ca_class_enabled_notify ( void *p, const char *lang )
+linuxdvb_ca_close_fd( linuxdvb_transport_t *lcat, int reset )
 {
-  linuxdvb_ca_t *lca = (linuxdvb_ca_t *) p;
+  const int fd = lcat->lcat_ca_fd;
+  linuxdvb_ca_t *lca;
+  linuxdvb_ca_write_t *lcw;
 
-  if (lca->lca_enabled) {
-    if (lca->lca_ca_fd < 0) {
-      lca->lca_ca_fd = tvh_open(lca->lca_ca_path, O_RDWR | O_NONBLOCK, 0);
-      tvhtrace(LS_LINUXDVB, "%s: opening %s (fd %d)",
-               lca->lca_name, lca->lca_ca_path, lca->lca_ca_fd);
-      if (lca->lca_ca_fd >= 0) {
+  pthread_mutex_lock(&linuxdvb_capmt_mutex);
+  LIST_FOREACH(lca, &lcat->lcat_slots, lca_link) {
+    while ((lcw = TAILQ_FIRST(&lca->lca_write_queue)) != NULL) {
+      TAILQ_REMOVE(&lca->lca_write_queue, lcw, lcw_link);
+      free(lcw);
+    }
+  }
+  pthread_mutex_unlock(&linuxdvb_capmt_mutex);
+
+  if (fd < 0)
+    return;
+  if (ioctl(fd, CA_RESET, NULL))
+    tvherror(LS_EN50221, "%s: unable to reset %s",
+             lcat->lcat_name, lcat->lcat_ca_path);
 #if ENABLE_DDCI
-        if (lca->lddci)
-          linuxdvb_ddci_open(lca->lddci);
+  if (lcat->lddci)
+    linuxdvb_ddci_close(lcat->lddci);
 #endif
-        mtimer_arm_rel(&lca->lca_monitor_timer, linuxdvb_ca_monitor, lca, ms2mono(250));
-      }
+  lcat->lcat_ca_fd = -1;
+  tvhtrace(LS_EN50221, "%s: close %s (fd %d)",
+           lcat->lcat_name, lcat->lcat_ca_path, fd);
+  close(fd);
+  pthread_mutex_lock(&linuxdvb_capmt_mutex);
+  LIST_FOREACH(lca, &lcat->lcat_slots, lca_link) {
+    while ((lcw = TAILQ_FIRST(&lca->lca_write_queue)) != NULL) {
+      TAILQ_REMOVE(&lca->lca_write_queue, lcw, lcw_link);
+      free(lcw);
     }
-  } else {
-    tvhtrace(LS_LINUXDVB, "%s: closing %s (fd %d)",
-             lca->lca_name, lca->lca_ca_path, lca->lca_ca_fd);
+  }
+  pthread_mutex_unlock(&linuxdvb_capmt_mutex);
+  if (reset)
+    en50221_transport_reset(lcat->lcat_transport);
+}
 
-    mtimer_disarm(&lca->lca_monitor_timer);
+static int
+linuxdvb_ca_process_cmd
+  ( linuxdvb_ca_t *lca, linuxdvb_ca_write_t *lcw, en50221_slot_t *slot )
+{
+  int r = 0;
+  int interval = lca->lca_capmt_interval;
+
+  switch (lcw->cmd) {
+  case CA_WRITE_CMD_SLOT_DISABLE:
+    if (atomic_get(&lca->lca_enabled) <= 0)
+      en50221_slot_disable(slot);
+    break;
+  case CA_WRITE_CMD_CAPMT_QUERY:
+    interval = lca->lca_capmt_query_interval;
+    /* Fall thru */
+  case CA_WRITE_CMD_CAPMT:
+    if (lca->lca_capmt_blocked >= mclk())
+      return 1;
+    if (lcw->len > 0) {
+      r = en50221_send_capmt(slot, lcw->data, lcw->len);
+      if (r < 0)
+        tvherror(LS_EN50221, "%s: unable to write capmt (%d)",
+                 lca->lca_name, r);
+      else if (interval > 0)
+        lca->lca_capmt_blocked = mclk() + ms2mono(interval);
+    }
+    break;
+  case CA_WRITE_CMD_PCMCIA_RATE:
+    if (lcw->len == 1) {
+      r = en50221_pcmcia_data_rate(slot, lcw->data[0]);
+      if (r < 0)
+        tvherror(LS_EN50221, "%s: unable to write pcmcia data rate (%d)",
+                 lca->lca_name, r);
+    }
+    break;
+  }
+  return r;
+}
 
-    if (lca->lca_en50221_thread_running) {
-      lca->lca_en50221_thread_running = 0;
-      pthread_join(lca->lca_en50221_thread, NULL);
+static void *
+linuxdvb_ca_thread ( void *aux )
+{
+  tvhpoll_t *poll;
+  tvhpoll_event_t *ev, *evn, *evp;
+  int evsize = 4, evcnt = 0;
+  linuxdvb_transport_t *lcat;
+  linuxdvb_ca_t *lca;
+  uint8_t buf[8192], *pbuf;
+  ssize_t l;
+  int64_t tm, tm2;
+  int r, monitor, quit = 0, cquit, waitms, busy;
+  linuxdvb_ca_write_t *lcw;
+  en50221_slot_t *slot;
+  
+  tvhtrace(LS_EN50221, "ca thread start");
+  ev = malloc(sizeof(*ev) * evsize);
+  poll = tvhpoll_create(ARRAY_SIZE(ev) + 1);
+  tm = mclk();
+  waitms = 250;
+  while (tvheadend_running && !quit) {
+    evp = ev;
+    evp->fd = linuxdvb_ca_pipe.rd;
+    evp->events = TVHPOLL_IN;
+    evp->data.ptr = &linuxdvb_ca_pipe;
+    evp++;
+    evcnt = 1;
+    pthread_mutex_lock(&linuxdvb_ca_mutex);
+    LIST_FOREACH(lcat, &linuxdvb_all_transports, lcat_all_link) {
+      if (lcat->lcat_ca_fd < 0) {
+        if (!lcat->lcat_enabled)
+          continue;
+        if (lcat->lcat_fatal_time + sec2mono(TRANSPORT_RECOVERY) > mclk())
+          continue;
+        if (linuxdvb_ca_open_fd(lcat))
+          continue;
+      } else if (!lcat->lcat_enabled) {
+        if (lcat->lcat_ca_fd >= 0 && lcat->lcat_close_time < mclk()) {
+          pthread_mutex_lock(&linuxdvb_capmt_mutex);
+          busy = 0;
+          LIST_FOREACH(lca, &lcat->lcat_slots, lca_link) {
+            slot = en50221_transport_find_slot(lcat->lcat_transport,
+                                               lca->lca_slotnum);
+            if (slot)
+              busy |= TAILQ_EMPTY(&slot->cil_write_queue) ? 0 : 2;
+            busy |= TAILQ_EMPTY(&lca->lca_write_queue) ? 0 : 1;
+          }
+          pthread_mutex_unlock(&linuxdvb_capmt_mutex);
+          tvhtrace(LS_EN50221, "%s: busy %x", lcat->lcat_name, busy);
+          if (!busy)
+            linuxdvb_ca_close_fd(lcat, 1);
+        }
+        continue;
+      }
+      if (evcnt == evsize) {
+        evn = realloc(ev, sizeof(*ev) * (evsize + 2));
+        if (evn == NULL) {
+          tvherror(LS_EN50221, "poll ev alloc error");
+          continue;
+        }
+        ev = evn;
+        evsize += 2;
+        evp = ev + evcnt;
+      }
+      evp->fd = lcat->lcat_ca_fd;
+      evp->events = TVHPOLL_IN;
+      evp->data.ptr = lcat;
+      evp++;
+      evcnt++;
     }
+    pthread_mutex_unlock(&linuxdvb_ca_mutex);
+    tvhpoll_add(poll, ev, evcnt);
+
+    r = tvhpoll_wait(poll, ev, evcnt, waitms);
+    if (r < 0 && ERRNO_AGAIN(errno))
+      continue;
+
+    pthread_mutex_lock(&linuxdvb_ca_mutex);
+    tm2 = mclk();
+    monitor = (tm2 - tm) > (MONOCLOCK_RESOLUTION / 4); /* 250ms */
+    if (monitor)
+      tm = tm2;
+    for (evp = ev; evcnt > 0; evp++, evcnt--) {
+      if (evp->data.ptr == &linuxdvb_ca_pipe) {
+        do {
+          l = read(linuxdvb_ca_pipe.rd, buf, sizeof(buf));
+        } while (l < 0 && errno == EINTR);
+        for (pbuf = buf; l > 0; l--, pbuf++) {
+          switch (*pbuf) {
+          case 'q':
+            quit = 1;
+            break;
+          }
+        }
+      }
+      LIST_FOREACH(lcat, &linuxdvb_all_transports, lcat_all_link) {
+        if (lcat->lcat_ca_fd < 0) continue;
+        if (lcat != evp->data.ptr) continue;
+        do {
+          l = read(lcat->lcat_ca_fd, buf, sizeof(buf));
+        } while (l < 0 && (r = errno) == EINTR);
+        if (l > 0) {
+          tvhtrace(LS_EN50221, "%s: read", lcat->lcat_name);
+          tvhlog_hexdump(LS_EN50221, buf, l);
+        }
+        if (l < 5 && !(l < 0 && ERRNO_AGAIN(r))) {
+          tvhtrace(LS_EN50221, "%s: unable to read from device %s: %s",
+                   lcat->lcat_name, lcat->lcat_ca_path, strerror(r));
+          lcat->lcat_fatal_time = mclk();
+          linuxdvb_ca_close_fd(lcat, 1);
+          break;
+        }
+        if (l > 0 && buf[1]) {
+          r = en50221_transport_read(lcat->lcat_transport,
+                                     buf[0], buf[1], buf + 2, l - 2);
+          if (r < 0) {
+            tvhtrace(LS_EN50221, "%s: transport read failed: %s",
+                     lcat->lcat_name, strerror(-r));
+            lcat->lcat_fatal_time = mclk();
+            linuxdvb_ca_close_fd(lcat, 1);
+            break;
+          }
+        }
+      }
+    }
+    waitms = 250;
+    LIST_FOREACH(lcat, &linuxdvb_all_transports, lcat_all_link) {
+      if (lcat->lcat_ca_fd < 0) continue;
+      if (monitor) {
+        r = en50221_transport_monitor(lcat->lcat_transport, tm);
+        if (r < 0) {
+          tvhtrace(LS_EN50221, "%s: monitor failed for device %s: %s",
+                   lcat->lcat_name, lcat->lcat_ca_path, strerror(-r));
+          lcat->lcat_fatal_time = mclk();
+          linuxdvb_ca_close_fd(lcat, 1);
+        }
+      }
+      LIST_FOREACH(lca, &lcat->lcat_slots, lca_link) {
+        cquit = 0;
+        while (!cquit) {
+          pthread_mutex_lock(&linuxdvb_capmt_mutex);
+          lcw = TAILQ_FIRST(&lca->lca_write_queue);
+          if (lcw)
+            TAILQ_REMOVE(&lca->lca_write_queue, lcw, lcw_link);
+          pthread_mutex_unlock(&linuxdvb_capmt_mutex);
+          if (lcw == NULL) {
+            lca->lca_capmt_blocked = 0;
+            break;
+          }
+          slot = en50221_transport_find_slot(lcat->lcat_transport,
+                                             lca->lca_slotnum);
+          if (slot) {
+            r = linuxdvb_ca_process_cmd(lca, lcw, slot);
+            if (r < 0) {
+              lcat->lcat_fatal_time = mclk();
+              linuxdvb_ca_close_fd(lcat, 1);
+            } else if (r > 0) {
+              pthread_mutex_lock(&linuxdvb_capmt_mutex);
+              TAILQ_INSERT_HEAD(&lca->lca_write_queue, lcw, lcw_link);
+              pthread_mutex_unlock(&linuxdvb_capmt_mutex);
+              cquit = 1;
+            } else {
+              free(lcw);
+            }
+          } else {
+            free(lcw);
+          }
+        }
+        tm2 = lca->lca_capmt_blocked - mclk();
+        if (tm2 > 0) {
+          if (mono2ms(tm2) < waitms)
+            waitms = mono2ms(tm2);
+        } else if (tm2 > -sec2mono(2))
+          waitms = 1;
+      }
+    }
+    pthread_mutex_unlock(&linuxdvb_ca_mutex);
+  }
 
-    if (lca->lca_ca_fd >= 0) {
-      if (ioctl(lca->lca_ca_fd, CA_RESET, NULL))
-        tvherror(LS_LINUXDVB, "%s: unable to reset %s",
-                 lca->lca_name, lca->lca_ca_path);
+  tvhpoll_destroy(poll);
+  free(ev);
+  tvhtrace(LS_EN50221, "ca thread end");
+  return NULL;
+}
 
-      close(lca->lca_ca_fd);
-      lca->lca_ca_fd = -1;
-#if ENABLE_DDCI
-      if (lca->lddci)
-        linuxdvb_ddci_close(lca->lddci);
-#endif
-    }
+static void linuxdvb_ca_thread_create(void)
+{
+  if (linuxdvb_ca_threadid)
+    return;
+  tvhthread_create(&linuxdvb_ca_threadid, NULL, linuxdvb_ca_thread,
+                   NULL, "lnxdvb-ca");
+  if (tvh_pipe(O_NONBLOCK, &linuxdvb_ca_pipe)) {
+    tvherror(LS_EN50221, "unable to create thread pipe");
+    pthread_join(linuxdvb_ca_threadid, NULL);
+    linuxdvb_ca_threadid = 0;
+  }
+}
 
-    idnode_notify_title_changed(&lca->lca_id);
+static void linuxdvb_ca_thread_join(void)
+{
+  if (linuxdvb_ca_threadid == 0)
+    return;
+  tvh_write(linuxdvb_ca_pipe.wr, "q", 1);
+  pthread_join(linuxdvb_ca_threadid, NULL);
+  linuxdvb_ca_threadid = 0;
+  tvh_pipe_close(&linuxdvb_ca_pipe);
+}
+
+static void linuxdvb_ca_thread_join_cb(void *aux)
+{
+  linuxdvb_ca_thread_join();
+}
+
+static int linuxdvb_ca_write_cmd
+  (linuxdvb_ca_t *lca, int cmd, const uint8_t *data, size_t datalen)
+{
+  linuxdvb_ca_write_t *lcw;
+  int trigger;
+
+  lcw = calloc(1, sizeof(*lcw) + datalen);
+  if (!lcw)
+    return -ENOMEM;
+  lcw->len = datalen;
+  lcw->cmd = cmd;
+  memcpy(lcw->data, data, datalen);
+
+  pthread_mutex_lock(&linuxdvb_capmt_mutex);
+  trigger = TAILQ_EMPTY(&lca->lca_write_queue);
+  TAILQ_INSERT_TAIL(&lca->lca_write_queue, lcw, lcw_link);
+  pthread_mutex_unlock(&linuxdvb_capmt_mutex);
+
+  if (trigger)
+    tvh_write(linuxdvb_ca_pipe.wr, "w", 1);
+  return 0;
+}
+
+/*
+ *
+ */
+
+static void
+linuxdvb_ca_class_changed ( idnode_t *in )
+{
+  linuxdvb_adapter_t *la = ((linuxdvb_ca_t*)in)->lca_transport->lcat_adapter;
+  linuxdvb_adapter_changed(la);
+}
+
+static void
+linuxdvb_ca_class_enabled_notify ( void *p, const char *lang )
+{
+  linuxdvb_ca_t *lca = (linuxdvb_ca_t *) p;
+  int notify_title = linuxdvb_transport_refresh(lca->lca_transport);
+  int notify = 0;
+  if (!lca->lca_enabled) {
+    notify = linuxdvb_ca_slot_change_state(lca, CA_SLOT_STATE_DISABLED);
+    if (notify)
+      linuxdvb_ca_write_cmd(lca, CA_WRITE_CMD_SLOT_DISABLE, NULL, 0);
   }
+  if (notify)
+    idnode_notify_changed(&lca->lca_id);
+  else if (notify_title)
+    idnode_notify_title_changed(&lca->lca_id);
 }
 
 static void
 linuxdvb_ca_class_high_bitrate_notify ( void *p, const char *lang )
 {
   linuxdvb_ca_t *lca = (linuxdvb_ca_t *) p;
-  ciplus13_app_ai_data_rate_info(lca, lca->lca_high_bitrate_mode ?
-                                 CIPLUS13_DATA_RATE_96_MBPS :
-                                 CIPLUS13_DATA_RATE_72_MBPS);
+  uint8_t b;
+
+  if (lca->lca_enabled) {
+    b = lca->lca_high_bitrate_mode ? 0x01 : 0x00;
+    linuxdvb_ca_write_cmd(lca, CA_WRITE_CMD_PCMCIA_RATE, &b, 1);
+  }
 }
 
 static void
@@ -180,13 +555,19 @@ linuxdvb_ca_class_get_title
   ( idnode_t *in, const char *lang, char *dst, size_t dstsize )
 {
   linuxdvb_ca_t *lca = (linuxdvb_ca_t *) in;
-  if (!lca->lca_enabled)
-    snprintf(dst, dstsize, "ca%u: disabled", lca->lca_number);
-  else if (lca->lca_state == CA_SLOT_STATE_EMPTY)
-    snprintf(dst, dstsize, "ca%u: slot empty", lca->lca_number);
-  else
-    snprintf(dst, dstsize, "ca%u: %s (%s)", lca->lca_number,
-            lca->lca_cam_menu_string, lca->lca_state_str);
+  const int anum = lca->lca_transport->lcat_number;
+  const int snum = lca->lca_slotnum;
+  int state = atomic_get(&lca->lca_state);
+  const char *s;
+  if (!lca->lca_enabled || state == CA_SLOT_STATE_EMPTY) {
+    s = !lca->lca_enabled ? N_("disabled") : N_("slot empty");
+    snprintf(dst, dstsize, "ca%u-%u: %s", anum, snum,
+             tvh_gettext_lang(lang, s));
+  } else {
+    snprintf(dst, dstsize, "ca%u-%u: %s (%s)", anum, snum,
+             lca->lca_modulename ?: "",
+             tvh_gettext_lang(lang, ca_slot_state2str(state)));
+  }
 }
 
 static const void *
@@ -198,6 +579,22 @@ linuxdvb_ca_class_active_get ( void *obj )
   return &active;
 }
 
+static const void *
+linuxdvb_ca_class_ca_path_get ( void *obj )
+{
+  linuxdvb_ca_t *lca = (linuxdvb_ca_t*)obj;
+  return &lca->lca_transport->lcat_ca_path;
+}
+
+static const void *
+linuxdvb_ca_class_state_get ( void *obj )
+{
+  linuxdvb_ca_t *lca = (linuxdvb_ca_t*)obj;
+  static const char *s;
+  s = ca_slot_state2str(lca->lca_state);
+  return &s;
+}
+
 const idclass_t linuxdvb_ca_class =
 {
   .ic_class      = "linuxdvb_ca",
@@ -258,19 +655,19 @@ const idclass_t linuxdvb_ca_class =
       .type     = PT_INT,
       .id       = "capmt_interval",
       .name     = N_("CAPMT interval (ms)"),
-      .desc     = N_("CAPMT interval (in ms)."),
+      .desc     = N_("A delay between CAPMT commands (in ms)."),
       .off      = offsetof(linuxdvb_ca_t, lca_capmt_interval),
       .opts     = PO_ADVANCED,
-      .def.i    = 100,
+      .def.i    = 0,
     },
     {
       .type     = PT_INT,
       .id       = "capmt_query_interval",
       .name     = N_("CAPMT query interval (ms)"),
-      .desc     = N_("CAPMT query interval (ms)."),
+      .desc     = N_("A delay before CAPMT after CAPMT query command (ms)."),
       .off      = offsetof(linuxdvb_ca_t, lca_capmt_query_interval),
       .opts     = PO_ADVANCED,
-      .def.i    = 1200,
+      .def.i    = 300,
     },
     {
       .type     = PT_BOOL,
@@ -286,7 +683,15 @@ const idclass_t linuxdvb_ca_class =
       .name     = N_("Device path"),
       .desc     = N_("Path used by the device."),
       .opts     = PO_RDONLY | PO_NOSAVE,
-      .off      = offsetof(linuxdvb_ca_t, lca_ca_path),
+      .get      = linuxdvb_ca_class_ca_path_get,
+    },
+    {
+      .type     = PT_INT,
+      .id       = "slotnum",
+      .name     = N_("Slot number"),
+      .desc     = N_("CAM slot number."),
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(linuxdvb_ca_t, lca_slotnum),
     },
     {
       .type     = PT_STR,
@@ -294,692 +699,456 @@ const idclass_t linuxdvb_ca_class =
       .name     = N_("Slot state"),
       .desc     = N_("The CAM slot status."),
       .opts     = PO_RDONLY | PO_NOSAVE,
-      .off      = offsetof(linuxdvb_ca_t, lca_state_str),
+      .get      = linuxdvb_ca_class_state_get,
     },
     {}
   }
 };
 
-static uint32_t
-resource_ids[] = { EN50221_APP_RM_RESOURCEID,
-                   EN50221_APP_MMI_RESOURCEID,
-                   EN50221_APP_DVB_RESOURCEID,
-                   EN50221_APP_CA_RESOURCEID,
-                   EN50221_APP_DATETIME_RESOURCEID,
-                   EN50221_APP_AI_RESOURCEID,
-                   TS101699_APP_AI_RESOURCEID,
-                   CIPLUS13_APP_AI_RESOURCEID};
+/*
+ *
+ */
 
-static int
-en50221_app_unknown_message(void *arg, uint8_t slot_id,
-                            uint16_t session_num, uint32_t resource_id,
-                            uint8_t *data, uint32_t data_length)
+linuxdvb_ca_t *
+linuxdvb_ca_create
+  ( htsmsg_t *conf, linuxdvb_transport_t *lcat, int slotnum )
 {
-  linuxdvb_ca_t * lca = arg;
-
-  tvhtrace(LS_EN50221, "%s: unknown message slot_id %u, "
-                       "session_num %u, resource_id %x",
-                       lca->lca_name, slot_id, session_num, resource_id);
-  tvhlog_hexdump(LS_EN50221, data, data_length);
-  return 0;
-}
-
-static int
-linuxdvb_ca_lookup_cb (void * arg, uint8_t slot_id, uint32_t requested_rid,
-                       en50221_sl_resource_callback *callback_out,
-                      void **arg_out, uint32_t *connected_rid)
-{
-    linuxdvb_ca_t * lca = arg;
-
-    switch (requested_rid) {
-      case EN50221_APP_RM_RESOURCEID:
-        *callback_out = (en50221_sl_resource_callback) en50221_app_rm_message;
-        *arg_out = lca->lca_rm_resource;
-        *connected_rid = EN50221_APP_RM_RESOURCEID;
-        break;
-      case EN50221_APP_AI_RESOURCEID:
-      case TS101699_APP_AI_RESOURCEID:
-      case CIPLUS13_APP_AI_RESOURCEID:
-        *callback_out = (en50221_sl_resource_callback) en50221_app_ai_message;
-        *arg_out = lca->lca_ai_resource;
-        *connected_rid = requested_rid;
-        break;
-      case EN50221_APP_CA_RESOURCEID:
-        *callback_out = (en50221_sl_resource_callback) en50221_app_ca_message;
-        *arg_out = lca->lca_ca_resource;
-        *connected_rid = EN50221_APP_CA_RESOURCEID;
-        break;
-      case EN50221_APP_DATETIME_RESOURCEID:
-        *callback_out = (en50221_sl_resource_callback) en50221_app_datetime_message;
-        *arg_out = lca->lca_dt_resource;
-        *connected_rid = EN50221_APP_DATETIME_RESOURCEID;
-        break;
-      case EN50221_APP_MMI_RESOURCEID:
-        *callback_out = (en50221_sl_resource_callback) en50221_app_mmi_message;
-        *arg_out = lca->lca_mmi_resource;
-        *connected_rid = EN50221_APP_MMI_RESOURCEID;
-        break;
-      default:
-        tvhtrace(LS_EN50221, "%s: lookup cb for unknown resource id %x on slot %u",
-                             lca->lca_name, requested_rid, slot_id);
-        *callback_out = (en50221_sl_resource_callback) en50221_app_unknown_message;
-        *arg_out = lca;
-        *connected_rid = requested_rid;
-    }
-    return 0;
-}
+  linuxdvb_ca_t *lca;
+  char id[6], buf[32];
+  const char *uuid = NULL;
 
-static int
-linuxdvb_ca_session_cb (void *arg, int reason, uint8_t slot_id,
-                        uint16_t session_num, uint32_t rid)
-{
-  linuxdvb_ca_t * lca = arg;
+  lca = calloc(1, sizeof(linuxdvb_ca_t));
+  lca->lca_state = CA_SLOT_STATE_EMPTY;
+  lca->lca_transport = lcat;
+  lca->lca_adapnum = lcat->lcat_adapter->la_dvb_number;
+  lca->lca_slotnum = slotnum;
+  snprintf(buf, sizeof(buf), "dvbca%d-%d", lcat->lcat_number, slotnum);
+  lca->lca_name = strdup(buf);
+  lca->lca_capmt_query_interval = 300;
+  TAILQ_INIT(&lca->lca_write_queue);
 
-  if (reason == S_SCALLBACK_REASON_CAMCONNECTING) {
-    tvhtrace(LS_EN50221, "%s: 0x%08x session %u connecting",
-                         lca->lca_name, rid, session_num);
-    return 0;
+  /* Internal config ID */
+  if (slotnum > 0) {
+    snprintf(id, sizeof(id), "ca%u-%u", lcat->lcat_number, slotnum);
+  } else {
+    snprintf(id, sizeof(id), "ca%u", lcat->lcat_number);
   }
 
-  if (reason == S_SCALLBACK_REASON_CAMCONNECTED) {
-    tvhtrace(LS_EN50221, "%s: 0x%08x session %u connected",
-                         lca->lca_name, rid, session_num);
-    switch(rid) {
-      case EN50221_APP_RM_RESOURCEID:
-        en50221_app_rm_enq(lca->lca_rm_resource, session_num);
-        break;
-      case EN50221_APP_AI_RESOURCEID:
-      case TS101699_APP_AI_RESOURCEID:
-      case CIPLUS13_APP_AI_RESOURCEID:
-        lca->lca_ai_version = rid & 0x3f;
-        lca->lca_ai_session_number = session_num;
-        en50221_app_ai_enquiry(lca->lca_ai_resource, session_num);
-        ciplus13_app_ai_data_rate_info(lca, lca->lca_high_bitrate_mode ?
-                                       CIPLUS13_DATA_RATE_96_MBPS :
-                                       CIPLUS13_DATA_RATE_72_MBPS );
-        break;
-      case EN50221_APP_CA_RESOURCEID:
-        en50221_app_ca_info_enq(lca->lca_ca_resource, session_num);
-        lca->lca_ca_session_number = session_num;
-        break;
-      case EN50221_APP_MMI_RESOURCEID:
-      case EN50221_APP_DATETIME_RESOURCEID:
-        break;
-      default:
-        tvhtrace(LS_EN50221, "%s: session %u with unknown rid 0x%08x is connected",
-                             lca->lca_name, session_num, rid);
-    }
-    return 0;
-  }
+  conf = htsmsg_get_map(conf, id);
+  if (conf)
+    uuid = htsmsg_get_str(conf, "uuid");
 
-  if (reason == S_SCALLBACK_REASON_CLOSE) {
-    tvhtrace(LS_EN50221, "%s: 0x%08x session %u close",
-                         lca->lca_name, rid, session_num);
-    switch(rid) {
-      case EN50221_APP_CA_RESOURCEID:
-        dvbcam_unregister_cam(lca, 0);
-        lca->lca_cam_menu_string[0] = 0;
-    }
-    return 0;
+  if (idnode_insert(&lca->lca_id, uuid, &linuxdvb_ca_class, 0)) {
+    free(lca);
+    return NULL;
   }
 
-  if (reason == S_SCALLBACK_REASON_TC_CONNECT) {
-    tvhtrace(LS_EN50221, "%s: 0x%08x session %u tc connect",
-                         lca->lca_name, rid, session_num);
-    return 0;
-  }
+  /* Transport link */
+  lca->lca_transport = lcat;
+  LIST_INSERT_HEAD(&lcat->lcat_slots, lca, lca_link);
+  pthread_mutex_lock(&linuxdvb_ca_mutex);
+  LIST_INSERT_HEAD(&linuxdvb_all_cas, lca, lca_all_link);
+  pthread_mutex_unlock(&linuxdvb_ca_mutex);
 
-  tvhtrace(LS_EN50221, "%s: unhandled session callback - reason %d slot_id %u "
-                       "session_num %u resource_id %x",
-                       lca->lca_name, reason, slot_id, session_num, rid);
-  return 0;
+  if (conf)
+    idnode_load(&lca->lca_id, conf);
+
+  return lca;
 }
 
-static int
-linuxdvb_ca_rm_enq_cb(void *arg, uint8_t slot_id, uint16_t session_num)
+static void linuxdvb_ca_destroy( linuxdvb_ca_t *lca )
 {
-    linuxdvb_ca_t * lca = arg;
-
-    tvhtrace(LS_EN50221, "%s: rm enq callback received for slot %d",
-                         lca->lca_name, slot_id);
-
-    if (en50221_app_rm_reply(lca->lca_rm_resource, session_num,
-        sizeof(resource_ids)/4, resource_ids))
-    {
-      tvherror(LS_EN50221, "%s: failed to send rm reply to slot %u session %u",
-                           lca->lca_name, slot_id, session_num);
-    }
+  linuxdvb_ca_write_t *lcw;
 
-    return 0;
+  if (lca == NULL)
+    return;
+  dvbcam_unregister_cam(lca);
+  pthread_mutex_lock(&linuxdvb_ca_mutex);
+  LIST_REMOVE(lca, lca_all_link);
+  while ((lcw = TAILQ_FIRST(&lca->lca_write_queue)) != NULL) {
+    TAILQ_REMOVE(&lca->lca_write_queue, lcw, lcw_link);
+    free(lcw);
+  }
+  pthread_mutex_unlock(&linuxdvb_ca_mutex);
+  LIST_REMOVE(lca, lca_link);
+  idnode_unlink(&lca->lca_id);
+  free(lca->lca_pin_str);
+  free(lca->lca_pin_match_str);
+  free(lca->lca_modulename);
+  free(lca->lca_name);
+  free(lca);
 }
 
-static int
-linuxdvb_ca_rm_reply_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                        uint32_t resource_id_count, uint32_t *_resource_ids)
+static void linuxdvb_ca_save( linuxdvb_ca_t *lca, htsmsg_t *msg )
 {
-    linuxdvb_ca_t * lca = arg;
-
-    tvhtrace(LS_EN50221, "%s: rm reply cb received for slot %d, count %u",
-                         lca->lca_name, slot_id, resource_id_count);
-
-    uint32_t i;
-    for(i=0; i< resource_id_count; i++) {
-        tvhtrace(LS_EN50221, "%s: CAM provided resource id: %08x",
-                             lca->lca_name, _resource_ids[i]);
-    }
+  char id[8], ubuf[UUID_HEX_SIZE];
+  htsmsg_t *m = htsmsg_create_map();
+  linuxdvb_transport_t *lcat = lca->lca_transport;
 
-    if (en50221_app_rm_changed(lca->lca_rm_resource, session_num)) {
-        tvherror(LS_EN50221, "%s: failed to send RM reply on slot %u",
-                             lca->lca_name, slot_id);
-    }
+  htsmsg_add_str(m, "uuid", idnode_uuid_as_str(&lca->lca_id, ubuf));
+  idnode_save(&lca->lca_id, m);
 
-    return 0;
+  /* Add to list */
+  if (lca->lca_slotnum > 0)
+    snprintf(id, sizeof(id), "ca%u-%u", lcat->lcat_number, lca->lca_slotnum);
+  else
+    snprintf(id, sizeof(id), "ca%u", lcat->lcat_number);
+  htsmsg_add_msg(msg, id, m);
 }
 
-static int
-linuxdvb_ca_rm_changed_cb(void *arg, uint8_t slot_id,
-                          uint16_t session_num)
-{
-    linuxdvb_ca_t * lca = arg;
-    tvhtrace(LS_EN50221, "%s: rm changed cb received for slot %d",
-                         lca->lca_name, slot_id);
-
-    if (en50221_app_rm_enq(lca->lca_rm_resource, session_num)) {
-        tvherror(LS_EN50221, "%s: failed to send ENQ to slot %d",
-                             lca->lca_name, slot_id);
-    }
+/*
+ *
+ */
 
-    return 0;
+static linuxdvb_ca_t *linuxdvb_ca_find_slot
+  ( linuxdvb_transport_t *lcat, en50221_slot_t *slot )
+{
+  linuxdvb_ca_t *lca;
+  LIST_FOREACH(lca, &lcat->lcat_slots, lca_link)
+    if (lca->lca_slotnum == slot->cil_number)
+      return lca;
+  return NULL;
 }
 
-static int
-linuxdvb_ca_dt_enquiry_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                          uint8_t response_int)
+static int linuxdvb_ca_ops_reset( void *aux )
 {
-    linuxdvb_ca_t * lca = arg;
-    tvhtrace(LS_EN50221, "%s: datetime enquiry cb received for slot %d",
-                         lca->lca_name, slot_id);
-
-    if (en50221_app_datetime_send(lca->lca_dt_resource, session_num, time(NULL), -1)) {
-        tvherror(LS_EN50221, "%s: Failed to send datetime to slot %d",
-                             lca->lca_name, slot_id);
-    }
-
-    return 0;
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_close_fd(lcat, 0);
+  return 0;
 }
 
-static int
-linuxdvb_ca_ai_callback(void *arg, uint8_t slot_id, uint16_t session_num,
-                     uint8_t app_type, uint16_t app_manufacturer,
-                     uint16_t manufacturer_code, uint8_t menu_string_len,
-                     uint8_t *menu_string)
+static int linuxdvb_ca_ops_cam_is_ready
+  ( void *aux, en50221_slot_t *slot )
 {
-    linuxdvb_ca_t * lca = arg;
-
-    tvhinfo(LS_EN50221, "%s: CAM slot %u: Application type: %02x, "
-                        "manufacturer: %04x, Manufacturer code: %04x",
-                        lca->lca_name, slot_id, app_type, app_manufacturer,
-                        manufacturer_code);
-
-    tvhinfo(LS_EN50221, "%s: CAM slot %u: Menu string: %.*s",
-                        lca->lca_name, slot_id, menu_string_len, menu_string);
-
-    snprintf(lca->lca_cam_menu_string, sizeof(lca->lca_cam_menu_string),
-             "%.*s", menu_string_len, menu_string);
-
-    idnode_notify_title_changed(&lca->lca_id);
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  int fd, r;
 
+  assert(lca);
+  if (lca->lca_enabled <= 0)
     return 0;
-}
 
-static int
-linuxdvb_ca_ca_info_callback(void *arg, uint8_t slot_id, uint16_t session_num,
-                             uint32_t ca_id_count, uint16_t *ca_ids)
-{
-    linuxdvb_ca_t * lca = arg;
-    uint32_t i, j;
-    char buf[256];
-    size_t c;
-
-    dvbcam_unregister_cam(lca, 0);
-    dvbcam_register_cam(lca, 0, ca_ids, ca_id_count);
-
-    for (i = 0; i < ca_id_count; ) {
-      for (j = 0, c = 0; j < 4 && i < ca_id_count; i++, j++)
-          tvh_strlcatf(buf, sizeof(buf), c, " %04X (%s)",
-                       ca_ids[i], caid2name(ca_ids[i]));
-      tvhinfo(LS_EN50221, "%s: CAM slot %u supported CAIDs: %s",
-                          lca->lca_name, slot_id, buf);
-    }
+  fd = lcat->lcat_ca_fd;
+  if (fd < 0)
+    return -ENXIO;
 
-    return 0;
+  r = linuxdvb_ca_slot_info(fd, lcat, lca);
+  if (r >= 0)
+    slot->cil_apdu_only = lca->lca_phys_layer;
+  if (r > 0)
+    return 1;
+  return r;
 }
 
-static int
-linuxdvb_ca_ca_pmt_reply_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                            struct en50221_app_pmt_reply *reply,
-                            uint32_t reply_size)
-{
-    linuxdvb_ca_t * lca = arg;
-    const char *str;
-
-    switch (reply->CA_enable) {
-      case 0x01: str = "possible"; break;
-      case 0x02: str = "possible under conditions (purchase dialogue)"; break;
-      case 0x03: str = "possible under conditions (technical dialogue)"; break;
-      case 0x71: str = "not possible (because no entitlement)"; break;
-      case 0x73: str = "not possible (for technical reasons)"; break;
-      default:   str = "state unknown (unknown value received)";
-    }
-
-    tvhinfo(LS_EN50221, "%s: CAM slot %u: descrambling %s",
-                        lca->lca_name, slot_id, str);
-
+static int linuxdvb_ca_ops_pdu_write
+  ( void *aux, en50221_slot_t *slot, uint8_t tcnum,
+    const uint8_t *data, size_t datalen )
+{
+  linuxdvb_transport_t *lcat = aux;
+  uint8_t *buf;
+  ssize_t l;
+  int r, fd = lcat->lcat_ca_fd;
+
+  if (fd < 0)
+    return -EIO;
+
+  buf = alloca(datalen + 2);
+  buf[0] = slot->cil_number;
+  buf[1] = tcnum;
+  memcpy(buf + 2, data, datalen);
+  tvhtrace(LS_EN50221, "%s: write", lcat->lcat_name);
+  tvhlog_hexdump(LS_EN50221, buf, datalen + 2);
+  do {
+    l = write(fd, buf, datalen + 2);
+  } while (l < 0 && errno == EINTR);
+  if (l < 0) {
+    r = errno;
+    tvherror(LS_EN50221, "%s: unable to write: %s",
+             lcat->lcat_name, strerror(r));
+    return -r;
+  } else if (l != datalen + 2) {
+    tvherror(LS_EN50221, "%s: partial write %zd (of %zd)",
+             lcat->lcat_name, l, datalen + 2);
+    return -EIO;
+  } else {
     return 0;
+  }
 }
 
-static int
-linuxdvb_ca_mmi_close_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                         uint8_t cmd_id, uint8_t delay)
+static int linuxdvb_ca_ops_apdu_write
+  ( void *aux, en50221_slot_t *slot, const uint8_t *data, size_t datalen )
 {
-    linuxdvb_ca_t * lca = arg;
-
-    tvhtrace(LS_EN50221, "%s: mmi close cb received for slot %u session_num %u "
-                         "cmd_id 0x%02x delay %u",
-                         lca->lca_name, slot_id, session_num, cmd_id, delay);
-
-    return 0;
+  linuxdvb_transport_t *lcat = aux;
+  ca_msg_t ca_msg;
+  int r, fd = lcat->lcat_ca_fd;
+
+  if (fd < 0)
+    return -EIO;
+  if (datalen > 256) {
+    tvherror(LS_EN50221, "%s: unable to write APDU with length %zd",
+             lcat->lcat_name, datalen);
+    return -E2BIG;
+  }
+  memset(&ca_msg, 0, sizeof(ca_msg));
+  ca_msg.index = slot->cil_number; /* correct? */
+  ca_msg.length = datalen;
+  memcpy(ca_msg.msg, data, datalen);
+  do {
+    r = ioctl(fd, CA_SEND_MSG, &ca_msg);
+  } while (r < 0 && errno == EINTR);
+  if (r < 0) {
+    r = errno;
+    tvherror(LS_EN50221, "%s: unable to write APDU: %s",
+             lcat->lcat_name, strerror(r));
+    return -r;
+  }
+  return 0;
 }
 
-static int
-linuxdvb_ca_mmi_display_ctl_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                               uint8_t cmd_id, uint8_t mmi_mode)
+static int linuxdvb_ca_ops_pcmcia_data_rate
+  ( void *aux, en50221_slot_t *slot, uint8_t *rate )
 {
-    linuxdvb_ca_t * lca = arg;
-
-    tvhtrace(LS_EN50221, "%s: mmi display ctl cb received for slot %u "
-                         "session_num %u cmd_id 0x%02x mmi_mode %u",
-                         lca->lca_name, slot_id, session_num, cmd_id, mmi_mode);
-
-    if (cmd_id == MMI_DISPLAY_CONTROL_CMD_ID_SET_MMI_MODE) {
-        struct en50221_app_mmi_display_reply_details d;
-
-        d.u.mode_ack.mmi_mode = mmi_mode;
-        if (en50221_app_mmi_display_reply(lca->lca_mmi_resource, session_num,
-                                          MMI_DISPLAY_REPLY_ID_MMI_MODE_ACK, &d)) {
-            tvherror(LS_EN50221, "%s: Slot %u: Failed to send MMI mode ack reply",
-                                 lca->lca_name, slot_id);
-        }
-    }
-
-    return 0;
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  *rate = lca->lca_high_bitrate_mode ? 0x01 : 0x00;
+  tvhtrace(LS_EN50221, "%s: pcmcia data rate set to %02x", lca->lca_name, *rate);
+  return 0;
 }
 
-static int
-linuxdvb_ca_mmi_enq_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                       uint8_t blind_answ, uint8_t exp_answ_len,
-                       uint8_t *text, uint32_t text_size)
+static int linuxdvb_ca_ops_appinfo
+  (void *aux, en50221_slot_t *slot, uint8_t ver,
+   char *name, uint8_t type, uint16_t manufacturer, uint16_t code)
 {
-    linuxdvb_ca_t * lca = arg;
-    char buffer[256];
-
-    snprintf(buffer, sizeof(buffer), "%.*s", text_size, text);
-
-    tvhnotice(LS_EN50221, "%s: MMI enquiry from CAM in slot %u:  %s (%s%u digits)",
-              lca->lca_name, slot_id, buffer, blind_answ ? "blind " : "" , exp_answ_len);
-
-    if (lca->lca_pin_reply &&
-        (strlen((char *) lca->lca_pin_str) == exp_answ_len) &&
-        strstr((char *) buffer, lca->lca_pin_match_str))
-    {
-      tvhtrace(LS_EN50221, "%s: answering to PIN enquiry", lca->lca_name);
-      en50221_app_mmi_answ(lca->lca_mmi_resource, session_num,
-                           MMI_ANSW_ID_ANSWER, (uint8_t *) lca->lca_pin_str,
-                           exp_answ_len);
-    }
-
-    en50221_app_mmi_close(lca->lca_mmi_resource, session_num,
-                          MMI_CLOSE_MMI_CMD_ID_IMMEDIATE, 0);
-
-    return 0;
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  if (lca)
+    free(atomic_set_ptr((atomic_refptr_t)&lca->lca_modulename, strdup(name)));
+  return 0;
 }
 
-static int
-linuxdvb_ca_mmi_menu_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                        struct en50221_app_mmi_text *title,
-                        struct en50221_app_mmi_text *sub_title,
-                        struct en50221_app_mmi_text *bottom,
-                        uint32_t item_count, struct en50221_app_mmi_text *items,
-                        uint32_t item_raw_length, uint8_t *items_raw)
-{
-    linuxdvb_ca_t * lca = arg;
-
-    tvhnotice(LS_EN50221, "%s: MMI menu from CAM in the slot %u:", lca->lca_name, slot_id);
-    tvhnotice(LS_EN50221, "%s:  title:    %.*s", lca->lca_name, title->text_length, title->text);
-    tvhnotice(LS_EN50221, "%s:  subtitle: %.*s", lca->lca_name, sub_title->text_length, sub_title->text);
-
-    uint32_t i;
-    for(i=0; i< item_count; i++) {
-        tvhnotice(LS_EN50221, "%s:  item %i:   %.*s", lca->lca_name, i+1, items[i].text_length, items[i].text);
-    }
-    tvhnotice(LS_EN50221, "%s:  bottom:   %.*s", lca->lca_name, bottom->text_length, bottom->text);
-
-    /* cancel menu */
-    en50221_app_mmi_close(lca->lca_mmi_resource, session_num,
-                          MMI_CLOSE_MMI_CMD_ID_IMMEDIATE, 0);
-    return 0;
+static int linuxdvb_ca_ops_caids
+  ( void *aux, en50221_slot_t *slot, uint16_t *list, int listsize )
+{
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  if (lca)
+    dvbcam_register_cam(lca, list, listsize);
+  return 0;
 }
 
-static int
-linuxdvb_ca_app_mmi_list_cb(void *arg, uint8_t slot_id, uint16_t session_num,
-                            struct en50221_app_mmi_text *title,
-                            struct en50221_app_mmi_text *sub_title,
-                            struct en50221_app_mmi_text *bottom,
-                            uint32_t item_count, struct en50221_app_mmi_text *items,
-                            uint32_t item_raw_length, uint8_t *items_raw)
-{
-    linuxdvb_ca_t * lca = arg;
-
-    tvhnotice(LS_EN50221, "%s: MMI list from CAM in the slot %u:", lca->lca_name, slot_id);
-    tvhnotice(LS_EN50221, "%s:  title:    %.*s", lca->lca_name, title->text_length, title->text);
-    tvhnotice(LS_EN50221, "%s:  subtitle: %.*s", lca->lca_name, sub_title->text_length, sub_title->text);
-
-    uint32_t i;
-    for(i=0; i< item_count; i++) {
-        tvhnotice(LS_EN50221, "%s:  item %i:   %.*s", lca->lca_name, i+1, items[i].text_length, items[i].text);
-    }
-    tvhnotice(LS_EN50221, "%s:  bottom:   %.*s", lca->lca_name, bottom->text_length, bottom->text);
-
-    /* cancel menu */
-    en50221_app_mmi_close(lca->lca_mmi_resource, session_num,
-                          MMI_CLOSE_MMI_CMD_ID_IMMEDIATE, 0);
-    return 0;
+static int linuxdvb_ca_ops_ca_close
+  ( void *aux, en50221_slot_t *slot )
+{
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  if (lca)
+    dvbcam_unregister_cam(lca);
+  return 0;
 }
 
-static void *
-linuxdvb_ca_en50221_thread ( void *aux )
+static int linuxdvb_ca_ops_menu
+  ( void *aux, en50221_slot_t *slot, htsmsg_t *menu )
 {
-  linuxdvb_ca_t *lca = aux;
-  int slot_id, lasterror = 0;
-
-  lca->lca_tl = en50221_tl_create(5, 32);
-  if (!lca->lca_tl) {
-    tvherror(LS_EN50221, "%s: failed to create transport layer", lca->lca_name);
-    return NULL;
-  }
-
-  slot_id = en50221_tl_register_slot(lca->lca_tl, lca->lca_ca_fd, 0, 1000, 100);
-  if (slot_id < 0) {
-    tvherror(LS_EN50221, "%s: slot registration failed", lca->lca_name);
-    return NULL;
-  }
-
-  lca->lca_sl = en50221_sl_create(lca->lca_tl, 256);
-  if (lca->lca_sl == NULL) {
-    tvherror(LS_EN50221, "%s: failed to create session layer", lca->lca_name);
-    return NULL;
-  }
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  char *s = htsmsg_json_serialize_to_str(menu, 0);
+  tvhinfo(LS_EN50221, "%s: ops menu: %s", lca->lca_name, s);
+  free(s);
+  en50221_mmi_close(slot);
+  return 0;
+}
 
-  // create the sendfuncs
-  lca->lca_sf.arg        = lca->lca_sl;
-  lca->lca_sf.send_data  = (en50221_send_data) en50221_sl_send_data;
-  lca->lca_sf.send_datav = (en50221_send_datav) en50221_sl_send_datav;
-
-  /* create app resources and assign callbacks */
-  lca->lca_rm_resource = en50221_app_rm_create(&lca->lca_sf);
-  en50221_app_rm_register_enq_callback(lca->lca_rm_resource, linuxdvb_ca_rm_enq_cb, lca);
-  en50221_app_rm_register_reply_callback(lca->lca_rm_resource, linuxdvb_ca_rm_reply_cb, lca);
-  en50221_app_rm_register_changed_callback(lca->lca_rm_resource, linuxdvb_ca_rm_changed_cb, lca);
-
-  lca->lca_dt_resource = en50221_app_datetime_create(&lca->lca_sf);
-  en50221_app_datetime_register_enquiry_callback(lca->lca_dt_resource, linuxdvb_ca_dt_enquiry_cb, lca);
-
-  lca->lca_ai_resource = en50221_app_ai_create(&lca->lca_sf);
-  en50221_app_ai_register_callback(lca->lca_ai_resource, linuxdvb_ca_ai_callback, lca);
-
-  lca->lca_ca_resource = en50221_app_ca_create(&lca->lca_sf);
-  en50221_app_ca_register_info_callback(lca->lca_ca_resource, linuxdvb_ca_ca_info_callback, lca);
-  en50221_app_ca_register_pmt_reply_callback(lca->lca_ca_resource, linuxdvb_ca_ca_pmt_reply_cb, lca);
-
-  lca->lca_mmi_resource = en50221_app_mmi_create(&lca->lca_sf);
-  en50221_app_mmi_register_close_callback(lca->lca_mmi_resource, linuxdvb_ca_mmi_close_cb, lca);
-  en50221_app_mmi_register_display_control_callback(lca->lca_mmi_resource, linuxdvb_ca_mmi_display_ctl_cb, lca);
-  en50221_app_mmi_register_enq_callback(lca->lca_mmi_resource, linuxdvb_ca_mmi_enq_cb, lca);
-  en50221_app_mmi_register_menu_callback(lca->lca_mmi_resource, linuxdvb_ca_mmi_menu_cb, lca);
-  en50221_app_mmi_register_list_callback(lca->lca_mmi_resource, linuxdvb_ca_app_mmi_list_cb, lca);
-
-  en50221_sl_register_lookup_callback(lca->lca_sl, linuxdvb_ca_lookup_cb, lca);
-  en50221_sl_register_session_callback(lca->lca_sl, linuxdvb_ca_session_cb, lca);
-
-  lca->lca_tc = en50221_tl_new_tc(lca->lca_tl, slot_id);
-
-  while (tvheadend_is_running() && lca->lca_en50221_thread_running) {
-        int error;
-        if ((error = en50221_tl_poll(lca->lca_tl)) != 0) {
-            if (error != lasterror) {
-                tvherror(LS_EN50221, "%s:poll error on slot %d [error:%i]",
-                                     lca->lca_name,
-                                     en50221_tl_get_error_slot(lca->lca_tl),
-                                     en50221_tl_get_error(lca->lca_tl));
-            }
-            lasterror = error;
-        }
+static int linuxdvb_ca_ops_enquiry
+  ( void *aux, en50221_slot_t *slot, htsmsg_t *enq )
+{
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  const char *text;
+  char *s;
+  int explen;
+
+  s = htsmsg_json_serialize_to_str(enq, 0);
+  tvhinfo(LS_EN50221, "%s: ops enquiry: %s", lca->lca_name, s);
+  free(s);
+
+  explen = htsmsg_get_s32_or_default(enq, "explen", 0);
+  text = htsmsg_get_str(enq, "text");
+
+  if (lca->lca_pin_reply &&
+      (strlen(lca->lca_pin_str) == explen) &&
+       strstr(text, lca->lca_pin_match_str)) {
+    tvhtrace(LS_EN50221, "%s: answering to PIN enquiry", lca->lca_name);
+    en50221_mmi_answer(slot, (uint8_t *)lca->lca_pin_str, explen);
   }
 
-  dvbcam_unregister_cam(lca, 0);
-
-  en50221_tl_destroy_slot(lca->lca_tl, slot_id);
-  en50221_sl_destroy(lca->lca_sl);
-  en50221_tl_destroy(lca->lca_tl);
+  en50221_mmi_close(slot);
   return 0;
 }
 
-static void
-linuxdvb_ca_monitor ( void *aux )
+static int linuxdvb_ca_ops_close
+  ( void *aux, en50221_slot_t *slot, int delay )
 {
-  linuxdvb_ca_t *lca = aux;
-  ca_slot_info_t csi;
-  int state;
+  linuxdvb_transport_t *lcat = aux;
+  linuxdvb_ca_t *lca = linuxdvb_ca_find_slot(lcat, slot);
+  tvhinfo(LS_EN50221, "%s: ops close", lca->lca_name);
+  return 0;
+}
 
-  if (lca->lca_ca_fd < 0)
-    return;
+static en50221_ops_t linuxdvb_ca_ops = {
+  .cihw_reset = linuxdvb_ca_ops_reset,
+  .cihw_cam_is_ready = linuxdvb_ca_ops_cam_is_ready,
+  .cihw_pdu_write = linuxdvb_ca_ops_pdu_write,
+  .cihw_apdu_write = linuxdvb_ca_ops_apdu_write,
+  .cisw_appinfo = linuxdvb_ca_ops_appinfo,
+  .cisw_pcmcia_data_rate = linuxdvb_ca_ops_pcmcia_data_rate,
+  .cisw_caids = linuxdvb_ca_ops_caids,
+  .cisw_ca_close = linuxdvb_ca_ops_ca_close,
+  .cisw_menu = linuxdvb_ca_ops_menu,
+  .cisw_enquiry = linuxdvb_ca_ops_enquiry,
+  .cisw_close = linuxdvb_ca_ops_close,
+};
 
-  memset(&csi, 0, sizeof(csi));
+linuxdvb_transport_t *linuxdvb_transport_create
+  ( linuxdvb_adapter_t *la, int slots,
+    const char *ca_path, const char *ci_path )
+{
+  linuxdvb_transport_t *lcat;
+  char buf[32];
+  int r;
 
-  if ((ioctl(lca->lca_ca_fd, CA_GET_SLOT_INFO, &csi)) != 0) {
-    tvherror(LS_LINUXDVB, "%s: failed to get CAM slot %u info [e=%s]",
-                          lca->lca_name, csi.num, strerror(errno));
-  }
-  if (csi.flags & CA_CI_MODULE_READY)
-    state = CA_SLOT_STATE_MODULE_READY;
-  else if (csi.flags & CA_CI_MODULE_PRESENT)
-    state = CA_SLOT_STATE_MODULE_PRESENT;
-  else
-    state = CA_SLOT_STATE_EMPTY;
+  lcat = calloc(1, sizeof(*lcat));
 
-  lca->lca_state_str = ca_slot_state2str(state);
+  lcat->lcat_adapter = la;
+  lcat->lcat_ca_path  = strdup(ca_path);
+  lcat->lcat_ca_fd = -1;
 
-  if (lca->lca_state != state) {
-    tvhnotice(LS_LINUXDVB, "%s: CAM slot %u status changed to %s",
-                           lca->lca_name, csi.num, lca->lca_state_str);
-    idnode_notify_title_changed(&lca->lca_id);
-    lca->lca_state = state;
-  }
+  snprintf(buf, sizeof(buf), "dvbca%d", la->la_dvb_number);
+  lcat->lcat_name = strdup(buf);
 
-  if ((!lca->lca_en50221_thread_running) &&
-      (state == CA_SLOT_STATE_MODULE_READY)) {
-    lca->lca_en50221_thread_running = 1;
-    tvhthread_create(&lca->lca_en50221_thread, NULL,
-                     linuxdvb_ca_en50221_thread, lca, "lnxdvb-ca");
-  } else if (lca->lca_en50221_thread_running &&
-             (state != CA_SLOT_STATE_MODULE_READY)) {
-    lca->lca_en50221_thread_running = 0;
-    pthread_join(lca->lca_en50221_thread, NULL);
+  r = en50221_create_transport(&linuxdvb_ca_ops, lcat, slots,
+                               buf, &lcat->lcat_transport);
+  if (r < 0) {
+    tvherror(LS_EN50221, "unable to create transport for %s", ca_path);
+    return NULL;
   }
 
-  mtimer_arm_rel(&lca->lca_monitor_timer, linuxdvb_ca_monitor, lca, ms2mono(250));
-}
-
-linuxdvb_ca_t *
-linuxdvb_ca_create
-  ( htsmsg_t *conf, linuxdvb_adapter_t *la, int number, const char *ca_path,
-    const char *ci_path)
-{
-  linuxdvb_ca_t *lca;
-  char id[6];
-  const char *uuid = NULL;
-
-  lca = calloc(1, sizeof(linuxdvb_ca_t));
-  memset(lca, 0, sizeof(linuxdvb_ca_t));
-  lca->lca_number = number;
-  snprintf(lca->lca_name, sizeof(lca->lca_name), "dvbca%d", number);
-  lca->lca_ca_path  = strdup(ca_path);
-  lca->lca_ca_fd = -1;
-  lca->lca_capmt_interval = 100;
-  lca->lca_capmt_query_interval = 1200;
-
 #if ENABLE_DDCI
   if (ci_path)
-    lca->lddci = linuxdvb_ddci_create(lca, ci_path);
+    lcat->lddci = linuxdvb_ddci_create(lcat, ci_path);
 #endif
 
-  /* Internal config ID */
-  snprintf(id, sizeof(id), "ca%u", number);
-
-  if (conf)
-    conf = htsmsg_get_map(conf, id);
-  if (conf)
-    uuid = htsmsg_get_str(conf, "uuid");
-
-  if (idnode_insert(&lca->lca_id, uuid, &linuxdvb_ca_class, 0)) {
-    free(lca);
-    return NULL;
-  }
-
-  if (conf)
-    idnode_load(&lca->lca_id, conf);
-
-  /* Adapter link */
-  lca->lca_adapter = la;
-  LIST_INSERT_HEAD(&la->la_ca_devices, lca, lca_link);
-
-  TAILQ_INIT(&lca->lca_capmt_queue);
-
-  return lca;
+  LIST_INSERT_HEAD(&la->la_ca_transports, lcat, lcat_link);
+  pthread_mutex_lock(&linuxdvb_ca_mutex);
+  LIST_INSERT_HEAD(&linuxdvb_all_transports, lcat, lcat_all_link);
+  pthread_mutex_unlock(&linuxdvb_ca_mutex);
+  return lcat;
 }
 
-static void
-linuxdvb_ca_process_capmt_queue ( void *aux )
+void linuxdvb_transport_destroy ( linuxdvb_transport_t *lcat )
 {
-  linuxdvb_ca_t *lca = aux;
-  linuxdvb_ca_capmt_t *lcc;
-  struct section *section;
-  struct section_ext *result;
-  struct mpeg_pmt_section *pmt;
-  uint8_t capmt[4096];
-  int size, i;
+  linuxdvb_ca_t *ca;
 
-  lcc = TAILQ_FIRST(&lca->lca_capmt_queue);
-
-  if (!lcc)
+  if (lcat == NULL)
     return;
+  pthread_mutex_lock(&linuxdvb_ca_mutex);
+  LIST_REMOVE(lcat, lcat_all_link);
+  pthread_mutex_unlock(&linuxdvb_ca_mutex);
+  while ((ca = LIST_FIRST(&lcat->lcat_slots)) != NULL)
+    linuxdvb_ca_destroy(ca);
+  LIST_REMOVE(lcat, lcat_link);
+#if ENABLE_DDCI
+  linuxdvb_ddci_destroy(lcat->lddci);
+#endif
+  linuxdvb_ca_close_fd(lcat, 0);
+  en50221_transport_destroy(lcat->lcat_transport);
+  free(lcat->lcat_ca_path);
+  free(lcat->lcat_name);
+  free(lcat);
+}
 
-  if (!(section = section_codec(lcc->data, lcc->len))){
-    tvherror(LS_EN50221, "%s: failed to decode PMT section", lca->lca_name);
-    goto done;
-  }
-
-  if (!(result = section_ext_decode(section, 0))){
-    tvherror(LS_EN50221, "%s: failed to decode PMT ext_section", lca->lca_name);
-    goto done;
-  }
-
-  if (!(pmt = mpeg_pmt_section_codec(result))){
-    tvherror(LS_EN50221, "%s: failed to decode PMT", lca->lca_name);
-    goto done;
-  }
-
-  size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), 0,
-                               lcc->list_mgmt, lcc->cmd_id);
-
-  if (size < 0) {
-    tvherror(LS_EN50221, "%s: Failed to format CAPMT", lca->lca_name);
-  }
+void linuxdvb_transport_save
+  ( linuxdvb_transport_t *lcat, htsmsg_t *msg )
+{
+  linuxdvb_ca_t *lca;
+  LIST_FOREACH(lca, &lcat->lcat_slots, lca_link)
+    linuxdvb_ca_save(lca, msg);
+}
 
-  if (en50221_app_ca_pmt(lca->lca_ca_resource, lca->lca_ca_session_number,
-                         capmt, size)) {
-        tvherror(LS_EN50221, "%s: Failed to send CAPMT", lca->lca_name);
+static int linuxdvb_transport_refresh(linuxdvb_transport_t *lcat)
+{
+  linuxdvb_transport_t *lcat2;
+  linuxdvb_ca_t *lca;
+  int enabled = 0, r;
+  LIST_FOREACH(lca, &lcat->lcat_slots, lca_link)
+    if (lca->lca_enabled) {
+      enabled = 1;
+      break;
+    }
+  r = atomic_set(&lcat->lcat_enabled, enabled) != enabled;
+  if (r) {
+    if (enabled) {
+      mtimer_disarm(&linuxdvb_ca_thread_join_timer);
+      if (!linuxdvb_ca_threadid) {
+        linuxdvb_ca_thread_create();
+      } else {
+        tvh_write(linuxdvb_ca_pipe.wr, "e", 1);
+      }
+    } else {
+      LIST_FOREACH(lca, &lcat->lcat_slots, lca_link)
+        linuxdvb_ca_slot_change_state(lca, CA_SLOT_STATE_DISABLED);
+      if (linuxdvb_ca_threadid) {
+        LIST_FOREACH(lcat2, &linuxdvb_all_transports, lcat_all_link)
+          if (lcat2->lcat_enabled) {
+            enabled = 1;
+            break;
+          }
+        if (!enabled) {
+          lcat->lcat_close_time = mclk() + sec2mono(3);
+          mtimer_arm_rel(&linuxdvb_ca_thread_join_timer, linuxdvb_ca_thread_join_cb,
+                         NULL, sec2mono(5));
+        }
+      }
+    }
   }
+  return r;
+}
 
-  tvhtrace(LS_EN50221, "%s: %s CAPMT sent (%s)",
-                       lca->lca_name,
-                       ca_pmt_cmd_id2str(lcc->cmd_id),
-                       ca_pmt_list_mgmt2str(lcc->list_mgmt));
-  tvhlog_hexdump(LS_EN50221, capmt, size);
-
-done:
-  i = (lcc->cmd_id == CA_PMT_CMD_ID_QUERY) ?
-    lca->lca_capmt_query_interval : lca->lca_capmt_interval;
-
-  TAILQ_REMOVE(&lca->lca_capmt_queue, lcc, lcc_link);
-
-  free(lcc->data);
-  free(lcc);
+void linuxdvb_ca_init ( void )
+{
+}
 
-  if (!TAILQ_EMPTY(&lca->lca_capmt_queue)) {
-    mtimer_arm_rel(&lca->lca_capmt_queue_timer,
-                   linuxdvb_ca_process_capmt_queue, lca, ms2mono(i));
-  }
+void linuxdvb_ca_done ( void )
+{
+  linuxdvb_ca_thread_join();
 }
 
+/*
+ *
+ */
+
 void
-linuxdvb_ca_enqueue_capmt(linuxdvb_ca_t *lca, uint8_t slot, const uint8_t *ptr,
-                          int len, uint8_t list_mgmt, uint8_t cmd_id)
+linuxdvb_ca_enqueue_capmt
+  ( linuxdvb_ca_t *lca, const uint8_t *capmt, size_t capmtlen, int descramble )
 {
-  linuxdvb_ca_capmt_t *lcc;
-  int c = 1;
+  uint8_t *capmt2;
+  size_t capmtlen2;
 
-  if (!lca)
+  if (!lca || !capmt)
     return;
 
-  if (lca->lca_capmt_query && cmd_id == CA_PMT_CMD_ID_OK_DESCRAMBLING)
-    c = 2;
-
-  while (c--) {
-    lcc = calloc(1, sizeof(*lcc));
-
-    if (!lcc)
-      return;
-
-    lcc->data = malloc(len);
-    lcc->len = len;
-    lcc->slot = slot;
-    lcc->list_mgmt = list_mgmt;
-    lcc->cmd_id = (c ? CA_PMT_CMD_ID_QUERY : cmd_id);
-    memcpy(lcc->data, ptr, len);
-
-    TAILQ_INSERT_TAIL(&lca->lca_capmt_queue, lcc, lcc_link);
-
-    tvhtrace(LS_EN50221, "%s: %s CAPMT enqueued (%s)",
-                         lca->lca_name,
-                         ca_pmt_cmd_id2str(lcc->cmd_id),
-                         ca_pmt_list_mgmt2str(lcc->list_mgmt));
+  if (descramble && lca->lca_capmt_query &&
+      !en50221_capmt_build_query(capmt, capmtlen, &capmt2, &capmtlen2)) {
+    if (!linuxdvb_ca_write_cmd(lca, CA_WRITE_CMD_CAPMT_QUERY, capmt2, capmtlen2)) {
+      tvhtrace(LS_EN50221, "%s: CAPMT enqueued query (len %zd)", lca->lca_name, capmtlen2);
+      en50221_capmt_dump(LS_EN50221, lca->lca_name, capmt2, capmtlen2);
+      free(capmt2);
+    }
   }
 
-  mtimer_arm_rel(&lca->lca_capmt_queue_timer,
-                 linuxdvb_ca_process_capmt_queue, lca, ms2mono(50));
-}
-
-void linuxdvb_ca_save( linuxdvb_ca_t *lca, htsmsg_t *msg )
-{
-  char id[8], ubuf[UUID_HEX_SIZE];
-  htsmsg_t *m = htsmsg_create_map();
-
-  htsmsg_add_str(m, "uuid", idnode_uuid_as_str(&lca->lca_id, ubuf));
-  idnode_save(&lca->lca_id, m);
-
-  /* Add to list */
-  snprintf(id, sizeof(id), "ca%u", lca->lca_number);
-  htsmsg_add_msg(msg, id, m);
+  if (!linuxdvb_ca_write_cmd(lca, CA_WRITE_CMD_CAPMT, capmt, capmtlen)) {
+    tvhtrace(LS_EN50221, "%s: CAPMT enqueued (len %zd)", lca->lca_name, capmtlen);
+    en50221_capmt_dump(LS_EN50221, lca->lca_name, capmt, capmtlen);
+  }
 
+  tvh_write(linuxdvb_ca_pipe.wr, "m", 1);
 }
index 8ce293fe9d986d1b6f51d1e390c05acfed0d22c6..f1cefb6125c86bef12cf3fc71bbb89bf45848471 100644 (file)
@@ -96,7 +96,7 @@ typedef struct linuxdvb_ddci_rd_thread
 
 struct linuxdvb_ddci
 {
-  linuxdvb_ca_t             *lca;    /* back link to the associated CA */
+  linuxdvb_transport_t      *lcat;    /* back link to the associated CA transport */
   char                      *lddci_path;
   char                       lddci_id[6];
   int                        lddci_fdW;
@@ -745,14 +745,15 @@ linuxdvb_ddci_rd_thread_stop ( linuxdvb_ddci_rd_thread_t *ddci_rd_thread )
  *****************************************************************************/
 
 linuxdvb_ddci_t *
-linuxdvb_ddci_create ( linuxdvb_ca_t *lca, const char *ci_path)
+linuxdvb_ddci_create ( linuxdvb_transport_t *lcat, 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->lcat = lcat;
+  lddci->lddci_path = strdup(ci_path);
+  snprintf(lddci->lddci_id, sizeof(lddci->lddci_id), "ci%u",
+           lcat->lcat_adapter->la_dvb_number);
   lddci->lddci_fdW = -1;
   lddci->lddci_fdR = -1;
   linuxdvb_ddci_wr_thread_init(lddci);
@@ -763,6 +764,17 @@ linuxdvb_ddci_create ( linuxdvb_ca_t *lca, const char *ci_path)
   return lddci;
 }
 
+void
+linuxdvb_ddci_destroy ( linuxdvb_ddci_t *lddci )
+{
+  if (!lddci)
+    return;
+  tvhtrace(LS_DDCI, "destroy %s %s", lddci->lddci_id, lddci->lddci_path);
+  linuxdvb_ddci_close(lddci);
+  free(lddci->lddci_path);
+  free(lddci);
+}
+
 void
 linuxdvb_ddci_close ( linuxdvb_ddci_t *lddci )
 {
index 2e8730231edf7b252d72c9e7c195d87badbb2425..1ff5e6b6f7ea4701287acf5c38d3911603bcf64c 100644 (file)
@@ -269,7 +269,7 @@ linuxdvb_frontend_dvbs_class_satconf_set ( void *self, const void *str )
   if (lfe->lfe_satconf) {
     if (!strcmp(str, lfe->lfe_satconf->ls_type))
       return 0;
-    linuxdvb_satconf_delete(lfe->lfe_satconf, 1);
+    linuxdvb_satconf_destroy(lfe->lfe_satconf, 1);
   }
   conf = htsmsg_create_map();
   htsmsg_add_str(conf, "type", str);
@@ -2039,7 +2039,7 @@ linuxdvb_frontend_wizard_set( tvh_input_t *ti, htsmsg_t *conf, const char *lang
       htsmsg_add_msg(elems, NULL, elem);
       htsmsg_add_msg(conf, "elements", elems);
       if (lfe->lfe_satconf) {
-        linuxdvb_satconf_delete(lfe->lfe_satconf, 0);
+        linuxdvb_satconf_destroy(lfe->lfe_satconf, 0);
         lfe->lfe_satconf = NULL;
       }
       lfe->lfe_satconf = linuxdvb_satconf_create(lfe, conf);
@@ -2129,8 +2129,7 @@ linuxdvb_frontend_create
   lfe->lfe_number = number;
   lfe->lfe_type   = type;
   lfe->lfe_master = muuid ? strdup(muuid) : NULL;
-  strncpy(lfe->lfe_name, name, sizeof(lfe->lfe_name));
-  lfe->lfe_name[sizeof(lfe->lfe_name)-1] = '\0';
+  lfe->lfe_name   = strdup(name);
   lfe->lfe_ibuf_size = 188000;
   lfe->lfe_status_period = 1000;
   lfe->lfe_pids_max = 32;
@@ -2228,8 +2227,10 @@ linuxdvb_frontend_save ( linuxdvb_frontend_t *lfe, htsmsg_t *fe )
 }
 
 void
-linuxdvb_frontend_delete ( linuxdvb_frontend_t *lfe )
+linuxdvb_frontend_destroy ( linuxdvb_frontend_t *lfe )
 {
+  char *name;
+
   lock_assert(&global_lock);
 
   /* Ensure we're stopped */
@@ -2251,13 +2252,16 @@ linuxdvb_frontend_delete ( linuxdvb_frontend_t *lfe )
   free(lfe->lfe_dmx_path);
   free(lfe->lfe_dvr_path);
   free(lfe->lfe_master);
+  name = lfe->lfe_name;
 
   /* Delete satconf */
   if (lfe->lfe_satconf)
-    linuxdvb_satconf_delete(lfe->lfe_satconf, 0);
+    linuxdvb_satconf_destroy(lfe->lfe_satconf, 0);
 
   /* Finish */
   mpegts_input_delete((mpegts_input_t*)lfe, 0);
+
+  free(name);
 }
 
 /******************************************************************************
index efbc7be1a5564ab0a60649a450d677bc97e55ae7..f79a68981577be91f45f076d932939a26cc5d060 100644 (file)
 
 #if ENABLE_LINUXDVB
 #include <linux/dvb/version.h>
-
-#if ENABLE_LIBDVBEN50221
-#include <libdvben50221/en50221_session.h>
-#include <libdvben50221/en50221_app_utils.h>
-#include <libdvben50221/en50221_app_ai.h>
-#include <libdvben50221/en50221_app_rm.h>
-#include <libdvben50221/en50221_app_ca.h>
-#include <libdvben50221/en50221_app_dvb.h>
-#include <libdvben50221/en50221_app_datetime.h>
-#include <libdvben50221/en50221_app_smartcard.h>
-#include <libdvben50221/en50221_app_teletext.h>
-#include <libdvben50221/en50221_app_mmi.h>
-#include <libdvben50221/en50221_app_epg.h>
-#include <libdvben50221/en50221_app_auth.h>
-#include <libdvben50221/en50221_app_lowspeed.h>
-#include <libdvbapi/dvbca.h>
 #endif
+
+#if ENABLE_LINUXDVB_CA
+#include "../en50221/en50221.h"
+#include <linux/dvb/ca.h>
 #endif
 
 #define DVB_VER_INT(maj,min) (((maj) << 16) + (min))
@@ -52,8 +40,9 @@ typedef struct linuxdvb_hardware    linuxdvb_hardware_t;
 typedef struct linuxdvb_adapter     linuxdvb_adapter_t;
 typedef struct linuxdvb_frontend    linuxdvb_frontend_t;
 #if ENABLE_LINUXDVB_CA
+typedef struct linuxdvb_transport   linuxdvb_transport_t;
 typedef struct linuxdvb_ca          linuxdvb_ca_t;
-typedef struct linuxdvb_ca_capmt    linuxdvb_ca_capmt_t;
+typedef struct linuxdvb_ca_write    linuxdvb_ca_write_t;
 #endif
 #if ENABLE_DDCI
 typedef struct linuxdvb_ddci        linuxdvb_ddci_t;
@@ -68,7 +57,7 @@ typedef struct linuxdvb_en50494     linuxdvb_en50494_t;
 typedef LIST_HEAD(,linuxdvb_hardware) linuxdvb_hardware_list_t;
 typedef TAILQ_HEAD(linuxdvb_satconf_ele_list,linuxdvb_satconf_ele) linuxdvb_satconf_ele_list_t;
 #if ENABLE_LINUXDVB_CA
-typedef TAILQ_HEAD(linuxdvb_ca_capmt_queue,linuxdvb_ca_capmt) linuxdvb_ca_capmt_queue_t;
+typedef TAILQ_HEAD(linuxdvb_ca_write_queue,linuxdvb_ca_write) linuxdvb_ca_write_queue_t;
 #endif
 
 struct linuxdvb_adapter
@@ -92,7 +81,7 @@ struct linuxdvb_adapter
    *  CA devices
    */
 #if ENABLE_LINUXDVB_CA
-  LIST_HEAD(,linuxdvb_ca) la_ca_devices;
+  LIST_HEAD(,linuxdvb_transport) la_ca_transports;
 #endif
   /*
   * Functions
@@ -115,7 +104,7 @@ struct linuxdvb_frontend
    */
   int                       lfe_number;
   dvb_fe_type_t             lfe_type;
-  char                      lfe_name[128];
+  char                     *lfe_name;
   char                     *lfe_fe_path;
   char                     *lfe_dmx_path;
   char                     *lfe_dvr_path;
@@ -170,72 +159,82 @@ struct linuxdvb_frontend
 };
 
 #if ENABLE_LINUXDVB_CA
-struct linuxdvb_ca
+struct linuxdvb_transport
 {
-  idnode_t                     lca_id;
+  LIST_ENTRY(linuxdvb_transport)  lcat_link;
+  LIST_ENTRY(linuxdvb_transport)  lcat_all_link;
+  en50221_transport_t            *lcat_transport;
+  LIST_HEAD(, linuxdvb_ca)        lcat_slots;
+  char                           *lcat_name;
+
   /*
    * Adapter
    */
-  linuxdvb_adapter_t          *lca_adapter;
-  LIST_ENTRY(linuxdvb_ca)      lca_link;
+  linuxdvb_adapter_t             *lcat_adapter;
+  int                             lcat_number;
+  char                           *lcat_ca_path;
+  int                             lcat_ca_fd;
+  int                             lcat_fatal;
+  int                             lcat_enabled;
+  int64_t                         lcat_fatal_time;
+  int64_t                         lcat_close_time;
+
+#if ENABLE_DDCI
+  linuxdvb_ddci_t                *lddci;
+#endif
+};
+
+struct linuxdvb_ca
+{
+  idnode_t                  lca_id;
+
+  /*
+   * Transport
+   */
+  LIST_ENTRY(linuxdvb_ca)   lca_link;
+  LIST_ENTRY(linuxdvb_ca)   lca_all_link;
+  linuxdvb_transport_t     *lca_transport;
 
   /*
    * CA handling
    */
-  int                       lca_ca_fd;
   int                       lca_enabled;
+  int                       lca_phys_layer;
   int                       lca_high_bitrate_mode;
   int                       lca_capmt_query;
-  mtimer_t                  lca_monitor_timer;
-  mtimer_t                  lca_capmt_queue_timer;
   int                       lca_capmt_interval;
   int                       lca_capmt_query_interval;
+  int64_t                   lca_capmt_blocked;
   pthread_t                 lca_en50221_thread;
   int                       lca_en50221_thread_running;
 
   /*
    * EN50221
    */
-  struct en50221_transport_layer    *lca_tl;
-  struct en50221_session_layer      *lca_sl;
-  struct en50221_app_send_functions  lca_sf;
-  struct en50221_app_rm             *lca_rm_resource;
-  struct en50221_app_ai             *lca_ai_resource;
-  struct en50221_app_ca             *lca_ca_resource;
-  struct en50221_app_datetime       *lca_dt_resource;
-  struct en50221_app_mmi            *lca_mmi_resource;
-  int                                lca_tc;
-  int                                lca_ai_version;
-  uint16_t                           lca_ai_session_number;
-  uint16_t                           lca_ca_session_number;
 
   /*
    * CA info
    */
-  int                      lca_number;
-  char                     lca_name[128];
-  char                     *lca_ca_path;
-  int                      lca_state;
-  const char               *lca_state_str;
-  linuxdvb_ca_capmt_queue_t lca_capmt_queue;
-#if ENABLE_DDCI
-  linuxdvb_ddci_t          *lddci;
-#endif
+  int                       lca_adapnum;
+  uint8_t                   lca_slotnum;
+  char                     *lca_name;
+  int                       lca_state;
+  char                     *lca_modulename;
+  linuxdvb_ca_write_queue_t lca_write_queue;
 
   /*
    * CAM module info
    */
-  char                     lca_cam_menu_string[64];
-  int                      lca_pin_reply;
-  char                    *lca_pin_str;
-  char                    *lca_pin_match_str;
+  int                       lca_pin_reply;
+  char                     *lca_pin_str;
+  char                     *lca_pin_match_str;
 };
 #endif
 
 struct linuxdvb_satconf
 {
-  idnode_t              ls_id;
-  const char           *ls_type;
+  idnode_t               ls_id;
+  const char            *ls_type;
 
   /*
    * MPEG-TS hooks
@@ -417,7 +416,7 @@ linuxdvb_frontend_create
 
 void linuxdvb_frontend_save ( linuxdvb_frontend_t *lfe, htsmsg_t *m );
 
-void linuxdvb_frontend_delete ( linuxdvb_frontend_t *lfe );
+void linuxdvb_frontend_destroy ( linuxdvb_frontend_t *lfe );
 
 void linuxdvb_frontend_add_network
   ( linuxdvb_frontend_t *lfe, linuxdvb_network_t *net );
@@ -433,23 +432,29 @@ int linuxdvb2tvh_delsys ( int delsys );
 
 #if ENABLE_LINUXDVB_CA
 
-linuxdvb_ca_t *
-linuxdvb_ca_create
-  ( htsmsg_t *conf, linuxdvb_adapter_t *la, int number, const char *ca_path,
-    const char *ci_path );
+linuxdvb_transport_t *
+linuxdvb_transport_create( linuxdvb_adapter_t *la, int number,
+                           const char *ca_path, const char *ci_path );
+void linuxdvb_transport_destroy( linuxdvb_transport_t *lcat );
+void linuxdvb_transport_save( linuxdvb_transport_t *lcat, htsmsg_t *m );
 
-void linuxdvb_ca_save( linuxdvb_ca_t *lca, htsmsg_t *m );
+linuxdvb_ca_t * linuxdvb_ca_create
+  ( htsmsg_t *conf, linuxdvb_transport_t *lcat, int slotnum );
 
-void
-linuxdvb_ca_enqueue_capmt(linuxdvb_ca_t *lca, uint8_t slot, const uint8_t *ptr,
-                          int len, uint8_t list_mgmt, uint8_t cmd_id);
+void linuxdvb_ca_enqueue_capmt
+  (linuxdvb_ca_t *lca, const uint8_t *capmt, size_t capmtlen, int descramble);
+
+void linuxdvb_ca_init(void);
+void linuxdvb_ca_done(void);
 
 #endif
 
 #if ENABLE_DDCI
 
 linuxdvb_ddci_t *
-linuxdvb_ddci_create ( linuxdvb_ca_t *lca, const char *ci_path);
+linuxdvb_ddci_create ( linuxdvb_transport_t *lcat, const char *ci_path);
+void
+linuxdvb_ddci_destroy ( linuxdvb_ddci_t *lddci );
 int
 linuxdvb_ddci_open ( linuxdvb_ddci_t *lddci );
 void
@@ -547,7 +552,7 @@ htsmsg_t *linuxdvb_satconf_type_list ( void *o, const char *lang );
 linuxdvb_satconf_t *linuxdvb_satconf_create
   ( linuxdvb_frontend_t *lfe, htsmsg_t *conf );
 
-void linuxdvb_satconf_delete ( linuxdvb_satconf_t *ls, int delconf );
+void linuxdvb_satconf_destroy ( linuxdvb_satconf_t *ls, int delconf );
 
 int linuxdvb_satconf_get_priority
   ( linuxdvb_satconf_t *ls, mpegts_mux_t *mm );
index 3c78e40291cad6499a6125157faeecf58cddd171..d62b7ab2b22b3e54fd1744f5174e5982db266e3d 100644 (file)
@@ -1612,7 +1612,7 @@ linuxdvb_satconf_ele_create0
 }
 
 void
-linuxdvb_satconf_delete ( linuxdvb_satconf_t *ls, int delconf )
+linuxdvb_satconf_destroy ( linuxdvb_satconf_t *ls, int delconf )
 {
   linuxdvb_satconf_ele_t *lse, *nxt;
   char ubuf[UUID_HEX_SIZE];
index ad5f911b661b4d874e12cec67cc1f912a1fa31ab..f28d19e4499d9e972a1165e22afc7287c40afc74 100644 (file)
@@ -75,6 +75,9 @@
 #include "packet.h"
 #include "streaming.h"
 #include "memoryinfo.h"
+#if CONFIG_LINUXDVB_CA
+#include "input/mpegts/en50221/en50221.h"
+#endif
 
 #ifdef PLATFORM_LINUX
 #include <sys/prctl.h>
@@ -1195,6 +1198,10 @@ main(int argc, char **argv)
   tvhthread_create(&mtimer_tick_tid, NULL, mtimer_tick_thread, NULL, "mtick");
   tvhthread_create(&tasklet_tid, NULL, tasklet_thread, NULL, "tasklet");
 
+#if CONFIG_LINUXDVB_CA
+  en50221_register_apps();
+#endif
+
   tvhftrace(LS_MAIN, streaming_init);
   tvhftrace(LS_MAIN, tvh_hardware_init);
   tvhftrace(LS_MAIN, dbus_server_init, opt_dbus, opt_dbus_session);
index 8f6dc19c9ae2090b235df6f8366594531304c0a5..49edfb522e442b4bdb977c7ebdf61ba4c41aaa40 100644 (file)
@@ -681,12 +681,6 @@ extern void scopedunlock(pthread_mutex_t **mtxp);
      char *tvh_b = alloca(tvh_l + 1); \
      memcpy(tvh_b, n, tvh_l + 1); })
 
-#define tvh_strlen(s) ((s) ? strlen(s) : 0)
-
-#define tvh_strlcatf(buf, size, ptr, fmt...) \
-  do { int __r = snprintf((buf) + ptr, (size) - ptr, fmt); \
-       ptr = __r >= (size) - ptr ? (size) - 1 : ptr + __r; } while (0)
-
 static inline const char *tvh_strbegins(const char *s1, const char *s2)
 {
   while(*s2)
index d1114350022934344e791b0e6821b6c84229f1a6..498cf3cd50cbb6f5ccb7c12cf806d745b5baff43 100644 (file)
@@ -243,6 +243,12 @@ static inline void tvhtrace_no_warnings(const char *fmt, ...) { (void)fmt; }
   abort(); \
 } while (0)
 
+#define tvh_strlen(s) ((s) ? strlen(s) : 0)
+
+#define tvh_strlcatf(buf, size, ptr, fmt...) \
+  do { int __r = snprintf((buf) + ptr, (size) - ptr, fmt); \
+       ptr = __r >= (size) - ptr ? (size) - 1 : ptr + __r; } while (0)
+
 void tvhlog_backtrace_printf(const char *fmt, ...);
 
 #endif /* __TVH_LOGGING_H__ */