[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

AmigaOne 2.6.x Linux kernel port



Hello!

First sorry for the long mail! :-)

I started another attemp to get the Linux kernel working correctly on the
AmigaOne with it's famous (and buggy) ArticiaS northbridge. There were a lot
of people that tried this before, but I think all of them didn't address the
real problem. The problem is that the ArticiaS northbrige doesn't support
cache coherency correctly. The previous developers of the AmigaOne port just
limited the transfer size of some DMA transfers, for example in the IDE
driver, or added a specific cache flush to the driver code to avoid data
corruption. This didn't work correctly. In the 2.6.x kernel series data
corruption appeared also with other drivers that use DMA (e.g. ethernet),
therefore I thought to fix the problem in the DMA layer
(include/asm-ppc/dma-mapping.h) for all drivers. I tried to change the code
to handle the AmigaOne platform as a non cache coherent architecture and to
do all necessary cache flushes in software. AFAIK also the developers of
AmigaOS4 have worked around the bugs of the ArticiaS in this way. Well,
first it seemed that I had succeeded and Linux was working correctly with
DMA (tested it by calculating the checksum of a 700MB ISO file and with DMA
activated for the IDE drives), but after porting my patches from kernel
2.6.12.6 to 2.6.14.2 it became apparent that the patches don't work
correctly yet (but better as the other attempts ;-). Also the kernel
reported a lot of "lost interrupts" messages and errors about expired "DMA
timers".
Well, as a complete kernel newbie (never hacked in the kernel before) and
because I don't fully understand the code for DMA on non cache coherent
platform, I have to seek advice now.

I included all relevant patches for the AmigaOne below. My patches are for
"include/asm-ppc/dma-mapping.h" and
"arch/ppc/kernel/amigaone_dma-mapping.c". The other patches were either made
by some OS4 developers (Ross Vumbaca) or Ken Moffat (ported the patches to
2.6.x). If somebody wants to take a look at all the patches for the 2.6.x
kernel, just write me.

The code in amigaone_dma-mapping.c was directly copied from dma-mapping.c
(in arch/ppc/kernel/). I think the main problem lies in dma-mapping.h, were
I just added some cache flush functions to the DMA allocation functions for
coherent architectures (dma_alloc_coherent, dma_free_coherent). So can
anyone explain me, what the differences between the DMA allocation functions
for coherent and non coherent architectures are? How can the non coherent
DMA memory allocation functions be adapted for the AmigaOne?

I have another question regarding the cputable.c file. Ken Moffat commented
out CPU_FTR_NEED_COHERENT for the AmigaOne platform. I tried to find
information about why this define is needed, but couldn't find anything.
Does anybody know, how this affects the functionality of the kernel on PPC
745x CPUs?

I'm glad for any useful input! Thanks in advance!

regards,

Gerhard

Patches:

diff -Naurp linux-2.6.14.2/arch/ppc/kernel/amigaone_dma-mapping.c
linux-2.6.14.2-a1-1/arch/ppc/kernel/amigaone_dma-mapping.c
--- linux-2.6.14.2/arch/ppc/kernel/amigaone_dma-mapping.c	1970-01-01
01:00:00.000000000 +0100
+++ linux-2.6.14.2-a1-1/arch/ppc/kernel/amigaone_dma-mapping.c	2005-11-23
21:45:41.000000000 +0100
@@ -0,0 +1,138 @@
/* includes commented out...
+/*
+ * make an area consistent.
+ */
+void __dma_sync(void *vaddr, size_t size, int direction)
+{
+	unsigned long start = (unsigned long)vaddr;
+	unsigned long end   = start + size;
+
+	switch (direction) {
+	case DMA_NONE:
+		BUG();
+	case DMA_FROM_DEVICE:	/* invalidate only */
+		invalidate_dcache_range(start, end);
+		break;
+	case DMA_TO_DEVICE:		/* writeback only */
+		clean_dcache_range(start, end);
+		break;
+	case DMA_BIDIRECTIONAL:	/* writeback and invalidate */
+		flush_dcache_range(start, end);
+		break;
+	}
+}
+EXPORT_SYMBOL(__dma_sync);
+
+#ifdef CONFIG_HIGHMEM
+/*
+ * __dma_sync_page() implementation for systems using highmem.
+ * In this case, each page of a buffer must be kmapped/kunmapped
+ * in order to have a virtual address for __dma_sync(). This must
+ * not sleep so kmap_atmomic()/kunmap_atomic() are used.
+ *
+ * Note: yes, it is possible and correct to have a buffer extend
+ * beyond the first page.
+ */
+static inline void __dma_sync_page_highmem(struct page *page,
+		unsigned long offset, size_t size, int direction)
+{
+	size_t seg_size = min((size_t)PAGE_SIZE, size) - offset;
+	size_t cur_size = seg_size;
+	unsigned long flags, start, seg_offset = offset;
+	int nr_segs = PAGE_ALIGN(size + (PAGE_SIZE - offset))/PAGE_SIZE;
+	int seg_nr = 0;
+
+	local_irq_save(flags);
+
+	do {
+		start = (unsigned long)kmap_atomic(page + seg_nr,
+				KM_PPC_SYNC_PAGE) + seg_offset;
+
+		/* Sync this buffer segment */
+		__dma_sync((void *)start, seg_size, direction);
+		kunmap_atomic((void *)start, KM_PPC_SYNC_PAGE);
+		seg_nr++;
+
+		/* Calculate next buffer segment size */
+		seg_size = min((size_t)PAGE_SIZE, size - cur_size);
+
+		/* Add the segment size to our running total */
+		cur_size += seg_size;
+		seg_offset = 0;
+	} while (seg_nr < nr_segs);
+
+	local_irq_restore(flags);
+}
+#endif /* CONFIG_HIGHMEM */
+
+/*
+ * __dma_sync_page makes memory consistent. identical to __dma_sync, but
+ * takes a struct page instead of a virtual address
+ */
+void __dma_sync_page(struct page *page, unsigned long offset,
+	size_t size, int direction)
+{
+#ifdef CONFIG_HIGHMEM
+	__dma_sync_page_highmem(page, offset, size, direction);
+#else
+	unsigned long start = (unsigned long)page_address(page) + offset;
+	__dma_sync((void *)start, size, direction);
+#endif
+}
+EXPORT_SYMBOL(__dma_sync_page);
diff -Naurp linux-2.6.14.2/arch/ppc/kernel/cputable.c
linux-2.6.14.2-a1-1/arch/ppc/kernel/cputable.c
--- linux-2.6.14.2/arch/ppc/kernel/cputable.c	2005-11-11 06:33:12.000000000
+0100
+++ linux-2.6.14.2-a1-1/arch/ppc/kernel/cputable.c	2005-11-23
21:45:41.000000000 +0100
@@ -410,8 +410,10 @@ struct cpu_spec	cpu_specs[] = {
 			CPU_FTR_MAYBE_CAN_NAP | CPU_FTR_L2CR |
 			CPU_FTR_ALTIVEC_COMP | CPU_FTR_L3CR |
 			CPU_FTR_HPTE_TABLE | CPU_FTR_SPEC7450 |
-			CPU_FTR_NAP_DISABLE_L2_PR | CPU_FTR_L3_DISABLE_NAP |
-			CPU_FTR_NEED_COHERENT,
+#ifndef CONFIG_AMIGAONE /* chipset cannot do coherent dma */
+			CPU_FTR_NEED_COHERENT |
+#endif /* !CONFIG_AMIGAONE */			
+			CPU_FTR_NAP_DISABLE_L2_PR | CPU_FTR_L3_DISABLE_NAP, 
 		.cpu_user_features	= COMMON_PPC | PPC_FEATURE_ALTIVEC_COMP,
 		.icache_bsize		= 32,
 		.dcache_bsize		= 32,
@@ -475,8 +477,10 @@ struct cpu_spec	cpu_specs[] = {
 			CPU_FTR_MAYBE_CAN_NAP | CPU_FTR_L2CR |
 			CPU_FTR_ALTIVEC_COMP | CPU_FTR_L3CR |
 			CPU_FTR_HPTE_TABLE | CPU_FTR_SPEC7450 |
-			CPU_FTR_NAP_DISABLE_L2_PR | CPU_FTR_HAS_HIGH_BATS |
-			CPU_FTR_NEED_COHERENT,
+#ifndef CONFIG_AMIGAONE /* chipset cannot do coherent dma */
+			CPU_FTR_NEED_COHERENT |
+#endif /* !CONFIG_AMIGAONE */			
+			CPU_FTR_NAP_DISABLE_L2_PR | CPU_FTR_HAS_HIGH_BATS,
 		.cpu_user_features	= COMMON_PPC | PPC_FEATURE_ALTIVEC_COMP,
 		.icache_bsize		= 32,
 		.dcache_bsize		= 32,
iff -Naurp linux-2.6.14.2/include/asm-ppc/dma-mapping.h
linux-2.6.14.2-a1-1/include/asm-ppc/dma-mapping.h
--- linux-2.6.14.2/include/asm-ppc/dma-mapping.h	2005-11-11
06:33:12.000000000 +0100
+++ linux-2.6.14.2-a1-1/include/asm-ppc/dma-mapping.h	2005-11-23
21:45:42.000000000 +0100
@@ -36,15 +36,35 @@ extern void __dma_sync_page(struct page 
  * Cache coherent cores.
  */
 
+#define __dma_alloc_coherent(gfp, size, handle)	NULL
+#define __dma_free_coherent(size, addr)		do { } while (0)
+
+#ifndef CONFIG_AMIGAONE
+
 #define dma_cache_inv(_start,_size)		do { } while (0)
 #define dma_cache_wback(_start,_size)		do { } while (0)
 #define dma_cache_wback_inv(_start,_size)	do { } while (0)
 
-#define __dma_alloc_coherent(gfp, size, handle)	NULL
-#define __dma_free_coherent(size, addr)		do { } while (0)
 #define __dma_sync(addr, size, rw)		do { } while (0)
 #define __dma_sync_page(pg, off, sz, rw)	do { } while (0)
 
+#else /* ! CONFIG_AMIGAONE */
+
+#include <asm/tlbflush.h>
+/* Include TLB cache flush functions! */
+
+extern void __dma_sync(void *vaddr, size_t size, int direction);
+extern void __dma_sync_page(struct page *page, unsigned long offset,
+				 size_t size, int direction);
+#define dma_cache_inv(_start,_size) \
+	invalidate_dcache_range(_start, (_start + _size))
+#define dma_cache_wback(_start,_size) \
+	clean_dcache_range(_start, (_start + _size))
+#define dma_cache_wback_inv(_start,_size) \
+	flush_dcache_range(_start, (_start + _size))
+
+#endif /* ! CONFIG_AMIGAONE */
+
 #endif /* ! CONFIG_NOT_COHERENT_CACHE */
 
 #define dma_supported(dev, mask)	(1)
@@ -77,6 +97,15 @@ static inline void *dma_alloc_coherent(s
 
 	if (ret != NULL) {
 		memset(ret, 0, size);
+
+#ifdef CONFIG_AMIGAONE
+		/*
+		 * Invalidate any data that might be lurking in the
+		 * kernel direct-mapped region for device DMA.
+		 */
+		flush_dcache_range((unsigned long)ret, (unsigned long)ret + size);
+#endif /* CONFIG_AMIGAONE */
+
 		*dma_handle = virt_to_bus(ret);
 	}
 
@@ -92,6 +121,13 @@ dma_free_coherent(struct device *dev, si
 	__dma_free_coherent(size, vaddr);
 #else
 	free_pages((unsigned long)vaddr, get_order(size));
+
+#ifdef CONFIG_AMIGAONE
+
+	flush_tlb_kernel_range((unsigned long)vaddr, (unsigned long)vaddr + size);
+
+#endif /* CONFIG_AMIGAONE */
+
 #endif
 }
 
diff -Naurp linux-2.6.14.2/include/asm-ppc/dma.h
linux-2.6.14.2-a1-1/include/asm-ppc/dma.h
--- linux-2.6.14.2/include/asm-ppc/dma.h	2005-11-11 06:33:12.000000000 +0100
+++ linux-2.6.14.2-a1-1/include/asm-ppc/dma.h	2005-11-23 21:45:42.000000000
+0100
@@ -33,8 +33,12 @@
 #endif
 
 /* The maximum address that we can perform a DMA transfer to on this
platform */
-/* Doesn't really apply... */
+/* Doesn't really apply... - except for the AmigaOne. */
+#ifdef CONFIG_AMIGAONE
+#define MAX_DMA_ADDRESS		0x00FFFFFF
+#else
 #define MAX_DMA_ADDRESS		0xFFFFFFFF
+#endif
 
 /* in arch/ppc/kernel/setup.c -- Cort */
 extern unsigned long DMA_MODE_WRITE, DMA_MODE_READ;

-- 
10 GB Mailbox, 100 FreeSMS/Monat http://www.gmx.net/de/go/topmail
+++ GMX - die erste Adresse für Mail, Message, More +++



Reply to: