瀏覽代碼

[ARM] dma: fix dmabounce dma_sync_xxx() implementations

The dmabounce dma_sync_xxx() implementation have been broken for
quite some time; they all copy data between the DMA buffer and
the CPU visible buffer no irrespective of the change of ownership.
(IOW, a DMA_FROM_DEVICE mapping copies data from the DMA buffer
to the CPU buffer during a call to dma_sync_single_for_device().)

Fix it by getting rid of sync_single(), moving the contents into
the recently created dmabounce_sync_for_xxx() functions and adjusting
appropriately.

This also makes it possible to properly support the DMA range sync
functions.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Russell King 16 年之前
父節點
當前提交
125ab12acf
共有 2 個文件被更改,包括 69 次插入101 次删除
  1. 58 86
      arch/arm/common/dmabounce.c
  2. 11 15
      arch/arm/include/asm/dma-mapping.h

+ 58 - 86
arch/arm/common/dmabounce.c

@@ -205,6 +205,21 @@ free_safe_buffer(struct dmabounce_device_info *device_info, struct safe_buffer *
 
 
 /* ************************************************** */
 /* ************************************************** */
 
 
+static struct safe_buffer *find_safe_buffer_dev(struct device *dev,
+		dma_addr_t dma_addr, const char *where)
+{
+	if (!dev || !dev->archdata.dmabounce)
+		return NULL;
+	if (dma_mapping_error(dev, dma_addr)) {
+		if (dev)
+			dev_err(dev, "Trying to %s invalid mapping\n", where);
+		else
+			pr_err("unknown device: Trying to %s invalid mapping\n", where);
+		return NULL;
+	}
+	return find_safe_buffer(dev->archdata.dmabounce, dma_addr);
+}
+
 static inline dma_addr_t
 static inline dma_addr_t
 map_single(struct device *dev, void *ptr, size_t size,
 map_single(struct device *dev, void *ptr, size_t size,
 		enum dma_data_direction dir)
 		enum dma_data_direction dir)
@@ -274,19 +289,7 @@ static inline void
 unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
 unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
 		enum dma_data_direction dir)
 		enum dma_data_direction dir)
 {
 {
-	struct dmabounce_device_info *device_info = dev->archdata.dmabounce;
-	struct safe_buffer *buf = NULL;
-
-	/*
-	 * Trying to unmap an invalid mapping
-	 */
-	if (dma_mapping_error(dev, dma_addr)) {
-		dev_err(dev, "Trying to unmap invalid mapping\n");
-		return;
-	}
-
-	if (device_info)
-		buf = find_safe_buffer(device_info, dma_addr);
+	struct safe_buffer *buf = find_safe_buffer_dev(dev, dma_addr, "unmap");
 
 
 	if (buf) {
 	if (buf) {
 		BUG_ON(buf->size != size);
 		BUG_ON(buf->size != size);
@@ -296,7 +299,7 @@ unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
 			__func__, buf->ptr, virt_to_dma(dev, buf->ptr),
 			__func__, buf->ptr, virt_to_dma(dev, buf->ptr),
 			buf->safe, buf->safe_dma_addr);
 			buf->safe, buf->safe_dma_addr);
 
 
-		DO_STATS ( device_info->bounce_count++ );
+		DO_STATS(dev->archdata.dmabounce->bounce_count++);
 
 
 		if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL) {
 		if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL) {
 			void *ptr = buf->ptr;
 			void *ptr = buf->ptr;
@@ -317,74 +320,7 @@ unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
 			dmac_clean_range(ptr, ptr + size);
 			dmac_clean_range(ptr, ptr + size);
 			outer_clean_range(__pa(ptr), __pa(ptr) + size);
 			outer_clean_range(__pa(ptr), __pa(ptr) + size);
 		}
 		}
-		free_safe_buffer(device_info, buf);
-	}
-}
-
-static int sync_single(struct device *dev, dma_addr_t dma_addr, size_t size,
-			enum dma_data_direction dir)
-{
-	struct dmabounce_device_info *device_info = dev->archdata.dmabounce;
-	struct safe_buffer *buf = NULL;
-
-	if (device_info)
-		buf = find_safe_buffer(device_info, dma_addr);
-
-	if (buf) {
-		/*
-		 * Both of these checks from original code need to be
-		 * commented out b/c some drivers rely on the following:
-		 *
-		 * 1) Drivers may map a large chunk of memory into DMA space
-		 *    but only sync a small portion of it. Good example is
-		 *    allocating a large buffer, mapping it, and then
-		 *    breaking it up into small descriptors. No point
-		 *    in syncing the whole buffer if you only have to
-		 *    touch one descriptor.
-		 *
-		 * 2) Buffers that are mapped as DMA_BIDIRECTIONAL are
-		 *    usually only synced in one dir at a time.
-		 *
-		 * See drivers/net/eepro100.c for examples of both cases.
-		 *
-		 * -ds
-		 *
-		 * BUG_ON(buf->size != size);
-		 * BUG_ON(buf->direction != dir);
-		 */
-
-		dev_dbg(dev,
-			"%s: unsafe buffer %p (dma=%#x) mapped to %p (dma=%#x)\n",
-			__func__, buf->ptr, virt_to_dma(dev, buf->ptr),
-			buf->safe, buf->safe_dma_addr);
-
-		DO_STATS ( device_info->bounce_count++ );
-
-		switch (dir) {
-		case DMA_FROM_DEVICE:
-			dev_dbg(dev,
-				"%s: copy back safe %p to unsafe %p size %d\n",
-				__func__, buf->safe, buf->ptr, size);
-			memcpy(buf->ptr, buf->safe, size);
-			break;
-		case DMA_TO_DEVICE:
-			dev_dbg(dev,
-				"%s: copy out unsafe %p to safe %p, size %d\n",
-				__func__,buf->ptr, buf->safe, size);
-			memcpy(buf->safe, buf->ptr, size);
-			break;
-		case DMA_BIDIRECTIONAL:
-			BUG();	/* is this allowed?  what does it mean? */
-		default:
-			BUG();
-		}
-		/*
-		 * No need to sync the safe buffer - it was allocated
-		 * via the coherent allocators.
-		 */
-		return 0;
-	} else {
-		return 1;
+		free_safe_buffer(dev->archdata.dmabounce, buf);
 	}
 	}
 }
 }
 
 
@@ -447,18 +383,54 @@ dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
 int dmabounce_sync_for_cpu(struct device *dev, dma_addr_t addr,
 int dmabounce_sync_for_cpu(struct device *dev, dma_addr_t addr,
 		unsigned long off, size_t sz, enum dma_data_direction dir)
 		unsigned long off, size_t sz, enum dma_data_direction dir)
 {
 {
-	dev_dbg(dev, "%s(dma=%#lx,off=%#lx,sz=%zx,dir=%x)\n",
+	struct safe_buffer *buf;
+
+	dev_dbg(dev, "%s(dma=%#x,off=%#lx,sz=%zx,dir=%x)\n",
 		__func__, addr, off, sz, dir);
 		__func__, addr, off, sz, dir);
-	return sync_single(dev, addr, off + sz, dir);
+
+	buf = find_safe_buffer_dev(dev, addr, __func__);
+	if (!buf)
+		return 1;
+
+	dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x) mapped to %p (dma=%#x)\n",
+		__func__, buf->ptr, virt_to_dma(dev, buf->ptr),
+		buf->safe, buf->safe_dma_addr);
+
+	DO_STATS(dev->archdata.dmabounce->bounce_count++);
+
+	if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL) {
+		dev_dbg(dev, "%s: copy back safe %p to unsafe %p size %d\n",
+			__func__, buf->safe + off, buf->ptr + off, sz);
+		memcpy(buf->ptr + off, buf->safe + off, sz);
+	}
+	return 0;
 }
 }
 EXPORT_SYMBOL(dmabounce_sync_for_cpu);
 EXPORT_SYMBOL(dmabounce_sync_for_cpu);
 
 
 int dmabounce_sync_for_device(struct device *dev, dma_addr_t addr,
 int dmabounce_sync_for_device(struct device *dev, dma_addr_t addr,
 		unsigned long off, size_t sz, enum dma_data_direction dir)
 		unsigned long off, size_t sz, enum dma_data_direction dir)
 {
 {
-	dev_dbg(dev, "%s(dma=%#lx,off=%#lx,sz=%zx,dir=%x)\n",
+	struct safe_buffer *buf;
+
+	dev_dbg(dev, "%s(dma=%#x,off=%#lx,sz=%zx,dir=%x)\n",
 		__func__, addr, off, sz, dir);
 		__func__, addr, off, sz, dir);
-	return sync_single(dev, addr, off + sz, dir);
+
+	buf = find_safe_buffer_dev(dev, addr, __func__);
+	if (!buf)
+		return 1;
+
+	dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x) mapped to %p (dma=%#x)\n",
+		__func__, buf->ptr, virt_to_dma(dev, buf->ptr),
+		buf->safe, buf->safe_dma_addr);
+
+	DO_STATS(dev->archdata.dmabounce->bounce_count++);
+
+	if (dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL) {
+		dev_dbg(dev, "%s: copy out unsafe %p to safe %p, size %d\n",
+			__func__,buf->ptr + off, buf->safe + off, sz);
+		memcpy(buf->safe + off, buf->ptr + off, sz);
+	}
+	return 0;
 }
 }
 EXPORT_SYMBOL(dmabounce_sync_for_device);
 EXPORT_SYMBOL(dmabounce_sync_for_device);
 
 

+ 11 - 15
arch/arm/include/asm/dma-mapping.h

@@ -241,6 +241,15 @@ extern void dmabounce_unregister_dev(struct device *);
  */
  */
 extern int dma_needs_bounce(struct device*, dma_addr_t, size_t);
 extern int dma_needs_bounce(struct device*, dma_addr_t, size_t);
 
 
+/*
+ * The DMA API, implemented by dmabounce.c.  See below for descriptions.
+ */
+extern dma_addr_t dma_map_single(struct device *,void *, size_t, enum dma_data_direction);
+extern dma_addr_t dma_map_page(struct device *dev, struct page *page,
+			unsigned long offset, size_t size,
+			enum dma_data_direction dir);
+extern void dma_unmap_single(struct device *, dma_addr_t, size_t, enum dma_data_direction);
+
 /*
 /*
  * Private functions
  * Private functions
  */
  */
@@ -251,7 +260,6 @@ int dmabounce_sync_for_device(struct device *, dma_addr_t, unsigned long,
 #else
 #else
 #define dmabounce_sync_for_cpu(dev,dma,off,sz,dir)	(1)
 #define dmabounce_sync_for_cpu(dev,dma,off,sz,dir)	(1)
 #define dmabounce_sync_for_device(dev,dma,off,sz,dir)	(1)
 #define dmabounce_sync_for_device(dev,dma,off,sz,dir)	(1)
-#endif /* CONFIG_DMABOUNCE */
 
 
 
 
 /**
 /**
@@ -268,7 +276,6 @@ int dmabounce_sync_for_device(struct device *, dma_addr_t, unsigned long,
  * can regain ownership by calling dma_unmap_single() or
  * can regain ownership by calling dma_unmap_single() or
  * dma_sync_single_for_cpu().
  * dma_sync_single_for_cpu().
  */
  */
-#ifndef CONFIG_DMABOUNCE
 static inline dma_addr_t
 static inline dma_addr_t
 dma_map_single(struct device *dev, void *cpu_addr, size_t size,
 dma_map_single(struct device *dev, void *cpu_addr, size_t size,
 	       enum dma_data_direction dir)
 	       enum dma_data_direction dir)
@@ -278,9 +285,7 @@ dma_map_single(struct device *dev, void *cpu_addr, size_t size,
 
 
 	return virt_to_dma(dev, cpu_addr);
 	return virt_to_dma(dev, cpu_addr);
 }
 }
-#else
-extern dma_addr_t dma_map_single(struct device *,void *, size_t, enum dma_data_direction);
-#endif
+
 
 
 /**
 /**
  * dma_map_page - map a portion of a page for streaming DMA
  * dma_map_page - map a portion of a page for streaming DMA
@@ -297,7 +302,6 @@ extern dma_addr_t dma_map_single(struct device *,void *, size_t, enum dma_data_d
  * can regain ownership by calling dma_unmap_page() or
  * can regain ownership by calling dma_unmap_page() or
  * dma_sync_single_for_cpu().
  * dma_sync_single_for_cpu().
  */
  */
-#ifndef CONFIG_DMABOUNCE
 static inline dma_addr_t
 static inline dma_addr_t
 dma_map_page(struct device *dev, struct page *page,
 dma_map_page(struct device *dev, struct page *page,
 	     unsigned long offset, size_t size,
 	     unsigned long offset, size_t size,
@@ -308,11 +312,6 @@ dma_map_page(struct device *dev, struct page *page,
 
 
 	return page_to_dma(dev, page) + offset;
 	return page_to_dma(dev, page) + offset;
 }
 }
-#else
-extern dma_addr_t dma_map_page(struct device *dev, struct page *page,
-			unsigned long offset, size_t size,
-			enum dma_data_direction dir);
-#endif
 
 
 /**
 /**
  * dma_unmap_single - unmap a single buffer previously mapped
  * dma_unmap_single - unmap a single buffer previously mapped
@@ -328,16 +327,13 @@ extern dma_addr_t dma_map_page(struct device *dev, struct page *page,
  * After this call, reads by the CPU to the buffer are guaranteed to see
  * After this call, reads by the CPU to the buffer are guaranteed to see
  * whatever the device wrote there.
  * whatever the device wrote there.
  */
  */
-#ifndef CONFIG_DMABOUNCE
 static inline void
 static inline void
 dma_unmap_single(struct device *dev, dma_addr_t handle, size_t size,
 dma_unmap_single(struct device *dev, dma_addr_t handle, size_t size,
 		 enum dma_data_direction dir)
 		 enum dma_data_direction dir)
 {
 {
 	/* nothing to do */
 	/* nothing to do */
 }
 }
-#else
-extern void dma_unmap_single(struct device *, dma_addr_t, size_t, enum dma_data_direction);
-#endif
+#endif /* CONFIG_DMABOUNCE */
 
 
 /**
 /**
  * dma_unmap_page - unmap a buffer previously mapped through dma_map_page()
  * dma_unmap_page - unmap a buffer previously mapped through dma_map_page()