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    jclass clazz = env->FindClass(
47        "com/android/usbtuner/TunerHal");
48    mOpenDvbFrontEndMethodID = env->GetMethodID(
49        clazz, "openDvbFrontEndFd", "()I");
50    mOpenDvbDemuxMethodID = env->GetMethodID(
51        clazz, "openDvbDemuxFd", "()I");
52    mOpenDvbDvrMethodID = env->GetMethodID(
53        clazz, "openDvbDvrFd", "()I");
54}
55
56DvbManager::~DvbManager() {
57    reset();
58}
59
60bool DvbManager::isFeLocked() {
61    struct dvb_frontend_event kevent;
62    memset(&kevent, 0, sizeof(kevent));
63    if (ioctl(mFeFd, FE_READ_STATUS, &kevent.status) == 0) {
64        return (kevent.status & FE_HAS_LOCK);
65    }
66    return false;
67}
68
69int DvbManager::tune(JNIEnv *env, jobject thiz,
70        const int frequency, const char *modulationStr, int timeout_ms) {
71    resetExceptFe();
72
73    struct dvb_frontend_parameters feParams;
74    memset(&feParams, 0, sizeof(struct dvb_frontend_parameters));
75    feParams.frequency = frequency;
76    if (strcmp(modulationStr, "8VSB") == 0) {
77        feParams.u.vsb.modulation = VSB_8;
78    } else if (strcmp(modulationStr, "QAM256") == 0) {
79        feParams.u.vsb.modulation = QAM_256;
80    } else {
81        ALOGE("Unrecognized modulation mode : %s", modulationStr);
82        return -1;
83    }
84
85    if (openDvbFe(env, thiz) != 0) {
86        return -1;
87    }
88
89    feParams.inversion = INVERSION_AUTO;
90    /* Check frontend capability */
91    struct dvb_frontend_info feInfo;
92    if (ioctl(mFeFd, FE_GET_INFO, &feInfo) != -1) {
93        if (!(feInfo.caps & FE_CAN_INVERSION_AUTO)) {
94            // FE can't do INVERSION_AUTO, trying INVERSION_OFF instead
95            feParams.inversion = INVERSION_OFF;
96        }
97    }
98
99    if (ioctl(mFeFd, FE_SET_FRONTEND, &feParams) != 0) {
100        ALOGD("Can't set Frontend : %s", strerror(errno));
101        return -1;
102    }
103
104    int lockSuccessCount = 0;
105    double tuneClock = currentTimeMillis();
106    while (currentTimeMillis() - tuneClock < timeout_ms) {
107        usleep(FE_LOCK_CHECK_INTERNAL_US);
108
109        bool lockStatus = isFeLocked();
110        if (lockStatus) {
111            lockSuccessCount++;
112        } else {
113            lockSuccessCount = 0;
114        }
115        ALOGI("Lock status : %s", lockStatus ? "true" : "false");
116        if (lockSuccessCount >= FE_CONSECUTIVE_LOCK_SUCCESS_COUNT) {
117            mFeHasLock = true;
118            openDvbDvr(env, thiz);
119            return 0;
120        }
121    }
122
123    return -1;
124}
125
126int DvbManager::stopTune() {
127    reset();
128    usleep(DVB_TUNE_STOP_DELAY_MS);
129    return 0;
130}
131
132int DvbManager::openDvbFeFromSystemApi(JNIEnv *env, jobject thiz) {
133    int fd = (int) env->CallIntMethod(thiz, mOpenDvbFrontEndMethodID);
134    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
135    return fd;
136}
137
138int DvbManager::openDvbDemuxFromSystemApi(JNIEnv *env, jobject thiz) {
139    int fd = (int) env->CallIntMethod(thiz, mOpenDvbDemuxMethodID);
140    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
141    return fd;
142}
143
144int DvbManager::openDvbDvrFromSystemApi(JNIEnv *env, jobject thiz) {
145    int fd = (int) env->CallIntMethod(thiz, mOpenDvbDvrMethodID);
146    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
147    return fd;
148}
149
150int DvbManager::openDvbFe(JNIEnv *env, jobject thiz) {
151    if (mFeFd == -1) {
152        if ((mFeFd = openDvbFeFromSystemApi(env, thiz)) < 0) {
153            ALOGD("Can't open FE file : %s", strerror(errno));
154            return -1;
155        }
156    }
157
158    struct dvb_frontend_info info;
159    if (ioctl(mFeFd, FE_GET_INFO, &info) == 0) {
160        const char *types;
161        switch (info.type) {
162            case FE_QPSK:
163                types = "DVB-S";
164                break;
165            case FE_QAM:
166                types = "DVB-C";
167                break;
168            case FE_OFDM:
169                types = "DVB-T";
170                break;
171            case FE_ATSC:
172                types = "ATSC";
173                break;
174            default:
175                types = "Unknown";
176        }
177        ALOGI("Using frontend \"%s\", type %s", info.name, types);
178    }
179    return 0;
180}
181
182int DvbManager::startTsPidFilter(JNIEnv *env, jobject thiz, int pid, int filterType) {
183    Mutex::Autolock autoLock(mFilterLock);
184
185    if (mPidFilters.find(pid) != mPidFilters.end() || (mPatFilterFd != -1 && pid == PAT_PID)) {
186        return 0;
187    }
188
189    int demuxFd;
190    if ((demuxFd = openDvbDemuxFromSystemApi(env, thiz)) < 0) {
191        ALOGD("Can't open DEMUX file : %s", strerror(errno));
192        return -1;
193    }
194
195    struct dmx_pes_filter_params filter;
196    memset(&filter, 0, sizeof(filter));
197    filter.pid = pid;
198    filter.input = DMX_IN_FRONTEND;
199    switch (filterType) {
200        case FILTER_TYPE_AUDIO:
201            filter.pes_type = DMX_PES_AUDIO;
202            break;
203        case FILTER_TYPE_VIDEO:
204            filter.pes_type = DMX_PES_VIDEO;
205            break;
206        case FILTER_TYPE_PCR:
207            filter.pes_type = DMX_PES_PCR;
208            break;
209        default:
210            filter.pes_type = DMX_PES_OTHER;
211            break;
212    }
213    filter.output = DMX_OUT_TS_TAP;
214    filter.flags |= (DMX_CHECK_CRC | DMX_IMMEDIATE_START);
215
216    // create a pes filter
217    if (ioctl(demuxFd, DMX_SET_PES_FILTER, &filter)) {
218        close(demuxFd);
219        return -1;
220    }
221
222    if (pid != PAT_PID) {
223        mPidFilters.insert(std::pair<int, int>(pid, demuxFd));
224    } else {
225        mPatFilterFd = demuxFd;
226    }
227
228    return 0;
229}
230
231void DvbManager::closeAllDvbPidFilter() {
232    // Close all dvb pid filters except PAT filter to maintain the opening status of the device.
233    Mutex::Autolock autoLock(mFilterLock);
234
235    for (std::map<int, int>::iterator it(mPidFilters.begin());
236                it != mPidFilters.end(); it++) {
237        close(it->second);
238    }
239    mPidFilters.clear();
240}
241
242void DvbManager::closePatFilter() {
243    Mutex::Autolock autoLock(mFilterLock);
244
245    if (mPatFilterFd != -1) {
246        close(mPatFilterFd);
247        mPatFilterFd = -1;
248    }
249}
250
251int DvbManager::openDvbDvr(JNIEnv *env, jobject thiz) {
252    if ((mDvrFd = openDvbDvrFromSystemApi(env, thiz)) < 0) {
253        ALOGD("Can't open DVR file : %s", strerror(errno));
254        return -1;
255    }
256    return 0;
257}
258
259void DvbManager::closeDvbFe() {
260    if (mFeFd != -1) {
261        close(mFeFd);
262        mFeFd = -1;
263    }
264}
265
266void DvbManager::closeDvbDvr() {
267    if (mDvrFd != -1) {
268        close(mDvrFd);
269        mDvrFd = -1;
270    }
271}
272
273void DvbManager::reset() {
274    mFeHasLock = false;
275    closeDvbDvr();
276    closeAllDvbPidFilter();
277    closePatFilter();
278    closeDvbFe();
279}
280
281void DvbManager::resetExceptFe() {
282    mFeHasLock = false;
283    closeDvbDvr();
284    closeAllDvbPidFilter();
285    closePatFilter();
286}
287
288int DvbManager::readTsStream(JNIEnv *env, jobject thiz,
289        uint8_t *tsBuffer, int tsBufferSize, int timeout_ms) {
290    if (!mFeHasLock) {
291        usleep(DVB_ERROR_RETRY_INTERVAL_MS);
292        return -1;
293    }
294
295    if (mDvrFd == -1) {
296        openDvbDvr(env, thiz);
297    }
298
299    struct pollfd pollFd;
300    pollFd.fd = mDvrFd;
301    pollFd.events = POLLIN|POLLPRI|POLLERR;
302    pollFd.revents = 0;
303    int poll_result = poll(&pollFd, NUM_POLLFDS, timeout_ms);
304    if (poll_result == 0) {
305        return 0;
306    } else if (poll_result == -1 || pollFd.revents & POLLERR) {
307        ALOGD("Can't read DVR : %s", strerror(errno));
308        // TODO: Find how to recover this situation correctly.
309        closeDvbDvr();
310        usleep(DVB_ERROR_RETRY_INTERVAL_MS);
311        return -1;
312    }
313    return read(mDvrFd, tsBuffer, tsBufferSize);
314}
315