GNU/Linux >> Belajar Linux >  >> Linux

Driver perangkat kernel Linux ke DMA dari perangkat ke memori ruang pengguna

Saya semakin bingung dengan arah penerapannya. Saya ingin...

Pertimbangkan aplikasi saat mendesain driver.
Apa sifat pergerakan data, frekuensi, ukuran, dan apa lagi yang mungkin terjadi di sistem?

Apakah API baca/tulis tradisional sudah cukup? Apakah pemetaan langsung perangkat ke ruang pengguna OK? Apakah memori bersama reflektif (semi-koheren) diinginkan?

Memanipulasi data secara manual (baca/tulis) adalah opsi yang cukup bagus jika data dapat dipahami dengan baik. Menggunakan VM tujuan umum dan baca/tulis mungkin cukup dengan salinan sebaris. Pemetaan langsung akses yang tidak dapat di-cache ke periferal itu nyaman, tetapi bisa jadi canggung. Jika akses adalah pergerakan blok besar yang relatif jarang, mungkin masuk akal untuk menggunakan memori biasa, memiliki pin drive, menerjemahkan alamat, DMA dan melepaskan halaman. Sebagai pengoptimalan, halaman (mungkin besar) dapat disematkan dan diterjemahkan sebelumnya; drive kemudian dapat mengenali memori yang disiapkan dan menghindari kompleksitas terjemahan dinamis. Jika ada banyak operasi I/O kecil, menjalankan drive secara asinkron masuk akal. Jika keanggunan itu penting, bendera halaman kotor VM dapat digunakan untuk secara otomatis mengidentifikasi apa yang perlu dipindahkan dan panggilan (meta_sync()) dapat digunakan untuk membersihkan halaman. Mungkin campuran di atas berfungsi...

Terlalu sering orang tidak melihat masalah yang lebih besar, sebelum menggali detailnya. Seringkali solusi paling sederhana sudah cukup. Sedikit usaha untuk membuat model perilaku dapat membantu memandu API mana yang lebih disukai.


Saya sebenarnya sedang mengerjakan hal yang persis sama sekarang dan saya akan menggunakan ioctl() rute. Ide umumnya adalah ruang pengguna untuk mengalokasikan buffer yang akan digunakan untuk transfer DMA dan ioctl() akan digunakan untuk meneruskan ukuran dan alamat buffer ini ke driver perangkat. Pengemudi kemudian akan menggunakan daftar pencar bersama dengan API DMA streaming untuk mentransfer data langsung ke dan dari perangkat dan buffer ruang pengguna.

Strategi implementasi yang saya gunakan adalah ioctl() di driver memasuki loop yang merupakan buffer ruang pengguna DMA dalam potongan 256k (yang merupakan batas yang diberlakukan perangkat keras untuk berapa banyak entri pencar/kumpulkan yang dapat ditanganinya). Ini diisolasi di dalam fungsi yang memblokir hingga setiap transfer selesai (lihat di bawah). Ketika semua byte ditransfer atau fungsi transfer inkremental mengembalikan kesalahan, ioctl() keluar dan kembali ke ruang pengguna

Kode semu untuk ioctl()

/*serialize all DMA transfers to/from the device*/
if (mutex_lock_interruptible( &device_ptr->mtx ) )
    return -EINTR;

chunk_data = (unsigned long) user_space_addr;
while( *transferred < total_bytes && !ret ) {
    chunk_bytes = total_bytes - *transferred;
    if (chunk_bytes > HW_DMA_MAX)
        chunk_bytes = HW_DMA_MAX; /* 256kb limit imposed by my device */
    ret = transfer_chunk(device_ptr, chunk_data, chunk_bytes, transferred);
    chunk_data += chunk_bytes;
    chunk_offset += chunk_bytes;
}

mutex_unlock(&device_ptr->mtx);

Kode pseudo untuk fungsi transfer inkremental:

/*Assuming the userspace pointer is passed as an unsigned long, */
/*calculate the first,last, and number of pages being transferred via*/

first_page = (udata & PAGE_MASK) >> PAGE_SHIFT;
last_page = ((udata+nbytes-1) & PAGE_MASK) >> PAGE_SHIFT;
first_page_offset = udata & PAGE_MASK;
npages = last_page - first_page + 1;

/* Ensure that all userspace pages are locked in memory for the */
/* duration of the DMA transfer */

down_read(&current->mm->mmap_sem);
ret = get_user_pages(current,
                     current->mm,
                     udata,
                     npages,
                     is_writing_to_userspace,
                     0,
                     &pages_array,
                     NULL);
up_read(&current->mm->mmap_sem);

/* Map a scatter-gather list to point at the userspace pages */

/*first*/
sg_set_page(&sglist[0], pages_array[0], PAGE_SIZE - fp_offset, fp_offset);

/*middle*/
for(i=1; i < npages-1; i++)
    sg_set_page(&sglist[i], pages_array[i], PAGE_SIZE, 0);

/*last*/
if (npages > 1) {
    sg_set_page(&sglist[npages-1], pages_array[npages-1],
        nbytes - (PAGE_SIZE - fp_offset) - ((npages-2)*PAGE_SIZE), 0);
}

/* Do the hardware specific thing to give it the scatter-gather list
   and tell it to start the DMA transfer */

/* Wait for the DMA transfer to complete */
ret = wait_event_interruptible_timeout( &device_ptr->dma_wait, 
         &device_ptr->flag_dma_done, HZ*2 );

if (ret == 0)
    /* DMA operation timed out */
else if (ret == -ERESTARTSYS )
    /* DMA operation interrupted by signal */
else {
    /* DMA success */
    *transferred += nbytes;
    return 0;
}

Penangan interupsi sangat singkat:

/* Do hardware specific thing to make the device happy */

/* Wake the thread waiting for this DMA operation to complete */
device_ptr->flag_dma_done = 1;
wake_up_interruptible(device_ptr->dma_wait);

Harap dicatat bahwa ini hanyalah pendekatan umum, saya telah mengerjakan driver ini selama beberapa minggu terakhir dan belum benar-benar mengujinya ... Jadi tolong, jangan perlakukan kode semu ini sebagai Injil dan pastikan untuk menggandakan periksa semua logika dan parameter;-).


Pada titik tertentu saya ingin mengizinkan aplikasi ruang pengguna untuk mengalokasikan buffer DMA dan memetakannya ke ruang pengguna dan mendapatkan alamat fisik untuk dapat mengontrol perangkat saya dan melakukan transaksi DMA (penguasaan bus) sepenuhnya dari ruang pengguna, sepenuhnya melewati kernel Linux. Saya telah menggunakan pendekatan yang sedikit berbeda. Pertama saya mulai dengan modul kernel minimal yang menginisialisasi/menyelidiki perangkat PCIe dan membuat perangkat karakter. Pengemudi tersebut kemudian mengizinkan aplikasi ruang pengguna untuk melakukan dua hal:

  1. Petakan bilah I/O perangkat PCIe ke ruang pengguna menggunakan remap_pfn_range() fungsi.
  2. Alokasikan dan kosongkan buffer DMA, petakan ke ruang pengguna dan teruskan alamat bus fisik ke aplikasi ruang pengguna.

Pada dasarnya, ini bermuara pada implementasi kustom mmap() panggilan (meskipun file_operations ). Satu untuk bilah I/O mudah:

struct vm_operations_struct a2gx_bar_vma_ops = {
};

static int a2gx_cdev_mmap_bar2(struct file *filp, struct vm_area_struct *vma)
{
    struct a2gx_dev *dev;
    size_t size;

    size = vma->vm_end - vma->vm_start;
    if (size != 134217728)
        return -EIO;

    dev = filp->private_data;
    vma->vm_ops = &a2gx_bar_vma_ops;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    vma->vm_private_data = dev;

    if (remap_pfn_range(vma, vma->vm_start,
                        vmalloc_to_pfn(dev->bar2),
                        size, vma->vm_page_prot))
    {
        return -EAGAIN;
    }

    return 0;
}

Dan satu lagi yang mengalokasikan buffer DMA menggunakan pci_alloc_consistent() sedikit lebih rumit:

static void a2gx_dma_vma_close(struct vm_area_struct *vma)
{
    struct a2gx_dma_buf *buf;
    struct a2gx_dev *dev;

    buf = vma->vm_private_data;
    dev = buf->priv_data;

    pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr, buf->dma_addr);
    buf->cpu_addr = NULL; /* Mark this buffer data structure as unused/free */
}

struct vm_operations_struct a2gx_dma_vma_ops = {
    .close = a2gx_dma_vma_close
};

static int a2gx_cdev_mmap_dma(struct file *filp, struct vm_area_struct *vma)
{
    struct a2gx_dev *dev;
    struct a2gx_dma_buf *buf;
    size_t size;
    unsigned int i;

    /* Obtain a pointer to our device structure and calculate the size
       of the requested DMA buffer */
    dev = filp->private_data;
    size = vma->vm_end - vma->vm_start;

    if (size < sizeof(unsigned long))
        return -EINVAL; /* Something fishy is happening */

    /* Find a structure where we can store extra information about this
       buffer to be able to release it later. */
    for (i = 0; i < A2GX_DMA_BUF_MAX; ++i) {
        buf = &dev->dma_buf[i];
        if (buf->cpu_addr == NULL)
            break;
    }

    if (buf->cpu_addr != NULL)
        return -ENOBUFS; /* Oops, hit the limit of allowed number of
                            allocated buffers. Change A2GX_DMA_BUF_MAX and
                            recompile? */

    /* Allocate consistent memory that can be used for DMA transactions */
    buf->cpu_addr = pci_alloc_consistent(dev->pci_dev, size, &buf->dma_addr);
    if (buf->cpu_addr == NULL)
        return -ENOMEM; /* Out of juice */

    /* There is no way to pass extra information to the user. And I am too lazy
       to implement this mmap() call using ioctl(). So we simply tell the user
       the bus address of this buffer by copying it to the allocated buffer
       itself. Hacks, hacks everywhere. */
    memcpy(buf->cpu_addr, &buf->dma_addr, sizeof(buf->dma_addr));

    buf->size = size;
    buf->priv_data = dev;
    vma->vm_ops = &a2gx_dma_vma_ops;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    vma->vm_private_data = buf;

    /*
     * Map this DMA buffer into user space.
     */
    if (remap_pfn_range(vma, vma->vm_start,
                        vmalloc_to_pfn(buf->cpu_addr),
                        size, vma->vm_page_prot))
    {
        /* Out of luck, rollback... */
        pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr,
                            buf->dma_addr);
        buf->cpu_addr = NULL;
        return -EAGAIN;
    }

    return 0; /* All good! */
}

Setelah itu ada, aplikasi ruang pengguna dapat melakukan hampir semua hal — mengontrol perangkat dengan membaca/menulis dari/ke register I/O, mengalokasikan dan membebaskan buffer DMA dengan ukuran sembarang, dan membuat perangkat melakukan transaksi DMA. Satu-satunya bagian yang hilang adalah penanganan interupsi. Saya melakukan polling di ruang pengguna, membakar CPU saya, dan interupsi dinonaktifkan.

Semoga membantu. Semoga Sukses!


Anda pada dasarnya memiliki ide yang tepat:di 2.1, Anda dapat meminta userspace mengalokasikan memori lama. Anda menginginkannya selaras dengan halaman, jadi posix_memalign() adalah API praktis untuk digunakan.

Kemudian minta userspace memasukkan alamat virtual userspace dan ukuran buffer ini entah bagaimana; ioctl() adalah cara cepat dan kotor yang bagus untuk melakukan ini. Di kernel, alokasikan array buffer struct page* dengan ukuran yang sesuai -- user_buf_size/PAGE_SIZE entri -- dan gunakan get_user_pages() untuk mendapatkan daftar halaman struct* untuk buffer userspace.

Setelah Anda memilikinya, Anda dapat mengalokasikan larik struct scatterlist itu adalah ukuran yang sama dengan susunan halaman Anda dan mengulang melalui daftar halaman yang melakukan sg_set_page() . Setelah daftar sg diatur, Anda melakukan dma_map_sg() pada larik daftar pencar dan kemudian Anda bisa mendapatkan sg_dma_address dan sg_dma_len untuk setiap entri dalam daftar pencar (perhatikan bahwa Anda harus menggunakan nilai pengembalian dma_map_sg() karena Anda mungkin berakhir dengan lebih sedikit entri yang dipetakan karena berbagai hal mungkin digabungkan oleh kode pemetaan DMA).

Itu memberi Anda semua alamat bus untuk diteruskan ke perangkat Anda, dan kemudian Anda dapat memicu DMA dan menunggu sesuka Anda. Skema berbasis read() yang Anda miliki mungkin baik-baik saja.

Anda dapat merujuk ke driver/infiniband/core/umem.c, khususnya ib_umem_get() , untuk beberapa kode yang menyusun pemetaan ini, meskipun keumuman yang perlu ditangani oleh kode tersebut mungkin membuatnya sedikit membingungkan.

Sebagai alternatif, jika perangkat Anda tidak menangani daftar sebar/kumpulkan dengan baik dan Anda menginginkan memori yang berdekatan, Anda dapat menggunakan get_free_pages() untuk mengalokasikan buffer yang berdekatan secara fisik dan menggunakan dma_map_page() pada itu. Untuk memberikan akses userspace ke memori tersebut, driver Anda hanya perlu menerapkan mmap metode alih-alih ioctl seperti yang dijelaskan di atas.


Linux
  1. Linux – Bagaimana Kernel Linux Mengetahui Nomor Mayor dan Minor Perangkat?

  2. Pengandar perangkat IOCTL Linux

  3. Memanggil fungsi userspace dari dalam modul kernel Linux

  1. Mengapa saya beralih dari Mac ke Linux

  2. Cara menginstal driver perangkat di Linux

  3. Bagaimana saya bisa memesan satu blok memori dari kernel Linux?

  1. Linux:Bagaimana Menemukan Driver Perangkat yang Digunakan Untuk Perangkat?

  2. Menjalankan fungsi ruang pengguna dari ruang kernel

  3. Bagaimana cara memuat modul kernel Linux dari kode C?