| // Copyright 2017 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 "rtl8111.h" |
| |
| #include <fuchsia/hardware/ethernet/cpp/banjo.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/device.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/io-buffer.h> |
| #include <lib/device-protocol/pci.h> |
| #include <lib/mmio/mmio-buffer.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <zircon/types.h> |
| |
| #include <ddktl/device.h> |
| #include <fbl/auto_lock.h> |
| |
| // DISCLAIMER: This driver has not been tested since rewriting in C++. See fxr/727017 for details. |
| |
| #define HI32(val) (((val) >> 32) & 0xffffffff) |
| #define LO32(val) ((val)&0xffffffff) |
| |
| typedef struct eth_desc { |
| uint32_t status1; |
| uint32_t status2; |
| uint64_t data_addr; |
| } eth_desc_t; |
| |
| class EthernetDevice; |
| using DeviceType = ddk::Device<EthernetDevice>; |
| |
| class EthernetDevice : public DeviceType, |
| public ddk::EthernetImplProtocol<EthernetDevice, ddk::base_protocol> { |
| public: |
| explicit EthernetDevice(zx_device_t* parent) : DeviceType(parent) {} |
| |
| static zx_status_t Bind(void* ctx, zx_device_t* parent) { |
| zxlogf(DEBUG, "rtl8111: binding device"); |
| |
| zx_status_t status; |
| auto edev = std::make_unique<EthernetDevice>(parent); |
| if (!edev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| mtx_init(&edev->lock_, mtx_plain); |
| mtx_init(&edev->tx_lock_, mtx_plain); |
| cnd_init(&edev->tx_cond_); |
| |
| edev->pci_ = ddk::Pci::FromFragment(parent); |
| if (!edev->pci_.is_valid()) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| status = edev->pci_.ConfigureInterruptMode(1, &edev->irq_mode_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: failed to configure irqs"); |
| return status; |
| } |
| |
| status = edev->pci_.MapInterrupt(0, &edev->irq_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: failed to map irq: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = edev->pci_.MapMmio(2u, ZX_CACHE_POLICY_UNCACHED_DEVICE, &edev->mmio_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: cannot map io: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = edev->pci_.SetBusMastering(true); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: cannot enable bus master: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = edev->pci_.GetBti(0, &edev->bti_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: could not get bti: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| uint32_t mac_version = edev->mmio_->Read32(RTL_TCR) & 0x7cf00000; |
| zxlogf(DEBUG, "rtl8111: version 0x%08x", mac_version); |
| |
| // TODO(stevensd): Don't require a contiguous buffer |
| uint32_t alloc_size = ((ETH_BUF_SIZE + ETH_DESC_ELT_SIZE) * ETH_BUF_COUNT) * 2; |
| status = edev->buffer_.Init(edev->bti_.get(), alloc_size, IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: cannot alloc io-buffer: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| edev->InitBuffers(); |
| edev->InitRegs(); |
| |
| status = edev->DdkAdd(ddk::DeviceAddArgs("rtl8111").set_proto_id(ZX_PROTOCOL_ETHERNET_IMPL)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: failed to add device: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = thrd_create_with_name( |
| &edev->irq_thread_, |
| [](void* ctx) { return reinterpret_cast<EthernetDevice*>(ctx)->IrqThread(); }, &edev, |
| "rtl-irq-thread"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rtl8111: failed to create irq thread: %s", zx_status_get_string(status)); |
| return ZX_OK; // The cleanup will be done in release |
| } |
| |
| zxlogf(DEBUG, "rtl8111: bind successful"); |
| |
| return ZX_OK; |
| } |
| |
| void InitBuffers() { |
| zxlogf(DEBUG, "rtl8111: Initializing buffers"); |
| txd_ring_ = reinterpret_cast<eth_desc_t*>(buffer_.virt()); |
| txd_phys_addr_ = buffer_.phys(); |
| txd_idx_ = 0; |
| txb_ = static_cast<uint8_t*>(buffer_.virt()) + (2 * ETH_DESC_RING_SIZE); |
| |
| rxd_ring_ = |
| reinterpret_cast<eth_desc_t*>(static_cast<uint8_t*>(buffer_.virt()) + ETH_DESC_RING_SIZE); |
| rxd_phys_addr_ = buffer_.phys() + ETH_DESC_RING_SIZE; |
| rxd_idx_ = 0; |
| rxb_ = static_cast<uint8_t*>(txb_) + (ETH_BUF_SIZE * ETH_BUF_COUNT); |
| |
| uint64_t txb_phys = buffer_.phys() + (2 * ETH_DESC_RING_SIZE); |
| uint64_t rxb_phys = txb_phys + (ETH_BUF_COUNT * ETH_BUF_SIZE); |
| for (int i = 0; i < ETH_BUF_COUNT; i++) { |
| bool is_end = i == (ETH_BUF_COUNT - 1); |
| rxd_ring_[i].status1 = RX_DESC_OWN | (is_end ? RX_DESC_EOR : 0) | ETH_BUF_SIZE; |
| rxd_ring_[i].status2 = 0; |
| rxd_ring_[i].data_addr = rxb_phys; |
| |
| txd_ring_[i].status1 = 0; |
| txd_ring_[i].status2 = 0; |
| txd_ring_[i].data_addr = txb_phys; |
| |
| rxb_phys += ETH_BUF_SIZE; |
| txb_phys += ETH_BUF_SIZE; |
| } |
| } |
| |
| void InitRegs() { |
| zxlogf(DEBUG, "rtl8111: Initializing registers"); |
| |
| // C+CR needs to be configured first - enable rx VLAN detagging and checksum offload |
| mmio_->Write16(RTL_CPLUSCR, |
| mmio_->Read16(RTL_CPLUSCR) | RTL_CPLUSCR_RXVLAN | RTL_CPLUSCR_RXCHKSUM); |
| |
| // Reset the controller and wait for the operation to finish |
| mmio_->Write8(RTL_CR, mmio_->Read8(RTL_CR) | RTL_CR_RST); |
| while (mmio_->Read8(RTL_CR) & RTL_CR_RST) { |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(10))); |
| } |
| |
| // Unlock the configuration registers |
| mmio_->Write8(RTL_9436CR, |
| (mmio_->Read8(RTL_9436CR) & RTL_9436CR_EEM_MASK) | RTL_9436CR_EEM_UNLOCK); |
| |
| // Set the tx and rx maximum packet size |
| mmio_->Write8(RTL_MTPS, (mmio_->Read8(RTL_MTPS) & RTL_MTPS_MTPS_MASK) | |
| ZX_ROUNDUP(ETH_BUF_SIZE, 128) / 128); |
| mmio_->Write16(RTL_RMS, (mmio_->Read16(RTL_RMS) & RTL_RMS_RMS_MASK) | ETH_BUF_SIZE); |
| |
| // Set the rx/tx descriptor ring addresses |
| mmio_->Write32(RTL_RDSAR_LOW, LO32(rxd_phys_addr_)); |
| mmio_->Write32(RTL_RDSAR_HIGH, HI32(rxd_phys_addr_)); |
| mmio_->Write32(RTL_TNPDS_LOW, LO32(txd_phys_addr_)); |
| mmio_->Write32(RTL_TNPDS_HIGH, HI32(txd_phys_addr_)); |
| |
| // Set the interframe gap and max DMA burst size in the tx config register |
| uint32_t tcr = mmio_->Read32(RTL_TCR) & ~(RTL_TCR_IFG_MASK | RTL_TCR_MXDMA_MASK); |
| mmio_->Write32(RTL_TCR, tcr | RTL_TCR_IFG96 | RTL_TCR_MXDMA_UNLIMITED); |
| |
| // Disable interrupts except link change and rx-ok and then clear all interrupts |
| mmio_->Write16(RTL_IMR, |
| (mmio_->Read16(RTL_IMR) & ~RTL_INT_MASK) | RTL_INT_LINKCHG | RTL_INT_ROK); |
| mmio_->Write16(RTL_ISR, 0xffff); |
| |
| // Lock the configuration registers and enable rx/tx |
| mmio_->Write8(RTL_9436CR, |
| (mmio_->Read8(RTL_9436CR) & RTL_9436CR_EEM_MASK) | RTL_9436CR_EEM_LOCK); |
| mmio_->Write8(RTL_CR, mmio_->Read8(RTL_CR) | RTL_CR_RE | RTL_CR_TE); |
| |
| // Configure the max dma burst, what types of packets we accept, and the multicast filter |
| uint32_t rcr = mmio_->Read32(RTL_RCR) & ~(RTL_RCR_MXDMA_MASK | RTL_RCR_ACCEPT_MASK); |
| mmio_->Write32(RTL_RCR, rcr | RTL_RCR_MXDMA_UNLIMITED | RTL_RCR_AB | RTL_RCR_AM | RTL_RCR_APM); |
| mmio_->Write32(RTL_MAR7, 0xffffffff); // Accept all multicasts |
| mmio_->Write32(RTL_MAR3, 0xffffffff); |
| |
| // Read the MAC and link status |
| uint32_t n = mmio_->Read32(RTL_MAC0); |
| memcpy(mac_, &n, 4); |
| n = mmio_->Read32(RTL_MAC1); |
| memcpy(mac_ + 4, &n, 2); |
| |
| online_ = mmio_->Read8(RTL_PHYSTATUS) & RTL_PHYSTATUS_LINKSTS; |
| |
| zxlogf(INFO, "rtl111: mac address=%02x:%02x:%02x:%02x:%02x:%02x, link %s", mac_[0], mac_[1], |
| mac_[2], mac_[3], mac_[4], mac_[5], online_ ? "online" : "offline"); |
| } |
| |
| int IrqThread() { |
| while (1) { |
| zx_status_t status = irq_.wait(nullptr); |
| if (status != ZX_OK) { |
| zxlogf(DEBUG, "rtl8111: irq wait failed: %s", zx_status_get_string(status)); |
| break; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| uint16_t isr = mmio_->Read16(RTL_ISR); |
| if (isr & RTL_INT_LINKCHG) { |
| bool was_online = online_; |
| bool online = mmio_->Read8(RTL_PHYSTATUS) & RTL_PHYSTATUS_LINKSTS; |
| if (online != was_online) { |
| zxlogf(INFO, "rtl8111: link %s", online ? "online" : "offline"); |
| online_ = online; |
| if (ifc_.ops) { |
| ethernet_ifc_status(&ifc_, online ? ETHERNET_STATUS_ONLINE : 0); |
| } |
| } |
| } |
| if (isr & RTL_INT_TOK) { |
| cnd_signal(&tx_cond_); |
| } |
| if (isr & RTL_INT_ROK) { |
| eth_desc_t* rxd; |
| while (!((rxd = rxd_ring_ + rxd_idx_)->status1 & RX_DESC_OWN)) { |
| if (ifc_.ops) { |
| size_t len = rxd->status1 & RX_DESC_LEN_MASK; |
| ethernet_ifc_recv(&ifc_, static_cast<uint8_t*>(rxb_) + (rxd_idx_ * ETH_BUF_SIZE), len, |
| 0); |
| } else { |
| zxlogf(ERROR, "rtl8111: No ethmac callback, dropping packet"); |
| } |
| |
| bool is_end = rxd_idx_ == (ETH_BUF_COUNT - 1); |
| rxd->status1 = RX_DESC_OWN | (is_end ? RX_DESC_EOR : 0) | ETH_BUF_SIZE; |
| |
| rxd_idx_ = (rxd_idx_ + 1) % ETH_BUF_COUNT; |
| } |
| } |
| |
| mmio_->Write16(RTL_ISR, 0xffff); |
| |
| if (irq_mode_ == fuchsia_hardware_pci::InterruptMode::kLegacy) { |
| pci_.AckInterrupt(); |
| } |
| } |
| return 0; |
| } |
| |
| zx_status_t EthernetImplQuery(uint32_t options, ethernet_info_t* info) { |
| if (options) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| memset(info, 0, sizeof(*info)); |
| info->mtu = ETH_BUF_SIZE; |
| memcpy(info->mac, mac_, sizeof(mac_)); |
| info->netbuf_size = sizeof(ethernet_netbuf_t); |
| |
| return ZX_OK; |
| } |
| |
| void EthernetImplStop() { |
| fbl::AutoLock lock(&lock_); |
| ifc_.ops = NULL; |
| } |
| |
| zx_status_t EthernetImplStart(const ethernet_ifc_protocol_t* ifc) { |
| zx_status_t status = ZX_OK; |
| |
| fbl::AutoLock lock(&lock_); |
| if (ifc_.ops) { |
| status = ZX_ERR_BAD_STATE; |
| } else { |
| ifc_ = *ifc; |
| ethernet_ifc_status(&ifc_, online_ ? ETHERNET_STATUS_ONLINE : 0); |
| } |
| |
| return status; |
| } |
| |
| void EthernetImplQueueTx(uint32_t options, ethernet_netbuf_t* netbuf, |
| ethernet_impl_queue_tx_callback completion_cb, void* cookie) { |
| if (netbuf->data_size > (size_t)ETH_BUF_SIZE) { |
| zxlogf(ERROR, "rtl8111: Unsupported packet length %zu", netbuf->data_size); |
| completion_cb(cookie, ZX_ERR_INVALID_ARGS, netbuf); |
| return; |
| } |
| uint32_t length = (uint32_t)netbuf->data_size; |
| |
| fbl::AutoLock tx_lock(&tx_lock_); |
| |
| if (txd_ring_[txd_idx_].status1 & TX_DESC_OWN) { |
| fbl::AutoLock lock(&lock_); |
| mmio_->Write16(RTL_IMR, mmio_->Read16(RTL_IMR) | RTL_INT_TOK); |
| mmio_->Write16(RTL_ISR, RTL_INT_TOK); |
| |
| while (txd_ring_[txd_idx_].status1 & TX_DESC_OWN) { |
| zxlogf(DEBUG, "rtl8111: Waiting for buffer"); |
| cnd_wait(&tx_cond_, &lock_); |
| } |
| |
| mmio_->Write16(RTL_IMR, mmio_->Read16(RTL_IMR) & ~RTL_INT_TOK); |
| } |
| |
| memcpy(static_cast<uint8_t*>(txb_) + (txd_idx_ * ETH_BUF_SIZE), netbuf->data_buffer, length); |
| |
| bool is_end = txd_idx_ == (ETH_BUF_COUNT - 1); |
| txd_ring_[txd_idx_].status1 = |
| (is_end ? TX_DESC_EOR : 0) | length | TX_DESC_OWN | TX_DESC_FS | TX_DESC_LS; |
| |
| mmio_->Write8(RTL_TPPOLL, mmio_->Read8(RTL_TPPOLL) | RTL_TPPOLL_NPQ); |
| |
| txd_idx_ = (txd_idx_ + 1) % ETH_BUF_COUNT; |
| |
| completion_cb(cookie, ZX_OK, netbuf); |
| } |
| |
| zx_status_t SetPromisc(bool on) { |
| if (on) { |
| mmio_->Write16(RTL_RCR, mmio_->Read16(RTL_RCR | RTL_RCR_AAP)); |
| } else { |
| mmio_->Write16(RTL_RCR, mmio_->Read16(RTL_RCR & ~RTL_RCR_AAP)); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t EthernetImplSetParam(uint32_t param, int32_t value, const uint8_t* data, |
| size_t data_size) { |
| fbl::AutoLock lock(&lock_); |
| switch (param) { |
| case ETHERNET_SETPARAM_PROMISC: |
| return SetPromisc((bool)value); |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| void EthernetImplGetBti(zx::bti* out_bti) {} |
| |
| void DdkRelease() { |
| mmio_->Write8(RTL_CR, mmio_->Read8(RTL_CR) | RTL_CR_RST); |
| pci_.SetBusMastering(false); |
| delete this; |
| } |
| |
| private: |
| mtx_t lock_; |
| mtx_t tx_lock_; |
| cnd_t tx_cond_; |
| ddk::Pci pci_; |
| fuchsia_hardware_pci::InterruptMode irq_mode_; |
| zx::interrupt irq_; |
| std::optional<fdf::MmioBuffer> mmio_; |
| thrd_t irq_thread_; |
| zx::bti bti_; |
| ddk::IoBuffer buffer_; |
| |
| eth_desc_t* txd_ring_; |
| uint64_t txd_phys_addr_; |
| int txd_idx_; |
| void* txb_; |
| |
| eth_desc_t* rxd_ring_; |
| uint64_t rxd_phys_addr_; |
| int rxd_idx_; |
| void* rxb_; |
| |
| uint8_t mac_[6]; |
| bool online_; |
| |
| ethernet_ifc_protocol_t ifc_; |
| }; |
| |
| static zx_driver_ops_t rtl8111_ethernet_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = EthernetDevice::Bind, |
| }; |
| |
| ZIRCON_DRIVER(realtek_rtl8111, rtl8111_ethernet_driver_ops, "zircon", "0.1"); |