1 /*
2 * AMD K8 NUMA support.
3 * Discover the memory map and associated nodes.
4 *
5 * Doesn't use the ACPI SRAT table because it has a questionable license.
6 * Instead the northbridge registers are read directly.
7 *
8 * Copyright 2002 Andi Kleen, SuSE Labs.
9 * $Id: k8topology.c,v 1.12 2004/01/29 00:51:01 ak Exp $
10 */
11 #include <linux/kernel.h>
12 #include <linux/init.h>
13 #include <linux/string.h>
14 #include <linux/module.h>
15 #include <asm/io.h>
16 #include <linux/pci_ids.h>
17 #include <asm/types.h>
18 #include <asm/mmzone.h>
19 #include <asm/proto.h>
20 #include <asm/e820.h>
21 #include <asm/pci-direct.h>
22
23 #define Dprintk(x...)
24
25 int memnode_shift;
26 u8 memnodemap[NODEMAPSIZE];
27
find_northbridge(void)28 static __init int find_northbridge(void)
29 {
30 int num;
31
32 for (num = 0; num < 32; num++) {
33 u32 header;
34
35 header = read_pci_config(0, num, 0, 0x00);
36 if (header != (PCI_VENDOR_ID_AMD | (0x1100<<16)))
37 continue;
38
39 header = read_pci_config(0, num, 1, 0x00);
40 if (header != (PCI_VENDOR_ID_AMD | (0x1101<<16)))
41 continue;
42 return num;
43 }
44
45 return -1;
46 }
47
48 #define MAXNODE 8
49 #define NODEMASK 0xff
50
51 struct node {
52 u64 start,end;
53 };
54
55 #define for_all_nodes(n) \
56 for (n=0; n<MAXNODE;n++) if (nodes[n].start!=nodes[n].end)
57
compute_hash_shift(struct node * nodes,int numnodes,u64 maxmem)58 static __init int compute_hash_shift(struct node *nodes, int numnodes, u64 maxmem)
59 {
60 int i;
61 int shift = 24;
62 u64 addr;
63
64 /* When in doubt use brute force. */
65 while (shift < 48) {
66 memset(memnodemap,0xff,sizeof(*memnodemap) * NODEMAPSIZE);
67 for_all_nodes (i) {
68 for (addr = nodes[i].start;
69 addr < nodes[i].end;
70 addr += (1UL << shift)) {
71 if (memnodemap[addr >> shift] != 0xff &&
72 memnodemap[addr >> shift] != i) {
73 printk("node %d shift %d addr %Lx conflict %d\n",
74 i, shift, addr, memnodemap[addr>>shift]);
75 goto next;
76 }
77 memnodemap[addr >> shift] = i;
78 }
79 }
80 return shift;
81 next:
82 shift++;
83 }
84 memset(memnodemap,0,sizeof(*memnodemap) * NODEMAPSIZE);
85 return -1;
86 }
87
88 extern unsigned long nodes_present;
89 extern unsigned long end_pfn;
90
k8_scan_nodes(unsigned long start,unsigned long end)91 int __init k8_scan_nodes(unsigned long start, unsigned long end)
92 {
93 unsigned long prevbase;
94 struct node nodes[MAXNODE];
95 int nodeid, numnodes, maxnode, i, nb;
96
97 nb = find_northbridge();
98 if (nb < 0)
99 return nb;
100
101 printk(KERN_INFO "Scanning NUMA topology in Northbridge %d\n", nb);
102
103 numnodes = (read_pci_config(0, nb, 0, 0x60 ) >> 4) & 7;
104
105 memset(&nodes,0,sizeof(nodes));
106 prevbase = 0;
107 maxnode = -1;
108 for (i = 0; i < MAXNODE; i++) {
109 unsigned long base,limit;
110
111 base = read_pci_config(0, nb, 1, 0x40 + i*8);
112 limit = read_pci_config(0, nb, 1, 0x44 + i*8);
113
114 nodeid = limit & 7;
115 if ((base & 3) == 0) {
116 continue;
117 }
118 if (!limit) {
119 printk(KERN_INFO "Skipping node entry %d (base %lx)\n", i, base);
120 continue;
121 }
122 if ((base >> 8) & 3 || (limit >> 8) & 3) {
123 printk(KERN_ERR "Node %d using interleaving mode %lx/%lx\n",
124 nodeid, (base>>8)&3, (limit>>8) & 3);
125 return -1;
126 }
127 if (nodeid > maxnode)
128 maxnode = nodeid;
129 if ((1UL << nodeid) & nodes_present) {
130 printk("Node %d already present. Skipping\n", nodeid);
131 continue;
132 }
133
134 limit >>= 16;
135 limit <<= 24;
136 limit |= (1<<24)-1;
137
138 if (limit > end_pfn << PAGE_SHIFT)
139 limit = end_pfn << PAGE_SHIFT;
140 if (limit <= base)
141 continue;
142
143 base >>= 16;
144 base <<= 24;
145
146 if (base < start)
147 base = start;
148 if (limit > end)
149 limit = end;
150 if (limit == base)
151 continue;
152 if (limit < base) {
153 Dprintk(KERN_INFO"Node %d bogus settings %lx-%lx. Ignored.\n",
154 nodeid, base, limit);
155 continue;
156 }
157
158 /* Could sort here, but pun for now. Should not happen anyroads. */
159 if (prevbase > base) {
160 printk(KERN_INFO "Node map not sorted %lx,%lx\n",
161 prevbase,base);
162 return -1;
163 }
164
165 printk(KERN_INFO "Node %d MemBase %016lx Limit %016lx\n",
166 nodeid, base, limit);
167
168 nodes[nodeid].start = base;
169 nodes[nodeid].end = limit;
170
171 prevbase = base;
172 }
173
174 if (maxnode <= 0)
175 return -1;
176
177 memnode_shift = compute_hash_shift(nodes,maxnode,end);
178 if (memnode_shift < 0) {
179 printk(KERN_ERR "No NUMA node hash function found. Contact maintainer\n");
180 return -1;
181 }
182 printk(KERN_INFO "Using node hash shift of %d\n", memnode_shift);
183
184 for (i = 0; i < MAXNODE; i++) {
185 if (nodes[i].start != nodes[i].end)
186 setup_node_bootmem(i, nodes[i].start, nodes[i].end);
187 }
188
189 /* There are unfortunately some poorly designed mainboards around
190 that only connect memory to a single CPU. This breaks the 1:1 cpu->node
191 mapping. To avoid this fill in the mapping for all possible
192 CPUs, as the number of CPUs is not known yet.
193 We round robin the existing nodes. */
194 int rr = 0;
195 for (i = 0; i < MAXNODE; i++) {
196 if (nodes[i].start != nodes[i].end)
197 continue;
198 if ((nodes_present >> rr) == 0)
199 rr = 0;
200 rr += ffz(~nodes_present >> rr);
201 PLAT_NODE_DATA(i) = PLAT_NODE_DATA(rr);
202 rr++;
203 }
204
205 return 0;
206 }
207
208