1 /* cs89x0.c: A Crystal Semiconductor CS89[02]0 driver for etherboot. */ 2 /* 3 Permission is granted to distribute the enclosed cs89x0.[ch] driver 4 only in conjunction with the Etherboot package. The code is 5 ordinarily distributed under the GPL. 6 7 Russ Nelson, January 2000 8 9 ChangeLog: 10 11 Thu Dec 6 22:40:00 1996 Markus Gutschke <gutschk (at) math.uni-muenster.de> 12 13 * disabled all "advanced" features; this should make the code more reliable 14 15 * reorganized the reset function 16 17 * always reset the address port, so that autoprobing will continue working 18 19 * some cosmetic changes 20 21 * 2.5 22 23 Thu Dec 5 21:00:00 1996 Markus Gutschke <gutschk (at) math.uni-muenster.de> 24 25 * tested the code against a CS8900 card 26 27 * lots of minor bug fixes and adjustments 28 29 * this is the first release, that actually works! it still requires some 30 changes in order to be more tolerant to different environments 31 32 * 4 33 34 Fri Nov 22 23:00:00 1996 Markus Gutschke <gutschk (at) math.uni-muenster.de> 35 36 * read the manuals for the CS89x0 chipsets and took note of all the 37 changes that will be neccessary in order to adapt Russel Nelson's code 38 to the requirements of a BOOT-Prom 39 40 * 6 41 42 Thu Nov 19 22:00:00 1996 Markus Gutschke <gutschk (at) math.uni-muenster.de> 43 44 * Synched with Russel Nelson's current code (v1.00) 45 46 * 2 47 48 Thu Nov 12 18:00:00 1996 Markus Gutschke <gutschk (at) math.uni-muenster.de> 49 50 * Cleaned up some of the code and tried to optimize the code size. 51 52 * 1.5 53 54 Sun Nov 10 16:30:00 1996 Markus Gutschke <gutschk (at) math.uni-muenster.de> 55 56 * First experimental release. This code compiles fine, but I 57 have no way of testing whether it actually works. 58 59 * I did not (yet) bother to make the code 16bit aware, so for 60 the time being, it will only work for Etherboot/32. 61 62 * 12 63 64 */ 65 66 #include "etherboot.h" 67 #include "nic.h" 68 #include "cards.h" 69 #include "cs89x0.h" 70 71 static unsigned short eth_nic_base; 72 static unsigned long eth_mem_start; 73 static unsigned short eth_irq; 74 static unsigned short eth_cs_type; /* one of: CS8900, CS8920, CS8920M */ 75 static unsigned short eth_auto_neg_cnf; 76 static unsigned short eth_adapter_cnf; 77 static unsigned short eth_linectl; 78 79 /************************************************************************* 80 CS89x0 - specific routines 81 **************************************************************************/ 82 83 static inline int readreg(int portno) 84 { 85 outw(portno, eth_nic_base + ADD_PORT); 86 return inw(eth_nic_base + DATA_PORT); 87 } 88 89 static inline void writereg(int portno, int value) 90 { 91 outw(portno, eth_nic_base + ADD_PORT); 92 outw(value, eth_nic_base + DATA_PORT); 93 return; 94 } 95 96 /************************************************************************* 97 EEPROM access 98 **************************************************************************/ 99 100 static int wait_eeprom_ready(void) 101 { 102 unsigned long tmo = currticks() + 4*TICKS_PER_SEC; 103 104 /* check to see if the EEPROM is ready, a timeout is used - 105 just in case EEPROM is ready when SI_BUSY in the 106 PP_SelfST is clear */ 107 while(readreg(PP_SelfST) & SI_BUSY) { 108 if (currticks() >= tmo) 109 return -1; } 110 return 0; 111 } 112 113 static int get_eeprom_data(int off, int len, unsigned short *buffer) 114 { 115 int i; 116 117 #ifdef EDEBUG 118 printf("\ncs: EEPROM data from %hX for %hX:",off,len); 119 #endif 120 for (i = 0; i < len; i++) { 121 if (wait_eeprom_ready() < 0) 122 return -1; 123 /* Now send the EEPROM read command and EEPROM location 124 to read */ 125 writereg(PP_EECMD, (off + i) | EEPROM_READ_CMD); 126 if (wait_eeprom_ready() < 0) 127 return -1; 128 buffer[i] = readreg(PP_EEData); 129 #ifdef EDEBUG 130 if (!(i%10)) 131 printf("\ncs: "); 132 printf("%hX ", buffer[i]); 133 #endif 134 } 135 #ifdef EDEBUG 136 putchar('\n'); 137 #endif 138 139 return(0); 140 } 141 142 static int get_eeprom_chksum(int off, int len, unsigned short *buffer) 143 { 144 int i, cksum; 145 146 cksum = 0; 147 for (i = 0; i < len; i++) 148 cksum += buffer[i]; 149 cksum &= 0xffff; 150 if (cksum == 0) 151 return 0; 152 return -1; 153 } 154 155 /************************************************************************* 156 Activate all of the available media and probe for network 157 **************************************************************************/ 158 159 static void clrline(void) 160 { 161 int i; 162 163 putchar('\r'); 164 for (i = 79; i--; ) putchar(' '); 165 printf("\rcs: "); 166 return; 167 } 168 169 static void control_dc_dc(int on_not_off) 170 { 171 unsigned int selfcontrol; 172 unsigned long tmo = currticks() + TICKS_PER_SEC; 173 174 /* control the DC to DC convertor in the SelfControl register. */ 175 selfcontrol = HCB1_ENBL; /* Enable the HCB1 bit as an output */ 176 if (((eth_adapter_cnf & A_CNF_DC_DC_POLARITY) != 0) ^ on_not_off) 177 selfcontrol |= HCB1; 178 else 179 selfcontrol &= ~HCB1; 180 writereg(PP_SelfCTL, selfcontrol); 181 182 /* Wait for the DC/DC converter to power up - 1000ms */ 183 while (currticks() < tmo); 184 185 return; 186 } 187 188 static int detect_tp(void) 189 { 190 unsigned long tmo; 191 192 /* Turn on the chip auto detection of 10BT/ AUI */ 193 194 clrline(); printf("attempting %s:","TP"); 195 196 /* If connected to another full duplex capable 10-Base-T card 197 the link pulses seem to be lost when the auto detect bit in 198 the LineCTL is set. To overcome this the auto detect bit 199 will be cleared whilst testing the 10-Base-T interface. 200 This would not be necessary for the sparrow chip but is 201 simpler to do it anyway. */ 202 writereg(PP_LineCTL, eth_linectl &~ AUI_ONLY); 203 control_dc_dc(0); 204 205 /* Delay for the hardware to work out if the TP cable is 206 present - 150ms */ 207 for (tmo = currticks() + 4; currticks() < tmo; ); 208 209 if ((readreg(PP_LineST) & LINK_OK) == 0) 210 return 0; 211 212 if (eth_cs_type != CS8900) { 213 214 writereg(PP_AutoNegCTL, eth_auto_neg_cnf & AUTO_NEG_MASK); 215 216 if ((eth_auto_neg_cnf & AUTO_NEG_BITS) == AUTO_NEG_ENABLE) { 217 printf(" negotiating duplex... "); 218 while (readreg(PP_AutoNegST) & AUTO_NEG_BUSY) { 219 if (currticks() - tmo > 40*TICKS_PER_SEC) { 220 printf("time out "); 221 break; 222 } 223 } 224 } 225 if (readreg(PP_AutoNegST) & FDX_ACTIVE) 226 printf("using full duplex"); 227 else 228 printf("using half duplex"); 229 } 230 231 return A_CNF_MEDIA_10B_T; 232 } 233 234 /* send a test packet - return true if carrier bits are ok */ 235 static int send_test_pkt(struct nic *nic) 236 { 237 static unsigned char testpacket[] = { 0,0,0,0,0,0, 0,0,0,0,0,0, 238 0, 46, /*A 46 in network order */ 239 0, 0, /*DSAP=0 & SSAP=0 fields */ 240 0xf3,0 /*Control (Test Req+P bit set)*/ }; 241 unsigned long tmo; 242 243 writereg(PP_LineCTL, readreg(PP_LineCTL) | SERIAL_TX_ON); 244 245 memcpy(testpacket, nic->node_addr, ETH_ALEN); 246 memcpy(testpacket+ETH_ALEN, nic->node_addr, ETH_ALEN); 247 248 outw(TX_AFTER_ALL, eth_nic_base + TX_CMD_PORT); 249 outw(ETH_ZLEN, eth_nic_base + TX_LEN_PORT); 250 251 /* Test to see if the chip has allocated memory for the packet */ 252 for (tmo = currticks() + 2; 253 (readreg(PP_BusST) & READY_FOR_TX_NOW) == 0; ) 254 if (currticks() >= tmo) 255 return(0); 256 257 /* Write the contents of the packet */ 258 outsw(eth_nic_base + TX_FRAME_PORT, testpacket, 259 (ETH_ZLEN+1)>>1); 260 261 printf(" sending test packet "); 262 /* wait a couple of timer ticks for packet to be received */ 263 for (tmo = currticks() + 2; currticks() < tmo; ); 264 265 if ((readreg(PP_TxEvent) & TX_SEND_OK_BITS) == TX_OK) { 266 printf("succeeded"); 267 return 1; 268 } 269 printf("failed"); 270 return 0; 271 } 272 273 274 static int detect_aui(struct nic *nic) 275 { 276 clrline(); printf("attempting %s:","AUI"); 277 control_dc_dc(0); 278 279 writereg(PP_LineCTL, (eth_linectl & ~AUTO_AUI_10BASET) | AUI_ONLY); 280 281 if (send_test_pkt(nic)) { 282 return A_CNF_MEDIA_AUI; } 283 else 284 return 0; 285 } 286 287 static int detect_bnc(struct nic *nic) 288 { 289 clrline(); printf("attempting %s:","BNC"); 290 control_dc_dc(1); 291 292 writereg(PP_LineCTL, (eth_linectl & ~AUTO_AUI_10BASET) | AUI_ONLY); 293 294 if (send_test_pkt(nic)) { 295 return A_CNF_MEDIA_10B_2; } 296 else 297 return 0; 298 } 299 300 /************************************************************************** 301 ETH_RESET - Reset adapter 302 ***************************************************************************/ 303 304 static void cs89x0_reset(struct nic *nic) 305 { 306 int i; 307 unsigned long reset_tmo; 308 309 writereg(PP_SelfCTL, readreg(PP_SelfCTL) | POWER_ON_RESET); 310 311 /* wait for two ticks; that is 2*55ms */ 312 for (reset_tmo = currticks() + 2; currticks() < reset_tmo; ); 313 314 if (eth_cs_type != CS8900) { 315 /* Hardware problem requires PNP registers to be reconfigured 316 after a reset */ 317 if (eth_irq != 0xFFFF) { 318 outw(PP_CS8920_ISAINT, eth_nic_base + ADD_PORT); 319 outb(eth_irq, eth_nic_base + DATA_PORT); 320 outb(0, eth_nic_base + DATA_PORT + 1); } 321 322 if (eth_mem_start) { 323 outw(PP_CS8920_ISAMemB, eth_nic_base + ADD_PORT); 324 outb((eth_mem_start >> 8) & 0xff, eth_nic_base + DATA_PORT); 325 outb((eth_mem_start >> 24) & 0xff, eth_nic_base + DATA_PORT + 1); } } 326 327 /* Wait until the chip is reset */ 328 for (reset_tmo = currticks() + 2; 329 (readreg(PP_SelfST) & INIT_DONE) == 0 && 330 currticks() < reset_tmo; ); 331 332 /* disable interrupts and memory accesses */ 333 writereg(PP_BusCTL, 0); 334 335 /* set the ethernet address */ 336 for (i=0; i < ETH_ALEN/2; i++) 337 writereg(PP_IA+i*2, 338 nic->node_addr[i*2] | 339 (nic->node_addr[i*2+1] << 8)); 340 341 /* receive only error free packets addressed to this card */ 342 writereg(PP_RxCTL, DEF_RX_ACCEPT); 343 344 /* do not generate any interrupts on receive operations */ 345 writereg(PP_RxCFG, 0); 346 347 /* do not generate any interrupts on transmit operations */ 348 writereg(PP_TxCFG, 0); 349 350 /* do not generate any interrupts on buffer operations */ 351 writereg(PP_BufCFG, 0); 352 353 /* reset address port, so that autoprobing will keep working */ 354 outw(PP_ChipID, eth_nic_base + ADD_PORT); 355 356 return; 357 } 358 359 /************************************************************************** 360 ETH_TRANSMIT - Transmit a frame 361 ***************************************************************************/ 362 363 static void cs89x0_transmit( 364 struct nic *nic, 365 const char *d, /* Destination */ 366 unsigned int t, /* Type */ 367 unsigned int s, /* size */ 368 const char *p) /* Packet */ 369 { 370 unsigned long tmo; 371 int sr; 372 373 /* does this size have to be rounded??? please, 374 somebody have a look in the specs */ 375 if ((sr = ((s + ETH_HLEN + 1)&~1)) < ETH_ZLEN) 376 sr = ETH_ZLEN; 377 378 retry: 379 /* initiate a transmit sequence */ 380 outw(TX_AFTER_ALL, eth_nic_base + TX_CMD_PORT); 381 outw(sr, eth_nic_base + TX_LEN_PORT); 382 383 /* Test to see if the chip has allocated memory for the packet */ 384 if ((readreg(PP_BusST) & READY_FOR_TX_NOW) == 0) { 385 /* Oops... this should not happen! */ 386 printf("cs: unable to send packet; retrying...\n"); 387 for (tmo = currticks() + 5*TICKS_PER_SEC; currticks() < tmo; ); 388 cs89x0_reset(nic); 389 goto retry; } 390 391 /* Write the contents of the packet */ 392 outsw(eth_nic_base + TX_FRAME_PORT, d, ETH_ALEN/2); 393 outsw(eth_nic_base + TX_FRAME_PORT, nic->node_addr, 394 ETH_ALEN/2); 395 outw(((t >> 8)&0xFF)|(t << 8), eth_nic_base + TX_FRAME_PORT); 396 outsw(eth_nic_base + TX_FRAME_PORT, p, (s+1)/2); 397 for (sr = sr/2 - (s+1)/2 - ETH_ALEN - 1; sr-- > 0; 398 outw(0, eth_nic_base + TX_FRAME_PORT)); 399 400 /* wait for transfer to succeed */ 401 for (tmo = currticks()+5*TICKS_PER_SEC; 402 (s = readreg(PP_TxEvent)&~0x1F) == 0 && currticks() < tmo;) 403 /* nothing */ ; 404 if ((s & TX_SEND_OK_BITS) != TX_OK) { 405 printf("\ntransmission error %#hX\n", s); 406 } 407 408 return; 409 } 410 411 /************************************************************************** 412 ETH_POLL - Wait for a frame 413 ***************************************************************************/ 414 415 static int cs89x0_poll(struct nic *nic) 416 { 417 int status; 418 419 status = readreg(PP_RxEvent); 420 421 if ((status & RX_OK) == 0) 422 return(0); 423 424 status = inw(eth_nic_base + RX_FRAME_PORT); 425 nic->packetlen = inw(eth_nic_base + RX_FRAME_PORT); 426 insw(eth_nic_base + RX_FRAME_PORT, nic->packet, nic->packetlen >> 1); 427 if (nic->packetlen & 1) 428 nic->packet[nic->packetlen-1] = inw(eth_nic_base + RX_FRAME_PORT); 429 return 1; 430 } 431 432 static void cs89x0_disable(struct nic *nic) 433 { 434 cs89x0_reset(nic); 435 } 436 437 /************************************************************************** 438 ETH_PROBE - Look for an adapter 439 ***************************************************************************/ 440 441 struct nic *cs89x0_probe(struct nic *nic, unsigned short *probe_addrs) 442 { 443 static const unsigned int netcard_portlist[] = { 444 #ifdef CS_SCAN 445 CS_SCAN, 446 #else /* use "conservative" default values for autoprobing */ 447 0x300,0x320,0x340,0x200,0x220,0x240, 448 0x260,0x280,0x2a0,0x2c0,0x2e0, 449 /* if that did not work, then be more aggressive */ 450 0x301,0x321,0x341,0x201,0x221,0x241, 451 0x261,0x281,0x2a1,0x2c1,0x2e1, 452 #endif 453 0}; 454 455 int i, result = -1; 456 unsigned rev_type = 0, ioaddr, ioidx, isa_cnf, cs_revision; 457 unsigned short eeprom_buff[CHKSUM_LEN]; 458 459 460 for (ioidx = 0; (ioaddr=netcard_portlist[ioidx++]) != 0; ) { 461 /* if they give us an odd I/O address, then do ONE write to 462 the address port, to get it back to address zero, where we 463 expect to find the EISA signature word. */ 464 if (ioaddr & 1) { 465 ioaddr &= ~1; 466 if ((inw(ioaddr + ADD_PORT) & ADD_MASK) != ADD_SIG) 467 continue; 468 outw(PP_ChipID, ioaddr + ADD_PORT); 469 } 470 471 if (inw(ioaddr + DATA_PORT) != CHIP_EISA_ID_SIG) 472 continue; 473 eth_nic_base = ioaddr; 474 475 /* get the chip type */ 476 rev_type = readreg(PRODUCT_ID_ADD); 477 eth_cs_type = rev_type &~ REVISON_BITS; 478 cs_revision = ((rev_type & REVISON_BITS) >> 8) + 'A'; 479 480 printf("\ncs: cs89%c0%s rev %c, base %#hX", 481 eth_cs_type==CS8900?'0':'2', 482 eth_cs_type==CS8920M?"M":"", 483 cs_revision, 484 eth_nic_base); 485 486 /* First check to see if an EEPROM is attached*/ 487 if ((readreg(PP_SelfST) & EEPROM_PRESENT) == 0) { 488 printf("\ncs: no EEPROM...\n"); 489 outw(PP_ChipID, eth_nic_base + ADD_PORT); 490 continue; } 491 else if (get_eeprom_data(START_EEPROM_DATA,CHKSUM_LEN, 492 eeprom_buff) < 0) { 493 printf("\ncs: EEPROM read failed...\n"); 494 outw(PP_ChipID, eth_nic_base + ADD_PORT); 495 continue; } 496 else if (get_eeprom_chksum(START_EEPROM_DATA,CHKSUM_LEN, 497 eeprom_buff) < 0) { 498 printf("\ncs: EEPROM checksum bad...\n"); 499 outw(PP_ChipID, eth_nic_base + ADD_PORT); 500 continue; } 501 502 /* get transmission control word but keep the 503 autonegotiation bits */ 504 eth_auto_neg_cnf = eeprom_buff[AUTO_NEG_CNF_OFFSET/2]; 505 /* Store adapter configuration */ 506 eth_adapter_cnf = eeprom_buff[ADAPTER_CNF_OFFSET/2]; 507 /* Store ISA configuration */ 508 isa_cnf = eeprom_buff[ISA_CNF_OFFSET/2]; 509 510 /* store the initial memory base address */ 511 eth_mem_start = eeprom_buff[PACKET_PAGE_OFFSET/2] << 8; 512 513 printf("%s%s%s, addr ", 514 (eth_adapter_cnf & A_CNF_10B_T)?", RJ-45":"", 515 (eth_adapter_cnf & A_CNF_AUI)?", AUI":"", 516 (eth_adapter_cnf & A_CNF_10B_2)?", BNC":""); 517 518 /* If this is a CS8900 then no pnp soft */ 519 if (eth_cs_type != CS8900 && 520 /* Check if the ISA IRQ has been set */ 521 (i = readreg(PP_CS8920_ISAINT) & 0xff, 522 (i != 0 && i < CS8920_NO_INTS))) 523 eth_irq = i; 524 else { 525 i = isa_cnf & INT_NO_MASK; 526 if (eth_cs_type == CS8900) { 527 /* the table that follows is dependent 528 upon how you wired up your cs8900 529 in your system. The table is the 530 same as the cs8900 engineering demo 531 board. irq_map also depends on the 532 contents of the table. Also see 533 write_irq, which is the reverse 534 mapping of the table below. */ 535 if (i < 4) i = "\012\013\014\005"[i]; 536 else printf("\ncs: BUG: isa_config is %d\n", i); } 537 eth_irq = i; } 538 539 /* Retrieve and print the ethernet address. */ 540 for (i=0; i<ETH_ALEN; i++) { 541 nic->node_addr[i] = ((unsigned char *)eeprom_buff)[i]; 542 } 543 printf("%!\n", nic->node_addr); 544 545 /* Set the LineCTL quintuplet based on adapter 546 configuration read from EEPROM */ 547 if ((eth_adapter_cnf & A_CNF_EXTND_10B_2) && 548 (eth_adapter_cnf & A_CNF_LOW_RX_SQUELCH)) 549 eth_linectl = LOW_RX_SQUELCH; 550 else 551 eth_linectl = 0; 552 553 /* check to make sure that they have the "right" 554 hardware available */ 555 switch(eth_adapter_cnf & A_CNF_MEDIA_TYPE) { 556 case A_CNF_MEDIA_10B_T: result = eth_adapter_cnf & A_CNF_10B_T; 557 break; 558 case A_CNF_MEDIA_AUI: result = eth_adapter_cnf & A_CNF_AUI; 559 break; 560 case A_CNF_MEDIA_10B_2: result = eth_adapter_cnf & A_CNF_10B_2; 561 break; 562 default: result = eth_adapter_cnf & (A_CNF_10B_T | A_CNF_AUI | 563 A_CNF_10B_2); 564 } 565 if (!result) { 566 printf("cs: EEPROM is configured for unavailable media\n"); 567 error: 568 writereg(PP_LineCTL, readreg(PP_LineCTL) & 569 ~(SERIAL_TX_ON | SERIAL_RX_ON)); 570 outw(PP_ChipID, eth_nic_base + ADD_PORT); 571 continue; 572 } 573 574 /* Initialize the card for probing of the attached media */ 575 cs89x0_reset(nic); 576 577 /* set the hardware to the configured choice */ 578 switch(eth_adapter_cnf & A_CNF_MEDIA_TYPE) { 579 case A_CNF_MEDIA_10B_T: 580 result = detect_tp(); 581 if (!result) { 582 clrline(); 583 printf("10Base-T (RJ-45%s", 584 ") has no cable\n"); } 585 /* check "ignore missing media" bit */ 586 if (eth_auto_neg_cnf & IMM_BIT) 587 /* Yes! I don't care if I see a link pulse */ 588 result = A_CNF_MEDIA_10B_T; 589 break; 590 case A_CNF_MEDIA_AUI: 591 result = detect_aui(nic); 592 if (!result) { 593 clrline(); 594 printf("10Base-5 (AUI%s", 595 ") has no cable\n"); } 596 /* check "ignore missing media" bit */ 597 if (eth_auto_neg_cnf & IMM_BIT) 598 /* Yes! I don't care if I see a carrrier */ 599 result = A_CNF_MEDIA_AUI; 600 break; 601 case A_CNF_MEDIA_10B_2: 602 result = detect_bnc(nic); 603 if (!result) { 604 clrline(); 605 printf("10Base-2 (BNC%s", 606 ") has no cable\n"); } 607 /* check "ignore missing media" bit */ 608 if (eth_auto_neg_cnf & IMM_BIT) 609 /* Yes! I don't care if I can xmit a packet */ 610 result = A_CNF_MEDIA_10B_2; 611 break; 612 case A_CNF_MEDIA_AUTO: 613 writereg(PP_LineCTL, eth_linectl | AUTO_AUI_10BASET); 614 if (eth_adapter_cnf & A_CNF_10B_T) 615 if ((result = detect_tp()) != 0) 616 break; 617 if (eth_adapter_cnf & A_CNF_AUI) 618 if ((result = detect_aui(nic)) != 0) 619 break; 620 if (eth_adapter_cnf & A_CNF_10B_2) 621 if ((result = detect_bnc(nic)) != 0) 622 break; 623 clrline(); printf("no media detected\n"); 624 goto error; 625 } 626 clrline(); 627 switch(result) { 628 case 0: printf("no network cable attached to configured media\n"); 629 goto error; 630 case A_CNF_MEDIA_10B_T: printf("using 10Base-T (RJ-45)\n"); 631 break; 632 case A_CNF_MEDIA_AUI: printf("using 10Base-5 (AUI)\n"); 633 break; 634 case A_CNF_MEDIA_10B_2: printf("using 10Base-2 (BNC)\n"); 635 break; 636 } 637 638 /* Turn on both receive and transmit operations */ 639 writereg(PP_LineCTL, readreg(PP_LineCTL) | SERIAL_RX_ON | 640 SERIAL_TX_ON); 641 642 break; 643 } 644 645 if (ioaddr == 0) 646 return (0); 647 nic->reset = cs89x0_reset; 648 nic->poll = cs89x0_poll; 649 nic->transmit = cs89x0_transmit; 650 nic->disable = cs89x0_disable; 651 return (nic); 652 } 653 654 /* 655 * Local variables: 656 * c-basic-offset: 8 657 * End: 658 */ 659 660