/*******************************************************************************************/ /** **/ /** miitool.c **/ /** SANA-II extension for access to Ethernet PHY registers over MII interface **/ /** **/ /** Copyright (c) 2019, Henryk Richter, Timm S. Müller **/ /** **/ /** Redistribution and use in source and binary forms, with or without **/ /** modification, are permitted provided that the following conditions are met: **/ /** 1. Redistributions of source code must retain the above copyright **/ /** notice, this list of conditions and the following disclaimer. **/ /** 2. Redistributions in binary form must reproduce the above copyright **/ /** notice, this list of conditions and the following disclaimer in the **/ /** documentation and/or other materials provided with the distribution. **/ /** 3. All advertising materials mentioning features or use of this software **/ /** must display the following acknowledgement: **/ /** This product includes software developed by Henryk Richter. **/ /** **/ /** THIS SOFTWARE IS PROVIDED BY Henryk Richter ''AS IS'' AND ANY **/ /** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED **/ /** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE **/ /** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY **/ /** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES **/ /** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; **/ /** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND **/ /** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT **/ /** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS **/ /** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **/ /** **/ /*******************************************************************************************/ /** Acknowledgements: **/ /** text and definitions in "media" table and "miitool.h" file originate from mii-tool.c **/ /** written/copyright 1997-2000 by Donald Becker **/ /*******************************************************************************************/ /* Notes: - make sure you have an up-to-date devices/sana2devqueryext.h - make sure you have devices/newstyle.h - compilation sc miitool.c link INCLUDEDIR=sc:netinclude gcc miitool.c -Igg:netinclude -o miitool - if you don`t have the Roadshow SDK installed, just comment "RSHOW" out to build without auto-detection code */ #define RSHOW #include #include #include #include #include #include #include "miitool.h" #ifdef RSHOW #include #include #endif #ifdef __GNUC__ struct UtilityBase *UtilityBase; static USHORT Utility_MY = 0; #endif #define OPT_TEMPLATE "DEVICE=DEV/K,UNIT/K/N,VERBOSE=V/S,FORCEMEDIA/K,ADVERTISE/K,RENEG=RESTART/S,RESET/S,VERYVERBOSE=VV/S" const char opt_template[] = OPT_TEMPLATE; struct progopts { BYTE *devname; LONG *unit; /* unit to open */ ULONG verbose; /* verbose mode enabled */ BYTE *force; /* force media type */ BYTE *advertise; /* advertise specific types */ ULONG renegotiate; /* restart autonegotiation */ ULONG reset; /* reset MII */ ULONG vverbose; /* higher verbosity level */ }; struct prgdata { struct MsgPort *sana2devport; struct IOSana2Req *sana2devio; struct Device *sana2dev; UBYTE devvalid; struct progopts opts; BYTE *auto_devname; /* device name, if auto-detected */ }; void end_prog( struct prgdata *prg ) { if( prg->auto_devname ) FreeVec( prg->auto_devname ); if( prg->sana2devio ) { if( prg->devvalid ) CloseDevice( (struct IORequest *)prg->sana2devio ); DeleteIORequest( (struct IORequest *)prg->sana2devio ); } if( prg->sana2devport ) DeleteMsgPort( prg->sana2devport ); #ifdef __GNUC__ if( (UtilityBase) && (Utility_MY) ) { CloseLibrary( (struct Library*)UtilityBase ); Utility_MY = 0; UtilityBase = NULL; } #endif /* __GNUC__ */ } /* note: call this only once at program startup */ LONG start_prog( struct prgdata *prg ) { SHORT i; UBYTE *t = (UBYTE*)prg; for( i=0 ; i < sizeof( struct prgdata ) ; i++ ) *t++ = 0; prg->sana2devport = CreateMsgPort(); if( !prg->sana2devport ) goto err; prg->sana2devio = (struct IOSana2Req *) CreateIORequest( prg->sana2devport, sizeof(struct IOSana2Req)); if( !prg->sana2devio ) goto err; #ifdef __GNUC__ if( !UtilityBase ) { UtilityBase = (struct UtilityBase*)OpenLibrary("utility.library",37); Utility_MY = 1; } #endif /* __GNUC__ */ return 1; err: end_prog( prg ); return 0; } void clr_device( struct prgdata *prg ) { if( !prg->devvalid ) return; prg->devvalid = 0; CloseDevice( (struct IORequest *)prg->sana2devio ); } LONG get_device(struct prgdata *prg, BYTE *name, LONG unit ) { if( !name ) return 0; if( prg->devvalid ) /* close previously opened device */ clr_device( prg ); prg->devvalid = OpenDevice(name,unit,(struct IORequest *)prg->sana2devio,0) == 0; if( !prg->devvalid ) { Printf( "Cannot open %s unit %ld\n",(ULONG)name,unit ); return 0; } return 1; } static BOOL findcmd(struct NSDeviceQueryResult *nsdqr, UWORD cmd) { UWORD *p; for (p = nsdqr->nsdqr_SupportedCommands; (*p); ++p ) if (*p == cmd) return TRUE; return FALSE; } /* verify NSD support, SANA-II type, Device Query extension on previously opened device */ LONG probe_device( struct prgdata *prg ) { struct IOStdReq *stdreq; struct NSDeviceQueryResult nsdqr; if( !prg ) return 0; if( !prg->devvalid ) return 0; stdreq = (struct IOStdReq *)prg->sana2devio; stdreq->io_Command = NSCMD_DEVICEQUERY; stdreq->io_Flags = 0; stdreq->io_Data = &nsdqr; stdreq->io_Length = sizeof nsdqr; if (DoIO((struct IORequest *) stdreq) != 0) { Printf("I/O error: no NewStyleDevice.\n"); return 0; } if ( nsdqr.nsdqr_DeviceType != NSDEVTYPE_SANA2 ) { Printf("Device is not a SANA-II device (type %ld, needed %ld\n).", (LONG)nsdqr.nsdqr_DeviceType, (LONG) NSDEVTYPE_SANA2 ); return 0; } if (!findcmd(&nsdqr, S2_DEVICEQUERYEXT)) { Printf("Device does not support S2_DEVICEQUERYEXT.\n"); return 0; } return 1; } const struct { BYTE *name; UWORD namelen; UWORD value; } mediatypes[] = { /* these are kept in this order for the dump loop decision about best mode */ { "10baseT-HD", 10,MII_AN_10BASET_HD }, { "10baseT-FD", 10,MII_AN_10BASET_FD }, { "100baseTx-HD", 12,MII_AN_100BASETX_HD }, { "100baseTx-FD", 12,MII_AN_100BASETX_FD }, { "100baseT4", 9,MII_AN_100BASET4 }, /* generic mode names where only the speed matters */ { "100baseTx", 9,MII_AN_100BASETX_FD | MII_AN_100BASETX_HD }, { "10baseT", 7,MII_AN_10BASET_FD | MII_AN_10BASET_HD }, { "100bTx", 6,MII_AN_100BASETX_FD | MII_AN_100BASETX_HD }, { "10bT", 4,MII_AN_10BASET_FD | MII_AN_10BASET_HD }, }; #define NMEDIA (sizeof(mediatypes)/sizeof(mediatypes[0])) void dump_mii_media( USHORT parms, BOOL bestonly ) { LONG i; for( i=9 ; i >= 5 ; i-- ) { if( parms & (1< 1 ) { Printf("PHY Registers:"); for( i=0 ; i < length/sizeof( struct S2DevQueryMIIParam ) ; i++ ) { if(!(i%8)) Printf("\n"); if( mii[i].ID == S2_DEVQUERYEXT_MII_INVALID ) Printf("N/A "); else Printf("%04lx ",(LONG)(mii[i].Content)); } Printf("\n"); } if( verbose ) { Printf(" vendor %02lx:%02lx:%02lx, model %ld rev %ld\n", (LONG)mii[2].Content>>10, (LONG)(mii[2].Content>>2)&0xff, (LONG)((mii[2].Content<<6)|(mii[3].Content>>10))&0xff, (LONG)(mii[3].Content>>4)&0x3f, (LONG)(mii[3].Content&0x0f) ); Printf(" basic mode: "); if( bmcr & MII_BMCR_RESET ) Printf("software reset, "); if( bmcr & MII_BMCR_LOOPBACK ) Printf("loopback, "); if( bmcr & MII_BMCR_ISOLATE ) Printf("isolate, "); if( bmcr & MII_BMCR_COLTEST ) Printf("collision test, "); if( bmcr & MII_BMCR_AN_ENA ) Printf("autonegotiation enabled\n"); else Printf("%s Mbit, %s duplex\n", (bmcr & MII_BMCR_100MBIT) ? (ULONG)"100" : (ULONG)"10", (bmcr & MII_BMCR_DUPLEX) ? (ULONG)"full" : (ULONG)"half"); Printf(" basic status: "); if( bmsr & MII_BMSR_AN_COMPLETE ) Printf("autonegotiation complete, "); else if( bmcr & MII_BMCR_RESTART ) Printf("autonegotiation restarted, "); if( bmsr & MII_BMSR_REMOTE_FAULT ) Printf(" remote fault, "); Printf( (bmsr & MII_BMSR_LINK_VALID) ? "link ok" : "no link"); Printf("\n capabilities:"); dump_mii_media( bmsr >> 6, 0 ); Printf("\n advertising: "); dump_mii_media( selfad, 0 ); if( curlink & MII_AN_ABILITY_MASK ) { Printf("\n link partner:"); dump_mii_media( curlink, 0 ); } Printf("\n"); } return 1; } /* Query consecutive set of MII registers from start to end (inclusive) */ LONG query_mii( struct prgdata *prg, struct S2DevQueryMIIParam *mii, LONG start, LONG end ) { struct S2DevQueryMIIParam *miitmp; struct S2DevQueryExtParam parm_mii; struct TagItem tags[2]; LONG i; /* MII specific */ parm_mii.Data = (APTR)mii; parm_mii.Length = (end-start+1)*sizeof(struct S2DevQueryMIIParam); parm_mii.Actual = 0; for( i=start,miitmp=mii ; i <= end ; i++,miitmp++ ) /* MII standard register set: 8 */ { miitmp->ID = i; } tags[i=0].ti_Tag = S2_DevQueryExtGetMII; tags[i++].ti_Data = (ULONG) &parm_mii; tags[i ].ti_Tag = TAG_DONE; prg->sana2devio->ios2_Req.io_Command = S2_DEVICEQUERYEXT; prg->sana2devio->ios2_Data = tags; prg->sana2devio->ios2_DataLength = sizeof(tags); if( DoIO((struct IORequest *) prg->sana2devio ) != 0 ) return 0; else return parm_mii.Actual; } LONG write_mii( struct prgdata *prg, struct S2DevQueryMIIParam *mii, LONG num ) { struct S2DevQueryExtParam parm_mii; struct TagItem tags[2]; LONG i; /* MII specific */ parm_mii.Data = (APTR)mii; parm_mii.Length = (num)*sizeof(struct S2DevQueryMIIParam); parm_mii.Actual = 0; tags[i=0].ti_Tag = S2_DevQueryExtSetMII; tags[i++].ti_Data = (ULONG) &parm_mii; tags[i ].ti_Tag = TAG_DONE; prg->sana2devio->ios2_Req.io_Command = S2_DEVICEQUERYEXT; prg->sana2devio->ios2_Data = tags; prg->sana2devio->ios2_DataLength = sizeof(tags); if( DoIO((struct IORequest *) prg->sana2devio ) != 0 ) return 0; else return parm_mii.Actual; } #ifdef RSHOW /* return string length without closing 0 */ LONG myStrLen( BYTE *str ) { LONG ret = 0; if( str ) { while( *str++ ) ret++; } return ret; } /* copy string, append 0 */ void myStrNCpy( BYTE *dst, BYTE*src, LONG bytes ) { if( !dst ) return; if( src ) { while( (bytes--) && (*src != 0) ) { *dst++ = *src++; } } *dst = 0; } /* find active interface and its device in RoadShow (optional) */ /* "which" index of Ethernet adapter 0,1,2,... */ /* "unit" is the output unit */ BYTE *find_active_interface(LONG which, LONG *unit ) { struct Library *SocketBase; BYTE *ret = NULL; struct List * interface_list = NULL; struct Node *n; BYTE *dev = NULL; LONG hw = -1; SocketBase = OpenLibrary("bsdsocket.library",4); if( !SocketBase ) { Printf("Cannot open bsdsocket.library version 4 for device auto-detection\n"); return NULL; } do { LONG have_iface = 0; if( !(SocketBaseTags(SBTM_GETREF(SBTC_HAVE_INTERFACE_API), &have_iface, TAG_DONE) == 0) ||(!have_iface) ) { Printf("RoadShow-style interface API not available in IP stack\n"); break; } interface_list = ObtainInterfaceList(); if( !interface_list ) { break; } for( n=interface_list->lh_Head ; n->ln_Succ != NULL ; n=n->ln_Succ ) { /* Printf("Interface %s\n",n->ln_Name); */ /* find Ethernet interface */ if(!QueryInterfaceTags( n->ln_Name, IFQ_HardwareType, &hw, IFQ_DeviceName, &dev, IFQ_DeviceUnit, unit ) ) continue; /* Printf("device %s hwtype %ld\n",dev,hw); */ if( hw == S2WireType_Ethernet ) break; } if( hw == S2WireType_Ethernet ) { if( dev ) { ret = AllocVec( myStrLen( dev ), 0 ); if( ret ) myStrNCpy( ret, dev, myStrLen(dev) ); } } } while(0); if( interface_list ) ReleaseInterfaceList(interface_list); CloseLibrary(SocketBase); return ret; } #endif /* 8 basic registers, we poll 16 out of curiosity */ #define MII_REGNUM 16 /* two spare registers for write mode */ #define MII_SPARE 2 int main(int argc, char **argv) { struct prgdata _prg,*prg=&_prg; int res = RETURN_FAIL; char vendorname[128], productname[128]; struct S2DevQueryExtParam parm_productname; struct S2DevQueryExtParam parm_vendorname; struct S2DevQueryExtParam parm_mii; struct S2DevQueryMIIParam mii[MII_REGNUM+MII_SPARE]; struct TagItem tags[3]; int i; struct RDArgs *rdargs; BYTE *name; LONG unit,autoneg,force; if( !start_prog(prg) ) return res; if( !(rdargs = ReadArgs( (char*)opt_template,(LONG*)&prg->opts,NULL) ) ) { Printf("Argument error\n"); end_prog(prg); return res; } unit = (prg->opts.unit) ? *prg->opts.unit : 0; if( !prg->opts.devname ) { #ifdef RSHOW LONG _unit; prg->auto_devname = find_active_interface( 0, &_unit ); if( prg->auto_devname ) { unit = _unit; name = prg->auto_devname; } else { Printf("cannot auto-determine device, please use DEVICE=... as argument\n"); end_prog(prg); return res; } #else name = "enc624j6net.device"; Printf("device auto-detect not compiled in, now trying default %s\n",name); #endif /* end_prog(prg); return res; */ } else name = prg->opts.devname; if( prg->opts.vverbose ) prg->opts.verbose = 2; else if( prg->opts.verbose ) prg->opts.verbose = 1; /* parameter check */ if( ( (prg->opts.force) && (prg->opts.renegotiate) ) || ( (prg->opts.force) && (prg->opts.advertise) ) ) { Printf("Error: Forced and Autonegotiation parameter mismatch\n"); end_prog(prg); return RETURN_FAIL; } /* convert text to binary mask */ autoneg = parse_speedduplex( prg->opts.advertise ); force = parse_speedduplex( prg->opts.force ); if( (autoneg|force) < 0 ) { end_prog(prg); return RETURN_FAIL; } if( get_device( prg, name , unit ) ) { if( probe_device( prg ) ) res = RETURN_OK; } if( res != RETURN_OK ) { end_prog(prg); return res; } /* device open, query extension present, now query it */ { /* general information */ parm_vendorname.Data = vendorname; parm_vendorname.Length = sizeof(vendorname); parm_vendorname.Actual = 0; parm_productname.Data = productname; parm_productname.Length = sizeof(productname); parm_productname.Actual = 0; tags[i=0].ti_Tag = S2_DevQueryExtVendorName; tags[i++].ti_Data = (ULONG) &parm_vendorname; tags[i ].ti_Tag = S2_DevQueryExtProductName; tags[i++].ti_Data = (ULONG) &parm_productname; tags[i ].ti_Tag = TAG_DONE; prg->sana2devio->ios2_Req.io_Command = S2_DEVICEQUERYEXT; prg->sana2devio->ios2_Data = tags; prg->sana2devio->ios2_DataLength = sizeof(tags); DoIO( (struct IORequest *)prg->sana2devio ); /* separate query subroutine in here for MII, though not absolutely necessary (MII is queried more than once) */ parm_mii.Actual = query_mii( prg, mii, 0, 15 ); if( parm_mii.Actual ) { /* read BMSR a second time */ query_mii( prg, mii+MII_BMSR, MII_BMSR, MII_BMSR ); res = RETURN_OK; } else { Printf("Sana2DeviceQueryExtension for MII access not present\n"); res = RETURN_WARN; } } /* out of curiosity: get MAC address */ prg->sana2devio->ios2_Req.io_Command = S2_GETSTATIONADDRESS; prg->sana2devio->ios2_Data = (0); prg->sana2devio->ios2_DataLength = 0; DoIO( (struct IORequest *)prg->sana2devio ); Printf("Device: %s\n",(ULONG)name); /* general info */ if( parm_vendorname.Actual != 0 ) { vendorname[parm_vendorname.Actual-1] = 0; Printf("Vendor: %s\n",(ULONG)vendorname); } if( parm_productname.Actual != 0 ) { productname[parm_productname.Actual-1] = 0; Printf("Product: %s\n",(ULONG)productname); } /* MAC address */ { unsigned char*p = (unsigned char*)prg->sana2devio->ios2_SrcAddr; Printf("MAC: %02lx:%02lx:%02lx:%02lx:%02lx:%02lx\n", (ULONG)p[0],(ULONG)p[1],(ULONG)p[2],(ULONG)p[3],(ULONG)p[4],(ULONG)p[5] ); } /* after query, see what we've got */ if( res == RETURN_OK ) { /* MII data */ if( parm_mii.Actual == 0 ) { Printf("Unable to query MII data.\n"); } else { /* command or query ? */ if( !(prg->opts.renegotiate) && !(prg->opts.reset) && !(prg->opts.advertise) && !(prg->opts.force) ) { Printf("Active Parameters:\n"); dump_mii( mii, parm_mii.Actual, prg->opts.verbose ); } } if( prg->opts.reset ) { Printf("resetting the transceiver...\n"); mii[MII_REGNUM].ID = MII_BMCR; mii[MII_REGNUM].Content = MII_BMCR_RESET; write_mii( prg, &mii[MII_REGNUM], 1 ); } if( prg->opts.advertise ) { mii[MII_REGNUM].ID = MII_ANAR; mii[MII_REGNUM].Content = autoneg|1; write_mii( prg, &mii[MII_REGNUM], 1 ); prg->opts.renegotiate = 1; } if( prg->opts.renegotiate ) { Printf("restarting autonegotiation...\n"); mii[MII_REGNUM].ID = MII_BMCR; mii[MII_REGNUM].Content = 0x0000; write_mii( prg, &mii[MII_REGNUM], 1 ); mii[MII_REGNUM].ID = MII_BMCR; mii[MII_REGNUM].Content = MII_BMCR_AN_ENA|MII_BMCR_RESTART; write_mii( prg, &mii[MII_REGNUM], 1 ); } if( prg->opts.force ) { UWORD bmcr = 0; if( force & (MII_AN_100BASETX_FD|MII_AN_100BASETX_HD)) bmcr |= MII_BMCR_100MBIT; if( force & (MII_AN_100BASETX_FD|MII_AN_10BASET_FD)) bmcr |= MII_BMCR_DUPLEX; mii[MII_REGNUM].ID = MII_BMCR; mii[MII_REGNUM].Content = bmcr; write_mii( prg, &mii[MII_REGNUM], 1 ); } } end_prog(prg); return res; }