blob: 44683efc43998c9e4a937de7c800f796d72b2f66 [file] [log] [blame]
// Copyright 2018 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 "dwmac.h"
#include "dw-gmac-dma.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/ethernet/mac.h>
#include <ddk/protocol/platform-device-lib.h>
#include <ddk/protocol/platform/device.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <hw/arch_ops.h>
#include <hw/reg.h>
#include <lib/fzl/vmar-manager.h>
#include <stdio.h>
#include <string.h>
#include <zircon/compiler.h>
namespace eth {
namespace {
// MMIO Indexes.
constexpr uint32_t kEthMacMmio = 0;
} // namespace
template <typename T, typename U>
static inline T* offset_ptr(U* ptr, size_t offset) {
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(ptr) + offset);
}
int DWMacDevice::Thread() {
zxlogf(INFO, "ethmac started\n");
zx_status_t status;
while (true) {
status = dma_irq_.wait(nullptr);
if (!running_.load()) {
status = ZX_OK;
break;
}
if (status != ZX_OK) {
zxlogf(ERROR, "dwmac: Interrupt error\n");
break;
}
uint32_t stat = dwdma_regs_->status;
dwdma_regs_->status = stat;
if (stat & DMA_STATUS_GLI) {
fbl::AutoLock lock(&lock_); //Note: limited scope of autolock
UpdateLinkStatus();
}
if (stat & DMA_STATUS_RI) {
ProcRxBuffer(stat);
}
if (stat & DMA_STATUS_AIS) {
bus_errors_++;
zxlogf(ERROR, "dwmac: abnormal interrupt %08x\n", stat);
}
}
return status;
}
int DWMacDevice::WorkerThread() {
// Note: Need to wait here for all PHY's to register
// their callbacks before proceeding further.
// Currently only supporting single PHY, we can add
// support for multiple PHY's easily when needed.
sync_completion_wait(&cb_registered_signal_, ZX_TIME_INFINITE);
// Configure the phy.
cbs_.config_phy(cbs_.ctx, mac_);
InitDevice();
auto thunk = [](void* arg) -> int { return reinterpret_cast<DWMacDevice*>(arg)->Thread(); };
running_.store(true);
int ret = thrd_create_with_name(&thread_, thunk,
this,
"mac-thread");
ZX_DEBUG_ASSERT(ret == thrd_success);
zx_status_t status = DdkAdd("Designware MAC");
if (status != ZX_OK) {
zxlogf(ERROR, "dwmac: Could not create eth device: %d\n", status);
return status;
} else {
zxlogf(INFO, "dwmac: Added dwMac device\n");
}
return status;
}
void DWMacDevice::UpdateLinkStatus() {
bool temp = dwmac_regs_->rgmiistatus & GMAC_RGMII_STATUS_LNKSTS;
if (temp != online_) {
online_ = temp;
if (ethmac_client_.is_valid()) {
ethmac_client_.Status(online_ ? ETHMAC_STATUS_ONLINE : 0u);
} else {
zxlogf(ERROR, "dwmac: System not ready\n");
}
}
if (online_) {
dwmac_regs_->conf |= GMAC_CONF_TE | GMAC_CONF_RE;
} else {
dwmac_regs_->conf &= ~(GMAC_CONF_TE | GMAC_CONF_RE);
}
zxlogf(INFO, "dwmac: Link is now %s\n", online_ ? "up" : "down");
}
zx_status_t DWMacDevice::InitPdev() {
zx_status_t status = device_get_protocol(parent_,
ZX_PROTOCOL_PDEV,
&pdev_);
if (status != ZX_OK) {
return status;
}
// Map mac control registers and dma control registers.
status = pdev_.MapMmio(kEthMacMmio, &dwmac_regs_iobuff_);
if (status != ZX_OK) {
zxlogf(ERROR, "dwmac: could not map dwmac mmio: %d\n", status);
return status;
}
dwmac_regs_ = static_cast<dw_mac_regs_t*>(dwmac_regs_iobuff_->get());
dwdma_regs_ = offset_ptr<dw_dma_regs_t>(dwmac_regs_, DW_DMA_BASE_OFFSET);
// Map dma interrupt.
status = pdev_.GetInterrupt(0, &dma_irq_);
if (status != ZX_OK) {
zxlogf(ERROR, "dwmac: could not map dma interrupt\n");
return status;
}
// Get our bti.
status = pdev_.GetBti(0, &bti_);
if (status != ZX_OK) {
zxlogf(ERROR, "dwmac: could not obtain bti: %d\n", status);
return status;
}
// Get ETH_BOARD protocol.
if (!eth_board_.is_valid()) {
zxlogf(ERROR, "dwmac: could not obtain ETH_BOARD protocol: %d\n", status);
return status;
}
return status;
}
zx_status_t DWMacDevice::Create(zx_device_t* device) {
auto mac_device = fbl::make_unique<DWMacDevice>(device);
zx_status_t status = mac_device->InitPdev();
if (status != ZX_OK) {
return status;
}
// Reset the phy.
mac_device->eth_board_.ResetPhy();
// Get and cache the mac address.
mac_device->GetMAC(device);
// Reset the dma peripheral.
mac_device->dwdma_regs_->busmode |= DMAMAC_SRST;
uint32_t loop_count = 10;
do {
zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
loop_count--;
} while ((mac_device->dwdma_regs_->busmode & DMAMAC_SRST) && loop_count);
if (!loop_count) {
return ZX_ERR_TIMED_OUT;
}
// Mac address register was erased by the reset; set it!
mac_device->dwmac_regs_->macaddr0hi = (mac_device->mac_[5] << 8) | (mac_device->mac_[4] << 0);
mac_device->dwmac_regs_->macaddr0lo = (mac_device->mac_[3] << 24) | (mac_device->mac_[2] << 16)
| (mac_device->mac_[1] << 8) | (mac_device->mac_[0] << 0);
auto cleanup = fbl::MakeAutoCall([&]() { mac_device->ShutDown(); });
status = mac_device->InitBuffers();
if (status != ZX_OK)
return status;
sync_completion_reset(&mac_device->cb_registered_signal_);
// Populate board specific information
eth_dev_metadata_t phy_info;
size_t actual;
status = device_get_metadata(device, DEVICE_METADATA_PRIVATE, &phy_info,
sizeof(eth_dev_metadata_t), &actual);
if (status != ZX_OK || actual != sizeof(eth_dev_metadata_t)) {
zxlogf(ERROR, "dwmac: Could not get PHY metadata %d\n", status);
return status;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, phy_info.vid},
{BIND_PLATFORM_DEV_DID, 0, phy_info.did},
{BIND_PLATFORM_DEV_PID, 0, phy_info.pid},
};
device_add_args_t phy_device_args = {};
phy_device_args.version = DEVICE_ADD_ARGS_VERSION;
phy_device_args.name = "eth_phy";
phy_device_args.ops = &mac_device->ddk_device_proto_,
phy_device_args.proto_id = ZX_PROTOCOL_ETH_MAC;
phy_device_args.props = props;
phy_device_args.prop_count = countof(props);
phy_device_args.ctx = mac_device.get();
phy_device_args.proto_ops = &mac_device->eth_mac_protocol_ops_;
// TODO(braval): use proper device pointer, depending on how
// many PHY devices we have to load, from the metadata.
zx_device_t* dev;
status = device_add(device, &phy_device_args, &dev);
if (status != ZX_OK) {
zxlogf(ERROR, "dwmac: Could not create phy device: %d\n", status);
return status;
}
auto worker_thunk = [](void* arg) -> int {
return reinterpret_cast<DWMacDevice*>(arg)->WorkerThread();
};
int ret = thrd_create_with_name(&mac_device->worker_thread_, worker_thunk,
reinterpret_cast<void*>(mac_device.get()),
"mac-worker-thread");
ZX_DEBUG_ASSERT(ret == thrd_success);
cleanup.cancel();
// mac_device intentionally leaked as it is now held by DevMgr.
__UNUSED auto ptr = mac_device.release();
return ZX_OK;
} // namespace eth
zx_status_t DWMacDevice::InitBuffers() {
constexpr size_t kDescSize = ROUNDUP(2 * kNumDesc * sizeof(dw_dmadescr_t), PAGE_SIZE);
constexpr size_t kBufSize = 2 * kNumDesc * kTxnBufSize;
txn_buffer_ = PinnedBuffer::Create(kBufSize, bti_, ZX_CACHE_POLICY_CACHED);
desc_buffer_ = PinnedBuffer::Create(kDescSize, bti_, ZX_CACHE_POLICY_UNCACHED);
tx_buffer_ = static_cast<uint8_t*>(txn_buffer_->GetBaseAddress());
zx_cache_flush(tx_buffer_, kBufSize, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE);
//rx buffer right after tx
rx_buffer_ = &tx_buffer_[kBufSize / 2];
tx_descriptors_ = static_cast<dw_dmadescr_t*>(desc_buffer_->GetBaseAddress());
//rx descriptors right after tx
rx_descriptors_ = &tx_descriptors_[kNumDesc];
zx_paddr_t tmpaddr;
// Initialize descriptors. Doing tx and rx all at once
for (uint i = 0; i < kNumDesc; i++) {
desc_buffer_->LookupPhys(((i + 1) % kNumDesc) * sizeof(dw_dmadescr_t), &tmpaddr);
tx_descriptors_[i].dmamac_next = static_cast<uint32_t>(tmpaddr);
txn_buffer_->LookupPhys(i * kTxnBufSize, &tmpaddr);
tx_descriptors_[i].dmamac_addr = static_cast<uint32_t>(tmpaddr);
tx_descriptors_[i].txrx_status = 0;
tx_descriptors_[i].dmamac_cntl = DESC_TXCTRL_TXCHAIN;
desc_buffer_->LookupPhys((((i + 1) % kNumDesc) + kNumDesc) * sizeof(dw_dmadescr_t),
&tmpaddr);
rx_descriptors_[i].dmamac_next = static_cast<uint32_t>(tmpaddr);
txn_buffer_->LookupPhys((i + kNumDesc) * kTxnBufSize, &tmpaddr);
rx_descriptors_[i].dmamac_addr = static_cast<uint32_t>(tmpaddr);
rx_descriptors_[i].dmamac_cntl =
(MAC_MAX_FRAME_SZ & DESC_RXCTRL_SIZE1MASK) |
DESC_RXCTRL_RXCHAIN;
rx_descriptors_[i].txrx_status = DESC_RXSTS_OWNBYDMA;
}
desc_buffer_->LookupPhys(0, &tmpaddr);
dwdma_regs_->txdesclistaddr = static_cast<uint32_t>(tmpaddr);
desc_buffer_->LookupPhys(kNumDesc * sizeof(dw_dmadescr_t), &tmpaddr);
dwdma_regs_->rxdesclistaddr = static_cast<uint32_t>(tmpaddr);
return ZX_OK;
}
void DWMacDevice::EthmacGetBti(zx::bti* bti) {
bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, bti);
}
zx_status_t DWMacDevice::EthMacMdioWrite(uint32_t reg, uint32_t val) {
dwmac_regs_->miidata = val;
uint32_t miiaddr = (mii_addr_ << MIIADDRSHIFT) |
(reg << MIIREGSHIFT) |
MII_WRITE;
dwmac_regs_->miiaddr = miiaddr | MII_CLKRANGE_150_250M | MII_BUSY;
zx_time_t deadline = zx_deadline_after(ZX_MSEC(3));
do {
if (!(dwmac_regs_->miiaddr & MII_BUSY)) {
return ZX_OK;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
} while (zx_clock_get_monotonic() < deadline);
return ZX_ERR_TIMED_OUT;
}
zx_status_t DWMacDevice::EthMacMdioRead(uint32_t reg, uint32_t* val) {
uint32_t miiaddr = (mii_addr_ << MIIADDRSHIFT) |
(reg << MIIREGSHIFT);
dwmac_regs_->miiaddr = miiaddr | MII_CLKRANGE_150_250M | MII_BUSY;
zx_time_t deadline = zx_deadline_after(ZX_MSEC(3));
do {
if (!(dwmac_regs_->miiaddr & MII_BUSY)) {
*val = dwmac_regs_->miidata;
return ZX_OK;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
} while (zx_clock_get_monotonic() < deadline);
return ZX_ERR_TIMED_OUT;
}
zx_status_t DWMacDevice::EthMacRegisterCallbacks(const eth_mac_callbacks_t* cbs) {
if (cbs == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
cbs_ = *cbs;
sync_completion_signal(&cb_registered_signal_);
return ZX_OK;
}
DWMacDevice::DWMacDevice(zx_device_t* device)
: ddk::Device<DWMacDevice, ddk::Unbindable>(device), pdev_(device), eth_board_(device) {}
void DWMacDevice::ReleaseBuffers() {
//Unpin the memory used for the dma buffers
if (txn_buffer_->UnPin() != ZX_OK) {
zxlogf(ERROR, "dwmac: Error unpinning transaction buffers\n");
}
if (desc_buffer_->UnPin() != ZX_OK) {
zxlogf(ERROR, "dwmac: Error unpinning description buffers\n");
}
}
void DWMacDevice::DdkRelease() {
zxlogf(INFO, "Ethmac release...\n");
delete this;
}
void DWMacDevice::DdkUnbind() {
zxlogf(INFO, "Ethmac DdkUnbind\n");
ShutDown();
DdkRemove();
}
zx_status_t DWMacDevice::ShutDown() {
running_.store(false);
dma_irq_.destroy();
thrd_join(thread_, NULL);
fbl::AutoLock lock(&lock_);
online_ = false;
ethmac_client_.clear();
DeInitDevice();
ReleaseBuffers();
return ZX_OK;
}
zx_status_t DWMacDevice::GetMAC(zx_device_t* dev) {
// look for MAC address device metadata
// metadata is padded so we need buffer size > 6 bytes
uint8_t buffer[16];
size_t actual;
zx_status_t status = device_get_metadata(dev, DEVICE_METADATA_MAC_ADDRESS, buffer,
sizeof(buffer), &actual);
if (status != ZX_OK || actual < 6) {
zxlogf(ERROR, "dwmac: MAC address metadata load failed. Falling back on HW setting.");
// read MAC address from hardware register
uint32_t hi = dwmac_regs_->macaddr0hi;
uint32_t lo = dwmac_regs_->macaddr0lo;
/* Extract the MAC address from the high and low words */
buffer[0] = static_cast<uint8_t>(lo & 0xff);
buffer[1] = static_cast<uint8_t>((lo >> 8) & 0xff);
buffer[2] = static_cast<uint8_t>((lo >> 16) & 0xff);
buffer[3] = static_cast<uint8_t>((lo >> 24) & 0xff);
buffer[4] = static_cast<uint8_t>(hi & 0xff);
buffer[5] = static_cast<uint8_t>((hi >> 8) & 0xff);
}
zxlogf(INFO, "dwmac: MAC address %02x:%02x:%02x:%02x:%02x:%02x\n",
buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
memcpy(mac_, buffer, sizeof mac_);
return ZX_OK;
}
zx_status_t DWMacDevice::EthmacQuery(uint32_t options, ethmac_info_t* info) {
memset(info, 0, sizeof(*info));
info->features = ETHMAC_FEATURE_DMA;
info->mtu = 1500;
memcpy(info->mac, mac_, sizeof info->mac);
info->netbuf_size = sizeof(ethmac_netbuf_t);
return ZX_OK;
}
void DWMacDevice::EthmacStop() {
zxlogf(INFO, "Stopping Ethermac\n");
fbl::AutoLock lock(&lock_);
ethmac_client_.clear();
}
zx_status_t DWMacDevice::EthmacStart(const ethmac_ifc_t* ifc) {
fbl::AutoLock lock(&lock_);
if (ethmac_client_.is_valid()) {
zxlogf(ERROR, "dwmac: Already bound!!!");
return ZX_ERR_ALREADY_BOUND;
} else {
ethmac_client_ = ddk::EthmacIfcClient(ifc);
UpdateLinkStatus();
zxlogf(INFO, "dwmac: Started\n");
}
return ZX_OK;
}
zx_status_t DWMacDevice::InitDevice() {
dwdma_regs_->intenable = 0;
dwdma_regs_->busmode = X8PBL | DMA_PBL;
dwdma_regs_->opmode = DMA_OPMODE_TSF | DMA_OPMODE_RSF;
dwdma_regs_->opmode |= DMA_OPMODE_SR | DMA_OPMODE_ST; //start tx and rx
//Clear all the interrupt flags
dwdma_regs_->status = ~0;
//Enable Interrupts
dwdma_regs_->intenable = DMA_INT_NIE | DMA_INT_AIE | DMA_INT_FBE |
DMA_INT_RIE | DMA_INT_RUE | DMA_INT_OVE |
DMA_INT_UNE | DMA_INT_TSE | DMA_INT_RSE;
dwmac_regs_->macaddr1lo = 0;
dwmac_regs_->macaddr1hi = 0;
dwmac_regs_->hashtablehigh = 0xffffffff;
dwmac_regs_->hashtablelow = 0xffffffff;
//TODO - configure filters
zxlogf(INFO, "macaddr0hi = %08x\n", dwmac_regs_->macaddr0hi);
zxlogf(INFO, "macaddr0lo = %08x\n", dwmac_regs_->macaddr0lo);
dwmac_regs_->framefilt |= (1 << 10) | (1 << 4) | (1 << 0); //promiscuous
dwmac_regs_->conf = GMAC_CORE_INIT;
return ZX_OK;
}
zx_status_t DWMacDevice::DeInitDevice() {
//Disable Interrupts
dwdma_regs_->intenable = 0;
//Disable Transmit and Receive
dwmac_regs_->conf &= ~(GMAC_CONF_TE | GMAC_CONF_RE);
//reset the phy (hold in reset)
//gpio_write(&gpios_[PHY_RESET], 0);
//transmit and receive are not disables, safe to null descriptor list ptrs
dwdma_regs_->txdesclistaddr = 0;
dwdma_regs_->rxdesclistaddr = 0;
return ZX_OK;
}
uint32_t DWMacDevice::DmaRxStatus() {
return (dwdma_regs_->status & DMA_STATUS_RS_MASK) >> DMA_STATUS_RS_POS;
}
void DWMacDevice::ProcRxBuffer(uint32_t int_status) {
while (true) {
uint32_t pkt_stat = rx_descriptors_[curr_rx_buf_].txrx_status;
if (pkt_stat & DESC_RXSTS_OWNBYDMA) {
return;
}
size_t fr_len = (pkt_stat & DESC_RXSTS_FRMLENMSK) >> DESC_RXSTS_FRMLENSHFT;
if (fr_len > kTxnBufSize) {
zxlogf(ERROR, "dwmac: unsupported packet size received\n");
return;
}
uint8_t* temptr = &rx_buffer_[curr_rx_buf_ * kTxnBufSize];
zx_cache_flush(temptr, kTxnBufSize, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE);
{ // limit scope of autolock
fbl::AutoLock lock(&lock_);
if ((ethmac_client_.is_valid())) {
ethmac_client_.Recv(temptr, fr_len, 0);
} else {
zxlogf(ERROR, "Dropping bad packet\n");
}
};
rx_descriptors_[curr_rx_buf_].txrx_status = DESC_RXSTS_OWNBYDMA;
rx_packet_++;
curr_rx_buf_ = (curr_rx_buf_ + 1) % kNumDesc;
if (curr_rx_buf_ == 0) {
loop_count_++;
}
dwdma_regs_->rxpolldemand = ~0;
}
}
zx_status_t DWMacDevice::EthmacQueueTx(uint32_t options, ethmac_netbuf_t* netbuf) {
{ //Check to make sure we are ready to accept packets
fbl::AutoLock lock(&lock_);
if (!online_) {
return ZX_ERR_UNAVAILABLE;
}
}
if (netbuf->data_size > kTxnBufSize) {
return ZX_ERR_INVALID_ARGS;
}
if (tx_descriptors_[curr_tx_buf_].txrx_status & DESC_TXSTS_OWNBYDMA) {
zxlogf(ERROR, "TX buffer overrun@ %u\n", curr_tx_buf_);
return ZX_ERR_UNAVAILABLE;
}
uint8_t* temptr = &tx_buffer_[curr_tx_buf_ * kTxnBufSize];
memcpy(temptr, netbuf->data_buffer, netbuf->data_size);
hw_mb();
zx_cache_flush(temptr, netbuf->data_size, ZX_CACHE_FLUSH_DATA);
// Descriptors are pre-iniitialized with the paddr of their corresponding
// buffers, only need to setup the control and status fields.
tx_descriptors_[curr_tx_buf_].dmamac_cntl =
DESC_TXCTRL_TXINT |
DESC_TXCTRL_TXLAST |
DESC_TXCTRL_TXFIRST |
DESC_TXCTRL_TXCHAIN |
((uint32_t)netbuf->data_size & DESC_TXCTRL_SIZE1MASK);
tx_descriptors_[curr_tx_buf_].txrx_status = DESC_TXSTS_OWNBYDMA;
curr_tx_buf_ = (curr_tx_buf_ + 1) % kNumDesc;
hw_mb();
dwdma_regs_->txpolldemand = ~0;
tx_counter_++;
return ZX_OK;
}
zx_status_t DWMacDevice::EthmacSetParam(uint32_t param, int32_t value, const void* data,
size_t data_size) {
zxlogf(INFO, "SetParam called %x %x\n", param, value);
return ZX_OK;
}
} // namespace eth
extern "C" zx_status_t dwmac_bind(void* ctx, zx_device_t* device, void** cookie) {
return eth::DWMacDevice::Create(device);
}