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ū)動。

硬件拓撲描述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ù)由入隊(寫入)覆蓋。