【Linux】关于T113 Linux 5.10内核系统显示卡顿原因调查
0.关于T113 Linux 5.10内核系统显示卡顿原因调查1.现象T113平台外接10.1寸MIPI DSI屏幕或者7寸RGB屏幕使用Linux 5.4内核加openwrt系统显示流畅使用Linux 5.10内核加buildroot系统显示卡顿。两个内核使用相同的DSI timimg。2.问题分析2.1.查看disp状态发现skip counter基本与irq持平fps非常低。2.2.分析代码2.2.1. skip counter计算代码void sync_event_proc(u32 disp, bool timeout) { int ret; unsigned long flags; gdisp.screen[disp].health_info.irq_cnt; DISP_TRACE_INT2(vsync-irq, disp, gdisp.screen[disp].health_info.irq_cnt 0x01); DISP_TRACE_INT2(vsync-timeout, disp, timeout ? 1 : 0); DISP_TRACE_INT2(vsync-en, disp, gdisp.screen[disp].vsync_event_en); if (!disp_feat_is_using_rcq(disp)) { if (!timeout) disp_sync_checkin(disp); else gdisp.screen[disp].health_info.skip_cnt;// 在这里增加 spin_lock_irqsave(gdisp.screen[disp].flag_lock, flags); if ((bsp_disp_cfg_get(disp) 0) (!timeout)) { gdisp.screen[disp].have_cfg_reg true; spin_unlock_irqrestore(gdisp.screen[disp].flag_lock, flags); disp_sync_all(disp, true); gdisp.screen[disp].have_cfg_reg false; if (gdisp.init_para.disp_int_process) gdisp.init_para.disp_int_process(disp); } else { spin_unlock_irqrestore(gdisp.screen[disp].flag_lock, flags); disp_sync_all(disp, false); } if (gdisp.screen[disp].vsync_event_en gdisp.init_para.vsync_event) { ret gdisp.init_para.vsync_event(disp); if (ret 0) gdisp.screen[disp].health_info.vsync_cnt; else gdisp.screen[disp].health_info.vsync_skip_cnt; } tasklet_schedule(gdisp.screen[disp].tasklet); } else { struct disp_manager *mgr; mgr disp_get_layer_manager(disp); if (!timeout) disp_sync_checkin(disp); disp_sync_all(disp, false); if (gdisp.init_para.disp_int_process) gdisp.init_para.disp_int_process(disp); if (gdisp.screen[disp].vsync_event_en gdisp.init_para.vsync_event) { ret gdisp.init_para.vsync_event(disp); if (ret 0) gdisp.screen[disp].health_info.vsync_cnt; else gdisp.screen[disp].health_info.vsync_skip_cnt; } tasklet_schedule(gdisp.screen[disp].tasklet); } DISP_TRACE_INT2(vsync-cnt, disp, gdisp.screen[disp].health_info.vsync_cnt 0x01); DISP_TRACE_INT2(vsync-skip-cnt, disp, gdisp.screen[disp].health_info.vsync_skip_cnt 0x01); }查看上一级调用是#if defined(__LINUX_PLAT__) static irqreturn_t disp_lcd_event_proc(int irq, void *parg) #else static irqreturn_t disp_lcd_event_proc(void *parg) #endif { struct disp_device *lcd (struct disp_device *)parg; struct disp_lcd_private_data *lcdp NULL; struct disp_manager *mgr NULL; #if defined(SUPPORT_EINK) defined(CONFIG_EINK_PANEL_USED) struct disp_eink_manager *eink_manager NULL; #endif u32 hwdev_index; u32 irq_flag 0; unsigned int panel_extend_dirty; unsigned long flags; curr_irq_count; if (lcd NULL) return DISP_IRQ_RETURN; /* Init real fps data. */ DISP_DO_ONCE(disp_lcd_get_real_fps(lcd)); hwdev_index lcd-hwdev_index; lcdp disp_lcd_get_priv(lcd); if (lcdp NULL) return DISP_IRQ_RETURN; #if defined(SUPPORT_EINK) defined(CONFIG_EINK_PANEL_USED) eink_manager disp_get_eink_manager(0); if (eink_manager NULL) return DISP_IRQ_RETURN; #endif if (disp_al_lcd_query_irq (hwdev_index, LCD_IRQ_TCON0_VBLK, lcdp-panel_info)) { #if defined(SUPPORT_EINK) defined(CONFIG_EINK_PANEL_USED) eink_display_one_frame(eink_manager); #else int cur_line disp_al_lcd_get_cur_line(hwdev_index, lcdp-panel_info); int start_delay disp_al_lcd_get_start_delay(hwdev_index, lcdp-panel_info); #if IS_ENABLED(CONFIG_DISP2_LCD_ESD_DETECT) if (lcdp-lcd_panel_fun.esd_check lcdp-lcd_panel_fun.reset_panel) { lcdp-esd_inf.cnt; if (cur_line 2 !atomic_read(lcdp-lcd_resetting) lcdp-esd_inf.cnt lcdp-esd_inf.freq) { if (!lcdp-esd_inf.esd_check_func_pos || lcdp-lcd_panel_fun.esd_check(lcd-disp)) { /* request reset */ atomic_set(lcdp-lcd_resetting, 1); schedule_work(lcdp-reflush_work); } lcdp-esd_inf.cnt 0; } } #endif mgr lcd-manager; if (mgr NULL) return DISP_IRQ_RETURN; // if (cur_line (start_delay - lcdp-judge_line)) // sync_event_proc(mgr-disp, false); // else // sync_event_proc(mgr-disp, true); if (cur_line (start_delay - lcdp-judge_line)) { sync_event_proc(mgr-disp, false); } else { sync_event_proc(mgr-disp, true); // printk(lcd irq: vblk, cur_line %d, start_delay %d, judge_line %d\n, // cur_line, start_delay, lcdp-judge_line); } #endif } else { irq_flag disp_al_lcd_query_irq(hwdev_index, LCD_IRQ_TCON0_CNTR, lcdp-panel_info); irq_flag | disp_al_lcd_query_irq(hwdev_index, LCD_IRQ_TCON0_TRIF, lcdp-panel_info); if (irq_flag 0) goto exit; if (disp_al_lcd_tri_busy(hwdev_index, lcdp-panel_info)) { /* if lcd is still busy when tri/cnt irq coming, * take it as failture, record failture times, * when it reach 2 times, clear counter */ lcdp-tri_finish_fail; lcdp-tri_finish_fail (lcdp-tri_finish_fail 2) ? 0 : lcdp-tri_finish_fail; } else lcdp-tri_finish_fail 0; mgr lcd-manager; if (mgr NULL) return DISP_IRQ_RETURN; #if IS_ENABLED(CONFIG_DISP2_LCD_ESD_DETECT) if (lcdp-lcd_panel_fun.esd_check lcdp-lcd_panel_fun.reset_panel) { lcdp-esd_inf.cnt; if (!atomic_read(lcdp-lcd_resetting) lcdp-esd_inf.cnt lcdp-esd_inf.freq) { if (!lcdp-esd_inf.esd_check_func_pos || lcdp-lcd_panel_fun.esd_check(lcd-disp)) { /* request reset */ atomic_set(lcdp-lcd_resetting, 1); schedule_work(lcdp-reflush_work); } lcdp-esd_inf.cnt 0; } } #endif if (lcdp-tri_finish_fail 0) { sync_event_proc(mgr-disp, false); disp_al_lcd_tri_start(hwdev_index, lcdp-panel_info); } else { sync_event_proc(mgr-disp, true); printk(lcd irq: tri/cnt, tri_finish_fail %d\n, lcdp-tri_finish_fail); } } spin_lock_irqsave(lcd_data_lock, flags); panel_extend_dirty lcdp-panel_extend_dirty; lcdp-panel_extend_dirty 0; spin_unlock_irqrestore(lcd_data_lock, flags); if (panel_extend_dirty 1) disp_al_lcd_cfg_ext(lcd-disp, lcdp-panel_extend_info_set); exit: return DISP_IRQ_RETURN; }该函数是vbank中断函数是tcon没传输完成一帧就会产生vbank中断执行的函数主要是根据当前的系统时间点判断是否合适更新DE的寄存器执行流程如下图 图1 vbak中断执行流程可以看到如果中断执行时间不及时就可能导致超时。如图2如果cpu没有在vbank中断发生后在更新DE寄存器这个时间内完成图1的判断就会执行一次超时并且skip counter增加1。 图2 tcon时序2.3.可能原因存在两个可能一是时序不对tcon的消隐区间配置太小了cpu来不及更新二是cpu负载太高来不及执行vbank中断。1对于可能一的猜想 RGB屏幕增加vbp为2倍问题解决DSI屏幕增加vbp为2倍无法解决。 查代码发现RBG与DSI的cur_line计算方式不一致DSI的计算更加复杂一点int cur_line disp_al_lcd_get_cur_line(hwdev_index, lcdp-panel_info); - int disp_al_lcd_get_cur_line(u32 screen_id, struct disp_panel_para *panel) { #if defined(SUPPORT_DSI) defined(DSI_VERSION_40) if (panel-lcd_if LCD_IF_DSI) return dsi_get_cur_line(screen_id); #endif return tcon_get_cur_line(screen_id, al_priv.tcon_type[screen_id]); } RGB: u32 tcon_get_cur_line(u32 sel, u32 tcon_index) { if (tcon_index 0) return lcd_dev[sel]-tcon_debug.bits.tcon0_current_line; else if (tcon_index 1) return lcd_dev[sel]-tcon_debug.bits.tcon1_current_line; return 0; } DSI: u32 dsi_get_cur_line(u32 sel) { u32 curr_line dsi_dev[sel]-dsi_debug_video0.bits.video_curr_line; u32 vt dsi_dev[sel]-dsi_basic_size1.bits.vt; u32 vsa dsi_dev[sel]-dsi_basic_size0.bits.vsa; u32 vbp dsi_dev[sel]-dsi_basic_size0.bits.vbp; u32 y dsi_dev[sel]-dsi_basic_size1.bits.vact; u32 vfp vt - vsa - vbp - y; curr_line vfp; if (curr_line vt) curr_line - vt; return curr_line; } start_delay也不一样DSI的更加复杂。 int disp_al_lcd_get_start_delay(u32 screen_id, struct disp_panel_para *panel) { #if defined(SUPPORT_DSI) defined(DSI_VERSION_40) u32 lcd_start_delay 0; u32 de_clk_rate de_get_clk_rate() / 1000000; if (panel panel-lcd_if LCD_IF_DSI) { lcd_start_delay ((tcon0_get_cpu_tri2_start_delay(screen_id) 1) 3) * (panel-lcd_dclk_freq) / (panel-lcd_ht * de_clk_rate); return lcd_start_delay; } else #endif return tcon_get_start_delay(screen_id, al_priv.tcon_type[screen_id]); }对比两个版本内核的驱动发现上面的计算方式都是一样的。因此该猜想可能不对于是想其他方法。2对于可能二的猜想中断没有及时执行。为什么可能是因为两个内核对中断的优化不一样中断线程化都有可能。继续查查看系统的中断情况5.10内核如下# cat /proc/interrupts CPU0 CPU1 25: 0 0 GIC-0 29 Level arch_timer 26: 75462 403 GIC-0 30 Level arch_timer 29: 70401 0 GIC-0 91 Level timer2050000 30: 0 0 GIC-0 96 Level 2010000.iommu 31: 0 0 GIC-0 82 Level 3002000.dma-controller 32: 0 0 GIC-0 121 Level 5410000.g2d 40: 297 0 GIC-0 37 Level uart3 46: 0 0 GIC-0 176 Level 7090000.rtc 47: 74 0 GIC-0 43 Level 2502800.twi 48: 0 0 GIC-0 44 Level 2502c00.twi 49: 0 0 GIC-0 50 Level pwm 51: 0 0 GIC-0 47 Level 4025000.spi 53: 0 0 GIC-0 78 Level 4500000.gmac0 54: 291 0 GIC-0 72 Level mmc0 55: 27 0 GIC-0 73 Level mmc1 56: 1 0 GIC-0 61 Level sunxi_usb_udc 59: 0 0 GIC-0 65 Level ehci_hcd:usb1 60: 0 0 GIC-0 66 Level ohci_hcd:usb2 63: 39441 0 GIC-0 124 Level dispaly 64: 0 0 GIC-0 98 Level cedar_dev 239: 0 0 sunxi_pio_edge 174 Edge ilitek_touch_irq IPI0: 0 0 CPU wakeup interrupts IPI1: 0 330 Timer broadcast interrupts IPI2: 254 242 Rescheduling interrupts IPI3: 314 476 Function call interrupts IPI4: 0 0 CPU stop interrupts IPI5: 0 0 IRQ work interrupts IPI6: 0 0 completion interrupts Err: 05.4内核如下rootTinaLinux:/# cat /proc/interrupts CPU0 CPU1 17: 0 0 GIC-0 29 Level arch_timer 18: 1555 1786 GIC-0 30 Level arch_timer 21: 0 0 GIC-0 91 Level timer2050000 24: 0 0 GIC-0 96 Level 2010000.iommu 25: 0 0 GIC-0 176 Level 7090000.rtc 26: 0 0 GIC-0 82 Level 3002000.dma-controller 35: 218 0 GIC-0 37 Level uart3 39: 0 0 GIC-0 78 Level 4500000.gmac0 40: 278 0 GIC-0 72 Level mmc0 41: 0 0 GIC-0 47 Level spi0 42: 0 0 GIC-0 48 Level spi1 43: 0 0 GIC-0 98 Level cedar_dev 44: 135 0 GIC-0 43 Level 2502800.twi 45: 17 0 GIC-0 44 Level 2502c00.twi 51: 0 0 GIC-0 121 Level 5410000.g2d 54: 753 0 GIC-0 124 Level dispaly 55: 0 0 GIC-0 32 Level 3003000.msgbox 56: 0 0 GIC-0 155 Level 3003000.msgbox 57: 0 0 GIC-0 162 Level 3003000.msgbox 58: 1 0 GIC-0 61 Level sunxi_usb_udc 61: 0 0 GIC-0 65 Level ehci_hcd:usb1 62: 0 0 GIC-0 66 Level ohci_hcd:usb2 239: 0 0 sunxi_pio_edge 174 Edge ilitek_touch_irq IPI0: 0 0 CPU wakeup interrupts IPI1: 0 0 Timer broadcast interrupts IPI2: 1029 1491 Rescheduling interrupts IPI3: 6 2 Function call interrupts IPI4: 0 0 CPU stop interrupts IPI5: 0 0 IRQ work interrupts IPI6: 0 0 completion interrupts Err: 0通过对比发现两个系统的中断差异集中在两个地方1.timer2050000不一致 2.IPI3不一致在5.10内核中timer2050000产生了7万次的中断。所以这个timer的频繁中断可能会导致系统其他中断的响应。3.解决方法对比两个内核地设备树终于发现了原因5.4内核 soc_timer0: timer2050000 { compatible allwinner,sun4i-a10-timer; device_type soc_timer; reg 0x0 0x02050000 0x0 0xA0; interrupt-parent gic; interrupts GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH; clocks dcxo24M; }; 5.10内核 soc_timer0: timer2050000 { compatible allwinner,sunxi-timer-v101; device_type soc_timer; reg 0x0 0x02050000 0x0 0xA0; interrupt-parent gic; interrupts GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH; clocks dcxo24M; };两个内核的timer驱动发生了改变对于5.4内核由于t113并发sun4i架构因此猜测该驱动不会加载最后发现内核配置是没有开启该驱动。对于5.10内核timer的配置是AW_TIMER系统开启了timer并且加载了驱动导致timer频繁中断占用系统资源。4.疑问5.10内核为什么开启该timer是系统有上面功能需要还是关闭也可以原因是启用了CPUIdle功能。原厂回应启用该功能会导致显示中断延迟加大丢帧明显。