tvheadend/src/dvb/dvb_fe.c
Andreas Öman c26a0b4b65 dvb: Better handling of uncorrected blocks counter
Some DVB adapters return delta value from last read. Some return
absolute value since adapter was created.
Add some heuristics that tries to autodetect this.

Ticket #393
2011-05-03 21:53:21 +02:00

513 lines
12 KiB
C

/*
* TV Input - Linux DVB interface
* Copyright (C) 2007 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include "tvheadend.h"
#include "dvb.h"
#include "dvb_support.h"
#include "diseqc.h"
#include "notify.h"
#include "dvr/dvr.h"
/**
* Return uncorrected block (since last read)
*
* Some adapters report delta themselfs, some return ever increasing value
* we need to deal with that ourselfs
*/
static int
dvb_fe_get_unc(th_dvb_adapter_t *tda)
{
uint32_t fec;
int d, r;
if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec))
return 0; // read failed, just say 0
if(tda->tda_unc_is_delta)
return fec;
d = (int)fec - (int)tda->tda_last_fec;
if(d < 0) {
tda->tda_unc_is_delta = 1;
tvhlog(LOG_DEBUG, "dvb",
"%s: FE_READ_UNCORRECTED_BLOCKS returns delta updates (delta=%d)",
tda->tda_displayname, d);
return fec;
}
r = fec - tda->tda_last_fec;
tda->tda_last_fec = fec;
return r;
}
/**
* Front end monitor
*
* Monitor status every second
*/
static void
dvb_fe_monitor(void *aux)
{
th_dvb_adapter_t *tda = aux;
fe_status_t fe_status;
int status, v, update = 0, vv, i, fec, q;
th_dvb_mux_instance_t *tdmi = tda->tda_mux_current;
char buf[50];
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
if(tdmi == NULL)
return;
/**
* Read out front end status
*/
if(ioctl(tda->tda_fe_fd, FE_READ_STATUS, &fe_status))
fe_status = 0;
if(fe_status & FE_HAS_LOCK)
status = -1;
else if(fe_status & (FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_CARRIER))
status = TDMI_FE_BAD_SIGNAL;
else if(fe_status & FE_HAS_SIGNAL)
status = TDMI_FE_FAINT_SIGNAL;
else
status = TDMI_FE_NO_SIGNAL;
if(tda->tda_fe_monitor_hold > 0) {
/* Post tuning threshold */
if(status == -1) { /* We have a lock, don't hold off */
tda->tda_fe_monitor_hold = 0;
/* Reset FEC counter */
dvb_fe_get_unc(tda);
} else {
tda->tda_fe_monitor_hold--;
return;
}
}
if(status == -1) {
/* Read FEC counter (delta) */
fec = dvb_fe_get_unc(tda);
tdmi->tdmi_fec_err_histogram[tdmi->tdmi_fec_err_ptr++] = fec;
if(tdmi->tdmi_fec_err_ptr == TDMI_FEC_ERR_HISTOGRAM_SIZE)
tdmi->tdmi_fec_err_ptr = 0;
v = vv = 0;
for(i = 0; i < TDMI_FEC_ERR_HISTOGRAM_SIZE; i++) {
if(tdmi->tdmi_fec_err_histogram[i] > DVB_FEC_ERROR_LIMIT)
v++;
vv += tdmi->tdmi_fec_err_histogram[i];
}
vv = vv / TDMI_FEC_ERR_HISTOGRAM_SIZE;
if(v == 0) {
status = TDMI_FE_OK;
} else if(v == 1) {
status = TDMI_FE_BURSTY_FEC;
} else {
status = TDMI_FE_CONSTANT_FEC;
}
/* bit error rate */
if(ioctl(tda->tda_fe_fd, FE_READ_BER, &tdmi->tdmi_ber) == -1)
tdmi->tdmi_ber = -2;
/* signal strength */
if(ioctl(tda->tda_fe_fd, FE_READ_SIGNAL_STRENGTH, &tdmi->tdmi_signal) == -1)
tdmi->tdmi_signal = -2;
/* signal/noise ratio */
if(ioctl(tda->tda_fe_fd, FE_READ_SNR, &tdmi->tdmi_snr) == -1)
tdmi->tdmi_snr = -2;
}
if(status != tdmi->tdmi_fe_status) {
tdmi->tdmi_fe_status = status;
dvb_mux_nicename(buf, sizeof(buf), tdmi);
tvhlog(LOG_DEBUG,
"dvb", "\"%s\" on adapter \"%s\", status changed to %s",
buf, tda->tda_displayname, dvb_mux_status(tdmi));
update = 1;
}
if(status != TDMI_FE_UNKNOWN) {
if(tda->tda_qmon) {
q = tdmi->tdmi_quality + (status - TDMI_FE_OK + 1);
q = MAX(MIN(q, 100), 0);
} else {
q = 100;
}
if(q != tdmi->tdmi_quality) {
tdmi->tdmi_quality = q;
update = 1;
}
}
if(update) {
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
htsmsg_add_u32(m, "quality", tdmi->tdmi_quality);
notify_by_msg("dvbMux", m);
dvb_mux_save(tdmi);
}
}
/**
* Stop the given TDMI
*/
void
dvb_fe_stop(th_dvb_mux_instance_t *tdmi)
{
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
assert(tdmi == tda->tda_mux_current);
tda->tda_mux_current = NULL;
if(tda->tda_allpids_dmx_fd != -1) {
close(tda->tda_allpids_dmx_fd);
tda->tda_allpids_dmx_fd = -1;
}
if(tda->tda_dump_fd != -1) {
close(tda->tda_dump_fd);
tda->tda_dump_fd = -1;
}
if(tdmi->tdmi_table_initial) {
tdmi->tdmi_table_initial = 0;
tda->tda_initial_num_mux--;
}
dvb_table_flush_all(tdmi);
assert(tdmi->tdmi_scan_queue == NULL);
if(tdmi->tdmi_enabled) {
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[tdmi->tdmi_quality == 100];
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
}
time(&tdmi->tdmi_lost_adapter);
}
/**
* Open a dump file which we write the entire mux output to
*/
static void
dvb_adapter_open_dump_file(th_dvb_adapter_t *tda)
{
struct dmx_pes_filter_params dmx_param;
char fullname[1000];
char path[500];
const char *fname = tda->tda_mux_current->tdmi_identifier;
int fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
if(fd == -1)
return;
memset(&dmx_param, 0, sizeof(dmx_param));
dmx_param.pid = 0x2000;
dmx_param.input = DMX_IN_FRONTEND;
dmx_param.output = DMX_OUT_TS_TAP;
dmx_param.pes_type = DMX_PES_OTHER;
dmx_param.flags = DMX_IMMEDIATE_START;
if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
tvhlog(LOG_ERR, "dvb",
"\"%s\" unable to configure demuxer \"%s\" for all PIDs -- %s",
fname, tda->tda_demux_path,
strerror(errno));
close(fd);
return;
}
snprintf(path, sizeof(path), "%s/muxdumps",
dvr_config_find_by_name_default("")->dvr_storage);
if(mkdir(path, 0777) && errno != EEXIST) {
tvhlog(LOG_ERR, "dvb", "\"%s\" unable to create mux dump dir %s -- %s",
fname, path, strerror(errno));
close(fd);
return;
}
int attempt = 1;
while(1) {
struct stat st;
snprintf(fullname, sizeof(fullname), "%s/%s.dump%d.ts",
path, fname, attempt);
if(stat(fullname, &st) == -1)
break;
attempt++;
}
int f = open(fullname, O_CREAT | O_TRUNC | O_WRONLY, 0777);
if(f == -1) {
tvhlog(LOG_ERR, "dvb", "\"%s\" unable to create mux dump file %s -- %s",
fname, fullname, strerror(errno));
close(fd);
return;
}
tvhlog(LOG_WARNING, "dvb", "\"%s\" writing to mux dump file %s",
fname, fullname);
tda->tda_allpids_dmx_fd = fd;
tda->tda_dump_fd = f;
}
#if DVB_API_VERSION >= 5
static int check_frontend (int fe_fd, int dvr, int human_readable) {
(void)dvr;
fe_status_t status;
uint16_t snr, signal;
uint32_t ber;
int timeout = 0;
do {
if (ioctl(fe_fd, FE_READ_STATUS, &status) == -1)
perror("FE_READ_STATUS failed");
/* some frontends might not support all these ioctls, thus we
* avoid printing errors
*/
if (ioctl(fe_fd, FE_READ_SIGNAL_STRENGTH, &signal) == -1)
signal = -2;
if (ioctl(fe_fd, FE_READ_SNR, &snr) == -1)
snr = -2;
if (ioctl(fe_fd, FE_READ_BER, &ber) == -1)
ber = -2;
if (human_readable) {
printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | ",
status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber);
} else {
printf ("status %02x | signal %04x | snr %04x | ber %08x | ",
status, signal, snr, ber);
}
if (status & FE_HAS_LOCK)
printf("FE_HAS_LOCK");
printf("\n");
if ((status & FE_HAS_LOCK) || (++timeout >= 10))
break;
usleep(1000000);
} while (1);
return 0;
}
static struct dtv_property clear_p[] = {
{ .cmd = DTV_CLEAR },
};
static struct dtv_properties clear_cmdseq = {
.num = 1,
.props = clear_p
};
/**
*
*/
static int
dvb_fe_tune_s2(th_dvb_mux_instance_t *tdmi, dvb_mux_conf_t *dmc)
{
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
struct dvb_frontend_parameters *p = &dmc->dmc_fe_params;
int r;
if ((ioctl(tda->tda_fe_fd, FE_SET_PROPERTY, &clear_cmdseq)) != 0)
return -1;
struct dvb_frontend_event ev;
/* Support for legacy satellite tune, with the new API. */
struct dtv_property _dvbs_cmdargs[] = {
{ .cmd = DTV_DELIVERY_SYSTEM, .u.data = dmc->dmc_fe_delsys },
{ .cmd = DTV_FREQUENCY, .u.data = p->frequency },
{ .cmd = DTV_MODULATION, .u.data = dmc->dmc_fe_modulation },
{ .cmd = DTV_SYMBOL_RATE, .u.data = p->u.qpsk.symbol_rate },
{ .cmd = DTV_INNER_FEC, .u.data = p->u.qpsk.fec_inner },
{ .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO },
{ .cmd = DTV_ROLLOFF, .u.data = dmc->dmc_fe_rolloff },
{ .cmd = DTV_PILOT, .u.data = PILOT_AUTO },
{ .cmd = DTV_TUNE },
};
struct dtv_properties _dvbs_cmdseq = {
.num = 9,
.props = _dvbs_cmdargs
};
/* discard stale QPSK events */
while (1) {
if (ioctl(tda->tda_fe_fd, FE_GET_EVENT, &ev) == -1)
break;
}
/* do tuning now */
r = ioctl(tda->tda_fe_fd, FE_SET_PROPERTY, &_dvbs_cmdseq);
if(0)
check_frontend (tda->tda_fe_fd, 0, 1);
return r;
}
#endif
/**
*
*/
int
dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
{
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
// copy dmc, cause frequency may be change with FE_QPSK
dvb_mux_conf_t dmc = tdmi->tdmi_conf;
dvb_frontend_parameters_t* p = &dmc.dmc_fe_params;
char buf[256];
int r;
lock_assert(&global_lock);
if(tda->tda_mux_current == tdmi)
return 0;
if(tdmi->tdmi_scan_queue != NULL) {
TAILQ_REMOVE(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
tdmi->tdmi_scan_queue = NULL;
}
if(tda->tda_mux_current != NULL)
dvb_fe_stop(tda->tda_mux_current);
if(tda->tda_type == FE_QPSK) {
/* DVB-S */
dvb_satconf_t *sc;
int port, lowfreq, hifreq, switchfreq, hiband, pol;
lowfreq = 9750000;
hifreq = 10600000;
switchfreq = 11700000;
port = 0;
if((sc = tdmi->tdmi_conf.dmc_satconf) != NULL) {
port = sc->sc_port;
if(sc->sc_lnb != NULL)
dvb_lnb_get_frequencies(sc->sc_lnb, &lowfreq, &hifreq, &switchfreq);
}
hiband = switchfreq && p->frequency > switchfreq;
pol = tdmi->tdmi_conf.dmc_polarisation;
if ((r = diseqc_setup(tda->tda_fe_fd,
port,
pol == POLARISATION_HORIZONTAL ||
pol == POLARISATION_CIRCULAR_LEFT,
hiband, tda->tda_diseqc_version)) != 0)
tvhlog(LOG_ERR, "dvb", "diseqc setup failed %d\n", r);
if(hiband)
p->frequency = abs(p->frequency - hifreq);
else
p->frequency = abs(p->frequency - lowfreq);
}
dvb_mux_nicename(buf, sizeof(buf), tdmi);
tda->tda_fe_monitor_hold = 4;
#if DVB_API_VERSION >= 5
if (tda->tda_type == FE_QPSK) {
tvhlog(LOG_DEBUG, "dvb", "\"%s\" tuning via s2api to \"%s\" (%d, %d Baud, "
"%s, %s, %s)", tda->tda_rootpath, buf, p->frequency, p->u.qpsk.symbol_rate,
dvb_mux_fec2str(p->u.qpsk.fec_inner), dvb_mux_delsys2str(dmc.dmc_fe_delsys),
dvb_mux_qam2str(dmc.dmc_fe_modulation));
r = dvb_fe_tune_s2(tdmi, &dmc);
} else
#endif
{
tvhlog(LOG_DEBUG, "dvb", "\"%s\" tuning to \"%s\" (%s)", tda->tda_rootpath, buf, reason);
r = ioctl(tda->tda_fe_fd, FE_SET_FRONTEND, p);
}
if(r != 0) {
tvhlog(LOG_ERR, "dvb", "\"%s\" tuning to \"%s\""
" -- Front configuration failed -- %s, frequency: %ld",
tda->tda_rootpath, buf, strerror(errno), p->frequency);
return SM_CODE_TUNING_FAILED;
}
tda->tda_mux_current = tdmi;
if(tda->tda_dump_muxes)
dvb_adapter_open_dump_file(tda);
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
dvb_table_add_default(tdmi);
dvb_adapter_notify(tda);
return 0;
}