1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <errno.h>
20#include <unistd.h>
21#include <limits.h>
22#include <string.h>
23#include <fcntl.h>
24#include <sys/poll.h>
25#include <sys/ioctl.h>
26#include <linux/dvb/dmx.h>
27#include <linux/dvb/frontend.h>
28
29#define LOG_TAG "DvbManager"
30#include "logging.h"
31
32#include "DvbManager.h"
33
34static double currentTimeMillis() {
35    struct timeval tv;
36    gettimeofday(&tv, (struct timezone *) NULL);
37    return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
38}
39
40DvbManager::DvbManager(JNIEnv *env, jobject)
41        : mFeFd(-1),
42          mDemuxFd(-1),
43          mDvrFd(-1),
44          mPatFilterFd(-1),
45          mFeHasLock(false),
46          mHasPendingTune(false) {
47    jclass clazz = env->FindClass(
48        "com/android/tv/tuner/TunerHal");
49    mOpenDvbFrontEndMethodID = env->GetMethodID(
50        clazz, "openDvbFrontEndFd", "()I");
51    mOpenDvbDemuxMethodID = env->GetMethodID(
52        clazz, "openDvbDemuxFd", "()I");
53    mOpenDvbDvrMethodID = env->GetMethodID(
54        clazz, "openDvbDvrFd", "()I");
55}
56
57DvbManager::~DvbManager() {
58    reset();
59}
60
61bool DvbManager::isFeLocked() {
62    struct pollfd pollFd;
63    pollFd.fd = mFeFd;
64    pollFd.events = POLLIN;
65    pollFd.revents = 0;
66    int poll_result = poll(&pollFd, NUM_POLLFDS, FE_POLL_TIMEOUT_MS);
67    if (poll_result > 0 && (pollFd.revents & POLLIN)) {
68        struct dvb_frontend_event kevent;
69        memset(&kevent, 0, sizeof(kevent));
70        if (ioctl(mFeFd, FE_GET_EVENT, &kevent) == 0) {
71            return (kevent.status & FE_HAS_LOCK);
72        }
73    }
74    return false;
75}
76
77int DvbManager::tune(JNIEnv *env, jobject thiz,
78        const int frequency, const char *modulationStr, int timeout_ms) {
79    resetExceptFe();
80
81    struct dvb_frontend_parameters feParams;
82    memset(&feParams, 0, sizeof(struct dvb_frontend_parameters));
83    feParams.frequency = frequency;
84    if (strcmp(modulationStr, "8VSB") == 0) {
85        feParams.u.vsb.modulation = VSB_8;
86    } else if (strcmp(modulationStr, "QAM256") == 0) {
87        feParams.u.vsb.modulation = QAM_256;
88    } else {
89        ALOGE("Unrecognized modulation mode : %s", modulationStr);
90        return -1;
91    }
92
93    if (mHasPendingTune) {
94        return -1;
95    }
96    if (openDvbFe(env, thiz) != 0) {
97        return -1;
98    }
99
100    feParams.inversion = INVERSION_AUTO;
101    /* Check frontend capability */
102    struct dvb_frontend_info feInfo;
103    if (ioctl(mFeFd, FE_GET_INFO, &feInfo) != -1) {
104        if (!(feInfo.caps & FE_CAN_INVERSION_AUTO)) {
105            // FE can't do INVERSION_AUTO, trying INVERSION_OFF instead
106            feParams.inversion = INVERSION_OFF;
107        }
108    }
109
110    if (ioctl(mFeFd, FE_SET_FRONTEND, &feParams) != 0) {
111        ALOGD("Can't set Frontend : %s", strerror(errno));
112        return -1;
113    }
114
115    int lockSuccessCount = 0;
116    double tuneClock = currentTimeMillis();
117    while (currentTimeMillis() - tuneClock < timeout_ms) {
118        if (mHasPendingTune) {
119            // Return 0 here since we already call FE_SET_FRONTEND, and return due to having pending
120            // tune request. And the frontend setting could be successful.
121            mFeHasLock = true;
122            return 0;
123        }
124        bool lockStatus = isFeLocked();
125        if (lockStatus) {
126            lockSuccessCount++;
127        } else {
128            lockSuccessCount = 0;
129        }
130        ALOGI("Lock status : %s", lockStatus ? "true" : "false");
131        if (lockSuccessCount >= FE_CONSECUTIVE_LOCK_SUCCESS_COUNT) {
132            mFeHasLock = true;
133            openDvbDvr(env, thiz);
134            return 0;
135        }
136    }
137
138    return -1;
139}
140
141int DvbManager::stopTune() {
142    reset();
143    usleep(DVB_TUNE_STOP_DELAY_MS);
144    return 0;
145}
146
147int DvbManager::openDvbFeFromSystemApi(JNIEnv *env, jobject thiz) {
148    int fd = (int) env->CallIntMethod(thiz, mOpenDvbFrontEndMethodID);
149    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
150    return fd;
151}
152
153int DvbManager::openDvbDemuxFromSystemApi(JNIEnv *env, jobject thiz) {
154    int fd = (int) env->CallIntMethod(thiz, mOpenDvbDemuxMethodID);
155    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
156    return fd;
157}
158
159int DvbManager::openDvbDvrFromSystemApi(JNIEnv *env, jobject thiz) {
160    int fd = (int) env->CallIntMethod(thiz, mOpenDvbDvrMethodID);
161    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
162    return fd;
163}
164
165int DvbManager::openDvbFe(JNIEnv *env, jobject thiz) {
166    if (mFeFd == -1) {
167        if ((mFeFd = openDvbFeFromSystemApi(env, thiz)) < 0) {
168            ALOGD("Can't open FE file : %s", strerror(errno));
169            return -1;
170        }
171    }
172
173    struct dvb_frontend_info info;
174    if (ioctl(mFeFd, FE_GET_INFO, &info) == 0) {
175        const char *types;
176        switch (info.type) {
177            case FE_QPSK:
178                types = "DVB-S";
179                break;
180            case FE_QAM:
181                types = "DVB-C";
182                break;
183            case FE_OFDM:
184                types = "DVB-T";
185                break;
186            case FE_ATSC:
187                types = "ATSC";
188                break;
189            default:
190                types = "Unknown";
191        }
192        ALOGI("Using frontend \"%s\", type %s", info.name, types);
193    }
194    return 0;
195}
196
197int DvbManager::startTsPidFilter(JNIEnv *env, jobject thiz, int pid, int filterType) {
198    Mutex::Autolock autoLock(mFilterLock);
199
200    if (mPidFilters.find(pid) != mPidFilters.end() || (mPatFilterFd != -1 && pid == PAT_PID)) {
201        return 0;
202    }
203
204    if (mHasPendingTune) {
205        return -1;
206    }
207
208    int demuxFd;
209    if ((demuxFd = openDvbDemuxFromSystemApi(env, thiz)) < 0) {
210        ALOGD("Can't open DEMUX file : %s", strerror(errno));
211        return -1;
212    }
213
214    struct dmx_pes_filter_params filter;
215    memset(&filter, 0, sizeof(filter));
216    filter.pid = pid;
217    filter.input = DMX_IN_FRONTEND;
218    switch (filterType) {
219        case FILTER_TYPE_AUDIO:
220            filter.pes_type = DMX_PES_AUDIO;
221            break;
222        case FILTER_TYPE_VIDEO:
223            filter.pes_type = DMX_PES_VIDEO;
224            break;
225        case FILTER_TYPE_PCR:
226            filter.pes_type = DMX_PES_PCR;
227            break;
228        default:
229            filter.pes_type = DMX_PES_OTHER;
230            break;
231    }
232    filter.output = DMX_OUT_TS_TAP;
233    filter.flags |= (DMX_CHECK_CRC | DMX_IMMEDIATE_START);
234
235    // create a pes filter
236    if (ioctl(demuxFd, DMX_SET_PES_FILTER, &filter)) {
237        close(demuxFd);
238        return -1;
239    }
240
241    if (pid != PAT_PID) {
242        mPidFilters.insert(std::pair<int, int>(pid, demuxFd));
243    } else {
244        mPatFilterFd = demuxFd;
245    }
246
247    return 0;
248}
249
250void DvbManager::closeAllDvbPidFilter() {
251    // Close all dvb pid filters except PAT filter to maintain the opening status of the device.
252    Mutex::Autolock autoLock(mFilterLock);
253
254    for (std::map<int, int>::iterator it(mPidFilters.begin());
255                it != mPidFilters.end(); it++) {
256        close(it->second);
257    }
258    mPidFilters.clear();
259    // Close mDvrFd to make sure there is not buffer from previous channel left.
260    closeDvbDvr();
261}
262
263void DvbManager::closePatFilter() {
264    Mutex::Autolock autoLock(mFilterLock);
265
266    if (mPatFilterFd != -1) {
267        close(mPatFilterFd);
268        mPatFilterFd = -1;
269    }
270}
271
272int DvbManager::openDvbDvr(JNIEnv *env, jobject thiz) {
273    if ((mDvrFd = openDvbDvrFromSystemApi(env, thiz)) < 0) {
274        ALOGD("Can't open DVR file : %s", strerror(errno));
275        return -1;
276    }
277    return 0;
278}
279
280void DvbManager::closeDvbFe() {
281    if (mFeFd != -1) {
282        close(mFeFd);
283        mFeFd = -1;
284    }
285}
286
287void DvbManager::closeDvbDvr() {
288    if (mDvrFd != -1) {
289        close(mDvrFd);
290        mDvrFd = -1;
291    }
292}
293
294void DvbManager::reset() {
295    mFeHasLock = false;
296    closeDvbDvr();
297    closeAllDvbPidFilter();
298    closePatFilter();
299    closeDvbFe();
300}
301
302void DvbManager::resetExceptFe() {
303    mFeHasLock = false;
304    closeDvbDvr();
305    closeAllDvbPidFilter();
306    closePatFilter();
307}
308
309int DvbManager::readTsStream(JNIEnv *env, jobject thiz,
310        uint8_t *tsBuffer, int tsBufferSize, int timeout_ms) {
311    if (!mFeHasLock) {
312        usleep(DVB_ERROR_RETRY_INTERVAL_MS);
313        return -1;
314    }
315
316    if (mDvrFd == -1) {
317        openDvbDvr(env, thiz);
318    }
319
320    struct pollfd pollFd;
321    pollFd.fd = mDvrFd;
322    pollFd.events = POLLIN|POLLPRI|POLLERR;
323    pollFd.revents = 0;
324    int poll_result = poll(&pollFd, NUM_POLLFDS, timeout_ms);
325    if (poll_result == 0) {
326        return 0;
327    } else if (poll_result == -1 || pollFd.revents & POLLERR) {
328        ALOGD("Can't read DVR : %s", strerror(errno));
329        // TODO: Find how to recover this situation correctly.
330        closeDvbDvr();
331        usleep(DVB_ERROR_RETRY_INTERVAL_MS);
332        return -1;
333    }
334    return read(mDvrFd, tsBuffer, tsBufferSize);
335}
336
337void DvbManager::setHasPendingTune(bool hasPendingTune) {
338    mHasPendingTune = hasPendingTune;
339}
340