#include <libdevinfo.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/debug.h>
#include <unistd.h>
#include <sys/fm/protocol.h>
#include <fm/topo_mod.h>
#include <fm/topo_list.h>
#include <fm/topo_method.h>
#include <topo_port.h>
#include "topo_usb.h"
#include "topo_usb_int.h"
typedef enum topo_usb_type {
TOPO_USB_PCI,
TOPO_USB_MOBO,
TOPO_USB_CHASSIS
} topo_usb_type_t;
typedef enum topo_usb_cdrv {
TOPO_USB_D_UNKNOWN,
TOPO_USB_D_UHCI,
TOPO_USB_D_OHCI,
TOPO_USB_D_EHCI,
TOPO_USB_D_XHCI
} topo_usb_cdrv_t;
typedef enum topo_usb_protocol {
TOPO_USB_P_UNKNOWN,
TOPO_USB_P_1x,
TOPO_USB_P_20,
TOPO_USB_P_30,
TOPO_USB_P_31
} topo_usb_protocol_t;
typedef enum topo_usb_port_connected {
TOPO_USB_C_UNKNOWN,
TOPO_USB_C_DISCONNECTED,
TOPO_USB_C_CONNECTED
} topo_usb_port_connected_t;
typedef struct topo_usb_port {
topo_list_t tup_link;
uint_t tup_nlports;
topo_list_t tup_lports;
boolean_t tup_pld_valid;
acpi_pld_info_t tup_pld;
uint_t tup_port_type;
topo_usb_port_connected_t tup_port_connected;
topo_usb_meta_port_t *tup_meta;
} topo_usb_port_t;
typedef struct topo_usb_lport {
topo_list_t tul_link;
uint_t tul_portno;
topo_usb_protocol_t tul_protocol;
di_node_t tul_device;
di_node_t tul_acpi_device;
topo_usb_port_t *tul_port;
uint_t tul_nhubd_ports;
uint_t tul_nports;
topo_list_t tul_ports;
char tul_name[PATH_MAX];
const char *tul_acpi_name;
} topo_usb_lport_t;
typedef struct topo_usb_controller {
topo_list_t tuc_link;
di_node_t tuc_devinfo;
char *tuc_path;
char *tuc_acpi_path;
char tuc_name[PATH_MAX];
topo_usb_cdrv_t tuc_driver;
uint_t tuc_nports;
topo_list_t tuc_ports;
uint_t tuc_nhubd_ports;
uint_t tuc_nusb20;
uint_t tuc_fusb20;
uint_t tuc_nusb30;
uint_t tuc_fusb30;
uint_t tuc_nusb31;
uint_t tuc_fusb31;
boolean_t tuc_enumed;
} topo_usb_controller_t;
typedef struct topo_usb {
topo_list_t tu_controllers;
boolean_t tu_enum_done;
di_node_t tu_devinfo;
topo_list_t tu_metadata;
topo_usb_meta_flags_t tu_meta_flags;
topo_list_t tu_chassis_ports;
uint_t tu_nchassis_ports;
} topo_usb_t;
typedef struct topo_usb_devcfg_arg {
topo_usb_t *tda_usb;
topo_mod_t *tda_mod;
boolean_t tda_fatal;
} topo_usb_devcfg_arg_t;
static const topo_pgroup_info_t topo_usb_port_pgroup = {
TOPO_PGROUP_USB_PORT,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
static const topo_pgroup_info_t topo_io_pgroup = {
TOPO_PGROUP_IO,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
static const topo_pgroup_info_t topo_binding_pgroup = {
TOPO_PGROUP_BINDING,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
static const topo_pgroup_info_t topo_usb_props_pgroup = {
TOPO_PGROUP_USB_PROPS,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
static int topo_usb_enum_device(topo_mod_t *, tnode_t *, topo_usb_port_t *);
#define USB_TOPO_PORT_MAX 256
#define USB_TOPO_PORT_TYPE_DEFAULT 0xff
static const char *
topo_usb_port_type_to_string(int type)
{
switch (type) {
case 0x00:
return ("Type A connector");
case 0x01:
return ("Mini-AB connector");
case 0x02:
return ("ExpressCard");
case 0x03:
return ("USB 3 Standard-A connector");
case 0x04:
return ("USB 3 Standard-B connector");
case 0x05:
return ("USB 3 Micro-B connector");
case 0x06:
return ("USB 3 Micro-AB connector");
case 0x07:
return ("USB 3 Power-B connector");
case 0x08:
return ("Type C connector - USB2-only");
case 0x09:
return ("Type C connector - USB2 and SS with Switch");
case 0x0A:
return ("Type C connector - USB2 and SS without Switch");
case 0xFF:
default:
return ("Unknown");
}
}
static topo_usb_lport_t *
topo_usb_lport_find(topo_list_t *plist, uint_t logid)
{
topo_usb_port_t *p;
for (p = topo_list_next(plist); p != NULL; p = topo_list_next(p)) {
topo_usb_lport_t *l;
for (l = topo_list_next(&p->tup_lports); l != NULL;
l = topo_list_next(l)) {
if (l->tul_portno == logid)
return (l);
}
}
return (NULL);
}
static topo_usb_controller_t *
topo_usb_controller_create(topo_mod_t *mod, topo_usb_t *usb, di_node_t node)
{
int *pcount, inst;
char *drvname, *acpi;
topo_usb_controller_t *c;
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "usb-port-count",
&pcount) != 1) {
return (NULL);
}
if ((drvname = di_driver_name(node)) == NULL ||
(inst = di_instance(node) == -1))
return (NULL);
if ((c = topo_mod_zalloc(mod, sizeof (topo_usb_controller_t))) ==
NULL || *pcount <= 0) {
return (NULL);
}
if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "acpi-namespace",
&acpi) == 1) {
c->tuc_acpi_path = acpi;
}
c->tuc_nhubd_ports = (uint_t)*pcount;
c->tuc_devinfo = node;
c->tuc_path = di_devfs_path(node);
(void) snprintf(c->tuc_name, sizeof (c->tuc_name), "%s%d", drvname,
inst);
if (strcmp(drvname, "xhci") == 0) {
int *p;
c->tuc_driver = TOPO_USB_D_XHCI;
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
"usb2.0-port-count", &p) == 1 && *p > 0) {
c->tuc_nusb20 = (uint_t)*p;
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
"usb2.0-first-port", &p) == 1 && *p > 0) {
c->tuc_fusb20 = (uint_t)*p;
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
"usb3.0-port-count", &p) == 1 && *p > 0) {
c->tuc_nusb30 = (uint_t)*p;
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
"usb3.0-first-port", &p) == 1 && *p > 0) {
c->tuc_fusb30 = (uint_t)*p;
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
"usb3.1-port-count", &p) == 1 && *p > 0) {
c->tuc_nusb31 = (uint_t)*p;
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
"usb3.1-first-port", &p) == 1 && *p > 0) {
c->tuc_fusb31 = (uint_t)*p;
}
} else if (strcmp(drvname, "ehci") == 0) {
c->tuc_driver = TOPO_USB_D_EHCI;
} else if (strcmp(drvname, "uhci") == 0) {
c->tuc_driver = TOPO_USB_D_UHCI;
} else if (strcmp(drvname, "ohci") == 0) {
c->tuc_driver = TOPO_USB_D_OHCI;
} else {
c->tuc_driver = TOPO_USB_D_UNKNOWN;
}
topo_list_append(&usb->tu_controllers, c);
topo_mod_dprintf(mod, "created new USB controller at %s", c->tuc_path);
return (c);
}
static boolean_t
topo_usb_gather_acpi_port(topo_mod_t *mod, topo_usb_t *usb, topo_list_t *plist,
uint_t *nports, topo_usb_controller_t *tuc, di_node_t portinfo)
{
int64_t *portno;
uchar_t *loc;
int loclen, *type;
char *acpi;
acpi_pld_info_t pld;
boolean_t pld_valid = B_FALSE;
topo_usb_port_t *port = NULL;
topo_usb_lport_t *lport;
di_node_t child;
if (di_prop_lookup_int64(DDI_DEV_T_ANY, portinfo, "acpi-address",
&portno) != 1 || *portno < 1 || *portno >= USB_TOPO_PORT_MAX) {
return (B_FALSE);
}
if (di_prop_lookup_strings(DDI_DEV_T_ANY, portinfo, "acpi-namespace",
&acpi) != 1) {
return (B_FALSE);
}
if ((loclen = di_prop_lookup_bytes(DDI_DEV_T_ANY, portinfo,
"acpi-physical-location", &loc)) >= ACPI_PLD_REV1_BUFFER_SIZE &&
usbtopo_decode_pld(loc, loclen, &pld)) {
pld_valid = B_TRUE;
}
lport = topo_usb_lport_find(plist, (uint_t)*portno);
if (lport == NULL) {
topo_mod_dprintf(mod, "failed to find physical usb port for "
"%s/%u", acpi, (uint_t)*portno);
return (B_TRUE);
}
if (lport->tul_acpi_device != DI_NODE_NIL) {
topo_mod_dprintf(mod, "logical port already bound to %s, not "
"binding to %s", lport->tul_acpi_name, acpi);
return (B_FALSE);
}
lport->tul_acpi_device = portinfo;
lport->tul_acpi_name = acpi;
port = lport->tul_port;
if (pld_valid) {
port->tup_pld_valid = B_TRUE;
port->tup_pld = pld;
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, portinfo, "usb-port-type",
&type) == 1 && *type >= 0) {
port->tup_port_type = *type;
} else {
port->tup_port_type = USB_TOPO_PORT_TYPE_DEFAULT;
}
if (di_prop_find(DDI_DEV_T_ANY, portinfo,
"usb-port-connectable") != DI_PROP_NIL) {
port->tup_port_connected = TOPO_USB_C_CONNECTED;
} else {
port->tup_port_connected = TOPO_USB_C_DISCONNECTED;
}
for (child = di_child_node(portinfo); child != NULL;
child = di_sibling_node(child)) {
const char *pname;
pname = di_node_name(child);
if (pname == NULL || strcmp(pname, "port") != 0) {
continue;
}
if (!topo_usb_gather_acpi_port(mod, usb, &lport->tul_ports,
&lport->tul_nports, tuc, child)) {
return (B_FALSE);
}
}
topo_mod_dprintf(mod, "discovered %u ACPI usb child ports",
lport->tul_nports);
return (B_TRUE);
}
static boolean_t
topo_usb_gather_acpi(topo_mod_t *mod, topo_usb_t *usb)
{
di_node_t fwroot, sbnode;
fwroot = di_lookup_node(usb->tu_devinfo, "/fw");
if (fwroot == DI_NODE_NIL)
return (B_TRUE);
for (sbnode = di_child_node(fwroot); sbnode != DI_NODE_NIL;
sbnode = di_sibling_node(sbnode)) {
const char *sbname;
di_node_t hub;
sbname = di_node_name(sbnode);
if (sbname == NULL || strcmp(sbname, "sb") != 0) {
continue;
}
for (hub = di_child_node(sbnode); hub != DI_NODE_NIL;
hub = di_sibling_node(hub)) {
const char *hubname;
char *acpi;
topo_usb_controller_t *tuc;
di_node_t port;
hubname = di_node_name(hub);
if (hubname == NULL ||
strcmp(hubname, "usbroothub") != 0) {
continue;
}
if (di_prop_lookup_strings(DDI_DEV_T_ANY, hub,
"acpi-controller-name", &acpi) != 1) {
continue;
}
for (tuc = topo_list_next(&usb->tu_controllers);
tuc != NULL;
tuc = topo_list_next(tuc)) {
if (tuc->tuc_acpi_path != NULL &&
strcmp(acpi, tuc->tuc_acpi_path) == 0)
break;
}
if (tuc == NULL) {
topo_mod_dprintf(mod, "failed to find USB "
"controller for ACPI path %s", acpi);
continue;
}
for (port = di_child_node(hub); port != NULL;
port = di_sibling_node(port)) {
const char *pname;
pname = di_node_name(port);
if (pname == NULL ||
strcmp(pname, "port") != 0) {
continue;
}
if (!topo_usb_gather_acpi_port(mod, usb,
&tuc->tuc_ports, &tuc->tuc_nports, tuc,
port)) {
return (B_FALSE);
}
}
topo_mod_dprintf(mod, "found ACPI usb controller %s "
"with %d top-level ports", tuc->tuc_path,
tuc->tuc_nports);
}
}
return (B_TRUE);
}
static topo_usb_port_t *
topo_usb_port_create(topo_mod_t *mod, uint_t portno, const char *parent,
char sep)
{
topo_usb_lport_t *l;
topo_usb_port_t *p;
if ((l = topo_mod_zalloc(mod, sizeof (topo_usb_lport_t))) == NULL) {
return (NULL);
}
l->tul_portno = portno;
if (snprintf(l->tul_name, sizeof (l->tul_name), "%s%c%u", parent, sep,
portno) >= sizeof (l->tul_name)) {
topo_mod_free(mod, l, sizeof (topo_usb_lport_t));
return (NULL);
}
if ((p = topo_mod_zalloc(mod, sizeof (topo_usb_port_t))) == NULL) {
topo_mod_free(mod, l, sizeof (topo_usb_lport_t));
return (NULL);
}
l->tul_port = p;
p->tup_port_type = USB_TOPO_PORT_TYPE_DEFAULT;
topo_list_append(&p->tup_lports, l);
p->tup_nlports++;
return (p);
}
static void
topo_usb_set_rhub_port_protocol(topo_mod_t *mod, topo_usb_controller_t *tuc,
topo_usb_lport_t *lport)
{
switch (tuc->tuc_driver) {
case TOPO_USB_D_XHCI:
break;
case TOPO_USB_D_UHCI:
case TOPO_USB_D_OHCI:
lport->tul_protocol = TOPO_USB_P_1x;
return;
case TOPO_USB_D_EHCI:
lport->tul_protocol = TOPO_USB_P_20;
return;
case TOPO_USB_D_UNKNOWN:
default:
lport->tul_protocol = TOPO_USB_P_UNKNOWN;
return;
}
if (lport->tul_portno >= tuc->tuc_fusb20 &&
lport->tul_portno < tuc->tuc_fusb20 + tuc->tuc_nusb20) {
lport->tul_protocol = TOPO_USB_P_20;
} else if (lport->tul_portno >= tuc->tuc_fusb30 &&
lport->tul_portno < tuc->tuc_fusb30 + tuc->tuc_nusb30) {
lport->tul_protocol = TOPO_USB_P_30;
} else if (lport->tul_portno >= tuc->tuc_fusb31 &&
lport->tul_portno < tuc->tuc_fusb31 + tuc->tuc_nusb31) {
lport->tul_protocol = TOPO_USB_P_31;
} else {
lport->tul_protocol = TOPO_USB_P_UNKNOWN;
}
}
static int
topo_usb_gather_devcfg_port(topo_mod_t *mod, topo_usb_controller_t *c,
topo_list_t *plist, di_node_t node)
{
int *vend, *reg, *nports;
topo_usb_lport_t *l;
char *drvname;
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "usb-vendor-id",
&vend) != 1) {
topo_mod_dprintf(mod, "failed to find usb-vendor-id property "
"for child");
return (0);
}
if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®) != 1 ||
*reg <= 0) {
topo_mod_dprintf(mod, "got bad \"reg\" property");
return (topo_mod_seterrno(mod, EMOD_PARTIAL_ENUM));
}
if ((l = topo_usb_lport_find(plist, (uint_t)*reg)) == NULL) {
topo_mod_dprintf(mod, "failed to find topo_usb_lport_t for "
"port %d", *reg);
return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
}
l->tul_device = node;
if ((drvname = di_driver_name(node)) != NULL &&
strcmp(drvname, "hubd") == 0 &&
di_prop_lookup_ints(DDI_DEV_T_ANY, node, "usb-port-count",
&nports) == 1 && *nports >= 1) {
di_node_t child;
l->tul_nhubd_ports = (uint_t)*nports;
for (uint_t i = 1; i <= l->tul_nhubd_ports; i++) {
topo_usb_lport_t *clport;
topo_usb_port_t *cport;
if ((cport = topo_usb_port_create(mod, i, l->tul_name,
'.')) == NULL) {
return (topo_mod_seterrno(mod, EMOD_NOMEM));
}
clport = topo_list_next(&cport->tup_lports);
topo_list_append(&l->tul_ports, cport);
l->tul_nports++;
clport->tul_protocol = l->tul_protocol;
}
for (child = di_child_node(node); child != NULL;
child = di_sibling_node(child)) {
int ret;
if ((ret = topo_usb_gather_devcfg_port(mod, c,
&l->tul_ports, child)) != 0) {
return (-1);
}
}
}
return (0);
}
static int
topo_usb_gather_devcfg_cb(di_node_t node, void *arg)
{
uint_t i;
topo_usb_controller_t *tuc;
di_prop_t prop = DI_PROP_NIL;
boolean_t rh = B_FALSE, pc = B_FALSE;
topo_usb_devcfg_arg_t *tda = arg;
topo_usb_t *usb = tda->tda_usb;
topo_mod_t *mod = tda->tda_mod;
di_node_t child;
while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) {
const char *name = di_prop_name(prop);
int *ports;
if (strcmp(name, "root-hub") == 0 &&
di_prop_type(prop) == DI_PROP_TYPE_BOOLEAN) {
rh = B_TRUE;
} else if (strcmp(name, "usb-port-count") == 0 &&
di_prop_ints(prop, &ports) == 1 && *ports > 0 &&
*ports < USB_TOPO_PORT_MAX) {
pc = B_TRUE;
}
}
if (!rh || !pc)
return (DI_WALK_CONTINUE);
if ((tuc = topo_usb_controller_create(mod, usb, node)) == NULL) {
tda->tda_fatal = B_TRUE;
return (DI_WALK_TERMINATE);
}
for (i = 1; i <= tuc->tuc_nhubd_ports; i++) {
topo_usb_lport_t *l;
topo_usb_port_t *p;
topo_mod_dprintf(mod, "attempting to discover lport %u on "
"controller %s", i, tuc->tuc_path);
if ((p = topo_usb_port_create(mod, i, tuc->tuc_name, '@')) ==
NULL) {
topo_mod_dprintf(mod, "failed to create "
"port %u", i);
tda->tda_fatal = B_TRUE;
return (DI_WALK_TERMINATE);
}
topo_list_append(&tuc->tuc_ports, p);
tuc->tuc_nports++;
l = topo_list_next(&p->tup_lports);
topo_usb_set_rhub_port_protocol(mod, tuc, l);
}
for (child = di_child_node(tuc->tuc_devinfo); child != NULL;
child = di_sibling_node(child)) {
int ret;
if ((ret = topo_usb_gather_devcfg_port(mod, tuc,
&tuc->tuc_ports, child)) != 0) {
tda->tda_fatal = B_TRUE;
return (DI_WALK_TERMINATE);
}
}
return (DI_WALK_PRUNECHILD);
}
static boolean_t
topo_usb_gather_devcfg(topo_mod_t *mod, topo_usb_t *usb)
{
topo_usb_devcfg_arg_t tda;
tda.tda_usb = usb;
tda.tda_mod = mod;
tda.tda_fatal = B_FALSE;
(void) di_walk_node(usb->tu_devinfo, DI_WALK_CLDFIRST,
&tda, topo_usb_gather_devcfg_cb);
return (!tda.tda_fatal);
}
static boolean_t
topo_usb_acpi_pld_match(const acpi_pld_info_t *l, const acpi_pld_info_t *r)
{
if (l->Panel == r->Panel &&
l->VerticalPosition == r->VerticalPosition &&
l->HorizontalPosition == r->HorizontalPosition &&
l->Shape == r->Shape &&
l->GroupOrientation == r->GroupOrientation &&
l->GroupPosition == r->GroupPosition &&
l->GroupToken == r->GroupToken) {
return (B_TRUE);
}
return (B_FALSE);
}
typedef boolean_t (*topo_usb_port_match_f)(topo_usb_port_t *, void *);
static topo_usb_port_t *
topo_usb_port_match_lport(topo_usb_lport_t *lport, boolean_t remove,
topo_usb_port_match_f func, void *arg)
{
topo_usb_port_t *p;
for (p = topo_list_next(&lport->tul_ports); p != NULL;
p = topo_list_next(p)) {
topo_usb_lport_t *l;
topo_usb_port_t *ret;
if (func(p, arg)) {
if (remove) {
topo_list_delete(&lport->tul_ports, p);
lport->tul_nports--;
}
return (p);
}
for (l = topo_list_next(&p->tup_lports); l != NULL;
l = topo_list_next(l)) {
if ((ret = topo_usb_port_match_lport(l,
remove, func, arg)) != NULL) {
return (ret);
}
}
}
return (NULL);
}
static topo_usb_port_t *
topo_usb_port_match_controller(topo_usb_controller_t *c, boolean_t remove,
topo_usb_port_match_f func, void *arg)
{
topo_usb_port_t *p;
for (p = topo_list_next(&c->tuc_ports); p != NULL;
p = topo_list_next(p)) {
topo_usb_lport_t *l;
topo_usb_port_t *ret;
if (func(p, arg)) {
if (remove) {
topo_list_delete(&c->tuc_ports, p);
c->tuc_nports--;
}
return (p);
}
for (l = topo_list_next(&p->tup_lports); l != NULL;
l = topo_list_next(l)) {
if ((ret = topo_usb_port_match_lport(l,
remove, func, arg)) != NULL) {
return (ret);
}
}
}
return (NULL);
}
static topo_usb_port_t *
topo_usb_port_match(topo_usb_t *usb, boolean_t remove,
topo_usb_port_match_f func, void *arg)
{
topo_usb_controller_t *c;
for (c = topo_list_next(&usb->tu_controllers); c != NULL;
c = topo_list_next(c)) {
topo_usb_port_t *p;
if ((p = topo_usb_port_match_controller(c, remove, func,
arg)) != NULL)
return (p);
}
return (NULL);
}
static void
topo_usb_port_merge(topo_usb_port_t *sink, topo_usb_port_t *source)
{
topo_usb_lport_t *l;
while ((l = topo_list_next(&source->tup_lports)) != NULL) {
topo_list_delete(&source->tup_lports, l);
source->tup_nlports--;
topo_list_append(&sink->tup_lports, l);
sink->tup_nlports++;
}
if (sink->tup_port_type == USB_TOPO_PORT_TYPE_DEFAULT) {
sink->tup_port_type = source->tup_port_type;
}
if (sink->tup_port_connected == TOPO_USB_C_UNKNOWN) {
sink->tup_port_connected = source->tup_port_connected;
}
}
static boolean_t
topo_usb_acpi_port_match(topo_usb_port_t *port, void *arg)
{
topo_usb_port_t *target = arg;
return (port != target && port->tup_pld_valid &&
topo_usb_acpi_pld_match(&port->tup_pld, &target->tup_pld));
}
static void
topo_usb_acpi_match(topo_mod_t *mod, topo_usb_controller_t *tuc)
{
topo_usb_port_t *p;
for (p = topo_list_next(&tuc->tuc_ports); p != NULL;
p = topo_list_next(p)) {
topo_usb_port_t *match;
if ((match = topo_usb_port_match_controller(tuc, B_TRUE,
topo_usb_acpi_port_match, p)) != NULL) {
VERIFY3P(p, !=, match);
topo_usb_port_merge(p, match);
topo_mod_free(mod, match, sizeof (topo_usb_port_t));
}
}
}
static boolean_t
topo_usb_metadata_match(topo_usb_port_t *port, void *arg)
{
topo_usb_meta_port_path_t *path = arg;
topo_usb_lport_t *l;
if (path->tmpp_type != TOPO_USB_T_ACPI)
return (B_FALSE);
for (l = topo_list_next(&port->tup_lports); l != NULL;
l = topo_list_next(l)) {
if (l->tul_acpi_name != NULL && strcmp(path->tmpp_path,
l->tul_acpi_name) == 0) {
return (B_TRUE);
}
}
return (B_FALSE);
}
static void
topo_usb_apply_metadata(topo_mod_t *mod, topo_usb_t *usb)
{
topo_usb_meta_port_t *m;
for (m = topo_list_next(&usb->tu_metadata); m != NULL;
m = topo_list_next(m)) {
topo_usb_port_t *p, *sink = NULL;
topo_usb_meta_port_path_t *path;
boolean_t remove = B_FALSE;
if (m->tmp_flags & TOPO_USB_F_CHASSIS) {
remove = B_TRUE;
}
for (path = topo_list_next(&m->tmp_paths); path != NULL;
path = topo_list_next(path)) {
topo_mod_dprintf(mod, "considering metadata path %s",
path->tmpp_path);
if ((p = topo_usb_port_match(usb, remove,
topo_usb_metadata_match, path)) == NULL)
continue;
topo_mod_dprintf(mod, "matched path to a logical port");
p->tup_meta = m;
if ((m->tmp_flags & TOPO_USB_F_CHASSIS) != 0 &&
sink == NULL) {
topo_list_append(&usb->tu_chassis_ports, p);
usb->tu_nchassis_ports++;
}
if ((usb->tu_meta_flags & TOPO_USB_M_METADATA_MATCH) !=
0) {
if (sink == NULL) {
sink = p;
remove = B_TRUE;
} else {
VERIFY3P(p, !=, sink);
topo_usb_port_merge(sink, p);
topo_mod_free(mod, p,
sizeof (topo_usb_port_t));
}
continue;
}
break;
}
}
}
static int
topo_usb_gather(topo_mod_t *mod, topo_usb_t *usb, tnode_t *pnode)
{
int ret;
if ((ret = topo_usb_load_metadata(mod, pnode, &usb->tu_metadata,
&usb->tu_meta_flags)) != 0) {
topo_mod_dprintf(mod, "failed to read usb metadata");
return (-1);
}
topo_mod_dprintf(mod, "loaded metadata flags: %d", usb->tu_meta_flags);
if (!topo_usb_gather_devcfg(mod, usb)) {
topo_mod_dprintf(mod, "encountered fatal error while "
"gathering physical data");
return (-1);
}
if ((usb->tu_meta_flags & TOPO_USB_M_NO_ACPI) == 0 &&
!topo_usb_gather_acpi(mod, usb)) {
topo_mod_dprintf(mod, "encountered fatal error while "
"gathering ACPI data");
return (-1);
}
if ((usb->tu_meta_flags & TOPO_USB_M_ACPI_MATCH) != 0) {
topo_usb_controller_t *c;
for (c = topo_list_next(&usb->tu_controllers); c != NULL;
c = topo_list_next(c)) {
if (c->tuc_driver == TOPO_USB_D_XHCI) {
topo_usb_acpi_match(mod, c);
}
}
}
topo_usb_apply_metadata(mod, usb);
return (0);
}
static int
topo_usb_port_properties(topo_mod_t *mod, tnode_t *tn, topo_usb_port_t *port)
{
int err;
char **strs = NULL;
uint_t i;
topo_usb_lport_t *l;
char *label;
const char *ptype;
size_t strlen;
strlen = sizeof (char *) * MAX(port->tup_nlports,
TOPO_PROP_USB_PORT_NATTRS);
if ((strs = topo_mod_zalloc(mod, strlen)) == NULL) {
return (-1);
}
label = NULL;
if (port->tup_meta != NULL) {
label = port->tup_meta->tmp_label;
}
if (port->tup_meta != NULL && port->tup_meta->tmp_port_type !=
USB_TOPO_PORT_TYPE_DEFAULT) {
ptype =
topo_usb_port_type_to_string(port->tup_meta->tmp_port_type);
} else {
ptype = topo_usb_port_type_to_string(port->tup_port_type);
}
if (topo_pgroup_create(tn, &topo_usb_port_pgroup, &err) != 0) {
topo_mod_dprintf(mod, "failed to create property group %s: "
"%s\n", TOPO_PGROUP_USB_PORT, topo_strerror(err));
goto error;
}
if (label != NULL && topo_node_label_set(tn, label, &err) != 0) {
topo_mod_dprintf(mod, "failed to set label on port: %s",
topo_strerror(err));
goto error;
}
if (ptype != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PORT,
TOPO_PROP_USB_PORT_TYPE, TOPO_PROP_IMMUTABLE, ptype, &err) != 0) {
topo_mod_dprintf(mod, "failed to set %s property: %s",
TOPO_PROP_USB_PORT_TYPE, topo_strerror(err));
goto error;
}
for (i = 0, l = topo_list_next(&port->tup_lports); l != NULL;
l = topo_list_next(l)) {
char *vers;
int j;
switch (l->tul_protocol) {
case TOPO_USB_P_1x:
vers = "1.x";
break;
case TOPO_USB_P_20:
vers = "2.0";
break;
case TOPO_USB_P_30:
vers = "3.0";
break;
case TOPO_USB_P_31:
vers = "3.1";
break;
default:
continue;
}
for (j = 0; j < i; j++) {
if (strcmp(strs[j], vers) == 0)
break;
}
if (j < i)
continue;
strs[i++] = vers;
}
if (i > 0 && topo_prop_set_string_array(tn, TOPO_PGROUP_USB_PORT,
TOPO_PROP_USB_PORT_VERSIONS, TOPO_PROP_IMMUTABLE,
(const char **)strs, i, &err) != 0) {
topo_mod_dprintf(mod, "failed to set %s property: %s",
TOPO_PROP_USB_PORT_VERSIONS, topo_strerror(err));
goto error;
}
i = 0;
if (port->tup_pld_valid && port->tup_pld.UserVisible != 0 &&
port->tup_port_connected == TOPO_USB_C_CONNECTED) {
strs[i++] = TOPO_PROP_USB_PORT_A_VISIBLE;
} else if (port->tup_port_connected == TOPO_USB_C_CONNECTED) {
strs[i++] = TOPO_PROP_USB_PORT_A_CONNECTED;
} else if (port->tup_port_connected == TOPO_USB_C_DISCONNECTED) {
strs[i++] = TOPO_PROP_USB_PORT_A_DISCONNECTED;
}
if (port->tup_meta != NULL) {
if (port->tup_meta->tmp_flags & TOPO_USB_F_INTERNAL) {
strs[i++] = TOPO_PROP_USB_PORT_A_INTERNAL;
}
if (port->tup_meta->tmp_flags & TOPO_USB_F_EXTERNAL) {
strs[i++] = TOPO_PROP_USB_PORT_A_EXTERNAL;
}
}
if (i > 0 && topo_prop_set_string_array(tn, TOPO_PGROUP_USB_PORT,
TOPO_PROP_USB_PORT_ATTRIBUTES, TOPO_PROP_IMMUTABLE,
(const char **)strs, i, &err) != 0) {
topo_mod_dprintf(mod, "failed to set %s property: %s",
TOPO_PROP_USB_PORT_VERSIONS, topo_strerror(err));
goto error;
}
for (i = 0, l = topo_list_next(&port->tup_lports); l != NULL;
l = topo_list_next(l)) {
strs[i++] = l->tul_name;
}
if (i > 0 && topo_prop_set_string_array(tn, TOPO_PGROUP_USB_PORT,
TOPO_PROP_USB_PORT_LPORTS, TOPO_PROP_IMMUTABLE,
(const char **)strs, i, &err) != 0) {
topo_mod_dprintf(mod, "failed to set %s propert: %s",
TOPO_PROP_USB_PORT_LPORTS, topo_strerror(err));
goto error;
}
err = 0;
error:
if (strs != NULL) {
topo_mod_free(mod, strs, strlen);
}
if (err != 0) {
return (topo_mod_seterrno(mod, err));
}
return (err);
}
static int
topo_usb_enum_scsa2usb(topo_mod_t *mod, tnode_t *tn, topo_usb_lport_t *lport)
{
int ret;
di_node_t child;
char *devfs = NULL;
topo_instance_t min = 0, max = 0;
if ((child = di_child_node(lport->tul_device)) == DI_NODE_NIL ||
strcmp("disk", di_node_name(child)) != 0) {
return (0);
}
if ((devfs = di_devfs_path(child)) == NULL) {
topo_mod_dprintf(mod, "failed to get USB disk child device "
"devfs path");
return (topo_mod_seterrno(mod, EMOD_NOMEM));
}
if (topo_mod_load(mod, DISK, TOPO_VERSION) == NULL) {
topo_mod_dprintf(mod, "failed to load disk module: %s",
topo_mod_errmsg(mod));
goto error;
}
if (topo_pgroup_create(tn, &topo_binding_pgroup, &ret) != 0) {
topo_mod_dprintf(mod, "failed to create \"binding\" "
"property group: %s", topo_strerror(ret));
goto error;
}
if (topo_prop_set_string(tn, TOPO_PGROUP_BINDING,
TOPO_BINDING_OCCUPANT, TOPO_PROP_IMMUTABLE, devfs, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_IO_MODULE, topo_strerror(ret));
goto error;
}
if (topo_node_range_create(mod, tn, DISK, min, max) != 0) {
topo_mod_dprintf(mod, "failed to create disk node range %s: %s",
devfs, topo_mod_errmsg(mod));
goto error;
}
if (topo_mod_enumerate(mod, tn, DISK, DISK, min, max, NULL) != 0) {
topo_mod_dprintf(mod, "failed to create disk node %s: %s",
devfs, topo_mod_errmsg(mod));
goto error;
}
di_devfs_path_free(devfs);
return (0);
error:
di_devfs_path_free(devfs);
return (-1);
}
static int
topo_usb_enum_port_children(topo_mod_t *mod, tnode_t *pn,
topo_usb_lport_t *plport)
{
int ret;
topo_usb_port_t *port;
topo_instance_t min = 0, i;
if ((ret = port_range_create(mod, pn, min, plport->tul_nports)) != 0) {
topo_mod_dprintf(mod, "failed to create port range [%u, %u) "
"for child hub", 0, plport->tul_nports);
return (ret);
}
for (i = 0, port = topo_list_next(&plport->tul_ports); port != NULL;
port = topo_list_next(port)) {
tnode_t *tn;
if ((ret = port_create_usb(mod, pn, i, &tn)) != 0)
return (ret);
if ((ret = topo_usb_port_properties(mod, tn, port)) != 0) {
return (ret);
}
if ((ret = topo_usb_enum_device(mod, tn, port)) != 0)
return (ret);
i++;
}
return (0);
}
static int
topo_usb_enum_lport(topo_mod_t *mod, tnode_t *pn, topo_usb_port_t *port,
topo_usb_lport_t *lport, topo_instance_t topo_inst)
{
int ret, inst;
int *vendid = NULL, *prodid = NULL, *revid = NULL, *release = NULL;
char *vend = NULL, *prod = NULL, *serial = NULL, *speed = NULL;
char *min_speed = NULL, *sup_speeds = NULL;
int nsup_speeds = 0;
char *driver, *devfs;
char revbuf[32], relbuf[32];
tnode_t *tn = NULL;
di_prop_t prop = DI_PROP_NIL;
nvlist_t *auth = NULL, *fmri = NULL, *modnvl = NULL;
for (prop = di_prop_next(lport->tul_device, DI_PROP_NIL);
prop != DI_PROP_NIL; prop = di_prop_next(lport->tul_device, prop)) {
const char *pname = di_prop_name(prop);
if (strcmp(pname, "usb-vendor-id") == 0) {
if (di_prop_ints(prop, &vendid) != 1)
vendid = NULL;
} else if (strcmp(pname, "usb-product-id") == 0) {
if (di_prop_ints(prop, &prodid) != 1)
prodid = NULL;
} else if (strcmp(pname, "usb-revision-id") == 0) {
if (di_prop_ints(prop, &revid) != 1) {
revid = NULL;
} else {
(void) snprintf(revbuf, sizeof (revbuf), "%x",
*revid);
}
} else if (strcmp(pname, "usb-release") == 0) {
if (di_prop_ints(prop, &release) != 1) {
release = NULL;
} else {
(void) snprintf(relbuf, sizeof (relbuf),
"%x.%x", *release >> 8,
(*release >> 4) & 0xf);
}
} else if (strcmp(pname, "usb-vendor-name") == 0) {
if (di_prop_strings(prop, &vend) != 1)
vend = NULL;
} else if (strcmp(pname, "usb-product-name") == 0) {
if (di_prop_strings(prop, &prod) != 1)
prod = NULL;
} else if (strcmp(pname, "usb-serialno") == 0) {
if (di_prop_strings(prop, &serial) != 1)
serial = NULL;
} else if (strcmp(pname, "full-speed") == 0) {
speed = "full-speed";
} else if (strcmp(pname, "low-speed") == 0) {
speed = "low-speed";
} else if (strcmp(pname, "high-speed") == 0) {
speed = "high-speed";
} else if (strcmp(pname, "super-speed") == 0) {
speed = "super-speed";
} else if (strcmp(pname, "usb-minimum-speed") == 0) {
if (di_prop_strings(prop, &min_speed) != 1)
min_speed = NULL;
} else if (strcmp(pname, "usb-supported-speeds") == 0) {
nsup_speeds = di_prop_strings(prop, &sup_speeds);
if (nsup_speeds <= 0) {
sup_speeds = NULL;
}
}
}
driver = di_driver_name(lport->tul_device);
inst = di_instance(lport->tul_device);
devfs = di_devfs_path(lport->tul_device);
if ((auth = topo_mod_auth(mod, pn)) == NULL) {
topo_mod_dprintf(mod, "failed to get authority for USB device: "
"%s", topo_mod_errmsg(mod));
goto error;
}
if ((fmri = topo_mod_hcfmri(mod, pn, FM_HC_SCHEME_VERSION, USB_DEVICE,
topo_inst, NULL, auth, prod, revbuf, serial)) == NULL) {
topo_mod_dprintf(mod, "failed to generate fmri for USB "
"device %s: %s", di_devfs_path(lport->tul_device),
topo_mod_errmsg(mod));
goto error;
}
if ((tn = topo_node_bind(mod, pn, USB_DEVICE, topo_inst, fmri)) ==
NULL) {
topo_mod_dprintf(mod, "failed to bind USB device node: %s",
topo_mod_errmsg(mod));
goto error;
}
if (topo_node_fru_set(tn, fmri, 0, &ret) != 0) {
topo_mod_dprintf(mod, "failed to set FRU: %s",
topo_strerror(ret));
(void) topo_mod_seterrno(mod, ret);
goto error;
}
if (port->tup_meta != NULL && port->tup_meta->tmp_label != NULL &&
topo_node_label_set(tn, port->tup_meta->tmp_label, &ret) != 0) {
topo_mod_dprintf(mod, "failed to set label on device: %s",
topo_strerror(ret));
goto error;
}
if (topo_pgroup_create(tn, &topo_usb_props_pgroup, &ret) != 0) {
topo_mod_dprintf(mod, "failed to create \"usb-properties\" "
"property group: %s", topo_strerror(ret));
goto error;
}
if (topo_prop_set_uint32(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_PORT, TOPO_PROP_IMMUTABLE, lport->tul_portno,
&ret) != 0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_PORT, topo_strerror(ret));
goto error;
}
if (vendid != NULL && topo_prop_set_int32(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_VID, TOPO_PROP_IMMUTABLE, *vendid, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_VID, topo_strerror(ret));
goto error;
}
if (prodid != NULL && topo_prop_set_int32(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_PID, TOPO_PROP_IMMUTABLE, *prodid, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_PID, topo_strerror(ret));
goto error;
}
if (revid != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_REV, TOPO_PROP_IMMUTABLE, revbuf, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_REV, topo_strerror(ret));
goto error;
}
if (release != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_VERSION, TOPO_PROP_IMMUTABLE, relbuf, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_VERSION, topo_strerror(ret));
goto error;
}
if (vend != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_VNAME, TOPO_PROP_IMMUTABLE, vend, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_VNAME, topo_strerror(ret));
goto error;
}
if (prod != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_PNAME, TOPO_PROP_IMMUTABLE, prod, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_PNAME, topo_strerror(ret));
goto error;
}
if (serial != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_SN, TOPO_PROP_IMMUTABLE, serial, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_SN, topo_strerror(ret));
goto error;
}
if (speed != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_SPEED, TOPO_PROP_IMMUTABLE, speed, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_SPEED, topo_strerror(ret));
goto error;
}
if (min_speed != NULL && topo_prop_set_string(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_MIN_SPEED, TOPO_PROP_IMMUTABLE, min_speed,
&ret) != 0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_PGROUP_USB_PROPS_MIN_SPEED, topo_strerror(ret));
goto error;
}
if (sup_speeds != NULL) {
const char **strings, *c;
int i, rval;
if ((strings = topo_mod_zalloc(mod, sizeof (char *) *
nsup_speeds)) == NULL) {
topo_mod_dprintf(mod, "failed to allocate character "
"array for property %s",
TOPO_PGROUP_USB_PROPS_SUPPORTED_SPEEDS);
goto error;
}
for (c = sup_speeds, i = 0; i < nsup_speeds; i++) {
size_t len;
strings[i] = c;
if (i + 1 < nsup_speeds) {
len = strlen(c);
c += len + 1;
}
}
rval = topo_prop_set_string_array(tn, TOPO_PGROUP_USB_PROPS,
TOPO_PGROUP_USB_PROPS_SUPPORTED_SPEEDS, TOPO_PROP_IMMUTABLE,
strings, nsup_speeds, &ret);
topo_mod_free(mod, strings, sizeof (char *) * nsup_speeds);
if (rval != 0) {
topo_mod_dprintf(mod, "failed to create property %s: "
"%s", TOPO_PGROUP_USB_PROPS_SUPPORTED_SPEEDS,
topo_strerror(ret));
}
}
if (topo_pgroup_create(tn, &topo_io_pgroup, &ret) != 0) {
topo_mod_dprintf(mod, "failed to create \"io\" "
"property group: %s", topo_strerror(ret));
goto error;
}
if (driver != NULL && topo_prop_set_string(tn, TOPO_PGROUP_IO,
TOPO_IO_DRIVER, TOPO_PROP_IMMUTABLE, driver, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_IO_DRIVER, topo_strerror(ret));
goto error;
}
if (inst != -1 && topo_prop_set_uint32(tn, TOPO_PGROUP_IO,
TOPO_IO_INSTANCE, TOPO_PROP_IMMUTABLE, inst, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_IO_INSTANCE, topo_strerror(ret));
goto error;
}
if (devfs != NULL && topo_prop_set_string(tn, TOPO_PGROUP_IO,
TOPO_IO_DEV_PATH, TOPO_PROP_IMMUTABLE, devfs, &ret) !=
0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_IO_DEV_PATH, topo_strerror(ret));
goto error;
}
if (driver != NULL && (modnvl = topo_mod_modfmri(mod,
FM_MOD_SCHEME_VERSION, driver)) != NULL &&
topo_prop_set_fmri(tn, TOPO_PGROUP_IO, TOPO_IO_MODULE,
TOPO_PROP_IMMUTABLE, modnvl, &ret) != 0) {
topo_mod_dprintf(mod, "failed to create property %s: %s",
TOPO_IO_MODULE, topo_strerror(ret));
goto error;
}
if (driver != NULL && strcmp(driver, "scsa2usb") == 0) {
if ((ret = topo_usb_enum_scsa2usb(mod, tn, lport)) != 0)
goto error;
}
if (lport->tul_nports > 0 && driver != NULL &&
strcmp(driver, "hubd") == 0) {
if ((ret = topo_usb_enum_port_children(mod, tn, lport)) != 0)
goto error;
}
di_devfs_path_free(devfs);
nvlist_free(fmri);
nvlist_free(auth);
nvlist_free(modnvl);
return (0);
error:
topo_node_unbind(tn);
di_devfs_path_free(devfs);
nvlist_free(fmri);
nvlist_free(auth);
nvlist_free(modnvl);
return (-1);
}
static int
topo_usb_enum_device(topo_mod_t *mod, tnode_t *pn, topo_usb_port_t *port)
{
int ret;
topo_instance_t i, max;
topo_usb_lport_t *l;
max = 0;
for (l = topo_list_next(&port->tup_lports); l != NULL;
l = topo_list_next(l)) {
if (l->tul_device != DI_NODE_NIL)
max++;
}
if (max == 0) {
return (0);
}
if ((ret = topo_node_range_create(mod, pn, USB_DEVICE, 0, max - 1)) !=
0) {
return (-1);
}
for (i = 0, l = topo_list_next(&port->tup_lports); l != NULL;
l = topo_list_next(l)) {
if (l->tul_device != DI_NODE_NIL) {
topo_mod_dprintf(mod, "enumerating device on lport "
"%u, log inst %" PRIu64 "", l->tul_portno, i);
if ((ret = topo_usb_enum_lport(mod, pn, port, l,
i)) != 0) {
return (ret);
}
i++;
}
}
return (0);
}
static int
topo_usb_enum_controller(topo_mod_t *mod, tnode_t *pnode,
topo_usb_controller_t *c, topo_instance_t base)
{
int ret;
topo_usb_port_t *port;
if (c->tuc_enumed)
return (0);
c->tuc_enumed = B_TRUE;
if (c->tuc_nports == 0)
return (0);
for (port = topo_list_next(&c->tuc_ports); port != NULL;
port = topo_list_next(port)) {
tnode_t *tn;
if ((ret = port_create_usb(mod, pnode, base, &tn)) != 0)
return (ret);
if ((ret = topo_usb_port_properties(mod, tn, port)) != 0) {
return (ret);
}
if ((ret = topo_usb_enum_device(mod, tn, port)) != 0)
return (ret);
base++;
}
return (0);
}
static int
topo_usb_enum_mobo(topo_mod_t *mod, tnode_t *pnode, topo_usb_t *usb)
{
int ret;
topo_usb_controller_t *c;
topo_instance_t inst = 0;
for (c = topo_list_next(&usb->tu_controllers); c != NULL;
c = topo_list_next(c)) {
inst += c->tuc_nports;
}
if ((ret = port_range_create(mod, pnode, 0, inst)) != 0) {
topo_mod_dprintf(mod, "failed to create port range [0, %"
PRIu64 ") for mobo", inst);
return (ret);
}
inst = 0;
for (c = topo_list_next(&usb->tu_controllers); c != NULL;
c = topo_list_next(c)) {
if (c->tuc_enumed)
continue;
if ((ret = topo_usb_enum_controller(mod, pnode, c, inst)) !=
0) {
return (ret);
}
inst += c->tuc_nports;
}
return (0);
}
static int
topo_usb_enum_pci(topo_mod_t *mod, tnode_t *pnode, topo_usb_t *usb,
di_node_t din)
{
int ret;
topo_usb_controller_t *c;
for (c = topo_list_next(&usb->tu_controllers); c != NULL;
c = topo_list_next(c)) {
if (din == c->tuc_devinfo) {
break;
}
}
if (c == NULL) {
return (topo_mod_seterrno(mod, EMOD_PARTIAL_ENUM));
}
if ((ret = port_range_create(mod, pnode, 0, c->tuc_nports)) != 0) {
topo_mod_dprintf(mod, "failed to create port range [0, %u) "
"for controller %s", c->tuc_nports, c->tuc_path);
return (ret);
}
return (topo_usb_enum_controller(mod, pnode, c, 0));
}
static int
topo_usb_enum_chassis(topo_mod_t *mod, tnode_t *pnode, topo_usb_t *usb)
{
int ret;
topo_usb_port_t *p;
topo_instance_t base = 0;
if (usb->tu_nchassis_ports == 0)
return (0);
if ((ret = port_range_create(mod, pnode, 0, usb->tu_nchassis_ports)) !=
0) {
topo_mod_dprintf(mod, "failed to create port range [0, %u) "
"for chassis", usb->tu_nchassis_ports);
return (ret);
}
for (p = topo_list_next(&usb->tu_chassis_ports); p != NULL;
p = topo_list_next(p)) {
tnode_t *tn;
if ((ret = port_create_usb(mod, pnode, base, &tn)) != 0)
return (ret);
if ((ret = topo_usb_port_properties(mod, tn, p)) != 0) {
return (ret);
}
if ((ret = topo_usb_enum_device(mod, tn, p)) != 0)
return (ret);
base++;
}
return (0);
}
static int
topo_usb_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
topo_instance_t min, topo_instance_t max, void *modarg, void *data)
{
topo_usb_t *usb;
topo_usb_type_t type;
if (strcmp(name, USB_PCI) == 0) {
type = TOPO_USB_PCI;
} else if (strcmp(name, USB_MOBO) == 0) {
type = TOPO_USB_MOBO;
} else if (strcmp(name, USB_CHASSIS) == 0) {
type = TOPO_USB_CHASSIS;
} else {
topo_mod_dprintf(mod, "usb_enum: asked to enumerate unknown "
"component: %s\n", name);
return (-1);
}
if (type == TOPO_USB_PCI && data == NULL) {
topo_mod_dprintf(mod, "usb_enum: missing argument to "
"PCI controller enum");
return (-1);
} else if (type != TOPO_USB_PCI && data != NULL) {
topo_mod_dprintf(mod, "extraneous argument to non-controller "
"enum %s", name);
return (-1);
}
if ((usb = topo_mod_getspecific(mod)) == NULL) {
return (-1);
}
if (!usb->tu_enum_done) {
if (topo_usb_gather(mod, usb, pnode) != 0)
return (-1);
usb->tu_enum_done = B_TRUE;
}
if (type == TOPO_USB_PCI) {
return (topo_usb_enum_pci(mod, pnode, usb, data));
} else if (type == TOPO_USB_MOBO) {
return (topo_usb_enum_mobo(mod, pnode, usb));
} else if (type == TOPO_USB_CHASSIS) {
return (topo_usb_enum_chassis(mod, pnode, usb));
}
return (0);
}
static const topo_modops_t usb_ops = {
topo_usb_enum, NULL
};
static topo_modinfo_t usb_mod = {
USB, FM_FMRI_SCHEME_HC, USB_VERSION, &usb_ops
};
static void
topo_usb_port_free(topo_mod_t *mod, topo_usb_port_t *p)
{
topo_usb_lport_t *lport;
while ((lport = topo_list_next(&p->tup_lports)) != NULL) {
topo_usb_port_t *child;
topo_list_delete(&p->tup_lports, lport);
while ((child = topo_list_next(&lport->tul_ports)) != NULL) {
topo_list_delete(&lport->tul_ports, child);
topo_usb_port_free(mod, child);
}
topo_mod_free(mod, lport, sizeof (topo_usb_lport_t));
}
topo_mod_free(mod, p, sizeof (topo_usb_port_t));
}
static void
topo_usb_free(topo_mod_t *mod, topo_usb_t *usb)
{
topo_usb_controller_t *c;
topo_usb_port_t *p;
if (usb == NULL)
return;
while ((p = topo_list_next(&usb->tu_chassis_ports)) != NULL) {
topo_list_delete(&usb->tu_chassis_ports, p);
topo_usb_port_free(mod, p);
}
while ((c = topo_list_next(&usb->tu_controllers)) != NULL) {
topo_list_delete(&usb->tu_controllers, c);
di_devfs_path_free(c->tuc_path);
while ((p = topo_list_next(&c->tuc_ports)) != NULL) {
topo_list_delete(&c->tuc_ports, p);
topo_usb_port_free(mod, p);
}
topo_mod_free(mod, c, sizeof (topo_usb_controller_t));
}
topo_usb_free_metadata(mod, &usb->tu_metadata);
usb->tu_devinfo = DI_NODE_NIL;
topo_mod_free(mod, usb, sizeof (topo_usb_t));
}
static topo_usb_t *
topo_usb_alloc(topo_mod_t *mod)
{
topo_usb_t *usb = NULL;
if ((usb = topo_mod_zalloc(mod, sizeof (topo_usb_t))) == NULL) {
goto free;
}
if ((usb->tu_devinfo = topo_mod_devinfo(mod)) == DI_NODE_NIL) {
goto free;
}
return (usb);
free:
topo_usb_free(mod, usb);
return (NULL);
}
int
_topo_init(topo_mod_t *mod, topo_version_t version)
{
topo_usb_t *usb;
if (getenv("TOPOUSBDEBUG") != NULL)
topo_mod_setdebug(mod);
topo_mod_dprintf(mod, "_mod_init: initializing %s enumerator\n", USB);
if (version != USB_VERSION) {
return (-1);
}
if ((usb = topo_usb_alloc(mod)) == NULL) {
return (-1);
}
if (topo_mod_register(mod, &usb_mod, TOPO_VERSION) != 0) {
topo_usb_free(mod, usb);
return (-1);
}
topo_mod_setspecific(mod, usb);
return (0);
}
void
_topo_fini(topo_mod_t *mod)
{
topo_usb_free(mod, topo_mod_getspecific(mod));
topo_mod_setspecific(mod, NULL);
}