| From 6327ea0a9ef45cb41e24adc59acbf8fcc2cd443b Mon Sep 17 00:00:00 2001 |
| From: Andy Lutomirski <luto@amacapital.net> |
| Date: Mon, 13 May 2013 23:58:40 +0000 |
| Subject: Add arch_phys_wc_{add, del} to manipulate WC MTRRs if needed |
| |
| Several drivers currently use mtrr_add through various #ifdef guards |
| and/or drm wrappers. The vast majority of them want to add WC MTRRs |
| on x86 systems and don't actually need the MTRR if PAT (i.e. |
| ioremap_wc, etc) are working. |
| |
| arch_phys_wc_add and arch_phys_wc_del are new functions, available |
| on all architectures and configurations, that add WC MTRRs on x86 if |
| needed (and handle errors) and do nothing at all otherwise. They're |
| also easier to use than mtrr_add and mtrr_del, so the call sites can |
| be simplified. |
| |
| As an added benefit, this will avoid wasting MTRRs and possibly |
| warning pointlessly on PAT-supporting systems. |
| |
| Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch> |
| Signed-off-by: Andy Lutomirski <luto@amacapital.net> |
| Signed-off-by: Dave Airlie <airlied@redhat.com> |
| (cherry picked from commit d0d98eedee2178c803dd824bb09f52b0e2ac1811) |
| Signed-off-by: Darren Hart <dvhart@linux.intel.com> |
| --- |
| arch/x86/include/asm/io.h | 7 ++++ |
| arch/x86/include/asm/mtrr.h | 10 +++++- |
| arch/x86/kernel/cpu/mtrr/main.c | 71 +++++++++++++++++++++++++++++++++++++++++ |
| include/linux/io.h | 25 +++++++++++++++ |
| 4 files changed, 112 insertions(+), 1 deletion(-) |
| |
| diff --git a/arch/x86/include/asm/io.h b/arch/x86/include/asm/io.h |
| index d8e8eefbe24c..34f69cb9350a 100644 |
| --- a/arch/x86/include/asm/io.h |
| +++ b/arch/x86/include/asm/io.h |
| @@ -345,4 +345,11 @@ extern bool xen_biovec_phys_mergeable(const struct bio_vec *vec1, |
| |
| #define IO_SPACE_LIMIT 0xffff |
| |
| +#ifdef CONFIG_MTRR |
| +extern int __must_check arch_phys_wc_add(unsigned long base, |
| + unsigned long size); |
| +extern void arch_phys_wc_del(int handle); |
| +#define arch_phys_wc_add arch_phys_wc_add |
| +#endif |
| + |
| #endif /* _ASM_X86_IO_H */ |
| diff --git a/arch/x86/include/asm/mtrr.h b/arch/x86/include/asm/mtrr.h |
| index e235582f9930..f768f6298419 100644 |
| --- a/arch/x86/include/asm/mtrr.h |
| +++ b/arch/x86/include/asm/mtrr.h |
| @@ -26,7 +26,10 @@ |
| #include <uapi/asm/mtrr.h> |
| |
| |
| -/* The following functions are for use by other drivers */ |
| +/* |
| + * The following functions are for use by other drivers that cannot use |
| + * arch_phys_wc_add and arch_phys_wc_del. |
| + */ |
| # ifdef CONFIG_MTRR |
| extern u8 mtrr_type_lookup(u64 addr, u64 end); |
| extern void mtrr_save_fixed_ranges(void *); |
| @@ -45,6 +48,7 @@ extern void mtrr_aps_init(void); |
| extern void mtrr_bp_restore(void); |
| extern int mtrr_trim_uncached_memory(unsigned long end_pfn); |
| extern int amd_special_default_mtrr(void); |
| +extern int phys_wc_to_mtrr_index(int handle); |
| # else |
| static inline u8 mtrr_type_lookup(u64 addr, u64 end) |
| { |
| @@ -80,6 +84,10 @@ static inline int mtrr_trim_uncached_memory(unsigned long end_pfn) |
| static inline void mtrr_centaur_report_mcr(int mcr, u32 lo, u32 hi) |
| { |
| } |
| +static inline int phys_wc_to_mtrr_index(int handle) |
| +{ |
| + return -1; |
| +} |
| |
| #define mtrr_ap_init() do {} while (0) |
| #define mtrr_bp_init() do {} while (0) |
| diff --git a/arch/x86/kernel/cpu/mtrr/main.c b/arch/x86/kernel/cpu/mtrr/main.c |
| index ca22b73aaa25..f961de9964c7 100644 |
| --- a/arch/x86/kernel/cpu/mtrr/main.c |
| +++ b/arch/x86/kernel/cpu/mtrr/main.c |
| @@ -51,9 +51,13 @@ |
| #include <asm/e820.h> |
| #include <asm/mtrr.h> |
| #include <asm/msr.h> |
| +#include <asm/pat.h> |
| |
| #include "mtrr.h" |
| |
| +/* arch_phys_wc_add returns an MTRR register index plus this offset. */ |
| +#define MTRR_TO_PHYS_WC_OFFSET 1000 |
| + |
| u32 num_var_ranges; |
| |
| unsigned int mtrr_usage_table[MTRR_MAX_VAR_RANGES]; |
| @@ -525,6 +529,73 @@ int mtrr_del(int reg, unsigned long base, unsigned long size) |
| } |
| EXPORT_SYMBOL(mtrr_del); |
| |
| +/** |
| + * arch_phys_wc_add - add a WC MTRR and handle errors if PAT is unavailable |
| + * @base: Physical base address |
| + * @size: Size of region |
| + * |
| + * If PAT is available, this does nothing. If PAT is unavailable, it |
| + * attempts to add a WC MTRR covering size bytes starting at base and |
| + * logs an error if this fails. |
| + * |
| + * Drivers must store the return value to pass to mtrr_del_wc_if_needed, |
| + * but drivers should not try to interpret that return value. |
| + */ |
| +int arch_phys_wc_add(unsigned long base, unsigned long size) |
| +{ |
| + int ret; |
| + |
| + if (pat_enabled) |
| + return 0; /* Success! (We don't need to do anything.) */ |
| + |
| + ret = mtrr_add(base, size, MTRR_TYPE_WRCOMB, true); |
| + if (ret < 0) { |
| + pr_warn("Failed to add WC MTRR for [%p-%p]; performance may suffer.", |
| + (void *)base, (void *)(base + size - 1)); |
| + return ret; |
| + } |
| + return ret + MTRR_TO_PHYS_WC_OFFSET; |
| +} |
| +EXPORT_SYMBOL(arch_phys_wc_add); |
| + |
| +/* |
| + * arch_phys_wc_del - undoes arch_phys_wc_add |
| + * @handle: Return value from arch_phys_wc_add |
| + * |
| + * This cleans up after mtrr_add_wc_if_needed. |
| + * |
| + * The API guarantees that mtrr_del_wc_if_needed(error code) and |
| + * mtrr_del_wc_if_needed(0) do nothing. |
| + */ |
| +void arch_phys_wc_del(int handle) |
| +{ |
| + if (handle >= 1) { |
| + WARN_ON(handle < MTRR_TO_PHYS_WC_OFFSET); |
| + mtrr_del(handle - MTRR_TO_PHYS_WC_OFFSET, 0, 0); |
| + } |
| +} |
| +EXPORT_SYMBOL(arch_phys_wc_del); |
| + |
| +/* |
| + * phys_wc_to_mtrr_index - translates arch_phys_wc_add's return value |
| + * @handle: Return value from arch_phys_wc_add |
| + * |
| + * This will turn the return value from arch_phys_wc_add into an mtrr |
| + * index suitable for debugging. |
| + * |
| + * Note: There is no legitimate use for this function, except possibly |
| + * in printk line. Alas there is an illegitimate use in some ancient |
| + * drm ioctls. |
| + */ |
| +int phys_wc_to_mtrr_index(int handle) |
| +{ |
| + if (handle < MTRR_TO_PHYS_WC_OFFSET) |
| + return -1; |
| + else |
| + return handle - MTRR_TO_PHYS_WC_OFFSET; |
| +} |
| +EXPORT_SYMBOL_GPL(phys_wc_to_mtrr_index); |
| + |
| /* |
| * HACK ALERT! |
| * These should be called implicitly, but we can't yet until all the initcall |
| diff --git a/include/linux/io.h b/include/linux/io.h |
| index 069e4075f872..f4f42faec686 100644 |
| --- a/include/linux/io.h |
| +++ b/include/linux/io.h |
| @@ -76,4 +76,29 @@ void devm_ioremap_release(struct device *dev, void *res); |
| #define arch_has_dev_port() (1) |
| #endif |
| |
| +/* |
| + * Some systems (x86 without PAT) have a somewhat reliable way to mark a |
| + * physical address range such that uncached mappings will actually |
| + * end up write-combining. This facility should be used in conjunction |
| + * with pgprot_writecombine, ioremap-wc, or set_memory_wc, since it has |
| + * no effect if the per-page mechanisms are functional. |
| + * (On x86 without PAT, these functions manipulate MTRRs.) |
| + * |
| + * arch_phys_del_wc(0) or arch_phys_del_wc(any error code) is guaranteed |
| + * to have no effect. |
| + */ |
| +#ifndef arch_phys_wc_add |
| +static inline int __must_check arch_phys_wc_add(unsigned long base, |
| + unsigned long size) |
| +{ |
| + return 0; /* It worked (i.e. did nothing). */ |
| +} |
| + |
| +static inline void arch_phys_wc_del(int handle) |
| +{ |
| +} |
| + |
| +#define arch_phys_wc_add arch_phys_wc_add |
| +#endif |
| + |
| #endif /* _LINUX_IO_H */ |
| -- |
| 1.8.5.rc3 |
| |