Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(六)

Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(六)

本章及其以后的幾章,我們將通過PCI Express總線實現(xiàn)CPU和FPGA數(shù)據(jù)通信的簡單框架,介紹linux PCI內(nèi)核態(tài)設(shè)備驅(qū)動的實戰(zhàn)開發(fā)(KMD)。

這個框架就是開源界非常有名的RIFFA(reuseable integration framework for FPGA accelerators),它是一個FPGA加速器的一種可重用性集成框架,是一個第三方開源PCIe框架。

該框架要求具備一個支持PCIe的工作站和一個帶有PCIe連接器的FPGA板卡。RIFFA支持windows、linux,altera和xilinx,可以通過c/c++、python、matlab、java驅(qū)動來實現(xiàn)數(shù)據(jù)的發(fā)送和接收。驅(qū)動程序可以在linux或windows上運行,每一個系統(tǒng)最多支持5個FPGA device。

在用戶端有獨立的發(fā)送和接收端口,用戶只需要編寫幾行簡單代碼即可實現(xiàn)和FPGA IP內(nèi)核通信。

riffa使用直接存儲器訪問(DMA)傳輸和中斷信號傳輸數(shù)據(jù)。這實現(xiàn)了PCIe鏈路上的高帶寬,運行速率可以達到PCIe鏈路飽和點。

開源地址:https://github.com/KastnerRG/riffa

一、linux下PCI驅(qū)動結(jié)構(gòu)

在《Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(四)》文章中,我們了解到,一般來說,用模塊方式編寫PCI設(shè)備驅(qū)動,通常至少要實現(xiàn)以下幾個部分:初始化設(shè)備模塊、設(shè)備打開模塊、數(shù)據(jù)讀寫模塊、中斷處理模塊、設(shè)備釋放模塊、設(shè)備卸載模塊。
一般如下方式:

/* 指明該驅(qū)動程序適用于哪一些PCI設(shè)備 */
static struct pci_device_id demo = {
    PCI_VENDOR_ID_DEMO,
    PCI_DEVICE_ID_DEMO,
    PCI_ANY_ID,
    0,
    0,
    DEMO
};

/* 對特定PCI設(shè)備進行描述的數(shù)據(jù)結(jié)構(gòu) */
struct demo_card {
    unsigned int magic;
    /* 使用鏈表保存所有同類的PCI設(shè)備 */
    struct demo_card *next;
    ...
};

/* 中斷處理模塊 */
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    /* ... */
};

/* 設(shè)備文件操作接口 */
static struct file_operations demo_fops = {
    owner: THIS_MODULE, /* demo_fops 所屬的設(shè)備模塊 */
    read: demo_read, /* 讀設(shè)備操作 */
    write: demo_write, /* 寫設(shè)備操作 */
    ioctl: demo_ioctl, /* 控制設(shè)備操作 */
    mmap:demo_mmap, /* 內(nèi)存重映射操作 */
    open:demo_open, /* 打開設(shè)備操作 */
    release: demo_release /* 釋放設(shè)備操作 */
    /* ... */
};

/* 設(shè)備模塊信息 */
static struct pci_driver demo_pci_driver = {
    name: demo_MODULE_NAME, /* 設(shè)備模塊名稱 */
    id_table:demo_pci_tbl, /* 能夠驅(qū)動的設(shè)備列表 */
    probe:demo_probe; /* 查找并初始化設(shè)備 */
    remove:demo_remove /* 卸載設(shè)備模塊 */
    /* ... */
};

static int __init demo_init_module (void)
{
    /* ... */
};

static void __exit demo_cleanup_module(void)
{
    pci_unregister_driver(&demo_pci_driver);
}

/* 加載驅(qū)動程序模塊入口 */
module_init(demo_init_nodule);

/* 卸載驅(qū)動程序模塊入口 */
module_exit(demo_cleanup_module);

好的,帶著這個框架我們進入到下面RIFFA框架的driver源代碼分析。

二、初始化設(shè)備模塊

我們直接給出源代碼:

/**
 * Called to initialize the PCI device. 
 */
static int __init fpga_init(void) 
{
    int i;
    int error;

    /* 初始化host最大支持FPGA設(shè)備的個數(shù) */
    for (i = 0; i < NUM_FPGAS; i++)
        atomic_set(&used_fpgas[i], 0);

    /* 注冊硬件驅(qū)動程序 */
    error = pci_register_driver(&fpga_driver);
    if (error != 0) {
        printk(KERN_ERR "riffa: pci_module_register returned %d\n", error);
        return (error);
    }
    /* 向內(nèi)核注冊一個字符設(shè)備(可選) */
    error = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fpga_fops);
    if (error < 0) {
        printk(KERN_ERR "riffa: register_chrdev returned %d\n", error);
        return (error);
    }

    /* 向內(nèi)核注冊class */
    #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
        mymodule_class = class_create(THIS_MODULE, DEVICE_NAME);
    #else
        mymodule_class = class_create(DEVICE_NAME);
    #endif

    if (IS_ERR(mymodule_class)) {
        error = PTR_ERR(mymodule_class);
        printk(KERN_ERR "riffa: class_create() returned %d\n", error);
        return (error);
    }

    /* 創(chuàng)建設(shè)備文件節(jié)點 */
    devt = MKDEV(MAJOR_NUM, 0);
    device_create(mymodule_class, NULL, devt, "%s", DEVICE_NAME);

    return 0;
}

OK,我們有看到了幾個關(guān)鍵詞,驅(qū)動程序、字符設(shè)備、class、文件節(jié)點。在《Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(三)》中,我們知道總線、設(shè)備、驅(qū)動模型:

系統(tǒng)啟動后,會調(diào)用buses_init()函數(shù)創(chuàng)建/sys/bus文件目錄,這部分系統(tǒng)在開機時已經(jīng)幫我們準備好了。

接下去就是通過總線注冊函數(shù)bus_register()進行總線注冊(可選,一般不會注冊新的總線),注冊完成后,

在/sys/bus目錄下生成device文件夾和driver文件夾,最后分別通過device_register()以及driver_register()函數(shù)注冊對應(yīng)的設(shè)備

和驅(qū)動。
1703660835348.png

硬件拓撲描述linux設(shè)備模型中四個重要概念:

bus(總線):linux認為,總線是CPU和一個或多個設(shè)備之間信息交互的通道。而為了方便設(shè)備模型的抽象,所有設(shè)備都要連接到總線上。

class(分類):linux設(shè)備模型中,class的概念非常類似面向?qū)ο蟪绦蛟O(shè)計的class(類),它主要是集合具有類似功能或?qū)傩缘脑O(shè)備,這樣就可以抽象出一套可以在多個設(shè)備之間公用的數(shù)據(jù)結(jié)構(gòu)和接口函數(shù)。

device(設(shè)備):抽象系統(tǒng)中所有的硬件設(shè)備,描述它的名字、屬性、從屬bus,從屬class等信息。

driver(驅(qū)動):包括設(shè)備初始化、管理、read、write、銷毀等接口實現(xiàn)。

三、probe探測硬件設(shè)備

/**
 * probe設(shè)備
 */
 static int __devinit fpga_probe(struct pci_dev *dev, const struct pci_device_id *id)
 {
    ...

    // Setup the PCIe device.
    error = pci_enable_device(dev);
    if (error < 0) {
        printk(KERN_ERR "riffa: pci_enable_device returned %d\n", error);
        return (-ENODEV);
    }

    // Enable bus master
    pci_set_master(dev);

    // Set the mask size
    error = pci_set_dma_mask(dev, DMA_BIT_MASK(64));
    if (!error)
        error = pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(64));
    if (error) {
        printk(KERN_ERR "riffa: cannot set 64 bit DMA mode\n");
        pci_disable_device(dev);
        return error;
    }

    // Allocate device structure.
    sc = kzalloc(sizeof(*sc), GFP_KERNEL);
    ...

    // Setup the BAR memory regions
    error = pci_request_regions(dev, sc->name);
    ...

    // PCI BAR 0
    ...
    sc->bar0 = ioremap(sc->bar0_addr, sc->bar0_len);
    ...

    // setup msi interrupts
    error = pci_enable_msi(dev);
    ...

    // Request an interrupt
    error = request_irq(dev->irq, intrpt_handler, IRQF_SHARED, sc->name, sc);

    // Set extended tag bit
    error = pcie_capability_read_dword(dev,PCI_EXP_DEVCTL,&devctl_result);
    ...
    error = pcie_capability_write_dword(dev,PCI_EXP_DEVCTL,(devctl_result|PCI_EXP_DEVCTL_EXT_TAG));

    // Set IDO bits
    ...
    error = pcie_capability_read_dword(dev,PCI_EXP_DEVCTL2,&devctl2_result);
    error = pcie_capability_write_dword(dev,PCI_EXP_DEVCTL2,(devctl2_result | PCI_EXP_DEVCTL2_IDO_REQ_EN | PCI_EXP_DEVCTL2_IDO_CMP_EN));

    // Set RCB to 128
    error = pcie_capability_read_dword(dev,PCI_EXP_LNKCTL,&lnkctl_result);
    ...
    error = pcie_capability_write_dword(dev,PCI_EXP_LNKCTL,(lnkctl_result|PCI_EXP_LNKCTL_RCB));

    // Read device configuration
    ...

    // Create chnl_dir structs.
    sc->recv = (struct chnl_dir **) kzalloc(sc->num_chnls*sizeof(struct chnl_dir*), GFP_KERNEL);  
    sc->send = (struct chnl_dir **) kzalloc(sc->num_chnls*sizeof(struct chnl_dir*), GFP_KERNEL);  
    ...
    j = allocate_chnls(dev, sc);

    // Create spill buffer (for overflow on receive).
    sc->spill_buf_addr = pci_alloc_consistent(dev, SPILL_BUF_SIZE, &hw_addr);
    sc->spill_buf_hw_addr = hw_addr;

    // Save pointer to structure 
    ...
 }

這個fpga_probe函數(shù)非常重要和關(guān)鍵了:

1.  pci_enable_device():把PCI配置空間的command域的bit0和bit1設(shè)置成1,從而達到開啟設(shè)備的目的,即把config控制寄存器映射成IO/MEM空間。

2.  pci_set_master():設(shè)置主總線為DMA模式。

3.  pci_set_dma_mask():輔助函數(shù)用于檢查總線是否可以接收給定大小的總線地址(mask),如果可以,則通知總線層給定外圍設(shè)備將使用該大小的總線地址。

4.  kzalloc():內(nèi)核態(tài)的內(nèi)存分配,struct fpga_state 保存device_id、vendor_id、bar空間、通道數(shù)、接收/發(fā)送通道的地址信息等。

5.  pci_request_regions():該函數(shù)用于請求PCIe設(shè)備的IO資源。在probe函數(shù)中,驅(qū)動程序會調(diào)用pci_request_regions函數(shù)來請求設(shè)備的IO資源。

6.  ioremap():此函數(shù)用于映射PCIe設(shè)備的IO空間到內(nèi)核地址空間。在probe函數(shù)中,驅(qū)動程序會調(diào)用pci_iomap函數(shù)來映射設(shè)備的IO空間。

7.  pci_enable_msi():它允許PCI設(shè)備使用MSI中斷機制,當函數(shù)被調(diào)用時,它將被指定的PCI設(shè)備啟用MSI中斷,并返回中斷號。

8.  request_irq():注冊中斷服務(wù)函數(shù),當中斷發(fā)生時,系統(tǒng)調(diào)用這個函數(shù)。

9.  pcie_capability_write_dword(..., ..., PCI_EXP_DEVCTL_EXT_TAG):PCI_EXP_DEVCTL_EXT_TAG,標識設(shè)備的DevCtl設(shè)置了ExtTag+,設(shè)置了這個標識位,讀請求tlp中requester ID字段會擴展一個8位的tag,表示能暫存數(shù)據(jù)包的數(shù)量,但是需要FPGA PCIe設(shè)備支持,否則會出現(xiàn)數(shù)據(jù)溢出而丟包。

10. pcie_capability_write_dword(..., ..., PCI_EXP_DEVCTL2_IDO_REQ_EN):IDO標識位。

11. pcie_capability_write_dword(..., ..., PCI_EXP_LNKCTL_RCB):讀完成邊界,是 Completer 響應(yīng)讀請求的一種地址邊界對齊策略,應(yīng)用于 CplD,RC 的 RCB 可以為 64B 或 128B,默認 64 B;EP、Bridge、Switch 等其他設(shè)備的 RCB 只能為 128 B。

12. allocate_chnls():這個主要是通過pci_alloc_consistent申請dma的讀/寫multi-page內(nèi)存,并形成sglist環(huán)形鏈表,并保存在fpga_state中,完成用戶態(tài)多通道dma的讀寫請求。

四、寫操作

基本的讀寫操作通過ioctl來調(diào)用對應(yīng)的driver驅(qū)動的實現(xiàn)。我們補充一下,ioctl是設(shè)備驅(qū)動程序中設(shè)備控制接口函數(shù),一個字符設(shè)備驅(qū)動通常會實現(xiàn)設(shè)備打開、關(guān)閉、讀、寫等功能,在一些需要細分的情境下,如果需要擴展新的功能,通常以增設(shè) ioctl() 命令的方式實現(xiàn)。

直接給出代碼:

static long fpga_ioctl(struct file *filp, unsigned int ioctlnum, 
        unsigned long ioctlparam)
{
    int rc;
    fpga_chnl_io io;
    fpga_info_list list;

    switch (ioctlnum) {
    case IOCTL_SEND:
        if ((rc = copy_from_user(&io, (void *)ioctlparam, sizeof(fpga_chnl_io)))) {
            printk(KERN_ERR "riffa: cannot read ioctl user parameter.\n");
            return rc;
        }
        if (io.id < 0 || io.id >= NUM_FPGAS || !atomic_read(&used_fpgas[io.id]))
            return 0;
        return chnl_send_wrapcheck(fpgas[io.id], io.chnl, io.data, io.len, io.offset,
                io.last, io.timeout);
    case IOCTL_RECV:
        if ((rc = copy_from_user(&io, (void *)ioctlparam, sizeof(fpga_chnl_io)))) {
            printk(KERN_ERR "riffa: cannot read ioctl user parameter.\n");
            return rc;
        }
        if (io.id < 0 || io.id >= NUM_FPGAS || !atomic_read(&used_fpgas[io.id]))
            return 0;
        return chnl_recv_wrapcheck(fpgas[io.id], io.chnl, io.data, io.len, io.timeout);
    case IOCTL_LIST:
        list_fpgas(&list);
        if ((rc = copy_to_user((void *)ioctlparam, &list, sizeof(fpga_info_list))))
            printk(KERN_ERR "riffa: cannot write ioctl user parameter.\n");
        return rc;
    case IOCTL_RESET:
        reset((int)ioctlparam);
        break;
    default:
        return -ENOTTY;
        break;
    }
    return 0;
}

在處理ioctl_send的時候,我們發(fā)現(xiàn)實現(xiàn)用戶數(shù)據(jù)拷貝到內(nèi)核態(tài)之后,調(diào)用了chnl_send_wrapcheck,將api層打包過來的參數(shù)一一傳遞過去。

直接給出chnl_send_wrapcheck():

static inline unsigned int chnl_send_wrapcheck(struct fpga_state * sc, int chnl,
                const char  __user * bufp, unsigned int len, unsigned int offset,
                unsigned int last, unsigned long long timeout)
{
    // Validate the parameters.
    ...
    // Ensure no simultaneous operations from several threads
    ...
    ret = chnl_send(sc, chnl, bufp, len, offset, last, timeout);

    // Clear the busy flag
    ...

    return ret;
}

這段代碼主要做了一些避免錯誤的判斷,值得一提的就是通過自旋鎖避免了多線程錯誤的判斷,其實我們可以知道riffa架構(gòu)支持多線程,之后調(diào)用了chnl_send.

static inline unsigned int chnl_send(struct fpga_state * sc, int chnl,
                const char  __user * bufp, unsigned int len, unsigned int offset,
                unsigned int last, unsigned long long timeout)
{
    ...
    // Convert timeout to jiffies.
    ...

    // Clear the message queue.
    while (!pop_circ_queue(sc->send[chnl]->msgs, &msg_type, &msg));

    // Initialize the sg_maps
    sc->send[chnl]->sg_map_0 = NULL;
    sc->send[chnl]->sg_map_1 = NULL;

    // Let FPGA know about transfer.
    DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send (len:%d off:%d last:%d)\n", sc->id, chnl, len, offset, last);
    write_reg(sc, CHNL_REG(chnl, RX_OFFLAST_REG_OFF), ((offset<<1) | last));
    write_reg(sc, CHNL_REG(chnl, RX_LEN_REG_OFF), len);
    if (len == 0)
        return 0;

    // Use the send common buffer to share the scatter gather data
    sg_map = fill_sg_buf(sc, chnl, sc->send[chnl]->buf_addr, udata, length, 0, DMA_TO_DEVICE);
    if (sg_map == NULL || sg_map->num_sg == 0)
        return (unsigned int)(sent>>2);

    // Update based on the sg_mapping
    udata += sg_map->length;
    length -= sg_map->length;
    sc->send[chnl]->sg_map_1 = sg_map;

    // Let FPGA know about the scatter gather buffer.
    write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_LO_REG_OFF), (sc->send[chnl]->buf_hw_addr & 0xFFFFFFFF));
    write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_HI_REG_OFF), ((sc->send[chnl]->buf_hw_addr>>32) & 0xFFFFFFFF));
    write_reg(sc, CHNL_REG(chnl, RX_SG_LEN_REG_OFF), 4 * sg_map->num_sg);
    DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send sg buf populated, %d sent\n", sc->id, chnl, sg_map->num_sg);

    // Continue until we get a message or timeout.
    while (1) {
        while ((nomsg = pop_circ_queue(sc->send[chnl]->msgs, &msg_type, &msg))) {
            prepare_to_wait(&sc->send[chnl]->waitq, &wait, TASK_INTERRUPTIBLE);
            // Another check before we schedule.
            if ((nomsg = pop_circ_queue(sc->send[chnl]->msgs, &msg_type, &msg)))
                tymeout = schedule_timeout(tymeout);
            finish_wait(&sc->send[chnl]->waitq, &wait);
            if (signal_pending(current)) {
                free_sg_buf(sc, sc->send[chnl]->sg_map_0);
                free_sg_buf(sc, sc->send[chnl]->sg_map_1);
                return -ERESTARTSYS;
            }
            if (!nomsg)
                break;
            if (tymeout == 0) {
                printk(KERN_ERR "riffa: fpga:%d chnl:%d, send timed out\n", sc->id, chnl);
                free_sg_buf(sc, sc->send[chnl]->sg_map_0);
                free_sg_buf(sc, sc->send[chnl]->sg_map_1);
                return (unsigned int)(sent>>2);
            }
        }
        tymeout = tymeouto;

        // Process the message.
        switch (msg_type) {
        case EVENT_SG_BUF_READ:
            // Release the previous scatter gather data?
            if (sc->send[chnl]->sg_map_0 != NULL)
                sent += sc->send[chnl]->sg_map_0->length;
            free_sg_buf(sc, sc->send[chnl]->sg_map_0);
            sc->send[chnl]->sg_map_0 = NULL;
            // Populate the common buffer with more scatter gather data?
            if (length > 0) {
                sg_map = fill_sg_buf(sc, chnl, sc->send[chnl]->buf_addr, udata, length, 0, DMA_TO_DEVICE);
                if (sg_map == NULL || sg_map->num_sg == 0) {
                    free_sg_buf(sc, sc->send[chnl]->sg_map_0);
                    free_sg_buf(sc, sc->send[chnl]->sg_map_1);
                    return (unsigned int)(sent>>2);
                }
                // Update based on the sg_mapping
                udata += sg_map->length;
                length -= sg_map->length;
                sc->send[chnl]->sg_map_0 = sc->send[chnl]->sg_map_1;
                sc->send[chnl]->sg_map_1 = sg_map;
                write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_LO_REG_OFF), (sc->send[chnl]->buf_hw_addr & 0xFFFFFFFF));
                write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_HI_REG_OFF), ((sc->send[chnl]->buf_hw_addr>>32) & 0xFFFFFFFF));
                write_reg(sc, CHNL_REG(chnl, RX_SG_LEN_REG_OFF), 4 * sg_map->num_sg);
                DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send sg buf populated, %d sent\n", sc->id, chnl, sg_map->num_sg);
            }
            break;

        case EVENT_TXN_DONE:
            // Update with the true value of words transferred.
            sent = (((unsigned long long)msg)<<2);
            // Return as this is the end of the transaction.
            free_sg_buf(sc, sc->send[chnl]->sg_map_0);
            free_sg_buf(sc, sc->send[chnl]->sg_map_1);
            DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, sent %d words\n", sc->id, chnl, (unsigned int)(sent>>2));
            return (unsigned int)(sent>>2);
            break;

        default: 
            printk(KERN_ERR "riffa: fpga:%d chnl:%d, received unknown msg: %08x\n", sc->id, chnl, msg);
            break;
        }
    }

    return 0;
}

將數(shù)據(jù)寫入指定的FPGA通道。除非配置了非零超時,否則將阻塞,直到所有數(shù)據(jù)都發(fā)送到 FPGA。如果超時不為零,則該函數(shù)將阻塞,直到發(fā)送所有數(shù)據(jù)或超時毫秒過去。來自 bufp 指針的用戶數(shù)據(jù)將被發(fā)送,最多 len 字(每個字 == 32 位)。通道將被告知預(yù)期數(shù)據(jù)量和偏移量。如果 last == 1,則 FPGA 通道將在發(fā)送后將此事務(wù)識別為完成。如果 last == 0,則 FPGA 通道將需要額外的事務(wù)。

成功后,返回發(fā)送的字數(shù)。出錯時,返回負值。

核心思想就是,初始化sg_maps,通過bar空間告知FPGA通道號、長度、大小等信息、使用通用buffer發(fā)送數(shù)據(jù)、更新sg_mapping,最后進入到while(1)的循環(huán)函數(shù)中。

while(1)大循環(huán),只有當處理完Tx數(shù)據(jù)完成中斷或出錯時函數(shù)才會返回。在每一輪執(zhí)行中,首先執(zhí)行內(nèi)嵌的小while,在小while中首先讀取對應(yīng)通道上的send消息隊列,若返回值為0說明成功出隊,小while運行一遍后就會執(zhí)行下面的代碼;若返回值為1說明隊列可能是空的,也就是還沒有中斷到來,此時調(diào)用prepare_to_wait函數(shù)將本進程添加到等待隊列里,然后執(zhí)行schedule_timeout休眠該進程(有阻塞時間限制),此時在用戶看來表現(xiàn)為ioctl函數(shù)阻塞等待,但中斷還能在后臺運行(中斷也是一個進程)。

若此時驅(qū)動接收到一個該通道的Tx中斷,那么在中斷回調(diào)函數(shù)里將中斷信息推入消息隊列后就會喚醒chnl_send所在的進程。進程喚醒后調(diào)用finish_wait函數(shù)將本進程pop出等待隊列并用signal_pending查看是否因信號而被喚醒,如果是 需要返回給用戶并讓其再次重試。如果不是被信號喚醒,則再去讀一下消息隊列,此時會將消息類型存入msg_type,消息存入msg中,然后退出小while。

接下來進入一個switch語句,這個switch是根據(jù)msg_type消息類型選擇處理動作的,即中斷處理的下半部。

若執(zhí)行Tx SG讀完成中斷,則消息類型發(fā)送EVENT_SG_BUF_READ,數(shù)據(jù)填0,其實是沒用的數(shù)據(jù)。在這里如果剩余長度大于0或者剩余溢出值大于0時就會重新執(zhí)行上一段講述的過程,即從上一次分配的結(jié)尾處再分配SG緩沖區(qū),并發(fā)送SG鏈表給FPGA等等,不過一般不會發(fā)送這種情況,除非分配頁時的get_user_pages函數(shù)鎖定物理頁出現(xiàn)了問題,少分了頁才會出現(xiàn)這樣的現(xiàn)象。

然后FPGA就會按SG鏈表一個一個SG緩存塊的進行流式DMA傳輸,傳輸完畢后FPGA發(fā)送一個Tx數(shù)據(jù)讀完成中斷,即EVENT_TXN_DONE消息類型。這里比較好處理,調(diào)用dma_unmap_sg取消內(nèi)存空間的SGDMA映射,然后釋放掉頁。

五、讀操作

讀操作和寫操作類似,不再詳細描述。

函數(shù)chnl_recv用于將FPGA發(fā)送的數(shù)據(jù)讀到緩沖區(qū)內(nèi)。

首先調(diào)用宏DEFINE_WAIT初始化等待隊列項;然后把傳入的參數(shù)timeout換算成毫秒,這個時間是最長阻塞時間。

剩下的就是中斷處理過程,等待讀完成。

六、銷毀/卸載設(shè)備

釋放設(shè)備模塊主要是負責釋放對設(shè)備的控制權(quán),釋放占用的內(nèi)存和中斷等,所做的事情正好和打開設(shè)備模塊相反。

static void __exit fpga_exit(void)
{
    device_destroy(mymodule_class, devt); 
    class_destroy(mymodule_class);
    pci_unregister_driver(&fpga_driver);
    unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}

本文從詳細介紹了RIFFA框架的驅(qū)動模塊,涉及的內(nèi)容非常多,內(nèi)核頁面、中斷處理等。

一個驅(qū)動的框架主要包括:初始化設(shè)備模塊、設(shè)備打開模塊、數(shù)據(jù)讀寫模塊、中斷處理模塊、設(shè)備釋放模塊、設(shè)備卸載模塊。

七、未完待續(xù)

Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(七),將詳細分析一下RIFFA的環(huán)形通信隊列,最大的好處就是不需要對后續(xù)的隊列內(nèi)容進行搬移,可以后續(xù)由入隊(寫入)覆蓋。

八、參考資料

https://blog.csdn.net/mcupro/article/details/121526536

https://zhuanlan.zhihu.com/p/534098236

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容