"liburiparser-dev",
"libpcre3-dev",
"python",
- "dvb-apps",
"debhelper",
"ccache",
"libx11-xcb-dev"
"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"
],
"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"
],
# 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)
"bundle:no"
"pngquant:no"
"dvbcsa:no"
- "dvben50221:auto"
"kqueue:no"
"dbus_1:auto"
"android:no"
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);
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)
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
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
#
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
#include <stdint.h>
#include <time.h>
+typedef void * volatile * atomic_refptr_t;
+
extern pthread_mutex_t atomic_lock;
/*
#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
*/
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)
*/
#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
}
}
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;
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);
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);
#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;
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;
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;
}
*
*/
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)
{
extern const idclass_t linuxdvb_frontend_class;
linuxdvb_frontend_t *lfe = NULL;
+ linuxdvb_transport_t *lcat;
int i;
if (ac->ca == NULL)
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++)
{
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);
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;
}
/* 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);
{
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;
}
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
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;
}
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)
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)",
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;
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);
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) {
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 &&
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
#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;
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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
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];
/* 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
{
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;
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;
}
{
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;
}
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;
#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;
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) */
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 */
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;
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);
#if ENABLE_LINUXDVB_CA
idclass_register(&linuxdvb_ca_class);
+ linuxdvb_ca_init();
#endif
/* Install monitor on /dev */
linuxdvb_adapter_del(la->la_rootpath);
}
}
+#if ENABLE_LINUXDVB_CA
+ linuxdvb_ca_done();
+#endif
pthread_mutex_unlock(&global_lock);
}
- /*
+/*
* 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
#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
( 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 *
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",
.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,
.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,
.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);
}
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;
*****************************************************************************/
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);
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 )
{
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);
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);
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;
}
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 */
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);
}
/******************************************************************************
#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))
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;
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
* CA devices
*/
#if ENABLE_LINUXDVB_CA
- LIST_HEAD(,linuxdvb_ca) la_ca_devices;
+ LIST_HEAD(,linuxdvb_transport) la_ca_transports;
#endif
/*
* Functions
*/
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;
};
#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
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 );
#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
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 );
}
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];
#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>
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);
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)
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__ */