[PATCH 3/3] Use MTRR for pci_mmap_resource_wc if PAT is not available

From: Thomas Schlichter
Date: Sat Oct 17 2009 - 15:17:16 EST


X.org uses libpciaccess which tries to mmap with write combining enabled via
/sys/bus/pci/devices/*/resource0_wc. Currently, when PAT is not enabled, the
kernel does fall back to uncached mmap. Then libpciaccess thinks it succeeded
mapping with write combining enabled and does not set up suited MTRR entries.
;-(

So when falling back to uncached mapping, we better try to set up MTRR
entries automatically. When the resource file is closed, we remove the MTRR
entries again.

Signed-off-by: Thomas Schlichter <thomas.schlichter@xxxxxx>
---
arch/x86/include/asm/pci.h | 2 +
arch/x86/pci/i386.c | 60 ++++++++++++++++++++++++++++++++++++++++++++
drivers/pci/pci-sysfs.c | 8 ++++++
drivers/pci/pci.h | 6 ++++
drivers/pci/proc.c | 5 +++-
5 files changed, 80 insertions(+), 1 deletions(-)

diff --git a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h
index ada8c20..e94aeb1 100644
--- a/arch/x86/include/asm/pci.h
+++ b/arch/x86/include/asm/pci.h
@@ -64,6 +64,8 @@ struct irq_routing_table *pcibios_get_irq_routing_table(void);
int pcibios_set_irq_routing(struct pci_dev *dev, int pin, int irq);


+#define HAVE_PCI_RELEASE_FILE
+
#define HAVE_PCI_MMAP
extern int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma,
enum pci_mmap_state mmap_state,
diff --git a/arch/x86/pci/i386.c b/arch/x86/pci/i386.c
index b22d13b..60b6fdc 100644
--- a/arch/x86/pci/i386.c
+++ b/arch/x86/pci/i386.c
@@ -31,7 +31,9 @@
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/bootmem.h>
+#include <linux/fs.h>

+#include <asm/mtrr.h>
#include <asm/pat.h>
#include <asm/e820.h>
#include <asm/pci_x86.h>
@@ -301,5 +303,63 @@ int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma,

vma->vm_ops = &pci_mmap_ops;

+#ifdef CONFIG_MTRR
+ if (!pat_enabled && write_combine) {
+ int i;
+ unsigned long base = vma->vm_pgoff << PAGE_SHIFT;
+ unsigned long size = vma->vm_end - vma->vm_start;
+
+ if ((size < PAGE_SIZE) || (size & (size - 1)) ||
+ (base & (size - 1)))
+ {
+ printk(KERN_DEBUG
+ "Unaligned memory region passed to "
+ "pci_mmap_page_range().\n");
+ printk(KERN_DEBUG
+ "Thus no write combining MTRR entry "
+ "is created.\n");
+ printk(KERN_DEBUG
+ " base = 0x%lx size = 0x%lx\n", base, size);
+ return 0;
+ }
+
+ i = mtrr_add(base, size, MTRR_TYPE_WRCOMB, true);
+ if (i >= 0) {
+ int **p_mtrr_usage = (int **)vma->vm_file->private_data;
+
+ if (*p_mtrr_usage == NULL)
+ *p_mtrr_usage = kzalloc(num_var_ranges *
+ sizeof(int),
+ GFP_KERNEL);
+ if (*p_mtrr_usage != NULL)
+ (*p_mtrr_usage)[i]++;
+ }
+ }
+#endif // CONFIG_MTRR
+
+ return 0;
+}
+
+int pci_release_file(struct file *file)
+{
+#ifdef CONFIG_MTRR
+ int i;
+ int **p_mtrr_usage = (int **)file->private_data;
+ int *mtrr_usage = *p_mtrr_usage;
+
+ if (!mtrr_usage)
+ return 0;
+
+ for (i = 0; i < num_var_ranges; ++i) {
+ while (mtrr_usage[i] > 0) {
+ mtrr_del(i, 0, 0);
+ --mtrr_usage[i];
+ }
+ }
+
+ kfree(*p_mtrr_usage);
+ *p_mtrr_usage = NULL;
+#endif // CONFIG_MTRR
+
return 0;
}
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 0f6382f..4f36835 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -733,6 +733,13 @@ pci_mmap_resource_wc(struct kobject *kobj, struct bin_attribute *attr,
return pci_mmap_resource(kobj, attr, vma, 1);
}

+static int
+pci_release_resource(struct kobject *kobj, struct bin_attribute *attr,
+ struct file *file)
+{
+ return pci_release_file(file);
+}
+
/**
* pci_remove_resource_files - cleanup resource files
* @pdev: dev to cleanup
@@ -782,6 +789,7 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine)
sprintf(res_attr_name, "resource%d", num);
res_attr->mmap = pci_mmap_resource_uc;
}
+ res_attr->release = pci_release_resource;
res_attr->attr.name = res_attr_name;
res_attr->attr.mode = S_IRUSR | S_IWUSR;
res_attr->size = pci_resource_len(pdev, num);
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index d92d195..cff3b7d 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -84,6 +84,12 @@ static inline void pci_vpd_release(struct pci_dev *dev)
dev->vpd->ops->release(dev);
}

+#ifdef HAVE_PCI_RELEASE_FILE
+extern int pci_release_file(struct file *file);
+#else
+static inline int pci_release_file(struct file *file) { return 0; }
+#endif
+
/* PCI /proc functions */
#ifdef CONFIG_PROC_FS
extern int pci_proc_attach_device(struct pci_dev *dev);
diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index 593bb84..91c8a48 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -197,6 +197,7 @@ proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, lof
}

struct pci_filp_private {
+ void *private;
enum pci_mmap_state mmap_state;
int write_combine;
};
@@ -277,7 +278,7 @@ static int proc_bus_pci_mmap(struct file *file, struct vm_area_struct *vma)

static int proc_bus_pci_open(struct inode *inode, struct file *file)
{
- struct pci_filp_private *fpriv = kmalloc(sizeof(*fpriv), GFP_KERNEL);
+ struct pci_filp_private *fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL);

if (!fpriv)
return -ENOMEM;
@@ -292,6 +293,8 @@ static int proc_bus_pci_open(struct inode *inode, struct file *file)

static int proc_bus_pci_release(struct inode *inode, struct file *file)
{
+ pci_release_file(file);
+
kfree(file->private_data);
file->private_data = NULL;

--
1.6.5.1


--Boundary-00=_gi23KkNb/H9fV0U--
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/