PCIE Learning Series 5 (PCIe Device Driver Development Framework for Linux)

Overview

This article describes an open source PCIe device driver. Through this example, you can basically understand all PCIe device drivers. I will also write articles about various Linux drivers in the future.

Through the previous study, we know that PCIe devices need to be enumerated before accessing them. Generally speaking, we do not need to do the enumeration operation of PCI devices. It has been done during BIOS or system initialization. After the system has enumerated all the devices, the PCI devices will be added to the system. Use "lspci" under Linux. You can see all the PCI devices scanned by the system. We only need to pay attention to the implementation of the PCI device driver.

I randomly found an open source code in the Linux source code, some source code of tsi721 (a PCIe to RapidIO chip). Basically, an ordinary PCIE device driver model is like this, with some unique processing processes of the device added.

So where is the entrance to the PCIe driver?

When the vendor id and device id of the PCI device enumerated by the system match the id in the driver, the probe function in the driver will be called.

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);

The PCIe device driver is not matched by name, but by id. When the driver matches the device, the tsi721_probe function will be executed. A general PCIe driver will basically use the following steps:

Driver loading process

  1. pci_enable_device(pdev); enable pci device

  2. pci_resource_len(pdev, BAR_0); Get the resource size of the pci device, which is the value obtained during enumeration

  3. pci_request_regions(pdev, DRV_NAME); Apply for pci device resources

  4. pci_ioremap_bar(pdev, BAR_0); Map virtual memory

  5. pci_set_master(pdev); Set the pci device to master. Only in master mode can data transmission be actively initiated.

  6. pci_enable_msi(pdev); Enable MSI interrupt

  7. request_irq(priv->pdev->irq, tsi721_irqhandler,(priv->flags & TSI721_USING_MSI) ? 0 : IRQF_SHARED,DRV_NAME, (void *)priv); Register interrupt handler function

    The PCIe part of the initialization process is basically completed, and there are other initializations related to specific services. You can take a look at the source code below.

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设备
    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设备bar0的资源大小,这是枚举时得到的值
        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设备资源
    if (err) {
    
    
        tsi_err(&pdev->dev, "Unable to obtain PCI resources");
        goto err_disable_pdev;
    }

    pci_set_master(pdev);			// 设置pci设备为master模式

    priv->regs = pci_ioremap_bar(pdev, BAR_0); // 映射虚拟内存
    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);// 映射虚拟内存
    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)); // 设置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 calls tsi721_setup_mport(priv), as shown below, related to pcie is the enabling of msi interrupt, the registration of interrupt function, and the other is the specific business-related initialization of the chip.

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;
}

The driver loading and initialization code ends.

Driver uninstall process

When the driver is uninstalled, the tsi721_remove function will be called, and its process is basically the reverse operation of the probe.

  1. free_irq(priv->pdev->irq, (void *)priv); Release interrupt
  2. iounmap(priv->regs); Address demapping
  3. pci_disable_msi(priv->pdev); disable msi interrupt
  4. pci_release_regions(pdev); Release the requested resources
  5. pci_clear_master(pdev); Clear device master attributes
  6. pci_disable_device(pdev); Disable pci device

Let’s take a look at the source code below

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);					// 清除设备master属性
    pci_disable_device(pdev);				// 失能pci设备
    pci_set_drvdata(pdev, NULL);
    kfree(priv);
    tsi_debug(EXIT, &pdev->dev, "exit");
}

At this point, the uninstallation of the PCIe device driver is completed.

The above is the basic driver framework of a PCIe device.

Guess you like

Origin blog.csdn.net/qq_42208449/article/details/132907862