b182a360fd2e9c62c9f3fe15e23cafeb24b22a57
[vuplus_dvbapp] / lib / dvb_ci / dvbci.cpp
1 #include <fcntl.h>
2 #include <sys/ioctl.h>
3
4 #include <lib/base/init.h>
5 #include <lib/base/init_num.h>
6 #include <lib/base/ebase.h>
7
8 #include <lib/base/eerror.h>
9 #include <lib/dvb/pmt.h>
10 #include <lib/dvb_ci/dvbci.h>
11 #include <lib/dvb_ci/dvbci_session.h>
12 #include <lib/dvb_ci/dvbci_camgr.h>
13 #include <lib/dvb_ci/dvbci_ui.h>
14 #include <lib/dvb_ci/dvbci_appmgr.h>
15 #include <lib/dvb_ci/dvbci_mmi.h>
16
17 #include <dvbsi++/ca_program_map_section.h>
18
19 eDVBCIInterfaces *eDVBCIInterfaces::instance = 0;
20
21 eDVBCIInterfaces::eDVBCIInterfaces()
22 {
23         int num_ci = 0;
24         
25         instance = this;
26         
27         eDebug("scanning for common interfaces..");
28
29         while (1)
30         {
31                 struct stat s;
32                 char filename[128];
33                 sprintf(filename, "/dev/ci%d", num_ci);
34
35                 if (stat(filename, &s))
36                         break;
37
38                 ePtr<eDVBCISlot> cislot;
39
40                 cislot = new eDVBCISlot(eApp, num_ci);
41                 m_slots.push_back(cislot);
42
43                 ++num_ci;
44         }
45
46         eDebug("done, found %d common interface slots", num_ci);
47 }
48
49 eDVBCIInterfaces::~eDVBCIInterfaces()
50 {
51 }
52
53 eDVBCIInterfaces *eDVBCIInterfaces::getInstance()
54 {
55         return instance;
56 }
57
58 eDVBCISlot *eDVBCIInterfaces::getSlot(int slotid)
59 {
60         for(eSmartPtrList<eDVBCISlot>::iterator i(m_slots.begin()); i != m_slots.end(); ++i)
61                 if(i->getSlotID() == slotid)
62                         return i;
63
64         printf("FIXME: request for unknown slot\n");
65                         
66         return 0;
67 }
68
69 int eDVBCIInterfaces::getSlotState(int slotid)
70 {
71         eDVBCISlot *slot;
72
73         if( (slot = getSlot(slotid)) == 0 )
74                 return eDVBCISlot::stateInvalid;
75
76         return slot->getState();
77 }
78
79 int eDVBCIInterfaces::reset(int slotid)
80 {
81         eDVBCISlot *slot;
82
83         if( (slot = getSlot(slotid)) == 0 )
84                 return -1;
85
86         eDVBCISession::deleteSessions(slot);
87         ciRemoved(slot);
88
89         return slot->reset();
90 }
91
92 int eDVBCIInterfaces::enableTS(int slotid, int enable)
93 {
94         eDVBCISlot *slot;
95
96         if( (slot = getSlot(slotid)) == 0 )
97                 return -1;
98
99         int tunernum = 0;
100         PMTHandlerList::iterator it = m_pmt_handlers.begin();
101         while (it != m_pmt_handlers.end())
102         {
103                 if ( it->cislot == slot )
104                 {
105                         eDVBServicePMTHandler *pmthandler = it->pmthandler;
106                         eUsePtr<iDVBChannel> channel;
107                         if (!pmthandler->getChannel(channel))
108                         {
109                                 ePtr<iDVBFrontend> frontend;
110                                 if (!channel->getFrontend(frontend))
111                                 {
112                                         eDVBFrontend *fe = (eDVBFrontend*) &(*frontend);
113                                         tunernum = fe->getID();
114                                 }
115                         }
116                         break;
117                 }
118                 ++it;
119         }
120         return slot->enableTS(enable, tunernum);
121 }
122
123 int eDVBCIInterfaces::initialize(int slotid)
124 {
125         eDVBCISlot *slot;
126
127         if( (slot = getSlot(slotid)) == 0 )
128                 return -1;
129
130         slot->removeService();
131
132         return sendCAPMT(slotid);
133 }
134
135 int eDVBCIInterfaces::sendCAPMT(int slotid)
136 {
137         eDVBCISlot *slot;
138
139         if( (slot = getSlot(slotid)) == 0 )
140                 return -1;
141
142         PMTHandlerList::iterator it = m_pmt_handlers.begin();
143         while (it != m_pmt_handlers.end())
144         {
145                 if ( it->cislot == slot )
146                         slot->sendCAPMT(it->pmthandler);  // send capmt
147                 ++it;
148         }
149
150         return 0;
151 }
152
153 int eDVBCIInterfaces::startMMI(int slotid)
154 {
155         eDVBCISlot *slot;
156
157         if( (slot = getSlot(slotid)) == 0 )
158                 return -1;
159         
160         return slot->startMMI();
161 }
162
163 int eDVBCIInterfaces::stopMMI(int slotid)
164 {
165         eDVBCISlot *slot;
166
167         if( (slot = getSlot(slotid)) == 0 )
168                 return -1;
169         
170         return slot->stopMMI();
171 }
172
173 int eDVBCIInterfaces::answerText(int slotid, int answer)
174 {
175         eDVBCISlot *slot;
176
177         if( (slot = getSlot(slotid)) == 0 )
178                 return -1;
179         
180         return slot->answerText(answer);
181 }
182
183 int eDVBCIInterfaces::answerEnq(int slotid, char *value)
184 {
185         eDVBCISlot *slot;
186
187         if( (slot = getSlot(slotid)) == 0 )
188                 return -1;
189         
190         return slot->answerEnq(value);
191 }
192
193 int eDVBCIInterfaces::cancelEnq(int slotid)
194 {
195         eDVBCISlot *slot;
196
197         if( (slot = getSlot(slotid)) == 0 )
198                 return -1;
199         
200         return slot->cancelEnq();
201 }
202
203 void eDVBCIInterfaces::ciRemoved(eDVBCISlot *slot)
204 {
205         for (PMTHandlerList::iterator it(m_pmt_handlers.begin());
206                 it != m_pmt_handlers.end(); ++it)
207         {
208                 if (it->cislot == slot)
209                 {
210                         eServiceReferenceDVB ref;
211                         it->pmthandler->getServiceReference(ref);
212                         slot->removeService(ref.getServiceID().get());
213                         if (!--slot->use_count)
214                                 enableTS(slot->getSlotID(), 0);
215                         it->cislot=0;
216                 }
217         }
218 }
219
220 void eDVBCIInterfaces::recheckPMTHandlers()
221 {
222 //      eDebug("recheckPMTHAndlers()");
223         for (PMTHandlerList::iterator it(m_pmt_handlers.begin());
224                 it != m_pmt_handlers.end(); ++it)
225         {
226                 CAID_LIST caids;
227                 ePtr<eDVBService> service;
228                 eServiceReferenceDVB ref;
229                 eDVBServicePMTHandler *pmthandler = it->pmthandler;
230                 eDVBServicePMTHandler::program p;
231
232                 pmthandler->getServiceReference(ref);
233                 pmthandler->getService(service);
234                 if (!pmthandler->getProgramInfo(p))
235                 {
236                         int cnt=0;
237                         for (std::set<uint16_t>::reverse_iterator x(p.caids.rbegin()); x != p.caids.rend(); ++x, ++cnt)
238                                 caids.push_front(*x);
239                         if (service && cnt)
240                                 service->m_ca = caids;
241                 }
242
243                 if (it->cislot)
244                         continue; // already running
245
246                 if (service)
247                         caids = service->m_ca;
248
249                 if (!caids.empty())
250                 {
251                         for (eSmartPtrList<eDVBCISlot>::iterator ci_it(m_slots.begin()); ci_it != m_slots.end(); ++ci_it)
252                         {
253                                 if (ci_it->getState() == eDVBCISlot::stateInvalid)
254                                         ci_it->reset();
255
256                                 bool useThis=false;
257                                 eDVBCICAManagerSession *ca_manager = ci_it->getCAManager();
258                                 if (ca_manager)
259                                 {
260                                         const std::vector<uint16_t> &ci_caids = ca_manager->getCAIDs();
261                                         for (CAID_LIST::iterator ca(caids.begin()); ca != caids.end(); ++ca)
262                                         {
263                                                 std::vector<uint16_t>::const_iterator z =
264                                                         std::lower_bound(ci_caids.begin(), ci_caids.end(), *ca);
265                                                 if ( z != ci_caids.end() && *z == *ca )
266                                                 {
267                                                         eDebug("found ci for caid %04x", *z);
268                                                         useThis=true;
269                                                         break;
270                                                 }
271                                         }
272                                 }
273
274                                 if (useThis)
275                                 {
276                                         bool send_ca_pmt = false;
277                                         if (ci_it->use_count)  // check if this CI can descramble more than one service
278                                         {
279                                                 PMTHandlerList::iterator tmp = m_pmt_handlers.begin();
280                                                 while (tmp != m_pmt_handlers.end())
281                                                 {
282                                                         if ( tmp->cislot )
283                                                         {
284                                                                 bool canHandleMultipleServices=false;
285                                                                 eServiceReferenceDVB ref2;
286                                                                 tmp->pmthandler->getServiceReference(ref2);
287                                                                 eDVBChannelID s1, s2;
288                                                                 if (ref != ref2)
289                                                                 {
290                                                                         ref.getChannelID(s1);
291                                                                         ref2.getChannelID(s2);
292                                                                         // FIXME .. build a "ci can handle multiple services" config entry
293                                                                         // Yes / No / Auto
294                                                                         if ( eDVBCI_UI::getInstance()->getAppName(ci_it->getSlotID()) == "AlphaCrypt" )
295                                                                         {
296                                                                                 canHandleMultipleServices = true;
297                                                                                 eDebug("Alphacrypt can handle multiple services");
298                                                                         }
299                                                                 }
300                                                                 if (ref == ref2 || (s1 == s2 && canHandleMultipleServices) )
301                                                                 {
302                                                                         it->cislot = tmp->cislot;
303                                                                         ++it->cislot->use_count;
304                                                                         send_ca_pmt = true;
305 //                                                                      eDebug("usecount now %d", it->cislot->use_count);
306                                                                         break;
307                                                                 }
308                                                         }
309                                                         ++tmp;
310                                                 }
311                                         }
312                                         else
313                                         {
314                                                 ci_it->use_count=1;
315                                                 it->cislot = ci_it;
316 //                                              eDebug("usecount now %d", it->cislot->use_count);
317                                                 enableTS(ci_it->getSlotID(), 1);
318                                                 send_ca_pmt = true;
319                                         }
320                                         if (send_ca_pmt)
321                                                 gotPMT(pmthandler);
322                                 }
323                         }
324                 }
325         }
326 }
327
328 void eDVBCIInterfaces::addPMTHandler(eDVBServicePMTHandler *pmthandler)
329 {
330         // check if this pmthandler is already registered
331         PMTHandlerList::iterator it = m_pmt_handlers.begin();
332         while (it != m_pmt_handlers.end())
333         {
334                 if ( *it++ == pmthandler )
335                         return;
336         }
337
338         eServiceReferenceDVB ref;
339         pmthandler->getServiceReference(ref);
340         eDebug("[eDVBCIInterfaces] addPMTHandler %s", ref.toString().c_str());
341
342         m_pmt_handlers.push_back(CIPmtHandler(pmthandler));
343         recheckPMTHandlers();
344 }
345
346 void eDVBCIInterfaces::removePMTHandler(eDVBServicePMTHandler *pmthandler)
347 {
348         PMTHandlerList::iterator it=std::find(m_pmt_handlers.begin(),m_pmt_handlers.end(),pmthandler);
349         if (it != m_pmt_handlers.end())
350         {
351                 eDVBCISlot *slot = it->cislot;
352                 eDVBServicePMTHandler *pmthandler = it->pmthandler;
353                 m_pmt_handlers.erase(it);
354
355                 eServiceReferenceDVB service_to_remove;
356                 pmthandler->getServiceReference(service_to_remove);
357
358                 bool sameServiceExist=false;
359                 for (PMTHandlerList::iterator i=m_pmt_handlers.begin(); i != m_pmt_handlers.end(); ++i)
360                 {
361                         eServiceReferenceDVB ref;
362                         i->pmthandler->getServiceReference(ref);
363                         if ( ref == service_to_remove )
364                         {
365                                 sameServiceExist=true;
366                                 break;
367                         }
368                 }
369
370                 if (slot && !sameServiceExist)
371                 {
372                         if (slot->getNumOfServices() > 1)
373                         {
374                                 eDebug("[eDVBCIInterfaces] remove last pmt handler for service %s send empty capmt",
375                                         service_to_remove.toString().c_str());
376                                 std::vector<uint16_t> caids;
377                                 caids.push_back(0xFFFF);
378                                 slot->sendCAPMT(pmthandler, caids);  // send a capmt without caids to remove a running service
379                         }
380                         slot->removeService(service_to_remove.getServiceID().get());
381                 }
382
383                 if (slot && !--slot->use_count)
384                 {
385                         ASSERT(!slot->getNumOfServices());
386                         enableTS(slot->getSlotID(),0);
387                 }
388         }
389         // check if another service is waiting for the CI
390         recheckPMTHandlers();
391 }
392
393 void eDVBCIInterfaces::gotPMT(eDVBServicePMTHandler *pmthandler)
394 {
395         eDebug("[eDVBCIInterfaces] gotPMT");
396         PMTHandlerList::iterator it=std::find(m_pmt_handlers.begin(), m_pmt_handlers.end(), pmthandler);
397         if (it != m_pmt_handlers.end() && it->cislot)
398                 it->cislot->sendCAPMT(pmthandler);
399 }
400
401 int eDVBCIInterfaces::getMMIState(int slotid)
402 {
403         eDVBCISlot *slot;
404
405         if( (slot = getSlot(slotid)) == 0 )
406                 return -1;
407         
408         return slot->getMMIState();
409 }
410
411 int eDVBCISlot::send(const unsigned char *data, size_t len)
412 {
413         int res=0;
414         //int i;
415         //printf("< ");
416         //for(i=0;i<len;i++)
417         //      printf("%02x ",data[i]);
418         //printf("\n");
419
420         if (sendqueue.empty())
421                 res = ::write(fd, data, len);
422
423         if (res < 0 || (unsigned int)res != len)
424         {
425                 unsigned char *d = new unsigned char[len];
426                 memcpy(d, data, len);
427                 sendqueue.push( queueData(d, len) );
428                 notifier->setRequested(eSocketNotifier::Read | eSocketNotifier::Priority | eSocketNotifier::Write);
429         }
430
431         return res;
432 }
433
434 void eDVBCISlot::data(int what)
435 {
436         if (state == stateInvalid)
437                 return;
438         if(what == eSocketNotifier::Priority) {
439                 if(state != stateRemoved) {
440                         state = stateRemoved;
441                         printf("ci removed\n");
442                         while(sendqueue.size())
443                         {
444                                 delete [] sendqueue.top().data;
445                                 sendqueue.pop();
446                         }
447                         eDVBCIInterfaces::getInstance()->ciRemoved(this);
448                         eDVBCISession::deleteSessions(this);
449                         notifier->setRequested(eSocketNotifier::Read);
450                         eDVBCI_UI::getInstance()->setState(getSlotID(),0);
451                 }
452                 return;
453         }
454
455         if(state != stateInserted) {
456                 eDebug("ci inserted");
457                 state = stateInserted;
458                 eDVBCI_UI::getInstance()->setState(getSlotID(),1);
459                 notifier->setRequested(eSocketNotifier::Read|eSocketNotifier::Priority);
460                 /* enable PRI to detect removal or errors */
461         }
462
463         if (what & eSocketNotifier::Read) {
464                 __u8 data[4096];
465                 int r;
466                 r = ::read(fd, data, 4096);
467                 if(r > 0) {
468 //                      int i;
469 //                      printf("> ");
470 //                      for(i=0;i<r;i++)
471 //                              printf("%02x ",data[i]);
472 //                      printf("\n");
473                         eDVBCISession::receiveData(this, data, r);
474                         eDVBCISession::pollAll();
475                         return;
476                 }
477         }
478         else if (what & eSocketNotifier::Write) {
479                 if (!sendqueue.empty()) {
480                         const queueData &qe = sendqueue.top();
481                         int res = ::write(fd, qe.data, qe.len);
482                         if (res >= 0 && (unsigned int)res == qe.len)
483                         {
484                                 delete [] qe.data;
485                                 sendqueue.pop();
486                         }
487                 }
488                 else
489                         notifier->setRequested(eSocketNotifier::Read|eSocketNotifier::Priority);
490         }
491 }
492
493 DEFINE_REF(eDVBCISlot);
494
495 eDVBCISlot::eDVBCISlot(eMainloop *context, int nr)
496 {
497         char filename[128];
498
499         application_manager = 0;
500         mmi_session = 0;
501         ca_manager = 0;
502         use_count = 0;
503         
504         slotid = nr;
505
506         sprintf(filename, "/dev/ci%d", nr);
507
508         fd = ::open(filename, O_RDWR | O_NONBLOCK);
509
510         eDebug("eDVBCISlot has fd %d", fd);
511         state = stateInvalid;
512
513         if (fd >= 0)
514         {
515                 notifier = new eSocketNotifier(context, fd, eSocketNotifier::Read | eSocketNotifier::Priority | eSocketNotifier::Write);
516                 CONNECT(notifier->activated, eDVBCISlot::data);
517         } else
518         {
519                 perror(filename);
520         }
521
522         enableTS(0, 0);
523 }
524
525 eDVBCISlot::~eDVBCISlot()
526 {
527 }
528
529 void eDVBCISlot::setAppManager( eDVBCIApplicationManagerSession *session )
530 {
531         application_manager=session;
532 }
533
534 void eDVBCISlot::setMMIManager( eDVBCIMMISession *session )
535 {
536         mmi_session = session;
537 }
538
539 void eDVBCISlot::setCAManager( eDVBCICAManagerSession *session )
540 {
541         ca_manager = session;
542 }
543
544 int eDVBCISlot::getSlotID()
545 {
546         return slotid;
547 }
548
549 int eDVBCISlot::reset()
550 {
551         printf("edvbcislot: reset requested\n");
552
553         if (state == stateInvalid)
554         {
555                 unsigned char buf[256];
556                 eDebug("ci flush");
557                 while(::read(fd, buf, 256)>0);
558                 state = stateResetted;
559         }
560
561         while(sendqueue.size())
562         {
563                 delete [] sendqueue.top().data;
564                 sendqueue.pop();
565         }
566
567         ioctl(fd, 0);
568
569         return 0;
570 }
571
572 int eDVBCISlot::startMMI()
573 {
574         printf("edvbcislot: startMMI()\n");
575         
576         if(application_manager)
577                 application_manager->startMMI();
578         
579         return 0;
580 }
581
582 int eDVBCISlot::stopMMI()
583 {
584         printf("edvbcislot: stopMMI()\n");
585
586         if(mmi_session)
587                 mmi_session->stopMMI();
588         
589         return 0;
590 }
591
592 int eDVBCISlot::answerText(int answer)
593 {
594         printf("edvbcislot: answerText(%d)\n", answer);
595
596         if(mmi_session)
597                 mmi_session->answerText(answer);
598
599         return 0;
600 }
601
602 int eDVBCISlot::getMMIState()
603 {
604         if(mmi_session)
605                 return 1;
606
607         return 0;
608 }
609
610 int eDVBCISlot::answerEnq(char *value)
611 {
612         printf("edvbcislot: answerENQ(%s)\n", value);
613
614         if(mmi_session)
615                 mmi_session->answerEnq(value);
616
617         return 0;
618 }
619
620 int eDVBCISlot::cancelEnq()
621 {
622         printf("edvbcislot: cancelENQ\n");
623
624         if(mmi_session)
625                 mmi_session->cancelEnq();
626
627         return 0;
628 }
629
630 int eDVBCISlot::sendCAPMT(eDVBServicePMTHandler *pmthandler, const std::vector<uint16_t> &ids)
631 {
632         if (!ca_manager)
633         {
634                 eDebug("no ca_manager (no CI plugged?)");
635                 return -1;
636         }
637         const std::vector<uint16_t> &caids = ids.empty() ? ca_manager->getCAIDs() : ids;
638         ePtr<eTable<ProgramMapSection> > ptr;
639         if (pmthandler->getPMT(ptr))
640                 return -1;
641         else
642         {
643                 eDVBTableSpec table_spec;
644                 ptr->getSpec(table_spec);
645                 int pmt_version = table_spec.version & 0x1F; // just 5 bits
646
647                 eServiceReferenceDVB ref;
648                 pmthandler->getServiceReference(ref);
649                 uint16_t program_number = ref.getServiceID().get();
650                 std::map<uint16_t, uint8_t>::iterator it =
651                         running_services.find(program_number);
652
653                 if ( it != running_services.end() &&
654                         (pmt_version == it->second) &&
655                         !(caids.size() == 1 && caids[0] == 0xFFFF) )
656                 {
657                         eDebug("[eDVBCISlot] dont sent self capmt version twice");
658                         return -1;
659                 }
660
661                 std::vector<ProgramMapSection*>::const_iterator i=ptr->getSections().begin();
662                 if ( i == ptr->getSections().end() )
663                         return -1;
664                 else
665                 {
666                         unsigned char raw_data[2048];
667
668 //                      eDebug("send %s capmt for service %04x",
669 //                              it != running_services.end() ? "UPDATE" : running_services.empty() ? "ONLY" : "ADD",
670 //                              program_number);
671
672                         CaProgramMapSection capmt(*i++,
673                                 it != running_services.end() ? 0x05 /*update*/ : running_services.empty() ? 0x03 /*only*/ : 0x04 /*add*/, 0x01, caids );
674                         while( i != ptr->getSections().end() )
675                         {
676                 //                      eDebug("append");
677                                 capmt.append(*i++);
678                         }
679                         capmt.writeToBuffer(raw_data);
680 #if 1
681 // begin calc capmt length
682                         int wp=0;
683                         int hlen;
684                         if ( raw_data[3] & 0x80 )
685                         {
686                                 int i=0;
687                                 int lenbytes = raw_data[3] & ~0x80;
688                                 while(i < lenbytes)
689                                         wp = (wp << 8) | raw_data[4 + i++];
690                                 wp+=4;
691                                 wp+=lenbytes;
692                                 hlen = 4 + lenbytes;
693                         }
694                         else
695                         {
696                                 wp = raw_data[3];
697                                 wp+=4;
698                                 hlen = 4;
699                         }
700 // end calc capmt length
701 //                      eDebug("ca_manager %p dump capmt:", ca_manager);
702 //                      for(int i=0;i<wp;i++)
703 //                              eDebugNoNewLine("%02x ", raw_data[i]);
704 //                      eDebug("");
705 #endif
706                         if (caids.size() == 1 && caids[0] == 0xFFFF)
707                         {
708 //                              eDebugNoNewLine("SEND EMPTY CAPMT.. old version is %02x", raw_data[hlen+3]);
709                                 raw_data[hlen+3] &= ~0x3E;
710                                 raw_data[hlen+3] |= ((pmt_version+1) & 0x1F) << 1;
711 //                              eDebug(" new version is %02x", raw_data[hlen+3]);
712                         }
713
714                         //dont need tag and lenfield
715                         ca_manager->sendCAPMT(raw_data + hlen, wp - hlen);
716                         running_services[program_number] = pmt_version;
717                 }
718         }
719         return 0;
720 }
721
722 void eDVBCISlot::removeService(uint16_t program_number)
723 {
724         if (program_number == 0xFFFF)
725                 running_services.clear();  // remove all
726         else
727                 running_services.erase(program_number);  // remove single service
728 }
729
730 int eDVBCISlot::enableTS(int enable, int tuner)
731 {
732 //      printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
733 //      printf("eDVBCISlot::enableTS(%d %d)\n", enable, tuner);
734
735         FILE *input0, *input1, *ci;
736         if((input0 = fopen("/proc/stb/tsmux/input0", "wb")) == NULL) {
737                 printf("cannot open /proc/stb/tsmux/input0\n");
738                 return 0;
739         }
740         if((input1 = fopen("/proc/stb/tsmux/input1", "wb")) == NULL) {
741                 printf("cannot open /proc/stb/tsmux/input1\n");
742                 return 0;
743         }
744         if((ci = fopen("/proc/stb/tsmux/input2", "wb")) == NULL) {
745                 printf("cannot open /proc/stb/tsmux/input2\n");
746                 return 0;
747         }
748
749         fprintf(ci, "%s", tuner==0 ? "A" : "B");  // configure CI data source (TunerA, TunerB)
750         fprintf(input0, "%s", tuner==0 && enable ? "CI" : "A"); // configure ATI input 0 data source
751         fprintf(input1, "%s", tuner==1 && enable ? "CI" : "B"); // configure ATI input 1 data source
752
753         fclose(input0);
754         fclose(input1);
755         fclose(ci);
756         return 0;
757 }
758
759 eAutoInitP0<eDVBCIInterfaces> init_eDVBCIInterfaces(eAutoInitNumbers::dvb, "CI Slots");