vdr  1.7.27
dvbdevice.c
Go to the documentation of this file.
00001 /*
00002  * dvbdevice.c: The DVB device tuner interface
00003  *
00004  * See the main source file 'vdr.c' for copyright information and
00005  * how to reach the author.
00006  *
00007  * $Id: dvbdevice.c 2.69 2012/03/25 10:41:45 kls Exp $
00008  */
00009 
00010 #include "dvbdevice.h"
00011 #include <ctype.h>
00012 #include <errno.h>
00013 #include <limits.h>
00014 #include <linux/dvb/dmx.h>
00015 #include <linux/dvb/frontend.h>
00016 #include <sys/ioctl.h>
00017 #include <sys/mman.h>
00018 #include "channels.h"
00019 #include "diseqc.h"
00020 #include "dvbci.h"
00021 #include "menuitems.h"
00022 #include "sourceparams.h"
00023 
00024 #define FE_CAN_TURBO_FEC  0x8000000 // TODO: remove this once it is defined in the driver
00025 
00026 #define DVBS_TUNE_TIMEOUT  9000 //ms
00027 #define DVBS_LOCK_TIMEOUT  2000 //ms
00028 #define DVBC_TUNE_TIMEOUT  9000 //ms
00029 #define DVBC_LOCK_TIMEOUT  2000 //ms
00030 #define DVBT_TUNE_TIMEOUT  9000 //ms
00031 #define DVBT_LOCK_TIMEOUT  2000 //ms
00032 #define ATSC_TUNE_TIMEOUT  9000 //ms
00033 #define ATSC_LOCK_TIMEOUT  2000 //ms
00034 
00035 #define SCR_RANDOM_TIMEOUT  500 // ms (add random value up to this when tuning SCR device to avoid lockups)
00036 
00037 // --- DVB Parameter Maps ----------------------------------------------------
00038 
00039 const tDvbParameterMap InversionValues[] = {
00040   {   0, INVERSION_OFF,  trNOOP("off") },
00041   {   1, INVERSION_ON,   trNOOP("on") },
00042   { 999, INVERSION_AUTO, trNOOP("auto") },
00043   {  -1, 0, NULL }
00044   };
00045 
00046 const tDvbParameterMap BandwidthValues[] = {
00047   {    5,  5000000, "5 MHz" },
00048   {    6,  6000000, "6 MHz" },
00049   {    7,  7000000, "7 MHz" },
00050   {    8,  8000000, "8 MHz" },
00051   {   10, 10000000, "10 MHz" },
00052   { 1712,  1712000, "1.712 MHz" },
00053   {  -1, 0, NULL }
00054   };
00055 
00056 const tDvbParameterMap CoderateValues[] = {
00057   {   0, FEC_NONE, trNOOP("none") },
00058   {  12, FEC_1_2,  "1/2" },
00059   {  23, FEC_2_3,  "2/3" },
00060   {  34, FEC_3_4,  "3/4" },
00061   {  35, FEC_3_5,  "3/5" },
00062   {  45, FEC_4_5,  "4/5" },
00063   {  56, FEC_5_6,  "5/6" },
00064   {  67, FEC_6_7,  "6/7" },
00065   {  78, FEC_7_8,  "7/8" },
00066   {  89, FEC_8_9,  "8/9" },
00067   { 910, FEC_9_10, "9/10" },
00068   { 999, FEC_AUTO, trNOOP("auto") },
00069   {  -1, 0, NULL }
00070   };
00071 
00072 const tDvbParameterMap ModulationValues[] = {
00073   {  16, QAM_16,   "QAM16" },
00074   {  32, QAM_32,   "QAM32" },
00075   {  64, QAM_64,   "QAM64" },
00076   { 128, QAM_128,  "QAM128" },
00077   { 256, QAM_256,  "QAM256" },
00078   {   2, QPSK,     "QPSK" },
00079   {   5, PSK_8,    "8PSK" },
00080   {   6, APSK_16,  "16APSK" },
00081   {   7, APSK_32,  "32APSK" },
00082   {  10, VSB_8,    "VSB8" },
00083   {  11, VSB_16,   "VSB16" },
00084   {  12, DQPSK,    "DQPSK" },
00085   { 999, QAM_AUTO, trNOOP("auto") },
00086   {  -1, 0, NULL }
00087   };
00088 
00089 #define DVB_SYSTEM_1 0 // see also nit.c
00090 #define DVB_SYSTEM_2 1
00091 
00092 const tDvbParameterMap SystemValuesSat[] = {
00093   {   0, DVB_SYSTEM_1, "DVB-S" },
00094   {   1, DVB_SYSTEM_2, "DVB-S2" },
00095   {  -1, 0, NULL }
00096   };
00097 
00098 const tDvbParameterMap SystemValuesTerr[] = {
00099   {   0, DVB_SYSTEM_1, "DVB-T" },
00100   {   1, DVB_SYSTEM_2, "DVB-T2" },
00101   {  -1, 0, NULL }
00102   };
00103 
00104 const tDvbParameterMap TransmissionValues[] = {
00105   {   1, TRANSMISSION_MODE_1K,   "1K" },
00106   {   2, TRANSMISSION_MODE_2K,   "2K" },
00107   {   4, TRANSMISSION_MODE_4K,   "4K" },
00108   {   8, TRANSMISSION_MODE_8K,   "8K" },
00109   {  16, TRANSMISSION_MODE_16K,  "16K" },
00110   {  32, TRANSMISSION_MODE_32K,  "32K" },
00111   { 999, TRANSMISSION_MODE_AUTO, trNOOP("auto") },
00112   {  -1, 0, NULL }
00113   };
00114 
00115 const tDvbParameterMap GuardValues[] = {
00116   {     4, GUARD_INTERVAL_1_4,    "1/4" },
00117   {     8, GUARD_INTERVAL_1_8,    "1/8" },
00118   {    16, GUARD_INTERVAL_1_16,   "1/16" },
00119   {    32, GUARD_INTERVAL_1_32,   "1/32" },
00120   {   128, GUARD_INTERVAL_1_128,  "1/128" },
00121   { 19128, GUARD_INTERVAL_19_128, "19/128" },
00122   { 19256, GUARD_INTERVAL_19_256, "19/256" },
00123   {   999, GUARD_INTERVAL_AUTO,   trNOOP("auto") },
00124   {  -1, 0, NULL }
00125   };
00126 
00127 const tDvbParameterMap HierarchyValues[] = {
00128   {   0, HIERARCHY_NONE, trNOOP("none") },
00129   {   1, HIERARCHY_1,    "1" },
00130   {   2, HIERARCHY_2,    "2" },
00131   {   4, HIERARCHY_4,    "4" },
00132   { 999, HIERARCHY_AUTO, trNOOP("auto") },
00133   {  -1, 0, NULL }
00134   };
00135 
00136 const tDvbParameterMap RollOffValues[] = {
00137   {   0, ROLLOFF_AUTO, trNOOP("auto") },
00138   {  20, ROLLOFF_20, "0.20" },
00139   {  25, ROLLOFF_25, "0.25" },
00140   {  35, ROLLOFF_35, "0.35" },
00141   {  -1, 0, NULL }
00142   };
00143 
00144 int UserIndex(int Value, const tDvbParameterMap *Map)
00145 {
00146   const tDvbParameterMap *map = Map;
00147   while (map && map->userValue != -1) {
00148         if (map->userValue == Value)
00149            return map - Map;
00150         map++;
00151         }
00152   return -1;
00153 }
00154 
00155 int DriverIndex(int Value, const tDvbParameterMap *Map)
00156 {
00157   const tDvbParameterMap *map = Map;
00158   while (map && map->userValue != -1) {
00159         if (map->driverValue == Value)
00160            return map - Map;
00161         map++;
00162         }
00163   return -1;
00164 }
00165 
00166 int MapToUser(int Value, const tDvbParameterMap *Map, const char **String)
00167 {
00168   int n = DriverIndex(Value, Map);
00169   if (n >= 0) {
00170      if (String)
00171         *String = tr(Map[n].userString);
00172      return Map[n].userValue;
00173      }
00174   return -1;
00175 }
00176 
00177 const char *MapToUserString(int Value, const tDvbParameterMap *Map)
00178 {
00179   int n = DriverIndex(Value, Map);
00180   if (n >= 0)
00181      return Map[n].userString;
00182   return "???";
00183 }
00184 
00185 int MapToDriver(int Value, const tDvbParameterMap *Map)
00186 {
00187   int n = UserIndex(Value, Map);
00188   if (n >= 0)
00189      return Map[n].driverValue;
00190   return -1;
00191 }
00192 
00193 // --- cDvbTransponderParameters ---------------------------------------------
00194 
00195 cDvbTransponderParameters::cDvbTransponderParameters(const char *Parameters)
00196 {
00197   polarization = 0;
00198   inversion    = INVERSION_AUTO;
00199   bandwidth    = 8000000;
00200   coderateH    = FEC_AUTO;
00201   coderateL    = FEC_AUTO;
00202   modulation   = QPSK;
00203   system       = DVB_SYSTEM_1;
00204   transmission = TRANSMISSION_MODE_AUTO;
00205   guard        = GUARD_INTERVAL_AUTO;
00206   hierarchy    = HIERARCHY_AUTO;
00207   rollOff      = ROLLOFF_AUTO;
00208   plpId        = 0;
00209   Parse(Parameters);
00210 }
00211 
00212 int cDvbTransponderParameters::PrintParameter(char *p, char Name, int Value) const
00213 {
00214   return Value >= 0 && Value != 999 ? sprintf(p, "%c%d", Name, Value) : 0;
00215 }
00216 
00217 cString cDvbTransponderParameters::ToString(char Type) const
00218 {
00219 #define ST(s) if (strchr(s, Type) && (strchr(s, '0' + system + 1) || strchr(s, '*')))
00220   char buffer[64];
00221   char *q = buffer;
00222   *q = 0;
00223   ST("  S *")  q += sprintf(q, "%c", polarization);
00224   ST("   T*")  q += PrintParameter(q, 'B', MapToUser(bandwidth, BandwidthValues));
00225   ST(" CST*")  q += PrintParameter(q, 'C', MapToUser(coderateH, CoderateValues));
00226   ST("   T*")  q += PrintParameter(q, 'D', MapToUser(coderateL, CoderateValues));
00227   ST("   T*")  q += PrintParameter(q, 'G', MapToUser(guard, GuardValues));
00228   ST("ACST*")  q += PrintParameter(q, 'I', MapToUser(inversion, InversionValues));
00229   ST("ACST*")  q += PrintParameter(q, 'M', MapToUser(modulation, ModulationValues));
00230   ST("  S 2")  q += PrintParameter(q, 'O', MapToUser(rollOff, RollOffValues));
00231   ST("   T2")  q += PrintParameter(q, 'P', plpId);
00232   ST("  ST*")  q += PrintParameter(q, 'S', MapToUser(system, SystemValuesSat)); // we only need the numerical value, so Sat or Terr doesn't matter
00233   ST("   T*")  q += PrintParameter(q, 'T', MapToUser(transmission, TransmissionValues));
00234   ST("   T*")  q += PrintParameter(q, 'Y', MapToUser(hierarchy, HierarchyValues));
00235   return buffer;
00236 }
00237 
00238 const char *cDvbTransponderParameters::ParseParameter(const char *s, int &Value, const tDvbParameterMap *Map)
00239 {
00240   if (*++s) {
00241      char *p = NULL;
00242      errno = 0;
00243      int n = strtol(s, &p, 10);
00244      if (!errno && p != s) {
00245         Value = Map ? MapToDriver(n, Map) : n;
00246         if (Value >= 0)
00247            return p;
00248         }
00249      }
00250   esyslog("ERROR: invalid value for parameter '%c'", *(s - 1));
00251   return NULL;
00252 }
00253 
00254 bool cDvbTransponderParameters::Parse(const char *s)
00255 {
00256   while (s && *s) {
00257         switch (toupper(*s)) {
00258           case 'B': s = ParseParameter(s, bandwidth, BandwidthValues); break;
00259           case 'C': s = ParseParameter(s, coderateH, CoderateValues); break;
00260           case 'D': s = ParseParameter(s, coderateL, CoderateValues); break;
00261           case 'G': s = ParseParameter(s, guard, GuardValues); break;
00262           case 'H': polarization = *s++; break;
00263           case 'I': s = ParseParameter(s, inversion, InversionValues); break;
00264           case 'L': polarization = *s++; break;
00265           case 'M': s = ParseParameter(s, modulation, ModulationValues); break;
00266           case 'O': s = ParseParameter(s, rollOff, RollOffValues); break;
00267           case 'P': s = ParseParameter(s, plpId); break;
00268           case 'R': polarization = *s++; break;
00269           case 'S': s = ParseParameter(s, system, SystemValuesSat); break; // we only need the numerical value, so Sat or Terr doesn't matter
00270           case 'T': s = ParseParameter(s, transmission, TransmissionValues); break;
00271           case 'V': polarization = *s++; break;
00272           case 'Y': s = ParseParameter(s, hierarchy, HierarchyValues); break;
00273           default: esyslog("ERROR: unknown parameter key '%c'", *s);
00274                    return false;
00275           }
00276         }
00277   return true;
00278 }
00279 
00280 // --- cDvbTuner -------------------------------------------------------------
00281 
00282 #define TUNER_POLL_TIMEOUT  10 // ms
00283 
00284 class cDvbTuner : public cThread {
00285 private:
00286   static cMutex bondMutex;
00287   enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
00288   const cDvbDevice *device;
00289   int fd_frontend;
00290   int adapter, frontend;
00291   uint32_t subsystemId;
00292   int tuneTimeout;
00293   int lockTimeout;
00294   time_t lastTimeoutReport;
00295   cChannel channel;
00296   const cDiseqc *lastDiseqc;
00297   const cScr *scr;
00298   bool lnbPowerTurnedOn;
00299   eTunerStatus tunerStatus;
00300   cMutex mutex;
00301   cCondVar locked;
00302   cCondVar newSet;
00303   cDvbTuner *bondedTuner;
00304   bool bondedMaster;
00305   bool bondedMasterFailed;
00306   bool SetFrontendType(const cChannel *Channel);
00307   cString GetBondingParams(const cChannel *Channel = NULL) const;
00308   void ClearEventQueue(void) const;
00309   bool GetFrontendStatus(fe_status_t &Status) const;
00310   void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency);
00311   void ResetToneAndVoltage(void);
00312   bool SetFrontend(void);
00313   virtual void Action(void);
00314 public:
00315   cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
00316   virtual ~cDvbTuner();
00317   bool Bond(cDvbTuner *Tuner);
00318   void UnBond(void);
00319   bool BondingOk(const cChannel *Channel, bool ConsiderOccupied = false) const;
00320   cDvbTuner *GetBondedMaster(void);
00321   const cChannel *GetTransponder(void) const { return &channel; }
00322   uint32_t SubsystemId(void) const { return subsystemId; }
00323   bool IsTunedTo(const cChannel *Channel) const;
00324   void SetChannel(const cChannel *Channel);
00325   bool Locked(int TimeoutMs = 0);
00326   int GetSignalStrength(void) const;
00327   int GetSignalQuality(void) const;
00328   };
00329 
00330 cMutex cDvbTuner::bondMutex;
00331 
00332 cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend)
00333 {
00334   device = Device;
00335   fd_frontend = Fd_Frontend;
00336   adapter = Adapter;
00337   frontend = Frontend;
00338   subsystemId = cDvbDeviceProbe::GetSubsystemId(adapter, frontend);
00339   tuneTimeout = 0;
00340   lockTimeout = 0;
00341   lastTimeoutReport = 0;
00342   lastDiseqc = NULL;
00343   scr = NULL;
00344   lnbPowerTurnedOn = false;
00345   tunerStatus = tsIdle;
00346   bondedTuner = NULL;
00347   bondedMaster = false;
00348   bondedMasterFailed = false;
00349   SetDescription("tuner on frontend %d/%d", adapter, frontend);
00350   Start();
00351 }
00352 
00353 cDvbTuner::~cDvbTuner()
00354 {
00355   tunerStatus = tsIdle;
00356   newSet.Broadcast();
00357   locked.Broadcast();
00358   Cancel(3);
00359   UnBond();
00360   /* looks like this irritates the SCR switch, so let's leave it out for now
00361   if (lastDiseqc && lastDiseqc->IsScr()) {
00362      unsigned int Frequency = 0;
00363      ExecuteDiseqc(lastDiseqc, &Frequency);
00364      }
00365   */
00366 }
00367 
00368 bool cDvbTuner::Bond(cDvbTuner *Tuner)
00369 {
00370   cMutexLock MutexLock(&bondMutex);
00371   if (!bondedTuner) {
00372      ResetToneAndVoltage();
00373      bondedMaster = false; // makes sure we don't disturb an existing master
00374      bondedTuner = Tuner->bondedTuner ? Tuner->bondedTuner : Tuner;
00375      Tuner->bondedTuner = this;
00376      dsyslog("tuner %d/%d bonded with tuner %d/%d", adapter, frontend, bondedTuner->adapter, bondedTuner->frontend);
00377      return true;
00378      }
00379   else
00380      esyslog("ERROR: tuner %d/%d already bonded with tuner %d/%d, can't bond with tuner %d/%d", adapter, frontend, bondedTuner->adapter, bondedTuner->frontend, Tuner->adapter, Tuner->frontend);
00381   return false;
00382 }
00383 
00384 void cDvbTuner::UnBond(void)
00385 {
00386   cMutexLock MutexLock(&bondMutex);
00387   if (cDvbTuner *t = bondedTuner) {
00388      dsyslog("tuner %d/%d unbonded from tuner %d/%d", adapter, frontend, bondedTuner->adapter, bondedTuner->frontend);
00389      while (t->bondedTuner != this)
00390            t = t->bondedTuner;
00391      if (t == bondedTuner)
00392         t->bondedTuner = NULL;
00393      else
00394         t->bondedTuner = bondedTuner;
00395      bondedMaster = false; // another one will automatically become master whenever necessary
00396      bondedTuner = NULL;
00397      }
00398 }
00399 
00400 cString cDvbTuner::GetBondingParams(const cChannel *Channel) const
00401 {
00402   if (!Channel)
00403      Channel = &channel;
00404   cDvbTransponderParameters dtp(Channel->Parameters());
00405   if (Setup.DiSEqC) {
00406      if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization(), NULL))
00407         return diseqc->Commands();
00408      }
00409   else {
00410      bool ToneOff = Channel->Frequency() < (unsigned int)Setup.LnbSLOF;
00411      bool VoltOff = dtp.Polarization() == 'V' || dtp.Polarization() == 'R';
00412      return cString::sprintf("%c %c", ToneOff ? 't' : 'T', VoltOff ? 'v' : 'V');
00413      }
00414   return "";
00415 }
00416 
00417 bool cDvbTuner::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
00418 {
00419   cMutexLock MutexLock(&bondMutex);
00420   if (cDvbTuner *t = bondedTuner) {
00421      cString BondingParams = GetBondingParams(Channel);
00422      do {
00423         if (t->device->Priority() > IDLEPRIORITY || ConsiderOccupied && t->device->Occupied()) {
00424            if (strcmp(BondingParams, t->GetBondingParams()) != 0)
00425               return false;
00426            }
00427         t = t->bondedTuner;
00428         } while (t != bondedTuner);
00429      }
00430   return true;
00431 }
00432 
00433 cDvbTuner *cDvbTuner::GetBondedMaster(void)
00434 {
00435   if (!bondedTuner)
00436      return this; // an unbonded tuner is always "master"
00437   cMutexLock MutexLock(&bondMutex);
00438   if (bondedMaster) {
00439      if (!bondedMasterFailed)
00440         return this;
00441      else
00442         bondedMaster = false;
00443      }
00444   // This tuner is bonded, but it's not the master, so let's see if there is a master at all:
00445   if (cDvbTuner *t = bondedTuner) {
00446      while (t != this) {
00447            if (t->bondedMaster)
00448               return t;
00449            t = t->bondedTuner;
00450            }
00451      }
00452   // None of the other bonded tuners is master, so make this one the master:
00453   cDvbTuner *t = this;
00454   if (bondedMasterFailed) {
00455      // This one has failed, so switch to the next one:
00456      t = bondedTuner;
00457      t->bondedMasterFailed = false;
00458      cMutexLock MutexLock(&t->mutex);
00459      t->channel = channel;
00460      t->tunerStatus = tsSet;
00461      }
00462   t->bondedMaster = true;
00463   dsyslog("tuner %d/%d is now bonded master", t->adapter, t->frontend);
00464   return t;
00465 }
00466 
00467 bool cDvbTuner::IsTunedTo(const cChannel *Channel) const
00468 {
00469   if (tunerStatus == tsIdle)
00470      return false; // not tuned to
00471   if (channel.Source() != Channel->Source() || channel.Transponder() != Channel->Transponder())
00472      return false; // sufficient mismatch
00473   // Polarization is already checked as part of the Transponder.
00474   return strcmp(channel.Parameters(), Channel->Parameters()) == 0;
00475 }
00476 
00477 void cDvbTuner::SetChannel(const cChannel *Channel)
00478 {
00479   if (Channel) {
00480      if (bondedTuner) {
00481         cMutexLock MutexLock(&bondMutex);
00482         cDvbTuner *BondedMaster = GetBondedMaster();
00483         if (BondedMaster == this) {
00484            if (strcmp(GetBondingParams(Channel), GetBondingParams()) != 0) {
00485               // switching to a completely different band, so set all others to idle:
00486               for (cDvbTuner *t = bondedTuner; t && t != this; t = t->bondedTuner)
00487                   t->SetChannel(NULL);
00488               }
00489            }
00490         else if (strcmp(GetBondingParams(Channel), BondedMaster->GetBondingParams()) != 0)
00491            BondedMaster->SetChannel(Channel);
00492         }
00493      cMutexLock MutexLock(&mutex);
00494      if (!IsTunedTo(Channel))
00495         tunerStatus = tsSet;
00496      channel = *Channel;
00497      lastTimeoutReport = 0;
00498      newSet.Broadcast();
00499      }
00500   else {
00501      cMutexLock MutexLock(&mutex);
00502      tunerStatus = tsIdle;
00503      ResetToneAndVoltage();
00504      }
00505   if (bondedTuner && device->IsPrimaryDevice())
00506      cDevice::PrimaryDevice()->DelLivePids(); // 'device' is const, so we must do it this way
00507 }
00508 
00509 bool cDvbTuner::Locked(int TimeoutMs)
00510 {
00511   bool isLocked = (tunerStatus >= tsLocked);
00512   if (isLocked || !TimeoutMs)
00513      return isLocked;
00514 
00515   cMutexLock MutexLock(&mutex);
00516   if (TimeoutMs && tunerStatus < tsLocked)
00517      locked.TimedWait(mutex, TimeoutMs);
00518   return tunerStatus >= tsLocked;
00519 }
00520 
00521 void cDvbTuner::ClearEventQueue(void) const
00522 {
00523   cPoller Poller(fd_frontend);
00524   if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
00525      dvb_frontend_event Event;
00526      while (ioctl(fd_frontend, FE_GET_EVENT, &Event) == 0)
00527            ; // just to clear the event queue - we'll read the actual status below
00528      }
00529 }
00530 
00531 bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const
00532 {
00533   ClearEventQueue();
00534   while (1) {
00535         if (ioctl(fd_frontend, FE_READ_STATUS, &Status) != -1)
00536            return true;
00537         if (errno != EINTR)
00538            break;
00539         }
00540   return false;
00541 }
00542 
00543 //#define DEBUG_SIGNALSTRENGTH
00544 //#define DEBUG_SIGNALQUALITY
00545 
00546 int cDvbTuner::GetSignalStrength(void) const
00547 {
00548   ClearEventQueue();
00549   uint16_t Signal;
00550   while (1) {
00551         if (ioctl(fd_frontend, FE_READ_SIGNAL_STRENGTH, &Signal) != -1)
00552            break;
00553         if (errno != EINTR)
00554            return -1;
00555         }
00556   uint16_t MaxSignal = 0xFFFF; // Let's assume the default is using the entire range.
00557   // Use the subsystemId to identify individual devices in case they need
00558   // special treatment to map their Signal value into the range 0...0xFFFF.
00559   switch (subsystemId) {
00560     case 0x13C21019: MaxSignal = 670; break; // TT-budget S2-3200 (DVB-S/DVB-S2)
00561     }
00562   int s = int(Signal) * 100 / MaxSignal;
00563   if (s > 100)
00564      s = 100;
00565 #ifdef DEBUG_SIGNALSTRENGTH
00566   fprintf(stderr, "FE %d/%d: %08X S = %04X %04X %3d%%\n", adapter, frontend, subsystemId, MaxSignal, Signal, s);
00567 #endif
00568   return s;
00569 }
00570 
00571 #define LOCK_THRESHOLD 5 // indicates that all 5 FE_HAS_* flags are set
00572 
00573 int cDvbTuner::GetSignalQuality(void) const
00574 {
00575   fe_status_t Status;
00576   if (GetFrontendStatus(Status)) {
00577      // Actually one would expect these checks to be done from FE_HAS_SIGNAL to FE_HAS_LOCK, but some drivers (like the stb0899) are broken, so FE_HAS_LOCK is the only one that (hopefully) is generally reliable...
00578      if ((Status & FE_HAS_LOCK) == 0) {
00579         if ((Status & FE_HAS_SIGNAL) == 0)
00580            return 0;
00581         if ((Status & FE_HAS_CARRIER) == 0)
00582            return 1;
00583         if ((Status & FE_HAS_VITERBI) == 0)
00584            return 2;
00585         if ((Status & FE_HAS_SYNC) == 0)
00586            return 3;
00587         return 4;
00588         }
00589      bool HasSnr = true;
00590      uint16_t Snr;
00591      while (1) {
00592            if (ioctl(fd_frontend, FE_READ_SNR, &Snr) != -1)
00593               break;
00594            if (errno == EOPNOTSUPP) {
00595               Snr = 0xFFFF;
00596               HasSnr = false;
00597               break;
00598               }
00599            if (errno != EINTR)
00600               return -1;
00601            }
00602      bool HasBer = true;
00603      uint32_t Ber;
00604      while (1) {
00605            if (ioctl(fd_frontend, FE_READ_BER, &Ber) != -1)
00606               break;
00607            if (errno == EOPNOTSUPP) {
00608               Ber = 0;
00609               HasBer = false;
00610               break;
00611               }
00612            if (errno != EINTR)
00613               return -1;
00614            }
00615      bool HasUnc = true;
00616      uint32_t Unc;
00617      while (1) {
00618            if (ioctl(fd_frontend, FE_READ_UNCORRECTED_BLOCKS, &Unc) != -1)
00619               break;
00620            if (errno == EOPNOTSUPP) {
00621               Unc = 0;
00622               HasUnc = false;
00623               break;
00624               }
00625            if (errno != EINTR)
00626               return -1;
00627            }
00628      uint16_t MaxSnr = 0xFFFF; // Let's assume the default is using the entire range.
00629      // Use the subsystemId to identify individual devices in case they need
00630      // special treatment to map their Snr value into the range 0...0xFFFF.
00631      switch (subsystemId) {
00632        case 0x13C21019: MaxSnr = 200; break; // TT-budget S2-3200 (DVB-S/DVB-S2)
00633        }
00634      int a = int(Snr) * 100 / MaxSnr;
00635      int b = 100 - (Unc * 10 + (Ber / 256) * 5);
00636      if (b < 0)
00637         b = 0;
00638      int q = LOCK_THRESHOLD + a * b * (100 - LOCK_THRESHOLD) / 100 / 100;
00639      if (q > 100)
00640         q = 100;
00641 #ifdef DEBUG_SIGNALQUALITY
00642      fprintf(stderr, "FE %d/%d: %08X Q = %04X %04X %d %5d %5d %3d%%\n", adapter, frontend, subsystemId, MaxSnr, Snr, HasSnr, HasBer ? int(Ber) : -1, HasUnc ? int(Unc) : -1, q);
00643 #endif
00644      return q;
00645      }
00646   return -1;
00647 }
00648 
00649 static unsigned int FrequencyToHz(unsigned int f)
00650 {
00651   while (f && f < 1000000)
00652         f *= 1000;
00653   return f;
00654 }
00655 
00656 void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency)
00657 {
00658   if (!lnbPowerTurnedOn) {
00659      CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power
00660      lnbPowerTurnedOn = true;
00661      }
00662   static cMutex Mutex;
00663   if (Diseqc->IsScr())
00664      Mutex.Lock(); 
00665   struct dvb_diseqc_master_cmd cmd;
00666   const char *CurrentAction = NULL;
00667   for (;;) {
00668       cmd.msg_len = sizeof(cmd.msg);
00669       cDiseqc::eDiseqcActions da = Diseqc->Execute(&CurrentAction, cmd.msg, &cmd.msg_len, scr, Frequency);
00670       if (da == cDiseqc::daNone)
00671          break;
00672       switch (da) {
00673         case cDiseqc::daToneOff:   CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF)); break;
00674         case cDiseqc::daToneOn:    CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_ON)); break;
00675         case cDiseqc::daVoltage13: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); break;
00676         case cDiseqc::daVoltage18: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_18)); break;
00677         case cDiseqc::daMiniA:     CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_A)); break;
00678         case cDiseqc::daMiniB:     CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_B)); break;
00679         case cDiseqc::daCodes:     CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd)); break;
00680         default: esyslog("ERROR: unknown diseqc command %d", da);
00681         }
00682       }
00683   if (scr)
00684      ResetToneAndVoltage(); // makes sure we don't block the bus!
00685   if (Diseqc->IsScr())
00686      Mutex.Unlock(); 
00687 }
00688 
00689 void cDvbTuner::ResetToneAndVoltage(void)
00690 {
00691   CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13));
00692   CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF));
00693 }
00694 
00695 static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTransponderParameters *Dtp)
00696 {
00697   int ds = SYS_UNDEFINED;
00698   if (Channel->IsAtsc())
00699      ds = SYS_ATSC;
00700   else if (Channel->IsCable())
00701      ds = SYS_DVBC_ANNEX_AC;
00702   else if (Channel->IsSat())
00703      ds = Dtp->System() == DVB_SYSTEM_1 ? SYS_DVBS : SYS_DVBS2;
00704   else if (Channel->IsTerr())
00705      ds = Dtp->System() == DVB_SYSTEM_1 ? SYS_DVBT : SYS_DVBT2;
00706   else
00707      esyslog("ERROR: can't determine frontend type for channel %d", Channel->Number());
00708   return ds;
00709 }
00710 
00711 bool cDvbTuner::SetFrontend(void)
00712 {
00713 #define MAXFRONTENDCMDS 16
00714 #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
00715                        Frontend[CmdSeq.num].u.data = (d);\
00716                        if (CmdSeq.num++ > MAXFRONTENDCMDS) {\
00717                           esyslog("ERROR: too many tuning commands on frontend %d/%d", adapter, frontend);\
00718                           return false;\
00719                           }\
00720                      }
00721   dtv_property Frontend[MAXFRONTENDCMDS];
00722   memset(&Frontend, 0, sizeof(Frontend));
00723   dtv_properties CmdSeq;
00724   memset(&CmdSeq, 0, sizeof(CmdSeq));
00725   CmdSeq.props = Frontend;
00726   SETCMD(DTV_CLEAR, 0);
00727   if (ioctl(fd_frontend, FE_SET_PROPERTY, &CmdSeq) < 0) {
00728      esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
00729      return false;
00730      }
00731   CmdSeq.num = 0;
00732 
00733   cDvbTransponderParameters dtp(channel.Parameters());
00734 
00735   // Determine the required frontend type:
00736   int frontendType = GetRequiredDeliverySystem(&channel, &dtp);
00737   if (frontendType == SYS_UNDEFINED)
00738      return false;
00739 
00740   SETCMD(DTV_DELIVERY_SYSTEM, frontendType);
00741   if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) {
00742      unsigned int frequency = channel.Frequency();
00743      if (Setup.DiSEqC) {
00744         if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, channel.Source(), frequency, dtp.Polarization(), &scr)) {
00745            frequency -= diseqc->Lof();
00746            if (diseqc != lastDiseqc || diseqc->IsScr()) {
00747               if (GetBondedMaster() == this) {
00748                  ExecuteDiseqc(diseqc, &frequency);
00749                  if (frequency == 0)
00750                     return false;
00751                  }
00752               else
00753                  ResetToneAndVoltage();
00754               lastDiseqc = diseqc;
00755               }
00756            }
00757         else {
00758            esyslog("ERROR: no DiSEqC parameters found for channel %d", channel.Number());
00759            return false;
00760            }
00761         }
00762      else {
00763         int tone = SEC_TONE_OFF;
00764         if (frequency < (unsigned int)Setup.LnbSLOF) {
00765            frequency -= Setup.LnbFrequLo;
00766            tone = SEC_TONE_OFF;
00767            }
00768         else {
00769            frequency -= Setup.LnbFrequHi;
00770            tone = SEC_TONE_ON;
00771            }
00772         int volt = (dtp.Polarization() == 'V' || dtp.Polarization() == 'R') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18;
00773         if (GetBondedMaster() != this) {
00774            tone = SEC_TONE_OFF;
00775            volt = SEC_VOLTAGE_13;
00776            }
00777         CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, volt));
00778         CHECK(ioctl(fd_frontend, FE_SET_TONE, tone));
00779         }
00780      frequency = abs(frequency); // Allow for C-band, where the frequency is less than the LOF
00781 
00782      // DVB-S/DVB-S2 (common parts)
00783      SETCMD(DTV_FREQUENCY, frequency * 1000UL);
00784      SETCMD(DTV_MODULATION, dtp.Modulation());
00785      SETCMD(DTV_SYMBOL_RATE, channel.Srate() * 1000UL);
00786      SETCMD(DTV_INNER_FEC, dtp.CoderateH());
00787      SETCMD(DTV_INVERSION, dtp.Inversion());
00788      if (frontendType == SYS_DVBS2) {
00789         // DVB-S2
00790         SETCMD(DTV_PILOT, PILOT_AUTO);
00791         SETCMD(DTV_ROLLOFF, dtp.RollOff());
00792         }
00793      else {
00794         // DVB-S
00795         SETCMD(DTV_ROLLOFF, ROLLOFF_35); // DVB-S always has a ROLLOFF of 0.35
00796         }
00797 
00798      tuneTimeout = DVBS_TUNE_TIMEOUT;
00799      lockTimeout = DVBS_LOCK_TIMEOUT;
00800      }
00801   else if (frontendType == SYS_DVBC_ANNEX_AC || frontendType == SYS_DVBC_ANNEX_B) {
00802      // DVB-C
00803      SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency()));
00804      SETCMD(DTV_INVERSION, dtp.Inversion());
00805      SETCMD(DTV_SYMBOL_RATE, channel.Srate() * 1000UL);
00806      SETCMD(DTV_INNER_FEC, dtp.CoderateH());
00807      SETCMD(DTV_MODULATION, dtp.Modulation());
00808 
00809      tuneTimeout = DVBC_TUNE_TIMEOUT;
00810      lockTimeout = DVBC_LOCK_TIMEOUT;
00811      }
00812   else if (frontendType == SYS_DVBT || frontendType == SYS_DVBT2) {
00813      // DVB-T/DVB-T2 (common parts)
00814      SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency()));
00815      SETCMD(DTV_INVERSION, dtp.Inversion());
00816      SETCMD(DTV_BANDWIDTH_HZ, dtp.Bandwidth());
00817      SETCMD(DTV_CODE_RATE_HP, dtp.CoderateH());
00818      SETCMD(DTV_CODE_RATE_LP, dtp.CoderateL());
00819      SETCMD(DTV_MODULATION, dtp.Modulation());
00820      SETCMD(DTV_TRANSMISSION_MODE, dtp.Transmission());
00821      SETCMD(DTV_GUARD_INTERVAL, dtp.Guard());
00822      SETCMD(DTV_HIERARCHY, dtp.Hierarchy());
00823      if (frontendType == SYS_DVBT2) {
00824         // DVB-T2
00825         SETCMD(DTV_DVBT2_PLP_ID, dtp.PlpId());
00826         }
00827 
00828      tuneTimeout = DVBT_TUNE_TIMEOUT;
00829      lockTimeout = DVBT_LOCK_TIMEOUT;
00830      }
00831   else if (frontendType == SYS_ATSC) {
00832      // ATSC
00833      SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency()));
00834      SETCMD(DTV_INVERSION, dtp.Inversion());
00835      SETCMD(DTV_MODULATION, dtp.Modulation());
00836 
00837      tuneTimeout = ATSC_TUNE_TIMEOUT;
00838      lockTimeout = ATSC_LOCK_TIMEOUT;
00839      }
00840   else {
00841      esyslog("ERROR: attempt to set channel with unknown DVB frontend type");
00842      return false;
00843      }
00844   SETCMD(DTV_TUNE, 0);
00845   if (ioctl(fd_frontend, FE_SET_PROPERTY, &CmdSeq) < 0) {
00846      esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
00847      return false;
00848      }
00849   return true;
00850 }
00851 
00852 void cDvbTuner::Action(void)
00853 {
00854   cTimeMs Timer;
00855   bool LostLock = false;
00856   fe_status_t Status = (fe_status_t)0;
00857   while (Running()) {
00858         fe_status_t NewStatus;
00859         if (GetFrontendStatus(NewStatus))
00860            Status = NewStatus;
00861         cMutexLock MutexLock(&mutex);
00862         int WaitTime = 1000;
00863         switch (tunerStatus) {
00864           case tsIdle:
00865                break;
00866           case tsSet:
00867                tunerStatus = SetFrontend() ? tsTuned : tsIdle;
00868                Timer.Set(tuneTimeout + (scr ? rand() % SCR_RANDOM_TIMEOUT : 0));
00869                continue;
00870           case tsTuned:
00871                if (Timer.TimedOut()) {
00872                   tunerStatus = tsSet;
00873                   lastDiseqc = NULL;
00874                   if (time(NULL) - lastTimeoutReport > 60) { // let's not get too many of these
00875                      isyslog("frontend %d/%d timed out while tuning to channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder());
00876                      lastTimeoutReport = time(NULL);
00877                      }
00878                   cMutexLock MutexLock(&bondMutex);
00879                   if (bondedTuner && bondedMaster)
00880                      bondedMasterFailed = true; // give an other tuner a chance in case the sat cable was disconnected
00881                   continue;
00882                   }
00883                WaitTime = 100; // allows for a quick change from tsTuned to tsLocked
00884           case tsLocked:
00885                if (Status & FE_REINIT) {
00886                   tunerStatus = tsSet;
00887                   lastDiseqc = NULL;
00888                   isyslog("frontend %d/%d was reinitialized", adapter, frontend);
00889                   lastTimeoutReport = 0;
00890                   continue;
00891                   }
00892                else if (Status & FE_HAS_LOCK) {
00893                   if (LostLock) {
00894                      isyslog("frontend %d/%d regained lock on channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder());
00895                      LostLock = false;
00896                      }
00897                   tunerStatus = tsLocked;
00898                   locked.Broadcast();
00899                   lastTimeoutReport = 0;
00900                   }
00901                else if (tunerStatus == tsLocked) {
00902                   LostLock = true;
00903                   isyslog("frontend %d/%d lost lock on channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder());
00904                   tunerStatus = tsTuned;
00905                   Timer.Set(lockTimeout);
00906                   lastTimeoutReport = 0;
00907                   continue;
00908                   }
00909                break;
00910           default: esyslog("ERROR: unknown tuner status %d", tunerStatus);
00911           }
00912         newSet.TimedWait(mutex, WaitTime);
00913         }
00914 }
00915 
00916 // --- cDvbSourceParam -------------------------------------------------------
00917 
00918 class cDvbSourceParam : public cSourceParam {
00919 private:
00920   int param;
00921   int srate;
00922   cDvbTransponderParameters dtp;
00923 public:
00924   cDvbSourceParam(char Source, const char *Description);
00925   virtual void SetData(cChannel *Channel);
00926   virtual void GetData(cChannel *Channel);
00927   virtual cOsdItem *GetOsdItem(void);
00928   };
00929 
00930 cDvbSourceParam::cDvbSourceParam(char Source, const char *Description)
00931 :cSourceParam(Source, Description)
00932 {
00933   param = 0;
00934   srate = 0;
00935 }
00936 
00937 void cDvbSourceParam::SetData(cChannel *Channel)
00938 {
00939   srate = Channel->Srate();
00940   dtp.Parse(Channel->Parameters());
00941   param = 0;
00942 }
00943 
00944 void cDvbSourceParam::GetData(cChannel *Channel)
00945 {
00946   Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), srate, dtp.ToString(Source()), true);
00947 }
00948 
00949 cOsdItem *cDvbSourceParam::GetOsdItem(void)
00950 {
00951   char type = Source();
00952   const tDvbParameterMap *SystemValues = type == 'S' ? SystemValuesSat : SystemValuesTerr;
00953 #undef ST
00954 #define ST(s) if (strchr(s, type))
00955   switch (param++) {
00956     case  0: ST("  S ")  return new cMenuEditChrItem( tr("Polarization"), &dtp.polarization, "HVLR");             else return GetOsdItem();
00957     case  1: ST("  ST")  return new cMenuEditMapItem( tr("System"),       &dtp.system,       SystemValues);       else return GetOsdItem();
00958     case  2: ST(" CS ")  return new cMenuEditIntItem( tr("Srate"),        &srate);                                else return GetOsdItem();
00959     case  3: ST("ACST")  return new cMenuEditMapItem( tr("Inversion"),    &dtp.inversion,    InversionValues);    else return GetOsdItem();
00960     case  4: ST(" CST")  return new cMenuEditMapItem( tr("CoderateH"),    &dtp.coderateH,    CoderateValues);     else return GetOsdItem();
00961     case  5: ST("   T")  return new cMenuEditMapItem( tr("CoderateL"),    &dtp.coderateL,    CoderateValues);     else return GetOsdItem();
00962     case  6: ST("ACST")  return new cMenuEditMapItem( tr("Modulation"),   &dtp.modulation,   ModulationValues);   else return GetOsdItem();
00963     case  7: ST("   T")  return new cMenuEditMapItem( tr("Bandwidth"),    &dtp.bandwidth,    BandwidthValues);    else return GetOsdItem();
00964     case  8: ST("   T")  return new cMenuEditMapItem( tr("Transmission"), &dtp.transmission, TransmissionValues); else return GetOsdItem();
00965     case  9: ST("   T")  return new cMenuEditMapItem( tr("Guard"),        &dtp.guard,        GuardValues);        else return GetOsdItem();
00966     case 10: ST("   T")  return new cMenuEditMapItem( tr("Hierarchy"),    &dtp.hierarchy,    HierarchyValues);    else return GetOsdItem();
00967     case 11: ST("  S ")  return new cMenuEditMapItem( tr("Rolloff"),      &dtp.rollOff,      RollOffValues);      else return GetOsdItem();
00968     case 12: ST("   T")  return new cMenuEditIntItem( tr("PlpId"),        &dtp.plpId,        0, 255);             else return GetOsdItem();
00969     default: return NULL;
00970     }
00971   return NULL;
00972 }
00973 
00974 // --- cDvbDevice ------------------------------------------------------------
00975 
00976 int cDvbDevice::setTransferModeForDolbyDigital = 1;
00977 cMutex cDvbDevice::bondMutex;
00978 
00979 const char *DeliverySystemNames[] = {
00980   "UNDEFINED",
00981   "DVB-C",
00982   "DVB-C",
00983   "DVB-T",
00984   "DSS",
00985   "DVB-S",
00986   "DVB-S2",
00987   "DVB-H",
00988   "ISDBT",
00989   "ISDBS",
00990   "ISDBC",
00991   "ATSC",
00992   "ATSCMH",
00993   "DMBTH",
00994   "CMMB",
00995   "DAB",
00996   "DVB-T2",
00997   "TURBO",
00998   NULL
00999   };
01000 
01001 cDvbDevice::cDvbDevice(int Adapter, int Frontend)
01002 {
01003   adapter = Adapter;
01004   frontend = Frontend;
01005   ciAdapter = NULL;
01006   dvbTuner = NULL;
01007   numDeliverySystems = 0;
01008   numModulations = 0;
01009   bondedDevice = NULL;
01010   needsDetachBondedReceivers = false;
01011   tsBuffer = NULL;
01012 
01013   // Devices that are present on all card types:
01014 
01015   int fd_frontend = DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
01016 
01017   // Common Interface:
01018 
01019   fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
01020   if (fd_ca >= 0)
01021      ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
01022 
01023   // The DVR device (will be opened and closed as needed):
01024 
01025   fd_dvr = -1;
01026 
01027   // We only check the devices that must be present - the others will be checked before accessing them://XXX
01028 
01029   if (fd_frontend >= 0) {
01030      if (QueryDeliverySystems(fd_frontend))
01031         dvbTuner = new cDvbTuner(this, fd_frontend, adapter, frontend);
01032      }
01033   else
01034      esyslog("ERROR: can't open DVB device %d/%d", adapter, frontend);
01035 
01036   StartSectionHandler();
01037 }
01038 
01039 cDvbDevice::~cDvbDevice()
01040 {
01041   StopSectionHandler();
01042   delete dvbTuner;
01043   delete ciAdapter;
01044   UnBond();
01045   // We're not explicitly closing any device files here, since this sometimes
01046   // caused segfaults. Besides, the program is about to terminate anyway...
01047 }
01048 
01049 cString cDvbDevice::DvbName(const char *Name, int Adapter, int Frontend)
01050 {
01051   return cString::sprintf("%s/%s%d/%s%d", DEV_DVB_BASE, DEV_DVB_ADAPTER, Adapter, Name, Frontend);
01052 }
01053 
01054 int cDvbDevice::DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError)
01055 {
01056   cString FileName = DvbName(Name, Adapter, Frontend);
01057   int fd = open(FileName, Mode);
01058   if (fd < 0 && ReportError)
01059      LOG_ERROR_STR(*FileName);
01060   return fd;
01061 }
01062 
01063 bool cDvbDevice::Exists(int Adapter, int Frontend)
01064 {
01065   cString FileName = DvbName(DEV_DVB_FRONTEND, Adapter, Frontend);
01066   if (access(FileName, F_OK) == 0) {
01067      int f = open(FileName, O_RDONLY);
01068      if (f >= 0) {
01069         close(f);
01070         return true;
01071         }
01072      else if (errno != ENODEV && errno != EINVAL)
01073         LOG_ERROR_STR(*FileName);
01074      }
01075   else if (errno != ENOENT)
01076      LOG_ERROR_STR(*FileName);
01077   return false;
01078 }
01079 
01080 bool cDvbDevice::Probe(int Adapter, int Frontend)
01081 {
01082   cString FileName = DvbName(DEV_DVB_FRONTEND, Adapter, Frontend);
01083   dsyslog("probing %s", *FileName);
01084   for (cDvbDeviceProbe *dp = DvbDeviceProbes.First(); dp; dp = DvbDeviceProbes.Next(dp)) {
01085       if (dp->Probe(Adapter, Frontend))
01086          return true; // a plugin has created the actual device
01087       }
01088   dsyslog("creating cDvbDevice");
01089   new cDvbDevice(Adapter, Frontend); // it's a "budget" device
01090   return true;
01091 }
01092 
01093 cString cDvbDevice::DeviceName(void) const
01094 {
01095   return frontendInfo.name;
01096 }
01097 
01098 bool cDvbDevice::Initialize(void)
01099 {
01100   new cDvbSourceParam('A', "ATSC");
01101   new cDvbSourceParam('C', "DVB-C");
01102   new cDvbSourceParam('S', "DVB-S");
01103   new cDvbSourceParam('T', "DVB-T");
01104   cStringList Nodes;
01105   cReadDir DvbDir(DEV_DVB_BASE);
01106   if (DvbDir.Ok()) {
01107      struct dirent *a;
01108      while ((a = DvbDir.Next()) != NULL) {
01109            if (strstr(a->d_name, DEV_DVB_ADAPTER) == a->d_name) {
01110               int Adapter = strtol(a->d_name + strlen(DEV_DVB_ADAPTER), NULL, 10);
01111               cReadDir AdapterDir(AddDirectory(DEV_DVB_BASE, a->d_name));
01112               if (AdapterDir.Ok()) {
01113                  struct dirent *f;
01114                  while ((f = AdapterDir.Next()) != NULL) {
01115                        if (strstr(f->d_name, DEV_DVB_FRONTEND) == f->d_name) {
01116                           int Frontend = strtol(f->d_name + strlen(DEV_DVB_FRONTEND), NULL, 10);
01117                           Nodes.Append(strdup(cString::sprintf("%2d %2d", Adapter, Frontend)));
01118                           }
01119                        }
01120                  }
01121               }
01122            }
01123      }
01124   int Checked = 0;
01125   int Found = 0;
01126   if (Nodes.Size() > 0) {
01127      Nodes.Sort();
01128      for (int i = 0; i < Nodes.Size(); i++) {
01129          int Adapter;
01130          int Frontend;
01131          if (2 == sscanf(Nodes[i], "%d %d", &Adapter, &Frontend)) {
01132             if (Exists(Adapter, Frontend)) {
01133                if (Checked++ < MAXDVBDEVICES) {
01134                   if (UseDevice(NextCardIndex())) {
01135                      if (Probe(Adapter, Frontend))
01136                         Found++;
01137                      }
01138                   else
01139                      NextCardIndex(1); // skips this one
01140                   }
01141                }
01142             }
01143          }
01144      }
01145   NextCardIndex(MAXDVBDEVICES - Checked); // skips the rest
01146   if (Found > 0)
01147      isyslog("found %d DVB device%s", Found, Found > 1 ? "s" : "");
01148   else
01149      isyslog("no DVB device found");
01150   return Found > 0;
01151 }
01152 
01153 bool cDvbDevice::QueryDeliverySystems(int fd_frontend)
01154 {
01155   numDeliverySystems = 0;
01156   if (ioctl(fd_frontend, FE_GET_INFO, &frontendInfo) < 0) {
01157      LOG_ERROR;
01158      return false;
01159      }
01160 #if (DVB_API_VERSION << 8 | DVB_API_VERSION_MINOR) >= 0x0505
01161   dtv_property Frontend[1];
01162   memset(&Frontend, 0, sizeof(Frontend));
01163   dtv_properties CmdSeq;
01164   memset(&CmdSeq, 0, sizeof(CmdSeq));
01165   CmdSeq.props = Frontend;
01166   SETCMD(DTV_ENUM_DELSYS, 0);
01167   int Result = ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq);
01168   if (Result == 0) {
01169      for (uint i = 0; i < Frontend[0].u.buffer.len; i++) {
01170          if (numDeliverySystems >= MAXDELIVERYSYSTEMS) {
01171             esyslog("ERROR: too many delivery systems on frontend %d/%d", adapter, frontend);
01172             break;
01173             }
01174          deliverySystems[numDeliverySystems++] = Frontend[0].u.buffer.data[i];
01175          }
01176      }
01177   else {
01178      esyslog("ERROR: can't query delivery systems on frontend %d/%d - falling back to legacy mode", adapter, frontend);
01179 #else
01180      {
01181 #endif
01182      // Legacy mode (DVB-API < 5.5):
01183      switch (frontendInfo.type) {
01184        case FE_QPSK: deliverySystems[numDeliverySystems++] = SYS_DVBS;
01185                      if (frontendInfo.caps & FE_CAN_2G_MODULATION)
01186                         deliverySystems[numDeliverySystems++] = SYS_DVBS2;
01187                      break;
01188        case FE_OFDM: deliverySystems[numDeliverySystems++] = SYS_DVBT;
01189                      if (frontendInfo.caps & FE_CAN_2G_MODULATION)
01190                         deliverySystems[numDeliverySystems++] = SYS_DVBT2;
01191                      break;
01192        case FE_QAM:  deliverySystems[numDeliverySystems++] = SYS_DVBC_ANNEX_AC; break;
01193        case FE_ATSC: deliverySystems[numDeliverySystems++] = SYS_ATSC; break;
01194        default: esyslog("ERROR: unknown frontend type %d on frontend %d/%d", frontendInfo.type, adapter, frontend);
01195        }
01196      }
01197   if (numDeliverySystems > 0) {
01198      cString ds("");
01199      for (int i = 0; i < numDeliverySystems; i++)
01200          ds = cString::sprintf("%s%s%s", *ds, i ? "," : "", DeliverySystemNames[deliverySystems[i]]);
01201      cString ms("");
01202      if (frontendInfo.caps & FE_CAN_QPSK)      { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QPSK, ModulationValues)); }
01203      if (frontendInfo.caps & FE_CAN_QAM_16)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_16, ModulationValues)); }
01204      if (frontendInfo.caps & FE_CAN_QAM_32)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_32, ModulationValues)); }
01205      if (frontendInfo.caps & FE_CAN_QAM_64)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_64, ModulationValues)); }
01206      if (frontendInfo.caps & FE_CAN_QAM_128)   { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_128, ModulationValues)); }
01207      if (frontendInfo.caps & FE_CAN_QAM_256)   { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_256, ModulationValues)); }
01208      if (frontendInfo.caps & FE_CAN_8VSB)      { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(VSB_8, ModulationValues)); }
01209      if (frontendInfo.caps & FE_CAN_16VSB)     { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(VSB_16, ModulationValues)); }
01210      if (frontendInfo.caps & FE_CAN_TURBO_FEC) { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", "TURBO_FEC"); }
01211      if (!**ms)
01212         ms = "unknown modulations";
01213      isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, *ds, *ms, frontendInfo.name);
01214      return true;
01215      }
01216   else
01217      esyslog("ERROR: frontend %d/%d doesn't provide any delivery systems", adapter, frontend);
01218   return false;
01219 }
01220 
01221 bool cDvbDevice::Ready(void)
01222 {
01223   if (ciAdapter)
01224      return ciAdapter->Ready();
01225   return true;
01226 }
01227 
01228 bool cDvbDevice::BondDevices(const char *Bondings)
01229 {
01230   UnBondDevices();
01231   if (Bondings) {
01232      cSatCableNumbers SatCableNumbers(MAXDEVICES, Bondings);
01233      for (int i = 0; i < cDevice::NumDevices(); i++) {
01234          int d = SatCableNumbers.FirstDeviceIndex(i);
01235          if (d >= 0) {
01236             int ErrorDevice = 0;
01237             if (cDevice *Device1 = cDevice::GetDevice(i)) {
01238                if (cDevice *Device2 = cDevice::GetDevice(d)) {
01239                   if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
01240                      if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
01241                         if (!DvbDevice1->Bond(DvbDevice2))
01242                            return false; // Bond() has already logged the error
01243                         }
01244                      else
01245                         ErrorDevice = d + 1;
01246                      }
01247                   else
01248                      ErrorDevice = i + 1;
01249                   if (ErrorDevice) {
01250                      esyslog("ERROR: device '%d' in device bondings '%s' is not a cDvbDevice", ErrorDevice, Bondings);
01251                      return false;
01252                      }
01253                   }
01254                else
01255                   ErrorDevice = d + 1;
01256                }
01257             else
01258                ErrorDevice = i + 1;
01259             if (ErrorDevice) {
01260                esyslog("ERROR: unknown device '%d' in device bondings '%s'", ErrorDevice, Bondings);
01261                return false;
01262                }
01263             }
01264          }
01265      }
01266   return true;
01267 }
01268 
01269 void cDvbDevice::UnBondDevices(void)
01270 {
01271   for (int i = 0; i < cDevice::NumDevices(); i++) {
01272       if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
01273          d->UnBond();
01274       }
01275 }
01276 
01277 bool cDvbDevice::Bond(cDvbDevice *Device)
01278 {
01279   cMutexLock MutexLock(&bondMutex);
01280   if (!bondedDevice) {
01281      if (Device != this) {
01282         if ((ProvidesDeliverySystem(SYS_DVBS) || ProvidesDeliverySystem(SYS_DVBS2)) && (Device->ProvidesDeliverySystem(SYS_DVBS) || Device->ProvidesDeliverySystem(SYS_DVBS2))) {
01283            if (dvbTuner && Device->dvbTuner && dvbTuner->Bond(Device->dvbTuner)) {
01284               bondedDevice = Device->bondedDevice ? Device->bondedDevice : Device;
01285               Device->bondedDevice = this;
01286               dsyslog("device %d bonded with device %d", CardIndex() + 1, bondedDevice->CardIndex() + 1);
01287               return true;
01288               }
01289            }
01290         else
01291            esyslog("ERROR: can't bond device %d with device %d (only DVB-S(2) devices can be bonded)", CardIndex() + 1, Device->CardIndex() + 1);
01292         }
01293      else
01294         esyslog("ERROR: can't bond device %d with itself", CardIndex() + 1);
01295      }
01296   else
01297      esyslog("ERROR: device %d already bonded with device %d, can't bond with device %d", CardIndex() + 1, bondedDevice->CardIndex() + 1, Device->CardIndex() + 1);
01298   return false;
01299 }
01300 
01301 void cDvbDevice::UnBond(void)
01302 {
01303   cMutexLock MutexLock(&bondMutex);
01304   if (cDvbDevice *d = bondedDevice) {
01305      if (dvbTuner)
01306         dvbTuner->UnBond();
01307      dsyslog("device %d unbonded from device %d", CardIndex() + 1, bondedDevice->CardIndex() + 1);
01308      while (d->bondedDevice != this)
01309            d = d->bondedDevice;
01310      if (d == bondedDevice)
01311         d->bondedDevice = NULL;
01312      else
01313         d->bondedDevice = bondedDevice;
01314      bondedDevice = NULL;
01315      }
01316 }
01317 
01318 bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
01319 {
01320   cMutexLock MutexLock(&bondMutex);
01321   if (bondedDevice)
01322      return dvbTuner && dvbTuner->BondingOk(Channel, ConsiderOccupied);
01323   return true;
01324 }
01325 
01326 bool cDvbDevice::HasCi(void)
01327 {
01328   return ciAdapter;
01329 }
01330 
01331 bool cDvbDevice::SetPid(cPidHandle *Handle, int Type, bool On)
01332 {
01333   if (Handle->pid) {
01334      dmx_pes_filter_params pesFilterParams;
01335      memset(&pesFilterParams, 0, sizeof(pesFilterParams));
01336      if (On) {
01337         if (Handle->handle < 0) {
01338            Handle->handle = DvbOpen(DEV_DVB_DEMUX, adapter, frontend, O_RDWR | O_NONBLOCK, true);
01339            if (Handle->handle < 0) {
01340               LOG_ERROR;
01341               return false;
01342               }
01343            }
01344         pesFilterParams.pid     = Handle->pid;
01345         pesFilterParams.input   = DMX_IN_FRONTEND;
01346         pesFilterParams.output  = DMX_OUT_TS_TAP;
01347         pesFilterParams.pes_type= DMX_PES_OTHER;
01348         pesFilterParams.flags   = DMX_IMMEDIATE_START;
01349         if (ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams) < 0) {
01350            LOG_ERROR;
01351            return false;
01352            }
01353         }
01354      else if (!Handle->used) {
01355         CHECK(ioctl(Handle->handle, DMX_STOP));
01356         if (Type <= ptTeletext) {
01357            pesFilterParams.pid     = 0x1FFF;
01358            pesFilterParams.input   = DMX_IN_FRONTEND;
01359            pesFilterParams.output  = DMX_OUT_DECODER;
01360            pesFilterParams.pes_type= DMX_PES_OTHER;
01361            pesFilterParams.flags   = DMX_IMMEDIATE_START;
01362            CHECK(ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams));
01363            }
01364         close(Handle->handle);
01365         Handle->handle = -1;
01366         }
01367      }
01368   return true;
01369 }
01370 
01371 int cDvbDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
01372 {
01373   cString FileName = DvbName(DEV_DVB_DEMUX, adapter, frontend);
01374   int f = open(FileName, O_RDWR | O_NONBLOCK);
01375   if (f >= 0) {
01376      dmx_sct_filter_params sctFilterParams;
01377      memset(&sctFilterParams, 0, sizeof(sctFilterParams));
01378      sctFilterParams.pid = Pid;
01379      sctFilterParams.timeout = 0;
01380      sctFilterParams.flags = DMX_IMMEDIATE_START;
01381      sctFilterParams.filter.filter[0] = Tid;
01382      sctFilterParams.filter.mask[0] = Mask;
01383      if (ioctl(f, DMX_SET_FILTER, &sctFilterParams) >= 0)
01384         return f;
01385      else {
01386         esyslog("ERROR: can't set filter (pid=%d, tid=%02X, mask=%02X): %m", Pid, Tid, Mask);
01387         close(f);
01388         }
01389      }
01390   else
01391      esyslog("ERROR: can't open filter handle on '%s'", *FileName);
01392   return -1;
01393 }
01394 
01395 void cDvbDevice::CloseFilter(int Handle)
01396 {
01397   close(Handle);
01398 }
01399 
01400 bool cDvbDevice::ProvidesDeliverySystem(int DeliverySystem) const
01401 {
01402   for (int i = 0; i < numDeliverySystems; i++) {
01403       if (deliverySystems[i] == DeliverySystem)
01404          return true;
01405       }
01406   return false;
01407 }
01408 
01409 bool cDvbDevice::ProvidesSource(int Source) const
01410 {
01411   int type = Source & cSource::st_Mask;
01412   return type == cSource::stNone
01413       || type == cSource::stAtsc  && ProvidesDeliverySystem(SYS_ATSC)
01414       || type == cSource::stCable && (ProvidesDeliverySystem(SYS_DVBC_ANNEX_AC) || ProvidesDeliverySystem(SYS_DVBC_ANNEX_B))
01415       || type == cSource::stSat   && (ProvidesDeliverySystem(SYS_DVBS) || ProvidesDeliverySystem(SYS_DVBS2))
01416       || type == cSource::stTerr  && (ProvidesDeliverySystem(SYS_DVBT) || ProvidesDeliverySystem(SYS_DVBT2));
01417 }
01418 
01419 bool cDvbDevice::ProvidesTransponder(const cChannel *Channel) const
01420 {
01421   if (!ProvidesSource(Channel->Source()))
01422      return false; // doesn't provide source
01423   cDvbTransponderParameters dtp(Channel->Parameters());
01424   if (!ProvidesDeliverySystem(GetRequiredDeliverySystem(Channel, &dtp)) ||
01425      dtp.Modulation() == QPSK     && !(frontendInfo.caps & FE_CAN_QPSK) ||
01426      dtp.Modulation() == QAM_16   && !(frontendInfo.caps & FE_CAN_QAM_16) ||
01427      dtp.Modulation() == QAM_32   && !(frontendInfo.caps & FE_CAN_QAM_32) ||
01428      dtp.Modulation() == QAM_64   && !(frontendInfo.caps & FE_CAN_QAM_64) ||
01429      dtp.Modulation() == QAM_128  && !(frontendInfo.caps & FE_CAN_QAM_128) ||
01430      dtp.Modulation() == QAM_256  && !(frontendInfo.caps & FE_CAN_QAM_256) ||
01431      dtp.Modulation() == QAM_AUTO && !(frontendInfo.caps & FE_CAN_QAM_AUTO) ||
01432      dtp.Modulation() == VSB_8    && !(frontendInfo.caps & FE_CAN_8VSB) ||
01433      dtp.Modulation() == VSB_16   && !(frontendInfo.caps & FE_CAN_16VSB) ||
01434      dtp.Modulation() == PSK_8    && !(frontendInfo.caps & FE_CAN_TURBO_FEC) && dtp.System() == SYS_DVBS) // "turbo fec" is a non standard FEC used by North American broadcasters - this is a best guess to determine this condition
01435      return false; // requires modulation system which frontend doesn't provide
01436   if (!cSource::IsSat(Channel->Source()) ||
01437      (!Setup.DiSEqC || Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization(), NULL)))
01438      return DeviceHooksProvidesTransponder(Channel);
01439   return false;
01440 }
01441 
01442 bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
01443 {
01444   bool result = false;
01445   bool hasPriority = Priority == IDLEPRIORITY || Priority > this->Priority();
01446   bool needsDetachReceivers = false;
01447   needsDetachBondedReceivers = false;
01448 
01449   if (dvbTuner && ProvidesTransponder(Channel)) {
01450      result = hasPriority;
01451      if (Priority > IDLEPRIORITY) {
01452         if (Receiving()) {
01453            if (dvbTuner->IsTunedTo(Channel)) {
01454               if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0)) || Channel->Dpid(0) && !HasPid(Channel->Dpid(0))) {
01455                  if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) {
01456                     if (CamSlot()->CanDecrypt(Channel))
01457                        result = true;
01458                     else
01459                        needsDetachReceivers = true;
01460                     }
01461                  else
01462                     result = true;
01463                  }
01464               else
01465                  result = true;
01466               }
01467            else
01468               needsDetachReceivers = Receiving();
01469            }
01470         if (result) {
01471            if (!BondingOk(Channel)) {
01472               // This device is bonded, so we need to check the priorities of the others:
01473               for (cDvbDevice *d = bondedDevice; d && d != this; d = d->bondedDevice) {
01474                   if (d->Priority() >= Priority) {
01475                      result = false;
01476                      break;
01477                      }
01478                   }
01479               needsDetachBondedReceivers = true;
01480               needsDetachReceivers = Receiving();
01481               }
01482            }
01483         }
01484      }
01485   if (NeedsDetachReceivers)
01486      *NeedsDetachReceivers = needsDetachReceivers;
01487   return result;
01488 }
01489 
01490 bool cDvbDevice::ProvidesEIT(void) const
01491 {
01492   return dvbTuner != NULL;
01493 }
01494 
01495 int cDvbDevice::NumProvidedSystems(void) const
01496 {
01497   return numDeliverySystems + numModulations;
01498 }
01499 
01500 int cDvbDevice::SignalStrength(void) const
01501 {
01502   return dvbTuner ? dvbTuner->GetSignalStrength() : -1;
01503 }
01504 
01505 int cDvbDevice::SignalQuality(void) const
01506 {
01507   return dvbTuner ? dvbTuner->GetSignalQuality() : -1;
01508 }
01509 
01510 const cChannel *cDvbDevice::GetCurrentlyTunedTransponder(void) const
01511 {
01512   return dvbTuner ? dvbTuner->GetTransponder() : NULL;
01513 }
01514 
01515 bool cDvbDevice::IsTunedToTransponder(const cChannel *Channel) const
01516 {
01517   return dvbTuner ? dvbTuner->IsTunedTo(Channel) : false;
01518 }
01519 
01520 bool cDvbDevice::MaySwitchTransponder(const cChannel *Channel) const
01521 {
01522   return BondingOk(Channel, true) && cDevice::MaySwitchTransponder(Channel);
01523 }
01524 
01525 bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
01526 {
01527   if (dvbTuner)
01528      dvbTuner->SetChannel(Channel);
01529   return true;
01530 }
01531 
01532 bool cDvbDevice::HasLock(int TimeoutMs)
01533 {
01534   return dvbTuner ? dvbTuner->Locked(TimeoutMs) : false;
01535 }
01536 
01537 void cDvbDevice::SetTransferModeForDolbyDigital(int Mode)
01538 {
01539   setTransferModeForDolbyDigital = Mode;
01540 }
01541 
01542 bool cDvbDevice::OpenDvr(void)
01543 {
01544   CloseDvr();
01545   fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
01546   if (fd_dvr >= 0)
01547      tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(2), CardIndex() + 1);
01548   return fd_dvr >= 0;
01549 }
01550 
01551 void cDvbDevice::CloseDvr(void)
01552 {
01553   if (fd_dvr >= 0) {
01554      delete tsBuffer;
01555      tsBuffer = NULL;
01556      close(fd_dvr);
01557      fd_dvr = -1;
01558      }
01559 }
01560 
01561 bool cDvbDevice::GetTSPacket(uchar *&Data)
01562 {
01563   if (tsBuffer) {
01564      Data = tsBuffer->Get();
01565      return true;
01566      }
01567   return false;
01568 }
01569 
01570 void cDvbDevice::DetachAllReceivers(void)
01571 {
01572   cMutexLock MutexLock(&bondMutex);
01573   cDvbDevice *d = this;
01574   do {
01575      d->cDevice::DetachAllReceivers();
01576      d = d->bondedDevice;
01577      } while (d && d != this && needsDetachBondedReceivers);
01578   needsDetachBondedReceivers = false;
01579 }
01580 
01581 // --- cDvbDeviceProbe -------------------------------------------------------
01582 
01583 cList<cDvbDeviceProbe> DvbDeviceProbes;
01584 
01585 cDvbDeviceProbe::cDvbDeviceProbe(void)
01586 {
01587   DvbDeviceProbes.Add(this);
01588 }
01589 
01590 cDvbDeviceProbe::~cDvbDeviceProbe()
01591 {
01592   DvbDeviceProbes.Del(this, false);
01593 }
01594 
01595 uint32_t cDvbDeviceProbe::GetSubsystemId(int Adapter, int Frontend)
01596 {
01597   uint32_t SubsystemId = 0;
01598   cString FileName = cString::sprintf("/dev/dvb/adapter%d/frontend%d", Adapter, Frontend);
01599   struct stat st;
01600   if (stat(FileName, &st) == 0) {
01601      cReadDir d("/sys/class/dvb");
01602      if (d.Ok()) {
01603         struct dirent *e;
01604         while ((e = d.Next()) != NULL) {
01605               if (strstr(e->d_name, "frontend")) {
01606                  FileName = cString::sprintf("/sys/class/dvb/%s/dev", e->d_name);
01607                  if (FILE *f = fopen(FileName, "r")) {
01608                     cReadLine ReadLine;
01609                     char *s = ReadLine.Read(f);
01610                     fclose(f);
01611                     unsigned Major;
01612                     unsigned Minor;
01613                     if (s && 2 == sscanf(s, "%u:%u", &Major, &Minor)) {
01614                        if (((Major << 8) | Minor) == st.st_rdev) {
01615                           FileName = cString::sprintf("/sys/class/dvb/%s/device/subsystem_vendor", e->d_name);
01616                           if ((f = fopen(FileName, "r")) != NULL) {
01617                              if (char *s = ReadLine.Read(f))
01618                                 SubsystemId = strtoul(s, NULL, 0) << 16;
01619                              fclose(f);
01620                              }
01621                           FileName = cString::sprintf("/sys/class/dvb/%s/device/subsystem_device", e->d_name);
01622                           if ((f = fopen(FileName, "r")) != NULL) {
01623                              if (char *s = ReadLine.Read(f))
01624                                 SubsystemId |= strtoul(s, NULL, 0);
01625                              fclose(f);
01626                              }
01627                           break;
01628                           }
01629                        }
01630                     }
01631                  }
01632               }
01633         }
01634      }
01635   return SubsystemId;
01636 }