[RFC PATCH 07/25] mm: migrate: Add copy_page_dma to use DMA Engine to copy pages.

From: Zi Yan
Date: Wed Apr 03 2019 - 22:10:58 EST


From: Zi Yan <ziy@xxxxxxxxxx>

vm.use_all_dma_chans will grab all usable DMA channels
vm.limit_dma_chans will limit how many DMA channels in use

Signed-off-by: Zi Yan <ziy@xxxxxxxxxx>
---
include/linux/highmem.h | 1 +
include/linux/sched/sysctl.h | 3 +
kernel/sysctl.c | 19 +++
mm/copy_page.c | 291 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 314 insertions(+)

diff --git a/include/linux/highmem.h b/include/linux/highmem.h
index 0f50dc5..119bb39 100644
--- a/include/linux/highmem.h
+++ b/include/linux/highmem.h
@@ -277,5 +277,6 @@ static inline void copy_highpage(struct page *to, struct page *from)
#endif

int copy_page_multithread(struct page *to, struct page *from, int nr_pages);
+int copy_page_dma(struct page *to, struct page *from, int nr_pages);

#endif /* _LINUX_HIGHMEM_H */
diff --git a/include/linux/sched/sysctl.h b/include/linux/sched/sysctl.h
index 99ce6d7..ce11241 100644
--- a/include/linux/sched/sysctl.h
+++ b/include/linux/sched/sysctl.h
@@ -90,4 +90,7 @@ extern int sched_energy_aware_handler(struct ctl_table *table, int write,
loff_t *ppos);
#endif

+extern int sysctl_dma_page_migration(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp,
+ loff_t *ppos);
#endif /* _LINUX_SCHED_SYSCTL_H */
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 0eae0b8..b8712eb 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -103,6 +103,8 @@

extern int accel_page_copy;
extern unsigned int limit_mt_num;
+extern int use_all_dma_chans;
+extern int limit_dma_chans;

/* External variables not in a header file. */
extern int suid_dumpable;
@@ -1451,6 +1453,23 @@ static struct ctl_table vm_table[] = {
.extra1 = &zero,
},
{
+ .procname = "use_all_dma_chans",
+ .data = &use_all_dma_chans,
+ .maxlen = sizeof(use_all_dma_chans),
+ .mode = 0644,
+ .proc_handler = sysctl_dma_page_migration,
+ .extra1 = &zero,
+ .extra2 = &one,
+ },
+ {
+ .procname = "limit_dma_chans",
+ .data = &limit_dma_chans,
+ .maxlen = sizeof(limit_dma_chans),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &zero,
+ },
+ {
.procname = "hugetlb_shm_group",
.data = &sysctl_hugetlb_shm_group,
.maxlen = sizeof(gid_t),
diff --git a/mm/copy_page.c b/mm/copy_page.c
index 6665e3d..5e7a797 100644
--- a/mm/copy_page.c
+++ b/mm/copy_page.c
@@ -126,3 +126,294 @@ int copy_page_multithread(struct page *to, struct page *from, int nr_pages)

return err;
}
+/* ======================== DMA copy page ======================== */
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+
+#define NUM_AVAIL_DMA_CHAN 16
+
+
+int use_all_dma_chans = 0;
+int limit_dma_chans = NUM_AVAIL_DMA_CHAN;
+
+
+struct dma_chan *copy_chan[NUM_AVAIL_DMA_CHAN] = {0};
+struct dma_device *copy_dev[NUM_AVAIL_DMA_CHAN] = {0};
+
+
+
+#ifdef CONFIG_PROC_SYSCTL
+extern int proc_dointvec_minmax(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos);
+int sysctl_dma_page_migration(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp,
+ loff_t *ppos)
+{
+ int err = 0;
+ int use_all_dma_chans_prior_val = use_all_dma_chans;
+ dma_cap_mask_t copy_mask;
+
+ if (write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ err = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
+
+ if (err < 0)
+ return err;
+ if (write) {
+ /* Grab all DMA channels */
+ if (use_all_dma_chans_prior_val == 0 && use_all_dma_chans == 1) {
+ int i;
+
+ dma_cap_zero(copy_mask);
+ dma_cap_set(DMA_MEMCPY, copy_mask);
+
+ dmaengine_get();
+ for (i = 0; i < NUM_AVAIL_DMA_CHAN; ++i) {
+ if (!copy_chan[i]) {
+ copy_chan[i] = dma_request_channel(copy_mask, NULL, NULL);
+ }
+ if (!copy_chan[i]) {
+ pr_err("%s: cannot grab channel: %d\n", __func__, i);
+ continue;
+ }
+
+ copy_dev[i] = copy_chan[i]->device;
+
+ if (!copy_dev[i]) {
+ pr_err("%s: no device: %d\n", __func__, i);
+ continue;
+ }
+ }
+
+ }
+ /* Release all DMA channels */
+ else if (use_all_dma_chans_prior_val == 1 && use_all_dma_chans == 0) {
+ int i;
+
+ for (i = 0; i < NUM_AVAIL_DMA_CHAN; ++i) {
+ if (copy_chan[i]) {
+ dma_release_channel(copy_chan[i]);
+ copy_chan[i] = NULL;
+ copy_dev[i] = NULL;
+ }
+ }
+
+ dmaengine_put();
+ }
+
+ if (err)
+ use_all_dma_chans = use_all_dma_chans_prior_val;
+ }
+ return err;
+}
+
+#endif
+
+static int copy_page_dma_once(struct page *to, struct page *from, int nr_pages)
+{
+ static struct dma_chan *copy_chan = NULL;
+ struct dma_device *device = NULL;
+ struct dma_async_tx_descriptor *tx = NULL;
+ dma_cookie_t cookie;
+ enum dma_ctrl_flags flags = 0;
+ struct dmaengine_unmap_data *unmap = NULL;
+ dma_cap_mask_t mask;
+ int ret_val = 0;
+
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ dmaengine_get();
+
+ copy_chan = dma_request_channel(mask, NULL, NULL);
+
+ if (!copy_chan) {
+ pr_err("%s: cannot get a channel\n", __func__);
+ ret_val = -1;
+ goto no_chan;
+ }
+
+ device = copy_chan->device;
+
+ if (!device) {
+ pr_err("%s: cannot get a device\n", __func__);
+ ret_val = -2;
+ goto release;
+ }
+
+ unmap = dmaengine_get_unmap_data(device->dev, 2, GFP_NOWAIT);
+
+ if (!unmap) {
+ pr_err("%s: cannot get unmap data\n", __func__);
+ ret_val = -3;
+ goto release;
+ }
+
+ unmap->to_cnt = 1;
+ unmap->addr[0] = dma_map_page(device->dev, from, 0, PAGE_SIZE*nr_pages,
+ DMA_TO_DEVICE);
+ unmap->from_cnt = 1;
+ unmap->addr[1] = dma_map_page(device->dev, to, 0, PAGE_SIZE*nr_pages,
+ DMA_FROM_DEVICE);
+ unmap->len = PAGE_SIZE*nr_pages;
+
+ tx = device->device_prep_dma_memcpy(copy_chan,
+ unmap->addr[1],
+ unmap->addr[0], unmap->len,
+ flags);
+
+ if (!tx) {
+ pr_err("%s: null tx descriptor\n", __func__);
+ ret_val = -4;
+ goto unmap_dma;
+ }
+
+ cookie = tx->tx_submit(tx);
+
+ if (dma_submit_error(cookie)) {
+ pr_err("%s: submission error\n", __func__);
+ ret_val = -5;
+ goto unmap_dma;
+ }
+
+ if (dma_sync_wait(copy_chan, cookie) != DMA_COMPLETE) {
+ pr_err("%s: dma does not complete properly\n", __func__);
+ ret_val = -6;
+ }
+
+unmap_dma:
+ dmaengine_unmap_put(unmap);
+release:
+ if (copy_chan) {
+ dma_release_channel(copy_chan);
+ }
+no_chan:
+ dmaengine_put();
+
+ return ret_val;
+}
+
+static int copy_page_dma_always(struct page *to, struct page *from, int nr_pages)
+{
+ struct dma_async_tx_descriptor *tx[NUM_AVAIL_DMA_CHAN] = {0};
+ dma_cookie_t cookie[NUM_AVAIL_DMA_CHAN];
+ enum dma_ctrl_flags flags[NUM_AVAIL_DMA_CHAN] = {0};
+ struct dmaengine_unmap_data *unmap[NUM_AVAIL_DMA_CHAN] = {0};
+ int ret_val = 0;
+ int total_available_chans = NUM_AVAIL_DMA_CHAN;
+ int i;
+ size_t page_offset;
+
+ for (i = 0; i < NUM_AVAIL_DMA_CHAN; ++i) {
+ if (!copy_chan[i]) {
+ total_available_chans = i;
+ }
+ }
+ if (total_available_chans != NUM_AVAIL_DMA_CHAN) {
+ pr_err("%d channels are missing", NUM_AVAIL_DMA_CHAN - total_available_chans);
+ }
+
+ total_available_chans = min_t(int, total_available_chans, limit_dma_chans);
+
+ /* round down to closest 2^x value */
+ total_available_chans = 1<<ilog2(total_available_chans);
+
+ if ((nr_pages != 1) && (nr_pages % total_available_chans != 0))
+ return -5;
+
+ for (i = 0; i < total_available_chans; ++i) {
+ unmap[i] = dmaengine_get_unmap_data(copy_dev[i]->dev, 2, GFP_NOWAIT);
+ if (!unmap[i]) {
+ pr_err("%s: no unmap data at chan %d\n", __func__, i);
+ ret_val = -3;
+ goto unmap_dma;
+ }
+ }
+
+ for (i = 0; i < total_available_chans; ++i) {
+ if (nr_pages == 1) {
+ page_offset = PAGE_SIZE / total_available_chans;
+
+ unmap[i]->to_cnt = 1;
+ unmap[i]->addr[0] = dma_map_page(copy_dev[i]->dev, from, page_offset*i,
+ page_offset,
+ DMA_TO_DEVICE);
+ unmap[i]->from_cnt = 1;
+ unmap[i]->addr[1] = dma_map_page(copy_dev[i]->dev, to, page_offset*i,
+ page_offset,
+ DMA_FROM_DEVICE);
+ unmap[i]->len = page_offset;
+ } else {
+ page_offset = nr_pages / total_available_chans;
+
+ unmap[i]->to_cnt = 1;
+ unmap[i]->addr[0] = dma_map_page(copy_dev[i]->dev,
+ from + page_offset*i,
+ 0,
+ PAGE_SIZE*page_offset,
+ DMA_TO_DEVICE);
+ unmap[i]->from_cnt = 1;
+ unmap[i]->addr[1] = dma_map_page(copy_dev[i]->dev,
+ to + page_offset*i,
+ 0,
+ PAGE_SIZE*page_offset,
+ DMA_FROM_DEVICE);
+ unmap[i]->len = PAGE_SIZE*page_offset;
+ }
+ }
+
+ for (i = 0; i < total_available_chans; ++i) {
+ tx[i] = copy_dev[i]->device_prep_dma_memcpy(copy_chan[i],
+ unmap[i]->addr[1],
+ unmap[i]->addr[0],
+ unmap[i]->len,
+ flags[i]);
+ if (!tx[i]) {
+ pr_err("%s: no tx descriptor at chan %d\n", __func__, i);
+ ret_val = -4;
+ goto unmap_dma;
+ }
+ }
+
+ for (i = 0; i < total_available_chans; ++i) {
+ cookie[i] = tx[i]->tx_submit(tx[i]);
+
+ if (dma_submit_error(cookie[i])) {
+ pr_err("%s: submission error at chan %d\n", __func__, i);
+ ret_val = -5;
+ goto unmap_dma;
+ }
+
+ dma_async_issue_pending(copy_chan[i]);
+ }
+
+ for (i = 0; i < total_available_chans; ++i) {
+ if (dma_sync_wait(copy_chan[i], cookie[i]) != DMA_COMPLETE) {
+ ret_val = -6;
+ pr_err("%s: dma does not complete at chan %d\n", __func__, i);
+ }
+ }
+
+unmap_dma:
+
+ for (i = 0; i < total_available_chans; ++i) {
+ if (unmap[i])
+ dmaengine_unmap_put(unmap[i]);
+ }
+
+ return ret_val;
+}
+
+int copy_page_dma(struct page *to, struct page *from, int nr_pages)
+{
+ BUG_ON(hpage_nr_pages(from) != nr_pages);
+ BUG_ON(hpage_nr_pages(to) != nr_pages);
+
+ if (!use_all_dma_chans) {
+ return copy_page_dma_once(to, from, nr_pages);
+ }
+
+ return copy_page_dma_always(to, from, nr_pages);
+}
--
2.7.4