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 | |
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(segment: seg, bus); |
22 | |
23 | if (cfg && cfg->virt) |
24 | return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12)); |
25 | return NULL; |
26 | } |
27 | |
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(pos: addr + reg); |
55 | break; |
56 | case 2: |
57 | *value = mmio_config_readw(pos: addr + reg); |
58 | break; |
59 | case 4: |
60 | *value = mmio_config_readl(pos: addr + reg); |
61 | break; |
62 | } |
63 | rcu_read_unlock(); |
64 | |
65 | return 0; |
66 | } |
67 | |
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(pos: addr + reg, val: value); |
91 | break; |
92 | case 2: |
93 | mmio_config_writew(pos: addr + reg, val: value); |
94 | break; |
95 | case 4: |
96 | mmio_config_writel(pos: addr + reg, val: 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 | |
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(domain: 0, bus: 0, PCI_DEVFN(0x18, 0), reg: 0x60, len: sizeof(val), 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 | |