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)。主机内存缓冲区是用于设备和主机之间的数据传输的一块内存区域。以下是函数的主要步骤:
-
将主机内存缓冲区的大小(以页为单位)存储在
host_mem_size
变量中,并将其右移NVME_CTRL_PAGE_SHIFT
位,得到以控制器页大小为单位的大小。 -
将主机内存缓冲区描述符的DMA地址存储在
dma_addr
变量中。 -
创建一个用于设置特性的NVMe命令结构体
c
,并填充相应的字段:操作码、特性ID、特性参数等。 -
调用
nvme_submit_sync_cmd
函数,将创建的命令提交到管理员队列(Admin Queue)中,并等待同步完成。如果设置特性命令执行失败,输出警告信息,指明错误码和特性标志。 -
如果设置特性命令执行成功,从设置的特性标志中提取主机内存缓冲区的状态,并将其保存在
dev->hmb
变量中。 -
返回执行结果。
总体来说,这个函数用于向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设备中,主机内存缓冲区用于数据传输和操作,因此在不再需要时需要释放这些资源。以下是函数的主要步骤:
-
使用循环遍历主机内存描述符数组中的每个描述符。
-
对于每个描述符,获取其大小并将其乘以控制器页大小,得到描述符对应的内存大小。
-
调用
dma_free_attrs
函数释放与描述符关联的主机内存缓冲区。传递描述符中的地址和大小,同时使用DMA_ATTR_NO_KERNEL_MAPPING
和DMA_ATTR_NO_WARN
属性,以确保释放操作不会与内核映射产生冲突。 -
在循环结束后,释放主机内存描述符缓冲区本身,并将相关指针设置为NULL。
-
最后,使用
dma_free_coherent
函数释放主机内存描述符数组本身的内存,并将相关指针设置为NULL。 -
将主机内存描述符数量重置为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;
}
这两个函数一起处理主机内存缓冲区的分配和释放。
-
__nvme_alloc_host_mem
函数用于分配主机内存缓冲区。它的主要步骤如下:- 根据给定的
preferred
(期望大小)和chunk_size
(块大小),计算可以容纳多少个缓冲区条目。 - 如果控制器支持限制最大缓冲区条目数量
dev->ctrl.hmmaxd
,则使用它来限制缓冲区条目数量。 - 分配内存来存储缓冲区描述符数组
descs
,并获取其DMA地址descs_dma
。 - 分配一个用于存储每个缓冲区的指针数组
bufs
。 - 在循环中,依次为每个缓冲区分配内存,更新描述符和DMA地址,直到达到预期大小或无法再分配为止。
- 根据分配成功的缓冲区数量,更新设备的相关信息(
dev->nr_host_mem_descs
、dev->host_mem_size
、dev->host_mem_descs
、dev->host_mem_descs_dma
、dev->host_mem_desc_bufs
)。 - 如果无法分配任何缓冲区,释放之前分配的资源。
- 根据给定的
-
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设备分配主机内存缓冲区。以下是函数的主要步骤:
-
首先,根据给定的
min
(最小大小)和preferred
(期望大小)计算出最小的缓冲区块大小min_chunk
,限制在PAGE_SIZE * MAX_ORDER_NR_PAGES
以内。 -
从控制器的
hmminds
值(以4KB为单位)和PAGE_SIZE * 2
中选择较大的值,作为每次循环迭代时的最小块大小hmminds
。 -
通过循环从大到小逐步分配主机内存缓冲区。每次循环迭代,将缓冲区块大小
chunk_size
减半,从min_chunk
开始,直到达到或小于hmminds
。 -
在每个循环迭代中,调用
__nvme_alloc_host_mem
函数尝试分配主机内存缓冲区,使用preferred
作为期望大小,chunk_size
作为块大小。如果分配成功,则检查min
是否为零,或者已分配的主机内存大小是否达到min
。如果是,则返回0,表示分配成功。 -
如果分配失败,则调用
nvme_free_host_mem
函数释放之前分配的主机内存资源。 -
如果循环结束后仍未成功分配主机内存缓冲区,返回错误码 -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设备的主机内存缓冲区。以下是函数的主要步骤:
-
计算最大可用主机内存大小(以MB为单位)和设备控制器预设的主机内存大小
preferred
(以4KB为单位),以及控制器设定的最小主机内存大小min
(以4KB为单位)。 -
如果控制器没有设置主机内存预设
dev->ctrl.hmpre
,则直接返回。 -
将
preferred
限制在最大可用主机内存大小以内,以防超出范围。 -
如果
min
大于max
,输出警告信息并释放之前分配的主机内存资源(如果有),然后返回。 -
如果已经有主机内存缓冲区分配,检查其大小是否满足
min
。如果满足,将enable_bits
设置为NVME_HOST_MEM_ENABLE | NVME_HOST_MEM_RETURN
,表示可以重用已分配的缓冲区。否则,释放之前分配的主机内存资源。 -
如果尚未分配主机内存缓冲区,尝试通过调用
nvme_alloc_host_mem
函数来分配。如果分配失败,输出警告信息,但不会阻止控制器继续工作。 -
如果成功分配了主机内存缓冲区,输出信息表示已分配的大小。
-
尝试通过调用
nvme_set_host_mem
函数来设置主机内存缓冲区。如果设置失败,释放分配的主机内存资源。 -
返回设置的结果码。
总体来说,这个函数用于根据控制器设置和限制,设置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)的相关设置。以下是各个属性的作用:
-
cmb_show
函数用于显示CMB的相关信息,包括CMB的起始位置cmbloc
和CMB的大小cmbsz
。 -
DEVICE_ATTR_RO(cmb)
定义只读属性cmb
,用于在sysfs
中显示CMB的相关信息。 -
cmbloc_show
函数用于显示CMB的起始位置cmbloc
。 -
DEVICE_ATTR_RO(cmbloc)
定义只读属性cmbloc
,用于在sysfs
中显示CMB的起始位置。 -
cmbsz_show
函数用于显示CMB的大小cmbsz
。 -
DEVICE_ATTR_RO(cmbsz)
定义只读属性cmbsz
,用于在sysfs
中显示CMB的大小。 -
hmb_show
函数用于显示HMB的当前状态,表示是否启用。 -
hmb_store
函数用于配置HMB的状态。当用户写入1
时,表示启用HMB;当用户写入0
时,表示禁用HMB。 -
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 设备属性的显示和可见性控制。以下是这些代码的作用:
-
nvme_pci_attrs_are_visible
函数用于判断 NVMe 设备属性是否应该在 sysfs 中显示。它基于属性的可见性规则来决定是否显示某个属性。具体来说:- 如果属性是 CMB 相关属性(
cmb
、cmbloc
、cmbsz
),并且dev->cmbsz
(CMB 大小)为零,则将这些属性的可见性设置为不可见。 - 如果属性是 HMB 属性(
hmb
),并且ctrl->hmpre
(HMB 预设大小)为零,则将该属性的可见性设置为不可见。 - 对于其他属性,将根据其模式来确定是否显示。
- 如果属性是 CMB 相关属性(
-
nvme_pci_attrs
数组列出了 NVMe PCI 设备属性的指针,包括 CMB 相关属性和 HMB 属性。 -
nvme_pci_dev_attrs_group
定义了 NVMe PCI 设备属性组,包括nvme_pci_attrs
数组中列出的属性,以及nvme_pci_attrs_are_visible
函数来控制可见性。 -
nvme_pci_dev_attr_groups
数组包含了 NVMe 设备属性组的指针列表,其中包括了nvme_dev_attrs_group
(来自于其他代码),以及nvme_pci_dev_attrs_group
(定义在这里),用于将属性组添加到设备的 sysfs 对象中。 -
nvme_update_attrs
函数用于更新 NVMe 设备属性,通过调用sysfs_update_group
函数,将设备的 kobject 更新为包含 NVMe 设备属性组的 kobject。
这些代码片段的主要作用是在 sysfs 中管理 NVMe 设备的属性显示和可见性,以便在不同条件下控制哪些属性应该显示给用户。这允许用户查看并配置与 CMB 和 HMB 相关的属性。