From: David Mosberger This patch cleans up driver/char/mem.c a bit. Also, it fixes the driver such that read()/write() accesses to uncached space are really done that way (via ioremap() and readb()/writeb()). drivers/char/mem.c | 183 ++++++++++++++++++++++++++----------------- include/asm-i386/pgtable.h | 7 + include/asm-m68k/pgtable.h | 17 +++ include/asm-ppc/pgtable.h | 5 + include/asm-ppc64/pgtable.h | 5 + include/asm-x86_64/pgtable.h | 5 + 6 files changed, 150 insertions(+), 72 deletions(-) diff -puN drivers/char/mem.c~mem_driver_cleanup drivers/char/mem.c --- 25/drivers/char/mem.c~mem_driver_cleanup 2003-06-05 23:36:42.000000000 -0700 +++ 25-akpm/drivers/char/mem.c 2003-06-05 23:36:42.000000000 -0700 @@ -29,15 +29,59 @@ #include #include +#ifdef CONFIG_IA64 +#include +#endif + #ifdef CONFIG_FB extern void fbmem_init(void); #endif #if defined(CONFIG_S390_TAPE) && defined(CONFIG_S390_TAPE_CHAR) extern void tapechar_init(void); #endif - + +/* + * Architectures vary in how they handle caching for addresses + * outside of main memory. + * + */ +static inline int uncached_access(struct file *file, unsigned long addr) +{ +#if defined(__i386__) + /* + * On the PPro and successors, the MTRRs are used to set + * memory types for physical addresses outside main memory, + * so blindly setting PCD or PWT on those pages is wrong. + * For Pentiums and earlier, the surround logic should disable + * caching for the high addresses through the KEN pin, but + * we maintain the tradition of paranoia in this code. + */ + if (file->f_flags & O_SYNC) + return 1; + return !( test_bit(X86_FEATURE_MTRR, boot_cpu_data.x86_capability) || + test_bit(X86_FEATURE_K6_MTRR, boot_cpu_data.x86_capability) || + test_bit(X86_FEATURE_CYRIX_ARR, boot_cpu_data.x86_capability) || + test_bit(X86_FEATURE_CENTAUR_MCR, boot_cpu_data.x86_capability) ) + && addr >= __pa(high_memory); +#elif defined(CONFIG_IA64) + /* + * On ia64, we ignore O_SYNC because we cannot tolerate memory + * attribute aliases. + */ + return !(efi_mem_attributes(addr) & EFI_MEMORY_WB); +#else + /* + * Accessing memory above the top the kernel knows about or through a + * file pointer that was marked O_SYNC will be done non-cached. + */ + if (file->f_flags & O_SYNC) + return 1; + return addr >= __pa(high_memory); +#endif +} + static ssize_t do_write_mem(struct file * file, void *p, unsigned long realp, - const char * buf, size_t count, loff_t *ppos) + const char * buf, size_t count, loff_t *ppos) { ssize_t written; @@ -62,6 +106,40 @@ static ssize_t do_write_mem(struct file } +static ssize_t do_write_mmio(struct file *file, void *p, unsigned long realp, + const char *buf, size_t count, loff_t *ppos) +{ + ssize_t written; + char byte; + size_t i; + + /* this is slow but nobody cares... */ + + written = 0; +#if defined(__sparc__) || (defined(__mc68000__) && defined(CONFIG_MMU)) + /* we don't have page 0 mapped on sparc and m68k.. */ + if (realp < PAGE_SIZE) { + unsigned long sz = PAGE_SIZE-realp; + + if (sz > count) + sz = count; + /* Hmm. Do something? */ + buf += sz; + p += sz; + count -= sz; + written += sz; + } +#endif + for (i = 0; i < count; ++i) { + if (get_user(byte, buf + i)) + return -EFAULT; + writeb(byte, p + i); + } + written += count; + *ppos += written;; + return written; +} + /* * This funcion reads the *physical* memory. The f_pos points directly to the * memory location. @@ -72,7 +150,8 @@ static ssize_t read_mem(struct file * fi unsigned long p = *ppos; unsigned long end_mem; ssize_t read; - + void *addr; + end_mem = __pa(high_memory); if (p >= end_mem) return 0; @@ -95,8 +174,22 @@ static ssize_t read_mem(struct file * fi } } #endif - if (copy_to_user(buf, __va(p), count)) - return -EFAULT; + if (uncached_access(file, (unsigned long) p)) { + char byte; + size_t i; + + if (!(addr = ioremap(p, count))) + return -EFAULT; + /* this is slow but nobody cares... */ + for (i = 0; i < count; ++i) { + byte = readb(addr + i); + if (put_user(byte, buf + i)) + return -EFAULT; + } + iounmap(addr); + } else + if (copy_to_user(buf, __va(p), count)) + return -EFAULT; read += count; *ppos += read; return read; @@ -107,86 +200,32 @@ static ssize_t write_mem(struct file * f { unsigned long p = *ppos; unsigned long end_mem; + ssize_t ret; + void *addr; end_mem = __pa(high_memory); if (p >= end_mem) return 0; if (count > end_mem - p) count = end_mem - p; - return do_write_mem(file, __va(p), p, buf, count, ppos); -} - -#ifndef pgprot_noncached - -/* - * This should probably be per-architecture in - */ -static inline pgprot_t pgprot_noncached(pgprot_t _prot) -{ - unsigned long prot = pgprot_val(_prot); - -#if defined(__i386__) || defined(__x86_64__) - /* On PPro and successors, PCD alone doesn't always mean - uncached because of interactions with the MTRRs. PCD | PWT - means definitely uncached. */ - if (boot_cpu_data.x86 > 3) - prot |= _PAGE_PCD | _PAGE_PWT; -#elif defined(__powerpc__) - prot |= _PAGE_NO_CACHE | _PAGE_GUARDED; -#elif defined(__mc68000__) && defined(CONFIG_MMU) -#ifdef SUN3_PAGE_NOCACHE - if (MMU_IS_SUN3) - prot |= SUN3_PAGE_NOCACHE; - else -#endif - if (MMU_IS_851 || MMU_IS_030) - prot |= _PAGE_NOCACHE030; - /* Use no-cache mode, serialized */ - else if (MMU_IS_040 || MMU_IS_060) - prot = (prot & _CACHEMASK040) | _PAGE_NOCACHE_S; -#endif - - return __pgprot(prot); -} - -#endif /* !pgprot_noncached */ - -/* - * Architectures vary in how they handle caching for addresses - * outside of main memory. - */ -static inline int noncached_address(unsigned long addr) -{ -#if defined(__i386__) - /* - * On the PPro and successors, the MTRRs are used to set - * memory types for physical addresses outside main memory, - * so blindly setting PCD or PWT on those pages is wrong. - * For Pentiums and earlier, the surround logic should disable - * caching for the high addresses through the KEN pin, but - * we maintain the tradition of paranoia in this code. - */ - return !( test_bit(X86_FEATURE_MTRR, boot_cpu_data.x86_capability) || - test_bit(X86_FEATURE_K6_MTRR, boot_cpu_data.x86_capability) || - test_bit(X86_FEATURE_CYRIX_ARR, boot_cpu_data.x86_capability) || - test_bit(X86_FEATURE_CENTAUR_MCR, boot_cpu_data.x86_capability) ) - && addr >= __pa(high_memory); -#else - return addr >= __pa(high_memory); -#endif + if (uncached_access(file, (unsigned long) p)) { + if (!(addr = ioremap(p, count))) + return -EFAULT; + ret = do_write_mmio(file, addr, p, buf, count, ppos); + iounmap(addr); + } else + ret = do_write_mem(file, __va(p), p, buf, count, ppos); + return ret; } static int mmap_mem(struct file * file, struct vm_area_struct * vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; - /* - * Accessing memory above the top the kernel knows about or - * through a file pointer that was marked O_SYNC will be - * done non-cached. - */ - if (noncached_address(offset) || (file->f_flags & O_SYNC)) +#ifdef pgprot_noncached + if (uncached_access(file, offset)) vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); +#endif /* Don't try to swap out physical pages.. */ vma->vm_flags |= VM_RESERVED; diff -puN include/asm-i386/pgtable.h~mem_driver_cleanup include/asm-i386/pgtable.h --- 25/include/asm-i386/pgtable.h~mem_driver_cleanup 2003-06-05 23:36:42.000000000 -0700 +++ 25-akpm/include/asm-i386/pgtable.h 2003-06-05 23:36:42.000000000 -0700 @@ -221,6 +221,13 @@ static inline void ptep_set_wrprotect(pt static inline void ptep_mkdirty(pte_t *ptep) { set_bit(_PAGE_BIT_DIRTY, &ptep->pte_low); } /* + * Macro to mark a page protection value as "uncacheable". On processors which do not support + * it, this is a no-op. + */ +#define pgprot_noncached(prot) ((boot_cpu_data.x86 > 3) \ + ? (__pgprot(pgprot_val(prot) | _PAGE_PCD | _PAGE_PWT)) : (prot)) + +/* * Conversion functions: convert a page and protection to a page entry, * and a page entry and page directory to the page they refer to. */ diff -puN include/asm-m68k/pgtable.h~mem_driver_cleanup include/asm-m68k/pgtable.h --- 25/include/asm-m68k/pgtable.h~mem_driver_cleanup 2003-06-05 23:36:42.000000000 -0700 +++ 25-akpm/include/asm-m68k/pgtable.h 2003-06-05 23:36:42.000000000 -0700 @@ -153,6 +153,23 @@ extern inline void update_mmu_cache(stru #ifndef __ASSEMBLY__ #include +/* + * Macro to mark a page protection value as "uncacheable". + */ +#ifdef SUN3_PAGE_NOCACHE +# define __SUN3_PAGE_NOCACHE SUN3_PAGE_NOCACHE +#else +# define __SUN3_PAGE_NOCACHE 0 +#endif +#define pgprot_noncached(prot) \ + (MMU_IS_SUN3 \ + ? (__pgprot(pgprot_val(prot) | __SUN3_PAGE_NOCACHE)) \ + : ((MMU_IS_851 || MMU_IS_030) \ + ? (__pgprot(pgprot_val(prot) | _PAGE_NOCACHE030)) \ + : (MMU_IS_040 || MMU_IS_060) \ + ? (__pgprot((pgprot_val(prot) & _CACHEMASK040) | _PAGE_NOCACHE_S)) \ + : (prot))) + typedef pte_t *pte_addr_t; #endif /* !__ASSEMBLY__ */ diff -puN include/asm-ppc64/pgtable.h~mem_driver_cleanup include/asm-ppc64/pgtable.h --- 25/include/asm-ppc64/pgtable.h~mem_driver_cleanup 2003-06-05 23:36:42.000000000 -0700 +++ 25-akpm/include/asm-ppc64/pgtable.h 2003-06-05 23:36:42.000000000 -0700 @@ -303,6 +303,11 @@ static inline void ptep_mkdirty(pte_t *p pte_update(ptep, 0, _PAGE_DIRTY); } +/* + * Macro to mark a page protection value as "uncacheable". + */ +#define pgprot_noncached(prot) (__pgprot(pgprot_val(prot) | _PAGE_NO_CACHE | _PAGE_GUARDED)) + #define pte_same(A,B) (((pte_val(A) ^ pte_val(B)) & ~_PAGE_HPTEFLAGS) == 0) /* diff -puN include/asm-ppc/pgtable.h~mem_driver_cleanup include/asm-ppc/pgtable.h --- 25/include/asm-ppc/pgtable.h~mem_driver_cleanup 2003-06-05 23:36:42.000000000 -0700 +++ 25-akpm/include/asm-ppc/pgtable.h 2003-06-05 23:36:42.000000000 -0700 @@ -478,6 +478,11 @@ static inline void ptep_mkdirty(pte_t *p pte_update(ptep, 0, _PAGE_DIRTY); } +/* + * Macro to mark a page protection value as "uncacheable". + */ +#define pgprot_noncached(prot) (__pgprot(pgprot_val(prot) | _PAGE_NO_CACHE | _PAGE_GUARDED)) + #define pte_same(A,B) (((pte_val(A) ^ pte_val(B)) & ~_PAGE_HASHPTE) == 0) #define pmd_page_kernel(pmd) \ diff -puN include/asm-x86_64/pgtable.h~mem_driver_cleanup include/asm-x86_64/pgtable.h --- 25/include/asm-x86_64/pgtable.h~mem_driver_cleanup 2003-06-05 23:36:42.000000000 -0700 +++ 25-akpm/include/asm-x86_64/pgtable.h 2003-06-05 23:36:42.000000000 -0700 @@ -265,6 +265,11 @@ static inline int ptep_test_and_clear_y static inline void ptep_set_wrprotect(pte_t *ptep) { clear_bit(_PAGE_BIT_RW, ptep); } static inline void ptep_mkdirty(pte_t *ptep) { set_bit(_PAGE_BIT_DIRTY, ptep); } +/* + * Macro to mark a page protection value as "uncacheable". + */ +#define pgprot_noncached(prot) (__pgprot(pgprot_val(prot) | _PAGE_PCD | _PAGE_PWT)) + #define __LARGE_PTE (_PAGE_PSE|_PAGE_PRESENT) static inline int pmd_large(pmd_t pte) { return (pmd_val(pte) & __LARGE_PTE) == __LARGE_PTE; _