/* natsemi.c - gPXE driver for the NatSemi DP8381x series. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TX_RING_SIZE 4 #define NUM_RX_DESC 4 struct natsemi_tx { uint32_t link; uint32_t cmdsts; uint32_t bufptr; }; struct natsemi_rx { uint32_t link; uint32_t cmdsts; uint32_t bufptr; }; struct natsemi_nic { unsigned short ioaddr; unsigned short tx_cur; unsigned short tx_dirty; unsigned short rx_cur; struct natsemi_tx tx[TX_RING_SIZE]; struct natsemi_rx rx[NUM_RX_DESC]; /* need to add iobuf as we cannot free iobuf->data in close without this * alternatively substracting sizeof(head) and sizeof(list_head) can also * give the same.*/ struct io_buffer *iobuf[NUM_RX_DESC]; struct spi_bit_basher spibit; struct spi_device eeprom; struct nvo_block nvo; }; /* Tuning Parameters */ #define TX_FIFO_THRESH 256 /* In bytes, rounded down to 32 byte units. */ #define RX_FIFO_THRESH 4 /* Rx buffer level before first PCI xfer. */ #define RX_DMA_BURST 4 /* Maximum PCI burst, '4' is 256 bytes */ #define TX_DMA_BURST 4 /* Calculate as 16<ioaddr + Cfg9346 ); return ( eereg & mask ); } static void rtl_spi_write_bit ( struct bit_basher *basher, unsigned int bit_id, unsigned long data ) { struct rtl8139_nic *rtl = container_of ( basher, struct rtl8139_nic, spibit.basher ); uint8_t mask = rtl_ee_bits[bit_id]; uint8_t eereg; eereg = inb ( rtl->ioaddr + Cfg9346 ); eereg &= ~mask; eereg |= ( data & mask ); outb ( eereg, rtl->ioaddr + Cfg9346 ); } static struct bit_basher_operations rtl_basher_ops = { .read = rtl_spi_read_bit, .write = rtl_spi_write_bit, }; /** Portion of EEPROM available for non-volatile stored options * * We use offset 0x40 (i.e. address 0x20), length 0x40. This block is * marked as VPD in the rtl8139 datasheets, so we use it only if we * detect that the card is not supporting VPD. */ static struct nvo_fragment rtl_nvo_fragments[] = { { 0x20, 0x40 }, { 0, 0 } }; /** * Set up for EEPROM access * * @v NAT NATSEMI NIC */ void rtl_init_eeprom ( struct natsemi_nic *rtl ) { int ee9356; int vpd; /* Initialise three-wire bus */ rtl->spibit.basher.op = &rtl_basher_ops; rtl->spibit.bus.mode = SPI_MODE_THREEWIRE; init_spi_bit_basher ( &rtl->spibit ); /* Detect EEPROM type and initialise three-wire device */ ee9356 = ( inw ( rtl->ioaddr + RxConfig ) & Eeprom9356 ); if ( ee9356 ) { DBG ( "EEPROM is an AT93C56\n" ); init_at93c56 ( &rtl->eeprom, 16 ); } else { DBG ( "EEPROM is an AT93C46\n" ); init_at93c46 ( &rtl->eeprom, 16 ); } rtl->eeprom.bus = &rtl->spibit.bus; /* Initialise space for non-volatile options, if available */ vpd = ( inw ( rtl->ioaddr + Config1 ) & VPDEnable ); if ( vpd ) { DBG ( "EEPROM in use for VPD; cannot use for options\n" ); } else { rtl->nvo.nvs = &rtl->eeprom.nvs; rtl->nvo.fragments = rtl_nvo_fragments; } } /** * Reset NIC * * @v NATSEMI NIC * * Issues a hardware reset and waits for the reset to complete. */ static void nat_reset ( struct nat_nic *nat ) { int i; /* Reset chip */ outb ( ChipReset, nat->ioaddr + ChipCmd ); mdelay ( 10 ); nat->tx_dirty=0; nat->tx_cur=0; for(i=0;itx[i].link=0; nat->tx[i].cmdsts=0; nat->tx[i].bufptr=0; } nat->rx_cur = 0; outl(virt_to_bus(&nat->tx[0]),nat->ioaddr+TxRingPtr); outl(virt_to_bus(&nat->rx[0]), nat->ioaddr + RxRingPtr); outl(TxOff|RxOff, nat->ioaddr + ChipCmd); /* Restore PME enable bit */ outl(SavedClkRun, nat->ioaddr + ClkRun); } /** * Open NIC * * @v netdev Net device * @ret rc Return status code */ static int nat_open ( struct net_device *netdev ) { struct natsemi_nic *nat = netdev->priv; //struct io_buffer *iobuf; int i; /* Disable PME: * The PME bit is initialized from the EEPROM contents. * PCI cards probably have PME disabled, but motherboard * implementations may have PME set to enable WakeOnLan. * With PME set the chip will scan incoming packets but * nothing will be written to memory. */ SavedClkRun = inl(nat->ioaddr + ClkRun); outl(SavedClkRun & ~0x100, nat->ioaddr + ClkRun); /* Program the MAC address TODO enable this comment */ /* for ( i = 0 ; i < ETH_ALEN ; i++ ) outb ( netdev->ll_addr[i], rtl->ioaddr + MAC0 + i ); */ /*Set up the Tx Ring */ nat->tx_cur=0; nat->tx_dirty=0; for (i=0;itx[i].link = virt_to_bus((i+1 < TX_RING_SIZE) ? &nat->tx[i+1] : &nat->tx[0]); nat->tx[i].cmdsts = 0; nat->tx[i].bufptr = 0; } /* Set up RX ring */ nat->rx_cur=0; for (i=0;iiobuf[i] = alloc_iob ( RX_BUF_SIZE ); if (!nat->iobuf[i]) return -ENOMEM; nat->rx[i].link = virt_to_bus((i+1 < NUM_RX_DESC) ? &nat->rx[i+1] : &nat->rx[0]); nat->rx[i].cmdsts = (uint32_t) RX_BUF_SIZE; nat->rx[i].bufptr = virt_to_bus(nat->iobuf[i]->data); } /* load Receive Descriptor Register */ outl(virt_to_bus(&nat->rx[0]), nat->ioaddr + RxRingPtr); DBG("Natsemi Rx descriptor loaded with: %X\n",inl(nat->ioaddr+RingPtr)); /* setup Tx ring */ outl(virt_to_bus(&nat->tx[0]),nat->ioaddr+TxRingPtr); DBG("Natsemi Tx descriptor loaded with: %X\n",inl(nat->ioaddr+TxRingPtr)); /* Enables RX */ outl(RxFilterEnable|AcceptBroadcast|AcceptAllMulticast|AcceptMyPhys, nat->ioaddr+RxFilterAddr); /* Initialize other registers. */ /* Configure the PCI bus bursts and FIFO thresholds. */ /* Configure for standard, in-spec Ethernet. */ if (inl(nat->ioaddr + ChipConfig) & 0x20000000) { /* Full duplex */ tx_config = 0xD0801002; rx_config = 0x10000020; } else { tx_config = 0x10801002; rx_config = 0x0020; } outl(tx_config, nat->ioaddr + TxConfig); outl(rx_config, nat->ioaddr + RxConfig); /*start the receiver */ outl(RxOn, nat->ioaddr + ChipCmd); return 0; } /** * Close NIC * * @v netdev Net device */ static void nat_close ( struct net_device *netdev ) { struct natsemi_nic *nat = netdev->priv; /* Reset the hardware to disable everything in one go */ nat_reset ( nat ); /* Free RX ring */ for (i=0;iiobuf[i] ); } } /** * Transmit packet * * @v netdev Network device * @v iobuf I/O buffer * @ret rc Return status code */ static int natsemi_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { struct natsemi_nic *nat = netdev->priv; /* check for space in TX ring */ if (nat->tx[nat->tx_cur].cmdsts !=0) { printf ( "TX overflow\n" ); return -ENOBUFS; } /* Pad and align packet */ iob_pad ( iobuf, ETH_ZLEN ); /* Add to TX ring */ DBG ( "TX id %d at %lx+%x\n", nat->tx_cur, virt_to_bus ( iobuf->data ), iob_len ( iobuf ) ); nat->tx[nat->tx_cur].bufptr = virt_to_bus(iobuf->data); nat->tx[nat->tx_cur].cmdsts= (uint32_t) iob_len(iobuf)|OWN; nat->tx_cur=(nat->tx_cur+1) % TX_RING_SIZE; /*start the transmitter */ outl(TxOn, nat->ioaddr + ChipCmd); return 0; } /** * Poll for received packets * * @v netdev Network device * @v rx_quota Maximum number of packets to receive */ static void nat_poll ( struct net_device *netdev, unsigned int rx_quota ) { struct natsemi_nic *nat = netdev->priv; unsigned int status; unsigned int rx_status; unsigned int rx_len; struct io_buffer *rx_iob; int i; /* check the status of packets given to card for transmission */ for ( i = 0 ; i < TX_RING_SIZE ; i++ ) { status=bus_to_virt(nat->tx[nat->tx_dirty].cmdsts); /* check if current packet has been transmitted or not */ if(status & own) break; /* Check if any errors in transmission */ if (! (status & DescPktOK)) { printf("Error in sending Packet with data: %s\n and status:%X\n", bus_to_virt(nat->tx[nat->tx_dirty].bufptr), status); } else { DBG("Success in transmitting Packet with data: %s", bus_to_virt(nat->tx[nat->tx_dirty].bufptr)); } /* setting cmdsts zero, indicating that it can be reused */ nat->tx[nat->tx_dirty].cmdsts=0; nat->tx_dirty=(nat->tx_dirty +1) % TX_RING_SIZE; } rx_status=bus_to_virt(nat->rx[nat->rx_cur].cmdsts); /* Handle received packets */ while (rx_quota && (rx_status & OWN)) { rx_len= (rx_status & DSIZE) - CRC_SIZE; /*check for the corrupt packet */ if((rx_status & (DescMore|DescPktOK|RxTooLong)) != DescPktOK) { printf("natsemi_poll: Corrupted packet received, buffer status = %X\n",rx_status); } else { rx_iob = alloc_iob(rx_len); if(!rx_iob) /* leave packet for next call to poll*/ return; memcpy(iob_put(rx_iob,rx_len), nat->rx[nat->rx_cur].bufptr,rxlen); /* add to the receive queue. */ netdev_rx(netdev,rx_iob); rx_quota--; } nat->rx[nat->rx_cur].cmdsts = RX_BUF_SIZE; nat->rx_cur=(nat->rx_cur+1) % NUM_RX_DESC; } /* re-enable the potentially idle receive state machine */ outl(RxOn, ioaddr + ChipCmd); } /** * Probe PCI device * * @v pci PCI device * @v id PCI ID * @ret rc Return status code */ static int nat_probe ( struct pci_device *pci, const struct pci_device_id *id __unused ) { struct net_device *netdev; struct natsemi_nic *nat = NULL; int registered_netdev = 0; int rc; uint32_t advertising; /* Fix up PCI device */ adjust_pci_device ( pci ); /* Allocate net device */ netdev = alloc_etherdev ( sizeof ( *nat ) ); if ( ! netdev ) { rc = -ENOMEM; goto err; } nat = netdev->priv; pci_set_drvdata ( pci, netdev ); netdev->dev = &pci->dev; memset ( nat, 0, sizeof ( *nat ) ); nat->ioaddr = pci->ioaddr; /* Reset the NIC, set up EEPROM access and read MAC address */ nat_reset ( nat ); /* commenitng two line below. Have to be included in final natsemi.c TODO*/ /* nat_init_eeprom ( rtl ); nvs_read ( &nat->eeprom.nvs, EE_MAC, netdev->ll_addr, ETH_ALEN ); */ /* mdio routine of etherboot-5.4.0 natsemi driver has been removed and * statement to read from MII transceiver control section is used directly */ advertising = inl(nat->ioaddr + 0x80 + (4<<2)) & 0xffff; { uint32_t chip_config = inl(ioaddr + ChipConfig); DBG("%s: Transceiver default autoneg. %s 10 %s %s duplex.\n", pci->driver_name, chip_config & 0x2000 ? "enabled, advertise" : "disabled, force", chip_config & 0x4000 ? "0" : "", chip_config & 0x8000 ? "full" : "half"); } DBG("%s: Transceiver status %hX advertising %hX\n",pci->driver_name, (int)inl(nat->ioaddr + 0x84), advertising); /* Point to NIC specific routines */ netdev->open = nat_open; netdev->close = nat_close; netdev->transmit = nat_transmit; netdev->poll = nat_poll; /* Register network device */ if ( ( rc = register_netdev ( netdev ) ) != 0 ) goto err; registered_netdev = 1; /* Register non-volatile storagei * uncomment lines below in final version*/ /* if ( rtl->nvo.nvs ) { if ( ( rc = nvo_register ( &rtl->nvo ) ) != 0 ) goto err; } */ return 0; err: /* Disable NIC */ if ( nat ) nat_reset ( nat ); if ( registered_netdev ) unregister_netdev ( netdev ); /* Free net device */ free_netdev ( netdev ); return rc; } /** * Remove PCI device * * @v pci PCI device */ static void nat_remove ( struct pci_device *pci ) { struct net_device *netdev = pci_get_drvdata ( pci ); struct natsemi_nic *nat = netdev->priv; /* TODO if ( rtl->nvo.nvs ) nvo_unregister ( &rtl->nvo ); */ unregister_netdev ( netdev ); nat_reset ( nat ); free_netdev ( netdev ); } static struct pci_device_id natsemi_nics[] = { PCI_ROM(0x100b, 0x0020, "dp83815", "DP83815"), }; struct pci_driver natsemi_driver __pci_driver = { .ids = natsemi_nics, .id_count = ( sizeof ( natsemi_nics ) / sizeof ( natsemi_nics[0] ) ), .probe = nat_probe, .remove = nat_remove, };