PCIE学习系列 五(Linux之PCIe设备驱动开发框架)

这篇具有很好参考价值的文章主要介绍了PCIE学习系列 五(Linux之PCIe设备驱动开发框架)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

本文讲述一个开源的PCIe设备驱动,通过这个例子可以基本上理解所有的PCIe设备驱动。后续也会做关于Linux各类驱动的文章。

通过前面的学习,我们知道PCIe设备访问之前需要先做枚举。一般来说,PCI设备的枚举操作不需要我们来做,BIOS或者系统初始化时已经做好了,当系统枚举完所有设备之后,PCI设备就会添加进系统,在Linux下使用 “lspci” 就能看到系统扫描到的所有PCI设备,我们只需要关注PCI设备driver的实现就好了。

在Linux源码中随便找了一个开源代码,tsi721(一款PCIe转RapidIO芯片)的一些源码,基本上一个普通的PCIE设备驱动模型都是这样的,其中在加上一些设备独有的处理流程。

那么PCIe驱动的入口在哪呢?

当系统枚举到的PCI设备的vendor id和device id与driver中的id匹配上之后,就会调用driver中的probe函数。

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设备驱动不是通过name匹配,而是通过id去匹配的,当驱动与设备匹配上之后,就会执行tsi721_probe函数,一般的PCIe驱动基本上会用到以下步骤:

驱动加载流程

  1. pci_enable_device(pdev); 使能pci设备

  2. pci_resource_len(pdev, BAR_0);获取pci设备的资源大小,这是枚举时得到的值

  3. pci_request_regions(pdev, DRV_NAME); 申请pci设备资源

  4. pci_ioremap_bar(pdev, BAR_0); 映射虚拟内存

  5. pci_set_master(pdev); 设置pci设备为master,master模式才能主动发起数据传输

  6. pci_enable_msi(pdev); 使能MSI中断

  7. request_irq(priv->pdev->irq, tsi721_irqhandler,(priv->flags & TSI721_USING_MSI) ? 0 : IRQF_SHARED,DRV_NAME, (void *)priv); 注册中断处理函数

    初始化流程中关于PCIe的就基本上完成了,其余还有关于具体业务的初始化,下面可以结合源码看看。

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调用tsi721_setup_mport(priv),如下所示,和pcie相关的就是msi 中断的使能,中断函数的注册,其他就是芯片具体的业务相关的初始化。

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

驱动加载初始化的代码就结束了。

驱动卸载流程

当驱动卸载时会调用到tsi721_remove函数,其流程基本上就是probe的逆向操作。

  1. free_irq(priv->pdev->irq, (void *)priv); 释放中断
  2. iounmap(priv->regs); 地址解映射
  3. pci_disable_msi(priv->pdev);失能msi中断
  4. pci_release_regions(pdev);释放申请的资源
  5. pci_clear_master(pdev); 清除设备master属性
  6. pci_disable_device(pdev);失能pci设备

下面一起看看源码

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

至此关于PCIe设备驱动的卸载就完成了。

以上就是一个PCIe设备的基本驱动框架。文章来源地址https://www.toymoban.com/news/detail-754549.html

到了这里,关于PCIE学习系列 五(Linux之PCIe设备驱动开发框架)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134533533 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:《Linux驱动开发笔记(三

    2024年02月05日
    浏览(53)
  • 4、Linux驱动开发:设备-设备号&设备号注册

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(57)
  • Linux驱动开发实战(一)——设备驱动模型

    在早期的Linux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Linux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。 设备驱动模型

    2024年02月16日
    浏览(51)
  • 【Linux 设备驱动系列 4 -- 设备树 64bit 寄存器 reg 属性描述 】

    address-cells:address要用多少个32位数来表示; size-cells:size 要用多少个32位数来表示。 32bit 寄存器 reg 属性描述 在 Linux 设备树(Device Tree)语法中,\\\" reg \\\"和\\\" reg-names \\\"属性是用于描述设备资源的主要属性。 \\\" reg \\\"属性用于描述设备的物理地址和大小。它是一个32位或64位的整数数

    2024年02月16日
    浏览(57)
  • Linux设备驱动开发 - 虚拟时钟Clock驱动示例

    By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 很多设备里面系统时钟架构极其复杂,让学习Clock驱动的盆友头大。这里我参考S3C2440的clock驱动写了一个virtual clock,即虚拟时钟驱动,分别包含clock的provider和

    2023年04月21日
    浏览(43)
  • Linux 驱动学习笔记 ——(1)字符设备驱动

    《【正点原子】I.MX6U嵌入式Linux驱动开发指南》学习笔记 字符设备是 Linux 驱动中最基本的一类设备驱动,字节设备就是按照字节流来读写的设备,常见的字符设备包括:LED、蜂鸣器、按键、I2C 以及 SPI 等。 Linux 中一切皆文件,字符设备驱动加载成功后会在 /dev 目录下生成相

    2024年02月08日
    浏览(56)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(37)
  • 嵌入式Linux驱动开发 04:基于设备树的驱动开发

    前面文章 《嵌入式Linux驱动开发 03:平台(platform)总线驱动模型》 引入了资源和驱动分离的概念,这篇文章将在前面基础上更进一步,引入设备树的概念。 在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现

    2024年02月16日
    浏览(70)
  • 正点原子嵌入式linux驱动开发——Linux 网络设备驱动

    网络驱动是linux里面驱动三巨头之一 ,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面已经讲过了字符设备驱动和块设备驱动,本章就来学习一下linux里面的 网络设备驱动 。 本次笔记中讨论的都是有线网络! 提起网络,一般想到的硬件就是“网卡”。在

    2024年01月17日
    浏览(71)
  • 嵌入式Linux系统中的设备驱动开发:从设备树到驱动实现

    大家好,今天给大家介绍 嵌入式Linux系统中的设备驱动开发:从设备树到驱动实现 ,文章末尾附有分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全! 可进群免费领取。 在嵌入式Linux系统中,设备驱动是连接硬件设备和操作系统之间的桥梁。

    2024年02月19日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包