blob: 5b97ae58e653038d03fe6acdf373014e48f8f73b [file] [log] [blame]
/*
* 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/compat.h>
#include <linux/amlogic/media/vout/vout_notify.h>
#include <linux/amlogic/major.h>
#include <uapi/linux/amlogic/msync.h>
#define KERNEL_ATRACE_TAG KERNEL_ATRACE_TAG_MSYNC
#include <trace/events/meson_atrace.h>
#define UNIT90K (90000)
#define DEFAULT_WALL_ADJ_THRES (UNIT90K / 10) //100ms
#define MAX_SESSION_NUM 8
#define CHECK_INTERVAL ((HZ / 10) * 3) //300ms
#define WAIT_INTERVAL (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 av_sync_stat {
AVS_STAT_INIT,
AVS_STAT_STARTING,
AVS_STAT_STARTED,
AVS_STAT_PAUSED,
AVS_STAT_TRANSITION,
};
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)
struct sync_session {
u32 id;
atomic_t refcnt;
struct list_head node;
/* mutext for event handling */
struct mutex session_mutex;
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 freerun_period;
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 pcr_disc_cnt;
u32 pcr_cont_cnt;
u32 pcr_disc_clock;
int clk_dev; /*pcr mono deviation. Positive if pcr is faster.*/
struct pts_tri first_vpts;
struct pts_tri last_vpts;
struct pts_tri first_apts;
struct pts_tri last_apts;
struct device *session_dev;
/* wait queue for poll */
wait_queue_head_t poll_wait;
u32 event_pending;
/* start policy timer */
bool wait_work_on;
struct delayed_work wait_work;
bool pcr_work_on;
struct delayed_work pcr_start_work;
bool transit_work_on;
struct delayed_work transit_work;
bool audio_change_work_on;
struct delayed_work audio_change_work;
bool start_posted;
bool v_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];
/* audio disc recovery */
int audio_drop_cnt;
u64 audio_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];
/* callback for vout_register_client() */
struct notifier_block msync_notifier;
/* 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,
};
#define msync_dbg(level, x...) \
do { \
if ((level) <= log_level) \
pr_info(x); \
} while (0)
static struct msync sync;
static int log_level;
static void pcr_set(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 = sync.sync_duration_den;
u64 now;
/* both den and num * 20 to handle
* speed with multiple of 0.05
*/
den = den * 20 * session->rate / 1000;
spin_lock_irqsave(&session->lock, flags);
temp = div_u64_rem(90000ULL * den, sync.sync_duration_num * 20, &r);
r /= 20;
session->wall_clock += temp;
session->wall_clock_inc_remainer += r;
if (session->wall_clock_inc_remainer >= sync.sync_duration_num) {
session->wall_clock++;
session->wall_clock_inc_remainer -= sync.sync_duration_num;
}
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;
}
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;
wake_up_interruptible(&session->poll_wait);
}
static void transit_work_func(struct work_struct *work)
{
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) {
session_set_wall_clock(session, session->last_apts.wall_clock);
session->stat = AVS_STAT_STARTED;
session->cur_mode = AVS_MODE_A_MASTER;
session->transit_work_on = false;
}
mutex_unlock(&session->session_mutex);
}
static void 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, wait_work);
mutex_lock(&session->session_mutex);
if (!session->wait_work_on) {
mutex_unlock(&session->session_mutex);
return;
}
if (!VALID_PTS(session->first_vpts.pts)) {
msync_dbg(LOG_WARN, "[%d]wait video timeout\n",
session->id);
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;
wake = true;
}
session->wait_work_on = false;
mutex_unlock(&session->session_mutex);
if (wake)
wakeup_poll(session, WAKE_A);
}
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_policy.policy == AMSYNC_START_ALIGN &&
!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 pcr_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, pcr_start_work);
mutex_lock(&session->session_mutex);
if (!session->pcr_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->pcr_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 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;
/* give audio enough buffer but do not block video */
if (min_pts == apts)
min_pts -= thres;
else
min_pts = pts_later(min_pts - thres, vpts);
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 0
if (VALID_PTS(cur_pcr) && VALID_PTS(cur_vpts) && VALID_PTS(cur_apts)) {
u32 gap_pa, gap_pv, gap_av;
gap_pa = abs_diff(cur_pcr, cur_apts);
gap_av = abs_diff(cur_apts, cur_vpts);
gap_pv = abs_diff(cur_pcr, cur_vpts);
if (gap_pa > MAX_GAP && gap_pv > MAX_GAP) {
if (gap_av > MAX_GAP)
ref_pcr = cur_vpts;
else
ref_pcr = min_pts;
session->pcr_init_flag |= INITCHECK_VPTS;
session->pcr_init_mode = INIT_PRIORITY_VIDEO;
use_pcr_clock(session, false, ref_pcr);
msync_dbg(LOG_TRACE, "[%d]%d pcr %u v %u a %u\n",
session->id, __LINE__,
cur_pcr, cur_vpts, cur_apts);
msync_dbg(LOG_TRACE, "[%d]%d disable pcr %u\n",
session->id, __LINE__, ref_pcr);
return;
}
if (cur_vpts < cur_pcr && cur_vpts < cur_apts) {
session->pcr_init_flag |= INITCHECK_VPTS;
session->pcr_init_mode = INIT_PRIORITY_VIDEO;
use_pcr_clock(session, false, cur_vpts);
msync_dbg(LOG_TRACE, "[%d]%d disable pcr %u\n",
session->id, __LINE__, cur_vpts);
return;
}
}
if (VALID_PTS(cur_pcr) && VALID_PTS(min_pts)) {
session->pcr_init_flag |= INITCHECK_PCR;
if (VALID_PTS(cur_vpts) &&
abs_diff(cur_pcr, cur_vpts) > PCR_INVALID_THRES) {
if (VALID_PTS(session->first_vpts.pts))
ref_pcr = session->first_vpts.pts;
else
ref_pcr = cur_vpts;
session->pcr_init_mode = INIT_PRIORITY_VIDEO;
use_pcr_clock(session, false, ref_pcr);
msync_dbg(LOG_TRACE, "[%d]%d disable pcr %u\n",
session->id, __LINE__, ref_pcr);
} else if (((cur_pcr > min_pts) &&
(cur_pcr - min_pts) > (UNIT90K / 2)) ||
session->use_pcr) {
if (VALID_PTS(cur_apts) &&
VALID_PTS(cur_vpts) &&
abs_diff(cur_apts, cur_vpts) > MAX_GAP) {
if (VALID_PTS(session->first_vpts.pts))
ref_pcr = session->first_vpts.pts;
else
ref_pcr = cur_vpts;
} else {
ref_pcr = min_pts;
}
session->pcr_init_mode = INIT_PRIORITY_VIDEO;
use_pcr_clock(session, false, ref_pcr);
msync_dbg(LOG_TRACE, "[%d]%d disable pcr %u\n",
session->id, __LINE__, ref_pcr);
} else {
session->pcr_init_mode = INIT_PRIORITY_PCR;
use_pcr_clock(session, true, 0);
msync_dbg(LOG_TRACE, "[%d]%d enable pcr %u\n",
session->id, __LINE__, ref_pcr);
}
return;
}
#endif
if (VALID_PTS(session->first_apts.pts)) {
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,
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->pcr_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_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;
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->wait_work,
msecs_to_jiffies(delay));
} else {
session->clock_start = true;
cancel_delayed_work_sync(&session->wait_work);
session->wait_work_on = false;
session->start_posted = true;
wakeup = true;
msync_dbg(LOG_INFO, "[%d]%d video start %u\n",
session->id, __LINE__, pts);
}
}
} else if (session->mode == AVS_MODE_IPTV) {
update_f_vpts(session, 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 keep AMASTER\n",
session->id, __LINE__, pts);
}
} 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->pcr_work_on)
cancel_delayed_work_sync(&session->pcr_start_work);
queue_delayed_work(session->wq,
&session->pcr_start_work,
CHECK_INTERVAL);
session->stat = AVS_STAT_STARTING;
session->pcr_work_on = true;
} else {
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 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;
mutex_lock(&session->session_mutex);
if (session->audio_switching) {
update_f_apts(session, pts);
if (session->wall_clock > pts ||
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_policy.policy == AMSYNC_START_ALIGN &&
!session->start_posted) {
session->start_posted = true;
wakeup = true;
}
} else if (session->start_policy.policy == AMSYNC_START_ALIGN) {
// normal case, wait audio start point
u32 diff_ms = (pts - session->wall_clock) / 90;
u32 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->wait_work_on)
cancel_delayed_work_sync(&session->wait_work);
queue_delayed_work(session->wq,
&session->wait_work,
session->start_policy.timeout);
session->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->start_posted = true;
session->stat = AVS_STAT_STARTED;
wakeup = true;
goto exit;
}
/* use video as start, delay audio start */
delay = (pts - vpts) / 90;
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;
if (session->wait_work_on)
cancel_delayed_work_sync(&session->wait_work);
queue_delayed_work(session->wq,
&session->wait_work,
msecs_to_jiffies(delay));
session->wait_work_on = true;
session->stat = AVS_STAT_STARTING;
} else {
session->clock_start = true;
session->stat = AVS_STAT_STARTED;
}
}
} else if (session->mode == AVS_MODE_IPTV) {
update_f_apts(session, pts);
if (session->start_policy.policy == AMSYNC_START_ASAP &&
!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;
msync_dbg(LOG_INFO, "[%d]%d audio start %u\n",
session->id, __LINE__, pts);
} else if (session->start_policy.policy == AMSYNC_START_ASAP) {
/* transition start from V to A master */
session->stat = AVS_STAT_TRANSITION;
/* enter grace period */
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;
wakeup = true;
msync_dbg(LOG_INFO, "[%d]%d audio start %u\n",
session->id, __LINE__, pts);
}
} 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);
if (session->start_policy.policy == AMSYNC_START_ALIGN &&
session->pcr_work_on) {
cancel_delayed_work_sync(&session->pcr_start_work);
session->pcr_work_on = false;
}
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;
if (!session->audio_drop_cnt)
session->audio_drop_start = ktime_get_raw_ns();
session->last_apts = session->first_apts;
session->audio_drop_cnt++;
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_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;
}
mutex_unlock(&session->session_mutex);
}
static void session_video_stop(struct sync_session *session)
{
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->stat = AVS_STAT_INIT;
msync_dbg(LOG_INFO, "[%d]%s clock stop\n",
session->id, __func__);
} else if (session->mode == AVS_MODE_IPTV) {
session->cur_mode = AVS_MODE_A_MASTER;
session_set_wall_clock(session, session->last_apts.wall_clock);
msync_dbg(LOG_INFO, "[%d]%s to Amaster\n",
session->id, __func__);
} else if (session->mode == AVS_MODE_PCR_MASTER) {
if (!session->a_active)
use_pcr_clock(session, true, 0);
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);
wakeup_poll(session, WAKE_A);
}
static void session_audio_stop(struct sync_session *session)
{
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->stat = AVS_STAT_INIT;
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;
}
msync_dbg(LOG_INFO, "[%d]%s audio switching stop audio\n",
session->id, __func__);
} else if (session->mode == AVS_MODE_IPTV) {
session->cur_mode = AVS_MODE_V_MASTER;
session_set_wall_clock(session, session->last_vpts.wall_clock);
msync_dbg(LOG_INFO, "[%d]%s to Vmaster\n",
session->id, __func__);
} else if (session->mode == AVS_MODE_PCR_MASTER) {
if (!session->v_active)
use_pcr_clock(session, true, 0);
session->first_apts.pts = AVS_INVALID_PTS;
session->last_apts.pts = AVS_INVALID_PTS;
session->last_check_apts_cnt = 0;
}
mutex_unlock(&session->session_mutex);
wakeup_poll(session, WAKE_V);
}
/*
*Video and audio PTS discontinuity happen typically with a loopback
*playback, with same bit stream play in loop and PTS wrap back from
*starting point.
*When video discontinuity happens first, wall clock is set
*immediately to keep video running in VMATSER mode. This
*mode is restored to AMASTER when audio discontinuity follows,
*or apts is close to wall clock in a later time.
*When audio discontinuity happens first, VMASTER mode is
*set to keep video running w/o setting wall clock. This mode
*is restored to AMASTER when video discontinuity follows.
*And wall clock is restored along with video time stamp.
*/
static void session_video_disc_iptv(struct sync_session *session, u32 pts)
{
u32 last_pts = session->last_vpts.pts;
u32 wall = session->wall_clock;
if (session->mode != AVS_MODE_PCR_MASTER &&
session->mode != AVS_MODE_IPTV)
return;
mutex_lock(&session->session_mutex);
if (VALID_PTS(session->pcr_clock.pts))
wall = session->pcr_clock.pts;
if (VALID_PTS(session->pcr_clock.pts) &&
abs_diff(pts, last_pts) > session->disc_thres_min) {
session->v_disc = true;
if (session->a_disc) {
session->v_disc = false;
session->a_disc = false;
session_set_wall_clock(session, pts);
msync_dbg(LOG_INFO, "[%d]%s reset wall %u\n",
session->id, __func__, pts);
} else {
session_set_wall_clock(session, last_pts);
msync_dbg(LOG_INFO, "[%d]%s reset wall %u\n",
session->id, __func__, pts);
}
goto exit;
}
if (abs_diff(pts, last_pts) > session->disc_thres_min)
session->v_disc = true;
exit:
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);
} else if (pcr_v_disc(session, pts)) {
/* TODO: dead code */
u32 pcr = pcr_get(session);
if (session->use_pcr) {
if ((int)(pts - (pcr + 10 * UNIT90K)) > 0) {
use_pcr_clock(session, false, pts);
msync_dbg(LOG_DEBUG,
"[%d]%d disable pcr %u\n",
__LINE__, session->id, pts);
} else {
session_set_wall_clock(session, pts);
msync_dbg(LOG_DEBUG,
"[%d]%d vdisc set wall %u\n",
__LINE__, session->id, pts);
}
}
}
session->pcr_disc_flag |= VIDEO_DISC;
session->last_vpts.pts = pts;
session->last_vpts.wall_clock = session->wall_clock;
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);
//u32 last_pts = session->last_apts.pts;
if (session->mode != AVS_MODE_PCR_MASTER &&
session->mode != AVS_MODE_IPTV)
goto exit;
if (session->mode == AVS_MODE_PCR_MASTER) {
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) {
if (!session->audio_drop_cnt)
session->audio_drop_start = ktime_get_raw_ns();
session->audio_drop_cnt++;
} else {
session->audio_drop_cnt = 0;
msync_dbg(LOG_TRACE, "[%d] clear drop cnt\n",
session->id);
}
}
exit:
mutex_unlock(&session->session_mutex);
}
static void session_audio_switch(struct sync_session *session, u32 start)
{
//u32 last_pts = session->last_apts.pts;
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)
{
if (session->mode == AVS_MODE_V_MASTER ||
session->mode == AVS_MODE_FREE_RUN) {
struct pts_tri *p = &session->last_vpts;
u32 pts = p->pts;
if (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)
{
if (session->mode == AVS_MODE_A_MASTER) {
struct pts_tri *p = &session->last_apts;
u32 pts = p->pts;
if (session->debug_freerun)
return;
if (pts > p->delay)
pts -= p->delay;
if (abs_diff(pts, session->wall_clock) >=
session->wall_adj_thres) {
unsigned long flags;
if (session->rate != 1000) {
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);
}
} else if (session->mode == AVS_MODE_PCR_MASTER) {
if (session->audio_drop_cnt)
msync_dbg(LOG_TRACE, "[%d] a %u clear drop cnt\n",
session->id, session->last_apts.pts);
session->audio_drop_cnt = 0;
}
}
static void pcr_check(struct sync_session *session)
{
u32 checkin_vpts = AVS_INVALID_PTS;
u32 checkin_apts = AVS_INVALID_PTS;
u32 min_pts = AVS_INVALID_PTS;
int max_gap = 40;
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;
}
#if 0
//TODO nowhere to clear pcr_init_flag to 0
if (!session->pcr_init_flag) {
u64 cur_time = div64_u64((u64)jiffies * UNIT90K, HZ);
if (!VALID_PTS(checkin_apts) && !VALID_PTS(checkin_vpts)) {
session->first_time_record = cur_time;
} else if ((cur_time - session->first_time_record <
3 * UNIT90K) && session->v_active) {
//do nothing
}
return;
}
if (session->pcr_init_mode != INIT_PRIORITY_PCR) {
if (V_DISC_SET(session->pcr_disc_flag) &&
VALID_PTS(checkin_apts) &&
VALID_PTS(checkin_vpts)) {
msync_dbg(LOG_INFO, "[%d] %d pcr_disc reset %u\n",
session->id, __LINE__, checkin_vpts);
use_pcr_clock(session, false, checkin_vpts);
session->pcr_disc_flag = 0;
}
return;
}
#endif
min_pts = pts_early(checkin_apts, checkin_vpts);
#if 0
if (session->pcr_disc_cnt > 100 && VALID_PTS(min_pts)) {
min_pts -= sync.start_buf_thres;
msync_dbg(LOG_INFO, "[%d] %d pcr_disc reset %u\n",
session->id, __LINE__, min_pts);
session->pcr_init_mode = INIT_PRIORITY_VIDEO;
use_pcr_clock(session, false, min_pts);
}
#endif
/* 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,
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 {
if ((int)(checkin_vpts - checkin_apts) > 0)
min_pts = checkin_vpts;
else
min_pts = checkin_apts - 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,
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 = checkin_apts - sync.start_buf_thres;
use_pcr_clock(session, false, min_pts);
session->pcr_disc_flag &= ~AUDIO_DISC;
session->audio_drop_cnt = 0;
msync_dbg(LOG_INFO,
"[%d] %d adj pcr %u(w) --> %u(m)\n",
session->id, __LINE__,
session->wall_clock, min_pts);
}
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);
pcr_check(session);
queue_delayed_work(session->wq,
&session->pcr_check_work,
TEN_MS_INTERVAL);
}
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\n",
session->id, session->mode, mode);
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 (session->mode == AVS_MODE_PCR_MASTER &&
mode == AVS_MODE_FREE_RUN) {
session->cur_mode = AVS_MODE_FREE_RUN;
wakeup = true;
} 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] policys %u timeout %u, old timeout=%d.\n",
session->id, policy.policy,
policy.timeout, session->start_policy.timeout);
session->start_policy.policy = policy.policy;
if (policy.timeout >= 0) {
session->start_policy.timeout = msecs_to_jiffies(policy.timeout);
msync_dbg(LOG_DEBUG,
"session[%d]new timeout %u.\n",
session->id, session->start_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);
}
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);
ATRACE_COUNTER(session->atrace_a, ts.pts);
}
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.audio_switch = session->audio_switching;
if (copy_to_user(argp, &stat, sizeof(stat)))
return -EFAULT;
if (session->v_active && flag == SRC_V)
session->event_pending &= ~SRC_V;
else if (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;
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;
}
default:
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);
mutex_lock(&session->session_mutex);
if (session->wait_work_on)
cancel_delayed_work_sync(&session->wait_work);
if (session->transit_work_on)
cancel_delayed_work_sync(&session->transit_work);
if (session->pcr_work_on)
cancel_delayed_work_sync(&session->pcr_start_work);
if (session->audio_change_work_on)
cancel_delayed_work_sync(&session->audio_change_work);
if (session->pcr_check_work_added)
cancel_delayed_work_sync(&session->pcr_check_work);
mutex_unlock(&session->session_mutex);
if (session->wq) {
flush_workqueue(session->wq);
destroy_workqueue(session->wq);
}
device_destroy(&session->session_class,
MKDEV(AMSYNC_SESSION_MAJOR, session->id));
class_unregister(&session->session_class);
vfree(session->session_class.name);
spin_lock_irqsave(&sync.lock, flags);
sync.id_pool[session->id] = 0;
spin_unlock_irqrestore(&sync.lock, flags);
vfree(session);
}
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,
"active v/%d a/%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 %d\n"
"w %x pcr(%c) %x\n"
"audio switch %c\n",
session->v_active, session->a_active,
session->first_vpts.pts,
session->first_apts.pts,
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,
session->clock_start, session->rate,
session->wall_clock,
session->use_pcr ? 'y' : 'n',
session->pcr_clock.pts,
session->audio_switching ? 'y' : 'n');
}
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);
#define AVS_DEV_NAME_MAX 20
static int create_session(u32 id)
{
int r;
unsigned long flags;
char device_name[AVS_DEV_NAME_MAX];
char *class_name = NULL;
struct sync_session *session;
class_name = vmalloc(AVS_DEV_NAME_MAX);
if (!class_name) {
r = -ENOMEM;
goto err;
}
snprintf(device_name, AVS_DEV_NAME_MAX, "avsync_s%d", id);
snprintf(class_name, AVS_DEV_NAME_MAX, "avsync_session%d", id);
session = vzalloc(sizeof(*session));
if (!session) {
r = -ENOMEM;
goto err;
}
session->session_class.name = class_name;
session->session_class.class_groups = session_class_groups;
r = class_register(&session->session_class);
if (r) {
msync_dbg(LOG_ERR, "session %d class fail\n", id);
goto err2;
}
session->session_dev = device_create(&session->session_class, NULL,
MKDEV(AMSYNC_SESSION_MAJOR, id), NULL, device_name);
if (IS_ERR(session->session_dev)) {
msync_dbg(LOG_ERR, "Can't create avsync_session device %d\n", id);
r = -ENXIO;
goto err3;
}
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 err4;
}
session->id = id;
atomic_set(&session->refcnt, 1);
init_waitqueue_head(&session->poll_wait);
session->rate = 1000;
session->stat = AVS_STAT_INIT;
strncpy(session->name, current->comm, sizeof(session->name));
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->pcr_clock.pts = AVS_INVALID_PTS;
session->last_check_apts = AVS_INVALID_PTS;
session->last_check_vpts = AVS_INVALID_PTS;
session->last_check_pcr_clock = AVS_INVALID_PTS;
session->pcr_disc_clock = AVS_INVALID_PTS;
session->d_vsync_last = -1;
session->start_policy.policy = AMSYNC_START_ASAP;
session->start_policy.timeout = WAIT_INTERVAL;
INIT_DELAYED_WORK(&session->wait_work, wait_work_func);
INIT_DELAYED_WORK(&session->transit_work, transit_work_func);
INIT_DELAYED_WORK(&session->pcr_start_work, pcr_start_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;
err4:
device_destroy(&session->session_class,
MKDEV(AMSYNC_SESSION_MAJOR, id));
err3:
class_unregister(&session->session_class);
err2:
vfree(session);
err:
vfree(class_name);
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;
spin_lock_irqsave(&sync.lock, flags);
list_del(&session->node);
spin_unlock_irqrestore(&sync.lock, flags);
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,
};
int __init msync_init(void)
{
int r = 0;
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;
return 0;
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)
{
if (sync.msync_dev) {
class_unregister(&msync_class);
device_destroy(&msync_class, MKDEV(AMSYNC_MAJOR, 0));
}
unregister_chrdev(AMSYNC_MAJOR, "aml_msync");
}
#ifndef MODULE
module_init(msync_init);
module_exit(msync_exit);
#endif
MODULE_LICENSE("GPL");