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 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 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 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 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 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 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 * 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 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 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 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