162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2004 Matthew Wilcox <matthew@wil.cx>
462306a36Sopenharmony_ci * Copyright (C) 2004 Intel Corp.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci/*
862306a36Sopenharmony_ci * mmconfig.c - Low-level direct PCI config space access via MMCONFIG
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/pci.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/rcupdate.h>
1462306a36Sopenharmony_ci#include <asm/e820/api.h>
1562306a36Sopenharmony_ci#include <asm/pci_x86.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/* Assume systems with more busses have correct MCFG */
1862306a36Sopenharmony_ci#define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG))
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* The base address of the last MMCONFIG device accessed */
2162306a36Sopenharmony_cistatic u32 mmcfg_last_accessed_device;
2262306a36Sopenharmony_cistatic int mmcfg_last_accessed_cpu;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * Functions for accessing PCI configuration space with MMCONFIG accesses
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_cistatic u32 get_base_addr(unsigned int seg, int bus, unsigned devfn)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	if (cfg)
3262306a36Sopenharmony_ci		return cfg->address;
3362306a36Sopenharmony_ci	return 0;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/*
3762306a36Sopenharmony_ci * This is always called under pci_config_lock
3862306a36Sopenharmony_ci */
3962306a36Sopenharmony_cistatic void pci_exp_set_dev_base(unsigned int base, int bus, int devfn)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	u32 dev_base = base | PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12);
4262306a36Sopenharmony_ci	int cpu = smp_processor_id();
4362306a36Sopenharmony_ci	if (dev_base != mmcfg_last_accessed_device ||
4462306a36Sopenharmony_ci	    cpu != mmcfg_last_accessed_cpu) {
4562306a36Sopenharmony_ci		mmcfg_last_accessed_device = dev_base;
4662306a36Sopenharmony_ci		mmcfg_last_accessed_cpu = cpu;
4762306a36Sopenharmony_ci		set_fixmap_nocache(FIX_PCIE_MCFG, dev_base);
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic int pci_mmcfg_read(unsigned int seg, unsigned int bus,
5262306a36Sopenharmony_ci			  unsigned int devfn, int reg, int len, u32 *value)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	unsigned long flags;
5562306a36Sopenharmony_ci	u32 base;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if ((bus > 255) || (devfn > 255) || (reg > 4095)) {
5862306a36Sopenharmony_cierr:		*value = -1;
5962306a36Sopenharmony_ci		return -EINVAL;
6062306a36Sopenharmony_ci	}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	rcu_read_lock();
6362306a36Sopenharmony_ci	base = get_base_addr(seg, bus, devfn);
6462306a36Sopenharmony_ci	if (!base) {
6562306a36Sopenharmony_ci		rcu_read_unlock();
6662306a36Sopenharmony_ci		goto err;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	raw_spin_lock_irqsave(&pci_config_lock, flags);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	pci_exp_set_dev_base(base, bus, devfn);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	switch (len) {
7462306a36Sopenharmony_ci	case 1:
7562306a36Sopenharmony_ci		*value = mmio_config_readb(mmcfg_virt_addr + reg);
7662306a36Sopenharmony_ci		break;
7762306a36Sopenharmony_ci	case 2:
7862306a36Sopenharmony_ci		*value = mmio_config_readw(mmcfg_virt_addr + reg);
7962306a36Sopenharmony_ci		break;
8062306a36Sopenharmony_ci	case 4:
8162306a36Sopenharmony_ci		*value = mmio_config_readl(mmcfg_virt_addr + reg);
8262306a36Sopenharmony_ci		break;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&pci_config_lock, flags);
8562306a36Sopenharmony_ci	rcu_read_unlock();
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return 0;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int pci_mmcfg_write(unsigned int seg, unsigned int bus,
9162306a36Sopenharmony_ci			   unsigned int devfn, int reg, int len, u32 value)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	unsigned long flags;
9462306a36Sopenharmony_ci	u32 base;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if ((bus > 255) || (devfn > 255) || (reg > 4095))
9762306a36Sopenharmony_ci		return -EINVAL;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	rcu_read_lock();
10062306a36Sopenharmony_ci	base = get_base_addr(seg, bus, devfn);
10162306a36Sopenharmony_ci	if (!base) {
10262306a36Sopenharmony_ci		rcu_read_unlock();
10362306a36Sopenharmony_ci		return -EINVAL;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	raw_spin_lock_irqsave(&pci_config_lock, flags);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	pci_exp_set_dev_base(base, bus, devfn);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	switch (len) {
11162306a36Sopenharmony_ci	case 1:
11262306a36Sopenharmony_ci		mmio_config_writeb(mmcfg_virt_addr + reg, value);
11362306a36Sopenharmony_ci		break;
11462306a36Sopenharmony_ci	case 2:
11562306a36Sopenharmony_ci		mmio_config_writew(mmcfg_virt_addr + reg, value);
11662306a36Sopenharmony_ci		break;
11762306a36Sopenharmony_ci	case 4:
11862306a36Sopenharmony_ci		mmio_config_writel(mmcfg_virt_addr + reg, value);
11962306a36Sopenharmony_ci		break;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&pci_config_lock, flags);
12262306a36Sopenharmony_ci	rcu_read_unlock();
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return 0;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ciconst struct pci_raw_ops pci_mmcfg = {
12862306a36Sopenharmony_ci	.read =		pci_mmcfg_read,
12962306a36Sopenharmony_ci	.write =	pci_mmcfg_write,
13062306a36Sopenharmony_ci};
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ciint __init pci_mmcfg_arch_init(void)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	printk(KERN_INFO "PCI: Using MMCONFIG for extended config space\n");
13562306a36Sopenharmony_ci	raw_pci_ext_ops = &pci_mmcfg;
13662306a36Sopenharmony_ci	return 1;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_civoid __init pci_mmcfg_arch_free(void)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ciint pci_mmcfg_arch_map(struct pci_mmcfg_region *cfg)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_civoid pci_mmcfg_arch_unmap(struct pci_mmcfg_region *cfg)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	unsigned long flags;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	/* Invalidate the cached mmcfg map entry. */
15362306a36Sopenharmony_ci	raw_spin_lock_irqsave(&pci_config_lock, flags);
15462306a36Sopenharmony_ci	mmcfg_last_accessed_device = 0;
15562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&pci_config_lock, flags);
15662306a36Sopenharmony_ci}
157