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) 2017, Joyent, Inc. 14 * Copyright 2020 Robert Mustacchi 15 */ 16 17 /* 18 * This module covers enumerating properties of physical NICs. At this time, as 19 * various devices are discovered that may relate to various networking gear, we 20 * will attempt to enumerate ports and transceivers under them, if requested. 21 */ 22 23 #include <strings.h> 24 #include <libdevinfo.h> 25 #include <libdladm.h> 26 #include <libdllink.h> 27 #include <libdlstat.h> 28 #include <libsff.h> 29 #include <unistd.h> 30 #include <sys/dld_ioc.h> 31 #include <sys/dld.h> 32 #include <sys/mac.h> 33 34 #include <sys/fm/protocol.h> 35 #include <fm/topo_mod.h> 36 #include <fm/topo_list.h> 37 #include <fm/topo_method.h> 38 39 #include <topo_port.h> 40 #include <topo_transceiver.h> 41 42 #include "topo_nic.h" 43 44 typedef enum { 45 NIC_PORT_UNKNOWN, 46 NIC_PORT_SFF 47 } nic_port_type_t; 48 49 static const topo_pgroup_info_t datalink_pgroup = { 50 TOPO_PGROUP_DATALINK, 51 TOPO_STABILITY_PRIVATE, 52 TOPO_STABILITY_PRIVATE, 53 1 54 }; 55 56 typedef struct nic_port_mac { 57 char npm_mac[ETHERADDRSTRL]; 58 boolean_t npm_valid; 59 topo_mod_t *npm_mod; 60 } nic_port_mac_t; 61 62 /* 63 * The first MAC address is always the primary MAC address, so we only worry 64 * about the first. Thus this function always returns B_FALSE, to terminate 65 * iteration. 66 */ 67 static boolean_t 68 nic_port_datalink_mac_cb(void *arg, dladm_macaddr_attr_t *attr) 69 { 70 nic_port_mac_t *mac = arg; 71 72 if (attr->ma_addrlen != ETHERADDRL) { 73 topo_mod_dprintf(mac->npm_mod, 74 "found address with bad length: %u\n", attr->ma_addrlen); 75 return (B_FALSE); 76 } 77 78 (void) snprintf(mac->npm_mac, sizeof (mac->npm_mac), 79 "%02x:%02x:%02x:%02x:%02x:%02x", 80 attr->ma_addr[0], attr->ma_addr[1], attr->ma_addr[2], 81 attr->ma_addr[3], attr->ma_addr[4], attr->ma_addr[5]); 82 mac->npm_valid = B_TRUE; 83 return (B_FALSE); 84 } 85 86 static int 87 nic_port_datalink_props(topo_mod_t *mod, tnode_t *port, dladm_handle_t handle, 88 datalink_id_t linkid) 89 { 90 int err; 91 dladm_status_t status; 92 uint64_t ifspeed; 93 link_duplex_t duplex; 94 link_state_t state; 95 const char *duplex_str, *state_str; 96 datalink_class_t dlclass; 97 uint32_t media; 98 char dlname[MAXLINKNAMELEN * 2]; 99 char dlerr[DLADM_STRSIZE]; 100 nic_port_mac_t mac; 101 102 status = dladm_datalink_id2info(handle, linkid, NULL, &dlclass, &media, 103 dlname, sizeof (dlname)); 104 if (status != DLADM_STATUS_OK) { 105 topo_mod_dprintf(mod, "failed to get link info: %s\n", 106 dladm_status2str(status, dlerr)); 107 return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); 108 } 109 110 if (dlclass != DATALINK_CLASS_PHYS) { 111 return (0); 112 } 113 114 status = dladm_get_single_mac_stat(handle, linkid, "ifspeed", 115 KSTAT_DATA_UINT64, &ifspeed); 116 if (status != DLADM_STATUS_OK) { 117 topo_mod_dprintf(mod, "failed to get ifspeed: %s\n", 118 dladm_status2str(status, dlerr)); 119 return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); 120 } 121 122 status = dladm_get_single_mac_stat(handle, linkid, "link_duplex", 123 KSTAT_DATA_UINT32, &duplex); 124 if (status != DLADM_STATUS_OK) { 125 topo_mod_dprintf(mod, "failed to get link_duplex: %s\n", 126 dladm_status2str(status, dlerr)); 127 return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); 128 } 129 130 switch (duplex) { 131 case LINK_DUPLEX_HALF: 132 duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_HALF; 133 break; 134 case LINK_DUPLEX_FULL: 135 duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_FULL; 136 break; 137 default: 138 duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_UNKNOWN; 139 break; 140 } 141 142 status = dladm_get_single_mac_stat(handle, linkid, "link_state", 143 KSTAT_DATA_UINT32, &state); 144 if (status != DLADM_STATUS_OK) { 145 topo_mod_dprintf(mod, "failed to get link_duplex: %s\n", 146 dladm_status2str(status, dlerr)); 147 return (topo_mod_seterrno(mod, status)); 148 } 149 150 switch (state) { 151 case LINK_STATE_UP: 152 state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_UP; 153 break; 154 case LINK_STATE_DOWN: 155 state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_DOWN; 156 break; 157 default: 158 state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_UNKNOWN; 159 break; 160 } 161 162 /* 163 * Override the duplex if the link is down. Some devices will leave it 164 * set at half as opposed to unknown. 165 */ 166 if (state == LINK_STATE_DOWN || state == LINK_STATE_UNKNOWN) { 167 duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_UNKNOWN; 168 } 169 170 mac.npm_mac[0] = '\0'; 171 mac.npm_valid = B_FALSE; 172 mac.npm_mod = mod; 173 if (media == DL_ETHER) { 174 (void) dladm_walk_macaddr(handle, linkid, &mac, 175 nic_port_datalink_mac_cb); 176 } 177 178 if (topo_pgroup_create(port, &datalink_pgroup, &err) != 0) { 179 topo_mod_dprintf(mod, "falied to create property group %s: " 180 "%s\n", TOPO_PGROUP_DATALINK, topo_strerror(err)); 181 return (topo_mod_seterrno(mod, err)); 182 } 183 184 if (topo_prop_set_uint64(port, TOPO_PGROUP_DATALINK, 185 TOPO_PGROUP_DATALINK_LINK_SPEED, TOPO_PROP_IMMUTABLE, ifspeed, 186 &err) != 0) { 187 topo_mod_dprintf(mod, "failed to set %s property: %s\n", 188 TOPO_PGROUP_DATALINK_LINK_SPEED, topo_strerror(err)); 189 return (topo_mod_seterrno(mod, err)); 190 } 191 192 if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK, 193 TOPO_PGROUP_DATALINK_LINK_DUPLEX, TOPO_PROP_IMMUTABLE, duplex_str, 194 &err) != 0) { 195 topo_mod_dprintf(mod, "failed to set %s property: %s\n", 196 TOPO_PGROUP_DATALINK_LINK_DUPLEX, topo_strerror(err)); 197 return (topo_mod_seterrno(mod, err)); 198 } 199 200 if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK, 201 TOPO_PGROUP_DATALINK_LINK_STATUS, TOPO_PROP_IMMUTABLE, state_str, 202 &err) != 0) { 203 topo_mod_dprintf(mod, "failed to set %s property: %s\n", 204 TOPO_PGROUP_DATALINK_LINK_STATUS, topo_strerror(err)); 205 return (topo_mod_seterrno(mod, err)); 206 } 207 208 if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK, 209 TOPO_PGROUP_DATALINK_LINK_NAME, TOPO_PROP_IMMUTABLE, dlname, 210 &err) != 0) { 211 topo_mod_dprintf(mod, "failed to set %s propery: %s\n", 212 TOPO_PGROUP_DATALINK_LINK_NAME, topo_strerror(err)); 213 return (topo_mod_seterrno(mod, err)); 214 } 215 216 if (mac.npm_valid) { 217 if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK, 218 TOPO_PGROUP_DATALINK_PMAC, TOPO_PROP_IMMUTABLE, 219 mac.npm_mac, &err) != 0) { 220 topo_mod_dprintf(mod, "failed to set %s propery: %s\n", 221 TOPO_PGROUP_DATALINK_PMAC, topo_strerror(err)); 222 return (topo_mod_seterrno(mod, err)); 223 } 224 } 225 226 227 return (0); 228 } 229 230 /* 231 * Create an instance of a transceiver with the specified id. We must create 232 * both its port and the transceiver node. 233 */ 234 static int 235 nic_create_transceiver(topo_mod_t *mod, tnode_t *pnode, dladm_handle_t handle, 236 datalink_id_t linkid, uint_t tranid, nic_port_type_t port_type) 237 { 238 int ret; 239 tnode_t *port; 240 dld_ioc_gettran_t dgt; 241 dld_ioc_tranio_t dti; 242 uint8_t buf[256]; 243 char ouibuf[16]; 244 char *vendor = NULL, *part = NULL, *rev = NULL, *serial = NULL; 245 nvlist_t *nvl = NULL; 246 247 switch (port_type) { 248 case NIC_PORT_UNKNOWN: 249 ret = port_create_unknown(mod, pnode, tranid, &port); 250 break; 251 case NIC_PORT_SFF: 252 ret = port_create_sff(mod, pnode, tranid, &port); 253 break; 254 } 255 256 if ((ret = nic_port_datalink_props(mod, port, handle, linkid)) != 0) 257 return (ret); 258 259 if (port_type != NIC_PORT_SFF) 260 return (0); 261 262 bzero(&dgt, sizeof (dgt)); 263 dgt.dgt_linkid = linkid; 264 dgt.dgt_tran_id = tranid; 265 266 if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) { 267 if (errno == ENOTSUP) 268 return (0); 269 return (-1); 270 } 271 272 if (dgt.dgt_present == 0) 273 return (0); 274 275 bzero(&dti, sizeof (dti)); 276 dti.dti_linkid = linkid; 277 dti.dti_tran_id = tranid; 278 dti.dti_page = 0xa0; 279 dti.dti_nbytes = sizeof (buf); 280 dti.dti_buf = (uintptr_t)buf; 281 282 if (ioctl(dladm_dld_fd(handle), DLDIOC_READTRAN, &dti) == 0) { 283 uchar_t *oui; 284 uint_t nbyte; 285 286 if (libsff_parse(buf, dti.dti_nbytes, dti.dti_page, 287 &nvl) == 0) { 288 if ((ret = nvlist_lookup_string(nvl, LIBSFF_KEY_VENDOR, 289 &vendor)) != 0 && nvlist_lookup_byte_array(nvl, 290 LIBSFF_KEY_OUI, &oui, &nbyte) == 0 && nbyte == 3) { 291 if (snprintf(ouibuf, sizeof (ouibuf), 292 "%02x:%02x:%02x", oui[0], oui[1], oui[2]) < 293 sizeof (ouibuf)) { 294 vendor = ouibuf; 295 } 296 } else if (ret != 0) { 297 vendor = NULL; 298 } 299 300 if (nvlist_lookup_string(nvl, LIBSFF_KEY_PART, 301 &part) != 0) { 302 part = NULL; 303 } 304 305 if (nvlist_lookup_string(nvl, LIBSFF_KEY_REVISION, 306 &rev) != 0) { 307 rev = NULL; 308 } 309 310 if (nvlist_lookup_string(nvl, LIBSFF_KEY_SERIAL, 311 &serial) != 0) { 312 serial = NULL; 313 } 314 } 315 } 316 317 if (transceiver_range_create(mod, port, 0, 0) != 0) { 318 nvlist_free(nvl); 319 return (-1); 320 } 321 322 if (transceiver_create_sff(mod, port, 0, dgt.dgt_usable, vendor, part, 323 rev, serial, NULL) != 0) { 324 nvlist_free(nvl); 325 return (-1); 326 } 327 328 nvlist_free(nvl); 329 return (0); 330 } 331 332 /* ARGSUSED */ 333 static int 334 nic_enum(topo_mod_t *mod, tnode_t *pnode, const char *name, 335 topo_instance_t min, topo_instance_t max, void *modarg, void *data) 336 { 337 di_node_t din = data; 338 datalink_id_t linkid; 339 dladm_handle_t handle; 340 dld_ioc_gettran_t dgt; 341 uint_t ntrans, i; 342 char dname[MAXNAMELEN]; 343 nic_port_type_t pt; 344 345 if (strcmp(name, NIC) != 0) { 346 topo_mod_dprintf(mod, "nic_enum: asked to enumerate unknown " 347 "component: %s\n", name); 348 return (-1); 349 } 350 351 if (din == NULL) { 352 topo_mod_dprintf(mod, "nic_enum: missing data argument\n"); 353 return (-1); 354 } 355 356 if ((handle = topo_mod_getspecific(mod)) == NULL) { 357 topo_mod_dprintf(mod, "nic_enum: failed to get nic module " 358 "specific data\n"); 359 return (-1); 360 } 361 362 if (snprintf(dname, sizeof (dname), "%s%d", di_driver_name(din), 363 di_instance(din)) >= sizeof (dname)) { 364 topo_mod_dprintf(mod, "nic_enum: device name overflowed " 365 "internal buffer\n"); 366 return (-1); 367 } 368 369 if (dladm_dev2linkid(handle, dname, &linkid) != DLADM_STATUS_OK) 370 return (-1); 371 372 bzero(&dgt, sizeof (dgt)); 373 dgt.dgt_linkid = linkid; 374 dgt.dgt_tran_id = DLDIOC_GETTRAN_GETNTRAN; 375 376 if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) { 377 if (errno != ENOTSUP) { 378 return (-1); 379 } 380 pt = NIC_PORT_UNKNOWN; 381 dgt.dgt_tran_id = 1; 382 } else { 383 pt = NIC_PORT_SFF; 384 } 385 386 ntrans = dgt.dgt_tran_id; 387 if (ntrans == 0) 388 return (0); 389 390 if (port_range_create(mod, pnode, 0, ntrans - 1) != 0) 391 return (-1); 392 393 for (i = 0; i < ntrans; i++) { 394 if (nic_create_transceiver(mod, pnode, handle, linkid, i, 395 pt) != 0) { 396 return (-1); 397 } 398 } 399 400 return (0); 401 } 402 403 static const topo_modops_t nic_ops = { 404 nic_enum, NULL 405 }; 406 407 static topo_modinfo_t nic_mod = { 408 NIC, FM_FMRI_SCHEME_HC, NIC_VERSION, &nic_ops 409 }; 410 411 int 412 _topo_init(topo_mod_t *mod, topo_version_t version) 413 { 414 dladm_handle_t handle; 415 416 if (getenv("TOPONICDEBUG") != NULL) 417 topo_mod_setdebug(mod); 418 419 topo_mod_dprintf(mod, "_mod_init: " 420 "initializing %s enumerator\n", NIC); 421 422 if (version != NIC_VERSION) { 423 return (-1); 424 } 425 426 if (dladm_open(&handle) != 0) 427 return (-1); 428 429 if (topo_mod_register(mod, &nic_mod, TOPO_VERSION) != 0) { 430 dladm_close(handle); 431 return (-1); 432 } 433 434 topo_mod_setspecific(mod, handle); 435 436 return (0); 437 } 438 439 void 440 _topo_fini(topo_mod_t *mod) 441 { 442 dladm_handle_t handle; 443 444 if ((handle = topo_mod_getspecific(mod)) == NULL) 445 return; 446 447 dladm_close(handle); 448 topo_mod_setspecific(mod, NULL); 449 } 450