mii-tool.c 15.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
/*

    mii-tool: monitor and control the MII for a network interface

    Usage:

	mii-tool [-VvRrw] [-A media,... | -F media] [interface ...]

    This program is based on Donald Becker's "mii-diag" program, which
    is more capable and verbose than this tool, but also somewhat
    harder to use.

    Copyright (C) 2000 David A. Hinds -- dhinds@pcmcia.sourceforge.org

    mii-diag is written/copyright 1997-2000 by Donald Becker
        <becker@scyld.com>

    This program is free software; you can redistribute it
    and/or modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation.

    Donald Becker may be reached as becker@scyld.com, or C/O
    Scyld Computing Corporation, 410 Severn Av., Suite 210,
    Annapolis, MD 21403

    References
	http://www.scyld.com/diag/mii-status.html
	http://www.scyld.com/expert/NWay.html
	http://www.national.com/pf/DP/DP83840.html
*/


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <time.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
47 48
#include <linux/sockios.h>

49 50
#include <linux/mii.h>
#include <linux/sockios.h>
51
#include "version.h"
52
#include "net-support.h"
53
#include "util.h"
54

55 56
static char *Release = RELEASE, *Signature = "David Hinds based on Donald Becker's mii-diag";

57
#define MAX_ETH		8		/* Maximum # of interfaces */
58
#define LPA_ABILITY_MASK	0x07e0
59 60

/* Table of known MII's */
61
static const struct {
62 63 64 65 66 67 68 69
    u_short	id1, id2;
    char	*name;
} mii_id[] = {
    { 0x0022, 0x5610, "AdHoc AH101LF" },
    { 0x0022, 0x5520, "Altimata AC101LF" },
    { 0x0000, 0x6b90, "AMD 79C901A HomePNA" },
    { 0x0000, 0x6b70, "AMD 79C901A 10baseT" },
    { 0x0181, 0xb800, "Davicom DM9101" },
70
    { 0x0043, 0x7410, "Enable EL40-331" },
71
    { 0x0243, 0x0c50, "ICPlus IP101A" },
72 73 74 75 76
    { 0x0015, 0xf410, "ICS 1889" },
    { 0x0015, 0xf420, "ICS 1890" },
    { 0x0015, 0xf430, "ICS 1892" },
    { 0x02a8, 0x0150, "Intel 82555" },
    { 0x7810, 0x0000, "Level One LXT970/971" },
77 78
    { 0x0022, 0x1510, "Micrel KSZ8041" },
    { 0x0022, 0x1610, "Micrel KSZ9021" },
79
    { 0x2000, 0x5c00, "National DP83840A" },
80
    { 0x2000, 0x5c70, "National DP83865" },
81 82
    { 0x0181, 0x4410, "Quality QS6612" },
    { 0x0282, 0x1c50, "SMSC 83C180" },
83 84
    { 0x0203, 0x8460, "STMicroelectronics ST802RT" },
    { 0x1c04, 0x0010, "STMicroelectronics STE100P" },
85
    { 0x0300, 0xe540, "TDK 78Q2120" },
86 87 88
    { 0x0141, 0x0c20, "Yukon 88E1011" },
    { 0x0141, 0x0cc0, "Yukon-EC 88E1111" },
    { 0x0141, 0x0c90, "Yukon-2 88E1112" },
89 90 91 92 93 94 95
};
#define NMII (sizeof(mii_id)/sizeof(mii_id[0]))

/*--------------------------------------------------------------------*/

struct option longopts[] = {
 /* { name  has_arg  *flag  val } */
96 97
    {"advertise",	1, 0, 'A'},	/* Advertise only specified media. */
    {"force",		1, 0, 'F'},	/* Force specified media technology. */
98
    {"phy",		1, 0, 'p'},	/* Set PHY (MII address) to report. */
99
    {"log",		0, 0, 'l'},	/* With --watch, write events to syslog. */
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
    {"restart",		0, 0, 'r'},	/* Restart link negotiation */
    {"reset",		0, 0, 'R'},	/* Reset the transceiver. */
    {"verbose", 	0, 0, 'v'},	/* Report each action taken.  */
    {"version", 	0, 0, 'V'},	/* Emit version information.  */
    {"watch", 		0, 0, 'w'},	/* Constantly monitor the port.  */
    {"help", 		0, 0, '?'},	/* Give help */
    { 0, 0, 0, 0 }
};

static unsigned int
    verbose = 0,
    opt_version = 0,
    opt_restart = 0,
    opt_reset = 0,
    opt_log = 0,
    opt_watch = 0;
static int nway_advertise = 0;
static int fixed_speed = 0;
static int override_phy = -1;

static int skfd = -1;		/* AF_INET socket for ioctl() calls. */
static struct ifreq ifr;

/*--------------------------------------------------------------------*/

static int mdio_read(int skfd, int location)
{
127
    struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data;
128 129 130 131 132 133 134 135 136 137 138
    mii->reg_num = location;
    if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) {
	fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,
		strerror(errno));
	return -1;
    }
    return mii->val_out;
}

static void mdio_write(int skfd, int location, int value)
{
139
    struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data;
140 141 142 143 144 145 146 147 148 149 150 151
    mii->reg_num = location;
    mii->val_in = value;
    if (ioctl(skfd, SIOCSMIIREG, &ifr) < 0) {
	fprintf(stderr, "SIOCSMIIREG on %s failed: %s\n", ifr.ifr_name,
		strerror(errno));
    }
}

/*--------------------------------------------------------------------*/

const struct {
    char	*name;
152
    u_short	value[2];
153 154
} media[] = {
    /* The order through 100baseT4 matches bits in the BMSR */
155 156 157 158 159 160 161 162 163 164 165
    { "10baseT-HD",	{LPA_10HALF} },
    { "10baseT-FD",	{LPA_10FULL} },
    { "100baseTx-HD",	{LPA_100HALF} },
    { "100baseTx-FD",	{LPA_100FULL} },
    { "100baseT4",	{LPA_100BASE4} },
    { "100baseTx",	{LPA_100FULL | LPA_100HALF} },
    { "10baseT",	{LPA_10FULL | LPA_10HALF} },

    { "1000baseT-HD",	{0, ADVERTISE_1000HALF} },
    { "1000baseT-FD",	{0, ADVERTISE_1000FULL} },
    { "1000baseT",	{0, ADVERTISE_1000HALF|ADVERTISE_1000FULL} },
166 167
};
#define NMEDIA (sizeof(media)/sizeof(media[0]))
168

169
/* Parse an argument list of media types */
170
static int parse_media(char *arg, unsigned *bmcr2)
171 172 173 174 175
{
    int mask, i;
    char *s;
    mask = strtoul(arg, &s, 16);
    if ((*arg != '\0') && (*s == '\0')) {
176 177
	if ((mask & LPA_ABILITY_MASK) &&
	    !(mask & ~LPA_ABILITY_MASK)) {
178 179 180
		*bmcr2 = 0;
		return mask;
	}
181
	goto failed;
182 183 184 185 186
    }
    mask = 0;
    *bmcr2 = 0;
    s = strtok(arg, ", ");
    do {
187
	    for (i = 0; i < NMEDIA; i++)
188
		if (s && strcasecmp(media[i].name, s) == 0) break;
189
	    if (i == NMEDIA) goto failed;
190 191 192 193
	    mask |= media[i].value[0];
	    *bmcr2 |= media[i].value[1];
    } while ((s = strtok(NULL, ", ")) != NULL);

194 195 196 197 198 199 200 201
    return mask;
failed:
    fprintf(stderr, "Invalid media specification '%s'.\n", arg);
    return -1;
}

/*--------------------------------------------------------------------*/

202
static const char *media_list(unsigned mask, unsigned mask2, int best)
203 204 205 206
{
    static char buf[100];
    int i;
    *buf = '\0';
207

208 209
    if (mask & BMCR_SPEED1000) {
	if (mask2 & ADVERTISE_1000HALF) {
210 211 212 213
	    strcat(buf, " ");
	    strcat(buf, "1000baseT-HD");
	    if (best) goto out;
	}
214
	if (mask2 & ADVERTISE_1000FULL) {
215 216 217 218 219
	    strcat(buf, " ");
	    strcat(buf, "1000baseT-FD");
	    if (best) goto out;
	}
    }
220

221 222 223 224 225 226 227 228
    mask >>= 5;
    for (i = 4; i >= 0; i--) {
	if (mask & (1<<i)) {
	    strcat(buf, " ");
	    strcat(buf, media[i].name);
	    if (best) break;
	}
    }
229
 out:
230 231 232 233 234 235 236
    if (mask & (1<<5))
	strcat(buf, " flow-control");
    return buf;
}

int show_basic_mii(int sock, int phy_id)
{
237
    char buf[200];
238
    int i, mii_val[32];
239
    unsigned bmcr, bmsr, advert, lkpar, bmcr2, lpa2;
240 241 242 243

    /* Some bits in the BMSR are latched, but we can't rely on being
       the only reader, so only the current values are meaningful */
    mdio_read(sock, MII_BMSR);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
    for (i = 0; i < ((verbose > 1) ? 32 : (MII_STAT1000+1)); i++)
	switch (i & 0x1F) {
	    case MII_BMCR:
	    case MII_BMSR:
	    case MII_PHYSID1:
	    case MII_PHYSID2:
	    case MII_ADVERTISE:
	    case MII_LPA:
	    case MII_EXPANSION:
	    case MII_CTRL1000:
	    case MII_STAT1000:
	    case MII_ESTATUS:
	    case MII_DCOUNTER:
	    case MII_FCSCOUNTER:
	    case MII_NWAYTEST:
	    case MII_RERRCOUNTER:
	    case MII_SREVISION:
	    case MII_RESV1:
	    case MII_LBRERROR:
	    case MII_PHYADDR:
	    case MII_RESV2:
	    case MII_TPISTATUS:
	    case MII_NCONFIG:
		mii_val[i] = mdio_read(sock, i);
		break;
	    default:
270 271 272 273
		if (verbose > 2)
		    mii_val[i] = mdio_read(sock, i);
		else
		    mii_val[i] = 0;
274 275
		break;
        }
276

277
    if (mii_val[MII_BMCR] == 0xffff  || mii_val[MII_BMSR] == 0x0000) {
278 279 280 281 282 283
	fprintf(stderr, "  No MII transceiver present!.\n");
	return -1;
    }

    /* Descriptive rename. */
    bmcr = mii_val[MII_BMCR]; bmsr = mii_val[MII_BMSR];
284
    advert = mii_val[MII_ADVERTISE]; lkpar = mii_val[MII_LPA];
285
    bmcr2 = mii_val[MII_CTRL1000]; lpa2 = mii_val[MII_STAT1000];
286 287

    sprintf(buf, "%s: ", ifr.ifr_name);
288 289
    if (bmcr & BMCR_ANENABLE) {
	if (bmsr & BMSR_ANEGCOMPLETE) {
290
	    if (advert & lkpar) {
291
		strcat(buf, (lkpar & LPA_LPACK) ?
292
		       "negotiated" : "no autonegotiation,");
293
		strcat(buf, media_list(advert & lkpar, bmcr2 & lpa2>>2, 1));
294 295 296 297
		strcat(buf, ", ");
	    } else {
		strcat(buf, "autonegotiation failed, ");
	    }
298
	} else if (bmcr & BMCR_ANRESTART) {
299 300 301 302
	    strcat(buf, "autonegotiation restarted, ");
	}
    } else {
	sprintf(buf+strlen(buf), "%s Mbit, %s duplex, ",
303
		((bmcr2 & (ADVERTISE_1000HALF | ADVERTISE_1000FULL)) & lpa2 >> 2)
304
		? "1000"
305 306
		: (bmcr & BMCR_SPEED100) ? "100" : "10",
		(bmcr & BMCR_FULLDPLX) ? "full" : "half");
307
    }
308
    strcat(buf, (bmsr & BMSR_LSTATUS) ? "link ok" : "no link");
309 310 311

    if (opt_watch) {
	if (opt_log) {
312
	    syslog(LOG_INFO, "%s", buf);
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
	} else {
	    char s[20];
	    time_t t = time(NULL);
	    strftime(s, sizeof(s), "%T", localtime(&t));
	    printf("%s %s\n", s, buf);
	}
    } else {
	printf("%s\n", buf);
    }

    if (verbose > 1) {
	printf("  registers for MII PHY %d: ", phy_id);
	for (i = 0; i < 32; i++)
	    printf("%s %4.4x", ((i % 8) ? "" : "\n   "), mii_val[i]);
	printf("\n");
    }

    if (verbose) {
	printf("  product info: ");
	for (i = 0; i < NMII; i++)
	    if ((mii_id[i].id1 == mii_val[2]) &&
		(mii_id[i].id2 == (mii_val[3] & 0xfff0)))
		break;
	if (i < NMII)
	    printf("%s rev %d\n", mii_id[i].name, mii_val[3]&0x0f);
	else
	    printf("vendor %02x:%02x:%02x, model %d rev %d\n",
		   mii_val[2]>>10, (mii_val[2]>>2)&0xff,
		   ((mii_val[2]<<6)|(mii_val[3]>>10))&0xff,
		   (mii_val[3]>>4)&0x3f, mii_val[3]&0x0f);
	printf("  basic mode:   ");
344
	if (bmcr & BMCR_RESET)
345
	    printf("software reset, ");
346
	if (bmcr & BMCR_LOOPBACK)
347
	    printf("loopback, ");
348
	if (bmcr & BMCR_ISOLATE)
349
	    printf("isolate, ");
350
	if (bmcr & BMCR_CTST)
351
	    printf("collision test, ");
352
	if (bmcr & BMCR_ANENABLE) {
353 354 355
	    printf("autonegotiation enabled\n");
	} else {
	    printf("%s Mbit, %s duplex\n",
356 357
		   (bmcr & BMCR_SPEED100) ? "100" : "10",
		   (bmcr & BMCR_FULLDPLX) ? "full" : "half");
358 359
	}
	printf("  basic status: ");
360
	if (bmsr & BMSR_ANEGCOMPLETE)
361
	    printf("autonegotiation complete, ");
362
	else if (bmcr & BMCR_ANRESTART)
363
	    printf("autonegotiation restarted, ");
364
	if (bmsr & BMSR_RFAULT)
365
	    printf("remote fault, ");
366
	printf((bmsr & BMSR_LSTATUS) ? "link ok" : "no link");
367
	printf("\n  capabilities:%s", media_list(bmsr >> 6, bmcr2, 0));
368
	printf("\n  advertising: %s", media_list(advert, bmcr2, 0));
369
	if (lkpar & LPA_ABILITY_MASK)
370
	    printf("\n  link partner:%s", media_list(lkpar, lpa2 >> 2, 0));
371 372
	printf("\n");
    }
373
    fflush(stdout);
374 375 376 377 378 379 380
    return 0;
}

/*--------------------------------------------------------------------*/

static int do_one_xcvr(int skfd, char *ifname, int maybe)
{
381
    struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data;
382 383

    /* Get the vitals from the interface. */
384
    safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
385 386 387 388 389 390 391 392 393 394 395 396 397 398
    if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) {
	if (!maybe || (errno != ENODEV))
	    fprintf(stderr, "SIOCGMIIPHY on '%s' failed: %s\n",
		    ifname, strerror(errno));
	return 1;
    }

    if (override_phy >= 0) {
	printf("using the specified MII index %d.\n", override_phy);
	mii->phy_id = override_phy;
    }

    if (opt_reset) {
	printf("resetting the transceiver...\n");
399
	mdio_write(skfd, MII_BMCR, BMCR_RESET);
400
    }
401
    if (nway_advertise > 0) {
402
	mdio_write(skfd, MII_ADVERTISE, nway_advertise | 1);
403 404 405 406 407
	opt_restart = 1;
    }
    if (opt_restart) {
	printf("restarting autonegotiation...\n");
	mdio_write(skfd, MII_BMCR, 0x0000);
408
	mdio_write(skfd, MII_BMCR, BMCR_ANENABLE|BMCR_ANRESTART);
409 410 411
    }
    if (fixed_speed) {
	int bmcr = 0;
412 413 414 415
	if (fixed_speed & (LPA_100FULL|LPA_100HALF))
	    bmcr |= BMCR_SPEED100;
	if (fixed_speed & (LPA_100FULL|LPA_10FULL))
	    bmcr |= BMCR_FULLDPLX;
416 417 418 419 420 421 422 423 424 425 426 427 428
	mdio_write(skfd, MII_BMCR, bmcr);
    }

    if (!opt_restart && !opt_reset && !fixed_speed && !nway_advertise)
	show_basic_mii(skfd, mii->phy_id);

    return 0;
}

/*--------------------------------------------------------------------*/

static void watch_one_xcvr(int skfd, char *ifname, int index)
{
429
    struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data;
430 431 432 433
    static int status[MAX_ETH] = { 0, /* ... */ };
    int now;

    /* Get the vitals from the interface. */
434
    safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
435 436 437 438 439 440
    if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) {
	if (errno != ENODEV)
	    fprintf(stderr, "SIOCGMIIPHY on '%s' failed: %s\n",
		    ifname, strerror(errno));
	return;
    }
441 442 443
    if (override_phy >= 0) {
        mii->phy_id = override_phy;
    }
444 445 446 447 448 449 450 451 452
    now = (mdio_read(skfd, MII_BMCR) |
	   (mdio_read(skfd, MII_BMSR) << 16));
    if (status[index] && (status[index] != now))
	show_basic_mii(skfd, mii->phy_id);
    status[index] = now;
}

/*--------------------------------------------------------------------*/

453
const char *usage =
454
"usage: %s [-VvRrwl] [-A media,... | -F media] [-p addr] <interface ...>\n"
455 456 457 458 459 460 461 462
"       -V, --version               display version information\n"
"       -v, --verbose               more verbose output\n"
"       -R, --reset                 reset MII to poweron state\n"
"       -r, --restart               restart autonegotiation\n"
"       -w, --watch                 monitor for link status changes\n"
"       -l, --log                   with -w, write events to syslog\n"
"       -A, --advertise=media,...   advertise only specified media\n"
"       -F, --force=media           force specified media technology\n"
463
"       -p, --phy=addr              set PHY (MII address) to report\n"
464 465 466 467 468 469 470 471
"media: 1000baseTx-HD, 1000baseTx-FD,\n"
"       100baseT4, 100baseTx-FD, 100baseTx-HD,\n"
"       10baseT-FD, 10baseT-HD,\n"
"       (to advertise both HD and FD) 1000baseTx, 100baseTx, 10baseT\n";


static void version(void)
{
472
    fprintf(stderr, "%s\n%s\n", Release, Signature);
473
    exit(E_VERSION);
474 475
}

476 477 478 479

int main(int argc, char **argv)
{
    int i, c, ret, errflag = 0;
480
    unsigned ctrl1000 = 0;
481

482 483
    while ((c = getopt_long(argc, argv, "A:F:p:lrRvVw?", longopts, 0)) != EOF)
	switch (c) {
484 485
	case 'A': nway_advertise = parse_media(optarg, &ctrl1000); break;
	case 'F': fixed_speed = parse_media(optarg, &ctrl1000); break;
486 487 488 489 490 491 492 493 494 495 496
	case 'p': override_phy = atoi(optarg); break;
	case 'r': opt_restart++;	break;
	case 'R': opt_reset++;		break;
	case 'v': verbose++;		break;
	case 'V': opt_version++;	break;
	case 'w': opt_watch++;		break;
	case 'l': opt_log++;		break;
	case '?': errflag++;
	}
    /* Check for a few inappropriate option combinations */
    if (opt_watch) verbose = 0;
497 498 499 500

    if ((nway_advertise < 0) || (fixed_speed < 0))
    	return 2;

501 502 503 504 505 506 507
    if (errflag || (fixed_speed & (fixed_speed-1)) ||
	(fixed_speed && (opt_restart || nway_advertise))) {
	fprintf(stderr, usage, argv[0]);
	return 2;
    }

    if (opt_version)
508
	version();
509 510 511 512 513 514

    /* Open a basic socket. */
    if ((skfd = socket(AF_INET, SOCK_DGRAM,0)) < 0) {
	perror("socket");
	exit(-1);
    }
515

516
    if (verbose > 1)
517
    	printf("Using SIOCGMIIPHY=0x%x\n", SIOCGMIIPHY);
518 519 520

    /* No remaining args means show all interfaces. */
    if (optind == argc) {
521 522 523 524
	fprintf(stderr, "No interface specified\n");
	fprintf(stderr, usage, argv[0]);
	close(skfd);
	return 2;
525 526 527 528 529 530 531 532 533 534
    } else {
	ret = 0;
	for (i = optind; i < argc; i++) {
	    ret |= do_one_xcvr(skfd, argv[i], 0);
	}
    }

    if (opt_watch && (ret == 0)) {
	while (1) {
	    sleep(1);
535
	    for (i = optind; i < argc; i++)
536 537 538 539 540 541 542
		    watch_one_xcvr(skfd, argv[i], i-optind);
	}
    }

    close(skfd);
    return ret;
}