1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright (c) 1999-2001 by Sun Microsystems, Inc.
24 * All rights reserved.
25 * Copyright (c) 2020 Peter Tribble.
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <ctype.h>
32 #include <string.h>
33 #include <kvm.h>
34 #include <varargs.h>
35 #include <errno.h>
36 #include <time.h>
37 #include <dirent.h>
38 #include <fcntl.h>
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/utsname.h>
43 #include <sys/openpromio.h>
44 #include <kstat.h>
45 #include <libintl.h>
46 #include <syslog.h>
47 #include <sys/dkio.h>
48 #include <sys/sbd_ioctl.h>
49 #include <sys/sbdp_mem.h>
50 #include <sys/serengeti.h>
51 #include <sys/mc.h>
52 #include "pdevinfo.h"
53 #include "display.h"
54 #include "pdevinfo_sun4u.h"
55 #include "display_sun4u.h"
56 #include "libprtdiag.h"
57
58 #if !defined(TEXT_DOMAIN)
59 #define TEXT_DOMAIN "SYS_TEST"
60 #endif
61
62 #define KBYTE 1024
63 #define MBYTE (KBYTE * KBYTE)
64
65 #define MEM_UK_SIZE_MASK 0x3FF
66
67 /*
68 * Global variables.
69 */
70 static memory_bank_t *bank_head;
71 static memory_bank_t *bank_tail;
72 static memory_seg_t *seg_head;
73
74 /*
75 * Local functions.
76 */
77 static void add_bank_node(uint64_t mc_decode, int portid, char *bank_status);
78 static void add_seg_node(void);
79 static memory_seg_t *match_seg(uint64_t);
80
81
82 /*
83 * Used for US-I and US-II systems
84 */
85 /*ARGSUSED0*/
86 void
display_memorysize(Sys_tree * tree,struct system_kstat_data * kstats,struct mem_total * memory_total)87 display_memorysize(Sys_tree *tree, struct system_kstat_data *kstats,
88 struct mem_total *memory_total)
89 {
90 log_printf(dgettext(TEXT_DOMAIN, "Memory size: "), 0);
91
92 if (sysconf(_SC_PAGESIZE) == -1 || sysconf(_SC_PHYS_PAGES) == -1)
93 log_printf(dgettext(TEXT_DOMAIN, "unable to determine\n"), 0);
94 else {
95 uint64_t mem_size;
96
97 mem_size =
98 (uint64_t)sysconf(_SC_PAGESIZE) * \
99 (uint64_t)sysconf(_SC_PHYS_PAGES);
100
101 if (mem_size >= MBYTE)
102 log_printf(dgettext(TEXT_DOMAIN, "%d Megabytes\n"),
103 (int)((mem_size+MBYTE-1) / MBYTE), 0);
104 else
105 log_printf(dgettext(TEXT_DOMAIN, "%d Kilobytes\n"),
106 (int)((mem_size+KBYTE-1) / KBYTE), 0);
107 }
108 }
109
110 /*ARGSUSED0*/
111 void
display_memoryconf(Sys_tree * tree)112 display_memoryconf(Sys_tree *tree)
113 {
114 /*
115 * This function is intentionally blank
116 */
117 }
118
119 /*
120 * The following functions are for use by any US-III based systems.
121 * All they need to do is to call get_us3_mem_regs()
122 * and then display_us3_banks(). Each platform then needs to decide how
123 * to format this data by over-riding the generic function
124 * print_us3_memory_line().
125 */
126 int
get_us3_mem_regs(Board_node * bnode)127 get_us3_mem_regs(Board_node *bnode)
128 {
129 Prom_node *pnode;
130 int portid;
131 uint64_t *ma_reg_arr;
132 uint64_t madr[NUM_MBANKS_PER_MC];
133 void *bank_status_array;
134 char *bank_status;
135 int i, status_offset;
136
137 for (pnode = dev_find_node(bnode->nodes, "memory-controller");
138 pnode != NULL;
139 pnode = dev_next_node(pnode, "memory-controller")) {
140
141 /* Get portid of this mc from libdevinfo. */
142 portid = (*(int *)get_prop_val(find_prop(pnode, "portid")));
143
144 /* read the logical_bank_ma_regs property for this mc node. */
145 ma_reg_arr = (uint64_t *)get_prop_val(
146 find_prop(pnode, MEM_CFG_PROP_NAME));
147
148 /*
149 * There are situations where a memory-controller node
150 * will not have the logical_bank_ma_regs property and
151 * we need to allow for these cases. They include:
152 * - Excalibur/Littleneck systems that only
153 * support memory on one of their CPUs.
154 * - Systems that support DR where a cpu board
155 * can be unconfigured but still connected.
156 * It is up to the caller of this function to ensure
157 * that the bank_head and seg_head pointers are not
158 * NULL after processing all memory-controllers in the
159 * system. This would indicate a situation where no
160 * memory-controllers in the system have a logical_bank_ma_regs
161 * property which should never happen.
162 */
163 if (ma_reg_arr == NULL)
164 continue;
165
166 /*
167 * The first NUM_MBANKS_PER_MC of uint64_t's in the
168 * logical_bank_ma_regs property are the madr values.
169 */
170 for (i = 0; i < NUM_MBANKS_PER_MC; i++) {
171 madr[i] = *ma_reg_arr++;
172 }
173
174 /*
175 * Get the bank_status property for this mem controller from
176 * OBP. This contains the bank-status for each logical bank.
177 */
178 bank_status_array = (void *)get_prop_val(
179 find_prop(pnode, "bank-status"));
180 status_offset = 0;
181
182 /*
183 * process each logical bank
184 */
185 for (i = 0; i < NUM_MBANKS_PER_MC; i++) {
186 /*
187 * Get the bank-status string for this bank
188 * from the bank_status_array we just retrieved
189 * from OBP. If the prop was not found, we
190 * malloc a bank_status and set it to "no_status".
191 */
192 if (bank_status_array) {
193 bank_status = ((char *)bank_status_array +
194 status_offset);
195
196 /* Move offset to next bank_status string */
197 status_offset += (strlen(bank_status) + 1);
198 } else {
199 bank_status = strdup("no_status");
200 }
201
202 /*
203 * create a bank_node for this bank
204 * and add it to the list.
205 */
206 add_bank_node(madr[i], portid, bank_status);
207
208 /*
209 * find the segment to which this bank
210 * belongs. If it doesn't already exist
211 * then create it. If it exists, add to it.
212 */
213 add_seg_node();
214 }
215 }
216 return (0);
217 }
218
219 static void
add_bank_node(uint64_t mc_decode,int portid,char * bank_status)220 add_bank_node(uint64_t mc_decode, int portid, char *bank_status)
221 {
222 static int id = 0;
223 memory_bank_t *new, *bank;
224 uint32_t ifactor = MC_INTLV(mc_decode);
225 uint64_t seg_size;
226
227 if ((new = malloc(sizeof (memory_bank_t))) == NULL) {
228 perror("malloc");
229 exit(1);
230 }
231
232 new->portid = portid;
233 new->id = id++;
234 new->valid = (mc_decode >> 63);
235 new->uk = MC_UK(mc_decode);
236 new->um = MC_UM(mc_decode);
237 new->lk = MC_LK(mc_decode);
238 new->lm = MC_LM(mc_decode);
239
240 seg_size = ((((uint64_t)new->uk & MEM_UK_SIZE_MASK) + 1) << 26);
241 new->bank_size = seg_size / ifactor;
242 new->bank_status = bank_status;
243
244 new->next = NULL;
245 new->seg_next = NULL;
246
247 /* Handle the first bank found */
248 if (bank_head == NULL) {
249 bank_head = new;
250 bank_tail = new;
251 return;
252 }
253
254 /* find last bank in list */
255 bank = bank_head;
256 while (bank->next)
257 bank = bank->next;
258
259 /* insert this bank into the list */
260 bank->next = new;
261 bank_tail = new;
262 }
263
264 void
display_us3_banks(void)265 display_us3_banks(void)
266 {
267 uint64_t base, bank_size;
268 uint32_t intlv;
269 memory_bank_t *bank, *tmp_bank;
270 memory_seg_t *seg;
271 int mcid;
272 uint64_t dimm_size;
273 uint64_t total_bank_size = 0;
274 uint64_t total_sys_mem;
275 static uint64_t bank0_size, bank1_size, bank2_size, bank3_size;
276
277 if ((bank_head == NULL) || (seg_head == NULL)) {
278 log_printf("\nCannot find any memory bank/segment info.\n");
279 return;
280 }
281
282 for (bank = bank_head; bank; bank = bank->next) {
283 /*
284 * Interleave factor is determined from the
285 * lk bits in the Mem Addr Decode register.
286 *
287 * The Base Address of the memory segment in which this
288 * bank belongs is determined from the um abd uk bits
289 * of the Mem Addr Decode register.
290 *
291 * See section 9.1.5 of Cheetah Programmer's reference
292 * manual.
293 */
294 intlv = ((bank->lk ^ 0xF) + 1);
295 base = bank->um & ~(bank->uk);
296
297 mcid = SG_PORTID_TO_SAFARI_ID(bank->portid);
298
299 /* If bank is not valid, set size to zero incase it's garbage */
300 if (bank->valid)
301 bank_size = ((bank->bank_size) / MBYTE);
302 else
303 bank_size = 0;
304
305 /*
306 * Keep track of all banks found so we can check later
307 * that this value matches the total memory in the
308 * system using the pagesize and number of pages.
309 */
310 total_bank_size += bank_size;
311
312 /* Find the matching segment for this bank. */
313 seg = match_seg(base);
314
315 /*
316 * Find the Dimm size by adding banks 0 + 2 and divide by 4
317 * and then adding banks 1 + 3 and divide by 4. We divide
318 * by 2 if one of the logical banks size is zero.
319 */
320 switch ((bank->id) % 4) {
321 case 0:
322 /* have bank0_size, need bank2_size */
323 bank0_size = bank_size;
324 bank2_size = 0;
325
326 tmp_bank = bank->next;
327 while (tmp_bank) {
328 if (tmp_bank->valid == 0) {
329 tmp_bank = tmp_bank->next;
330 continue;
331 }
332 /* Is next bank on the same mc ? */
333 if (mcid != SG_PORTID_TO_SAFARI_ID(
334 tmp_bank->portid)) {
335 break;
336 }
337 if ((tmp_bank->id) % 4 == 2) {
338 bank2_size =
339 (tmp_bank->bank_size / MBYTE);
340 break;
341 }
342 tmp_bank = tmp_bank->next;
343 }
344 if (bank2_size)
345 dimm_size = (bank0_size + bank2_size) / 4;
346 else
347 dimm_size = bank0_size / 2;
348 break;
349 case 1:
350 /* have bank1_size, need bank3_size */
351 bank1_size = bank_size;
352 bank3_size = 0;
353
354 tmp_bank = bank->next;
355 while (tmp_bank) {
356 if (tmp_bank->valid == 0) {
357 tmp_bank = tmp_bank->next;
358 continue;
359 }
360 /* Is next bank on the same mc ? */
361 if (mcid != SG_PORTID_TO_SAFARI_ID(
362 tmp_bank->portid)) {
363 break;
364 }
365 if ((tmp_bank->id) % 4 == 3) {
366 bank3_size =
367 (tmp_bank->bank_size / MBYTE);
368 break;
369 }
370 tmp_bank = tmp_bank->next;
371 }
372 if (bank3_size)
373 dimm_size = (bank1_size + bank3_size) / 4;
374 else
375 dimm_size = bank1_size / 2;
376 break;
377 case 2:
378 /* have bank0_size and bank2_size */
379 bank2_size = bank_size;
380 if (bank0_size)
381 dimm_size = (bank0_size + bank2_size) / 4;
382 else
383 dimm_size = bank2_size / 2;
384 break;
385 case 3:
386 /* have bank1_size and bank3_size */
387 bank3_size = bank_size;
388 if (bank1_size)
389 dimm_size = (bank1_size + bank3_size) / 4;
390 else
391 dimm_size = bank3_size / 4;
392 break;
393 }
394
395 if (bank->valid == 0)
396 continue;
397
398 /*
399 * Call platform specific code for formatting memory
400 * information.
401 */
402 print_us3_memory_line(bank->portid, bank->id, bank_size,
403 bank->bank_status, dimm_size, intlv, seg->id);
404 }
405
406 printf("\n");
407
408 /*
409 * Sanity check to ensure that the total amount of system
410 * memory matches the total number of memory banks that
411 * we find here. Scream if there is a mis-match.
412 */
413 total_sys_mem = (((uint64_t)sysconf(_SC_PAGESIZE) * \
414 (uint64_t)sysconf(_SC_PHYS_PAGES)) / MBYTE);
415
416 if (total_bank_size != total_sys_mem) {
417 log_printf(dgettext(TEXT_DOMAIN,
418 "\nError: total bank size [%lldMB] does not match total "
419 "system memory [%lldMB]\n"), total_bank_size,
420 total_sys_mem, 0);
421 }
422
423 }
424
425 static void
add_seg_node(void)426 add_seg_node(void)
427 {
428 uint64_t base;
429 memory_seg_t *new;
430 static int id = 0;
431 memory_bank_t *bank = bank_tail;
432
433 if (bank->valid != 1)
434 return;
435
436 base = bank->um & ~(bank->uk);
437
438 if ((new = match_seg(base)) == NULL) {
439 /*
440 * This bank is part of a new segment, so create
441 * a struct for it and added to the list of segments
442 */
443 if ((new = malloc(sizeof (memory_seg_t))) == NULL) {
444 perror("malloc");
445 exit(1);
446 }
447 new->id = id++;
448 new->base = base;
449 new->size = (((uint64_t)bank->uk +1) << 26);
450 new->intlv = ((bank->lk ^ 0xF) + 1);
451
452 /*
453 * add to the seg list
454 */
455 new->next = seg_head;
456 seg_head = new;
457 }
458
459 new->nbanks++;
460 /*
461 * add bank into segs bank list. Note we add at the head
462 */
463 bank->seg_next = new->banks;
464 new->banks = bank;
465 }
466
467 static memory_seg_t *
match_seg(uint64_t base)468 match_seg(uint64_t base)
469 {
470 memory_seg_t *cur_seg;
471
472 for (cur_seg = seg_head; cur_seg; cur_seg = cur_seg->next) {
473 if (cur_seg-> base == base)
474 break;
475 }
476 return (cur_seg);
477 }
478
479 /*ARGSUSED0*/
480 void
print_us3_memory_line(int portid,int bank_id,uint64_t bank_size,char * bank_status,uint64_t dimm_size,uint32_t intlv,int seg_id)481 print_us3_memory_line(int portid, int bank_id, uint64_t bank_size,
482 char *bank_status, uint64_t dimm_size, uint32_t intlv, int seg_id)
483 {
484 log_printf(dgettext(TEXT_DOMAIN,
485 "\n No print_us3_memory_line() function specified for"
486 " this platform\n"), 0);
487 }
488
489 int
display_us3_failed_banks(int system_failed)490 display_us3_failed_banks(int system_failed)
491 {
492 memory_bank_t *bank;
493 int found_failed_bank = 0;
494
495 if ((bank_head == NULL) || (seg_head == NULL)) {
496 log_printf("\nCannot find any memory bank/segment info.\n");
497 return (1);
498 }
499
500 for (bank = bank_head; bank; bank = bank->next) {
501 /*
502 * check to see if the bank is invalid and also
503 * check if the bank_status is unpopulated. Unpopulated
504 * means the bank is empty.
505 */
506
507 if ((bank->valid == 0) &&
508 (strcmp(bank->bank_status, "unpopulated"))) {
509 if (!system_failed && !found_failed_bank) {
510 found_failed_bank = TRUE;
511 log_printf("\n", 0);
512 log_printf(dgettext(TEXT_DOMAIN,
513 "Failed Field Replaceable Units (FRU) in "
514 "System:\n"), 0);
515 log_printf("=========================="
516 "====================\n", 0);
517 }
518 /*
519 * Call platform specific code for formatting memory
520 * information.
521 */
522 print_us3_failed_memory_line(bank->portid, bank->id,
523 bank->bank_status);
524 }
525 }
526 if (found_failed_bank)
527 return (1);
528 else
529 return (0);
530 }
531
532 /*ARGSUSED0*/
533 void
print_us3_failed_memory_line(int portid,int bank_id,char * bank_status)534 print_us3_failed_memory_line(int portid, int bank_id, char *bank_status)
535 {
536 log_printf(dgettext(TEXT_DOMAIN,
537 "\n No print_us3_failed_memory_line() function specified for"
538 " this platform\n"), 0);
539 }
540