mmconfig.c 3.1 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * Copyright (C) 2004 Matthew Wilcox <matthew@wil.cx>
 * Copyright (C) 2004 Intel Corp.
 *
 * This code is released under the GNU General Public License version 2.
 */

/*
 * mmconfig.c - Low-level direct PCI config space access via MMCONFIG
 */

#include <linux/pci.h>
#include <linux/init.h>
14
#include <linux/acpi.h>
L
Linus Torvalds 已提交
15 16 17 18 19 20 21 22 23 24
#include "pci.h"

#define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG))

/* The base address of the last MMCONFIG device accessed */
static u32 mmcfg_last_accessed_device;

/*
 * Functions for accessing PCI configuration space with MMCONFIG accesses
 */
25 26 27 28 29 30 31 32
static u32 get_base_addr(unsigned int seg, int bus)
{
	int cfg_num = -1;
	struct acpi_table_mcfg_config *cfg;

	while (1) {
		++cfg_num;
		if (cfg_num >= pci_mmcfg_config_num) {
33 34
			/* Not found - fallback to type 1 */
			return 0;
35 36 37 38 39 40 41 42 43
		}
		cfg = &pci_mmcfg_config[cfg_num];
		if (cfg->pci_segment_group_number != seg)
			continue;
		if ((cfg->start_bus_number <= bus) &&
		    (cfg->end_bus_number >= bus))
			return cfg->base_address;
	}
}
L
Linus Torvalds 已提交
44

45
static inline void pci_exp_set_dev_base(unsigned int base, int bus, int devfn)
L
Linus Torvalds 已提交
46
{
47
	u32 dev_base = base | (bus << 20) | (devfn << 12);
L
Linus Torvalds 已提交
48 49 50 51 52 53 54 55 56 57
	if (dev_base != mmcfg_last_accessed_device) {
		mmcfg_last_accessed_device = dev_base;
		set_fixmap_nocache(FIX_PCIE_MCFG, dev_base);
	}
}

static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
			  unsigned int devfn, int reg, int len, u32 *value)
{
	unsigned long flags;
58
	u32 base;
L
Linus Torvalds 已提交
59 60 61 62

	if (!value || (bus > 255) || (devfn > 255) || (reg > 4095))
		return -EINVAL;

63 64 65 66
	base = get_base_addr(seg, bus);
	if (!base)
		return pci_conf1_read(seg,bus,devfn,reg,len,value);

L
Linus Torvalds 已提交
67 68
	spin_lock_irqsave(&pci_config_lock, flags);

69
	pci_exp_set_dev_base(base, bus, devfn);
L
Linus Torvalds 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91

	switch (len) {
	case 1:
		*value = readb(mmcfg_virt_addr + reg);
		break;
	case 2:
		*value = readw(mmcfg_virt_addr + reg);
		break;
	case 4:
		*value = readl(mmcfg_virt_addr + reg);
		break;
	}

	spin_unlock_irqrestore(&pci_config_lock, flags);

	return 0;
}

static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
			   unsigned int devfn, int reg, int len, u32 value)
{
	unsigned long flags;
92
	u32 base;
L
Linus Torvalds 已提交
93 94 95 96

	if ((bus > 255) || (devfn > 255) || (reg > 4095)) 
		return -EINVAL;

97 98 99 100
	base = get_base_addr(seg, bus);
	if (!base)
		return pci_conf1_write(seg,bus,devfn,reg,len,value);

L
Linus Torvalds 已提交
101 102
	spin_lock_irqsave(&pci_config_lock, flags);

103
	pci_exp_set_dev_base(base, bus, devfn);
L
Linus Torvalds 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

	switch (len) {
	case 1:
		writeb(value, mmcfg_virt_addr + reg);
		break;
	case 2:
		writew(value, mmcfg_virt_addr + reg);
		break;
	case 4:
		writel(value, mmcfg_virt_addr + reg);
		break;
	}

	spin_unlock_irqrestore(&pci_config_lock, flags);

	return 0;
}

static struct pci_raw_ops pci_mmcfg = {
	.read =		pci_mmcfg_read,
	.write =	pci_mmcfg_write,
};

static int __init pci_mmcfg_init(void)
{
	if ((pci_probe & PCI_PROBE_MMCONF) == 0)
		goto out;
131 132 133 134 135

	acpi_table_parse(ACPI_MCFG, acpi_parse_mcfg);
	if ((pci_mmcfg_config_num == 0) ||
	    (pci_mmcfg_config == NULL) ||
	    (pci_mmcfg_config[0].base_address == 0))
L
Linus Torvalds 已提交
136 137 138 139 140 141 142 143 144 145 146
		goto out;

	printk(KERN_INFO "PCI: Using MMCONFIG\n");
	raw_pci_ops = &pci_mmcfg;
	pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF;

 out:
	return 0;
}

arch_initcall(pci_mmcfg_init);