מעבר מערימות של ION ל-DMA-BUF

ב-Android 12, GKI 2.0 מחליף את מנהל הקצאת ה-ION ב-DMA-BUF heaps מהסיבות הבאות:

  • אבטחה: מאחר שכל אשכול DMA-BUF הוא מכשיר תווים נפרד, אפשר לשלוט בגישה לכל אשכול בנפרד באמצעות מדיניות האבטחה. לא ניתן היה לעשות זאת באמצעות ION כי הקצאה מכל אשכול דרשה רק גישה למכשיר /dev/ion.
  • יציבות ABI: בניגוד ל-ION, ממשק ה-IOCTL של מסגרת ה-DMA-BUF heaps יציב מבחינת ABI כי הוא מתוחזק בליבה של Linux ב-upstream.
  • סטנדרטיזציה: מסגרת ה-DMA-BUF heaps מציעה UAPI מוגדר היטב. ב-ION הותר להשתמש בדגלים מותאמים אישית ובמזהי אשכול, דבר שגרם לכך שלא ניתן היה לפתח מסגרת בדיקה משותפת, כי ההטמעה של ION בכל מכשיר עשויה להתנהג בצורה שונה.

ההסתעפות android12-5.10 של הליבה המשותפת של Android הושבתה ב-CONFIG_ION ב-1 במרץ 2021.

רקע

יש כאן השוואה קצרה בין ערימות של ION לבין ערימות של DMA-BUF.

קווי דמיון בין מסגרת ה-heaps של ION לבין מסגרת ה-heaps של DMA-BUF

  • מסגרות ה-heap של ION ו-DMA-BUF הן שתי מסגרות של ייצוא DMA-BUF שמבוססות על heap.
  • בשתי השיטות, כל אשכול יכול להגדיר את המקצה ואת הפעולות של DMA-BUF שלו.
  • ביצועי ההקצאה דומים כי בשתי הסכימות נדרש IOCTL יחיד להקצאה.

ההבדלים בין מסגרת ה-heaps של ION לבין מסגרת ה-heaps של DMA-BUF

ערמות (heaps) של ION אשכולות DMA-BUF
כל ההקצאות של ION מתבצעות באמצעות /dev/ion. כל ערימה של DMA-BUF היא מכשיר תווים שנמצא ב-/dev/dma_heap/<heap_name>.
ב-ION יש תמיכה בדגלים פרטיים של אשכול. ערמות DMA-BUF לא תומכות בדגלים פרטיים של ערימה. במקום זאת, כל סוג הקצאה מתבצע מערימה אחרת. לדוגמה, וריאציות של אשכול המערכת שנשמרו במטמון ושל אשכול המערכת שלא נשמר במטמון הן אשכולות נפרדים שנמצאים במיקומים /dev/dma_heap/system ו-/dev/dma_heap/system_uncached.
כדי להקצות, צריך לציין את המזהה או המסכה של ה-heap ואת הדגלים. שם האשפה משמש להקצאה.

בקטעים הבאים מפורטים הרכיבים שקשורים ל-ION, ומוסבר איך להעביר אותם למסגרת של אשכולות DMA-BUF.

מעבר של מנהלי ליבה מ-ION למקבצים של DMA-BUF

מנהלי התקן של ליבה שמטמיעים ערמות ION

ערימות של ION וגם DMA-BUF מאפשרות לכל ערימה ליישם הקצאות משלה ופעולות DMA-BUF. כך תוכלו לעבור מהטמעה של אשכול ION להטמעה של אשכול DMA-BUF באמצעות קבוצה אחרת של ממשקי API כדי לרשום את האשכול. בטבלה הזו מפורטים ממשקי ה-API לרישום אשכול ION וממשקי ה-API המקבילים של אשכול DMA-BUF.

ערימות של ION אשכולות DMA-BUF
void ion_device_add_heap(struct ion_heap *heap) struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info);
void ion_device_remove_heap(struct ion_heap *heap) void dma_heap_put(struct dma_heap *heap);

ערמות DMA-BUF לא תומכות בדגלים פרטיים של ערימה. לכן, כל וריאנט של הערימה צריך להיות רשום בנפרד באמצעות ה-API dma_heap_add(). כדי להקל על שיתוף הקוד, מומלץ לרשום את כל הווריאציות של אותו אשכול באותו מנהל. בדוגמה הזו של dma-buf: system_heap מוצגת ההטמעה של הגרסאות השמורות במטמון והגרסאות שלא שמורות במטמון של אשכול המערכת.

משתמשים ב-dma-buf: heaps: תבנית לדוגמה כדי ליצור ערימה מאפס של DMA-BUF.

מנהלי ליבה שמקצים ישירות מערמות ION

מסגרת הערימה (heaps) של DMA-BUF מציעה גם ממשק הקצאה ללקוחות בתוך הליבה. במקום לציין את המסכה והדגלים של האשפה כדי לבחור את סוג ההקצאה, הממשק שמוצג על ידי אשפה של DMA-BUF מקבל שם של אשפה כקלט.

למטה מוצגים ה-API להקצאת ION בתוך הליבה, וממשקי ה-API המקבילים שלהם ב-DMA-BUF. מנהלי התקנים של הליבה יכולים להשתמש ב-API dma_heap_find() כדי לבדוק אם אשכול קיים. ה-API מחזיר הפניה למכונה של struct dma_heap, שאפשר להעביר כארגומנטים ל-API של dma_heap_buffer_alloc().

ערמות (heaps) של ION אשכולות DMA-BUF
struct dma_buf *ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags)

struct dma_heap *dma_heap_find(const char *name)

struct dma_buf *struct dma_buf *dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, unsigned int fd_flags, unsigned int heap_flags)

מנהלי ליבה שמשתמשים ב-DMA-BUFs

לא נדרשים שינויים בנהגים שמייבאים רק DMA-BUFs, כי מאגר של מאגר ION מתנהג בדיוק כמו מאגר של מאגר DMA-BUF מקביל.

העברת הלקוחות של ION במרחב המשתמש לאשכולות DMA-BUF

כדי להקל על המעבר ללקוחות ION במרחב המשתמש, קיימת ספריית הפשטה שנקראת libdmabufheap. libdmabufheap תומך בהקצאה ב-DMA-BUF heaps וב-ION heaps. קודם כול, המערכת בודקת אם יש אשכול DMA-BUF בשם שצוין. אם לא, היא עוברת לאשכול ION מקביל, אם קיים כזה.

לקוחות צריכים לאתחל אובייקט BufferAllocator במהלך האתחול שלהם במקום לפתוח את /dev/ion using ion_open(). הסיבה לכך היא שמתארי הקבצים שנוצרים על ידי פתיחת /dev/ion ו-/dev/dma_heap/<heap_name> מנוהלים באופן פנימי על ידי האובייקט BufferAllocator.

כדי לעבור מ-libion ל-libdmabufheap, משנים את ההתנהגות של הלקוחות באופן הבא:

  • מעקב אחרי שם האשפה לשימוש בהקצאה, במקום אחרי המזהה/המסכה של ה-head ודגל האשפה.
  • מחליפים את ה-API ion_alloc_fd(), שמשתמש במסכת ערימה ובארגומנט של דגל, ב-API BufferAllocator::Alloc(), שמקבל במקום זאת שם של ערימה (heap).

בטבלה הזו מוצגים השינויים האלה, דרך האופן שבו libion ו-libdmabufheap מבצעים הקצאה של אשכול מערכת ללא מטמון.

סוג ההקצאה ליביון libdmabufheap
הקצאה של קובץ שמור מערימה של המערכת ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
הקצאה ללא שמירה במטמון מהמקבץ של המערכת ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

הווריאנט של תמונת הערימה של המערכת שלא נשמר במטמון ממתין לאישור ב-upstream, אבל הוא כבר חלק מהסתעפות android12-5.10.

כדי לתמוך בשדרוג מכשירים, ה-API של MapNameToIonHeap() מאפשר למפות שם ערימה לפרמטרים של ערימה (heap name) או mask and דגלים) כדי לאפשר לממשקים האלה להשתמש בהקצאות מבוססות-שם. דוגמה להקצאה לפי שם

המסמכים של כל ממשק API שlibdmabufheap חושף זמינים. הספרייה חושפת גם קובץ כותרת לשימוש של לקוחות C.

יישום Gralloc למטרות עזר

הטמעת gralloc של Hikey960 משתמשת ב-libdmabufheap, כך שאפשר להשתמש בה כהטמעת עזר.

תוספות נדרשות ל-ueventd

לכל אשכול DMA-BUF חדש ספציפי למכשיר שנוצר, מוסיפים רשומה חדשה לקובץ ueventd.rc של המכשיר. בדוגמה הזו הגדרה שתומכת בערימה (heap) של DMA-BUF ממחישה איך עושים את זה בשביל הערימה (heap) של מערכת DMA-BUF.

התוספות של המדיניות הרלוונטית

הוספת הרשאות של מדיניות אבטחה (sepolicy) כדי לאפשר ללקוח במרחב המשתמש לגשת למחסנית DMA-BUF חדשה. בדוגמה הזו להוספת ההרשאות הנדרשות מוצגות ההרשאות של מדיניות האבטחה שנוצרו ללקוחות שונים כדי לגשת לאוסף המערכת של DMA-BUF.

גישה לאשכולות של ספקים מקוד של מסגרת

כדי לוודא תאימות ל-Treble, קוד המסגרת יכול להקצות רק מקטגוריות שאושרו מראש של אשכולות של ספקים.

על סמך המשוב שקיבלנו מהשותפים, Google זיהתה שתי קטגוריות של אשכולות של ספקים שצריך לגשת אליהם מקוד המסגרת:

  1. אשכולות שמבוססים על אשכול המערכת עם אופטימיזציות ביצועים ספציפיות למכשיר או ל-SoC.
  2. אשכולות להקצאה מזיכרון מוגן.

אשכולות שמבוססים על אשכול המערכת עם אופטימיזציות ביצועים ספציפיות למכשיר או ל-SoC

כדי לתמוך בתרחיש לדוגמה הזה, אפשר לשנות את ההטמעה של אשכול מערכת ברירת המחדל של DMA-BUF.

  • CONFIG_DMABUF_HEAPS_SYSTEM מושבת ב-gki_defconfig כדי שהוא יוכל לשמש כמודול של ספק.
  • בדיקות התאימות ל-VTS מוודאות שהמקבץ קיים ב-/dev/dma_heap/system. הבדיקות גם מאמתות שאפשר להקצות את הערימה (heap) ושמתאר הקובץ שמוחזר (fd) ניתן למיפוי זיכרון (מ"מ) ממרחב המשתמשים.

הנקודות הקודמות נכונות גם לגרסה ללא מטמון של אשכול המערכת, אבל קיומה לא חובה במכשירים עם תאימות מלאה ל-IO.

ערימות להקצאה מזיכרון מוגן

הטמעות של אשכול מאובטח חייבות להיות ספציפיות לספק, כי ל-Android Common Kernel אין תמיכה בהטמעה של אשכול מאובטח כללי.

  • רשום את ההטמעות הספציפיות לספק שלך בתור /dev/dma_heap/system-secure<vendor-suffix>.
  • ההטמעות האלה של אשכול הן אופציונליות.
  • אם ה-heaps קיימים, בדיקות VTS מוודאות שאפשר לבצע הקצאות מהם.
  • לרכיבי המסגרת יש גישה לאשכולות האלה, כדי שיוכלו לאפשר שימוש באשכולות דרך HAL של Codec2 או HAL ללא קישור באותו תהליך. עם זאת, תכונות כלליות של מסגרת Android לא יכולות להיות תלויות בהן בגלל השונות בפרטי ההטמעה שלהן. אם בעתיד תתווסף ל-Android Common Kernel הטמעה של אשכול מאובטח גנרי, היא תצטרך להשתמש ב-ABI שונה כדי למנוע התנגשויות עם מכשירי שדרוג.

מנהל הקצאות של Codec 2 למקבצים של DMA-BUF

מקצה codec2 לממשק של אשכולות DMA-BUF זמין ב-AOSP.

ממשק מאגר הרכיבים שמאפשר לציין פרמטרים של אשכול מ-C2 HAL זמין עם מנהל האשכולות C2 DMA-BUF.

תהליך מעבר לדוגמה של ערימת ION

כדי שהמעבר מ-ION למקבצים של DMA-BUF יהיה חלק, אפשר להשתמש ב-libdmabufheap כדי לעבור למקבץ אחד בכל פעם. בשלבים הבאים מוצגת הצעה לתהליך עבודה להעברת ערימת ION לא מדור קודם בשם my_heap שתומכת בדגל אחד, ION_FLAG_MY_FLAG.

שלב 1: יוצרים מקבילות של ערמת ION במסגרת DMA-BUF. בדוגמה הזו, מכיוון שהערימה my_heap של ION תומכת בדגל ION_FLAG_MY_FLAG, אנחנו רושמים שתי ערימות DMA-BUF:

  • ההתנהגות של my_heap תואמת בדיוק להתנהגות של ערמת ION כשהדגל ION_FLAG_MY_FLAG מושבת.
  • ההתנהגות של my_heap_special זהה לזו של אשכול ION כשהדגל ION_FLAG_MY_FLAG מופעל.

שלב 2: יוצרים את השינויים ב-ueventd עבור ערמות ה-DMA-BUF החדשות my_heap ו-my_heap_special. בשלב הזה, האשכולות גלויים בתור /dev/dma_heap/my_heap ו-/dev/dma_heap/my_heap_special, עם ההרשאות המיועדות.

שלב 3: ללקוחות שמקצים מ-my_heap, משנים את קובצי ה-makefile שלהם כך שיקשרו ל-libdmabufheap. במהלך האי initialization של הלקוח, יוצרים אובייקט BufferAllocator ומשתמשים ב-API של MapNameToIonHeap() כדי למפות את השילוב <ION heap name/mask, flag> לשמות equivaent DMA-BUF heap.

לדוגמה:

allocator->MapNameToIonHeap("my_heap_special" /* name of DMA-BUF heap */, "my_heap" /* name of the ION heap */, ION_FLAG_MY_FLAG /* ion flags */ )

במקום להשתמש ב-API MapNameToIonHeap() עם הפרמטרים name ו-flag, אפשר ליצור את המיפוי מ-<ION heap mask, flag> לשמות equivaent DMA-BUF heap על ידי הגדרת הפרמטר name של ION heap כריק.

שלב 4: מחליפים את ion_alloc_fd() הפעלות ב-BufferAllocator::Alloc() עם שם הערימה המתאים.

סוג הקצאה ליביון libdmabufheap
הקצאה מ-my_heap בלי להגדיר את הדגל ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
הקצאה מ-my_heap עם הדגל ION_FLAG_MY_FLAG מוגדר ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

בשלב הזה, הלקוח פעיל אבל עדיין מקצה את הערימה של ION כי אין לו את ההרשאות הנדרשות בנוגע למדיניות לפתיחת ערימת ה-DMA-BUF.

שלב 5: יוצרים את ההרשאות של מדיניות האבטחה הנדרשות כדי שהלקוח יוכל לגשת למקבצים החדשים של DMA-BUF. עכשיו הלקוח מוכן לחלוטין להקצות מהמקבץ החדש של DMA-BUF.

שלב 6: כדי לוודא שההקצאות מתבצעות מערימת ה-DMA-BUF החדשה, בודקים את logcat.

שלב 7: משביתים את my_heap של ION heap בליבה. אם קוד הלקוח לא צריך לתמוך בשדרוג של מכשירים (שליבת המעבד שלהם עשויה לתמוך רק ב-heaps של ION), אפשר גם להסיר את ההפעלות של MapNameToIonHeap().