diff options
author | Matthew Wilcox <matthew@wil.cx> | 2007-12-03 14:16:24 -0500 |
---|---|---|
committer | Matthew Wilcox <matthew@wil.cx> | 2007-12-04 10:39:58 -0500 |
commit | e34f44b3517fe545f7fd45a8c2f6ee1e5e4432d3 (patch) | |
tree | 826655b80622dee3cff9ddd0e9aa3ee9908627fe /mm | |
parent | a35a3455142976e3fffdf27027f3082cbaba6e8c (diff) |
pool: Improve memory usage for devices which can't cross boundaries
The previous implementation simply refused to allocate more than a
boundary's worth of data from an entire page. Some users didn't know
this, so specified things like SMP_CACHE_BYTES, not realising the
horrible waste of memory that this was. It's fairly easy to correct
this problem, just by ensuring we don't cross a boundary within a page.
This even helps drivers like EHCI (which can't cross a 4k boundary)
on machines with larger page sizes.
Signed-off-by: Matthew Wilcox <willy@linux.intel.com>
Acked-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'mm')
-rw-r--r-- | mm/dmapool.c | 36 |
1 files changed, 20 insertions, 16 deletions
diff --git a/mm/dmapool.c b/mm/dmapool.c index 72e7ece7ee9d..34aaac451a96 100644 --- a/mm/dmapool.c +++ b/mm/dmapool.c | |||
@@ -43,6 +43,7 @@ struct dma_pool { /* the pool */ | |||
43 | size_t size; | 43 | size_t size; |
44 | struct device *dev; | 44 | struct device *dev; |
45 | size_t allocation; | 45 | size_t allocation; |
46 | size_t boundary; | ||
46 | char name[32]; | 47 | char name[32]; |
47 | wait_queue_head_t waitq; | 48 | wait_queue_head_t waitq; |
48 | struct list_head pools; | 49 | struct list_head pools; |
@@ -107,7 +108,7 @@ static DEVICE_ATTR(pools, S_IRUGO, show_pools, NULL); | |||
107 | * @dev: device that will be doing the DMA | 108 | * @dev: device that will be doing the DMA |
108 | * @size: size of the blocks in this pool. | 109 | * @size: size of the blocks in this pool. |
109 | * @align: alignment requirement for blocks; must be a power of two | 110 | * @align: alignment requirement for blocks; must be a power of two |
110 | * @allocation: returned blocks won't cross this boundary (or zero) | 111 | * @boundary: returned blocks won't cross this power of two boundary |
111 | * Context: !in_interrupt() | 112 | * Context: !in_interrupt() |
112 | * | 113 | * |
113 | * Returns a dma allocation pool with the requested characteristics, or | 114 | * Returns a dma allocation pool with the requested characteristics, or |
@@ -117,15 +118,16 @@ static DEVICE_ATTR(pools, S_IRUGO, show_pools, NULL); | |||
117 | * cache flushing primitives. The actual size of blocks allocated may be | 118 | * cache flushing primitives. The actual size of blocks allocated may be |
118 | * larger than requested because of alignment. | 119 | * larger than requested because of alignment. |
119 | * | 120 | * |
120 | * If allocation is nonzero, objects returned from dma_pool_alloc() won't | 121 | * If @boundary is nonzero, objects returned from dma_pool_alloc() won't |
121 | * cross that size boundary. This is useful for devices which have | 122 | * cross that size boundary. This is useful for devices which have |
122 | * addressing restrictions on individual DMA transfers, such as not crossing | 123 | * addressing restrictions on individual DMA transfers, such as not crossing |
123 | * boundaries of 4KBytes. | 124 | * boundaries of 4KBytes. |
124 | */ | 125 | */ |
125 | struct dma_pool *dma_pool_create(const char *name, struct device *dev, | 126 | struct dma_pool *dma_pool_create(const char *name, struct device *dev, |
126 | size_t size, size_t align, size_t allocation) | 127 | size_t size, size_t align, size_t boundary) |
127 | { | 128 | { |
128 | struct dma_pool *retval; | 129 | struct dma_pool *retval; |
130 | size_t allocation; | ||
129 | 131 | ||
130 | if (align == 0) { | 132 | if (align == 0) { |
131 | align = 1; | 133 | align = 1; |
@@ -142,27 +144,26 @@ struct dma_pool *dma_pool_create(const char *name, struct device *dev, | |||
142 | if ((size % align) != 0) | 144 | if ((size % align) != 0) |
143 | size = ALIGN(size, align); | 145 | size = ALIGN(size, align); |
144 | 146 | ||
145 | if (allocation == 0) { | 147 | allocation = max_t(size_t, size, PAGE_SIZE); |
146 | if (PAGE_SIZE < size) | 148 | |
147 | allocation = size; | 149 | if (!boundary) { |
148 | else | 150 | boundary = allocation; |
149 | allocation = PAGE_SIZE; | 151 | } else if ((boundary < size) || (boundary & (boundary - 1))) { |
150 | /* FIXME: round up for less fragmentation */ | ||
151 | } else if (allocation < size) | ||
152 | return NULL; | 152 | return NULL; |
153 | } | ||
153 | 154 | ||
154 | if (! | 155 | retval = kmalloc_node(sizeof(*retval), GFP_KERNEL, dev_to_node(dev)); |
155 | (retval = | 156 | if (!retval) |
156 | kmalloc_node(sizeof *retval, GFP_KERNEL, dev_to_node(dev)))) | ||
157 | return retval; | 157 | return retval; |
158 | 158 | ||
159 | strlcpy(retval->name, name, sizeof retval->name); | 159 | strlcpy(retval->name, name, sizeof(retval->name)); |
160 | 160 | ||
161 | retval->dev = dev; | 161 | retval->dev = dev; |
162 | 162 | ||
163 | INIT_LIST_HEAD(&retval->page_list); | 163 | INIT_LIST_HEAD(&retval->page_list); |
164 | spin_lock_init(&retval->lock); | 164 | spin_lock_init(&retval->lock); |
165 | retval->size = size; | 165 | retval->size = size; |
166 | retval->boundary = boundary; | ||
166 | retval->allocation = allocation; | 167 | retval->allocation = allocation; |
167 | init_waitqueue_head(&retval->waitq); | 168 | init_waitqueue_head(&retval->waitq); |
168 | 169 | ||
@@ -192,11 +193,14 @@ EXPORT_SYMBOL(dma_pool_create); | |||
192 | static void pool_initialise_page(struct dma_pool *pool, struct dma_page *page) | 193 | static void pool_initialise_page(struct dma_pool *pool, struct dma_page *page) |
193 | { | 194 | { |
194 | unsigned int offset = 0; | 195 | unsigned int offset = 0; |
196 | unsigned int next_boundary = pool->boundary; | ||
195 | 197 | ||
196 | do { | 198 | do { |
197 | unsigned int next = offset + pool->size; | 199 | unsigned int next = offset + pool->size; |
198 | if (unlikely((next + pool->size) >= pool->allocation)) | 200 | if (unlikely((next + pool->size) >= next_boundary)) { |
199 | next = pool->allocation; | 201 | next = next_boundary; |
202 | next_boundary += pool->boundary; | ||
203 | } | ||
200 | *(int *)(page->vaddr + offset) = next; | 204 | *(int *)(page->vaddr + offset) = next; |
201 | offset = next; | 205 | offset = next; |
202 | } while (offset < pool->allocation); | 206 | } while (offset < pool->allocation); |