/* Copyright (c) 2003-2011 * Stefano Ceccherini . All rights reserved. * This file is released under the MIT license */ #include "device.h" #include "driver.h" #include "debug.h" #include "ether_driver.h" #include "interface.h" #include "wb840.h" #include #include #include #include #include #define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1)) // MII chip info table #define PHY_ID0_DAVICOM_DM9101 0x0181 #define PHY_ID1_DAVICOM_DM9101 0xb800 #define MII_HOME 0x0001 #define MII_LAN 0x0002 struct mii_chip_info { const char* name; uint16 id0; uint16 id1; uint8 types; }; const static struct mii_chip_info gMIIChips[] = { {"DAVICOM_DM9101", PHY_ID0_DAVICOM_DM9101, PHY_ID1_DAVICOM_DM9101, MII_LAN}, {NULL, 0, 0, 0} }; static int mii_readstatus(wb_device* device) { int i = 0; int status; // status bit has to be retrieved 2 times while (i++ < 2) status = wb_miibus_readreg(device, device->phy, MII_STATUS); return status; } static phys_addr_t physicalAddress(volatile void* addr, uint32 length) { physical_entry table; get_memory_map((void*)addr, length, &table, 1); return table.address; } // Prepares a RX descriptor to be used by the chip void wb_put_rx_descriptor(volatile wb_desc* descriptor) { descriptor->wb_status = WB_RXSTAT_OWN; descriptor->wb_ctl |= WB_MAX_FRAMELEN | WB_RXCTL_RLINK; } void wb_enable_interrupts(wb_device* device) { write32(device->reg_base + WB_IMR, WB_INTRS); write32(device->reg_base + WB_ISR, 0xFFFFFFFF); } void wb_disable_interrupts(wb_device* device) { write32(device->reg_base + WB_IMR, 0L); write32(device->reg_base + WB_ISR, 0L); } static void wb_selectPHY(wb_device* device) { uint16 status; // ToDo: need to be changed, select PHY in relation to the link mode device->currentPHY = device->firstPHY; device->phy = device->currentPHY->address; status = wb_miibus_readreg(device, device->phy, MII_CONTROL); status &= ~MII_CONTROL_ISOLATE; wb_miibus_writereg(device, device->phy, MII_CONTROL, status); wb_read_mode(device); } status_t wb_initPHYs(wb_device* device) { uint16 phy; // search for total of 32 possible MII PHY addresses for (phy = 0; phy < 32; phy++) { struct mii_phy* mii; uint16 status; int i = 0; status = wb_miibus_readreg(device, phy, MII_STATUS); status = wb_miibus_readreg(device, phy, MII_STATUS); if (status == 0xffff || status == 0x0000) // this MII is not accessable continue; mii = (struct mii_phy*)calloc(1, sizeof(struct mii_phy)); if (mii == NULL) return B_NO_MEMORY; mii->address = phy; mii->id0 = wb_miibus_readreg(device, phy, MII_PHY_ID0); mii->id1 = wb_miibus_readreg(device, phy, MII_PHY_ID1); mii->types = MII_HOME; mii->next = device->firstPHY; device->firstPHY = mii; while (gMIIChips[i].name != NULL) { if (gMIIChips[i].id0 == mii->id0 && gMIIChips[i].id1 == (mii->id1 & 0xfff0)) { dprintf("Found MII PHY: %s\n", gMIIChips[i].name); mii->types = gMIIChips[i].types; break; } i++; } if (gMIIChips[i].name == NULL) { dprintf("Unknown MII PHY transceiver: id = (%x, %x).\n", mii->id0, mii->id1); } } if (device->firstPHY == NULL) { dprintf("No MII PHY transceiver found!\n"); return B_ENTRY_NOT_FOUND; } wb_selectPHY(device); device->link = mii_readstatus(device) & MII_STATUS_LINK; return B_OK; } void wb_init(wb_device* device) { LOG((DEVICE_NAME": init()\n")); wb_reset(device); device->wb_txthresh = WB_TXTHRESH_INIT; switch(device->wb_cachesize) { case 32: WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_32LONG); break; case 16: WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_16LONG); break; case 8: WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_8LONG); break; case 0: default: WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_NONE); break; } write32(device->reg_base + WB_BUSCTL, WB_BUSCTL_MUSTBEONE | WB_BUSCTL_ARBITRATION); WB_SETBIT(device->reg_base + WB_BUSCTL, WB_BURSTLEN_16LONG); write32(device->reg_base + WB_BUSCTL_SKIPLEN, WB_SKIPLEN_4LONG); // Disable early TX/RX interrupt, as we can't take advantage // from them, at least for now. WB_CLRBIT(device->reg_base + WB_NETCFG, (WB_NETCFG_TX_EARLY_ON | WB_NETCFG_RX_EARLY_ON)); wb_set_rx_filter(device); } void wb_reset(wb_device *device) { int i = 0; LOG((DEVICE_NAME": reset()\n")); write32(device->reg_base + WB_NETCFG, 0L); write32(device->reg_base + WB_BUSCTL, 0L); write32(device->reg_base + WB_TXADDR, 0L); write32(device->reg_base + WB_RXADDR, 0L); WB_SETBIT(device->reg_base + WB_BUSCTL, WB_BUSCTL_RESET); WB_SETBIT(device->reg_base + WB_BUSCTL, WB_BUSCTL_RESET); for (i = 0; i < WB_TIMEOUT; i++) { if (!(read32(device->reg_base + WB_BUSCTL) & WB_BUSCTL_RESET)) break; } if (i == WB_TIMEOUT) LOG((DEVICE_NAME": reset hasn't completed!!!")); /* Wait a bit while the chip reorders his toughts */ snooze(1000); } status_t wb_stop(wb_device* device) { uint32 cfgAddress = (uint32)device->reg_base + WB_NETCFG; int32 i = 0; if (read32(cfgAddress) & (WB_NETCFG_TX_ON | WB_NETCFG_RX_ON)) { WB_CLRBIT(cfgAddress, (WB_NETCFG_TX_ON | WB_NETCFG_RX_ON)); for (i = 0; i < WB_TIMEOUT; i++) { if ((read32(device->reg_base + WB_ISR) & WB_ISR_TX_IDLE) && (read32(device->reg_base + WB_ISR) & WB_ISR_RX_IDLE)) break; } } if (i < WB_TIMEOUT) return B_OK; return B_ERROR; } static void wb_updateLink(wb_device* device) { if (!device->autoNegotiationComplete) { int32 mode = wb_read_mode(device); if (mode) wb_set_mode(device, mode); return; } if (device->link) { uint16 status = mii_readstatus(device); // Check if link lost if ((status & MII_STATUS_LINK) == 0) device->link = false; } else { uint16 status; wb_selectPHY(device); // Check if we have a new link status = mii_readstatus(device); if (status & MII_STATUS_LINK) device->link = true; } } int32 wb_tick(timer* arg) { wb_device* device = (wb_device*)arg; wb_updateLink(device); return B_HANDLED_INTERRUPT; } /* * Program the rx filter. */ void wb_set_rx_filter(wb_device* device) { // TODO: Basically we just config the filter to accept broadcasts // packets. We'll need also to configure it to multicast. WB_SETBIT(device->reg_base + WB_NETCFG, WB_NETCFG_RX_BROAD); } /***************** Interrupt handling ******************************/ static status_t wb_rxok(wb_device* device) { uint32 releaseRxSem = 0; int16 limit; acquire_spinlock(&device->rxSpinlock); for (limit = device->rxFree; limit > 0; limit--) { if (device->rxDescriptor[device->rxInterruptIndex].wb_status & WB_RXSTAT_OWN) { break; } releaseRxSem++; device->rxInterruptIndex = (device->rxInterruptIndex + 1) & WB_RX_CNT_MASK; device->rxFree--; } // Re-enable receive queue write32(device->reg_base + WB_RXSTART, 0xFFFFFFFF); release_spinlock(&device->rxSpinlock); if (releaseRxSem > 0) { release_sem_etc(device->rxSem, releaseRxSem, B_DO_NOT_RESCHEDULE); return B_INVOKE_SCHEDULER; } return B_HANDLED_INTERRUPT; } static status_t wb_tx_nobuf(wb_device* info) { int16 releaseTxSem = 0; int16 limit; status_t status; acquire_spinlock(&info->txSpinlock); for (limit = info->txSent; limit > 0; limit--) { status = info->txDescriptor[info->txInterruptIndex].wb_status; LOG(("wb_tx_nobuf, status: %lx\n", status)); if (status & WB_TXSTAT_TXERR) { LOG(("TX_STAT_ERR\n")); break; } else if (status & WB_UNSENT) { LOG(("TX_STAT_UNSENT\n")); break; } else if (status & WB_TXSTAT_OWN) { LOG((DEVICE_NAME": Device still owns the descriptor\n")); break; } else info->txDescriptor[info->txInterruptIndex].wb_status = 0; releaseTxSem++; // this many buffers are free info->txInterruptIndex = (info->txInterruptIndex + 1) & WB_TX_CNT_MASK; info->txSent--; if (info->txSent < 0 || info->txSent > WB_TX_LIST_CNT) dprintf("ERROR interrupt: txSent = %d\n", info->txSent); } release_spinlock(&info->txSpinlock); if (releaseTxSem) { release_sem_etc(info->txSem, releaseTxSem, B_DO_NOT_RESCHEDULE); return B_INVOKE_SCHEDULER; } return B_HANDLED_INTERRUPT; } int32 wb_interrupt(void* arg) { wb_device* device = (wb_device*)arg; int32 retval = B_UNHANDLED_INTERRUPT; uint32 status; // TODO: Handle other interrupts acquire_spinlock(&device->intLock); status = read32(device->reg_base + WB_ISR); // Did this card request the interrupt ? if (status & WB_INTRS) { // Clean all the interrupts bits if (status) write32(device->reg_base + WB_ISR, status); if (status & WB_ISR_ABNORMAL) LOG((DEVICE_NAME": *** Abnormal Interrupt received ***\n")); else LOG((DEVICE_NAME": interrupt received: \n")); if (status & WB_ISR_RX_EARLY) { LOG(("WB_ISR_RX_EARLY\n")); } if (status & WB_ISR_RX_NOBUF) { LOG(("WB_ISR_RX_NOBUF\n")); // Something is screwed } if (status & WB_ISR_RX_ERR) { LOG(("WB_ISR_RX_ERR\n")); // TODO: Do something useful } if (status & WB_ISR_RX_OK) { LOG(("WB_ISR_RX_OK\n")); retval = wb_rxok(device); } if (status & WB_ISR_RX_IDLE) { LOG(("WB_ISR_RX_IDLE\n")); // ??? } if (status & WB_ISR_TX_EARLY) { LOG(("WB_ISR_TX_EARLY\n")); } if (status & WB_ISR_TX_NOBUF) { LOG(("WB_ISR_TX_NOBUF\n")); retval = wb_tx_nobuf(device); } if (status & WB_ISR_TX_UNDERRUN) { LOG(("WB_ISR_TX_UNDERRUN\n")); // TODO: Jack up TX Threshold } if (status & WB_ISR_TX_IDLE) { LOG(("WB_ISR_TX_IDLE\n")); } if (status & WB_ISR_TX_OK) { LOG(("WB_ISR_TX_OK\n")); // So what ? } if (status & WB_ISR_BUS_ERR) { LOG(("WB_ISR_BUS_ERROR: %lx\n", (status & WB_ISR_BUSERRTYPE) >> 4)); //wb_reset(device); } if (status & WB_ISR_TIMER_EXPIRED) { LOG(("WB_ISR_TIMER_EXPIRED\n")); // ?? } } release_spinlock(&device->intLock); return retval; } /* * Print an ethernet address */ void print_address(ether_address_t* addr) { int i; char buf[3 * 6 + 1]; for (i = 0; i < 5; i++) { sprintf(&buf[3 * i], "%02x:", addr->ebyte[i]); } sprintf(&buf[3 * 5], "%02x", addr->ebyte[5]); dprintf("%s\n", buf); } status_t wb_create_semaphores(wb_device* device) { device->rxSem = create_sem(0, "wb840 receive"); if (device->rxSem < B_OK) { LOG(("Couldn't create sem, sem_id %ld\n", device->rxSem)); return device->rxSem; } device->txSem = create_sem(WB_TX_LIST_CNT, "wb840 transmit"); if (device->txSem < B_OK) { LOG(("Couldn't create sem, sem_id %ld\n", device->txSem)); delete_sem(device->rxSem); return device->txSem; } set_sem_owner(device->rxSem, B_SYSTEM_TEAM); set_sem_owner(device->txSem, B_SYSTEM_TEAM); device->rxLock = 0; device->txLock = 0; return B_OK; } void wb_delete_semaphores(wb_device* device) { if (device->rxSem >= 0) delete_sem(device->rxSem); if (device->txSem >= 0) delete_sem(device->txSem); } status_t wb_create_rings(wb_device* device) { int i; device->rxArea = create_area("wb840 rx buffer", (void**)&device->rxBuffer[0], B_ANY_KERNEL_ADDRESS, ROUND_TO_PAGE_SIZE(WB_BUFBYTES * WB_RX_LIST_CNT), B_32_BIT_FULL_LOCK, B_READ_AREA | B_WRITE_AREA); if (device->rxArea < B_OK) return device->rxArea; for (i = 1; i < WB_RX_LIST_CNT; i++) { device->rxBuffer[i] = (void*)(((addr_t)device->rxBuffer[0]) + (i * WB_BUFBYTES)); } for (i = 0; i < WB_RX_LIST_CNT; i++) { device->rxDescriptor[i].wb_status = 0; device->rxDescriptor[i].wb_ctl = WB_RXCTL_RLINK; wb_put_rx_descriptor(&device->rxDescriptor[i]); device->rxDescriptor[i].wb_data = physicalAddress( device->rxBuffer[i], WB_BUFBYTES); device->rxDescriptor[i].wb_next = physicalAddress( &device->rxDescriptor[(i + 1) & WB_RX_CNT_MASK], sizeof(struct wb_desc)); } device->rxFree = WB_RX_LIST_CNT; device->txArea = create_area("wb840 tx buffer", (void**)&device->txBuffer[0], B_ANY_KERNEL_ADDRESS, ROUND_TO_PAGE_SIZE(WB_BUFBYTES * WB_TX_LIST_CNT), B_32_BIT_FULL_LOCK, B_READ_AREA | B_WRITE_AREA); if (device->txArea < B_OK) { delete_area(device->rxArea); return device->txArea; } for (i = 1; i < WB_TX_LIST_CNT; i++) { device->txBuffer[i] = (void*)(((addr_t)device->txBuffer[0]) + (i * WB_BUFBYTES)); } for (i = 0; i < WB_TX_LIST_CNT; i++) { device->txDescriptor[i].wb_status = 0; device->txDescriptor[i].wb_ctl = WB_TXCTL_TLINK; device->txDescriptor[i].wb_data = physicalAddress( device->txBuffer[i], WB_BUFBYTES); device->txDescriptor[i].wb_next = physicalAddress( &device->txDescriptor[(i + 1) & WB_TX_CNT_MASK], sizeof(struct wb_desc)); } if (wb_stop(device) == B_OK) { write32(device->reg_base + WB_RXADDR, physicalAddress(&device->rxDescriptor[0], sizeof(struct wb_desc))); write32(device->reg_base + WB_TXADDR, physicalAddress(&device->txDescriptor[0], sizeof(struct wb_desc))); } return B_OK; } void wb_delete_rings(wb_device* device) { delete_area(device->rxArea); delete_area(device->txArea); } int32 wb_read_mode(wb_device* info) { uint16 autoAdv; uint16 autoLinkPartner; int32 speed; int32 duplex; uint16 status = mii_readstatus(info); if (!(status & MII_STATUS_LINK)) { LOG((DEVICE_NAME ": no link detected (status = %x)\n", status)); return 0; } // auto negotiation completed autoAdv = wb_miibus_readreg(info, info->phy, MII_AUTONEG_ADV); autoLinkPartner = wb_miibus_readreg(info, info->phy, MII_AUTONEG_LINK_PARTNER); status = autoAdv & autoLinkPartner; speed = status & (MII_NWAY_TX | MII_NWAY_TX_FDX) ? LINK_SPEED_100_MBIT : LINK_SPEED_10_MBIT; duplex = status & (MII_NWAY_TX_FDX | MII_NWAY_T_FDX) ? LINK_FULL_DUPLEX : LINK_HALF_DUPLEX; info->autoNegotiationComplete = true; LOG((DEVICE_NAME ": linked, 10%s MBit, %s duplex\n", speed == LINK_SPEED_100_MBIT ? "0" : "", duplex == LINK_FULL_DUPLEX ? "full" : "half")); return speed | duplex; } void wb_set_mode(wb_device* info, int mode) { uint32 cfgAddress = (uint32)info->reg_base + WB_NETCFG; uint32 configFlags = 0; status_t status; info->speed = mode & LINK_SPEED_MASK; info->full_duplex = mode & LINK_DUPLEX_MASK; status = wb_stop(info); if ((mode & LINK_DUPLEX_MASK) == LINK_FULL_DUPLEX) configFlags |= WB_NETCFG_FULLDUPLEX; if (info->speed == LINK_SPEED_100_MBIT) configFlags |= WB_NETCFG_100MBPS; write32(cfgAddress, configFlags); if (status == B_OK) WB_SETBIT(cfgAddress, WB_NETCFG_TX_ON|WB_NETCFG_RX_ON); }