1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Numascale NumaConnect-specific PCI code
4  *
5  * Copyright (C) 2012 Numascale AS. All rights reserved.
6  *
7  * Send feedback to <support@numascale.com>
8  *
9  * PCI accessor functions derived from mmconfig_64.c
10  *
11  */
12 
13 #include <linux/pci.h>
14 #include <asm/pci_x86.h>
15 #include <asm/numachip/numachip.h>
16 
17 static u8 limit __read_mostly;
18 
pci_dev_base(unsigned int seg,unsigned int bus,unsigned int devfn)19 static inline char __iomem *pci_dev_base(unsigned int seg, unsigned int bus, unsigned int devfn)
20 {
21 	struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus);
22 
23 	if (cfg && cfg->virt)
24 		return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12));
25 	return NULL;
26 }
27 
pci_mmcfg_read_numachip(unsigned int seg,unsigned int bus,unsigned int devfn,int reg,int len,u32 * value)28 static int pci_mmcfg_read_numachip(unsigned int seg, unsigned int bus,
29 			  unsigned int devfn, int reg, int len, u32 *value)
30 {
31 	char __iomem *addr;
32 
33 	/* Why do we have this when nobody checks it. How about a BUG()!? -AK */
34 	if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) {
35 err:		*value = -1;
36 		return -EINVAL;
37 	}
38 
39 	/* Ensure AMD Northbridges don't decode reads to other devices */
40 	if (unlikely(bus == 0 && devfn >= limit)) {
41 		*value = -1;
42 		return 0;
43 	}
44 
45 	rcu_read_lock();
46 	addr = pci_dev_base(seg, bus, devfn);
47 	if (!addr) {
48 		rcu_read_unlock();
49 		goto err;
50 	}
51 
52 	switch (len) {
53 	case 1:
54 		*value = mmio_config_readb(addr + reg);
55 		break;
56 	case 2:
57 		*value = mmio_config_readw(addr + reg);
58 		break;
59 	case 4:
60 		*value = mmio_config_readl(addr + reg);
61 		break;
62 	}
63 	rcu_read_unlock();
64 
65 	return 0;
66 }
67 
pci_mmcfg_write_numachip(unsigned int seg,unsigned int bus,unsigned int devfn,int reg,int len,u32 value)68 static int pci_mmcfg_write_numachip(unsigned int seg, unsigned int bus,
69 			   unsigned int devfn, int reg, int len, u32 value)
70 {
71 	char __iomem *addr;
72 
73 	/* Why do we have this when nobody checks it. How about a BUG()!? -AK */
74 	if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095)))
75 		return -EINVAL;
76 
77 	/* Ensure AMD Northbridges don't decode writes to other devices */
78 	if (unlikely(bus == 0 && devfn >= limit))
79 		return 0;
80 
81 	rcu_read_lock();
82 	addr = pci_dev_base(seg, bus, devfn);
83 	if (!addr) {
84 		rcu_read_unlock();
85 		return -EINVAL;
86 	}
87 
88 	switch (len) {
89 	case 1:
90 		mmio_config_writeb(addr + reg, value);
91 		break;
92 	case 2:
93 		mmio_config_writew(addr + reg, value);
94 		break;
95 	case 4:
96 		mmio_config_writel(addr + reg, value);
97 		break;
98 	}
99 	rcu_read_unlock();
100 
101 	return 0;
102 }
103 
104 static const struct pci_raw_ops pci_mmcfg_numachip = {
105 	.read = pci_mmcfg_read_numachip,
106 	.write = pci_mmcfg_write_numachip,
107 };
108 
pci_numachip_init(void)109 int __init pci_numachip_init(void)
110 {
111 	int ret = 0;
112 	u32 val;
113 
114 	/* For remote I/O, restrict bus 0 access to the actual number of AMD
115 	   Northbridges, which starts at device number 0x18 */
116 	ret = raw_pci_read(0, 0, PCI_DEVFN(0x18, 0), 0x60, sizeof(val), &val);
117 	if (ret)
118 		goto out;
119 
120 	/* HyperTransport fabric size in bits 6:4 */
121 	limit = PCI_DEVFN(0x18 + ((val >> 4) & 7) + 1, 0);
122 
123 	/* Use NumaChip PCI accessors for non-extended and extended access */
124 	raw_pci_ops = raw_pci_ext_ops = &pci_mmcfg_numachip;
125 out:
126 	return ret;
127 }
128