// Copyright 2016 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #if _KERNEL //TODO: proper includes/defines kernel driver #else // includes and defines for userspace driver #include #include #include typedef int status_t; #define nanosleep(x) zx_nanosleep(zx_deadline_after(x)); #define usleep(x) nanosleep((x) * 1000) #define REG32(addr) ((volatile uint32_t *)(uintptr_t)(addr)) #define writel(v, a) (*REG32(eth->iobase + (a)) = (v)) #define readl(a) (*REG32(eth->iobase + (a))) #endif #include "ie.h" void eth_dump_regs(ethdev_t* eth) { printf("STAT %08x CTRL %08x EXT %08x IMS %08x\n", readl(IE_STATUS), readl(IE_CTRL), readl(IE_CTRL_EXT), readl(IE_IMS)); printf("RCTL %08x RDLN %08x RDH %08x RDT %08x\n", readl(IE_RCTL), readl(IE_RDLEN), readl(IE_RDH), readl(IE_RDT)); printf("RXDC %08x RDTR %08x RBH %08x RBL %08x\n", readl(IE_RXDCTL), readl(IE_RDTR), readl(IE_RDBAH), readl(IE_RDBAL)); printf("TCTL %08x TDLN %08x TDH %08x TDT %08x\n", readl(IE_TCTL), readl(IE_TDLEN), readl(IE_TDH), readl(IE_TDT)); printf("TXDC %08x TIDV %08x TBH %08x TBL %08x\n", readl(IE_TXDCTL), readl(IE_TIDV), readl(IE_TDBAH), readl(IE_TDBAL)); } unsigned eth_handle_irq(ethdev_t* eth) { // clears irqs on read return readl(IE_ICR); } bool eth_status_online(ethdev_t* eth) { return readl(IE_STATUS) & IE_STATUS_LU; } status_t eth_rx(ethdev_t* eth, void** data, size_t* len) { uint32_t n = eth->rx_rd_ptr; uint64_t info = eth->rxd[n].info; if (!(info & IE_RXD_DONE)) { return ZX_ERR_SHOULD_WAIT; } // copy out packet zx_status_t r = IE_RXD_LEN(info); *data = eth->rxb + ETH_RXBUF_SIZE * n; *len = r; return ZX_OK; } void eth_rx_ack(ethdev_t* eth) { uint32_t n = eth->rx_rd_ptr; // make buffer available to hw eth->rxd[n].info = 0; writel(n, IE_RDT); n = (n + 1) & (ETH_RXBUF_COUNT - 1); eth->rx_rd_ptr = n; } void eth_enable_rx(ethdev_t* eth) { uint32_t rctl = readl(IE_RCTL); writel(rctl | IE_RCTL_EN, IE_RCTL); } void eth_disable_rx(ethdev_t* eth) { uint32_t rctl = readl(IE_RCTL); writel(rctl & ~IE_RCTL_EN, IE_RCTL); } static void reap_tx_buffers(ethdev_t* eth) { uint32_t n = eth->tx_rd_ptr; for (;;) { uint64_t info = eth->txd[n].info; if (!(info & IE_TXD_DONE)) { break; } framebuf_t* frame = list_remove_head_type(ð->busy_frames, framebuf_t, node); if (frame == NULL) { panic(); } // TODO: verify that this is the matching buffer to txd[n] addr? list_add_tail(ð->free_frames, &frame->node); eth->txd[n].info = 0; n = (n + 1) & (ETH_TXBUF_COUNT - 1); } eth->tx_rd_ptr = n; } status_t eth_tx(ethdev_t* eth, const void* data, size_t len) { if (len > ETH_TXBUF_DSIZE) { printf("intel-eth: unsupported packet length %zu\n", len); return ZX_ERR_INVALID_ARGS; } zx_status_t status = ZX_OK; mtx_lock(ð->send_lock); reap_tx_buffers(eth); // obtain buffer, copy into it, setup descriptor framebuf_t *frame = list_remove_head_type(ð->free_frames, framebuf_t, node); if (frame == NULL) { status = ZX_ERR_NO_RESOURCES; goto out; } uint32_t n = eth->tx_wr_ptr; memcpy(frame->data, data, len); // Pad out short packets. if (len < 60) { memset(frame->data + len, 0, 60 - len); len = 60; } eth->txd[n].addr = frame->phys; eth->txd[n].info = IE_TXD_LEN(len) | IE_TXD_EOP | IE_TXD_IFCS | IE_TXD_RS; list_add_tail(ð->busy_frames, &frame->node); // inform hw of buffer availability n = (n + 1) & (ETH_TXBUF_COUNT - 1); eth->tx_wr_ptr = n; writel(n, IE_TDT); out: mtx_unlock(ð->send_lock); return status; } // Returns the number of Tx packets in the hw queue size_t eth_tx_queued(ethdev_t* eth) { reap_tx_buffers(eth); return ((eth->tx_wr_ptr + ETH_TXBUF_COUNT) - eth->tx_rd_ptr) & (ETH_TXBUF_COUNT - 1); } void eth_enable_tx(ethdev_t* eth) { uint32_t tctl = readl(IE_TCTL); writel(tctl | IE_TCTL_EN, IE_TCTL); } void eth_disable_tx(ethdev_t* eth) { uint32_t tctl = readl(IE_TCTL); writel(tctl & ~IE_TCTL_EN, IE_TCTL); } void eth_start_promisc(ethdev_t* eth) { uint32_t rctl = readl(IE_RCTL); writel(rctl | IE_RCTL_UPE, IE_RCTL); } void eth_stop_promisc(ethdev_t* eth) { uint32_t rctl = readl(IE_RCTL); writel(rctl & ~IE_RCTL_UPE, IE_RCTL); } static zx_status_t wait_for_mdic(ethdev_t* eth, uint32_t* reg_value) { uint32_t mdic; uint32_t iterations = 0; do { nanosleep(50); mdic = readl(IE_MDIC); if (mdic & IE_MDIC_R) { goto success; } iterations++; } while (!(mdic & IE_MDIC_R) && (iterations < 100)); printf("intel-eth: timed out waiting for MDIC to be ready\n"); return ZX_ERR_TIMED_OUT; success: if (reg_value) { *reg_value = mdic; } return ZX_OK; } static zx_status_t phy_read(ethdev_t* eth, uint8_t phyadd, uint8_t regadd, uint16_t* result) { uint32_t mdic = IE_MDIC_PUT_PHYADD(phyadd) | IE_MDIC_PUT_REGADD(regadd) | IE_MDIC_OP_READ; writel(mdic, IE_MDIC); zx_status_t status = wait_for_mdic(eth, &mdic); if (status == ZX_OK) { *result = IE_MDIC_GET_DATA(mdic); } return status; } static zx_status_t phy_write(ethdev_t* eth, uint8_t phyadd, uint8_t regadd, uint16_t value) { uint32_t mdic = IE_MDIC_PUT_DATA(value) | IE_MDIC_PUT_PHYADD(phyadd) | IE_MDIC_PUT_REGADD(regadd) | IE_MDIC_OP_WRITE; writel(mdic, IE_MDIC); return wait_for_mdic(eth, NULL); } static zx_status_t get_phy_addr(ethdev_t* eth, uint8_t* phy_addr) { if (eth->phy_addr != 0) { *phy_addr = eth->phy_addr; } for (uint8_t addr = 1; addr <= IE_MAX_PHY_ADDR; addr++) { uint16_t pid; zx_status_t status = phy_read(eth, addr, IE_PHY_PID, &pid); // TODO: Identify the PHY more precisely if (status == ZX_OK && pid != 0) { *phy_addr = pid; return ZX_OK; } } printf("intel-eth: unable to identify valid PHY address\n"); return ZX_ERR_NOT_FOUND; } zx_status_t eth_enable_phy(ethdev_t* eth) { uint8_t phy_addr; zx_status_t status = get_phy_addr(eth, &phy_addr); if (status != ZX_OK) { return status; } uint16_t phy_ctrl; status = phy_read(eth, phy_addr, IE_PHY_PCTRL, &phy_ctrl); if (status != ZX_OK) { return status; } if (phy_ctrl & IE_PHY_PCTRL_POWER_DOWN) { return phy_write(eth, phy_addr, IE_PHY_PCTRL, phy_ctrl & ~IE_PHY_PCTRL_POWER_DOWN); } return ZX_OK; } zx_status_t eth_disable_phy(ethdev_t* eth) { uint8_t phy_addr; zx_status_t status = get_phy_addr(eth, &phy_addr); if (status != ZX_OK) { return status; } uint16_t phy_ctrl; status = phy_read(eth, phy_addr, IE_PHY_PCTRL, &phy_ctrl); if (status != ZX_OK) { return status; } return phy_write(eth, phy_addr, IE_PHY_PCTRL, phy_ctrl | IE_PHY_PCTRL_POWER_DOWN); } status_t eth_reset_hw(ethdev_t* eth) { // TODO: don't rely on bootloader having initialized the // controller in order to obtain the mac address uint32_t n; n = readl(IE_RAL(0)); memcpy(eth->mac + 0, &n, 4); n = readl(IE_RAH(0)); memcpy(eth->mac + 4, &n, 2); printf("eth: mac: %02x:%02x:%02x:%02x:%02x:%02x\n", eth->mac[0],eth->mac[1],eth->mac[2], eth->mac[3],eth->mac[4],eth->mac[5]); // disable all interrupts if (eth->pci_did == IE_DID_I211_AT) { writel(0, IE_IAM); } writel(0xffffffff, IE_IMC); // disable tx/rx writel(0, IE_RCTL); writel(IE_TCTL_PSP, IE_TCTL); // global reset uint32_t reg = readl(IE_CTRL); writel(reg | IE_CTRL_RST, IE_CTRL); if (eth->pci_did == IE_DID_I211_AT) { usleep(20); reg = readl(IE_STATUS); if (!(reg & IE_STATUS_PF_RST_DONE)) { printf("eth: reset failed (1)\n"); return ZX_ERR_BAD_STATE; } reg = readl(IE_EEC); if (!(reg & IE_EEC_AUTO_RD)) { printf("eth: reset failed (2)\n"); return ZX_ERR_BAD_STATE; } } else { usleep(5); if (readl(IE_CTRL) & IE_CTRL_RST) { printf("eth: reset failed\n"); return ZX_ERR_BAD_STATE; } } // disable all interrupts if (eth->pci_did == IE_DID_I211_AT) { writel(0, IE_IAM); } writel(0xffffffff, IE_IMC); // clear any pending interrupts readl(IE_ICR); return ZX_OK; } void eth_init_hw(ethdev_t* eth) { //TODO: tune RXDCTL and TXDCTL settings //TODO: TCTL COLD should be based on link state //TODO: use address filtering for multicast // set link up (Must be set to enable communications between MAC and PHY.) uint32_t reg = readl(IE_CTRL); writel(reg | IE_CTRL_SLU, IE_CTRL); usleep(15); // setup rx ring eth->rx_rd_ptr = 0; writel(eth->rxd_phys, IE_RDBAL); writel(eth->rxd_phys >> 32, IE_RDBAH); writel(ETH_RXBUF_COUNT * 16, IE_RDLEN); reg = IE_RXDCTL_PTHRESH(12) | IE_RXDCTL_HTHRESH(10) | IE_RXDCTL_WTHRESH(1); if (eth->pci_did == IE_DID_I211_AT) { reg |= IE_RXDCTL_ENABLE; } else { reg |= IE_RXDCTL_GRAN; } writel(reg, IE_RXDCTL); // wait for enable to complete if (eth->pci_did == IE_DID_I211_AT) { while (!(readl(IE_RXDCTL) & IE_RXDCTL_ENABLE)) { } } writel(ETH_RXBUF_COUNT - 1, IE_RDT); writel(IE_RCTL_BSIZE2048 | IE_RCTL_DPF | IE_RCTL_SECRC | IE_RCTL_BAM | IE_RCTL_MPE | IE_RCTL_EN, IE_RCTL); // setup tx ring eth->tx_wr_ptr = 0; eth->tx_rd_ptr = 0; writel(eth->txd_phys, IE_TDBAL); writel(eth->txd_phys >> 32, IE_TDBAH); writel(ETH_TXBUF_COUNT * 16, IE_TDLEN); reg = IE_TXDCTL_WTHRESH(1); if (eth->pci_did == IE_DID_I211_AT) { reg |= IE_TXDCTL_ENABLE; } else { reg |= IE_TXDCTL_GRAN; } writel(reg, IE_TXDCTL); // wait for enable to complete if (eth->pci_did == IE_DID_I211_AT) { while (!(readl(IE_TXDCTL) & IE_TXDCTL_ENABLE)) { } } if (eth->pci_did == IE_DID_I211_AT) { reg = IE_TCTL_CT(15) | IE_TCTL_BST(64) | IE_TCTL_PSP | IE_TCTL_EN; } else { reg = readl(IE_TCTL) & IE_TCTL_RESERVED; reg |= IE_TCTL_CT(15) | IE_TCTL_COLD_FD | IE_TCTL_EN; } writel(reg, IE_TCTL); // enable interrupts if (eth->pci_did == IE_DID_I211_AT) { // Receiver Descriptor Write Back & Link Status Change interrupts writel(IE_INT_RXDW | IE_INT_LSC, IE_IMS); } else { // enable rx & link status change irqs writel(IE_INT_RXT0 | IE_INT_LSC, IE_IMS); } } void eth_setup_buffers(ethdev_t* eth, void* iomem, zx_paddr_t iophys) { printf("eth: iomem @%p (phys %" PRIxPTR ")\n", iomem, iophys); list_initialize(ð->free_frames); list_initialize(ð->busy_frames); eth->rxd = iomem; eth->rxd_phys = iophys; iomem += ETH_DRING_SIZE; iophys += ETH_DRING_SIZE; memset(eth->rxd, 0, ETH_DRING_SIZE); eth->txd = iomem; eth->txd_phys = iophys; iomem += ETH_DRING_SIZE; iophys += ETH_DRING_SIZE; memset(eth->txd, 0, ETH_DRING_SIZE); eth->rxb = iomem; eth->rxb_phys = iophys; iomem += ETH_RXBUF_SIZE * ETH_RXBUF_COUNT; iophys += ETH_RXBUF_SIZE * ETH_RXBUF_COUNT; for (int n = 0; n < ETH_RXBUF_COUNT; n++) { eth->rxd[n].addr = eth->rxb_phys + ETH_RXBUF_SIZE * n; } for (int n = 0; n < ETH_TXBUF_COUNT - 1; n++) { framebuf_t *txb = iomem; txb->phys = iophys + ETH_TXBUF_HSIZE; txb->size = ETH_TXBUF_SIZE - ETH_TXBUF_HSIZE; txb->data = iomem + ETH_TXBUF_HSIZE; list_add_tail(ð->free_frames, &txb->node); iomem += ETH_TXBUF_SIZE; iophys += ETH_TXBUF_SIZE; } }