日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

AMD GPU任务调度(3) —— fence机制

發布時間:2023/12/29 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 AMD GPU任务调度(3) —— fence机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • GPU fence
    • command format
    • EOP event
  • DMA fence
    • 數據結構
      • dma-fence
      • amdgpu-fence
      • dma_fence_ops
      • amdgpu_fence_driver
    • API
      • dma_fence_add_callback
      • dma_fence_signal
  • API
    • amdgpu_fence_driver_init_ring
    • amdgpu_fence_driver_start_ring
    • amdgpu_fence_emit
    • amdgpu_fence_process
  • 應用舉例
    • 調度任務
    • 中斷注冊
    • 中斷處理
    • 觸發回調

GPU fence

  • AMD GPU的fence機制用來產生一個GPU的事件,用于CPU與GPU之間數據同步。當CPU發送一個fence到GPU某個IP的Ring Buffer后,GPU IP硬件會刷新該Ring上的cache。fence機制可以用來保證用戶態程序下發的渲染命令被順序執行,從而保證上層應用程序渲染相關數據的一致性。實現fence機制的硬件基礎是AMD GPU提供的EOP(end-of-pipe event)渲染命令,它和普通的渲染命令使用方法相同,作為渲染命令提交到Ring Buffer,然后被GPU執行。

command format

  • CPU與GPU通過Ring Buffer實現渲染命令的提交,渲染命令作為一個packet被提交到Ring Buffer上,AMD規定packet的格式,它被分為兩部分,Header和IT_BODY(information body),EOP渲染命令也有如下通用格式,如下圖所示:
  • packet的HEADER最高2bit代表packet的類型,AMD將packet分為了3類,分別是0,2,3。每類packet的頭部除了高2bit代表類型以外其它字段與packet類型相關,其中type-0和type-2是用作寫寄存器的packet,type-3用做發送渲染命令以及一些特殊操作的命令,比如IB和Fence操作。type-3 packet的格式如下:
  • HEADER由4個字段組成,如下:
  • PREDICATE:待分析
  • IT_OPCODE:操作碼,用于描述具體的渲染命令類型或者特殊操作,比如Indirect Buffer和事件觸發操作
  • COUNT:描述IT_BODY的大小的字段,4字節為單位。它的值為IT_BODY長度減1。
  • TYPE:packet類型,這里是3
    • AMD驅動中有如下宏定義type-3類型的packet:
    #define PACKET_TYPE3 3 #define PACKET3(op, n) ((PACKET_TYPE3 << 30) | (((op) & 0xFF) << 8) | ((n) & 0x3FFF) << 16) #define PACKET3_EVENT_WRITE_EOP 0x47/* 組裝一個操作碼為PACKET3_EVENT_WRITE_EOP的type-3類型的HEADER,并設置其IT_BODY長度為5 DWORD */ PACKET3(PACKET3_EVENT_WRITE_EOP, 4)

    EOP event

    • EOP(end-of-pipe)作為一個特殊命令,它的主要功能是設置GPU,在flush cache之后,往CPU可以訪問的一個內存地址處寫入時間戳或者EOP packet中定義的序列號,然后發送中斷通知CPU flush動作完成,CPU在中斷處理例程中實現fence完成后的相關軟件處理,通知所有感興趣的上層應用。通過這樣的方式,CPU可以確認渲染命令是否已經完成,從而實現同步。AMD GPU定義了一個type-3 packet專門用于實現fence機制,packet格式如下:
    • fence的packet包主要用來指示GPU在end-of-pipe之后應該在哪個內存空間,寫入什么值,因此packet中有兩個最主要的信息是序列號和地址,GPU在end-of-pipe之后會根據這兩個信息產生寫內存事件或者中斷。下面依次介紹每個字段含義:
  • HEADER:packet包頭部,IT_OPCODE為event_write_eop(0x47),COUNT為4。
  • event initiator:GPU產生事件是寫入VGT_EVENT_INITIATOR寄存器的值
  • event type:事件類型,GPU提供了幾種事件類型,包括Cache Flush,Cache Flush And Tnval,Bottom Of Pipe等,通常情況下選擇第二種,在cach flush并且使無效之后產生事件
  • address_lo/address_hi:事件產生時寫入的內存地址
  • interrupt select:選擇以何種方式產生事件,包括Send Interrupt Only,Send Interrupt when Write Confirm is received from the MC
  • data select:產生事件時發送的數據長度
  • data_lo/data_hi:事件產生時要寫的數據
    • AMD fence機制的常用場景是每次CPU提交渲染命令之后,發送一個fence到GPU,同時啟動一個定時器每隔一段事件查詢一次EOP中給出的內存地址是否被填入了指定的數據。之后,就可以處理其它事情了。在定時器處理例程中如果查詢到內存地址被填入指定數據,說明渲染命令已經處理完成。CPU根據這個信息,就可以繼續提交新的渲染命令或者處理一些同步相關事情了。下面是amdgpu驅動中一段填充fence信息到Ring Buffer的操作:
    amdgpu_ring_write(ring, PACKET3(PACKET3_EVENT_WRITE_EOP, 4)); /* 1 */ amdgpu_ring_write(ring, (EOP_TCL1_ACTION_EN | /* 2 */EOP_TC_ACTION_EN |EOP_TC_WB_ACTION_EN |EVENT_TYPE(CACHE_FLUSH_AND_INV_TS_EVENT) |EVENT_INDEX(5))); amdgpu_ring_write(ring, addr & 0xfffffffc); /* 3 */ amdgpu_ring_write(ring, (upper_32_bits(addr) & 0xffff) | /* 4 */DATA_SEL(write64bit ? 2 : 1) | INT_SEL(int_sel ? 2 : 0)); amdgpu_ring_write(ring, lower_32_bits(seq)); /* 5 */ amdgpu_ring_write(ring, upper_32_bits(seq)); 1. 組裝HEADER,將IT_OPCODE和COUNT填入HEADER中 2. 設置event的類型 3. 寫入內存地址,因為內存地址是雙字為單位,4字節對齊的,因此低2位會設置為0 4. 寫入余下的內存地址,同時設置數據發送的位數已經中斷產生方式 5. 填入要往內存地址寫入的數據

    DMA fence

    • 設想這樣一個場景,用戶態應用往Ring Buffer提交渲染命令,GPU設備著手處理,渲染命令提交完成后返回到用戶態空間,GPU異步執行渲染操作。此時用戶態應用繼續處理其它事務,它下發ioctl命令讓顯示設備輸出剛剛發給GPU渲染的那一幀畫面,通常的做法是,這個ioctl命令陷入內核態等待GPU渲染完成,獲取渲染buffer的地址,然后將其設置為顯示設備的掃描地址,最后返回用戶態空間。整個過程可以歸納為向GPU發送渲染數據,然后讓顯示設備輸出,更高效的做法是在內核空間一次性將這兩個事情做完,這樣就可以節省一次內核態與用戶態上下文切換的開銷。出現這種問題的本質是內核態缺乏一種機制,沒法讓兩個硬件單元(GPU和display)同步數據,因此只能返回用戶態,讓用戶態程序來負責這兩個事情,間接完成數據同步。
    • dma-fence的設計就是用于解決這類問題,它用來實現多個硬件之間共享buffer的同步,同樣是上面的場景,當用戶態應用往Ring Buffer提交渲染命令后,將GPU處理的渲染buffer共享給顯示設備,同時將一個dma-fence關聯到這個buffer上并往dma-fence添加一個回調函數,當GPU處理完buffer之后,觸發buffer關聯的dma-fence上的回調,回調函數中,就可以將渲染buffer的地址設置為顯示設備的掃描地址,完成原本用戶態應用負責的事情。
    • dma fence有三個狀態,一是初始化后的默認狀態;二是使能狀態(in-fence),通常在注冊fence回調函數的時候被設置成這個狀態;三是完成狀態(out-fence),通常硬件的fence完成后在中斷處理中設置。如下:
    enum dma_fence_flag_bits {DMA_FENCE_FLAG_SIGNALED_BIT, /* 1 */DMA_FENCE_FLAG_TIMESTAMP_BIT,DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, /* 2 */DMA_FENCE_FLAG_USER_BITS, /* must always be last member */ }; 1. out-fence狀態,標記著fence完成。GPU發送EOP中斷后,CPU響應該中斷,并將fence的flag設置為這個狀態。 2. in-fence狀態,標記渲染命令處于fence狀態。CPU發送渲染命令之后,使能fence時將flag標記為此狀態。

    數據結構

    dma-fence

    struct dma_fence {const struct dma_fence_ops *ops; /* 1 */union {struct list_head cb_list; /* 2 *//* @cb_list replaced by @timestamp on dma_fence_signal() */ktime_t timestamp;/* @timestamp replaced by @rcu on dma_fence_release() */struct rcu_head rcu;};u64 context; /* 3 */u64 seqno; /* 4 */unsigned long flags; /* 5 */struct kref refcount; /* 6 */ } 1. 定義dma-fence等待,釋放,完成等操作 2. 維護該dma-fence完成后需要觸發的回調函數 3. fence所在上下文,同一個上下文可以有多個fence,通過序列號seqno區分,每個fence有唯一一個序列號不同。在不同上下文中fence序列號可以相 同,fence序列號只在同一個上下文起到區分不同fence的作用。對于amdgpu這種應用場景,一個fence上下文就是一個Ring Buffer,一個fence通常關聯 到這個Ring Buffer上的一個任務 4. fence關聯的序號,當amdgpu驅動提交一個任務到Ring Buffer上時,就增加這個序列號并分配一個dma-fence關聯到該任務,用于跟蹤任務是否完成 5. 用于標記該fence的狀態,當fence完成時會被標記為DMA_FENCE_FLAG_SIGNALED_BIT,表示fence已經完成,其它對該fence感興趣的模塊會據此執行相應的事務 6. 該fence的引用計數,可以有多個硬件模塊或者軟件驅動引用同一個fence

    amdgpu-fence

    • amdgpu驅動使用內核提供的dma-fence機制實現Host驅動和GPU的同步,amdgpu驅動將dma-fence附加到一個GPU IP的Ring Buffer上,因此封裝了一個amdgpu_fence結構體用于將dma-fence與Ring Buffer關聯起來。注意,一個Ring Buffer上可以包含若干個fence,因此多個amdgpu_fence的ring成員可能指向同一個環。
    struct amdgpu_fence {struct dma_fence base; /* Ring Buffer上的dma-fence *//* RB, DMA, etc. */struct amdgpu_ring *ring; /* amdgpu fence所在的Ring Buffer,amdgpu fence與amdgpu ring是多對一的關系 */ };

    dma_fence_ops

    • 內核的dma-fence只提供框架,可以讓內核其它模塊定制自己的實現,包括如何等待fence完成,fence完成時如何觸發相應的回調,fence如何被釋放等等,這個定制接口通過定義如下結構體dma_fence_ops來實現
    struct dma_fence_ops {bool (*enable_signaling)(struct dma_fence *fence); /* 使能fence,fence初始化后到完成之前,都處于使能狀態 */bool (*signaled)(struct dma_fence *fence); /* 向fence發信號,標記fence完成,通常這是在硬件fence完成時中斷處理中實現 */signed long (*wait)(struct dma_fence *fence, /* 等待fence完成 */bool intr, signed long timeout);void (*release)(struct dma_fence *fence); /* 釋放fence */...... } /* amdgpu 驅動定制的fence接口 */ static const struct dma_fence_ops amdgpu_fence_ops = {.get_driver_name = amdgpu_fence_get_driver_name,.get_timeline_name = amdgpu_fence_get_timeline_name,.enable_signaling = amdgpu_fence_enable_signaling,.release = amdgpu_fence_release, };

    amdgpu_fence_driver

    struct amdgpu_fence_driver {uint64_t gpu_addr; /* 1 */volatile uint32_t *cpu_addr; /* 2 *//* sync_seq is protected by ring emission lock */uint32_t sync_seq; /* 3 */atomic_t last_seq; /* 4 */struct amdgpu_irq_src *irq_src; /* 5 */unsigned irq_type;struct timer_list fallback_timer; /* 6 */unsigned num_fences_mask; /* 7 */struct dma_fence **fences; /* 8 */ }; 1. GPU可訪問的內存地址,該地址是GPU產生fence事件時將序列號寫入的地址,GPU寫gpu_addr地址,CPU從cpu_addr地址讀出值 2. fence機制中gpu地址對應的cpu地址,驅動側通過讀取這個地址的內容,與序列號比較,如果相同,說明GPU fence事件產生 3. GPU往gpu_addr寫入的序列號,單調遞增。初始值為0,CPU每執行一個job,就會往GPU ring上發送一個fence,序列號就加1。fence的序列號用來 區分不同job。假設CPU往ring buffer上先后放了兩個job,此時產生了GPU產生了end-of-pipe中斷,CPU的中斷處理函數例程中可以通過序列號區分是哪 個job對應的渲染命令被GPU處理完成。 4. 從cpu_addr中取出的GPU寫入的序列號 5. 待分析 6. 定時器,用于每隔一段事件查詢是否有fence被通知(signaled) 7. fence個數掩碼,用于查詢fences數組中的dma_fence,使其不越界 8. fences數組,調度器每提交一個job,相應地會增加fence的序列號,同時會生成一個dma_fence對象放到fences數組中。

    API

    • 對于amdgpu來說,一個Ring Buffer就是一個共享buffer,它上面可以關聯dma-fence,當GPU執行完Ring Buffer上的渲染命令時,通過附加在buffer上面的dma-fence,就可以通知其它硬件模塊并觸發相應回調函數

    dma_fence_add_callback

    • fence被發送后,發送fence的線程就可以轉而去處理其它事務,當fence完成時會觸發相應的回調函數,一個dmp-fence可以注冊多個回調函數,但一個回調函數只能注冊到一個dmap-fence上,即dma-fence與callback時一對多的關系。amdgpu驅動中fence的回調函數注冊在dma_fence_add_callback中實現,如下:
    int dma_fence_add_callback(struct dma_fence *fence, struct dma_fence_cb *cb,dma_fence_func_t func) {if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) { /* 1 */INIT_LIST_HEAD(&cb->node);return -ENOENT;}if (__dma_fence_enable_signaling(fence)) { /* 2 */cb->func = func;list_add_tail(&cb->node, &fence->cb_list);}...... } 1. 檢測fence是否已經完成,如果已經完成,直接返回,調用者跳過回調函數的注冊,直接執行就可以了 2. 判斷fence是否注冊了觸發fence完成的函數,如果有,滿足條件,將回調函數添加到dma-fence的回調函數鏈表中。

    dma_fence_signal

    dma_fence_signaldma_fence_signal_locked int dma_fence_signal_locked(struct dma_fence *fence) {if (unlikely(test_and_set_bit(DMA_FENCE_FLAG_SIGNALED_BIT, /* 1 */&fence->flags)))return -EINVAL;list_for_each_entry_safe(cur, tmp, &cb_list, node) { /* 2 */INIT_LIST_HEAD(&cur->node);cur->func(fence, cur);}...... } 1. 設置fence狀態標記為SIGNEALED,其余接口通過查詢此狀態標記檢查fence是否處于out-fence狀態 2. 對fence感興趣的其它模塊通過向cb_list添加回調函數,可以讓fence在完成后執行該回調。這里就是fence完成后的回調

    API

    amdgpu_fence_driver_init_ring

    • 初始化一個GPU IP上Buffer Ring的fence_driver,每個Buffer Ring上維護這一個fence driver,它用來記錄提交到Ring Buffer上的job的完成情況。為什么要用這個才能記錄?因為CPU提交渲染到Ring Buffer讓GPU執行是異步,只能通過fence機制來確認job的完成情況。fence driver為上層渲染命令的同步提供底層基礎,否則上層是無法知道渲染命令是否完成,當上層應用下發的渲染命令前后有依賴時,無法保證順序
    int amdgpu_fence_driver_init_ring(struct amdgpu_ring *ring,unsigned num_hw_submission) { ring->fence_drv.sync_seq = 0; /* 1 */atomic_set(&ring->fence_drv.last_seq, 0);ring->fence_drv.initialized = false; /* 2 */timer_setup(&ring->fence_drv.fallback_timer, amdgpu_fence_fallback, 0); /* 3 */ring->fence_drv.num_fences_mask = num_hw_submission * 2 - 1; /* 4 */ring->fence_drv.fences = kcalloc(num_hw_submission * 2, sizeof(void *), /* 5 */GFP_KERNEL);...... } 1. 將當前序列號和上一次的序列號初始化為0,sync_seq在真正發送fence的時候會加1,因此sync_seq從1開始,last_seq從0開始。 2. fence driver是否啟動,這里設置為false,fence driver在啟動start ring接口中設置為true 3. 設置定時器,該定時器用于查詢是否有完成的fence。除了GPU IP核中斷例程中會處理完成的fence外,這里注冊的定時器回調函數也會周期性查詢fence是否完成。 4. 設置fence個數的掩碼,amd gpu調度器中允許同時有num_hw_submission個任務提交到Ring Buffer。調度器每提交一個任務,至少要發送一個fence用于跟蹤當前任務是否完成。同時在提交任務之前,可能刷GPU的緩存,這時也會發送一個fence用于跟蹤當前緩存是否刷新完成,因此一個任務最多可能會有2個fence發送。所以設置的fence個數掩碼為num_hw_submission * 2 - 1。注意,num_hw_submission是2的冪級數。 5. 為fence driver的fences結構體分配空間,個數為num_hw_submission的2倍

    amdgpu_fence_driver_start_ring

    • fence driver的初始化主要完成基本成員的賦初值,但關鍵的gpu和cpu地址沒有設置,這個設置在start ring中完成。
    int amdgpu_fence_driver_start_ring(struct amdgpu_ring *ring,struct amdgpu_irq_src *irq_src,unsigned irq_type) { ring->fence_drv.cpu_addr = &adev->wb.wb[ring->fence_offs]; /* 1 */ring->fence_drv.gpu_addr = adev->wb.gpu_addr + (ring->fence_offs * 4);amdgpu_fence_write(ring, atomic_read(&ring->fence_drv.last_seq)); /* 2 */amdgpu_irq_get(adev, irq_src, irq_type);ring->fence_drv.irq_src = irq_src;ring->fence_drv.irq_type = irq_type;ring->fence_drv.initialized = true; /* 3 */...... } 1. 設置fence driver的gpu和cpu地址 2. 將last_seq序列號寫入fence driver的cpu地址中,由于last_seq初始值為0,因此cpu地址中讀出的序列號為0,在提交任務時,當前序列號sync_seq會 先加1再發送fence,中斷處理函數和定時器函數中通過判斷sync_seq與last_seq是否相等,來判斷是否有任務在執行或者有flush正在刷新 3. 將fence driver的初始化設置為true

    amdgpu_fence_emit

    • 向GPU發送一個fence,具體實現是往Ring Buffer上填入fence內容。真正地發送在設置Ring Buffer寫偏移時觸發。
    int amdgpu_fence_emit(struct amdgpu_ring *ring, struct dma_fence **f,unsigned flags) {struct amdgpu_fence *fence;fence = kmem_cache_alloc(amdgpu_fence_slab, GFP_KERNEL); /* 1 */seq = ++ring->fence_drv.sync_seq; /* 2 */fence->ring = ring; /* 3 */dma_fence_init(&fence->base, &amdgpu_fence_ops,&ring->fence_drv.lock,adev->fence_context + ring->idx,seq);amdgpu_ring_emit_fence(ring, ring->fence_drv.gpu_addr, /* 4 */seq, flags | AMDGPU_FENCE_FLAG_INT);ptr = &ring->fence_drv.fences[seq & ring->fence_drv.num_fences_mask]; /* 5 */if (unlikely(rcu_dereference_protected(*ptr, 1))) { struct dma_fence *old;old = dma_fence_get_rcu_safe(ptr);dma_fence_wait(old, false); /* 6 */dma_fence_put(old); /* 7 */}rcu_assign_pointer(*ptr, dma_fence_get(&fence->base)); /* 8 */...... } 1. 為amdgpu-fence結構體分配內存 2. 增加序列號,每發送一次fence,序列號自增一次 3. 設置fence所在的環 4. 往Ring Buffer中填入fence信息,即之前介紹的EOP packet 5. 取出序列號在fence數組中對應元素,判斷是否為空,如果為空,將該元素指向新分配的fence。通常情況下,fence數組對應位置不會存在fence,如果存在,我們認為它是之前未完成的fence,我們等待fence完成,然后再釋放老的fence。 6. 等到老的fence完成 7. 完成后釋放老的fence,如果是最后一個fence的引用被釋放,則刪除老的fence 8. 設置fence數組對應元素指向即將發送的新fence。

    amdgpu_fence_process

    • 當GPU完成渲染任務后,會觸發EOP中斷,amdgpu在中斷處理函數中會比較EOP packet中填入的序列號和GPU往GPU地址空間寫入的值是否相同,如果相同,說明GPU完成了渲染操作或者完成的flush cache的操作,過程如下:
    bool amdgpu_fence_process(struct amdgpu_ring *ring) {struct amdgpu_fence_driver *drv = &ring->fence_drv;uint32_t seq, last_seq;do {last_seq = atomic_read(&ring->fence_drv.last_seq); /* 1 */seq = amdgpu_fence_read(ring); } while (atomic_cmpxchg(&drv->last_seq, last_seq, seq) != last_seq); /* 2 */if (unlikely(seq == last_seq)) /* 3 */return false;last_seq &= drv->num_fences_mask; /* 4 */seq &= drv->num_fences_mask;do {struct dma_fence *fence, **ptr;++last_seq; /* 5 */last_seq &= drv->num_fences_mask;ptr = &drv->fences[last_seq];/* There is always exactly one thread signaling this fence slot */fence = rcu_dereference_protected(*ptr, 1);RCU_INIT_POINTER(*ptr, NULL);dma_fence_signal(fence); /* 6 */dma_fence_put(fence);} while (last_seq != seq);...... } 1. 取出上一次fence的序列號last_seq和這一次fence的序列號seq 2. 用這一次fence的序列號更新上次任務的序列號,原子操作 3. 如果兩次fence的序列號相等,說明當前沒有fence完成,直接返回false,表示fence還處在未完成階段 4. 計算序號在fences數組中的索引 5. 逐次增加last_seq,從fences數組中取出對應的dma-fence,更新fence的狀態,最終觸發fence上的回調,直到last_seq等于seq。在fence完成的回調 處理中,多個fence可能合并一次性處理。 6. 更新fence的狀態為完成(signaled) 7. 釋放fence

    應用舉例

    • GPU調度器通過fence機制為上層應用提供了不同硬件之間數據的同步方法,GPU調度器本身也通過fence機制跟蹤任務的完成情況。下面通過分析GPU調度器執行任務的過程,進一步理解fence機制

    調度任務

    • 調度器按從高到低的順序,依次從對應的運行隊列中選擇合適的調度實體,取出調度實體的任務,將任務關聯IB包含的渲染命令提交到Ring Buffer上。調度任務的執行在drm_sched_main函數中實現,GPU調度器利用fence跟蹤渲染任務是否執行完,還可以利用fence機制注冊回調函數,讓GPU渲染任務執行完成后觸發任務相關的收尾工作。處理其中fence相關的操作如下:
    static int drm_sched_main(void *param) {while (!kthread_should_stop()) {......fence = sched->ops->run_job(sched_job); /* 1 */ if (!IS_ERR_OR_NULL(fence)) {r = dma_fence_add_callback(fence, &sched_job->cb, /* 2 */drm_sched_process_job);if (r == -ENOENT)drm_sched_process_job(fence, &sched_job->cb); /* 3 */dma_fence_put(fence); /* 4 */}......} } 1. 執行渲染任務,該動作的實質是將渲染命令放到Ring Buffer上,然后讓GPU異步處理,run_job返回一個fence,上層通過此fence注冊回調函數 2. 往fence上注冊回調函數,當該fence完成時,執行drm_sched_process_job函數 3. 如果此時fence已經完成,說明GPU已經將渲染任務完成,不再需要注冊回調,直接運行回調函數即可 4. 釋放fence

    中斷注冊

    • 渲染任務完成后,GPU硬件模塊產生中斷,CPU響應中斷后觸發中斷服務例程,理論上上面的回調函數源頭是中斷服務例程觸發的。我們跟蹤一下GPU中斷服務例程的注冊,以GFX模塊為例。中斷注冊在amdgpu驅動中完成,大致流程如下:
    amdgpu_initpci_register_driver(&amdgpu_kms_pci_driver)amdgpu_kms_pci_driver.probe <=> amdgpu_pci_probeamdgpu_driver_load_kmsamdgpu_device_initamdgpu_device_ip_early_initvi_set_ip_blockscase CHIP_TONGA:amdgpu_device_ip_block_add(adev, &gfx_v8_0_ip_block) const struct amdgpu_ip_block_version gfx_v8_0_ip_block = {.......funcs = &gfx_v8_0_ip_funcs, }; static const struct amd_ip_funcs gfx_v8_0_ip_funcs = {.name = "gfx_v8_0",.early_init = gfx_v8_0_early_init,...... } gfx_v8_0_early_initgfx_v8_0_set_irq_funcs(adev);adev->gfx.eop_irq.funcs = &gfx_v8_0_eop_irq_funcs;static const struct amdgpu_irq_src_funcs gfx_v8_0_eop_irq_funcs = {.set = gfx_v8_0_set_eop_interrupt_state, /* 1 */.process = gfx_v8_0_eop_irq, /* 2 */ }; 1. 設置中斷狀態的操作,和具體的硬件編程接口有關對于amdgpu來說,主要時使能中斷和禁止中斷這兩個功能 2. EOP中斷例程處理函數

    中斷處理

    • GPU的中斷處理通過drm_driver注冊,drm_driver提供統一的中斷框架用于實現GPU中斷處理例程的注冊,內核公共的中斷處理函數最終會調用到drm_driver的中斷處理函數irq_handler,如下:
    static struct drm_driver kms_driver = {.irq_handler = amdgpu_irq_handler,...... }
    • 當GPU硬件產生中斷并提交到CPU后,CPU根據中斷向量表跳轉到對應的服務例程,最終調用amdgpu_irq_handler,看下其具體流程:
    amdgpu_irq_handleramdgpu_ih_processamdgpu_irq_dispatchsrc->funcs->process(adev, src, &entry) <=> gfx_v8_0_eop_irqamdgpu_fence_processdma_fence_signaldma_fence_signal_locked

    觸發回調

    • 從上面的流程中可以看到,渲染任務執行完了會觸發drm_sched_process_job回調,該函數會將調度相關的fence設置為完成,從而鏈式地觸發其它監聽模塊的回調,具體如下:
    static void drm_sched_process_job(struct dma_fence *f, struct dma_fence_cb *cb) {struct drm_sched_job *s_job = container_of(cb, struct drm_sched_job, cb);struct drm_sched_fence *s_fence = s_job->s_fence; /* 獲取job關聯的dma-fence */dma_fence_get(&s_fence->finished); /* 引用finished dma-fence */drm_sched_fence_finished(s_fence); /* 通知監聽finished dma-fence的其它模塊,job已經處理完成,可以觸發對應回調了 */dma_fence_put(&s_fence->finished); /* 釋放finished dma-fence */ }

    總結

    以上是生活随笔為你收集整理的AMD GPU任务调度(3) —— fence机制的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。