1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2018, Joyent, Inc.
14  */
15 
16 /*
17  * This module parses the private file format used for describing
18  * platform-specific USB overrides.
19  *
20  * FILE FORMAT
21  * -----------
22  *
23  * A USB topology file contains a series of lines which are separated by new
24  * lines. Leading and trailing whitespace on a line are ignored and empty lines
25  * are ignored as well. The '#' character is used as a comment character. There
26  * are a series of keywords that are supported which are used to indicate
27  * different control aspects. These keywords are all treated in a
28  * case-insensitive fashion. There are both top-level keywords and keywords that
29  * are only accepted within the context of a scope.
30  *
31  * Top-level keywords
32  * ------------------
33  *
34  * The following keywords are accepted, but must not be found inside a nested
35  * scope:
36  *
37  *   'disable-acpi'		Disables the use of ACPI for this platform. This
38  *				includes getting information about the port's
39  *				type and visibility. This implies
40  *				'disable-acpi-match'.
41  *
42  *   'disable-acpi-match'	Disables the act of trying to match ports based
43  *				on ACPI.
44  *
45  *
46  *   'enable-acpi-match'	Explicitly enables ACPI port matching on the
47  *				platform based on ACPI.
48  *
49  *   'enable-metadata-match'	Enables port matching based on metadata. This is
50  *				most commonly used on platforms that have ehci
51  *				and xhci controllers that share ports.
52  *
53  *   'port'			Begins a port stanza that describes a single
54  *				physical port. This stanza will continue until
55  *				the 'end-port' keyword is encountered.
56  *
57  * Port Keywords
58  * -------------
59  *
60  * Some port keywords take arguments and others do not. When an argument exists,
61  * will occur on the subsequent line. Ports have a series of directives that
62  * describe metadata as well as directives that describe how to determine the
63  * port.
64  *
65  *   'label'			Indicates that the next line contains the
66  *				human-readable label for the port.
67  *
68  *   'chassis'			Indicates that this port is part of the chassis
69  *				and should not be enumerated elsewhere.
70  *
71  *   'external'			Indicates that this port is externally visible.
72  *
73  *   'internal'			Indicates that this port is internal to the
74  *				chassis and cannot be accessed without opening
75  *				the chassis.
76  *
77  *   'port-type'		Indicates that the next line contains a number
78  *				which corresponds to the type of the port. The
79  *				port numbers are based on the ACPI table and
80  *				may be in either base 10 or hexadecimal.
81  *
82  *   'acpi-path'		Indicates that the next line contains an ACPI
83  *				based name that matches the port.
84  *
85  *   'end-port'			Closes the port-clause.
86  */
87 
88 #include <libnvpair.h>
89 #include <sys/types.h>
90 #include <sys/stat.h>
91 #include <fcntl.h>
92 #include <fm/topo_list.h>
93 #include <fm/topo_mod.h>
94 #include <stdio.h>
95 #include <string.h>
96 #include <strings.h>
97 #include <libnvpair.h>
98 #include <sys/debug.h>
99 #include <ctype.h>
100 #include <unistd.h>
101 
102 #include "topo_usb.h"
103 #include "topo_usb_int.h"
104 
105 /*
106  * Maximum number of characters we expect to encounter in a line.
107  */
108 #define	TOPO_USB_META_LINE_MAX	1000
109 
110 /*
111  * This constant is the default set of flags that we'd like to apply when there
112  * is no configuration file present to determine the desired behavior. If one is
113  * present, we always defer to what it asks for.
114  *
115  * It's a difficult decision to enable ACPI by default or not. Unfortunately,
116  * we've encountered some systems where the ACPI information is wrong. However,
117  * we've encountered a larger number where it is correct. When it's correct,
118  * this greatly simplifies some of the work that we have to do. Our default
119  * disposition at the moment is to opt to decide its correct as that ends up
120  * giving us much better information.
121  */
122 #define	USB_TOPO_META_DEFAULT_FLAGS	TOPO_USB_M_ACPI_MATCH
123 
124 typedef enum {
125 	TOPO_USB_P_START,
126 	TOPO_USB_P_PORT,
127 	TOPO_USB_P_LABEL,
128 	TOPO_USB_P_PORT_TYPE,
129 	TOPO_USB_P_ACPI_PATH
130 } topo_usb_parse_state_t;
131 
132 typedef struct topo_usb_parse {
133 	topo_usb_parse_state_t	tp_state;
134 	topo_list_t		*tp_ports;
135 	topo_usb_meta_port_t	*tp_cport;
136 	topo_usb_meta_flags_t	tp_flags;
137 } topo_usb_parse_t;
138 
139 /*
140  * Read the next line in the file with content. Trim trailing and leading
141  * whitespace and trim comments out. If this results in an empty line, read the
142  * next. Returns zero if we hit EOF. Otherwise, returns one if data, or negative
143  * one if an error occurred.
144  */
145 static int
topo_usb_getline(topo_mod_t * mod,char * buf,size_t len,FILE * f,char ** first)146 topo_usb_getline(topo_mod_t *mod, char *buf, size_t len, FILE *f, char **first)
147 {
148 	while (fgets(buf, len, f) != NULL) {
149 		char *c;
150 		size_t i;
151 
152 		if ((c = strrchr(buf, '\n')) == NULL) {
153 			topo_mod_dprintf(mod, "failed to find new line in "
154 			    "metadata file");
155 			return (-1);
156 		}
157 
158 		while (isspace(*c) != 0 && c >= buf) {
159 			*c = '\0';
160 			c--;
161 			continue;
162 		}
163 
164 		if ((c = strchr(buf, '#')) != 0) {
165 			*c = '\0';
166 		}
167 
168 		for (i = 0; buf[i] != '\0'; i++) {
169 			if (isspace(buf[i]) == 0)
170 				break;
171 		}
172 
173 		if (buf[i] == '\0')
174 			continue;
175 		*first = &buf[i];
176 		return (1);
177 	}
178 
179 	return (0);
180 }
181 
182 static boolean_t
topo_usb_parse_start(topo_mod_t * mod,topo_usb_parse_t * parse,const char * line)183 topo_usb_parse_start(topo_mod_t *mod, topo_usb_parse_t *parse, const char *line)
184 {
185 	topo_usb_meta_port_t *port;
186 
187 	VERIFY3S(parse->tp_state, ==, TOPO_USB_P_START);
188 	VERIFY3P(parse->tp_cport, ==, NULL);
189 
190 	if (strcasecmp(line, "disable-acpi") == 0) {
191 		parse->tp_flags |= TOPO_USB_M_NO_ACPI;
192 		parse->tp_flags &= ~TOPO_USB_M_ACPI_MATCH;
193 		return (B_TRUE);
194 	} else if (strcasecmp(line, "disable-acpi-match") == 0) {
195 		parse->tp_flags &= ~TOPO_USB_M_ACPI_MATCH;
196 		return (B_TRUE);
197 	} else if (strcasecmp(line, "enable-acpi-match") == 0) {
198 		parse->tp_flags |= TOPO_USB_M_ACPI_MATCH;
199 		return (B_TRUE);
200 	} else if (strcasecmp(line, "enable-metadata-match") == 0) {
201 		parse->tp_flags |= TOPO_USB_M_METADATA_MATCH;
202 		return (B_TRUE);
203 	} else if (strcasecmp(line, "port") != 0) {
204 		topo_mod_dprintf(mod, "expected 'port', encountered %s",
205 		    line);
206 		return (B_FALSE);
207 	}
208 
209 	if ((port = topo_mod_zalloc(mod, sizeof (topo_usb_meta_port_t))) ==
210 	    NULL) {
211 		topo_mod_dprintf(mod, "failed to allocate metadata port");
212 		return (B_FALSE);
213 	}
214 	port->tmp_port_type = 0xff;
215 
216 	parse->tp_cport = port;
217 	parse->tp_state = TOPO_USB_P_PORT;
218 	return (B_TRUE);
219 }
220 
221 static boolean_t
topo_usb_parse_port(topo_mod_t * mod,topo_usb_parse_t * parse,const char * line)222 topo_usb_parse_port(topo_mod_t *mod, topo_usb_parse_t *parse, const char *line)
223 {
224 	VERIFY3S(parse->tp_state, ==, TOPO_USB_P_PORT);
225 	VERIFY3P(parse->tp_cport, !=, NULL);
226 
227 	if (strcasecmp(line, "label") == 0) {
228 		parse->tp_state = TOPO_USB_P_LABEL;
229 	} else if (strcasecmp(line, "chassis") == 0) {
230 		parse->tp_cport->tmp_flags |= TOPO_USB_F_CHASSIS;
231 	} else if (strcasecmp(line, "external") == 0) {
232 		parse->tp_cport->tmp_flags |= TOPO_USB_F_EXTERNAL;
233 	} else if (strcasecmp(line, "internal") == 0) {
234 		parse->tp_cport->tmp_flags |= TOPO_USB_F_INTERNAL;
235 	} else if (strcasecmp(line, "port-type") == 0) {
236 		parse->tp_state = TOPO_USB_P_PORT_TYPE;
237 	} else if (strcasecmp(line, "acpi-path") == 0) {
238 		parse->tp_state = TOPO_USB_P_ACPI_PATH;
239 	} else if (strcasecmp(line, "end-port") == 0) {
240 		topo_list_append(parse->tp_ports, parse->tp_cport);
241 		parse->tp_cport = NULL;
242 		parse->tp_state = TOPO_USB_P_START;
243 	} else {
244 		topo_mod_dprintf(mod, "illegal directive in port block: %s",
245 		    line);
246 		return (B_FALSE);
247 	}
248 
249 	return (B_TRUE);
250 }
251 
252 static boolean_t
topo_usb_parse_label(topo_mod_t * mod,topo_usb_parse_t * parse,const char * line)253 topo_usb_parse_label(topo_mod_t *mod, topo_usb_parse_t *parse, const char *line)
254 {
255 	size_t i, len;
256 
257 	VERIFY3S(parse->tp_state, ==, TOPO_USB_P_LABEL);
258 
259 	len = strlen(line);
260 	for (i = 0; i < len; i++) {
261 		if (isascii(line[i]) == 0 || isprint(line[i]) == 0) {
262 			topo_mod_dprintf(mod, "label character %zu is "
263 			    "invalid: 0x%x", i, line[i]);
264 			return (B_FALSE);
265 		}
266 	}
267 
268 	if (parse->tp_cport->tmp_label != NULL) {
269 		topo_mod_strfree(mod, parse->tp_cport->tmp_label);
270 	}
271 
272 	if ((parse->tp_cport->tmp_label = topo_mod_strdup(mod, line)) == NULL) {
273 		topo_mod_dprintf(mod, "failed to duplicate label for port");
274 		return (B_FALSE);
275 	}
276 
277 	parse->tp_state = TOPO_USB_P_PORT;
278 
279 	return (B_TRUE);
280 }
281 
282 static boolean_t
topo_usb_parse_port_type(topo_mod_t * mod,topo_usb_parse_t * parse,const char * line)283 topo_usb_parse_port_type(topo_mod_t *mod, topo_usb_parse_t *parse,
284     const char *line)
285 {
286 	unsigned long val;
287 	char *eptr;
288 
289 	VERIFY3S(parse->tp_state, ==, TOPO_USB_P_PORT_TYPE);
290 
291 	errno = 0;
292 	val = strtoul(line, &eptr, 0);
293 	if (errno != 0 || *eptr != '\0' || val >= UINT_MAX) {
294 		topo_mod_dprintf(mod, "encountered bad value for port-type "
295 		    "line: %s", line);
296 		return (B_FALSE);
297 	}
298 
299 	parse->tp_cport->tmp_port_type = (uint_t)val;
300 
301 	parse->tp_state = TOPO_USB_P_PORT;
302 	return (B_TRUE);
303 }
304 
305 static boolean_t
topo_usb_parse_path(topo_mod_t * mod,topo_usb_parse_t * parse,topo_usb_path_type_t ptype,const char * line)306 topo_usb_parse_path(topo_mod_t *mod, topo_usb_parse_t *parse,
307     topo_usb_path_type_t ptype, const char *line)
308 {
309 	char *fspath;
310 	topo_usb_meta_port_path_t *path;
311 
312 	VERIFY(parse->tp_state == TOPO_USB_P_ACPI_PATH);
313 	VERIFY3P(parse->tp_cport, !=, NULL);
314 
315 	if ((fspath = topo_mod_strdup(mod, line)) == NULL) {
316 		topo_mod_dprintf(mod, "failed to duplicate path");
317 		return (B_FALSE);
318 	}
319 
320 	if ((path = topo_mod_zalloc(mod, sizeof (topo_usb_meta_port_path_t))) ==
321 	    NULL) {
322 		topo_mod_dprintf(mod, "failed to allocate meta port path "
323 		    "structure");
324 		topo_mod_strfree(mod, fspath);
325 		return (B_FALSE);
326 	}
327 
328 	path->tmpp_type = ptype;
329 	path->tmpp_path = fspath;
330 
331 	topo_list_append(&parse->tp_cport->tmp_paths, path);
332 
333 	parse->tp_state = TOPO_USB_P_PORT;
334 	return (B_TRUE);
335 }
336 
337 
338 void
topo_usb_free_metadata(topo_mod_t * mod,topo_list_t * metadata)339 topo_usb_free_metadata(topo_mod_t *mod, topo_list_t *metadata)
340 {
341 	topo_usb_meta_port_t *mp;
342 
343 	while ((mp = topo_list_next(metadata)) != NULL) {
344 		topo_usb_meta_port_path_t *path;
345 
346 		while ((path = topo_list_next((&mp->tmp_paths))) != NULL) {
347 			topo_list_delete(&mp->tmp_paths, path);
348 			topo_mod_strfree(mod, path->tmpp_path);
349 			topo_mod_free(mod, path,
350 			    sizeof (topo_usb_meta_port_path_t));
351 		}
352 
353 		topo_list_delete(metadata, mp);
354 		topo_mod_strfree(mod, mp->tmp_label);
355 		topo_mod_free(mod, mp, sizeof (topo_usb_meta_port_t));
356 	}
357 }
358 
359 int
topo_usb_load_metadata(topo_mod_t * mod,tnode_t * pnode,topo_list_t * list,topo_usb_meta_flags_t * flagsp)360 topo_usb_load_metadata(topo_mod_t *mod, tnode_t *pnode, topo_list_t *list,
361     topo_usb_meta_flags_t *flagsp)
362 {
363 	int fd;
364 	FILE *f = NULL;
365 	char buf[TOPO_USB_META_LINE_MAX], *first, *prod;
366 	int ret;
367 	topo_usb_parse_t parse;
368 	char pbuf[PATH_MAX];
369 
370 	*flagsp = USB_TOPO_META_DEFAULT_FLAGS;
371 
372 	/*
373 	 * If no product string, just leave it as is and don't attempt to get
374 	 * metadata.
375 	 */
376 	if ((topo_prop_get_string(pnode, FM_FMRI_AUTHORITY,
377 	    FM_FMRI_AUTH_PRODUCT, &prod, &ret)) != 0) {
378 		topo_mod_dprintf(mod, "skipping metadata load: failed to get "
379 		    "auth");
380 		return (0);
381 	}
382 
383 	if (snprintf(pbuf, sizeof (pbuf), "maps/%s-usb.usbtopo", prod) >=
384 	    sizeof (pbuf)) {
385 		topo_mod_dprintf(mod, "skipping metadata load: product name "
386 		    "too long");
387 		topo_mod_strfree(mod, prod);
388 		return (0);
389 	}
390 	topo_mod_strfree(mod, prod);
391 
392 	if ((fd = topo_mod_file_search(mod, pbuf, O_RDONLY)) < 0) {
393 		topo_mod_dprintf(mod, "skipping metadata load: couldn't find "
394 		    "%s", pbuf);
395 		return (0);
396 	}
397 
398 
399 	if ((f = fdopen(fd, "r")) == NULL) {
400 		topo_mod_dprintf(mod, "failed to fdopen metadata file %s: %s",
401 		    pbuf, strerror(errno));
402 		VERIFY0(close(fd));
403 		ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
404 		goto err;
405 	}
406 
407 	bzero(&parse, sizeof (parse));
408 	parse.tp_ports = list;
409 	parse.tp_state = TOPO_USB_P_START;
410 
411 	while ((ret = topo_usb_getline(mod, buf, sizeof (buf), f, &first)) !=
412 	    0) {
413 		if (ret == -1) {
414 			ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
415 			goto err;
416 		}
417 
418 		switch (parse.tp_state) {
419 		case TOPO_USB_P_START:
420 			if (!topo_usb_parse_start(mod, &parse, first)) {
421 				ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
422 				goto err;
423 			}
424 			break;
425 		case TOPO_USB_P_PORT:
426 			if (!topo_usb_parse_port(mod, &parse, first)) {
427 				ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
428 				goto err;
429 			}
430 			break;
431 		case TOPO_USB_P_LABEL:
432 				if (!topo_usb_parse_label(mod, &parse, first)) {
433 				ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
434 				goto err;
435 			}
436 			break;
437 		case TOPO_USB_P_PORT_TYPE:
438 			if (!topo_usb_parse_port_type(mod, &parse, first)) {
439 				ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
440 				goto err;
441 			}
442 			break;
443 
444 		case TOPO_USB_P_ACPI_PATH:
445 			if (!topo_usb_parse_path(mod, &parse, TOPO_USB_T_ACPI,
446 			    first)) {
447 				ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
448 				goto err;
449 			}
450 			break;
451 		}
452 	}
453 
454 	if (parse.tp_state != TOPO_USB_P_START) {
455 		topo_mod_dprintf(mod, "metadata file didn't end in correct "
456 		    "state, failing");
457 		ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
458 		goto err;
459 	}
460 
461 	topo_mod_dprintf(mod, "successfully loaded metadata %s", pbuf);
462 	VERIFY0(fclose(f));
463 	*flagsp = parse.tp_flags;
464 	return (0);
465 
466 err:
467 	if (f != NULL)
468 		VERIFY0(fclose(f));
469 	topo_usb_free_metadata(mod, list);
470 	return (ret);
471 }
472