etherfcc.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. * FCCn ethernet
  3. */
  4. #include "u.h"
  5. #include "../port/lib.h"
  6. #include "mem.h"
  7. #include "dat.h"
  8. #include "fns.h"
  9. #include "io.h"
  10. #include "m8260.h"
  11. #include "../port/error.h"
  12. #include "../port/netif.h"
  13. #include "etherif.h"
  14. enum {
  15. Nrdre = 128, /* receive descriptor ring entries */
  16. Ntdre = 128, /* transmit descriptor ring entries */
  17. Rbsize = ETHERMAXTU+4, /* ring buffer size (+4 for CRC) */
  18. Bufsize = (Rbsize+7)&~7, /* aligned */
  19. };
  20. enum {
  21. /* ether-specific Rx BD bits */
  22. RxMiss= SBIT(7),
  23. RxeLG= SBIT(10),
  24. RxeNO= SBIT(11),
  25. RxeSH= SBIT(12),
  26. RxeCR= SBIT(13),
  27. RxeOV= SBIT(14),
  28. RxeCL= SBIT(15),
  29. RxError= (RxeLG|RxeNO|RxeSH|RxeCR|RxeOV|RxeCL), /* various error flags */
  30. /* ether-specific Tx BD bits */
  31. TxPad= SBIT(1), /* pad short frames */
  32. TxTC= SBIT(5), /* transmit CRC */
  33. TxeDEF= SBIT(6),
  34. TxeHB= SBIT(7),
  35. TxeLC= SBIT(8),
  36. TxeRL= SBIT(9),
  37. TxeUN= SBIT(14),
  38. TxeCSL= SBIT(15),
  39. /* psmr */
  40. PRO= BIT(9), /* promiscuous mode */
  41. LPB= BIT(3), /* local protect bit */
  42. FDE= BIT(5), /* full duplex ethernet */
  43. CRCE= BIT(24), /* Ethernet CRC */
  44. /* gfmr */
  45. ENET= 0xc, /* ethernet mode */
  46. ENT= BIT(27),
  47. ENR= BIT(26),
  48. TCI= BIT(2),
  49. /* FCC function code register */
  50. GBL= 0x20,
  51. BO= 0x18,
  52. EB= 0x10, /* Motorola byte order */
  53. TC2= 0x04,
  54. DTB= 0x02,
  55. BDB= 0x01,
  56. /* FCC Event/Mask bits */
  57. GRA= SBIT(8),
  58. RXC= SBIT(9),
  59. TXC= SBIT(10),
  60. TXE= SBIT(11),
  61. RXF= SBIT(12),
  62. BSY= SBIT(13),
  63. TXB= SBIT(14),
  64. RXB= SBIT(15),
  65. };
  66. typedef struct Etherparam Etherparam;
  67. struct Etherparam {
  68. /*0x00*/ FCCparam;
  69. /*0x3c*/ ulong stat_buf;
  70. /*0x40*/ ulong cam_ptr;
  71. /*0x44*/ ulong cmask;
  72. /*0x48*/ ulong cpres;
  73. /*0x4c*/ ulong crcec;
  74. /*0x50*/ ulong alec;
  75. /*0x54*/ ulong disfc;
  76. /*0x58*/ ushort retlim;
  77. /*0x5a*/ ushort retcnt;
  78. /*0x5c*/ ushort p_per;
  79. /*0x5e*/ ushort boff_cnt;
  80. /*0x60*/ ulong gaddr[2];
  81. /*0x68*/ ushort tfcstat;
  82. /*0x6a*/ ushort tfclen;
  83. /*0x6c*/ ulong tfcptr;
  84. /*0x70*/ ushort mflr;
  85. /*0x72*/ ushort paddr[3];
  86. /*0x78*/ ushort ibd_cnt;
  87. /*0x7a*/ ushort ibd_start;
  88. /*0x7c*/ ushort ibd_end;
  89. /*0x7e*/ ushort tx_len;
  90. /*0x80*/ uchar ibd_base[32];
  91. /*0xa0*/ ulong iaddr[2];
  92. /*0xa8*/ ushort minflr;
  93. /*0xaa*/ ushort taddr[3];
  94. /*0xb0*/ ushort padptr;
  95. /*0xb2*/ ushort Rsvdb2;
  96. /*0xb4*/ ushort cf_range;
  97. /*0xb6*/ ushort max_b;
  98. /*0xb8*/ ushort maxd1;
  99. /*0xba*/ ushort maxd2;
  100. /*0xbc*/ ushort maxd;
  101. /*0xbe*/ ushort dma_cnt;
  102. /*0xc0*/ ulong octc;
  103. /*0xc4*/ ulong colc;
  104. /*0xc8*/ ulong broc;
  105. /*0xcc*/ ulong mulc;
  106. /*0xd0*/ ulong uspc;
  107. /*0xd4*/ ulong frgc;
  108. /*0xd8*/ ulong ospc;
  109. /*0xdc*/ ulong jbrc;
  110. /*0xe0*/ ulong p64c;
  111. /*0xe4*/ ulong p65c;
  112. /*0xe8*/ ulong p128c;
  113. /*0xec*/ ulong p256c;
  114. /*0xf0*/ ulong p512c;
  115. /*0xf4*/ ulong p1024c;
  116. /*0xf8*/ ulong cam_buf;
  117. /*0xfc*/ ulong Rsvdfc;
  118. /*0x100*/
  119. };
  120. typedef struct {
  121. Lock;
  122. int fccid;
  123. int port;
  124. ulong pmdio;
  125. ulong pmdck;
  126. int init;
  127. int active;
  128. FCC* fcc;
  129. Ring;
  130. ulong interrupts; /* statistics */
  131. ulong deferred;
  132. ulong heartbeat;
  133. ulong latecoll;
  134. ulong retrylim;
  135. ulong underrun;
  136. ulong overrun;
  137. ulong carrierlost;
  138. ulong retrycount;
  139. } Ctlr;
  140. static int fccirq[] = {0x20, 0x21, 0x22};
  141. static int fccid[] = {FCC1ID, FCC2ID, FCC3ID};
  142. int ioringinit(Ring* r, int nrdre, int ntdre, int bufsize);
  143. static void
  144. attach(Ether *ether)
  145. {
  146. Ctlr *ctlr;
  147. ctlr = ether->ctlr;
  148. ctlr->active = 1;
  149. ctlr->fcc->gfmr |= ENR|ENT;
  150. eieio();
  151. }
  152. static void
  153. closed(Ether *ether)
  154. {
  155. Ctlr *ctlr;
  156. ctlr = ether->ctlr;
  157. ilock(ctlr);
  158. ctlr->active = 0;
  159. ctlr->fcc->gfmr &= ~(ENR|ENT);
  160. iunlock(ctlr);
  161. print("Ether closed\n");
  162. }
  163. static void
  164. promiscuous(void* arg, int on)
  165. {
  166. Ether *ether;
  167. Ctlr *ctlr;
  168. ether = (Ether*)arg;
  169. ctlr = ether->ctlr;
  170. ilock(ctlr);
  171. if(on || ether->nmaddr)
  172. ctlr->fcc->fpsmr |= PRO;
  173. else
  174. ctlr->fcc->fpsmr &= ~PRO;
  175. iunlock(ctlr);
  176. }
  177. static void
  178. multicast(void* arg, uchar *addr, int on)
  179. {
  180. Ether *ether;
  181. Ctlr *ctlr;
  182. USED(addr, on); /* if on, could SetGroupAddress; if !on, it's hard */
  183. ether = (Ether*)arg;
  184. ctlr = ether->ctlr;
  185. ilock(ctlr);
  186. if(ether->prom || ether->nmaddr)
  187. ctlr->fcc->fpsmr |= PRO;
  188. else
  189. ctlr->fcc->fpsmr &= ~PRO;
  190. iunlock(ctlr);
  191. }
  192. static void
  193. txstart(Ether *ether)
  194. {
  195. int len;
  196. Ctlr *ctlr;
  197. Block *b;
  198. BD *dre;
  199. ctlr = ether->ctlr;
  200. if(ctlr->init)
  201. return;
  202. while(ctlr->ntq < Ntdre-1){
  203. b = qget(ether->oq);
  204. if(b == 0)
  205. break;
  206. dre = &ctlr->tdr[ctlr->tdrh];
  207. dczap(dre, sizeof(BD));
  208. if(dre->status & BDReady)
  209. panic("ether: txstart");
  210. /*
  211. * Give ownership of the descriptor to the chip, increment the
  212. * software ring descriptor pointer and tell the chip to poll.
  213. */
  214. len = BLEN(b);
  215. if(ctlr->txb[ctlr->tdrh] != nil)
  216. panic("fcc/ether: txstart");
  217. ctlr->txb[ctlr->tdrh] = b;
  218. if((ulong)b->rp&1)
  219. panic("fcc/ether: txstart align"); /* TO DO: ensure alignment */
  220. dre->addr = PADDR(b->rp);
  221. dre->length = len;
  222. dcflush(b->rp, len);
  223. dcflush(dre, sizeof(BD));
  224. dre->status = (dre->status & BDWrap) | BDReady|TxPad|BDInt|BDLast|TxTC;
  225. dcflush(dre, sizeof(BD));
  226. ctlr->fcc->ftodr = 1<<15; /* transmit now */
  227. ctlr->ntq++;
  228. ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre);
  229. }
  230. }
  231. static void
  232. transmit(Ether* ether)
  233. {
  234. Ctlr *ctlr;
  235. ctlr = ether->ctlr;
  236. ilock(ctlr);
  237. txstart(ether);
  238. iunlock(ctlr);
  239. }
  240. static void
  241. interrupt(Ureg*, void *arg)
  242. {
  243. int len, status;
  244. ushort events;
  245. Ctlr *ctlr;
  246. BD *dre;
  247. Block *b;
  248. Ether *ether = arg;
  249. ctlr = ether->ctlr;
  250. if(!ctlr->active)
  251. return; /* not ours */
  252. /*
  253. * Acknowledge all interrupts and whine about those that shouldn't
  254. * happen.
  255. */
  256. events = ctlr->fcc->fcce;
  257. eieio();
  258. ctlr->fcc->fcce = events; /* clear events */
  259. eieio();
  260. ctlr->interrupts++;
  261. if(events & RXB)
  262. ctlr->overrun++;
  263. if(events & TXE)
  264. ether->oerrs++;
  265. /*
  266. * Receiver interrupt: run round the descriptor ring logging
  267. * errors and passing valid receive data up to the higher levels
  268. * until we encounter a descriptor still owned by the chip.
  269. */
  270. if(events & (RXF|RXB)){
  271. dre = &ctlr->rdr[ctlr->rdrx];
  272. dczap(dre, sizeof(BD));
  273. while(((status = dre->status) & BDEmpty) == 0){
  274. if(status & RxError || (status & (BDFirst|BDLast)) != (BDFirst|BDLast)){
  275. if(status & (RxeLG|RxeSH))
  276. ether->buffs++;
  277. if(status & RxeNO)
  278. ether->frames++;
  279. if(status & RxeCR)
  280. ether->crcs++;
  281. if(status & RxeOV)
  282. ether->overflows++;
  283. print("eth rx: %ux\n", status);
  284. }
  285. else{
  286. /*
  287. * We have a packet. Read it in.
  288. */
  289. len = dre->length-4;
  290. dczap(KADDR(dre->addr), len);
  291. if((b = iallocb(len)) != 0){
  292. memmove(b->wp, KADDR(dre->addr), len);
  293. b->wp += len;
  294. etheriq(ether, b, 1);
  295. }else
  296. ether->soverflows++;
  297. }
  298. /*
  299. * Finished with this descriptor, reinitialise it,
  300. * give it back to the chip, then on to the next...
  301. */
  302. dre->length = 0;
  303. dre->status = (status & BDWrap) | BDEmpty | BDInt;
  304. dcflush(dre, sizeof(BD));
  305. ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
  306. dre = &ctlr->rdr[ctlr->rdrx];
  307. dczap(dre, sizeof(BD));
  308. }
  309. }
  310. /*
  311. * Transmitter interrupt: handle anything queued for a free descriptor.
  312. */
  313. if(events & (TXB|TXE)){
  314. lock(ctlr);
  315. while(ctlr->ntq){
  316. dre = &ctlr->tdr[ctlr->tdri];
  317. dczap(dre, sizeof(BD));
  318. status = dre->status;
  319. if(status & BDReady)
  320. break;
  321. if(status & TxeDEF)
  322. ctlr->deferred++;
  323. if(status & TxeHB)
  324. ctlr->heartbeat++;
  325. if(status & TxeLC)
  326. ctlr->latecoll++;
  327. if(status & TxeRL)
  328. ctlr->retrylim++;
  329. if(status & TxeUN)
  330. ctlr->underrun++;
  331. if(status & TxeCSL)
  332. ctlr->carrierlost++;
  333. ctlr->retrycount += (status>>2)&0xF;
  334. b = ctlr->txb[ctlr->tdri];
  335. if(b == nil)
  336. panic("fcce/interrupt: bufp");
  337. ctlr->txb[ctlr->tdri] = nil;
  338. freeb(b);
  339. ctlr->ntq--;
  340. ctlr->tdri = NEXT(ctlr->tdri, Ntdre);
  341. }
  342. if(events & TXE)
  343. cpmop(RestartTx, ctlr->fccid, 0xc);
  344. txstart(ether);
  345. unlock(ctlr);
  346. }
  347. }
  348. static long
  349. ifstat(Ether* ether, void* a, long n, ulong offset)
  350. {
  351. char *p;
  352. int len;
  353. Ctlr *ctlr;
  354. if(n == 0)
  355. return 0;
  356. ctlr = ether->ctlr;
  357. p = malloc(READSTR);
  358. len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
  359. len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost);
  360. len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat);
  361. len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim);
  362. len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount);
  363. len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll);
  364. len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun);
  365. len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun);
  366. snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred);
  367. n = readstr(offset, a, n, p);
  368. free(p);
  369. return n;
  370. }
  371. /*
  372. * This follows the MPC8260 user guide: section28.9's initialisation sequence.
  373. */
  374. static void
  375. fccsetup(Ctlr *ctlr, FCC *fcc, uchar *ea)
  376. {
  377. int i;
  378. Etherparam *p;
  379. /* Turn Ethernet off */
  380. fcc->gfmr &= ~(ENR | ENT);
  381. ioplock();
  382. switch(ctlr->port) {
  383. default:
  384. iopunlock();
  385. return;
  386. case 0:
  387. /* Step 1 (Section 28.9), write the parallel ports */
  388. ctlr->pmdio = 0x01000000;
  389. ctlr->pmdck = 0x08000000;
  390. iomem->port[0].pdir &= ~A1dir0;
  391. iomem->port[0].pdir |= A1dir1;
  392. iomem->port[0].psor &= ~A1psor0;
  393. iomem->port[0].psor |= A1psor1;
  394. iomem->port[0].ppar |= (A1dir0 | A1dir1);
  395. /* Step 2, Port C clocks */
  396. iomem->port[2].psor &= ~0x00000c00;
  397. iomem->port[2].pdir &= ~0x00000c00;
  398. iomem->port[2].ppar |= 0x00000c00;
  399. iomem->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
  400. iomem->port[3].podr |= ctlr->pmdio;
  401. iomem->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
  402. iomem->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
  403. eieio();
  404. /* Step 3, Serial Interface clock routing */
  405. iomem->cmxfcr &= ~0xff000000; /* Clock mask */
  406. iomem->cmxfcr |= 0x37000000; /* Clock route */
  407. break;
  408. case 1:
  409. /* Step 1 (Section 28.9), write the parallel ports */
  410. ctlr->pmdio = 0x00400000;
  411. ctlr->pmdck = 0x00200000;
  412. iomem->port[1].pdir &= ~B2dir0;
  413. iomem->port[1].pdir |= B2dir1;
  414. iomem->port[1].psor &= ~B2psor0;
  415. iomem->port[1].psor |= B2psor1;
  416. iomem->port[1].ppar |= (B2dir0 | B2dir1);
  417. /* Step 2, Port C clocks */
  418. iomem->port[2].psor &= ~0x00003000;
  419. iomem->port[2].pdir &= ~0x00003000;
  420. iomem->port[2].ppar |= 0x00003000;
  421. iomem->port[2].pdat |= (ctlr->pmdio | ctlr->pmdck);
  422. iomem->port[2].podr |= ctlr->pmdio;
  423. iomem->port[2].pdir |= (ctlr->pmdio | ctlr->pmdck);
  424. iomem->port[2].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
  425. eieio();
  426. /* Step 3, Serial Interface clock routing */
  427. iomem->cmxfcr &= ~0x00ff0000;
  428. iomem->cmxfcr |= 0x00250000;
  429. break;
  430. case 2:
  431. /* Step 1 (Section 28.9), write the parallel ports */
  432. iomem->port[1].pdir &= ~B3dir0;
  433. iomem->port[1].pdir |= B3dir1;
  434. iomem->port[1].psor &= ~B3psor0;
  435. iomem->port[1].psor |= B3psor1;
  436. iomem->port[1].ppar |= (B3dir0 | B3dir1);
  437. /* Step 2, Port C clocks */
  438. iomem->port[2].psor &= ~0x0000c000;
  439. iomem->port[2].pdir &= ~0x0000c000;
  440. iomem->port[2].ppar |= 0x0000c000;
  441. iomem->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
  442. iomem->port[3].podr |= ctlr->pmdio;
  443. iomem->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
  444. iomem->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
  445. eieio();
  446. /* Step 3, Serial Interface clock routing */
  447. iomem->cmxfcr &= ~0x0000ff00;
  448. iomem->cmxfcr |= 0x00003700;
  449. break;
  450. }
  451. iopunlock();
  452. p = (Etherparam*)(m->imap->prmfcc + ctlr->port);
  453. memset(p, 0, sizeof(Etherparam));
  454. /* Step 4 */
  455. // fcc->gfmr |= (TCI | ENET);
  456. fcc->gfmr |= ENET;
  457. /* Step 5 */
  458. fcc->fpsmr = CRCE | FDE | LPB; /* full duplex operation */
  459. // fcc->fpsmr = CRCE; /* half duplex operation */
  460. /* Step 6 */
  461. fcc->fdsr = 0xd555;
  462. /* Step 7, initialize parameter ram */
  463. p->rbase = PADDR(ctlr->rdr);
  464. p->tbase = PADDR(ctlr->tdr);
  465. p->rstate = (GBL | EB) << 24;
  466. p->tstate = (GBL | EB) << 24;
  467. p->cmask = 0xdebb20e3;
  468. p->cpres = 0xffffffff;
  469. p->retlim = 15; /* retry limit */
  470. p->mrblr = Bufsize;
  471. p->mflr = Rbsize;
  472. p->minflr = ETHERMINTU + 4;
  473. p->maxd1 = (Rbsize+3) & ~3;
  474. p->maxd2 = (Rbsize+3) & ~3;
  475. for(i=0; i<Eaddrlen; i+=2)
  476. p->paddr[2-i/2] = (ea[i+1]<<8)|ea[i];
  477. /* Step 7, initialize parameter ram, configuration-dependent values */
  478. p->riptr = m->imap->fccextra[ctlr->port].ri - (uchar*)INTMEM;
  479. p->tiptr = m->imap->fccextra[ctlr->port].ti - (uchar*)INTMEM;
  480. p->padptr = m->imap->fccextra[ctlr->port].pad - (uchar*)INTMEM;
  481. memset(m->imap->fccextra[ctlr->port].pad, 0x88, 0x20);
  482. /* Step 8, clear out events */
  483. fcc->fcce = ~0;
  484. /* Step 9, Interrupt enable */
  485. fcc->fccm = TXE | RXF | TXB;
  486. /* Step 10, Configure interrupt priority (not done here) */
  487. /* Step 11, Clear out current events */
  488. /* Step 12, Enable interrupts to the CP interrupt controller */
  489. /* Step 13, Issue the Init Tx and Rx command, specifying 0xc for ethernet*/
  490. cpmop(InitRxTx, fccid[ctlr->port], 0xc);
  491. // Step 14, Enable ethernet: done at attach time
  492. }
  493. static int
  494. reset(Ether* ether)
  495. {
  496. uchar ea[Eaddrlen];
  497. Ctlr *ctlr;
  498. FCC *fcc;
  499. if(m->cpuhz < 24000000){
  500. print("%s ether: system speed must be >= 24MHz for ether use\n", ether->type);
  501. return -1;
  502. }
  503. if(!(ether->port >= 0 && ether->port < 3)){
  504. print("%s ether: no FCC port %ld\n", ether->type, ether->port);
  505. return -1;
  506. }
  507. ether->irq = fccirq[ether->port];
  508. ether->tbdf = BusPPC;
  509. fcc = iomem->fcc + ether->port;
  510. ctlr = malloc(sizeof(*ctlr));
  511. ether->ctlr = ctlr;
  512. memset(ctlr, 0, sizeof(*ctlr));
  513. ctlr->fcc = fcc;
  514. ctlr->port = ether->port;
  515. ctlr->fccid = fccid[ether->port];
  516. /* Ioringinit will allocate the buffer descriptors in normal memory
  517. * and NOT in Dual-Ported Ram, as prescribed by the MPC8260
  518. * PowerQUICC II manual (Section 28.6). When they are allocated
  519. * in DPram and the Dcache is enabled, the processor will hang
  520. */
  521. if(ioringinit(ctlr, Nrdre, Ntdre, Bufsize) < 0)
  522. panic("etherfcc init");
  523. fccsetup(ctlr, fcc, ether->ea);
  524. ether->mbps = 100; /* TO DO: could be 10mbps */
  525. ether->attach = attach;
  526. ether->transmit = transmit;
  527. ether->interrupt = interrupt;
  528. ether->ifstat = ifstat;
  529. ether->arg = ether;
  530. ether->promiscuous = promiscuous;
  531. ether->multicast = multicast;
  532. /*
  533. * Until we know where to find it, insist that the plan9.ini
  534. * entry holds the Ethernet address.
  535. */
  536. memset(ea, 0, Eaddrlen);
  537. if(memcmp(ea, ether->ea, Eaddrlen) == 0){
  538. print("no ether address");
  539. return -1;
  540. }
  541. return 0;
  542. }
  543. void
  544. etherfcclink(void)
  545. {
  546. addethercard("fcc", reset);
  547. }