blob: ea296e30f811ecebeac224cf836ed78ef46d18af [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
#include "common.h"
static int dvb_fe_set_properties(int fefd, int sys, int mod, int ifreq, int sr,
int fec, int voltage, int tone) {
int num = 0;
struct dtv_properties ps;
struct dtv_property props[DTV_IOCTL_MAX_MSGS];
int pairs[][2] = {
{DTV_FREQUENCY, ifreq},
{DTV_MODULATION, mod},
{DTV_INVERSION, INVERSION_AUTO},
{DTV_SYMBOL_RATE, sr},
{DTV_INNER_FEC, fec},
{DTV_VOLTAGE, voltage},
{DTV_TONE, tone},
{DTV_PILOT, PILOT_AUTO},
{DTV_ROLLOFF, ROLLOFF_AUTO},
{DTV_DELIVERY_SYSTEM, sys},
{DTV_TUNE, 1},
};
for (num = 0; num < ARRAY_SIZE(pairs); num++) {
props[num].cmd = pairs[num][0];
props[num].u.data = pairs[num][1];
}
ps.num = num;
ps.props = props;
return ioctl(fefd, FE_SET_PROPERTY, &ps);
}
// Returns 0 on timeout, 1 on event, and < 0 on error.
static int dvb_fe_get_event(int fefd, struct dvb_frontend_event* ev, int timeout) {
int err;
struct pollfd fds = {.fd = fefd, .events = POLLPRI};
if (!ev) {
return -EINVAL;
}
err = poll(&fds, 1, timeout);
if (err > 0) {
if ((fds.revents & POLLPRI) != 0) {
err = ioctl(fefd, FE_GET_EVENT, ev);
if (err >= 0) {
return 1;
}
}
return 0;
}
return err;
}
static int str2fec(const char* s) {
int fec = FEC_NONE;
if (strcasecmp(s, "none") == 0) {
fec = FEC_NONE;
} else if (strcasecmp(s, "auto") == 0) {
fec = FEC_AUTO;
} else {
int fec = strtol(s, NULL, 0);
switch (fec) {
case 12:
fec = FEC_1_2;
break;
case 23:
fec = FEC_2_3;
break;
case 34:
fec = FEC_3_4;
break;
case 45:
fec = FEC_4_5;
break;
case 56:
fec = FEC_5_6;
break;
case 67:
fec = FEC_6_7;
break;
case 78:
fec = FEC_7_8;
break;
case 35:
fec = FEC_3_5;
break;
case 910:
fec = FEC_9_10;
break;
default:
fec = FEC_AUTO;
}
}
return fec;
}
static struct {
const char* name;
fe_delivery_system_t system;
} system_map[] = {
{"atsc", SYS_ATSC},
{"cmmb", SYS_CMMB},
{"dab", SYS_DAB},
{"dss", SYS_DSS},
{"dvbc_annex_ac", SYS_DVBC_ANNEX_AC},
{"dvbc_annex_b", SYS_DVBC_ANNEX_B},
{"dvbh", SYS_DVBH},
{"dvbs", SYS_DVBS},
{"dvbs2", SYS_DVBS2},
{"dvbt", SYS_DVBT},
{"dvbt2", SYS_DVBT2},
{"isdbc", SYS_ISDBC},
{"isdbs", SYS_ISDBS},
{"isdbt", SYS_ISDBT},
{"turbo", SYS_TURBO},
};
static fe_delivery_system_t str2system(const char* s) {
int i;
for (i = 0; i < ARRAY_SIZE(system_map); i++) {
if (strcasecmp(s, system_map[i].name) == 0) {
return system_map[i].system;
}
}
return SYS_UNDEFINED;
}
static struct {
const char* name;
fe_modulation_t modulation;
} modulation_map[] = {
{"apsk16", APSK_16},
{"apsk32", APSK_32},
{"dqpsk", DQPSK},
{"psk8", PSK_8},
{"qam128", QAM_128},
{"qam16", QAM_16},
{"qam256", QAM_256},
{"qam32", QAM_32},
{"qam64", QAM_64},
{"qamauto", QAM_AUTO},
{"qpsk", QPSK},
{"vsb16", VSB_16},
{"vsb8", VSB_8},
};
static fe_modulation_t str2modulation(const char* s) {
int i;
for (i = 0; i < ARRAY_SIZE(modulation_map); i++) {
if (strcasecmp(s, modulation_map[i].name) == 0) {
return modulation_map[i].modulation;
}
}
return QPSK;
}
// Return 1 if locked and 0 otherwise.
static int wait_for_lock(int fefd, int timeout) {
int locked = 0;
int64_t t0 = time_ms();
struct dvb_frontend_event ev;
while (1) {
int err = dvb_fe_get_event(fefd, &ev, 100);
if (err < 0) {
if (EOVERFLOW == errno) {
continue;
}
perror("dvb_fe_get_event");
break;
}
if (err > 0 && (ev.status & FE_HAS_LOCK) != 0) {
printf("Status %#x Locked!\n", ev.status);
locked = 1;
break;
}
if ((time_ms() - t0) > timeout) {
printf("Status %#x No lock!\n", ev.status);
break;
}
}
return locked;
}
static void usage(const char* prog) {
fprintf(stderr, "Usage: %s [options]\n", prog);
fprintf(stderr, " -a Adapter Adapter device (default 0)\n");
fprintf(stderr, " -d Device Front end device (default 0)\n");
fprintf(stderr, " -s System Delivery system (default DVBS2)\n");
fprintf(stderr, " [atsc cmmb dab dss dvbc_annex_ac]\n");
fprintf(stderr, " [dvbc_annex_b dvbh dvbs dvbs2 dvbt]\n");
fprintf(stderr, " [dvbt2 isdbc isdbs isdbt turbo]\n");
fprintf(stderr, " -m Mod Modulation (default psk8)\n");
fprintf(stderr, " [apsk16 apsk32 dqpsk psk8 qpsk vsb8 vsb16]\n");
fprintf(stderr, " [qam16 qam32 qam64 qam128 qam256 qamauto]\n");
fprintf(stderr, " -i Freq Intermediate frequency in kHz (required)\n");
fprintf(stderr, " -r Rate Symbol rate in 1000's (required)\n");
fprintf(stderr, " -c FEC Forward Error Correction code\n");
fprintf(stderr, " [none auto 12 23 34 35 45 56 67 78 910]\n");
fprintf(stderr, " -p <v|h> Polarization voltage (default off)\n");
fprintf(stderr, " -t Turn on 22kHz tone\n");
fprintf(stderr, " -w timeout Milliseconds to wait for lock\n");
fprintf(stderr, " -x Exit after tuning\n");
exit(EXIT_FAILURE);
}
int main(int argc, char** argv) {
int err;
int opt;
int adapter = 0;
int ifreq_khz = 0;
int mod = PSK_8;
int sr_k = 0;
int fec = FEC_AUTO;
int voltage = SEC_VOLTAGE_OFF;
int tone = SEC_TONE_OFF;
int dev = 0;
int fefd = 0;
int required_args = 0;
int loop = 1;
int timeout = 2000;
int delivery_sys = SYS_DVBS2;
while ((opt = getopt(argc, argv, "a:d:i:p:r:c:w:s:m:txh")) != -1) {
switch (opt) {
case 'a':
adapter = atoi(optarg);
break;
case 'd':
dev = atoi(optarg);
break;
case 'i':
ifreq_khz = atoi(optarg);
required_args++;
break;
case 'r':
sr_k = atoi(optarg);
required_args++;
break;
case 'c':
fec = str2fec(optarg);
break;
case 'p': {
char p = optarg[0];
if (p == 'h' || p == 'H') {
voltage = SEC_VOLTAGE_18;
} else if (p == 'v' || p == 'V') {
voltage = SEC_VOLTAGE_13;
}
break;
}
case 't':
tone = SEC_TONE_ON;
break;
case 'w': {
int t = atoi(optarg);
if (t > 0) {
timeout = t;
}
break;
}
case 's':
delivery_sys = str2system(optarg);
break;
case 'm':
mod = str2modulation(optarg);
break;
case 'x':
loop = 0;
break;
default:
usage(argv[0]);
}
}
if (required_args < 2) {
usage(argv[0]);
}
fefd = dvb_open(adapter, dev, "frontend", 0);
if (fefd < 0) {
return 1;
}
err = dvb_fe_set_properties(fefd, delivery_sys, mod, ifreq_khz,
sr_k*1000, fec, voltage, tone);
if (err < 0) {
perror("dvb_fe_set_properties");
}
if (err == 0) {
// Check lock status at least once.
do {
if (wait_for_lock(fefd, timeout)) {
break;
}
} while (loop);
// Loop to keep driver active.
while (loop) {
struct dvb_frontend_event ev;
dvb_fe_get_event(fefd, &ev, 1000);
}
}
close(fefd);
return err;
}