From a49ce434f6152a4431b0dd717f4070dc0da401ef Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 4 Dec 2011 12:45:26 +0100 Subject: Implemented "Device Bonding" --- dvbdevice.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 327 insertions(+), 37 deletions(-) (limited to 'dvbdevice.c') diff --git a/dvbdevice.c b/dvbdevice.c index 726159ff..163fce36 100644 --- a/dvbdevice.c +++ b/dvbdevice.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbdevice.c 2.47 2011/09/17 12:53:46 kls Exp $ + * $Id: dvbdevice.c 2.48 2011/12/03 15:24:27 kls Exp $ */ #include "dvbdevice.h" @@ -259,8 +259,9 @@ bool cDvbTransponderParameters::Parse(const char *s) class cDvbTuner : public cThread { private: + static cMutex bondMutex; enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked }; - int device; + const cDvbDevice *device; int fd_frontend; int adapter, frontend; uint32_t subsystemId; @@ -275,24 +276,35 @@ private: cMutex mutex; cCondVar locked; cCondVar newSet; + cDvbTuner *bondedTuner; + bool bondedMaster; + bool bondedMasterFailed; + cString GetBondingParams(const cChannel *Channel = NULL) const; void ClearEventQueue(void) const; bool GetFrontendStatus(fe_status_t &Status) const; - void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency) const; + void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency); + void ResetToneAndVoltage(void); bool SetFrontend(void); virtual void Action(void); public: - cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType); + cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType); virtual ~cDvbTuner(); + bool Bond(cDvbTuner *Tuner); + void UnBond(void); + bool BondingOk(const cChannel *Channel, bool ConsiderOccupied = false) const; + cDvbTuner *GetBondedMaster(void); const cChannel *GetTransponder(void) const { return &channel; } uint32_t SubsystemId(void) const { return subsystemId; } bool IsTunedTo(const cChannel *Channel) const; - void Set(const cChannel *Channel); + void SetChannel(const cChannel *Channel); bool Locked(int TimeoutMs = 0); int GetSignalStrength(void) const; int GetSignalQuality(void) const; }; -cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType) +cMutex cDvbTuner::bondMutex; + +cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType) { device = Device; fd_frontend = Fd_Frontend; @@ -306,8 +318,11 @@ cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_ lastDiseqc = NULL; scr = NULL; tunerStatus = tsIdle; + bondedTuner = NULL; + bondedMaster = false; + bondedMasterFailed = false; if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) - CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power + ResetToneAndVoltage(); // must explicitly turn on LNB power SetDescription("tuner on frontend %d/%d", adapter, frontend); Start(); } @@ -318,6 +333,7 @@ cDvbTuner::~cDvbTuner() newSet.Broadcast(); locked.Broadcast(); Cancel(3); + UnBond(); /* looks like this irritates the SCR switch, so let's leave it out for now if (lastDiseqc && lastDiseqc->IsScr()) { unsigned int Frequency = 0; @@ -326,6 +342,109 @@ cDvbTuner::~cDvbTuner() */ } +bool cDvbTuner::Bond(cDvbTuner *Tuner) +{ + cMutexLock MutexLock(&bondMutex); + if (!bondedTuner) { + if ((frontendType == SYS_DVBS || frontendType == SYS_DVBS2) && (Tuner->frontendType == SYS_DVBS || Tuner->frontendType == SYS_DVBS2)) { + ResetToneAndVoltage(); + bondedMaster = false; // makes sure we don't disturb an existing master + bondedTuner = Tuner->bondedTuner ? Tuner->bondedTuner : Tuner; + Tuner->bondedTuner = this; + dsyslog("tuner %d/%d bonded with tuner %d/%d", adapter, frontend, bondedTuner->adapter, bondedTuner->frontend); + return true; + } + else + esyslog("ERROR: can't bond tuner %d/%d with tuner %d/%d (only DVB-S(2) tuners can be bonded)", adapter, frontend, Tuner->adapter, Tuner->frontend); + } + else + 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); + return false; +} + +void cDvbTuner::UnBond(void) +{ + cMutexLock MutexLock(&bondMutex); + if (cDvbTuner *t = bondedTuner) { + dsyslog("tuner %d/%d unbonded from tuner %d/%d", adapter, frontend, bondedTuner->adapter, bondedTuner->frontend); + while (t->bondedTuner != this) + t = t->bondedTuner; + if (t == bondedTuner) + t->bondedTuner = NULL; + else + t->bondedTuner = bondedTuner; + bondedMaster = false; // another one will automatically become master whenever necessary + bondedTuner = NULL; + } +} + +cString cDvbTuner::GetBondingParams(const cChannel *Channel) const +{ + if (!Channel) + Channel = &channel; + cDvbTransponderParameters dtp(Channel->Parameters()); + if (Setup.DiSEqC) { + if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization(), NULL)) + return diseqc->Commands(); + } + else { + bool ToneOff = Channel->Frequency() < (unsigned int)Setup.LnbSLOF; + bool VoltOff = dtp.Polarization() == 'V' || dtp.Polarization() == 'R'; + return cString::sprintf("%c %c", ToneOff ? 't' : 'T', VoltOff ? 'v' : 'V'); + } + return ""; +} + +bool cDvbTuner::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const +{ + cMutexLock MutexLock(&bondMutex); + if (cDvbTuner *t = bondedTuner) { + cString BondingParams = GetBondingParams(Channel); + do { + if (t->device->Receiving() || t->tunerStatus != tsIdle && (t->device == cDevice::ActualDevice() || ConsiderOccupied && t->device->Occupied())) { + if (strcmp(BondingParams, t->GetBondingParams()) != 0) + return false; + } + t = t->bondedTuner; + } while (t != bondedTuner); + } + return true; +} + +cDvbTuner *cDvbTuner::GetBondedMaster(void) +{ + if (!bondedTuner) + return this; // an unbonded tuner is always "master" + cMutexLock MutexLock(&bondMutex); + if (bondedMaster) { + if (!bondedMasterFailed) + return this; + else + bondedMaster = false; + } + // This tuner is bonded, but it's not the master, so let's see if there is a master at all: + if (cDvbTuner *t = bondedTuner) { + while (t != this) { + if (t->bondedMaster) + return t; + t = t->bondedTuner; + } + } + // None of the other bonded tuners is master, so make this one the master: + cDvbTuner *t = this; + if (bondedMasterFailed) { + // This one has failed, so switch to the next one: + t = bondedTuner; + t->bondedMasterFailed = false; + cMutexLock MutexLock(&t->mutex); + t->channel = channel; + t->tunerStatus = tsSet; + } + t->bondedMaster = true; + dsyslog("tuner %d/%d is now bonded master", t->adapter, t->frontend); + return t; +} + bool cDvbTuner::IsTunedTo(const cChannel *Channel) const { if (tunerStatus == tsIdle) @@ -336,14 +455,34 @@ bool cDvbTuner::IsTunedTo(const cChannel *Channel) const return strcmp(channel.Parameters(), Channel->Parameters()) == 0; } -void cDvbTuner::Set(const cChannel *Channel) +void cDvbTuner::SetChannel(const cChannel *Channel) { - cMutexLock MutexLock(&mutex); - if (!IsTunedTo(Channel)) - tunerStatus = tsSet; - channel = *Channel; - lastTimeoutReport = 0; - newSet.Broadcast(); + if (Channel) { + if (bondedTuner) { + cMutexLock MutexLock(&bondMutex); + cDvbTuner *BondedMaster = GetBondedMaster(); + if (BondedMaster == this) { + if (strcmp(GetBondingParams(Channel), GetBondingParams()) != 0) { + // switching to a completely different band, so set all others to idle: + for (cDvbTuner *t = bondedTuner; t && t != this; t = t->bondedTuner) + t->SetChannel(NULL); + } + } + else if (!BondedMaster->device->Receiving()) + BondedMaster->SetChannel(Channel); + } + cMutexLock MutexLock(&mutex); + if (!IsTunedTo(Channel)) + tunerStatus = tsSet; + channel = *Channel; + lastTimeoutReport = 0; + newSet.Broadcast(); + } + else { + cMutexLock MutexLock(&mutex); + tunerStatus = tsIdle; + ResetToneAndVoltage(); + } } bool cDvbTuner::Locked(int TimeoutMs) @@ -493,7 +632,7 @@ static unsigned int FrequencyToHz(unsigned int f) return f; } -void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency) const +void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency) { struct dvb_diseqc_master_cmd cmd; const char *CurrentAction = NULL; @@ -514,7 +653,13 @@ void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency) co } } if (scr) - CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // makes sure we don't block the bus! + ResetToneAndVoltage(); // makes sure we don't block the bus! +} + +void cDvbTuner::ResetToneAndVoltage(void) +{ + CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); + CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF)); } bool cDvbTuner::SetFrontend(void) @@ -544,12 +689,16 @@ bool cDvbTuner::SetFrontend(void) if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) { unsigned int frequency = channel.Frequency(); if (Setup.DiSEqC) { - if (const cDiseqc *diseqc = Diseqcs.Get(device, channel.Source(), frequency, dtp.Polarization(), &scr)) { + if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, channel.Source(), frequency, dtp.Polarization(), &scr)) { frequency -= diseqc->Lof(); if (diseqc != lastDiseqc || diseqc->IsScr()) { - ExecuteDiseqc(diseqc, &frequency); - if (frequency == 0) - return false; + if (GetBondedMaster() == this) { + ExecuteDiseqc(diseqc, &frequency); + if (frequency == 0) + return false; + } + else + ResetToneAndVoltage(); lastDiseqc = diseqc; } } @@ -568,7 +717,11 @@ bool cDvbTuner::SetFrontend(void) frequency -= Setup.LnbFrequHi; tone = SEC_TONE_ON; } - int volt = (dtp.Polarization() == 'v' || dtp.Polarization() == 'V' || dtp.Polarization() == 'r' || dtp.Polarization() == 'R') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; + int volt = (dtp.Polarization() == 'V' || dtp.Polarization() == 'R') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; + if (GetBondedMaster() != this) { + tone = SEC_TONE_OFF; + volt = SEC_VOLTAGE_13; + } CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, volt)); CHECK(ioctl(fd_frontend, FE_SET_TONE, tone)); } @@ -675,6 +828,9 @@ void cDvbTuner::Action(void) isyslog("frontend %d/%d timed out while tuning to channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder()); lastTimeoutReport = time(NULL); } + cMutexLock MutexLock(&bondMutex); + if (bondedTuner && bondedMaster) + bondedMasterFailed = true; // give an other tuner a chance in case the sat cable was disconnected continue; } case tsLocked: @@ -770,6 +926,7 @@ cOsdItem *cDvbSourceParam::GetOsdItem(void) // --- cDvbDevice ------------------------------------------------------------ int cDvbDevice::setTransferModeForDolbyDigital = 1; +cMutex cDvbDevice::bondMutex; const char *DeliverySystems[] = { "UNDEFINED", @@ -799,6 +956,8 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend) dvbTuner = NULL; frontendType = SYS_UNDEFINED; numProvidedSystems = 0; + bondedDevice = NULL; + needsDetachBondedReceivers = false; // Devices that are present on all card types: @@ -848,7 +1007,7 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend) else p = (char *)"unknown modulations"; isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, DeliverySystems[frontendType], p, frontendInfo.name); - dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType); + dvbTuner = new cDvbTuner(this, fd_frontend, adapter, frontend, frontendType); } } else @@ -862,6 +1021,7 @@ cDvbDevice::~cDvbDevice() StopSectionHandler(); delete dvbTuner; delete ciAdapter; + UnBond(); // We're not explicitly closing any device files here, since this sometimes // caused segfaults. Besides, the program is about to terminate anyway... } @@ -953,6 +1113,104 @@ bool cDvbDevice::Ready(void) return true; } +bool cDvbDevice::BondDevices(const char *Bondings) +{ + UnBondDevices(); + if (Bondings) { + cSatCableNumbers SatCableNumbers(MAXDEVICES, Bondings); + for (int i = 0; i < cDevice::NumDevices(); i++) { + int d = SatCableNumbers.FirstDeviceIndex(i); + if (d >= 0) { + int ErrorDevice = 0; + if (cDevice *Device1 = cDevice::GetDevice(i)) { + if (cDevice *Device2 = cDevice::GetDevice(d)) { + if (cDvbDevice *DvbDevice1 = dynamic_cast(Device1)) { + if (cDvbDevice *DvbDevice2 = dynamic_cast(Device2)) { + if (!DvbDevice2->Bond(DvbDevice1)) + return false; // Bond() has already logged the error + } + else + ErrorDevice = d + 1; + } + else + ErrorDevice = i + 1; + if (ErrorDevice) { + esyslog("ERROR: device '%d' in device bondings '%s' is not a cDvbDevice", ErrorDevice, Bondings); + return false; + } + } + else + ErrorDevice = d + 1; + } + else + ErrorDevice = i + 1; + if (ErrorDevice) { + esyslog("ERROR: unknown device '%d' in device bondings '%s'", ErrorDevice, Bondings); + return false; + } + } + } + } + return true; +} + +void cDvbDevice::UnBondDevices(void) +{ + for (int i = 0; i < cDevice::NumDevices(); i++) { + if (cDvbDevice *d = dynamic_cast(cDevice::GetDevice(i))) + d->UnBond(); + } +} + +bool cDvbDevice::Bond(cDvbDevice *Device) +{ + cMutexLock MutexLock(&bondMutex); + if (!bondedDevice) { + if (Device != this) { + if ((frontendType == SYS_DVBS || frontendType == SYS_DVBS2) && (Device->frontendType == SYS_DVBS || Device->frontendType == SYS_DVBS2)) { + if (dvbTuner && Device->dvbTuner && dvbTuner->Bond(Device->dvbTuner)) { + bondedDevice = Device->bondedDevice ? Device->bondedDevice : Device; + Device->bondedDevice = this; + dsyslog("device %d bonded with device %d", CardIndex() + 1, bondedDevice->CardIndex() + 1); + return true; + } + } + else + esyslog("ERROR: can't bond device %d with device %d (only DVB-S(2) devices can be bonded)", CardIndex() + 1, Device->CardIndex() + 1); + } + else + esyslog("ERROR: can't bond device %d with itself", CardIndex() + 1); + } + else + esyslog("ERROR: device %d already bonded with device %d, can't bond with device %d", CardIndex() + 1, bondedDevice->CardIndex() + 1, Device->CardIndex() + 1); + return false; +} + +void cDvbDevice::UnBond(void) +{ + cMutexLock MutexLock(&bondMutex); + if (cDvbDevice *d = bondedDevice) { + if (dvbTuner) + dvbTuner->UnBond(); + dsyslog("device %d unbonded from device %d", CardIndex() + 1, bondedDevice->CardIndex() + 1); + while (d->bondedDevice != this) + d = d->bondedDevice; + if (d == bondedDevice) + d->bondedDevice = NULL; + else + d->bondedDevice = bondedDevice; + bondedDevice = NULL; + } +} + +bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const +{ + cMutexLock MutexLock(&bondMutex); + if (bondedDevice) + return dvbTuner && dvbTuner->BondingOk(Channel, ConsiderOccupied); + return true; +} + bool cDvbDevice::HasCi(void) { return ciAdapter; @@ -1055,7 +1313,7 @@ bool cDvbDevice::ProvidesTransponder(const cChannel *Channel) const 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 return false; // requires modulation system which frontend doesn't provide if (!cSource::IsSat(Channel->Source()) || - !Setup.DiSEqC || Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization(), NULL)) + (!Setup.DiSEqC || Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization(), NULL))) return DeviceHooksProvidesTransponder(Channel); return false; } @@ -1065,28 +1323,44 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne bool result = false; bool hasPriority = Priority < 0 || Priority > this->Priority(); bool needsDetachReceivers = false; + needsDetachBondedReceivers = false; if (dvbTuner && ProvidesTransponder(Channel)) { result = hasPriority; - if (Priority >= 0 && Receiving(true)) { - if (dvbTuner->IsTunedTo(Channel)) { - if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) { - if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) { - if (CamSlot()->CanDecrypt(Channel)) + if (Priority >= 0) { + if (Receiving(true)) { + if (dvbTuner->IsTunedTo(Channel)) { + if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) { + if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) { + if (CamSlot()->CanDecrypt(Channel)) + result = true; + else + needsDetachReceivers = true; + } + else if (!IsPrimaryDevice()) result = true; else - needsDetachReceivers = true; + result = Priority >= Setup.PrimaryLimit; } - else if (!IsPrimaryDevice()) - result = true; else - result = Priority >= Setup.PrimaryLimit; + result = !IsPrimaryDevice() || Priority >= Setup.PrimaryLimit; } else - result = !IsPrimaryDevice() || Priority >= Setup.PrimaryLimit; + needsDetachReceivers = true; + } + if (result) { + if (!BondingOk(Channel)) { + // This device is bonded, so we need to check the priorities of the others: + for (cDvbDevice *d = bondedDevice; d && d != this; d = d->bondedDevice) { + if (d->Priority() >= Priority) { + result = false; + break; + } + } + needsDetachBondedReceivers = true; + needsDetachReceivers = true; + } } - else - needsDetachReceivers = true; } } if (NeedsDetachReceivers) @@ -1119,15 +1393,20 @@ const cChannel *cDvbDevice::GetCurrentlyTunedTransponder(void) const return dvbTuner ? dvbTuner->GetTransponder() : NULL; } -bool cDvbDevice::IsTunedToTransponder(const cChannel *Channel) +bool cDvbDevice::IsTunedToTransponder(const cChannel *Channel) const { return dvbTuner ? dvbTuner->IsTunedTo(Channel) : false; } +bool cDvbDevice::MaySwitchTransponder(const cChannel *Channel) const +{ + return BondingOk(Channel, true) && cDevice::MaySwitchTransponder(Channel); +} + bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) { if (dvbTuner) - dvbTuner->Set(Channel); + dvbTuner->SetChannel(Channel); return true; } @@ -1169,6 +1448,17 @@ bool cDvbDevice::GetTSPacket(uchar *&Data) return false; } +void cDvbDevice::DetachAllReceivers(void) +{ + cMutexLock MutexLock(&bondMutex); + cDvbDevice *d = this; + do { + d->cDevice::DetachAllReceivers(); + d = d->bondedDevice; + } while (d && d != this && needsDetachBondedReceivers); + needsDetachBondedReceivers = false; +} + // --- cDvbDeviceProbe ------------------------------------------------------- cList DvbDeviceProbes; -- cgit v1.2.3