概述
本文講述一個開源的PCIe設(shè)備驅(qū)動,通過這個例子可以基本上理解所有的PCIe設(shè)備驅(qū)動。后續(xù)也會做關(guān)于Linux各類驅(qū)動的文章。
通過前面的學(xué)習(xí),我們知道PCIe設(shè)備訪問之前需要先做枚舉。一般來說,PCI設(shè)備的枚舉操作不需要我們來做,BIOS或者系統(tǒng)初始化時(shí)已經(jīng)做好了,當(dāng)系統(tǒng)枚舉完所有設(shè)備之后,PCI設(shè)備就會添加進(jìn)系統(tǒng),在Linux下使用 “l(fā)spci” 就能看到系統(tǒng)掃描到的所有PCI設(shè)備,我們只需要關(guān)注PCI設(shè)備driver的實(shí)現(xiàn)就好了。
在Linux源碼中隨便找了一個開源代碼,tsi721(一款PCIe轉(zhuǎn)RapidIO芯片)的一些源碼,基本上一個普通的PCIE設(shè)備驅(qū)動模型都是這樣的,其中在加上一些設(shè)備獨(dú)有的處理流程。
那么PCIe驅(qū)動的入口在哪呢?
當(dāng)系統(tǒng)枚舉到的PCI設(shè)備的vendor id和device id與driver中的id匹配上之后,就會調(diào)用driver中的probe函數(shù)。
static const struct pci_device_id tsi721_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_IDT, PCI_DEVICE_ID_TSI721) },
{ 0, } /* terminate list */
};
MODULE_DEVICE_TABLE(pci, tsi721_pci_tbl);
static struct pci_driver tsi721_driver = {
.name = "tsi721",
.id_table = tsi721_pci_tbl,
.probe = tsi721_probe,
.remove = tsi721_remove,
.shutdown = tsi721_shutdown,
};
module_pci_driver(tsi721_driver);
PCIe設(shè)備驅(qū)動不是通過name匹配,而是通過id去匹配的,當(dāng)驅(qū)動與設(shè)備匹配上之后,就會執(zhí)行tsi721_probe函數(shù),一般的PCIe驅(qū)動基本上會用到以下步驟:
驅(qū)動加載流程
-
pci_enable_device(pdev); 使能pci設(shè)備
-
pci_resource_len(pdev, BAR_0);獲取pci設(shè)備的資源大小,這是枚舉時(shí)得到的值
-
pci_request_regions(pdev, DRV_NAME); 申請pci設(shè)備資源
-
pci_ioremap_bar(pdev, BAR_0); 映射虛擬內(nèi)存
-
pci_set_master(pdev); 設(shè)置pci設(shè)備為master,master模式才能主動發(fā)起數(shù)據(jù)傳輸
-
pci_enable_msi(pdev); 使能MSI中斷
-
request_irq(priv->pdev->irq, tsi721_irqhandler,(priv->flags & TSI721_USING_MSI) ? 0 : IRQF_SHARED,DRV_NAME, (void *)priv); 注冊中斷處理函數(shù)
初始化流程中關(guān)于PCIe的就基本上完成了,其余還有關(guān)于具體業(yè)務(wù)的初始化,下面可以結(jié)合源碼看看。
static int tsi721_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct tsi721_device *priv;
int err;
priv = kzalloc(sizeof(struct tsi721_device), GFP_KERNEL);
if (!priv) {
err = -ENOMEM;
goto err_exit;
}
err = pci_enable_device(pdev); // 使能pci設(shè)備
if (err) {
tsi_err(&pdev->dev, "Failed to enable PCI device");
goto err_clean;
}
priv->pdev = pdev;
#ifdef DEBUG
{
int i;
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
tsi_debug(INIT, &pdev->dev, "res%d %pR",
i, &pdev->resource[i]);
}
}
#endif
/*
* Verify BAR configuration
*/
/* BAR_0 (registers) must be 512KB+ in 32-bit address space */
if (!(pci_resource_flags(pdev, BAR_0) & IORESOURCE_MEM) ||
pci_resource_flags(pdev, BAR_0) & IORESOURCE_MEM_64 ||
pci_resource_len(pdev, BAR_0) < TSI721_REG_SPACE_SIZE) {// 獲取pci設(shè)備bar0的資源大小,這是枚舉時(shí)得到的值
tsi_err(&pdev->dev, "Missing or misconfigured CSR BAR0");
err = -ENODEV;
goto err_disable_pdev;
}
/* BAR_1 (outbound doorbells) must be 16MB+ in 32-bit address space */
if (!(pci_resource_flags(pdev, BAR_1) & IORESOURCE_MEM) ||
pci_resource_flags(pdev, BAR_1) & IORESOURCE_MEM_64 ||
pci_resource_len(pdev, BAR_1) < TSI721_DB_WIN_SIZE) {
tsi_err(&pdev->dev, "Missing or misconfigured Doorbell BAR1");
err = -ENODEV;
goto err_disable_pdev;
}
/*
* BAR_2 and BAR_4 (outbound translation) must be in 64-bit PCIe address
* space.
* NOTE: BAR_2 and BAR_4 are not used by this version of driver.
* It may be a good idea to keep them disabled using HW configuration
* to save PCI memory space.
*/
priv->p2r_bar[0].size = priv->p2r_bar[1].size = 0;
if (pci_resource_flags(pdev, BAR_2) & IORESOURCE_MEM_64) {
if (pci_resource_flags(pdev, BAR_2) & IORESOURCE_PREFETCH)
tsi_debug(INIT, &pdev->dev,
"Prefetchable OBW BAR2 will not be used");
else {
priv->p2r_bar[0].base = pci_resource_start(pdev, BAR_2);// 獲取bar2的起始地址
priv->p2r_bar[0].size = pci_resource_len(pdev, BAR_2);// 獲取bar2的起始長度
}
}
if (pci_resource_flags(pdev, BAR_4) & IORESOURCE_MEM_64) {
if (pci_resource_flags(pdev, BAR_4) & IORESOURCE_PREFETCH)
tsi_debug(INIT, &pdev->dev,
"Prefetchable OBW BAR4 will not be used");
else {
priv->p2r_bar[1].base = pci_resource_start(pdev, BAR_4);// 獲取bar4的起始地址
priv->p2r_bar[1].size = pci_resource_len(pdev, BAR_4);// 獲取bar4的起始長度
}
}
err = pci_request_regions(pdev, DRV_NAME); // 申請pci設(shè)備資源
if (err) {
tsi_err(&pdev->dev, "Unable to obtain PCI resources");
goto err_disable_pdev;
}
pci_set_master(pdev); // 設(shè)置pci設(shè)備為master模式
priv->regs = pci_ioremap_bar(pdev, BAR_0); // 映射虛擬內(nèi)存
if (!priv->regs) {
tsi_err(&pdev->dev, "Unable to map device registers space");
err = -ENOMEM;
goto err_free_res;
}
priv->odb_base = pci_ioremap_bar(pdev, BAR_1);// 映射虛擬內(nèi)存
if (!priv->odb_base) {
tsi_err(&pdev->dev, "Unable to map outbound doorbells space");
err = -ENOMEM;
goto err_unmap_bars;
}
/* Configure DMA attributes. */
if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {
err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); // 設(shè)置dma掩碼
if (err) {
tsi_err(&pdev->dev, "Unable to set DMA mask");
goto err_unmap_bars;
}
if (pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)))
tsi_info(&pdev->dev, "Unable to set consistent DMA mask");
} else {
err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
if (err)
tsi_info(&pdev->dev, "Unable to set consistent DMA mask");
}
BUG_ON(!pci_is_pcie(pdev));
/* Clear "no snoop" and "relaxed ordering" bits. */
pcie_capability_clear_and_set_word(pdev, PCI_EXP_DEVCTL,
PCI_EXP_DEVCTL_RELAX_EN | PCI_EXP_DEVCTL_NOSNOOP_EN, 0);
/* Override PCIe Maximum Read Request Size setting if requested */
if (pcie_mrrs >= 0) {
if (pcie_mrrs <= 5)
pcie_capability_clear_and_set_word(pdev, PCI_EXP_DEVCTL,
PCI_EXP_DEVCTL_READRQ, pcie_mrrs << 12);
else
tsi_info(&pdev->dev,
"Invalid MRRS override value %d", pcie_mrrs);
}
/* Set PCIe completion timeout to 1-10ms */
pcie_capability_clear_and_set_word(pdev, PCI_EXP_DEVCTL2,
PCI_EXP_DEVCTL2_COMP_TIMEOUT, 0x2);
/*
* FIXUP: correct offsets of MSI-X tables in the MSI-X Capability Block
*/
pci_write_config_dword(pdev, TSI721_PCIECFG_EPCTL, 0x01);
pci_write_config_dword(pdev, TSI721_PCIECFG_MSIXTBL,
TSI721_MSIXTBL_OFFSET);
pci_write_config_dword(pdev, TSI721_PCIECFG_MSIXPBA,
TSI721_MSIXPBA_OFFSET);
pci_write_config_dword(pdev, TSI721_PCIECFG_EPCTL, 0);
/* End of FIXUP */
tsi721_disable_ints(priv);
tsi721_init_pc2sr_mapping(priv);
tsi721_init_sr2pc_mapping(priv);
if (tsi721_bdma_maint_init(priv)) {
tsi_err(&pdev->dev, "BDMA initialization failed");
err = -ENOMEM;
goto err_unmap_bars;
}
err = tsi721_doorbell_init(priv);
if (err)
goto err_free_bdma;
tsi721_port_write_init(priv);
err = tsi721_messages_init(priv);
if (err)
goto err_free_consistent;
err = tsi721_setup_mport(priv);
if (err)
goto err_free_consistent;
pci_set_drvdata(pdev, priv);
tsi721_interrupts_init(priv);
return 0;
err_free_consistent:
tsi721_port_write_free(priv);
tsi721_doorbell_free(priv);
err_free_bdma:
tsi721_bdma_maint_free(priv);
err_unmap_bars:
if (priv->regs)
iounmap(priv->regs);
if (priv->odb_base)
iounmap(priv->odb_base);
err_free_res:
pci_release_regions(pdev);
pci_clear_master(pdev);
err_disable_pdev:
pci_disable_device(pdev);
err_clean:
kfree(priv);
err_exit:
return err;
}
tsi721_probe調(diào)用tsi721_setup_mport(priv),如下所示,和pcie相關(guān)的就是msi 中斷的使能,中斷函數(shù)的注冊,其他就是芯片具體的業(yè)務(wù)相關(guān)的初始化。
static int tsi721_setup_mport(struct tsi721_device *priv)
{
struct pci_dev *pdev = priv->pdev;
int err = 0;
struct rio_mport *mport = &priv->mport;
err = rio_mport_initialize(mport);
if (err)
return err;
mport->ops = &tsi721_rio_ops;
mport->index = 0;
mport->sys_size = 0; /* small system */
mport->priv = (void *)priv;
mport->phys_efptr = 0x100;
mport->phys_rmap = 1;
mport->dev.parent = &pdev->dev;
mport->dev.release = tsi721_mport_release;
INIT_LIST_HEAD(&mport->dbells);
rio_init_dbell_res(&mport->riores[RIO_DOORBELL_RESOURCE], 0, 0xffff);
rio_init_mbox_res(&mport->riores[RIO_INB_MBOX_RESOURCE], 0, 3);
rio_init_mbox_res(&mport->riores[RIO_OUTB_MBOX_RESOURCE], 0, 3);
snprintf(mport->name, RIO_MAX_MPORT_NAME, "%s(%s)",
dev_driver_string(&pdev->dev), dev_name(&pdev->dev));
/* Hook up interrupt handler */
#ifdef CONFIG_PCI_MSI
if (!tsi721_enable_msix(priv)) // 使能MSI-X中斷
priv->flags |= TSI721_USING_MSIX;
else if (!pci_enable_msi(pdev)) // 使能MSI中斷
priv->flags |= TSI721_USING_MSI;
else
tsi_debug(MPORT, &pdev->dev,
"MSI/MSI-X is not available. Using legacy INTx.");
#endif /* CONFIG_PCI_MSI */
err = tsi721_request_irq(priv); // 中斷注冊,展開即為 request_irq
if (err) {
tsi_err(&pdev->dev, "Unable to get PCI IRQ %02X (err=0x%x)",
pdev->irq, err);
return err;
}
#ifdef CONFIG_RAPIDIO_DMA_ENGINE
err = tsi721_register_dma(priv);
if (err)
goto err_exit;
#endif
/* Enable SRIO link */
iowrite32(ioread32(priv->regs + TSI721_DEVCTL) |
TSI721_DEVCTL_SRBOOT_CMPL,
priv->regs + TSI721_DEVCTL);
if (mport->host_deviceid >= 0)
iowrite32(RIO_PORT_GEN_HOST | RIO_PORT_GEN_MASTER |
RIO_PORT_GEN_DISCOVERED,
priv->regs + (0x100 + RIO_PORT_GEN_CTL_CSR));
else
iowrite32(0, priv->regs + (0x100 + RIO_PORT_GEN_CTL_CSR));
err = rio_register_mport(mport);
if (err) {
tsi721_unregister_dma(priv);
goto err_exit;
}
return 0;
err_exit:
tsi721_free_irq(priv);
return err;
}
驅(qū)動加載初始化的代碼就結(jié)束了。
驅(qū)動卸載流程
當(dāng)驅(qū)動卸載時(shí)會調(diào)用到tsi721_remove函數(shù),其流程基本上就是probe的逆向操作。
- free_irq(priv->pdev->irq, (void *)priv); 釋放中斷
- iounmap(priv->regs); 地址解映射
- pci_disable_msi(priv->pdev);失能msi中斷
- pci_release_regions(pdev);釋放申請的資源
- pci_clear_master(pdev); 清除設(shè)備master屬性
- pci_disable_device(pdev);失能pci設(shè)備
下面一起看看源碼
static void tsi721_remove(struct pci_dev *pdev)
{
struct tsi721_device *priv = pci_get_drvdata(pdev);
tsi_debug(EXIT, &pdev->dev, "enter");
tsi721_disable_ints(priv);
tsi721_free_irq(priv); // 釋放中斷
flush_scheduled_work();
rio_unregister_mport(&priv->mport);
tsi721_unregister_dma(priv);
tsi721_bdma_maint_free(priv);
tsi721_doorbell_free(priv);
tsi721_port_write_free(priv);
tsi721_close_sr2pc_mapping(priv);
if (priv->regs)
iounmap(priv->regs); // 地址解映射
if (priv->odb_base)
iounmap(priv->odb_base);
#ifdef CONFIG_PCI_MSI
if (priv->flags & TSI721_USING_MSIX)
pci_disable_msix(priv->pdev); // 失能msi-x中斷
else if (priv->flags & TSI721_USING_MSI)
pci_disable_msi(priv->pdev); // 失能msi中斷
#endif
pci_release_regions(pdev); // 釋放申請的資源
pci_clear_master(pdev); // 清除設(shè)備master屬性
pci_disable_device(pdev); // 失能pci設(shè)備
pci_set_drvdata(pdev, NULL);
kfree(priv);
tsi_debug(EXIT, &pdev->dev, "exit");
}
至此關(guān)于PCIe設(shè)備驅(qū)動的卸載就完成了。文章來源:http://www.zghlxwxcb.cn/news/detail-754549.html
以上就是一個PCIe設(shè)備的基本驅(qū)動框架。文章來源地址http://www.zghlxwxcb.cn/news/detail-754549.html
到了這里,關(guān)于PCIE學(xué)習(xí)系列 五(Linux之PCIe設(shè)備驅(qū)動開發(fā)框架)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!