Root/
Source at commit 2381 created 10 years 21 days ago. By ifabio, Apply patch: (Credits to Thomas Jansen aka tja) - Reading options from all devices during boot. The options for the boot menu are only read from the devices rd(0,0) or bt(0,0). Consequently, boot menu options (e.g. "Quiet Boot", "Timeout", etc.) in plists on other devices (like most users have) are ignored. This patch extends the list of paths to search for the options plist on all devices that can be found. | |
---|---|
1 | /*␊ |
2 | * spd.c - serial presence detect memory information␊ |
3 | *␊ |
4 | * Originally restored from pcefi10.5␊ |
5 | * Dynamic mem detection original impl. by Rekursor␊ |
6 | * System profiler fix and other fixes by Mozodojo.␊ |
7 | */␊ |
8 | ␊ |
9 | #include "libsaio.h"␊ |
10 | #include "pci.h"␊ |
11 | #include "platform.h"␊ |
12 | #include "spd.h"␊ |
13 | #include "cpu.h"␊ |
14 | #include "saio_internal.h"␊ |
15 | #include "bootstruct.h"␊ |
16 | #include "memvendors.h"␊ |
17 | ␊ |
18 | #ifndef DEBUG_SPD␊ |
19 | #define DEBUG_SPD 0␊ |
20 | #endif␊ |
21 | ␊ |
22 | #if DEBUG_SPD␊ |
23 | #define DBG(x...)␉printf(x)␊ |
24 | #else␊ |
25 | #define DBG(x...)␉msglog(x)␊ |
26 | #endif␊ |
27 | ␊ |
28 | static const char *spd_memory_types[] =␊ |
29 | {␊ |
30 | ␉"RAM", /* 00h Undefined */␊ |
31 | ␉"FPM", /* 01h FPM */␊ |
32 | ␉"EDO", /* 02h EDO */␊ |
33 | ␉"",␉␉␉␉/* 03h PIPELINE NIBBLE */␊ |
34 | ␉"SDRAM", /* 04h SDRAM */␊ |
35 | ␉"",␉␉␉␉/* 05h MULTIPLEXED ROM */␊ |
36 | ␉"DDR SGRAM",␉/* 06h SGRAM DDR */␊ |
37 | ␉"DDR SDRAM",␉/* 07h SDRAM DDR */␊ |
38 | ␉"DDR2 SDRAM", /* 08h SDRAM DDR 2 */␊ |
39 | ␉"",␉␉␉␉/* 09h Undefined */␊ |
40 | ␉"",␉␉␉␉/* 0Ah Undefined */␊ |
41 | ␉"DDR3 SDRAM"␉/* 0Bh SDRAM DDR 3 */␊ |
42 | };␊ |
43 | ␊ |
44 | #define UNKNOWN_MEM_TYPE 2␊ |
45 | static uint8_t spd_mem_to_smbios[] =␊ |
46 | {␊ |
47 | ␉UNKNOWN_MEM_TYPE,␉␉/* 00h Undefined */␊ |
48 | ␉UNKNOWN_MEM_TYPE,␉␉/* 01h FPM */␊ |
49 | ␉UNKNOWN_MEM_TYPE,␉␉/* 02h EDO */␊ |
50 | ␉UNKNOWN_MEM_TYPE,␉␉/* 03h PIPELINE NIBBLE */␊ |
51 | ␉SMB_MEM_TYPE_SDRAM,␉␉/* 04h SDRAM */␊ |
52 | ␉SMB_MEM_TYPE_ROM,␉␉/* 05h MULTIPLEXED ROM */␊ |
53 | ␉SMB_MEM_TYPE_SGRAM,␉␉/* 06h SGRAM DDR */␊ |
54 | ␉SMB_MEM_TYPE_DDR,␉␉/* 07h SDRAM DDR */␊ |
55 | ␉SMB_MEM_TYPE_DDR2,␉␉/* 08h SDRAM DDR 2 */␊ |
56 | ␉UNKNOWN_MEM_TYPE,␉␉/* 09h Undefined */␊ |
57 | ␉UNKNOWN_MEM_TYPE,␉␉/* 0Ah Undefined */␊ |
58 | ␉SMB_MEM_TYPE_DDR3␉␉/* 0Bh SDRAM DDR 3 */␊ |
59 | };␊ |
60 | #define SPD_TO_SMBIOS_SIZE (sizeof(spd_mem_to_smbios)/sizeof(uint8_t))␊ |
61 | ␊ |
62 | #define rdtsc(low,high) \␊ |
63 | __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))␊ |
64 | ␊ |
65 | #define SMBHSTSTS 0␊ |
66 | #define SMBHSTCNT 2␊ |
67 | #define SMBHSTCMD 3␊ |
68 | #define SMBHSTADD 4␊ |
69 | #define SMBHSTDAT 5␊ |
70 | #define SBMBLKDAT 7␊ |
71 | ␊ |
72 | /** Read one byte from the intel i2c, used for reading SPD on intel chipsets only. */␊ |
73 | unsigned char smb_read_byte_intel(uint32_t base, uint8_t adr, uint8_t cmd)␊ |
74 | {␊ |
75 | int l1, h1, l2, h2;␊ |
76 | unsigned long long t;␊ |
77 | ␉␊ |
78 | outb(base + SMBHSTSTS, 0x1f);␉␉␉␉␉// reset SMBus Controller␊ |
79 | outb(base + SMBHSTDAT, 0xff);␊ |
80 | ␉␊ |
81 | rdtsc(l1, h1);␊ |
82 | while ( inb(base + SMBHSTSTS) & 0x01) // wait until read␊ |
83 | { ␊ |
84 | rdtsc(l2, h2);␊ |
85 | t = ((h2 - h1) * 0xffffffff + (l2 - l1)) / (Platform.CPU.TSCFrequency / 100);␊ |
86 | if (t > 5)␊ |
87 | return 0xFF; // break␊ |
88 | }␊ |
89 | ␉␊ |
90 | outb(base + SMBHSTCMD, cmd);␊ |
91 | outb(base + SMBHSTADD, (adr << 1) | 0x01 );␊ |
92 | outb(base + SMBHSTCNT, 0x48 );␊ |
93 | ␉␊ |
94 | rdtsc(l1, h1);␊ |
95 | ␉␊ |
96 | ␉while (!( inb(base + SMBHSTSTS) & 0x02))␉␉// wait til command finished␊ |
97 | ␉{␉␊ |
98 | ␉␉rdtsc(l2, h2);␊ |
99 | ␉␉t = ((h2 - h1) * 0xffffffff + (l2 - l1)) / (Platform.CPU.TSCFrequency / 100);␊ |
100 | ␉␉if (t > 5)␊ |
101 | ␉␉␉break;␉␉␉␉␉␉␉␉␉// break after 5ms␊ |
102 | }␊ |
103 | return inb(base + SMBHSTDAT);␊ |
104 | }␊ |
105 | ␊ |
106 | /* SPD i2c read optimization: prefetch only what we need, read non prefetcheable bytes on the fly */␊ |
107 | #define READ_SPD(spd, base, slot, x) spd[x] = smb_read_byte_intel(base, 0x50 + slot, x)␊ |
108 | ␊ |
109 | int spd_indexes[] = {␊ |
110 | ␉SPD_MEMORY_TYPE,␊ |
111 | ␉SPD_DDR3_MEMORY_BANK,␊ |
112 | ␉SPD_DDR3_MEMORY_CODE,␊ |
113 | ␉SPD_NUM_ROWS,␊ |
114 | ␉SPD_NUM_COLUMNS,␊ |
115 | ␉SPD_NUM_DIMM_BANKS,␊ |
116 | ␉SPD_NUM_BANKS_PER_SDRAM,␊ |
117 | ␉4,7,8,9,12,64, /* TODO: give names to these values */␊ |
118 | ␉95,96,97,98, 122,123,124,125 /* UIS */␊ |
119 | };␊ |
120 | #define SPD_INDEXES_SIZE (sizeof(spd_indexes) / sizeof(int))␊ |
121 | ␊ |
122 | /** Read from spd *used* values only*/␊ |
123 | static void init_spd(char * spd, uint32_t base, int slot)␊ |
124 | {␊ |
125 | ␉int i;␊ |
126 | ␉for (i=0; i< SPD_INDEXES_SIZE; i++) {␊ |
127 | ␉␉READ_SPD(spd, base, slot, spd_indexes[i]);␊ |
128 | ␉}␊ |
129 | }␊ |
130 | ␊ |
131 | /** Get Vendor Name from spd, 2 cases handled DDR3 and DDR2, ␊ |
132 | have different formats, always return a valid ptr.*/␊ |
133 | const char * getVendorName(RamSlotInfo_t* slot, uint32_t base, int slot_num)␊ |
134 | {␊ |
135 | uint8_t bank = 0;␊ |
136 | uint8_t code = 0;␊ |
137 | int i = 0;␊ |
138 | uint8_t * spd = (uint8_t *) slot->spd;␊ |
139 | ␊ |
140 | if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR3) { // DDR3␊ |
141 | bank = (spd[SPD_DDR3_MEMORY_BANK] & 0x07f); // constructors like Patriot use b7=1␊ |
142 | code = spd[SPD_DDR3_MEMORY_CODE];␊ |
143 | for (i=0; i < VEN_MAP_SIZE; i++)␊ |
144 | if (bank==vendorMap[i].bank && code==vendorMap[i].code)␊ |
145 | return vendorMap[i].name;␊ |
146 | }␊ |
147 | else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR2) {␊ |
148 | if(spd[64]==0x7f) {␊ |
149 | for (i=64; i<72 && spd[i]==0x7f;i++) {␊ |
150 | ␉␉␉ bank++;␊ |
151 | ␉␉␉ READ_SPD(spd, base, slot_num,i+1); // prefetch next spd byte to read for next loop␊ |
152 | ␉␉␉}␊ |
153 | ␉␉␉READ_SPD(spd, base, slot_num,i);␊ |
154 | code = spd[i];␊ |
155 | } else {␊ |
156 | code = spd[64]; ␊ |
157 | bank = 0;␊ |
158 | }␊ |
159 | for (i=0; i < VEN_MAP_SIZE; i++)␊ |
160 | if (bank==vendorMap[i].bank && code==vendorMap[i].code)␊ |
161 | return vendorMap[i].name;␊ |
162 | }␊ |
163 | /* OK there is no vendor id here lets try to match the partnum if it exists */␊ |
164 | if (strstr(slot->PartNo,"GU332") == slot->PartNo) // Unifosa fingerprint␊ |
165 | return "Unifosa";␊ |
166 | return "NoName";␊ |
167 | }␊ |
168 | ␊ |
169 | /** Get Default Memory Module Speed (no overclocking handled) */␊ |
170 | int getDDRspeedMhz(const char * spd)␊ |
171 | {␊ |
172 | if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR3) { ␊ |
173 | switch(spd[12]) {␊ |
174 | case 0x0f:␊ |
175 | return 1066;␊ |
176 | case 0x0c:␊ |
177 | return 1333;␊ |
178 | case 0x0a:␊ |
179 | return 1600;␊ |
180 | case 0x14:␊ |
181 | default:␊ |
182 | return 800;␊ |
183 | }␊ |
184 | } ␊ |
185 | else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR2) {␊ |
186 | switch(spd[9]) {␊ |
187 | case 0x50:␊ |
188 | return 400;␊ |
189 | case 0x3d:␊ |
190 | return 533;␊ |
191 | case 0x30:␊ |
192 | return 667;␊ |
193 | case 0x25:␊ |
194 | default:␊ |
195 | return 800;␊ |
196 | }␊ |
197 | }␊ |
198 | return 800; // default freq for unknown types␊ |
199 | }␊ |
200 | ␊ |
201 | #define SMST(a) ((uint8_t)((spd[a] & 0xf0) >> 4))␊ |
202 | #define SLST(a) ((uint8_t)(spd[a] & 0x0f))␊ |
203 | ␊ |
204 | /** Get DDR3 or DDR2 serial number, 0 most of the times, always return a valid ptr */␊ |
205 | const char *getDDRSerial(const char* spd)␊ |
206 | {␊ |
207 | static char asciiSerial[16];␊ |
208 | ␊ |
209 | if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR3) // DDR3␊ |
210 | {␊ |
211 | ␉sprintf(asciiSerial, "%X%X%X%X%X%X%X%X", SMST(122) /*& 0x7*/, SLST(122), SMST(123), SLST(123), SMST(124), SLST(124), SMST(125), SLST(125));␊ |
212 | }␊ |
213 | else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR2) // DDR2 or DDR␊ |
214 | { ␊ |
215 | ␉sprintf(asciiSerial, "%X%X%X%X%X%X%X%X", SMST(95) /*& 0x7*/, SLST(95), SMST(96), SLST(96), SMST(97), SLST(97), SMST(98), SLST(98));␊ |
216 | }␊ |
217 | ␊ |
218 | return strdup(asciiSerial);␊ |
219 | }␊ |
220 | ␊ |
221 | /** Get DDR3 or DDR2 Part Number, always return a valid ptr */␊ |
222 | const char * getDDRPartNum(char* spd, uint32_t base, int slot)␊ |
223 | {␊ |
224 | ␉static char asciiPartNo[32];␊ |
225 | ␉int i, start=0, index = 0;␊ |
226 | ␊ |
227 | if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR3) {␊ |
228 | ␉␉start = 128;␊ |
229 | ␉}␊ |
230 | else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR2) {␊ |
231 | ␉␉start = 73;␊ |
232 | ␉}␊ |
233 | ␉␊ |
234 | // Check that the spd part name is zero terminated and that it is ascii:␊ |
235 | bzero(asciiPartNo, sizeof(asciiPartNo));␊ |
236 | ␉char c;␊ |
237 | ␉for (i=start; i < start + sizeof(asciiPartNo); i++) {␊ |
238 | ␉␉READ_SPD(spd, base, slot, i); // only read once the corresponding model part (ddr3 or ddr2)␊ |
239 | ␉␉c = spd[i];␊ |
240 | ␉␉if (isalpha(c) || isdigit(c) || ispunct(c)) // It seems that System Profiler likes only letters and digits...␊ |
241 | ␉␉␉asciiPartNo[index++] = c;␊ |
242 | ␉␉else if (!isascii(c))␊ |
243 | ␉␉␉break;␊ |
244 | ␉}␊ |
245 | ␉␊ |
246 | ␉return strdup(asciiPartNo);␊ |
247 | }␊ |
248 | ␊ |
249 | int mapping []= {0,2,1,3,4,6,5,7,8,10,9,11};␊ |
250 | ␊ |
251 | ␊ |
252 | /** Read from smbus the SPD content and interpret it for detecting memory attributes */␊ |
253 | static void read_smb_intel(pci_dt_t *smbus_dev)␊ |
254 | { ␊ |
255 | int i, speed;␊ |
256 | uint8_t spd_size, spd_type;␊ |
257 | uint32_t base, mmio, hostc;␊ |
258 | // bool dump = false;␊ |
259 | RamSlotInfo_t* slot;␊ |
260 | ␊ |
261 | ␉uint16_t cmd = pci_config_read16(smbus_dev->dev.addr, 0x04);␊ |
262 | ␉DBG("SMBus CmdReg: 0x%x\n", cmd);␊ |
263 | ␉pci_config_write16(smbus_dev->dev.addr, 0x04, cmd | 1);␊ |
264 | ␊ |
265 | ␉mmio = pci_config_read32(smbus_dev->dev.addr, 0x10);// & ~0x0f;␊ |
266 | base = pci_config_read16(smbus_dev->dev.addr, 0x20) & 0xFFFE;␊ |
267 | ␉hostc = pci_config_read8(smbus_dev->dev.addr, 0x40);␊ |
268 | verbose("Scanning SMBus [%04x:%04x], mmio: 0x%x, ioport: 0x%x, hostc: 0x%x\n", ␊ |
269 | ␉␉smbus_dev->vendor_id, smbus_dev->device_id, mmio, base, hostc);␊ |
270 | ␊ |
271 | //Azi: no use for this!␊ |
272 | // getBoolForKey("DumpSPD", &dump, &bootInfo->chameleonConfig);␊ |
273 | ␉// needed at least for laptops␊ |
274 | bool fullBanks = Platform.DMI.MemoryModules == Platform.DMI.CntMemorySlots;␊ |
275 | ␊ |
276 | ␉char spdbuf[MAX_SPD_SIZE];␊ |
277 | // Search MAX_RAM_SLOTS slots␊ |
278 | for (i = 0; i < MAX_RAM_SLOTS; i++){␊ |
279 | slot = &Platform.RAM.DIMM[i];␊ |
280 | spd_size = smb_read_byte_intel(base, 0x50 + i, 0);␊ |
281 | ␉␉DBG("SPD[0] (size): %d @0x%x\n", spd_size, 0x50 + i);␊ |
282 | // Check spd is present␊ |
283 | if (spd_size && (spd_size != 0xff))␊ |
284 | {␊ |
285 | ␊ |
286 | ␉␉␉slot->spd = spdbuf;␊ |
287 | slot->InUse = true;␊ |
288 | ␊ |
289 | bzero(slot->spd, spd_size);␊ |
290 | ␊ |
291 | // Copy spd data into buffer␊ |
292 | ␊ |
293 | ␉␉␉//for (x = 0; x < spd_size; x++) slot->spd[x] = smb_read_byte_intel(base, 0x50 + i, x);␊ |
294 | init_spd(slot->spd, base, i);␊ |
295 | ␉␉␊ |
296 | switch (slot->spd[SPD_MEMORY_TYPE]) {␊ |
297 | case SPD_MEMORY_TYPE_SDRAM_DDR2:␊ |
298 | ␊ |
299 | slot->ModuleSize = ((1 << (slot->spd[SPD_NUM_ROWS] & 0x0f) + (slot->spd[SPD_NUM_COLUMNS] & 0x0f) - 17) * ␊ |
300 | ((slot->spd[SPD_NUM_DIMM_BANKS] & 0x7) + 1) * slot->spd[SPD_NUM_BANKS_PER_SDRAM]);␊ |
301 | break;␊ |
302 | ␊ |
303 | case SPD_MEMORY_TYPE_SDRAM_DDR3:␊ |
304 | ␊ |
305 | slot->ModuleSize = ((slot->spd[4] & 0x0f) + 28 ) + ((slot->spd[8] & 0x7) + 3 );␊ |
306 | slot->ModuleSize -= (slot->spd[7] & 0x7) + 25;␊ |
307 | slot->ModuleSize = ((1 << slot->ModuleSize) * (((slot->spd[7] >> 3) & 0x1f) + 1));␊ |
308 | ␊ |
309 | break;␊ |
310 | }␊ |
311 | ␊ |
312 | spd_type = (slot->spd[SPD_MEMORY_TYPE] < ((char) 12) ? slot->spd[SPD_MEMORY_TYPE] : 0);␊ |
313 | slot->Type = spd_mem_to_smbios[spd_type];␊ |
314 | slot->PartNo = getDDRPartNum(slot->spd, base, i);␊ |
315 | slot->Vendor = getVendorName(slot, base, i);␊ |
316 | slot->SerialNo = getDDRSerial(slot->spd);␊ |
317 | ␊ |
318 | // determine spd speed␊ |
319 | speed = getDDRspeedMhz(slot->spd);␊ |
320 | if (slot->Frequency<speed) slot->Frequency = speed;␊ |
321 | ␉␉␉␊ |
322 | ␉␉␉// pci memory controller if available, is more reliable␊ |
323 | ␉␉␉if (Platform.RAM.Frequency > 0) {␊ |
324 | ␉␉␉␉uint32_t freq = (uint32_t)Platform.RAM.Frequency / 500000;␊ |
325 | ␉␉␉␉// now round off special cases␊ |
326 | ␉␉␉␉uint32_t fmod100 = freq %100;␊ |
327 | ␉␉␉␉switch(fmod100) {␊ |
328 | ␉␉␉␉␉case 1:␉freq--;␉break;␊ |
329 | ␉␉␉␉␉case 32:␉freq++;␉break;␊ |
330 | ␉␉␉␉␉case 65:␉freq++; break;␊ |
331 | ␉␉␉␉␉case 98:␉freq+=2;break;␊ |
332 | ␉␉␉␉␉case 99:␉freq++; break;␊ |
333 | ␉␉␉␉}␊ |
334 | ␉␉␉␉slot->Frequency = freq;␊ |
335 | ␉␉␉}␊ |
336 | ␊ |
337 | ␉␉␉verbose("Slot: %d Type %d %dMB (%s) %dMHz Vendor=%s\n PartNo=%s SerialNo=%s\n", ␊ |
338 | i, ␊ |
339 | (int)slot->Type,␊ |
340 | slot->ModuleSize, ␊ |
341 | spd_memory_types[spd_type],␊ |
342 | slot->Frequency,␊ |
343 | slot->Vendor,␊ |
344 | slot->PartNo,␊ |
345 | slot->SerialNo); ␊ |
346 | ␊ |
347 | ␊ |
348 | }␊ |
349 | ␊ |
350 | // laptops sometimes show slot 0 and 2 with slot 1 empty when only 2 slots are presents so:␊ |
351 | Platform.DMI.DIMM[i]= ␊ |
352 | i>0 && Platform.RAM.DIMM[1].InUse==false && fullBanks && Platform.DMI.CntMemorySlots == 2 ? ␊ |
353 | mapping[i] : i; // for laptops case, mapping setup would need to be more generic than this␊ |
354 | ␊ |
355 | ␊ |
356 | ␊ |
357 | ␉␉slot->spd = NULL;␊ |
358 | ␊ |
359 | } // for␊ |
360 | }␊ |
361 | ␊ |
362 | static struct smbus_controllers_t smbus_controllers[] = {␊ |
363 | ␊ |
364 | ␉{0x8086, 0x269B, "ESB2",␉␉read_smb_intel },␊ |
365 | ␉{0x8086, 0x25A4, "6300ESB",␉␉read_smb_intel },␊ |
366 | ␉{0x8086, 0x24C3, "ICH4",␉␉read_smb_intel },␊ |
367 | ␉{0x8086, 0x24D3, "ICH5",␉␉read_smb_intel },␊ |
368 | ␉{0x8086, 0x266A, "ICH6",␉␉read_smb_intel },␊ |
369 | ␉{0x8086, 0x27DA, "ICH7",␉␉read_smb_intel },␊ |
370 | ␉{0x8086, 0x283E, "ICH8",␉␉read_smb_intel },␊ |
371 | ␉{0x8086, 0x2930, "ICH9",␉␉read_smb_intel },␉␊ |
372 | ␉{0x8086, 0x3A30, "ICH10R",␉␉read_smb_intel },␊ |
373 | ␉{0x8086, 0x3A60, "ICH10B",␉␉read_smb_intel },␊ |
374 | ␉{0x8086, 0x3B30, "5 Series",␉␉read_smb_intel },␊ |
375 | ␉{0x8086, 0x1C22, "6 Series",␉␉read_smb_intel },␊ |
376 | ␉{0x8086, 0x1E22, "7 Series",␉␉read_smb_intel },␊ |
377 | ␉{0x8086, 0x5032, "EP80579",␉␉read_smb_intel },␊ |
378 | ␉{0x8086, 0x1D22, "X79 Series",␉␉read_smb_intel },␊ |
379 | ␊ |
380 | };␊ |
381 | ␊ |
382 | // initial call : pci_dt = root_pci_dev;␊ |
383 | // find_and_read_smbus_controller(root_pci_dev);␊ |
384 | bool find_and_read_smbus_controller(pci_dt_t* pci_dt)␊ |
385 | {␊ |
386 | pci_dt_t␉*current = pci_dt;␊ |
387 | int i;␊ |
388 | ␊ |
389 | while (current) {␊ |
390 | #if 0␊ |
391 | printf("%02x:%02x.%x [%04x] [%04x:%04x] :: %s\n", ␊ |
392 | current->dev.bits.bus, current->dev.bits.dev, current->dev.bits.func, ␊ |
393 | current->class_id, current->vendor_id, current->device_id, ␊ |
394 | get_pci_dev_path(current));␊ |
395 | #endif␊ |
396 | ␉for ( i = 0; i < sizeof(smbus_controllers) / sizeof(smbus_controllers[0]); i++ )␊ |
397 | {␊ |
398 | if (current->vendor_id == smbus_controllers[i].vendor &&␊ |
399 | current->device_id == smbus_controllers[i].device)␊ |
400 | {␊ |
401 | smbus_controllers[i].read_smb(current); // read smb␊ |
402 | return true;␊ |
403 | } ␊ |
404 | }␊ |
405 | find_and_read_smbus_controller(current->children);␊ |
406 | current = current->next;␊ |
407 | }␊ |
408 | return false; // not found␊ |
409 | }␊ |
410 | ␊ |
411 | void scan_spd(PlatformInfo_t *p)␊ |
412 | {␊ |
413 | find_and_read_smbus_controller(root_pci_dev);␊ |
414 | }␊ |
415 | ␊ |
416 |