Isolasi GPU berbasis IOMMU

Halaman ini menjelaskan fitur isolasi GPU berbasis IOMMU untuk perangkat berkemampuan IOMMU, yang diperkenalkan dalam versi Windows 10 1803 (WDDM 2.4). Lihat remapping IOMMU DMA untuk pembaruan IOMMU yang lebih baru.

Gambaran Umum

Isolasi GPU berbasis IOMMU memungkinkan Dxgkrnl membatasi akses ke memori sistem dari GPU dengan menggunakan perangkat keras IOMMU. OS dapat menyediakan alamat logis alih-alih alamat fisik. Alamat logis ini dapat digunakan untuk membatasi akses perangkat ke memori sistem hanya ke memori yang dapat diaksesnya. Hal ini dilakukan dengan memastikan bahwa IOMMU menerjemahkan akses memori melalui PCIe ke halaman fisik yang valid dan dapat diakses.

Jika alamat logis yang diakses oleh perangkat tidak valid, perangkat tidak bisa mendapatkan akses ke memori fisik. Pembatasan ini mencegah berbagai eksploitasi yang memungkinkan penyerang untuk mendapatkan akses ke memori fisik melalui perangkat keras yang disusupi dan membaca konten memori sistem yang tidak diperlukan untuk operasi perangkat.

Mulai Windows 10 versi 1803, secara default fitur ini hanya diaktifkan untuk PC di mana Pertahanan Windows Application Guard diaktifkan untuk Microsoft Edge (yaitu, virtualisasi kontainer).

Untuk tujuan pengembangan, fungsionalitas remapping IOMMU yang sebenarnya diaktifkan atau dinonaktifkan melalui kunci registri berikut:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers
DWORD: IOMMUFlags

0x01 Enabled
     * Enables creation of domain and interaction with HAL

0x02 EnableMappings
     * Maps all physical memory to the domain
     * EnabledMappings is only valid if Enabled is also set. Otherwise no action is performed

0x04 EnableAttach
     * Attaches the domain to the device(s)
     * EnableAttach is only valid if EnableMappings is also set. Otherwise no action is performed

0x08 BypassDriverCap
     * Allows IOMMU functionality regardless of support in driver caps. If the driver does not indicate support for the IOMMU and this bit is not set, the enabled bits are ignored.

0x10 AllowFailure
     * Ignore failures in IOMMU enablement and allow adapter creation to succeed anyway.
     * This value cannot override the behavior when created a secure VM, and only applies to forced IOMMU enablement at device startup time using this registry key.

Jika fitur ini diaktifkan, IOMMU diaktifkan segera setelah adaptor dimulai. Semua alokasi driver yang dilakukan sebelum waktu ini dipetakan ketika diaktifkan.

Selain itu, jika kunci penahapan kecepatan 14688597 diatur sebagai diaktifkan, IOMMU diaktifkan saat komputer virtual aman dibuat. Untuk saat ini, kunci penahapan ini dinonaktifkan secara default untuk memungkinkan hosting mandiri tanpa dukungan IOMMU yang tepat.

Saat diaktifkan, memulai komputer virtual yang aman gagal jika driver tidak memberikan dukungan IOMMU.

Saat ini tidak ada cara untuk menonaktifkan IOMMU setelah diaktifkan.

Akses memori

Dxgkrnl memastikan bahwa semua memori yang dapat diakses oleh GPU dipetakan ulang melalui IOMMU untuk memastikan bahwa memori ini dapat diakses. Memori fisik yang perlu diakses GPU saat ini dapat dipecah menjadi empat kategori:

  • Alokasi khusus driver yang dibuat melalui fungsi gaya MmAllocateContiguousMemory- atau MmAllocatePagesForMdl (termasuk variasi SpecifyCache dan extended) harus dipetakan ke IOMMU sebelum GPU mengaksesnya. Alih-alih memanggil API Mm , Dxgkrnl menyediakan panggilan balik ke driver mode kernel untuk memungkinkan alokasi dan pembukaan ulang dalam satu langkah. Memori apa pun yang dimaksudkan agar dapat diakses GPU harus melalui panggilan balik ini, atau GPU tidak dapat mengakses memori ini.

  • Semua memori yang diakses oleh GPU selama operasi penomoran halaman, atau dipetakan melalui GpuMmu harus dipetakan ke IOMMU. Proses ini sepenuhnya internal untuk Manajer Memori Video (VidMm), yang merupakan subkomponen Dxgkrnl. VidMm menangani pemetaan dan membatalkan pemetaan ruang alamat logis kapan saja GPU diharapkan untuk mengakses memori ini, termasuk:

  • Memetakan penyimpanan cadangan alokasi selama seluruh durasi selama transfer ke atau dari VRAM atau sepanjang waktu yang dipetakan ke memori sistem atau segmen aperture.

  • Pemetaan dan pembukaan pagar yang dipantau.

  • Selama transisi daya, driver mungkin perlu menyimpan sebagian memori yang dicadangkan perangkat keras. Untuk menangani situasi ini, Dxgkrnl menyediakan mekanisme bagi driver untuk menentukan berapa banyak memori di depan untuk menyimpan data ini. Jumlah memori yang tepat yang diperlukan oleh driver dapat berubah secara dinamis, tetapi Dxgkrnl mengambil biaya penerapan pada batas atas pada saat adaptor diinisialisasi untuk memastikan bahwa halaman fisik dapat diperoleh ketika diperlukan. Dxgkrnl bertanggung jawab untuk memastikan memori ini dikunci dan dipetakan ke IOMMU untuk transfer selama transisi daya.

  • Untuk sumber daya yang dipesan perangkat keras apa pun, VidMm memastikan bahwa ia memetakan sumber daya IOMMU dengan benar pada saat perangkat dilampirkan ke IOMMU. Ini termasuk memori yang dilaporkan oleh segmen memori yang dilaporkan dengan PopulatedFromSystemMemory. Untuk memori yang dipesan (misalnya, firmware/BIOD yang dicadangkan) yang tidak diekspos melalui segmen VidMm, Dxgkrnl melakukan panggilan DXGKDDI_QUERYADAPTERINFO untuk mengkueri semua rentang memori yang dipesan yang perlu dipetakan driver sebelumnya. Lihat Memori yang dipesan perangkat keras untuk detailnya.

Penetapan domain

Selama inisialisasi perangkat keras, Dxgkrnl membuat domain untuk setiap adaptor logis pada sistem. Domain mengelola ruang alamat logis dan melacak tabel halaman dan data lain yang diperlukan untuk pemetaan. Semua adaptor fisik dalam satu adaptor logis milik domain yang sama. Dxgkrnl melacak semua memori fisik yang dipetakan melalui rutinitas panggilan balik alokasi baru, dan memori apa pun yang dialokasikan oleh VidMm itu sendiri.

Domain akan dilampirkan ke perangkat saat pertama kali komputer virtual aman dibuat, atau tidak lama setelah perangkat dimulai jika kunci registri di atas digunakan.

Akses eksklusif

Lampiran dan pencopotan domain IOMMU sangat cepat, tetapi tetap tidak atomik saat ini. Ini berarti bahwa transaksi yang dikeluarkan melalui PCIe tidak dijamin akan diterjemahkan dengan benar saat bertukar ke domain IOMMU dengan pemetaan yang berbeda.

Untuk menangani situasi ini, mulai dari Windows 10 versi 1803 (WDDM 2.4), KMD harus menerapkan pasangan DDI berikut agar Dxgkrnl dapat memanggil:

DDI ini membentuk pemasangan awal/akhir, di mana Dxgkrnl meminta agar perangkat keras diam di atas bus. Driver harus memastikan bahwa perangkat kerasnya diam setiap kali perangkat dialihkan ke domain IOMMU baru. Artinya, driver harus memastikan bahwa driver tidak membaca atau menulis ke memori sistem dari perangkat di antara kedua panggilan ini.

Di antara kedua panggilan ini, Dxgkrnl membuat jaminan berikut:

  • Penjadwal ditangguhkan. Semua beban kerja aktif dibersihkan, dan tidak ada beban kerja baru yang dikirim atau dijadwalkan pada perangkat keras.
  • Tidak ada panggilan DDI lain yang dilakukan.

Sebagai bagian dari panggilan ini, driver dapat memilih untuk menonaktifkan dan menekan gangguan (termasuk interupsi Vsync) selama akses eksklusif, bahkan tanpa pemberitahuan eksplisit dari OS.

Dxgkrnl memastikan bahwa pekerjaan yang tertunda yang dijadwalkan pada perangkat keras selesai, lalu memasuki wilayah akses eksklusif ini. Selama waktu ini, Dxgkrnl menetapkan domain ke perangkat. Dxgkrnl tidak membuat permintaan driver atau perangkat keras di antara panggilan ini.

Perubahan DDI

Perubahan DDI berikut dilakukan untuk mendukung isolasi GPU berbasis IOMMU:

Alokasi memori dan pemetaan ke IOMMU

Dxgkrnl menyediakan enam panggilan balik pertama dalam tabel di atas ke driver mode kernel untuk memungkinkannya mengalokasikan memori dan memetakannya kembali ke ruang alamat logis IOMMU. Fungsi panggilan balik ini meniru rutinitas yang disediakan oleh antarmuka MM API. Mereka menyediakan driver dengan MDL, atau pointer yang menggambarkan memori yang juga dipetakan ke IOMMU. MDL ini terus menjelaskan halaman fisik, tetapi ruang alamat logis IOMMU dipetakan di alamat yang sama.

Dxgkrnl melacak permintaan ke panggilan balik ini untuk membantu memastikan tidak ada kebocoran oleh pengemudi. Panggilan balik alokasi menyediakan handel tambahan sebagai bagian dari output yang harus disediakan kembali ke panggilan balik gratis masing-masing.

Untuk memori yang tidak dapat dialokasikan melalui salah satu panggilan balik alokasi yang disediakan, panggilan balik DXGKCB_MAPMDLTOIOMMU disediakan untuk memungkinkan MDL yang dikelola driver dilacak dan digunakan dengan IOMMU. Driver yang menggunakan panggilan balik ini bertanggung jawab untuk memastikan bahwa masa pakai MDL melebihi panggilan unmap yang sesuai. Jika tidak, panggilan unmap memiliki perilaku yang tidak terdefinisi yang mungkin menyebabkan keamanan halaman yang disusupi dari MDL yang digunakan kembali oleh Mm pada saat mereka tidak dipetakan.

VidMm secara otomatis mengelola alokasi apa pun yang dibuatnya (misalnya, DdiCreateAllocationCb, pagar yang dipantau, dll.) dalam memori sistem. Driver tidak perlu melakukan apa pun untuk membuat alokasi ini berfungsi.

Reservasi buffer bingkai

Untuk driver yang harus menyimpan bagian yang dipesan dari buffer bingkai ke memori sistem selama transisi daya, Dxgkrnl mengambil biaya penerapan pada memori yang diperlukan ketika adaptor diinisialisasi. Jika driver melaporkan dukungan isolasi IOMMU, Dxgkrnl akan mengeluarkan panggilan ke DXGKDDI_QUERYADAPTERINFO dengan yang berikut ini segera setelah mengkueri batas adaptor fisik:

  • JenisDXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • Inputnya adalah jenis UINT, yang merupakan indeks adaptor fisik.
  • Outputnya adalah jenis DXGK_FRAMEBUFFERSAVEAREA, dan harus menjadi ukuran maksimum yang diperlukan oleh driver untuk menyimpan area cadangan buffer bingkai selama transisi daya.

Dxgkrnl mengambil biaya penerapan pada jumlah yang ditentukan oleh pengemudi untuk memastikan bahwa ia selalu bisa mendapatkan halaman fisik berdasarkan permintaan. Tindakan ini dilakukan dengan membuat objek bagian unik untuk setiap adaptor fisik yang menentukan nilai bukan nol untuk ukuran maksimum.

Ukuran maksimum yang dilaporkan oleh driver harus kelipatan PAGE_SIZE.

Melakukan transfer ke dan dari buffer bingkai dapat dilakukan pada saat pilihan driver. Untuk membantu transfer, Dxgkrnl menyediakan empat panggilan balik terakhir dalam tabel di atas ke driver mode kernel. Panggilan balik ini dapat digunakan untuk memetakan bagian yang sesuai dari objek bagian yang dibuat saat adaptor diinisialisasi.

Driver harus selalu menyediakan hAdapter untuk perangkat master/lead dalam rantai LDA ketika memanggil keempat fungsi panggilan balik ini.

Driver memiliki dua opsi untuk mengimplementasikan reservasi buffer bingkai:

  1. (Metode yang disukai) Driver harus mengalokasikan ruang per adaptor fisik menggunakan panggilan DXGKDDI_QUERYADAPTERINFO di atas untuk menentukan jumlah penyimpanan yang diperlukan per adaptor. Pada saat transisi daya, driver harus menyimpan atau memulihkan adaptor fisik memori satu per satu. Memori ini dibagi di beberapa objek bagian, satu per adaptor fisik.

  2. Secara opsional, driver dapat menyimpan atau memulihkan semua data ke dalam satu objek bagian bersama. Tindakan ini dapat dilakukan dengan menentukan satu ukuran maksimum besar dalam panggilan DXGKDDI_QUERYADAPTERINFO untuk adaptor fisik 0, dan kemudian nilai nol untuk semua adaptor fisik lainnya. Driver kemudian dapat menyematkan seluruh objek bagian sekali untuk digunakan di semua operasi simpan/pulihkan, untuk semua adaptor fisik. Metode ini memiliki kelemahan utama yang diperlukan untuk mengunci jumlah memori yang lebih besar sekaligus, karena tidak mendukung penyematan hanya subrange memori ke dalam MDL. Akibatnya, operasi ini lebih mungkin gagal di bawah tekanan memori. Driver juga akan diharapkan untuk memetakan halaman di MDL ke GPU menggunakan offset halaman yang benar.

Driver harus melakukan tugas berikut untuk menyelesaikan transfer ke atau dari buffer bingkai:

  • Selama inisialisasi, driver harus melakukan pra-alokasi potongan kecil memori yang dapat diakses GPU menggunakan salah satu rutinitas panggilan balik alokasi. Memori ini digunakan untuk membantu memastikan kemajuan penerusan jika seluruh objek bagian tidak dapat dipetakan/dikunci sekaligus.

  • Pada saat transisi daya, driver harus terlebih dahulu memanggil Dxgkrnl untuk menyematkan buffer bingkai. Jika berhasil, Dxgkrnl memberi driver MDL ke halaman terkunci yang dipetakan ke IOMMU. Driver kemudian dapat melakukan transfer langsung ke halaman-halaman ini dalam cara apa pun yang paling efisien untuk perangkat keras. Driver kemudian harus memanggil Dxgkrnl untuk membuka kunci/membatalkan peta memori.

  • Jika Dxgkrnl tidak dapat menyematkan seluruh buffer bingkai sekaligus, driver harus mencoba untuk membuat kemajuan dengan menggunakan buffer yang dialokasikan sebelumnya yang dialokasikan selama inisialisasi. Dalam hal ini, driver melakukan transfer dalam gugus kecil. Selama setiap iterasi transfer (untuk setiap gugus), driver harus meminta Dxgkrnl untuk menyediakan rentang objek bagian yang dipetakan yang dapat mereka salin hasilnya. Driver kemudian harus membatalkan peta bagian objek bagian sebelum iterasi berikutnya.

Pseudocode berikut adalah contoh implementasi algoritma ini.


#define SMALL_SIZE (PAGE_SIZE)

PMDL PHYSICAL_ADAPTER::m_SmallMdl;
PMDL PHYSICAL_ADAPTER::m_PinnedMdl;

NTSTATUS PHYSICAL_ADAPTER::Init()
{
    DXGKARGCB_ALLOCATEPAGESFORMDL Args = {};
    Args.TotalBytes = SMALL_SIZE;
    
    // Allocate small buffer up front for forward progress transfers
    Status = DxgkCbAllocatePagesForMdl(SMALL_SIZE, &Args);
    m_SmallMdl = Args.pMdl;

    ...
}

NTSTATUS PHYSICAL_ADAPTER::OnPowerDown()
{    
    Status = DxgkCbPinFrameBufferForSave(&m_pPinnedMdl);
    if(!NT_SUCCESS(Status))
    {
        m_pPinnedMdl = NULL;
    }
    
    if(m_pPinnedMdl != NULL)
    {        
        // Normal GPU copy: frame buffer -> m_pPinnedMdl
        GpuCopyFromFrameBuffer(m_pPinnedMdl, Size);
        DxgkCbUnpinFrameBufferForSave(m_pPinnedMdl);
    }
    else
    {
        SIZE_T Offset = 0;
        while(Offset != TotalSize)
        {
            SIZE_T MappedOffset = Offset;
            PVOID pCpuPointer;
            Status = DxgkCbMapFrameBufferPointer(SMALL_SIZE, &MappedOffset, &pCpuPointer);
            if(!NT_SUCCESS(Status))
            {
                // Driver must handle failure here. Even a 4KB mapping may
                // not succeed. The driver should attempt to cancel the
                // transfer and reset the adapter.
            }
            
            GpuCopyFromFrameBuffer(m_pSmallMdl, SMALL_SIZE);
            
            RtlCopyMemory(pCpuPointer + MappedOffset, m_pSmallCpuPointer, SMALL_SIZE);
            
            DxgkCbUnmapFrameBufferPointer(pCpuPointer);
            Offset += SMALL_SIZE;
        }
    }
}

NTSTATUS PHYSICAL_ADAPTER::OnPowerUp()
{
    Status = DxgkCbPinFrameBufferForSave(&m_pPinnedMdl);
    if(!NT_SUCCESS(Status))
    {
        m_pPinnedMdl = NULL;
    }
    
    if(pPinnedMemory != NULL)
    {
        // Normal GPU copy: m_pPinnedMdl -> frame buffer
        GpuCopyToFrameBuffer(m_pPinnedMdl, Size);
        DxgkCbUnpinFrameBufferForSave(m_pPinnedMdl);
    }
    else
    {
        SIZE_T Offset = 0;
        while(Offset != TotalSize)
        {
            SIZE_T MappedOffset = Offset;
            PVOID pCpuPointer;
            Status = DxgkCbMapFrameBufferPointer(SMALL_SIZE, &MappedOffset, &pCpuPointer);
            if(!NT_SUCCESS(Status))
            {
                // Driver must handle failure here. Even a 4KB mapping may
                // not succeed. The driver should attempt to cancel the
                // transfer and reset the adapter.
            }
                        
            RtlCopyMemory(m_pSmallCpuPointer, pCpuPointer + MappedOffset, SMALL_SIZE);
            
            GpuCopyToFrameBuffer(m_pSmallMdl, SMALL_SIZE);

            DxgkCbUnmapFrameBufferPointer(pCpuPointer);
            Offset += SMALL_SIZE;
        }
    }
}

Memori yang dipesan perangkat keras

VidMm memetakan memori cadangan perangkat keras sebelum perangkat dilampirkan ke IOMMU.

VidMm secara otomatis menangani memori apa pun yang dilaporkan sebagai segmen dengan bendera PopulatedFromSystemMemory . VidMm memetakan memori ini berdasarkan alamat fisik yang disediakan.

Untuk wilayah cadangan perangkat keras privat yang tidak diekspos oleh segmen, VidMm melakukan panggilan DXGKDDI_QUERYADAPTERINFO untuk mengkueri rentang oleh driver. Rentang yang disediakan tidak boleh tumpang tindih dengan wilayah memori apa pun yang digunakan oleh manajer memori NTOS; VidMm memvalidasi bahwa tidak ada persimpangan seperti itu yang terjadi. Validasi ini memastikan bahwa driver tidak dapat secara tidak sengaja melaporkan wilayah memori fisik yang berada di luar rentang yang dipesan, yang akan melanggar jaminan keamanan fitur.

Panggilan kueri dilakukan satu kali untuk mengkueri jumlah rentang yang diperlukan, dan diikuti dengan panggilan kedua untuk mengisi array rentang yang dipesan.

Pengujian

Jika driver ikut serta dalam fitur ini, pengujian HLK memindai tabel impor driver untuk memastikan bahwa tidak ada fungsi Mm berikut yang dipanggil:

  • MmAllocateContiguousMemory
  • MmAllocateContiguousMemorySpecifyCache
  • MmFreeContiguousMemory
  • MmAllocatePagesForMdl
  • MmAllocatePagesForMdlEx
  • MmFreePagesFromMdl
  • MmProbeAndLockPages

Semua alokasi memori untuk memori yang berdampingan dan MDL harus melalui antarmuka panggilan balik Dxgkrnl menggunakan fungsi yang tercantum. Driver juga tidak boleh mengunci memori apa pun. Dxgkrnl mengelola halaman terkunci untuk pengemudi. Setelah memori dipetakan ulang, alamat logis halaman yang diberikan kepada driver mungkin tidak lagi cocok dengan alamat fisik.