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