diff options
-rw-r--r-- | HISTORY | 53 | ||||
-rw-r--r-- | MANUAL | 16 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | PLUGINS.html | 47 | ||||
-rw-r--r-- | channels.h | 3 | ||||
-rw-r--r-- | ci.c | 1742 | ||||
-rw-r--r-- | ci.h | 247 | ||||
-rw-r--r-- | config.h | 10 | ||||
-rw-r--r-- | device.c | 276 | ||||
-rw-r--r-- | device.h | 51 | ||||
-rw-r--r-- | dvbci.c | 108 | ||||
-rw-r--r-- | dvbci.h | 31 | ||||
-rw-r--r-- | dvbdevice.c | 77 | ||||
-rw-r--r-- | dvbdevice.h | 10 | ||||
-rw-r--r-- | i18n.c | 200 | ||||
-rw-r--r-- | menu.c | 416 | ||||
-rw-r--r-- | menu.h | 28 | ||||
-rw-r--r-- | osdbase.c | 23 | ||||
-rw-r--r-- | osdbase.h | 11 | ||||
-rw-r--r-- | pat.c | 14 | ||||
-rw-r--r-- | pat.h | 4 | ||||
-rw-r--r-- | receiver.c | 6 | ||||
-rw-r--r-- | receiver.h | 19 | ||||
-rw-r--r-- | recorder.c | 10 | ||||
-rw-r--r-- | recorder.h | 8 | ||||
-rw-r--r-- | thread.c | 30 | ||||
-rw-r--r-- | thread.h | 3 | ||||
-rw-r--r-- | tools.c | 6 | ||||
-rw-r--r-- | tools.h | 6 | ||||
-rw-r--r-- | transfer.c | 17 | ||||
-rw-r--r-- | transfer.h | 6 | ||||
-rw-r--r-- | vdr.c | 17 |
32 files changed, 2079 insertions, 1420 deletions
@@ -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. @@ -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: @@ -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 @@ <center><h1>The VDR Plugin System</h1></center> -<center><b>Version 1.4.1</b></center> +<center><b>Version 1.5.0</b></center> <p> <center> Copyright © 2006 Klaus Schmidinger<br> @@ -14,6 +14,10 @@ Copyright © 2006 Klaus Schmidinger<br> <a href="http://www.cadsoft.de/vdr">www.cadsoft.de/vdr</a> </center> <p> +<!--X1.5.0--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%> +Important modifications introduced in version 1.5.0 are marked like this. +<!--X1.5.0--></td></tr></table> +<p> 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 <li><a href="#Devices">Devices</a> <li><a href="#Audio">Audio</a> <li><a href="#Remote Control">Remote Control</a> +<!--X1.5.0--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%> +<li><a href="#Conditional Access">Conditional Access</a> +<!--X1.5.0--></td></tr></table> </ul> </ul> @@ -1727,6 +1734,7 @@ selecting which channel it shall tune to: <p><table><tr><td bgcolor=#F0F0F0><pre> 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); </pre></td></tr></table><p> @@ -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. +<!--X1.5.0--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%> +<a name="Conditional Access"><hr><h2>Conditional Access</h2> + +<center><i><b>Members only!</b></i></center><p> + +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. +<p> +VDR's mechanisms for supporting Conditional Access are mainly the two classes +<tt>cCiAdapter</tt> and <tt>cCamSlot</tt>. A <tt>cCiAdapter</tt> handles exactly +one CI, and can provide several CAM slots, represented by the appropriate +number of <tt>cCamSlot</tt> objects. +<p> +In order to decrypt a particular channel, a <tt>cCiAdapter</tt> with a <tt>cCamSlot</tt> +that contains the necessary CAM will be assigned to a <tt>cDevice</tt>, and exactly +one of its CAM slots will be activated. Whether or not a <tt>cCiAdapter</tt> 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 <tt>cCiAdapter</tt> 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. +<p> +If a plugin implements a derived <tt>cCiAdapter</tt>, it has to implement +several low level functions that handle the actual data transfer (see <tt>dvbci.c</tt> +for example). The decision whether the adapter can actually be assigned to different +devices is made in the function + +<p><table><tr><td bgcolor=#F0F0F0><pre> +virtual bool Assign(cDevice *Device, bool Query = false); +</pre></td></tr></table><p> + +See the description of this function in <tt>ci.h</tt> for details. +<!--X1.5.0--></td></tr></table> + </body> </html> @@ -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; } @@ -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 <sys/ioctl.h> #include <time.h> #include <unistd.h> +#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<cCiCaPidData> 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<cCiCaPmt> 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); } @@ -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 <stdint.h> #include <stdio.h> +#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<cCiCaPidData> 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<cCiCaProgramData> 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<cCamSlot> {}; + +extern cCamSlots CamSlots; + +class cChannelCamRelation; + +class cChannelCamRelations : public cList<cChannelCamRelation> { +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 @@ -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 @@ -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 ------------------------------------------------------------- @@ -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); @@ -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 <linux/dvb/ca.h> +#include <sys/ioctl.h> +#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; +} @@ -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 <sys/mman.h> #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: @@ -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)", @@ -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) @@ -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); @@ -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; } - @@ -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); @@ -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); } @@ -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 @@ -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 <stdio.h> #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) @@ -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 @@ -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: @@ -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(); }; @@ -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; @@ -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); @@ -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) @@ -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 <sys/types.h> 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); @@ -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; @@ -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; } @@ -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 <getopt.h> @@ -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 |