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 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 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 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 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 %llu 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 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 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 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 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 }