לדלג לתוכן

גישה להתקן ובעלות על נתוני DMA

אותה כתובת אינה אותה משמעות

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

נפריד בין כתובת וירטואלית של המעבד, כתובת פיזית של המעבד וכתובת גישה ישירה לזיכרון שנמסרת להתקן. גם אם שתי כתובות מספריות שוות במכונה אחת, השוויון אינו חוזה נייד. מנגנון IOMMU יכול למפות כתובת של התקן לדף פיזי אחר ולבודד תחומי גישה של התקנים.

אוגרים אינם מערך רגיל

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

בקרנל משתמשים בפונקציות גישה מתאימות כמו readl ו־writel לאחר מיפוי המשאב בממשק המתאים. הן מבטאות דרישות ארכיטקטורה וסדר שאינן מתקבלות מהוספת המילה volatile למצביע. גם מחסום מהדר אינו בהכרח מחסום מול התקן.

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

העברת בעלות על באפר

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

הכיוון מתאר את זרימת הנתונים ביחס להתקן. כאשר ההתקן קורא מידע שהמעבד הכין, הכיוון שונה מהמקרה שבו ההתקן כותב תוצאה למעבד. בחירה בכיוון דו כיווני לכל פעולה אינה הסבר ואינה בהכרח בחירה יעילה. נבחר לפי הפעולה האמיתית.

הקטע הבא הוא תבנית הסבר בתוך דרייבר שכבר מחזיק התקן תקף, ואינו תוכנית עצמאית. פונקציות ההגשה וההמתנה תלויות בחומרה ולכן אינן מומצאות כאן כממשק קרנל כללי.

dma_addr_t address;

address = dma_map_single(dev, buffer, length, DMA_TO_DEVICE);
if (dma_mapping_error(dev, address))
    return -EIO;

/* submit_to_device(address, length); */
/* wait_until_device_has_stopped_using_buffer(); */

dma_unmap_single(dev, address, length, DMA_TO_DEVICE);

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

מודל בעלות שאפשר להריץ

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

use std::thread::{self, JoinHandle};

struct CpuBuffer(Vec<u8>);
struct InFlight(JoinHandle<Vec<u8>>);

impl CpuBuffer {
    fn new(size: usize) -> Self { Self(vec![0; size]) }
    fn bytes(&mut self) -> &mut [u8] { &mut self.0 }
    fn submit(self) -> InFlight {
        InFlight(thread::spawn(move || {
            let mut bytes = self.0;
            for byte in &mut bytes { *byte = byte.wrapping_add(1); }
            bytes
        }))
    }
}
impl InFlight {
    fn complete(self) -> thread::Result<CpuBuffer> {
        self.0.join().map(CpuBuffer)
    }
}
fn main() {
    let mut buffer = CpuBuffer::new(16);
    buffer.bytes().fill(7);
    let operation = buffer.submit();
    let mut buffer = operation.complete().unwrap();
    assert!(buffer.bytes().iter().all(|&value| value == 8));
    println!("ownership returned after completion");
}

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

קוהרנטיות אינה סדר פרסום

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

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

בבדיקת דרייבר נשרטט את המעברים בין בעלות מעבד לבעלות התקן ונציין היכן מתבצעים מיפוי, סנכרון, השלמה וביטול. התרשים הזה שימושי יותר מהנחה כללית שהזיכרון משותף ולכן כולם יכולים להשתמש בו יחד.

להרחבה ניעזר במדריך המיפוי להתקנים ובתיעוד גישת קלט ופלט.