NVMe Linux驱动系列一:host端[pci.c]<49>

nvme_set_host_mem


static int nvme_set_host_mem(struct nvme_dev *dev, u32 bits)
{
	u32 host_mem_size = dev->host_mem_size >> NVME_CTRL_PAGE_SHIFT;
	u64 dma_addr = dev->host_mem_descs_dma;
	struct nvme_command c = { };
	int ret;

	c.features.opcode	= nvme_admin_set_features;
	c.features.fid		= cpu_to_le32(NVME_FEAT_HOST_MEM_BUF);
	c.features.dword11	= cpu_to_le32(bits);
	c.features.dword12	= cpu_to_le32(host_mem_size);
	c.features.dword13	= cpu_to_le32(lower_32_bits(dma_addr));
	c.features.dword14	= cpu_to_le32(upper_32_bits(dma_addr));
	c.features.dword15	= cpu_to_le32(dev->nr_host_mem_descs);

	ret = nvme_submit_sync_cmd(dev->ctrl.admin_q, &c, NULL, 0);
	if (ret) {
		dev_warn(dev->ctrl.device,
			 "failed to set host mem (err %d, flags %#x).\n",
			 ret, bits);
	} else
		dev->hmb = bits & NVME_HOST_MEM_ENABLE;

	return ret;
}

这个函数的作用是设置NVMe设备的主机内存缓冲区(Host Memory Buffer)。主机内存缓冲区是用于设备和主机之间的数据传输的一块内存区域。以下是函数的主要步骤:

  1. 将主机内存缓冲区的大小(以页为单位)存储在 host_mem_size 变量中,并将其右移 NVME_CTRL_PAGE_SHIFT 位,得到以控制器页大小为单位的大小。

  2. 将主机内存缓冲区描述符的DMA地址存储在 dma_addr 变量中。

  3. 创建一个用于设置特性的NVMe命令结构体 c,并填充相应的字段:操作码、特性ID、特性参数等。

  4. 调用 nvme_submit_sync_cmd 函数,将创建的命令提交到管理员队列(Admin Queue)中,并等待同步完成。如果设置特性命令执行失败,输出警告信息,指明错误码和特性标志。

  5. 如果设置特性命令执行成功,从设置的特性标志中提取主机内存缓冲区的状态,并将其保存在 dev->hmb 变量中。

  6. 返回执行结果。

总体来说,这个函数用于向NVMe设备发送命令,以设置主机内存缓冲区的参数和状态,从而影响设备与主机之间的数据传输。特别是通过启用或禁用主机内存缓冲区,可以调整数据传输的性能和行为。

nvme_free_host_mem


static void nvme_free_host_mem(struct nvme_dev *dev)
{
	int i;

	for (i = 0; i < dev->nr_host_mem_descs; i++) {
		struct nvme_host_mem_buf_desc *desc = &dev->host_mem_descs[i];
		size_t size = le32_to_cpu(desc->size) * NVME_CTRL_PAGE_SIZE;

		dma_free_attrs(dev->dev, size, dev->host_mem_desc_bufs[i],
			       le64_to_cpu(desc->addr),
			       DMA_ATTR_NO_KERNEL_MAPPING | DMA_ATTR_NO_WARN);
	}

	kfree(dev->host_mem_desc_bufs);
	dev->host_mem_desc_bufs = NULL;
	dma_free_coherent(dev->dev,
			dev->nr_host_mem_descs * sizeof(*dev->host_mem_descs),
			dev->host_mem_descs, dev->host_mem_descs_dma);
	dev->host_mem_descs = NULL;
	dev->nr_host_mem_descs = 0;
}

这个函数的作用是释放NVMe设备的主机内存缓冲区。在NVMe设备中,主机内存缓冲区用于数据传输和操作,因此在不再需要时需要释放这些资源。以下是函数的主要步骤:

  1. 使用循环遍历主机内存描述符数组中的每个描述符。

  2. 对于每个描述符,获取其大小并将其乘以控制器页大小,得到描述符对应的内存大小。

  3. 调用 dma_free_attrs 函数释放与描述符关联的主机内存缓冲区。传递描述符中的地址和大小,同时使用 DMA_ATTR_NO_KERNEL_MAPPINGDMA_ATTR_NO_WARN 属性,以确保释放操作不会与内核映射产生冲突。

  4. 在循环结束后,释放主机内存描述符缓冲区本身,并将相关指针设置为NULL。

  5. 最后,使用 dma_free_coherent 函数释放主机内存描述符数组本身的内存,并将相关指针设置为NULL。

  6. 将主机内存描述符数量重置为0。

总体来说,这个函数负责释放NVMe设备使用的主机内存缓冲区及其相关资源,以确保资源的正确释放和回收。

__nvme_alloc_host_mem



static int __nvme_alloc_host_mem(struct nvme_dev *dev, u64 preferred,
		u32 chunk_size)
{
	struct nvme_host_mem_buf_desc *descs;
	u32 max_entries, len;
	dma_addr_t descs_dma;
	int i = 0;
	void **bufs;
	u64 size, tmp;

	tmp = (preferred + chunk_size - 1);
	do_div(tmp, chunk_size);
	max_entries = tmp;

	if (dev->ctrl.hmmaxd && dev->ctrl.hmmaxd < max_entries)
		max_entries = dev->ctrl.hmmaxd;

	descs = dma_alloc_coherent(dev->dev, max_entries * sizeof(*descs),
				   &descs_dma, GFP_KERNEL);
	if (!descs)
		goto out;

	bufs = kcalloc(max_entries, sizeof(*bufs), GFP_KERNEL);
	if (!bufs)
		goto out_free_descs;

	for (size = 0; size < preferred && i < max_entries; size += len) {
		dma_addr_t dma_addr;

		len = min_t(u64, chunk_size, preferred - size);
		bufs[i] = dma_alloc_attrs(dev->dev, len, &dma_addr, GFP_KERNEL,
				DMA_ATTR_NO_KERNEL_MAPPING | DMA_ATTR_NO_WARN);
		if (!bufs[i])
			break;

		descs[i].addr = cpu_to_le64(dma_addr);
		descs[i].size = cpu_to_le32(len / NVME_CTRL_PAGE_SIZE);
		i++;
	}

	if (!size)
		goto out_free_bufs;

	dev->nr_host_mem_descs = i;
	dev->host_mem_size = size;
	dev->host_mem_descs = descs;
	dev->host_mem_descs_dma = descs_dma;
	dev->host_mem_desc_bufs = bufs;
	return 0;

out_free_bufs:
	while (--i >= 0) {
		size_t size = le32_to_cpu(descs[i].size) * NVME_CTRL_PAGE_SIZE;

		dma_free_attrs(dev->dev, size, bufs[i],
			       le64_to_cpu(descs[i].addr),
			       DMA_ATTR_NO_KERNEL_MAPPING | DMA_ATTR_NO_WARN);
	}

	kfree(bufs);
out_free_descs:
	dma_free_coherent(dev->dev, max_entries * sizeof(*descs), descs,
			descs_dma);
out:
	dev->host_mem_descs = NULL;
	return -ENOMEM;
}

这两个函数一起处理主机内存缓冲区的分配和释放。

  1. __nvme_alloc_host_mem 函数用于分配主机内存缓冲区。它的主要步骤如下:

    • 根据给定的 preferred(期望大小)和 chunk_size(块大小),计算可以容纳多少个缓冲区条目。
    • 如果控制器支持限制最大缓冲区条目数量 dev->ctrl.hmmaxd,则使用它来限制缓冲区条目数量。
    • 分配内存来存储缓冲区描述符数组 descs,并获取其DMA地址 descs_dma
    • 分配一个用于存储每个缓冲区的指针数组 bufs
    • 在循环中,依次为每个缓冲区分配内存,更新描述符和DMA地址,直到达到预期大小或无法再分配为止。
    • 根据分配成功的缓冲区数量,更新设备的相关信息(dev->nr_host_mem_descsdev->host_mem_sizedev->host_mem_descsdev->host_mem_descs_dmadev->host_mem_desc_bufs)。
    • 如果无法分配任何缓冲区,释放之前分配的资源。
  2. nvme_free_host_mem 函数用于释放之前分配的主机内存缓冲区。它的主要步骤如下:

    • 使用循环遍历主机内存描述符数组,逐个释放对应的缓冲区。
    • 释放主机内存描述符缓冲区 dev->host_mem_desc_bufs,并将相关指针设置为NULL。
    • 释放主机内存描述符数组内存 dev->host_mem_descs,并将相关指针设置为NULL。
    • 将主机内存描述符数量重置为0。

这两个函数一起实现了主机内存缓冲区的分配和释放操作,确保在需要时为NVMe设备分配足够的主机内存以支持数据传输,并在不需要时释放相应的资源。

nvme_alloc_host_mem


static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
{
	u64 min_chunk = min_t(u64, preferred, PAGE_SIZE * MAX_ORDER_NR_PAGES);
	u64 hmminds = max_t(u32, dev->ctrl.hmminds * 4096, PAGE_SIZE * 2);
	u64 chunk_size;

	/* start big and work our way down */
	for (chunk_size = min_chunk; chunk_size >= hmminds; chunk_size /= 2) {
		if (!__nvme_alloc_host_mem(dev, preferred, chunk_size)) {
			if (!min || dev->host_mem_size >= min)
				return 0;
			nvme_free_host_mem(dev);
		}
	}

	return -ENOMEM;
}

这个函数 nvme_alloc_host_mem 用于为NVMe设备分配主机内存缓冲区。以下是函数的主要步骤:

  1. 首先,根据给定的 min(最小大小)和 preferred(期望大小)计算出最小的缓冲区块大小 min_chunk,限制在 PAGE_SIZE * MAX_ORDER_NR_PAGES 以内。

  2. 从控制器的 hmminds 值(以4KB为单位)和 PAGE_SIZE * 2 中选择较大的值,作为每次循环迭代时的最小块大小 hmminds

  3. 通过循环从大到小逐步分配主机内存缓冲区。每次循环迭代,将缓冲区块大小 chunk_size 减半,从 min_chunk 开始,直到达到或小于 hmminds

  4. 在每个循环迭代中,调用 __nvme_alloc_host_mem 函数尝试分配主机内存缓冲区,使用 preferred 作为期望大小,chunk_size 作为块大小。如果分配成功,则检查 min 是否为零,或者已分配的主机内存大小是否达到 min。如果是,则返回0,表示分配成功。

  5. 如果分配失败,则调用 nvme_free_host_mem 函数释放之前分配的主机内存资源。

  6. 如果循环结束后仍未成功分配主机内存缓冲区,返回错误码 -ENOMEM,表示分配失败。

总体来说,这个函数实现了在不同块大小范围内尝试分配主机内存缓冲区的逻辑。它会从较大的块大小开始,逐渐减小块大小,尝试分配满足要求的主机内存缓冲区,如果成功则返回0,否则返回错误码。

nvme_setup_host_mem


static int nvme_setup_host_mem(struct nvme_dev *dev)
{
	u64 max = (u64)max_host_mem_size_mb * SZ_1M;
	u64 preferred = (u64)dev->ctrl.hmpre * 4096;
	u64 min = (u64)dev->ctrl.hmmin * 4096;
	u32 enable_bits = NVME_HOST_MEM_ENABLE;
	int ret;

	if (!dev->ctrl.hmpre)
		return 0;

	preferred = min(preferred, max);
	if (min > max) {
		dev_warn(dev->ctrl.device,
			"min host memory (%lld MiB) above limit (%d MiB).\n",
			min >> ilog2(SZ_1M), max_host_mem_size_mb);
		nvme_free_host_mem(dev);
		return 0;
	}

	/*
	 * If we already have a buffer allocated check if we can reuse it.
	 */
	if (dev->host_mem_descs) {
		if (dev->host_mem_size >= min)
			enable_bits |= NVME_HOST_MEM_RETURN;
		else
			nvme_free_host_mem(dev);
	}

	if (!dev->host_mem_descs) {
		if (nvme_alloc_host_mem(dev, min, preferred)) {
			dev_warn(dev->ctrl.device,
				"failed to allocate host memory buffer.\n");
			return 0; /* controller must work without HMB */
		}

		dev_info(dev->ctrl.device,
			"allocated %lld MiB host memory buffer.\n",
			dev->host_mem_size >> ilog2(SZ_1M));
	}

	ret = nvme_set_host_mem(dev, enable_bits);
	if (ret)
		nvme_free_host_mem(dev);
	return ret;
}

这个函数 nvme_setup_host_mem 用于设置NVMe设备的主机内存缓冲区。以下是函数的主要步骤:

  1. 计算最大可用主机内存大小(以MB为单位)和设备控制器预设的主机内存大小 preferred(以4KB为单位),以及控制器设定的最小主机内存大小 min(以4KB为单位)。

  2. 如果控制器没有设置主机内存预设 dev->ctrl.hmpre,则直接返回。

  3. preferred 限制在最大可用主机内存大小以内,以防超出范围。

  4. 如果 min 大于 max,输出警告信息并释放之前分配的主机内存资源(如果有),然后返回。

  5. 如果已经有主机内存缓冲区分配,检查其大小是否满足 min。如果满足,将 enable_bits 设置为 NVME_HOST_MEM_ENABLE | NVME_HOST_MEM_RETURN,表示可以重用已分配的缓冲区。否则,释放之前分配的主机内存资源。

  6. 如果尚未分配主机内存缓冲区,尝试通过调用 nvme_alloc_host_mem 函数来分配。如果分配失败,输出警告信息,但不会阻止控制器继续工作。

  7. 如果成功分配了主机内存缓冲区,输出信息表示已分配的大小。

  8. 尝试通过调用 nvme_set_host_mem 函数来设置主机内存缓冲区。如果设置失败,释放分配的主机内存资源。

  9. 返回设置的结果码。

总体来说,这个函数用于根据控制器设置和限制,设置NVMe设备的主机内存缓冲区。它会根据控制器预设、最小和最大值,尝试分配和设置主机内存缓冲区,以便在可能的情况下提升设备的性能。如果分配或设置失败,函数会相应地释放资源。

cmb_show & cmbloc_show & cmbsz_show & hmb_show & hmb_store


static ssize_t cmb_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct nvme_dev *ndev = to_nvme_dev(dev_get_drvdata(dev));

	return sysfs_emit(buf, "cmbloc : x%08x\ncmbsz  : x%08x\n",
		       ndev->cmbloc, ndev->cmbsz);
}
static DEVICE_ATTR_RO(cmb);

static ssize_t cmbloc_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct nvme_dev *ndev = to_nvme_dev(dev_get_drvdata(dev));

	return sysfs_emit(buf, "%u\n", ndev->cmbloc);
}
static DEVICE_ATTR_RO(cmbloc);

static ssize_t cmbsz_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct nvme_dev *ndev = to_nvme_dev(dev_get_drvdata(dev));

	return sysfs_emit(buf, "%u\n", ndev->cmbsz);
}
static DEVICE_ATTR_RO(cmbsz);

static ssize_t hmb_show(struct device *dev, struct device_attribute *attr,
			char *buf)
{
	struct nvme_dev *ndev = to_nvme_dev(dev_get_drvdata(dev));

	return sysfs_emit(buf, "%d\n", ndev->hmb);
}

static ssize_t hmb_store(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count)
{
	struct nvme_dev *ndev = to_nvme_dev(dev_get_drvdata(dev));
	bool new;
	int ret;

	if (kstrtobool(buf, &new) < 0)
		return -EINVAL;

	if (new == ndev->hmb)
		return count;

	if (new) {
		ret = nvme_setup_host_mem(ndev);
	} else {
		ret = nvme_set_host_mem(ndev, 0);
		if (!ret)
			nvme_free_host_mem(ndev);
	}

	if (ret < 0)
		return ret;

	return count;
}
static DEVICE_ATTR_RW(hmb);

这一组代码片段是用于在Linux内核的sysfs文件系统中创建设备属性,以便用户能够查看和配置NVMe设备的相关信息。这些属性涉及共享内存缓冲区(CMB)以及主机内存缓冲区(HMB)的相关设置。以下是各个属性的作用:

  1. cmb_show 函数用于显示CMB的相关信息,包括CMB的起始位置 cmbloc 和CMB的大小 cmbsz

  2. DEVICE_ATTR_RO(cmb) 定义只读属性 cmb,用于在sysfs中显示CMB的相关信息。

  3. cmbloc_show 函数用于显示CMB的起始位置 cmbloc

  4. DEVICE_ATTR_RO(cmbloc) 定义只读属性 cmbloc,用于在sysfs中显示CMB的起始位置。

  5. cmbsz_show 函数用于显示CMB的大小 cmbsz

  6. DEVICE_ATTR_RO(cmbsz) 定义只读属性 cmbsz,用于在sysfs中显示CMB的大小。

  7. hmb_show 函数用于显示HMB的当前状态,表示是否启用。

  8. hmb_store 函数用于配置HMB的状态。当用户写入1时,表示启用HMB;当用户写入0时,表示禁用HMB。

  9. DEVICE_ATTR_RW(hmb) 定义读写属性 hmb,用于在sysfs中显示和配置HMB的状态。

通过在sysfs中创建这些属性,用户可以查看和配置NVMe设备的CMB和HMB设置,以及决定是否启用HMB。例如,用户可以通过写入1来启用HMB,然后可以使用相应的属性来查看CMB和HMB的信息。这使得用户能够在运行时管理设备的内存缓冲区配置。

nvme_pci_attrs*


static umode_t nvme_pci_attrs_are_visible(struct kobject *kobj,
		struct attribute *a, int n)
{
	struct nvme_ctrl *ctrl =
		dev_get_drvdata(container_of(kobj, struct device, kobj));
	struct nvme_dev *dev = to_nvme_dev(ctrl);

	if (a == &dev_attr_cmb.attr ||
	    a == &dev_attr_cmbloc.attr ||
	    a == &dev_attr_cmbsz.attr) {
	    	if (!dev->cmbsz)
			return 0;
	}
	if (a == &dev_attr_hmb.attr && !ctrl->hmpre)
		return 0;

	return a->mode;
}

static struct attribute *nvme_pci_attrs[] = {
	&dev_attr_cmb.attr,
	&dev_attr_cmbloc.attr,
	&dev_attr_cmbsz.attr,
	&dev_attr_hmb.attr,
	NULL,
};

static const struct attribute_group nvme_pci_dev_attrs_group = {
	.attrs		= nvme_pci_attrs,
	.is_visible	= nvme_pci_attrs_are_visible,
};

static const struct attribute_group *nvme_pci_dev_attr_groups[] = {
	&nvme_dev_attrs_group,
	&nvme_pci_dev_attrs_group,
	NULL,
};

static void nvme_update_attrs(struct nvme_dev *dev)
{
	sysfs_update_group(&dev->ctrl.device->kobj, &nvme_pci_dev_attrs_group);
}

这一组代码片段涉及 sysfs 文件系统中 NVMe 设备属性的显示和可见性控制。以下是这些代码的作用:

  1. nvme_pci_attrs_are_visible 函数用于判断 NVMe 设备属性是否应该在 sysfs 中显示。它基于属性的可见性规则来决定是否显示某个属性。具体来说:

    • 如果属性是 CMB 相关属性(cmbcmbloccmbsz),并且 dev->cmbsz(CMB 大小)为零,则将这些属性的可见性设置为不可见。
    • 如果属性是 HMB 属性(hmb),并且 ctrl->hmpre(HMB 预设大小)为零,则将该属性的可见性设置为不可见。
    • 对于其他属性,将根据其模式来确定是否显示。
  2. nvme_pci_attrs 数组列出了 NVMe PCI 设备属性的指针,包括 CMB 相关属性和 HMB 属性。

  3. nvme_pci_dev_attrs_group 定义了 NVMe PCI 设备属性组,包括 nvme_pci_attrs 数组中列出的属性,以及 nvme_pci_attrs_are_visible 函数来控制可见性。

  4. nvme_pci_dev_attr_groups 数组包含了 NVMe 设备属性组的指针列表,其中包括了 nvme_dev_attrs_group(来自于其他代码),以及 nvme_pci_dev_attrs_group(定义在这里),用于将属性组添加到设备的 sysfs 对象中。

  5. nvme_update_attrs 函数用于更新 NVMe 设备属性,通过调用 sysfs_update_group 函数,将设备的 kobject 更新为包含 NVMe 设备属性组的 kobject。

这些代码片段的主要作用是在 sysfs 中管理 NVMe 设备的属性显示和可见性,以便在不同条件下控制哪些属性应该显示给用户。这允许用户查看并配置与 CMB 和 HMB 相关的属性。

Linux内核中包含了大量的PCI驱动程序,这些驱动程序的功能各不相同,但都是用于支持PCI设备的工作。下面我们来详细分析些典型的PCI驱动程序。 1. e1000e驱动程序 e1000e驱动程序是用于Intel网卡的驱动程序,它支持Intel 82563/6/7, 82571/2/3/4/7/8/9, or 82583 NICs。e1000e驱动程序采用DMA总线传输机制,能够提供高性能的网络传输。 2. ahci驱动程序 ahci驱动程序是用于SATA硬盘控制器的驱动程序,它支持AHCI(Advanced Host Controller Interface)标准,能够提供高速稳定的数据传输,支持NCQ(Native Command Queuing)和Hot Plug等特性。 3. igb驱动程序 igb驱动程序是用于Intel Gigabit以太网卡的驱动程序,它支持Intel 82575/6, 82580, I350, I210/1, and I211 NICs。igb驱动程序采用DMA总线传输机制,能够提供高性能的网络传输。 4. nvme驱动程序 nvme驱动程序是用于NVMe(NVM Express)SSD固态硬盘的驱动程序,它支持PCIe接口,能够提供高速稳定的数据传输,支持多队列和命令集等特性。 5. mlx4驱动程序 mlx4驱动程序是用于Mellanox Connect-IB/Connect-X Infiniband和以太网适配器的驱动程序,它采用DMA总线传输机制,支持InfiniBand和Ethernet网络通信协议,能够提供高性能的网络传输。 总之,Linux内核中的PCI驱动程序功能丰富,能够支持各种类型的PCI设备,为系统的性能和稳定性提供了重要的保障。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值