mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
ab50cfd133
PD#TV-105087 Problem: Remove session inside destroy_session() is too early. The session may be used by application. Solution: Move the logic inside free_session() Verify: TM2 + LLAMA Signed-off-by: Song Zhao <song.zhao@amlogic.com> Change-Id: I4ff8e3d19b2f646fa4a4303837f97313f16bcc6d
2984 lines
80 KiB
C
2984 lines
80 KiB
C
/*
|
|
* Copyright (C) 2020 Amlogic, Inc. All rights reserved.
|
|
*
|
|
* 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 2 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.
|
|
*
|
|
*/
|
|
#include <linux/atomic.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/pid.h>
|
|
#include <linux/amlogic/media/vout/vout_notify.h>
|
|
#include <linux/amlogic/major.h>
|
|
#include <uapi/amlogic/msync.h>
|
|
#ifdef CONFIG_AMLOGIC_MEDIA_MINFO
|
|
#include <uapi/amlogic/media_info.h>
|
|
#endif
|
|
#define KERNEL_ATRACE_TAG KERNEL_ATRACE_TAG_MSYNC
|
|
#include <trace/events/meson_atrace.h>
|
|
#include <linux/compat.h>
|
|
|
|
#define AVS_DEV_NAME_MAX 20
|
|
#define UNIT90K (90000)
|
|
#define DEFAULT_WALL_ADJ_THRES (UNIT90K / 10) //100ms
|
|
#define MAX_SESSION_NUM 4
|
|
#define CHECK_INTERVAL ((HZ / 10) * 3) //300ms
|
|
#define WAIT_INTERVAL (2000) //2s
|
|
#define VIDEO_START_TO (2 * (HZ)) //2s
|
|
#define TRANSIT_INTERVAL (HZ) //1s
|
|
#define DISC_THRE_MIN (UNIT90K / 3)
|
|
#define DISC_THRE_MAX (UNIT90K * 20)
|
|
#define C_THRE (UNIT90K / 50) /* 20ms */
|
|
|
|
#define TEN_MS_INTERVAL (HZ / 100)
|
|
#define MIN_GAP (UNIT90K * 3) /* 3s */
|
|
#define MAX_GAP (UNIT90K * 3) /* 4s */
|
|
#define MAX_AV_DIFF (UNIT90K * 5)
|
|
#define PCR_INVALID_THRES (10 * UNIT90K)
|
|
#define PCR_DISC_THRES (UNIT90K * 3 / 2)
|
|
#define VALID_PTS(pts) ((pts) != AVS_INVALID_PTS)
|
|
|
|
enum pcr_init_flag {
|
|
INITCHECK_PCR = 1,
|
|
INITCHECK_VPTS = 2,
|
|
INITCHECK_APTS = 4,
|
|
INITCHECK_DONE = 6, /* a/v set */
|
|
};
|
|
|
|
enum pcr_init_priority_e {
|
|
INIT_PRIORITY_PCR = 0,
|
|
INIT_PRIORITY_AUDIO = 1,
|
|
INIT_PRIORITY_VIDEO = 2,
|
|
};
|
|
|
|
enum pcr_disc_flag {
|
|
PCR_DISC = 1,
|
|
VIDEO_DISC = 2,
|
|
AUDIO_DISC = 4
|
|
};
|
|
|
|
enum wake_up_flag {
|
|
WAKE_V = 1,
|
|
WAKE_A = 2,
|
|
WAKE_AV = 3
|
|
};
|
|
|
|
#define A_DISC_SET(flag) (((flag) & AUDIO_DISC) == AUDIO_DISC)
|
|
#define V_DISC_SET(flag) (((flag) & VIDEO_DISC) == VIDEO_DISC)
|
|
#define PCR_DISC_SET(flag) (((flag) & PCR_DISC) == PCR_DISC)
|
|
#define LIVE_MODE(m) (((m) == AVS_MODE_IPTV) || ((m) == AVS_MODE_PCR_MASTER))
|
|
|
|
struct sync_session {
|
|
u32 id;
|
|
atomic_t refcnt;
|
|
struct list_head node;
|
|
/* mutext for event handling */
|
|
struct mutex session_mutex;
|
|
char *class_name;
|
|
char *device_name;
|
|
struct device *session_dev;
|
|
struct class session_class;
|
|
struct workqueue_struct *wq;
|
|
char name[16];
|
|
|
|
/* target mode */
|
|
enum av_sync_mode mode;
|
|
/* current working mode */
|
|
enum av_sync_mode cur_mode;
|
|
enum av_sync_stat stat;
|
|
struct ker_start_policy start_policy;
|
|
|
|
bool clock_start;
|
|
/* lock for ISR resource */
|
|
spinlock_t lock;
|
|
u32 wall_clock;
|
|
u32 wall_clock_inc_remainer;
|
|
u32 wall_adj_thres;
|
|
/* 1000 for 1.0f, 2000 for 2.0f, 100 for 0.1f */
|
|
u32 rate;
|
|
|
|
bool v_active;
|
|
bool a_active;
|
|
|
|
/* pts wrapping */
|
|
u32 disc_thres_min;
|
|
u32 disc_thres_max;
|
|
bool v_disc;
|
|
bool a_disc;
|
|
|
|
/* pcr master */
|
|
bool use_pcr;
|
|
struct pcr_pair pcr_clock;
|
|
u32 pcr_init_flag;
|
|
u32 pcr_init_mode;
|
|
struct delayed_work pcr_check_work;
|
|
bool pcr_check_work_added;
|
|
u64 first_time_record;
|
|
u32 last_check_vpts;
|
|
u32 last_check_vpts_cnt;
|
|
u32 last_check_apts;
|
|
u32 last_check_apts_cnt;
|
|
u32 last_check_pcr_clock;
|
|
u32 pcr_disc_flag;
|
|
u32 astart_pcr;
|
|
u32 vstart_pcr;
|
|
u32 pcr_disc_cnt;
|
|
u32 pcr_cont_cnt;
|
|
u32 pcr_disc_clock;
|
|
int clk_dev; /*pcr mono deviation. Positive if pcr is faster.*/
|
|
|
|
/* iptv */
|
|
struct delayed_work a_check_work;
|
|
struct delayed_work v_check_work;
|
|
bool a_check_work_added;
|
|
bool v_check_work_added;
|
|
|
|
struct pts_tri first_vpts;
|
|
struct pts_tri last_vpts;
|
|
struct pts_tri first_apts;
|
|
struct pts_tri last_apts;
|
|
|
|
/* wait queue for poll */
|
|
wait_queue_head_t poll_wait;
|
|
u32 event_pending;
|
|
|
|
/* timers */
|
|
bool v_wait_work_on;
|
|
struct delayed_work v_wait_work;
|
|
bool video_start_work_on;
|
|
struct delayed_work video_start_work;
|
|
bool a_wait_work_on;
|
|
struct delayed_work a_wait_work;
|
|
bool transit_work_on;
|
|
struct delayed_work transit_work;
|
|
bool audio_change_work_on;
|
|
struct delayed_work audio_change_work;
|
|
/* async audio start */
|
|
bool start_posted;
|
|
bool v_timeout;
|
|
bool a_timeout;
|
|
|
|
/* debug */
|
|
bool debug_freerun;
|
|
bool audio_switching;
|
|
bool debug_vsync;
|
|
u64 d_vsync_start;
|
|
u32 d_wall_start;
|
|
int d_vsync_cnt;
|
|
u64 d_vsync_last;
|
|
char atrace_v[8];
|
|
char atrace_a[8];
|
|
int sync_status_cnt;
|
|
|
|
/* A/V disc recovery */
|
|
int audio_drop_cnt;
|
|
u64 audio_drop_start;
|
|
int video_drop_cnt;
|
|
u64 video_drop_start;
|
|
};
|
|
|
|
struct msync {
|
|
s32 multi_sync_major;
|
|
s32 session_major;
|
|
/* vsync isr context */
|
|
spinlock_t lock;
|
|
/* session management */
|
|
struct list_head head;
|
|
struct device *msync_dev;
|
|
u8 id_pool[MAX_SESSION_NUM];
|
|
struct sync_session *session[MAX_SESSION_NUM];
|
|
|
|
/* callback for vout_register_client() */
|
|
struct notifier_block msync_notifier;
|
|
#ifdef CONFIG_AMLOGIC_MEDIA_MINFO
|
|
/* callback from minfo */
|
|
struct notifier_block msync_minfo_notifier;
|
|
#endif
|
|
/* ready to receive vsync */
|
|
bool ready;
|
|
|
|
/* vout info */
|
|
u32 sync_duration_den;
|
|
u32 sync_duration_num;
|
|
u32 vsync_pts_inc;
|
|
|
|
/* start buffering time in 90K */
|
|
u32 start_buf_thres;
|
|
};
|
|
|
|
struct msync_priv {
|
|
int session_id;
|
|
};
|
|
|
|
enum {
|
|
LOG_ERR = 0,
|
|
LOG_WARN = 1,
|
|
LOG_INFO = 2,
|
|
LOG_DEBUG = 3,
|
|
LOG_TRACE = 4,
|
|
LOG_AVSYNC = 5,
|
|
};
|
|
|
|
#define msync_dbg(level, x...) \
|
|
do { \
|
|
if ((level) <= log_level) \
|
|
pr_info(x); \
|
|
} while (0)
|
|
|
|
static struct msync sync;
|
|
static int log_level = LOG_INFO;
|
|
static void pcr_set(struct sync_session *session);
|
|
static void start_transit_work(struct sync_session *session);
|
|
|
|
static u32 abs_diff(u32 a, u32 b)
|
|
{
|
|
return (int)(a - b) > 0 ? a - b : b - a;
|
|
}
|
|
|
|
static u32 pts_early(u32 a, u32 b)
|
|
{
|
|
return (int)(a - b) > 0 ? b : a;
|
|
}
|
|
|
|
/*
|
|
static u32 pts_later(u32 a, u32 b)
|
|
{
|
|
return (int)(a - b) > 0 ? a : b;
|
|
}
|
|
*/
|
|
|
|
static void session_set_wall_clock(struct sync_session *session, u32 clock)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&session->lock, flags);
|
|
session->wall_clock = clock;
|
|
session->wall_clock_inc_remainer = 0;
|
|
spin_unlock_irqrestore(&session->lock, flags);
|
|
}
|
|
|
|
static void session_vsync_update(struct sync_session *session)
|
|
{
|
|
if (session->clock_start) {
|
|
unsigned long flags;
|
|
u32 temp = 0;
|
|
u32 r = 0;
|
|
u32 den, tmp2;
|
|
u64 now;
|
|
|
|
spin_lock_irqsave(&session->lock, flags);
|
|
den = sync.sync_duration_den * session->rate;
|
|
tmp2 = sync.sync_duration_num * 1000;
|
|
temp = div_u64_rem(90000ULL * den, tmp2, &r);
|
|
session->wall_clock += temp;
|
|
session->wall_clock_inc_remainer += r;
|
|
if (session->wall_clock_inc_remainer >= tmp2) {
|
|
session->wall_clock++;
|
|
session->wall_clock_inc_remainer -= tmp2;
|
|
}
|
|
spin_unlock_irqrestore(&session->lock, flags);
|
|
if (!session->debug_vsync)
|
|
return;
|
|
/* check vsync quality */
|
|
now = ktime_get_raw_ns();
|
|
if (session->d_vsync_last != -1)
|
|
msync_dbg(LOG_TRACE, "[%d]vsync %llu ns\n",
|
|
session->id,
|
|
now - session->d_vsync_last);
|
|
session->d_vsync_last = now;
|
|
if (!session->d_vsync_cnt) {
|
|
session->d_vsync_start = now;
|
|
session->d_wall_start = session->wall_clock;
|
|
}
|
|
session->d_vsync_cnt++;
|
|
if (session->d_vsync_cnt == 1801) {
|
|
u64 mono_delta = now - session->d_vsync_start;
|
|
|
|
msync_dbg(LOG_DEBUG, "[%d]1800 vsync mono %llu ns\n",
|
|
session->id, mono_delta);
|
|
session->d_vsync_cnt = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int msync_vsync_update(void)
|
|
{
|
|
struct sync_session *session;
|
|
struct list_head *p;
|
|
unsigned long flags;
|
|
|
|
if (!sync.ready)
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
list_for_each(p, &sync.head) {
|
|
session = list_entry(p, struct sync_session, node);
|
|
session_vsync_update(session);
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msync_vsync_update);
|
|
|
|
static void msync_update_mode(void)
|
|
{
|
|
u32 inc;
|
|
unsigned long flags;
|
|
const struct vinfo_s *info;
|
|
|
|
info = get_current_vinfo();
|
|
/* pre-calculate vsync_pts_inc in 90k unit */
|
|
inc = 90000 * info->sync_duration_den /
|
|
info->sync_duration_num;
|
|
sync.vsync_pts_inc = inc;
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
if (info->sync_duration_num == 2997 ||
|
|
info->sync_duration_num == 5994) {
|
|
/* this is an adjustment to give accurate num/dem pair */
|
|
sync.sync_duration_den = 1001;
|
|
sync.sync_duration_num = 3000 * 1000 / info->sync_duration_den;
|
|
sync.sync_duration_num *= (info->sync_duration_num / 2997);
|
|
} else {
|
|
sync.sync_duration_den = info->sync_duration_den;
|
|
sync.sync_duration_num = info->sync_duration_num;
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
msync_dbg(0, "vsync_pts_inc %d %d/%d\n", inc,
|
|
sync.sync_duration_den, sync.sync_duration_num);
|
|
}
|
|
|
|
static int msync_notify_callback(struct notifier_block *block,
|
|
unsigned long cmd, void *para)
|
|
{
|
|
switch (cmd) {
|
|
case VOUT_EVENT_MODE_CHANGE:
|
|
msync_update_mode();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_AMLOGIC_MEDIA_MINFO
|
|
static int minfo_notify_callback(struct notifier_block *block,
|
|
unsigned long cmd, void *para)
|
|
{
|
|
switch (cmd) {
|
|
case 0:
|
|
msync_dbg(LOG_INFO, "msync %d render delay %s", __LINE__, (char *)para);
|
|
break;
|
|
default:
|
|
msync_dbg(LOG_ERR, "msync %d invalid cmd", __LINE__);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static unsigned int session_poll(struct file *file, poll_table *wait_table)
|
|
{
|
|
struct sync_session *session = file->private_data;
|
|
|
|
poll_wait(file, &session->poll_wait, wait_table);
|
|
if (session->event_pending)
|
|
return POLLPRI;
|
|
return 0;
|
|
}
|
|
|
|
static void wakeup_poll(struct sync_session *session, u32 flag)
|
|
{
|
|
if (session->v_active && (flag & WAKE_V))
|
|
session->event_pending |= WAKE_V;
|
|
if (session->a_active && (flag & WAKE_A))
|
|
session->event_pending |= WAKE_A;
|
|
if (session->event_pending)
|
|
wake_up_interruptible(&session->poll_wait);
|
|
}
|
|
|
|
static void transit_work_func(struct work_struct *work)
|
|
{
|
|
bool wake = false;
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, transit_work);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->transit_work_on) {
|
|
if (session->stat == AVS_STAT_TRANSITION_V_2_A) {
|
|
session->cur_mode = AVS_MODE_A_MASTER;
|
|
session_set_wall_clock(session, session->last_apts.wall_clock);
|
|
wake = true;
|
|
} else if (session->stat == AVS_STAT_TRANSITION_A_2_V) {
|
|
session->cur_mode = AVS_MODE_V_MASTER;
|
|
session_set_wall_clock(session, session->last_vpts.wall_clock);
|
|
wake = true;
|
|
}
|
|
session->stat = AVS_STAT_STARTED;
|
|
session->transit_work_on = false;
|
|
msync_dbg(LOG_DEBUG, "[%d]%d cur_mode %d\n",
|
|
session->id, __LINE__, session->cur_mode);
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
|
|
if (wake)
|
|
wakeup_poll(session, WAKE_A);
|
|
}
|
|
|
|
static void v_wait_work_func(struct work_struct *work)
|
|
{
|
|
bool wake = false;
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, v_wait_work);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (!session->v_wait_work_on)
|
|
goto exit;
|
|
|
|
if (session->mode == AVS_MODE_IPTV) {
|
|
msync_dbg(LOG_DEBUG, "[%d]%d audio started w %u\n",
|
|
session->id, __LINE__, session->wall_clock);
|
|
queue_delayed_work(session->wq,
|
|
&session->a_check_work,
|
|
CHECK_INTERVAL);
|
|
session->a_check_work_added = true;
|
|
session->stat = AVS_STAT_TRANSITION_V_2_A;
|
|
start_transit_work(session);
|
|
wake = true;
|
|
goto exit;
|
|
}
|
|
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN &&
|
|
!VALID_PTS(session->first_vpts.pts)) {
|
|
msync_dbg(LOG_WARN, "[%d]wait video timeout\n",
|
|
session->id);
|
|
|
|
session->v_wait_work_on = false;
|
|
if (session->mode == AVS_MODE_PCR_MASTER)
|
|
pcr_set(session);
|
|
session->clock_start = true;
|
|
session->v_timeout = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
}
|
|
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN &&
|
|
!session->start_posted) {
|
|
msync_dbg(LOG_DEBUG, "[%d]start posted\n", session->id);
|
|
session->start_posted = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
wake = true;
|
|
}
|
|
|
|
exit:
|
|
session->v_wait_work_on = false;
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wake)
|
|
wakeup_poll(session, WAKE_A);
|
|
}
|
|
|
|
static void video_start_work_func(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, video_start_work);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (!session->video_start_work_on) {
|
|
mutex_unlock(&session->session_mutex);
|
|
return;
|
|
}
|
|
|
|
if (session->mode == AVS_MODE_A_MASTER &&
|
|
!VALID_PTS(session->first_apts.pts)) {
|
|
msync_dbg(LOG_WARN, "[%d]wait audio timeout\n",
|
|
session->id);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
session->a_timeout = true;
|
|
}
|
|
session->video_start_work_on = false;
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void audio_change_work_func(struct work_struct *work)
|
|
{
|
|
bool wake = false;
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, audio_change_work);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
msync_dbg(LOG_WARN, "[%d] audio start now clock %u apts %u\n",
|
|
session->id, session->wall_clock, session->first_apts.pts);
|
|
if (!session->audio_change_work_on) {
|
|
mutex_unlock(&session->session_mutex);
|
|
return;
|
|
}
|
|
if (!session->start_posted) {
|
|
session->start_posted = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
msync_dbg(LOG_WARN, "[%d] audio allow start\n",
|
|
session->id);
|
|
wake = true;
|
|
}
|
|
session->audio_change_work_on = false;
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wake)
|
|
wakeup_poll(session, WAKE_A);
|
|
}
|
|
|
|
static void a_wait_work_func(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, a_wait_work);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (!session->a_wait_work_on) {
|
|
mutex_unlock(&session->session_mutex);
|
|
return;
|
|
}
|
|
if (!VALID_PTS(session->first_apts.pts)) {
|
|
msync_dbg(LOG_WARN, "[%d]wait audio timeout\n",
|
|
session->id);
|
|
}
|
|
pcr_set(session);
|
|
session->stat = AVS_STAT_STARTED;
|
|
session->a_wait_work_on = false;
|
|
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d video start %u w %u\n",
|
|
session->id, __LINE__,
|
|
session->last_vpts.pts, session->wall_clock);
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void use_pcr_clock(struct sync_session *session, bool enable, u32 pts)
|
|
{
|
|
if (enable) {
|
|
/* use pcr as reference */
|
|
session->use_pcr = true;
|
|
session_set_wall_clock(session, session->pcr_clock.pts);
|
|
} else {
|
|
/* use wall clock as reference */
|
|
session->use_pcr = false;
|
|
session_set_wall_clock(session, pts);
|
|
}
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
}
|
|
|
|
#if 0
|
|
static u32 get_ref_pcr(struct sync_session *session,
|
|
u32 cur_vpts, u32 cur_apts, u32 min_pts)
|
|
{
|
|
//u32 v_cache_pts = AVS_INVALID_PTS;
|
|
u32 a_pts_span = AVS_INVALID_PTS;
|
|
u32 first_vpts = AVS_INVALID_PTS;
|
|
u32 first_apts = AVS_INVALID_PTS;
|
|
u32 ref_pcr = AVS_INVALID_PTS;
|
|
|
|
//if (VALID_PTS(cur_vpts) && VALID_PTS(session->first_vpts.pts))
|
|
// v_cache_pts = cur_vpts - session->first_vpts.pts;
|
|
if (VALID_PTS(cur_apts) && VALID_PTS(session->first_apts.pts))
|
|
a_pts_span = cur_apts - session->first_apts.pts;
|
|
if (VALID_PTS(session->first_vpts.pts))
|
|
first_vpts = session->first_vpts.pts;
|
|
if (VALID_PTS(session->first_apts.pts))
|
|
first_apts = session->first_apts.pts;
|
|
|
|
if ((VALID_PTS(first_apts) && VALID_PTS(first_vpts) &&
|
|
VALID_PTS(a_pts_span) &&
|
|
first_apts < first_vpts &&
|
|
(first_vpts - first_apts <= MAX_AV_DIFF) &&
|
|
(a_pts_span < first_vpts - first_apts)) ||
|
|
(!VALID_PTS(first_vpts) && !VALID_PTS(cur_vpts))) {
|
|
//use audio
|
|
if (VALID_PTS(cur_apts))
|
|
ref_pcr = cur_apts;
|
|
else
|
|
ref_pcr = first_apts;
|
|
} else if ((VALID_PTS(first_vpts) && VALID_PTS(first_apts) &&
|
|
first_apts >= first_vpts) ||
|
|
(!VALID_PTS(first_apts) && !VALID_PTS(cur_apts))) {
|
|
//use video
|
|
if (VALID_PTS(cur_vpts))
|
|
ref_pcr = cur_vpts;
|
|
else
|
|
ref_pcr = first_vpts;
|
|
} else if (!VALID_PTS(first_vpts) && !VALID_PTS(first_apts)) {
|
|
//use smaller one
|
|
ref_pcr = min_pts;
|
|
} else if (!VALID_PTS(first_apts) && VALID_PTS(cur_apts) &&
|
|
VALID_PTS(first_vpts) &&
|
|
VALID_PTS(a_pts_span) &&
|
|
(first_vpts > cur_apts) &&
|
|
(first_vpts - cur_apts <= MAX_AV_DIFF) &&
|
|
(a_pts_span < first_vpts - cur_apts)) {
|
|
//use audio
|
|
ref_pcr = cur_apts;
|
|
} else {
|
|
//use video by default
|
|
if (VALID_PTS(cur_vpts))
|
|
ref_pcr = cur_vpts;
|
|
else
|
|
ref_pcr = first_vpts;
|
|
}
|
|
|
|
return ref_pcr;
|
|
}
|
|
#endif
|
|
|
|
/* Starting policy */
|
|
static u32 get_start_pts(u32 vpts, u32 apts, u32 pcr, u32 thres)
|
|
{
|
|
u32 min_pts = AVS_INVALID_PTS;
|
|
|
|
if (VALID_PTS(vpts) && VALID_PTS(apts))
|
|
min_pts = pts_early(vpts, apts);
|
|
else if (VALID_PTS(vpts))
|
|
min_pts = vpts;
|
|
else if (VALID_PTS(apts))
|
|
min_pts = apts;
|
|
|
|
if (min_pts == apts) {
|
|
/* give audio enough buffer but do not block video */
|
|
min_pts -= thres;
|
|
} else {
|
|
/* video buffer can be less */
|
|
min_pts -= thres / 2;
|
|
}
|
|
|
|
/* handle DTG stream where pcr is not aligned with A/V */
|
|
if (VALID_PTS(pcr))
|
|
min_pts = pts_early(min_pts, pcr);
|
|
return min_pts;
|
|
}
|
|
|
|
/* only handle first arrived A/V/PCR */
|
|
static void pcr_set(struct sync_session *session)
|
|
{
|
|
u32 cur_pcr = AVS_INVALID_PTS;
|
|
u32 cur_vpts = AVS_INVALID_PTS;
|
|
u32 cur_apts = AVS_INVALID_PTS;
|
|
//u32 ref_pcr = AVS_INVALID_PTS;
|
|
u32 min_pts = AVS_INVALID_PTS;
|
|
|
|
if (session->pcr_init_flag & INITCHECK_DONE)
|
|
return;
|
|
|
|
if (VALID_PTS(session->first_vpts.pts))
|
|
cur_vpts = session->first_vpts.pts;
|
|
if (VALID_PTS(session->first_apts.pts))
|
|
cur_apts = session->first_apts.pts;
|
|
if (VALID_PTS(session->pcr_clock.pts))
|
|
cur_pcr = session->pcr_clock.pts;
|
|
|
|
if (VALID_PTS(session->last_vpts.pts))
|
|
cur_vpts = session->last_vpts.pts - session->last_vpts.delay;
|
|
if (VALID_PTS(session->last_apts.pts))
|
|
cur_apts = session->last_apts.pts;
|
|
|
|
if (VALID_PTS(cur_vpts) && VALID_PTS(cur_apts))
|
|
min_pts = pts_early(cur_vpts, cur_apts);
|
|
else if (VALID_PTS(cur_vpts))
|
|
min_pts = cur_vpts;
|
|
else if (VALID_PTS(cur_apts))
|
|
min_pts = cur_apts;
|
|
|
|
/* pcr comes first */
|
|
if (VALID_PTS(cur_pcr)) {
|
|
session->pcr_init_flag |= INITCHECK_PCR;
|
|
session->pcr_init_mode = INIT_PRIORITY_PCR;
|
|
if (!VALID_PTS(cur_vpts) && !VALID_PTS(cur_apts)) {
|
|
use_pcr_clock(session, true, 0);
|
|
msync_dbg(LOG_TRACE, "[%d]%d enable pcr %u\n",
|
|
session->id, __LINE__, cur_pcr);
|
|
}
|
|
}
|
|
|
|
if (VALID_PTS(session->first_apts.pts) && !session->v_wait_work_on) {
|
|
session->pcr_init_flag |= INITCHECK_APTS;
|
|
//ref_pcr = get_ref_pcr(session, cur_vpts, cur_apts, min_pts);
|
|
if (VALID_PTS(cur_pcr))
|
|
session->pcr_init_mode = INIT_PRIORITY_PCR;
|
|
else
|
|
session->pcr_init_mode = INIT_PRIORITY_AUDIO;
|
|
|
|
min_pts = get_start_pts(session->first_vpts.pts,
|
|
session->first_apts.pts, session->astart_pcr,
|
|
sync.start_buf_thres);
|
|
use_pcr_clock(session, false, min_pts);
|
|
msync_dbg(LOG_DEBUG, "[%d]%d disable pcr %u buf %u\n",
|
|
session->id, __LINE__, min_pts,
|
|
(int)(session->first_apts.pts - min_pts));
|
|
}
|
|
|
|
if (VALID_PTS(session->first_vpts.pts) && !session->a_wait_work_on) {
|
|
session->pcr_init_flag |= INITCHECK_VPTS;
|
|
//ref_pcr = get_ref_pcr(session, cur_vpts, cur_apts, min_pts);
|
|
if (VALID_PTS(cur_pcr))
|
|
session->pcr_init_mode = INIT_PRIORITY_PCR;
|
|
else
|
|
session->pcr_init_mode = INIT_PRIORITY_VIDEO;
|
|
use_pcr_clock(session, false, min_pts);
|
|
msync_dbg(LOG_DEBUG, "[%d]%d disable pcr %u\n",
|
|
session->id, __LINE__, min_pts);
|
|
}
|
|
}
|
|
|
|
/*
|
|
static u32 pcr_get(struct sync_session *session)
|
|
{
|
|
if (session->use_pcr)
|
|
return session->pcr_clock.pts;
|
|
else
|
|
return session->wall_clock;
|
|
}
|
|
*/
|
|
|
|
static void update_f_vpts(struct sync_session *session, u32 pts)
|
|
{
|
|
session->first_vpts.wall_clock = pts;
|
|
session->first_vpts.pts = pts;
|
|
}
|
|
|
|
static void update_f_apts(struct sync_session *session, u32 pts)
|
|
{
|
|
session->first_apts.wall_clock = pts;
|
|
session->first_apts.pts = pts;
|
|
}
|
|
|
|
static void session_inc_v_drop_cnt(struct sync_session *session)
|
|
{
|
|
if (!session->video_drop_cnt)
|
|
session->video_drop_start = ktime_get_raw_ns();
|
|
session->video_drop_cnt++;
|
|
}
|
|
|
|
static void session_video_start(struct sync_session *session, u32 pts)
|
|
{
|
|
bool wakeup = false;
|
|
|
|
session->v_active = true;
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->mode == AVS_MODE_V_MASTER) {
|
|
session_set_wall_clock(session, pts);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
update_f_vpts(session, pts);
|
|
msync_dbg(LOG_INFO, "[%d]%d video start %u\n",
|
|
session->id, __LINE__, pts);
|
|
} else if (session->mode == AVS_MODE_A_MASTER) {
|
|
update_f_vpts(session, pts);
|
|
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN &&
|
|
VALID_PTS(session->first_apts.pts)) {
|
|
if (session->clock_start)
|
|
goto exit;
|
|
session->stat = AVS_STAT_STARTED;
|
|
if ((int)(session->first_apts.pts - pts) > 900) {
|
|
u32 delay = (session->first_apts.pts - pts) / 90;
|
|
|
|
if (session->start_policy.timeout != -1 &&
|
|
delay > session->start_policy.timeout)
|
|
delay = session->start_policy.timeout;
|
|
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d video start %u ad %u\n",
|
|
session->id, __LINE__, pts, delay);
|
|
/* use video pts as starting point */
|
|
session_set_wall_clock(session, pts);
|
|
session->clock_start = true;
|
|
mod_delayed_work(session->wq,
|
|
&session->v_wait_work,
|
|
msecs_to_jiffies(delay));
|
|
} else {
|
|
session->clock_start = true;
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
session->v_wait_work_on = false;
|
|
session->start_posted = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
wakeup = true;
|
|
msync_dbg(LOG_INFO, "[%d]%d video start %u\n",
|
|
session->id, __LINE__, pts);
|
|
}
|
|
}
|
|
if (!VALID_PTS(session->first_apts.pts) &&
|
|
!session->video_start_work_on) {
|
|
queue_delayed_work(session->wq,
|
|
&session->video_start_work,
|
|
VIDEO_START_TO);
|
|
session->video_start_work_on = true;
|
|
}
|
|
} else if (session->mode == AVS_MODE_IPTV) {
|
|
update_f_vpts(session, pts);
|
|
session->vstart_pcr = session->pcr_clock.pts;
|
|
|
|
if (session->start_policy.policy == AMSYNC_START_ASAP &&
|
|
!VALID_PTS(session->first_apts.pts)) {
|
|
session_set_wall_clock(session, pts);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
session->cur_mode = AVS_MODE_V_MASTER;
|
|
wakeup = true;
|
|
msync_dbg(LOG_INFO, "[%d]%d video start %u\n",
|
|
session->id, __LINE__, pts);
|
|
} else if (session->start_policy.policy == AMSYNC_START_ASAP) {
|
|
msync_dbg(LOG_INFO, "[%d]%d video start %u wall %u keep AMASTER\n",
|
|
session->id, __LINE__, pts, session->wall_clock);
|
|
}
|
|
queue_delayed_work(session->wq,
|
|
&session->v_check_work,
|
|
CHECK_INTERVAL);
|
|
session->v_check_work_added = true;
|
|
} else if (session->mode == AVS_MODE_PCR_MASTER) {
|
|
update_f_vpts(session, pts);
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN &&
|
|
!VALID_PTS(session->first_apts.pts)) {
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d video start %u deferred\n",
|
|
session->id, __LINE__, pts);
|
|
if (session->a_wait_work_on)
|
|
cancel_delayed_work_sync(&session->a_wait_work);
|
|
queue_delayed_work(session->wq,
|
|
&session->a_wait_work,
|
|
msecs_to_jiffies(session->start_policy.timeout));
|
|
session->stat = AVS_STAT_STARTING;
|
|
session->a_wait_work_on = true;
|
|
} else {
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN) {
|
|
wakeup = true;
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
session->v_wait_work_on = false;
|
|
session->start_posted = true;
|
|
}
|
|
pcr_set(session);
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d video start %u w %u\n",
|
|
session->id, __LINE__,
|
|
pts, session->wall_clock);
|
|
}
|
|
} else if (session->mode == AVS_MODE_FREE_RUN) {
|
|
session_set_wall_clock(session, pts);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
update_f_vpts(session, pts);
|
|
msync_dbg(LOG_INFO, "[%d]%d video start %u\n",
|
|
session->id, __LINE__, pts);
|
|
}
|
|
exit:
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_A);
|
|
}
|
|
|
|
static void session_inc_a_drop_cnt(struct sync_session *session)
|
|
{
|
|
if (!session->audio_drop_cnt)
|
|
session->audio_drop_start = ktime_get_raw_ns();
|
|
session->audio_drop_cnt++;
|
|
}
|
|
|
|
static u32 session_audio_start(struct sync_session *session,
|
|
const struct audio_start *start)
|
|
{
|
|
bool wakeup = false;
|
|
u32 ret = AVS_START_SYNC;
|
|
u32 pts = start->pts;
|
|
u32 start_pts = start->pts - start->delay;
|
|
|
|
session->a_active = true;
|
|
session->a_timeout = false;
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->audio_switching) {
|
|
update_f_apts(session, pts);
|
|
if ((int)(session->wall_clock - pts) >= 0 ||
|
|
session->start_policy.policy != AMSYNC_START_ALIGN) {
|
|
/* audio start immediately pts to small */
|
|
/* (need drop) or no wait */
|
|
session->stat = AVS_STAT_STARTED;
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d audio immediate start %u clock %u\n",
|
|
session->id, __LINE__, pts,
|
|
session->wall_clock);
|
|
if (!session->start_posted) {
|
|
session->start_posted = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
wakeup = true;
|
|
}
|
|
} else if (session->start_policy.policy == AMSYNC_START_ALIGN) {
|
|
// normal case, wait audio start point
|
|
u32 diff_ms = (int)(pts - session->wall_clock) / 90;
|
|
u32 delay_jiffies;
|
|
|
|
if (session->start_policy.timeout != -1 &&
|
|
diff_ms > jiffies_to_msecs(session->start_policy.timeout))
|
|
diff_ms = session->start_policy.timeout;
|
|
delay_jiffies = msecs_to_jiffies(diff_ms);
|
|
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d audio start %u def %u ms clock %u\n",
|
|
session->id, __LINE__,
|
|
pts, diff_ms, session->wall_clock);
|
|
|
|
if (session->audio_change_work_on)
|
|
cancel_delayed_work_sync
|
|
(&session->audio_change_work);
|
|
|
|
queue_delayed_work(session->wq,
|
|
&session->audio_change_work,
|
|
delay_jiffies);
|
|
session->audio_change_work_on = true;
|
|
session->stat = AVS_STAT_TRANSITION;
|
|
ret = AVS_START_ASYNC;
|
|
}
|
|
} else if (session->mode == AVS_MODE_A_MASTER) {
|
|
session_set_wall_clock(session, start_pts);
|
|
update_f_apts(session, pts);
|
|
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN &&
|
|
!VALID_PTS(session->first_vpts.pts)) {
|
|
msync_dbg(LOG_INFO, "[%d]%d audio start %u deferred\n",
|
|
session->id, __LINE__, pts);
|
|
if (session->v_wait_work_on)
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
queue_delayed_work(session->wq,
|
|
&session->v_wait_work,
|
|
msecs_to_jiffies(session->start_policy.timeout));
|
|
session->v_wait_work_on = true;
|
|
session->stat = AVS_STAT_STARTING;
|
|
ret = AVS_START_ASYNC;
|
|
} else {
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN) {
|
|
u32 vpts = session->first_vpts.pts;
|
|
u32 delay;
|
|
|
|
if ((int)(pts - vpts) <= 900) {
|
|
/* use audio as start */
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d audio start %u\n",
|
|
session->id, __LINE__, pts);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
goto exit;
|
|
}
|
|
/* use video as start, audio will adjust wall on first PTS */
|
|
delay = (pts - vpts) / 90;
|
|
if (session->start_policy.timeout != -1 &&
|
|
delay > session->start_policy.timeout) {
|
|
msync_dbg(LOG_WARN,
|
|
"[%d]%d audio start %u late then video %ums\n",
|
|
session->id, __LINE__, pts, delay);
|
|
}
|
|
|
|
msync_dbg(LOG_INFO,
|
|
"[%d]%d audio start %u deferred %ums\n",
|
|
session->id, __LINE__, pts, delay);
|
|
session_set_wall_clock(session, vpts);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
} else {
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
cancel_delayed_work_sync(&session->video_start_work);
|
|
session->video_start_work_on = false;
|
|
msync_dbg(LOG_INFO, "[%d]%d clock start %u\n",
|
|
session->id, __LINE__, pts);
|
|
}
|
|
}
|
|
} else if (session->mode == AVS_MODE_IPTV) {
|
|
bool start_check = false;
|
|
u32 delay_jiffy = TRANSIT_INTERVAL;
|
|
|
|
update_f_apts(session, pts);
|
|
|
|
if (!VALID_PTS(session->first_vpts.pts)) {
|
|
session_set_wall_clock(session, start_pts);
|
|
session->clock_start = true;
|
|
session->stat = AVS_STAT_STARTED;
|
|
session->cur_mode = AVS_MODE_A_MASTER;
|
|
wakeup = true;
|
|
start_check = true;
|
|
msync_dbg(LOG_INFO, "[%d]%d audio start %u w %u\n",
|
|
session->id, __LINE__, pts, session->wall_clock);
|
|
} else {
|
|
int delay = (int)(pts - session->wall_clock) / 90;
|
|
|
|
if (delay > 30) {
|
|
delay_jiffy = msecs_to_jiffies(delay);
|
|
if (session->v_wait_work_on)
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
queue_delayed_work(session->wq,
|
|
&session->v_wait_work,
|
|
delay_jiffy);
|
|
session->v_wait_work_on = true;
|
|
session->stat = AVS_STAT_STARTING;
|
|
ret = AVS_START_ASYNC;
|
|
msync_dbg(LOG_INFO, "[%d]%d audio start %u deferred %d ms\n",
|
|
session->id, __LINE__, pts, delay);
|
|
} else if (delay < -30) {
|
|
session_inc_a_drop_cnt(session);
|
|
session->last_apts = session->first_apts;
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
ret = AVS_START_AGAIN;
|
|
msync_dbg(LOG_DEBUG, "[%d]%d audio drop %u w %u\n",
|
|
session->id, __LINE__,
|
|
pts, session->wall_clock);
|
|
} else {
|
|
session->stat = AVS_STAT_TRANSITION_V_2_A;
|
|
start_transit_work(session);
|
|
start_check = true;
|
|
msync_dbg(LOG_INFO, "[%d]%d audio start %u w %u\n",
|
|
session->id, __LINE__, pts, session->wall_clock);
|
|
}
|
|
}
|
|
if (start_check) {
|
|
queue_delayed_work(session->wq,
|
|
&session->a_check_work,
|
|
CHECK_INTERVAL);
|
|
session->a_check_work_added = true;
|
|
}
|
|
} else if (session->mode == AVS_MODE_PCR_MASTER) {
|
|
msync_dbg(LOG_INFO, "[%d]%d audio start %u\n",
|
|
session->id, __LINE__, pts);
|
|
update_f_apts(session, pts);
|
|
session->astart_pcr = session->pcr_clock.pts;
|
|
if (session->start_policy.policy == AMSYNC_START_ALIGN) {
|
|
if (session->a_wait_work_on) {
|
|
cancel_delayed_work_sync(&session->a_wait_work);
|
|
session->a_wait_work_on = false;
|
|
}
|
|
if (!VALID_PTS(session->first_vpts.pts)) {
|
|
msync_dbg(LOG_INFO, "[%d]%d audio start %u deferred\n",
|
|
session->id, __LINE__, pts);
|
|
if (session->v_wait_work_on)
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
queue_delayed_work(session->wq,
|
|
&session->v_wait_work,
|
|
msecs_to_jiffies(session->start_policy.timeout));
|
|
session->v_wait_work_on = true;
|
|
session->stat = AVS_STAT_STARTING;
|
|
ret = AVS_START_ASYNC;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
pcr_set(session);
|
|
session->stat = AVS_STAT_STARTED;
|
|
if (session->clock_start &&
|
|
((int)(session->wall_clock - pts) > 0 ||
|
|
(int)(pts - session->wall_clock) <
|
|
sync.start_buf_thres)) {
|
|
ret = AVS_START_AGAIN;
|
|
session_inc_a_drop_cnt(session);
|
|
session->last_apts = session->first_apts;
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
|
|
msync_dbg(LOG_DEBUG, "[%d]%d audio drop %u w %u\n",
|
|
session->id, __LINE__,
|
|
pts, session->wall_clock);
|
|
}
|
|
}
|
|
exit:
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_A);
|
|
return ret;
|
|
}
|
|
|
|
static void session_stop_audio_wait(struct sync_session *session)
|
|
{
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->start_policy.policy != AMSYNC_START_ALIGN ||
|
|
session->clock_start)
|
|
goto exit;
|
|
if (session->v_wait_work_on) {
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
session->v_wait_work_on = false;
|
|
msync_dbg(LOG_DEBUG, "[%d]stop audio wait\n", session->id);
|
|
} else {
|
|
goto exit;
|
|
}
|
|
if (!session->a_active)
|
|
goto exit;
|
|
session->a_active = false;
|
|
mutex_unlock(&session->session_mutex);
|
|
|
|
session->event_pending |= WAKE_A;
|
|
wake_up_interruptible(&session->poll_wait);
|
|
return;
|
|
exit:
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void session_pause(struct sync_session *session, bool pause)
|
|
{
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->stat == AVS_STAT_INIT) {
|
|
msync_dbg(LOG_INFO, "[%d]%s ignore pause %d before starting\n",
|
|
session->id, __func__, pause);
|
|
} else if (session->stat == AVS_STAT_STARTING) {
|
|
msync_dbg(LOG_INFO, "[%d]%s ignore pause %d during starting\n",
|
|
session->id, __func__, pause);
|
|
} else {
|
|
session->clock_start = !pause;
|
|
msync_dbg(LOG_INFO, "[%d]%s pause %d\n",
|
|
session->id, __func__, pause);
|
|
if (pause)
|
|
session->stat = AVS_STAT_PAUSED;
|
|
else
|
|
session->stat = AVS_STAT_STARTED;
|
|
session->d_vsync_last = -1;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void session_video_stop(struct sync_session *session)
|
|
{
|
|
bool wakeup = true;
|
|
session->v_active = false;
|
|
update_f_vpts(session, AVS_INVALID_PTS);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (!session->a_active) {
|
|
session->clock_start = false;
|
|
session->start_posted = false;
|
|
session->v_timeout = false;
|
|
session->event_pending = 0;
|
|
session->stat = AVS_STAT_INIT;
|
|
wakeup = false;
|
|
msync_dbg(LOG_INFO, "[%d]%s clock stop\n",
|
|
session->id, __func__);
|
|
} else if (LIVE_MODE(session->mode)) {
|
|
if (session->mode == AVS_MODE_PCR_MASTER) {
|
|
use_pcr_clock(session, true, 0);
|
|
} else if (session->mode == AVS_MODE_IPTV) {
|
|
session->cur_mode = AVS_MODE_A_MASTER;
|
|
if (session->v_check_work_added) {
|
|
cancel_delayed_work_sync(&session->v_check_work);
|
|
session->v_check_work_added = false;
|
|
}
|
|
msync_dbg(LOG_INFO, "[%d]%s to Amaster w %u a %u\n",
|
|
session->id, __func__, session->wall_clock,
|
|
session->last_apts.wall_clock);
|
|
}
|
|
session->first_vpts.pts = AVS_INVALID_PTS;
|
|
session->last_vpts.pts = AVS_INVALID_PTS;
|
|
session->pcr_disc_clock = AVS_INVALID_PTS;
|
|
session->last_check_vpts_cnt = 0;
|
|
session->pcr_disc_flag &= ~(VIDEO_DISC);
|
|
session->first_time_record =
|
|
div64_u64((u64)jiffies * UNIT90K, HZ);
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_A);
|
|
}
|
|
|
|
static void session_audio_stop(struct sync_session *session)
|
|
{
|
|
bool wakeup = true;
|
|
session->a_active = false;
|
|
session->audio_drop_cnt = 0;
|
|
update_f_apts(session, AVS_INVALID_PTS);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (!session->v_active) {
|
|
session->clock_start = false;
|
|
session->start_posted = false;
|
|
session->v_timeout = false;
|
|
session->event_pending = 0;
|
|
session->stat = AVS_STAT_INIT;
|
|
wakeup = false;
|
|
msync_dbg(LOG_INFO, "[%d]%d clock stop\n",
|
|
session->id, __LINE__);
|
|
} else if (session->audio_switching) {
|
|
session->start_posted = false;
|
|
if (session->audio_change_work_on) {
|
|
cancel_delayed_work_sync(&session->audio_change_work);
|
|
session->audio_change_work_on = false;
|
|
}
|
|
wakeup = false;
|
|
session->event_pending &= ~SRC_A;
|
|
msync_dbg(LOG_INFO, "[%d]%s audio switching stop audio\n",
|
|
session->id, __func__);
|
|
} else if (LIVE_MODE(session->mode)) {
|
|
if (session->mode == AVS_MODE_PCR_MASTER) {
|
|
use_pcr_clock(session, true, 0);
|
|
} else if (session->mode == AVS_MODE_IPTV) {
|
|
session->cur_mode = AVS_MODE_V_MASTER;
|
|
if (session->a_check_work_added) {
|
|
cancel_delayed_work_sync(&session->a_check_work);
|
|
session->a_check_work_added = false;
|
|
}
|
|
msync_dbg(LOG_INFO, "[%d]%s to Vmaster w %u v %u\n",
|
|
session->id, __func__, session->wall_clock,
|
|
session->last_vpts.wall_clock);
|
|
}
|
|
session->first_apts.pts = AVS_INVALID_PTS;
|
|
session->last_apts.pts = AVS_INVALID_PTS;
|
|
session->last_check_apts_cnt = 0;
|
|
session->pcr_disc_flag &= ~(AUDIO_DISC);
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_V);
|
|
}
|
|
|
|
static void session_video_disc_iptv(struct sync_session *session, u32 pts)
|
|
{
|
|
mutex_lock(&session->session_mutex);
|
|
|
|
if (session->cur_mode == AVS_MODE_V_MASTER) {
|
|
msync_dbg(LOG_WARN, "[%d]vdisc reset wall %u --> %u\n",
|
|
session->id, session->wall_clock, pts);
|
|
session_set_wall_clock(session, pts);
|
|
}
|
|
|
|
session->pcr_disc_flag |= VIDEO_DISC;
|
|
session->last_vpts.pts = pts;
|
|
session->last_vpts.wall_clock = session->wall_clock;
|
|
|
|
if ((int)(session->wall_clock - pts) > 0) {
|
|
session_inc_v_drop_cnt(session);
|
|
} else {
|
|
session->video_drop_cnt = 0;
|
|
msync_dbg(LOG_TRACE, "[%d] clear v drop cnt\n",
|
|
session->id);
|
|
}
|
|
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
/*
|
|
static bool pcr_v_disc(struct sync_session *session, u32 pts)
|
|
{
|
|
u32 pcr = pcr_get(session);
|
|
|
|
if (!VALID_PTS(pcr))
|
|
return false;
|
|
if (abs_diff(pts, pcr) > session->disc_thres_min)
|
|
return true;
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
static void session_video_disc_pcr(struct sync_session *session, u32 pts)
|
|
{
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->pcr_init_mode != INIT_PRIORITY_PCR) {
|
|
/* set wall clock */
|
|
session_set_wall_clock(session, pts);
|
|
}
|
|
|
|
session->pcr_disc_flag |= VIDEO_DISC;
|
|
session->last_vpts.pts = pts;
|
|
session->last_vpts.wall_clock = session->wall_clock;
|
|
|
|
if ((int)(session->wall_clock - pts) > 0) {
|
|
session_inc_v_drop_cnt(session);
|
|
} else {
|
|
session->video_drop_cnt = 0;
|
|
msync_dbg(LOG_TRACE, "[%d] clear v drop cnt\n",
|
|
session->id);
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void session_video_disc_v(struct sync_session *session, u32 pts)
|
|
{
|
|
mutex_lock(&session->session_mutex);
|
|
|
|
msync_dbg(LOG_WARN, "[%d]vdisc reset wall %u --> %u\n",
|
|
session->id, session->wall_clock, pts);
|
|
session_set_wall_clock(session, pts);
|
|
session->last_vpts.pts = pts;
|
|
session->last_vpts.wall_clock = session->wall_clock;
|
|
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void session_audio_disc(struct sync_session *session, u32 pts)
|
|
{
|
|
mutex_lock(&session->session_mutex);
|
|
if (LIVE_MODE(session->mode)) {
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
session->last_apts.pts = pts;
|
|
session->last_apts.wall_clock = session->wall_clock;
|
|
|
|
if ((int)(session->wall_clock - pts) > 0) {
|
|
session_inc_a_drop_cnt(session);
|
|
} else {
|
|
session->audio_drop_cnt = 0;
|
|
msync_dbg(LOG_TRACE, "[%d] clear a drop cnt\n",
|
|
session->id);
|
|
}
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
static void session_audio_switch(struct sync_session *session, u32 start)
|
|
{
|
|
msync_dbg(LOG_WARN, "[%d] set audio switch to %u @ pos %u\n",
|
|
session->id, start, session->wall_clock);
|
|
session->audio_switching = start ? true : false;
|
|
}
|
|
|
|
static void session_update_vpts(struct sync_session *session)
|
|
{
|
|
bool reset_wall = false;
|
|
struct pts_tri *p = &session->last_vpts;
|
|
u32 pts = p->pts;
|
|
|
|
if (session->mode == AVS_MODE_V_MASTER) {
|
|
reset_wall = true;
|
|
} else if (session->mode == AVS_MODE_IPTV) {
|
|
mod_delayed_work(session->wq,
|
|
&session->v_check_work,
|
|
CHECK_INTERVAL);
|
|
if (session->cur_mode == AVS_MODE_V_MASTER)
|
|
reset_wall = true;
|
|
}
|
|
|
|
if (reset_wall &&
|
|
abs_diff(pts, session->wall_clock) >=
|
|
session->wall_adj_thres) {
|
|
unsigned long flags;
|
|
|
|
msync_dbg(LOG_WARN, "[%d]v reset wall %u --> %u\n",
|
|
session->id, session->wall_clock, pts);
|
|
/* correct wall with vpts */
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
session->wall_clock = pts;
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
}
|
|
}
|
|
|
|
static void session_update_apts(struct sync_session *session)
|
|
{
|
|
bool reset_wall = false;
|
|
struct pts_tri *p = &session->last_apts;
|
|
u32 pts = p->pts;
|
|
|
|
if (session->mode == AVS_MODE_A_MASTER) {
|
|
|
|
if (session->debug_freerun)
|
|
return;
|
|
if (pts > p->delay)
|
|
pts -= p->delay;
|
|
reset_wall = true;
|
|
} else if (LIVE_MODE(session->mode)) {
|
|
if (session->audio_drop_cnt)
|
|
msync_dbg(LOG_TRACE, "[%d] a %u clear a drop cnt\n",
|
|
session->id, session->last_apts.pts);
|
|
session->audio_drop_cnt = 0;
|
|
if (session->mode == AVS_MODE_IPTV) {
|
|
mod_delayed_work(session->wq,
|
|
&session->a_check_work,
|
|
CHECK_INTERVAL);
|
|
if (session->cur_mode == AVS_MODE_A_MASTER)
|
|
reset_wall = true;
|
|
}
|
|
}
|
|
if (reset_wall &&
|
|
abs_diff(pts, session->wall_clock) >=
|
|
session->wall_adj_thres) {
|
|
unsigned long flags;
|
|
|
|
if (session->audio_switching) {
|
|
msync_dbg(LOG_DEBUG,
|
|
"[%d]ignore reset %u --> %u\n",
|
|
session->id, session->wall_clock, pts);
|
|
return;
|
|
}
|
|
/* correct wall with apts */
|
|
msync_dbg(LOG_WARN, "[%d]a reset wall %u --> %u\n",
|
|
session->id, session->wall_clock, pts);
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
session->wall_clock = pts;
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
}
|
|
}
|
|
|
|
static void iptv_check(struct sync_session *session)
|
|
{
|
|
u32 checkin_vpts = AVS_INVALID_PTS;
|
|
u32 checkin_apts = AVS_INVALID_PTS;
|
|
int max_gap = (sync.start_buf_thres / 900) * 4 / 3;
|
|
u32 min_pts;
|
|
u32 flag, last_pts, gap_cnt = 0;
|
|
|
|
if (session->a_active) {
|
|
checkin_apts = session->last_apts.pts;
|
|
if (VALID_PTS(checkin_apts) &&
|
|
VALID_PTS(session->last_check_apts)) {
|
|
flag = session->pcr_disc_flag;
|
|
last_pts = session->last_check_apts;
|
|
gap_cnt = 0;
|
|
|
|
/* apts timeout */
|
|
if (abs_diff(last_pts, checkin_apts)
|
|
> session->disc_thres_max) {
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
msync_dbg(LOG_DEBUG, "[%d] %d adisc %x\n",
|
|
session->id, __LINE__,
|
|
session->pcr_disc_flag);
|
|
}
|
|
if (last_pts == checkin_apts) {
|
|
session->last_check_apts_cnt++;
|
|
if (session->last_check_apts_cnt > max_gap) {
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
gap_cnt = session->last_check_apts_cnt;
|
|
session->last_check_apts_cnt = 0;
|
|
|
|
/* audio stopping detection */
|
|
if (session->cur_mode == AVS_MODE_A_MASTER) {
|
|
mutex_lock(&session->session_mutex);
|
|
session->cur_mode = AVS_MODE_V_MASTER;
|
|
mutex_unlock(&session->session_mutex);
|
|
|
|
wakeup_poll(session, WAKE_A);
|
|
session->audio_drop_cnt = 0;
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d switch to vamster %u(w)\n",
|
|
session->id, __LINE__,
|
|
session->wall_clock);
|
|
}
|
|
}
|
|
} else {
|
|
session->last_check_apts_cnt = 0;
|
|
if (abs_diff(session->wall_clock,
|
|
checkin_apts) <= C_THRE)
|
|
session->pcr_disc_flag &= ~(AUDIO_DISC);
|
|
}
|
|
if (flag != session->pcr_disc_flag)
|
|
msync_dbg(LOG_WARN,
|
|
"[%d] %d adisc f:%x %u --> %u x %u w %u\n",
|
|
session->id, __LINE__,
|
|
session->pcr_disc_flag,
|
|
last_pts,
|
|
checkin_apts,
|
|
gap_cnt,
|
|
session->wall_clock);
|
|
}
|
|
session->last_check_apts = checkin_apts;
|
|
}
|
|
|
|
if (session->v_active)
|
|
checkin_vpts = session->last_vpts.pts - session->last_vpts.delay;
|
|
|
|
if (session->cur_mode == AVS_MODE_V_MASTER &&
|
|
A_DISC_SET(session->pcr_disc_flag) &&
|
|
!V_DISC_SET(session->pcr_disc_flag)) {
|
|
/* audio dropping recovery */
|
|
u64 now = ktime_get_raw_ns();
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->audio_drop_cnt > 10 &&
|
|
now - session->audio_drop_start > 500000000LLU) {
|
|
min_pts = checkin_apts - sync.start_buf_thres;
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d adj pcr %u(w) --> %u(m)\n",
|
|
session->id, __LINE__,
|
|
session->wall_clock, min_pts);
|
|
|
|
use_pcr_clock(session, false, min_pts);
|
|
|
|
session->pcr_disc_flag &= ~AUDIO_DISC;
|
|
session->audio_drop_cnt = 0;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
if (session->cur_mode == AVS_MODE_A_MASTER &&
|
|
V_DISC_SET(session->pcr_disc_flag) &&
|
|
!A_DISC_SET(session->pcr_disc_flag)) {
|
|
/* video dropping recovery */
|
|
u64 now = ktime_get_raw_ns();
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->video_drop_cnt > 10 &&
|
|
now - session->video_drop_start > 500000000LLU) {
|
|
min_pts = checkin_vpts - sync.start_buf_thres;
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d adj pcr %u(w) --> %u(m)\n",
|
|
session->id, __LINE__,
|
|
session->wall_clock, min_pts);
|
|
|
|
use_pcr_clock(session, false, min_pts);
|
|
|
|
session->pcr_disc_flag &= ~VIDEO_DISC;
|
|
session->video_drop_cnt = 0;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
}
|
|
|
|
static void pcr_check(struct sync_session *session)
|
|
{
|
|
u32 checkin_vpts = AVS_INVALID_PTS;
|
|
u32 checkin_apts = AVS_INVALID_PTS;
|
|
u32 checkin_pcr = AVS_INVALID_PTS;
|
|
u32 min_pts = AVS_INVALID_PTS;
|
|
int max_gap = sync.start_buf_thres / 900 * 4 / 3;
|
|
u32 flag, last_pts, gap_cnt = 0;
|
|
|
|
if (session->a_active) {
|
|
checkin_apts = session->last_apts.pts;
|
|
if (VALID_PTS(checkin_apts) &&
|
|
VALID_PTS(session->last_check_apts)) {
|
|
flag = session->pcr_disc_flag;
|
|
last_pts = session->last_check_apts;
|
|
gap_cnt = 0;
|
|
|
|
/* apts timeout */
|
|
if (abs_diff(last_pts, checkin_apts)
|
|
> session->disc_thres_max) {
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
msync_dbg(LOG_DEBUG, "[%d] %d adisc %x\n",
|
|
session->id, __LINE__,
|
|
session->pcr_disc_flag);
|
|
}
|
|
if (last_pts == checkin_apts) {
|
|
session->last_check_apts_cnt++;
|
|
if (session->last_check_apts_cnt > max_gap) {
|
|
session->pcr_disc_flag |= AUDIO_DISC;
|
|
gap_cnt = session->last_check_apts_cnt;
|
|
session->last_check_apts_cnt = 0;
|
|
}
|
|
} else {
|
|
session->last_check_apts = checkin_apts;
|
|
session->last_check_apts_cnt = 0;
|
|
if (abs_diff(session->wall_clock,
|
|
checkin_apts) <= C_THRE)
|
|
session->pcr_disc_flag &= ~(AUDIO_DISC);
|
|
}
|
|
if (flag != session->pcr_disc_flag)
|
|
msync_dbg(LOG_WARN,
|
|
"[%d] %d adisc f:%x %u --> %u x %u w %u\n",
|
|
session->id, __LINE__,
|
|
session->pcr_disc_flag,
|
|
last_pts,
|
|
checkin_apts,
|
|
gap_cnt,
|
|
session->wall_clock);
|
|
}
|
|
session->last_check_apts = checkin_apts;
|
|
}
|
|
|
|
if (session->v_active) {
|
|
checkin_vpts =
|
|
session->last_vpts.pts - session->last_vpts.delay;
|
|
if (VALID_PTS(checkin_vpts) &&
|
|
VALID_PTS(session->last_check_vpts)) {
|
|
flag = session->pcr_disc_flag;
|
|
last_pts = session->last_check_vpts;
|
|
gap_cnt = 0;
|
|
|
|
/* vpts timeout */
|
|
if (abs_diff(last_pts, checkin_vpts) > 2 * UNIT90K) {
|
|
session->pcr_disc_flag |= VIDEO_DISC;
|
|
} else if (last_pts == checkin_vpts) {
|
|
session->last_check_vpts_cnt++;
|
|
if (session->last_check_vpts_cnt > max_gap) {
|
|
session->pcr_disc_flag |= VIDEO_DISC;
|
|
gap_cnt = session->last_check_vpts_cnt;
|
|
session->last_check_vpts_cnt = 0;
|
|
}
|
|
} else {
|
|
session->last_check_vpts = checkin_vpts;
|
|
session->last_check_vpts_cnt = 0;
|
|
if (abs_diff(session->wall_clock,
|
|
checkin_vpts) <=
|
|
(session->disc_thres_min >> 1))
|
|
session->pcr_disc_flag &= ~(VIDEO_DISC);
|
|
}
|
|
if (flag != session->pcr_disc_flag)
|
|
msync_dbg(LOG_WARN,
|
|
"[%d] %d vdisc f:%x %u --> %u x %u w %u\n",
|
|
session->id, __LINE__,
|
|
session->pcr_disc_flag,
|
|
last_pts,
|
|
checkin_vpts,
|
|
gap_cnt,
|
|
session->wall_clock);
|
|
}
|
|
session->last_check_vpts = checkin_vpts;
|
|
}
|
|
|
|
if (session->use_pcr) {
|
|
if (VALID_PTS(session->pcr_clock.pts) &&
|
|
VALID_PTS(session->last_check_pcr_clock) &&
|
|
abs_diff(session->pcr_clock.pts,
|
|
session->last_check_pcr_clock) > PCR_DISC_THRES &&
|
|
session->pcr_init_flag) {
|
|
session->pcr_disc_flag |= PCR_DISC;
|
|
session->pcr_disc_clock = session->pcr_clock.pts;
|
|
msync_dbg(LOG_WARN, "[%d] %d pdisc f:%x %u --> %u\n",
|
|
session->id, __LINE__,
|
|
session->pcr_disc_flag,
|
|
session->last_check_vpts,
|
|
checkin_vpts);
|
|
} else if (PCR_DISC_SET(session->pcr_disc_flag) &&
|
|
VALID_PTS(session->pcr_disc_clock)) {
|
|
if (abs_diff(session->pcr_clock.pts,
|
|
session->pcr_disc_clock) > (4 * UNIT90K)) {
|
|
/* to pause the pcr check */
|
|
session->pcr_disc_flag = 0;
|
|
session->pcr_disc_clock =
|
|
AVS_INVALID_PTS;
|
|
msync_dbg(LOG_WARN, "[%d] %d pdisc reset\n",
|
|
session->id, __LINE__);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* pcr discontinuity detection */
|
|
if (VALID_PTS(session->pcr_clock.pts) &&
|
|
VALID_PTS(session->last_check_pcr_clock) &&
|
|
abs_diff(session->pcr_clock.pts,
|
|
session->last_check_pcr_clock) > PCR_DISC_THRES) {
|
|
session->last_check_pcr_clock = session->pcr_clock.pts;
|
|
session->pcr_disc_cnt++;
|
|
session->pcr_cont_cnt = 0;
|
|
} else if (VALID_PTS(session->pcr_clock.pts)) {
|
|
session->last_check_pcr_clock = session->pcr_clock.pts;
|
|
session->pcr_cont_cnt++;
|
|
session->pcr_disc_cnt = 0;
|
|
checkin_pcr = session->pcr_clock.pts;
|
|
}
|
|
|
|
min_pts = pts_early(checkin_apts, checkin_vpts);
|
|
|
|
/* TODO: dead code? */
|
|
if (!V_DISC_SET(session->pcr_disc_flag) &&
|
|
PCR_DISC_SET(session->pcr_disc_flag) &&
|
|
session->use_pcr) {
|
|
session->use_pcr = false;
|
|
msync_dbg(LOG_INFO, "[%d] %d pdisc ignored\n",
|
|
session->id, __LINE__);
|
|
}
|
|
|
|
if (A_DISC_SET(session->pcr_disc_flag) &&
|
|
V_DISC_SET(session->pcr_disc_flag) &&
|
|
VALID_PTS(checkin_apts) &&
|
|
VALID_PTS(checkin_vpts)) {
|
|
|
|
if (abs_diff(checkin_apts, checkin_vpts) <
|
|
session->disc_thres_min) {
|
|
min_pts = get_start_pts(checkin_vpts,
|
|
checkin_apts, checkin_pcr,
|
|
sync.start_buf_thres);
|
|
} else if (abs_diff(checkin_vpts, checkin_apts) <
|
|
session->disc_thres_max &&
|
|
(int)(checkin_vpts - checkin_apts) > 0) {
|
|
/* reset to buffer audio only */
|
|
min_pts = checkin_apts;
|
|
min_pts -= sync.start_buf_thres;
|
|
} else {
|
|
min_pts = get_start_pts(checkin_vpts,
|
|
checkin_apts, checkin_pcr,
|
|
sync.start_buf_thres);
|
|
}
|
|
use_pcr_clock(session, false, min_pts);
|
|
session->pcr_disc_flag = 0;
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d disable pcr %u(p) %u(a) %u(v) --> %u(m)\n",
|
|
session->id, __LINE__,
|
|
session->pcr_clock.pts,
|
|
checkin_apts, checkin_vpts, min_pts);
|
|
}
|
|
|
|
if (A_DISC_SET(session->pcr_disc_flag) && !session->v_active) {
|
|
min_pts = get_start_pts(checkin_vpts,
|
|
checkin_apts, checkin_pcr,
|
|
sync.start_buf_thres);
|
|
|
|
use_pcr_clock(session, false, min_pts);
|
|
session->pcr_disc_flag &= ~AUDIO_DISC;
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d disable pcr %u(p) %u(a) --> %u(m)\n",
|
|
session->id, __LINE__,
|
|
session->pcr_clock.pts, checkin_apts, min_pts);
|
|
}
|
|
|
|
if (A_DISC_SET(session->pcr_disc_flag) &&
|
|
!V_DISC_SET(session->pcr_disc_flag)) {
|
|
/* audio dropping recovery */
|
|
u64 now = ktime_get_raw_ns();
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->audio_drop_cnt &&
|
|
now - session->audio_drop_start > 500000000LLU) {
|
|
min_pts = get_start_pts(checkin_vpts,
|
|
checkin_apts, checkin_pcr,
|
|
sync.start_buf_thres);
|
|
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d adj pcr %u(w) --> %u(m)\n",
|
|
session->id, __LINE__,
|
|
session->wall_clock, min_pts);
|
|
|
|
use_pcr_clock(session, false, min_pts);
|
|
|
|
session->pcr_disc_flag &= ~AUDIO_DISC;
|
|
session->audio_drop_cnt = 0;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
|
|
if (!A_DISC_SET(session->pcr_disc_flag)) {
|
|
/* video dropping recovery, don't have to check vdisc because
|
|
* this flag will be cleared in vpts timeout logic
|
|
*/
|
|
u64 now = ktime_get_raw_ns();
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->video_drop_cnt > 3 &&
|
|
now - session->video_drop_start < 1000000000LLU) {
|
|
min_pts = get_start_pts(checkin_vpts,
|
|
checkin_apts, checkin_pcr,
|
|
sync.start_buf_thres);
|
|
|
|
msync_dbg(LOG_INFO,
|
|
"[%d] %d adj pcr %u(w) --> %u(m)\n",
|
|
session->id, __LINE__,
|
|
session->wall_clock, min_pts);
|
|
|
|
use_pcr_clock(session, false, min_pts);
|
|
|
|
session->pcr_disc_flag &= ~VIDEO_DISC;
|
|
session->video_drop_cnt = 0;
|
|
} else if (session->video_drop_cnt &&
|
|
now - session->video_drop_start > 1000000000LLU) {
|
|
session->video_drop_cnt = 0;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
}
|
|
}
|
|
|
|
static void pcr_timer_func(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, pcr_check_work);
|
|
|
|
if (session->mode == AVS_MODE_PCR_MASTER)
|
|
pcr_check(session);
|
|
else if (session->mode == AVS_MODE_IPTV)
|
|
iptv_check(session);
|
|
|
|
queue_delayed_work(session->wq,
|
|
&session->pcr_check_work,
|
|
TEN_MS_INTERVAL);
|
|
}
|
|
|
|
static void start_transit_work(struct sync_session *session)
|
|
{
|
|
if (session->transit_work_on)
|
|
cancel_delayed_work_sync(&session->transit_work);
|
|
queue_delayed_work(session->wq,
|
|
&session->transit_work,
|
|
TRANSIT_INTERVAL);
|
|
session->transit_work_on = true;
|
|
}
|
|
|
|
static void v_check_timer_func(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, v_check_work);
|
|
bool wakeup = false;
|
|
|
|
/* video stops coming */
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->stat == AVS_STAT_TRANSITION_A_2_V) {
|
|
msync_dbg(LOG_WARN, "session[%d] %d no A/V\n",
|
|
session->id, __LINE__);
|
|
} else if (!session->a_active) {
|
|
msync_dbg(LOG_WARN, "session[%d] %d no A/V\n",
|
|
session->id, __LINE__);
|
|
} else if (session->stat != AVS_STAT_TRANSITION_V_2_A &&
|
|
session->cur_mode != AVS_MODE_A_MASTER) {
|
|
session->stat = AVS_STAT_TRANSITION_V_2_A;
|
|
msync_dbg(LOG_INFO, "session[%d] %d to Amaster\n",
|
|
session->id, __LINE__);
|
|
start_transit_work(session);
|
|
wakeup = true;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_A);
|
|
}
|
|
|
|
static void a_check_timer_func(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct sync_session *session =
|
|
container_of(dwork, struct sync_session, a_check_work);
|
|
bool wakeup = false;
|
|
|
|
/* audio stops coming */
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->stat == AVS_STAT_TRANSITION_V_2_A) {
|
|
msync_dbg(LOG_WARN, "session[%d] %d no A/V\n",
|
|
session->id, __LINE__);
|
|
} else if (!session->v_active) {
|
|
msync_dbg(LOG_WARN, "session[%d] %d no A/V\n",
|
|
session->id, __LINE__);
|
|
} else if (session->stat != AVS_STAT_TRANSITION_A_2_V &&
|
|
session->cur_mode != AVS_MODE_V_MASTER) {
|
|
session->stat = AVS_STAT_TRANSITION_A_2_V;
|
|
msync_dbg(LOG_INFO, "session[%d] %d to Vmaster\n",
|
|
session->id, __LINE__);
|
|
start_transit_work(session);
|
|
wakeup = true;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_V);
|
|
}
|
|
|
|
static const char *event_dbg[AVS_EVENT_MAX] = {
|
|
"video_start",
|
|
"pause",
|
|
"resume",
|
|
"video_stop",
|
|
"audio_stop",
|
|
"video_disc",
|
|
"audio_disc",
|
|
"audio_switch",
|
|
};
|
|
|
|
static void session_handle_event(struct sync_session *session,
|
|
const struct session_event *event)
|
|
{
|
|
msync_dbg(LOG_DEBUG, "[%d]event %s/%u\n",
|
|
session->id,
|
|
event_dbg[event->event], event->value);
|
|
switch (event->event) {
|
|
case AVS_VIDEO_START:
|
|
session_video_start(session, event->value);
|
|
break;
|
|
case AVS_PAUSE:
|
|
session_pause(session, true);
|
|
break;
|
|
case AVS_RESUME:
|
|
session_pause(session, false);
|
|
break;
|
|
case AVS_VIDEO_STOP:
|
|
session_video_stop(session);
|
|
break;
|
|
case AVS_AUDIO_STOP:
|
|
session_audio_stop(session);
|
|
break;
|
|
case AVS_VIDEO_TSTAMP_DISCONTINUITY:
|
|
if (session->mode == AVS_MODE_IPTV)
|
|
session_video_disc_iptv(session, event->value);
|
|
else if (session->mode == AVS_MODE_PCR_MASTER)
|
|
session_video_disc_pcr(session, event->value);
|
|
else if (session->mode == AVS_MODE_V_MASTER)
|
|
session_video_disc_v(session, event->value);
|
|
break;
|
|
case AVS_AUDIO_TSTAMP_DISCONTINUITY:
|
|
session_audio_disc(session, event->value);
|
|
break;
|
|
case AVS_AUDIO_SWITCH:
|
|
session_audio_switch(session, event->value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static long session_ioctl(struct file *file, unsigned int cmd, ulong arg)
|
|
{
|
|
struct sync_session *session = file->private_data;
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
switch (cmd) {
|
|
case AMSYNCS_IOC_SET_MODE:
|
|
{
|
|
enum av_sync_mode mode;
|
|
bool wakeup = false;
|
|
|
|
get_user(mode, (u32 __user *)argp);
|
|
msync_dbg(LOG_INFO,
|
|
"session[%d] mode %d --> %d by %s\n",
|
|
session->id, session->mode, mode, current->comm);
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->mode != AVS_MODE_PCR_MASTER &&
|
|
mode == AVS_MODE_PCR_MASTER &&
|
|
!session->pcr_check_work_added) {
|
|
session->use_pcr = true;
|
|
session->pcr_init_mode = INIT_PRIORITY_PCR;
|
|
|
|
session->first_time_record =
|
|
div64_u64((u64)jiffies * UNIT90K, HZ);
|
|
INIT_DELAYED_WORK(&session->pcr_check_work,
|
|
pcr_timer_func);
|
|
queue_delayed_work(session->wq,
|
|
&session->pcr_check_work,
|
|
TEN_MS_INTERVAL);
|
|
session->pcr_check_work_added = true;
|
|
session->mode = mode;
|
|
session->cur_mode = mode;
|
|
} else if (LIVE_MODE(session->mode) &&
|
|
mode == AVS_MODE_FREE_RUN) {
|
|
session->cur_mode = AVS_MODE_FREE_RUN;
|
|
wakeup = true;
|
|
} else if (session->mode != AVS_MODE_IPTV &&
|
|
mode == AVS_MODE_IPTV) {
|
|
INIT_DELAYED_WORK(&session->a_check_work,
|
|
a_check_timer_func);
|
|
INIT_DELAYED_WORK(&session->v_check_work,
|
|
v_check_timer_func);
|
|
INIT_DELAYED_WORK(&session->pcr_check_work,
|
|
pcr_timer_func);
|
|
session->pcr_check_work_added = true;
|
|
queue_delayed_work(session->wq,
|
|
&session->pcr_check_work,
|
|
TEN_MS_INTERVAL);
|
|
|
|
session->mode = mode;
|
|
} else {
|
|
session->mode = mode;
|
|
if (session->mode == AVS_MODE_PCR_MASTER)
|
|
session->cur_mode = mode;
|
|
}
|
|
mutex_unlock(&session->session_mutex);
|
|
if (wakeup)
|
|
wakeup_poll(session, WAKE_A);
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_MODE:
|
|
put_user(session->mode, (u32 __user *)argp);
|
|
break;
|
|
case AMSYNCS_IOC_SET_START_POLICY:
|
|
{
|
|
struct ker_start_policy policy;
|
|
|
|
if (!copy_from_user(&policy, argp, sizeof(policy))) {
|
|
msync_dbg(LOG_DEBUG,
|
|
"session[%d] policy %u timeout %u, old timeout=%d.\n",
|
|
session->id, policy.policy,
|
|
policy.timeout, session->start_policy.timeout);
|
|
session->start_policy.policy = policy.policy;
|
|
session->start_policy.timeout = policy.timeout;
|
|
}
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_START_POLICY:
|
|
if (copy_to_user(argp, &session->start_policy,
|
|
sizeof(struct ker_start_policy)))
|
|
return -EFAULT;
|
|
break;
|
|
case AMSYNCS_IOC_SET_V_TS:
|
|
{
|
|
struct pts_tri ts;
|
|
|
|
if (!copy_from_user(&ts, argp, sizeof(ts))) {
|
|
if (!VALID_PTS(session->last_vpts.pts))
|
|
msync_dbg(LOG_DEBUG,
|
|
"session[%d] first vpts %u w %u\n",
|
|
session->id, ts.pts,
|
|
session->wall_clock);
|
|
session->last_vpts = ts;
|
|
session_update_vpts(session);
|
|
ATRACE_COUNTER(session->atrace_v, ts.pts);
|
|
} else {
|
|
msync_dbg(LOG_ERR, "fail copy %d\n", __LINE__);
|
|
}
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_V_TS:
|
|
{
|
|
if (copy_to_user(argp, &session->last_vpts, sizeof(struct pts_tri)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_SET_A_TS:
|
|
{
|
|
struct pts_tri ts;
|
|
|
|
if (!copy_from_user(&ts, argp, sizeof(ts))) {
|
|
if (!VALID_PTS(session->last_apts.pts))
|
|
msync_dbg(LOG_DEBUG,
|
|
"session[%d] first apts %u w %u\n",
|
|
session->id, ts.pts,
|
|
session->wall_clock);
|
|
session->last_apts = ts;
|
|
session_update_apts(session);
|
|
|
|
session->sync_status_cnt++;
|
|
if (session->sync_status_cnt > 4) {
|
|
session->sync_status_cnt = 0;
|
|
msync_dbg(LOG_AVSYNC,
|
|
"last v/%x a/%x,diff-ms a-w %d v-w %d a-v %d\n",
|
|
session->last_vpts.pts - session->last_vpts.delay,
|
|
session->last_apts.pts,
|
|
(int)(session->last_apts.pts - session->wall_clock) / 90,
|
|
(int)(session->last_vpts.pts - session->wall_clock -
|
|
session->last_vpts.delay) / 90,
|
|
(int)(session->last_apts.pts - session->last_vpts.pts +
|
|
session->last_vpts.delay) / 90);
|
|
}
|
|
|
|
ATRACE_COUNTER(session->atrace_a, ts.pts);
|
|
} else {
|
|
msync_dbg(LOG_ERR, "fail copy %d\n", __LINE__);
|
|
}
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_A_TS:
|
|
{
|
|
if (copy_to_user(argp, &session->last_apts, sizeof(struct pts_tri)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_SEND_EVENT:
|
|
{
|
|
struct session_event event;
|
|
|
|
if (!copy_from_user(&event, argp, sizeof(event))) {
|
|
if (event.event >= AVS_EVENT_MAX)
|
|
return -EINVAL;
|
|
session_handle_event(session, &event);
|
|
}
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_SYNC_STAT:
|
|
{
|
|
struct session_sync_stat stat;
|
|
enum src_flag flag;
|
|
|
|
if (copy_from_user(&stat, argp, sizeof(stat)))
|
|
return -EFAULT;
|
|
flag = stat.flag;
|
|
stat.v_active = session->v_active;
|
|
stat.v_timeout = session->v_timeout;
|
|
stat.a_active = session->a_active;
|
|
stat.mode = session->cur_mode;
|
|
stat.stat = session->stat;
|
|
stat.audio_switch = session->audio_switching;
|
|
if (copy_to_user(argp, &stat, sizeof(stat)))
|
|
return -EFAULT;
|
|
if (stat.clean_poll && session->v_active && flag == SRC_V)
|
|
session->event_pending &= ~SRC_V;
|
|
else if (stat.clean_poll && session->a_active && flag == SRC_A)
|
|
session->event_pending &= ~SRC_A;
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_SET_PCR:
|
|
{
|
|
struct pcr_pair pcr;
|
|
|
|
if (copy_from_user(&pcr, argp, sizeof(pcr)))
|
|
return -EFAULT;
|
|
if (!VALID_PTS(session->pcr_clock.pts))
|
|
msync_dbg(LOG_INFO, "[%d]pcr set %u\n",
|
|
__LINE__, pcr.pts);
|
|
mutex_lock(&session->session_mutex);
|
|
session->pcr_clock = pcr;
|
|
pcr_set(session);
|
|
mutex_unlock(&session->session_mutex);
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_PCR:
|
|
if (copy_to_user(argp, &session->pcr_clock, sizeof(struct pcr_pair)))
|
|
return -EFAULT;
|
|
break;
|
|
case AMSYNCS_IOC_GET_WALL:
|
|
{
|
|
struct pts_wall wall;
|
|
|
|
memset(&wall, 0, sizeof(wall));
|
|
wall.interval = sync.vsync_pts_inc;
|
|
if (session->mode != AVS_MODE_PCR_MASTER) {
|
|
if (session->clock_start)
|
|
wall.wall_clock = session->wall_clock;
|
|
else
|
|
wall.wall_clock = AVS_INVALID_PTS;
|
|
} else {
|
|
wall.wall_clock = session->wall_clock;
|
|
}
|
|
if (copy_to_user(argp, &wall, sizeof(struct pts_wall)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_SET_RATE:
|
|
{
|
|
u32 rate;
|
|
|
|
get_user(rate, (u32 __user *)argp);
|
|
if (rate == 0 || rate > 10 * 1000) {
|
|
msync_dbg(LOG_ERR, "[%d]wrong rate %u\n",
|
|
session->id, rate);
|
|
return -EINVAL;
|
|
}
|
|
if (session->rate == rate)
|
|
return 0;
|
|
msync_dbg(LOG_WARN, "[%d]rate %u --> %u\n",
|
|
session->id, session->rate, rate);
|
|
session->rate = rate;
|
|
if (session->mode == AVS_MODE_A_MASTER)
|
|
wakeup_poll(session, WAKE_A);
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_RATE:
|
|
put_user(session->rate, (u32 __user *)argp);
|
|
break;
|
|
case AMSYNCS_IOC_SET_NAME:
|
|
if (strncpy_from_user(session->name,
|
|
(const char __user *)argp,
|
|
sizeof(session->name)))
|
|
return -EFAULT;
|
|
break;
|
|
case AMSYNCS_IOC_SET_WALL_ADJ_THRES:
|
|
get_user(session->wall_adj_thres, (u32 __user *)argp);
|
|
msync_dbg(LOG_WARN, "[%d]wall_adj_thres %d\n",
|
|
session->id, session->wall_adj_thres);
|
|
break;
|
|
case AMSYNCS_IOC_GET_WALL_ADJ_THRES:
|
|
put_user(session->wall_adj_thres, (u32 __user *)argp);
|
|
break;
|
|
case AMSYNCS_IOC_GET_CLOCK_START:
|
|
put_user(session->clock_start, (u32 __user *)argp);
|
|
break;
|
|
case AMSYNCS_IOC_AUDIO_START:
|
|
{
|
|
struct audio_start start;
|
|
|
|
if (!copy_from_user(&start, argp, sizeof(struct audio_start))) {
|
|
start.mode = session_audio_start(session, &start);
|
|
msync_dbg(LOG_DEBUG, "[%d]audio start mode %u\n",
|
|
session->id, start.mode);
|
|
}
|
|
if (copy_to_user(argp, &start, sizeof(struct audio_start)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_GET_DEBUG_MODE:
|
|
{
|
|
struct session_debug debug;
|
|
|
|
debug.debug_freerun = session->debug_freerun;
|
|
debug.pcr_init_mode = session->pcr_init_mode;
|
|
debug.pcr_init_flag = session->pcr_init_flag;
|
|
if (copy_to_user(argp, &debug, sizeof(debug)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_SET_CLK_DEV:
|
|
{
|
|
int dev, ret = 0;
|
|
|
|
get_user(dev, (int __user *)argp);
|
|
if (dev > 1000 || dev < -1000)
|
|
return -EINVAL;
|
|
session->clk_dev = dev;
|
|
//ret = set_clock_drift(dev);
|
|
msync_dbg(LOG_WARN, "[%d]clk dev %d ret %d\n",
|
|
session->id, dev, ret);
|
|
return ret;
|
|
}
|
|
case AMSYNCS_IOC_GET_CLK_DEV:
|
|
{
|
|
put_user(session->clk_dev, (int __user *)argp);
|
|
break;
|
|
}
|
|
case AMSYNCS_IOC_SET_STOP_AUDIO_WAIT:
|
|
{
|
|
session_stop_audio_wait(session);
|
|
break;
|
|
}
|
|
default:
|
|
msync_dbg(LOG_WARN, "[%d]unknown cmd %d\n",
|
|
session->id, cmd);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long session_compat_ioctl(struct file *filp, u32 cmd, ulong arg)
|
|
{
|
|
long ret;
|
|
|
|
arg = (ulong)compat_ptr(arg);
|
|
ret = session_ioctl(filp, cmd, arg);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int session_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct sync_session *session = NULL;
|
|
struct list_head *p;
|
|
int id = iminor(inode);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
list_for_each(p, &sync.head) {
|
|
session = list_entry(p, struct sync_session, node);
|
|
if (session->id == id)
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
|
|
if (!session) {
|
|
msync_dbg(LOG_ERR, "can not find %d\n", id);
|
|
return -EBADF;
|
|
}
|
|
|
|
atomic_inc(&session->refcnt);
|
|
msync_dbg(LOG_DEBUG, "session[%d] open pid[%d]\n",
|
|
id, task_tgid_nr(current));
|
|
file->private_data = session;
|
|
return 0;
|
|
}
|
|
|
|
static void free_session(struct sync_session *session)
|
|
{
|
|
unsigned long flags;
|
|
|
|
msync_dbg(LOG_INFO, "free session[%d]\n", session->id);
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
list_del(&session->node);
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
|
|
mutex_lock(&session->session_mutex);
|
|
if (session->v_wait_work_on) {
|
|
cancel_delayed_work_sync(&session->v_wait_work);
|
|
session->v_wait_work_on = false;
|
|
}
|
|
if (session->video_start_work_on) {
|
|
cancel_delayed_work_sync(&session->video_start_work);
|
|
session->video_start_work_on = false;
|
|
}
|
|
if (session->transit_work_on) {
|
|
cancel_delayed_work_sync(&session->transit_work);
|
|
session->transit_work_on = false;
|
|
}
|
|
if (session->a_wait_work_on) {
|
|
cancel_delayed_work_sync(&session->a_wait_work);
|
|
session->a_wait_work_on = false;
|
|
}
|
|
if (session->audio_change_work_on) {
|
|
cancel_delayed_work_sync(&session->audio_change_work);
|
|
session->audio_change_work_on = false;
|
|
}
|
|
if (session->pcr_check_work_added) {
|
|
cancel_delayed_work_sync(&session->pcr_check_work);
|
|
session->pcr_check_work_added = false;
|
|
}
|
|
if (session->v_check_work_added) {
|
|
cancel_delayed_work_sync(&session->v_check_work);
|
|
session->v_check_work_added = false;
|
|
}
|
|
if (session->a_check_work_added) {
|
|
cancel_delayed_work_sync(&session->a_check_work);
|
|
session->a_check_work_added = false;
|
|
}
|
|
|
|
mutex_unlock(&session->session_mutex);
|
|
|
|
if (session->wq) {
|
|
flush_workqueue(session->wq);
|
|
destroy_workqueue(session->wq);
|
|
session->wq = NULL;
|
|
}
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
sync.id_pool[session->id] = 0;
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
}
|
|
|
|
static int session_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct sync_session *session = file->private_data;
|
|
|
|
msync_dbg(LOG_DEBUG, "session[%d] close pid[%d]\n",
|
|
session->id, task_tgid_nr(current));
|
|
file->private_data = NULL;
|
|
if (atomic_dec_and_test(&session->refcnt))
|
|
free_session(session);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations session_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = session_open,
|
|
.release = session_release,
|
|
.unlocked_ioctl = session_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = session_compat_ioctl,
|
|
#endif
|
|
.poll = session_poll,
|
|
};
|
|
|
|
static ssize_t session_stat_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
return sprintf(buf,
|
|
"id: %u\n"
|
|
"active v/%d a/%d m/c %d-%d\n"
|
|
"first v/%x a/%x\n"
|
|
"last v/%x a/%x\n"
|
|
"diff-ms a-w %d v-w %d a-v %d\n"
|
|
"start %d r %u\n"
|
|
"w %x pcr(%c) %x\n"
|
|
"aswitch %c\n"
|
|
"poll event %x\n",
|
|
session->id,
|
|
session->v_active, session->a_active,
|
|
session->mode, session->cur_mode,
|
|
session->first_vpts.pts,
|
|
session->first_apts.pts,
|
|
session->last_vpts.pts - session->last_vpts.delay,
|
|
session->last_apts.pts,
|
|
session->v_active && session->a_active ?
|
|
(int)(session->last_apts.pts - session->wall_clock) / 90 : -1,
|
|
session->v_active ?
|
|
(int)(session->last_vpts.pts - session->wall_clock -
|
|
session->last_vpts.delay) / 90 : -1,
|
|
session->a_active ?
|
|
(int)(session->last_apts.pts - session->last_vpts.pts +
|
|
session->last_vpts.delay) / 90 : -1,
|
|
session->clock_start, session->rate,
|
|
session->wall_clock,
|
|
session->use_pcr ? 'y' : 'n',
|
|
session->pcr_clock.pts,
|
|
session->audio_switching ? 'y' : 'n',
|
|
session->event_pending);
|
|
}
|
|
|
|
static ssize_t pcr_stat_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
if (session->mode != AVS_MODE_PCR_MASTER)
|
|
return sprintf(buf, "not pcr master mode\n");
|
|
|
|
return sprintf(buf,
|
|
"init_flag %x\n"
|
|
"init_mode %x\n"
|
|
"pcr_disc_flag %x\n"
|
|
"pcr_cont (d)%u/(c)%u\n"
|
|
"pcr_disc_clock %x\n",
|
|
session->pcr_init_flag,
|
|
session->pcr_init_mode,
|
|
session->pcr_disc_flag,
|
|
session->pcr_disc_cnt,
|
|
session->pcr_cont_cnt,
|
|
session->pcr_disc_clock);
|
|
}
|
|
|
|
static ssize_t disc_thres_min_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
return sprintf(buf, "%d", session->disc_thres_min);
|
|
}
|
|
|
|
static ssize_t disc_thres_min_store(struct class *cla,
|
|
struct class_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sync_session *session;
|
|
size_t r;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
r = kstrtoint(buf, 0, &session->disc_thres_min);
|
|
if (r != 0)
|
|
return -EINVAL;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t disc_thres_max_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
return sprintf(buf, "%d", session->disc_thres_max);
|
|
}
|
|
|
|
static ssize_t disc_thres_max_store(struct class *cla,
|
|
struct class_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sync_session *session;
|
|
size_t r;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
r = kstrtoint(buf, 0, &session->disc_thres_max);
|
|
if (r != 0)
|
|
return -EINVAL;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t free_run_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
return sprintf(buf, "%u", session->debug_freerun);
|
|
}
|
|
|
|
static ssize_t free_run_store(struct class *cla,
|
|
struct class_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sync_session *session;
|
|
size_t r;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
r = kstrtobool(buf, &session->debug_freerun);
|
|
if (r != 0)
|
|
return -EINVAL;
|
|
wakeup_poll(session, WAKE_AV);
|
|
msync_dbg(LOG_WARN, "session[%d] freerun %d\n",
|
|
session->id, session->debug_freerun);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t debug_vsync_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
return sprintf(buf, "%u", session->debug_vsync);
|
|
}
|
|
|
|
static ssize_t debug_vsync_store(struct class *cla,
|
|
struct class_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sync_session *session;
|
|
size_t r;
|
|
|
|
session = container_of(cla, struct sync_session, session_class);
|
|
r = kstrtobool(buf, &session->debug_vsync);
|
|
if (r != 0)
|
|
return -EINVAL;
|
|
return count;
|
|
}
|
|
|
|
static struct class_attribute session_attrs[] = {
|
|
__ATTR_RO(session_stat),
|
|
__ATTR_RW(disc_thres_min),
|
|
__ATTR_RW(disc_thres_max),
|
|
__ATTR_RW(free_run),
|
|
__ATTR_RO(pcr_stat),
|
|
__ATTR_RW(debug_vsync),
|
|
__ATTR_NULL
|
|
};
|
|
|
|
static struct attribute *session_class_attrs[] = {
|
|
&session_attrs[0].attr,
|
|
&session_attrs[1].attr,
|
|
&session_attrs[2].attr,
|
|
&session_attrs[3].attr,
|
|
&session_attrs[4].attr,
|
|
&session_attrs[5].attr,
|
|
NULL
|
|
};
|
|
ATTRIBUTE_GROUPS(session_class);
|
|
|
|
static int create_session(u32 id)
|
|
{
|
|
int r;
|
|
unsigned long flags;
|
|
struct sync_session *session;
|
|
|
|
session = sync.session[id];
|
|
if (!session) {
|
|
msync_dbg(LOG_ERR, "session %d empty\n", id);
|
|
return -EFAULT;
|
|
}
|
|
memset(session, 0, sizeof(*session));
|
|
|
|
session->wq = alloc_ordered_workqueue("avs_wq",
|
|
WQ_MEM_RECLAIM | WQ_FREEZABLE);
|
|
if (!session->wq) {
|
|
msync_dbg(LOG_ERR, "session %d create wq fail\n", id);
|
|
r = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
session->id = id;
|
|
atomic_set(&session->refcnt, 1);
|
|
init_waitqueue_head(&session->poll_wait);
|
|
session->clock_start = false;
|
|
session->rate = 1000;
|
|
session->wall_clock = 0;
|
|
session->wall_clock_inc_remainer = 0;
|
|
session->stat = AVS_STAT_INIT;
|
|
session->v_active = session->a_active = false;
|
|
session->v_disc = session->a_disc = false;
|
|
session->event_pending = 0;
|
|
memset(session->name, 0, sizeof(session->name));
|
|
strncpy(session->name, current->comm, sizeof(session->name) - 1);
|
|
session->first_vpts.pts = AVS_INVALID_PTS;
|
|
session->last_vpts.pts = AVS_INVALID_PTS;
|
|
session->first_apts.pts = AVS_INVALID_PTS;
|
|
session->last_apts.pts = AVS_INVALID_PTS;
|
|
session->wall_adj_thres = DEFAULT_WALL_ADJ_THRES;
|
|
session->lock = __SPIN_LOCK_UNLOCKED(session->lock);
|
|
session->disc_thres_min = DISC_THRE_MIN;
|
|
session->disc_thres_max = DISC_THRE_MAX;
|
|
session->use_pcr = false;
|
|
session->clk_dev = 0;
|
|
session->pcr_clock.pts = AVS_INVALID_PTS;
|
|
session->astart_pcr = AVS_INVALID_PTS;
|
|
session->vstart_pcr = AVS_INVALID_PTS;
|
|
session->last_check_apts = AVS_INVALID_PTS;
|
|
session->last_check_vpts = AVS_INVALID_PTS;
|
|
session->last_check_vpts_cnt = 0;
|
|
session->last_check_apts_cnt = 0;
|
|
session->last_check_pcr_clock = AVS_INVALID_PTS;
|
|
session->pcr_disc_clock = AVS_INVALID_PTS;
|
|
session->pcr_init_flag = session->pcr_init_mode = 0;
|
|
session->pcr_disc_flag = 0;
|
|
session->pcr_disc_cnt = session->pcr_cont_cnt = 0;
|
|
session->d_vsync_last = -1;
|
|
session->v_timeout = session->start_posted = false;
|
|
session->debug_freerun = session->audio_switching = false;
|
|
session->debug_vsync = false;
|
|
session->audio_drop_cnt = 0;
|
|
session->audio_drop_start = 0;
|
|
session->video_drop_cnt = 0;
|
|
session->video_drop_start = 0;
|
|
session->start_policy.policy = AMSYNC_START_ASAP;
|
|
session->start_policy.timeout = WAIT_INTERVAL;
|
|
session->sync_status_cnt = 0;
|
|
INIT_DELAYED_WORK(&session->v_wait_work, v_wait_work_func);
|
|
INIT_DELAYED_WORK(&session->video_start_work, video_start_work_func);
|
|
INIT_DELAYED_WORK(&session->transit_work, transit_work_func);
|
|
INIT_DELAYED_WORK(&session->a_wait_work, a_wait_work_func);
|
|
INIT_DELAYED_WORK(&session->audio_change_work, audio_change_work_func);
|
|
snprintf(session->atrace_v, sizeof(session->atrace_v), "vpts%u", id);
|
|
snprintf(session->atrace_a, sizeof(session->atrace_a), "apts%u", id);
|
|
|
|
mutex_init(&session->session_mutex);
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
list_add(&session->node, &sync.head);
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
msync_dbg(LOG_INFO, "av session %d created\n", id);
|
|
return 0;
|
|
|
|
err:
|
|
if (session->wq)
|
|
destroy_workqueue(session->wq);
|
|
return r;
|
|
}
|
|
|
|
static void destroy_session(u32 id)
|
|
{
|
|
struct sync_session *session = NULL;
|
|
struct list_head *p;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
list_for_each(p, &sync.head) {
|
|
session = list_entry(p, struct sync_session, node);
|
|
if (session->id == id)
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
if (!session)
|
|
return;
|
|
|
|
if (atomic_dec_and_test(&session->refcnt))
|
|
free_session(session);
|
|
msync_dbg(LOG_INFO, "av session %d deref\n", id);
|
|
}
|
|
|
|
static long msync_ioctl(struct file *file, unsigned int cmd, ulong arg)
|
|
{
|
|
u32 i;
|
|
int rc;
|
|
unsigned long flags;
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
switch (cmd) {
|
|
case AMSYNC_IOC_ALLOC_SESSION:
|
|
{
|
|
struct msync_priv *priv = NULL;
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
for (i = 0 ; i < MAX_SESSION_NUM ; i++) {
|
|
if (!sync.id_pool[i]) {
|
|
sync.id_pool[i] = 1;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
if (i >= MAX_SESSION_NUM) {
|
|
msync_dbg(LOG_ERR, "out of sync session\n");
|
|
return -EMFILE;
|
|
}
|
|
|
|
priv = vmalloc(sizeof(*priv));
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
rc = create_session(i);
|
|
if (rc) {
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
sync.id_pool[i] = 0;
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
msync_dbg(LOG_ERR, "fail to create session %d\n", i);
|
|
vfree(priv);
|
|
return rc;
|
|
}
|
|
put_user(i, (u32 __user *)argp);
|
|
priv->session_id = i;
|
|
file->private_data = priv;
|
|
break;
|
|
}
|
|
case AMSYNC_IOC_REMOVE_SESSION:
|
|
{
|
|
u32 id;
|
|
|
|
get_user(id, (u32 __user *)argp);
|
|
if (id >= MAX_SESSION_NUM) {
|
|
msync_dbg(LOG_ERR, "destroy invalid id %d\n", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
if (!sync.id_pool[id]) {
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
return -EBADF;
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
|
|
destroy_session(id);
|
|
vfree(file->private_data);
|
|
file->private_data = NULL;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long msync_compat_ioctl(struct file *filp, u32 cmd, ulong arg)
|
|
{
|
|
long ret;
|
|
|
|
arg = (ulong)compat_ptr(arg);
|
|
ret = msync_ioctl(filp, cmd, arg);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int msync_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int msync_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct msync_priv *priv = file->private_data;
|
|
int id = MAX_SESSION_NUM;
|
|
unsigned long flags;
|
|
|
|
if (!priv)
|
|
return 0;
|
|
id = priv->session_id;
|
|
if (id >= MAX_SESSION_NUM) {
|
|
msync_dbg(LOG_ERR, "destroy invalid id %d\n", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
if (!sync.id_pool[id]) {
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
return -EBADF;
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
|
|
destroy_session(id);
|
|
vfree(priv);
|
|
file->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations msync_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = msync_open,
|
|
.release = msync_release,
|
|
.unlocked_ioctl = msync_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = msync_compat_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static ssize_t log_level_show(struct class *cla,
|
|
struct class_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", log_level);
|
|
}
|
|
|
|
static ssize_t log_level_store(struct class *cla,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int r;
|
|
|
|
r = kstrtoint(buf, 0, &log_level);
|
|
if (r < 0)
|
|
return -EINVAL;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t vout_mode_show(struct class *cla,
|
|
struct class_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "den %d num %d inc %d\n",
|
|
sync.sync_duration_den,
|
|
sync.sync_duration_num,
|
|
sync.vsync_pts_inc);
|
|
}
|
|
|
|
static ssize_t list_session_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct sync_session *session = NULL;
|
|
struct list_head *p;
|
|
unsigned long flags;
|
|
char tmp[256];
|
|
int i = 0;
|
|
|
|
spin_lock_irqsave(&sync.lock, flags);
|
|
list_for_each(p, &sync.head) {
|
|
session = list_entry(p, struct sync_session, node);
|
|
i += snprintf(tmp + i, sizeof(tmp) - i,
|
|
"%d: %s\n", session->id, session->name);
|
|
if (i >= sizeof(tmp))
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&sync.lock, flags);
|
|
if (i == 0)
|
|
return sprintf(buf, "empty\n");
|
|
|
|
return snprintf(buf, sizeof(tmp), "%s", tmp);
|
|
}
|
|
|
|
static ssize_t start_buf_thres_show(struct class *cla,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%d in 90K unit\n",
|
|
sync.start_buf_thres);
|
|
}
|
|
|
|
static ssize_t start_buf_thres_store(struct class *cla,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int r, thres;
|
|
|
|
r = kstrtouint(buf, 0, &thres);
|
|
if (r != 0)
|
|
return -EINVAL;
|
|
if (thres >= UNIT90K) {
|
|
msync_dbg(LOG_ERR, "must less than 90K");
|
|
return -EINVAL;
|
|
}
|
|
sync.start_buf_thres = thres;
|
|
return count;
|
|
}
|
|
|
|
static struct class_attribute msync_attrs[] = {
|
|
__ATTR_RW(log_level),
|
|
__ATTR_RO(list_session),
|
|
__ATTR_RO(vout_mode),
|
|
__ATTR_RW(start_buf_thres),
|
|
__ATTR_NULL
|
|
};
|
|
|
|
static struct attribute *msync_class_attrs[] = {
|
|
&msync_attrs[0].attr,
|
|
&msync_attrs[1].attr,
|
|
&msync_attrs[2].attr,
|
|
&msync_attrs[3].attr,
|
|
NULL
|
|
};
|
|
ATTRIBUTE_GROUPS(msync_class);
|
|
|
|
static struct class msync_class = {
|
|
.name = "aml_msync",
|
|
.class_groups = msync_class_groups,
|
|
};
|
|
|
|
static void session_dev_cleanup(void)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0 ; i < MAX_SESSION_NUM ; i++) {
|
|
struct sync_session *session = sync.session[i];
|
|
|
|
if (!session)
|
|
continue;
|
|
|
|
device_destroy(&session->session_class,
|
|
MKDEV(AMSYNC_SESSION_MAJOR, i));
|
|
class_unregister(&session->session_class);
|
|
vfree(session->class_name);
|
|
vfree(session->device_name);
|
|
vfree(session);
|
|
sync.session[i] = NULL;
|
|
}
|
|
}
|
|
|
|
int __init msync_init(void)
|
|
{
|
|
int r = 0, i;
|
|
|
|
r = register_chrdev(AMSYNC_MAJOR, "aml_msync", &msync_fops);
|
|
if (r < 0) {
|
|
msync_dbg(LOG_ERR,
|
|
"Can't register major for aml_msync device\n");
|
|
return r;
|
|
}
|
|
sync.multi_sync_major = r;
|
|
|
|
r = register_chrdev(AMSYNC_SESSION_MAJOR, "aml_msync_session", &session_fops);
|
|
if (r < 0) {
|
|
msync_dbg(LOG_ERR,
|
|
"Can't register major for aml_msync_session device\n");
|
|
goto err;
|
|
}
|
|
sync.session_major = r;
|
|
|
|
r = class_register(&msync_class);
|
|
if (r) {
|
|
msync_dbg(LOG_ERR, "msync class fail.\n");
|
|
goto err2;
|
|
}
|
|
|
|
sync.msync_dev = device_create(&msync_class, NULL,
|
|
MKDEV(AMSYNC_MAJOR, 0), NULL, "aml_msync");
|
|
|
|
if (IS_ERR(sync.msync_dev)) {
|
|
msync_dbg(LOG_ERR, "Can't create aml_msync device\n");
|
|
r = -ENXIO;
|
|
goto err3;
|
|
}
|
|
sync.lock = __SPIN_LOCK_UNLOCKED(sync.lock);
|
|
INIT_LIST_HEAD(&sync.head);
|
|
msync_update_mode();
|
|
sync.msync_notifier.notifier_call = msync_notify_callback;
|
|
vout_register_client(&sync.msync_notifier);
|
|
sync.ready = true;
|
|
sync.start_buf_thres = UNIT90K / 5;
|
|
|
|
#ifdef CONFIG_AMLOGIC_MEDIA_MINFO
|
|
sync.msync_minfo_notifier.notifier_call = minfo_notify_callback;
|
|
media_info_register_event("vport/latency", &sync.msync_minfo_notifier);
|
|
#endif
|
|
|
|
for (i = 0 ; i < MAX_SESSION_NUM ; i++) {
|
|
struct sync_session *s = NULL;
|
|
|
|
sync.session[i] = vzalloc(sizeof(*s));
|
|
if (!sync.session[i]) {
|
|
r = -ENOMEM;
|
|
goto err4;
|
|
}
|
|
s = sync.session[i];
|
|
|
|
s->class_name = vmalloc(AVS_DEV_NAME_MAX);
|
|
if (!s->class_name) {
|
|
r = -ENOMEM;
|
|
goto err4;
|
|
}
|
|
s->device_name = vmalloc(AVS_DEV_NAME_MAX);
|
|
if (!s->device_name) {
|
|
r = -ENOMEM;
|
|
goto err4;
|
|
}
|
|
|
|
snprintf(s->device_name, AVS_DEV_NAME_MAX, "avsync_s%d", i);
|
|
snprintf(s->class_name, AVS_DEV_NAME_MAX, "avsync_session%d", i);
|
|
|
|
s->session_class.name = s->class_name;
|
|
s->session_class.class_groups = session_class_groups;
|
|
|
|
r = class_register(&s->session_class);
|
|
if (r) {
|
|
msync_dbg(LOG_ERR, "session %d class fail\n", i);
|
|
goto err4;
|
|
}
|
|
|
|
s->session_dev = device_create(&s->session_class, NULL,
|
|
MKDEV(AMSYNC_SESSION_MAJOR, i), NULL, s->device_name);
|
|
if (IS_ERR(s->session_dev)) {
|
|
msync_dbg(LOG_ERR, "Can't create avsync_session device %d\n", i);
|
|
r = -ENXIO;
|
|
goto err4;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
err4:
|
|
session_dev_cleanup();
|
|
err3:
|
|
class_unregister(&msync_class);
|
|
err2:
|
|
unregister_chrdev(AMSYNC_SESSION_MAJOR, "aml_msync_session");
|
|
err:
|
|
unregister_chrdev(AMSYNC_MAJOR, "aml_msync");
|
|
return r;
|
|
}
|
|
|
|
void __exit msync_exit(void)
|
|
{
|
|
session_dev_cleanup();
|
|
if (sync.msync_dev) {
|
|
class_unregister(&msync_class);
|
|
device_destroy(&msync_class, MKDEV(AMSYNC_MAJOR, 0));
|
|
}
|
|
unregister_chrdev(AMSYNC_MAJOR, "aml_msync");
|
|
vout_unregister_client(&sync.msync_notifier);
|
|
#ifdef CONFIG_AMLOGIC_MEDIA_MINFO
|
|
media_info_unregister_event(&sync.msync_minfo_notifier);
|
|
#endif
|
|
}
|
|
|
|
#ifndef MODULE
|
|
module_init(msync_init);
|
|
module_exit(msync_exit);
|
|
MODULE_LICENSE("GPL");
|
|
#endif
|