1 /* 2 ** File: 3c509.c Jun. 01, 2000 3 ** 4 ** Author: Giovanni Falzoni <gfalzoni@inwind.it> 5 ** 6 ** This file contains specific implementation of the ethernet 7 ** device driver for 3Com Etherlink III (3c509) boards. 8 ** NOTE: The board has to be setup to disable PnP and to assign 9 ** I/O base and IRQ. The driver is for ISA bus only 10 */ 11 12 #include <minix/drivers.h> 13 #include <minix/com.h> 14 #include <net/gen/ether.h> 15 #include <net/gen/eth_io.h> 16 17 #include "dp.h" 18 19 #if (ENABLE_3C509 == 1) 20 21 #include "3c509.h" 22 23 static const char *const IfNamesMsg[] = { 24 "10BaseT", "AUI", "unknown", "BNC", 25 }; 26 27 /* 28 ** Name: void el3_update_stats(dpeth_t *dep) 29 ** Function: Reads statistic counters from board 30 ** and updates local counters. 31 */ 32 static void el3_update_stats(dpeth_t * dep) 33 { 34 35 /* Disables statistics while reading and switches to the correct window */ 36 outw_el3(dep, REG_CmdStatus, CMD_StatsDisable); 37 SetWindow(WNO_Statistics); 38 39 /* Reads everything, adding values to the local counters */ 40 dep->de_stat.ets_sendErr += inb_el3(dep, REG_TxCarrierLost); /* Reg. 00 */ 41 dep->de_stat.ets_sendErr += inb_el3(dep, REG_TxNoCD); /* Reg. 01 */ 42 dep->de_stat.ets_collision += inb_el3(dep, REG_TxMultColl); /* Reg. 02 */ 43 dep->de_stat.ets_collision += inb_el3(dep, REG_TxSingleColl); /* Reg. 03 */ 44 dep->de_stat.ets_collision += inb_el3(dep, REG_TxLate); /* Reg. 04 */ 45 dep->de_stat.ets_recvErr += inb_el3(dep, REG_RxDiscarded); /* Reg. 05 */ 46 dep->de_stat.ets_packetT += inb_el3(dep, REG_TxFrames); /* Reg. 06 */ 47 dep->de_stat.ets_packetR += inb_el3(dep, REG_RxFrames); /* Reg. 07 */ 48 dep->de_stat.ets_transDef += inb_el3(dep, REG_TxDefer); /* Reg. 08 */ 49 dep->bytes_Rx += (unsigned) inw_el3(dep, REG_RxBytes); /* Reg. 10 */ 50 dep->bytes_Tx += (unsigned) inw_el3(dep, REG_TxBytes); /* Reg. 12 */ 51 52 /* Goes back to operating window and enables statistics */ 53 SetWindow(WNO_Operating); 54 outw_el3(dep, REG_CmdStatus, CMD_StatsEnable); 55 56 return; 57 } 58 59 /* 60 ** Name: void el3_getstats(dpeth_t *dep) 61 ** Function: Reads statistics counters from board. 62 */ 63 static void el3_getstats(dpeth_t * dep) 64 { 65 66 lock(); 67 el3_update_stats(dep); 68 unlock(); 69 return; 70 } 71 72 /* 73 ** Name: void el3_dodump(dpeth_t *dep) 74 ** Function: Dumps counter on screen (support for console display). 75 */ 76 static void el3_dodump(dpeth_t * dep) 77 { 78 79 el3_getstats(dep); 80 return; 81 } 82 83 /* 84 ** Name: void el3_rx_mode(dpeth_t *dep) 85 ** Function: Initializes receiver mode 86 */ 87 static void el3_rx_mode(dpeth_t * dep) 88 { 89 90 dep->de_recv_mode = FilterIndividual; 91 if (dep->de_flags & DEF_BROAD) dep->de_recv_mode |= FilterBroadcast; 92 if (dep->de_flags & DEF_MULTI) dep->de_recv_mode |= FilterMulticast; 93 if (dep->de_flags & DEF_PROMISC) dep->de_recv_mode |= FilterPromiscuous; 94 95 outw_el3(dep, REG_CmdStatus, CMD_RxReset); 96 outw_el3(dep, REG_CmdStatus, CMD_SetRxFilter | dep->de_recv_mode); 97 outw_el3(dep, REG_CmdStatus, CMD_RxEnable); 98 99 return; 100 } 101 102 /* 103 ** Name: void el3_reset(dpeth_t *dep) 104 ** Function: Reset function specific for Etherlink hardware. 105 */ 106 static void el3_reset(dpeth_t * UNUSED(dep)) 107 { 108 109 return; /* Done */ 110 } 111 112 /* 113 ** Name: void el3_write_fifo(dpeth_t * dep, int pktsize); 114 ** Function: Writes a packet from user area to board. 115 ** Remark: Writing a word/dword at a time may result faster 116 ** but is a lot more complicated. Let's go simpler way. 117 */ 118 static void el3_write_fifo(dpeth_t * dep, int pktsize) 119 { 120 int bytes, ix = 0; 121 iovec_dat_s_t *iovp = &dep->de_write_iovec; 122 int r, padding = pktsize; 123 124 do { /* Writes chuncks of packet from user buffers */ 125 126 bytes = iovp->iod_iovec[ix].iov_size; /* Size of buffer */ 127 if (bytes > pktsize) bytes = pktsize; 128 /* Writes from user buffer to Tx FIFO */ 129 r= sys_safe_outsb(dep->de_data_port, iovp->iod_proc_nr, 130 iovp->iod_iovec[ix].iov_grant, 0, bytes); 131 if (r != OK) 132 panic("el3_write_fifo: sys_safe_outsb failed: %d", r); 133 134 if (++ix >= IOVEC_NR) { /* Next buffer of IO vector */ 135 dp_next_iovec(iovp); 136 ix = 0; 137 } 138 /* Till packet done */ 139 } while ((pktsize -= bytes) > 0); 140 while ((padding++ % sizeof(long)) != 0) outb(dep->de_data_port, 0x00); 141 return; 142 } 143 144 /* 145 ** Name: void el3_recv(dpeth_t *dep, int fromint, int size) 146 ** Function: Receive function. Called from interrupt handler or 147 ** from main to unload recv. buffer (packet to client) 148 */ 149 static void el3_recv(dpeth_t *dep, int fromint, int size) 150 { 151 buff_t *rxptr; 152 153 while ((dep->de_flags & DEF_READING) && (rxptr = dep->de_recvq_head)) { 154 155 lock(); /* Remove buffer from queue */ 156 if (dep->de_recvq_tail == dep->de_recvq_head) 157 dep->de_recvq_head = dep->de_recvq_tail = NULL; 158 else 159 dep->de_recvq_head = rxptr->next; 160 unlock(); 161 162 /* Copy buffer to user area and free it */ 163 mem2user(dep, rxptr); 164 165 dep->de_read_s = rxptr->size; 166 dep->de_flags |= DEF_ACK_RECV; 167 dep->de_flags &= NOT(DEF_READING); 168 169 /* Return buffer to the idle pool */ 170 free_buff(dep, rxptr); 171 } 172 return; 173 } 174 175 /* 176 ** Name: void el3_rx_complete(dpeth_t * dep); 177 ** Function: Upon receiving a packet, provides status checks 178 ** and if packet is OK copies it to local buffer. 179 */ 180 static void el3_rx_complete(dpeth_t * dep) 181 { 182 short int RxStatus; 183 int pktsize; 184 buff_t *rxptr; 185 186 RxStatus = inw_el3(dep, REG_RxStatus); 187 pktsize = RxStatus & RXS_Length; /* Mask off packet length */ 188 189 if (RxStatus & RXS_Error) { 190 191 /* First checks for receiving errors */ 192 RxStatus &= RXS_ErrType; 193 switch (RxStatus) { /* Bad packet (see error type) */ 194 case RXS_Dribble: 195 case RXS_Oversize: 196 case RXS_Runt: dep->de_stat.ets_recvErr += 1; break; 197 case RXS_Overrun: dep->de_stat.ets_OVW += 1; break; 198 case RXS_Framing: dep->de_stat.ets_frameAll += 1; break; 199 case RXS_CRC: dep->de_stat.ets_CRCerr += 1; break; 200 } 201 202 } else if ((rxptr = alloc_buff(dep, pktsize + sizeof(buff_t))) == NULL) { 203 /* Memory not available. Drop packet */ 204 dep->de_stat.ets_fifoOver += 1; 205 206 } else { 207 /* Good packet. Read it from FIFO */ 208 insb(dep->de_data_port, SELF, rxptr->buffer, pktsize); 209 rxptr->next = NULL; 210 rxptr->size = pktsize; 211 212 lock(); /* Queue packet to receive queue */ 213 if (dep->de_recvq_head == NULL) 214 dep->de_recvq_head = rxptr; 215 else 216 dep->de_recvq_tail->next = rxptr; 217 dep->de_recvq_tail = rxptr; 218 unlock(); 219 220 /* Reply to pending Receive requests, if any */ 221 el3_recv(dep, TRUE, pktsize); 222 } 223 224 /* Discard top packet from queue */ 225 outw_el3(dep, REG_CmdStatus, CMD_RxDiscard); 226 227 return; 228 } 229 230 /* 231 ** Name: void el3_send(dpeth_t *dep, int count) 232 ** Function: Send function. Called from main to transit a packet or 233 ** from interrupt handler when Tx FIFO gets available. 234 */ 235 static void el3_send(dpeth_t * dep, int from_int, int count) 236 { 237 clock_t now; 238 int ix; 239 short int TxStatus; 240 241 getticks(&now); 242 if ((dep->de_flags & DEF_XMIT_BUSY) && 243 (now - dep->de_xmit_start) > 4) { 244 245 DEBUG(printf("3c509: Transmitter timed out. Resetting ....\n");) 246 dep->de_stat.ets_sendErr += 1; 247 /* Resets and restars the transmitter */ 248 outw_el3(dep, REG_CmdStatus, CMD_TxReset); 249 outw_el3(dep, REG_CmdStatus, CMD_TxEnable); 250 dep->de_flags &= NOT(DEF_XMIT_BUSY); 251 } 252 if (!(dep->de_flags & DEF_XMIT_BUSY)) { 253 254 /* Writes Transmitter preamble 1st Word (packet len, no ints) */ 255 outw_el3(dep, REG_TxFIFO, count); 256 /* Writes Transmitter preamble 2nd Word (all zero) */ 257 outw_el3(dep, REG_TxFIFO, 0); 258 /* Writes packet */ 259 el3_write_fifo(dep, count); 260 261 getticks(&dep->de_xmit_start); 262 dep->de_flags |= (DEF_XMIT_BUSY | DEF_ACK_SEND); 263 if (inw_el3(dep, REG_TxFree) > ETH_MAX_PACK_SIZE) { 264 /* Tx has enough room for a packet of maximum size */ 265 dep->de_flags &= NOT(DEF_XMIT_BUSY | DEF_SENDING); 266 } else { 267 /* Interrupt driver when enough room is available */ 268 outw_el3(dep, REG_CmdStatus, CMD_SetTxAvailable | ETH_MAX_PACK_SIZE); 269 dep->de_flags &= NOT(DEF_SENDING); 270 } 271 272 /* Pops Tx status stack */ 273 for (ix = 4; --ix && (TxStatus = inb_el3(dep, REG_TxStatus)) > 0;) { 274 if (TxStatus & 0x38) dep->de_stat.ets_sendErr += 1; 275 if (TxStatus & 0x30) 276 outw_el3(dep, REG_CmdStatus, CMD_TxReset); 277 if (TxStatus & 0x3C) 278 outw_el3(dep, REG_CmdStatus, CMD_TxEnable); 279 outb_el3(dep, REG_TxStatus, 0); 280 } 281 } 282 return; 283 } 284 285 /* 286 ** Name: void el3_close(dpeth_t *dep) 287 ** Function: Stops board and makes it ready to shut down. 288 */ 289 static void el3_close(dpeth_t * dep) 290 { 291 292 /* Disables statistics, Receiver and Transmitter */ 293 outw_el3(dep, REG_CmdStatus, CMD_StatsDisable); 294 outw_el3(dep, REG_CmdStatus, CMD_RxDisable); 295 outw_el3(dep, REG_CmdStatus, CMD_TxDisable); 296 297 if (dep->de_if_port == BNC_XCVR) { 298 outw_el3(dep, REG_CmdStatus, CMD_StopIntXcvr); 299 /* milli_delay(5); */ 300 301 } else if (dep->de_if_port == TP_XCVR) { 302 SetWindow(WNO_Diagnostics); 303 outw_el3(dep, REG_MediaStatus, inw_el3(dep, REG_MediaStatus) & 304 NOT((MediaLBeatEnable | MediaJabberEnable))); 305 /* milli_delay(5); */ 306 } 307 DEBUG(printf("%s: stopping Etherlink ... \n", dep->de_name)); 308 /* Issues a global reset 309 outw_el3(dep, REG_CmdStatus, CMD_GlobalReset); */ 310 sys_irqdisable(&dep->de_hook); /* Disable interrupt */ 311 312 return; 313 } 314 315 /* 316 ** Name: void el3_interrupt(dpeth_t *dep) 317 ** Function: Interrupt handler. Acknwledges transmit interrupts 318 ** or unloads receive buffer to memory queue. 319 */ 320 static void el3_interrupt(dpeth_t * dep) 321 { 322 int loop; 323 unsigned short isr; 324 325 for (loop = 5; loop > 0 && ((isr = inw_el3(dep, REG_CmdStatus)) & 326 (INT_Latch | INT_RxComplete | INT_UpdateStats)); loop -= 1) { 327 328 if (isr & INT_RxComplete) /* Got a new packet */ 329 el3_rx_complete(dep); 330 331 if (isr & INT_TxAvailable) { /* Tx has room for big packets */ 332 DEBUG(printf("3c509: got Tx interrupt, Status=0x%04x\n", isr);) 333 dep->de_flags &= NOT(DEF_XMIT_BUSY); 334 outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | INT_TxAvailable); 335 if (dep->de_flags & DEF_SENDING) /* Send pending */ 336 el3_send(dep, TRUE, dep->de_send_s); 337 } 338 if (isr & (INT_AdapterFail | INT_RxEarly | INT_UpdateStats)) { 339 340 if (isr & INT_UpdateStats) /* Empties statistics */ 341 el3_getstats(dep); 342 343 if (isr & INT_RxEarly) /* Not really used. Do nothing */ 344 outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | (INT_RxEarly)); 345 346 if (isr & INT_AdapterFail) { 347 /* Adapter error. Reset and re-enable receiver */ 348 DEBUG(printf("3c509: got Rx fail interrupt, Status=0x%04x\n", isr);) 349 el3_rx_mode(dep); 350 outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | INT_AdapterFail); 351 } 352 } 353 354 /* Acknowledge interrupt */ 355 outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | (INT_Latch | INT_Requested)); 356 } 357 return; 358 } 359 360 /* 361 ** Name: unsigned el3_read_eeprom(port_t port, unsigned address); 362 ** Function: Reads the EEPROM at specified address 363 */ 364 static unsigned el3_read_eeprom(port_t port, unsigned address) 365 { 366 unsigned int result; 367 int bit; 368 369 address |= EL3_READ_EEPROM; 370 outb(port, address); 371 milli_delay(5); /* Allows EEPROM reads */ 372 for (result = 0, bit = 16; bit > 0; bit -= 1) { 373 result = (result << 1) | (inb(port) & 0x0001); 374 } 375 return result; 376 } 377 378 /* 379 ** Name: void el3_read_StationAddress(dpeth_t *dep) 380 ** Function: Reads station address from board 381 */ 382 static void el3_read_StationAddress(dpeth_t * dep) 383 { 384 unsigned int ix, rc; 385 386 for (ix = EE_3COM_NODE_ADDR; ix < SA_ADDR_LEN+EE_3COM_NODE_ADDR;) { 387 /* Accesses with word No. */ 388 rc = el3_read_eeprom(dep->de_id_port, ix / 2); 389 /* Swaps bytes of word */ 390 dep->de_address.ea_addr[ix++] = (rc >> 8) & 0xFF; 391 dep->de_address.ea_addr[ix++] = rc & 0xFF; 392 } 393 return; 394 } 395 396 /* 397 ** Name: void el3_open(dpeth_t *dep) 398 ** Function: Initalizes board hardware and driver data structures. 399 */ 400 static void el3_open(dpeth_t * dep) 401 { 402 unsigned int AddrCfgReg, ResCfgReg; 403 unsigned int ix; 404 405 el3_read_StationAddress(dep); /* Get ethernet address */ 406 407 /* Get address and resource configurations */ 408 AddrCfgReg = el3_read_eeprom(dep->de_id_port, EE_ADDR_CFG); 409 ResCfgReg = el3_read_eeprom(dep->de_id_port, EE_RESOURCE_CFG); 410 outb(dep->de_id_port, EL3_ACTIVATE); /* Activate the board */ 411 412 /* Gets xcvr configuration */ 413 dep->de_if_port = AddrCfgReg & EL3_CONFIG_XCVR_MASK; 414 415 AddrCfgReg = ((AddrCfgReg & EL3_CONFIG_IOBASE_MASK) << 4) + EL3_IO_BASE_ADDR; 416 if (AddrCfgReg != dep->de_base_port) 417 panic("Bad I/O port for Etherlink board"); 418 419 ResCfgReg >>= 12; 420 dep->de_irq &= NOT(DEI_DEFAULT); /* Strips the default flag */ 421 if (ResCfgReg != dep->de_irq) panic("Bad IRQ for Etherlink board"); 422 423 SetWindow(WNO_Setup); 424 425 /* Reset transmitter and receiver */ 426 outw_el3(dep, REG_CmdStatus, CMD_TxReset); 427 outw_el3(dep, REG_CmdStatus, CMD_RxReset); 428 429 /* Enable the adapter */ 430 outb_el3(dep, REG_CfgControl, EL3_EnableAdapter); 431 /* Disable Status bits */ 432 outw_el3(dep, REG_CmdStatus, CMD_SetStatusEnab + 0x00); 433 434 /* Set "my own" address */ 435 SetWindow(WNO_StationAddress); 436 for (ix = 0; ix < 6; ix += 1) 437 outb_el3(dep, REG_SA0_1 + ix, dep->de_address.ea_addr[ix]); 438 439 /* Start Transceivers as required */ 440 if (dep->de_if_port == BNC_XCVR) { 441 /* Start internal transceiver for Coaxial cable */ 442 outw_el3(dep, REG_CmdStatus, CMD_StartIntXcvr); 443 milli_delay(5); 444 445 } else if (dep->de_if_port == TP_XCVR) { 446 /* Start internal transceiver for Twisted pair cable */ 447 SetWindow(WNO_Diagnostics); 448 outw_el3(dep, REG_MediaStatus, 449 inw_el3(dep, REG_MediaStatus) | (MediaLBeatEnable | MediaJabberEnable)); 450 } 451 452 /* Switch to the statistic window, and clear counts (by reading) */ 453 SetWindow(WNO_Statistics); 454 for (ix = REG_TxCarrierLost; ix <= REG_TxDefer; ix += 1) inb_el3(dep, ix); 455 inw_el3(dep, REG_RxBytes); 456 inw_el3(dep, REG_TxBytes); 457 458 /* Switch to operating window for normal use */ 459 SetWindow(WNO_Operating); 460 461 /* Receive individual address & broadcast. (Mofified later by rx_mode) */ 462 outw_el3(dep, REG_CmdStatus, CMD_SetRxFilter | 463 (FilterIndividual | FilterBroadcast)); 464 465 /* Turn on statistics */ 466 outw_el3(dep, REG_CmdStatus, CMD_StatsEnable); 467 468 /* Enable transmitter and receiver */ 469 outw_el3(dep, REG_CmdStatus, CMD_TxEnable); 470 outw_el3(dep, REG_CmdStatus, CMD_RxEnable); 471 472 /* Enable all the status bits */ 473 outw_el3(dep, REG_CmdStatus, CMD_SetStatusEnab | 0xFF); 474 475 /* Acknowledge all interrupts to clear adapter. Enable interrupts */ 476 outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | 0xFF); 477 outw_el3(dep, REG_CmdStatus, CMD_SetIntMask | 478 (INT_Latch | INT_TxAvailable | INT_RxComplete | INT_UpdateStats)); 479 480 /* Ready to operate, sets the environment for eth_task */ 481 dep->de_data_port = dep->de_base_port; 482 /* Allocates Rx/Tx buffers */ 483 init_buff(dep, NULL); 484 485 /* Device specific functions */ 486 dep->de_recvf = el3_recv; 487 dep->de_sendf = el3_send; 488 dep->de_flagsf = el3_rx_mode; 489 dep->de_resetf = el3_reset; 490 dep->de_getstatsf = el3_getstats; 491 dep->de_dumpstatsf = el3_dodump; 492 dep->de_interruptf = el3_interrupt; 493 494 printf("%s: Etherlink III (%s) at %X:%d, %s port - ", 495 dep->de_name, "3c509", dep->de_base_port, dep->de_irq, 496 IfNamesMsg[dep->de_if_port >> 14]); 497 for (ix = 0; ix < SA_ADDR_LEN; ix += 1) 498 printf("%02X%c", dep->de_address.ea_addr[ix], 499 ix < SA_ADDR_LEN - 1 ? ':' : '\n'); 500 501 return; /* Done */ 502 } 503 504 /* 505 ** Name: unsigned int el3_checksum(port_t port); 506 ** Function: Reads EEPROM and computes checksum. 507 */ 508 static unsigned short el3_checksum(port_t port) 509 { 510 unsigned short rc, checksum, address; 511 unsigned char lo, hi; 512 513 for (checksum = address = 0; address < 15; address += 1) { 514 rc = el3_read_eeprom(port, address); 515 lo = rc & 0xFF; 516 hi = (rc >> 8) & 0xFF; 517 if ((address == EE_PROD_ID && (rc & EE_PROD_ID_MASK) != EL3_PRODUCT_ID) || 518 (address == EE_3COM_CODE && rc != EL3_3COM_CODE)) 519 return address; 520 if (address == EE_ADDR_CFG || 521 address == EE_RESOURCE_CFG || 522 address == EE_SW_CONFIG_INFO) { 523 lo ^= hi; 524 hi = 0; 525 } else { 526 hi ^= lo; 527 lo = 0; 528 } 529 rc = ((unsigned) hi << 8) + lo; 530 checksum ^= rc; 531 } 532 rc = el3_read_eeprom(port, address); 533 return(checksum ^= rc); /* If OK checksum is 0 */ 534 } 535 536 /* 537 ** Name: void el3_write_id(port_t port); 538 ** Function: Writes the ID sequence to the board. 539 */ 540 static void el3_write_id(port_t port) 541 { 542 int ix, pattern; 543 544 outb(port, 0); /* Selects the ID port */ 545 outb(port, 0); /* Resets hardware pattern generator */ 546 for (pattern = ix = 0x00FF; ix > 0; ix -= 1) { 547 outb(port, pattern); 548 pattern <<= 1; 549 pattern = (pattern & 0x0100) ? pattern ^ 0xCF : pattern; 550 } 551 return; 552 } 553 554 /* 555 ** Name: int el3_probe(dpeth_t *dep) 556 ** Function: Checks for presence of the board. 557 */ 558 int el3_probe(dpeth_t * dep) 559 { 560 port_t id_port; 561 562 /* Don't ask me what is this for !! */ 563 outb(0x0279, 0x02); /* Select PnP config control register. */ 564 outb(0x0A79, 0x02); /* Return to WaitForKey state. */ 565 /* Tests I/O ports in the 0x1xF range for a valid ID port */ 566 for (id_port = 0x110; id_port < 0x200; id_port += 0x10) { 567 outb(id_port, 0x00); 568 outb(id_port, 0xFF); 569 if (inb(id_port) & 0x01) break; 570 } 571 if (id_port == 0x200) return 0; /* No board responding */ 572 573 el3_write_id(id_port); 574 outb(id_port, EL3_ID_GLOBAL_RESET); /* Reset the board */ 575 milli_delay(5); /* Technical reference says 162 micro sec. */ 576 el3_write_id(id_port); 577 outb(id_port, EL3_SET_TAG_REGISTER); 578 milli_delay(5); 579 580 dep->de_id_port = id_port; /* Stores ID port No. */ 581 dep->de_ramsize = /* RAM size is meaningless */ 582 dep->de_offset_page = 0; 583 dep->de_linmem = 0L; /* Access is via I/O port */ 584 585 /* Device specific functions */ 586 dep->de_initf = el3_open; 587 dep->de_stopf = el3_close; 588 589 return(el3_checksum(id_port) == 0); /* Etherlink board found/not found */ 590 } 591 592 #endif /* ENABLE_3C509 */ 593 594 /** 3c509.c **/ 595