diff options
| author | Dan Williams <dan.j.williams@intel.com> | 2013-10-18 13:35:24 -0400 |
|---|---|---|
| committer | Dan Williams <dan.j.williams@intel.com> | 2013-11-13 19:25:06 -0500 |
| commit | 45c463ae924c62af4aa64ded1ca831f334a1db65 (patch) | |
| tree | 1087e0630ec29998f15a7fc2a7dbec2a3ff35e80 | |
| parent | d38a8c622a1b382336c3e152c6caf4e11d1f1b2a (diff) | |
dmaengine: reference counted unmap data
Hang a common 'unmap' object off of dma descriptors for the purpose of
providing a unified unmapping interface. The lifetime of a mapping may
span multiple descriptors, so these unmap objects are reference counted
by related descriptor.
Cc: Vinod Koul <vinod.koul@intel.com>
Cc: Tomasz Figa <t.figa@samsung.com>
Cc: Dave Jiang <dave.jiang@intel.com>
[bzolnier: fix IS_ENABLED() check]
[bzolnier: fix release ordering in dmaengine_destroy_unmap_pool()]
[bzolnier: fix check for success in dmaengine_init_unmap_pool()]
[bzolnier: use mempool_free() instead of kmem_cache_free()]
[bzolnier: add missing unmap->len initializations]
[bzolnier: add __init tag to dmaengine_init_unmap_pool()]
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
[djbw: move DMAENGINE=n support to this patch for async_tx]
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
| -rw-r--r-- | drivers/dma/dmaengine.c | 156 | ||||
| -rw-r--r-- | include/linux/dmaengine.h | 3 |
2 files changed, 150 insertions, 9 deletions
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index bbc89df6bc56..e721a1caff7f 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c | |||
| @@ -65,6 +65,7 @@ | |||
| 65 | #include <linux/acpi.h> | 65 | #include <linux/acpi.h> |
| 66 | #include <linux/acpi_dma.h> | 66 | #include <linux/acpi_dma.h> |
| 67 | #include <linux/of_dma.h> | 67 | #include <linux/of_dma.h> |
| 68 | #include <linux/mempool.h> | ||
| 68 | 69 | ||
| 69 | static DEFINE_MUTEX(dma_list_mutex); | 70 | static DEFINE_MUTEX(dma_list_mutex); |
| 70 | static DEFINE_IDR(dma_idr); | 71 | static DEFINE_IDR(dma_idr); |
| @@ -901,6 +902,129 @@ void dma_async_device_unregister(struct dma_device *device) | |||
| 901 | } | 902 | } |
| 902 | EXPORT_SYMBOL(dma_async_device_unregister); | 903 | EXPORT_SYMBOL(dma_async_device_unregister); |
| 903 | 904 | ||
| 905 | struct dmaengine_unmap_pool { | ||
| 906 | struct kmem_cache *cache; | ||
| 907 | const char *name; | ||
| 908 | mempool_t *pool; | ||
| 909 | size_t size; | ||
| 910 | }; | ||
| 911 | |||
| 912 | #define __UNMAP_POOL(x) { .size = x, .name = "dmaengine-unmap-" __stringify(x) } | ||
| 913 | static struct dmaengine_unmap_pool unmap_pool[] = { | ||
| 914 | __UNMAP_POOL(2), | ||
| 915 | #if IS_ENABLED(CONFIG_ASYNC_TX_DMA) | ||
| 916 | __UNMAP_POOL(16), | ||
| 917 | __UNMAP_POOL(128), | ||
| 918 | __UNMAP_POOL(256), | ||
| 919 | #endif | ||
| 920 | }; | ||
| 921 | |||
| 922 | static struct dmaengine_unmap_pool *__get_unmap_pool(int nr) | ||
| 923 | { | ||
| 924 | int order = get_count_order(nr); | ||
| 925 | |||
| 926 | switch (order) { | ||
| 927 | case 0 ... 1: | ||
| 928 | return &unmap_pool[0]; | ||
| 929 | case 2 ... 4: | ||
| 930 | return &unmap_pool[1]; | ||
| 931 | case 5 ... 7: | ||
| 932 | return &unmap_pool[2]; | ||
| 933 | case 8: | ||
| 934 | return &unmap_pool[3]; | ||
| 935 | default: | ||
| 936 | BUG(); | ||
| 937 | return NULL; | ||
| 938 | } | ||
| 939 | } | ||
| 940 | |||
| 941 | static void dmaengine_unmap(struct kref *kref) | ||
| 942 | { | ||
| 943 | struct dmaengine_unmap_data *unmap = container_of(kref, typeof(*unmap), kref); | ||
| 944 | struct device *dev = unmap->dev; | ||
| 945 | int cnt, i; | ||
| 946 | |||
| 947 | cnt = unmap->to_cnt; | ||
| 948 | for (i = 0; i < cnt; i++) | ||
| 949 | dma_unmap_page(dev, unmap->addr[i], unmap->len, | ||
| 950 | DMA_TO_DEVICE); | ||
| 951 | cnt += unmap->from_cnt; | ||
| 952 | for (; i < cnt; i++) | ||
| 953 | dma_unmap_page(dev, unmap->addr[i], unmap->len, | ||
| 954 | DMA_FROM_DEVICE); | ||
| 955 | cnt += unmap->bidi_cnt; | ||
| 956 | for (; i < cnt; i++) | ||
| 957 | dma_unmap_page(dev, unmap->addr[i], unmap->len, | ||
| 958 | DMA_BIDIRECTIONAL); | ||
| 959 | mempool_free(unmap, __get_unmap_pool(cnt)->pool); | ||
| 960 | } | ||
| 961 | |||
| 962 | void dmaengine_unmap_put(struct dmaengine_unmap_data *unmap) | ||
| 963 | { | ||
| 964 | if (unmap) | ||
| 965 | kref_put(&unmap->kref, dmaengine_unmap); | ||
| 966 | } | ||
| 967 | EXPORT_SYMBOL_GPL(dmaengine_unmap_put); | ||
| 968 | |||
| 969 | static void dmaengine_destroy_unmap_pool(void) | ||
| 970 | { | ||
| 971 | int i; | ||
| 972 | |||
| 973 | for (i = 0; i < ARRAY_SIZE(unmap_pool); i++) { | ||
| 974 | struct dmaengine_unmap_pool *p = &unmap_pool[i]; | ||
| 975 | |||
| 976 | if (p->pool) | ||
| 977 | mempool_destroy(p->pool); | ||
| 978 | p->pool = NULL; | ||
| 979 | if (p->cache) | ||
| 980 | kmem_cache_destroy(p->cache); | ||
| 981 | p->cache = NULL; | ||
| 982 | } | ||
| 983 | } | ||
| 984 | |||
| 985 | static int __init dmaengine_init_unmap_pool(void) | ||
| 986 | { | ||
| 987 | int i; | ||
| 988 | |||
| 989 | for (i = 0; i < ARRAY_SIZE(unmap_pool); i++) { | ||
| 990 | struct dmaengine_unmap_pool *p = &unmap_pool[i]; | ||
| 991 | size_t size; | ||
| 992 | |||
| 993 | size = sizeof(struct dmaengine_unmap_data) + | ||
| 994 | sizeof(dma_addr_t) * p->size; | ||
| 995 | |||
| 996 | p->cache = kmem_cache_create(p->name, size, 0, | ||
| 997 | SLAB_HWCACHE_ALIGN, NULL); | ||
| 998 | if (!p->cache) | ||
| 999 | break; | ||
| 1000 | p->pool = mempool_create_slab_pool(1, p->cache); | ||
| 1001 | if (!p->pool) | ||
| 1002 | break; | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | if (i == ARRAY_SIZE(unmap_pool)) | ||
| 1006 | return 0; | ||
| 1007 | |||
| 1008 | dmaengine_destroy_unmap_pool(); | ||
| 1009 | return -ENOMEM; | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | static struct dmaengine_unmap_data * | ||
| 1013 | dmaengine_get_unmap_data(struct device *dev, int nr, gfp_t flags) | ||
| 1014 | { | ||
| 1015 | struct dmaengine_unmap_data *unmap; | ||
| 1016 | |||
| 1017 | unmap = mempool_alloc(__get_unmap_pool(nr)->pool, flags); | ||
| 1018 | if (!unmap) | ||
| 1019 | return NULL; | ||
| 1020 | |||
| 1021 | memset(unmap, 0, sizeof(*unmap)); | ||
| 1022 | kref_init(&unmap->kref); | ||
| 1023 | unmap->dev = dev; | ||
| 1024 | |||
| 1025 | return unmap; | ||
| 1026 | } | ||
| 1027 | |||
| 904 | /** | 1028 | /** |
| 905 | * dma_async_memcpy_pg_to_pg - offloaded copy from page to page | 1029 | * dma_async_memcpy_pg_to_pg - offloaded copy from page to page |
| 906 | * @chan: DMA channel to offload copy to | 1030 | * @chan: DMA channel to offload copy to |
| @@ -922,24 +1046,34 @@ dma_async_memcpy_pg_to_pg(struct dma_chan *chan, struct page *dest_pg, | |||
| 922 | { | 1046 | { |
| 923 | struct dma_device *dev = chan->device; | 1047 | struct dma_device *dev = chan->device; |
| 924 | struct dma_async_tx_descriptor *tx; | 1048 | struct dma_async_tx_descriptor *tx; |
| 925 | dma_addr_t dma_dest, dma_src; | 1049 | struct dmaengine_unmap_data *unmap; |
| 926 | dma_cookie_t cookie; | 1050 | dma_cookie_t cookie; |
| 927 | unsigned long flags; | 1051 | unsigned long flags; |
| 928 | 1052 | ||
| 929 | dma_src = dma_map_page(dev->dev, src_pg, src_off, len, DMA_TO_DEVICE); | 1053 | unmap = dmaengine_get_unmap_data(dev->dev, 2, GFP_NOIO); |
| 930 | dma_dest = dma_map_page(dev->dev, dest_pg, dest_off, len, | 1054 | if (!unmap) |
| 931 | DMA_FROM_DEVICE); | 1055 | return -ENOMEM; |
| 932 | flags = DMA_CTRL_ACK; | 1056 | |
| 933 | tx = dev->device_prep_dma_memcpy(chan, dma_dest, dma_src, len, flags); | 1057 | unmap->to_cnt = 1; |
| 1058 | unmap->from_cnt = 1; | ||
| 1059 | unmap->addr[0] = dma_map_page(dev->dev, src_pg, src_off, len, | ||
| 1060 | DMA_TO_DEVICE); | ||
| 1061 | unmap->addr[1] = dma_map_page(dev->dev, dest_pg, dest_off, len, | ||
| 1062 | DMA_FROM_DEVICE); | ||
| 1063 | unmap->len = len; | ||
| 1064 | flags = DMA_CTRL_ACK | DMA_COMPL_SKIP_SRC_UNMAP | | ||
| 1065 | DMA_COMPL_SKIP_DEST_UNMAP; | ||
| 1066 | tx = dev->device_prep_dma_memcpy(chan, unmap->addr[1], unmap->addr[0], | ||
| 1067 | len, flags); | ||
| 934 | 1068 | ||
| 935 | if (!tx) { | 1069 | if (!tx) { |
| 936 | dma_unmap_page(dev->dev, dma_src, len, DMA_TO_DEVICE); | 1070 | dmaengine_unmap_put(unmap); |
| 937 | dma_unmap_page(dev->dev, dma_dest, len, DMA_FROM_DEVICE); | ||
| 938 | return -ENOMEM; | 1071 | return -ENOMEM; |
| 939 | } | 1072 | } |
| 940 | 1073 | ||
| 941 | tx->callback = NULL; | 1074 | dma_set_unmap(tx, unmap); |
| 942 | cookie = tx->tx_submit(tx); | 1075 | cookie = tx->tx_submit(tx); |
| 1076 | dmaengine_unmap_put(unmap); | ||
| 943 | 1077 | ||
| 944 | preempt_disable(); | 1078 | preempt_disable(); |
| 945 | __this_cpu_add(chan->local->bytes_transferred, len); | 1079 | __this_cpu_add(chan->local->bytes_transferred, len); |
| @@ -1069,6 +1203,10 @@ EXPORT_SYMBOL_GPL(dma_run_dependencies); | |||
| 1069 | 1203 | ||
| 1070 | |||
