#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/hexdump.h>
#include <fcntl.h>
#include <strings.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#include <stdarg.h>
#include <libgen.h>
#include <libdladm.h>
#include <libdllink.h>
#include <sys/dld.h>
#include <sys/dld_ioc.h>
#include <sys/dls_mgmt.h>
#include <libsff.h>
#define DLTRAN_KIND_LEN 64
static dladm_handle_t dltran_hdl;
static char dltran_dlerrmsg[DLADM_STRSIZE];
static const char *dltran_progname;
static boolean_t dltran_verbose;
static boolean_t dltran_hex;
static boolean_t dltran_write;
static int dltran_outfd;
static int dltran_errors;
static int
dltran_dump_page_cb(void *arg, uint64_t addr, const char *buf,
size_t len __unused)
{
uint_t page = (uint_t)(uintptr_t)arg;
int ret;
if (addr == UINT64_MAX) {
ret = printf("page %s\n", buf);
} else {
ret = printf("0x%02x %s\n", page, buf);
}
return (ret < 0 ? -1 : 0);
}
static void
dltran_dump_page(uint8_t *buf, size_t nbytes, uint_t page)
{
static boolean_t first = B_TRUE;
hexdump_flag_t flags = HDF_DEFAULT;
if (first)
first = B_FALSE;
else
flags &= ~HDF_HEADER;
(void) hexdump(buf, nbytes, flags, dltran_dump_page_cb,
(void *)(uintptr_t)page);
}
static int
dltran_read_page(datalink_id_t link, uint_t tranid, uint_t page, uint8_t *bufp,
size_t *buflen)
{
dld_ioc_tranio_t dti;
bzero(bufp, *buflen);
bzero(&dti, sizeof (dti));
dti.dti_linkid = link;
dti.dti_tran_id = tranid;
dti.dti_page = page;
dti.dti_nbytes = *buflen;
dti.dti_off = 0;
dti.dti_buf = (uintptr_t)(void *)bufp;
if (ioctl(dladm_dld_fd(dltran_hdl), DLDIOC_READTRAN, &dti) != 0) {
(void) fprintf(stderr, "failed to read transceiver page "
"0x%2x: %s\n", page, strerror(errno));
return (1);
}
*buflen = dti.dti_nbytes;
return (0);
}
static boolean_t
dltran_is_8472(uint8_t *buf)
{
switch (buf[0]) {
case 0xc:
case 0xd:
case 0x11:
return (B_FALSE);
default:
break;
}
if (buf[94] == 0)
return (B_FALSE);
return (B_TRUE);
}
static void
dltran_hex_dump(datalink_id_t linkid, uint_t tranid)
{
uint8_t buf[256];
size_t buflen = sizeof (buf);
if (dltran_read_page(linkid, tranid, 0xa0, buf, &buflen) != 0) {
dltran_errors++;
return;
}
dltran_dump_page(buf, buflen, 0xa0);
if (!dltran_is_8472(buf)) {
return;
}
buflen = sizeof (buf);
if (dltran_read_page(linkid, tranid, 0xa2, buf, &buflen) != 0) {
dltran_errors++;
return;
}
dltran_dump_page(buf, buflen, 0xa2);
}
static void
dltran_write_page(datalink_id_t linkid, uint_t tranid)
{
uint8_t buf[256];
size_t buflen = sizeof (buf);
off_t off;
if (dltran_read_page(linkid, tranid, 0xa0, buf, &buflen) != 0) {
dltran_errors++;
return;
}
off = 0;
while (buflen > 0) {
ssize_t ret;
ret = write(dltran_outfd, buf + off, buflen);
if (ret == -1) {
(void) fprintf(stderr, "failed to write data "
"to output file: %s\n", strerror(errno));
dltran_errors++;
return;
}
off += ret;
buflen -= ret;
}
}
static void
dltran_verbose_dump(datalink_id_t linkid, uint_t tranid)
{
uint8_t buf[256];
size_t buflen = sizeof (buf);
int ret;
nvlist_t *nvl;
if (dltran_read_page(linkid, tranid, 0xa0, buf, &buflen) != 0) {
dltran_errors++;
return;
}
ret = libsff_parse(buf, buflen, 0xa0, &nvl);
if (ret == 0) {
dump_nvlist(nvl, 8);
nvlist_free(nvl);
} else {
fprintf(stderr, "failed to parse sfp data: %s\n",
strerror(ret));
dltran_errors++;
}
}
static int
dltran_dump_transceivers(dladm_handle_t hdl, datalink_id_t linkid, void *arg)
{
dladm_status_t status;
char name[MAXLINKNAMELEN];
dld_ioc_gettran_t gt;
uint_t count, i, tranid = UINT_MAX;
boolean_t tran_found = B_FALSE;
uint_t *tranidp = arg;
if (tranidp != NULL)
tranid = *tranidp;
if ((status = dladm_datalink_id2info(hdl, linkid, NULL, NULL, NULL,
name, sizeof (name))) != DLADM_STATUS_OK) {
(void) fprintf(stderr, "failed to get datalink name for link "
"%d: %s", linkid, dladm_status2str(status,
dltran_dlerrmsg));
dltran_errors++;
return (DLADM_WALK_CONTINUE);
}
bzero(>, sizeof (gt));
gt.dgt_linkid = linkid;
gt.dgt_tran_id = DLDIOC_GETTRAN_GETNTRAN;
if (ioctl(dladm_dld_fd(hdl), DLDIOC_GETTRAN, >) != 0) {
if (errno != ENOTSUP) {
(void) fprintf(stderr, "failed to get transceiver "
"count for device %s: %s\n",
name, strerror(errno));
dltran_errors++;
}
return (DLADM_WALK_CONTINUE);
}
count = gt.dgt_tran_id;
(void) printf("%s: discovered %d transceiver%s\n", name, count,
count > 1 ? "s" : "");
for (i = 0; i < count; i++) {
if (tranid != UINT_MAX && i != tranid)
continue;
if (tranid != UINT_MAX)
tran_found = B_TRUE;
bzero(>, sizeof (gt));
gt.dgt_linkid = linkid;
gt.dgt_tran_id = i;
if (ioctl(dladm_dld_fd(hdl), DLDIOC_GETTRAN, >) != 0) {
(void) fprintf(stderr, "failed to get tran info for "
"%s: %s\n", name, strerror(errno));
dltran_errors++;
return (DLADM_WALK_CONTINUE);
}
if (dltran_hex && !gt.dgt_present)
continue;
if (!dltran_hex && !dltran_write) {
(void) printf("\ttransceiver %d present: %s\n", i,
gt.dgt_present ? "yes" : "no");
if (!gt.dgt_present)
continue;
(void) printf("\ttransceiver %d usable: %s\n", i,
gt.dgt_usable ? "yes" : "no");
}
if (dltran_verbose) {
dltran_verbose_dump(linkid, i);
}
if (dltran_write) {
if (!gt.dgt_present) {
(void) fprintf(stderr, "warning: no "
"transceiver present in port %d, not "
"writing\n", i);
dltran_errors++;
continue;
}
dltran_write_page(linkid, i);
}
if (dltran_hex) {
printf("transceiver %d data:\n", i);
dltran_hex_dump(linkid, i);
}
}
if (tranid != UINT_MAX && !tran_found) {
dltran_errors++;
(void) fprintf(stderr, "failed to find transceiver %d on "
"link %s\n", tranid, name);
}
return (DLADM_WALK_CONTINUE);
}
static void
dltran_usage(const char *fmt, ...)
{
if (fmt != NULL) {
va_list ap;
(void) fprintf(stderr, "%s: ", dltran_progname);
va_start(ap, fmt);
(void) vfprintf(stderr, fmt, ap);
va_end(ap);
}
(void) fprintf(stderr, "Usage: %s [-x | -v | -w file] [tran]...\n"
"\n"
"\t-v display all transceiver information\n"
"\t-w write transceiver data page 0xa0 to file\n"
"\t-x dump raw hexadecimal for transceiver\n",
dltran_progname);
}
int
main(int argc, char *argv[])
{
int c;
dladm_status_t status;
const char *outfile = NULL;
uint_t count = 0;
dltran_progname = basename(argv[0]);
while ((c = getopt(argc, argv, ":xvw:")) != -1) {
switch (c) {
case 'v':
dltran_verbose = B_TRUE;
break;
case 'x':
dltran_hex = B_TRUE;
break;
case 'w':
dltran_write = B_TRUE;
outfile = optarg;
break;
case ':':
dltran_usage("option -%c requires an "
"operand\n", optopt);
return (2);
case '?':
default:
dltran_usage("unknown option: -%c\n", optopt);
return (2);
}
}
argc -= optind;
argv += optind;
if (dltran_verbose)
count++;
if (dltran_hex)
count++;
if (dltran_write)
count++;
if (count > 1) {
(void) fprintf(stderr, "only one of -v, -w, and -x may be "
"specified\n");
return (2);
}
if (dltran_write) {
if ((dltran_outfd = open(outfile, O_RDWR | O_TRUNC | O_CREAT,
0644)) < 0) {
(void) fprintf(stderr, "failed to open output file "
"%s: %s\n", outfile, strerror(errno));
return (1);
}
}
if ((status = dladm_open(&dltran_hdl)) != DLADM_STATUS_OK) {
(void) fprintf(stderr, "failed to open /dev/dld: %s\n",
dladm_status2str(status, dltran_dlerrmsg));
return (1);
}
if (argc == 0) {
(void) dladm_walk_datalink_id(dltran_dump_transceivers,
dltran_hdl, NULL, DATALINK_CLASS_PHYS,
DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
} else {
int i;
char *c;
for (i = 0; i < argc; i++) {
uint_t tran;
uint_t *tranidp = NULL;
datalink_id_t linkid;
if ((c = strrchr(argv[i], '/')) != NULL) {
unsigned long u;
char *eptr;
c++;
errno = 0;
u = strtoul(c, &eptr, 10);
if (errno != 0 || *eptr != '\0' ||
u >= UINT_MAX) {
(void) fprintf(stderr, "failed to "
"parse link/transceiver: %s\n",
argv[i]);
return (1);
}
c--;
*c = '\0';
tran = (uint_t)u;
tranidp = &tran;
}
if ((status = dladm_name2info(dltran_hdl, argv[i],
&linkid, NULL, NULL, NULL)) != DLADM_STATUS_OK) {
(void) fprintf(stderr, "failed to get link "
"id for link %s: %s\n", argv[i],
dladm_status2str(status, dltran_dlerrmsg));
return (1);
}
(void) dltran_dump_transceivers(dltran_hdl, linkid,
tranidp);
}
}
return (dltran_errors != 0 ? 1 : 0);
}