From 66ab78a40f5b57e20142a33484e32c785a0c4017 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 7 Jan 2007 18:00:00 +0100 Subject: Version 1.5.0 - The CAM handling has been refactored. Instead of a cCiHandler per device there is now an abstract cCiAdapter and a cCamSlot. This allows each slot to be accessed individually. - The general 15 seconds workaround time before opening the CAM menu has been removed. If the CAM menu doesn't open within a timeout, the enter menu command is now sent again. - If a CAM is reset or pulled and reinserted, it now automatically starts decrypting the current channel again. - The Setup/CAM menu now dynamically refreshes its items and displays whether a CAM is present or ready. The 'Reset' function no longer leaves the menu. - The CAM menu will now be openend when pressing the Ok key on a slot entry. - The CAM menu now stays within the current menu context and doesn't close and reopen the menu every time an option is selected. - When an encrypted channel is switched to for the first time, VDR now checks explicitly whether a CAM can actually decrypt that channel. If there is more than one CAM in the system that claims to be able to decrypt the channel, they are all tried in turn. To make this possible, an encrypted channel needs to be received in Transfer Mode when it is switched to for the first time, so that VDR can determine whether the TS packets are actually decrypted. Once a channel is known to be decrypted by a particular CAM, the next time it is switched to it will be shown in normal live viewing mode. - A cDevice now automatically detaches all cReceiver objects that receive PIDs that can't be decrypted with the current CAM. A plugin that attaches a cReceiver to a device should therefore watch the receiver's IsAttached() function to see if it is still attached to the device. - The cReceiver constructor no longer takes an 'int Ca' as its first parameter, but rather a 'tChannelID ChannelID'. This is necessary for the device to be able to determine which CAM a particular channel can be decrypted with. If the channel is known to be unencrypted, or a plugin doesn't want to provide the channel id for other reasons, an invalid tChannelID() can be given. - The cThread::Start() function now waits until a previous incarnation of this thread has actually stopped. Before this it could happen that a thread's Cancel(-1) function was called and immediately after that it was started again, but the Start() function still found it to be 'active'. - The parameter NeedsDetachReceivers in cDevice::GetDevice(const cChannel *Channel, ...) has been removed. A call to this function will automatically detach all receivers from the device if it returns a non-NULL pointer. - The cTimeMs class now accepts an initial timeout value in its constructor. - A CAM is now explicitly instructed to stop decrypting when switching away from an encrypted channel. - If the CAM in use can decrypt several channels at the same time, VDR can now make use if this capability. Whether or not a CAM can decrypt more than one channel is determined by sending it an initial empty QUERY command and testing whether it replies to it. - Ca values in the range 0...F in channels.conf can still be used to assign a channel to a particular device, but this will no longer work with encrypted channels because without valid CA ids VDR can't decide which CAM slot to use. However, since VDR now automatically determines which CAM can decrypt which channel, setting fixed channel/device relations should no longer be necessary. IF AN ENCRYPTED CHANNEL CAN'T BE DECRYPTED AND YOU HAVE A CA VALUE IN THE RANGE 0...F FOR THAT CHANNEL, SET IT TO 0 (FTA) AND TUNE TO THE CHANNEL AGAIN. --- HISTORY | 53 ++ MANUAL | 16 +- Makefile | 4 +- PLUGINS.html | 47 +- channels.h | 3 +- ci.c | 1742 ++++++++++++++++++++++++++++++++-------------------------- ci.h | 247 ++++++--- config.h | 10 +- device.c | 276 ++++++---- device.h | 51 +- dvbci.c | 108 ++++ dvbci.h | 31 ++ dvbdevice.c | 77 ++- dvbdevice.h | 10 +- i18n.c | 200 ++++--- menu.c | 416 +++++++------- menu.h | 28 +- osdbase.c | 23 +- osdbase.h | 11 +- pat.c | 14 +- pat.h | 4 +- receiver.c | 6 +- receiver.h | 19 +- recorder.c | 10 +- recorder.h | 8 +- thread.c | 30 +- thread.h | 3 +- tools.c | 6 +- tools.h | 6 +- transfer.c | 17 +- transfer.h | 6 +- vdr.c | 17 +- 32 files changed, 2079 insertions(+), 1420 deletions(-) create mode 100644 dvbci.c create mode 100644 dvbci.h diff --git a/HISTORY b/HISTORY index e290b64..4649a22 100644 --- a/HISTORY +++ b/HISTORY @@ -5028,3 +5028,56 @@ Video Disk Recorder Revision History 2007-01-07: Version 1.4.5 - Official release. + +2007-01-07: Version 1.5.0 + +- The CAM handling has been refactored. Instead of a cCiHandler per device there + is now an abstract cCiAdapter and a cCamSlot. This allows each slot to be + accessed individually. +- The general 15 seconds workaround time before opening the CAM menu has been + removed. If the CAM menu doesn't open within a timeout, the enter menu command + is now sent again. +- If a CAM is reset or pulled and reinserted, it now automatically starts + decrypting the current channel again. +- The Setup/CAM menu now dynamically refreshes its items and displays whether + a CAM is present or ready. The 'Reset' function no longer leaves the menu. +- The CAM menu will now be openend when pressing the Ok key on a slot entry. +- The CAM menu now stays within the current menu context and doesn't close and + reopen the menu every time an option is selected. +- When an encrypted channel is switched to for the first time, VDR now checks + explicitly whether a CAM can actually decrypt that channel. If there is more + than one CAM in the system that claims to be able to decrypt the channel, + they are all tried in turn. + To make this possible, an encrypted channel needs to be received in Transfer + Mode when it is switched to for the first time, so that VDR can determine + whether the TS packets are actually decrypted. Once a channel is known to + be decrypted by a particular CAM, the next time it is switched to it will + be shown in normal live viewing mode. +- A cDevice now automatically detaches all cReceiver objects that receive PIDs + that can't be decrypted with the current CAM. A plugin that attaches a cReceiver + to a device should therefore watch the receiver's IsAttached() function to + see if it is still attached to the device. +- The cReceiver constructor no longer takes an 'int Ca' as its first parameter, + but rather a 'tChannelID ChannelID'. This is necessary for the device to be + able to determine which CAM a particular channel can be decrypted with. If the + channel is known to be unencrypted, or a plugin doesn't want to provide the + channel id for other reasons, an invalid tChannelID() can be given. +- The cThread::Start() function now waits until a previous incarnation of this + thread has actually stopped. Before this it could happen that a thread's + Cancel(-1) function was called and immediately after that it was started again, + but the Start() function still found it to be 'active'. +- The parameter NeedsDetachReceivers in cDevice::GetDevice(const cChannel *Channel, ...) + has been removed. A call to this function will automatically detach all receivers + from the device if it returns a non-NULL pointer. +- The cTimeMs class now accepts an initial timeout value in its constructor. +- A CAM is now explicitly instructed to stop decrypting when switching away from + an encrypted channel. +- If the CAM in use can decrypt several channels at the same time, VDR can + now make use if this capability. Whether or not a CAM can decrypt more + than one channel is determined by sending it an initial empty QUERY command + and testing whether it replies to it. +- Ca values in the range 0...F in channels.conf can still be used to assign a channel + to a particular device, but this will no longer work with encrypted channels because + without valid CA ids VDR can't decide which CAM slot to use. However, since VDR now + automatically determines which CAM can decrypt which channel, setting fixed + channel/device relations should no longer be necessary. diff --git a/MANUAL b/MANUAL index 52c2a94..3377fb0 100644 --- a/MANUAL +++ b/MANUAL @@ -667,13 +667,15 @@ Version 1.4 Use DiSEqC = no Generally turns DiSEqC support on or off. - CICAM: - - CICAM DVBn m Shows the CAMs that each device contains, where 'n' is - the number of the device, and 'm' is the number of the - Common Interface slot of that device. The "Red" key - can be pressed to enter the CAM menu, and the "Green" key - triggers a reset of the selected CAM. + CAM: + + n CAM Name Shows the CAM slots that are present in this system, where + 'n' is the number of the slot, followed by the name of the + CAM. If a CAM slot is empty, '-' is displayed as name, and + if it is in the process of being reset, its current status + is displayed. The "Red" key can be pressed to enter the CAM + menu, and the "Green" key triggers a reset of the selected + slot. The "Ok" key also opens the CAM menu. Recording: diff --git a/Makefile b/Makefile index 1d73b79..cfa7354 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.95 2006/08/20 10:44:22 kls Exp $ +# $Id: Makefile 1.96 2006/11/11 13:55:32 kls Exp $ .DELETE_ON_ERROR: @@ -32,7 +32,7 @@ DOXYFILE = Doxyfile SILIB = $(LSIDIR)/libsi.a -OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbosd.o\ +OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o dvbosd.o\ dvbplayer.o dvbspu.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\ lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o rcu.o\ receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o\ diff --git a/PLUGINS.html b/PLUGINS.html index 51424ce..71c1b77 100644 --- a/PLUGINS.html +++ b/PLUGINS.html @@ -6,7 +6,7 @@

The VDR Plugin System

-
Version 1.4.1
+
Version 1.5.0

Copyright © 2006 Klaus Schmidinger
@@ -14,6 +14,10 @@ Copyright © 2006 Klaus Schmidinger
www.cadsoft.de/vdr

+
  +Important modifications introduced in version 1.5.0 are marked like this. +
+

VDR provides an easy to use plugin interface that allows additional functionality to be added to the program by implementing a dynamically loadable library file. This interface allows programmers to develop additional functionality for VDR completely @@ -72,6 +76,9 @@ structures and allows it to hook itself into specific areas to perform special a

  • Devices
  • Audio
  • Remote Control +
      +
  • Conditional Access +
  • @@ -1727,6 +1734,7 @@ selecting which channel it shall tune to:

     virtual bool ProvidesSource(int Source) const;
    +virtual bool ProvidesTransponder(const cChannel *Channel) const;
     virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
     virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
     

    @@ -2038,5 +2046,42 @@ Put(uint64 Code, bool Repeat = false, bool Release = false); The other parameters have the same meaning as in the first version of this function. +
      +

    Conditional Access

    + +
    Members only!

    + +Pay TV providers usually encrypt their broadcasts, so that only viewers who +have the proper smart card can watch them. Such a smart card needs to be inserted +into a CAM (Conditional Access Module), which in turn goes into a CI (Common +Interface) slot. +

    +VDR's mechanisms for supporting Conditional Access are mainly the two classes +cCiAdapter and cCamSlot. A cCiAdapter handles exactly +one CI, and can provide several CAM slots, represented by the appropriate +number of cCamSlot objects. +

    +In order to decrypt a particular channel, a cCiAdapter with a cCamSlot +that contains the necessary CAM will be assigned to a cDevice, and exactly +one of its CAM slots will be activated. Whether or not a cCiAdapter can +be assigned to a particular device depends on the hardware implementation. +Some devices (like the Siemens/Technotrend DVB cards) are hardwired with their +CI adapters, so the cCiAdapter for these can only be used with one device. +Other hardware implementations may allow CI adapters and devices to be connected +through some sort of matrix switch. When trying to decrypt an encrypted channel, +VDR will automatically select a useful combination of device and CAM slot. +

    +If a plugin implements a derived cCiAdapter, it has to implement +several low level functions that handle the actual data transfer (see dvbci.c +for example). The decision whether the adapter can actually be assigned to different +devices is made in the function + +

    +virtual bool Assign(cDevice *Device, bool Query = false);
    +

    + +See the description of this function in ci.h for details. +

    + diff --git a/channels.h b/channels.h index c0dd37e..143bb47 100644 --- a/channels.h +++ b/channels.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.h 1.42 2006/05/28 15:03:56 kls Exp $ + * $Id: channels.h 1.43 2007/01/05 10:37:35 kls Exp $ */ #ifndef __CHANNELS_H @@ -179,6 +179,7 @@ public: const char *Dlang(int i) const { return (0 <= i && i < MAXDPIDS) ? dlangs[i] : ""; } const char *Slang(int i) const { return (0 <= i && i < MAXSPIDS) ? slangs[i] : ""; } int Tpid(void) const { return tpid; } + const int *Caids(void) const { return caids; } int Ca(int Index = 0) const { return Index < MAXCAIDS ? caids[Index] : 0; } int Nid(void) const { return nid; } int Tid(void) const { return tid; } diff --git a/ci.c b/ci.c index cce7b5d..26447cb 100644 --- a/ci.c +++ b/ci.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ci.c 1.46 2007/01/05 10:08:46 kls Exp $ + * $Id: ci.c 1.47 2007/01/05 10:38:03 kls Exp $ */ #include "ci.h" @@ -17,38 +17,18 @@ #include #include #include +#include "device.h" #include "pat.h" #include "tools.h" -/* these might come in handy in case you want to use this code without VDR's other files: -#ifndef MALLOC -#define MALLOC(type, size) (type *)malloc(sizeof(type) * (size)) -#endif - -#ifndef esyslog -static int SysLogLevel = 3; -#define esyslog(a...) void( (SysLogLevel > 0) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() ) -#define isyslog(a...) void( (SysLogLevel > 1) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() ) -#define dsyslog(a...) void( (SysLogLevel > 2) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() ) -#endif -*/ - // Set these to 'true' for debug output: static bool DumpTPDUDataTransfer = false; static bool DebugProtocol = false; +static bool DumpPolls = false; +static bool DumpDateTime = false; #define dbgprotocol(a...) if (DebugProtocol) fprintf(stderr, a) -#define OK 0 -#define TIMEOUT -1 -#define ERROR -2 - -// --- Workarounds ----------------------------------------------------------- - -// The Irdeto AllCAM 4.7 (and maybe others, too) does not react on AOT_ENTER_MENU -// during the first few seconds of a newly established connection -#define WRKRND_TIME_BEFORE_ENTER_MENU 15 // seconds - // --- Helper functions ------------------------------------------------------ #define SIZE_INDICATOR 0x80 @@ -144,27 +124,29 @@ static char *GetString(int &Length, const uint8_t **Data) class cTPDU { private: int size; - uint8_t data[MAX_TPDU_SIZE]; + uint8_t buffer[MAX_TPDU_SIZE]; const uint8_t *GetData(const uint8_t *Data, int &Length); public: cTPDU(void) { size = 0; } cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length = 0, const uint8_t *Data = NULL); - uint8_t Slot(void) { return data[0]; } - uint8_t Tcid(void) { return data[1]; } - uint8_t Tag(void) { return data[2]; } - const uint8_t *Data(int &Length) { return GetData(data + 3, Length); } + uint8_t Slot(void) { return buffer[0]; } + uint8_t Tcid(void) { return buffer[1]; } + uint8_t Tag(void) { return buffer[2]; } + const uint8_t *Data(int &Length) { return GetData(buffer + 3, Length); } uint8_t Status(void); - int Write(int fd); - int Read(int fd); - void Dump(int fd, bool Outgoing); + uint8_t *Buffer(void) { return buffer; } + int Size(void) { return size; } + void SetSize(int Size) { size = Size; } + int MaxSize(void) { return sizeof(buffer); } + void Dump(int SlotNumber, bool Outgoing); }; cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t *Data) { size = 0; - data[0] = Slot; - data[1] = Tcid; - data[2] = Tag; + buffer[0] = Slot; + buffer[1] = Tcid; + buffer[2] = Tag; switch (Tag) { case T_RCV: case T_CREATE_TC: @@ -172,72 +154,51 @@ cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t case T_DELETE_TC: case T_DTC_REPLY: case T_REQUEST_TC: - data[3] = 1; // length - data[4] = Tcid; + buffer[3] = 1; // length + buffer[4] = Tcid; size = 5; break; case T_NEW_TC: case T_TC_ERROR: if (Length == 1) { - data[3] = 2; // length - data[4] = Tcid; - data[5] = Data[0]; + buffer[3] = 2; // length + buffer[4] = Tcid; + buffer[5] = Data[0]; size = 6; } else - esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length); + esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid); break; case T_DATA_LAST: case T_DATA_MORE: if (Length <= MAX_TPDU_DATA) { - uint8_t *p = data + 3; + uint8_t *p = buffer + 3; p = SetLength(p, Length + 1); *p++ = Tcid; if (Length) memcpy(p, Data, Length); - size = Length + (p - data); + size = Length + (p - buffer); } else - esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length); + esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid); break; default: - esyslog("ERROR: unknown TPDU tag: 0x%02X", Tag); + esyslog("ERROR: unknown TPDU tag: 0x%02X (%d/%d)", Tag, Slot, Tcid); } } -int cTPDU::Write(int fd) -{ - Dump(fd, true); - if (size) - return safe_write(fd, data, size) == size ? OK : ERROR; - esyslog("ERROR: attemp to write TPDU with zero size"); - return ERROR; -} - -int cTPDU::Read(int fd) +void cTPDU::Dump(int SlotNumber, bool Outgoing) { - size = safe_read(fd, data, sizeof(data)); - if (size < 0) { - esyslog("ERROR: %m"); - size = 0; - return ERROR; - } - Dump(fd, false); - return OK; -} - -void cTPDU::Dump(int fd, bool Outgoing) -{ - if (DumpTPDUDataTransfer) { + if (DumpTPDUDataTransfer && (DumpPolls || Tag() != T_SB)) { #define MAX_DUMP 256 - fprintf(stderr, "%2d %s ", fd, Outgoing ? "-->" : "<--"); + fprintf(stderr, " %d: %s ", SlotNumber, Outgoing ? "-->" : "<--"); for (int i = 0; i < size && i < MAX_DUMP; i++) - fprintf(stderr, "%02X ", data[i]); + fprintf(stderr, "%02X ", buffer[i]); fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : ""); if (!Outgoing) { - fprintf(stderr, " "); + fprintf(stderr, " "); for (int i = 0; i < size && i < MAX_DUMP; i++) - fprintf(stderr, "%2c ", isprint(data[i]) ? data[i] : '.'); + fprintf(stderr, "%2c ", isprint(buffer[i]) ? buffer[i] : '.'); fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : ""); } } @@ -257,301 +218,51 @@ const uint8_t *cTPDU::GetData(const uint8_t *Data, int &Length) uint8_t cTPDU::Status(void) { - if (size >= 4 && data[size - 4] == T_SB && data[size - 3] == 2) { - //XXX test tcid??? - return data[size - 1]; - } + if (size >= 4 && buffer[size - 4] == T_SB && buffer[size - 3] == 2) + return buffer[size - 1]; return 0; } // --- cCiTransportConnection ------------------------------------------------ -enum eState { stIDLE, stCREATION, stACTIVE, stDELETION }; +#define MAX_SESSIONS_PER_TC 16 class cCiTransportConnection { - friend class cCiTransportLayer; private: - int fd; - uint8_t slot; + enum eState { stIDLE, stCREATION, stACTIVE, stDELETION }; + cCamSlot *camSlot; uint8_t tcid; eState state; - cTPDU *tpdu; - int lastResponse; - bool dataAvailable; - void Init(int Fd, uint8_t Slot, uint8_t Tcid); - int SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL); - int RecvTPDU(void); - int CreateConnection(void); - int Poll(void); - eState State(void) { return state; } - int LastResponse(void) { return lastResponse; } - bool DataAvailable(void) { return dataAvailable; } -public: - cCiTransportConnection(void); - ~cCiTransportConnection(); - int Slot(void) const { return slot; } - int SendData(int Length, const uint8_t *Data); - int RecvData(void); - const uint8_t *Data(int &Length); - //XXX Close() - void Reset(void); - }; - -cCiTransportConnection::cCiTransportConnection(void) -{ - tpdu = NULL; - Init(-1, 0, 0); -} - -cCiTransportConnection::~cCiTransportConnection() -{ - delete tpdu; -} - -void cCiTransportConnection::Init(int Fd, uint8_t Slot, uint8_t Tcid) -{ - fd = Fd; - slot = Slot; - tcid = Tcid; - state = stIDLE; - if (fd >= 0 && !tpdu) - tpdu = new cTPDU; - lastResponse = ERROR; - dataAvailable = false; -//XXX Clear()??? -} - -int cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data) -{ - cTPDU TPDU(slot, tcid, Tag, Length, Data); - return TPDU.Write(fd); -} - -#define CAM_READ_TIMEOUT 3500 // ms - -int cCiTransportConnection::RecvTPDU(void) -{ - struct pollfd pfd[1]; - pfd[0].fd = fd; - pfd[0].events = POLLIN; - lastResponse = ERROR; - if (poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN) && tpdu->Read(fd) == OK && tpdu->Tcid() == tcid) { - switch (state) { - case stIDLE: break; - case stCREATION: if (tpdu->Tag() == T_CTC_REPLY) { - dataAvailable = tpdu->Status() & DATA_INDICATOR; - state = stACTIVE; - lastResponse = tpdu->Tag(); - } - break; - case stACTIVE: switch (tpdu->Tag()) { - case T_SB: - case T_DATA_LAST: - case T_DATA_MORE: - case T_REQUEST_TC: break; - case T_DELETE_TC: if (SendTPDU(T_DTC_REPLY) != OK) - return ERROR; - Init(fd, slot, tcid); - break; - default: return ERROR; - } - dataAvailable = tpdu->Status() & DATA_INDICATOR; - lastResponse = tpdu->Tag(); - break; - case stDELETION: if (tpdu->Tag() == T_DTC_REPLY) { - Init(fd, slot, tcid); - //XXX Status()??? - lastResponse = tpdu->Tag(); - } - break; - } - } - else if (FATALERRNO) { - esyslog("ERROR: CAM: Read failed: slot %d, tcid %d - %m", slot, tcid); - Init(-1, slot, tcid); - } - return lastResponse; -} - -int cCiTransportConnection::SendData(int Length, const uint8_t *Data) -{ - while (state == stACTIVE && Length > 0) { - uint8_t Tag = T_DATA_LAST; - int l = Length; - if (l > MAX_TPDU_DATA) { - Tag = T_DATA_MORE; - l = MAX_TPDU_DATA; - } - if (SendTPDU(Tag, l, Data) != OK || RecvTPDU() != T_SB) - break; - Length -= l; - Data += l; - } - return Length ? ERROR : OK; -} - -int cCiTransportConnection::RecvData(void) -{ - if (SendTPDU(T_RCV) == OK) - return RecvTPDU(); - return ERROR; -} - -const uint8_t *cCiTransportConnection::Data(int &Length) -{ - return tpdu->Data(Length); -} - -#define MAX_CONNECT_RETRIES 2 - -int cCiTransportConnection::CreateConnection(void) -{ - if (state == stIDLE) { - if (SendTPDU(T_CREATE_TC) == OK) { - state = stCREATION; - if (RecvTPDU() == T_CTC_REPLY) - return OK; - // the following is a workaround for CAMs that don't quite follow the specs... - else { - for (int i = 0; i < MAX_CONNECT_RETRIES; i++) { - dsyslog("CAM: retrying to establish connection"); - if (RecvTPDU() == T_CTC_REPLY) { - dsyslog("CAM: connection established"); - return OK; - } - } - return ERROR; - } - } - } - return ERROR; -} - -int cCiTransportConnection::Poll(void) -{ - if (state == stACTIVE) { - if (SendTPDU(T_DATA_LAST) == OK) - return RecvTPDU(); - } - return ERROR; -} - -void cCiTransportConnection::Reset(void) -{ - Init(-1, 0, 0); -} - -// --- cCiTransportLayer ----------------------------------------------------- - -#define MAX_CI_CONNECT 16 // maximum possible value is 254 - -class cCiTransportLayer { -private: - int fd; - int numSlots; - cCiTransportConnection tc[MAX_CI_CONNECT]; + bool createConnectionRequested; + bool deleteConnectionRequested; + bool hasUserIO; + cTimeMs alive; + cTimeMs timer; + cCiSession *sessions[MAX_SESSIONS_PER_TC + 1]; // session numbering starts with 1 + void SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL); + void SendTag(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId = 0, int Status = -1); + void Poll(void); + uint32_t ResourceIdToInt(const uint8_t *Data); + cCiSession *GetSessionBySessionId(uint16_t SessionId); + void OpenSession(int Length, const uint8_t *Data); + void CloseSession(uint16_t SessionId); + void HandleSessions(cTPDU *TPDU); public: - cCiTransportLayer(int Fd, int NumSlots); - cCiTransportConnection *NewConnection(int Slot); - bool ResetSlot(int Slot, bool Wait = false); - bool ModuleReady(int Slot); - cCiTransportConnection *Process(int Slot); + cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid); + virtual ~cCiTransportConnection(); + cCamSlot *CamSlot(void) { return camSlot; } + uint8_t Tcid(void) const { return tcid; } + void CreateConnection(void) { createConnectionRequested = true; } + void DeleteConnection(void) { deleteConnectionRequested = true; } + const char *GetCamName(void); + bool Ready(void); + bool HasUserIO(void) { return hasUserIO; } + void SendData(int Length, const uint8_t *Data); + bool Process(cTPDU *TPDU = NULL); + cCiSession *GetSessionByResourceId(uint32_t ResourceId); }; -cCiTransportLayer::cCiTransportLayer(int Fd, int NumSlots) -{ - fd = Fd; - numSlots = NumSlots; - for (int s = 0; s < numSlots; s++) - ResetSlot(s); - cCondWait::SleepMs(2000); -} - -cCiTransportConnection *cCiTransportLayer::NewConnection(int Slot) -{ - for (int i = 0; i < MAX_CI_CONNECT; i++) { - if (tc[i].State() == stIDLE) { - dbgprotocol("Creating connection: slot %d, tcid %d\n", Slot, i + 1); - tc[i].Init(fd, Slot, i + 1); - if (tc[i].CreateConnection() == OK) - return &tc[i]; - break; - } - } - return NULL; -} - -bool cCiTransportLayer::ResetSlot(int Slot, bool Wait) -{ - for (int i = 0; i < MAX_CI_CONNECT; i++) { - if (tc[i].State() != stIDLE && tc[i].Slot() == Slot) - tc[i].Reset(); - } - dbgprotocol("Resetting slot %d...", Slot); - if (ioctl(fd, CA_RESET, 1 << Slot) != -1) { - if (Wait) - cCondWait::SleepMs(2000); - dbgprotocol("ok.\n"); - return true; - } - else - esyslog("ERROR: can't reset CAM slot %d: %m", Slot); - dbgprotocol("failed!\n"); - return false; -} - -bool cCiTransportLayer::ModuleReady(int Slot) -{ - ca_slot_info_t sinfo; - sinfo.num = Slot; - if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) - return sinfo.flags & CA_CI_MODULE_READY; - else - esyslog("ERROR: can't get info on CAM slot %d: %m", Slot); - return false; -} - -cCiTransportConnection *cCiTransportLayer::Process(int Slot) -{ - for (int i = 0; i < MAX_CI_CONNECT; i++) { - cCiTransportConnection *Tc = &tc[i]; - if (Tc->Slot() == Slot) { - switch (Tc->State()) { - case stCREATION: - case stACTIVE: - if (!Tc->DataAvailable()) { - if (Tc->Poll() != OK) - ;//XXX continue; - } - switch (Tc->LastResponse()) { - case T_REQUEST_TC: - //XXX - break; - case T_DATA_MORE: - case T_DATA_LAST: - case T_CTC_REPLY: - case T_SB: - if (Tc->DataAvailable()) - Tc->RecvData(); - break; - case TIMEOUT: - case ERROR: - default: - //XXX Tc->state = stIDLE;//XXX Init()??? - return NULL; - break; - } - //XXX this will only work with _one_ transport connection per slot! - return Tc; - break; - default: ; - } - } - } - return NULL; -} - -// -- cCiSession ------------------------------------------------------------- +// --- cCiSession ------------------------------------------------------------ // Session Tags: @@ -636,15 +347,15 @@ private: protected: int GetTag(int &Length, const uint8_t **Data); const uint8_t *GetData(const uint8_t *Data, int &Length); - int SendData(int Tag, int Length = 0, const uint8_t *Data = NULL); + void SendData(int Tag, int Length = 0, const uint8_t *Data = NULL); + cCiTransportConnection *Tc(void) { return tc; } public: cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc); virtual ~cCiSession(); - const cCiTransportConnection *Tc(void) { return tc; } uint16_t SessionId(void) { return sessionId; } uint32_t ResourceId(void) { return resourceId; } virtual bool HasUserIO(void) { return false; } - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); }; cCiSession::cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc) @@ -679,7 +390,7 @@ const uint8_t *cCiSession::GetData(const uint8_t *Data, int &Length) return Length ? Data : NULL; } -int cCiSession::SendData(int Tag, int Length, const uint8_t *Data) +void cCiSession::SendData(int Tag, int Length, const uint8_t *Data) { uint8_t buffer[2048]; uint8_t *p = buffer; @@ -694,78 +405,75 @@ int cCiSession::SendData(int Tag, int Length, const uint8_t *Data) if (p - buffer + Length < int(sizeof(buffer))) { memcpy(p, Data, Length); p += Length; - return tc->SendData(p - buffer, buffer); + tc->SendData(p - buffer, buffer); } - esyslog("ERROR: CAM: data length (%d) exceeds buffer size", Length); - return ERROR; + else + esyslog("ERROR: CAM %d: data length (%d) exceeds buffer size", Tc()->CamSlot()->SlotNumber(), Length); } -bool cCiSession::Process(int Length, const uint8_t *Data) +void cCiSession::Process(int Length, const uint8_t *Data) { - return true; } -// -- cCiResourceManager ----------------------------------------------------- +// --- cCiResourceManager ---------------------------------------------------- class cCiResourceManager : public cCiSession { private: int state; public: cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); }; cCiResourceManager::cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_RESOURCE_MANAGER, Tc) { - dbgprotocol("New Resource Manager (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Resource Manager (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); state = 0; } -bool cCiResourceManager::Process(int Length, const uint8_t *Data) +void cCiResourceManager::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_PROFILE_ENQ: { - dbgprotocol("%d: <== Profile Enquiry\n", SessionId()); + dbgprotocol("Slot %d: <== Profile Enquiry (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); uint32_t resources[] = { htonl(RI_RESOURCE_MANAGER), htonl(RI_APPLICATION_INFORMATION), htonl(RI_CONDITIONAL_ACCESS_SUPPORT), htonl(RI_DATE_TIME), htonl(RI_MMI) }; - dbgprotocol("%d: ==> Profile\n", SessionId()); + dbgprotocol("Slot %d: ==> Profile (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_PROFILE, sizeof(resources), (uint8_t*)resources); state = 3; } break; case AOT_PROFILE: { - dbgprotocol("%d: <== Profile\n", SessionId()); + dbgprotocol("Slot %d: <== Profile (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); if (state == 1) { int l = 0; const uint8_t *d = GetData(Data, l); if (l > 0 && d) - esyslog("CI resource manager: unexpected data"); - dbgprotocol("%d: ==> Profile Change\n", SessionId()); + esyslog("ERROR: CAM %d: resource manager: unexpected data", Tc()->CamSlot()->SlotNumber()); + dbgprotocol("Slot %d: ==> Profile Change (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_PROFILE_CHANGE); state = 2; } else { - esyslog("ERROR: CI resource manager: unexpected tag %06X in state %d", Tag, state); + esyslog("ERROR: CAM %d: resource manager: unexpected tag %06X in state %d", Tc()->CamSlot()->SlotNumber(), Tag, state); } } break; - default: esyslog("ERROR: CI resource manager: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: resource manager: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (state == 0) { - dbgprotocol("%d: ==> Profile Enq\n", SessionId()); + dbgprotocol("Slot %d: ==> Profile Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_PROFILE_ENQ); state = 1; } - return true; } // --- cCiApplicationInformation --------------------------------------------- @@ -773,7 +481,6 @@ bool cCiResourceManager::Process(int Length, const uint8_t *Data) class cCiApplicationInformation : public cCiSession { private: int state; - time_t creationTime; uint8_t applicationType; uint16_t applicationManufacturer; uint16_t manufacturerCode; @@ -781,7 +488,7 @@ private: public: cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc); virtual ~cCiApplicationInformation(); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); bool EnterMenu(void); const char *GetMenuString(void) { return menuString; } }; @@ -789,9 +496,8 @@ public: cCiApplicationInformation::cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_APPLICATION_INFORMATION, Tc) { - dbgprotocol("New Application Information (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Application Information (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); state = 0; - creationTime = time(NULL); menuString = NULL; } @@ -800,13 +506,13 @@ cCiApplicationInformation::~cCiApplicationInformation() free(menuString); } -bool cCiApplicationInformation::Process(int Length, const uint8_t *Data) +void cCiApplicationInformation::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_APPLICATION_INFO: { - dbgprotocol("%d: <== Application Info\n", SessionId()); + dbgprotocol("Slot %d: <== Application Info (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); int l = 0; const uint8_t *d = GetData(Data, l); if ((l -= 1) < 0) break; @@ -819,28 +525,26 @@ bool cCiApplicationInformation::Process(int Length, const uint8_t *Data) d += 2; free(menuString); menuString = GetString(l, &d); - isyslog("CAM: %s, %02X, %04X, %04X", menuString, applicationType, applicationManufacturer, manufacturerCode);//XXX make externally accessible! - } + isyslog("CAM %d: %s, %02X, %04X, %04X", Tc()->CamSlot()->SlotNumber(), menuString, applicationType, applicationManufacturer, manufacturerCode); state = 2; + } break; - default: esyslog("ERROR: CI application information: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: application information: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (state == 0) { - dbgprotocol("%d: ==> Application Info Enq\n", SessionId()); + dbgprotocol("Slot %d: ==> Application Info Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_APPLICATION_INFO_ENQ); state = 1; } - return true; } bool cCiApplicationInformation::EnterMenu(void) { - if (state == 2 && time(NULL) - creationTime > WRKRND_TIME_BEFORE_ENTER_MENU) { - dbgprotocol("%d: ==> Enter Menu\n", SessionId()); + if (state == 2) { + dbgprotocol("Slot %d: ==> Enter Menu (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_ENTER_MENU); - return true;//XXX + return true; } return false; } @@ -875,13 +579,14 @@ private: bool streamFlag; void AddCaDescriptors(int Length, const uint8_t *Data); public: - cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds); + cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds); + uint8_t CmdId(void) { return cmdId; } void SetListManagement(uint8_t ListManagement); - bool Valid(void); + uint8_t ListManagement(void) { return capmt[0]; } void AddPid(int Pid, uint8_t StreamType); }; -cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds) +cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds) { cmdId = CmdId; caDescriptorsLength = GetCaDescriptors(Source, Transponder, ProgramNumber, CaSystemIds, sizeof(caDescriptors), caDescriptors, streamFlag); @@ -902,11 +607,6 @@ void cCiCaPmt::SetListManagement(uint8_t ListManagement) capmt[0] = ListManagement; } -bool cCiCaPmt::Valid(void) -{ - return caDescriptorsLength > 0; -} - void cCiCaPmt::AddPid(int Pid, uint8_t StreamType) { if (Pid) { @@ -957,61 +657,77 @@ void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data) #define CA_ENABLE(x) (((x) & CA_ENABLE_FLAG) ? (x) & ~CA_ENABLE_FLAG : 0) +#define QUERY_WAIT_TIME 1000 // ms to wait before sending a query +#define QUERY_REPLY_TIMEOUT 2000 // ms to wait for a reply to a query + class cCiConditionalAccessSupport : public cCiSession { private: int state; int numCaSystemIds; - unsigned short caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated! + int caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated! + bool repliesToQuery; + cTimeMs timer; public: cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); - const unsigned short *GetCaSystemIds(void) { return caSystemIds; } - bool SendPMT(cCiCaPmt *CaPmt); - bool ReceivedReply(bool CanDescramble = false); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); + const int *GetCaSystemIds(void) { return caSystemIds; } + void SendPMT(cCiCaPmt *CaPmt); + bool RepliesToQuery(void) { return repliesToQuery; } + bool Ready(void) { return state >= 4; } + bool ReceivedReply(void) { return state >= 5; } + bool CanDecrypt(void) { return state == 6; } }; cCiConditionalAccessSupport::cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_CONDITIONAL_ACCESS_SUPPORT, Tc) { - dbgprotocol("New Conditional Access Support (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Conditional Access Support (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); state = 0; // inactive caSystemIds[numCaSystemIds = 0] = 0; + repliesToQuery = false; } -bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) +void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_CA_INFO: { - dbgprotocol("%d: <== Ca Info", SessionId()); + dbgprotocol("Slot %d: <== Ca Info (%d)", Tc()->CamSlot()->SlotNumber(), SessionId()); numCaSystemIds = 0; int l = 0; const uint8_t *d = GetData(Data, l); while (l > 1) { - unsigned short id = ((unsigned short)(*d) << 8) | *(d + 1); + uint16_t id = ((uint16_t)(*d) << 8) | *(d + 1); dbgprotocol(" %04X", id); d += 2; l -= 2; if (numCaSystemIds < MAXCASYSTEMIDS) caSystemIds[numCaSystemIds++] = id; else { - esyslog("ERROR: too many CA system IDs!"); + esyslog("ERROR: CAM %d: too many CA system IDs!", Tc()->CamSlot()->SlotNumber()); break; } } caSystemIds[numCaSystemIds] = 0; dbgprotocol("\n"); + if (state == 1) { + timer.Set(QUERY_WAIT_TIME); // WORKAROUND: Alphacrypt 3.09 doesn't reply to QUERY immediately after reset + state = 2; // got ca info + } } - state = 2; // got ca info break; case AOT_CA_PMT_REPLY: { - dbgprotocol("%d: <== Ca Pmt Reply", SessionId()); - state = 4; // got ca pmt reply + dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId()); + if (!repliesToQuery) { + dsyslog("CAM %d: replies to QUERY - multi channel decryption possible", Tc()->CamSlot()->SlotNumber()); + repliesToQuery = true; + } + state = 5; // got ca pmt reply int l = 0; const uint8_t *d = GetData(Data, l); if (l > 1) { - unsigned short pnr = ((unsigned short)(*d) << 8) | *(d + 1); + uint16_t pnr = ((uint16_t)(*d) << 8) | *(d + 1); dbgprotocol(" %d", pnr); d += 2; l -= 2; @@ -1026,7 +742,7 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) // 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: - unsigned short len = ((unsigned short)(*d) << 8) | *(d + 1); + uint16_t len = ((uint16_t)(*d) << 8) | *(d + 1); if (len == l - 2) { d += 2; l -= 2; @@ -1040,7 +756,7 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) if (l <= 2) ok = CA_ENABLE(caepl) == CAEI_POSSIBLE; while (l > 2) { - unsigned short pid = ((unsigned short)(*d) << 8) | *(d + 1); + uint16_t pid = ((uint16_t)(*d) << 8) | *(d + 1); unsigned char caees = *(d + 2); dbgprotocol(" %d=%02X", pid, caees); d += 3; @@ -1049,39 +765,40 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) ok = false; } if (ok) - state = 5; // descrambling possible + state = 6; // descrambling possible } } } dbgprotocol("\n"); } break; - default: esyslog("ERROR: CI conditional access support: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: conditional access support: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (state == 0) { - dbgprotocol("%d: ==> Ca Info Enq\n", SessionId()); + dbgprotocol("Slot %d: ==> Ca Info Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_CA_INFO_ENQ); state = 1; // enquired ca info } - return true; + else if (state == 2 && timer.TimedOut()) { + cCiCaPmt CaPmt(CPCI_QUERY, 0, 0, 0, NULL); + SendPMT(&CaPmt); + timer.Set(QUERY_REPLY_TIMEOUT); + state = 3; // waiting for reply + } + else if (state == 3 && timer.TimedOut()) { + dsyslog("CAM %d: doesn't reply to QUERY - only a single channel can be decrypted", Tc()->CamSlot()->SlotNumber()); + state = 4; // normal operation + } } -bool cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt) +void cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt) { if (CaPmt && state >= 2) { - dbgprotocol("%d: ==> Ca Pmt\n", SessionId()); + dbgprotocol("Slot %d: ==> Ca Pmt (%d) %d %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), CaPmt->ListManagement(), CaPmt->CmdId()); SendData(AOT_CA_PMT, CaPmt->length, CaPmt->capmt); - state = 3; // sent ca pmt - return true; + state = 4; // sent ca pmt } - return false; -} - -bool cCiConditionalAccessSupport::ReceivedReply(bool CanDescramble) -{ - return state >= (CanDescramble ? 5 : 4); } // --- cCiDateTime ----------------------------------------------------------- @@ -1090,10 +807,10 @@ class cCiDateTime : public cCiSession { private: int interval; time_t lastTime; - bool SendDateTime(void); + void SendDateTime(void); public: cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); }; cCiDateTime::cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc) @@ -1101,10 +818,10 @@ cCiDateTime::cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc) { interval = 0; lastTime = 0; - dbgprotocol("New Date Time (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Date Time (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); } -bool cCiDateTime::SendDateTime(void) +void cCiDateTime::SendDateTime(void) { time_t t = time(NULL); struct tm tm_gmt; @@ -1116,17 +833,18 @@ bool cCiDateTime::SendDateTime(void) int L = (M == 1 || M == 2) ? 1 : 0; int MJD = 14956 + D + int((Y - L) * 365.25) + int((M + 1 + L * 12) * 30.6001); #define DEC2BCD(d) (((d / 10) << 4) + (d % 10)) - struct tTime { unsigned short mjd; uint8_t h, m, s; short offset; }; + struct tTime { uint16_t mjd; uint8_t h, m, s; short offset; }; tTime T = { mjd : htons(MJD), h : DEC2BCD(tm_gmt.tm_hour), m : DEC2BCD(tm_gmt.tm_min), s : DEC2BCD(tm_gmt.tm_sec), offset : htons(tm_loc.tm_gmtoff / 60) }; - dbgprotocol("%d: ==> Date Time\n", SessionId()); + bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer; + DumpTPDUDataTransfer &= DumpDateTime; + if (DumpDateTime) + dbgprotocol("Slot %d: ==> Date Time (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_DATE_TIME, 7, (uint8_t*)&T); - //XXX return value of all SendData() calls??? - return true; + DumpTPDUDataTransfer = OldDumpTPDUDataTransfer; } - return false; } -bool cCiDateTime::Process(int Length, const uint8_t *Data) +void cCiDateTime::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); @@ -1137,20 +855,18 @@ bool cCiDateTime::Process(int Length, const uint8_t *Data) const uint8_t *d = GetData(Data, l); if (l > 0) interval = *d; - dbgprotocol("%d: <== Date Time Enq, interval = %d\n", SessionId(), interval); + dbgprotocol("Slot %d: <== Date Time Enq (%d), interval = %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), interval); lastTime = time(NULL); - return SendDateTime(); + SendDateTime(); } break; - default: esyslog("ERROR: CI date time: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: date time: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (interval && time(NULL) - lastTime > interval) { lastTime = time(NULL); - return SendDateTime(); + SendDateTime(); } - return true; } // --- cCiMMI ---------------------------------------------------------------- @@ -1197,11 +913,11 @@ private: public: cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc); virtual ~cCiMMI(); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); virtual bool HasUserIO(void) { return menu || enquiry; } cCiMenu *Menu(bool Clear = false); cCiEnquiry *Enquiry(bool Clear = false); - bool SendMenuAnswer(uint8_t Selection); + void SendMenuAnswer(uint8_t Selection); bool SendAnswer(const char *Text); bool SendCloseMMI(void); }; @@ -1209,7 +925,7 @@ public: cCiMMI::cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_MMI, Tc) { - dbgprotocol("New MMI (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new MMI (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); menu = fetchedMenu = NULL; enquiry = fetchedEnquiry = NULL; } @@ -1236,21 +952,21 @@ char *cCiMMI::GetText(int &Length, const uint8_t **Data) int Tag = GetTag(Length, Data); if (Tag == AOT_TEXT_LAST) { char *s = GetString(Length, Data); - dbgprotocol("%d: <== Text Last '%s'\n", SessionId(), s); + dbgprotocol("Slot %d: <== Text Last (%d) '%s'\n", Tc()->CamSlot()->SlotNumber(), SessionId(), s); return s; } else - esyslog("CI MMI: unexpected text tag: %06X", Tag); + esyslog("ERROR: CAM %d: MMI: unexpected text tag: %06X", Tc()->CamSlot()->SlotNumber(), Tag); return NULL; } -bool cCiMMI::Process(int Length, const uint8_t *Data) +void cCiMMI::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_DISPLAY_CONTROL: { - dbgprotocol("%d: <== Display Control\n", SessionId()); + dbgprotocol("Slot %d: <== Display Control (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); int l = 0; const uint8_t *d = GetData(Data, l); if (l > 0) { @@ -1259,19 +975,18 @@ bool cCiMMI::Process(int Length, const uint8_t *Data) if (l == 2 && *++d == MM_HIGH_LEVEL) { struct tDisplayReply { uint8_t id; uint8_t mode; }; tDisplayReply dr = { id : DRI_MMI_MODE_ACK, mode : MM_HIGH_LEVEL }; - dbgprotocol("%d: ==> Display Reply\n", SessionId()); + dbgprotocol("Slot %d: ==> Display Reply (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_DISPLAY_REPLY, 2, (uint8_t *)&dr); } break; - default: esyslog("CI MMI: unsupported display control command %02X", *d); - return false; + default: esyslog("ERROR: CAM %d: MMI: unsupported display control command %02X", Tc()->CamSlot()->SlotNumber(), *d); } } } break; case AOT_LIST_LAST: case AOT_MENU_LAST: { - dbgprotocol("%d: <== Menu Last\n", SessionId()); + dbgprotocol("Slot %d: <== Menu Last (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); delete menu; menu = new cCiMenu(this, Tag == AOT_MENU_LAST); int l = 0; @@ -1296,7 +1011,7 @@ bool cCiMMI::Process(int Length, const uint8_t *Data) } break; case AOT_ENQ: { - dbgprotocol("%d: <== Enq\n", SessionId()); + dbgprotocol("Slot %d: <== Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); delete enquiry; enquiry = new cCiEnquiry(this); int l = 0; @@ -1323,14 +1038,12 @@ bool cCiMMI::Process(int Length, const uint8_t *Data) if (l > 1) delay = *d; } - dbgprotocol("%d: <== Close MMI id = %02X delay = %d\n", SessionId(), id, delay); + dbgprotocol("Slot %d: <== Close MMI (%d) id = %02X delay = %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), id, delay); } break; - default: esyslog("ERROR: CI MMI: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: MMI: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } - return true; } cCiMenu *cCiMMI::Menu(bool Clear) @@ -1355,32 +1068,28 @@ cCiEnquiry *cCiMMI::Enquiry(bool Clear) return fetchedEnquiry; } -bool cCiMMI::SendMenuAnswer(uint8_t Selection) +void cCiMMI::SendMenuAnswer(uint8_t Selection) { - dbgprotocol("%d: ==> Menu Answ\n", SessionId()); + dbgprotocol("Slot %d: ==> Menu Answ (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_MENU_ANSW, 1, &Selection); - //XXX return value of all SendData() calls??? - return true; } bool cCiMMI::SendAnswer(const char *Text) { - dbgprotocol("%d: ==> Answ\n", SessionId()); + dbgprotocol("Slot %d: ==> Answ (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); struct tAnswer { uint8_t id; char text[256]; };//XXX tAnswer answer; answer.id = Text ? AI_ANSWER : AI_CANCEL; if (Text) strncpy(answer.text, Text, sizeof(answer.text)); SendData(AOT_ANSW, Text ? strlen(Text) + 1 : 1, (uint8_t *)&answer); - //XXX return value of all SendData() calls??? return true; } bool cCiMMI::SendCloseMMI(void) { - dbgprotocol("%d: ==> Close MMI\n", SessionId()); + dbgprotocol("Slot %d: ==> Close MMI (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_CLOSE_MMI, 0); - //XXX return value of all SendData() calls??? return true; } @@ -1422,24 +1131,23 @@ bool cCiMenu::HasUpdate(void) return !mmi || mmi->HasUserIO(); } -bool cCiMenu::Select(int Index) +void cCiMenu::Select(int Index) { cMutexLock MutexLock(mutex); - dbgprotocol("%d: ==> Select %d\n", mmi ? mmi->SessionId() : -1, Index); if (mmi && -1 <= Index && Index < numEntries) - return mmi->SendMenuAnswer(Index + 1); - return false; + mmi->SendMenuAnswer(Index + 1); } -bool cCiMenu::Cancel(void) +void cCiMenu::Cancel(void) { - return Select(-1); + Select(-1); } -bool cCiMenu::Abort(void) +void cCiMenu::Abort(void) { cMutexLock MutexLock(mutex); - return mmi && mmi->SendCloseMMI(); + if (mmi) + mmi->SendCloseMMI(); } // --- cCiEnquiry ------------------------------------------------------------ @@ -1460,78 +1168,77 @@ cCiEnquiry::~cCiEnquiry() free(text); } -bool cCiEnquiry::Reply(const char *s) +void cCiEnquiry::Reply(const char *s) { cMutexLock MutexLock(mutex); - return mmi ? mmi->SendAnswer(s) : false; + if (mmi) + mmi->SendAnswer(s); } -bool cCiEnquiry::Cancel(void) +void cCiEnquiry::Cancel(void) { - return Reply(NULL); + Reply(NULL); } -bool cCiEnquiry::Abort(void) +void cCiEnquiry::Abort(void) { cMutexLock MutexLock(mutex); - return mmi && mmi->SendCloseMMI(); + if (mmi) + mmi->SendCloseMMI(); } -// -- cCiHandler ------------------------------------------------------------- +// --- cCiTransportConnection (cont'd) --------------------------------------- -cCiHandler::cCiHandler(int Fd, int NumSlots) +#define TC_POLL_TIMEOUT 300 // ms WORKAROUND: TC_POLL_TIMEOUT < 300ms doesn't work with DragonCAM +#define TC_ALIVE_TIMEOUT 2000 // ms after which a transport connection is assumed dead + +cCiTransportConnection::cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid) { - fd = Fd; - numSlots = NumSlots; - newCaSupport = false; + dbgprotocol("Slot %d: creating connection %d/%d\n", CamSlot->SlotNumber(), CamSlot->SlotIndex(), Tcid); + camSlot = CamSlot; + tcid = Tcid; + state = stIDLE; + createConnectionRequested = false; + deleteConnectionRequested = false; hasUserIO = false; - for (int i = 0; i < MAX_CI_SESSION; i++) + alive.Set(TC_ALIVE_TIMEOUT); + for (int i = 0; i <= MAX_SESSIONS_PER_TC; i++) // sessions[0] is not used, but initialized anyway sessions[i] = NULL; - for (int i = 0; i < MAX_CI_SLOT; i++) - moduleReady[i] = false; - tpl = new cCiTransportLayer(Fd, numSlots); - tc = NULL; - source = transponder = 0; } -cCiHandler::~cCiHandler() +cCiTransportConnection::~cCiTransportConnection() { - for (int i = 0; i < MAX_CI_SESSION; i++) + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) delete sessions[i]; - delete tpl; - close(fd); } -cCiHandler *cCiHandler::CreateCiHandler(const char *FileName) +bool cCiTransportConnection::Ready(void) { - int fd_ca = open(FileName, O_RDWR); - if (fd_ca >= 0) { - ca_caps_t Caps; - if (ioctl(fd_ca, CA_GET_CAP, &Caps) == 0) { - int NumSlots = Caps.slot_num; - if (NumSlots > 0) { - //XXX dsyslog("CAM: found %d CAM slots", NumSlots); // TODO let's do this only once we can be sure that there _really_ is a CAM adapter! - if ((Caps.slot_type & CA_CI_LINK) != 0) - return new cCiHandler(fd_ca, NumSlots); - else - isyslog("CAM doesn't support link layer interface"); - } - else - esyslog("ERROR: no CAM slots found"); - } - else - LOG_ERROR_STR(FileName); - close(fd_ca); - } - return NULL; + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + return cas && cas->Ready(); } -uint32_t cCiHandler::ResourceIdToInt(const uint8_t *Data) +const char *cCiTransportConnection::GetCamName(void) { - return (ntohl(get_unaligned((uint32_t *)Data))); + cCiApplicationInformation *ai = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION); + return ai ? ai->GetMenuString() : NULL; } -bool cCiHandler::Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int Status) +void cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data) +{ + cTPDU TPDU(camSlot->SlotIndex(), tcid, Tag, Length, Data); + camSlot->Write(&TPDU); + timer.Set(TC_POLL_TIMEOUT); +} + +void cCiTransportConnection::SendData(int Length, const uint8_t *Data) +{ + // if Length ever exceeds MAX_TPDU_DATA this needs to be handled differently + if (state == stACTIVE && Length > 0) + SendTPDU(T_DATA_LAST, Length, Data); +} + +void cCiTransportConnection::SendTag(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int Status) { uint8_t buffer[16]; uint8_t *p = buffer; @@ -1546,297 +1253,587 @@ bool cCiHandler::Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int put_unaligned(htons(SessionId), (uint16_t *)p); p += 2; buffer[1] = p - buffer - 2; // length - return tc && tc->SendData(p - buffer, buffer) == OK; + SendData(p - buffer, buffer); } -cCiSession *cCiHandler::GetSessionBySessionId(uint16_t SessionId) +void cCiTransportConnection::Poll(void) { - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->SessionId() == SessionId) - return sessions[i]; - } - return NULL; + bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer; + DumpTPDUDataTransfer &= DumpPolls; + if (DumpPolls) + dbgprotocol("Slot %d: ==> Poll\n", camSlot->SlotNumber()); + SendTPDU(T_DATA_LAST); + DumpTPDUDataTransfer = OldDumpTPDUDataTransfer; } -cCiSession *cCiHandler::GetSessionByResourceId(uint32_t ResourceId, int Slot) +uint32_t cCiTransportConnection::ResourceIdToInt(const uint8_t *Data) { - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->Tc()->Slot() == Slot && sessions[i]->ResourceId() == ResourceId) - return sessions[i]; - } - return NULL; + return (ntohl(get_unaligned((uint32_t *)Data))); } -cCiSession *cCiHandler::CreateSession(uint32_t ResourceId) +cCiSession *cCiTransportConnection::GetSessionBySessionId(uint16_t SessionId) { - if (!GetSessionByResourceId(ResourceId, tc->Slot())) { - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (!sessions[i]) { - switch (ResourceId) { - case RI_RESOURCE_MANAGER: return sessions[i] = new cCiResourceManager(i + 1, tc); - case RI_APPLICATION_INFORMATION: return sessions[i] = new cCiApplicationInformation(i + 1, tc); - case RI_CONDITIONAL_ACCESS_SUPPORT: newCaSupport = true; - return sessions[i] = new cCiConditionalAccessSupport(i + 1, tc); - case RI_HOST_CONTROL: break; //XXX - case RI_DATE_TIME: return sessions[i] = new cCiDateTime(i + 1, tc); - case RI_MMI: return sessions[i] = new cCiMMI(i + 1, tc); - } - } - } - } + return (SessionId <= MAX_SESSIONS_PER_TC) ? sessions[SessionId] : NULL; +} + +cCiSession *cCiTransportConnection::GetSessionByResourceId(uint32_t ResourceId) +{ + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) { + if (sessions[i] && sessions[i]->ResourceId() == ResourceId) + return sessions[i]; + } return NULL; } -bool cCiHandler::OpenSession(int Length, const uint8_t *Data) +void cCiTransportConnection::OpenSession(int Length, const uint8_t *Data) { if (Length == 6 && *(Data + 1) == 0x04) { uint32_t ResourceId = ResourceIdToInt(Data + 2); - dbgprotocol("OpenSession %08X\n", ResourceId); - switch (ResourceId) { - case RI_RESOURCE_MANAGER: - case RI_APPLICATION_INFORMATION: - case RI_CONDITIONAL_ACCESS_SUPPORT: - case RI_HOST_CONTROL: - case RI_DATE_TIME: - case RI_MMI: - { - cCiSession *Session = CreateSession(ResourceId); - if (Session) { - Send(ST_OPEN_SESSION_RESPONSE, Session->SessionId(), Session->ResourceId(), SS_OK); - return true; + dbgprotocol("Slot %d: open session %08X\n", camSlot->SlotNumber(), ResourceId); + if (!GetSessionByResourceId(ResourceId)) { + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) { + if (!sessions[i]) { + switch (ResourceId) { + case RI_RESOURCE_MANAGER: sessions[i] = new cCiResourceManager(i, this); break; + case RI_APPLICATION_INFORMATION: sessions[i] = new cCiApplicationInformation(i, this); break; + case RI_CONDITIONAL_ACCESS_SUPPORT: sessions[i] = new cCiConditionalAccessSupport(i, this); break; + case RI_DATE_TIME: sessions[i] = new cCiDateTime(i, this); break; + case RI_MMI: sessions[i] = new cCiMMI(i, this); break; + case RI_HOST_CONTROL: // not implemented + default: esyslog("ERROR: CAM %d: unknown resource identifier: %08X (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid); } - esyslog("ERROR: can't create session for resource identifier: %08X", ResourceId); + if (sessions[i]) + SendTag(ST_OPEN_SESSION_RESPONSE, sessions[i]->SessionId(), sessions[i]->ResourceId(), SS_OK); + return; + } } - break; - default: esyslog("ERROR: unknown resource identifier: %08X", ResourceId); - } + esyslog("ERROR: CAM %d: no free session slot for resource identifier %08X (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid); + } + else + esyslog("ERROR: CAM %d: session for resource identifier %08X already exists (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid); } - return false; } -bool cCiHandler::CloseSession(uint16_t SessionId) +void cCiTransportConnection::CloseSession(uint16_t SessionId) { - dbgprotocol("CloseSession %d\n", SessionId); + dbgprotocol("Slot %d: close session %d\n", camSlot->SlotNumber(), SessionId); cCiSession *Session = GetSessionBySessionId(SessionId); - if (Session && sessions[SessionId - 1] == Session) { + if (Session && sessions[SessionId] == Session) { delete Session; - sessions[SessionId - 1] = NULL; - Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK); - return true; + sessions[SessionId] = NULL; + SendTag(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK); } else { - esyslog("ERROR: unknown session id: %d", SessionId); - Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED); + esyslog("ERROR: CAM %d: unknown session id: %d (%d/%d)", camSlot->SlotNumber(), SessionId, camSlot->SlotIndex(), tcid); + SendTag(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED); + } +} + +void cCiTransportConnection::HandleSessions(cTPDU *TPDU) +{ + int Length; + const uint8_t *Data = TPDU->Data(Length); + if (Data && Length > 1) { + switch (*Data) { + case ST_SESSION_NUMBER: if (Length > 4) { + uint16_t SessionId = ntohs(get_unaligned((uint16_t *)&Data[2])); + cCiSession *Session = GetSessionBySessionId(SessionId); + if (Session) + Session->Process(Length - 4, Data + 4); + else + esyslog("ERROR: CAM %d: unknown session id: %d (%d/%d)", camSlot->SlotNumber(), SessionId, camSlot->SlotIndex(), tcid); + } + break; + case ST_OPEN_SESSION_REQUEST: OpenSession(Length, Data); + break; + case ST_CLOSE_SESSION_REQUEST: if (Length == 4) + CloseSession(ntohs(get_unaligned((uint16_t *)&Data[2]))); + break; + case ST_CREATE_SESSION_RESPONSE: // not implemented + case ST_CLOSE_SESSION_RESPONSE: // not implemented + default: esyslog("ERROR: CAM %d: unknown session tag: %02X (%d/%d)", camSlot->SlotNumber(), *Data, camSlot->SlotIndex(), tcid); + } } - return false; } -int cCiHandler::CloseAllSessions(int Slot) +bool cCiTransportConnection::Process(cTPDU *TPDU) { - int result = 0; - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->Tc()->Slot() == Slot) { - CloseSession(sessions[i]->SessionId()); - result++; - } - } - return result; + if (TPDU) + alive.Set(TC_ALIVE_TIMEOUT); + else if (alive.TimedOut()) + return false; + switch (state) { + case stIDLE: + if (createConnectionRequested) { + dbgprotocol("Slot %d: create connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + createConnectionRequested = false; + SendTPDU(T_CREATE_TC); + state = stCREATION; + } + return true; + case stCREATION: + if (TPDU && TPDU->Tag() == T_CTC_REPLY) { + dbgprotocol("Slot %d: connection created %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + Poll(); + state = stACTIVE; + } + else if (timer.TimedOut()) { + dbgprotocol("Slot %d: timeout while creating connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + state = stIDLE; + } + return true; + case stACTIVE: + if (deleteConnectionRequested) { + dbgprotocol("Slot %d: delete connection requested %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + deleteConnectionRequested = false; + SendTPDU(T_DELETE_TC); + state = stDELETION; + return true; + } + if (TPDU) { + switch (TPDU->Tag()) { + case T_REQUEST_TC: + esyslog("ERROR: CAM %d: T_REQUEST_TC not implemented (%d/%d)", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + break; + case T_DATA_MORE: + case T_DATA_LAST: + HandleSessions(TPDU); + // continue with T_SB + case T_SB: + if ((TPDU->Status() & DATA_INDICATOR) != 0) { + dbgprotocol("Slot %d: receive data %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + SendTPDU(T_RCV); + } + break; + case T_DELETE_TC: + dbgprotocol("Slot %d: delete connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + SendTPDU(T_DTC_REPLY); + state = stIDLE; + return true; + } + } + else if (timer.TimedOut()) + Poll(); + hasUserIO = false; + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) { + if (sessions[i]) { + sessions[i]->Process(); + if (sessions[i]->HasUserIO()) + hasUserIO = true; + } + } + break; + case stDELETION: + if (TPDU && TPDU->Tag() == T_DTC_REPLY || timer.TimedOut()) { + dbgprotocol("Slot %d: connection deleted %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + state = stIDLE; + } + return true; + } + return true; } -int cCiHandler::NumCams(void) +// --- cCiCaPidData ---------------------------------------------------------- + +class cCiCaPidData : public cListObject { +public: + bool active; + int pid; + int streamType; + cCiCaPidData(int Pid, int StreamType) + { + active = false; + pid = Pid; + streamType = StreamType; + } + }; + +// --- cCiCaProgramData ------------------------------------------------------ + +class cCiCaProgramData : public cListObject { +public: + int programNumber; + bool modified; + cList pidList; + cCiCaProgramData(int ProgramNumber) + { + programNumber = ProgramNumber; + modified = false; + } + }; + +// --- cCiAdapter ------------------------------------------------------------ + +cCiAdapter::cCiAdapter(void) +:cThread("CI adapter") { - int result = 0; - for (int i = 0; i < MAX_CI_SLOT; i++) - if (moduleReady[i]) - result++; - return result; + assignedDevice = NULL; + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) + camSlots[i] = NULL; } -bool cCiHandler::Ready(void) +cCiAdapter::~cCiAdapter() { - cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - if (moduleReady[Slot]) { - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (!cas || !*cas->GetCaSystemIds()) - return false; - } + Cancel(3); + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) + delete camSlots[i]; +} + +void cCiAdapter::AddCamSlot(cCamSlot *CamSlot) +{ + if (CamSlot) { + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) { + if (!camSlots[i]) { + CamSlot->slotIndex = i; + camSlots[i] = CamSlot; + return; + } + } + esyslog("ERROR: no free CAM slot in CI adapter"); + } +} + +bool cCiAdapter::Ready(void) +{ + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) { + if (camSlots[i] && !camSlots[i]->Ready()) + return false; } return true; } -bool cCiHandler::Process(int Slot) +void cCiAdapter::Action(void) +{ + cTPDU TPDU; + while (Running()) { + int n = Read(TPDU.Buffer(), TPDU.MaxSize()); + if (n > 0 && TPDU.Slot() < MAX_CAM_SLOTS_PER_ADAPTER) { + TPDU.SetSize(n); + cCamSlot *cs = camSlots[TPDU.Slot()]; + TPDU.Dump(cs ? cs->SlotNumber() : 0, false); + if (cs) + cs->Process(&TPDU); + } + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) { + if (camSlots[i]) + camSlots[i]->Process(); + } + } +} + +// --- cCamSlot -------------------------------------------------------------- + +cCamSlots CamSlots; + +#define MODULE_CHECK_INTERVAL 100 // ms +#define MODULE_RESET_TIMEOUT 2 // s + +cCamSlot::cCamSlot(cCiAdapter *CiAdapter) +{ + ciAdapter = CiAdapter; + slotIndex = -1; + lastModuleStatus = msReset; // avoids initial reset log message + resetTime = 0; + resendPmt = false; + source = transponder = 0; + for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway + tc[i] = NULL; + CamSlots.Add(this); + slotNumber = Index() + 1; + if (ciAdapter) + ciAdapter->AddCamSlot(this); + Reset(); +} + +cCamSlot::~cCamSlot() +{ + CamSlots.Del(this, false); + DeleteAllConnections(); +} + +bool cCamSlot::Assign(cDevice *Device, bool Query) { - bool result = true; cMutexLock MutexLock(&mutex); - for (int slot = 0; slot < numSlots; slot++) { - if (Slot < 0 || slot == Slot) { - tc = tpl->Process(slot); - if (tc) { - int Length; - const uint8_t *Data = tc->Data(Length); - if (Data && Length > 1) { - switch (*Data) { - case ST_SESSION_NUMBER: if (Length > 4) { - uint16_t SessionId = ntohs(get_unaligned((uint16_t *)&Data[2])); - cCiSession *Session = GetSessionBySessionId(SessionId); - if (Session) - Session->Process(Length - 4, Data + 4); - else - esyslog("ERROR: unknown session id: %d", SessionId); - } - break; - case ST_OPEN_SESSION_REQUEST: OpenSession(Length, Data); - break; - case ST_CLOSE_SESSION_REQUEST: if (Length == 4) - CloseSession(ntohs(get_unaligned((uint16_t *)&Data[2]))); - break; - case ST_CREATE_SESSION_RESPONSE: //XXX fall through to default - case ST_CLOSE_SESSION_RESPONSE: //XXX fall through to default - default: esyslog("ERROR: unknown session tag: %02X", *Data); + if (ciAdapter) { + if (ciAdapter->Assign(Device, true)) { + if (!Device && ciAdapter->assignedDevice) + ciAdapter->assignedDevice->SetCamSlot(NULL); + if (!Query) { + StopDecrypting(); + source = transponder = 0; + if (ciAdapter->Assign(Device)) { + ciAdapter->assignedDevice = Device; + if (Device) { + Device->SetCamSlot(this); + dsyslog("CAM %d: assigned to device %d", slotNumber, Device->DeviceNumber() + 1); } - } - } - else if (CloseAllSessions(slot)) { - tpl->ResetSlot(slot); - result = false; - } - else if (tpl->ModuleReady(slot)) { - dbgprotocol("Module ready in slot %d\n", slot); - moduleReady[slot] = true; - tpl->NewConnection(slot); - } + else + dsyslog("CAM %d: unassigned", slotNumber); + } + else + return false; + } + return true; + } + } + return false; +} + +cDevice *cCamSlot::Device(void) +{ + cMutexLock MutexLock(&mutex); + if (ciAdapter) { + cDevice *d = ciAdapter->assignedDevice; + if (d && d->CamSlot() == this) + return d; + } + return NULL; +} + +void cCamSlot::NewConnection(void) +{ + cMutexLock MutexLock(&mutex); + for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { + if (!tc[i]) { + tc[i] = new cCiTransportConnection(this, i); + tc[i]->CreateConnection(); + return; } } - SendCaPmt(); - bool UserIO = false; - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->Process()) - UserIO |= sessions[i]->HasUserIO(); + esyslog("ERROR: CAM %d: can't create new transport connection!", slotNumber); +} + +void cCamSlot::DeleteAllConnections(void) +{ + cMutexLock MutexLock(&mutex); + for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { + delete tc[i]; + tc[i] = NULL; } - hasUserIO = UserIO; - return result; } -void cCiHandler::SendCaPmt(void) +void cCamSlot::Process(cTPDU *TPDU) { cMutexLock MutexLock(&mutex); - if (newCaSupport) { - newCaSupport = false; - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (cas) { - // build the list of CA_PMT data: - cList CaPmtList; - for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { - bool Active = false; - cCiCaPmt *CaPmt = new cCiCaPmt(CPCI_OK_DESCRAMBLING, source, transponder, p->programNumber, GetCaSystemIds(Slot)); - if (CaPmt->Valid()) { - for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { - if (q->active) { - CaPmt->AddPid(q->pid, q->streamType); - Active = true; - } - } - } - if (Active) - CaPmtList.Add(CaPmt); - else - delete CaPmt; - } - // send the CA_PMT data: - uint8_t ListManagement = CaPmtList.Count() > 1 ? CPLM_FIRST : CPLM_ONLY; - for (cCiCaPmt *CaPmt = CaPmtList.First(); CaPmt; CaPmt = CaPmtList.Next(CaPmt)) { - CaPmt->SetListManagement(ListManagement); - if (!cas->SendPMT(CaPmt)) - newCaSupport = true; - ListManagement = CaPmt->Next() && CaPmt->Next()->Next() ? CPLM_MORE : CPLM_LAST; - } - } + if (TPDU) { + int n = TPDU->Tcid(); + if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) { + if (tc[n]) + tc[n]->Process(TPDU); + } + } + for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { + if (tc[i]) { + if (!tc[i]->Process()) { + Reset(); + return; + } } + } + if (moduleCheckTimer.TimedOut()) { + eModuleStatus ms = ModuleStatus(); + if (ms != lastModuleStatus) { + switch (ms) { + case msNone: + dbgprotocol("Slot %d: no module present\n", slotNumber); + isyslog("CAM %d: no module present", slotNumber); + DeleteAllConnections(); + break; + case msReset: + dbgprotocol("Slot %d: module reset\n", slotNumber); + isyslog("CAM %d: module reset", slotNumber); + DeleteAllConnections(); + break; + case msPresent: + dbgprotocol("Slot %d: module present\n", slotNumber); + isyslog("CAM %d: module present", slotNumber); + break; + case msReady: + dbgprotocol("Slot %d: module ready\n", slotNumber); + isyslog("CAM %d: module ready", slotNumber); + NewConnection(); + resendPmt = caProgramList.Count() > 0; + break; + } + lastModuleStatus = ms; + } + moduleCheckTimer.Set(MODULE_CHECK_INTERVAL); + } + if (resendPmt) + SendCaPmt(CPCI_OK_DESCRAMBLING); + processed.Broadcast(); +} + +cCiSession *cCamSlot::GetSessionByResourceId(uint32_t ResourceId) +{ + cMutexLock MutexLock(&mutex); + return tc[1] ? tc[1]->GetSessionByResourceId(ResourceId) : NULL; +} + +void cCamSlot::Write(cTPDU *TPDU) +{ + cMutexLock MutexLock(&mutex); + if (ciAdapter && TPDU->Size()) { + TPDU->Dump(SlotNumber(), true); + ciAdapter->Write(TPDU->Buffer(), TPDU->Size()); + } +} + +bool cCamSlot::Reset(void) +{ + cMutexLock MutexLock(&mutex); + ChannelCamRelations.Reset(slotNumber); + DeleteAllConnections(); + if (ciAdapter) { + dbgprotocol("Slot %d: reset...", slotNumber); + if (ciAdapter->Reset(slotIndex)) { + resetTime = time(NULL); + dbgprotocol("ok.\n"); + return true; + } + dbgprotocol("failed!\n"); + } + return false; +} + +eModuleStatus cCamSlot::ModuleStatus(void) +{ + cMutexLock MutexLock(&mutex); + eModuleStatus ms = ciAdapter ? ciAdapter->ModuleStatus(slotIndex) : msNone; + if (resetTime) { + if (ms <= msReset) { + if (time(NULL) - resetTime < MODULE_RESET_TIMEOUT) + return msReset; + } + resetTime = 0; } + return ms; +} + +const char *cCamSlot::GetCamName(void) +{ + cMutexLock MutexLock(&mutex); + return tc[1] ? tc[1]->GetCamName() : NULL; } -bool cCiHandler::EnterMenu(int Slot) +bool cCamSlot::Ready(void) { cMutexLock MutexLock(&mutex); - cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot); + return ModuleStatus() == msNone || tc[1] && tc[1]->Ready(); +} + +bool cCamSlot::HasMMI(void) +{ + return GetSessionByResourceId(RI_MMI); +} + +bool cCamSlot::HasUserIO(void) +{ + cMutexLock MutexLock(&mutex); + return tc[1] && tc[1]->HasUserIO(); +} + +bool cCamSlot::EnterMenu(void) +{ + cMutexLock MutexLock(&mutex); + cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION); return api ? api->EnterMenu() : false; } -cCiMenu *cCiHandler::GetMenu(void) +cCiMenu *cCamSlot::GetMenu(void) { cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot); - if (mmi) { - cCiMenu *Menu = mmi->Menu(); - if (Menu) - Menu->mutex = &mutex; - return Menu; - } - } + cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI); + if (mmi) { + cCiMenu *Menu = mmi->Menu(); + if (Menu) + Menu->mutex = &mutex; + return Menu; + } return NULL; } -cCiEnquiry *cCiHandler::GetEnquiry(void) +cCiEnquiry *cCamSlot::GetEnquiry(void) { cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot); - if (mmi) { - cCiEnquiry *Enquiry = mmi->Enquiry(); - if (Enquiry) - Enquiry->mutex = &mutex; - return Enquiry; - } - } + cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI); + if (mmi) { + cCiEnquiry *Enquiry = mmi->Enquiry(); + if (Enquiry) + Enquiry->mutex = &mutex; + return Enquiry; + } return NULL; } -const char *cCiHandler::GetCamName(int Slot) +void cCamSlot::SendCaPmt(uint8_t CmdId) { cMutexLock MutexLock(&mutex); - cCiApplicationInformation *ai = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot); - return ai ? ai->GetMenuString() : NULL; + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + if (cas) { + const int *CaSystemIds = cas->GetCaSystemIds(); + if (CaSystemIds && *CaSystemIds) { + if (caProgramList.Count()) { + for (int Loop = 1; Loop <= 2; Loop++) { + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + if (p->modified || resendPmt) { + bool Active = false; + cCiCaPmt CaPmt(CmdId, source, transponder, p->programNumber, CaSystemIds); + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->active) { + CaPmt.AddPid(q->pid, q->streamType); + Active = true; + } + } + if ((Loop == 1) != Active) { // first remove, then add + if (cas->RepliesToQuery()) + CaPmt.SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE); + if (Active || cas->RepliesToQuery()) + cas->SendPMT(&CaPmt); + p->modified = false; + } + } + } + } + resendPmt = false; + } + else { + cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL); + cas->SendPMT(&CaPmt); + } + } + } } -const unsigned short *cCiHandler::GetCaSystemIds(int Slot) +const int *cCamSlot::GetCaSystemIds(void) { cMutexLock MutexLock(&mutex); - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); return cas ? cas->GetCaSystemIds() : NULL; } -bool cCiHandler::ProvidesCa(const unsigned short *CaSystemIds) +int cCamSlot::Priority(void) { - cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (cas) { - for (const unsigned short *ids = cas->GetCaSystemIds(); ids && *ids; ids++) { - for (const unsigned short *id = CaSystemIds; *id; id++) { - if (*id == *ids) - return true; - } - } - } - } - return false; + cDevice *d = Device(); + return d ? d->Priority() : -1; } -void cCiHandler::SetSource(int Source, int Transponder) +bool cCamSlot::ProvidesCa(const int *CaSystemIds) { cMutexLock MutexLock(&mutex); - if (source != Source || transponder != Transponder) { - //XXX if there are active entries, send an empty CA_PMT - caProgramList.Clear(); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + if (cas) { + for (const int *ids = cas->GetCaSystemIds(); ids && *ids; ids++) { + for (const int *id = CaSystemIds; *id; id++) { + if (*id == *ids) + return true; + } + } } - source = Source; - transponder = Transponder; + return false; } -void cCiHandler::AddPid(int ProgramNumber, int Pid, int StreamType) +void cCamSlot::AddPid(int ProgramNumber, int Pid, int StreamType) { cMutexLock MutexLock(&mutex); cCiCaProgramData *ProgramData = NULL; @@ -1854,65 +1851,276 @@ void cCiHandler::AddPid(int ProgramNumber, int Pid, int StreamType) ProgramData->pidList.Add(new cCiCaPidData(Pid, StreamType)); } -void cCiHandler::SetPid(int Pid, bool Active) +void cCamSlot::SetPid(int Pid, bool Active) { cMutexLock MutexLock(&mutex); for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { if (q->pid == Pid) { - q->active = Active; + if (q->active != Active) { + q->active = Active; + p->modified = true; + } return; } } } } -bool cCiHandler::CanDecrypt(int ProgramNumber) +// see ISO/IEC 13818-1 +#define STREAM_TYPE_VIDEO 0x02 +#define STREAM_TYPE_AUDIO 0x04 +#define STREAM_TYPE_DOLBY 0x06 + +void cCamSlot::AddChannel(const cChannel *Channel) { cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (cas) { - for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { - if (p->programNumber == ProgramNumber) { - cCiCaPmt CaPmt(CPCI_QUERY, source, transponder, p->programNumber, GetCaSystemIds(Slot));//XXX??? - if (CaPmt.Valid()) { - for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { -//XXX if (q->active) - CaPmt.AddPid(q->pid, q->streamType); - } - } - if (!cas->SendPMT(&CaPmt)) - return false;//XXX - //XXX - time_t timeout = time(NULL) + 3;//XXX - while (time(NULL) <= timeout) { - Process(Slot); - cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (!cas) - return false;//XXX - if (cas->ReceivedReply(true)) - return true; - //XXX remember if a slot doesn't receive a reply - } - break; - } + if (source != Channel->Source() || transponder != Channel->Transponder()) + StopDecrypting(); + source = Channel->Source(); + transponder = Channel->Transponder(); + if (Channel->Ca() >= CA_ENCRYPTED_MIN) { + AddPid(Channel->Sid(), Channel->Vpid(), STREAM_TYPE_VIDEO); + for (const int *Apid = Channel->Apids(); *Apid; Apid++) + AddPid(Channel->Sid(), *Apid, STREAM_TYPE_AUDIO); + for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++) + AddPid(Channel->Sid(), *Dpid, STREAM_TYPE_DOLBY); + } +} + +#define QUERY_REPLY_WAIT 100 // ms to wait between checks for a reply + +bool cCamSlot::CanDecrypt(const cChannel *Channel) +{ + if (Channel->Ca() < CA_ENCRYPTED_MIN) + return true; // channel not encrypted + if (!IsDecrypting()) + return true; // any CAM can decrypt at least one channel + cMutexLock MutexLock(&mutex); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + if (cas && cas->RepliesToQuery()) { + cCiCaPmt CaPmt(CPCI_QUERY, Channel->Source(), Channel->Transponder(), Channel->Sid(), GetCaSystemIds()); + CaPmt.SetListManagement(CPLM_ADD); // WORKAROUND: CPLM_ONLY doesn't work with Alphacrypt 3.09 (deletes existing CA_PMTs) + CaPmt.AddPid(Channel->Vpid(), STREAM_TYPE_VIDEO); + for (const int *Apid = Channel->Apids(); *Apid; Apid++) + CaPmt.AddPid(*Apid, STREAM_TYPE_AUDIO); + for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++) + CaPmt.AddPid(*Dpid, STREAM_TYPE_DOLBY); + cas->SendPMT(&CaPmt); + cTimeMs Timeout(QUERY_REPLY_TIMEOUT); + do { + processed.TimedWait(mutex, QUERY_REPLY_WAIT); + if ((cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT)) != NULL) { // must re-fetch it, there might have been a reset + if (cas->ReceivedReply()) + return cas->CanDecrypt(); + } + else + return false; + } while (!Timeout.TimedOut()); + dsyslog("CAM %d: didn't reply to QUERY", SlotNumber()); + } + return false; +} + +void cCamSlot::StartDecrypting(void) +{ + SendCaPmt(CPCI_OK_DESCRAMBLING); +} + +void cCamSlot::StopDecrypting(void) +{ + cMutexLock MutexLock(&mutex); + if (caProgramList.Count()) { + caProgramList.Clear(); + SendCaPmt(CPCI_NOT_SELECTED); + } +} + +bool cCamSlot::IsDecrypting(void) +{ + cMutexLock MutexLock(&mutex); + if (caProgramList.Count()) { + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + if (p->modified) + return true; // any modifications need to be processed before we can assume it's no longer decrypting + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->active) + return true; } } - } + } return false; } -void cCiHandler::StartDecrypting(void) +// --- cChannelCamRelation --------------------------------------------------- + +#define CAM_CHECKED_TIMEOUT 15 // seconds before a CAM that has been checked for a particular channel will be checked again + +class cChannelCamRelation : public cListObject { +private: + tChannelID channelID; + uint32_t camSlotsChecked; + uint32_t camSlotsDecrypt; + time_t lastChecked; +public: + cChannelCamRelation(tChannelID ChannelID); + bool TimedOut(void); + tChannelID ChannelID(void) { return channelID; } + bool CamChecked(int CamSlotNumber); + bool CamDecrypt(int CamSlotNumber); + void SetChecked(int CamSlotNumber); + void SetDecrypt(int CamSlotNumber); + void ClrChecked(int CamSlotNumber); + void ClrDecrypt(int CamSlotNumber); + }; + +cChannelCamRelation::cChannelCamRelation(tChannelID ChannelID) +{ + channelID = ChannelID; + camSlotsChecked = 0; + camSlotsDecrypt = 0; + lastChecked = 0; +} + +bool cChannelCamRelation::TimedOut(void) +{ + return !camSlotsDecrypt && time(NULL) - lastChecked > CAM_CHECKED_TIMEOUT; +} + +bool cChannelCamRelation::CamChecked(int CamSlotNumber) +{ + if (lastChecked && time(NULL) - lastChecked > CAM_CHECKED_TIMEOUT) { + lastChecked = 0; + camSlotsChecked = 0; + } + return camSlotsChecked & (1 << (CamSlotNumber - 1)); +} + +bool cChannelCamRelation::CamDecrypt(int CamSlotNumber) +{ + return camSlotsDecrypt & (1 << (CamSlotNumber - 1)); +} + +void cChannelCamRelation::SetChecked(int CamSlotNumber) +{ + camSlotsChecked |= (1 << (CamSlotNumber - 1)); + lastChecked = time(NULL); + ClrDecrypt(CamSlotNumber); +} + +void cChannelCamRelation::SetDecrypt(int CamSlotNumber) +{ + camSlotsDecrypt |= (1 << (CamSlotNumber - 1)); + ClrChecked(CamSlotNumber); +} + +void cChannelCamRelation::ClrChecked(int CamSlotNumber) +{ + camSlotsChecked &= ~(1 << (CamSlotNumber - 1)); + lastChecked = 0; +} + +void cChannelCamRelation::ClrDecrypt(int CamSlotNumber) +{ + camSlotsDecrypt &= ~(1 << (CamSlotNumber - 1)); +} + +// --- cChannelCamRelations -------------------------------------------------- + +#define CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL 3600 // seconds between cleanups + +cChannelCamRelations ChannelCamRelations; + +cChannelCamRelations::cChannelCamRelations(void) +{ + lastCleanup = time(NULL); +} + +void cChannelCamRelations::Cleanup(void) +{ + cMutexLock MutexLock(&mutex); + if (time(NULL) - lastCleanup > CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL) { + for (cChannelCamRelation *ccr = First(); ccr; ) { + cChannelCamRelation *c = ccr; + ccr = Next(ccr); + if (c->TimedOut()) + Del(c); + } + lastCleanup = time(NULL); + } +} + +cChannelCamRelation *cChannelCamRelations::GetEntry(tChannelID ChannelID) +{ + cMutexLock MutexLock(&mutex); + Cleanup(); + for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) { + if (ccr->ChannelID() == ChannelID) + return ccr; + } + return NULL; +} + +cChannelCamRelation *cChannelCamRelations::AddEntry(tChannelID ChannelID) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = GetEntry(ChannelID); + if (!ccr) + Add(ccr = new cChannelCamRelation(ChannelID)); + return ccr; +} + +void cChannelCamRelations::Reset(int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) { + ccr->ClrChecked(CamSlotNumber); + ccr->ClrDecrypt(CamSlotNumber); + } +} + +bool cChannelCamRelations::CamChecked(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = GetEntry(ChannelID); + return ccr ? ccr->CamChecked(CamSlotNumber) : false; +} + +bool cChannelCamRelations::CamDecrypt(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = GetEntry(ChannelID); + return ccr ? ccr->CamDecrypt(CamSlotNumber) : false; +} + +void cChannelCamRelations::SetChecked(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = AddEntry(ChannelID); + if (ccr) + ccr->SetChecked(CamSlotNumber); +} + +void cChannelCamRelations::SetDecrypt(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = AddEntry(ChannelID); + if (ccr) + ccr->SetDecrypt(CamSlotNumber); +} + +void cChannelCamRelations::ClrChecked(tChannelID ChannelID, int CamSlotNumber) { cMutexLock MutexLock(&mutex); - newCaSupport = true; - SendCaPmt(); + cChannelCamRelation *ccr = GetEntry(ChannelID); + if (ccr) + ccr->ClrChecked(CamSlotNumber); } -bool cCiHandler::Reset(int Slot) +void cChannelCamRelations::ClrDecrypt(tChannelID ChannelID, int CamSlotNumber) { cMutexLock MutexLock(&mutex); - CloseAllSessions(Slot); - return tpl->ResetSlot(Slot, true); + cChannelCamRelation *ccr = GetEntry(ChannelID); + if (ccr) + ccr->ClrDecrypt(CamSlotNumber); } diff --git a/ci.h b/ci.h index 4574010..37514a5 100644 --- a/ci.h +++ b/ci.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ci.h 1.22 2006/08/12 09:43:31 kls Exp $ + * $Id: ci.h 1.23 2007/01/03 12:49:10 kls Exp $ */ #ifndef __CI_H @@ -12,13 +12,18 @@ #include #include +#include "channels.h" #include "thread.h" #include "tools.h" +#define MAX_CAM_SLOTS_PER_ADAPTER 8 // maximum possible value is 255 +#define MAX_CONNECTIONS_PER_CAM_SLOT 8 // maximum possible value is 254 +#define CAM_READ_TIMEOUT 50 // ms + class cCiMMI; class cCiMenu { - friend class cCiHandler; + friend class cCamSlot; friend class cCiMMI; private: enum { MAX_CIMENU_ENTRIES = 64 }; ///< XXX is there a specified maximum? @@ -40,14 +45,14 @@ public: const char *Entry(int n) { return n < numEntries ? entries[n] : NULL; } int NumEntries(void) { return numEntries; } bool Selectable(void) { return selectable; } - bool Select(int Index); - bool Cancel(void); - bool Abort(void); + void Select(int Index); + void Cancel(void); + void Abort(void); bool HasUpdate(void); }; class cCiEnquiry { - friend class cCiHandler; + friend class cCamSlot; friend class cCiMMI; private: cCiMMI *mmi; @@ -61,103 +66,140 @@ public: const char *Text(void) { return text; } bool Blind(void) { return blind; } int ExpectedLength(void) { return expectedLength; } - bool Reply(const char *s); - bool Cancel(void); - bool Abort(void); + void Reply(const char *s); + void Cancel(void); + void Abort(void); }; -#define MAX_CI_SESSION 16 //XXX -#define MAX_CI_SLOT 16 +class cDevice; +class cCamSlot; -class cCiCaPidData : public cListObject { -public: - bool active; - int pid; - int streamType; - cCiCaPidData(int Pid, int StreamType) - { - active = false; - pid = Pid; - streamType = StreamType; - } - }; +enum eModuleStatus { msNone, msReset, msPresent, msReady }; -class cCiCaProgramData : public cListObject { +class cCiAdapter : public cThread { + friend class cCamSlot; +private: + cDevice *assignedDevice; + cCamSlot *camSlots[MAX_CAM_SLOTS_PER_ADAPTER]; + void AddCamSlot(cCamSlot *CamSlot); + ///< Adds the given CamSlot to this CI adapter. +protected: + virtual void Action(void); + ///< Handles the attached CAM slots in a separate thread. + ///< The derived class must call the Start() function to + ///< actually start CAM handling. + virtual int Read(uint8_t *Buffer, int MaxLength) = 0; + ///< Reads one chunk of data into the given Buffer, up to MaxLength bytes. + ///< If no data is available immediately, wait for up to CAM_READ_TIMEOUT. + ///< Returns the number of bytes read (in case of an error it will also + ///< return 0). + virtual void Write(const uint8_t *Buffer, int Length) = 0; + ///< Writes Length bytes of the given Buffer. + virtual bool Reset(int Slot) = 0; + ///< Resets the CAM in the given Slot. + ///< Returns true if the operation was successful. + virtual eModuleStatus ModuleStatus(int Slot) = 0; + ///< Returns the status of the CAM in the given Slot. + virtual bool Assign(cDevice *Device, bool Query = false) = 0; + ///< Assigns this adapter to the given Device, if this is possible. + ///< If Query is 'true', the adapter only checks whether it can be + ///< assigned to the Device, but doesn't actually assign itself to it. + ///< Returns true if the adapter can be assigned to the Device. + ///< If Device is NULL, the adapter will be unassigned from any + ///< device it was previously assigned to. The value of Query + ///< is ignored in that case, and this function always returns + ///< 'true'. public: - int programNumber; - cList pidList; - cCiCaProgramData(int ProgramNumber) - { - programNumber = ProgramNumber; - } + cCiAdapter(void); + virtual ~cCiAdapter(); + ///< The derived class must call Cancel(3) in its destructor. + virtual bool Ready(void); + ///< Returns 'true' if all present CAMs in this adapter are ready. }; -class cCiSession; -class cCiTransportLayer; +class cTPDU; class cCiTransportConnection; +class cCiSession; +class cCiCaProgramData; -class cCiHandler { +class cCamSlot : public cListObject { + friend class cCiAdapter; + friend class cCiTransportConnection; private: cMutex mutex; - int fd; - int numSlots; - bool newCaSupport; - bool hasUserIO; - bool moduleReady[MAX_CI_SLOT]; - cCiSession *sessions[MAX_CI_SESSION]; - cCiTransportLayer *tpl; - cCiTransportConnection *tc; + cCondVar processed; + cCiAdapter *ciAdapter; + int slotIndex; + int slotNumber; + cCiTransportConnection *tc[MAX_CONNECTIONS_PER_CAM_SLOT + 1]; // connection numbering starts with 1 + eModuleStatus lastModuleStatus; + time_t resetTime; + cTimeMs moduleCheckTimer; + bool resendPmt; int source; int transponder; cList caProgramList; - uint32_t ResourceIdToInt(const uint8_t *Data); - bool Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId = 0, int Status = -1); - const unsigned short *GetCaSystemIds(int Slot); - cCiSession *GetSessionBySessionId(uint16_t SessionId); - cCiSession *GetSessionByResourceId(uint32_t ResourceId, int Slot); - cCiSession *CreateSession(uint32_t ResourceId); - bool OpenSession(int Length, const uint8_t *Data); - bool CloseSession(uint16_t SessionId); - int CloseAllSessions(int Slot); - cCiHandler(int Fd, int NumSlots); - void SendCaPmt(void); + const int *GetCaSystemIds(void); + void SendCaPmt(uint8_t CmdId); + void NewConnection(void); + void DeleteAllConnections(void); + void Process(cTPDU *TPDU = NULL); + void Write(cTPDU *TPDU); + cCiSession *GetSessionByResourceId(uint32_t ResourceId); public: - ~cCiHandler(); - static cCiHandler *CreateCiHandler(const char *FileName); - ///< Creates a new cCiHandler for the given CA device. - int NumSlots(void) { return numSlots; } - ///< Returns the number of CAM slots provided by this CA device. - int NumCams(void); - ///< Returns the number of actual CAMs inserted into this CA device. + cCamSlot(cCiAdapter *CiAdapter); + ///< Creates a new CAM slot for the given CiAdapter. + ///< The CiAdapter will take care of deleting the CAM slot, + ///< so the caller must not delete it! + virtual ~cCamSlot(); + bool Assign(cDevice *Device, bool Query = false); + ///< Assigns this CAM slot to the given Device, if this is possible. + ///< If Query is 'true', the CI adapter of this slot only checks whether + ///< it can be assigned to the Device, but doesn't actually assign itself to it. + ///< Returns true if this slot can be assigned to the Device. + ///< If Device is NULL, the slot will be unassigned from any + ///< device it was previously assigned to. The value of Query + ///< is ignored in that case, and this function always returns + ///< 'true'. + cDevice *Device(void); + ///< Returns the device this CAM slot is currently assigned to. + int SlotIndex(void) { return slotIndex; } + ///< Returns the index of this CAM slot within its CI adapter. + ///< The first slot has an index of 0. + int SlotNumber(void) { return slotNumber; } + ///< Returns the number of this CAM slot within the whole system. + ///< The first slot has the number 1. + bool Reset(void); + ///< Resets the CAM in this slot. + ///< Returns true if the operation was successful. + eModuleStatus ModuleStatus(void); + ///< Returns the status of the CAM in this slot. + const char *GetCamName(void); + ///< Returns the name of the CAM in this slot, or NULL if there is + ///< no ready CAM in this slot. bool Ready(void); - ///< Returns true if all CAMs in this CA device are ready. - bool Process(int Slot = -1); - ///< Processes the given Slot. If Slot is -1, all slots are processed. - ///< Returns false in case of an error. - bool HasUserIO(void) { return hasUserIO; } + ///< Returns 'true' if the CAM in this slot is ready to decrypt. + bool HasMMI(void); + ///< Returns 'true' if the CAM in this slot has an active MMI. + bool HasUserIO(void); ///< Returns true if there is a pending user interaction, which shall ///< be retrieved via GetMenu() or GetEnquiry(). - bool EnterMenu(int Slot); - ///< Requests the CAM in the given Slot to start its menu. + bool EnterMenu(void); + ///< Requests the CAM in this slot to start its menu. cCiMenu *GetMenu(void); ///< Gets a pending menu, or NULL if there is no menu. cCiEnquiry *GetEnquiry(void); ///< Gets a pending enquiry, or NULL if there is no enquiry. - const char *GetCamName(int Slot); - ///< Returns the name of the CAM in the given Slot, or NULL if there - ///< is no CAM in that slot. - bool ProvidesCa(const unsigned short *CaSystemIds); //XXX Slot??? - ///< Returns true if any of the CAMs can provide one of the given + int Priority(void); + ///< Returns the priority if the device this slot is currently assigned + ///< to, or -1 if it is not assigned to any device. + bool ProvidesCa(const int *CaSystemIds); + ///< Returns true if the CAM in this slot provides one of the given ///< CaSystemIds. This doesn't necessarily mean that it will be ///< possible to actually decrypt such a programme, since CAMs ///< usually advertise several CA system ids, while the actual ///< decryption is controlled by the smart card inserted into ///< the CAM. - void SetSource(int Source, int Transponder); - ///< Sets the Source and Transponder of the device this cCiHandler is - ///< currently tuned to. If Source or Transponder are different than - ///< what was given in a previous call to SetSource(), any previously - ///< added PIDs will be cleared. void AddPid(int ProgramNumber, int Pid, int StreamType); ///< Adds the given PID information to the list of PIDs. A later call ///< to SetPid() will (de)activate one of these entries. @@ -165,16 +207,55 @@ public: ///< Sets the given Pid (which has previously been added through a ///< call to AddPid()) to Active. A later call to StartDecrypting() will ///< send the full list of currently active CA_PMT entries to the CAM. - bool CanDecrypt(int ProgramNumber); - ///< XXX - ///< Returns true if there is a CAM in this CA device that is able - ///< to decrypt the programme with the given ProgramNumber. The PIDs - ///< for this ProgramNumber must have been set through previous calls - ///< to SetPid(). + void AddChannel(const cChannel *Channel); + ///< Adds all PIDs if the given Channel to the current list of PIDs. + ///< If the source or transponder of the channel are different than + ///< what was given in a previous call to AddChannel(), any previously + ///< added PIDs will be cleared. + bool CanDecrypt(const cChannel *Channel); + ///< Returns true if there is a CAM in this slot that is able to decrypt + ///< the given Channel (or at least claims to be able to do so). + ///< Since the QUERY/REPLY mechanism for CAMs is pretty unreliable (some + ///< CAMs don't reply to queries at all), we always return true if the + ///< CAM is currently not decrypting anything. If there is already a + ///< channel being decrypted, a call to CanDecrypt() checks whether the + ///< CAM can also decrypt the given channel. Only CAMs that have replied + ///< to the inital QUERY will perform this check at all. CAMs that never + ///< replied to the initial QUERY are assumed not to be able to handle + ///< more than one channel at a time. void StartDecrypting(void); ///< Triggers sending all currently active CA_PMT entries to the CAM, ///< so that it will start decrypting. - bool Reset(int Slot); + void StopDecrypting(void); + ///< Clears the list of CA_PMT entries and tells the CAM to stop decrypting. + bool IsDecrypting(void); + ///< Returns true if the CAM in this slot is currently used for decrypting. }; +class cCamSlots : public cList {}; + +extern cCamSlots CamSlots; + +class cChannelCamRelation; + +class cChannelCamRelations : public cList { +private: + cMutex mutex; + cChannelCamRelation *GetEntry(tChannelID ChannelID); + cChannelCamRelation *AddEntry(tChannelID ChannelID); + time_t lastCleanup; + void Cleanup(void); +public: + cChannelCamRelations(void); + void Reset(int CamSlotNumber); + bool CamChecked(tChannelID ChannelID, int CamSlotNumber); + bool CamDecrypt(tChannelID ChannelID, int CamSlotNumber); + void SetChecked(tChannelID ChannelID, int CamSlotNumber); + void SetDecrypt(tChannelID ChannelID, int CamSlotNumber); + void ClrChecked(tChannelID ChannelID, int CamSlotNumber); + void ClrDecrypt(tChannelID ChannelID, int CamSlotNumber); + }; + +extern cChannelCamRelations ChannelCamRelations; + #endif //__CI_H diff --git a/config.h b/config.h index 3597ff7..e795ad7 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.282 2007/01/07 13:45:19 kls Exp $ + * $Id: config.h 1.283 2007/01/07 14:09:31 kls Exp $ */ #ifndef __CONFIG_H @@ -21,13 +21,13 @@ // VDR's own version number: -#define VDRVERSION "1.4.5" -#define VDRVERSNUM 10405 // Version * 10000 + Major * 100 + Minor +#define VDRVERSION "1.5.0" +#define VDRVERSNUM 10500 // Version * 10000 + Major * 100 + Minor // The plugin API's version number: -#define APIVERSION "1.4.5" -#define APIVERSNUM 10405 // Version * 10000 + Major * 100 + Minor +#define APIVERSION "1.5.0" +#define APIVERSNUM 10500 // Version * 10000 + Major * 100 + Minor // When loading plugins, VDR searches them by their APIVERSION, which // may be smaller than VDRVERSION in case there have been no changes to diff --git a/device.c b/device.c index 0fb4fc7..e26af00 100644 --- a/device.c +++ b/device.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.c 1.137 2006/09/03 10:13:25 kls Exp $ + * $Id: device.c 1.138 2007/01/07 13:41:07 kls Exp $ */ #include "device.h" @@ -164,7 +164,9 @@ cDevice::cDevice(void) sdtFilter = NULL; nitFilter = NULL; - ciHandler = NULL; + camSlot = NULL; + startScrambleDetection = 0; + player = NULL; pesAssembler = new cPesAssembler; ClrAvailableTracks(); @@ -183,9 +185,7 @@ cDevice::cDevice(void) cDevice::~cDevice() { Detach(player); - for (int i = 0; i < MAXRECEIVERS; i++) - Detach(receiver[i]); - delete ciHandler; + DetachAllReceivers(); delete nitFilter; delete sdtFilter; delete patFilter; @@ -199,8 +199,10 @@ bool cDevice::WaitForAllDevicesReady(int Timeout) for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) { bool ready = true; for (int i = 0; i < numDevices; i++) { - if (device[i] && !device[i]->Ready()) + if (device[i] && !device[i]->Ready()) { ready = false; + cCondWait::SleepMs(100); + } } if (ready) return true; @@ -278,39 +280,98 @@ cDevice *cDevice::GetDevice(int Index) return (0 <= Index && Index < numDevices) ? device[Index] : NULL; } -cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) -{ - cDevice *d = NULL; - uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact - for (int i = 0; i < numDevices; i++) { - bool ndr; - if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job - // Put together an integer number that reflects the "impact" using - // this device would have on the overall system. Each condition is represented - // by one bit in the number (or several bits, if the condition is actually - // a numeric value). The sequence in which the conditions are listed corresponds - // to their individual severity, where the one listed first will make the most - // difference, because it results in the most significant bit of the result. - uint imp = 0; - imp <<= 1; imp |= !device[i]->Receiving() || ndr; // use receiving devices if we don't need to detach existing receivers - imp <<= 1; imp |= device[i]->Receiving(); // avoid devices that are receiving - imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device - imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) - imp <<= 8; imp |= min(max(device[i]->ProvidesCa(Channel), 0), 0xFF); // use the device that provides the lowest number of conditional access methods - imp <<= 1; imp |= device[i]->IsPrimaryDevice(); // avoid the primary device - imp <<= 1; imp |= device[i]->HasDecoder(); // avoid full featured cards - if (imp < Impact) { - // This device has less impact than any previous one, so we take it. - Impact = imp; - d = device[i]; - if (NeedsDetachReceivers) - *NeedsDetachReceivers = ndr; +cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView) +{ + // Collect the current priorities of all CAM slots that can decrypt the channel: + int NumCamSlots = CamSlots.Count(); + int SlotPriority[NumCamSlots]; + int NumUsableSlots = 0; + if (Channel->Ca() >= CA_ENCRYPTED_MIN) { + for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { + SlotPriority[CamSlot->Index()] = MAXPRIORITY + 1; // assumes it can't be used + if (CamSlot->ModuleStatus() == msReady) { + if (CamSlot->ProvidesCa(Channel->Caids())) { + if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->SlotNumber())) { + SlotPriority[CamSlot->Index()] = CamSlot->Priority(); + NumUsableSlots++; + } + } } } + if (!NumUsableSlots) + return NULL; // no CAM is able to decrypt this channel + } + + bool NeedsDetachReceivers = false; + cDevice *d = NULL; + cCamSlot *s = NULL; + + uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact + for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) { + if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY) + continue; // there is no CAM available in this slot + for (int i = 0; i < numDevices; i++) { + if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device[i]->CardIndex() + 1) + continue; // a specific card was requested, but not this one + if (NumUsableSlots && !CamSlots.Get(j)->Assign(device[i], true)) + continue; // CAM slot can't be used with this device + bool ndr; + if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job + if (NumUsableSlots && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j)) + ndr = true; // using a different CAM slot requires detaching receivers + // Put together an integer number that reflects the "impact" using + // this device would have on the overall system. Each condition is represented + // by one bit in the number (or several bits, if the condition is actually + // a numeric value). The sequence in which the conditions are listed corresponds + // to their individual severity, where the one listed first will make the most + // difference, because it results in the most significant bit of the result. + uint32_t imp = 0; + imp <<= 1; imp |= LiveView ? !device[i]->IsPrimaryDevice() || ndr : 0; // prefer the primary device for live viewing if we don't need to detach existing receivers + imp <<= 1; imp |= !device[i]->Receiving() || ndr; // use receiving devices if we don't need to detach existing receivers + imp <<= 1; imp |= device[i]->Receiving(); // avoid devices that are receiving + imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device + imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + imp <<= 8; imp |= min(max((NumUsableSlots ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF); // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + imp <<= 1; imp |= ndr; // avoid devices if we need to detach existing receivers + imp <<= 1; imp |= device[i]->IsPrimaryDevice(); // avoid the primary device + imp <<= 1; imp |= device[i]->HasDecoder(); // avoid full featured cards + imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel + if (imp < Impact) { + // This device has less impact than any previous one, so we take it. + Impact = imp; + d = device[i]; + NeedsDetachReceivers = ndr; + if (NumUsableSlots) + s = CamSlots.Get(j); + } + } + } + if (!NumUsableSlots) + break; // no CAM necessary, so just one loop over the devices } + if (d) { + if (NeedsDetachReceivers) + d->DetachAllReceivers(); + if (s) { + if (s->Device() != d) { + if (s->Device()) + s->Device()->DetachAllReceivers(); + if (d->CamSlot()) + d->CamSlot()->Assign(NULL); + s->Assign(d); + } + } + else if (d->CamSlot() && !d->CamSlot()->IsDecrypting()) + d->CamSlot()->Assign(NULL); + } return d; } +void cDevice::SetCamSlot(cCamSlot *CamSlot) +{ + camSlot = CamSlot; +} + void cDevice::Shutdown(void) { primaryDevice = NULL; @@ -422,8 +483,8 @@ bool cDevice::AddPid(int Pid, ePidType PidType) DelPid(Pid, PidType); return false; } - if (ciHandler) - ciHandler->SetPid(Pid, true); + if (camSlot) + camSlot->SetPid(Pid, true); } PRINTPIDS("a"); return true; @@ -451,8 +512,8 @@ bool cDevice::AddPid(int Pid, ePidType PidType) DelPid(Pid, PidType); return false; } - if (ciHandler) - ciHandler->SetPid(Pid, true); + if (camSlot) + camSlot->SetPid(Pid, true); } } return true; @@ -479,8 +540,8 @@ void cDevice::DelPid(int Pid, ePidType PidType) if (pidHandles[n].used == 0) { pidHandles[n].handle = -1; pidHandles[n].pid = 0; - if (ciHandler) - ciHandler->SetPid(Pid, false); + if (camSlot) + camSlot->SetPid(Pid, false); } } PRINTPIDS("E"); @@ -557,8 +618,10 @@ bool cDevice::MaySwitchTransponder(void) bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView) { - if (LiveView) + if (LiveView) { isyslog("switching to channel %d", Channel->Number()); + cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer + } for (int i = 3; i--;) { switch (SetChannel(Channel, LiveView)) { case scrOk: return true; @@ -578,12 +641,13 @@ bool cDevice::SwitchChannel(int Direction) bool result = false; Direction = sgn(Direction); if (Direction) { + cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer int n = CurrentChannel() + Direction; int first = n; cChannel *channel; while ((channel = Channels.GetByNumber(n, Direction)) != NULL) { // try only channels which are currently available - if (PrimaryDevice()->ProvidesChannel(channel, Setup.PrimaryLimit) || PrimaryDevice()->CanReplay() && GetDevice(channel, 0)) + if (GetDevice(channel, 0, true)) break; n = channel->Number() + Direction; } @@ -607,14 +671,9 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) if (LiveView) StopReplay(); - // If this card is switched to an other transponder, any receivers still - // attached to it need to be automatically detached: - bool NeedsDetachReceivers = false; + cDevice *Device = (LiveView && IsPrimaryDevice()) ? GetDevice(Channel, 0, LiveView) : this; - // If this card can't receive this channel, we must not actually switch - // the channel here, because that would irritate the driver when we - // start replaying in Transfer Mode immediately after switching the channel: - bool NeedsTransferMode = (LiveView && IsPrimaryDevice() && !ProvidesChannel(Channel, Setup.PrimaryLimit, &NeedsDetachReceivers)); + bool NeedsTransferMode = Device != this; eSetChannelResult Result = scrOk; @@ -622,14 +681,10 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) // use the card that actually can receive it and transfer data from there: if (NeedsTransferMode) { - cDevice *CaDevice = GetDevice(Channel, 0, &NeedsDetachReceivers); - if (CaDevice && CanReplay()) { + if (Device && CanReplay()) { cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel - if (CaDevice->SetChannel(Channel, false) == scrOk) { // calling SetChannel() directly, not SwitchChannel()! - if (NeedsDetachReceivers) - CaDevice->DetachAllReceivers(); - cControl::Launch(new cTransferControl(CaDevice, Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); - } + if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()! + cControl::Launch(new cTransferControl(Device, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); else Result = scrNoTransfer; } @@ -644,27 +699,10 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) sectionHandler->SetStatus(false); sectionHandler->SetChannel(NULL); } - // Tell the ciHandler about the channel switch and add all PIDs of this + // Tell the camSlot about the channel switch and add all PIDs of this // channel to it, for possible later decryption: - if (ciHandler) { - ciHandler->SetSource(Channel->Source(), Channel->Transponder()); -// Men at work - please stand clear! ;-) -#ifdef XXX_DO_MULTIPLE_CA_CHANNELS - if (Channel->Ca() >= CA_ENCRYPTED_MIN) { -#endif - ciHandler->AddPid(Channel->Sid(), Channel->Vpid(), 2); - for (const int *Apid = Channel->Apids(); *Apid; Apid++) - ciHandler->AddPid(Channel->Sid(), *Apid, 4); - for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++) - ciHandler->AddPid(Channel->Sid(), *Dpid, 0); -#ifdef XXX_DO_MULTIPLE_CA_CHANNELS - bool CanDecrypt = ciHandler->CanDecrypt(Channel->Sid());//XXX - dsyslog("CanDecrypt %d %d %d %s", CardIndex() + 1, CanDecrypt, Channel->Number(), Channel->Name());//XXX - } -#endif - } - if (NeedsDetachReceivers) - DetachAllReceivers(); + if (camSlot) + camSlot->AddChannel(Channel); if (SetChannelDevice(Channel, LiveView)) { // Start section handling: if (sectionHandler) { @@ -672,8 +710,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) sectionHandler->SetStatus(true); } // Start decrypting any PIDs that might have been set in SetChannelDevice(): - if (ciHandler) - ciHandler->StartDecrypting(); + if (camSlot) + camSlot->StartDecrypting(); } else Result = scrFailed; @@ -968,9 +1006,10 @@ bool cDevice::AttachPlayer(cPlayer *Player) void cDevice::Detach(cPlayer *Player) { if (Player && player == Player) { - player->Activate(false); - player->device = NULL; - player = NULL; + cPlayer *p = player; + player = NULL; // avoids recursive calls to Detach() + p->Activate(false); + p->device = NULL; SetPlayMode(pmNone); SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat)); Audios.ClearAudio(); @@ -1143,16 +1182,6 @@ int cDevice::PlayPes(const uchar *Data, int Length, bool VideoOnly) return Length; } -int cDevice::Ca(void) const -{ - int ca = 0; - for (int i = 0; i < MAXRECEIVERS; i++) { - if (receiver[i] && (ca = receiver[i]->ca) != 0) - break; // all receivers have the same ca - } - return ca; -} - int cDevice::Priority(void) const { int priority = IsPrimaryDevice() ? Setup.PrimaryLimit - 1 : DEFAULTPRIORITY; @@ -1168,16 +1197,6 @@ bool cDevice::Ready(void) return true; } -int cDevice::ProvidesCa(const cChannel *Channel) const -{ - int Ca = Channel->Ca(); - if (Ca == CardIndex() + 1) - return 1; // exactly _this_ card was requested - if (Ca && Ca <= CA_DVB_MAX) - return 0; // a specific card was requested, but not _this_ one - return !Ca; // by default every card can provide FTA -} - bool cDevice::Receiving(bool CheckAny) const { for (int i = 0; i < MAXRECEIVERS; i++) { @@ -1187,6 +1206,10 @@ bool cDevice::Receiving(bool CheckAny) const return false; } +#define TS_SCRAMBLING_CONTROL 0xC0 +#define TS_SCRAMBLING_TIMEOUT 3 // seconds to wait until a TS becomes unscrambled +#define TS_SCRAMBLING_TIME_OK 10 // seconds before a Channel/CAM combination is marked a known to decrypt + void cDevice::Action(void) { if (Running() && OpenDvr()) { @@ -1196,11 +1219,39 @@ void cDevice::Action(void) if (GetTSPacket(b)) { if (b) { int Pid = (((uint16_t)b[1] & PID_MASK_HI) << 8) | b[2]; + // Check whether the TS packets are scrambled: + bool DetachReceivers = false; + bool DescramblingOk = false; + int CamSlotNumber = 0; + if (startScrambleDetection) { + cCamSlot *cs = CamSlot(); + CamSlotNumber = cs ? cs->SlotNumber() : 0; + if (CamSlotNumber) { + bool Scrambled = b[3] & TS_SCRAMBLING_CONTROL; + int t = time(NULL) - startScrambleDetection; + if (Scrambled) { + if (t > TS_SCRAMBLING_TIMEOUT) + DetachReceivers = true; + } + else if (t > TS_SCRAMBLING_TIME_OK) { + DescramblingOk = true; + startScrambleDetection = 0; + } + } + } // Distribute the packet to all attached receivers: Lock(); for (int i = 0; i < MAXRECEIVERS; i++) { - if (receiver[i] && receiver[i]->WantsPid(Pid)) - receiver[i]->Receive(b, TS_SIZE); + if (receiver[i] && receiver[i]->WantsPid(Pid)) { + if (DetachReceivers) { + ChannelCamRelations.SetChecked(receiver[i]->ChannelID(), CamSlotNumber); + Detach(receiver[i]); + } + else + receiver[i]->Receive(b, TS_SIZE); + if (DescramblingOk) + ChannelCamRelations.SetDecrypt(receiver[i]->ChannelID(), CamSlotNumber); + } } Unlock(); } @@ -1256,10 +1307,11 @@ bool cDevice::AttachReceiver(cReceiver *Receiver) Receiver->device = this; receiver[i] = Receiver; Unlock(); - if (!Running()) - Start(); - if (ciHandler) - ciHandler->StartDecrypting(); + if (camSlot) { + camSlot->StartDecrypting(); + startScrambleDetection = time(NULL); + } + Start(); return true; } } @@ -1286,10 +1338,10 @@ void cDevice::Detach(cReceiver *Receiver) else if (receiver[i]) receiversLeft = true; } - if (ciHandler) - ciHandler->StartDecrypting(); + if (camSlot) + camSlot->StartDecrypting(); if (!receiversLeft) - Cancel(3); + Cancel(-1); } void cDevice::DetachAll(int Pid) @@ -1307,10 +1359,8 @@ void cDevice::DetachAll(int Pid) void cDevice::DetachAllReceivers(void) { cMutexLock MutexLock(&mutexReceiver); - for (int i = 0; i < MAXRECEIVERS; i++) { - if (receiver[i]) - Detach(receiver[i]); - } + for (int i = 0; i < MAXRECEIVERS; i++) + Detach(receiver[i]); } // --- cTSBuffer ------------------------------------------------------------- diff --git a/device.h b/device.h index dced092..e41d5ae 100644 --- a/device.h +++ b/device.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.h 1.79 2006/06/15 09:32:48 kls Exp $ + * $Id: device.h 1.80 2007/01/03 14:14:29 kls Exp $ */ #ifndef __DEVICE_H @@ -128,12 +128,21 @@ public: ///< Gets the device with the given Index. ///< \param Index must be in the range 0..numDevices-1. ///< \return A pointer to the device, or NULL if the Index was invalid. - static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL); + static cDevice *GetDevice(const cChannel *Channel, int Priority, bool LiveView); ///< Returns a device that is able to receive the given Channel at the ///< given Priority, with the least impact on active recordings and - ///< live viewing. - ///< See ProvidesChannel() for more information on how - ///< priorities are handled, and the meaning of NeedsDetachReceivers. + ///< live viewing. The LiveView parameter tells whether the device will + ///< be used for live viewing or a recording. + ///< If the Channel is encrypted, a CAM slot that claims to be able to + ///< decrypt the channel is automatically selected and assigned to the + ///< returned device. Whether or not this combination of device and CAM + ///< slot is actually able to decrypt the channel can only be determined + ///< by checking the "scrambling control" bits of the received TS packets. + ///< The Action() function automatically does this and takes care that + ///< after detaching any receivers because the channel can't be decrypted, + ///< this device/CAM combination will be skipped in the next call to + ///< GetDevice(). + ///< See also ProvidesChannel(). static void Shutdown(void); ///< Closes down all devices. ///< Must be called at the end of the program. @@ -171,16 +180,6 @@ public: ///< Returns the card index of this device (0 ... MAXDEVICES - 1). int DeviceNumber(void) const; ///< Returns the number of this device (0 ... numDevices). - virtual int ProvidesCa(const cChannel *Channel) const; - ///< Checks whether this device provides the conditional access - ///< facilities to decrypt the given Channel. - ///< Returns 0 if the Channel can't be decrypted, 1 if this is a - ///< Free To Air channel or only exactly this device can decrypt it, - ///< and > 1 if this device can decrypt the Channel. - ///< If the result is greater than 1 and the device has more than one - ///< CAM, the value will be increased by the number of CAMs, which - ///< allows to select the device with the smallest number of CAMs - ///< in order to preserve resources for other recordings. virtual bool HasDecoder(void) const; ///< Tells whether this device has an MPEG decoder. @@ -199,7 +198,9 @@ public: virtual bool ProvidesSource(int Source) const; ///< Returns true if this device can provide the given source. virtual bool ProvidesTransponder(const cChannel *Channel) const; - ///< XXX -> PLUGINS.html! + ///< Returns true if this device can provide the transponder of the + ///< given Channel (which implies that it can provide the Channel's + ///< source). virtual bool ProvidesTransponderExclusively(const cChannel *Channel) const; ///< Returns true if this is the only device that is able to provide ///< the given channel's transponder. @@ -246,7 +247,7 @@ public: ///< channel number while replaying. void ForceTransferMode(void); ///< Forces the device into transfermode for the current channel. - virtual bool HasLock(int TimeoutMs = 0);//XXX PLUGINS.html + virtual bool HasLock(int TimeoutMs = 0); ///< Returns true if the device has a lock on the requested transponder. ///< Default is true, a specific device implementation may return false ///< to indicate that it is not ready yet. @@ -309,10 +310,15 @@ public: // Common Interface facilities: -protected: - cCiHandler *ciHandler; +private: + time_t startScrambleDetection; + cCamSlot *camSlot; public: - cCiHandler *CiHandler(void) { return ciHandler; } + void SetCamSlot(cCamSlot *CamSlot); + ///< Sets the given CamSlot to be used with this device. + cCamSlot *CamSlot(void) const { return camSlot; } + ///< Returns the CAM slot that is currently used with this device, + ///< or NULL if no CAM slot is in use. // Image Grab facilities @@ -512,11 +518,12 @@ public: private: cMutex mutexReceiver; cReceiver *receiver[MAXRECEIVERS]; -protected: +public: int Priority(void) const; ///< Returns the priority of the current receiving session (0..MAXPRIORITY), ///< or -1 if no receiver is currently active. The primary device will ///< always return at least Setup.PrimaryLimit-1. +protected: virtual bool OpenDvr(void); ///< Opens the DVR of this device and prepares it to deliver a Transport ///< Stream for use in a cReceiver. @@ -530,8 +537,6 @@ protected: ///< false in case of a non recoverable error, otherwise it returns true, ///< even if Data is NULL. public: - int Ca(void) const; - ///< Returns the ca of the current receiving session(s). bool Receiving(bool CheckAny = false) const; ///< Returns true if we are currently receiving. bool AttachReceiver(cReceiver *Receiver); diff --git a/dvbci.c b/dvbci.c new file mode 100644 index 0000000..9d44008 --- /dev/null +++ b/dvbci.c @@ -0,0 +1,108 @@ +/* + * dvbci.h: Common Interface for DVB devices + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: dvbci.c 1.1 2007/01/04 12:49:10 kls Exp $ + */ + +#include "dvbci.h" +#include +#include +#include "device.h" + +// --- cDvbCiAdapter --------------------------------------------------------- + +cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd) +{ + device = Device; + SetDescription("CI adapter on device %d", device->DeviceNumber()); + fd = Fd; + ca_caps_t Caps; + if (ioctl(fd, CA_GET_CAP, &Caps) == 0) { + if ((Caps.slot_type & CA_CI_LINK) != 0) { + int NumSlots = Caps.slot_num; + if (NumSlots > 0) { + for (int i = 0; i < NumSlots; i++) + new cCamSlot(this); + Start(); + } + else + esyslog("ERROR: no CAM slots found on device %d", device->DeviceNumber()); + } + else + isyslog("device %d doesn't support CI link layer interface", device->DeviceNumber()); + } + else + esyslog("ERROR: can't get CA capabilities on device %d", device->DeviceNumber()); +} + +cDvbCiAdapter::~cDvbCiAdapter() +{ + Cancel(3); +} + +int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength) +{ + if (Buffer && MaxLength > 0) { + struct pollfd pfd[1]; + pfd[0].fd = fd; + pfd[0].events = POLLIN; + if (poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN)) { + int n = safe_read(fd, Buffer, MaxLength); + if (n >= 0) + return n; + esyslog("ERROR: can't read from CI adapter on device %d: %m", device->DeviceNumber()); + } + } + return 0; +} + +void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length) +{ + if (Buffer && Length > 0) { + if (safe_write(fd, Buffer, Length) != Length) + esyslog("ERROR: can't write to CI adapter on device %d: %m", device->DeviceNumber()); + } +} + +bool cDvbCiAdapter::Reset(int Slot) +{ + if (ioctl(fd, CA_RESET, 1 << Slot) != -1) + return true; + else + esyslog("ERROR: can't reset CAM slot %d on device %d: %m", Slot, device->DeviceNumber()); + return false; +} + +eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot) +{ + ca_slot_info_t sinfo; + sinfo.num = Slot; + if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) { + if ((sinfo.flags & CA_CI_MODULE_READY) != 0) + return msReady; + else if ((sinfo.flags & CA_CI_MODULE_PRESENT) != 0) + return msPresent; + } + else + esyslog("ERROR: can't get info of CAM slot %d on device %d: %m", Slot, device->DeviceNumber()); + return msNone; +} + +bool cDvbCiAdapter::Assign(cDevice *Device, bool Query) +{ + // The CI is hardwired to its device, so there's not really much to do here + if (Device) + return Device == device; + return true; +} + +cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd) +{ + // TODO check whether a CI is actually present? + if (Device) + return new cDvbCiAdapter(Device, Fd); + return NULL; +} diff --git a/dvbci.h b/dvbci.h new file mode 100644 index 0000000..1669c8c --- /dev/null +++ b/dvbci.h @@ -0,0 +1,31 @@ +/* + * dvbci.h: Common Interface for DVB devices + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: dvbci.h 1.1 2006/11/26 11:19:42 kls Exp $ + */ + +#ifndef __DVBCI_H +#define __DVBCI_H + +#include "ci.h" + +class cDvbCiAdapter : public cCiAdapter { +private: + cDevice *device; + int fd; +protected: + virtual int Read(uint8_t *Buffer, int MaxLength); + virtual void Write(const uint8_t *Buffer, int Length); + virtual bool Reset(int Slot); + virtual eModuleStatus ModuleStatus(int Slot); + virtual bool Assign(cDevice *Device, bool Query = false); + cDvbCiAdapter(cDevice *Device, int Fd); +public: + virtual ~cDvbCiAdapter(); + static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd); + }; + +#endif //__DVBCI_H diff --git a/dvbdevice.c b/dvbdevice.c index fb8ec93..487aec5 100644 --- a/dvbdevice.c +++ b/dvbdevice.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbdevice.c 1.160 2006/08/14 09:38:32 kls Exp $ + * $Id: dvbdevice.c 1.161 2007/01/05 11:09:51 kls Exp $ */ #include "dvbdevice.h" @@ -19,6 +19,7 @@ #include #include "channels.h" #include "diseqc.h" +#include "dvbci.h" #include "dvbosd.h" #include "eitscan.h" #include "player.h" @@ -28,7 +29,6 @@ #define DO_REC_AND_PLAY_ON_PRIMARY_DEVICE 1 #define DO_MULTIPLE_RECORDINGS 1 -//#define DO_MULTIPLE_CA_CHANNELS #define DEV_VIDEO "/dev/video" #define DEV_DVB_ADAPTER "/dev/dvb/adapter" @@ -77,7 +77,6 @@ private: int lockTimeout; time_t lastTimeoutReport; fe_type_t frontendType; - cCiHandler *ciHandler; cChannel channel; const char *diseqcCommands; eTunerStatus tunerStatus; @@ -88,19 +87,18 @@ private: bool SetFrontend(void); virtual void Action(void); public: - cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType, cCiHandler *CiHandler); + cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType); virtual ~cDvbTuner(); bool IsTunedTo(const cChannel *Channel) const; void Set(const cChannel *Channel, bool Tune); bool Locked(int TimeoutMs = 0); }; -cDvbTuner::cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType, cCiHandler *CiHandler) +cDvbTuner::cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType) { fd_frontend = Fd_Frontend; cardIndex = CardIndex; frontendType = FrontendType; - ciHandler = CiHandler; tuneTimeout = 0; lockTimeout = 0; lastTimeoutReport = 0; @@ -346,8 +344,6 @@ void cDvbTuner::Action(void) } } - if (ciHandler) - ciHandler->Process(); if (tunerStatus != tsTuned) newSet.TimedWait(mutex, 1000); } @@ -360,6 +356,7 @@ int cDvbDevice::setTransferModeForDolbyDigital = 1; cDvbDevice::cDvbDevice(int n) { + ciAdapter = NULL; dvbTuner = NULL; frontendType = fe_type_t(-1); // don't know how else to initialize this - there is no FE_UNKNOWN spuDecoder = NULL; @@ -377,6 +374,12 @@ cDvbDevice::cDvbDevice(int n) fd_audio = DvbOpen(DEV_DVB_AUDIO, n, O_RDWR | O_NONBLOCK); fd_stc = DvbOpen(DEV_DVB_DEMUX, n, O_RDWR); + // Common Interface: + + fd_ca = DvbOpen(DEV_DVB_CA, n, O_RDWR); + if (fd_ca >= 0) + ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca); + // The DVR device (will be opened and closed as needed): fd_dvr = -1; @@ -420,8 +423,7 @@ cDvbDevice::cDvbDevice(int n) dvb_frontend_info feinfo; if (ioctl(fd_frontend, FE_GET_INFO, &feinfo) >= 0) { frontendType = feinfo.type; - ciHandler = cCiHandler::CreateCiHandler(*cDvbName(DEV_DVB_CA, n)); - dvbTuner = new cDvbTuner(fd_frontend, CardIndex(), frontendType, ciHandler); + dvbTuner = new cDvbTuner(fd_frontend, CardIndex(), frontendType); } else LOG_ERROR; @@ -436,6 +438,7 @@ cDvbDevice::~cDvbDevice() { delete spuDecoder; delete dvbTuner; + delete ciAdapter; // We're not explicitly closing any device files here, since this sometimes // caused segfaults. Besides, the program is about to terminate anyway... } @@ -494,32 +497,11 @@ bool cDvbDevice::HasDecoder(void) const bool cDvbDevice::Ready(void) { - if (ciHandler) { - ciHandler->Process(); - return ciHandler->Ready(); - } + if (ciAdapter) + return ciAdapter->Ready(); return true; } -int cDvbDevice::ProvidesCa(const cChannel *Channel) const -{ - int NumCams = 0; - if (ciHandler) { - NumCams = ciHandler->NumCams(); - if (Channel->Ca() >= CA_ENCRYPTED_MIN) { - unsigned short ids[MAXCAIDS + 1]; - for (int i = 0; i <= MAXCAIDS; i++) // '<=' copies the terminating 0! - ids[i] = Channel->Ca(i); - if (ciHandler->ProvidesCa(ids)) - return NumCams + 1; - } - } - int result = cDevice::ProvidesCa(Channel); - if (result > 0) - result += NumCams; - return result; -} - cSpuDecoder *cDvbDevice::GetSpuDecoder(void) { if (!spuDecoder && IsPrimaryDevice()) @@ -770,18 +752,19 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne bool hasPriority = Priority < 0 || Priority > this->Priority(); bool needsDetachReceivers = false; - if (ProvidesSource(Channel->Source()) && ProvidesCa(Channel)) { + if (ProvidesSource(Channel->Source())) { result = hasPriority; if (Priority >= 0 && Receiving(true)) { if (dvbTuner->IsTunedTo(Channel)) { if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) { #ifdef DO_MULTIPLE_RECORDINGS -#ifndef DO_MULTIPLE_CA_CHANNELS - if (Ca() >= CA_ENCRYPTED_MIN || Channel->Ca() >= CA_ENCRYPTED_MIN) - needsDetachReceivers = Ca() != Channel->Ca(); - else -#endif - if (!IsPrimaryDevice()) + if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) { + if (CamSlot()->CanDecrypt(Channel)) + result = true; + else + needsDetachReceivers = true; + } + else if (!IsPrimaryDevice()) result = true; #ifdef DO_REC_AND_PLAY_ON_PRIMARY_DEVICE else @@ -821,6 +804,8 @@ bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) && (LiveView && HasPid(Channel->Vpid() ? Channel->Vpid() : Channel->Apid(0)) && (pidHandles[ptVideo].pid != Channel->Vpid() || (pidHandles[ptAudio].pid != Channel->Apid(0) && (Channel->Dpid(0) ? pidHandles[ptAudio].pid != Channel->Dpid(0) : true)))// the PID is already set as DMX_PES_OTHER || !LiveView && (pidHandles[ptVideo].pid == Channel->Vpid() || pidHandles[ptAudio].pid == Channel->Apid(0)) // a recording is going to shift the PIDs from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER ); + if (CamSlot() && !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), CamSlot()->SlotNumber())) + StartTransferMode |= LiveView && IsPrimaryDevice() && Channel->Ca() >= CA_ENCRYPTED_MIN; bool TurnOnLivePIDs = HasDecoder() && !StartTransferMode && LiveView; @@ -861,7 +846,7 @@ bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); } else if (StartTransferMode) - cControl::Launch(new cTransferControl(this, Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); + cControl::Launch(new cTransferControl(this, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); return true; } @@ -922,13 +907,13 @@ void cDvbDevice::SetAudioTrackDevice(eTrackType Type) if (IS_AUDIO_TRACK(Type) || (IS_DOLBY_TRACK(Type) && SetAudioBypass(true))) { if (pidHandles[ptAudio].pid && pidHandles[ptAudio].pid != TrackId->id) { DetachAll(pidHandles[ptAudio].pid); - if (ciHandler) - ciHandler->SetPid(pidHandles[ptAudio].pid, false); + if (CamSlot()) + CamSlot()->SetPid(pidHandles[ptAudio].pid, false); pidHandles[ptAudio].pid = TrackId->id; SetPid(&pidHandles[ptAudio], ptAudio, true); - if (ciHandler) { - ciHandler->SetPid(pidHandles[ptAudio].pid, true); - ciHandler->StartDecrypting(); + if (CamSlot()) { + CamSlot()->SetPid(pidHandles[ptAudio].pid, true); + CamSlot()->StartDecrypting(); } } } diff --git a/dvbdevice.h b/dvbdevice.h index effa5fb..670421f 100644 --- a/dvbdevice.h +++ b/dvbdevice.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbdevice.h 1.41 2006/05/28 15:05:19 kls Exp $ + * $Id: dvbdevice.h 1.42 2007/01/05 10:39:52 kls Exp $ */ #ifndef __DVBDEVICE_H @@ -36,16 +36,20 @@ public: ///< \return True if any devices are available. private: fe_type_t frontendType; - int fd_osd, fd_audio, fd_video, fd_dvr, fd_stc; + int fd_osd, fd_audio, fd_video, fd_dvr, fd_stc, fd_ca; protected: virtual void MakePrimaryDevice(bool On); public: cDvbDevice(int n); virtual ~cDvbDevice(); virtual bool Ready(void); - virtual int ProvidesCa(const cChannel *Channel) const; virtual bool HasDecoder(void) const; +// Common Interface facilities: + +private: + cCiAdapter *ciAdapter; + // SPU facilities private: diff --git a/i18n.c b/i18n.c index 475264b..54d39c4 100644 --- a/i18n.c +++ b/i18n.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.285 2006/10/14 09:26:41 kls Exp $ + * $Id: i18n.c 1.286 2007/01/07 12:19:15 kls Exp $ * * Translations provided by: * @@ -2598,27 +2598,27 @@ const tI18nPhrase Phrases[] = { "Kan ikke åbne CAM menuen!", "Menu CAM není dostupné", }, - { "Resetting CAM...", - "CAM wird zurückgesetzt...", - "Resetiram CAM...", - "Reimpostazione modulo CAM...", - "CAM wordt herstart...", + { "CAM is in use - really reset?" + "CAM wird benutzt - wirklich zurücksetzen?", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO "",//TODO - "Réinitialisation du CAM", "",//TODO - "CA-moduuli palautetaan alkutilaan...", - "Resetujê CAM...", - "Reiniciando CAM...", "",//TODO - "Återställer CAM ...", - "Se reseteazã CAM...", - "A CAM újra indul...", "",//TODO - "¿ÕàÕÓàã×ÚÐ CAM...", "",//TODO - "CAM mooduli taaskäivitus...", - "Nulstiller CAM...", - "Restartuje se CAM...", }, { "Can't reset CAM!", "Zurücksetzen des CAM fehlgeschlagen!", @@ -2642,27 +2642,93 @@ const tI18nPhrase Phrases[] = { "Kan ikke nulstille CAM!", "CAM modul nelze restartovat!", }, - { "CAM has been reset", - "CAM wurde zurückgesetzt", - "CAM je resetiran", - "Modulo CAM reimpostato", - "CAM is herstart", - "",//TODO - "La CAM a été réinitialisée", - "",//TODO - "CA-moduuli palautettu alkutilaan", - "CAM zosta³ zresetowany", - "CAM reiniciado", - "¸ãéíå åðáíáöïñÜ óôï CAM", - "CA modulen har återställts", - "CAM-ul a fost resetat", - "A CAM vissza lett állítva", - "CAM reiniciada", - "CAM-ÜÞÔãÛì ßÕàÕ×ÐßãéÕÝ", - "CAM je resetiran", - "CAM mooduli taaskäivitus tehtud", - "CAM er blevet nulstillet", - "CAM byl restartován", + { "CAM reset", + "CAM zurückgesetzt", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "CAM present", + "CAM vorhanden", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "CAM ready", + "CAM bereit", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "CAM not responding!", + "CAM antwortet nicht!", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO }, { "Please enter %d digits!", "Bitte geben Sie %d Ziffern ein!", @@ -2797,27 +2863,27 @@ const tI18nPhrase Phrases[] = { "LNB", "LNB", }, - { "CICAM", - "CICAM", - "CICAM", - "Accesso condizionato CICAM", - "CICAM", - "CICAM", + { "CAM", + "CAM", + "CAM", + "Accesso condizionato CAM", + "CAM", + "CAM", "Accès conditionnel", - "CICAM", - "CICAM", - "CICAM", - "CICAM", - "CICAM", - "CICAM", - "CICAM", - "CICAM", + "CAM", + "CAM", + "CAM", + "CAM", + "CAM", + "CAM", + "CAM", + "CAM", "CI Accés condicional", "ÃáÛÞÒÝëÙ ÔÞáâãß", - "CICAM", - "CICAM", - "CICAM", - "CICAM", + "CAM", + "CAM", + "CAM", + "CAM", }, { "Recording", "Aufnahme", @@ -3964,28 +4030,6 @@ const tI18nPhrase Phrases[] = { "Anvend DiSEqC", "Pou¾ívat DiSEqC", }, - { "Setup.CICAM$CICAM DVB", - "CICAM-DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "Accès conditionnel", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "Accés condicional CICAM", - "CAM-ÜÞÔãÛì DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - "CICAM DVB", - }, { "Setup.Recording$Margin at start (min)", "Vorlauf zum Timer-Beginn (min)", "Premik zaèetka snemanja (min)", diff --git a/menu.c b/menu.c index 10a3b26..203eee0 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.446 2006/12/02 11:12:02 kls Exp $ + * $Id: menu.c 1.447 2007/01/07 12:19:48 kls Exp $ */ #include "menu.h" @@ -38,7 +38,9 @@ #define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS) #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours -#define MAXWAITFORCAMMENU 4 // seconds to wait for the CAM menu to open +#define MAXWAITFORCAMMENU 10 // seconds to wait for the CAM menu to open +#define CAMMENURETYTIMEOUT 3 // seconds after which opening the CAM menu is retried +#define CAMRESPONSETIMEOUT 5 // seconds to wait for a response from a CAM #define MINFREEDISK 300 // minimum free disk space (in MB) required to start recording #define NODISKSPACEDELTA 300 // seconds between "Not enough disk space to start recording!" messages @@ -1579,38 +1581,104 @@ eOSState cMenuCommands::ProcessKey(eKeys Key) // --- cMenuCam -------------------------------------------------------------- -cMenuCam::cMenuCam(cCiMenu *CiMenu) -:cOsdMenu("") +class cMenuCam : public cOsdMenu { +private: + cCamSlot *camSlot; + cCiMenu *ciMenu; + cCiEnquiry *ciEnquiry; + char *input; + int offset; + time_t lastCamExchange; + void GenerateTitle(const char *s = NULL); + void QueryCam(void); + void AddMultiLineItem(const char *s); + void Set(void); + eOSState Select(void); +public: + cMenuCam(cCamSlot *CamSlot); + virtual ~cMenuCam(); + virtual eOSState ProcessKey(eKeys Key); + }; + +cMenuCam::cMenuCam(cCamSlot *CamSlot) +:cOsdMenu("", 1) // tab necessary for enquiry! { - dsyslog("CAM: Menu ------------------"); - ciMenu = CiMenu; - selected = false; + camSlot = CamSlot; + ciMenu = NULL; + ciEnquiry = NULL; + input = NULL; offset = 0; - if (ciMenu->Selectable()) - SetHasHotkeys(); - SetTitle(*ciMenu->TitleText() ? ciMenu->TitleText() : "CAM"); - dsyslog("CAM: '%s'", ciMenu->TitleText()); - if (*ciMenu->SubTitleText()) { - dsyslog("CAM: '%s'", ciMenu->SubTitleText()); - AddMultiLineItem(ciMenu->SubTitleText()); - offset = Count(); - } - for (int i = 0; i < ciMenu->NumEntries(); i++) { - Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable())); - dsyslog("CAM: '%s'", ciMenu->Entry(i)); - } - if (*ciMenu->BottomText()) { - AddMultiLineItem(ciMenu->BottomText()); - dsyslog("CAM: '%s'", ciMenu->BottomText()); - } - Display(); + lastCamExchange = time(NULL); + SetNeedsFastResponse(true); + QueryCam(); } cMenuCam::~cMenuCam() { - if (!selected) + if (ciMenu) ciMenu->Abort(); delete ciMenu; + if (ciEnquiry) + ciEnquiry->Abort(); + delete ciEnquiry; + free(input); +} + +void cMenuCam::GenerateTitle(const char *s) +{ + SetTitle(cString::sprintf("CAM %d - %s", camSlot->SlotNumber(), (s && *s) ? s : camSlot->GetCamName())); +} + +void cMenuCam::QueryCam(void) +{ + delete ciMenu; + ciMenu = NULL; + delete ciEnquiry; + ciEnquiry = NULL; + if (camSlot->HasUserIO()) { + ciMenu = camSlot->GetMenu(); + ciEnquiry = camSlot->GetEnquiry(); + } + Set(); +} + +void cMenuCam::Set(void) +{ + if (ciMenu) { + Clear(); + free(input); + input = NULL; + dsyslog("CAM %d: Menu ------------------", camSlot->SlotNumber()); + offset = 0; + SetHasHotkeys(ciMenu->Selectable()); + GenerateTitle(ciMenu->TitleText()); + dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->TitleText()); + if (*ciMenu->SubTitleText()) { + dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->SubTitleText()); + AddMultiLineItem(ciMenu->SubTitleText()); + offset = Count(); + } + for (int i = 0; i < ciMenu->NumEntries(); i++) { + Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable())); + dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->Entry(i)); + } + if (*ciMenu->BottomText()) { + AddMultiLineItem(ciMenu->BottomText()); + dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->BottomText()); + } + } + else if (ciEnquiry) { + Clear(); + int Length = ciEnquiry->ExpectedLength(); + free(input); + input = MALLOC(char, Length + 1); + *input = 0; + GenerateTitle(); + Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false)); + Add(new cOsdItem("", osUnknown, false)); + Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind())); + } + Display(); } void cMenuCam::AddMultiLineItem(const char *s) @@ -1628,90 +1696,61 @@ void cMenuCam::AddMultiLineItem(const char *s) eOSState cMenuCam::Select(void) { - if (ciMenu->Selectable()) { - ciMenu->Select(Current() - offset); - dsyslog("CAM: select %d", Current() - offset); - } - else - ciMenu->Cancel(); - selected = true; - return osEnd; -} - -eOSState cMenuCam::ProcessKey(eKeys Key) -{ - eOSState state = cOsdMenu::ProcessKey(Key); - - if (state == osUnknown) { - switch (Key) { - case kOk: return Select(); - default: break; - } - } - else if (state == osBack) { - ciMenu->Cancel(); - selected = true; - return osEnd; + if (ciMenu) { + if (ciMenu->Selectable()) { + ciMenu->Select(Current() - offset); + dsyslog("CAM %d: select %d", camSlot->SlotNumber(), Current() - offset); + } + else + ciMenu->Cancel(); } - if (ciMenu->HasUpdate()) { - selected = true; - return osEnd; + else if (ciEnquiry) { + if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) { + char buffer[64]; + snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength()); + Skins.Message(mtError, buffer); + return osContinue; + } + ciEnquiry->Reply(input); + dsyslog("CAM %d: entered '%s'", camSlot->SlotNumber(), ciEnquiry->Blind() ? "****" : input); } - return state; -} - -// --- cMenuCamEnquiry ------------------------------------------------------- - -cMenuCamEnquiry::cMenuCamEnquiry(cCiEnquiry *CiEnquiry) -:cOsdMenu("", 1) -{ - ciEnquiry = CiEnquiry; - int Length = ciEnquiry->ExpectedLength(); - input = MALLOC(char, Length + 1); - *input = 0; - replied = false; - SetTitle("CAM"); - Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false)); - Add(new cOsdItem("", osUnknown, false)); - Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind())); - Display(); -} - -cMenuCamEnquiry::~cMenuCamEnquiry() -{ - if (!replied) - ciEnquiry->Abort(); - free(input); - delete ciEnquiry; + QueryCam(); + return osContinue; } -eOSState cMenuCamEnquiry::Reply(void) +eOSState cMenuCam::ProcessKey(eKeys Key) { - if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) { - char buffer[64]; - snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength()); - Skins.Message(mtError, buffer); - return osContinue; - } - ciEnquiry->Reply(input); - replied = true; - return osEnd; -} + if (!camSlot->HasMMI()) + return osBack; -eOSState cMenuCamEnquiry::ProcessKey(eKeys Key) -{ eOSState state = cOsdMenu::ProcessKey(Key); - if (state == osUnknown) { - switch (Key) { - case kOk: return Reply(); - default: break; - } + if (ciMenu || ciEnquiry) { + lastCamExchange = time(NULL); + if (state == osUnknown) { + switch (Key) { + case kOk: return Select(); + default: break; + } + } + else if (state == osBack) { + if (ciMenu) + ciMenu->Cancel(); + if (ciEnquiry) + ciEnquiry->Cancel(); + QueryCam(); + return osContinue; + } + if (ciMenu && ciMenu->HasUpdate()) { + QueryCam(); + return osContinue; + } } - else if (state == osBack) { - ciEnquiry->Cancel(); - replied = true; - return osEnd; + else if (time(NULL) - lastCamExchange < CAMRESPONSETIMEOUT) + QueryCam(); + else { + Skins.Message(mtError, tr("CAM not responding!")); + return osBack; } return state; } @@ -1720,21 +1759,9 @@ eOSState cMenuCamEnquiry::ProcessKey(eKeys Key) cOsdObject *CamControl(void) { - for (int d = 0; d < cDevice::NumDevices(); d++) { - cDevice *Device = cDevice::GetDevice(d); - if (Device) { - cCiHandler *CiHandler = Device->CiHandler(); - if (CiHandler && CiHandler->HasUserIO()) { - cCiMenu *CiMenu = CiHandler->GetMenu(); - if (CiMenu) - return new cMenuCam(CiMenu); - else { - cCiEnquiry *CiEnquiry = CiHandler->GetEnquiry(); - if (CiEnquiry) - return new cMenuCamEnquiry(CiEnquiry); - } - } - } + for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { + if (CamSlot->HasUserIO()) + return new cMenuCam(CamSlot); } return NULL; } @@ -2454,95 +2481,117 @@ eOSState cMenuSetupLNB::ProcessKey(eKeys Key) return state; } -// --- cMenuSetupCICAM ------------------------------------------------------- +// --- cMenuSetupCAM --------------------------------------------------------- -class cMenuSetupCICAMItem : public cOsdItem { +class cMenuSetupCAMItem : public cOsdItem { private: - cCiHandler *ciHandler; - int slot; + cCamSlot *camSlot; public: - cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot); - cCiHandler *CiHandler(void) { return ciHandler; } - int Slot(void) { return slot; } + cMenuSetupCAMItem(cCamSlot *CamSlot); + cCamSlot *CamSlot(void) { return camSlot; } + bool Changed(void); }; -cMenuSetupCICAMItem::cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot) +cMenuSetupCAMItem::cMenuSetupCAMItem(cCamSlot *CamSlot) +{ + camSlot = CamSlot; + SetText(""); + Changed(); +} + +bool cMenuSetupCAMItem::Changed(void) { - ciHandler = CiHandler; - slot = Slot; char buffer[32]; - const char *CamName = CiHandler->GetCamName(slot); - snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), Device + 1, slot + 1, CamName ? CamName : "-"); - SetText(buffer); + const char *CamName = camSlot->GetCamName(); + if (!CamName) { + switch (camSlot->ModuleStatus()) { + case msReset: CamName = tr("CAM reset"); break; + case msPresent: CamName = tr("CAM present"); break; + case msReady: CamName = tr("CAM ready"); break; + default: CamName = "-"; break; + } + } + snprintf(buffer, sizeof(buffer), " %d %s", camSlot->SlotNumber(), CamName); + if (strcmp(buffer, Text()) != 0) { + SetText(buffer); + return true; + } + return false; } -class cMenuSetupCICAM : public cMenuSetupBase { +class cMenuSetupCAM : public cMenuSetupBase { private: eOSState Menu(void); eOSState Reset(void); public: - cMenuSetupCICAM(void); + cMenuSetupCAM(void); virtual eOSState ProcessKey(eKeys Key); }; -cMenuSetupCICAM::cMenuSetupCICAM(void) +cMenuSetupCAM::cMenuSetupCAM(void) { - SetSection(tr("CICAM")); - for (int d = 0; d < cDevice::NumDevices(); d++) { - cDevice *Device = cDevice::GetDevice(d); - if (Device) { - cCiHandler *CiHandler = Device->CiHandler(); - if (CiHandler) { - for (int Slot = 0; Slot < CiHandler->NumSlots(); Slot++) - Add(new cMenuSetupCICAMItem(Device->CardIndex(), CiHandler, Slot)); - } - } - } + SetSection(tr("CAM")); + SetCols(15); + SetHasHotkeys(); + for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) + Add(new cMenuSetupCAMItem(CamSlot)); SetHelp(tr("Button$Menu"), tr("Button$Reset")); } -eOSState cMenuSetupCICAM::Menu(void) +eOSState cMenuSetupCAM::Menu(void) { - cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current()); + cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current()); if (item) { - if (item->CiHandler()->EnterMenu(item->Slot())) { - Skins.Message(mtWarning, tr("Opening CAM menu...")); - time_t t = time(NULL); - while (time(NULL) - t < MAXWAITFORCAMMENU && !item->CiHandler()->HasUserIO()) - item->CiHandler()->Process(); - return osEnd; // the CAM menu will be executed explicitly from the main loop + if (item->CamSlot()->EnterMenu()) { + Skins.Message(mtStatus, tr("Opening CAM menu...")); + time_t t0 = time(NULL); + time_t t1 = t0; + while (time(NULL) - t0 <= MAXWAITFORCAMMENU) { + if (item->CamSlot()->HasUserIO()) + break; + if (time(NULL) - t1 >= CAMMENURETYTIMEOUT) { + dsyslog("CAM %d: retrying to enter CAM menu...", item->CamSlot()->SlotNumber()); + item->CamSlot()->EnterMenu(); + t1 = time(NULL); + } + cCondWait::SleepMs(100); + } + Skins.Message(mtStatus, NULL); + if (item->CamSlot()->HasUserIO()) + return AddSubMenu(new cMenuCam(item->CamSlot())); } - else - Skins.Message(mtError, tr("Can't open CAM menu!")); + Skins.Message(mtError, tr("Can't open CAM menu!")); } return osContinue; } -eOSState cMenuSetupCICAM::Reset(void) +eOSState cMenuSetupCAM::Reset(void) { - cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current()); + cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current()); if (item) { - Skins.Message(mtWarning, tr("Resetting CAM...")); - if (item->CiHandler()->Reset(item->Slot())) { - Skins.Message(mtInfo, tr("CAM has been reset")); - return osEnd; + if (!item->CamSlot()->Device() || Interface->Confirm(tr("CAM is in use - really reset?"))) { + if (!item->CamSlot()->Reset()) + Skins.Message(mtError, tr("Can't reset CAM!")); } - else - Skins.Message(mtError, tr("Can't reset CAM!")); } return osContinue; } -eOSState cMenuSetupCICAM::ProcessKey(eKeys Key) +eOSState cMenuSetupCAM::ProcessKey(eKeys Key) { - eOSState state = cMenuSetupBase::ProcessKey(Key); + eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key); - if (state == osUnknown) { + if (!HasSubMenu()) { switch (Key) { + case kOk: case kRed: return Menu(); - case kGreen: return Reset(); + case kGreen: state = Reset(); break; default: break; } + for (cMenuSetupCAMItem *ci = (cMenuSetupCAMItem *)First(); ci; ci = (cMenuSetupCAMItem *)ci->Next()) { + if (ci->Changed()) + DisplayItem(ci); + } } return state; } @@ -2710,7 +2759,7 @@ void cMenuSetup::Set(void) Add(new cOsdItem(hk(tr("EPG")), osUser2)); Add(new cOsdItem(hk(tr("DVB")), osUser3)); Add(new cOsdItem(hk(tr("LNB")), osUser4)); - Add(new cOsdItem(hk(tr("CICAM")), osUser5)); + Add(new cOsdItem(hk(tr("CAM")), osUser5)); Add(new cOsdItem(hk(tr("Recording")), osUser6)); Add(new cOsdItem(hk(tr("Replay")), osUser7)); Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8)); @@ -2740,7 +2789,7 @@ eOSState cMenuSetup::ProcessKey(eKeys Key) case osUser2: return AddSubMenu(new cMenuSetupEPG); case osUser3: return AddSubMenu(new cMenuSetupDVB); case osUser4: return AddSubMenu(new cMenuSetupLNB); - case osUser5: return AddSubMenu(new cMenuSetupCICAM); + case osUser5: return AddSubMenu(new cMenuSetupCAM); case osUser6: return AddSubMenu(new cMenuSetupRecord); case osUser7: return AddSubMenu(new cMenuSetupReplay); case osUser8: return AddSubMenu(new cMenuSetupMisc); @@ -3126,7 +3175,7 @@ cChannel *cDisplayChannel::NextAvailableChannel(cChannel *Channel, int Direction if (Direction) { while (Channel) { Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel); - if (Channel && !Channel->GroupSep() && (cDevice::PrimaryDevice()->ProvidesChannel(Channel, Setup.PrimaryLimit) || cDevice::GetDevice(Channel, 0))) + if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, 0, true)) return Channel; } } @@ -3541,7 +3590,7 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) isyslog("record %s", fileName); if (MakeDirs(fileName, true)) { const cChannel *ch = timer->Channel(); - recorder = new cRecorder(fileName, ch->Ca(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids()); + recorder = new cRecorder(fileName, ch->GetChannelID(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids()); if (device->AttachReceiver(recorder)) { Recording.WriteInfo(); cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); @@ -3610,7 +3659,7 @@ void cRecordControl::Stop(void) bool cRecordControl::Process(time_t t) { - if (!recorder || !timer || !timer->Matches(t)) + if (!recorder || !recorder->IsAttached() || !timer || !timer->Matches(t)) return false; AssertFreeDiskSpace(timer->Priority()); return true; @@ -3645,15 +3694,9 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause) cChannel *channel = Channels.GetByNumber(ch); if (channel) { - bool NeedsDetachReceivers = false; int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority; - cDevice *device = cDevice::GetDevice(channel, Priority, &NeedsDetachReceivers); + cDevice *device = cDevice::GetDevice(channel, Priority, false); if (device) { - if (NeedsDetachReceivers) { - Stop(device); - if (device == cTransferControl::ReceiverDevice()) - cControl::Shutdown(); // in case this device was used for Transfer Mode - } dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number()); if (!device->SwitchChannel(channel, false)) { cThread::EmergencyExit(true); @@ -3698,19 +3741,6 @@ void cRecordControls::Stop(const char *InstantId) } } -void cRecordControls::Stop(cDevice *Device) -{ - ChangeState(); - for (int i = 0; i < MAXRECORDCONTROLS; i++) { - if (RecordControls[i]) { - if (RecordControls[i]->Device() == Device) { - isyslog("stopping recording on DVB device %d due to higher priority", Device->CardIndex() + 1); - RecordControls[i]->Stop(); - } - } - } -} - bool cRecordControls::PauseLiveVideo(void) { Skins.Message(mtStatus, tr("Pausing live video...")); @@ -3882,7 +3912,8 @@ void cReplayControl::Hide(void) if (visible) { delete displayReplay; displayReplay = NULL; - needsFastResponse = visible = false; + SetNeedsFastResponse(false); + visible = false; modeOnly = false; lastPlay = lastForward = false; lastSpeed = -2; // an invalid value @@ -3923,7 +3954,8 @@ bool cReplayControl::ShowProgress(bool Initial) if (!visible) { displayReplay = Skins.Current()->DisplayReplay(modeOnly); displayReplay->SetMarks(&marks); - needsFastResponse = visible = true; + SetNeedsFastResponse(true); + visible = true; } if (Initial) { if (title) diff --git a/menu.h b/menu.h index 7376b71..9851ae1 100644 --- a/menu.h +++ b/menu.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 1.86 2006/10/20 13:09:57 kls Exp $ + * $Id: menu.h 1.87 2007/01/05 10:40:54 kls Exp $ */ #ifndef __MENU_H @@ -128,31 +128,6 @@ public: eOSState ProcessKey(eKeys Key); }; -class cMenuCam : public cOsdMenu { -private: - cCiMenu *ciMenu; - bool selected; - int offset; - void AddMultiLineItem(const char *s); - eOSState Select(void); -public: - cMenuCam(cCiMenu *CiMenu); - virtual ~cMenuCam(); - virtual eOSState ProcessKey(eKeys Key); - }; - -class cMenuCamEnquiry : public cOsdMenu { -private: - cCiEnquiry *ciEnquiry; - char *input; - bool replied; - eOSState Reply(void); -public: - cMenuCamEnquiry(cCiEnquiry *CiEnquiry); - virtual ~cMenuCamEnquiry(); - virtual eOSState ProcessKey(eKeys Key); - }; - cOsdObject *CamControl(void); class cMenuRecordingItem; @@ -206,7 +181,6 @@ private: public: static bool Start(cTimer *Timer = NULL, bool Pause = false); static void Stop(const char *InstantId); - static void Stop(cDevice *Device); static bool PauseLiveVideo(void); static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); diff --git a/osdbase.c b/osdbase.c index 65b66cb..5a43321 100644 --- a/osdbase.c +++ b/osdbase.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osdbase.c 1.29 2006/02/05 14:37:03 kls Exp $ + * $Id: osdbase.c 1.30 2007/01/05 10:41:16 kls Exp $ */ #include "osdbase.h" @@ -127,9 +127,9 @@ void cOsdMenu::SetCols(int c0, int c1, int c2, int c3, int c4) cols[4] = c4; } -void cOsdMenu::SetHasHotkeys(void) +void cOsdMenu::SetHasHotkeys(bool HasHotkeys) { - hasHotkeys = true; + hasHotkeys = HasHotkeys; digit = 0; } @@ -256,6 +256,20 @@ void cOsdMenu::DisplayCurrent(bool Current) } } +void cOsdMenu::DisplayItem(cOsdItem *Item) +{ + if (Item) { + int Index = Item->Index(); + int Offset = Index - first; + if (Offset >= 0 && Offset < first + displayMenuItems) { + bool Current = Index == current; + displayMenu->SetItem(Item->Text(), Offset, Current && Item->Selectable(), Item->Selectable()); + if (Current && Item->Selectable()) + cStatus::MsgOsdCurrentItem(Item->Text()); + } + } +} + void cOsdMenu::Clear(void) { if (marked >= 0) @@ -432,6 +446,8 @@ eOSState cOsdMenu::HotKey(eKeys Key) if (s && (s = skipspace(s)) != NULL) { if (*s == Key - k1 + '1') { current = item->Index(); + RefreshCurrent(); + Display(); cRemote::Put(kOk, true); break; } @@ -499,4 +515,3 @@ eOSState cOsdMenu::ProcessKey(eKeys Key) } return osContinue; } - diff --git a/osdbase.h b/osdbase.h index 0ca284b..fac1a57 100644 --- a/osdbase.h +++ b/osdbase.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osdbase.h 1.14 2006/01/06 11:55:30 kls Exp $ + * $Id: osdbase.h 1.15 2007/01/05 10:41:32 kls Exp $ */ #ifndef __OSDBASE_H @@ -70,12 +70,13 @@ class cOsdObject { friend class cOsdMenu; private: bool isMenu; -protected: bool needsFastResponse; +protected: + void SetNeedsFastResponse(bool NeedsFastResponse) { needsFastResponse = NeedsFastResponse; } public: cOsdObject(bool FastResponse = false) { isMenu = false; needsFastResponse = FastResponse; } virtual ~cOsdObject() {} - bool NeedsFastResponse(void) { return needsFastResponse; } + virtual bool NeedsFastResponse(void) { return needsFastResponse; } bool IsMenu(void) { return isMenu; } virtual void Show(void); virtual eOSState ProcessKey(eKeys Key) { return osUnknown; } @@ -98,12 +99,13 @@ protected: cSkinDisplayMenu *DisplayMenu(void) { return displayMenu; } const char *hk(const char *s); void SetCols(int c0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0); - void SetHasHotkeys(void); + void SetHasHotkeys(bool HasHotkeys = true); virtual void Clear(void); bool SelectableItem(int idx); void SetCurrent(cOsdItem *Item); void RefreshCurrent(void); void DisplayCurrent(bool Current); + void DisplayItem(cOsdItem *Item); void CursorUp(void); void CursorDown(void); void PageUp(void); @@ -120,6 +122,7 @@ protected: public: cOsdMenu(const char *Title, int c0 = 0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0); virtual ~cOsdMenu(); + virtual bool NeedsFastResponse(void) { return subMenu ? subMenu->NeedsFastResponse() : cOsdObject::NeedsFastResponse(); } int Current(void) { return current; } void Add(cOsdItem *Item, bool Current = false, cOsdItem *After = NULL); void Ins(cOsdItem *Item, bool Current = false, cOsdItem *Before = NULL); diff --git a/pat.c b/pat.c index f898c70..559111d 100644 --- a/pat.c +++ b/pat.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: pat.c 1.16 2006/03/31 12:39:34 kls Exp $ + * $Id: pat.c 1.17 2007/01/05 10:41:55 kls Exp $ */ #include "pat.h" @@ -78,7 +78,7 @@ public: bool Is(cCaDescriptors * CaDescriptors); bool Empty(void) { return caDescriptors.Count() == 0; } void AddCaDescriptor(SI::CaDescriptor *d, bool Stream); - int GetCaDescriptors(const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag); + int GetCaDescriptors(const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag); const int *CaIds(void) { return caIds; } }; @@ -148,7 +148,7 @@ void cCaDescriptors::AddCaDescriptor(SI::CaDescriptor *d, bool Stream) #endif } -int cCaDescriptors::GetCaDescriptors(const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag) +int cCaDescriptors::GetCaDescriptors(const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag) { if (!CaSystemIds || !*CaSystemIds) return 0; @@ -156,7 +156,7 @@ int cCaDescriptors::GetCaDescriptors(const unsigned short *CaSystemIds, int BufS int length = 0; int IsStream = -1; for (cCaDescriptor *d = caDescriptors.First(); d; d = caDescriptors.Next(d)) { - const unsigned short *caids = CaSystemIds; + const int *caids = CaSystemIds; do { if (d->CaSystem() == *caids) { if (length + d->Length() <= BufSize) { @@ -187,7 +187,7 @@ public: // Returns 0 if this is an already known descriptor, // 1 if it is an all new descriptor with actual contents, // and 2 if an existing descriptor was changed. - int GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag); + int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag); }; int cCaDescriptorHandler::AddCaDescriptors(cCaDescriptors *CaDescriptors) @@ -208,7 +208,7 @@ int cCaDescriptorHandler::AddCaDescriptors(cCaDescriptors *CaDescriptors) return CaDescriptors->Empty() ? 0 : 1; } -int cCaDescriptorHandler::GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag) +int cCaDescriptorHandler::GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag) { cMutexLock MutexLock(&mutex); StreamFlag = false; @@ -221,7 +221,7 @@ int cCaDescriptorHandler::GetCaDescriptors(int Source, int Transponder, int Serv cCaDescriptorHandler CaDescriptorHandler; -int GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag) +int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag) { return CaDescriptorHandler.GetCaDescriptors(Source, Transponder, ServiceId, CaSystemIds, BufSize, Data, StreamFlag); } diff --git a/pat.h b/pat.h index 88b2115..a8053ae 100644 --- a/pat.h +++ b/pat.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: pat.h 1.6 2006/03/29 15:18:38 kls Exp $ + * $Id: pat.h 1.7 2007/01/05 10:42:11 kls Exp $ */ #ifndef __PAT_H @@ -32,7 +32,7 @@ public: void Trigger(void); }; -int GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag); +int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag); ///< Gets all CA descriptors for a given channel. ///< Copies all available CA descriptors for the given Source, Transponder and ServiceId ///< into the provided buffer at Data (at most BufSize bytes). Only those CA descriptors diff --git a/receiver.c b/receiver.c index ddaafef..20be19a 100644 --- a/receiver.c +++ b/receiver.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: receiver.c 1.5 2006/03/26 14:07:21 kls Exp $ + * $Id: receiver.c 1.6 2007/01/05 10:42:29 kls Exp $ */ #include "receiver.h" @@ -12,10 +12,10 @@ #include #include "tools.h" -cReceiver::cReceiver(int Ca, int Priority, int Pid, const int *Pids1, const int *Pids2, const int *Pids3) +cReceiver::cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1, const int *Pids2, const int *Pids3) { device = NULL; - ca = Ca; + channelID = ChannelID; priority = Priority; numPids = 0; if (Pid) diff --git a/receiver.h b/receiver.h index e123499..e223274 100644 --- a/receiver.h +++ b/receiver.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: receiver.h 1.4 2006/05/27 09:04:22 kls Exp $ + * $Id: receiver.h 1.5 2007/01/05 11:00:36 kls Exp $ */ #ifndef __RECEIVER_H @@ -18,7 +18,7 @@ class cReceiver { friend class cDevice; private: cDevice *device; - int ca; + tChannelID channelID; int priority; int pids[MAXRECEIVEPIDS]; int numPids; @@ -38,8 +38,8 @@ protected: ///< will be delivered only ONCE, so the cReceiver must make sure that ///< it will be able to buffer the data if necessary. public: - cReceiver(int Ca, int Priority, int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); - ///< Creates a new receiver that requires conditional access Ca and has + cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); + ///< Creates a new receiver for the channel with the given ChannelID with ///< the given Priority. Pid is a single PID (typically the video PID), while ///< Pids1...Pids3 are pointers to zero terminated lists of PIDs. ///< If any of these PIDs are 0, they will be silently ignored. @@ -47,7 +47,18 @@ public: ///< Priority may be any value in the range -99..99. Negative values indicate ///< that this cReceiver may be detached at any time (without blocking the ///< cDevice it is attached to). + ///< The ChannelID is necessary to allow the device that will be used for this + ///< receiver to detect and store whether the channel can be decrypted in case + ///< this is an encrypted channel. If the channel is not encrypted or this + ///< detection is not wanted, an invalid tChannelID may be given. virtual ~cReceiver(); + tChannelID ChannelID(void) { return channelID; } + bool IsAttached(void) { return device != NULL; } + ///< Returns true if this receiver is (still) attached to a device. + ///< A receiver may be automatically detached from its device in + ///< case the device is needed otherwise, so code that uses a cReceiver + ///< should repeatedly check whether it is still attached, and if + ///< it isn't, delete it (or take any other appropriate measures). }; #endif //__RECEIVER_H diff --git a/recorder.c b/recorder.c index 8bb1621..2542bca 100644 --- a/recorder.c +++ b/recorder.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.c 1.17 2006/01/08 11:01:25 kls Exp $ + * $Id: recorder.c 1.18 2007/01/05 10:43:09 kls Exp $ */ #include "recorder.h" @@ -21,6 +21,8 @@ #define MINFREEDISKSPACE (512) // MB #define DISKCHECKINTERVAL 100 // seconds +// --- cFileWriter ----------------------------------------------------------- + class cFileWriter : public cThread { private: cRemux *remux; @@ -121,8 +123,10 @@ void cFileWriter::Action(void) } } -cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids) -:cReceiver(Ca, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) +// --- cRecorder ------------------------------------------------------------- + +cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids) +:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) ,cThread("recording") { // Make sure the disk is up and running: diff --git a/recorder.h b/recorder.h index c4aebbf..920d909 100644 --- a/recorder.h +++ b/recorder.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.h 1.4 2005/08/13 11:31:18 kls Exp $ + * $Id: recorder.h 1.5 2007/01/05 10:44:05 kls Exp $ */ #ifndef __RECORDER_H @@ -28,9 +28,9 @@ protected: virtual void Receive(uchar *Data, int Length); virtual void Action(void); public: - cRecorder(const char *FileName, int Ca, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids); - // Creates a new recorder that requires conditional access Ca, has - // the given Priority and will record the given PIDs into the file FileName. + cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids); + // Creates a new recorder for the channel with the given ChannelID and + // the given Priority that will record the given PIDs into the file FileName. virtual ~cRecorder(); }; diff --git a/thread.c b/thread.c index 2e145e5..d2debef 100644 --- a/thread.c +++ b/thread.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 1.58 2006/09/24 12:54:47 kls Exp $ + * $Id: thread.c 1.59 2007/01/05 10:44:22 kls Exp $ */ #include "thread.h" @@ -249,17 +249,29 @@ void *cThread::StartThread(cThread *Thread) return NULL; } +#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it +#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop + bool cThread::Start(void) { - if (!active) { - active = running = true; - if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) { - pthread_detach(childTid); // auto-reap + if (!running) { + if (active) { + // Wait until the previous incarnation of this thread has completely ended + // before starting it newly: + cTimeMs RestartTimeout; + while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT) + cCondWait::SleepMs(THREAD_STOP_SLEEP); } - else { - LOG_ERROR; - active = running = false; - return false; + if (!active) { + active = running = true; + if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) { + pthread_detach(childTid); // auto-reap + } + else { + LOG_ERROR; + active = running = false; + return false; + } } } return true; diff --git a/thread.h b/thread.h index 6211e2c..be21c19 100644 --- a/thread.h +++ b/thread.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 1.37 2006/09/24 10:10:37 kls Exp $ + * $Id: thread.h 1.38 2007/01/05 10:44:38 kls Exp $ */ #ifndef __THREAD_H @@ -115,6 +115,7 @@ public: void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3))); bool Start(void); ///< Actually starts the thread. + ///< If the thread is already running, nothing happens. bool Active(void); ///< Checks whether the thread is still alive. static bool EmergencyExit(bool Request = false); diff --git a/tools.c b/tools.c index 255c806..db79f95 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.121 2006/12/02 11:12:59 kls Exp $ + * $Id: tools.c 1.122 2007/01/05 10:44:57 kls Exp $ */ #include "tools.h" @@ -542,9 +542,9 @@ time_t LastModifiedTime(const char *FileName) // --- cTimeMs --------------------------------------------------------------- -cTimeMs::cTimeMs(void) +cTimeMs::cTimeMs(int Ms) { - Set(); + Set(Ms); } uint64_t cTimeMs::Now(void) diff --git a/tools.h b/tools.h index 1f71ec4..6f07375 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.96 2006/12/03 17:38:38 kls Exp $ + * $Id: tools.h 1.97 2007/01/05 10:45:11 kls Exp $ */ #ifndef __TOOLS_H @@ -23,7 +23,6 @@ #include typedef unsigned char uchar; -#define uint64 uint64_t // for compatibility - TODO remove in version 1.5 extern int SysLogLevel; @@ -162,7 +161,8 @@ class cTimeMs { private: uint64_t begin; public: - cTimeMs(void); + cTimeMs(int Ms = 0); + ///< Creates a timer with ms resolution and an initial timeout of Ms. static uint64_t Now(void); void Set(int Ms = 0); bool TimedOut(void); diff --git a/transfer.c b/transfer.c index afaccd1..79efab5 100644 --- a/transfer.c +++ b/transfer.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: transfer.c 1.33 2006/01/29 17:24:39 kls Exp $ + * $Id: transfer.c 1.34 2007/01/05 10:45:28 kls Exp $ */ #include "transfer.h" @@ -14,8 +14,8 @@ // --- cTransfer ------------------------------------------------------------- -cTransfer::cTransfer(int VPid, const int *APids, const int *DPids, const int *SPids) -:cReceiver(0, -1, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) +cTransfer::cTransfer(tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids) +:cReceiver(ChannelID, -1, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) ,cThread("transfer") { ringBuffer = new cRingBufferLinear(TRANSFERBUFSIZE, TS_SIZE * 2, true, "Transfer"); @@ -34,17 +34,18 @@ void cTransfer::Activate(bool On) { if (On) Start(); - else + else { Cancel(3); + cPlayer::Detach(); + } } void cTransfer::Receive(uchar *Data, int Length) { - if (IsAttached() && Running()) { + if (cPlayer::IsAttached() && Running()) { int p = ringBuffer->Put(Data, Length); if (p != Length && Running()) ringBuffer->ReportOverflow(Length - p); - return; } } @@ -110,8 +111,8 @@ void cTransfer::Action(void) cDevice *cTransferControl::receiverDevice = NULL; -cTransferControl::cTransferControl(cDevice *ReceiverDevice, int VPid, const int *APids, const int *DPids, const int *SPids) -:cControl(transfer = new cTransfer(VPid, APids, DPids, SPids), true) +cTransferControl::cTransferControl(cDevice *ReceiverDevice, tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids) +:cControl(transfer = new cTransfer(ChannelID, VPid, APids, DPids, SPids), true) { ReceiverDevice->AttachReceiver(transfer); receiverDevice = ReceiverDevice; diff --git a/transfer.h b/transfer.h index 7210ac0..c891808 100644 --- a/transfer.h +++ b/transfer.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: transfer.h 1.11 2006/01/29 17:24:43 kls Exp $ + * $Id: transfer.h 1.12 2007/01/05 10:45:45 kls Exp $ */ #ifndef __TRANSFER_H @@ -25,7 +25,7 @@ protected: virtual void Receive(uchar *Data, int Length); virtual void Action(void); public: - cTransfer(int VPid, const int *APids, const int *DPids, const int *SPids); + cTransfer(tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids); virtual ~cTransfer(); }; @@ -34,7 +34,7 @@ private: cTransfer *transfer; static cDevice *receiverDevice; public: - cTransferControl(cDevice *ReceiverDevice, int VPid, const int *APids, const int *DPids, const int *SPids); + cTransferControl(cDevice *ReceiverDevice, tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids); ~cTransferControl(); virtual void Hide(void) {} static cDevice *ReceiverDevice(void) { return receiverDevice; } diff --git a/vdr.c b/vdr.c index a628cd0..ae845f5 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/vdr * - * $Id: vdr.c 1.282 2006/12/02 16:22:12 kls Exp $ + * $Id: vdr.c 1.283 2007/01/05 10:46:14 kls Exp $ */ #include @@ -68,8 +68,6 @@ #define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown #define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start #define CHANNELSAVEDELTA 600 // seconds before saving channels.conf after automatic modifications -#define LASTCAMMENUTIMEOUT 3 // seconds to run the main loop 'fast' after a CAM menu has been closed - // in order to react on a possible new CAM menu as soon as possible #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready #define MENUTIMEOUT 120 // seconds of user inactivity after which an OSD display is closed #define SHUTDOWNRETRY 300 // seconds before trying again to shut down @@ -501,7 +499,6 @@ int main(int argc, char *argv[]) int PreviousChannelIndex = 0; time_t LastChannelChanged = time(NULL); time_t LastActivity = 0; - time_t LastCamMenu = 0; int MaxLatencyTime = 0; bool ForceShutdown = false; bool UserShutdown = false; @@ -851,19 +848,14 @@ int main(int argc, char *argv[]) DeletedRecordings.Update(); } // CAM control: - if (!Menu && !cOsd::IsOpen()) { + if (!Menu && !cOsd::IsOpen()) Menu = CamControl(); - if (Menu) - LastCamMenu = 0; - else if (!LastCamMenu) - LastCamMenu = time(NULL); - } // Queued messages: if (!Skins.IsOpen()) Skins.ProcessQueuedMessages(); // User Input: cOsdObject *Interact = Menu ? Menu : cControl::Control(); - eKeys key = Interface->GetKey((!Interact || !Interact->NeedsFastResponse()) && time(NULL) - LastCamMenu > LASTCAMMENUTIMEOUT); + eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse()); if (NORMALKEY(key) != kNone) { EITScanner.Activity(); LastActivity = time(NULL); @@ -1052,9 +1044,6 @@ int main(int argc, char *argv[]) else if (time(NULL) - LastActivity > MENUTIMEOUT) state = osEnd; } - // TODO make the CAM menu stay open in case of automatic updates and have it return osContinue; then the following two lines can be removed again - else if (state == osEnd && LastActivity > 1) - LastActivity = time(NULL); switch (state) { case osPause: DELETE_MENU; cControl::Shutdown(); // just in case -- cgit v1.2.3