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  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * Implements auxiliary routines declared in pcp_utils.h to facilitate
28  * finding the appropriate communication transport & device path for a
29  * given service.  This supports the transition from the legacy service channel
30  * transport (glvc) to the logical domain channel (vldc) transport native
31  * to platforms running Logical Domains (LDoms).
32  */
33 
34 #pragma ident	"%Z%%M%	%I%	%E% SMI"
35 
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <strings.h>
40 #include <stdlib.h>
41 #include <libgen.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <libdevinfo.h>
45 
46 #include "pcp_utils.h"
47 
48 typedef enum { false = 0, true = 1 } bool_t;
49 
50 #define	SERVICE_PREFIX	"SUNW,sun4v-"
51 #define	DEVICES_DIR "/devices"
52 #define	GLVC	":glvc"
53 #define	VCHAN	"virtual-channel@"
54 #define	VCHAN_C	"virtual-channel-client@"
55 
56 /*
57  * The mechanism to relate a service to a device path is different for
58  * vldc and glvc, due to the way the device pathnames are encoded:
59  * Sample service: sunvts
60  * Service Name: SUNW,sun4v-sunvts
61  * GLVC device path:
62  * "/devices/virtual-devices@100/sunvts@a:glvc"
63  * VLDC device path:
64  * "/devices/virtual-devices@100/channel-devices@200/virtual-channel@3:sunvts"
65  *
66  * As VLDC is the communication mechanism used in an LDoms environment, it is
67  * the preferred channel, and its existence is checked for first.
68  */
69 
70 /*
71  * Extract from dev_path the "service name" portion.
72  * For vldc, the service corresponds to the minor name of the device path
73  * (e.g. virtual-channel@3:sunvts for sunvts).  If service is non-NULL, it must
74  * match the extracted service name for the function to succeed.
75  * The service name is returned in match (if non-NULL), and the function
76  * itself returns true on success; false on failure.
77  */
78 static bool_t
79 get_vldc_svc_name(char *dev_path, char *service, char **match)
80 {
81 	bool_t ret = false;
82 	char *pathname = strdup(dev_path);
83 	char *devname, *s;
84 
85 	if (NULL == pathname)
86 		return (false);
87 
88 	devname = basename(pathname);
89 	s = strrchr(devname, ':');
90 
91 	if (s++ == NULL) {
92 		goto end;
93 	}
94 
95 	if ((strncmp(devname, VCHAN, strlen(VCHAN)) == 0) ||
96 	    (strncmp(devname, VCHAN_C, strlen(VCHAN_C)) == 0)) {
97 		/*
98 		 * If in addition, a service string is specified to
99 		 * be matched, do a comparison
100 		 */
101 		if (service != NULL) {
102 			if (strcmp(s, service) == 0) {
103 				if (match)
104 					*match = strdup(s);
105 				ret = true;
106 				goto end;
107 			} else {
108 				ret = false;
109 				goto end;
110 			}
111 		} else if (match) {
112 			*match = strdup(s);
113 		}
114 
115 		ret = true;
116 		goto end;
117 	}
118 end:
119 
120 	free(pathname);
121 	return (ret);
122 }
123 
124 /*
125  * Extract from dev_path the "service name" portion.
126  * For glvc, the service corresponds to the node name of the device path
127  * (e.g. sunvts@a:glvc for sunvts).  If service is non-NULL, it must
128  * match the extracted service name for the function to succeed.
129  * The service name is returned in match (if non-NULL), and the function
130  * itself returns true on success; false on failure.
131  */
132 static bool_t
133 get_glvc_svc_name(char *dev_path, char *service, char **match)
134 {
135 	bool_t ret = true;
136 	char *pathname = strdup(dev_path);
137 	char *devname, *substr, *t;
138 	int len;
139 
140 	if (NULL == pathname)
141 		return (false);
142 
143 	devname = basename(pathname);
144 	substr = strstr(devname, GLVC);
145 
146 	if (!((substr != NULL) && (strcmp(substr, GLVC) == 0))) {
147 		ret = false;
148 		goto end;
149 	}
150 
151 	if ((t = strrchr(devname, '@')) == NULL) {
152 		ret = false;
153 		goto end;
154 	}
155 
156 	len = t - devname;
157 
158 	/*
159 	 * If a service string is specified, check if there
160 	 * is a match
161 	 */
162 	if ((service != NULL) && (strncmp(devname, service, len) != 0))
163 		ret = false;
164 
165 	if ((ret == true) && (match != NULL)) {
166 		*match = calloc(len + 1, 1);
167 		if (*match)
168 			(void) strncpy(*match, devname, len);
169 	}
170 
171 end:
172 	free(pathname);
173 	return (ret);
174 }
175 
176 /*
177  * This routine accepts either a prefixed service name or a legacy full
178  * pathname (which might not even exist in the filesystem), and in either case
179  * returns a canonical service name.  If the parameter is neither a service
180  * name (i.e. with a "SUNW,sun4v-" prefix), nor a path to a legacy glvc or
181  * vldc device, NULL is returned.
182  */
183 char *
184 platsvc_extract_svc_name(char *devname)
185 {
186 	char *sname = NULL;
187 	char *vldc_path, *glvc_path;
188 
189 	/*
190 	 * First check whether a service name
191 	 */
192 	if (strncmp(devname, SERVICE_PREFIX, strlen(SERVICE_PREFIX)) == 0) {
193 		sname = strdup(devname + strlen(SERVICE_PREFIX));
194 		return (sname);
195 	}
196 
197 	/*
198 	 * Not a service name, check if it's a valid pathname
199 	 */
200 	if (!(devname[0] == '/' || devname[0] == '.')) {
201 		return (NULL);
202 	}
203 
204 	/*
205 	 * Ideally, we should only check for a valid glvc pathname,
206 	 * requiring all vldc access to be only via service names.  But
207 	 * to prevent a flag day with code that's already passing in
208 	 * vldc full pathnames (e.g. sunMC), we allow them here.
209 	 */
210 	if (get_vldc_svc_name(devname, NULL, &vldc_path) == true) {
211 		return (vldc_path);
212 	} else if (get_glvc_svc_name(devname, NULL, &glvc_path) == true) {
213 		return (glvc_path);
214 	}
215 
216 	return (NULL);
217 }
218 
219 /*
220  * Walk all "service" device nodes to find the one with the
221  * matching glvc minor name
222  */
223 static char *
224 svc_name_to_glvc_dev_path(char *service)
225 {
226 	di_node_t root_node, service_node;
227 	char *glvc_path;
228 	char *minor_name;
229 	di_minor_t minor;
230 	char *dev_path = NULL;
231 
232 	if (service == NULL)
233 		return (NULL);
234 
235 	/* Ensure that the 'glvc' driver is loaded */
236 	(void) di_init_driver("glvc", DI_CACHE_SNAPSHOT_FLAGS | DINFOFORCE);
237 
238 	/* Get device node */
239 	root_node = di_init("/", DINFOCPYALL);
240 	if (root_node == DI_NODE_NIL) {
241 		return (dev_path);
242 	}
243 
244 	service_node = di_drv_first_node("glvc", root_node);
245 
246 	while (service_node != DI_NODE_NIL) {
247 		/* Make sure node name matches service name */
248 		if (strcmp(service, di_node_name(service_node)) == 0) {
249 			/* Walk minor nodes */
250 			minor = di_minor_next(service_node, DI_NODE_NIL);
251 
252 			while (minor != DI_NODE_NIL) {
253 				glvc_path = di_devfs_minor_path(minor);
254 				minor_name = di_minor_name(minor);
255 
256 				if (strcmp(minor_name, "glvc") == 0) {
257 					dev_path = malloc(strlen(glvc_path) +
258 					    strlen(DEVICES_DIR) + 1);
259 					(void) strcpy(dev_path, DEVICES_DIR);
260 					(void) strcat(dev_path, glvc_path);
261 					di_devfs_path_free(glvc_path);
262 					break;
263 				}
264 
265 				di_devfs_path_free(glvc_path);
266 				minor = di_minor_next(service_node, minor);
267 			}
268 		}
269 		if (dev_path != NULL)
270 			break;
271 
272 		service_node = di_drv_next_node(service_node);
273 	}
274 
275 	di_fini(root_node);
276 	return (dev_path);
277 }
278 
279 /*
280  * Walk all vldc device nodes to find the one with the
281  * matching minor name
282  */
283 static char *
284 svc_name_to_vldc_dev_path(char *service)
285 {
286 	di_node_t root_node, vldc_node;
287 	char *vldc_path;
288 	char *minor_name;
289 	di_minor_t minor;
290 	char *dev_path = NULL;
291 
292 	/* Get device node */
293 	root_node = di_init("/", DINFOCPYALL);
294 	if (root_node == DI_NODE_NIL) {
295 		return (dev_path);
296 	}
297 
298 	vldc_node = di_drv_first_node("vldc", root_node);
299 
300 	while (vldc_node != DI_NODE_NIL) {
301 		/* Walk minor nodes */
302 		minor = di_minor_next(vldc_node, DI_NODE_NIL);
303 
304 		while (minor != DI_NODE_NIL) {
305 			vldc_path = di_devfs_minor_path(minor);
306 			minor_name = di_minor_name(minor);
307 
308 			if (strcmp(minor_name, service) == 0) {
309 				dev_path = malloc(strlen(vldc_path) +
310 				    strlen(DEVICES_DIR) + 1);
311 				(void) strcpy(dev_path, DEVICES_DIR);
312 				(void) strcat(dev_path, vldc_path);
313 				di_devfs_path_free(vldc_path);
314 				break;
315 			}
316 
317 			di_devfs_path_free(vldc_path);
318 			minor = di_minor_next(vldc_node, minor);
319 		}
320 		if (dev_path != NULL)
321 			break;
322 
323 		vldc_node = di_drv_next_node(vldc_node);
324 	}
325 
326 	di_fini(root_node);
327 	return (dev_path);
328 }
329 
330 /*
331  * Given a service name or a full legacy pathname, return
332  * the full pathname to the appropriate vldc or glvc device.
333  */
334 char *
335 platsvc_name_to_path(char *svc_or_path, pcp_xport_t *type)
336 {
337 	char		*pathn_p;
338 	char		*service;
339 
340 	if ((service = platsvc_extract_svc_name(svc_or_path)) == NULL)
341 		return (NULL);
342 
343 	/*
344 	 * First lookup vldc nodes
345 	 */
346 	pathn_p = svc_name_to_vldc_dev_path(service);
347 	if (pathn_p != NULL) {
348 		*type = VLDC_STREAMING;
349 	} else {
350 		/*
351 		 * If no vldc, try to find a glvc node
352 		 */
353 		pathn_p = svc_name_to_glvc_dev_path(service);
354 		if (pathn_p != NULL) {
355 			*type = GLVC_NON_STREAM;
356 		}
357 	}
358 
359 	free(service);
360 	return (pathn_p);
361 }
362