aboutsummaryrefslogblamecommitdiffstats
path: root/arch/x86/pci/mmconfig_64.c
blob: acc48c5b68632fa96013e29ebe24fc66fc70466c (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                                     
  





                                                                         
                       
                         
                           
                     
                        
 

                      
                                                                                         
 
                                                                     
 


                                                                               




                                                                           
                           
 
                                                                                
                                                                     
                            
                               
         
 
                        
                                             

                                  
                         
         
 

                      
                                                       

                      
                                                       

                      
                                                       

                      
                          






                                                                           
                           
 
                                                                                


                                                                   
                        
                                             

                                  
                               
         
 

                      
                                                      

                      
                                                      

                      
                                                      

                      
                          



                 
                                             



                                        
                                                                       

                           
                        
                      
 

                                                                    
                                               
                                            
                 
                                                             


                    
                                    
 
                                     
 
                                                         

                                              

                                                                             
                                              
                                 
                 
         
                                     
                 
 


                                     
                                     
 
                                                         


                                                                                  

                 
 
/*
 * mmconfig.c - Low-level direct PCI config space access via MMCONFIG
 *
 * This is an 64bit optimized version that always keeps the full mmconfig
 * space mapped. This allows lockless config space operation.
 */

#include <linux/pci.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/bitmap.h>
#include <linux/rcupdate.h>
#include <asm/e820.h>
#include <asm/pci_x86.h>

#define PREFIX "PCI: "

static char __iomem *pci_dev_base(unsigned int seg, unsigned int bus, unsigned int devfn)
{
	struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus);

	if (cfg && cfg->virt)
		return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12));
	return NULL;
}

static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
			  unsigned int devfn, int reg, int len, u32 *value)
{
	char __iomem *addr;

	/* Why do we have this when nobody checks it. How about a BUG()!? -AK */
	if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) {
err:		*value = -1;
		return -EINVAL;
	}

	rcu_read_lock();
	addr = pci_dev_base(seg, bus, devfn);
	if (!addr) {
		rcu_read_unlock();
		goto err;
	}

	switch (len) {
	case 1:
		*value = mmio_config_readb(addr + reg);
		break;
	case 2:
		*value = mmio_config_readw(addr + reg);
		break;
	case 4:
		*value = mmio_config_readl(addr + reg);
		break;
	}
	rcu_read_unlock();

	return 0;
}

static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
			   unsigned int devfn, int reg, int len, u32 value)
{
	char __iomem *addr;

	/* Why do we have this when nobody checks it. How about a BUG()!? -AK */
	if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095)))
		return -EINVAL;

	rcu_read_lock();
	addr = pci_dev_base(seg, bus, devfn);
	if (!addr) {
		rcu_read_unlock();
		return -EINVAL;
	}

	switch (len) {
	case 1:
		mmio_config_writeb(addr + reg, value);
		break;
	case 2:
		mmio_config_writew(addr + reg, value);
		break;
	case 4:
		mmio_config_writel(addr + reg, value);
		break;
	}
	rcu_read_unlock();

	return 0;
}

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

static void __iomem * __init mcfg_ioremap(struct pci_mmcfg_region *cfg)
{
	void __iomem *addr;
	u64 start, size;
	int num_buses;

	start = cfg->address + PCI_MMCFG_BUS_OFFSET(cfg->start_bus);
	num_buses = cfg->end_bus - cfg->start_bus + 1;
	size = PCI_MMCFG_BUS_OFFSET(num_buses);
	addr = ioremap_nocache(start, size);
	if (addr)
		addr -= PCI_MMCFG_BUS_OFFSET(cfg->start_bus);
	return addr;
}

int __init pci_mmcfg_arch_init(void)
{
	struct pci_mmcfg_region *cfg;

	list_for_each_entry(cfg, &pci_mmcfg_list, list) {
		cfg->virt = mcfg_ioremap(cfg);
		if (!cfg->virt) {
			printk(KERN_ERR PREFIX "can't map MMCONFIG at %pR\n",
			       &cfg->res);
			pci_mmcfg_arch_free();
			return 0;
		}
	}
	raw_pci_ext_ops = &pci_mmcfg;
	return 1;
}

void __init pci_mmcfg_arch_free(void)
{
	struct pci_mmcfg_region *cfg;

	list_for_each_entry(cfg, &pci_mmcfg_list, list) {
		if (cfg->virt) {
			iounmap(cfg->virt + PCI_MMCFG_BUS_OFFSET(cfg->start_bus));
			cfg->virt = NULL;
		}
	}
}