xref: /illumos-gate/usr/src/lib/libprtdiag/common/memory.c (revision dd72704bd9e794056c558153663c739e2012d721)
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