]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
linuxdvb_ca: improve CAPMT sending to CAM, support multiple services
authorDamjan Marion <damjan.marion@gmail.com>
Mon, 18 May 2015 11:16:20 +0000 (13:16 +0200)
committerJaroslav Kysela <perex@perex.cz>
Mon, 18 May 2015 18:56:16 +0000 (20:56 +0200)
src/descrambler/dvbcam.c
src/input/mpegts/linuxdvb/linuxdvb_ca.c
src/input/mpegts/linuxdvb/linuxdvb_private.h

index 03eaa4978be3b008617d0cf956e567befb4f63a0..360e87c58c5ac9a80df4e16681e74dba7322971f 100644 (file)
@@ -38,16 +38,17 @@ typedef struct dvbcam_active_service {
   uint8_t              slot;
 } dvbcam_active_service_t;
 
-typedef struct dvbcam_active_caid {
-  TAILQ_ENTRY(dvbcam_active_caid) link;
+typedef struct dvbcam_active_cam {
+  TAILQ_ENTRY(dvbcam_active_cam) link;
   uint16_t             caids[CAIDS_PER_CA_SLOT];
   int                  num_caids;
   linuxdvb_ca_t       *ca;
   uint8_t              slot;
-} dvbcam_active_caid_t;
+  int                  active_programs;
+} dvbcam_active_cam_t;
 
 TAILQ_HEAD(,dvbcam_active_service) dvbcam_active_services;
-TAILQ_HEAD(,dvbcam_active_caid) dvbcam_active_caids;
+TAILQ_HEAD(,dvbcam_active_cam) dvbcam_active_cams;
 
 pthread_mutex_t dvbcam_mutex;
 
@@ -55,11 +56,14 @@ void
 dvbcam_register_cam(linuxdvb_ca_t * lca, uint8_t slot, uint16_t * caids,
                     int num_caids)
 {
-  dvbcam_active_caid_t *ac;
+  dvbcam_active_cam_t *ac;
+
+  tvhtrace("dvbcam", "register cam ca %p slot %u num_caids %u",
+           lca, slot, num_caids);
 
   num_caids = MIN(CAIDS_PER_CA_SLOT, num_caids);
 
-  if ((ac = malloc(sizeof(*ac))) == NULL)
+  if ((ac = calloc(1, sizeof(*ac))) == NULL)
        return;
 
   ac->ca = lca;
@@ -69,7 +73,7 @@ dvbcam_register_cam(linuxdvb_ca_t * lca, uint8_t slot, uint16_t * caids,
 
   pthread_mutex_lock(&dvbcam_mutex);
 
-  TAILQ_INSERT_TAIL(&dvbcam_active_caids, ac, link);
+  TAILQ_INSERT_TAIL(&dvbcam_active_cams, ac, link);
 
   pthread_mutex_unlock(&dvbcam_mutex);
 }
@@ -77,9 +81,11 @@ dvbcam_register_cam(linuxdvb_ca_t * lca, uint8_t slot, uint16_t * caids,
 void
 dvbcam_unregister_cam(linuxdvb_ca_t * lca, uint8_t slot)
 {
-  dvbcam_active_caid_t *ac, *ac_tmp;
+  dvbcam_active_cam_t *ac, *ac_tmp;
   dvbcam_active_service_t *as;
 
+  tvhtrace("dvbcam", "unregister cam lca %p slot %u", lca, slot);
+
   pthread_mutex_lock(&dvbcam_mutex);
 
   /* remove pointer to this CAM in all active services */
@@ -88,10 +94,10 @@ dvbcam_unregister_cam(linuxdvb_ca_t * lca, uint8_t slot)
       as->ca = NULL;
 
   /* delete entry */
-  for (ac = TAILQ_FIRST(&dvbcam_active_caids); ac != NULL; ac = ac_tmp) {
+  for (ac = TAILQ_FIRST(&dvbcam_active_cams); ac != NULL; ac = ac_tmp) {
     ac_tmp = TAILQ_NEXT(ac, link);
     if(ac && ac->ca == lca && ac->slot == slot) {
-      TAILQ_REMOVE(&dvbcam_active_caids, ac, link);
+      TAILQ_REMOVE(&dvbcam_active_cams, ac, link);
       free(ac);
     }
   }
@@ -103,64 +109,87 @@ void
 dvbcam_pmt_data(mpegts_service_t *s, const uint8_t *ptr, int len)
 {
   linuxdvb_frontend_t *lfe;
-  dvbcam_active_caid_t *ac;
+  dvbcam_active_cam_t *ac = NULL, *ac2;
   dvbcam_active_service_t *as = NULL, *as2;
   elementary_stream_t *st;
   caid_t *c;
+  uint8_t list_mgmt;
+  int is_update = 0;
   int i;
 
-       lfe = (linuxdvb_frontend_t*) s->s_dvb_active_input;
+  lfe = (linuxdvb_frontend_t*) s->s_dvb_active_input;
 
   if (!lfe)
     return;
 
   pthread_mutex_lock(&dvbcam_mutex);
 
+  /* find this service in the list of active services */
   TAILQ_FOREACH(as2, &dvbcam_active_services, link)
     if (as2->t == (service_t *) s) {
       as = as2;
       break;
     }
 
-  pthread_mutex_unlock(&dvbcam_mutex);
-
-  if (!as)
-       return;
+  if (!as) {
+    tvhtrace("dvbcam", "cannot find active service entry");
+       goto done;
+  }
 
-  if(as->last_pmt)
+  if(as->last_pmt) {
     free(as->last_pmt);
+    is_update = 1;
+  }
 
   as->last_pmt = malloc(len + 3);
   memcpy(as->last_pmt, ptr-3, len + 3);
   as->last_pmt_len = len + 3;
-  as->ca = NULL;
 
-  pthread_mutex_lock(&dvbcam_mutex);
+  /*if this is update just send updated CAPMT to CAM */
+  if (is_update) {
 
-  /* check all ellementary streams for CAIDs, if any send PMT to CAM */
+    tvhtrace("dvbcam", "CAPMT sent to CAM (update)");
+    list_mgmt = CA_LIST_MANAGEMENT_UPDATE;
+    goto enqueue;
+  }
+
+  /* check all ellementary streams for CAIDs and find CAM */
   TAILQ_FOREACH(st, &s->s_components, es_link) {
     LIST_FOREACH(c, &st->es_caids, link) {
-      TAILQ_FOREACH(ac, &dvbcam_active_caids, link) {
-        for(i=0;i<ac->num_caids;i++) {
-          if(ac->ca && ac->ca->lca_adapter == lfe->lfe_adapter &&
-             ac->caids[i] == c->caid)
+      TAILQ_FOREACH(ac2, &dvbcam_active_cams, link) {
+        for(i=0;i<ac2->num_caids;i++) {
+          if(ac2->ca && ac2->ca->lca_adapter == lfe->lfe_adapter &&
+             ac2->caids[i] == c->caid)
           {
-            as->ca = ac->ca;
-            as->slot = ac->slot;
-            break;
+            as->ca = ac2->ca;
+            as->slot = ac2->slot;
+            ac = ac2;
+            goto end_of_search_for_cam;
           }
         }
       }
     }
   }
 
-  pthread_mutex_unlock(&dvbcam_mutex);
-
-  /* this service doesn't have assigned CAM */
-  if (!as->ca)
-    return;
+end_of_search_for_cam:
 
-  linuxdvb_ca_send_capmt(as->ca, as->slot, as->last_pmt, as->last_pmt_len);
+  if (!ac) {
+    tvhtrace("dvbcam", "cannot find active cam entry");
+    goto done;
+  }
+  tvhtrace("dvbcam", "found active cam entry");
+
+  if (ac->active_programs++)
+    list_mgmt = CA_LIST_MANAGEMENT_ADD;
+  else
+    list_mgmt = CA_LIST_MANAGEMENT_ONLY;
+
+enqueue:
+  if (as->ca)
+    linuxdvb_ca_enqueue_capmt(as->ca, as->slot, as->last_pmt, as->last_pmt_len,
+                              list_mgmt, CA_PMT_CMD_ID_OK_DESCRAMBLING);
+done:
+  pthread_mutex_unlock(&dvbcam_mutex);
 }
 
 void
@@ -168,11 +197,13 @@ dvbcam_service_start(service_t *t)
 {
   dvbcam_active_service_t *as;
 
+  tvhtrace("dvbcam", "start service %p", t);
+
   TAILQ_FOREACH(as, &dvbcam_active_services, link)
     if (as->t == t)
       return;
 
-  if ((as = malloc(sizeof(*as))) == NULL)
+  if ((as = calloc(1, sizeof(*as))) == NULL)
     return;
 
   as->t = t;
@@ -188,19 +219,37 @@ void
 dvbcam_service_stop(service_t *t)
 {
        dvbcam_active_service_t *as, *as_tmp;
+  linuxdvb_ca_t *ca = NULL;
+  dvbcam_active_cam_t *ac2;
+  uint8_t slot;
+
+  tvhtrace("dvbcam", "stop service %p", t);
 
   pthread_mutex_lock(&dvbcam_mutex);
 
   for (as = TAILQ_FIRST(&dvbcam_active_services); as != NULL; as = as_tmp) {
     as_tmp = TAILQ_NEXT(as, link);
     if(as && as->t == t) {
-      TAILQ_REMOVE(&dvbcam_active_services, as, link);
-      if(as->last_pmt)
+      if(as->last_pmt) {
+        linuxdvb_ca_enqueue_capmt(as->ca, as->slot, as->last_pmt,
+                                  as->last_pmt_len,
+                                  CA_LIST_MANAGEMENT_UPDATE,
+                                  CA_PMT_CMD_ID_NOT_SELECTED);
         free(as->last_pmt);
+      }
+      slot = as->slot;
+      ca = as->ca;
+      TAILQ_REMOVE(&dvbcam_active_services, as, link);
       free(as);
     }
   }
 
+  if (ca)
+    TAILQ_FOREACH(ac2, &dvbcam_active_cams, link)
+      if (ac2->slot == slot && ac2->ca == ca) {
+        ac2->active_programs--;
+      }
+
   pthread_mutex_unlock(&dvbcam_mutex);
 }
 
@@ -209,7 +258,7 @@ dvbcam_init(void)
 {
   pthread_mutex_init(&dvbcam_mutex, NULL);
   TAILQ_INIT(&dvbcam_active_services);
-  TAILQ_INIT(&dvbcam_active_caids);
+  TAILQ_INIT(&dvbcam_active_cams);
 }
 
 #endif /* ENABLE_LINUXDVB_CA */
index 4b65d4ebecd0f120f6e23fd46007567fa3c0a886..bb6a54788b1628c1d39a7fa8a6d6a2c14749aa62 100644 (file)
@@ -53,6 +53,41 @@ ca_slot_state2str(ca_slot_state_t v)
   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";
+}
+
+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;
+  int      len;
+  uint8_t *data;
+  uint8_t  slot;
+  uint8_t  list_mgmt;
+  uint8_t  cmd_id;
+};
+
 /*
  *  ts101699 and CI+ 1.3 definitions
  */
@@ -733,48 +768,46 @@ linuxdvb_ca_create
   lca->lca_adapter = la;
   LIST_INSERT_HEAD(&la->la_ca_devices, lca, lca_link);
 
+  TAILQ_INIT(&lca->lca_capmt_queue);
+
   gtimer_arm_ms(&lca->lca_monitor_timer, linuxdvb_ca_monitor, lca, 250);
 
   return lca;
 }
 
-void
-linuxdvb_ca_send_capmt(linuxdvb_ca_t *lca, uint8_t slot, const uint8_t *ptr, int len)
+static void
+linuxdvb_ca_process_capmt_queue ( void *aux )
 {
+  linuxdvb_ca_t *lca = aux;
+  linuxdvb_ca_capmt_t *lcc;
   struct section *section;
   struct section_ext *result;
   struct mpeg_pmt_section *pmt;
-  uint8_t *buffer;
   uint8_t capmt[4096];
   int size;
 
-  buffer = malloc(len);
-  if (!buffer)
-    return;
+  lcc = TAILQ_FIRST(&lca->lca_capmt_queue);
 
-  memcpy(buffer, ptr, len);
+  if (!lcc)
+    return;
 
-  section = section_codec(buffer, len);
-  if (!section){
+  if (!(section = section_codec(lcc->data, lcc->len))){
     tvherror("en50221", "failed to decode PMT section");
-    goto fail;
+    goto done;
   }
 
-  result = section_ext_decode(section, 0);
-  if (!result){
+  if (!(result = section_ext_decode(section, 0))){
     tvherror("en50221", "failed to decode PMT ext_section");
-    goto fail;
+    goto done;
   }
 
-  pmt = mpeg_pmt_section_codec(result);
-  if (!pmt){
+  if (!(pmt = mpeg_pmt_section_codec(result))){
     tvherror("en50221", "failed to decode PMT");
-    goto fail;
+    goto done;
   }
 
-  size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), 1,
-                               CA_LIST_MANAGEMENT_ONLY,
-                               CA_PMT_CMD_ID_OK_DESCRAMBLING);
+  size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), 0,
+                               lcc->list_mgmt, lcc->cmd_id);
 
   if (size < 0) {
     tvherror("en50221", "Failed to format CAPMT");
@@ -785,11 +818,51 @@ linuxdvb_ca_send_capmt(linuxdvb_ca_t *lca, uint8_t slot, const uint8_t *ptr, int
         tvherror("en50221", "Failed to send CAPMT");
   }
 
-  tvhtrace("en50221", "OK Descrambling CAPMT sent");
+  tvhtrace("en50221", "%s CAPMT sent (%s)", ca_pmt_cmd_id2str(lcc->cmd_id),
+           ca_pmt_list_mgmt2str(lcc->list_mgmt));
   tvhlog_hexdump("en50221", capmt, size);
 
-fail:
-  free(buffer);
+done:
+
+  TAILQ_REMOVE(&lca->lca_capmt_queue, lcc, lcc_link);
+
+  free(lcc->data);
+  free(lcc);
+
+  if (!TAILQ_EMPTY(&lca->lca_capmt_queue)) {
+    gtimer_arm_ms(&lca->lca_capmt_queue_timer,
+                  linuxdvb_ca_process_capmt_queue, lca, 1000);
+  }
+}
+
+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_capmt_t *lcc;
+
+  if (!lca)
+    return;
+
+  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 = cmd_id;
+  memcpy(lcc->data, ptr, len);
+
+  TAILQ_INSERT_TAIL(&lca->lca_capmt_queue, lcc, lcc_link);
+
+  tvhtrace("en50221", "%s CAPMT enqueued (%s)", ca_pmt_cmd_id2str(lcc->cmd_id),
+           ca_pmt_list_mgmt2str(lcc->list_mgmt));
+
+  gtimer_arm_ms(&lca->lca_capmt_queue_timer,
+                linuxdvb_ca_process_capmt_queue, lca, 50);
 }
 
 void linuxdvb_ca_save( linuxdvb_ca_t *lca, htsmsg_t *msg )
index bed7fe4ada28cfa07df2ad585891233c354af324..b88ae94cecfaedb7493d78c3ba99f3d8c320f5cc 100644 (file)
@@ -53,6 +53,7 @@ typedef struct linuxdvb_adapter     linuxdvb_adapter_t;
 typedef struct linuxdvb_frontend    linuxdvb_frontend_t;
 #if ENABLE_LINUXDVB_CA
 typedef struct linuxdvb_ca          linuxdvb_ca_t;
+typedef struct linuxdvb_ca_capmt    linuxdvb_ca_capmt_t;
 #endif
 typedef struct linuxdvb_satconf     linuxdvb_satconf_t;
 typedef struct linuxdvb_satconf_ele linuxdvb_satconf_ele_t;
@@ -63,6 +64,9 @@ 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;
+#endif
 
 struct linuxdvb_adapter
 {
@@ -172,6 +176,7 @@ struct linuxdvb_ca
   int                       lca_enabled;
   int                       lca_high_bitrate_mode;
   gtimer_t                  lca_monitor_timer;
+  gtimer_t                  lca_capmt_queue_timer;
   pthread_t                 lca_en50221_thread;
   int                       lca_en50221_thread_running;
 
@@ -199,6 +204,7 @@ struct linuxdvb_ca
   char                     *lca_ca_path;
   int                      lca_state;
   const char               *lca_state_str;
+  linuxdvb_ca_capmt_queue_t lca_capmt_queue;
   /*
    * CAM module info
    */
@@ -374,7 +380,8 @@ linuxdvb_ca_create
 void linuxdvb_ca_save( linuxdvb_ca_t *lca, htsmsg_t *m );
 
 void
-linuxdvb_ca_send_capmt(linuxdvb_ca_t *lca, uint8_t slot, const uint8_t *ptr, int len);
+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);
 
 #endif