#include <strings.h>
#include <libdevinfo.h>
#include <libdladm.h>
#include <libdllink.h>
#include <libdlstat.h>
#include <libsff.h>
#include <unistd.h>
#include <sys/dld_ioc.h>
#include <sys/dld.h>
#include <sys/mac.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_transceiver.h>
#include "topo_nic.h"
typedef enum {
NIC_PORT_UNKNOWN,
NIC_PORT_SFF
} nic_port_type_t;
static const topo_pgroup_info_t datalink_pgroup = {
TOPO_PGROUP_DATALINK,
TOPO_STABILITY_PRIVATE,
TOPO_STABILITY_PRIVATE,
1
};
typedef struct nic_port_mac {
char npm_mac[ETHERADDRSTRL];
boolean_t npm_valid;
topo_mod_t *npm_mod;
} nic_port_mac_t;
static const char *nic_nexuses[] = {
"t4nex",
NULL
};
static boolean_t
nic_port_datalink_mac_cb(void *arg, dladm_macaddr_attr_t *attr)
{
nic_port_mac_t *mac = arg;
if (attr->ma_addrlen != ETHERADDRL) {
topo_mod_dprintf(mac->npm_mod,
"found address with bad length: %u\n", attr->ma_addrlen);
return (B_FALSE);
}
(void) snprintf(mac->npm_mac, sizeof (mac->npm_mac),
"%02x:%02x:%02x:%02x:%02x:%02x",
attr->ma_addr[0], attr->ma_addr[1], attr->ma_addr[2],
attr->ma_addr[3], attr->ma_addr[4], attr->ma_addr[5]);
mac->npm_valid = B_TRUE;
return (B_FALSE);
}
static int
nic_port_datalink_props(topo_mod_t *mod, tnode_t *port, dladm_handle_t handle,
datalink_id_t linkid)
{
int err;
dladm_status_t status;
uint64_t ifspeed;
link_duplex_t duplex;
link_state_t state;
const char *duplex_str, *state_str, *media_str;
datalink_class_t dlclass;
uint32_t media;
char dlname[MAXLINKNAMELEN * 2];
char dlerr[DLADM_STRSIZE], dlmedia[DLADM_PROP_VAL_MAX], *valptr[1];
uint_t valcnt = 1;
nic_port_mac_t mac;
status = dladm_datalink_id2info(handle, linkid, NULL, &dlclass, &media,
dlname, sizeof (dlname));
if (status != DLADM_STATUS_OK) {
topo_mod_dprintf(mod, "failed to get link info: %s\n",
dladm_status2str(status, dlerr));
return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
}
if (dlclass != DATALINK_CLASS_PHYS) {
return (0);
}
status = dladm_get_single_mac_stat(handle, linkid, "ifspeed",
KSTAT_DATA_UINT64, &ifspeed);
if (status != DLADM_STATUS_OK) {
topo_mod_dprintf(mod, "failed to get ifspeed: %s\n",
dladm_status2str(status, dlerr));
return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
}
status = dladm_get_single_mac_stat(handle, linkid, "link_duplex",
KSTAT_DATA_UINT32, &duplex);
if (status != DLADM_STATUS_OK) {
topo_mod_dprintf(mod, "failed to get link_duplex: %s\n",
dladm_status2str(status, dlerr));
return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
}
switch (duplex) {
case LINK_DUPLEX_HALF:
duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_HALF;
break;
case LINK_DUPLEX_FULL:
duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_FULL;
break;
default:
duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_UNKNOWN;
break;
}
status = dladm_get_single_mac_stat(handle, linkid, "link_state",
KSTAT_DATA_UINT32, &state);
if (status != DLADM_STATUS_OK) {
topo_mod_dprintf(mod, "failed to get link_duplex: %s\n",
dladm_status2str(status, dlerr));
return (topo_mod_seterrno(mod, status));
}
switch (state) {
case LINK_STATE_UP:
state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_UP;
break;
case LINK_STATE_DOWN:
state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_DOWN;
break;
default:
state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_UNKNOWN;
break;
}
if (state == LINK_STATE_DOWN || state == LINK_STATE_UNKNOWN) {
duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_UNKNOWN;
}
media_str = NULL;
if (state == LINK_STATE_UP) {
valptr[0] = dlmedia;
if (dladm_get_linkprop(handle, linkid, DLADM_PROP_VAL_CURRENT,
"media", valptr, &valcnt) == DLADM_STATUS_OK) {
media_str = dlmedia;
}
}
mac.npm_mac[0] = '\0';
mac.npm_valid = B_FALSE;
mac.npm_mod = mod;
if (media == DL_ETHER) {
(void) dladm_walk_macaddr(handle, linkid, &mac,
nic_port_datalink_mac_cb);
}
if (topo_pgroup_create(port, &datalink_pgroup, &err) != 0) {
topo_mod_dprintf(mod, "falied to create property group %s: "
"%s\n", TOPO_PGROUP_DATALINK, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
if (topo_prop_set_uint64(port, TOPO_PGROUP_DATALINK,
TOPO_PGROUP_DATALINK_LINK_SPEED, TOPO_PROP_IMMUTABLE, ifspeed,
&err) != 0) {
topo_mod_dprintf(mod, "failed to set %s property: %s\n",
TOPO_PGROUP_DATALINK_LINK_SPEED, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
TOPO_PGROUP_DATALINK_LINK_DUPLEX, TOPO_PROP_IMMUTABLE, duplex_str,
&err) != 0) {
topo_mod_dprintf(mod, "failed to set %s property: %s\n",
TOPO_PGROUP_DATALINK_LINK_DUPLEX, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
TOPO_PGROUP_DATALINK_LINK_STATUS, TOPO_PROP_IMMUTABLE, state_str,
&err) != 0) {
topo_mod_dprintf(mod, "failed to set %s property: %s\n",
TOPO_PGROUP_DATALINK_LINK_STATUS, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
TOPO_PGROUP_DATALINK_LINK_NAME, TOPO_PROP_IMMUTABLE, dlname,
&err) != 0) {
topo_mod_dprintf(mod, "failed to set %s propery: %s\n",
TOPO_PGROUP_DATALINK_LINK_NAME, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
if (media_str != NULL && topo_prop_set_string(port,
TOPO_PGROUP_DATALINK, TOPO_PGROUP_DATALINK_LINK_MEDIA,
TOPO_PROP_IMMUTABLE, media_str, &err) != 0) {
topo_mod_dprintf(mod, "failed to set %s propery: %s\n",
TOPO_PGROUP_DATALINK_LINK_MEDIA, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
if (mac.npm_valid) {
if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
TOPO_PGROUP_DATALINK_PMAC, TOPO_PROP_IMMUTABLE,
mac.npm_mac, &err) != 0) {
topo_mod_dprintf(mod, "failed to set %s propery: %s\n",
TOPO_PGROUP_DATALINK_PMAC, topo_strerror(err));
return (topo_mod_seterrno(mod, err));
}
}
return (0);
}
static int
nic_create_transceiver(topo_mod_t *mod, tnode_t *pnode, dladm_handle_t handle,
datalink_id_t linkid, topo_instance_t inst, uint_t tranid,
nic_port_type_t port_type)
{
int ret;
tnode_t *port;
dld_ioc_gettran_t dgt;
dld_ioc_tranio_t dti;
uint8_t buf[256];
char ouibuf[16];
char *vendor = NULL, *part = NULL, *rev = NULL, *serial = NULL;
nvlist_t *nvl = NULL;
switch (port_type) {
case NIC_PORT_UNKNOWN:
ret = port_create_unknown(mod, pnode, inst, &port);
break;
case NIC_PORT_SFF:
ret = port_create_sff(mod, pnode, inst, &port);
break;
default:
return (-1);
}
if ((ret = nic_port_datalink_props(mod, port, handle, linkid)) != 0)
return (ret);
if (port_type != NIC_PORT_SFF)
return (0);
bzero(&dgt, sizeof (dgt));
dgt.dgt_linkid = linkid;
dgt.dgt_tran_id = tranid;
if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) {
if (errno == ENOTSUP)
return (0);
return (-1);
}
if (dgt.dgt_present == 0)
return (0);
bzero(&dti, sizeof (dti));
dti.dti_linkid = linkid;
dti.dti_tran_id = tranid;
dti.dti_page = 0xa0;
dti.dti_nbytes = sizeof (buf);
dti.dti_buf = (uintptr_t)buf;
if (ioctl(dladm_dld_fd(handle), DLDIOC_READTRAN, &dti) == 0) {
uchar_t *oui;
uint_t nbyte;
if (libsff_parse(buf, dti.dti_nbytes, dti.dti_page,
&nvl) == 0) {
if ((ret = nvlist_lookup_string(nvl, LIBSFF_KEY_VENDOR,
&vendor)) != 0 && nvlist_lookup_byte_array(nvl,
LIBSFF_KEY_OUI, &oui, &nbyte) == 0 && nbyte == 3) {
if (snprintf(ouibuf, sizeof (ouibuf),
"%02x:%02x:%02x", oui[0], oui[1], oui[2]) <
sizeof (ouibuf)) {
vendor = ouibuf;
}
} else if (ret != 0) {
vendor = NULL;
}
if (nvlist_lookup_string(nvl, LIBSFF_KEY_PART,
&part) != 0) {
part = NULL;
}
if (nvlist_lookup_string(nvl, LIBSFF_KEY_REVISION,
&rev) != 0) {
rev = NULL;
}
if (nvlist_lookup_string(nvl, LIBSFF_KEY_SERIAL,
&serial) != 0) {
serial = NULL;
}
}
}
if (transceiver_range_create(mod, port, 0, 0) != 0) {
nvlist_free(nvl);
return (-1);
}
if (transceiver_create_sff(mod, port, 0, dgt.dgt_usable, vendor, part,
rev, serial, NULL) != 0) {
nvlist_free(nvl);
return (-1);
}
nvlist_free(nvl);
return (0);
}
static boolean_t
nic_enum_link_ntrans(dladm_handle_t handle, datalink_id_t linkid, uint_t *ntran,
nic_port_type_t *pt)
{
dld_ioc_gettran_t dgt;
memset(&dgt, 0, sizeof (dgt));
dgt.dgt_linkid = linkid;
dgt.dgt_tran_id = DLDIOC_GETTRAN_GETNTRAN;
if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) {
if (errno != ENOTSUP) {
return (B_FALSE);
}
*pt = NIC_PORT_UNKNOWN;
*ntran = 1;
} else {
*ntran = dgt.dgt_tran_id;
*pt = NIC_PORT_SFF;
}
return (B_TRUE);
}
static boolean_t
nic_enum_devinfo_linkid(dladm_handle_t handle, di_node_t din,
datalink_id_t *linkidp)
{
char dname[MAXNAMELEN];
if (snprintf(dname, sizeof (dname), "%s%d", di_driver_name(din),
di_instance(din)) >= sizeof (dname)) {
return (B_FALSE);
}
if (dladm_dev2linkid(handle, dname, linkidp) != DLADM_STATUS_OK)
return (B_FALSE);
return (B_TRUE);
}
static int
nic_enum_nexus(topo_mod_t *mod, tnode_t *pnode, dladm_handle_t handle,
di_node_t din)
{
uint_t total_ports = 0;
nic_port_type_t pt;
di_node_t child;
for (child = di_child_node(din); child != DI_NODE_NIL;
child = di_sibling_node(child)) {
datalink_id_t linkid;
uint_t ntrans;
if (!nic_enum_devinfo_linkid(handle, child, &linkid))
return (-1);
if (!nic_enum_link_ntrans(handle, linkid, &ntrans, &pt))
return (-1);
total_ports += ntrans;
}
if (total_ports == 0)
return (0);
if (port_range_create(mod, pnode, 0, total_ports - 1) != 0)
return (-1);
total_ports = 0;
for (child = di_child_node(din); child != DI_NODE_NIL;
child = di_sibling_node(child)) {
datalink_id_t linkid;
uint_t i, ntrans;
if (!nic_enum_devinfo_linkid(handle, child, &linkid))
return (-1);
if (!nic_enum_link_ntrans(handle, linkid, &ntrans, &pt))
return (-1);
for (i = 0; i < ntrans; i++) {
if (nic_create_transceiver(mod, pnode, handle, linkid,
total_ports + i, i, pt) != 0) {
return (-1);
}
}
total_ports += ntrans;
}
return (0);
}
static int
nic_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
topo_instance_t min, topo_instance_t max, void *modarg, void *data)
{
di_node_t din = data;
datalink_id_t linkid;
dladm_handle_t handle;
uint_t ntrans, i;
nic_port_type_t pt;
const char *drv;
if (strcmp(name, NIC) != 0) {
topo_mod_dprintf(mod, "nic_enum: asked to enumerate unknown "
"component: %s\n", name);
return (-1);
}
if (din == NULL) {
topo_mod_dprintf(mod, "nic_enum: missing data argument\n");
return (-1);
}
if ((handle = topo_mod_getspecific(mod)) == NULL) {
topo_mod_dprintf(mod, "nic_enum: failed to get nic module "
"specific data\n");
return (-1);
}
if ((drv = di_driver_name(din)) == NULL) {
return (0);
}
for (i = 0; nic_nexuses[i] != NULL; i++) {
if (strcmp(drv, nic_nexuses[i]) == 0) {
return (nic_enum_nexus(mod, pnode, handle, din));
}
}
if (!nic_enum_devinfo_linkid(handle, din, &linkid))
return (-1);
if (!nic_enum_link_ntrans(handle, linkid, &ntrans, &pt))
return (-1);
if (ntrans == 0)
return (0);
if (port_range_create(mod, pnode, 0, ntrans - 1) != 0)
return (-1);
for (i = 0; i < ntrans; i++) {
if (nic_create_transceiver(mod, pnode, handle, linkid, i, i,
pt) != 0) {
return (-1);
}
}
return (0);
}
static const topo_modops_t nic_ops = {
nic_enum, NULL
};
static topo_modinfo_t nic_mod = {
NIC, FM_FMRI_SCHEME_HC, NIC_VERSION, &nic_ops
};
int
_topo_init(topo_mod_t *mod, topo_version_t version)
{
dladm_handle_t handle;
if (getenv("TOPONICDEBUG") != NULL)
topo_mod_setdebug(mod);
topo_mod_dprintf(mod, "_mod_init: "
"initializing %s enumerator\n", NIC);
if (version != NIC_VERSION) {
return (-1);
}
if (dladm_open(&handle) != 0)
return (-1);
if (topo_mod_register(mod, &nic_mod, TOPO_VERSION) != 0) {
dladm_close(handle);
return (-1);
}
topo_mod_setspecific(mod, handle);
return (0);
}
void
_topo_fini(topo_mod_t *mod)
{
dladm_handle_t handle;
if ((handle = topo_mod_getspecific(mod)) == NULL)
return;
dladm_close(handle);
topo_mod_setspecific(mod, NULL);
}