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 = malloc(strlen("no_status")); 200 strcpy(bank_status, "no_status"); 201 } 202 203 /* 204 * create a bank_node for this bank 205 * and add it to the list. 206 */ 207 add_bank_node(madr[i], portid, bank_status); 208 209 /* 210 * find the segment to which this bank 211 * belongs. If it doesn't already exist 212 * then create it. If it exists, add to it. 213 */ 214 add_seg_node(); 215 } 216 } 217 return (0); 218 } 219 220 static void 221 add_bank_node(uint64_t mc_decode, int portid, char *bank_status) 222 { 223 static int id = 0; 224 memory_bank_t *new, *bank; 225 uint32_t ifactor = MC_INTLV(mc_decode); 226 uint64_t seg_size; 227 228 if ((new = malloc(sizeof (memory_bank_t))) == NULL) { 229 perror("malloc"); 230 exit(1); 231 } 232 233 new->portid = portid; 234 new->id = id++; 235 new->valid = (mc_decode >> 63); 236 new->uk = MC_UK(mc_decode); 237 new->um = MC_UM(mc_decode); 238 new->lk = MC_LK(mc_decode); 239 new->lm = MC_LM(mc_decode); 240 241 seg_size = ((((uint64_t)new->uk & MEM_UK_SIZE_MASK) + 1) << 26); 242 new->bank_size = seg_size / ifactor; 243 new->bank_status = bank_status; 244 245 new->next = NULL; 246 new->seg_next = NULL; 247 248 /* Handle the first bank found */ 249 if (bank_head == NULL) { 250 bank_head = new; 251 bank_tail = new; 252 return; 253 } 254 255 /* find last bank in list */ 256 bank = bank_head; 257 while (bank->next) 258 bank = bank->next; 259 260 /* insert this bank into the list */ 261 bank->next = new; 262 bank_tail = new; 263 } 264 265 void 266 display_us3_banks(void) 267 { 268 uint64_t base, bank_size; 269 uint32_t intlv; 270 memory_bank_t *bank, *tmp_bank; 271 memory_seg_t *seg; 272 int mcid; 273 uint64_t dimm_size; 274 uint64_t total_bank_size = 0; 275 uint64_t total_sys_mem; 276 static uint64_t bank0_size, bank1_size, bank2_size, bank3_size; 277 278 if ((bank_head == NULL) || (seg_head == NULL)) { 279 log_printf("\nCannot find any memory bank/segment info.\n"); 280 return; 281 } 282 283 for (bank = bank_head; bank; bank = bank->next) { 284 /* 285 * Interleave factor is determined from the 286 * lk bits in the Mem Addr Decode register. 287 * 288 * The Base Address of the memory segment in which this 289 * bank belongs is determined from the um abd uk bits 290 * of the Mem Addr Decode register. 291 * 292 * See section 9.1.5 of Cheetah Programmer's reference 293 * manual. 294 */ 295 intlv = ((bank->lk ^ 0xF) + 1); 296 base = bank->um & ~(bank->uk); 297 298 mcid = SG_PORTID_TO_SAFARI_ID(bank->portid); 299 300 /* If bank is not valid, set size to zero incase it's garbage */ 301 if (bank->valid) 302 bank_size = ((bank->bank_size) / MBYTE); 303 else 304 bank_size = 0; 305 306 /* 307 * Keep track of all banks found so we can check later 308 * that this value matches the total memory in the 309 * system using the pagesize and number of pages. 310 */ 311 total_bank_size += bank_size; 312 313 /* Find the matching segment for this bank. */ 314 seg = match_seg(base); 315 316 /* 317 * Find the Dimm size by adding banks 0 + 2 and divide by 4 318 * and then adding banks 1 + 3 and divide by 4. We divide 319 * by 2 if one of the logical banks size is zero. 320 */ 321 switch ((bank->id) % 4) { 322 case 0: 323 /* have bank0_size, need bank2_size */ 324 bank0_size = bank_size; 325 bank2_size = 0; 326 327 tmp_bank = bank->next; 328 while (tmp_bank) { 329 if (tmp_bank->valid == 0) { 330 tmp_bank = tmp_bank->next; 331 continue; 332 } 333 /* Is next bank on the same mc ? */ 334 if (mcid != SG_PORTID_TO_SAFARI_ID( 335 tmp_bank->portid)) { 336 break; 337 } 338 if ((tmp_bank->id) % 4 == 2) { 339 bank2_size = 340 (tmp_bank->bank_size / MBYTE); 341 break; 342 } 343 tmp_bank = tmp_bank->next; 344 } 345 if (bank2_size) 346 dimm_size = (bank0_size + bank2_size) / 4; 347 else 348 dimm_size = bank0_size / 2; 349 break; 350 case 1: 351 /* have bank1_size, need bank3_size */ 352 bank1_size = bank_size; 353 bank3_size = 0; 354 355 tmp_bank = bank->next; 356 while (tmp_bank) { 357 if (tmp_bank->valid == 0) { 358 tmp_bank = tmp_bank->next; 359 continue; 360 } 361 /* Is next bank on the same mc ? */ 362 if (mcid != SG_PORTID_TO_SAFARI_ID( 363 tmp_bank->portid)) { 364 break; 365 } 366 if ((tmp_bank->id) % 4 == 3) { 367 bank3_size = 368 (tmp_bank->bank_size / MBYTE); 369 break; 370 } 371 tmp_bank = tmp_bank->next; 372 } 373 if (bank3_size) 374 dimm_size = (bank1_size + bank3_size) / 4; 375 else 376 dimm_size = bank1_size / 2; 377 break; 378 case 2: 379 /* have bank0_size and bank2_size */ 380 bank2_size = bank_size; 381 if (bank0_size) 382 dimm_size = (bank0_size + bank2_size) / 4; 383 else 384 dimm_size = bank2_size / 2; 385 break; 386 case 3: 387 /* have bank1_size and bank3_size */ 388 bank3_size = bank_size; 389 if (bank1_size) 390 dimm_size = (bank1_size + bank3_size) / 4; 391 else 392 dimm_size = bank3_size / 4; 393 break; 394 } 395 396 if (bank->valid == 0) 397 continue; 398 399 /* 400 * Call platform specific code for formatting memory 401 * information. 402 */ 403 print_us3_memory_line(bank->portid, bank->id, bank_size, 404 bank->bank_status, dimm_size, intlv, seg->id); 405 } 406 407 printf("\n"); 408 409 /* 410 * Sanity check to ensure that the total amount of system 411 * memory matches the total number of memory banks that 412 * we find here. Scream if there is a mis-match. 413 */ 414 total_sys_mem = (((uint64_t)sysconf(_SC_PAGESIZE) * \ 415 (uint64_t)sysconf(_SC_PHYS_PAGES)) / MBYTE); 416 417 if (total_bank_size != total_sys_mem) { 418 log_printf(dgettext(TEXT_DOMAIN, 419 "\nError: total bank size [%lldMB] does not match total " 420 "system memory [%lldMB]\n"), total_bank_size, 421 total_sys_mem, 0); 422 } 423 424 } 425 426 static void 427 add_seg_node(void) 428 { 429 uint64_t base; 430 memory_seg_t *new; 431 static int id = 0; 432 memory_bank_t *bank = bank_tail; 433 434 if (bank->valid != 1) 435 return; 436 437 base = bank->um & ~(bank->uk); 438 439 if ((new = match_seg(base)) == NULL) { 440 /* 441 * This bank is part of a new segment, so create 442 * a struct for it and added to the list of segments 443 */ 444 if ((new = malloc(sizeof (memory_seg_t))) == NULL) { 445 perror("malloc"); 446 exit(1); 447 } 448 new->id = id++; 449 new->base = base; 450 new->size = (((uint64_t)bank->uk +1) << 26); 451 new->intlv = ((bank->lk ^ 0xF) + 1); 452 453 /* 454 * add to the seg list 455 */ 456 new->next = seg_head; 457 seg_head = new; 458 } 459 460 new->nbanks++; 461 /* 462 * add bank into segs bank list. Note we add at the head 463 */ 464 bank->seg_next = new->banks; 465 new->banks = bank; 466 } 467 468 static memory_seg_t * 469 match_seg(uint64_t base) 470 { 471 memory_seg_t *cur_seg; 472 473 for (cur_seg = seg_head; cur_seg; cur_seg = cur_seg->next) { 474 if (cur_seg-> base == base) 475 break; 476 } 477 return (cur_seg); 478 } 479 480 /*ARGSUSED0*/ 481 void 482 print_us3_memory_line(int portid, int bank_id, uint64_t bank_size, 483 char *bank_status, uint64_t dimm_size, uint32_t intlv, int seg_id) 484 { 485 log_printf(dgettext(TEXT_DOMAIN, 486 "\n No print_us3_memory_line() function specified for" 487 " this platform\n"), 0); 488 } 489 490 int 491 display_us3_failed_banks(int system_failed) 492 { 493 memory_bank_t *bank; 494 int found_failed_bank = 0; 495 496 if ((bank_head == NULL) || (seg_head == NULL)) { 497 log_printf("\nCannot find any memory bank/segment info.\n"); 498 return (1); 499 } 500 501 for (bank = bank_head; bank; bank = bank->next) { 502 /* 503 * check to see if the bank is invalid and also 504 * check if the bank_status is unpopulated. Unpopulated 505 * means the bank is empty. 506 */ 507 508 if ((bank->valid == 0) && 509 (strcmp(bank->bank_status, "unpopulated"))) { 510 if (!system_failed && !found_failed_bank) { 511 found_failed_bank = TRUE; 512 log_printf("\n", 0); 513 log_printf(dgettext(TEXT_DOMAIN, 514 "Failed Field Replaceable Units (FRU) in " 515 "System:\n"), 0); 516 log_printf("==========================" 517 "====================\n", 0); 518 } 519 /* 520 * Call platform specific code for formatting memory 521 * information. 522 */ 523 print_us3_failed_memory_line(bank->portid, bank->id, 524 bank->bank_status); 525 } 526 } 527 if (found_failed_bank) 528 return (1); 529 else 530 return (0); 531 } 532 533 /*ARGSUSED0*/ 534 void 535 print_us3_failed_memory_line(int portid, int bank_id, char *bank_status) 536 { 537 log_printf(dgettext(TEXT_DOMAIN, 538 "\n No print_us3_failed_memory_line() function specified for" 539 " this platform\n"), 0); 540 } 541