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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26#include <unistd.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <stdarg.h>
30#include <string.h>
31#include <strings.h>
32#include <limits.h>
33#include <alloca.h>
34#include <kstat.h>
35#include <fcntl.h>
36#include <errno.h>
37#include <libnvpair.h>
38#include <sys/types.h>
39#include <sys/bitmap.h>
40#include <sys/processor.h>
41#include <sys/param.h>
42#include <sys/fm/protocol.h>
43#include <sys/systeminfo.h>
44#include <sys/mc.h>
45#include <sys/mc_amd.h>
46#include <sys/mc_intel.h>
47#include <fm/topo_mod.h>
48
49#include "chip.h"
50
51#ifndef MAX
52#define	MAX(a, b)	((a) > (b) ? (a) : (b))
53#endif
54
55static const topo_pgroup_info_t dimm_channel_pgroup =
56	{ PGNAME(CHAN), TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, 1 };
57static const topo_pgroup_info_t dimm_pgroup =
58	{ PGNAME(DIMM), TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, 1 };
59static const topo_pgroup_info_t rank_pgroup =
60	{ PGNAME(RANK), TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, 1 };
61static const topo_pgroup_info_t mc_pgroup =
62	{ PGNAME(MCT), TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, 1 };
63
64static const topo_method_t dimm_methods[] = {
65	{ SIMPLE_DIMM_LBL, "Property method", 0, TOPO_STABILITY_INTERNAL,
66	    simple_dimm_label},
67	{ SIMPLE_DIMM_LBL_MP, "Property method", 0, TOPO_STABILITY_INTERNAL,
68	    simple_dimm_label_mp},
69	{ SEQ_DIMM_LBL, "Property method", 0, TOPO_STABILITY_INTERNAL,
70	    seq_dimm_label},
71	{ NULL }
72};
73
74extern const topo_method_t rank_methods[];
75extern const topo_method_t ntv_page_retire_methods[];
76
77static int mc_fd;
78
79int
80mc_offchip_open()
81{
82	mc_fd = open("/dev/mc/mc", O_RDONLY);
83	return (mc_fd != -1);
84}
85
86static int
87mc_onchip(topo_instance_t id)
88{
89	char path[64];
90
91	(void) snprintf(path, sizeof (path), "/dev/mc/mc%d", id);
92	mc_fd = open(path, O_RDONLY);
93	return (mc_fd != -1);
94}
95
96static void
97mc_add_ranks(topo_mod_t *mod, tnode_t *dnode, nvlist_t *auth, int dimm,
98    nvlist_t **ranks_nvp, int start_rank, int nranks, char *serial, char *part,
99    char *rev, int maxranks)
100{
101	int i;
102	int rank;
103	tnode_t *rnode;
104	nvpair_t *nvp;
105	nvlist_t *fmri;
106	int err = 0;
107
108	/*
109	 * If start_rank is defined, it is assigned to the first rank of this
110	 * dimm.
111	 */
112	rank = start_rank >= 0 ? start_rank : dimm * maxranks;
113	if (topo_node_range_create(mod, dnode, RANK, rank,
114	    rank + nranks - 1) < 0) {
115		whinge(mod, NULL, "mc_add_ranks: node range create failed"
116		    " for rank\n");
117		return;
118	}
119	for (i = 0; i < nranks; i++) {
120		fmri = topo_mod_hcfmri(mod, dnode, FM_HC_SCHEME_VERSION,
121		    RANK, rank, NULL, auth, part, rev, serial);
122		if (fmri == NULL) {
123			whinge(mod, NULL,
124			    "mc_add_ranks: topo_mod_hcfmri failed\n");
125			return;
126		}
127		if ((rnode = topo_node_bind(mod, dnode, RANK, rank,
128		    fmri)) == NULL) {
129			nvlist_free(fmri);
130			whinge(mod, NULL, "mc_add_ranks: node bind failed"
131			    " for ranks\n");
132			return;
133		}
134		(void) topo_node_fru_set(rnode, NULL, 0, &err);
135
136		if (topo_method_register(mod, rnode, rank_methods) < 0)
137			whinge(mod, &err, "rank_create: "
138			    "topo_method_register failed");
139
140		if (! is_xpv() && topo_method_register(mod, rnode,
141		    ntv_page_retire_methods) < 0)
142			whinge(mod, &err, "mc_add_ranks: "
143			    "topo_method_register failed");
144
145		(void) topo_node_asru_set(rnode, fmri, TOPO_ASRU_COMPUTE, &err);
146
147		if (FM_AWARE_SMBIOS(mod))
148			(void) topo_node_label_set(rnode, NULL, &err);
149
150		nvlist_free(fmri);
151
152		(void) topo_pgroup_create(rnode, &rank_pgroup, &err);
153		for (nvp = nvlist_next_nvpair(ranks_nvp[i], NULL); nvp != NULL;
154		    nvp = nvlist_next_nvpair(ranks_nvp[i], nvp)) {
155			(void) nvprop_add(mod, nvp, PGNAME(RANK), rnode);
156		}
157		rank++;
158	}
159}
160
161static void
162mc_add_dimms(topo_mod_t *mod, uint16_t chip_smbid, tnode_t *pnode,
163    nvlist_t *auth, nvlist_t **nvl, uint_t ndimms, int maxdimms, int maxranks)
164{
165	int i;
166	nvlist_t *fmri;
167	tnode_t *dnode;
168	nvpair_t *nvp;
169	int err;
170	nvlist_t **ranks_nvp;
171	int32_t start_rank = -1;
172	uint_t nranks = 0;
173	uint32_t dimm_number;
174	char *serial = NULL;
175	char *part = NULL;
176	char *rev = NULL;
177	char *label = NULL;
178	char *name;
179	id_t smbid;
180
181	if (topo_node_range_create(mod, pnode, DIMM, 0,
182	    maxdimms ? maxdimms-1 : ndimms-1) < 0) {
183		whinge(mod, NULL,
184		    "mc_add_dimms: node range create failed\n");
185		return;
186	}
187	for (i = 0; i < ndimms; i++) {
188		dimm_number = i;
189		for (nvp = nvlist_next_nvpair(nvl[i], NULL); nvp != NULL;
190		    nvp = nvlist_next_nvpair(nvl[i], nvp)) {
191			name = nvpair_name(nvp);
192			if (strcmp(name, MCINTEL_NVLIST_RANKS) == 0) {
193				(void) nvpair_value_nvlist_array(nvp,
194				    &ranks_nvp, &nranks);
195			} else if (strcmp(name, MCINTEL_NVLIST_1ST_RANK) == 0) {
196				(void) nvpair_value_int32(nvp, &start_rank);
197			} else if (strcmp(name, FM_FMRI_HC_SERIAL_ID) == 0) {
198				(void) nvpair_value_string(nvp, &serial);
199			} else if (strcmp(name, FM_FMRI_HC_PART) == 0) {
200				(void) nvpair_value_string(nvp, &part);
201			} else if (strcmp(name, FM_FMRI_HC_REVISION) == 0) {
202				(void) nvpair_value_string(nvp, &rev);
203			} else if (strcmp(name, FM_FAULT_FRU_LABEL) == 0) {
204				(void) nvpair_value_string(nvp, &label);
205			} else if (strcmp(name, MCINTEL_NVLIST_DIMM_NUM) == 0) {
206				(void) nvpair_value_uint32(nvp, &dimm_number);
207			}
208		}
209		fmri = NULL;
210
211		if (FM_AWARE_SMBIOS(mod)) {
212			int channum;
213
214			channum = topo_node_instance(pnode);
215			smbid = memnode_to_smbiosid(mod, chip_smbid,
216			    DIMM_NODE_NAME, i, &channum);
217			if (serial == NULL)
218				serial = (char *)chip_serial_smbios_get(mod,
219				    smbid);
220			if (part == NULL)
221				part = (char *)chip_part_smbios_get(mod,
222				    smbid);
223			if (rev == NULL)
224				rev = (char *)chip_rev_smbios_get(mod,
225				    smbid);
226		}
227
228		fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
229		    DIMM, dimm_number, NULL, auth, part, rev, serial);
230		if (fmri == NULL) {
231			whinge(mod, NULL,
232			    "mc_add_dimms: topo_mod_hcfmri failed\n");
233			return;
234		}
235		if ((dnode = topo_node_bind(mod, pnode, DIMM, dimm_number,
236		    fmri)) == NULL) {
237			nvlist_free(fmri);
238			whinge(mod, NULL, "mc_add_dimms: node bind failed"
239			    " for dimm\n");
240			return;
241		}
242
243		if (!FM_AWARE_SMBIOS(mod))
244			if (topo_method_register(mod, dnode, dimm_methods) < 0)
245				whinge(mod, NULL, "mc_add_dimms: "
246				    "topo_method_register failed");
247
248		(void) topo_pgroup_create(dnode, &dimm_pgroup, &err);
249
250		for (nvp = nvlist_next_nvpair(nvl[i], NULL); nvp != NULL;
251		    nvp = nvlist_next_nvpair(nvl[i], nvp)) {
252			name = nvpair_name(nvp);
253			if (strcmp(name, MCINTEL_NVLIST_RANKS) != 0 &&
254			    strcmp(name, FM_FAULT_FRU_LABEL) != 0 &&
255			    strcmp(name, MCINTEL_NVLIST_1ST_RANK) != 0) {
256				(void) nvprop_add(mod, nvp, PGNAME(DIMM),
257				    dnode);
258			}
259		}
260
261		if (FM_AWARE_SMBIOS(mod)) {
262			nvlist_free(fmri);
263			(void) topo_node_resource(dnode, &fmri, &err);
264			/*
265			 * We will use a full absolute parent/child label
266			 */
267			label = (char *)chip_label_smbios_get(mod,
268			    pnode, smbid, label);
269		}
270
271		(void) topo_node_label_set(dnode, label, &err);
272
273		if (FM_AWARE_SMBIOS(mod))
274			topo_mod_strfree(mod, label);
275
276		(void) topo_node_fru_set(dnode, fmri, 0, &err);
277		(void) topo_node_asru_set(dnode, fmri, 0, &err);
278		nvlist_free(fmri);
279
280		if (nranks) {
281			mc_add_ranks(mod, dnode, auth, dimm_number, ranks_nvp,
282			    start_rank, nranks, serial, part, rev, maxranks);
283		}
284	}
285}
286
287static int
288mc_add_channel(topo_mod_t *mod, uint16_t chip_smbid, tnode_t *pnode,
289    int channel, nvlist_t *auth, nvlist_t *nvl, int maxdimms, int maxranks)
290{
291	tnode_t *mc_channel;
292	nvlist_t *fmri;
293	nvlist_t **dimm_nvl;
294	nvpair_t *nvp;
295	char *name;
296	uint_t ndimms;
297	int err;
298
299	if (mkrsrc(mod, pnode, DRAMCHANNEL, channel, auth, &fmri) != 0) {
300		whinge(mod, NULL, "mc_add_channel: mkrsrc failed\n");
301		return (-1);
302	}
303	if ((mc_channel = topo_node_bind(mod, pnode, DRAMCHANNEL, channel,
304	    fmri)) == NULL) {
305		whinge(mod, NULL, "mc_add_channel: node bind failed for %s\n",
306		    DRAMCHANNEL);
307		nvlist_free(fmri);
308		return (-1);
309	}
310	(void) topo_node_fru_set(mc_channel, NULL, 0, &err);
311	nvlist_free(fmri);
312	(void) topo_pgroup_create(mc_channel, &dimm_channel_pgroup, &err);
313
314	if (FM_AWARE_SMBIOS(mod))
315		(void) topo_node_label_set(mc_channel, NULL, &err);
316
317	if (nvlist_lookup_nvlist_array(nvl, MCINTEL_NVLIST_DIMMS, &dimm_nvl,
318	    &ndimms) == 0) {
319		mc_add_dimms(mod, chip_smbid, mc_channel, auth, dimm_nvl,
320		    ndimms, maxdimms, maxranks);
321	}
322	for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL;
323	    nvp = nvlist_next_nvpair(nvl, nvp)) {
324		name = nvpair_name(nvp);
325		if (strcmp(name, MCINTEL_NVLIST_DIMMS) != 0) {
326			(void) nvprop_add(mod, nvp, PGNAME(CHAN),
327			    mc_channel);
328		}
329	}
330
331	return (0);
332}
333
334static int
335mc_nb_create(topo_mod_t *mod, uint16_t chip_smbid, tnode_t *pnode,
336    const char *name, nvlist_t *auth, nvlist_t *nvl)
337{
338	int err;
339	int i, j;
340	int channel;
341	uint8_t nmc;
342	uint8_t maxranks;
343	uint8_t maxdimms;
344	tnode_t *mcnode;
345	nvlist_t *fmri;
346	nvlist_t **channel_nvl;
347	nvpair_t *nvp;
348	char *pname;
349	uint_t nchannels;
350
351	if (nvlist_lookup_nvlist_array(nvl, MCINTEL_NVLIST_MC, &channel_nvl,
352	    &nchannels) != 0) {
353		whinge(mod, NULL,
354		    "mc_nb_create: failed to find channel information\n");
355		return (-1);
356	}
357	if (nvlist_lookup_uint8(nvl, MCINTEL_NVLIST_NMEM, &nmc) == 0) {
358		/*
359		 * Assume channels are evenly divided among the controllers.
360		 * Convert nchannels to channels per controller
361		 */
362		nchannels = nchannels / nmc;
363	} else {
364		/*
365		 * if number of memory controllers is not specified then there
366		 * are two channels per controller and the nchannels is total
367		 * we will set up nmc as number of controllers and convert
368		 * nchannels to channels per controller
369		 */
370		nmc = nchannels / 2;
371		nchannels = nchannels / nmc;
372	}
373	if (nvlist_lookup_uint8(nvl, MCINTEL_NVLIST_NRANKS, &maxranks) != 0)
374		maxranks = 2;
375	if (nvlist_lookup_uint8(nvl, MCINTEL_NVLIST_NDIMMS, &maxdimms) != 0)
376		maxdimms = 0;
377	if (topo_node_range_create(mod, pnode, name, 0, nmc-1) < 0) {
378		whinge(mod, NULL,
379		    "mc_nb_create: node range create failed\n");
380		return (-1);
381	}
382	channel = 0;
383	for (i = 0; i < nmc; i++) {
384		if (mkrsrc(mod, pnode, name, i, auth, &fmri) != 0) {
385			whinge(mod, NULL, "mc_nb_create: mkrsrc failed\n");
386			return (-1);
387		}
388		if ((mcnode = topo_node_bind(mod, pnode, name, i,
389		    fmri)) == NULL) {
390			whinge(mod, NULL, "mc_nb_create: node bind failed"
391			    " for memory-controller\n");
392			nvlist_free(fmri);
393			return (-1);
394		}
395
396		(void) topo_node_fru_set(mcnode, NULL, 0, &err);
397		nvlist_free(fmri);
398		(void) topo_pgroup_create(mcnode, &mc_pgroup, &err);
399
400		if (FM_AWARE_SMBIOS(mod))
401			(void) topo_node_label_set(mcnode, NULL, &err);
402
403		if (topo_node_range_create(mod, mcnode, DRAMCHANNEL, channel,
404		    channel + nchannels - 1) < 0) {
405			whinge(mod, NULL,
406			    "mc_nb_create: channel node range create failed\n");
407			return (-1);
408		}
409		for (j = 0; j < nchannels; j++) {
410			if (mc_add_channel(mod, chip_smbid, mcnode, channel,
411			    auth, channel_nvl[channel], maxdimms,
412			    maxranks) < 0) {
413				return (-1);
414			}
415			channel++;
416		}
417		for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL;
418		    nvp = nvlist_next_nvpair(nvl, nvp)) {
419			pname = nvpair_name(nvp);
420			if (strcmp(pname, MCINTEL_NVLIST_MC) != 0 &&
421			    strcmp(pname, MCINTEL_NVLIST_NMEM) != 0 &&
422			    strcmp(pname, MCINTEL_NVLIST_NRANKS) != 0 &&
423			    strcmp(pname, MCINTEL_NVLIST_NDIMMS) != 0 &&
424			    strcmp(pname, MCINTEL_NVLIST_VERSTR) != 0 &&
425			    strcmp(pname, MCINTEL_NVLIST_MEM) != 0) {
426				(void) nvprop_add(mod, nvp, PGNAME(MCT),
427				    mcnode);
428			}
429		}
430	}
431
432	return (NULL);
433}
434
435int
436mc_node_create(topo_mod_t *mod, uint16_t chip_smbid, tnode_t *pnode,
437    const char *name, nvlist_t *auth)
438{
439	mc_snapshot_info_t mcs;
440	void *buf = NULL;
441	nvlist_t *nvl;
442	uint8_t ver;
443	int rc;
444
445	if (ioctl(mc_fd, MC_IOC_SNAPSHOT_INFO, &mcs) == -1 ||
446	    (buf = topo_mod_alloc(mod, mcs.mcs_size)) == NULL ||
447	    ioctl(mc_fd, MC_IOC_SNAPSHOT, buf) == -1) {
448
449		whinge(mod, NULL, "mc failed to snapshot %s\n",
450		    strerror(errno));
451
452		free(buf);
453		(void) close(mc_fd);
454		return (NULL);
455	}
456	(void) close(mc_fd);
457	(void) nvlist_unpack(buf, mcs.mcs_size, &nvl, 0);
458	topo_mod_free(mod, buf, mcs.mcs_size);
459
460	if (nvlist_lookup_uint8(nvl, MCINTEL_NVLIST_VERSTR, &ver) != 0) {
461		whinge(mod, NULL, "mc nvlist is not versioned\n");
462		nvlist_free(nvl);
463		return (NULL);
464	} else if (ver != MCINTEL_NVLIST_VERS0) {
465		whinge(mod, NULL, "mc nvlist version mismatch\n");
466		nvlist_free(nvl);
467		return (NULL);
468	}
469
470	rc = mc_nb_create(mod, chip_smbid, pnode, name, auth, nvl);
471
472	nvlist_free(nvl);
473	return (rc);
474}
475
476void
477onchip_mc_create(topo_mod_t *mod, uint16_t chip_smbid, tnode_t *pnode,
478    const char *name, nvlist_t *auth)
479{
480	if (mc_onchip(topo_node_instance(pnode)))
481		(void) mc_node_create(mod, chip_smbid, pnode, name, auth);
482}
483
484int
485mc_offchip_create(topo_mod_t *mod, tnode_t *pnode, const char *name,
486    nvlist_t *auth)
487{
488	return (mc_node_create(mod, IGNORE_ID, pnode, name, auth));
489}
490