Skip to content

Commit

Permalink
nvme-pci: use dma_alloc_noncontigous if possible
Browse files Browse the repository at this point in the history
Use dma_alloc_noncontigous to allocate a single IOVA-contigous segment
when backed by an IOMMU.  This allow to easily use bigger segments and
avoids running into segment limits if we can avoid it.

Signed-off-by: Christoph Hellwig <[email protected]>
Signed-off-by: Keith Busch <[email protected]>
  • Loading branch information
Christoph Hellwig authored and keithbusch committed Nov 5, 2024
1 parent 3c2fb1c commit 63a5c7a
Showing 1 changed file with 53 additions and 5 deletions.
58 changes: 53 additions & 5 deletions drivers/nvme/host/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ struct nvme_dev {
struct nvme_ctrl ctrl;
u32 last_ps;
bool hmb;
struct sg_table *hmb_sgt;

mempool_t *iod_mempool;

Expand Down Expand Up @@ -1952,7 +1953,7 @@ static int nvme_set_host_mem(struct nvme_dev *dev, u32 bits)
return ret;
}

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

Expand All @@ -1967,14 +1968,50 @@ static void nvme_free_host_mem(struct nvme_dev *dev)

kfree(dev->host_mem_desc_bufs);
dev->host_mem_desc_bufs = NULL;
}

static void nvme_free_host_mem(struct nvme_dev *dev)
{
if (dev->hmb_sgt)
dma_free_noncontiguous(dev->dev, dev->host_mem_size,
dev->hmb_sgt, DMA_BIDIRECTIONAL);
else
nvme_free_host_mem_multi(dev);

dma_free_coherent(dev->dev, dev->host_mem_descs_size,
dev->host_mem_descs, dev->host_mem_descs_dma);
dev->host_mem_descs = NULL;
dev->host_mem_descs_size = 0;
dev->nr_host_mem_descs = 0;
}

static int __nvme_alloc_host_mem(struct nvme_dev *dev, u64 preferred,
static int nvme_alloc_host_mem_single(struct nvme_dev *dev, u64 size)
{
dev->hmb_sgt = dma_alloc_noncontiguous(dev->dev, size,
DMA_BIDIRECTIONAL, GFP_KERNEL, 0);
if (!dev->hmb_sgt)
return -ENOMEM;

dev->host_mem_descs = dma_alloc_coherent(dev->dev,
sizeof(*dev->host_mem_descs), &dev->host_mem_descs_dma,
GFP_KERNEL);
if (!dev->host_mem_descs) {
dma_free_noncontiguous(dev->dev, dev->host_mem_size,
dev->hmb_sgt, DMA_BIDIRECTIONAL);
dev->hmb_sgt = NULL;
return -ENOMEM;
}
dev->host_mem_size = size;
dev->host_mem_descs_size = sizeof(*dev->host_mem_descs);
dev->nr_host_mem_descs = 1;

dev->host_mem_descs[0].addr =
cpu_to_le64(dev->hmb_sgt->sgl->dma_address);
dev->host_mem_descs[0].size = cpu_to_le32(size / NVME_CTRL_PAGE_SIZE);
return 0;
}

static int nvme_alloc_host_mem_multi(struct nvme_dev *dev, u64 preferred,
u32 chunk_size)
{
struct nvme_host_mem_buf_desc *descs;
Expand Down Expand Up @@ -2049,9 +2086,18 @@ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
u64 hmminds = max_t(u32, dev->ctrl.hmminds * 4096, PAGE_SIZE * 2);
u64 chunk_size;

/*
* If there is an IOMMU that can merge pages, try a virtually
* non-contiguous allocation for a single segment first.
*/
if (!(PAGE_SIZE & dma_get_merge_boundary(dev->dev))) {
if (!nvme_alloc_host_mem_single(dev, preferred))
return 0;
}

/* 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 (!nvme_alloc_host_mem_multi(dev, preferred, chunk_size)) {
if (!min || dev->host_mem_size >= min)
return 0;
nvme_free_host_mem(dev);
Expand Down Expand Up @@ -2099,8 +2145,10 @@ static int nvme_setup_host_mem(struct nvme_dev *dev)
}

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

ret = nvme_set_host_mem(dev, enable_bits);
Expand Down

0 comments on commit 63a5c7a

Please sign in to comment.