/* * Copyright (c) 2011-2012 - Mauro Carvalho Chehab * * 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 version 2 * of the License. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ #include #include "dvb-fe-priv.h" #include "dvb-v5.h" #include #include #include #include #include #include #include #include #ifdef ENABLE_NLS # include "gettext.h" # include # define _(string) dgettext(LIBDVBV5_DOMAIN, string) static int libdvbv5_initialized = 0; #else # define _(string) string #endif # define N_(string) string #define MAX_TIME 10 /* 1.0 seconds */ #define xioctl(fh, request, arg...) ({ \ int __rc; \ struct timespec __start, __end; \ \ clock_gettime(CLOCK_MONOTONIC, &__start); \ do { \ __rc = ioctl(fh, request, ##arg); \ if (__rc != -1) \ break; \ if ((errno != EINTR) && (errno != EAGAIN)) \ break; \ clock_gettime(CLOCK_MONOTONIC, &__end); \ if (__end.tv_sec * 10 + __end.tv_nsec / 100000000 > \ __start.tv_sec * 10 + __start.tv_nsec / 100000000 + \ MAX_TIME) \ break; \ } while (1); \ \ __rc; \ }) static void libdvbv5_initialize(void) { #ifdef ENABLE_NLS if (libdvbv5_initialized) return; bindtextdomain(LIBDVBV5_DOMAIN, LOCALEDIR); libdvbv5_initialized = 1; #endif } static void dvb_v5_free(struct dvb_v5_fe_parms_priv *parms) { if (parms->fname) free(parms->fname); free(parms); } struct dvb_v5_fe_parms *dvb_fe_dummy() { struct dvb_v5_fe_parms_priv *parms = NULL; libdvbv5_initialize(); parms = calloc(sizeof(*parms), 1); if (!parms) return NULL; parms->p.logfunc = dvb_default_log; parms->fd = -1; parms->p.default_charset = "iso-8859-1"; parms->p.output_charset = "utf-8"; return &parms->p; } struct dvb_v5_fe_parms *dvb_fe_open(int adapter, int frontend, unsigned verbose, unsigned use_legacy_call) { return dvb_fe_open_flags(adapter, frontend, verbose, use_legacy_call, NULL, O_RDWR); } struct dvb_v5_fe_parms *dvb_fe_open2(int adapter, int frontend, unsigned verbose, unsigned use_legacy_call, dvb_logfunc logfunc) { return dvb_fe_open_flags(adapter, frontend, verbose, use_legacy_call, logfunc, O_RDWR); } struct dvb_v5_fe_parms *dvb_fe_open_flags(int adapter, int frontend, unsigned verbose, unsigned use_legacy_call, dvb_logfunc logfunc, int flags) { int fd, i, r; char *fname; struct dtv_properties dtv_prop; struct dvb_v5_fe_parms_priv *parms = NULL; libdvbv5_initialize(); if (logfunc == NULL) logfunc = dvb_default_log; r = asprintf(&fname, "/dev/dvb/adapter%i/frontend%i", adapter, frontend); if (r < 0) { logfunc(LOG_ERR, _("asprintf error")); return NULL; } if (!fname) { logfunc(LOG_ERR, _("fname calloc: %s"), strerror(errno)); return NULL; } fd = open(fname, flags, 0); if (fd == -1) { logfunc(LOG_ERR, _("%s while opening %s"), strerror(errno), fname); free(fname); return NULL; } parms = calloc(sizeof(*parms), 1); if (!parms) { logfunc(LOG_ERR, _("parms calloc: %s"), strerror(errno)); close(fd); free(fname); return NULL; } parms->fname = fname; parms->fd = fd; parms->fe_flags = flags; parms->p.verbose = verbose; parms->p.default_charset = "iso-8859-1"; parms->p.output_charset = "utf-8"; parms->p.logfunc = logfunc; parms->p.lna = LNA_AUTO; parms->p.sat_number = -1; parms->p.abort = 0; parms->country = COUNTRY_UNKNOWN; if (xioctl(fd, FE_GET_INFO, &parms->p.info) == -1) { dvb_perror("FE_GET_INFO"); dvb_v5_free(parms); close(fd); free(fname); return NULL; } if (verbose) { fe_caps_t caps = parms->p.info.caps; dvb_log(_("Device %s (%s) capabilities:"), parms->p.info.name, fname); for (i = 0; i < ARRAY_SIZE(fe_caps_name); i++) { if (caps & fe_caps_name[i].idx) dvb_log (" %s", fe_caps_name[i].name); } } parms->dvb_prop[0].cmd = DTV_API_VERSION; parms->dvb_prop[1].cmd = DTV_DELIVERY_SYSTEM; dtv_prop.num = 2; dtv_prop.props = parms->dvb_prop; /* Detect a DVBv3 device */ if (xioctl(fd, FE_GET_PROPERTY, &dtv_prop) == -1) { parms->dvb_prop[0].u.data = 0x300; parms->dvb_prop[1].u.data = SYS_UNDEFINED; } parms->p.version = parms->dvb_prop[0].u.data; parms->p.current_sys = parms->dvb_prop[1].u.data; if (verbose) dvb_log (_("DVB API Version %d.%d%s, Current v5 delivery system: %s"), parms->p.version / 256, parms->p.version % 256, use_legacy_call ? _(" (forcing DVBv3 calls)") : "", delivery_system_name[parms->p.current_sys]); if (parms->p.version < 0x500) use_legacy_call = 1; if (parms->p.version >= 0x50a) parms->p.has_v5_stats = 1; else parms->p.has_v5_stats = 0; if (use_legacy_call || parms->p.version < 0x505) { parms->p.legacy_fe = 1; switch(parms->p.info.type) { case FE_QPSK: parms->p.current_sys = SYS_DVBS; parms->p.systems[parms->p.num_systems++] = parms->p.current_sys; if (parms->p.version < 0x0500) break; if (parms->p.info.caps & FE_CAN_2G_MODULATION) parms->p.systems[parms->p.num_systems++] = SYS_DVBS2; if (parms->p.info.caps & FE_CAN_TURBO_FEC) parms->p.systems[parms->p.num_systems++] = SYS_TURBO; break; case FE_QAM: parms->p.current_sys = SYS_DVBC_ANNEX_A; parms->p.systems[parms->p.num_systems++] = parms->p.current_sys; break; case FE_OFDM: parms->p.current_sys = SYS_DVBT; parms->p.systems[parms->p.num_systems++] = parms->p.current_sys; if (parms->p.version < 0x0500) break; if (parms->p.info.caps & FE_CAN_2G_MODULATION) parms->p.systems[parms->p.num_systems++] = SYS_DVBT2; break; case FE_ATSC: if (parms->p.info.caps & (FE_CAN_8VSB | FE_CAN_16VSB)) parms->p.systems[parms->p.num_systems++] = SYS_ATSC; if (parms->p.info.caps & (FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_QAM_AUTO)) parms->p.systems[parms->p.num_systems++] = SYS_DVBC_ANNEX_B; parms->p.current_sys = parms->p.systems[0]; break; } if (!parms->p.num_systems) { dvb_logerr(_("delivery system not detected")); dvb_v5_free(parms); close(fd); return NULL; } } else { parms->dvb_prop[0].cmd = DTV_ENUM_DELSYS; parms->n_props = 1; dtv_prop.num = 1; dtv_prop.props = parms->dvb_prop; if (xioctl(fd, FE_GET_PROPERTY, &dtv_prop) == -1) { dvb_perror("FE_GET_PROPERTY"); dvb_v5_free(parms); close(fd); return NULL; } parms->p.num_systems = parms->dvb_prop[0].u.buffer.len; for (i = 0; i < parms->p.num_systems; i++) parms->p.systems[i] = parms->dvb_prop[0].u.buffer.data[i]; if (parms->p.num_systems == 0) { dvb_logerr(_("driver returned 0 supported delivery systems!")); dvb_v5_free(parms); close(fd); return NULL; } } if (verbose) { dvb_log(_("Supported delivery system%s: "), (parms->p.num_systems > 1) ? "s" : ""); for (i = 0; i < parms->p.num_systems; i++) { if (parms->p.systems[i] == parms->p.current_sys) dvb_log (" [%s]", delivery_system_name[parms->p.systems[i]]); else dvb_log (" %s", delivery_system_name[parms->p.systems[i]]); } if (use_legacy_call || parms->p.version < 0x505) dvb_log(_("Warning: new delivery systems like ISDB-T, ISDB-S, DMB-TH, DSS, ATSC-MH will be miss-detected by a DVBv5.4 or earlier API call")); } /* * Fix a bug at some DVB drivers */ if (parms->p.current_sys == SYS_UNDEFINED) parms->p.current_sys = parms->p.systems[0]; /* Prepare to use the delivery system */ parms->n_props = dvb_add_parms_for_sys(&parms->p, parms->p.current_sys); if ((flags & O_ACCMODE) == O_RDWR) dvb_set_sys(&parms->p, parms->p.current_sys); /* * Prepare the status struct - DVBv5.10 parameters should * come first, as they'll be read together. */ parms->stats.prop[0].cmd = DTV_STAT_SIGNAL_STRENGTH; parms->stats.prop[1].cmd = DTV_STAT_CNR; parms->stats.prop[2].cmd = DTV_STAT_PRE_ERROR_BIT_COUNT; parms->stats.prop[3].cmd = DTV_STAT_PRE_TOTAL_BIT_COUNT; parms->stats.prop[4].cmd = DTV_STAT_POST_ERROR_BIT_COUNT; parms->stats.prop[5].cmd = DTV_STAT_POST_TOTAL_BIT_COUNT; parms->stats.prop[6].cmd = DTV_STAT_ERROR_BLOCK_COUNT; parms->stats.prop[7].cmd = DTV_STAT_TOTAL_BLOCK_COUNT; /* Now, status and the calculated stats */ parms->stats.prop[8].cmd = DTV_STATUS; parms->stats.prop[9].cmd = DTV_BER; parms->stats.prop[10].cmd = DTV_PER; parms->stats.prop[11].cmd = DTV_QUALITY; parms->stats.prop[12].cmd = DTV_PRE_BER; return &parms->p; } int dvb_fe_is_satellite(uint32_t delivery_system) { switch (delivery_system) { case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: case SYS_ISDBS: return 1; default: return 0; } } void dvb_fe_close(struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; if (!parms) return; if (parms->fd < 0) return; /* Disable LNBf power */ if (dvb_fe_is_satellite(parms->p.current_sys)) dvb_fe_sec_voltage(&parms->p, 0, 0); close(parms->fd); dvb_v5_free(parms); } int dvb_add_parms_for_sys(struct dvb_v5_fe_parms *p, fe_delivery_system_t sys) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dtv_property *dvb_prop = parms->dvb_prop; unsigned max_size = ARRAY_SIZE(parms->dvb_prop); const unsigned int *sys_props; int n; /* Make dvb properties reflect the current standard */ sys_props = dvb_v5_delivery_system[sys]; if (!sys_props) return EINVAL; n = 0; while (sys_props[n] && n < max_size - 1) { dvb_prop[n].cmd = sys_props[n]; dvb_prop[n].u.data = 0; n++; } dvb_prop[n].cmd = DTV_DELIVERY_SYSTEM; dvb_prop[n].u.data = sys; n++; return n; } int dvb_set_sys(struct dvb_v5_fe_parms *p, fe_delivery_system_t sys) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dtv_property dvb_prop[1]; struct dtv_properties prop; int rc; if (sys != parms->p.current_sys) { /* Disable LNBf power */ if (dvb_fe_is_satellite(parms->p.current_sys) && !dvb_fe_is_satellite(sys)) dvb_fe_sec_voltage(&parms->p, 0, 0); /* Can't change standard with the legacy FE support */ if (parms->p.legacy_fe) return EINVAL; dvb_prop[0].cmd = DTV_DELIVERY_SYSTEM; dvb_prop[0].u.data = sys; prop.num = 1; prop.props = dvb_prop; if (xioctl(parms->fd, FE_SET_PROPERTY, &prop) == -1) { dvb_perror(_("Set delivery system")); return errno; } } rc = dvb_add_parms_for_sys(&parms->p, sys); if (rc < 0) return EINVAL; parms->p.current_sys = sys; parms->n_props = rc; return 0; } static enum dvbv3_emulation_type dvbv3_type(uint32_t delivery_system) { switch (delivery_system) { case SYS_DVBC_ANNEX_A: case SYS_DVBC_ANNEX_C: return DVBV3_QAM; case SYS_DVBS: case SYS_DVBS2: case SYS_TURBO: case SYS_ISDBS: case SYS_DSS: return DVBV3_QPSK; case SYS_DVBT: case SYS_DVBT2: case SYS_ISDBT: case SYS_DTMB: return DVBV3_OFDM; case SYS_ATSC: case SYS_ATSCMH: case SYS_DVBC_ANNEX_B: return DVBV3_ATSC; default: return DVBV3_UNKNOWN; } }; static int is_dvbv3_delsys(uint32_t delsys) { int status; status = (delsys == SYS_DVBT) || (delsys == SYS_DVBC_ANNEX_A) || (delsys == SYS_DVBS) || (delsys == SYS_ATSC); return status; } int dvb_set_compat_delivery_system(struct dvb_v5_fe_parms *p, uint32_t desired_system) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int i; uint32_t delsys = SYS_UNDEFINED; enum dvbv3_emulation_type type; /* Check if the desired delivery system is supported */ for (i = 0; i < parms->p.num_systems; i++) { if (parms->p.systems[i] == desired_system) { dvb_set_sys(&parms->p, desired_system); return 0; } } /* * Find the closest DVBv3 system that matches the delivery * system. */ type = dvbv3_type(desired_system); /* * Get the last non-DVBv3 delivery system that has the same type * of the desired system */ for (i = 0; i < parms->p.num_systems; i++) { if ((dvbv3_type(parms->p.systems[i]) == type) && !is_dvbv3_delsys(parms->p.systems[i])) delsys = parms->p.systems[i]; } if (delsys == SYS_UNDEFINED) return EINVAL; dvb_log(_("Using a DVBv3 compat file for %s"), delivery_system_name[delsys]); dvb_set_sys(&parms->p, delsys); /* Put ISDB-T into auto mode */ if (delsys == SYS_ISDBT) { dvb_fe_store_parm(&parms->p, DTV_BANDWIDTH_HZ, 6000000); dvb_fe_store_parm(&parms->p, DTV_ISDBT_PARTIAL_RECEPTION, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_SOUND_BROADCASTING, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_SB_SUBCHANNEL_ID, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_SB_SEGMENT_IDX, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_SB_SEGMENT_COUNT, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYER_ENABLED, 7); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERA_FEC, FEC_AUTO); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERB_FEC, FEC_AUTO); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERC_FEC, FEC_AUTO); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERA_MODULATION, QAM_AUTO); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERB_MODULATION, QAM_AUTO); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERC_MODULATION, QAM_AUTO); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERA_SEGMENT_COUNT, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERA_TIME_INTERLEAVING, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERB_SEGMENT_COUNT, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERB_TIME_INTERLEAVING, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERC_SEGMENT_COUNT, 0); dvb_fe_store_parm(&parms->p, DTV_ISDBT_LAYERC_TIME_INTERLEAVING, 0); } return 0; } const char *dvb_cmd_name(int cmd) { if (cmd >= 0 && cmd < ARRAY_SIZE(dvb_v5_name)) return dvb_v5_name[cmd]; else if (cmd >= DTV_USER_COMMAND_START && cmd <= DTV_MAX_USER_COMMAND) return dvb_user_name[cmd - DTV_USER_COMMAND_START]; else if (cmd >= DTV_STAT_COMMAND_START && cmd <= DTV_MAX_STAT_COMMAND) return dvb_stat_name[cmd - DTV_STAT_COMMAND_START]; return NULL; } const char *const *dvb_attr_names(int cmd) { if (cmd >= 0 && cmd < DTV_MAX_COMMAND) return dvb_v5_attr_names[cmd]; if (cmd >= DTV_USER_COMMAND_START && cmd <= DTV_MAX_USER_COMMAND) return dvb_user_attr_names[cmd - DTV_USER_COMMAND_START]; return NULL; } void dvb_fe_prt_parms(const struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int i; for (i = 0; i < parms->n_props; i++) { const char * const *attr_name = dvb_attr_names(parms->dvb_prop[i].cmd); if (attr_name) { int j; for (j = 0; j < parms->dvb_prop[i].u.data; j++) { if (!*attr_name) break; attr_name++; } } if (!attr_name || !*attr_name) dvb_log("%s = %u", dvb_cmd_name(parms->dvb_prop[i].cmd), parms->dvb_prop[i].u.data); else dvb_log("%s = %s", dvb_cmd_name(parms->dvb_prop[i].cmd), *attr_name); } }; int dvb_fe_retrieve_parm(const struct dvb_v5_fe_parms *p, unsigned cmd, uint32_t *value) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int i; for (i = 0; i < parms->n_props; i++) { if (parms->dvb_prop[i].cmd != cmd) continue; *value = parms->dvb_prop[i].u.data; return 0; } dvb_logerr(_("command %s (%d) not found during retrieve"), dvb_cmd_name(cmd), cmd); return EINVAL; } int dvb_fe_store_parm(struct dvb_v5_fe_parms *p, unsigned cmd, uint32_t value) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int i; for (i = 0; i < parms->n_props; i++) { if (parms->dvb_prop[i].cmd != cmd) continue; parms->dvb_prop[i].u.data = value; return 0; } dvb_logerr(_("command %s (%d) not found during store"), dvb_cmd_name(cmd), cmd); return EINVAL; } static int dvb_copy_fe_props(const struct dtv_property *from, int n, struct dtv_property *to) { int i, j; for (i = 0, j = 0; i < n; i++) if (from[i].cmd < DTV_USER_COMMAND_START) to[j++] = from[i]; return j; } int dvb_fe_get_parms(struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int i, n = 0; const unsigned int *sys_props; struct dtv_properties prop; struct dvb_frontend_parameters v3_parms; uint32_t bw; sys_props = dvb_v5_delivery_system[parms->p.current_sys]; if (!sys_props) return EINVAL; while (sys_props[n]) { parms->dvb_prop[n].cmd = sys_props[n]; n++; } parms->dvb_prop[n].cmd = DTV_DELIVERY_SYSTEM; parms->dvb_prop[n].u.data = parms->p.current_sys; n++; /* Keep it ready for set */ parms->dvb_prop[n].cmd = DTV_TUNE; parms->n_props = n; struct dtv_property fe_prop[DTV_MAX_COMMAND]; n = dvb_copy_fe_props(parms->dvb_prop, n, fe_prop); prop.props = fe_prop; prop.num = n; if (!parms->p.legacy_fe) { if (xioctl(parms->fd, FE_GET_PROPERTY, &prop) == -1) { dvb_perror("FE_GET_PROPERTY"); return errno; } /* copy back params from temporary fe_prop */ for (i = 0; i < n; i++) { if (dvb_fe_is_satellite(p->current_sys) && fe_prop[i].cmd == DTV_FREQUENCY) fe_prop[i].u.data += parms->freq_offset; dvb_fe_store_parm(&parms->p, fe_prop[i].cmd, fe_prop[i].u.data); } if (parms->p.verbose) { dvb_log(_("Got parameters for %s:"), delivery_system_name[parms->p.current_sys]); dvb_fe_prt_parms(&parms->p); } return 0; } /* DVBv3 call */ if (xioctl(parms->fd, FE_GET_FRONTEND, &v3_parms) == -1) { dvb_perror("FE_GET_FRONTEND"); return EINVAL; } dvb_fe_store_parm(&parms->p, DTV_FREQUENCY, v3_parms.frequency); dvb_fe_store_parm(&parms->p, DTV_INVERSION, v3_parms.inversion); switch (parms->p.current_sys) { case SYS_DVBS: dvb_fe_store_parm(&parms->p, DTV_SYMBOL_RATE, v3_parms.u.qpsk.symbol_rate); dvb_fe_store_parm(&parms->p, DTV_INNER_FEC, v3_parms.u.qpsk.fec_inner); break; case SYS_DVBC_ANNEX_A: dvb_fe_store_parm(&parms->p, DTV_SYMBOL_RATE, v3_parms.u.qam.symbol_rate); dvb_fe_store_parm(&parms->p, DTV_INNER_FEC, v3_parms.u.qam.fec_inner); dvb_fe_store_parm(&parms->p, DTV_MODULATION, v3_parms.u.qam.modulation); break; case SYS_ATSC: case SYS_ATSCMH: case SYS_DVBC_ANNEX_B: dvb_fe_store_parm(&parms->p, DTV_MODULATION, v3_parms.u.vsb.modulation); break; case SYS_DVBT: if (v3_parms.u.ofdm.bandwidth < ARRAY_SIZE(fe_bandwidth_name) -1) bw = fe_bandwidth_name[v3_parms.u.ofdm.bandwidth]; else bw = 0; dvb_fe_store_parm(&parms->p, DTV_BANDWIDTH_HZ, bw); dvb_fe_store_parm(&parms->p, DTV_CODE_RATE_HP, v3_parms.u.ofdm.code_rate_HP); dvb_fe_store_parm(&parms->p, DTV_CODE_RATE_LP, v3_parms.u.ofdm.code_rate_LP); dvb_fe_store_parm(&parms->p, DTV_MODULATION, v3_parms.u.ofdm.constellation); dvb_fe_store_parm(&parms->p, DTV_TRANSMISSION_MODE, v3_parms.u.ofdm.transmission_mode); dvb_fe_store_parm(&parms->p, DTV_GUARD_INTERVAL, v3_parms.u.ofdm.guard_interval); dvb_fe_store_parm(&parms->p, DTV_HIERARCHY, v3_parms.u.ofdm.hierarchy_information); break; default: return EINVAL; } return 0; } /* set the delsys default/fixed parameters and replace DVBv5 default values */ static void dvb_setup_delsys_default(struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; uint32_t cc; switch (p->current_sys) { case SYS_ISDBT: /* Set country code. */ /* if the default country is not known, fallback to BR */ cc = COUNTRY_UNKNOWN; dvb_fe_retrieve_parm(p, DTV_COUNTRY_CODE, &cc); if (cc == COUNTRY_UNKNOWN) { cc = (parms->country == COUNTRY_UNKNOWN) ? BR : parms->country; dvb_fe_store_parm(p, DTV_COUNTRY_CODE, cc); } switch (cc) { case JP: p->default_charset = "arib-std-b24"; dvb_fe_store_parm(p, DTV_BANDWIDTH_HZ, 6000000); break; /* Americas (SBTVD) */ case AR: case BO: case BR: case CL: case CR: case EC: case GT: case HN: case NI: case PE: case PY: case UY: case VE: p->default_charset = "iso8859-15"; break; } break; case SYS_ISDBS: p->default_charset = "arib-std-b24"; if (!p->lnb) p->lnb = dvb_sat_get_lnb(dvb_sat_search_lnb("110BS")); break; default: break; } } int dvb_fe_set_parms(struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; /* Use a temporary copy of the parameters so we can safely perform * adjustments for satellite */ struct dvb_v5_fe_parms_priv tmp_parms = *parms; struct dtv_properties prop; struct dvb_frontend_parameters v3_parms; uint32_t bw; if (parms->p.lna != LNA_AUTO && !parms->p.legacy_fe) { struct dvb_v5_fe_parms_priv tmp_lna_parms; memset(&prop, 0, sizeof(prop)); prop.props = tmp_lna_parms.dvb_prop; prop.props[0].cmd = DTV_LNA; prop.props[0].u.data = parms->p.lna; prop.num = 1; if (xioctl(parms->fd, FE_SET_PROPERTY, &prop) == -1) { dvb_perror(_("Setting LNA")); parms->p.lna = LNA_AUTO; } else if (parms->p.lna != LNA_AUTO && parms->p.verbose) dvb_logdbg(_("LNA is %s"), parms->p.lna ? _("ON") : _("OFF")); } if (dvb_fe_is_satellite(tmp_parms.p.current_sys)) { dvb_sat_set_parms(&tmp_parms.p); /* * even though the frequncy prop is kept un-modified here, * a later call to dvb_fe_get_parms() issues FE_GET_PROPERTY * ioctl and overwrites it with the offset-ed value from * the FE. So we need to save the offset here and * re-add it in dvb_fe_get_parms(). * note that dvbv5-{scan,zap} utilities call dvb_fe_get_parms() * indirectly from check_frontend() via dvb_fe_get_stats(). */ parms->freq_offset = tmp_parms.freq_offset; } dvb_setup_delsys_default(p); /* Filter out any user DTV_foo property such as DTV_POLARIZATION */ tmp_parms.n_props = dvb_copy_fe_props(tmp_parms.dvb_prop, tmp_parms.n_props, tmp_parms.dvb_prop); memset(&prop, 0, sizeof(prop)); prop.props = tmp_parms.dvb_prop; prop.num = tmp_parms.n_props; prop.props[prop.num].cmd = DTV_TUNE; prop.num++; if (!parms->p.legacy_fe) { if (xioctl(parms->fd, FE_SET_PROPERTY, &prop) == -1) { dvb_perror("FE_SET_PROPERTY"); if (parms->p.verbose) dvb_fe_prt_parms(&parms->p); return -1; } return 0; } /* DVBv3 call */ dvb_fe_retrieve_parm(&tmp_parms.p, DTV_FREQUENCY, &v3_parms.frequency); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_INVERSION, &v3_parms.inversion); switch (tmp_parms.p.current_sys) { case SYS_DVBS: dvb_fe_retrieve_parm(&tmp_parms.p, DTV_SYMBOL_RATE, &v3_parms.u.qpsk.symbol_rate); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_INNER_FEC, &v3_parms.u.qpsk.fec_inner); break; case SYS_DVBC_ANNEX_AC: dvb_fe_retrieve_parm(&tmp_parms.p, DTV_SYMBOL_RATE, &v3_parms.u.qam.symbol_rate); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_INNER_FEC, &v3_parms.u.qam.fec_inner); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_MODULATION, &v3_parms.u.qam.modulation); break; case SYS_ATSC: case SYS_ATSCMH: case SYS_DVBC_ANNEX_B: dvb_fe_retrieve_parm(&tmp_parms.p, DTV_MODULATION, &v3_parms.u.vsb.modulation); break; case SYS_DVBT: for (bw = 0; fe_bandwidth_name[bw] != 0; bw++) { if (fe_bandwidth_name[bw] == v3_parms.u.ofdm.bandwidth) break; } dvb_fe_retrieve_parm(&tmp_parms.p, DTV_BANDWIDTH_HZ, &bw); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_CODE_RATE_HP, &v3_parms.u.ofdm.code_rate_HP); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_CODE_RATE_LP, &v3_parms.u.ofdm.code_rate_LP); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_MODULATION, &v3_parms.u.ofdm.constellation); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_TRANSMISSION_MODE, &v3_parms.u.ofdm.transmission_mode); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_GUARD_INTERVAL, &v3_parms.u.ofdm.guard_interval); dvb_fe_retrieve_parm(&tmp_parms.p, DTV_HIERARCHY, &v3_parms.u.ofdm.hierarchy_information); break; default: return -1; } if (xioctl(tmp_parms.fd, FE_SET_FRONTEND, &v3_parms) == -1) { dvb_perror("FE_SET_FRONTEND"); if (tmp_parms.p.verbose) dvb_fe_prt_parms(&tmp_parms.p); return -1; } return 0; } static struct dtv_stats *dvb_fe_store_stats(struct dvb_v5_fe_parms_priv *parms, unsigned cmd, enum fecap_scale_params scale, unsigned layer, uint32_t value) { int i; for (i = 0; i < DTV_NUM_STATS_PROPS; i++) { if (parms->stats.prop[i].cmd != cmd) continue; parms->stats.prop[i].u.st.stat[layer].scale = scale; parms->stats.prop[i].u.st.stat[layer].uvalue = value; if (parms->stats.prop[i].u.st.len < layer + 1) parms->stats.prop[i].u.st.len = layer + 1; return &parms->stats.prop[i].u.st.stat[layer]; } dvb_logerr(_("%s not found on store"), dvb_cmd_name(cmd)); return NULL; } static float calculate_postBER(struct dvb_v5_fe_parms_priv *parms, unsigned layer) { uint64_t n, d; if (!parms->stats.has_post_ber[layer]) return -1; d = parms->stats.cur[layer].post_bit_count - parms->stats.prev[layer].post_bit_count; if (!d) return -1; n = parms->stats.cur[layer].post_bit_error - parms->stats.prev[layer].post_bit_error; return ((float)n)/d; } static float calculate_preBER(struct dvb_v5_fe_parms_priv *parms, unsigned layer) { uint64_t n, d; if (!parms->stats.has_pre_ber[layer]) return -1; d = parms->stats.cur[layer].pre_bit_count - parms->stats.prev[layer].pre_bit_count; if (!d) return -1; n = parms->stats.cur[layer].pre_bit_error - parms->stats.prev[layer].pre_bit_error; return ((float)n)/d; } static struct dtv_stats *dvb_fe_retrieve_v5_BER(struct dvb_v5_fe_parms_priv *parms, unsigned layer) { float ber; uint64_t ber64; ber = calculate_postBER(parms, layer); if (ber < 0) return NULL; /* * Put BER into some DVBv3 compat scale. The thing is that DVBv3 has no * defined scale for BER. So, let's use 10^-7. */ ber64 = 10000000 * ber; return dvb_fe_store_stats(parms, DTV_BER, FE_SCALE_COUNTER, layer, ber64); } struct dtv_stats *dvb_fe_retrieve_stats_layer(struct dvb_v5_fe_parms *p, unsigned cmd, unsigned layer) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int i; if (cmd == DTV_BER && parms->p.has_v5_stats) return dvb_fe_retrieve_v5_BER(parms, layer); for (i = 0; i < DTV_NUM_STATS_PROPS; i++) { if (parms->stats.prop[i].cmd != cmd) continue; if (layer >= parms->stats.prop[i].u.st.len) return NULL; return &parms->stats.prop[i].u.st.stat[layer]; } dvb_logerr(_("%s not found on retrieve"), dvb_cmd_name(cmd)); return NULL; } int dvb_fe_retrieve_stats(struct dvb_v5_fe_parms *p, unsigned cmd, uint32_t *value) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dtv_stats *stat; enum fecap_scale_params scale; stat = dvb_fe_retrieve_stats_layer(&parms->p, cmd, 0); if (!stat) { if (parms->p.verbose) dvb_logdbg(_("%s not found on retrieve"), dvb_cmd_name(cmd)); return EINVAL; } scale = stat->scale; if (scale == FE_SCALE_NOT_AVAILABLE) { if (parms->p.verbose) dvb_logdbg(_("%s not available"), dvb_cmd_name(cmd)); return EINVAL; } *value = stat->uvalue; if (parms->p.verbose > 1) dvb_logdbg(_("Stats for %s = %d"), dvb_cmd_name(cmd), *value); return 0; } float dvb_fe_retrieve_ber(struct dvb_v5_fe_parms *p, unsigned layer, enum fecap_scale_params *scale) { struct dvb_v5_fe_parms_priv *parms = (void *)p; float ber; uint32_t ber32; if (parms->p.has_v5_stats) { ber = calculate_postBER(parms, layer); if (ber >= 0) *scale = FE_SCALE_COUNTER; else *scale = FE_SCALE_NOT_AVAILABLE; return ber; } if (layer) { *scale = FE_SCALE_NOT_AVAILABLE; return -1; } if (dvb_fe_retrieve_stats(&parms->p, DTV_BER, &ber32)) *scale = FE_SCALE_NOT_AVAILABLE; else *scale = FE_SCALE_RELATIVE; return ber32; } float dvb_fe_retrieve_per(struct dvb_v5_fe_parms *p, unsigned layer) { struct dvb_v5_fe_parms_priv *parms = (void *)p; uint64_t n, d; if (!parms->stats.has_per[layer]) { return -1; } d = parms->stats.cur[layer].block_count - parms->stats.prev[layer].block_count; if (!d) { return -1; } n = parms->stats.cur[layer].block_error - parms->stats.prev[layer].block_error; return ((float)n)/d; } struct cnr_to_qual_s { uint32_t modulation; /* use QAM_AUTO if it doesn't matter */ uint32_t fec; /* Use FEC_NONE if it doesn't matter */ float cnr_ok, cnr_good; }; static enum dvb_quality cnr_arr_to_qual(uint32_t modulation, uint32_t fec, float cnr, struct cnr_to_qual_s *arr, unsigned len) { int i; for (i = 0; i < len; i++) { if (modulation == arr[i].modulation) { if (cnr < arr[i].cnr_ok) return DVB_QUAL_POOR; else if (cnr < arr[i].cnr_good) return DVB_QUAL_OK; else return DVB_QUAL_GOOD; } } return DVB_QUAL_UNKNOWN; } /* Source: http://www.maxpeak.tv/articles/Maxpeak_Article_2.pdf */ struct cnr_to_qual_s dvb_c_cnr_2_qual[] = { { QAM_256, FEC_NONE, 34., 38.}, { QAM_64, FEC_NONE, 30., 34.}, }; /* * Base reference: http://www.maxpeak.tv/articles/Maxpeak_Article_2.pdf * Used http://www.nws.noaa.gov/noaaport/html/DVB%20S2%20Satellite%20Receiver%20Specs.pdf * to estimate the missing FEC's */ struct cnr_to_qual_s dvb_s_cnr_2_qual[] = { { QPSK, FEC_1_2, 7., 10.}, { QPSK, FEC_2_3, 9., 12.}, { QPSK, FEC_3_4, 10., 13.}, { QPSK, FEC_5_6, 11., 14.}, { QPSK, FEC_7_8, 12., 15.}, }; struct cnr_to_qual_s dvb_s2_cnr_2_qual[] = { { QPSK, FEC_1_2, 9., 12.}, { QPSK, FEC_2_3, 11., 14.}, { QPSK, FEC_3_4, 12., 15.}, { QPSK, FEC_5_6, 12., 15.}, { QPSK, FEC_8_9, 13., 16.}, { QPSK, FEC_9_10, 13.5, 16.5}, { PSK_8, FEC_2_3, 14.5, 17.5}, { PSK_8, FEC_3_4, 16., 19.}, { PSK_8, FEC_5_6, 17.5, 20.5}, { PSK_8, FEC_8_9, 19., 22.}, }; /* * Minimum values from ARIB STD-B21 for DVB_QUAL_OK. * As ARIB doesn't define a max value, assume +2dB for DVB_QUAL_GOOD */ static struct cnr_to_qual_s isdb_t_cnr_2_qual[] = { { DQPSK, FEC_1_2, 6.2, 8.2}, { DQPSK, FEC_2_3, 7.7, 9.7}, { DQPSK, FEC_3_4, 8.7, 10.7}, { DQPSK, FEC_5_6, 9.6, 11.6}, { DQPSK, FEC_7_8, 10.4, 12.4}, { QPSK, FEC_1_2, 4.9, 6.9}, { QPSK, FEC_2_3, 6.6, 8.6}, { QPSK, FEC_3_4, 7.5, 9.5}, { QPSK, FEC_5_6, 8.5, 10.5}, { QPSK, FEC_7_8, 9.1, 11.5}, { QAM_16, FEC_1_2, 11.5, 13.5}, { QAM_16, FEC_2_3, 13.5, 15.5}, { QAM_16, FEC_3_4, 14.6, 16.6}, { QAM_16, FEC_5_6, 15.6, 17.6}, { QAM_16, FEC_7_8, 16.2, 18.2}, { QAM_64, FEC_1_2, 16.5, 18.5}, { QAM_64, FEC_2_3, 18.7, 21.7}, { QAM_64, FEC_3_4, 20.1, 22.1}, { QAM_64, FEC_5_6, 21.3, 23.3}, { QAM_64, FEC_7_8, 22.0, 24.0}, }; /* * Values obtained from table A.1 of ETSI EN 300 744 v1.6.1 * OK corresponds to Ricean fading; Good to Rayleigh fading */ static struct cnr_to_qual_s dvb_t_cnr_2_qual[] = { { QPSK, FEC_1_2, 4.1, 5.9}, { QPSK, FEC_2_3, 6.1, 9.6}, { QPSK, FEC_3_4, 7.2, 12.4}, { QPSK, FEC_5_6, 8.5, 15.6}, { QPSK, FEC_7_8, 9.2, 17.5}, { QAM_16, FEC_1_2, 9.8, 11.8}, { QAM_16, FEC_2_3, 12.1, 15.3}, { QAM_16, FEC_3_4, 13.4, 18.1}, { QAM_16, FEC_5_6, 14.8, 21.3}, { QAM_16, FEC_7_8, 15.7, 23.6}, { QAM_64, FEC_1_2, 14.0, 16.0}, { QAM_64, FEC_2_3, 19.9, 25.4}, { QAM_64, FEC_3_4, 24.9, 27.9}, { QAM_64, FEC_5_6, 21.3, 23.3}, { QAM_64, FEC_7_8, 22.0, 24.0}, }; static enum dvb_quality dvbv_fe_cnr_to_quality(struct dvb_v5_fe_parms_priv *parms, struct dtv_stats *cnr) { uint32_t modulation, fec; enum dvb_quality qual = DVB_QUAL_UNKNOWN; switch (cnr->scale) { case FE_SCALE_RELATIVE: if (cnr->uvalue == 65535) return DVB_QUAL_GOOD; else if (cnr->uvalue >= 65535 / 2) return DVB_QUAL_OK; else return DVB_QUAL_POOR; return qual; case FE_SCALE_DECIBEL: break; default: return DVB_QUAL_UNKNOWN; } switch (parms->p.current_sys) { case SYS_DVBC_ANNEX_A: case SYS_DVBC_ANNEX_C: dvb_fe_retrieve_parm(&parms->p, DTV_MODULATION, &modulation); if (modulation == QAM_AUTO) modulation = QAM_64; /* Assume worse case */ qual = cnr_arr_to_qual(modulation, FEC_NONE, cnr->svalue, dvb_c_cnr_2_qual, ARRAY_SIZE(dvb_c_cnr_2_qual)); break; case SYS_DVBS: dvb_fe_retrieve_parm(&parms->p, DTV_INNER_FEC, &fec); qual = cnr_arr_to_qual(QPSK, fec, cnr->svalue, dvb_s_cnr_2_qual, ARRAY_SIZE(dvb_s_cnr_2_qual)); break; case SYS_DVBS2: dvb_fe_retrieve_parm(&parms->p, DTV_MODULATION, &modulation); dvb_fe_retrieve_parm(&parms->p, DTV_INNER_FEC, &fec); qual = cnr_arr_to_qual(modulation, fec, cnr->svalue, dvb_s2_cnr_2_qual, ARRAY_SIZE(dvb_s_cnr_2_qual)); break; case SYS_ISDBT: dvb_fe_retrieve_parm(&parms->p, DTV_ISDBT_LAYERA_MODULATION, &modulation); dvb_fe_retrieve_parm(&parms->p, DTV_ISDBT_LAYERA_FEC, &fec); if (modulation == QAM_AUTO) modulation = QAM_64; /* Assume worse case */ qual = cnr_arr_to_qual(modulation, fec, cnr->svalue, isdb_t_cnr_2_qual, ARRAY_SIZE(isdb_t_cnr_2_qual)); break; case SYS_DVBT: dvb_fe_retrieve_parm(&parms->p, DTV_MODULATION, &modulation); dvb_fe_retrieve_parm(&parms->p, DTV_CODE_RATE_LP, &fec); qual = cnr_arr_to_qual(modulation, fec, cnr->svalue, dvb_t_cnr_2_qual, ARRAY_SIZE(isdb_t_cnr_2_qual)); break; case SYS_DVBT2: case SYS_TURBO: case SYS_ISDBS: case SYS_DSS: case SYS_DTMB: case SYS_ATSC: case SYS_ATSCMH: case SYS_DVBC_ANNEX_B: default: /* Quality unknown */ break; } return qual; }; enum dvb_quality dvb_fe_retrieve_quality(struct dvb_v5_fe_parms *p, unsigned layer) { struct dvb_v5_fe_parms_priv *parms = (void *)p; float ber, per; struct dtv_stats *cnr; enum dvb_quality qual = DVB_QUAL_UNKNOWN; per = dvb_fe_retrieve_per(&parms->p, layer); if (per >= 0) { if (per > 1e-6) qual = DVB_QUAL_POOR; else if (per > 1e-7) return DVB_QUAL_OK; else return DVB_QUAL_GOOD; } ber = dvb_fe_retrieve_per(&parms->p, layer); if (ber >= 0) { if (ber > 1e-3) /* FIXME: good enough???? */ return DVB_QUAL_POOR; if (ber <= 2e-4) /* BER = 10^-11 at TS */ return DVB_QUAL_GOOD; else qual = DVB_QUAL_OK; /* OK or good */ } cnr = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_CNR, layer); if (cnr) dvbv_fe_cnr_to_quality(parms, cnr); return qual; } static void dvb_fe_update_counters(struct dvb_v5_fe_parms_priv *parms) { struct dtv_stats *error, *count; int i; for (i = 0; i < MAX_DTV_STATS; i++) { count = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_POST_TOTAL_BIT_COUNT, i); if (count && count->scale != FE_SCALE_NOT_AVAILABLE) { error = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_POST_ERROR_BIT_COUNT, i); if (!error || error->scale == FE_SCALE_NOT_AVAILABLE) { parms->stats.has_post_ber[i] = 0; } else if(count->uvalue != parms->stats.cur[i].post_bit_count) { parms->stats.prev[i].post_bit_count = parms->stats.cur[i].post_bit_count; parms->stats.cur[i].post_bit_count = count->uvalue; parms->stats.prev[i].post_bit_error = parms->stats.cur[i].post_bit_error; parms->stats.cur[i].post_bit_error = error->uvalue; parms->stats.has_post_ber[i] = 1; } } else parms->stats.has_post_ber[i] = 0; count = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_PRE_TOTAL_BIT_COUNT, i); if (count && count->scale != FE_SCALE_NOT_AVAILABLE) { error = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_PRE_ERROR_BIT_COUNT, i); if (!error || error->scale == FE_SCALE_NOT_AVAILABLE) { parms->stats.has_pre_ber[i] = 0; } else if(count->uvalue != parms->stats.cur[i].pre_bit_count) { parms->stats.prev[i].pre_bit_count = parms->stats.cur[i].pre_bit_count; parms->stats.cur[i].pre_bit_count = count->uvalue; parms->stats.prev[i].pre_bit_error = parms->stats.cur[i].pre_bit_error; parms->stats.cur[i].pre_bit_error = error->uvalue; parms->stats.has_pre_ber[i] = 1; } } else parms->stats.has_pre_ber[i] = 0; count = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_TOTAL_BLOCK_COUNT, i); if (count && count->scale != FE_SCALE_NOT_AVAILABLE) { error = dvb_fe_retrieve_stats_layer(&parms->p, DTV_STAT_ERROR_BLOCK_COUNT, i); if (!error || error->scale == FE_SCALE_NOT_AVAILABLE) { parms->stats.has_per[i] = 0; } else if (count->uvalue != parms->stats.cur[i].block_count) { parms->stats.prev[i].block_count = parms->stats.cur[i].block_count; parms->stats.cur[i].block_count = count->uvalue; parms->stats.prev[i].block_error = parms->stats.cur[i].block_error; parms->stats.cur[i].block_error = error->uvalue; parms->stats.has_per[i] = 1; } } else parms->stats.has_per[i] = 0; } } int dvb_fe_get_stats(struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; fe_status_t status = 0; uint32_t ber= 0, ucb = 0; uint16_t strength = 0, snr = 0; int i; enum fecap_scale_params scale; if (xioctl(parms->fd, FE_READ_STATUS, &status) == -1) { dvb_perror("FE_READ_STATUS"); return EINVAL; } dvb_fe_store_stats(parms, DTV_STATUS, FE_SCALE_RELATIVE, 0, status); /* if lock has obtained, get DVB parameters */ if (status != parms->stats.prev_status) { if ((status & FE_HAS_LOCK) && parms->stats.prev_status != status) dvb_fe_get_parms(&parms->p); parms->stats.prev_status = status; } if (parms->p.has_v5_stats) { struct dtv_properties props; props.num = DTV_NUM_KERNEL_STATS; props.props = parms->stats.prop; /* Do a DVBv5.10 stats call */ if (ioctl(parms->fd, FE_GET_PROPERTY, &props) == -1) { if (errno == EAGAIN) return 0; goto dvbv3_fallback; } /* * All props with len=0 mean that this device doesn't have any * dvbv5 stats. Try the legacy stats instead. */ for (i = 0; i < props.num; i++) if (parms->stats.prop[i].u.st.len) break; if (i == props.num) goto dvbv3_fallback; dvb_fe_update_counters(parms); return 0; } dvbv3_fallback: /* DVB v3 stats */ parms->p.has_v5_stats = 0; if (ioctl(parms->fd, FE_READ_BER, &ber) == -1) scale = FE_SCALE_NOT_AVAILABLE; else scale = FE_SCALE_RELATIVE; /* * BER scale on DVBv3 is not defined - different drivers use * different scales, even weird ones, like multiples of 1/65280 */ dvb_fe_store_stats(parms, DTV_BER, scale, 0, ber); if (ioctl(parms->fd, FE_READ_SIGNAL_STRENGTH, &strength) == -1) scale = FE_SCALE_NOT_AVAILABLE; else scale = FE_SCALE_RELATIVE; dvb_fe_store_stats(parms, DTV_STAT_SIGNAL_STRENGTH, scale, 0, strength); if (ioctl(parms->fd, FE_READ_SNR, &snr) == -1) scale = FE_SCALE_NOT_AVAILABLE; else scale = FE_SCALE_RELATIVE; dvb_fe_store_stats(parms, DTV_STAT_CNR, scale, 0, snr); if (ioctl(parms->fd, FE_READ_UNCORRECTED_BLOCKS, &ucb) == -1) scale = FE_SCALE_NOT_AVAILABLE; else scale = FE_SCALE_COUNTER; dvb_fe_store_stats(parms, DTV_STAT_ERROR_BLOCK_COUNT, scale, 0, snr); if (parms->p.verbose > 1) { dvb_log(_("Status: ")); for (i = 0; i < ARRAY_SIZE(fe_status_name); i++) { if (status & fe_status_name[i].idx) dvb_log (" %s", fe_status_name[i].name); } dvb_log(_("BER: %d, Strength: %d, SNR: %d, UCB: %d"), ber, strength, snr, ucb); } return 0; } int dvb_fe_get_event(struct dvb_v5_fe_parms *p) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dvb_frontend_event event; fe_status_t status; int i; if (!parms->p.legacy_fe) { dvb_fe_get_parms(&parms->p); return dvb_fe_get_stats(&parms->p); } if (xioctl(parms->fd, FE_GET_EVENT, &event) == -1) { dvb_perror("FE_GET_EVENT"); return errno; } status = event.status; if (parms->p.verbose > 1) { dvb_log(_("Status: ")); for (i = 0; i < ARRAY_SIZE(fe_status_name); i++) { if (status & fe_status_name[i].idx) dvb_log (" %s", fe_status_name[i].name); } } dvb_fe_store_stats(parms, DTV_STATUS, FE_SCALE_RELATIVE, 0, status); dvb_fe_retrieve_parm(&parms->p, DTV_FREQUENCY, &event.parameters.frequency); dvb_fe_retrieve_parm(&parms->p, DTV_INVERSION, &event.parameters.inversion); switch (parms->p.current_sys) { case SYS_DVBS: dvb_fe_retrieve_parm(&parms->p, DTV_SYMBOL_RATE, &event.parameters.u.qpsk.symbol_rate); dvb_fe_retrieve_parm(&parms->p, DTV_INNER_FEC, &event.parameters.u.qpsk.fec_inner); break; case SYS_DVBC_ANNEX_AC: dvb_fe_retrieve_parm(&parms->p, DTV_SYMBOL_RATE, &event.parameters.u.qam.symbol_rate); dvb_fe_retrieve_parm(&parms->p, DTV_INNER_FEC, &event.parameters.u.qam.fec_inner); dvb_fe_retrieve_parm(&parms->p, DTV_MODULATION, &event.parameters.u.qam.modulation); break; case SYS_ATSC: case SYS_ATSCMH: case SYS_DVBC_ANNEX_B: dvb_fe_retrieve_parm(&parms->p, DTV_MODULATION, &event.parameters.u.vsb.modulation); break; case SYS_DVBT: dvb_fe_retrieve_parm(&parms->p, DTV_BANDWIDTH_HZ, &event.parameters.u.ofdm.bandwidth); dvb_fe_retrieve_parm(&parms->p, DTV_CODE_RATE_HP, &event.parameters.u.ofdm.code_rate_HP); dvb_fe_retrieve_parm(&parms->p, DTV_CODE_RATE_LP, &event.parameters.u.ofdm.code_rate_LP); dvb_fe_retrieve_parm(&parms->p, DTV_MODULATION, &event.parameters.u.ofdm.constellation); dvb_fe_retrieve_parm(&parms->p, DTV_TRANSMISSION_MODE, &event.parameters.u.ofdm.transmission_mode); dvb_fe_retrieve_parm(&parms->p, DTV_GUARD_INTERVAL, &event.parameters.u.ofdm.guard_interval); dvb_fe_retrieve_parm(&parms->p, DTV_HIERARCHY, &event.parameters.u.ofdm.hierarchy_information); break; default: return EINVAL; } return dvb_fe_get_stats(&parms->p); } int dvb_fe_snprintf_eng(char *buf, int len, float val) { int digits = 3; int exp, signal = 1; /* If value is zero, nothing to do */ if (val == 0.) return snprintf(buf, len, " 0"); /* Take the absolute value */ if (val < 0.) { signal = -1; val = -val; } /* * Converts the number into an expoent and a * value between 0 and 1000, exclusive */ exp = (int)log10(val); if (exp > 0) exp = (exp / 3) * 3; else exp = (-exp + 3) / 3 * (-3); val *= pow(10, -exp); if (val >= 1000.) { val /= 1000.0; exp += 3; } else if(val >= 100.0) digits -= 2; else if(val >= 10.0) digits -= 1; if (exp) { if (signal > 0) return snprintf(buf, len, " %.*fx10^%d", digits - 1, val, exp); else return snprintf(buf, len, " -%.*fx10^%d", digits - 1, val, exp); } else { if (signal > 0) return snprintf(buf, len, " %.*f", digits - 1, val); else return snprintf(buf, len, " -%.*f", digits - 1, val); } } static char *sig_bits[7] = { [0] = N_("RF"), [1] = N_("Carrier"), [2] = N_("Viterbi"), [3] = N_("Sync"), [4] = N_("Lock"), [5] = N_("Timeout"), [6] = N_("Reinit"), }; static char *qual_name[] = { [DVB_QUAL_POOR] = N_("Poor"), [DVB_QUAL_OK] = N_("Ok"), [DVB_QUAL_GOOD] = N_("Good"), }; int dvb_fe_snprintf_stat(struct dvb_v5_fe_parms *p, uint32_t cmd, char *display_name, int layer, char **buf, int *len, int *show_layer_name) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dtv_stats *stat = NULL; enum dvb_quality qual = DVB_QUAL_UNKNOWN; enum fecap_scale_params scale; float val = -1; int initial_len = *len; int size, i; /* Print status, if layer == 0, as there is only global status */ if (cmd == DTV_STATUS) { fe_status_t status; if (layer) return 0; if (dvb_fe_retrieve_stats(&parms->p, DTV_STATUS, &status)) { dvb_logerr (_("Error: no adapter status")); return -1; } if (display_name) { size = snprintf(*buf, *len, " %s=", display_name); *buf += size; *len -= size; } /* Get the name of the highest status bit */ for (i = ARRAY_SIZE(sig_bits) - 1; i >= 0 ; i--) { if ((1 << i) & status) { size = snprintf(*buf, *len, _("%-7s"), _(sig_bits[i])); *buf += size; *len -= size; break; } } if (i < 0) { size = snprintf(*buf, *len, _("%7s"), ""); *buf += size; *len -= size; } /* Add the status bits */ size = snprintf(*buf, *len, "(0x%02x)", status); *buf += size; *len -= size; return initial_len - *len; } /* Retrieve the statistics */ switch (cmd) { case DTV_PRE_BER: val = calculate_preBER(parms, layer); if (val < 0) return 0; scale = FE_SCALE_COUNTER; break; case DTV_BER: val = dvb_fe_retrieve_ber(&parms->p, layer, &scale); if (scale == FE_SCALE_NOT_AVAILABLE) return 0; break; case DTV_PER: val = dvb_fe_retrieve_per(&parms->p, layer); if (val < 0) return 0; scale = FE_SCALE_COUNTER; break; case DTV_QUALITY: qual = dvb_fe_retrieve_quality(&parms->p, layer); if (qual == DVB_QUAL_UNKNOWN) return 0; break; default: stat = dvb_fe_retrieve_stats_layer(&parms->p, cmd, layer); if (!stat || stat->scale == FE_SCALE_NOT_AVAILABLE) return 0; } /* If requested, prints the layer name */ if (*show_layer_name && layer) { size = snprintf(*buf, *len, _(" Layer %c:"), 'A' + layer - 1); *buf += size; *len -= size; *show_layer_name = 0; } if (display_name) { size = snprintf(*buf, *len, " %s=", display_name); *buf += size; *len -= size; } /* Quality measure */ if (qual != DVB_QUAL_UNKNOWN) { size = snprintf(*buf, *len, " %-4s", _(qual_name[qual])); *buf += size; *len -= size; return initial_len - *len; } /* Special case: float point measures like BER/PER */ if (!stat) { switch (scale) { case FE_SCALE_RELATIVE: size = snprintf(*buf, *len, " %u", (unsigned int)val); break; case FE_SCALE_COUNTER: size = dvb_fe_snprintf_eng(*buf, *len, val); break; default: size = 0; } *buf += size; *len -= size; return initial_len - *len; } /* Prints the scale */ switch (stat->scale) { case FE_SCALE_DECIBEL: if (cmd == DTV_STAT_SIGNAL_STRENGTH) size = snprintf(*buf, *len, " %.2fdBm", stat->svalue / 1000.); else size = snprintf(*buf, *len, " %.2fdB", stat->svalue / 1000.); break; case FE_SCALE_RELATIVE: size = snprintf(*buf, *len, " %3.2f%%", (100 * stat->uvalue) / 65535.); break; case FE_SCALE_COUNTER: size = snprintf(*buf, *len, " %" PRIu64, (uint64_t)stat->uvalue); break; default: size = 0; } *buf += size; *len -= size; return initial_len - *len; } /* * Implement SEC/LNB/DISEqC specific functions * For now, DVBv5 API doesn't support those commands. So, use the DVBv3 * version. */ int dvb_fe_sec_voltage(struct dvb_v5_fe_parms *p, int on, int v18) { struct dvb_v5_fe_parms_priv *parms = (void *)p; fe_sec_voltage_t v; int rc; if (!on) { v = SEC_VOLTAGE_OFF; if (parms->p.verbose) dvb_log(_("DiSEqC VOLTAGE: OFF")); } else { v = v18 ? SEC_VOLTAGE_18 : SEC_VOLTAGE_13; if (parms->p.verbose) dvb_log(_("DiSEqC VOLTAGE: %s"), v18 ? "18" : "13"); } rc = xioctl(parms->fd, FE_SET_VOLTAGE, v); if (rc == -1) dvb_perror("FE_SET_VOLTAGE"); return rc; } int dvb_fe_sec_tone(struct dvb_v5_fe_parms *p, fe_sec_tone_mode_t tone) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int rc; if (parms->p.verbose) dvb_log( _("DiSEqC TONE: %s"), fe_tone_name[tone] ); rc = xioctl(parms->fd, FE_SET_TONE, tone); if (rc == -1) dvb_perror("FE_SET_TONE"); return rc; } int dvb_fe_lnb_high_voltage(struct dvb_v5_fe_parms *p, int on) { struct dvb_v5_fe_parms_priv *parms = (void *)p; int rc; if (on) on = 1; if (parms->p.verbose) dvb_log( _("DiSEqC HIGH LNB VOLTAGE: %s"), on ? _("ON") : _("OFF") ); rc = xioctl(parms->fd, FE_ENABLE_HIGH_LNB_VOLTAGE, on); if (rc == -1) dvb_perror("FE_ENABLE_HIGH_LNB_VOLTAGE"); return rc; } int dvb_fe_diseqc_burst(struct dvb_v5_fe_parms *p, int mini_b) { struct dvb_v5_fe_parms_priv *parms = (void *)p; fe_sec_mini_cmd_t mini; int rc; mini = mini_b ? SEC_MINI_B : SEC_MINI_A; if (parms->p.verbose) dvb_log( _("DiSEqC BURST: %s"), mini_b ? "SEC_MINI_B" : "SEC_MINI_A" ); rc = xioctl(parms->fd, FE_DISEQC_SEND_BURST, mini); if (rc == -1) dvb_perror("FE_DISEQC_SEND_BURST"); return rc; } int dvb_fe_diseqc_cmd(struct dvb_v5_fe_parms *p, const unsigned len, const unsigned char *buf) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dvb_diseqc_master_cmd msg; int rc; if (len > 6) return -EINVAL; msg.msg_len = len; memcpy(msg.msg, buf, len); if (parms->p.verbose) { int i; char log[len * 3 + 20], *p = log; p += sprintf(p, _("DiSEqC command: ")); for (i = 0; i < len; i++) p += sprintf (p, "%02x ", buf[i]); dvb_log("%s", log); } rc = xioctl(parms->fd, FE_DISEQC_SEND_MASTER_CMD, &msg); if (rc == -1) dvb_perror("FE_DISEQC_SEND_MASTER_CMD"); return rc; } int dvb_fe_diseqc_reply(struct dvb_v5_fe_parms *p, unsigned *len, char *buf, int timeout) { struct dvb_v5_fe_parms_priv *parms = (void *)p; struct dvb_diseqc_slave_reply reply; int rc; if (*len > 4) *len = 4; reply.timeout = timeout; reply.msg_len = *len; if (parms->p.verbose) dvb_log("DiSEqC FE_DISEQC_RECV_SLAVE_REPLY"); rc = xioctl(parms->fd, FE_DISEQC_RECV_SLAVE_REPLY, reply); if (rc == -1) { dvb_perror("FE_DISEQC_RECV_SLAVE_REPLY"); return rc; } *len = reply.msg_len; memcpy(buf, reply.msg, reply.msg_len); return 0; } int dvb_fe_set_default_country(struct dvb_v5_fe_parms *p, const char *cc) { struct dvb_v5_fe_parms_priv *parms = (void *)p; if (!cc) { parms->country = dvb_guess_user_country(); if (parms->p.verbose) { if (parms->country != COUNTRY_UNKNOWN) dvb_log(_("Assuming you're in %s.\n"), dvb_country_to_2letters(parms->country)); else dvb_log(_("Failed to guess country from the current locale setting.\n")); } return 0; } parms->country = dvb_country_a2_to_id(cc); return (parms->country == COUNTRY_UNKNOWN) ? -EINVAL : 0; }