Merge branch 'bug_474_fix_multicontent_clear'
[enigma2.git] / lib / dvb / dvbtime.cpp
index bc9d3b7e18d54165e7ca96aaa70c9d4bbc16c551..26b6767cf10862466e8bc2ed345bc18068341c8e 100644 (file)
 #define FP_IOCTL_SET_RTC         0x101
 #define FP_IOCTL_GET_RTC         0x102
 
+#define TIME_UPDATE_INTERVAL (30*60*1000)
+
 static time_t prev_time;
 
 void setRTC(time_t time)
 {
-       int fd = open("/dev/dbox/fp0", O_RDWR);
-       if ( fd >= 0 )
+       FILE *f = fopen("/proc/stb/fp/rtc", "w");
+       if (f)
        {
-               if ( ::ioctl(fd, FP_IOCTL_SET_RTC, (void*)&time ) < 0 )
-                       eDebug("FP_IOCTL_SET_RTC failed(%m)");
-               else
+               if (fprintf(f, "%u", (unsigned int)time))
                        prev_time = time;
-               close(fd);
+               else
+                       eDebug("write /proc/stb/fp/rtc failed (%m)");
+               fclose(f);
+       }
+       else
+       {
+               int fd = open("/dev/dbox/fp0", O_RDWR);
+               if ( fd >= 0 )
+               {
+                       if ( ::ioctl(fd, FP_IOCTL_SET_RTC, (void*)&time ) < 0 )
+                               eDebug("FP_IOCTL_SET_RTC failed(%m)");
+                       else
+                               prev_time = time;
+                       close(fd);
+               }
        }
 }
 
 time_t getRTC()
 {
        time_t rtc_time=0;
-       int fd = open("/dev/dbox/fp0", O_RDWR);
-       if ( fd >= 0 )
+       FILE *f = fopen("/proc/stb/fp/rtc", "r");
+       if (f)
        {
-               if ( ::ioctl(fd, FP_IOCTL_GET_RTC, (void*)&rtc_time ) < 0 )
-                       eDebug("FP_IOCTL_GET_RTC failed(%m)");
-               close(fd);
+               // sanity check to detect corrupt atmel firmware
+               unsigned int tmp;
+               if (fscanf(f, "%u", &tmp) != 1)
+                       eDebug("read /proc/stb/fp/rtc failed (%m)");
+               else
+                       rtc_time=tmp;
+               fclose(f);
+       }
+       else
+       {
+               int fd = open("/dev/dbox/fp0", O_RDWR);
+               if ( fd >= 0 )
+               {
+                       if ( ::ioctl(fd, FP_IOCTL_GET_RTC, (void*)&rtc_time ) < 0 )
+                               eDebug("FP_IOCTL_GET_RTC failed(%m)");
+                       close(fd);
+               }
        }
        return rtc_time != prev_time ? rtc_time : 0;
 }
 
-time_t parseDVBtime(__u8 t1, __u8 t2, __u8 t3, __u8 t4, __u8 t5)
+time_t parseDVBtime(__u8 t1, __u8 t2, __u8 t3, __u8 t4, __u8 t5, __u16 *hash)
 {
        tm t;
        t.tm_sec=fromBCD(t5);
@@ -59,32 +87,38 @@ time_t parseDVBtime(__u8 t1, __u8 t2, __u8 t3, __u8 t4, __u8 t5)
        t.tm_isdst =  0;
        t.tm_gmtoff = 0;
 
+       if (hash) {
+               *hash = t.tm_hour * 60 + t.tm_min;
+               *hash |= t.tm_mday << 11;
+       }
+
        return timegm(&t);
 }
 
-TDT::TDT(eDVBChannel *chan)
-       :chan(chan)
+TDT::TDT(eDVBChannel *chan, int update_count)
+       :chan(chan), m_interval_timer(eTimer::create()), update_count(update_count)
 {
        CONNECT(tableReady, TDT::ready);
-       CONNECT(m_interval_timer.timeout, TDT::start);
+       CONNECT(m_interval_timer->timeout, TDT::start);
        if (chan)
                chan->getDemux(demux, 0);
 }
 
 void TDT::ready(int error)
 {
-       eDVBLocalTimeHandler::getInstance()->updateTime(error, chan);
+       eDVBLocalTimeHandler::getInstance()->updateTime(error, chan, ++update_count);
 }
 
-int TDT::createTable(int nr, const __u8 *data, unsigned int max)
+int TDT::createTable(unsigned int nr, const __u8 *data, unsigned int max)
 {
-       if ( data && data[0] == 0x70 || data[0] == 0x73 )
+       if ( data && (data[0] == 0x70 || data[0] == 0x73 ))
        {
                int length = ((data[1] & 0x0F) << 8) | data[2];
                if ( length >= 5 )
                {
                        time_t tptime = parseDVBtime(data[3], data[4], data[5], data[6], data[7]);
-                       eDVBLocalTimeHandler::getInstance()->updateTime(tptime, chan);
+                       if (tptime && tptime != -1)
+                               eDVBLocalTimeHandler::getInstance()->updateTime(tptime, chan, update_count);
                        error=0;
                        return 1;
                }
@@ -112,14 +146,14 @@ void TDT::start()
 
 void TDT::startTimer( int interval )
 {
-       m_interval_timer.start(interval, true);
+       m_interval_timer->start(interval, true);
 }
 
 eDVBLocalTimeHandler *eDVBLocalTimeHandler::instance;
 DEFINE_REF(eDVBLocalTimeHandler);
 
 eDVBLocalTimeHandler::eDVBLocalTimeHandler()
-       :m_time_ready(false), m_time_difference(0)
+       :m_use_dvb_time(false), m_updateNonTunedTimer(eTimer::create(eApp)), m_time_ready(false)
 {
        if ( !instance )
                instance=this;
@@ -128,14 +162,29 @@ eDVBLocalTimeHandler::eDVBLocalTimeHandler()
        if (!res_mgr)
                eDebug("[eDVBLocalTimerHandler] no resource manager !!!!!!!");
        else
+       {
                res_mgr->connectChannelAdded(slot(*this,&eDVBLocalTimeHandler::DVBChannelAdded), m_chanAddedConn);
+               time_t now = time(0);
+               if ( now < 1072224000 ) // 01.01.2004
+                       eDebug("RTC not ready... wait for transponder time");
+               else // inform all who's waiting for valid system time..
+               {
+                       eDebug("Use valid Linux Time :) (RTC?)");
+                       m_time_ready = true;
+                       /*emit*/ m_timeUpdated();
+               }
+       }
+       CONNECT(m_updateNonTunedTimer->timeout, eDVBLocalTimeHandler::updateNonTuned);
 }
 
 eDVBLocalTimeHandler::~eDVBLocalTimeHandler()
 {
        instance=0;
-       for (std::map<iDVBChannel*, channel_data>::iterator it=m_knownChannels.begin(); it != m_knownChannels.end(); ++it)
-               delete it->second.tdt;
+       if (ready())
+       {
+               eDebug("set RTC to previous valid time");
+               setRTC(::time(0));
+       }
 }
 
 void eDVBLocalTimeHandler::readTimeOffsetData( const char* filename )
@@ -173,10 +222,44 @@ void eDVBLocalTimeHandler::writeTimeOffsetData( const char* filename )
        }
 }
 
-void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan )
+void eDVBLocalTimeHandler::setUseDVBTime(bool b)
 {
+       if (m_use_dvb_time != b) {
+               if (m_use_dvb_time) {
+                       eDebug("[eDVBLocalTimeHandler] disable sync local time with transponder time!");
+                       std::map<iDVBChannel*, channel_data>::iterator it =
+                               m_knownChannels.begin();
+                       for (; it != m_knownChannels.end(); ++it) {
+                               if (it->second.m_prevChannelState == iDVBChannel::state_ok)
+                                       it->second.tdt = 0;
+                       }
+               }
+               else {
+                       eDebug("[eDVBLocalTimeHandler] enable sync local time with transponder time!");
+                       std::map<iDVBChannel*, channel_data>::iterator it =
+                               m_knownChannels.begin();
+                       for (; it != m_knownChannels.end(); ++it) {
+                               if (it->second.m_prevChannelState == iDVBChannel::state_ok) {
+                                       it->second.tdt = new TDT(it->second.channel);
+                                       it->second.tdt->start();
+                               }
+                       }
+               }
+               m_use_dvb_time = b;
+       }
+}
+
+void eDVBLocalTimeHandler::updateNonTuned()
+{
+       updateTime(-1, 0, 0);
+       m_updateNonTunedTimer->start(TIME_UPDATE_INTERVAL, true);
+}
+
+void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan, int update_count )
+{
+       int time_difference;
        bool restart_tdt = false;
-       if (!tp_time )
+       if (!tp_time)
                restart_tdt = true;
        else if (tp_time == -1)
        {
@@ -189,33 +272,29 @@ void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan )
                        time_t rtc_time = getRTC();
                        if ( rtc_time ) // RTC Ready?
                        {
-                               tm now = *localtime(&rtc_time);
+                               tm now;
+                               localtime_r(&rtc_time, &now);
                                eDebug("[eDVBLocalTimerHandler] RTC time is %02d:%02d:%02d",
                                        now.tm_hour,
                                        now.tm_min,
                                        now.tm_sec);
                                time_t linuxTime=time(0);
-                               time_t nowTime=linuxTime+m_time_difference;
-                               now = *localtime(&nowTime);
+                               localtime_r(&linuxTime, &now);
                                eDebug("[eDVBLocalTimerHandler] Receiver time is %02d:%02d:%02d",
                                        now.tm_hour,
                                        now.tm_min,
                                        now.tm_sec);
-                               m_time_difference = rtc_time - linuxTime;
-                               eDebug("[eDVBLocalTimerHandler] RTC to Receiver time difference is %d seconds", nowTime - rtc_time );
-                               if ( abs(m_time_difference) > 59 )
+                               time_difference = rtc_time - linuxTime;
+                               eDebug("[eDVBLocalTimerHandler] RTC to Receiver time difference is %ld seconds", linuxTime - rtc_time );
+                               if ( time_difference )
                                {
                                        eDebug("[eDVBLocalTimerHandler] set Linux Time to RTC Time");
                                        timeval tnow;
                                        gettimeofday(&tnow,0);
                                        tnow.tv_sec=rtc_time;
                                        settimeofday(&tnow,0);
-                                       for (ePtrList<eMainloop>::iterator it(eMainloop::existing_loops)
-                                               ;it != eMainloop::existing_loops.end(); ++it)
-                                               it->addTimeOffset(m_time_difference);
-                                       m_time_difference=0;
                                }
-                               else if ( !m_time_difference )
+                               else if ( !time_difference )
                                        eDebug("[eDVBLocalTimerHandler] no change needed");
                                else
                                        eDebug("[eDVBLocalTimerHandler] set to RTC time");
@@ -232,14 +311,13 @@ void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan )
  // current linux time
                time_t linuxTime = time(0);
 
- // current enigma time
-               time_t nowTime=linuxTime+m_time_difference;
-
        // difference between current enigma time and transponder time
-               int enigma_diff = tp_time-nowTime;
+               int enigma_diff = tp_time-linuxTime;
 
                int new_diff=0;
 
+               bool updated = m_time_ready;
+
                if ( m_time_ready )  // ref time ready?
                {
                        // difference between reference time (current enigma time)
@@ -255,20 +333,19 @@ void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan )
                        {
                                eDebug("[eDVBLocalTimerHandler] we have correction %d", it->second);
                                time_t CorrectedTpTime = tp_time+it->second;
-                               int ddiff = CorrectedTpTime-nowTime;
+                               int ddiff = CorrectedTpTime-linuxTime;
                                eDebug("[eDVBLocalTimerHandler] diff after add correction is %d", ddiff);
                                if ( abs(it->second) < 300 ) // stored correction < 5 min
                                {
                                        eDebug("[eDVBLocalTimerHandler] use stored correction(<5 min)");
                                        new_diff = ddiff;
                                }
-                               else if ( /*eSystemInfo::getInstance()->getHwType() == eSystemInfo::DM7020 &&  TODO !!!*/
-                                               getRTC() )
+                               else if ( getRTC() )
                                {
                                        time_t rtc=getRTC();
                                        m_timeOffsetMap[chan->getChannelID()] = rtc-tp_time;
-                                       new_diff = rtc-nowTime;  // set enigma time to rtc
-                                       eDebug("[eDVBLocalTimerHandler] update stored correction to %d (calced against RTC time)", rtc-tp_time );
+                                       new_diff = rtc-linuxTime;  // set enigma time to rtc
+                                       eDebug("[eDVBLocalTimerHandler] update stored correction to %ld (calced against RTC time)", rtc-tp_time );
                                }
                                else if ( abs(ddiff) <= 120 )
                                {
@@ -303,38 +380,53 @@ void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan )
                        m_time_ready=true;
                }
 
-               time_t t = nowTime+new_diff;
+               time_t t = linuxTime+new_diff;
                m_last_tp_time_difference=tp_time-t;
 
-               if (!new_diff)
+               if (!new_diff &&
+                       updated) // overrride this check on first received TDT
                {
                        eDebug("[eDVBLocalTimerHandler] not changed");
                        return;
                }
 
-               tm now = *localtime(&t);
+               if ( !update_count )
+               {
+                       // set rtc to calced transponder time when the first tdt is received on this
+                       // transponder
+                       setRTC(t);
+                       eDebug("[eDVBLocalTimerHandler] update RTC");
+               }
+               else if (getRTC())
+               {
+                       if (abs(getRTC() - t) > 60)
+                       {
+                               eDebug("[eDVBLocalTimerHandler] difference between new linux time and RTC time is > 60 sec... transponder time looks not ok... use rtc time");
+                               t = getRTC();
+                       }
+                       else
+                               eDebug("[eDVBLocalTimerHandler] difference between linux time and RTC time is < 60 sec... so the transponder time looks ok");
+               }
+               else
+                       eDebug("[eDVBLocalTimerHandler] no RTC available :(");
+
+               tm now;
+               localtime_r(&t, &now);
                eDebug("[eDVBLocalTimerHandler] time update to %02d:%02d:%02d",
                        now.tm_hour,
                        now.tm_min,
                        now.tm_sec);
 
-               m_time_difference = t - linuxTime;   // calc our new linux_time -> enigma_time correction
-               eDebug("[eDVBLocalTimerHandler] m_time_difference is %d", m_time_difference );
-
-//             if ( eSystemInfo::getInstance()->getHwType() == eSystemInfo::DM7020 )  TODO !!
-                       setRTC(t);
+               time_difference = t - linuxTime;   // calc our new linux_time -> enigma_time correction
+               eDebug("[eDVBLocalTimerHandler] m_time_difference is %d", time_difference );
 
-               if ( abs(m_time_difference) > 59 )
+               if ( time_difference )
                {
                        eDebug("[eDVBLocalTimerHandler] set Linux Time");
                        timeval tnow;
                        gettimeofday(&tnow,0);
                        tnow.tv_sec=t;
                        settimeofday(&tnow,0);
-                       for (ePtrList<eMainloop>::iterator it(eMainloop::existing_loops)
-                               ;it != eMainloop::existing_loops.end(); ++it)
-                               it->addTimeOffset(m_time_difference);
-                       m_time_difference=0;
                }
 
                 /*emit*/ m_timeUpdated();
@@ -346,9 +438,10 @@ void eDVBLocalTimeHandler::updateTime( time_t tp_time, eDVBChannel *chan )
                        m_knownChannels.find(chan);
                if ( it != m_knownChannels.end() )
                {
-                       delete it->second.tdt;
-                       it->second.tdt = new TDT(chan);
-                       it->second.tdt->startTimer(60*60*1000);  // restart TDT for this transponder in 60min
+                       int updateCount = it->second.tdt->getUpdateCount();
+                       it->second.tdt = 0;
+                       it->second.tdt = new TDT(chan, updateCount);
+                       it->second.tdt->startTimer(TIME_UPDATE_INTERVAL);  // restart TDT for this transponder in 30min
                }
        }
 }
@@ -357,11 +450,12 @@ void eDVBLocalTimeHandler::DVBChannelAdded(eDVBChannel *chan)
 {
        if ( chan )
        {
-               eDebug("[eDVBLocalTimerHandler] add channel %p", chan);
+//             eDebug("[eDVBLocalTimerHandler] add channel %p", chan);
                std::pair<std::map<iDVBChannel*, channel_data>::iterator, bool> tmp =
                        m_knownChannels.insert( std::pair<iDVBChannel*, channel_data>(chan, channel_data()) );
-               tmp.first->second.tdt = new TDT(chan);
+               tmp.first->second.tdt = NULL;
                tmp.first->second.channel = chan;
+               tmp.first->second.m_prevChannelState = -1;
                chan->connectStateChange(slot(*this, &eDVBLocalTimeHandler::DVBChannelStateChanged), tmp.first->second.m_stateChangedConn);
        }
 }
@@ -374,23 +468,28 @@ void eDVBLocalTimeHandler::DVBChannelStateChanged(iDVBChannel *chan)
        {
                int state=0;
                chan->getState(state);
-               switch (state)
+               if ( state != it->second.m_prevChannelState )
                {
-                       case iDVBChannel::state_idle:
-                               break;
-                       case iDVBChannel::state_tuning:
-                               break;
-                       case iDVBChannel::state_unavailable:
-                               break;
-                       case iDVBChannel::state_ok:
-                               eDebug("[eDVBLocalTimerHandler] channel %p running", chan);
-                               it->second.tdt->start();
-                               break;
-                       case iDVBChannel::state_release:
-                               eDebug("[eDVBLocalTimerHandler] remove channel %p", chan);
-                               delete it->second.tdt;
-                               m_knownChannels.erase(it);
-                               break;
+                       switch (state)
+                       {
+                               case iDVBChannel::state_ok:
+                                       eDebug("[eDVBLocalTimerHandler] channel %p running", chan);
+                                       m_updateNonTunedTimer->stop();
+                                       if (m_use_dvb_time) {
+                                               it->second.tdt = new TDT(it->second.channel);
+                                               it->second.tdt->start();
+                                       }
+                                       break;
+                               case iDVBChannel::state_release:
+                                       eDebug("[eDVBLocalTimerHandler] remove channel %p", chan);
+                                       m_knownChannels.erase(it);
+                                       if (m_knownChannels.empty())
+                                               m_updateNonTunedTimer->start(TIME_UPDATE_INTERVAL, true);
+                                       break;
+                               default: // ignore all other events
+                                       return;
+                       }
+                       it->second.m_prevChannelState = state;
                }
        }
 }