1 /* 2 * ASIX AX88172/AX88772/AX88178 USB 2.0 Ethernet Driver. 3 * Copyright (c) 2008, 2011 S.Zharski <imker@gmx.li> 4 * Distributed under the terms of the MIT license. 5 * 6 * Heavily based on code of the 7 * Driver for USB Ethernet Control Model devices 8 * Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch> 9 * Distributed under the terms of the MIT license. 10 * 11 */ 12 13 14 #include "AX88772Device.h" 15 16 #include <net/if_media.h> 17 18 #include "ASIXVendorRequests.h" 19 #include "Settings.h" 20 21 22 // Most of vendor requests for all supported chip types use the same 23 // constants (see ASIXVendorRequests.h) but the layout of request data 24 // may be slightly diferrent for specific chip type. Below is a quick 25 // reference for AX88772 vendor requests data layout. 26 27 // READ_RXTX_SRAM, //C002_AA0B_0C00_0800 Rx/Tx SRAM Read 28 // WRITE_RXTX_SRAM, //4003_AA0B_0C00_0800 Rx/Tx SRAM Write 29 // SW_MII_OP, //4006_0000_0000_0000 SW Serial Management Control 30 // READ_MII, //c007_aa00_cc00_0200 PHY Read 31 // WRITE_MII, //4008_aa00_cc00_0200 PHY Write 32 // READ_MII_OP_MODE, //c009_0000_0000_0100 Serial Management Status 33 // HW_MII_OP, //400a_0000_0000_0000 HW Serial Management Control 34 // READ_SROM, //C00B_AA00_0000_0200 SROM Read 35 // WRITE_SROM, //400C_AA00_CCDD_0000 SROM Write 36 // WRITE_SROM_ENABLE, //400D_0000_0000_0000 SROM Write Enable 37 // WRITE_SROM_DISABLE, //400E_0000_0000_0000 SROM Write Disable 38 // READ_RX_CONTROL, //C00F_0000_0000_0200 Read Rx Control 39 // WRITE_RX_CONTROL, //4010_AABB_0000_0000 Write Rx Control 40 // READ_IPGS, //C011_0000_0000_0300 Read IPG/IPG1/IPG2 Register 41 // WRITE_IPGS, //4012_AABB_CC00_0000 Write IPG/IPG1/IPG2 Register 42 // READ_NODEID, //C013_0000_0000_0600 Read Node ID 43 // WRITE_NODEID, //4014_0000_0000_0600 Write Node ID 44 // READ_MF_ARRAY, //C015_0000_0000_0800 Read Multicast Filter Array 45 // WRITE_MF_ARRAY, //4016_0000_0000_0800 Write Multicast Filter Array 46 // READ_TEST, //4017_AA00_0000_0000 Write Test Register 47 // READ_PHYID, //C019_0000_0000_0200 Read Ethernet/HomePNA PHY Address 48 // READ_MEDIUM_STATUS, //C01A_0000_0000_0200 Read Medium Status 49 // WRITE_MEDIUM_MODE, //401B_AABB_0000_0000 Write Medium Mode Register 50 // GET_MONITOR_MODE, //C01C_0000_0000_0100 Read Monitor Mode Status 51 // SET_MONITOR_MODE, //401D_AA00_0000_0000 Write Monitor Mode Register 52 // READ_GPIOS, //C01E_0000_0000_0100 Read GPIOs Status 53 // WRITE_GPIOS, //401F_AA00_0000_0000 Write GPIOs 54 // WRITE_SOFT_RESET, //4020_AA00_0000_0000 Write Software Reset 55 // READ_PHY_SEL_STATE, //C021_AA00_0000_0100 Read Software PHY Select Status 56 // WRITE_PHY_SEL, //4022_AA00_0000_0000 Write Software PHY Select 57 58 // RX Control Register bits 59 // RXCTL_PROMISCUOUS, // forward all frames up to the host 60 // RXCTL_ALL_MULTICAT, // forward all multicast frames up to the host 61 // RXCTL_SEP, // forward frames with CRC error up to the host 62 // RXCTL_BROADCAST, // forward broadcast frames up to the host 63 // RXCTL_MULTICAST, // forward multicast frames that are 64 // matching to multicast filter up to the host 65 // RXCTL_AP, // forward unicast frames that are matching 66 // to multicast filter up to the host 67 // RXCTL_START, // ethernet MAC start operating 68 // RXCTL_USB_MFB, // Max Frame Burst TX on USB 69 70 71 // PHY IDs request answer data layout 72 struct AX88772_PhyIDs { 73 uint8 SecPhyID; 74 uint8 PriPhyID2; 75 } _PACKED; 76 77 78 // Medium state bits 79 enum AX88772_MediumState { 80 MEDIUM_STATE_FD = 0x0002, 81 MEDIUM_STATE_BIT2 = 0x0004, // must be always set 82 MEDIUM_STATE_RFC = 0x0010, 83 MEDIUM_STATE_TFC = 0x0020, 84 MEDIUM_STATE_PF_ON = 0x0040, 85 MEDIUM_STATE_PF_OFF = 0x0000, 86 MEDIUM_STATE_RE = 0x0100, 87 MEDIUM_STATE_PS_100 = 0x0200, 88 MEDIUM_STATE_PS_10 = 0x0000, 89 MEDIUM_STATE_SBP1 = 0x0800, 90 MEDIUM_STATE_SBP0 = 0x0000, 91 MEDIUM_STATE_SM_ON = 0x1000 92 }; 93 94 95 // Monitor Mode bits 96 enum AX88772_MonitorMode { 97 MONITOR_MODE_MOM = 0x01, 98 MONITOR_MODE_RWLU = 0x02, 99 MONITOR_MODE_RWMP = 0x04, 100 MONITOR_MODE_US = 0x10 101 }; 102 103 104 // General Purpose I/O Register 105 enum AX88772_GPIO { 106 GPIO_OO_0EN = 0x01, 107 GPIO_IO_0 = 0x02, 108 GPIO_OO_1EN = 0x04, 109 GPIO_IO_1 = 0x08, 110 GPIO_OO_2EN = 0x10, 111 GPIO_IO_2 = 0x20, 112 GPIO_RSE = 0x80 113 }; 114 115 116 // Software Reset Register bits 117 enum AX88772_SoftwareReset { 118 SW_RESET_CLR = 0x00, 119 SW_RESET_RR = 0x01, 120 SW_RESET_RT = 0x02, 121 SW_RESET_PRTE = 0x04, 122 SW_RESET_PRL = 0x08, 123 SW_RESET_BZ = 0x10, 124 SW_RESET_IPRL = 0x20, 125 SW_RESET_IPPD = 0x40 126 }; 127 128 129 // Software PHY Select Status 130 enum AX88772_SoftwarePHYSelStatus { 131 SW_PHY_SEL_STATUS_EXT = 0x00, 132 SW_PHY_SEL_STATUS_INT = 0x01, 133 SW_PHY_SEL_STATUS_ASEL = 0x02, 134 SW_PHY_SEL_STATUS_SS_MII = 0x04, 135 SW_PHY_SEL_STATUS_SS_RVRS_MII = 0x08, 136 SW_PHY_SEL_STATUS_SS_RVRS_GMII = 0x0C, 137 SW_PHY_SEL_STATUS_SS_ENB = 0x10 138 }; 139 140 141 // Notification data layout 142 struct AX88772_Notify { 143 uint8 btA1; 144 uint8 bt01; 145 uint8 btBB; // AX88772_BBState below 146 uint8 bt03; 147 uint16 regCCDD; 148 uint16 regEEFF; 149 } _PACKED; 150 151 152 // Link-State bits 153 enum AX88772_BBState { 154 LINK_STATE_PPLS = 0x01, 155 LINK_STATE_SPLS = 0x02, 156 LINK_STATE_FLE = 0x04, 157 LINK_STATE_MDINT = 0x08 158 }; 159 160 // RX Control Register bits (772B) 161 enum ASIX772RXControl { 162 RXCTL_HDR_TYPE_0 = 0x0000, 163 RXCTL_HDR_TYPE_1 = 0x0100, 164 RXCTL_HDR_IPALIGN = 0x0200, 165 RXCTL_ADD_CHKSUM = 0x0400, 166 }; 167 168 // EEPROM Map. 169 enum AX88772B_EEPROM { 170 EEPROM_772B_NODE_ID = 0x04, 171 EEPROM_772B_PHY_PWRCFG = 0x18 172 }; 173 174 enum AX88772B_MFB { 175 AX88772B_MFB_2K = 0, 176 AX88772B_MFB_4K = 1, 177 AX88772B_MFB_6K = 2, 178 AX88772B_MFB_8K = 3, 179 AX88772B_MFB_16K = 4, 180 AX88772B_MFB_20K = 5, 181 AX88772B_MFB_24K = 6, 182 AX88772B_MFB_32K = 7, 183 AX88772B_MFB_Count = 8 184 }; 185 186 struct _AX88772B_MFB { 187 size_t ByteCount; 188 size_t Threshold; 189 size_t Size; 190 191 } AX88772B_MFBTable[AX88772B_MFB_Count] = { 192 { 0x8000, 0x8001, 2048 }, 193 { 0x8100, 0x8147, 4096 }, 194 { 0x8200, 0x81EB, 6144 }, 195 { 0x8300, 0x83D7, 8192 }, 196 { 0x8400, 0x851E, 16384 }, 197 { 0x8500, 0x8666, 20480 }, 198 { 0x8600, 0x87AE, 24576 }, 199 { 0x8700, 0x8A3D, 32768 } 200 }; 201 202 const uint16 maxFrameSize = 1536; 203 204 205 AX88772Device::AX88772Device(usb_device device, DeviceInfo& deviceInfo) 206 : 207 ASIXDevice(device, deviceInfo) 208 { 209 fStatus = InitDevice(); 210 } 211 212 213 status_t 214 AX88772Device::InitDevice() 215 { 216 fFrameSize = maxFrameSize; 217 fUseTRXHeader = true; 218 219 fReadNodeIDRequest = READ_NODEID; 220 221 fNotifyBufferLength = sizeof(AX88772_Notify); 222 fNotifyBuffer = (uint8 *)malloc(fNotifyBufferLength); 223 if (fNotifyBuffer == NULL) { 224 TRACE_ALWAYS("Error of allocating memory for notify buffer.\n"); 225 return B_NO_MEMORY; 226 } 227 228 return B_OK; 229 } 230 231 232 status_t 233 AX88772Device::ReadMACAddress(ether_address_t *address) 234 { 235 if (fDeviceInfo.fType != DeviceInfo::AX88772B) 236 return ASIXDevice::ReadMACAddress(address); 237 238 // Auto-loaded default station address from internal ROM is 239 // 00:00:00:00:00:00 such that an explicit access to EEPROM 240 // is required to get real station address. 241 for (size_t i = 0; i < sizeof(ether_address_t) / 2; i++) { 242 size_t actual_length = 0; 243 uint16 addr = 0; 244 status_t result = gUSBModule->send_request(fDevice, 245 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, READ_SROM, 246 EEPROM_772B_NODE_ID + i, 0, sizeof(addr), &addr, &actual_length); 247 if (result != B_OK) { 248 TRACE_ALWAYS("Error reading MAC[%d] address:%#010x\n", i, result); 249 return result; 250 } 251 252 address->ebyte[i * 2 + 0] = (uint8)addr; 253 address->ebyte[i * 2 + 1] = (uint8)(addr >> 8); 254 } 255 256 return B_OK; 257 } 258 259 260 status_t 261 AX88772Device::SetupDevice(bool deviceReplugged) 262 { 263 status_t result = ASIXDevice::SetupDevice(deviceReplugged); 264 if (result != B_OK) { 265 return result; 266 } 267 268 result = fMII.Init(fDevice); 269 270 switch (fDeviceInfo.fType) { 271 case DeviceInfo::AX88772A: 272 result = _SetupAX88772A(); 273 break; 274 case DeviceInfo::AX88772B: 275 result = _SetupAX88772B(); 276 break; 277 default: 278 result = _SetupAX88772(); 279 break; 280 } 281 282 if (result != B_OK) 283 return result; 284 285 result = fMII.SetupPHY(); 286 if (result != B_OK) { 287 return result; 288 } 289 290 size_t actualLength = 0; 291 result = gUSBModule->send_request(fDevice, 292 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_MEDIUM_MODE, 293 MEDIUM_STATE_FD | MEDIUM_STATE_BIT2 | MEDIUM_STATE_RFC 294 | MEDIUM_STATE_TFC | MEDIUM_STATE_RE | MEDIUM_STATE_PS_100, 295 0, 0, 0, &actualLength); 296 297 if (result != B_OK) { 298 TRACE_ALWAYS("Error of setting medium mode: %#010x\n", result); 299 } 300 301 TRACE_RET(result); 302 return result; 303 } 304 305 306 status_t 307 AX88772Device::_SetupAX88772() 308 { 309 size_t actualLength = 0; 310 // enable GPIO2 - magic from FreeBSD's if_axe 311 uint16 GPIOs = GPIO_OO_2EN | GPIO_IO_2 | GPIO_RSE; 312 status_t result = gUSBModule->send_request(fDevice, 313 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_GPIOS, 314 GPIOs, 0, 0, 0, &actualLength); 315 316 if (result != B_OK) { 317 TRACE_ALWAYS("Error of wrinting GPIOs: %#010x\n", result); 318 return result; 319 } 320 321 // select PHY 322 bool useEmbeddedPHY = fMII.PHYID() == PHYIDEmbedded; 323 uint16 selectPHY = useEmbeddedPHY 324 ? SW_PHY_SEL_STATUS_INT : SW_PHY_SEL_STATUS_EXT; 325 326 result = gUSBModule->send_request(fDevice, 327 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, 328 WRITE_PHY_SEL, selectPHY, 0, 0, 0, &actualLength); 329 snooze(10000); 330 331 TRACE("Selecting %s PHY[%#02x].\n", 332 useEmbeddedPHY ? "embedded" : "external", selectPHY); 333 334 if (result != B_OK) { 335 TRACE_ALWAYS("Error of selecting PHY:%#010x\n", result); 336 return result; 337 } 338 339 struct SWReset { 340 uint16 reset; 341 bigtime_t delay; 342 } resetCommands[] = { 343 // EMBEDDED PHY 344 // power down and reset state, pin reset state 345 { SW_RESET_CLR, 60000 }, 346 // power down/reset state, pin operating state 347 { SW_RESET_PRL | SW_RESET_IPPD, 150000 }, 348 // power up, reset 349 { SW_RESET_PRL, 0 }, 350 // power up, operating 351 { SW_RESET_PRL | SW_RESET_IPRL, 0 }, 352 // EXTERNAL PHY 353 // power down/reset state, pin operating state 354 { SW_RESET_PRL | SW_RESET_IPPD, 0 } 355 }; 356 357 size_t from = useEmbeddedPHY ? 0 : 4; 358 size_t to = useEmbeddedPHY ? 3 : 4; 359 360 for (size_t i = from; i <= to; i++) { 361 result = gUSBModule->send_request(fDevice, 362 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_SOFT_RESET, 363 resetCommands[i].reset, 0, 0, 0, &actualLength); 364 365 snooze(resetCommands[i].delay); 366 367 if (result != B_OK) { 368 TRACE_ALWAYS("Error of SW reset command %d:[%#04x]: %#010x\n", 369 i, resetCommands[i].reset, result); 370 return result; 371 } 372 } 373 374 snooze(150000); 375 376 return B_OK; 377 } 378 379 380 status_t 381 AX88772Device::_WakeupPHY() 382 { 383 // select PHY 384 bool useEmbeddedPHY = fMII.PHYID() == PHYIDEmbedded; 385 uint16 selectPHY = useEmbeddedPHY 386 ? SW_PHY_SEL_STATUS_INT : SW_PHY_SEL_STATUS_EXT; 387 388 selectPHY |= SW_PHY_SEL_STATUS_SS_MII | SW_PHY_SEL_STATUS_SS_ENB; 389 390 size_t actualLength = 0; 391 status_t result = gUSBModule->send_request(fDevice, 392 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_PHY_SEL, 393 selectPHY, 0, 0, 0, &actualLength); 394 snooze(31000); 395 396 TRACE("Selecting %s PHY[%#02x].\n", 397 useEmbeddedPHY ? "embedded" : "external", selectPHY); 398 399 if (result != B_OK) { 400 TRACE_ALWAYS("Error of selecting PHY:%#010x\n", result); 401 return result; 402 } 403 404 struct SWReset { 405 uint16 reset; 406 bigtime_t delay; 407 } resetCommands[] = { 408 { SW_RESET_IPRL | SW_RESET_IPPD, 250000 }, 409 { SW_RESET_IPRL, 1000000 }, 410 { SW_RESET_CLR, 31000 }, 411 { SW_RESET_IPRL, 31000 } 412 }; 413 414 for (size_t i = 0; i < B_COUNT_OF(resetCommands); i++) { 415 result = gUSBModule->send_request(fDevice, 416 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_SOFT_RESET, 417 resetCommands[i].reset, 0, 0, 0, &actualLength); 418 419 snooze(resetCommands[i].delay); 420 421 if (result != B_OK) { 422 TRACE_ALWAYS("Error of SW reset command %d:[%#04x]: %#010x\n", 423 i, resetCommands[i].reset, result); 424 return result; 425 } 426 } 427 428 return B_OK; 429 } 430 431 432 status_t 433 AX88772Device::_SetupAX88772A() 434 { 435 // Reload EEPROM 436 size_t actualLength = 0; 437 status_t result = gUSBModule->send_request(fDevice, 438 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_GPIOS, 439 GPIO_RSE, 0, 0, 0, &actualLength); 440 441 if (result != B_OK) { 442 TRACE_ALWAYS("Error of reloading EEPROM: %#010x\n", result); 443 return result; 444 } 445 446 result = _WakeupPHY(); 447 if (result != B_OK) 448 return result; 449 450 fIPG[0] = 0x15; 451 fIPG[1] = 0x16; 452 fIPG[2] = 0x1A; 453 454 return B_OK; 455 } 456 457 458 status_t 459 AX88772Device::_SetupAX88772B() 460 { 461 // Reload EEPROM 462 size_t actualLength = 0; 463 status_t result = gUSBModule->send_request(fDevice, 464 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_GPIOS, 465 GPIO_RSE, 0, 0, 0, &actualLength); 466 467 if (result != B_OK) { 468 TRACE_ALWAYS("Error of reloading EEPROM: %#010x\n", result); 469 return result; 470 } 471 472 result = _WakeupPHY(); 473 if (result != B_OK) 474 return result; 475 476 fIPG[0] = 0x15; 477 fIPG[1] = 0x16; 478 fIPG[2] = 0x1A; 479 480 return B_OK; 481 } 482 483 484 status_t 485 AX88772Device::StartDevice() 486 { 487 size_t actualLength = 0; 488 status_t result = gUSBModule->send_request(fDevice, 489 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_IPGS, 490 0, 0, sizeof(fIPG), fIPG, &actualLength); 491 492 if (result != B_OK) { 493 TRACE_ALWAYS("Error of writing IPGs:%#010x\n", result); 494 return result; 495 } 496 497 if (actualLength != sizeof(fIPG)) { 498 TRACE_ALWAYS("Mismatch of written IPGs data. " 499 "%d bytes of %d written.\n", actualLength, sizeof(fIPG)); 500 501 } 502 503 uint16 rxcontrol = 0; 504 505 // AX88772B uses different maximum frame burst configuration. 506 if (fDeviceInfo.fType == DeviceInfo::AX88772B) { 507 result = gUSBModule->send_request(fDevice, 508 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_RXCONTROL_CFG, 509 AX88772B_MFBTable[AX88772B_MFB_2K].ByteCount, 510 AX88772B_MFBTable[AX88772B_MFB_2K].Threshold, 0, 0, &actualLength); 511 512 if (result != B_OK) { 513 TRACE_ALWAYS("Error of writing frame burst:%#010x\n", result); 514 return result; 515 } 516 rxcontrol = RXCTL_HDR_TYPE_1; 517 } else { 518 // TODO: FreeBSD documents this to speed up xfers, I don't 519 // have the hardware to test however. 520 // rxcontrol = RXCTL_USB_MFB_MAX; 521 } 522 523 rxcontrol |= RXCTL_START | RXCTL_BROADCAST; 524 result = WriteRXControlRegister(rxcontrol); 525 if (result != B_OK) { 526 TRACE_ALWAYS("Error of writing %#04x RX Control:%#010x\n", 527 rxcontrol, result); 528 } 529 530 TRACE_RET(result); 531 return result; 532 } 533 534 535 status_t 536 AX88772Device::OnNotify(uint32 actualLength) 537 { 538 if (actualLength < sizeof(AX88772_Notify)) { 539 TRACE_ALWAYS("Data underrun error. %d of %d bytes received\n", 540 actualLength, sizeof(AX88772_Notify)); 541 return B_BAD_DATA; 542 } 543 544 AX88772_Notify *notification = (AX88772_Notify *)fNotifyBuffer; 545 546 if (notification->btA1 != 0xa1) { 547 TRACE_ALWAYS("Notify magic byte is invalid: %#02x\n", 548 notification->btA1); 549 } 550 551 uint phyIndex = 0; 552 bool linkIsUp = fHasConnection; 553 switch(fMII.ActivePHY()) { 554 case PrimaryPHY: 555 phyIndex = 1; 556 linkIsUp = (notification->btBB & LINK_STATE_PPLS) 557 == LINK_STATE_PPLS; 558 break; 559 case SecondaryPHY: 560 phyIndex = 2; 561 linkIsUp = (notification->btBB & LINK_STATE_SPLS) 562 == LINK_STATE_SPLS; 563 break; 564 default: 565 case CurrentPHY: 566 TRACE_ALWAYS("Error: PHY is not initialized.\n"); 567 return B_NO_INIT; 568 } 569 570 bool linkStateChange = linkIsUp != fHasConnection; 571 fHasConnection = linkIsUp; 572 573 if (linkStateChange) { 574 TRACE("Link state of PHY%d has been changed to '%s'\n", 575 phyIndex, fHasConnection ? "up" : "down"); 576 } 577 578 if (linkStateChange && fLinkStateChangeSem >= B_OK) 579 release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE); 580 581 return B_OK; 582 } 583 584 585 status_t 586 AX88772Device::GetLinkState(ether_link_state *linkState) 587 { 588 size_t actualLength = 0; 589 uint16 mediumStatus = 0; 590 status_t result = gUSBModule->send_request(fDevice, 591 USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, READ_MEDIUM_STATUS, 592 0, 0, sizeof(mediumStatus), &mediumStatus, &actualLength); 593 594 if (result != B_OK) { 595 TRACE_ALWAYS("Error of reading medium status:%#010x.\n", result); 596 return result; 597 } 598 599 if (actualLength != sizeof(mediumStatus)) { 600 TRACE_ALWAYS("Mismatch of reading medium status." 601 "Read %d bytes instead of %d\n", actualLength, 602 sizeof(mediumStatus)); 603 } 604 605 TRACE_FLOW("Medium status is %#04x\n", mediumStatus); 606 607 linkState->quality = 1000; 608 609 linkState->media = IFM_ETHER | (fHasConnection ? IFM_ACTIVE : 0); 610 linkState->media |= (mediumStatus & MEDIUM_STATE_FD) 611 ? IFM_FULL_DUPLEX : IFM_HALF_DUPLEX; 612 613 linkState->speed = (mediumStatus & MEDIUM_STATE_PS_100) 614 ? 100000000 : 10000000; 615 616 TRACE_FLOW("Medium state: %s, %lld MBit/s, %s duplex.\n", 617 (linkState->media & IFM_ACTIVE) ? "active" : "inactive", 618 linkState->speed / 1000000, 619 (linkState->media & IFM_FULL_DUPLEX) ? "full" : "half"); 620 return B_OK; 621 } 622