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 }