/* 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "net-support.h" #include "util.h" static char *Release = RELEASE, *Signature = "David Hinds based on Donald Becker's mii-diag"; #define MAX_ETH 8 /* Maximum # of interfaces */ #define LPA_ABILITY_MASK 0x07e0 /* Table of known MII's */ static const struct { 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" }, { 0x0043, 0x7410, "Enable EL40-331" }, { 0x0243, 0x0c50, "ICPlus IP101A" }, { 0x0015, 0xf410, "ICS 1889" }, { 0x0015, 0xf420, "ICS 1890" }, { 0x0015, 0xf430, "ICS 1892" }, { 0x02a8, 0x0150, "Intel 82555" }, { 0x7810, 0x0000, "Level One LXT970/971" }, { 0x0022, 0x1510, "Micrel KSZ8041" }, { 0x0022, 0x1610, "Micrel KSZ9021" }, { 0x2000, 0x5c00, "National DP83840A" }, { 0x2000, 0x5c70, "National DP83865" }, { 0x0181, 0x4410, "Quality QS6612" }, { 0x0282, 0x1c50, "SMSC 83C180" }, { 0x0203, 0x8460, "STMicroelectronics ST802RT" }, { 0x1c04, 0x0010, "STMicroelectronics STE100P" }, { 0x0300, 0xe540, "TDK 78Q2120" }, { 0x0141, 0x0c20, "Yukon 88E1011" }, { 0x0141, 0x0cc0, "Yukon-EC 88E1111" }, { 0x0141, 0x0c90, "Yukon-2 88E1112" }, }; #define NMII (sizeof(mii_id)/sizeof(mii_id[0])) /*--------------------------------------------------------------------*/ struct option longopts[] = { /* { name has_arg *flag val } */ {"advertise", 1, 0, 'A'}, /* Advertise only specified media. */ {"force", 1, 0, 'F'}, /* Force specified media technology. */ {"phy", 1, 0, 'p'}, /* Set PHY (MII address) to report. */ {"log", 0, 0, 'l'}, /* With --watch, write events to syslog. */ {"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) { struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data; 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) { struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data; 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; u_short value[2]; } media[] = { /* The order through 100baseT4 matches bits in the BMSR */ { "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} }, }; #define NMEDIA (sizeof(media)/sizeof(media[0])) /* Parse an argument list of media types */ static int parse_media(char *arg, unsigned *bmcr2) { int mask, i; char *s; mask = strtoul(arg, &s, 16); if ((*arg != '\0') && (*s == '\0')) { if ((mask & LPA_ABILITY_MASK) && !(mask & ~LPA_ABILITY_MASK)) { *bmcr2 = 0; return mask; } goto failed; } mask = 0; *bmcr2 = 0; s = strtok(arg, ", "); do { for (i = 0; i < NMEDIA; i++) if (s && strcasecmp(media[i].name, s) == 0) break; if (i == NMEDIA) goto failed; mask |= media[i].value[0]; *bmcr2 |= media[i].value[1]; } while ((s = strtok(NULL, ", ")) != NULL); return mask; failed: fprintf(stderr, "Invalid media specification '%s'.\n", arg); return -1; } /*--------------------------------------------------------------------*/ static const char *media_list(unsigned mask, unsigned mask2, int best) { static char buf[100]; int i; *buf = '\0'; if (mask & BMCR_SPEED1000) { if (mask2 & ADVERTISE_1000HALF) { strcat(buf, " "); strcat(buf, "1000baseT-HD"); if (best) goto out; } if (mask2 & ADVERTISE_1000FULL) { strcat(buf, " "); strcat(buf, "1000baseT-FD"); if (best) goto out; } } mask >>= 5; for (i = 4; i >= 0; i--) { if (mask & (1< 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: if (verbose > 2) mii_val[i] = mdio_read(sock, i); else mii_val[i] = 0; break; } if (mii_val[MII_BMCR] == 0xffff || mii_val[MII_BMSR] == 0x0000) { fprintf(stderr, " No MII transceiver present!.\n"); return -1; } /* Descriptive rename. */ bmcr = mii_val[MII_BMCR]; bmsr = mii_val[MII_BMSR]; advert = mii_val[MII_ADVERTISE]; lkpar = mii_val[MII_LPA]; bmcr2 = mii_val[MII_CTRL1000]; lpa2 = mii_val[MII_STAT1000]; sprintf(buf, "%s: ", ifr.ifr_name); if (bmcr & BMCR_ANENABLE) { if (bmsr & BMSR_ANEGCOMPLETE) { if (advert & lkpar) { strcat(buf, (lkpar & LPA_LPACK) ? "negotiated" : "no autonegotiation,"); strcat(buf, media_list(advert & lkpar, bmcr2 & lpa2>>2, 1)); strcat(buf, ", "); } else { strcat(buf, "autonegotiation failed, "); } } else if (bmcr & BMCR_ANRESTART) { strcat(buf, "autonegotiation restarted, "); } } else { sprintf(buf+strlen(buf), "%s Mbit, %s duplex, ", ((bmcr2 & (ADVERTISE_1000HALF | ADVERTISE_1000FULL)) & lpa2 >> 2) ? "1000" : (bmcr & BMCR_SPEED100) ? "100" : "10", (bmcr & BMCR_FULLDPLX) ? "full" : "half"); } strcat(buf, (bmsr & BMSR_LSTATUS) ? "link ok" : "no link"); if (opt_watch) { if (opt_log) { syslog(LOG_INFO, "%s", buf); } 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: "); if (bmcr & BMCR_RESET) printf("software reset, "); if (bmcr & BMCR_LOOPBACK) printf("loopback, "); if (bmcr & BMCR_ISOLATE) printf("isolate, "); if (bmcr & BMCR_CTST) printf("collision test, "); if (bmcr & BMCR_ANENABLE) { printf("autonegotiation enabled\n"); } else { printf("%s Mbit, %s duplex\n", (bmcr & BMCR_SPEED100) ? "100" : "10", (bmcr & BMCR_FULLDPLX) ? "full" : "half"); } printf(" basic status: "); if (bmsr & BMSR_ANEGCOMPLETE) printf("autonegotiation complete, "); else if (bmcr & BMCR_ANRESTART) printf("autonegotiation restarted, "); if (bmsr & BMSR_RFAULT) printf("remote fault, "); printf((bmsr & BMSR_LSTATUS) ? "link ok" : "no link"); printf("\n capabilities:%s", media_list(bmsr >> 6, bmcr2, 0)); printf("\n advertising: %s", media_list(advert, bmcr2, 0)); if (lkpar & LPA_ABILITY_MASK) printf("\n link partner:%s", media_list(lkpar, lpa2 >> 2, 0)); printf("\n"); } fflush(stdout); return 0; } /*--------------------------------------------------------------------*/ static int do_one_xcvr(int skfd, char *ifname, int maybe) { struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data; /* Get the vitals from the interface. */ safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ); 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"); mdio_write(skfd, MII_BMCR, BMCR_RESET); } if (nway_advertise > 0) { mdio_write(skfd, MII_ADVERTISE, nway_advertise | 1); opt_restart = 1; } if (opt_restart) { printf("restarting autonegotiation...\n"); mdio_write(skfd, MII_BMCR, 0x0000); mdio_write(skfd, MII_BMCR, BMCR_ANENABLE|BMCR_ANRESTART); } if (fixed_speed) { int bmcr = 0; if (fixed_speed & (LPA_100FULL|LPA_100HALF)) bmcr |= BMCR_SPEED100; if (fixed_speed & (LPA_100FULL|LPA_10FULL)) bmcr |= BMCR_FULLDPLX; 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) { struct mii_ioctl_data *mii = (struct mii_ioctl_data *)&ifr.ifr_data; static int status[MAX_ETH] = { 0, /* ... */ }; int now; /* Get the vitals from the interface. */ safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ); if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) { if (errno != ENODEV) fprintf(stderr, "SIOCGMIIPHY on '%s' failed: %s\n", ifname, strerror(errno)); return; } if (override_phy >= 0) { mii->phy_id = override_phy; } 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; } /*--------------------------------------------------------------------*/ const char *usage = "usage: %s [-VvRrwl] [-A media,... | -F media] [-p addr] \n" " -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" " -p, --phy=addr set PHY (MII address) to report\n" "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) { fprintf(stderr, "%s\n%s\n", Release, Signature); exit(E_VERSION); } int main(int argc, char **argv) { int i, c, ret, errflag = 0; unsigned ctrl1000 = 0; while ((c = getopt_long(argc, argv, "A:F:p:lrRvVw?", longopts, 0)) != EOF) switch (c) { case 'A': nway_advertise = parse_media(optarg, &ctrl1000); break; case 'F': fixed_speed = parse_media(optarg, &ctrl1000); break; 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; if ((nway_advertise < 0) || (fixed_speed < 0)) return 2; if (errflag || (fixed_speed & (fixed_speed-1)) || (fixed_speed && (opt_restart || nway_advertise))) { fprintf(stderr, usage, argv[0]); return 2; } if (opt_version) version(); /* Open a basic socket. */ if ((skfd = socket(AF_INET, SOCK_DGRAM,0)) < 0) { perror("socket"); exit(-1); } if (verbose > 1) printf("Using SIOCGMIIPHY=0x%x\n", SIOCGMIIPHY); /* No remaining args means show all interfaces. */ if (optind == argc) { fprintf(stderr, "No interface specified\n"); fprintf(stderr, usage, argv[0]); close(skfd); return 2; } 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); for (i = optind; i < argc; i++) watch_one_xcvr(skfd, argv[i], i-optind); } } close(skfd); return ret; }