// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2025 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_AMLOGIC_MEDIA_MINFO #include #endif #define KERNEL_ATRACE_TAG KERNEL_ATRACE_TAG_MSYNC #include #include #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 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 wait_work_on; struct delayed_work wait_work; bool video_start_work_on; struct delayed_work video_start_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; /* async audio start */ bool start_posted; bool v_timeout; bool a_timeout; bool resync; /* 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 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 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) 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->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->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 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 (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_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->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; 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); 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->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 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->wait_work_on) cancel_delayed_work_sync(&session->wait_work); queue_delayed_work(session->wq, &session->wait_work, msecs_to_jiffies(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->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->wait_work_on) cancel_delayed_work_sync(&session->wait_work); queue_delayed_work(session->wq, &session->wait_work, delay_jiffy); session->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); 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; 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->wait_work_on) { cancel_delayed_work_sync(&session->wait_work); session->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); } 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); 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; u32 wall_adj_thres = session->wall_adj_thres; if (session->mode == AVS_MODE_A_MASTER) { if (session->debug_freerun) return; if (pts > p->delay) pts -= p->delay; reset_wall = true; if (session->resync) { wall_adj_thres = 0; session->resync = false; } } 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) >= 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; 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); } 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; use_pcr_clock(session, false, min_pts); session->pcr_disc_flag &= ~VIDEO_DISC; session->video_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_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 = 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; } 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, 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); 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; case AVS_RESYNC: session->resync = true; 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; 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); } 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: 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); session->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->pcr_work_on) { cancel_delayed_work_sync(&session->pcr_start_work); session->pcr_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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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(const struct class *cla, const 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; spin_lock_init(&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->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->wait_work, 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->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; 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; 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 = kmalloc(sizeof(*priv), GFP_KERNEL); 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); kfree(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); kfree(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); kfree(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(const struct class *class, const struct class_attribute *attr, char *buf) { return sprintf(buf, "%d\n", log_level); } static ssize_t log_level_store(const struct class *class, const 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(const struct class *class, const 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(const struct class *class, const 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(const struct class *class, const 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(const struct class *class, const 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); kfree(session->class_name); kfree(session->device_name); kfree(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; } spin_lock_init(&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] = kzalloc(sizeof(*s), GFP_KERNEL); if (!sync.session[i]) { r = -ENOMEM; goto err4; } s = sync.session[i]; s->class_name = kmalloc(AVS_DEV_NAME_MAX, GFP_KERNEL); if (!s->class_name) { r = -ENOMEM; goto err4; } s->device_name = kmalloc(AVS_DEV_NAME_MAX, GFP_KERNEL); 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