לדלג לתוכן

גישה לחומרה ובחירת מודל

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

השיעור אינו מדריך לקריאת כתובות פיזיות שרירותיות. נבחן גישה רק למשאבים שה־PnP manager הקצה להתקן שבבעלות הדרייבר, ובהתאם לתיעוד החומרה שלו. פרויקט התוכנה שלנו אינו מקבל BAR חומרתי ולכן אינו מפעיל את קטעי ה־MMIO או ה־DMA האלה.

רשימת משאבים היא נקודת המוצא

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

הקטע הבא הוא פונקציית עזר מלאה לשילוב בדרייבר חומרה שמקבל descriptor מתאים. הוא אינו תוכנית עצמאית ואינו מופעל על driver התוכנה. המצביע שמוחזר חייב להישמר ב־context ולהשתחרר ב־MmUnmapIoSpace לאחר עצירת כל המשתמשים:

#include <ntddk.h>

NTSTATUS MapRegisters(
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource,
    SIZE_T minimumLength,
    PVOID* mapped,
    SIZE_T* mappedLength) {
    if (!resource || !mapped || !mappedLength)
        return STATUS_INVALID_PARAMETER;
    *mapped = nullptr;
    *mappedLength = 0;
    if (resource->Type != CmResourceTypeMemory ||
        resource->u.Memory.Length < minimumLength)
        return STATUS_DEVICE_CONFIGURATION_ERROR;

    const SIZE_T length = resource->u.Memory.Length;
    PVOID address = MmMapIoSpaceEx(
        resource->u.Memory.Start, length, PAGE_READWRITE | PAGE_NOCACHE);
    if (!address) return STATUS_INSUFFICIENT_RESOURCES;
    *mapped = address;
    *mappedLength = length;
    return STATUS_SUCCESS;
}

הבחירה PAGE_NOCACHE בדוגמה מתאימה רק כאשר תיעוד המשאב דורש אותה. סוגי זיכרון אחרים של התקן, למשל buffer תצוגה, יכולים לדרוש מדיניות שונה. גם descriptor ראשון מסוג memory אינו בהכרח אזור הרגיסטרים שרצינו; צריך לזהות אותו לפי חוזה ההתקן וה־bus.

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

קריאת רגיסטר אינה קריאת משתנה רגיל

בדרייבר נשתמש במשפחת READ_REGISTER ו־WRITE_REGISTER המתאימה לרוחב ולחוזה, ולא בהעתקת struct רגיל אל אזור MMIO. הממשקים מספקים את סמנטיקת הגישה הנדרשת למערכת, אבל אינם מחליפים את כללי ההתקן לגבי סדר פקודות ו־posted writes.

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

מחסום זיכרון של CPU מסדר סוגי גישות לפי חוזהו; הוא אינו בהכרח flush של כל שכבת bus או המתנה לסיום DMA. ההבחנה דומה לזו שלמדנו בין release/acquire לבין השלמת פעולה עסקית, אך כאן מתווסף משתתף שאינו thread רגיל.

העברה ישירה לזיכרון

ב־DMA ההתקן ניגש לזיכרון לפי כתובות שהמערכת הכינה עבורו. אסור למסור לו pointer וירטואלי של תהליך או כתובת שמתקבלת מחישוב לא מתועד. מנגנוני DMA של Windows וה־framework מנהלים התאמות כגון רוחב כתובת, scatter/gather ותרגום דרך IOMMU.

ה־MDL מתאר דפים, אך אינו לבדו תכנות של ההתקן. יצירת DMA transaction, מיפוי הטווחים, הפעלת ההעברה ודיווח completion הם שלבים נפרדים. כל segment ברשימת scatter/gather הוא טווח שההתקן רשאי להשתמש בו לפי החוזה, ולא בקשה לקרוא מעבר לסופו.

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

בחירת המסגרת הנכונה

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

נבדוק אילו bus ו־DDI זמינים במודל שנבחר, מהן דרישות latency, אילו פעולות חייבות לרוץ ברמות פסיקה מסוימות וכיצד נראים deployment ו־recovery. מעבר ל־user mode משנה גבולות כשל ואבטחה, אך אינו מבטל את הצורך בפענוח קלט ובחיי משאבים.

ניסוי קריאה בתיעוד ובהקצאה

ב־VM או במכונת חומרה ייעודית, בחרו התקן שיש לו דוגמת driver רשמית ומפרט זמין. השוו את משאבי ה־translated list למבנה שהדוגמה משתמשת בו. אין לבצע כתיבות רגיסטרים לפני שיש מיפוי ברור בין משאב, offset ומשמעות.

ללא חומרה מתאימה, הניסוי הוא ניתוח של callback ההקמה ושל trace משאבים, ולא המצאת כתובת כדי לגרום לקוד "לעבוד". המיומנות הנמדדת היא יכולת להסביר את חוזה הכתובות והבעלות.

קריאה להעמקה

התיעוד של משאבי חומרה, מיפוי טווח I/O ואובייקטי DMA של framework מספק את בסיס ההרחבה להתקן אמיתי.