解決qemu虛擬機圖形界面卡死問題

1)基礎環境

Virtio-gpu雙heads+4.9.0內核+xserver1.9.3+modesettings0.5.0驅動

2)問題描述

終端中打開大量文字內容,不停上下滑動,或cat大量內容的文件,操作過程中用戶界面卡死,如下:
在這裏插入圖片描述

3)基礎排查

穩定復現方法:
$ cat /var/log/syslog
$ 上下滑動滾輪
卡死後:
kill pid of X能解決這個問題

kill掉kde程序仍然死機,只有kill掉xserver程序才能彈回登錄界面恢復響應,並且屏幕響應鼠標移動事件,不響應點擊事件和鍵盤輸入事件。
只在日誌中發現kdm的報錯:
localhost kdm_greet[2336]: Can’t open default user face

4)排查分析

重新執行後發現內核中出現drm ioctl報錯,並且重複測試後每次均會出現該問題:
[drm:drm_ioctl [drm]] pid=2285, dev=0xe200, auth=1, DRM_IOCTL_MODE_DIRTYFB
[drm:drm_mode_object_unreference [drm]] OBJ ID: 105 (6)
[drm:drm_ioctl [drm]] pid=2285, dev=0xe200, auth=1, DRM_IOCTL_MODE_DIRTYFB
[drm:drm_mode_object_unreference [drm]] OBJ ID: 105 (6)
[drm:drm_ioctl [drm]] pid=2285, dev=0xe200, auth=1, DRM_IOCTL_MODE_DIRTYFB
[drm:drm_mode_object_unreference [drm]] OBJ ID: 105 (6)
2019-10-16T01:34:21.108908+08:00 localhost kernel: [ 90.915464] [drm:drm_ioctl [drm]] ret = -22

-22出錯代表EINVAL,通過strace跟蹤xorg也發現了這個問題:
ioctl(10, 0xc01864b1, 0x7ffeaaed3e80) = -1 EINVAL (Invalid argument)
其中:
0xc01864b1代表的DRM_IOCTL_MODE_DIRTYFB
0x7ffeaaed3e80是參數地址

DRM_IOCTL_MODE_DIRTYFB調用執行如下:

int drm_mode_dirtyfb_ioctl(struct drm_device *dev, 
             void *data, struct drm_file *file_priv) 
{ 
    struct drm_clip_rect __user *clips_ptr; 
    struct drm_clip_rect *clips = NULL; 
    struct drm_mode_fb_dirty_cmd *r = data; 
    struct drm_framebuffer *fb; 
    unsigned flags; 
    int num_clips; 
    int ret; 
    
    if (!drm_core_check_feature(dev, DRIVER_MODESET)) 
        return -EINVAL; 

    fb = drm_framebuffer_lookup(dev, r->fb_id); 
    if (!fb) 
        return -ENOENT; 
        
    num_clips = r->num_clips; 
    clips_ptr = (struct drm_clip_rect __user *)(unsigned long)r->clips_ptr; 
    if (!num_clips != !clips_ptr) { 
        ret = -EINVAL; 
        goto out_err1; 
    } 
    
    flags = DRM_MODE_FB_DIRTY_FLAGS & r->flags; 
   
    /* If userspace annotates copy, clips must come in pairs */ 
    if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY && (num_clips % 2)) { 
        ret = -EINVAL; 
        goto out_err1; 
    } 
    
    if (num_clips && clips_ptr) { 
        if (num_clips < 0 || num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS) { 
            ret = -EINVAL; 
            goto out_err1; 
        } 
        
        clips = kcalloc(num_clips, sizeof(*clips), GFP_KERNEL); 
        if (!clips) { 
            ret = -ENOMEM; 
            goto out_err1; 
        } 

        ret = copy_from_user(clips, clips_ptr, 
                 num_clips * sizeof(*clips)); 
        if (ret) { 
            ret = -EFAULT; 
            goto out_err2; 
        } 
    } 

    if (fb->funcs->dirty) { 
        ret = fb->funcs->dirty(fb, file_priv, flags, r->color, 
                 clips, num_clips); 
    } else { 
        ret = -ENOSYS; 
    } 
    
out_err2: 
    kfree(clips); 
out_err1:
    drm_framebuffer_unreference(fb); 
    
    return ret; 
} 

這裏偏懷疑是num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS導致的,在xorg driver中添加打印,卡死時打印出:
[ 141.634] (II) modeset(0): fd_id:105 clip num:554

而驅動中的定義如下:
#define DRM_MODE_FB_DIRTY_MAX_CLIPS 256

這也就導致瞭如下的執行:

if (num_clips < 0 || num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS) { 
        ret = -EINVAL; 
        goto out_err1; 
} 

在xorg驅動中對ioctl傳入的clips num進行分片執行解決了這個問題,改動如下:

-       ret = drmModeDirtyFB(ms->fd, fb_id, clip, num_cliprects); 
-       DamageEmpty(damage); 
-       if (ret) { 
-           if (ret == -EINVAL) 
-               return ret; 
-       } 
+    drmModeClip *sig_clip = clip; 
+    int sig_cliprects; 
+    while(num_cliprects > 0) { 
+        if(num_cliprects > DRM_MODE_FB_DIRTY_MAX_CLIPS) { 
+            sig_cliprects = DRM_MODE_FB_DIRTY_MAX_CLIPS; 
+        } else { 
+            sig_cliprects = num_cliprects; 
+        } 
+           ret = drmModeDirtyFB(ms->fd, fb_id, sig_clip, sig_cliprects); 
+        sig_clip += sig_cliprects; 
+        num_cliprects -= sig_cliprects; 
+           DamageEmpty(damage); 
+           if (ret) { 
+               if (ret == -EINVAL) 
+                   return ret; 
+           } 
+    } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章