Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

MIPS: HIGHMEM DMA on noncoherent MIPS32 processors

[v4: Patch applies to linux-queue.git with kmap_atomic patches:
https://patchwork.kernel.org/patch/189932/
https://patchwork.kernel.org/patch/194552/
https://patchwork.kernel.org/patch/189912/ ]

The MIPS DMA coherency functions do not work properly (i.e. kernel oops)
when HIGHMEM pages are passed in as arguments. Use kmap_atomic() to
temporarily map high pages for cache maintenance operations.

Tested on a 2.6.36-rc7 1GB HIGHMEM SMP no-alias system.

Signed-off-by: Dezhong Diao <dediao@cisco.com>
Signed-off-by: Kevin Cernekee <cernekee@gmail.com>
Cc: Dezhong Diao <dediao@cisco.com>
Cc: David Daney <ddaney@caviumnetworks.com>
Cc: David VomLehn <dvomlehn@cisco.com>
Cc: Sergei Shtylyov <sshtylyov@mvista.com>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/1695/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>

authored by

Dezhong Diao and committed by
Ralf Baechle
e36863a5 d0be89f6

+68 -46
+68 -46
arch/mips/mm/dma-default.c
··· 15 15 #include <linux/scatterlist.h> 16 16 #include <linux/string.h> 17 17 #include <linux/gfp.h> 18 + #include <linux/highmem.h> 18 19 19 20 #include <asm/cache.h> 20 21 #include <asm/io.h> 21 22 22 23 #include <dma-coherence.h> 23 24 24 - static inline unsigned long dma_addr_to_virt(struct device *dev, 25 + static inline struct page *dma_addr_to_page(struct device *dev, 25 26 dma_addr_t dma_addr) 26 27 { 27 - unsigned long addr = plat_dma_addr_to_phys(dev, dma_addr); 28 - 29 - return (unsigned long)phys_to_virt(addr); 28 + return pfn_to_page( 29 + plat_dma_addr_to_phys(dev, dma_addr) >> PAGE_SHIFT); 30 30 } 31 31 32 32 /* ··· 148 148 free_pages(addr, get_order(size)); 149 149 } 150 150 151 - static inline void __dma_sync(unsigned long addr, size_t size, 151 + static inline void __dma_sync_virtual(void *addr, size_t size, 152 152 enum dma_data_direction direction) 153 153 { 154 154 switch (direction) { 155 155 case DMA_TO_DEVICE: 156 - dma_cache_wback(addr, size); 156 + dma_cache_wback((unsigned long)addr, size); 157 157 break; 158 158 159 159 case DMA_FROM_DEVICE: 160 - dma_cache_inv(addr, size); 160 + dma_cache_inv((unsigned long)addr, size); 161 161 break; 162 162 163 163 case DMA_BIDIRECTIONAL: 164 - dma_cache_wback_inv(addr, size); 164 + dma_cache_wback_inv((unsigned long)addr, size); 165 165 break; 166 166 167 167 default: ··· 169 169 } 170 170 } 171 171 172 + /* 173 + * A single sg entry may refer to multiple physically contiguous 174 + * pages. But we still need to process highmem pages individually. 175 + * If highmem is not configured then the bulk of this loop gets 176 + * optimized out. 177 + */ 178 + static inline void __dma_sync(struct page *page, 179 + unsigned long offset, size_t size, enum dma_data_direction direction) 180 + { 181 + size_t left = size; 182 + 183 + do { 184 + size_t len = left; 185 + 186 + if (PageHighMem(page)) { 187 + void *addr; 188 + 189 + if (offset + len > PAGE_SIZE) { 190 + if (offset >= PAGE_SIZE) { 191 + page += offset >> PAGE_SHIFT; 192 + offset &= ~PAGE_MASK; 193 + } 194 + len = PAGE_SIZE - offset; 195 + } 196 + 197 + addr = kmap_atomic(page); 198 + __dma_sync_virtual(addr + offset, len, direction); 199 + kunmap_atomic(addr); 200 + } else 201 + __dma_sync_virtual(page_address(page) + offset, 202 + size, direction); 203 + offset = 0; 204 + page++; 205 + left -= len; 206 + } while (left); 207 + } 208 + 172 209 static void mips_dma_unmap_page(struct device *dev, dma_addr_t dma_addr, 173 210 size_t size, enum dma_data_direction direction, struct dma_attrs *attrs) 174 211 { 175 212 if (cpu_is_noncoherent_r10000(dev)) 176 - __dma_sync(dma_addr_to_virt(dev, dma_addr), size, 177 - direction); 213 + __dma_sync(dma_addr_to_page(dev, dma_addr), 214 + dma_addr & ~PAGE_MASK, size, direction); 178 215 179 216 plat_unmap_dma_mem(dev, dma_addr, size, direction); 180 217 } ··· 222 185 int i; 223 186 224 187 for (i = 0; i < nents; i++, sg++) { 225 - unsigned long addr; 226 - 227 - addr = (unsigned long) sg_virt(sg); 228 - if (!plat_device_is_coherent(dev) && addr) 229 - __dma_sync(addr, sg->length, direction); 230 - sg->dma_address = plat_map_dma_mem(dev, 231 - (void *)addr, sg->length); 188 + if (!plat_device_is_coherent(dev)) 189 + __dma_sync(sg_page(sg), sg->offset, sg->length, 190 + direction); 191 + sg->dma_address = plat_map_dma_mem_page(dev, sg_page(sg)) + 192 + sg->offset; 232 193 } 233 194 234 195 return nents; ··· 236 201 unsigned long offset, size_t size, enum dma_data_direction direction, 237 202 struct dma_attrs *attrs) 238 203 { 239 - unsigned long addr; 240 - 241 - addr = (unsigned long) page_address(page) + offset; 242 - 243 204 if (!plat_device_is_coherent(dev)) 244 - __dma_sync(addr, size, direction); 205 + __dma_sync(page, offset, size, direction); 245 206 246 - return plat_map_dma_mem(dev, (void *)addr, size); 207 + return plat_map_dma_mem_page(dev, page) + offset; 247 208 } 248 209 249 210 static void mips_dma_unmap_sg(struct device *dev, struct scatterlist *sg, 250 211 int nhwentries, enum dma_data_direction direction, 251 212 struct dma_attrs *attrs) 252 213 { 253 - unsigned long addr; 254 214 int i; 255 215 256 216 for (i = 0; i < nhwentries; i++, sg++) { 257 217 if (!plat_device_is_coherent(dev) && 258 - direction != DMA_TO_DEVICE) { 259 - addr = (unsigned long) sg_virt(sg); 260 - if (addr) 261 - __dma_sync(addr, sg->length, direction); 262 - } 218 + direction != DMA_TO_DEVICE) 219 + __dma_sync(sg_page(sg), sg->offset, sg->length, 220 + direction); 263 221 plat_unmap_dma_mem(dev, sg->dma_address, sg->length, direction); 264 222 } 265 223 } ··· 260 232 static void mips_dma_sync_single_for_cpu(struct device *dev, 261 233 dma_addr_t dma_handle, size_t size, enum dma_data_direction direction) 262 234 { 263 - if (cpu_is_noncoherent_r10000(dev)) { 264 - unsigned long addr; 265 - 266 - addr = dma_addr_to_virt(dev, dma_handle); 267 - __dma_sync(addr, size, direction); 268 - } 235 + if (cpu_is_noncoherent_r10000(dev)) 236 + __dma_sync(dma_addr_to_page(dev, dma_handle), 237 + dma_handle & ~PAGE_MASK, size, direction); 269 238 } 270 239 271 240 static void mips_dma_sync_single_for_device(struct device *dev, 272 241 dma_addr_t dma_handle, size_t size, enum dma_data_direction direction) 273 242 { 274 243 plat_extra_sync_for_device(dev); 275 - if (!plat_device_is_coherent(dev)) { 276 - unsigned long addr; 277 - 278 - addr = dma_addr_to_virt(dev, dma_handle); 279 - __dma_sync(addr, size, direction); 280 - } 244 + if (!plat_device_is_coherent(dev)) 245 + __dma_sync(dma_addr_to_page(dev, dma_handle), 246 + dma_handle & ~PAGE_MASK, size, direction); 281 247 } 282 248 283 249 static void mips_dma_sync_sg_for_cpu(struct device *dev, ··· 282 260 /* Make sure that gcc doesn't leave the empty loop body. */ 283 261 for (i = 0; i < nelems; i++, sg++) { 284 262 if (cpu_is_noncoherent_r10000(dev)) 285 - __dma_sync((unsigned long)page_address(sg_page(sg)), 286 - sg->length, direction); 263 + __dma_sync(sg_page(sg), sg->offset, sg->length, 264 + direction); 287 265 } 288 266 } 289 267 ··· 295 273 /* Make sure that gcc doesn't leave the empty loop body. */ 296 274 for (i = 0; i < nelems; i++, sg++) { 297 275 if (!plat_device_is_coherent(dev)) 298 - __dma_sync((unsigned long)page_address(sg_page(sg)), 299 - sg->length, direction); 276 + __dma_sync(sg_page(sg), sg->offset, sg->length, 277 + direction); 300 278 } 301 279 } 302 280 ··· 317 295 318 296 plat_extra_sync_for_device(dev); 319 297 if (!plat_device_is_coherent(dev)) 320 - __dma_sync((unsigned long)vaddr, size, direction); 298 + __dma_sync_virtual(vaddr, size, direction); 321 299 } 322 300 323 301 EXPORT_SYMBOL(dma_cache_sync);