Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+671
View File
@@ -0,0 +1,671 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 7990.c -- LANCE ethernet IC generic routines.
* This is an attempt to separate out the bits of various ethernet
* drivers that are common because they all use the AMD 7990 LANCE
* (Local Area Network Controller for Ethernet) chip.
*
* Copyright (C) 05/1998 Peter Maydell <pmaydell@chiark.greenend.org.uk>
*
* Most of this stuff was obtained by looking at other LANCE drivers,
* in particular a2065.[ch]. The AMD C-LANCE datasheet was also helpful.
* NB: this was made easy by the fact that Jes Sorensen had cleaned up
* most of a2025 and sunlance with the aim of merging them, so the
* common code was pretty obvious.
*/
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/route.h>
#include <linux/string.h>
#include <linux/skbuff.h>
#include <linux/pgtable.h>
#include <asm/irq.h>
/* Used for the temporal inet entries and routing */
#include <linux/socket.h>
#include <linux/bitops.h>
#include <asm/io.h>
#include <asm/dma.h>
#ifdef CONFIG_HP300
#include <asm/blinken.h>
#endif
#include "7990.h"
#define WRITERAP(lp, x) out_be16(lp->base + LANCE_RAP, (x))
#define WRITERDP(lp, x) out_be16(lp->base + LANCE_RDP, (x))
#define READRDP(lp) in_be16(lp->base + LANCE_RDP)
#if IS_ENABLED(CONFIG_HPLANCE)
#include "hplance.h"
#undef WRITERAP
#undef WRITERDP
#undef READRDP
#if IS_ENABLED(CONFIG_MVME147_NET)
/* Lossage Factor Nine, Mr Sulu. */
#define WRITERAP(lp, x) (lp->writerap(lp, x))
#define WRITERDP(lp, x) (lp->writerdp(lp, x))
#define READRDP(lp) (lp->readrdp(lp))
#else
/* These inlines can be used if only CONFIG_HPLANCE is defined */
static inline void WRITERAP(struct lance_private *lp, __u16 value)
{
do {
out_be16(lp->base + HPLANCE_REGOFF + LANCE_RAP, value);
} while ((in_8(lp->base + HPLANCE_STATUS) & LE_ACK) == 0);
}
static inline void WRITERDP(struct lance_private *lp, __u16 value)
{
do {
out_be16(lp->base + HPLANCE_REGOFF + LANCE_RDP, value);
} while ((in_8(lp->base + HPLANCE_STATUS) & LE_ACK) == 0);
}
static inline __u16 READRDP(struct lance_private *lp)
{
__u16 value;
do {
value = in_be16(lp->base + HPLANCE_REGOFF + LANCE_RDP);
} while ((in_8(lp->base + HPLANCE_STATUS) & LE_ACK) == 0);
return value;
}
#endif
#endif /* IS_ENABLED(CONFIG_HPLANCE) */
/* debugging output macros, various flavours */
/* #define TEST_HITS */
#ifdef UNDEF
#define PRINT_RINGS() \
do { \
int t; \
for (t = 0; t < RX_RING_SIZE; t++) { \
printk("R%d: @(%02X %04X) len %04X, mblen %04X, bits %02X\n", \
t, ib->brx_ring[t].rmd1_hadr, ib->brx_ring[t].rmd0, \
ib->brx_ring[t].length, \
ib->brx_ring[t].mblength, ib->brx_ring[t].rmd1_bits); \
} \
for (t = 0; t < TX_RING_SIZE; t++) { \
printk("T%d: @(%02X %04X) len %04X, misc %04X, bits %02X\n", \
t, ib->btx_ring[t].tmd1_hadr, ib->btx_ring[t].tmd0, \
ib->btx_ring[t].length, \
ib->btx_ring[t].misc, ib->btx_ring[t].tmd1_bits); \
} \
} while (0)
#else
#define PRINT_RINGS()
#endif
/* Load the CSR registers. The LANCE has to be STOPped when we do this! */
static void load_csrs(struct lance_private *lp)
{
volatile struct lance_init_block *aib = lp->lance_init_block;
int leptr;
leptr = LANCE_ADDR(aib);
WRITERAP(lp, LE_CSR1); /* load address of init block */
WRITERDP(lp, leptr & 0xFFFF);
WRITERAP(lp, LE_CSR2);
WRITERDP(lp, leptr >> 16);
WRITERAP(lp, LE_CSR3);
WRITERDP(lp, lp->busmaster_regval); /* set byteswap/ALEctrl/byte ctrl */
/* Point back to csr0 */
WRITERAP(lp, LE_CSR0);
}
/* #define to 0 or 1 appropriately */
#define DEBUG_IRING 0
/* Set up the Lance Rx and Tx rings and the init block */
static void lance_init_ring(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_init_block *aib; /* for LANCE_ADDR computations */
int leptr;
int i;
aib = lp->lance_init_block;
lp->rx_new = lp->tx_new = 0;
lp->rx_old = lp->tx_old = 0;
ib->mode = LE_MO_PROM; /* normal, enable Tx & Rx */
/* Copy the ethernet address to the lance init block
* Notice that we do a byteswap if we're big endian.
* [I think this is the right criterion; at least, sunlance,
* a2065 and atarilance do the byteswap and lance.c (PC) doesn't.
* However, the datasheet says that the BSWAP bit doesn't affect
* the init block, so surely it should be low byte first for
* everybody? Um.]
* We could define the ib->physaddr as three 16bit values and
* use (addr[1] << 8) | addr[0] & co, but this is more efficient.
*/
#ifdef __BIG_ENDIAN
ib->phys_addr[0] = dev->dev_addr[1];
ib->phys_addr[1] = dev->dev_addr[0];
ib->phys_addr[2] = dev->dev_addr[3];
ib->phys_addr[3] = dev->dev_addr[2];
ib->phys_addr[4] = dev->dev_addr[5];
ib->phys_addr[5] = dev->dev_addr[4];
#else
for (i = 0; i < 6; i++)
ib->phys_addr[i] = dev->dev_addr[i];
#endif
if (DEBUG_IRING)
printk("TX rings:\n");
lp->tx_full = 0;
/* Setup the Tx ring entries */
for (i = 0; i < (1 << lp->lance_log_tx_bufs); i++) {
leptr = LANCE_ADDR(&aib->tx_buf[i][0]);
ib->btx_ring[i].tmd0 = leptr;
ib->btx_ring[i].tmd1_hadr = leptr >> 16;
ib->btx_ring[i].tmd1_bits = 0;
ib->btx_ring[i].length = 0xf000; /* The ones required by tmd2 */
ib->btx_ring[i].misc = 0;
if (DEBUG_IRING)
printk("%d: 0x%8.8x\n", i, leptr);
}
/* Setup the Rx ring entries */
if (DEBUG_IRING)
printk("RX rings:\n");
for (i = 0; i < (1 << lp->lance_log_rx_bufs); i++) {
leptr = LANCE_ADDR(&aib->rx_buf[i][0]);
ib->brx_ring[i].rmd0 = leptr;
ib->brx_ring[i].rmd1_hadr = leptr >> 16;
ib->brx_ring[i].rmd1_bits = LE_R1_OWN;
/* 0xf000 == bits that must be one (reserved, presumably) */
ib->brx_ring[i].length = -RX_BUFF_SIZE | 0xf000;
ib->brx_ring[i].mblength = 0;
if (DEBUG_IRING)
printk("%d: 0x%8.8x\n", i, leptr);
}
/* Setup the initialization block */
/* Setup rx descriptor pointer */
leptr = LANCE_ADDR(&aib->brx_ring);
ib->rx_len = (lp->lance_log_rx_bufs << 13) | (leptr >> 16);
ib->rx_ptr = leptr;
if (DEBUG_IRING)
printk("RX ptr: %8.8x\n", leptr);
/* Setup tx descriptor pointer */
leptr = LANCE_ADDR(&aib->btx_ring);
ib->tx_len = (lp->lance_log_tx_bufs << 13) | (leptr >> 16);
ib->tx_ptr = leptr;
if (DEBUG_IRING)
printk("TX ptr: %8.8x\n", leptr);
/* Clear the multicast filter */
ib->filter[0] = 0;
ib->filter[1] = 0;
PRINT_RINGS();
}
/* LANCE must be STOPped before we do this, too... */
static int init_restart_lance(struct lance_private *lp)
{
int i;
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_INIT);
/* Need a hook here for sunlance ledma stuff */
/* Wait for the lance to complete initialization */
for (i = 0; (i < 100) && !(READRDP(lp) & (LE_C0_ERR | LE_C0_IDON)); i++)
barrier();
if ((i == 100) || (READRDP(lp) & LE_C0_ERR)) {
printk("LANCE unopened after %d ticks, csr0=%4.4x.\n", i, READRDP(lp));
return -1;
}
/* Clear IDON by writing a "1", enable interrupts and start lance */
WRITERDP(lp, LE_C0_IDON);
WRITERDP(lp, LE_C0_INEA | LE_C0_STRT);
return 0;
}
static int lance_reset(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
int status;
/* Stop the lance */
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_STOP);
load_csrs(lp);
lance_init_ring(dev);
netif_trans_update(dev); /* prevent tx timeout */
status = init_restart_lance(lp);
#ifdef DEBUG_DRIVER
printk("Lance restart=%d\n", status);
#endif
return status;
}
static int lance_rx(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_rx_desc *rd;
unsigned char bits;
#ifdef TEST_HITS
int i;
#endif
#ifdef TEST_HITS
printk("[");
for (i = 0; i < RX_RING_SIZE; i++) {
if (i == lp->rx_new)
printk("%s",
ib->brx_ring[i].rmd1_bits & LE_R1_OWN ? "_" : "X");
else
printk("%s",
ib->brx_ring[i].rmd1_bits & LE_R1_OWN ? "." : "1");
}
printk("]");
#endif
#ifdef CONFIG_HP300
blinken_leds(0x40, 0);
#endif
WRITERDP(lp, LE_C0_RINT | LE_C0_INEA); /* ack Rx int, reenable ints */
for (rd = &ib->brx_ring[lp->rx_new]; /* For each Rx ring we own... */
!((bits = rd->rmd1_bits) & LE_R1_OWN);
rd = &ib->brx_ring[lp->rx_new]) {
/* We got an incomplete frame? */
if ((bits & LE_R1_POK) != LE_R1_POK) {
dev->stats.rx_over_errors++;
dev->stats.rx_errors++;
continue;
} else if (bits & LE_R1_ERR) {
/* Count only the end frame as a rx error,
* not the beginning
*/
if (bits & LE_R1_BUF)
dev->stats.rx_fifo_errors++;
if (bits & LE_R1_CRC)
dev->stats.rx_crc_errors++;
if (bits & LE_R1_OFL)
dev->stats.rx_over_errors++;
if (bits & LE_R1_FRA)
dev->stats.rx_frame_errors++;
if (bits & LE_R1_EOP)
dev->stats.rx_errors++;
} else {
int len = (rd->mblength & 0xfff) - 4;
struct sk_buff *skb = netdev_alloc_skb(dev, len + 2);
if (!skb) {
dev->stats.rx_dropped++;
rd->mblength = 0;
rd->rmd1_bits = LE_R1_OWN;
lp->rx_new = (lp->rx_new + 1) & lp->rx_ring_mod_mask;
return 0;
}
skb_reserve(skb, 2); /* 16 byte align */
skb_put(skb, len); /* make room */
skb_copy_to_linear_data(skb,
(unsigned char *)&(ib->rx_buf[lp->rx_new][0]),
len);
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += len;
}
/* Return the packet to the pool */
rd->mblength = 0;
rd->rmd1_bits = LE_R1_OWN;
lp->rx_new = (lp->rx_new + 1) & lp->rx_ring_mod_mask;
}
return 0;
}
static int lance_tx(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_tx_desc *td;
int i, j;
int status;
#ifdef CONFIG_HP300
blinken_leds(0x80, 0);
#endif
/* csr0 is 2f3 */
WRITERDP(lp, LE_C0_TINT | LE_C0_INEA);
/* csr0 is 73 */
j = lp->tx_old;
for (i = j; i != lp->tx_new; i = j) {
td = &ib->btx_ring[i];
/* If we hit a packet not owned by us, stop */
if (td->tmd1_bits & LE_T1_OWN)
break;
if (td->tmd1_bits & LE_T1_ERR) {
status = td->misc;
dev->stats.tx_errors++;
if (status & LE_T3_RTY)
dev->stats.tx_aborted_errors++;
if (status & LE_T3_LCOL)
dev->stats.tx_window_errors++;
if (status & LE_T3_CLOS) {
dev->stats.tx_carrier_errors++;
if (lp->auto_select) {
lp->tpe = 1 - lp->tpe;
printk("%s: Carrier Lost, trying %s\n",
dev->name,
lp->tpe ? "TPE" : "AUI");
/* Stop the lance */
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_STOP);
lance_init_ring(dev);
load_csrs(lp);
init_restart_lance(lp);
return 0;
}
}
/* buffer errors and underflows turn off the transmitter */
/* Restart the adapter */
if (status & (LE_T3_BUF|LE_T3_UFL)) {
dev->stats.tx_fifo_errors++;
printk("%s: Tx: ERR_BUF|ERR_UFL, restarting\n",
dev->name);
/* Stop the lance */
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_STOP);
lance_init_ring(dev);
load_csrs(lp);
init_restart_lance(lp);
return 0;
}
} else if ((td->tmd1_bits & LE_T1_POK) == LE_T1_POK) {
/*
* So we don't count the packet more than once.
*/
td->tmd1_bits &= ~(LE_T1_POK);
/* One collision before packet was sent. */
if (td->tmd1_bits & LE_T1_EONE)
dev->stats.collisions++;
/* More than one collision, be optimistic. */
if (td->tmd1_bits & LE_T1_EMORE)
dev->stats.collisions += 2;
dev->stats.tx_packets++;
}
j = (j + 1) & lp->tx_ring_mod_mask;
}
lp->tx_old = j;
WRITERDP(lp, LE_C0_TINT | LE_C0_INEA);
return 0;
}
static irqreturn_t
lance_interrupt(int irq, void *dev_id)
{
struct net_device *dev = (struct net_device *)dev_id;
struct lance_private *lp = netdev_priv(dev);
int csr0;
spin_lock(&lp->devlock);
WRITERAP(lp, LE_CSR0); /* LANCE Controller Status */
csr0 = READRDP(lp);
PRINT_RINGS();
if (!(csr0 & LE_C0_INTR)) { /* Check if any interrupt has */
spin_unlock(&lp->devlock);
return IRQ_NONE; /* been generated by the Lance. */
}
/* Acknowledge all the interrupt sources ASAP */
WRITERDP(lp, csr0 & ~(LE_C0_INEA|LE_C0_TDMD|LE_C0_STOP|LE_C0_STRT|LE_C0_INIT));
if ((csr0 & LE_C0_ERR)) {
/* Clear the error condition */
WRITERDP(lp, LE_C0_BABL|LE_C0_ERR|LE_C0_MISS|LE_C0_INEA);
}
if (csr0 & LE_C0_RINT)
lance_rx(dev);
if (csr0 & LE_C0_TINT)
lance_tx(dev);
/* Log misc errors. */
if (csr0 & LE_C0_BABL)
dev->stats.tx_errors++; /* Tx babble. */
if (csr0 & LE_C0_MISS)
dev->stats.rx_errors++; /* Missed a Rx frame. */
if (csr0 & LE_C0_MERR) {
printk("%s: Bus master arbitration failure, status %4.4x.\n",
dev->name, csr0);
/* Restart the chip. */
WRITERDP(lp, LE_C0_STRT);
}
if (lp->tx_full && netif_queue_stopped(dev) && (TX_BUFFS_AVAIL >= 0)) {
lp->tx_full = 0;
netif_wake_queue(dev);
}
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_BABL|LE_C0_CERR|LE_C0_MISS|LE_C0_MERR|LE_C0_IDON|LE_C0_INEA);
spin_unlock(&lp->devlock);
return IRQ_HANDLED;
}
int lance_open(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
int res;
/* Install the Interrupt handler. Or we could shunt this out to specific drivers? */
if (request_irq(lp->irq, lance_interrupt, IRQF_SHARED, lp->name, dev))
return -EAGAIN;
res = lance_reset(dev);
spin_lock_init(&lp->devlock);
netif_start_queue(dev);
return res;
}
EXPORT_SYMBOL_GPL(lance_open);
int lance_close(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
netif_stop_queue(dev);
/* Stop the LANCE */
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_STOP);
free_irq(lp->irq, dev);
return 0;
}
EXPORT_SYMBOL_GPL(lance_close);
void lance_tx_timeout(struct net_device *dev, unsigned int txqueue)
{
printk("lance_tx_timeout\n");
lance_reset(dev);
netif_trans_update(dev); /* prevent tx timeout */
netif_wake_queue(dev);
}
EXPORT_SYMBOL_GPL(lance_tx_timeout);
netdev_tx_t lance_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
int entry, skblen, len;
static int outs;
unsigned long flags;
netif_stop_queue(dev);
if (!TX_BUFFS_AVAIL) {
dev_consume_skb_any(skb);
return NETDEV_TX_OK;
}
skblen = skb->len;
#ifdef DEBUG_DRIVER
/* dump the packet */
{
int i;
for (i = 0; i < 64; i++) {
if ((i % 16) == 0)
printk("\n");
printk("%2.2x ", skb->data[i]);
}
}
#endif
len = (skblen <= ETH_ZLEN) ? ETH_ZLEN : skblen;
entry = lp->tx_new & lp->tx_ring_mod_mask;
ib->btx_ring[entry].length = (-len) | 0xf000;
ib->btx_ring[entry].misc = 0;
if (skb->len < ETH_ZLEN)
memset((void *)&ib->tx_buf[entry][0], 0, ETH_ZLEN);
skb_copy_from_linear_data(skb, (void *)&ib->tx_buf[entry][0], skblen);
/* Now, give the packet to the lance */
ib->btx_ring[entry].tmd1_bits = (LE_T1_POK|LE_T1_OWN);
lp->tx_new = (lp->tx_new + 1) & lp->tx_ring_mod_mask;
outs++;
/* Kick the lance: transmit now */
WRITERDP(lp, LE_C0_INEA | LE_C0_TDMD);
dev_consume_skb_any(skb);
spin_lock_irqsave(&lp->devlock, flags);
if (TX_BUFFS_AVAIL)
netif_start_queue(dev);
else
lp->tx_full = 1;
spin_unlock_irqrestore(&lp->devlock, flags);
return NETDEV_TX_OK;
}
EXPORT_SYMBOL_GPL(lance_start_xmit);
/* taken from the depca driver via a2065.c */
static void lance_load_multicast(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile u16 *mcast_table = (u16 *)&ib->filter;
struct netdev_hw_addr *ha;
u32 crc;
/* set all multicast bits */
if (dev->flags & IFF_ALLMULTI) {
ib->filter[0] = 0xffffffff;
ib->filter[1] = 0xffffffff;
return;
}
/* clear the multicast filter */
ib->filter[0] = 0;
ib->filter[1] = 0;
/* Add addresses */
netdev_for_each_mc_addr(ha, dev) {
crc = ether_crc_le(6, ha->addr);
crc = crc >> 26;
mcast_table[crc >> 4] |= 1 << (crc & 0xf);
}
}
void lance_set_multicast(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
int stopped;
stopped = netif_queue_stopped(dev);
if (!stopped)
netif_stop_queue(dev);
while (lp->tx_old != lp->tx_new)
schedule();
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_STOP);
lance_init_ring(dev);
if (dev->flags & IFF_PROMISC) {
ib->mode |= LE_MO_PROM;
} else {
ib->mode &= ~LE_MO_PROM;
lance_load_multicast(dev);
}
load_csrs(lp);
init_restart_lance(lp);
if (!stopped)
netif_start_queue(dev);
}
EXPORT_SYMBOL_GPL(lance_set_multicast);
#ifdef CONFIG_NET_POLL_CONTROLLER
void lance_poll(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
spin_lock(&lp->devlock);
WRITERAP(lp, LE_CSR0);
WRITERDP(lp, LE_C0_STRT);
spin_unlock(&lp->devlock);
lance_interrupt(dev->irq, dev);
}
EXPORT_SYMBOL_GPL(lance_poll);
#endif
MODULE_DESCRIPTION("LANCE Ethernet IC generic routines");
MODULE_LICENSE("GPL");
+251
View File
@@ -0,0 +1,251 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* 7990.h -- LANCE ethernet IC generic routines.
* This is an attempt to separate out the bits of various ethernet
* drivers that are common because they all use the AMD 7990 LANCE
* (Local Area Network Controller for Ethernet) chip.
*
* Copyright (C) 05/1998 Peter Maydell <pmaydell@chiark.greenend.org.uk>
*
* Most of this stuff was obtained by looking at other LANCE drivers,
* in particular a2065.[ch]. The AMD C-LANCE datasheet was also helpful.
*/
#ifndef _7990_H
#define _7990_H
/* The lance only has two register locations. We communicate mostly via memory. */
#define LANCE_RDP 0 /* Register Data Port */
#define LANCE_RAP 2 /* Register Address Port */
/* Transmit/receive ring definitions.
* We allow the specific drivers to override these defaults if they want to.
* NB: according to lance.c, increasing the number of buffers is a waste
* of space and reduces the chance that an upper layer will be able to
* reorder queued Tx packets based on priority. [Clearly there is a minimum
* limit too: too small and we drop rx packets and can't tx at full speed.]
* 4+4 seems to be the usual setting; the atarilance driver uses 3 and 5.
*/
/* Blast! This won't work. The problem is that we can't specify a default
* setting because that would cause the lance_init_block struct to be
* too long (and overflow the RAM on shared-memory cards like the HP LANCE.
*/
#ifndef LANCE_LOG_TX_BUFFERS
#define LANCE_LOG_TX_BUFFERS 1
#define LANCE_LOG_RX_BUFFERS 3
#endif
#define TX_RING_SIZE (1 << LANCE_LOG_TX_BUFFERS)
#define RX_RING_SIZE (1 << LANCE_LOG_RX_BUFFERS)
#define TX_RING_MOD_MASK (TX_RING_SIZE - 1)
#define RX_RING_MOD_MASK (RX_RING_SIZE - 1)
#define TX_RING_LEN_BITS ((LANCE_LOG_TX_BUFFERS) << 29)
#define RX_RING_LEN_BITS ((LANCE_LOG_RX_BUFFERS) << 29)
#define PKT_BUFF_SIZE (1544)
#define RX_BUFF_SIZE PKT_BUFF_SIZE
#define TX_BUFF_SIZE PKT_BUFF_SIZE
/* Each receive buffer is described by a receive message descriptor (RMD) */
struct lance_rx_desc {
volatile unsigned short rmd0; /* low address of packet */
volatile unsigned char rmd1_bits; /* descriptor bits */
volatile unsigned char rmd1_hadr; /* high address of packet */
volatile short length; /* This length is 2s complement (negative)!
* Buffer length */
volatile unsigned short mblength; /* Actual number of bytes received */
};
/* Ditto for TMD: */
struct lance_tx_desc {
volatile unsigned short tmd0; /* low address of packet */
volatile unsigned char tmd1_bits; /* descriptor bits */
volatile unsigned char tmd1_hadr; /* high address of packet */
volatile short length; /* Length is 2s complement (negative)! */
volatile unsigned short misc;
};
/* There are three memory structures accessed by the LANCE:
* the initialization block, the receive and transmit descriptor rings,
* and the data buffers themselves. In fact we might as well put the
* init block,the Tx and Rx rings and the buffers together in memory:
*/
struct lance_init_block {
volatile unsigned short mode; /* Pre-set mode (reg. 15) */
volatile unsigned char phys_addr[6]; /* Physical ethernet address */
volatile unsigned filter[2]; /* Multicast filter (64 bits) */
/* Receive and transmit ring base, along with extra bits. */
volatile unsigned short rx_ptr; /* receive descriptor addr */
volatile unsigned short rx_len; /* receive len and high addr */
volatile unsigned short tx_ptr; /* transmit descriptor addr */
volatile unsigned short tx_len; /* transmit len and high addr */
/* The Tx and Rx ring entries must be aligned on 8-byte boundaries.
* This will be true if this whole struct is 8-byte aligned.
*/
volatile struct lance_tx_desc btx_ring[TX_RING_SIZE];
volatile struct lance_rx_desc brx_ring[RX_RING_SIZE];
volatile char tx_buf[TX_RING_SIZE][TX_BUFF_SIZE];
volatile char rx_buf[RX_RING_SIZE][RX_BUFF_SIZE];
/* we use this just to make the struct big enough that we can move its startaddr
* in order to force alignment to an eight byte boundary.
*/
};
/* This is where we keep all the stuff the driver needs to know about.
* I'm definitely unhappy about the mechanism for allowing specific
* drivers to add things...
*/
struct lance_private {
const char *name;
unsigned long base;
volatile struct lance_init_block *init_block; /* CPU address of RAM */
volatile struct lance_init_block *lance_init_block; /* LANCE address of RAM */
int rx_new, tx_new;
int rx_old, tx_old;
int lance_log_rx_bufs, lance_log_tx_bufs;
int rx_ring_mod_mask, tx_ring_mod_mask;
int tpe; /* TPE is selected */
int auto_select; /* cable-selection is by carrier */
unsigned short busmaster_regval;
unsigned int irq; /* IRQ to register */
/* This is because the HP LANCE is disgusting and you have to check
* a DIO-specific register every time you read/write the LANCE regs :-<
* [could we get away with making these some sort of macro?]
*/
void (*writerap)(void *, unsigned short);
void (*writerdp)(void *, unsigned short);
unsigned short (*readrdp)(void *);
spinlock_t devlock;
char tx_full;
};
/*
* Am7990 Control and Status Registers
*/
#define LE_CSR0 0x0000 /* LANCE Controller Status */
#define LE_CSR1 0x0001 /* IADR[15:0] (bit0==0 ie word aligned) */
#define LE_CSR2 0x0002 /* IADR[23:16] (high bits reserved) */
#define LE_CSR3 0x0003 /* Misc */
/*
* Bit definitions for CSR0 (LANCE Controller Status)
*/
#define LE_C0_ERR 0x8000 /* Error = BABL | CERR | MISS | MERR */
#define LE_C0_BABL 0x4000 /* Babble: Transmitted too many bits */
#define LE_C0_CERR 0x2000 /* No Heartbeat (10BASE-T) */
#define LE_C0_MISS 0x1000 /* Missed Frame (no rx buffer to put it in) */
#define LE_C0_MERR 0x0800 /* Memory Error */
#define LE_C0_RINT 0x0400 /* Receive Interrupt */
#define LE_C0_TINT 0x0200 /* Transmit Interrupt */
#define LE_C0_IDON 0x0100 /* Initialization Done */
#define LE_C0_INTR 0x0080 /* Interrupt Flag
= BABL | MISS | MERR | RINT | TINT | IDON */
#define LE_C0_INEA 0x0040 /* Interrupt Enable */
#define LE_C0_RXON 0x0020 /* Receive On */
#define LE_C0_TXON 0x0010 /* Transmit On */
#define LE_C0_TDMD 0x0008 /* Transmit Demand */
#define LE_C0_STOP 0x0004 /* Stop */
#define LE_C0_STRT 0x0002 /* Start */
#define LE_C0_INIT 0x0001 /* Initialize */
/*
* Bit definitions for CSR3
*/
#define LE_C3_BSWP 0x0004 /* Byte Swap (on for big endian byte order) */
#define LE_C3_ACON 0x0002 /* ALE Control (on for active low ALE) */
#define LE_C3_BCON 0x0001 /* Byte Control */
/*
* Mode Flags
*/
#define LE_MO_PROM 0x8000 /* Promiscuous Mode */
/* these next ones 0x4000 -- 0x0080 are not available on the LANCE 7990,
* but they are in NetBSD's am7990.h, presumably for backwards-compatible chips
*/
#define LE_MO_DRCVBC 0x4000 /* disable receive broadcast */
#define LE_MO_DRCVPA 0x2000 /* disable physical address detection */
#define LE_MO_DLNKTST 0x1000 /* disable link status */
#define LE_MO_DAPC 0x0800 /* disable automatic polarity correction */
#define LE_MO_MENDECL 0x0400 /* MENDEC loopback mode */
#define LE_MO_LRTTSEL 0x0200 /* lower RX threshold / TX mode selection */
#define LE_MO_PSEL1 0x0100 /* port selection bit1 */
#define LE_MO_PSEL0 0x0080 /* port selection bit0 */
/* and this one is from the C-LANCE data sheet... */
#define LE_MO_EMBA 0x0080 /* Enable Modified Backoff Algorithm
(C-LANCE, not original LANCE) */
#define LE_MO_INTL 0x0040 /* Internal Loopback */
#define LE_MO_DRTY 0x0020 /* Disable Retry */
#define LE_MO_FCOLL 0x0010 /* Force Collision */
#define LE_MO_DXMTFCS 0x0008 /* Disable Transmit CRC */
#define LE_MO_LOOP 0x0004 /* Loopback Enable */
#define LE_MO_DTX 0x0002 /* Disable Transmitter */
#define LE_MO_DRX 0x0001 /* Disable Receiver */
/*
* Receive Flags
*/
#define LE_R1_OWN 0x80 /* LANCE owns the descriptor */
#define LE_R1_ERR 0x40 /* Error */
#define LE_R1_FRA 0x20 /* Framing Error */
#define LE_R1_OFL 0x10 /* Overflow Error */
#define LE_R1_CRC 0x08 /* CRC Error */
#define LE_R1_BUF 0x04 /* Buffer Error */
#define LE_R1_SOP 0x02 /* Start of Packet */
#define LE_R1_EOP 0x01 /* End of Packet */
#define LE_R1_POK 0x03 /* Packet is complete: SOP + EOP */
/*
* Transmit Flags
*/
#define LE_T1_OWN 0x80 /* LANCE owns the descriptor */
#define LE_T1_ERR 0x40 /* Error */
#define LE_T1_RES 0x20 /* Reserved, LANCE writes this with a zero */
#define LE_T1_EMORE 0x10 /* More than one retry needed */
#define LE_T1_EONE 0x08 /* One retry needed */
#define LE_T1_EDEF 0x04 /* Deferred */
#define LE_T1_SOP 0x02 /* Start of Packet */
#define LE_T1_EOP 0x01 /* End of Packet */
#define LE_T1_POK 0x03 /* Packet is complete: SOP + EOP */
/*
* Error Flags
*/
#define LE_T3_BUF 0x8000 /* Buffer Error */
#define LE_T3_UFL 0x4000 /* Underflow Error */
#define LE_T3_LCOL 0x1000 /* Late Collision */
#define LE_T3_CLOS 0x0800 /* Loss of Carrier */
#define LE_T3_RTY 0x0400 /* Retry Error */
#define LE_T3_TDR 0x03ff /* Time Domain Reflectometry */
/* Miscellaneous useful macros */
#define TX_BUFFS_AVAIL ((lp->tx_old <= lp->tx_new) ? \
lp->tx_old + lp->tx_ring_mod_mask - lp->tx_new : \
lp->tx_old - lp->tx_new - 1)
/* The LANCE only uses 24 bit addresses. This does the obvious thing. */
#define LANCE_ADDR(x) ((int)(x) & ~0xff000000)
/* Now the prototypes we export */
int lance_open(struct net_device *dev);
int lance_close(struct net_device *dev);
netdev_tx_t lance_start_xmit(struct sk_buff *skb, struct net_device *dev);
void lance_set_multicast(struct net_device *dev);
void lance_tx_timeout(struct net_device *dev, unsigned int txqueue);
#ifdef CONFIG_NET_POLL_CONTROLLER
void lance_poll(struct net_device *dev);
#endif
#endif /* ndef _7990_H */
@@ -0,0 +1,183 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# AMD network device configuration
#
config NET_VENDOR_AMD
bool "AMD devices"
default y
depends on DIO || MACH_DECSTATION || MVME147 || ATARI || SUN3 || \
SUN3X || SBUS || PCI || ZORRO || (ISA && ISA_DMA_API) || \
ISA || EISA || PCMCIA || ARM64
help
If you have a network (Ethernet) chipset belonging to this class,
say Y.
Note that the answer to this question does not directly affect
the kernel: saying N will just cause the configurator to skip all
the questions regarding AMD chipsets. If you say Y, you will be asked
for your specific chipset/driver in the following questions.
if NET_VENDOR_AMD
config A2065
tristate "A2065 support"
depends on ZORRO
select CRC32
help
If you have a Commodore A2065 Ethernet adapter, say Y. Otherwise,
say N.
To compile this driver as a module, choose M here: the module
will be called a2065.
config AMD8111_ETH
tristate "AMD 8111 (new PCI LANCE) support"
depends on PCI
select CRC32
select MII
help
If you have an AMD 8111-based PCI LANCE ethernet card,
answer Y here.
To compile this driver as a module, choose M here. The module
will be called amd8111e.
config PCNET32
tristate "AMD PCnet32 PCI support"
depends on PCI && HAS_IOPORT
select CRC32
select MII
help
If you have a PCnet32 or PCnetPCI based network (Ethernet) card,
answer Y here.
To compile this driver as a module, choose M here. The module
will be called pcnet32.
config ARIADNE
tristate "Ariadne support"
depends on ZORRO
help
If you have a Village Tronic Ariadne Ethernet adapter, say Y.
Otherwise, say N.
To compile this driver as a module, choose M here: the module
will be called ariadne.
config ATARILANCE
tristate "Atari LANCE support"
depends on ATARI
help
Say Y to include support for several Atari Ethernet adapters based
on the AMD LANCE chipset: RieblCard (with or without battery), or
PAMCard VME (also the version by Rhotron, with different addresses).
config DECLANCE
tristate "DEC LANCE ethernet controller support"
depends on MACH_DECSTATION
select CRC32
help
This driver is for the series of Ethernet controllers produced by
DEC (now Compaq) based on the AMD LANCE chipset, including the
DEPCA series. (This chipset is better known via the NE2100 cards.)
config HPLANCE
tristate "HP on-board LANCE support"
depends on DIO
select CRC32
help
If you want to use the builtin "LANCE" Ethernet controller on an
HP300 machine, say Y here.
config MIPS_AU1X00_ENET
tristate "MIPS AU1000 Ethernet support"
depends on MIPS_ALCHEMY
select PHYLIB
select CRC32
help
If you have an Alchemy Semi AU1X00 based system
say Y. Otherwise, say N.
config MVME147_NET
tristate "MVME147 (LANCE) Ethernet support"
depends on MVME147
select CRC32
help
Support for the on-board Ethernet interface on the Motorola MVME147
single-board computer. Say Y here to include the
driver for this chip in your kernel.
To compile this driver as a module, choose M here.
config SUN3LANCE
tristate "Sun3/Sun3x on-board LANCE support"
depends on (SUN3 || SUN3X)
help
Most Sun3 and Sun3x motherboards (including the 3/50, 3/60 and 3/80)
featured an AMD LANCE 10Mbit Ethernet controller on board; say Y
here to compile in the Linux driver for this and enable Ethernet.
General Linux information on the Sun 3 and 3x series (now
discontinued) is at
<http://www.angelfire.com/ca2/tech68k/sun3.html>.
If you're not building a kernel for a Sun 3, say N.
config SUNLANCE
tristate "Sun LANCE support"
depends on SBUS
select CRC32
help
This driver supports the "le" interface present on all 32-bit Sparc
systems, on some older Ultra systems and as an Sbus option. These
cards are based on the AMD LANCE chipset, which is better known
via the NE2100 cards.
To compile this driver as a module, choose M here: the module
will be called sunlance.
config AMD_XGBE
tristate "AMD 10GbE Ethernet driver"
depends on (OF_ADDRESS || ACPI || PCI) && HAS_IOMEM
depends on X86 || ARM64 || COMPILE_TEST
depends on PTP_1588_CLOCK_OPTIONAL
select BITREVERSE
select CRC32
select PHYLIB
select AMD_XGBE_HAVE_ECC if X86
imply NET_SELFTESTS
help
This driver supports the AMD 10GbE Ethernet device found on an
AMD SoC.
To compile this driver as a module, choose M here: the module
will be called amd-xgbe.
config AMD_XGBE_DCB
bool "Data Center Bridging (DCB) support"
default n
depends on AMD_XGBE && DCB
help
Say Y here to enable Data Center Bridging (DCB) support in the
driver.
If unsure, say N.
config AMD_XGBE_HAVE_ECC
bool
default n
config PDS_CORE
tristate "AMD/Pensando Data Systems Core Device Support"
depends on 64BIT && PCI
select AUXILIARY_BUS
select NET_DEVLINK
help
This enables the support for the AMD/Pensando Core device family of
adapters. More specific information on this driver can be
found in
<file:Documentation/networking/device_drivers/ethernet/amd/pds_core.rst>.
To compile this driver as a module, choose M here. The module
will be called pds_core.
endif # NET_VENDOR_AMD
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the AMD network device drivers.
#
obj-$(CONFIG_A2065) += a2065.o
obj-$(CONFIG_AMD8111_ETH) += amd8111e.o
obj-$(CONFIG_ARIADNE) += ariadne.o
obj-$(CONFIG_ATARILANCE) += atarilance.o
obj-$(CONFIG_DECLANCE) += declance.o
obj-$(CONFIG_HPLANCE) += hplance.o 7990.o
obj-$(CONFIG_MIPS_AU1X00_ENET) += au1000_eth.o
obj-$(CONFIG_MVME147_NET) += mvme147.o 7990.o
obj-$(CONFIG_PCNET32) += pcnet32.o
obj-$(CONFIG_SUN3LANCE) += sun3lance.o
obj-$(CONFIG_SUNLANCE) += sunlance.o
obj-$(CONFIG_AMD_XGBE) += xgbe/
obj-$(CONFIG_PDS_CORE) += pds_core/
@@ -0,0 +1,785 @@
/*
* Amiga Linux/68k A2065 Ethernet Driver
*
* (C) Copyright 1995-2003 by Geert Uytterhoeven <geert@linux-m68k.org>
*
* Fixes and tips by:
* - Janos Farkas (CHEXUM@sparta.banki.hu)
* - Jes Degn Soerensen (jds@kom.auc.dk)
* - Matt Domsch (Matt_Domsch@dell.com)
*
* ----------------------------------------------------------------------------
*
* This program is based on
*
* ariadne.?: Amiga Linux/68k Ariadne Ethernet Driver
* (C) Copyright 1995 by Geert Uytterhoeven,
* Peter De Schrijver
*
* lance.c: An AMD LANCE ethernet driver for linux.
* Written 1993-94 by Donald Becker.
*
* Am79C960: PCnet(tm)-ISA Single-Chip Ethernet Controller
* Advanced Micro Devices
* Publication #16907, Rev. B, Amendment/0, May 1994
*
* ----------------------------------------------------------------------------
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the Linux
* distribution for more details.
*
* ----------------------------------------------------------------------------
*
* The A2065 is a Zorro-II board made by Commodore/Ameristar. It contains:
*
* - an Am7990 Local Area Network Controller for Ethernet (LANCE) with
* both 10BASE-2 (thin coax) and AUI (DB-15) connectors
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/*#define DEBUG*/
/*#define TEST_HITS*/
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/crc32.h>
#include <linux/zorro.h>
#include <linux/bitops.h>
#include <asm/byteorder.h>
#include <asm/irq.h>
#include <asm/amigaints.h>
#include <asm/amigahw.h>
#include "a2065.h"
/* Transmit/Receive Ring Definitions */
#define LANCE_LOG_TX_BUFFERS (2)
#define LANCE_LOG_RX_BUFFERS (4)
#define TX_RING_SIZE (1 << LANCE_LOG_TX_BUFFERS)
#define RX_RING_SIZE (1 << LANCE_LOG_RX_BUFFERS)
#define TX_RING_MOD_MASK (TX_RING_SIZE - 1)
#define RX_RING_MOD_MASK (RX_RING_SIZE - 1)
#define PKT_BUF_SIZE (1544)
#define RX_BUFF_SIZE PKT_BUF_SIZE
#define TX_BUFF_SIZE PKT_BUF_SIZE
/* Layout of the Lance's RAM Buffer */
struct lance_init_block {
unsigned short mode; /* Pre-set mode (reg. 15) */
unsigned char phys_addr[6]; /* Physical ethernet address */
unsigned filter[2]; /* Multicast filter. */
/* Receive and transmit ring base, along with extra bits. */
unsigned short rx_ptr; /* receive descriptor addr */
unsigned short rx_len; /* receive len and high addr */
unsigned short tx_ptr; /* transmit descriptor addr */
unsigned short tx_len; /* transmit len and high addr */
/* The Tx and Rx ring entries must aligned on 8-byte boundaries. */
struct lance_rx_desc brx_ring[RX_RING_SIZE];
struct lance_tx_desc btx_ring[TX_RING_SIZE];
char rx_buf[RX_RING_SIZE][RX_BUFF_SIZE];
char tx_buf[TX_RING_SIZE][TX_BUFF_SIZE];
};
/* Private Device Data */
struct lance_private {
char *name;
volatile struct lance_regs *ll;
volatile struct lance_init_block *init_block; /* Hosts view */
volatile struct lance_init_block *lance_init_block; /* Lance view */
int rx_new, tx_new;
int rx_old, tx_old;
int lance_log_rx_bufs, lance_log_tx_bufs;
int rx_ring_mod_mask, tx_ring_mod_mask;
int tpe; /* cable-selection is TPE */
int auto_select; /* cable-selection by carrier */
unsigned short busmaster_regval;
struct timer_list multicast_timer;
struct net_device *dev;
};
#define LANCE_ADDR(x) ((int)(x) & ~0xff000000)
/* Load the CSR registers */
static void load_csrs(struct lance_private *lp)
{
volatile struct lance_regs *ll = lp->ll;
volatile struct lance_init_block *aib = lp->lance_init_block;
int leptr = LANCE_ADDR(aib);
ll->rap = LE_CSR1;
ll->rdp = (leptr & 0xFFFF);
ll->rap = LE_CSR2;
ll->rdp = leptr >> 16;
ll->rap = LE_CSR3;
ll->rdp = lp->busmaster_regval;
/* Point back to csr0 */
ll->rap = LE_CSR0;
}
/* Setup the Lance Rx and Tx rings */
static void lance_init_ring(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_init_block *aib = lp->lance_init_block;
/* for LANCE_ADDR computations */
int leptr;
int i;
/* Lock out other processes while setting up hardware */
netif_stop_queue(dev);
lp->rx_new = lp->tx_new = 0;
lp->rx_old = lp->tx_old = 0;
ib->mode = 0;
/* Copy the ethernet address to the lance init block
* Note that on the sparc you need to swap the ethernet address.
*/
ib->phys_addr[0] = dev->dev_addr[1];
ib->phys_addr[1] = dev->dev_addr[0];
ib->phys_addr[2] = dev->dev_addr[3];
ib->phys_addr[3] = dev->dev_addr[2];
ib->phys_addr[4] = dev->dev_addr[5];
ib->phys_addr[5] = dev->dev_addr[4];
/* Setup the Tx ring entries */
netdev_dbg(dev, "TX rings:\n");
for (i = 0; i <= 1 << lp->lance_log_tx_bufs; i++) {
leptr = LANCE_ADDR(&aib->tx_buf[i][0]);
ib->btx_ring[i].tmd0 = leptr;
ib->btx_ring[i].tmd1_hadr = leptr >> 16;
ib->btx_ring[i].tmd1_bits = 0;
ib->btx_ring[i].length = 0xf000; /* The ones required by tmd2 */
ib->btx_ring[i].misc = 0;
if (i < 3)
netdev_dbg(dev, "%d: 0x%08x\n", i, leptr);
}
/* Setup the Rx ring entries */
netdev_dbg(dev, "RX rings:\n");
for (i = 0; i < 1 << lp->lance_log_rx_bufs; i++) {
leptr = LANCE_ADDR(&aib->rx_buf[i][0]);
ib->brx_ring[i].rmd0 = leptr;
ib->brx_ring[i].rmd1_hadr = leptr >> 16;
ib->brx_ring[i].rmd1_bits = LE_R1_OWN;
ib->brx_ring[i].length = -RX_BUFF_SIZE | 0xf000;
ib->brx_ring[i].mblength = 0;
if (i < 3)
netdev_dbg(dev, "%d: 0x%08x\n", i, leptr);
}
/* Setup the initialization block */
/* Setup rx descriptor pointer */
leptr = LANCE_ADDR(&aib->brx_ring);
ib->rx_len = (lp->lance_log_rx_bufs << 13) | (leptr >> 16);
ib->rx_ptr = leptr;
netdev_dbg(dev, "RX ptr: %08x\n", leptr);
/* Setup tx descriptor pointer */
leptr = LANCE_ADDR(&aib->btx_ring);
ib->tx_len = (lp->lance_log_tx_bufs << 13) | (leptr >> 16);
ib->tx_ptr = leptr;
netdev_dbg(dev, "TX ptr: %08x\n", leptr);
/* Clear the multicast filter */
ib->filter[0] = 0;
ib->filter[1] = 0;
}
static int init_restart_lance(struct lance_private *lp)
{
volatile struct lance_regs *ll = lp->ll;
int i;
ll->rap = LE_CSR0;
ll->rdp = LE_C0_INIT;
/* Wait for the lance to complete initialization */
for (i = 0; (i < 100) && !(ll->rdp & (LE_C0_ERR | LE_C0_IDON)); i++)
barrier();
if ((i == 100) || (ll->rdp & LE_C0_ERR)) {
pr_err("unopened after %d ticks, csr0=%04x\n", i, ll->rdp);
return -EIO;
}
/* Clear IDON by writing a "1", enable interrupts and start lance */
ll->rdp = LE_C0_IDON;
ll->rdp = LE_C0_INEA | LE_C0_STRT;
return 0;
}
static int lance_rx(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_regs *ll = lp->ll;
volatile struct lance_rx_desc *rd;
unsigned char bits;
#ifdef TEST_HITS
int i;
char buf[RX_RING_SIZE + 1];
for (i = 0; i < RX_RING_SIZE; i++) {
char r1_own = ib->brx_ring[i].rmd1_bits & LE_R1_OWN;
if (i == lp->rx_new)
buf[i] = r1_own ? '_' : 'X';
else
buf[i] = r1_own ? '.' : '1';
}
buf[RX_RING_SIZE] = 0;
pr_debug("RxRing TestHits: [%s]\n", buf);
#endif
ll->rdp = LE_C0_RINT | LE_C0_INEA;
for (rd = &ib->brx_ring[lp->rx_new];
!((bits = rd->rmd1_bits) & LE_R1_OWN);
rd = &ib->brx_ring[lp->rx_new]) {
/* We got an incomplete frame? */
if ((bits & LE_R1_POK) != LE_R1_POK) {
dev->stats.rx_over_errors++;
dev->stats.rx_errors++;
continue;
} else if (bits & LE_R1_ERR) {
/* Count only the end frame as a rx error,
* not the beginning
*/
if (bits & LE_R1_BUF)
dev->stats.rx_fifo_errors++;
if (bits & LE_R1_CRC)
dev->stats.rx_crc_errors++;
if (bits & LE_R1_OFL)
dev->stats.rx_over_errors++;
if (bits & LE_R1_FRA)
dev->stats.rx_frame_errors++;
if (bits & LE_R1_EOP)
dev->stats.rx_errors++;
} else {
int len = (rd->mblength & 0xfff) - 4;
struct sk_buff *skb = netdev_alloc_skb(dev, len + 2);
if (!skb) {
dev->stats.rx_dropped++;
rd->mblength = 0;
rd->rmd1_bits = LE_R1_OWN;
lp->rx_new = (lp->rx_new + 1) & lp->rx_ring_mod_mask;
return 0;
}
skb_reserve(skb, 2); /* 16 byte align */
skb_put(skb, len); /* make room */
skb_copy_to_linear_data(skb,
(unsigned char *)&ib->rx_buf[lp->rx_new][0],
len);
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += len;
}
/* Return the packet to the pool */
rd->mblength = 0;
rd->rmd1_bits = LE_R1_OWN;
lp->rx_new = (lp->rx_new + 1) & lp->rx_ring_mod_mask;
}
return 0;
}
static int lance_tx(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_regs *ll = lp->ll;
volatile struct lance_tx_desc *td;
int i, j;
int status;
/* csr0 is 2f3 */
ll->rdp = LE_C0_TINT | LE_C0_INEA;
/* csr0 is 73 */
j = lp->tx_old;
for (i = j; i != lp->tx_new; i = j) {
td = &ib->btx_ring[i];
/* If we hit a packet not owned by us, stop */
if (td->tmd1_bits & LE_T1_OWN)
break;
if (td->tmd1_bits & LE_T1_ERR) {
status = td->misc;
dev->stats.tx_errors++;
if (status & LE_T3_RTY)
dev->stats.tx_aborted_errors++;
if (status & LE_T3_LCOL)
dev->stats.tx_window_errors++;
if (status & LE_T3_CLOS) {
dev->stats.tx_carrier_errors++;
if (lp->auto_select) {
lp->tpe = 1 - lp->tpe;
netdev_err(dev, "Carrier Lost, trying %s\n",
lp->tpe ? "TPE" : "AUI");
/* Stop the lance */
ll->rap = LE_CSR0;
ll->rdp = LE_C0_STOP;
lance_init_ring(dev);
load_csrs(lp);
init_restart_lance(lp);
return 0;
}
}
/* buffer errors and underflows turn off
* the transmitter, so restart the adapter
*/
if (status & (LE_T3_BUF | LE_T3_UFL)) {
dev->stats.tx_fifo_errors++;
netdev_err(dev, "Tx: ERR_BUF|ERR_UFL, restarting\n");
/* Stop the lance */
ll->rap = LE_CSR0;
ll->rdp = LE_C0_STOP;
lance_init_ring(dev);
load_csrs(lp);
init_restart_lance(lp);
return 0;
}
} else if ((td->tmd1_bits & LE_T1_POK) == LE_T1_POK) {
/* So we don't count the packet more than once. */
td->tmd1_bits &= ~(LE_T1_POK);
/* One collision before packet was sent. */
if (td->tmd1_bits & LE_T1_EONE)
dev->stats.collisions++;
/* More than one collision, be optimistic. */
if (td->tmd1_bits & LE_T1_EMORE)
dev->stats.collisions += 2;
dev->stats.tx_packets++;
}
j = (j + 1) & lp->tx_ring_mod_mask;
}
lp->tx_old = j;
ll->rdp = LE_C0_TINT | LE_C0_INEA;
return 0;
}
static int lance_tx_buffs_avail(struct lance_private *lp)
{
if (lp->tx_old <= lp->tx_new)
return lp->tx_old + lp->tx_ring_mod_mask - lp->tx_new;
return lp->tx_old - lp->tx_new - 1;
}
static irqreturn_t lance_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_regs *ll = lp->ll;
int csr0;
ll->rap = LE_CSR0; /* LANCE Controller Status */
csr0 = ll->rdp;
if (!(csr0 & LE_C0_INTR)) /* Check if any interrupt has */
return IRQ_NONE; /* been generated by the Lance. */
/* Acknowledge all the interrupt sources ASAP */
ll->rdp = csr0 & ~(LE_C0_INEA | LE_C0_TDMD | LE_C0_STOP | LE_C0_STRT |
LE_C0_INIT);
if (csr0 & LE_C0_ERR) {
/* Clear the error condition */
ll->rdp = LE_C0_BABL | LE_C0_ERR | LE_C0_MISS | LE_C0_INEA;
}
if (csr0 & LE_C0_RINT)
lance_rx(dev);
if (csr0 & LE_C0_TINT)
lance_tx(dev);
/* Log misc errors. */
if (csr0 & LE_C0_BABL)
dev->stats.tx_errors++; /* Tx babble. */
if (csr0 & LE_C0_MISS)
dev->stats.rx_errors++; /* Missed a Rx frame. */
if (csr0 & LE_C0_MERR) {
netdev_err(dev, "Bus master arbitration failure, status %04x\n",
csr0);
/* Restart the chip. */
ll->rdp = LE_C0_STRT;
}
if (netif_queue_stopped(dev) && lance_tx_buffs_avail(lp) > 0)
netif_wake_queue(dev);
ll->rap = LE_CSR0;
ll->rdp = (LE_C0_BABL | LE_C0_CERR | LE_C0_MISS | LE_C0_MERR |
LE_C0_IDON | LE_C0_INEA);
return IRQ_HANDLED;
}
static int lance_open(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_regs *ll = lp->ll;
int ret;
/* Stop the Lance */
ll->rap = LE_CSR0;
ll->rdp = LE_C0_STOP;
/* Install the Interrupt handler */
ret = request_irq(IRQ_AMIGA_PORTS, lance_interrupt, IRQF_SHARED,
dev->name, dev);
if (ret)
return ret;
load_csrs(lp);
lance_init_ring(dev);
netif_start_queue(dev);
return init_restart_lance(lp);
}
static int lance_close(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_regs *ll = lp->ll;
netif_stop_queue(dev);
timer_delete_sync(&lp->multicast_timer);
/* Stop the card */
ll->rap = LE_CSR0;
ll->rdp = LE_C0_STOP;
free_irq(IRQ_AMIGA_PORTS, dev);
return 0;
}
static inline int lance_reset(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_regs *ll = lp->ll;
int status;
/* Stop the lance */
ll->rap = LE_CSR0;
ll->rdp = LE_C0_STOP;
load_csrs(lp);
lance_init_ring(dev);
netif_trans_update(dev); /* prevent tx timeout */
netif_start_queue(dev);
status = init_restart_lance(lp);
netdev_dbg(dev, "Lance restart=%d\n", status);
return status;
}
static void lance_tx_timeout(struct net_device *dev, unsigned int txqueue)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_regs *ll = lp->ll;
netdev_err(dev, "transmit timed out, status %04x, reset\n", ll->rdp);
lance_reset(dev);
netif_wake_queue(dev);
}
static netdev_tx_t lance_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_regs *ll = lp->ll;
volatile struct lance_init_block *ib = lp->init_block;
int entry, skblen;
int status = NETDEV_TX_OK;
unsigned long flags;
if (skb_padto(skb, ETH_ZLEN))
return NETDEV_TX_OK;
skblen = max_t(unsigned, skb->len, ETH_ZLEN);
local_irq_save(flags);
if (!lance_tx_buffs_avail(lp))
goto out_free;
/* dump the packet */
print_hex_dump_debug("skb->data: ", DUMP_PREFIX_NONE, 16, 1, skb->data,
64, true);
entry = lp->tx_new & lp->tx_ring_mod_mask;
ib->btx_ring[entry].length = (-skblen) | 0xf000;
ib->btx_ring[entry].misc = 0;
skb_copy_from_linear_data(skb, (void *)&ib->tx_buf[entry][0], skblen);
/* Now, give the packet to the lance */
ib->btx_ring[entry].tmd1_bits = (LE_T1_POK | LE_T1_OWN);
lp->tx_new = (lp->tx_new+1) & lp->tx_ring_mod_mask;
dev->stats.tx_bytes += skblen;
if (lance_tx_buffs_avail(lp) <= 0)
netif_stop_queue(dev);
/* Kick the lance: transmit now */
ll->rdp = LE_C0_INEA | LE_C0_TDMD;
out_free:
dev_kfree_skb(skb);
local_irq_restore(flags);
return status;
}
/* taken from the depca driver */
static void lance_load_multicast(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile u16 *mcast_table = (u16 *)&ib->filter;
struct netdev_hw_addr *ha;
u32 crc;
/* set all multicast bits */
if (dev->flags & IFF_ALLMULTI) {
ib->filter[0] = 0xffffffff;
ib->filter[1] = 0xffffffff;
return;
}
/* clear the multicast filter */
ib->filter[0] = 0;
ib->filter[1] = 0;
/* Add addresses */
netdev_for_each_mc_addr(ha, dev) {
crc = ether_crc_le(6, ha->addr);
crc = crc >> 26;
mcast_table[crc >> 4] |= 1 << (crc & 0xf);
}
}
static void lance_set_multicast(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
volatile struct lance_init_block *ib = lp->init_block;
volatile struct lance_regs *ll = lp->ll;
if (!netif_running(dev))
return;
if (lp->tx_old != lp->tx_new) {
mod_timer(&lp->multicast_timer, jiffies + 4);
netif_wake_queue(dev);
return;
}
netif_stop_queue(dev);
ll->rap = LE_CSR0;
ll->rdp = LE_C0_STOP;
lance_init_ring(dev);
if (dev->flags & IFF_PROMISC) {
ib->mode |= LE_MO_PROM;
} else {
ib->mode &= ~LE_MO_PROM;
lance_load_multicast(dev);
}
load_csrs(lp);
init_restart_lance(lp);
netif_wake_queue(dev);
}
static void lance_set_multicast_retry(struct timer_list *t)
{
struct lance_private *lp = timer_container_of(lp, t, multicast_timer);
lance_set_multicast(lp->dev);
}
static int a2065_init_one(struct zorro_dev *z,
const struct zorro_device_id *ent);
static void a2065_remove_one(struct zorro_dev *z);
static const struct zorro_device_id a2065_zorro_tbl[] = {
{ .id = ZORRO_PROD_CBM_A2065_1 },
{ .id = ZORRO_PROD_CBM_A2065_2 },
{ .id = ZORRO_PROD_AMERISTAR_A2065 },
{ }
};
MODULE_DEVICE_TABLE(zorro, a2065_zorro_tbl);
static struct zorro_driver a2065_driver = {
.name = "a2065",
.id_table = a2065_zorro_tbl,
.probe = a2065_init_one,
.remove = a2065_remove_one,
};
static const struct net_device_ops lance_netdev_ops = {
.ndo_open = lance_open,
.ndo_stop = lance_close,
.ndo_start_xmit = lance_start_xmit,
.ndo_tx_timeout = lance_tx_timeout,
.ndo_set_rx_mode = lance_set_multicast,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
static int a2065_init_one(struct zorro_dev *z,
const struct zorro_device_id *ent)
{
struct net_device *dev;
struct lance_private *priv;
unsigned long board = z->resource.start;
unsigned long base_addr = board + A2065_LANCE;
unsigned long mem_start = board + A2065_RAM;
struct resource *r1, *r2;
u8 addr[ETH_ALEN];
u32 serial;
int err;
r1 = request_mem_region(base_addr, sizeof(struct lance_regs),
"Am7990");
if (!r1)
return -EBUSY;
r2 = request_mem_region(mem_start, A2065_RAM_SIZE, "RAM");
if (!r2) {
release_mem_region(base_addr, sizeof(struct lance_regs));
return -EBUSY;
}
dev = alloc_etherdev(sizeof(struct lance_private));
if (!dev) {
release_mem_region(base_addr, sizeof(struct lance_regs));
release_mem_region(mem_start, A2065_RAM_SIZE);
return -ENOMEM;
}
priv = netdev_priv(dev);
r1->name = dev->name;
r2->name = dev->name;
serial = be32_to_cpu(z->rom.er_SerialNumber);
addr[0] = 0x00;
if (z->id != ZORRO_PROD_AMERISTAR_A2065) { /* Commodore */
addr[1] = 0x80;
addr[2] = 0x10;
} else { /* Ameristar */
addr[1] = 0x00;
addr[2] = 0x9f;
}
addr[3] = (serial >> 16) & 0xff;
addr[4] = (serial >> 8) & 0xff;
addr[5] = serial & 0xff;
eth_hw_addr_set(dev, addr);
dev->base_addr = (unsigned long)ZTWO_VADDR(base_addr);
dev->mem_start = (unsigned long)ZTWO_VADDR(mem_start);
dev->mem_end = dev->mem_start + A2065_RAM_SIZE;
priv->ll = (volatile struct lance_regs *)dev->base_addr;
priv->init_block = (struct lance_init_block *)dev->mem_start;
priv->lance_init_block = (struct lance_init_block *)A2065_RAM;
priv->auto_select = 0;
priv->busmaster_regval = LE_C3_BSWP;
priv->lance_log_rx_bufs = LANCE_LOG_RX_BUFFERS;
priv->lance_log_tx_bufs = LANCE_LOG_TX_BUFFERS;
priv->rx_ring_mod_mask = RX_RING_MOD_MASK;
priv->tx_ring_mod_mask = TX_RING_MOD_MASK;
priv->dev = dev;
dev->netdev_ops = &lance_netdev_ops;
dev->watchdog_timeo = 5*HZ;
dev->dma = 0;
timer_setup(&priv->multicast_timer, lance_set_multicast_retry, 0);
err = register_netdev(dev);
if (err) {
release_mem_region(base_addr, sizeof(struct lance_regs));
release_mem_region(mem_start, A2065_RAM_SIZE);
free_netdev(dev);
return err;
}
zorro_set_drvdata(z, dev);
netdev_info(dev, "A2065 at 0x%08lx, Ethernet Address %pM\n",
board, dev->dev_addr);
return 0;
}
static void a2065_remove_one(struct zorro_dev *z)
{
struct net_device *dev = zorro_get_drvdata(z);
unregister_netdev(dev);
release_mem_region(ZTWO_PADDR(dev->base_addr),
sizeof(struct lance_regs));
release_mem_region(ZTWO_PADDR(dev->mem_start), A2065_RAM_SIZE);
free_netdev(dev);
}
static int __init a2065_init_module(void)
{
return zorro_register_driver(&a2065_driver);
}
static void __exit a2065_cleanup_module(void)
{
zorro_unregister_driver(&a2065_driver);
}
module_init(a2065_init_module);
module_exit(a2065_cleanup_module);
MODULE_DESCRIPTION("Commodore A2065 Ethernet driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,173 @@
/*
* Amiga Linux/68k A2065 Ethernet Driver
*
* (C) Copyright 1995 by Geert Uytterhoeven <geert@linux-m68k.org>
*
* ---------------------------------------------------------------------------
*
* This program is based on
*
* ariadne.?: Amiga Linux/68k Ariadne Ethernet Driver
* (C) Copyright 1995 by Geert Uytterhoeven,
* Peter De Schrijver
*
* lance.c: An AMD LANCE ethernet driver for linux.
* Written 1993-94 by Donald Becker.
*
* Am79C960: PCnet(tm)-ISA Single-Chip Ethernet Controller
* Advanced Micro Devices
* Publication #16907, Rev. B, Amendment/0, May 1994
*
* ---------------------------------------------------------------------------
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the Linux
* distribution for more details.
*
* ---------------------------------------------------------------------------
*
* The A2065 is a Zorro-II board made by Commodore/Ameristar. It contains:
*
* - an Am7990 Local Area Network Controller for Ethernet (LANCE) with
* both 10BASE-2 (thin coax) and AUI (DB-15) connectors
*/
/*
* Am7990 Local Area Network Controller for Ethernet (LANCE)
*/
struct lance_regs {
unsigned short rdp; /* Register Data Port */
unsigned short rap; /* Register Address Port */
};
/*
* Am7990 Control and Status Registers
*/
#define LE_CSR0 0x0000 /* LANCE Controller Status */
#define LE_CSR1 0x0001 /* IADR[15:0] */
#define LE_CSR2 0x0002 /* IADR[23:16] */
#define LE_CSR3 0x0003 /* Misc */
/*
* Bit definitions for CSR0 (LANCE Controller Status)
*/
#define LE_C0_ERR 0x8000 /* Error */
#define LE_C0_BABL 0x4000 /* Babble: Transmitted too many bits */
#define LE_C0_CERR 0x2000 /* No Heartbeat (10BASE-T) */
#define LE_C0_MISS 0x1000 /* Missed Frame */
#define LE_C0_MERR 0x0800 /* Memory Error */
#define LE_C0_RINT 0x0400 /* Receive Interrupt */
#define LE_C0_TINT 0x0200 /* Transmit Interrupt */
#define LE_C0_IDON 0x0100 /* Initialization Done */
#define LE_C0_INTR 0x0080 /* Interrupt Flag */
#define LE_C0_INEA 0x0040 /* Interrupt Enable */
#define LE_C0_RXON 0x0020 /* Receive On */
#define LE_C0_TXON 0x0010 /* Transmit On */
#define LE_C0_TDMD 0x0008 /* Transmit Demand */
#define LE_C0_STOP 0x0004 /* Stop */
#define LE_C0_STRT 0x0002 /* Start */
#define LE_C0_INIT 0x0001 /* Initialize */
/*
* Bit definitions for CSR3
*/
#define LE_C3_BSWP 0x0004 /* Byte Swap
(on for big endian byte order) */
#define LE_C3_ACON 0x0002 /* ALE Control
(on for active low ALE) */
#define LE_C3_BCON 0x0001 /* Byte Control */
/*
* Mode Flags
*/
#define LE_MO_PROM 0x8000 /* Promiscuous Mode */
#define LE_MO_INTL 0x0040 /* Internal Loopback */
#define LE_MO_DRTY 0x0020 /* Disable Retry */
#define LE_MO_FCOLL 0x0010 /* Force Collision */
#define LE_MO_DXMTFCS 0x0008 /* Disable Transmit CRC */
#define LE_MO_LOOP 0x0004 /* Loopback Enable */
#define LE_MO_DTX 0x0002 /* Disable Transmitter */
#define LE_MO_DRX 0x0001 /* Disable Receiver */
struct lance_rx_desc {
unsigned short rmd0; /* low address of packet */
unsigned char rmd1_bits; /* descriptor bits */
unsigned char rmd1_hadr; /* high address of packet */
short length; /* This length is 2s complement (negative)!
* Buffer length
*/
unsigned short mblength; /* Aactual number of bytes received */
};
struct lance_tx_desc {
unsigned short tmd0; /* low address of packet */
unsigned char tmd1_bits; /* descriptor bits */
unsigned char tmd1_hadr; /* high address of packet */
short length; /* Length is 2s complement (negative)! */
unsigned short misc;
};
/*
* Receive Flags
*/
#define LE_R1_OWN 0x80 /* LANCE owns the descriptor */
#define LE_R1_ERR 0x40 /* Error */
#define LE_R1_FRA 0x20 /* Framing Error */
#define LE_R1_OFL 0x10 /* Overflow Error */
#define LE_R1_CRC 0x08 /* CRC Error */
#define LE_R1_BUF 0x04 /* Buffer Error */
#define LE_R1_SOP 0x02 /* Start of Packet */
#define LE_R1_EOP 0x01 /* End of Packet */
#define LE_R1_POK 0x03 /* Packet is complete: SOP + EOP */
/*
* Transmit Flags
*/
#define LE_T1_OWN 0x80 /* LANCE owns the descriptor */
#define LE_T1_ERR 0x40 /* Error */
#define LE_T1_RES 0x20 /* Reserved,
LANCE writes this with a zero */
#define LE_T1_EMORE 0x10 /* More than one retry needed */
#define LE_T1_EONE 0x08 /* One retry needed */
#define LE_T1_EDEF 0x04 /* Deferred */
#define LE_T1_SOP 0x02 /* Start of Packet */
#define LE_T1_EOP 0x01 /* End of Packet */
#define LE_T1_POK 0x03 /* Packet is complete: SOP + EOP */
/*
* Error Flags
*/
#define LE_T3_BUF 0x8000 /* Buffer Error */
#define LE_T3_UFL 0x4000 /* Underflow Error */
#define LE_T3_LCOL 0x1000 /* Late Collision */
#define LE_T3_CLOS 0x0800 /* Loss of Carrier */
#define LE_T3_RTY 0x0400 /* Retry Error */
#define LE_T3_TDR 0x03ff /* Time Domain Reflectometry */
/*
* A2065 Expansion Board Structure
*/
#define A2065_LANCE 0x4000
#define A2065_RAM 0x8000
#define A2065_RAM_SIZE 0x8000
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,800 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Advanced Micro Devices Inc. AMD8111E Linux Network Driver
* Copyright (C) 2003 Advanced Micro Devices
*
Module Name:
amd8111e.h
Abstract:
AMD8111 based 10/100 Ethernet Controller driver definitions.
Environment:
Kernel Mode
Revision History:
3.0.0
Initial Revision.
3.0.1
*/
#ifndef _AMD811E_H
#define _AMD811E_H
/* Command style register access
Registers CMD0, CMD2, CMD3,CMD7 and INTEN0 uses a write access technique called command style access. It allows the write to selected bits of this register without altering the bits that are not selected. Command style registers are divided into 4 bytes that can be written independently. Higher order bit of each byte is the value bit that specifies the value that will be written into the selected bits of register.
eg., if the value 10011010b is written into the least significant byte of a command style register, bits 1,3 and 4 of the register will be set to 1, and the other bits will not be altered. If the value 00011010b is written into the same byte, bits 1,3 and 4 will be cleared to 0 and the other bits will not be altered.
*/
/* Offset for Memory Mapped Registers. */
/* 32 bit registers */
#define ASF_STAT 0x00 /* ASF status register */
#define CHIPID 0x04 /* Chip ID register */
#define MIB_DATA 0x10 /* MIB data register */
#define MIB_ADDR 0x14 /* MIB address register */
#define STAT0 0x30 /* Status0 register */
#define INT0 0x38 /* Interrupt0 register */
#define INTEN0 0x40 /* Interrupt0 enable register*/
#define CMD0 0x48 /* Command0 register */
#define CMD2 0x50 /* Command2 register */
#define CMD3 0x54 /* Command3 resiter */
#define CMD7 0x64 /* Command7 register */
#define CTRL1 0x6C /* Control1 register */
#define CTRL2 0x70 /* Control2 register */
#define XMT_RING_LIMIT 0x7C /* Transmit ring limit register */
#define AUTOPOLL0 0x88 /* Auto-poll0 register */
#define AUTOPOLL1 0x8A /* Auto-poll1 register */
#define AUTOPOLL2 0x8C /* Auto-poll2 register */
#define AUTOPOLL3 0x8E /* Auto-poll3 register */
#define AUTOPOLL4 0x90 /* Auto-poll4 register */
#define AUTOPOLL5 0x92 /* Auto-poll5 register */
#define AP_VALUE 0x98 /* Auto-poll value register */
#define DLY_INT_A 0xA8 /* Group A delayed interrupt register */
#define DLY_INT_B 0xAC /* Group B delayed interrupt register */
#define FLOW_CONTROL 0xC8 /* Flow control register */
#define PHY_ACCESS 0xD0 /* PHY access register */
#define STVAL 0xD8 /* Software timer value register */
#define XMT_RING_BASE_ADDR0 0x100 /* Transmit ring0 base addr register */
#define XMT_RING_BASE_ADDR1 0x108 /* Transmit ring1 base addr register */
#define XMT_RING_BASE_ADDR2 0x110 /* Transmit ring2 base addr register */
#define XMT_RING_BASE_ADDR3 0x118 /* Transmit ring2 base addr register */
#define RCV_RING_BASE_ADDR0 0x120 /* Transmit ring0 base addr register */
#define PMAT0 0x190 /* OnNow pattern register0 */
#define PMAT1 0x194 /* OnNow pattern register1 */
/* 16bit registers */
#define XMT_RING_LEN0 0x140 /* Transmit Ring0 length register */
#define XMT_RING_LEN1 0x144 /* Transmit Ring1 length register */
#define XMT_RING_LEN2 0x148 /* Transmit Ring2 length register */
#define XMT_RING_LEN3 0x14C /* Transmit Ring3 length register */
#define RCV_RING_LEN0 0x150 /* Receive Ring0 length register */
#define SRAM_SIZE 0x178 /* SRAM size register */
#define SRAM_BOUNDARY 0x17A /* SRAM boundary register */
/* 48bit register */
#define PADR 0x160 /* Physical address register */
#define IFS1 0x18C /* Inter-frame spacing Part1 register */
#define IFS 0x18D /* Inter-frame spacing register */
#define IPG 0x18E /* Inter-frame gap register */
/* 64bit register */
#define LADRF 0x168 /* Logical address filter register */
/* Register Bit Definitions */
typedef enum {
ASF_INIT_DONE = (1 << 1),
ASF_INIT_PRESENT = (1 << 0),
}STAT_ASF_BITS;
typedef enum {
MIB_CMD_ACTIVE = (1 << 15 ),
MIB_RD_CMD = (1 << 13 ),
MIB_CLEAR = (1 << 12 ),
MIB_ADDRESS = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3)|
(1 << 4) | (1 << 5),
}MIB_ADDR_BITS;
typedef enum {
PMAT_DET = (1 << 12),
MP_DET = (1 << 11),
LC_DET = (1 << 10),
SPEED_MASK = (1 << 9)|(1 << 8)|(1 << 7),
FULL_DPLX = (1 << 6),
LINK_STATS = (1 << 5),
AUTONEG_COMPLETE = (1 << 4),
MIIPD = (1 << 3),
RX_SUSPENDED = (1 << 2),
TX_SUSPENDED = (1 << 1),
RUNNING = (1 << 0),
}STAT0_BITS;
#define PHY_SPEED_10 0x2
#define PHY_SPEED_100 0x3
/* INT0 0x38, 32bit register */
typedef enum {
INTR = (1 << 31),
PCSINT = (1 << 28),
LCINT = (1 << 27),
APINT5 = (1 << 26),
APINT4 = (1 << 25),
APINT3 = (1 << 24),
TINT_SUM = (1 << 23),
APINT2 = (1 << 22),
APINT1 = (1 << 21),
APINT0 = (1 << 20),
MIIPDTINT = (1 << 19),
MCCINT = (1 << 17),
MREINT = (1 << 16),
RINT_SUM = (1 << 15),
SPNDINT = (1 << 14),
MPINT = (1 << 13),
SINT = (1 << 12),
TINT3 = (1 << 11),
TINT2 = (1 << 10),
TINT1 = (1 << 9),
TINT0 = (1 << 8),
UINT = (1 << 7),
STINT = (1 << 4),
RINT0 = (1 << 0),
}INT0_BITS;
typedef enum {
VAL3 = (1 << 31), /* VAL bit for byte 3 */
VAL2 = (1 << 23), /* VAL bit for byte 2 */
VAL1 = (1 << 15), /* VAL bit for byte 1 */
VAL0 = (1 << 7), /* VAL bit for byte 0 */
}VAL_BITS;
typedef enum {
/* VAL3 */
LCINTEN = (1 << 27),
APINT5EN = (1 << 26),
APINT4EN = (1 << 25),
APINT3EN = (1 << 24),
/* VAL2 */
APINT2EN = (1 << 22),
APINT1EN = (1 << 21),
APINT0EN = (1 << 20),
MIIPDTINTEN = (1 << 19),
MCCIINTEN = (1 << 18),
MCCINTEN = (1 << 17),
MREINTEN = (1 << 16),
/* VAL1 */
SPNDINTEN = (1 << 14),
MPINTEN = (1 << 13),
TINTEN3 = (1 << 11),
SINTEN = (1 << 12),
TINTEN2 = (1 << 10),
TINTEN1 = (1 << 9),
TINTEN0 = (1 << 8),
/* VAL0 */
STINTEN = (1 << 4),
RINTEN0 = (1 << 0),
INTEN0_CLEAR = 0x1F7F7F1F, /* Command style register */
}INTEN0_BITS;
typedef enum {
/* VAL2 */
RDMD0 = (1 << 16),
/* VAL1 */
TDMD3 = (1 << 11),
TDMD2 = (1 << 10),
TDMD1 = (1 << 9),
TDMD0 = (1 << 8),
/* VAL0 */
UINTCMD = (1 << 6),
RX_FAST_SPND = (1 << 5),
TX_FAST_SPND = (1 << 4),
RX_SPND = (1 << 3),
TX_SPND = (1 << 2),
INTREN = (1 << 1),
RUN = (1 << 0),
CMD0_CLEAR = 0x000F0F7F, /* Command style register */
}CMD0_BITS;
typedef enum {
/* VAL3 */
CONDUIT_MODE = (1 << 29),
/* VAL2 */
RPA = (1 << 19),
DRCVPA = (1 << 18),
DRCVBC = (1 << 17),
PROM = (1 << 16),
/* VAL1 */
ASTRP_RCV = (1 << 13),
RCV_DROP0 = (1 << 12),
EMBA = (1 << 11),
DXMT2PD = (1 << 10),
LTINTEN = (1 << 9),
DXMTFCS = (1 << 8),
/* VAL0 */
APAD_XMT = (1 << 6),
DRTY = (1 << 5),
INLOOP = (1 << 4),
EXLOOP = (1 << 3),
REX_RTRY = (1 << 2),
REX_UFLO = (1 << 1),
REX_LCOL = (1 << 0),
CMD2_CLEAR = 0x3F7F3F7F, /* Command style register */
}CMD2_BITS;
typedef enum {
/* VAL3 */
ASF_INIT_DONE_ALIAS = (1 << 29),
/* VAL2 */
JUMBO = (1 << 21),
VSIZE = (1 << 20),
VLONLY = (1 << 19),
VL_TAG_DEL = (1 << 18),
/* VAL1 */
EN_PMGR = (1 << 14),
INTLEVEL = (1 << 13),
FORCE_FULL_DUPLEX = (1 << 12),
FORCE_LINK_STATUS = (1 << 11),
APEP = (1 << 10),
MPPLBA = (1 << 9),
/* VAL0 */
RESET_PHY_PULSE = (1 << 2),
RESET_PHY = (1 << 1),
PHY_RST_POL = (1 << 0),
}CMD3_BITS;
typedef enum {
/* VAL0 */
PMAT_SAVE_MATCH = (1 << 4),
PMAT_MODE = (1 << 3),
MPEN_SW = (1 << 1),
LCMODE_SW = (1 << 0),
CMD7_CLEAR = 0x0000001B /* Command style register */
}CMD7_BITS;
typedef enum {
RESET_PHY_WIDTH = (0xF << 16) | (0xF<< 20), /* 0x00FF0000 */
XMTSP_MASK = (1 << 9) | (1 << 8), /* 9:8 */
XMTSP_128 = (1 << 9), /* 9 */
XMTSP_64 = (1 << 8),
CACHE_ALIGN = (1 << 4),
BURST_LIMIT_MASK = (0xF << 0 ),
CTRL1_DEFAULT = 0x00010111,
}CTRL1_BITS;
typedef enum {
FMDC_MASK = (1 << 9)|(1 << 8), /* 9:8 */
XPHYRST = (1 << 7),
XPHYANE = (1 << 6),
XPHYFD = (1 << 5),
XPHYSP = (1 << 4) | (1 << 3), /* 4:3 */
APDW_MASK = (1 << 2) | (1 << 1) | (1 << 0), /* 2:0 */
}CTRL2_BITS;
/* XMT_RING_LIMIT 0x7C, 32bit register */
typedef enum {
XMT_RING2_LIMIT = (0xFF << 16), /* 23:16 */
XMT_RING1_LIMIT = (0xFF << 8), /* 15:8 */
XMT_RING0_LIMIT = (0xFF << 0), /* 7:0 */
}XMT_RING_LIMIT_BITS;
typedef enum {
AP_REG0_EN = (1 << 15),
AP_REG0_ADDR_MASK = (0xF << 8) |(1 << 12),/* 12:8 */
AP_PHY0_ADDR_MASK = (0xF << 0) |(1 << 4),/* 4:0 */
}AUTOPOLL0_BITS;
/* AUTOPOLL1 0x8A, 16bit register */
typedef enum {
AP_REG1_EN = (1 << 15),
AP_REG1_ADDR_MASK = (0xF << 8) |(1 << 12),/* 12:8 */
AP_PRE_SUP1 = (1 << 6),
AP_PHY1_DFLT = (1 << 5),
AP_PHY1_ADDR_MASK = (0xF << 0) |(1 << 4),/* 4:0 */
}AUTOPOLL1_BITS;
typedef enum {
AP_REG2_EN = (1 << 15),
AP_REG2_ADDR_MASK = (0xF << 8) |(1 << 12),/* 12:8 */
AP_PRE_SUP2 = (1 << 6),
AP_PHY2_DFLT = (1 << 5),
AP_PHY2_ADDR_MASK = (0xF << 0) |(1 << 4),/* 4:0 */
}AUTOPOLL2_BITS;
typedef enum {
AP_REG3_EN = (1 << 15),
AP_REG3_ADDR_MASK = (0xF << 8) |(1 << 12),/* 12:8 */
AP_PRE_SUP3 = (1 << 6),
AP_PHY3_DFLT = (1 << 5),
AP_PHY3_ADDR_MASK = (0xF << 0) |(1 << 4),/* 4:0 */
}AUTOPOLL3_BITS;
typedef enum {
AP_REG4_EN = (1 << 15),
AP_REG4_ADDR_MASK = (0xF << 8) |(1 << 12),/* 12:8 */
AP_PRE_SUP4 = (1 << 6),
AP_PHY4_DFLT = (1 << 5),
AP_PHY4_ADDR_MASK = (0xF << 0) |(1 << 4),/* 4:0 */
}AUTOPOLL4_BITS;
typedef enum {
AP_REG5_EN = (1 << 15),
AP_REG5_ADDR_MASK = (0xF << 8) |(1 << 12),/* 12:8 */
AP_PRE_SUP5 = (1 << 6),
AP_PHY5_DFLT = (1 << 5),
AP_PHY5_ADDR_MASK = (0xF << 0) |(1 << 4),/* 4:0 */
}AUTOPOLL5_BITS;
/* AP_VALUE 0x98, 32bit ragister */
typedef enum {
AP_VAL_ACTIVE = (1 << 31),
AP_VAL_RD_CMD = ( 1 << 29),
AP_ADDR = (1 << 18)|(1 << 17)|(1 << 16), /* 18:16 */
AP_VAL = (0xF << 0) | (0xF << 4) |( 0xF << 8) |
(0xF << 12), /* 15:0 */
}AP_VALUE_BITS;
typedef enum {
DLY_INT_A_R3 = (1 << 31),
DLY_INT_A_R2 = (1 << 30),
DLY_INT_A_R1 = (1 << 29),
DLY_INT_A_R0 = (1 << 28),
DLY_INT_A_T3 = (1 << 27),
DLY_INT_A_T2 = (1 << 26),
DLY_INT_A_T1 = (1 << 25),
DLY_INT_A_T0 = ( 1 << 24),
EVENT_COUNT_A = (0xF << 16) | (0x1 << 20),/* 20:16 */
MAX_DELAY_TIME_A = (0xF << 0) | (0xF << 4) | (1 << 8)|
(1 << 9) | (1 << 10), /* 10:0 */
}DLY_INT_A_BITS;
typedef enum {
DLY_INT_B_R3 = (1 << 31),
DLY_INT_B_R2 = (1 << 30),
DLY_INT_B_R1 = (1 << 29),
DLY_INT_B_R0 = (1 << 28),
DLY_INT_B_T3 = (1 << 27),
DLY_INT_B_T2 = (1 << 26),
DLY_INT_B_T1 = (1 << 25),
DLY_INT_B_T0 = ( 1 << 24),
EVENT_COUNT_B = (0xF << 16) | (0x1 << 20),/* 20:16 */
MAX_DELAY_TIME_B = (0xF << 0) | (0xF << 4) | (1 << 8)|
(1 << 9) | (1 << 10), /* 10:0 */
}DLY_INT_B_BITS;
/* FLOW_CONTROL 0xC8, 32bit register */
typedef enum {
PAUSE_LEN_CHG = (1 << 30),
FTPE = (1 << 22),
FRPE = (1 << 21),
NAPA = (1 << 20),
NPA = (1 << 19),
FIXP = ( 1 << 18),
FCCMD = ( 1 << 16),
PAUSE_LEN = (0xF << 0) | (0xF << 4) |( 0xF << 8) | (0xF << 12), /* 15:0 */
}FLOW_CONTROL_BITS;
/* PHY_ ACCESS 0xD0, 32bit register */
typedef enum {
PHY_CMD_ACTIVE = (1 << 31),
PHY_WR_CMD = (1 << 30),
PHY_RD_CMD = (1 << 29),
PHY_RD_ERR = (1 << 28),
PHY_PRE_SUP = (1 << 27),
PHY_ADDR = (1 << 21) | (1 << 22) | (1 << 23)|
(1 << 24) |(1 << 25),/* 25:21 */
PHY_REG_ADDR = (1 << 16) | (1 << 17) | (1 << 18)| (1 << 19) | (1 << 20),/* 20:16 */
PHY_DATA = (0xF << 0)|(0xF << 4) |(0xF << 8)|
(0xF << 12),/* 15:0 */
}PHY_ACCESS_BITS;
/* PMAT0 0x190, 32bit register */
typedef enum {
PMR_ACTIVE = (1 << 31),
PMR_WR_CMD = (1 << 30),
PMR_RD_CMD = (1 << 29),
PMR_BANK = (1 <<28),
PMR_ADDR = (0xF << 16)|(1 << 20)|(1 << 21)|
(1 << 22),/* 22:16 */
PMR_B4 = (0xF << 0) | (0xF << 4),/* 15:0 */
}PMAT0_BITS;
/* PMAT1 0x194, 32bit register */
typedef enum {
PMR_B3 = (0xF << 24) | (0xF <<28),/* 31:24 */
PMR_B2 = (0xF << 16) |(0xF << 20),/* 23:16 */
PMR_B1 = (0xF << 8) | (0xF <<12), /* 15:8 */
PMR_B0 = (0xF << 0)|(0xF << 4),/* 7:0 */
}PMAT1_BITS;
/************************************************************************/
/* */
/* MIB counter definitions */
/* */
/************************************************************************/
#define rcv_miss_pkts 0x00
#define rcv_octets 0x01
#define rcv_broadcast_pkts 0x02
#define rcv_multicast_pkts 0x03
#define rcv_undersize_pkts 0x04
#define rcv_oversize_pkts 0x05
#define rcv_fragments 0x06
#define rcv_jabbers 0x07
#define rcv_unicast_pkts 0x08
#define rcv_alignment_errors 0x09
#define rcv_fcs_errors 0x0A
#define rcv_good_octets 0x0B
#define rcv_mac_ctrl 0x0C
#define rcv_flow_ctrl 0x0D
#define rcv_pkts_64_octets 0x0E
#define rcv_pkts_65to127_octets 0x0F
#define rcv_pkts_128to255_octets 0x10
#define rcv_pkts_256to511_octets 0x11
#define rcv_pkts_512to1023_octets 0x12
#define rcv_pkts_1024to1518_octets 0x13
#define rcv_unsupported_opcode 0x14
#define rcv_symbol_errors 0x15
#define rcv_drop_pkts_ring1 0x16
#define rcv_drop_pkts_ring2 0x17
#define rcv_drop_pkts_ring3 0x18
#define rcv_drop_pkts_ring4 0x19
#define rcv_jumbo_pkts 0x1A
#define xmt_underrun_pkts 0x20
#define xmt_octets 0x21
#define xmt_packets 0x22
#define xmt_broadcast_pkts 0x23
#define xmt_multicast_pkts 0x24
#define xmt_collisions 0x25
#define xmt_unicast_pkts 0x26
#define xmt_one_collision 0x27
#define xmt_multiple_collision 0x28
#define xmt_deferred_transmit 0x29
#define xmt_late_collision 0x2A
#define xmt_excessive_defer 0x2B
#define xmt_loss_carrier 0x2C
#define xmt_excessive_collision 0x2D
#define xmt_back_pressure 0x2E
#define xmt_flow_ctrl 0x2F
#define xmt_pkts_64_octets 0x30
#define xmt_pkts_65to127_octets 0x31
#define xmt_pkts_128to255_octets 0x32
#define xmt_pkts_256to511_octets 0x33
#define xmt_pkts_512to1023_octets 0x34
#define xmt_pkts_1024to1518_octet 0x35
#define xmt_oversize_pkts 0x36
#define xmt_jumbo_pkts 0x37
/* Driver definitions */
#define PCI_DEVICE_ID_AMD8111E_7462 0x7462
#define MAX_UNITS 8 /* Maximum number of devices possible */
#define NUM_TX_BUFFERS 32 /* Number of transmit buffers */
#define NUM_RX_BUFFERS 32 /* Number of receive buffers */
#define TX_BUFF_MOD_MASK 31 /* (NUM_TX_BUFFERS -1) */
#define RX_BUFF_MOD_MASK 31 /* (NUM_RX_BUFFERS -1) */
#define NUM_TX_RING_DR 32
#define NUM_RX_RING_DR 32
#define TX_RING_DR_MOD_MASK 31 /* (NUM_TX_RING_DR -1) */
#define RX_RING_DR_MOD_MASK 31 /* (NUM_RX_RING_DR -1) */
#define MAX_FILTER_SIZE 64 /* Maximum multicast address */
#define AMD8111E_MIN_MTU 60
#define AMD8111E_MAX_MTU 9000
#define PKT_BUFF_SZ 1536
#define MIN_PKT_LEN 60
#define AMD8111E_TX_TIMEOUT (3 * HZ)/* 3 sec */
#define SOFT_TIMER_FREQ 0xBEBC /* 0.5 sec */
#define DELAY_TIMER_CONV 50 /* msec to 10 usec conversion.
Only 500 usec resolution */
#define OPTION_VLAN_ENABLE 0x0001
#define OPTION_JUMBO_ENABLE 0x0002
#define OPTION_MULTICAST_ENABLE 0x0004
#define OPTION_WOL_ENABLE 0x0008
#define OPTION_WAKE_MAGIC_ENABLE 0x0010
#define OPTION_WAKE_PHY_ENABLE 0x0020
#define OPTION_INTR_COAL_ENABLE 0x0040
#define OPTION_DYN_IPG_ENABLE 0x0080
#define PHY_REG_ADDR_MASK 0x1f
/* ipg parameters */
#define DEFAULT_IPG 0x60
#define IFS1_DELTA 36
#define IPG_CONVERGE_JIFFIES (HZ/2)
#define IPG_STABLE_TIME 5
#define MIN_IPG 96
#define MAX_IPG 255
#define IPG_STEP 16
#define CSTATE 1
#define SSTATE 2
/* Assume controller gets data 10 times the maximum processing time */
#define REPEAT_CNT 10
/* amd8111e descriptor flag definitions */
typedef enum {
OWN_BIT = (1 << 15),
ADD_FCS_BIT = (1 << 13),
LTINT_BIT = (1 << 12),
STP_BIT = (1 << 9),
ENP_BIT = (1 << 8),
KILL_BIT = (1 << 6),
TCC_VLAN_INSERT = (1 << 1),
TCC_VLAN_REPLACE = (1 << 1) |( 1<< 0),
}TX_FLAG_BITS;
typedef enum {
ERR_BIT = (1 << 14),
FRAM_BIT = (1 << 13),
OFLO_BIT = (1 << 12),
CRC_BIT = (1 << 11),
PAM_BIT = (1 << 6),
LAFM_BIT = (1 << 5),
BAM_BIT = (1 << 4),
TT_VLAN_TAGGED = (1 << 3) |(1 << 2),/* 0x000 */
TT_PRTY_TAGGED = (1 << 3),/* 0x0008 */
}RX_FLAG_BITS;
#define RESET_RX_FLAGS 0x0000
#define TT_MASK 0x000c
#define TCC_MASK 0x0003
/* driver ioctl parameters */
#define AMD8111E_REG_DUMP_LEN 13*sizeof(u32)
/* amd8111e descriptor format */
struct amd8111e_tx_dr{
__le16 buff_count; /* Size of the buffer pointed by this descriptor */
__le16 tx_flags;
__le16 tag_ctrl_info;
__le16 tag_ctrl_cmd;
__le32 buff_phy_addr;
__le32 reserved;
};
struct amd8111e_rx_dr{
__le32 reserved;
__le16 msg_count; /* Received message len */
__le16 tag_ctrl_info;
__le16 buff_count; /* Len of the buffer pointed by descriptor. */
__le16 rx_flags;
__le32 buff_phy_addr;
};
struct amd8111e_link_config{
#define SPEED_INVALID 0xffff
#define DUPLEX_INVALID 0xff
#define AUTONEG_INVALID 0xff
unsigned long orig_phy_option;
u16 speed;
u8 duplex;
u8 autoneg;
u8 reserved; /* 32bit alignment */
};
enum coal_type{
NO_COALESCE,
LOW_COALESCE,
MEDIUM_COALESCE,
HIGH_COALESCE,
};
enum coal_mode{
RX_INTR_COAL,
TX_INTR_COAL,
DISABLE_COAL,
ENABLE_COAL,
};
#define MAX_TIMEOUT 40
#define MAX_EVENT_COUNT 31
struct amd8111e_coalesce_conf{
unsigned int rx_timeout;
unsigned int rx_event_count;
unsigned long rx_packets;
unsigned long rx_prev_packets;
unsigned long rx_bytes;
unsigned long rx_prev_bytes;
unsigned int rx_coal_type;
unsigned int tx_timeout;
unsigned int tx_event_count;
unsigned long tx_packets;
unsigned long tx_prev_packets;
unsigned long tx_bytes;
unsigned long tx_prev_bytes;
unsigned int tx_coal_type;
};
struct ipg_info{
unsigned int ipg_state;
unsigned int ipg;
unsigned int current_ipg;
unsigned int col_cnt;
unsigned int diff_col_cnt;
unsigned int timer_tick;
unsigned int prev_ipg;
struct timer_list ipg_timer;
};
struct amd8111e_priv{
struct amd8111e_tx_dr* tx_ring;
struct amd8111e_rx_dr* rx_ring;
dma_addr_t tx_ring_dma_addr; /* tx descriptor ring base address */
dma_addr_t rx_ring_dma_addr; /* rx descriptor ring base address */
const char *name;
struct pci_dev *pci_dev; /* Ptr to the associated pci_dev */
struct net_device* amd8111e_net_dev; /* ptr to associated net_device */
/* Transmit and receive skbs */
struct sk_buff *tx_skbuff[NUM_TX_BUFFERS];
struct sk_buff *rx_skbuff[NUM_RX_BUFFERS];
/* Transmit and receive dma mapped addr */
dma_addr_t tx_dma_addr[NUM_TX_BUFFERS];
dma_addr_t rx_dma_addr[NUM_RX_BUFFERS];
/* Reg memory mapped address */
void __iomem *mmio;
struct napi_struct napi;
spinlock_t lock; /* Guard lock */
unsigned long rx_idx, tx_idx; /* The next free ring entry */
unsigned long tx_complete_idx;
unsigned long tx_ring_complete_idx;
unsigned long tx_ring_idx;
unsigned int rx_buff_len; /* Buffer length of rx buffers */
int options; /* Options enabled/disabled for the device */
unsigned long ext_phy_option;
int ext_phy_addr;
u32 ext_phy_id;
struct amd8111e_link_config link_config;
struct net_device *next;
int mii;
struct mii_if_info mii_if;
char opened;
unsigned int drv_rx_errors;
struct amd8111e_coalesce_conf coal_conf;
struct ipg_info ipg_data;
};
/* kernel provided writeq does not write 64 bits into the amd8111e device register instead writes only higher 32bits data into lower 32bits of the register.
BUG? */
#define amd8111e_writeq(_UlData,_memMap) \
writel(*(u32*)(&_UlData), _memMap); \
writel(*(u32*)((u8*)(&_UlData)+4), _memMap+4)
/* maps the external speed options to internal value */
typedef enum {
SPEED_AUTONEG,
SPEED10_HALF,
SPEED10_FULL,
SPEED100_HALF,
SPEED100_FULL,
}EXT_PHY_OPTION;
static int card_idx;
static int speed_duplex[MAX_UNITS] = { 0, };
static bool coalesce[MAX_UNITS] = { [ 0 ... MAX_UNITS-1] = true };
static bool dynamic_ipg[MAX_UNITS] = { [ 0 ... MAX_UNITS-1] = false };
static unsigned int chip_version;
#endif /* _AMD8111E_H */
@@ -0,0 +1,794 @@
/*
* Amiga Linux/m68k Ariadne Ethernet Driver
*
* © Copyright 1995-2003 by Geert Uytterhoeven (geert@linux-m68k.org)
* Peter De Schrijver (p2@mind.be)
*
* ---------------------------------------------------------------------------
*
* This program is based on
*
* lance.c: An AMD LANCE ethernet driver for linux.
* Written 1993-94 by Donald Becker.
*
* Am79C960: PCnet(tm)-ISA Single-Chip Ethernet Controller
* Advanced Micro Devices
* Publication #16907, Rev. B, Amendment/0, May 1994
*
* MC68230: Parallel Interface/Timer (PI/T)
* Motorola Semiconductors, December, 1983
*
* ---------------------------------------------------------------------------
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the Linux
* distribution for more details.
*
* ---------------------------------------------------------------------------
*
* The Ariadne is a Zorro-II board made by Village Tronic. It contains:
*
* - an Am79C960 PCnet-ISA Single-Chip Ethernet Controller with both
* 10BASE-2 (thin coax) and 10BASE-T (UTP) connectors
*
* - an MC68230 Parallel Interface/Timer configured as 2 parallel ports
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/*#define DEBUG*/
#include <linux/module.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/interrupt.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/zorro.h>
#include <linux/bitops.h>
#include <asm/byteorder.h>
#include <asm/amigaints.h>
#include <asm/amigahw.h>
#include <asm/irq.h>
#include "ariadne.h"
#ifdef ARIADNE_DEBUG
int ariadne_debug = ARIADNE_DEBUG;
#else
int ariadne_debug = 1;
#endif
/* Macros to Fix Endianness problems */
/* Swap the Bytes in a WORD */
#define swapw(x) (((x >> 8) & 0x00ff) | ((x << 8) & 0xff00))
/* Get the Low BYTE in a WORD */
#define lowb(x) (x & 0xff)
/* Get the Swapped High WORD in a LONG */
#define swhighw(x) ((((x) >> 8) & 0xff00) | (((x) >> 24) & 0x00ff))
/* Get the Swapped Low WORD in a LONG */
#define swloww(x) ((((x) << 8) & 0xff00) | (((x) >> 8) & 0x00ff))
/* Transmit/Receive Ring Definitions */
#define TX_RING_SIZE 5
#define RX_RING_SIZE 16
#define PKT_BUF_SIZE 1520
/* Private Device Data */
struct ariadne_private {
volatile struct TDRE *tx_ring[TX_RING_SIZE];
volatile struct RDRE *rx_ring[RX_RING_SIZE];
volatile u_short *tx_buff[TX_RING_SIZE];
volatile u_short *rx_buff[RX_RING_SIZE];
int cur_tx, cur_rx; /* The next free ring entry */
int dirty_tx; /* The ring entries to be free()ed */
char tx_full;
};
/* Structure Created in the Ariadne's RAM Buffer */
struct lancedata {
struct TDRE tx_ring[TX_RING_SIZE];
struct RDRE rx_ring[RX_RING_SIZE];
u_short tx_buff[TX_RING_SIZE][PKT_BUF_SIZE / sizeof(u_short)];
u_short rx_buff[RX_RING_SIZE][PKT_BUF_SIZE / sizeof(u_short)];
};
static void memcpyw(volatile u_short *dest, u_short *src, int len)
{
while (len >= 2) {
*(dest++) = *(src++);
len -= 2;
}
if (len == 1)
*dest = (*(u_char *)src) << 8;
}
static void ariadne_init_ring(struct net_device *dev)
{
struct ariadne_private *priv = netdev_priv(dev);
volatile struct lancedata *lancedata = (struct lancedata *)dev->mem_start;
int i;
netif_stop_queue(dev);
priv->tx_full = 0;
priv->cur_rx = priv->cur_tx = 0;
priv->dirty_tx = 0;
/* Set up TX Ring */
for (i = 0; i < TX_RING_SIZE; i++) {
volatile struct TDRE *t = &lancedata->tx_ring[i];
t->TMD0 = swloww(ARIADNE_RAM +
offsetof(struct lancedata, tx_buff[i]));
t->TMD1 = swhighw(ARIADNE_RAM +
offsetof(struct lancedata, tx_buff[i])) |
TF_STP | TF_ENP;
t->TMD2 = swapw((u_short)-PKT_BUF_SIZE);
t->TMD3 = 0;
priv->tx_ring[i] = &lancedata->tx_ring[i];
priv->tx_buff[i] = lancedata->tx_buff[i];
netdev_dbg(dev, "TX Entry %2d at %p, Buf at %p\n",
i, &lancedata->tx_ring[i], lancedata->tx_buff[i]);
}
/* Set up RX Ring */
for (i = 0; i < RX_RING_SIZE; i++) {
volatile struct RDRE *r = &lancedata->rx_ring[i];
r->RMD0 = swloww(ARIADNE_RAM +
offsetof(struct lancedata, rx_buff[i]));
r->RMD1 = swhighw(ARIADNE_RAM +
offsetof(struct lancedata, rx_buff[i])) |
RF_OWN;
r->RMD2 = swapw((u_short)-PKT_BUF_SIZE);
r->RMD3 = 0x0000;
priv->rx_ring[i] = &lancedata->rx_ring[i];
priv->rx_buff[i] = lancedata->rx_buff[i];
netdev_dbg(dev, "RX Entry %2d at %p, Buf at %p\n",
i, &lancedata->rx_ring[i], lancedata->rx_buff[i]);
}
}
static int ariadne_rx(struct net_device *dev)
{
struct ariadne_private *priv = netdev_priv(dev);
int entry = priv->cur_rx % RX_RING_SIZE;
int i;
/* If we own the next entry, it's a new packet. Send it up */
while (!(lowb(priv->rx_ring[entry]->RMD1) & RF_OWN)) {
int status = lowb(priv->rx_ring[entry]->RMD1);
if (status != (RF_STP | RF_ENP)) { /* There was an error */
/* There is a tricky error noted by
* John Murphy <murf@perftech.com> to Russ Nelson:
* Even with full-sized buffers it's possible for a
* jabber packet to use two buffers, with only the
* last correctly noting the error
*/
/* Only count a general error at the end of a packet */
if (status & RF_ENP)
dev->stats.rx_errors++;
if (status & RF_FRAM)
dev->stats.rx_frame_errors++;
if (status & RF_OFLO)
dev->stats.rx_over_errors++;
if (status & RF_CRC)
dev->stats.rx_crc_errors++;
if (status & RF_BUFF)
dev->stats.rx_fifo_errors++;
priv->rx_ring[entry]->RMD1 &= 0xff00 | RF_STP | RF_ENP;
} else {
/* Malloc up new buffer, compatible with net-3 */
short pkt_len = swapw(priv->rx_ring[entry]->RMD3);
struct sk_buff *skb;
skb = netdev_alloc_skb(dev, pkt_len + 2);
if (!skb) {
for (i = 0; i < RX_RING_SIZE; i++)
if (lowb(priv->rx_ring[(entry + i) % RX_RING_SIZE]->RMD1) & RF_OWN)
break;
if (i > RX_RING_SIZE - 2) {
dev->stats.rx_dropped++;
priv->rx_ring[entry]->RMD1 |= RF_OWN;
priv->cur_rx++;
}
break;
}
skb_reserve(skb, 2); /* 16 byte align */
skb_put(skb, pkt_len); /* Make room */
skb_copy_to_linear_data(skb,
(const void *)priv->rx_buff[entry],
pkt_len);
skb->protocol = eth_type_trans(skb, dev);
netdev_dbg(dev, "RX pkt type 0x%04x from %pM to %pM data %p len %u\n",
((u_short *)skb->data)[6],
skb->data + 6, skb->data,
skb->data, skb->len);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += pkt_len;
}
priv->rx_ring[entry]->RMD1 |= RF_OWN;
entry = (++priv->cur_rx) % RX_RING_SIZE;
}
priv->cur_rx = priv->cur_rx % RX_RING_SIZE;
/* We should check that at least two ring entries are free.
* If not, we should free one and mark stats->rx_dropped++
*/
return 0;
}
static irqreturn_t ariadne_interrupt(int irq, void *data)
{
struct net_device *dev = (struct net_device *)data;
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
struct ariadne_private *priv;
int csr0, boguscnt;
int handled = 0;
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
if (!(lance->RDP & INTR)) /* Check if any interrupt has been */
return IRQ_NONE; /* generated by the board */
priv = netdev_priv(dev);
boguscnt = 10;
while ((csr0 = lance->RDP) & (ERR | RINT | TINT) && --boguscnt >= 0) {
/* Acknowledge all of the current interrupt sources ASAP */
lance->RDP = csr0 & ~(INEA | TDMD | STOP | STRT | INIT);
#ifdef DEBUG
if (ariadne_debug > 5) {
netdev_dbg(dev, "interrupt csr0=%#02x new csr=%#02x [",
csr0, lance->RDP);
if (csr0 & INTR)
pr_cont(" INTR");
if (csr0 & INEA)
pr_cont(" INEA");
if (csr0 & RXON)
pr_cont(" RXON");
if (csr0 & TXON)
pr_cont(" TXON");
if (csr0 & TDMD)
pr_cont(" TDMD");
if (csr0 & STOP)
pr_cont(" STOP");
if (csr0 & STRT)
pr_cont(" STRT");
if (csr0 & INIT)
pr_cont(" INIT");
if (csr0 & ERR)
pr_cont(" ERR");
if (csr0 & BABL)
pr_cont(" BABL");
if (csr0 & CERR)
pr_cont(" CERR");
if (csr0 & MISS)
pr_cont(" MISS");
if (csr0 & MERR)
pr_cont(" MERR");
if (csr0 & RINT)
pr_cont(" RINT");
if (csr0 & TINT)
pr_cont(" TINT");
if (csr0 & IDON)
pr_cont(" IDON");
pr_cont(" ]\n");
}
#endif
if (csr0 & RINT) { /* Rx interrupt */
handled = 1;
ariadne_rx(dev);
}
if (csr0 & TINT) { /* Tx-done interrupt */
int dirty_tx = priv->dirty_tx;
handled = 1;
while (dirty_tx < priv->cur_tx) {
int entry = dirty_tx % TX_RING_SIZE;
int status = lowb(priv->tx_ring[entry]->TMD1);
if (status & TF_OWN)
break; /* It still hasn't been Txed */
priv->tx_ring[entry]->TMD1 &= 0xff00;
if (status & TF_ERR) {
/* There was an major error, log it */
int err_status = priv->tx_ring[entry]->TMD3;
dev->stats.tx_errors++;
if (err_status & EF_RTRY)
dev->stats.tx_aborted_errors++;
if (err_status & EF_LCAR)
dev->stats.tx_carrier_errors++;
if (err_status & EF_LCOL)
dev->stats.tx_window_errors++;
if (err_status & EF_UFLO) {
/* Ackk! On FIFO errors the Tx unit is turned off! */
dev->stats.tx_fifo_errors++;
/* Remove this verbosity later! */
netdev_err(dev, "Tx FIFO error! Status %04x\n",
csr0);
/* Restart the chip */
lance->RDP = STRT;
}
} else {
if (status & (TF_MORE | TF_ONE))
dev->stats.collisions++;
dev->stats.tx_packets++;
}
dirty_tx++;
}
#ifndef final_version
if (priv->cur_tx - dirty_tx >= TX_RING_SIZE) {
netdev_err(dev, "out-of-sync dirty pointer, %d vs. %d, full=%d\n",
dirty_tx, priv->cur_tx,
priv->tx_full);
dirty_tx += TX_RING_SIZE;
}
#endif
if (priv->tx_full && netif_queue_stopped(dev) &&
dirty_tx > priv->cur_tx - TX_RING_SIZE + 2) {
/* The ring is no longer full */
priv->tx_full = 0;
netif_wake_queue(dev);
}
priv->dirty_tx = dirty_tx;
}
/* Log misc errors */
if (csr0 & BABL) {
handled = 1;
dev->stats.tx_errors++; /* Tx babble */
}
if (csr0 & MISS) {
handled = 1;
dev->stats.rx_errors++; /* Missed a Rx frame */
}
if (csr0 & MERR) {
handled = 1;
netdev_err(dev, "Bus master arbitration failure, status %04x\n",
csr0);
/* Restart the chip */
lance->RDP = STRT;
}
}
/* Clear any other interrupt, and set interrupt enable */
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = INEA | BABL | CERR | MISS | MERR | IDON;
if (ariadne_debug > 4)
netdev_dbg(dev, "exiting interrupt, csr%d=%#04x\n",
lance->RAP, lance->RDP);
return IRQ_RETVAL(handled);
}
static int ariadne_open(struct net_device *dev)
{
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
u_short in;
u_long version;
int i;
/* Reset the LANCE */
in = lance->Reset;
/* Stop the LANCE */
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = STOP;
/* Check the LANCE version */
lance->RAP = CSR88; /* Chip ID */
version = swapw(lance->RDP);
lance->RAP = CSR89; /* Chip ID */
version |= swapw(lance->RDP) << 16;
if ((version & 0x00000fff) != 0x00000003) {
pr_warn("Couldn't find AMD Ethernet Chip\n");
return -EAGAIN;
}
if ((version & 0x0ffff000) != 0x00003000) {
pr_warn("Couldn't find Am79C960 (Wrong part number = %ld)\n",
(version & 0x0ffff000) >> 12);
return -EAGAIN;
}
netdev_dbg(dev, "Am79C960 (PCnet-ISA) Revision %ld\n",
(version & 0xf0000000) >> 28);
ariadne_init_ring(dev);
/* Miscellaneous Stuff */
lance->RAP = CSR3; /* Interrupt Masks and Deferral Control */
lance->RDP = 0x0000;
lance->RAP = CSR4; /* Test and Features Control */
lance->RDP = DPOLL | APAD_XMT | MFCOM | RCVCCOM | TXSTRTM | JABM;
/* Set the Multicast Table */
lance->RAP = CSR8; /* Logical Address Filter, LADRF[15:0] */
lance->RDP = 0x0000;
lance->RAP = CSR9; /* Logical Address Filter, LADRF[31:16] */
lance->RDP = 0x0000;
lance->RAP = CSR10; /* Logical Address Filter, LADRF[47:32] */
lance->RDP = 0x0000;
lance->RAP = CSR11; /* Logical Address Filter, LADRF[63:48] */
lance->RDP = 0x0000;
/* Set the Ethernet Hardware Address */
lance->RAP = CSR12; /* Physical Address Register, PADR[15:0] */
lance->RDP = ((const u_short *)&dev->dev_addr[0])[0];
lance->RAP = CSR13; /* Physical Address Register, PADR[31:16] */
lance->RDP = ((const u_short *)&dev->dev_addr[0])[1];
lance->RAP = CSR14; /* Physical Address Register, PADR[47:32] */
lance->RDP = ((const u_short *)&dev->dev_addr[0])[2];
/* Set the Init Block Mode */
lance->RAP = CSR15; /* Mode Register */
lance->RDP = 0x0000;
/* Set the Transmit Descriptor Ring Pointer */
lance->RAP = CSR30; /* Base Address of Transmit Ring */
lance->RDP = swloww(ARIADNE_RAM + offsetof(struct lancedata, tx_ring));
lance->RAP = CSR31; /* Base Address of transmit Ring */
lance->RDP = swhighw(ARIADNE_RAM + offsetof(struct lancedata, tx_ring));
/* Set the Receive Descriptor Ring Pointer */
lance->RAP = CSR24; /* Base Address of Receive Ring */
lance->RDP = swloww(ARIADNE_RAM + offsetof(struct lancedata, rx_ring));
lance->RAP = CSR25; /* Base Address of Receive Ring */
lance->RDP = swhighw(ARIADNE_RAM + offsetof(struct lancedata, rx_ring));
/* Set the Number of RX and TX Ring Entries */
lance->RAP = CSR76; /* Receive Ring Length */
lance->RDP = swapw(((u_short)-RX_RING_SIZE));
lance->RAP = CSR78; /* Transmit Ring Length */
lance->RDP = swapw(((u_short)-TX_RING_SIZE));
/* Enable Media Interface Port Auto Select (10BASE-2/10BASE-T) */
lance->RAP = ISACSR2; /* Miscellaneous Configuration */
lance->IDP = ASEL;
/* LED Control */
lance->RAP = ISACSR5; /* LED1 Status */
lance->IDP = PSE|XMTE;
lance->RAP = ISACSR6; /* LED2 Status */
lance->IDP = PSE|COLE;
lance->RAP = ISACSR7; /* LED3 Status */
lance->IDP = PSE|RCVE;
netif_start_queue(dev);
i = request_irq(IRQ_AMIGA_PORTS, ariadne_interrupt, IRQF_SHARED,
dev->name, dev);
if (i)
return i;
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = INEA | STRT;
return 0;
}
static int ariadne_close(struct net_device *dev)
{
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
netif_stop_queue(dev);
lance->RAP = CSR112; /* Missed Frame Count */
dev->stats.rx_missed_errors = swapw(lance->RDP);
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
if (ariadne_debug > 1) {
netdev_dbg(dev, "Shutting down ethercard, status was %02x\n",
lance->RDP);
netdev_dbg(dev, "%lu packets missed\n",
dev->stats.rx_missed_errors);
}
/* We stop the LANCE here -- it occasionally polls memory if we don't */
lance->RDP = STOP;
free_irq(IRQ_AMIGA_PORTS, dev);
return 0;
}
static inline void ariadne_reset(struct net_device *dev)
{
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = STOP;
ariadne_init_ring(dev);
lance->RDP = INEA | STRT;
netif_start_queue(dev);
}
static void ariadne_tx_timeout(struct net_device *dev, unsigned int txqueue)
{
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
netdev_err(dev, "transmit timed out, status %04x, resetting\n",
lance->RDP);
ariadne_reset(dev);
netif_wake_queue(dev);
}
static netdev_tx_t ariadne_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct ariadne_private *priv = netdev_priv(dev);
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
int entry;
unsigned long flags;
int len = skb->len;
#if 0
if (ariadne_debug > 3) {
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
netdev_dbg(dev, "%s: csr0 %04x\n", __func__, lance->RDP);
lance->RDP = 0x0000;
}
#endif
/* FIXME: is the 79C960 new enough to do its own padding right ? */
if (skb->len < ETH_ZLEN) {
if (skb_padto(skb, ETH_ZLEN))
return NETDEV_TX_OK;
len = ETH_ZLEN;
}
/* Fill in a Tx ring entry */
netdev_dbg(dev, "TX pkt type 0x%04x from %pM to %pM data %p len %u\n",
((u_short *)skb->data)[6],
skb->data + 6, skb->data,
skb->data, skb->len);
local_irq_save(flags);
entry = priv->cur_tx % TX_RING_SIZE;
/* Caution: the write order is important here, set the base address with
the "ownership" bits last */
priv->tx_ring[entry]->TMD2 = swapw((u_short)-skb->len);
priv->tx_ring[entry]->TMD3 = 0x0000;
memcpyw(priv->tx_buff[entry], (u_short *)skb->data, len);
#ifdef DEBUG
print_hex_dump(KERN_DEBUG, "tx_buff: ", DUMP_PREFIX_OFFSET, 16, 1,
(void *)priv->tx_buff[entry],
skb->len > 64 ? 64 : skb->len, true);
#endif
priv->tx_ring[entry]->TMD1 = (priv->tx_ring[entry]->TMD1 & 0xff00)
| TF_OWN | TF_STP | TF_ENP;
dev_kfree_skb(skb);
priv->cur_tx++;
if ((priv->cur_tx >= TX_RING_SIZE) &&
(priv->dirty_tx >= TX_RING_SIZE)) {
netdev_dbg(dev, "*** Subtracting TX_RING_SIZE from cur_tx (%d) and dirty_tx (%d)\n",
priv->cur_tx, priv->dirty_tx);
priv->cur_tx -= TX_RING_SIZE;
priv->dirty_tx -= TX_RING_SIZE;
}
dev->stats.tx_bytes += len;
/* Trigger an immediate send poll */
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = INEA | TDMD;
if (lowb(priv->tx_ring[(entry + 1) % TX_RING_SIZE]->TMD1) != 0) {
netif_stop_queue(dev);
priv->tx_full = 1;
}
local_irq_restore(flags);
return NETDEV_TX_OK;
}
static struct net_device_stats *ariadne_get_stats(struct net_device *dev)
{
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
short saved_addr;
unsigned long flags;
local_irq_save(flags);
saved_addr = lance->RAP;
lance->RAP = CSR112; /* Missed Frame Count */
dev->stats.rx_missed_errors = swapw(lance->RDP);
lance->RAP = saved_addr;
local_irq_restore(flags);
return &dev->stats;
}
/* Set or clear the multicast filter for this adaptor.
* num_addrs == -1 Promiscuous mode, receive all packets
* num_addrs == 0 Normal mode, clear multicast list
* num_addrs > 0 Multicast mode, receive normal and MC packets,
* and do best-effort filtering.
*/
static void set_multicast_list(struct net_device *dev)
{
volatile struct Am79C960 *lance = (struct Am79C960 *)dev->base_addr;
if (!netif_running(dev))
return;
netif_stop_queue(dev);
/* We take the simple way out and always enable promiscuous mode */
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = STOP; /* Temporarily stop the lance */
ariadne_init_ring(dev);
if (dev->flags & IFF_PROMISC) {
lance->RAP = CSR15; /* Mode Register */
lance->RDP = PROM; /* Set promiscuous mode */
} else {
short multicast_table[4];
int num_addrs = netdev_mc_count(dev);
int i;
/* We don't use the multicast table,
* but rely on upper-layer filtering
*/
memset(multicast_table, (num_addrs == 0) ? 0 : -1,
sizeof(multicast_table));
for (i = 0; i < 4; i++) {
lance->RAP = CSR8 + (i << 8);
/* Logical Address Filter */
lance->RDP = swapw(multicast_table[i]);
}
lance->RAP = CSR15; /* Mode Register */
lance->RDP = 0x0000; /* Unset promiscuous mode */
}
lance->RAP = CSR0; /* PCnet-ISA Controller Status */
lance->RDP = INEA | STRT | IDON;/* Resume normal operation */
netif_wake_queue(dev);
}
static void ariadne_remove_one(struct zorro_dev *z)
{
struct net_device *dev = zorro_get_drvdata(z);
unregister_netdev(dev);
release_mem_region(ZTWO_PADDR(dev->base_addr), sizeof(struct Am79C960));
release_mem_region(ZTWO_PADDR(dev->mem_start), ARIADNE_RAM_SIZE);
free_netdev(dev);
}
static const struct zorro_device_id ariadne_zorro_tbl[] = {
{ .id = ZORRO_PROD_VILLAGE_TRONIC_ARIADNE },
{ }
};
MODULE_DEVICE_TABLE(zorro, ariadne_zorro_tbl);
static const struct net_device_ops ariadne_netdev_ops = {
.ndo_open = ariadne_open,
.ndo_stop = ariadne_close,
.ndo_start_xmit = ariadne_start_xmit,
.ndo_tx_timeout = ariadne_tx_timeout,
.ndo_get_stats = ariadne_get_stats,
.ndo_set_rx_mode = set_multicast_list,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
static int ariadne_init_one(struct zorro_dev *z,
const struct zorro_device_id *ent)
{
unsigned long board = z->resource.start;
unsigned long base_addr = board + ARIADNE_LANCE;
unsigned long mem_start = board + ARIADNE_RAM;
struct resource *r1, *r2;
struct net_device *dev;
u8 addr[ETH_ALEN];
u32 serial;
int err;
r1 = request_mem_region(base_addr, sizeof(struct Am79C960), "Am79C960");
if (!r1)
return -EBUSY;
r2 = request_mem_region(mem_start, ARIADNE_RAM_SIZE, "RAM");
if (!r2) {
release_mem_region(base_addr, sizeof(struct Am79C960));
return -EBUSY;
}
dev = alloc_etherdev(sizeof(struct ariadne_private));
if (!dev) {
release_mem_region(base_addr, sizeof(struct Am79C960));
release_mem_region(mem_start, ARIADNE_RAM_SIZE);
return -ENOMEM;
}
r1->name = dev->name;
r2->name = dev->name;
serial = be32_to_cpu(z->rom.er_SerialNumber);
addr[0] = 0x00;
addr[1] = 0x60;
addr[2] = 0x30;
addr[3] = (serial >> 16) & 0xff;
addr[4] = (serial >> 8) & 0xff;
addr[5] = serial & 0xff;
eth_hw_addr_set(dev, addr);
dev->base_addr = (unsigned long)ZTWO_VADDR(base_addr);
dev->mem_start = (unsigned long)ZTWO_VADDR(mem_start);
dev->mem_end = dev->mem_start + ARIADNE_RAM_SIZE;
dev->netdev_ops = &ariadne_netdev_ops;
dev->watchdog_timeo = 5 * HZ;
err = register_netdev(dev);
if (err) {
release_mem_region(base_addr, sizeof(struct Am79C960));
release_mem_region(mem_start, ARIADNE_RAM_SIZE);
free_netdev(dev);
return err;
}
zorro_set_drvdata(z, dev);
netdev_info(dev, "Ariadne at 0x%08lx, Ethernet Address %pM\n",
board, dev->dev_addr);
return 0;
}
static struct zorro_driver ariadne_driver = {
.name = "ariadne",
.id_table = ariadne_zorro_tbl,
.probe = ariadne_init_one,
.remove = ariadne_remove_one,
};
static int __init ariadne_init_module(void)
{
return zorro_register_driver(&ariadne_driver);
}
static void __exit ariadne_cleanup_module(void)
{
zorro_unregister_driver(&ariadne_driver);
}
module_init(ariadne_init_module);
module_exit(ariadne_cleanup_module);
MODULE_DESCRIPTION("Ariadne Ethernet Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,415 @@
/*
* Amiga Linux/m68k Ariadne Ethernet Driver
*
* © Copyright 1995 by Geert Uytterhoeven (geert@linux-m68k.org)
* Peter De Schrijver
* (Peter.DeSchrijver@linux.cc.kuleuven.ac.be)
*
* ----------------------------------------------------------------------------------
*
* This program is based on
*
* lance.c: An AMD LANCE ethernet driver for linux.
* Written 1993-94 by Donald Becker.
*
* Am79C960: PCnet(tm)-ISA Single-Chip Ethernet Controller
* Advanced Micro Devices
* Publication #16907, Rev. B, Amendment/0, May 1994
*
* MC68230: Parallel Interface/Timer (PI/T)
* Motorola Semiconductors, December, 1983
*
* ----------------------------------------------------------------------------------
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the Linux
* distribution for more details.
*
* ----------------------------------------------------------------------------------
*
* The Ariadne is a Zorro-II board made by Village Tronic. It contains:
*
* - an Am79C960 PCnet-ISA Single-Chip Ethernet Controller with both
* 10BASE-2 (thin coax) and 10BASE-T (UTP) connectors
*
* - an MC68230 Parallel Interface/Timer configured as 2 parallel ports
*/
/*
* Am79C960 PCnet-ISA
*/
struct Am79C960 {
volatile u_short AddressPROM[8];
/* IEEE Address PROM (Unused in the Ariadne) */
volatile u_short RDP; /* Register Data Port */
volatile u_short RAP; /* Register Address Port */
volatile u_short Reset; /* Reset Chip on Read Access */
volatile u_short IDP; /* ISACSR Data Port */
};
/*
* Am79C960 Control and Status Registers
*
* These values are already swap()ed!!
*
* Only registers marked with a `-' are intended for network software
* access
*/
#define CSR0 0x0000 /* - PCnet-ISA Controller Status */
#define CSR1 0x0100 /* - IADR[15:0] */
#define CSR2 0x0200 /* - IADR[23:16] */
#define CSR3 0x0300 /* - Interrupt Masks and Deferral Control */
#define CSR4 0x0400 /* - Test and Features Control */
#define CSR6 0x0600 /* RCV/XMT Descriptor Table Length */
#define CSR8 0x0800 /* - Logical Address Filter, LADRF[15:0] */
#define CSR9 0x0900 /* - Logical Address Filter, LADRF[31:16] */
#define CSR10 0x0a00 /* - Logical Address Filter, LADRF[47:32] */
#define CSR11 0x0b00 /* - Logical Address Filter, LADRF[63:48] */
#define CSR12 0x0c00 /* - Physical Address Register, PADR[15:0] */
#define CSR13 0x0d00 /* - Physical Address Register, PADR[31:16] */
#define CSR14 0x0e00 /* - Physical Address Register, PADR[47:32] */
#define CSR15 0x0f00 /* - Mode Register */
#define CSR16 0x1000 /* Initialization Block Address Lower */
#define CSR17 0x1100 /* Initialization Block Address Upper */
#define CSR18 0x1200 /* Current Receive Buffer Address */
#define CSR19 0x1300 /* Current Receive Buffer Address */
#define CSR20 0x1400 /* Current Transmit Buffer Address */
#define CSR21 0x1500 /* Current Transmit Buffer Address */
#define CSR22 0x1600 /* Next Receive Buffer Address */
#define CSR23 0x1700 /* Next Receive Buffer Address */
#define CSR24 0x1800 /* - Base Address of Receive Ring */
#define CSR25 0x1900 /* - Base Address of Receive Ring */
#define CSR26 0x1a00 /* Next Receive Descriptor Address */
#define CSR27 0x1b00 /* Next Receive Descriptor Address */
#define CSR28 0x1c00 /* Current Receive Descriptor Address */
#define CSR29 0x1d00 /* Current Receive Descriptor Address */
#define CSR30 0x1e00 /* - Base Address of Transmit Ring */
#define CSR31 0x1f00 /* - Base Address of transmit Ring */
#define CSR32 0x2000 /* Next Transmit Descriptor Address */
#define CSR33 0x2100 /* Next Transmit Descriptor Address */
#define CSR34 0x2200 /* Current Transmit Descriptor Address */
#define CSR35 0x2300 /* Current Transmit Descriptor Address */
#define CSR36 0x2400 /* Next Next Receive Descriptor Address */
#define CSR37 0x2500 /* Next Next Receive Descriptor Address */
#define CSR38 0x2600 /* Next Next Transmit Descriptor Address */
#define CSR39 0x2700 /* Next Next Transmit Descriptor Address */
#define CSR40 0x2800 /* Current Receive Status and Byte Count */
#define CSR41 0x2900 /* Current Receive Status and Byte Count */
#define CSR42 0x2a00 /* Current Transmit Status and Byte Count */
#define CSR43 0x2b00 /* Current Transmit Status and Byte Count */
#define CSR44 0x2c00 /* Next Receive Status and Byte Count */
#define CSR45 0x2d00 /* Next Receive Status and Byte Count */
#define CSR46 0x2e00 /* Poll Time Counter */
#define CSR47 0x2f00 /* Polling Interval */
#define CSR48 0x3000 /* Temporary Storage */
#define CSR49 0x3100 /* Temporary Storage */
#define CSR50 0x3200 /* Temporary Storage */
#define CSR51 0x3300 /* Temporary Storage */
#define CSR52 0x3400 /* Temporary Storage */
#define CSR53 0x3500 /* Temporary Storage */
#define CSR54 0x3600 /* Temporary Storage */
#define CSR55 0x3700 /* Temporary Storage */
#define CSR56 0x3800 /* Temporary Storage */
#define CSR57 0x3900 /* Temporary Storage */
#define CSR58 0x3a00 /* Temporary Storage */
#define CSR59 0x3b00 /* Temporary Storage */
#define CSR60 0x3c00 /* Previous Transmit Descriptor Address */
#define CSR61 0x3d00 /* Previous Transmit Descriptor Address */
#define CSR62 0x3e00 /* Previous Transmit Status and Byte Count */
#define CSR63 0x3f00 /* Previous Transmit Status and Byte Count */
#define CSR64 0x4000 /* Next Transmit Buffer Address */
#define CSR65 0x4100 /* Next Transmit Buffer Address */
#define CSR66 0x4200 /* Next Transmit Status and Byte Count */
#define CSR67 0x4300 /* Next Transmit Status and Byte Count */
#define CSR68 0x4400 /* Transmit Status Temporary Storage */
#define CSR69 0x4500 /* Transmit Status Temporary Storage */
#define CSR70 0x4600 /* Temporary Storage */
#define CSR71 0x4700 /* Temporary Storage */
#define CSR72 0x4800 /* Receive Ring Counter */
#define CSR74 0x4a00 /* Transmit Ring Counter */
#define CSR76 0x4c00 /* - Receive Ring Length */
#define CSR78 0x4e00 /* - Transmit Ring Length */
#define CSR80 0x5000 /* - Burst and FIFO Threshold Control */
#define CSR82 0x5200 /* - Bus Activity Timer */
#define CSR84 0x5400 /* DMA Address */
#define CSR85 0x5500 /* DMA Address */
#define CSR86 0x5600 /* Buffer Byte Counter */
#define CSR88 0x5800 /* - Chip ID */
#define CSR89 0x5900 /* - Chip ID */
#define CSR92 0x5c00 /* Ring Length Conversion */
#define CSR94 0x5e00 /* Transmit Time Domain Reflectometry Count */
#define CSR96 0x6000 /* Bus Interface Scratch Register 0 */
#define CSR97 0x6100 /* Bus Interface Scratch Register 0 */
#define CSR98 0x6200 /* Bus Interface Scratch Register 1 */
#define CSR99 0x6300 /* Bus Interface Scratch Register 1 */
#define CSR104 0x6800 /* SWAP */
#define CSR105 0x6900 /* SWAP */
#define CSR108 0x6c00 /* Buffer Management Scratch */
#define CSR109 0x6d00 /* Buffer Management Scratch */
#define CSR112 0x7000 /* - Missed Frame Count */
#define CSR114 0x7200 /* - Receive Collision Count */
#define CSR124 0x7c00 /* - Buffer Management Unit Test */
/*
* Am79C960 ISA Control and Status Registers
*
* These values are already swap()ed!!
*/
#define ISACSR0 0x0000 /* Master Mode Read Active */
#define ISACSR1 0x0100 /* Master Mode Write Active */
#define ISACSR2 0x0200 /* Miscellaneous Configuration */
#define ISACSR4 0x0400 /* LED0 Status (Link Integrity) */
#define ISACSR5 0x0500 /* LED1 Status */
#define ISACSR6 0x0600 /* LED2 Status */
#define ISACSR7 0x0700 /* LED3 Status */
/*
* Bit definitions for CSR0 (PCnet-ISA Controller Status)
*
* These values are already swap()ed!!
*/
#define ERR 0x0080 /* Error */
#define BABL 0x0040 /* Babble: Transmitted too many bits */
#define CERR 0x0020 /* No Heartbeat (10BASE-T) */
#define MISS 0x0010 /* Missed Frame */
#define MERR 0x0008 /* Memory Error */
#define RINT 0x0004 /* Receive Interrupt */
#define TINT 0x0002 /* Transmit Interrupt */
#define IDON 0x0001 /* Initialization Done */
#define INTR 0x8000 /* Interrupt Flag */
#define INEA 0x4000 /* Interrupt Enable */
#define RXON 0x2000 /* Receive On */
#define TXON 0x1000 /* Transmit On */
#define TDMD 0x0800 /* Transmit Demand */
#define STOP 0x0400 /* Stop */
#define STRT 0x0200 /* Start */
#define INIT 0x0100 /* Initialize */
/*
* Bit definitions for CSR3 (Interrupt Masks and Deferral Control)
*
* These values are already swap()ed!!
*/
#define BABLM 0x0040 /* Babble Mask */
#define MISSM 0x0010 /* Missed Frame Mask */
#define MERRM 0x0008 /* Memory Error Mask */
#define RINTM 0x0004 /* Receive Interrupt Mask */
#define TINTM 0x0002 /* Transmit Interrupt Mask */
#define IDONM 0x0001 /* Initialization Done Mask */
#define DXMT2PD 0x1000 /* Disable Transmit Two Part Deferral */
#define EMBA 0x0800 /* Enable Modified Back-off Algorithm */
/*
* Bit definitions for CSR4 (Test and Features Control)
*
* These values are already swap()ed!!
*/
#define ENTST 0x0080 /* Enable Test Mode */
#define DMAPLUS 0x0040 /* Disable Burst Transaction Counter */
#define TIMER 0x0020 /* Timer Enable Register */
#define DPOLL 0x0010 /* Disable Transmit Polling */
#define APAD_XMT 0x0008 /* Auto Pad Transmit */
#define ASTRP_RCV 0x0004 /* Auto Pad Stripping */
#define MFCO 0x0002 /* Missed Frame Counter Overflow Interrupt */
#define MFCOM 0x0001 /* Missed Frame Counter Overflow Mask */
#define RCVCCO 0x2000 /* Receive Collision Counter Overflow Interrupt */
#define RCVCCOM 0x1000 /* Receive Collision Counter Overflow Mask */
#define TXSTRT 0x0800 /* Transmit Start Status */
#define TXSTRTM 0x0400 /* Transmit Start Mask */
#define JAB 0x0200 /* Jabber Error */
#define JABM 0x0100 /* Jabber Error Mask */
/*
* Bit definitions for CSR15 (Mode Register)
*
* These values are already swap()ed!!
*/
#define PROM 0x0080 /* Promiscuous Mode */
#define DRCVBC 0x0040 /* Disable Receive Broadcast */
#define DRCVPA 0x0020 /* Disable Receive Physical Address */
#define DLNKTST 0x0010 /* Disable Link Status */
#define DAPC 0x0008 /* Disable Automatic Polarity Correction */
#define MENDECL 0x0004 /* MENDEC Loopback Mode */
#define LRTTSEL 0x0002 /* Low Receive Threshold/Transmit Mode Select */
#define PORTSEL1 0x0001 /* Port Select Bits */
#define PORTSEL2 0x8000 /* Port Select Bits */
#define INTL 0x4000 /* Internal Loopback */
#define DRTY 0x2000 /* Disable Retry */
#define FCOLL 0x1000 /* Force Collision */
#define DXMTFCS 0x0800 /* Disable Transmit CRC */
#define LOOP 0x0400 /* Loopback Enable */
#define DTX 0x0200 /* Disable Transmitter */
#define DRX 0x0100 /* Disable Receiver */
/*
* Bit definitions for ISACSR2 (Miscellaneous Configuration)
*
* These values are already swap()ed!!
*/
#define ASEL 0x0200 /* Media Interface Port Auto Select */
/*
* Bit definitions for ISACSR5-7 (LED1-3 Status)
*
* These values are already swap()ed!!
*/
#define LEDOUT 0x0080 /* Current LED Status */
#define PSE 0x8000 /* Pulse Stretcher Enable */
#define XMTE 0x1000 /* Enable Transmit Status Signal */
#define RVPOLE 0x0800 /* Enable Receive Polarity Signal */
#define RCVE 0x0400 /* Enable Receive Status Signal */
#define JABE 0x0200 /* Enable Jabber Signal */
#define COLE 0x0100 /* Enable Collision Signal */
/*
* Receive Descriptor Ring Entry
*/
struct RDRE {
volatile u_short RMD0; /* LADR[15:0] */
volatile u_short RMD1; /* HADR[23:16] | Receive Flags */
volatile u_short RMD2; /* Buffer Byte Count (two's complement) */
volatile u_short RMD3; /* Message Byte Count */
};
/*
* Transmit Descriptor Ring Entry
*/
struct TDRE {
volatile u_short TMD0; /* LADR[15:0] */
volatile u_short TMD1; /* HADR[23:16] | Transmit Flags */
volatile u_short TMD2; /* Buffer Byte Count (two's complement) */
volatile u_short TMD3; /* Error Flags */
};
/*
* Receive Flags
*/
#define RF_OWN 0x0080 /* PCnet-ISA controller owns the descriptor */
#define RF_ERR 0x0040 /* Error */
#define RF_FRAM 0x0020 /* Framing Error */
#define RF_OFLO 0x0010 /* Overflow Error */
#define RF_CRC 0x0008 /* CRC Error */
#define RF_BUFF 0x0004 /* Buffer Error */
#define RF_STP 0x0002 /* Start of Packet */
#define RF_ENP 0x0001 /* End of Packet */
/*
* Transmit Flags
*/
#define TF_OWN 0x0080 /* PCnet-ISA controller owns the descriptor */
#define TF_ERR 0x0040 /* Error */
#define TF_ADD_FCS 0x0020 /* Controls FCS Generation */
#define TF_MORE 0x0010 /* More than one retry needed */
#define TF_ONE 0x0008 /* One retry needed */
#define TF_DEF 0x0004 /* Deferred */
#define TF_STP 0x0002 /* Start of Packet */
#define TF_ENP 0x0001 /* End of Packet */
/*
* Error Flags
*/
#define EF_BUFF 0x0080 /* Buffer Error */
#define EF_UFLO 0x0040 /* Underflow Error */
#define EF_LCOL 0x0010 /* Late Collision */
#define EF_LCAR 0x0008 /* Loss of Carrier */
#define EF_RTRY 0x0004 /* Retry Error */
#define EF_TDR 0xff03 /* Time Domain Reflectometry */
/*
* MC68230 Parallel Interface/Timer
*/
struct MC68230 {
volatile u_char PGCR; /* Port General Control Register */
u_char Pad1[1];
volatile u_char PSRR; /* Port Service Request Register */
u_char Pad2[1];
volatile u_char PADDR; /* Port A Data Direction Register */
u_char Pad3[1];
volatile u_char PBDDR; /* Port B Data Direction Register */
u_char Pad4[1];
volatile u_char PCDDR; /* Port C Data Direction Register */
u_char Pad5[1];
volatile u_char PIVR; /* Port Interrupt Vector Register */
u_char Pad6[1];
volatile u_char PACR; /* Port A Control Register */
u_char Pad7[1];
volatile u_char PBCR; /* Port B Control Register */
u_char Pad8[1];
volatile u_char PADR; /* Port A Data Register */
u_char Pad9[1];
volatile u_char PBDR; /* Port B Data Register */
u_char Pad10[1];
volatile u_char PAAR; /* Port A Alternate Register */
u_char Pad11[1];
volatile u_char PBAR; /* Port B Alternate Register */
u_char Pad12[1];
volatile u_char PCDR; /* Port C Data Register */
u_char Pad13[1];
volatile u_char PSR; /* Port Status Register */
u_char Pad14[5];
volatile u_char TCR; /* Timer Control Register */
u_char Pad15[1];
volatile u_char TIVR; /* Timer Interrupt Vector Register */
u_char Pad16[3];
volatile u_char CPRH; /* Counter Preload Register (High) */
u_char Pad17[1];
volatile u_char CPRM; /* Counter Preload Register (Mid) */
u_char Pad18[1];
volatile u_char CPRL; /* Counter Preload Register (Low) */
u_char Pad19[3];
volatile u_char CNTRH; /* Count Register (High) */
u_char Pad20[1];
volatile u_char CNTRM; /* Count Register (Mid) */
u_char Pad21[1];
volatile u_char CNTRL; /* Count Register (Low) */
u_char Pad22[1];
volatile u_char TSR; /* Timer Status Register */
u_char Pad23[11];
};
/*
* Ariadne Expansion Board Structure
*/
#define ARIADNE_LANCE 0x360
#define ARIADNE_PIT 0x1000
#define ARIADNE_BOOTPROM 0x4000 /* I guess it's here :-) */
#define ARIADNE_BOOTPROM_SIZE 0x4000
#define ARIADNE_RAM 0x8000 /* Always access WORDs!! */
#define ARIADNE_RAM_SIZE 0x8000
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,115 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
*
* Alchemy Au1x00 ethernet driver include file
*
* Author: Pete Popov <ppopov@mvista.com>
*
* Copyright 2001 MontaVista Software Inc.
*/
#define MAC_IOSIZE 0x10000
#define NUM_RX_DMA 4 /* Au1x00 has 4 rx hardware descriptors */
#define NUM_TX_DMA 4 /* Au1x00 has 4 tx hardware descriptors */
#define NUM_RX_BUFFS 4
#define NUM_TX_BUFFS 4
#define MAX_BUF_SIZE 2048
#define ETH_TX_TIMEOUT (HZ/4)
#define MAC_MIN_PKT_SIZE 64
#define MULTICAST_FILTER_LIMIT 64
/*
* Data Buffer Descriptor. Data buffers must be aligned on 32 byte
* boundary for both, receive and transmit.
*/
struct db_dest {
struct db_dest *pnext;
u32 *vaddr;
dma_addr_t dma_addr;
};
/*
* The transmit and receive descriptors are memory
* mapped registers.
*/
struct tx_dma {
u32 status;
u32 buff_stat;
u32 len;
u32 pad;
};
struct rx_dma {
u32 status;
u32 buff_stat;
u32 pad[2];
};
/*
* MAC control registers, memory mapped.
*/
struct mac_reg {
u32 control;
u32 mac_addr_high;
u32 mac_addr_low;
u32 multi_hash_high;
u32 multi_hash_low;
u32 mii_control;
u32 mii_data;
u32 flow_control;
u32 vlan1_tag;
u32 vlan2_tag;
};
struct au1000_private {
struct db_dest *pDBfree;
struct db_dest db[NUM_RX_BUFFS+NUM_TX_BUFFS];
struct rx_dma *rx_dma_ring[NUM_RX_DMA];
struct tx_dma *tx_dma_ring[NUM_TX_DMA];
struct db_dest *rx_db_inuse[NUM_RX_DMA];
struct db_dest *tx_db_inuse[NUM_TX_DMA];
u32 rx_head;
u32 tx_head;
u32 tx_tail;
u32 tx_full;
int mac_id;
int mac_enabled; /* whether MAC is currently enabled and running
* (req. for mdio)
*/
int old_link; /* used by au1000_adjust_link */
int old_speed;
int old_duplex;
struct mii_bus *mii_bus;
/* PHY configuration */
int phy_static_config;
int phy_search_highest_addr;
int phy1_search_mac0;
int phy_addr;
int phy_busid;
int phy_irq;
/* These variables are just for quick access
* to certain regs addresses.
*/
struct mac_reg *mac; /* mac registers */
u32 *enable; /* address of MAC Enable Register */
void __iomem *macdma; /* base of MAC DMA port */
void *vaddr; /* virtual address of rx/tx buffers */
dma_addr_t dma_addr; /* dma address of rx/tx buffers */
spinlock_t lock; /* Serialise access to device */
u32 msg_enable;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0-only
/* hplance.c : the Linux/hp300/lance ethernet driver
*
* Copyright (C) 05/1998 Peter Maydell <pmaydell@chiark.greenend.org.uk>
* Based on the Sun Lance driver and the NetBSD HP Lance driver
* Uses the generic 7990.c LANCE code.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/pgtable.h>
/* Used for the temporal inet entries and routing */
#include <linux/socket.h>
#include <linux/route.h>
#include <linux/dio.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <asm/io.h>
#include "hplance.h"
/* We have 16392 bytes of RAM for the init block and buffers. This places
* an upper limit on the number of buffers we can use. NetBSD uses 8 Rx
* buffers and 2 Tx buffers, it takes (8 + 2) * 1544 bytes.
*/
#define LANCE_LOG_TX_BUFFERS 1
#define LANCE_LOG_RX_BUFFERS 3
#include "7990.h" /* use generic LANCE code */
/* Our private data structure */
struct hplance_private {
struct lance_private lance;
};
/* function prototypes... This is easy because all the grot is in the
* generic LANCE support. All we have to support is probing for boards,
* plus board-specific init, open and close actions.
* Oh, and we need to tell the generic code how to read and write LANCE registers...
*/
static int hplance_init_one(struct dio_dev *d, const struct dio_device_id *ent);
static void hplance_init(struct net_device *dev, struct dio_dev *d);
static void hplance_remove_one(struct dio_dev *d);
static void hplance_writerap(void *priv, unsigned short value);
static void hplance_writerdp(void *priv, unsigned short value);
static unsigned short hplance_readrdp(void *priv);
static int hplance_open(struct net_device *dev);
static int hplance_close(struct net_device *dev);
static struct dio_device_id hplance_dio_tbl[] = {
{ DIO_ID_LAN },
{ 0 }
};
static struct dio_driver hplance_driver = {
.name = "hplance",
.id_table = hplance_dio_tbl,
.probe = hplance_init_one,
.remove = hplance_remove_one,
};
static const struct net_device_ops hplance_netdev_ops = {
.ndo_open = hplance_open,
.ndo_stop = hplance_close,
.ndo_start_xmit = lance_start_xmit,
.ndo_set_rx_mode = lance_set_multicast,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = lance_poll,
#endif
};
/* Find all the HP Lance boards and initialise them... */
static int hplance_init_one(struct dio_dev *d, const struct dio_device_id *ent)
{
struct net_device *dev;
int err = -ENOMEM;
dev = alloc_etherdev(sizeof(struct hplance_private));
if (!dev)
goto out;
err = -EBUSY;
if (!request_mem_region(dio_resource_start(d),
dio_resource_len(d), d->name))
goto out_free_netdev;
hplance_init(dev, d);
err = register_netdev(dev);
if (err)
goto out_release_mem_region;
dio_set_drvdata(d, dev);
printk(KERN_INFO "%s: %s; select code %d, addr %pM, irq %d\n",
dev->name, d->name, d->scode, dev->dev_addr, d->ipl);
return 0;
out_release_mem_region:
release_mem_region(dio_resource_start(d), dio_resource_len(d));
out_free_netdev:
free_netdev(dev);
out:
return err;
}
static void hplance_remove_one(struct dio_dev *d)
{
struct net_device *dev = dio_get_drvdata(d);
unregister_netdev(dev);
release_mem_region(dio_resource_start(d), dio_resource_len(d));
free_netdev(dev);
}
/* Initialise a single lance board at the given DIO device */
static void hplance_init(struct net_device *dev, struct dio_dev *d)
{
unsigned long va = (d->resource.start + DIO_VIRADDRBASE);
struct hplance_private *lp;
u8 addr[ETH_ALEN];
int i;
/* reset the board */
out_8(va + DIO_IDOFF, 0xff);
udelay(100); /* ariba! ariba! udelay! udelay! */
/* Fill the dev fields */
dev->base_addr = va;
dev->netdev_ops = &hplance_netdev_ops;
dev->dma = 0;
for (i = 0; i < 6; i++) {
/* The NVRAM holds our ethernet address, one nibble per byte,
* at bytes NVRAMOFF+1,3,5,7,9...
*/
addr[i] = ((in_8(va + HPLANCE_NVRAMOFF + i*4 + 1) & 0xF) << 4)
| (in_8(va + HPLANCE_NVRAMOFF + i*4 + 3) & 0xF);
}
eth_hw_addr_set(dev, addr);
lp = netdev_priv(dev);
lp->lance.name = d->name;
lp->lance.base = va;
lp->lance.init_block = (struct lance_init_block *)(va + HPLANCE_MEMOFF); /* CPU addr */
lp->lance.lance_init_block = NULL; /* LANCE addr of same RAM */
lp->lance.busmaster_regval = LE_C3_BSWP; /* we're bigendian */
lp->lance.irq = d->ipl;
lp->lance.writerap = hplance_writerap;
lp->lance.writerdp = hplance_writerdp;
lp->lance.readrdp = hplance_readrdp;
lp->lance.lance_log_rx_bufs = LANCE_LOG_RX_BUFFERS;
lp->lance.lance_log_tx_bufs = LANCE_LOG_TX_BUFFERS;
lp->lance.rx_ring_mod_mask = RX_RING_MOD_MASK;
lp->lance.tx_ring_mod_mask = TX_RING_MOD_MASK;
}
/* This is disgusting. We have to check the DIO status register for ack every
* time we read or write the LANCE registers.
*/
static void hplance_writerap(void *priv, unsigned short value)
{
struct lance_private *lp = (struct lance_private *)priv;
do {
out_be16(lp->base + HPLANCE_REGOFF + LANCE_RAP, value);
} while ((in_8(lp->base + HPLANCE_STATUS) & LE_ACK) == 0);
}
static void hplance_writerdp(void *priv, unsigned short value)
{
struct lance_private *lp = (struct lance_private *)priv;
do {
out_be16(lp->base + HPLANCE_REGOFF + LANCE_RDP, value);
} while ((in_8(lp->base + HPLANCE_STATUS) & LE_ACK) == 0);
}
static unsigned short hplance_readrdp(void *priv)
{
struct lance_private *lp = (struct lance_private *)priv;
__u16 value;
do {
value = in_be16(lp->base + HPLANCE_REGOFF + LANCE_RDP);
} while ((in_8(lp->base + HPLANCE_STATUS) & LE_ACK) == 0);
return value;
}
static int hplance_open(struct net_device *dev)
{
int status;
struct lance_private *lp = netdev_priv(dev);
status = lance_open(dev); /* call generic lance open code */
if (status)
return status;
/* enable interrupts at board level. */
out_8(lp->base + HPLANCE_STATUS, LE_IE);
return 0;
}
static int hplance_close(struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
out_8(lp->base + HPLANCE_STATUS, 0); /* disable interrupts at boardlevel */
lance_close(dev);
return 0;
}
static int __init hplance_init_module(void)
{
return dio_register_driver(&hplance_driver);
}
static void __exit hplance_cleanup_module(void)
{
dio_unregister_driver(&hplance_driver);
}
module_init(hplance_init_module);
module_exit(hplance_cleanup_module);
MODULE_DESCRIPTION("HP300 on-board LANCE Ethernet driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Random defines and structures for the HP Lance driver.
* Copyright (C) 05/1998 Peter Maydell <pmaydell@chiark.greenend.org.uk>
* Based on the Sun Lance driver and the NetBSD HP Lance driver
*/
/* Registers */
#define HPLANCE_ID 0x01 /* DIO register: ID byte */
#define HPLANCE_STATUS 0x03 /* DIO register: interrupt enable/status */
/* Control and status bits for the status register */
#define LE_IE 0x80 /* interrupt enable */
#define LE_IR 0x40 /* interrupt requested */
#define LE_LOCK 0x08 /* lock status register */
#define LE_ACK 0x04 /* ack of lock */
#define LE_JAB 0x02 /* loss of tx clock (???) */
/* We can also extract the IPL from the status register with the standard
* DIO_IPL(hplance) macro, or using dio_scodetoipl()
*/
/* These are the offsets for the DIO regs (hplance_reg), lance_ioreg,
* memory and NVRAM:
*/
#define HPLANCE_IDOFF 0 /* board baseaddr */
#define HPLANCE_REGOFF 0x4000 /* lance registers */
#define HPLANCE_MEMOFF 0x8000 /* struct lance_init_block */
#define HPLANCE_NVRAMOFF 0xC008 /* etheraddress as one *nibble* per byte */
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0-only
/* mvme147.c : the Linux/mvme147/lance ethernet driver
*
* Copyright (C) 05/1998 Peter Maydell <pmaydell@chiark.greenend.org.uk>
* Based on the Sun Lance driver and the NetBSD HP Lance driver
* Uses the generic 7990.c LANCE code.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/pgtable.h>
/* Used for the temporal inet entries and routing */
#include <linux/socket.h>
#include <linux/route.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <asm/io.h>
#include <asm/mvme147hw.h>
/* We have 32K of RAM for the init block and buffers. This places
* an upper limit on the number of buffers we can use. NetBSD uses 8 Rx
* buffers and 2 Tx buffers, it takes (8 + 2) * 1544 bytes.
*/
#define LANCE_LOG_TX_BUFFERS 1
#define LANCE_LOG_RX_BUFFERS 3
#include "7990.h" /* use generic LANCE code */
/* Our private data structure */
struct m147lance_private {
struct lance_private lance;
unsigned long ram;
};
/* function prototypes... This is easy because all the grot is in the
* generic LANCE support. All we have to support is probing for boards,
* plus board-specific init, open and close actions.
* Oh, and we need to tell the generic code how to read and write LANCE registers...
*/
static int m147lance_open(struct net_device *dev);
static int m147lance_close(struct net_device *dev);
static void m147lance_writerap(struct lance_private *lp, unsigned short value);
static void m147lance_writerdp(struct lance_private *lp, unsigned short value);
static unsigned short m147lance_readrdp(struct lance_private *lp);
typedef void (*writerap_t)(void *, unsigned short);
typedef void (*writerdp_t)(void *, unsigned short);
typedef unsigned short (*readrdp_t)(void *);
static const struct net_device_ops lance_netdev_ops = {
.ndo_open = m147lance_open,
.ndo_stop = m147lance_close,
.ndo_start_xmit = lance_start_xmit,
.ndo_set_rx_mode = lance_set_multicast,
.ndo_tx_timeout = lance_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
/* Initialise the one and only on-board 7990 */
static struct net_device * __init mvme147lance_probe(void)
{
struct net_device *dev;
static int called;
static const char name[] = "MVME147 LANCE";
struct m147lance_private *lp;
u8 macaddr[ETH_ALEN];
u_long *addr;
u_long address;
int err;
if (!MACH_IS_MVME147 || called)
return ERR_PTR(-ENODEV);
called++;
dev = alloc_etherdev(sizeof(struct m147lance_private));
if (!dev)
return ERR_PTR(-ENOMEM);
/* Fill the dev fields */
dev->base_addr = (unsigned long)MVME147_LANCE_BASE;
dev->netdev_ops = &lance_netdev_ops;
dev->dma = 0;
addr = (u_long *)ETHERNET_ADDRESS;
address = *addr;
macaddr[0] = 0x08;
macaddr[1] = 0x00;
macaddr[2] = 0x3e;
address = address >> 8;
macaddr[5] = address&0xff;
address = address >> 8;
macaddr[4] = address&0xff;
address = address >> 8;
macaddr[3] = address&0xff;
eth_hw_addr_set(dev, macaddr);
lp = netdev_priv(dev);
lp->ram = __get_dma_pages(GFP_ATOMIC, 3); /* 32K */
if (!lp->ram) {
printk("%s: No memory for LANCE buffers\n", dev->name);
free_netdev(dev);
return ERR_PTR(-ENOMEM);
}
lp->lance.name = name;
lp->lance.base = dev->base_addr;
lp->lance.init_block = (struct lance_init_block *)(lp->ram); /* CPU addr */
lp->lance.lance_init_block = (struct lance_init_block *)(lp->ram); /* LANCE addr of same RAM */
lp->lance.busmaster_regval = LE_C3_BSWP; /* we're bigendian */
lp->lance.irq = MVME147_LANCE_IRQ;
lp->lance.writerap = (writerap_t)m147lance_writerap;
lp->lance.writerdp = (writerdp_t)m147lance_writerdp;
lp->lance.readrdp = (readrdp_t)m147lance_readrdp;
lp->lance.lance_log_rx_bufs = LANCE_LOG_RX_BUFFERS;
lp->lance.lance_log_tx_bufs = LANCE_LOG_TX_BUFFERS;
lp->lance.rx_ring_mod_mask = RX_RING_MOD_MASK;
lp->lance.tx_ring_mod_mask = TX_RING_MOD_MASK;
err = register_netdev(dev);
if (err) {
free_pages(lp->ram, 3);
free_netdev(dev);
return ERR_PTR(err);
}
netdev_info(dev, "MVME147 at 0x%08lx, irq %d, Hardware Address %pM\n",
dev->base_addr, MVME147_LANCE_IRQ, dev->dev_addr);
return dev;
}
static void m147lance_writerap(struct lance_private *lp, unsigned short value)
{
out_be16(lp->base + LANCE_RAP, value);
}
static void m147lance_writerdp(struct lance_private *lp, unsigned short value)
{
out_be16(lp->base + LANCE_RDP, value);
}
static unsigned short m147lance_readrdp(struct lance_private *lp)
{
return in_be16(lp->base + LANCE_RDP);
}
static int m147lance_open(struct net_device *dev)
{
int status;
status = lance_open(dev); /* call generic lance open code */
if (status)
return status;
/* enable interrupts at board level. */
m147_pcc->lan_cntrl = 0; /* clear the interrupts (if any) */
m147_pcc->lan_cntrl = 0x08 | 0x04; /* Enable irq 4 */
return 0;
}
static int m147lance_close(struct net_device *dev)
{
/* disable interrupts at boardlevel */
m147_pcc->lan_cntrl = 0x0; /* disable interrupts */
lance_close(dev);
return 0;
}
MODULE_DESCRIPTION("MVME147 LANCE Ethernet driver");
MODULE_LICENSE("GPL");
static struct net_device *dev_mvme147_lance;
static int __init m147lance_init(void)
{
dev_mvme147_lance = mvme147lance_probe();
return PTR_ERR_OR_ZERO(dev_mvme147_lance);
}
module_init(m147lance_init);
static void __exit m147lance_exit(void)
{
struct m147lance_private *lp = netdev_priv(dev_mvme147_lance);
unregister_netdev(dev_mvme147_lance);
free_pages(lp->ram, 3);
free_netdev(dev_mvme147_lance);
}
module_exit(m147lance_exit);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2023 Advanced Micro Devices, Inc.
obj-$(CONFIG_PDS_CORE) := pds_core.o
pds_core-y := main.o \
devlink.o \
auxbus.o \
dev.o \
adminq.o \
core.o \
debugfs.o \
fw.o
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/dynamic_debug.h>
#include "core.h"
static int pdsc_process_notifyq(struct pdsc_qcq *qcq)
{
union pds_core_notifyq_comp *comp;
struct pdsc *pdsc = qcq->pdsc;
struct pdsc_cq *cq = &qcq->cq;
struct pdsc_cq_info *cq_info;
int nq_work = 0;
u64 eid;
cq_info = &cq->info[cq->tail_idx];
comp = cq_info->comp;
eid = le64_to_cpu(comp->event.eid);
while (eid > pdsc->last_eid) {
u16 ecode;
/* Order the payload read after the event id, the field the
* driver uses to detect a new completion.
*/
dma_rmb();
ecode = le16_to_cpu(comp->event.ecode);
switch (ecode) {
case PDS_EVENT_LINK_CHANGE:
dev_info(pdsc->dev, "NotifyQ LINK_CHANGE ecode %d eid %lld\n",
ecode, eid);
pdsc_notify(PDS_EVENT_LINK_CHANGE, comp);
break;
case PDS_EVENT_RESET:
dev_info(pdsc->dev, "NotifyQ RESET ecode %d eid %lld\n",
ecode, eid);
pdsc_notify(PDS_EVENT_RESET, comp);
break;
case PDS_EVENT_XCVR:
dev_info(pdsc->dev, "NotifyQ XCVR ecode %d eid %lld\n",
ecode, eid);
break;
default:
dev_info(pdsc->dev, "NotifyQ ecode %d eid %lld\n",
ecode, eid);
break;
}
pdsc->last_eid = eid;
cq->tail_idx = (cq->tail_idx + 1) & (cq->num_descs - 1);
cq_info = &cq->info[cq->tail_idx];
comp = cq_info->comp;
eid = le64_to_cpu(comp->event.eid);
nq_work++;
}
qcq->accum_work += nq_work;
return nq_work;
}
static bool pdsc_adminq_inc_if_up(struct pdsc *pdsc)
{
if (pdsc->state & BIT_ULL(PDSC_S_STOPPING_DRIVER) ||
pdsc->state & BIT_ULL(PDSC_S_FW_DEAD))
return false;
return refcount_inc_not_zero(&pdsc->adminq_refcnt);
}
void pdsc_process_adminq(struct pdsc_qcq *qcq)
{
union pds_core_adminq_comp *comp;
struct pdsc_queue *q = &qcq->q;
struct pdsc *pdsc = qcq->pdsc;
struct pdsc_cq *cq = &qcq->cq;
struct pdsc_q_info *q_info;
unsigned long irqflags;
int nq_work = 0;
int aq_work = 0;
/* Don't process AdminQ when it's not up */
if (!pdsc_adminq_inc_if_up(pdsc)) {
dev_err(pdsc->dev, "%s: called while adminq is unavailable\n",
__func__);
return;
}
/* Check for NotifyQ event */
nq_work = pdsc_process_notifyq(&pdsc->notifyqcq);
/* Check for empty queue, which can happen if the interrupt was
* for a NotifyQ event and there are no new AdminQ completions.
*/
if (q->tail_idx == q->head_idx)
goto credits;
/* Find the first completion to clean,
* run the callback in the related q_info,
* and continue while we still match done color
*/
spin_lock_irqsave(&pdsc->adminq_lock, irqflags);
comp = cq->info[cq->tail_idx].comp;
while (pdsc_color_match(comp->color, cq->done_color)) {
/* Order the payload reads after the color bit, the field the
* driver uses to detect a new completion.
*/
dma_rmb();
q_info = &q->info[q->tail_idx];
q->tail_idx = (q->tail_idx + 1) & (q->num_descs - 1);
if (!completion_done(&q_info->completion)) {
memcpy(q_info->dest, comp, sizeof(*comp));
complete(&q_info->completion);
}
if (cq->tail_idx == cq->num_descs - 1)
cq->done_color = !cq->done_color;
cq->tail_idx = (cq->tail_idx + 1) & (cq->num_descs - 1);
comp = cq->info[cq->tail_idx].comp;
aq_work++;
}
spin_unlock_irqrestore(&pdsc->adminq_lock, irqflags);
qcq->accum_work += aq_work;
credits:
/* Return the interrupt credits, one for each completion */
pds_core_intr_credits(&pdsc->intr_ctrl[qcq->intx],
nq_work + aq_work,
PDS_CORE_INTR_CRED_REARM);
refcount_dec(&pdsc->adminq_refcnt);
}
void pdsc_work_thread(struct work_struct *work)
{
struct pdsc_qcq *qcq = container_of(work, struct pdsc_qcq, work);
pdsc_process_adminq(qcq);
}
irqreturn_t pdsc_adminq_isr(int irq, void *data)
{
struct pdsc *pdsc = data;
struct pdsc_qcq *qcq;
/* Don't process AdminQ when it's not up */
if (!pdsc_adminq_inc_if_up(pdsc)) {
dev_err(pdsc->dev, "%s: called while adminq is unavailable\n",
__func__);
return IRQ_HANDLED;
}
qcq = &pdsc->adminqcq;
queue_work(pdsc->wq, &qcq->work);
refcount_dec(&pdsc->adminq_refcnt);
return IRQ_HANDLED;
}
static int __pdsc_adminq_post(struct pdsc *pdsc,
struct pdsc_qcq *qcq,
union pds_core_adminq_cmd *cmd,
union pds_core_adminq_comp *comp)
{
struct pdsc_queue *q = &qcq->q;
struct pdsc_q_info *q_info;
unsigned long irqflags;
unsigned int avail;
int index;
int ret;
spin_lock_irqsave(&pdsc->adminq_lock, irqflags);
/* Check for space in the queue */
avail = q->tail_idx;
if (q->head_idx >= avail)
avail += q->num_descs - q->head_idx - 1;
else
avail -= q->head_idx + 1;
if (!avail) {
ret = -ENOSPC;
goto err_out_unlock;
}
/* Check that the FW is running */
if (!pdsc_is_fw_running(pdsc)) {
if (pdsc->info_regs) {
u8 fw_status =
ioread8(&pdsc->info_regs->fw_status);
dev_info(pdsc->dev, "%s: post failed - fw not running %#02x:\n",
__func__, fw_status);
} else {
dev_info(pdsc->dev, "%s: post failed - BARs not setup\n",
__func__);
}
ret = -ENXIO;
goto err_out_unlock;
}
/* Post the request */
index = q->head_idx;
q_info = &q->info[index];
q_info->dest = comp;
memcpy(q_info->desc, cmd, sizeof(*cmd));
reinit_completion(&q_info->completion);
dev_dbg(pdsc->dev, "head_idx %d tail_idx %d\n",
q->head_idx, q->tail_idx);
dev_dbg(pdsc->dev, "post admin queue command:\n");
dynamic_hex_dump("cmd ", DUMP_PREFIX_OFFSET, 16, 1,
cmd, sizeof(*cmd), true);
q->head_idx = (q->head_idx + 1) & (q->num_descs - 1);
pds_core_dbell_ring(pdsc->kern_dbpage,
q->hw_type, q->dbval | q->head_idx);
ret = index;
err_out_unlock:
spin_unlock_irqrestore(&pdsc->adminq_lock, irqflags);
return ret;
}
int pdsc_adminq_post(struct pdsc *pdsc,
union pds_core_adminq_cmd *cmd,
union pds_core_adminq_comp *comp,
bool fast_poll)
{
unsigned long poll_interval = 200;
unsigned long poll_jiffies;
unsigned long time_limit;
unsigned long time_start;
unsigned long time_done;
unsigned long remaining;
struct completion *wc;
int err = 0;
int index;
if (!pdsc_adminq_inc_if_up(pdsc)) {
dev_dbg(pdsc->dev, "%s: preventing adminq cmd %u\n",
__func__, cmd->opcode);
return -ENXIO;
}
index = __pdsc_adminq_post(pdsc, &pdsc->adminqcq, cmd, comp);
if (index < 0) {
err = index;
goto err_out;
}
wc = &pdsc->adminqcq.q.info[index].completion;
time_start = jiffies;
time_limit = time_start + HZ * pdsc->devcmd_timeout;
do {
/* Timeslice the actual wait to catch IO errors etc early */
poll_jiffies = usecs_to_jiffies(poll_interval);
remaining = wait_for_completion_timeout(wc, poll_jiffies);
if (remaining)
break;
if (!pdsc_is_fw_running(pdsc)) {
if (pdsc->info_regs) {
u8 fw_status =
ioread8(&pdsc->info_regs->fw_status);
dev_dbg(pdsc->dev, "%s: post wait failed - fw not running %#02x:\n",
__func__, fw_status);
} else {
dev_dbg(pdsc->dev, "%s: post wait failed - BARs not setup\n",
__func__);
}
err = -ENXIO;
break;
}
/* When fast_poll is not requested, prevent aggressive polling
* on failures due to timeouts by doing exponential back off.
*/
if (!fast_poll && poll_interval < PDSC_ADMINQ_MAX_POLL_INTERVAL)
poll_interval <<= 1;
} while (time_before(jiffies, time_limit));
time_done = jiffies;
dev_dbg(pdsc->dev, "%s: elapsed %d msecs\n",
__func__, jiffies_to_msecs(time_done - time_start));
/* Check the results and clear an un-completed timeout */
if (time_after_eq(time_done, time_limit) && !completion_done(wc)) {
err = -ETIMEDOUT;
complete(wc);
}
dev_dbg(pdsc->dev, "read admin queue completion idx %d:\n", index);
dynamic_hex_dump("comp ", DUMP_PREFIX_OFFSET, 16, 1,
comp, sizeof(*comp), true);
if (remaining && comp->status)
err = pdsc_err_to_errno(comp->status);
err_out:
if (err) {
dev_dbg(pdsc->dev, "%s: opcode %d status %d err %pe\n",
__func__, cmd->opcode, comp->status, ERR_PTR(err));
if (err == -ENXIO || err == -ETIMEDOUT)
queue_work(pdsc->wq, &pdsc->health_work);
}
refcount_dec(&pdsc->adminq_refcnt);
return err;
}
EXPORT_SYMBOL_GPL(pdsc_adminq_post);
@@ -0,0 +1,267 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/pci.h>
#include "core.h"
#include <linux/pds/pds_auxbus.h>
/**
* pds_client_register - Link the client to the firmware
* @pf: ptr to the PF driver's private data struct
* @devname: name that includes service into, e.g. pds_core.vDPA
*
* Return: positive client ID (ci) on success, or
* negative for error
*/
int pds_client_register(struct pdsc *pf, char *devname)
{
union pds_core_adminq_comp comp = {};
union pds_core_adminq_cmd cmd = {};
int err;
u16 ci;
cmd.client_reg.opcode = PDS_AQ_CMD_CLIENT_REG;
strscpy(cmd.client_reg.devname, devname,
sizeof(cmd.client_reg.devname));
err = pdsc_adminq_post(pf, &cmd, &comp, false);
if (err) {
dev_info(pf->dev, "register dev_name %s with DSC failed, status %d: %pe\n",
devname, comp.status, ERR_PTR(err));
return err;
}
ci = le16_to_cpu(comp.client_reg.client_id);
if (!ci) {
dev_err(pf->dev, "%s: device returned null client_id\n",
__func__);
return -EIO;
}
dev_dbg(pf->dev, "%s: device returned client_id %d for %s\n",
__func__, ci, devname);
return ci;
}
EXPORT_SYMBOL_GPL(pds_client_register);
/**
* pds_client_unregister - Unlink the client from the firmware
* @pf: ptr to the PF driver's private data struct
* @client_id: id returned from pds_client_register()
*
* Return: 0 on success, or
* negative for error
*/
int pds_client_unregister(struct pdsc *pf, u16 client_id)
{
union pds_core_adminq_comp comp = {};
union pds_core_adminq_cmd cmd = {};
int err;
cmd.client_unreg.opcode = PDS_AQ_CMD_CLIENT_UNREG;
cmd.client_unreg.client_id = cpu_to_le16(client_id);
err = pdsc_adminq_post(pf, &cmd, &comp, false);
if (err)
dev_info(pf->dev, "unregister client_id %d failed, status %d: %pe\n",
client_id, comp.status, ERR_PTR(err));
return err;
}
EXPORT_SYMBOL_GPL(pds_client_unregister);
/**
* pds_client_adminq_cmd - Process an adminq request for the client
* @padev: ptr to the client device
* @req: ptr to buffer with request
* @req_len: length of actual struct used for request
* @resp: ptr to buffer where answer is to be copied
* @flags: optional flags from pds_core_adminq_flags
*
* Return: 0 on success, or
* negative for error
*
* Client sends pointers to request and response buffers
* Core copies request data into pds_core_client_request_cmd
* Core sets other fields as needed
* Core posts to AdminQ
* Core copies completion data into response buffer
*/
int pds_client_adminq_cmd(struct pds_auxiliary_dev *padev,
union pds_core_adminq_cmd *req,
size_t req_len,
union pds_core_adminq_comp *resp,
u64 flags)
{
union pds_core_adminq_cmd cmd = {};
struct pci_dev *pf_pdev;
struct pdsc *pf;
size_t cp_len;
int err;
pf_pdev = pci_physfn(padev->vf_pdev);
pf = pci_get_drvdata(pf_pdev);
dev_dbg(pf->dev, "%s: %s opcode %d\n",
__func__, dev_name(&padev->aux_dev.dev), req->opcode);
/* Wrap the client's request */
cmd.client_request.opcode = PDS_AQ_CMD_CLIENT_CMD;
cmd.client_request.client_id = cpu_to_le16(padev->client_id);
cp_len = min_t(size_t, req_len, sizeof(cmd.client_request.client_cmd));
memcpy(cmd.client_request.client_cmd, req, cp_len);
err = pdsc_adminq_post(pf, &cmd, resp,
!!(flags & PDS_AQ_FLAG_FASTPOLL));
if (err && err != -EAGAIN)
dev_info(pf->dev, "client admin cmd failed: %pe\n",
ERR_PTR(err));
return err;
}
EXPORT_SYMBOL_GPL(pds_client_adminq_cmd);
static void pdsc_auxbus_dev_release(struct device *dev)
{
struct pds_auxiliary_dev *padev =
container_of(dev, struct pds_auxiliary_dev, aux_dev.dev);
kfree(padev);
}
static struct pds_auxiliary_dev *pdsc_auxbus_dev_register(struct pdsc *cf,
struct pdsc *pf,
u16 client_id,
char *name)
{
struct auxiliary_device *aux_dev;
struct pds_auxiliary_dev *padev;
int err;
padev = kzalloc_obj(*padev);
if (!padev)
return ERR_PTR(-ENOMEM);
padev->vf_pdev = cf->pdev;
padev->client_id = client_id;
aux_dev = &padev->aux_dev;
aux_dev->name = name;
aux_dev->id = cf->uid;
aux_dev->dev.parent = cf->dev;
aux_dev->dev.release = pdsc_auxbus_dev_release;
err = auxiliary_device_init(aux_dev);
if (err < 0) {
dev_warn(cf->dev, "auxiliary_device_init of %s failed: %pe\n",
name, ERR_PTR(err));
kfree(padev);
return ERR_PTR(err);
}
err = auxiliary_device_add(aux_dev);
if (err) {
dev_warn(cf->dev, "auxiliary_device_add of %s failed: %pe\n",
name, ERR_PTR(err));
auxiliary_device_uninit(aux_dev);
return ERR_PTR(err);
}
return padev;
}
void pdsc_auxbus_dev_del(struct pdsc *cf, struct pdsc *pf,
struct pds_auxiliary_dev **pd_ptr)
{
struct pds_auxiliary_dev *padev;
mutex_lock(&pf->config_lock);
/* A concurrent del may have already torn this device down and
* cleared it.
*/
padev = *pd_ptr;
if (!padev)
goto out_unlock;
pds_client_unregister(pf, padev->client_id);
auxiliary_device_delete(&padev->aux_dev);
auxiliary_device_uninit(&padev->aux_dev);
*pd_ptr = NULL;
out_unlock:
mutex_unlock(&pf->config_lock);
}
int pdsc_auxbus_dev_add(struct pdsc *cf, struct pdsc *pf,
enum pds_core_vif_types vt,
struct pds_auxiliary_dev **pd_ptr)
{
struct pds_auxiliary_dev *padev;
char devname[PDS_DEVNAME_LEN];
unsigned long mask;
u16 vt_support;
int client_id;
int err = 0;
if (!cf)
return -ENODEV;
if (vt >= PDS_DEV_TYPE_MAX)
return -EINVAL;
mutex_lock(&pf->config_lock);
/* Nothing to do if the aux device is already present. This also
* guards against a second add overwriting *pd_ptr and leaking the
* first, symmetric with the check in pdsc_auxbus_dev_del().
*/
if (*pd_ptr)
goto out_unlock;
mask = BIT_ULL(PDSC_S_FW_DEAD) |
BIT_ULL(PDSC_S_STOPPING_DRIVER);
if (cf->state & mask) {
dev_err(pf->dev, "%s: can't add dev, VF client in bad state %#lx\n",
__func__, cf->state);
err = -ENXIO;
goto out_unlock;
}
/* Verify that the type is supported and enabled. It is not
* an error if the firmware doesn't support the feature, the
* driver just won't set up an auxiliary_device for it.
*/
vt_support = !!le16_to_cpu(pf->dev_ident.vif_types[vt]);
if (!(vt_support &&
pf->viftype_status[vt].supported &&
pf->viftype_status[vt].enabled))
goto out_unlock;
/* Need to register with FW and get the client_id before
* creating the aux device so that the aux client can run
* adminq commands as part its probe
*/
snprintf(devname, sizeof(devname), "%s.%s.%d",
PDS_CORE_DRV_NAME, pf->viftype_status[vt].name, cf->uid);
client_id = pds_client_register(pf, devname);
if (client_id < 0) {
err = client_id;
goto out_unlock;
}
padev = pdsc_auxbus_dev_register(cf, pf, client_id,
pf->viftype_status[vt].name);
if (IS_ERR(padev)) {
pds_client_unregister(pf, client_id);
err = PTR_ERR(padev);
goto out_unlock;
}
*pd_ptr = padev;
out_unlock:
mutex_unlock(&pf->config_lock);
return err;
}
@@ -0,0 +1,672 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include "core.h"
static BLOCKING_NOTIFIER_HEAD(pds_notify_chain);
int pdsc_register_notify(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&pds_notify_chain, nb);
}
EXPORT_SYMBOL_GPL(pdsc_register_notify);
void pdsc_unregister_notify(struct notifier_block *nb)
{
blocking_notifier_chain_unregister(&pds_notify_chain, nb);
}
EXPORT_SYMBOL_GPL(pdsc_unregister_notify);
void pdsc_notify(unsigned long event, void *data)
{
blocking_notifier_call_chain(&pds_notify_chain, event, data);
}
void pdsc_intr_free(struct pdsc *pdsc, int index)
{
struct pdsc_intr_info *intr_info;
if (index >= pdsc->nintrs || index < 0) {
WARN(true, "bad intr index %d\n", index);
return;
}
intr_info = &pdsc->intr_info[index];
if (!intr_info->vector)
return;
dev_dbg(pdsc->dev, "%s: idx %d vec %d name %s\n",
__func__, index, intr_info->vector, intr_info->name);
pds_core_intr_mask(&pdsc->intr_ctrl[index], PDS_CORE_INTR_MASK_SET);
pds_core_intr_clean(&pdsc->intr_ctrl[index]);
free_irq(intr_info->vector, intr_info->data);
memset(intr_info, 0, sizeof(*intr_info));
}
int pdsc_intr_alloc(struct pdsc *pdsc, char *name,
irq_handler_t handler, void *data)
{
struct pdsc_intr_info *intr_info;
unsigned int index;
int err;
/* Find the first available interrupt */
for (index = 0; index < pdsc->nintrs; index++)
if (!pdsc->intr_info[index].vector)
break;
if (index >= pdsc->nintrs) {
dev_warn(pdsc->dev, "%s: no intr, index=%d nintrs=%d\n",
__func__, index, pdsc->nintrs);
return -ENOSPC;
}
pds_core_intr_clean_flags(&pdsc->intr_ctrl[index],
PDS_CORE_INTR_CRED_RESET_COALESCE);
intr_info = &pdsc->intr_info[index];
intr_info->index = index;
intr_info->data = data;
strscpy(intr_info->name, name, sizeof(intr_info->name));
/* Get the OS vector number for the interrupt */
err = pci_irq_vector(pdsc->pdev, index);
if (err < 0) {
dev_err(pdsc->dev, "failed to get intr vector index %d: %pe\n",
index, ERR_PTR(err));
goto err_out_free_intr;
}
intr_info->vector = err;
/* Init the device's intr mask */
pds_core_intr_clean(&pdsc->intr_ctrl[index]);
pds_core_intr_mask_assert(&pdsc->intr_ctrl[index], 1);
pds_core_intr_mask(&pdsc->intr_ctrl[index], PDS_CORE_INTR_MASK_SET);
/* Register the isr with a name */
err = request_irq(intr_info->vector, handler, 0, intr_info->name, data);
if (err) {
dev_err(pdsc->dev, "failed to get intr irq vector %d: %pe\n",
intr_info->vector, ERR_PTR(err));
goto err_out_free_intr;
}
return index;
err_out_free_intr:
pdsc_intr_free(pdsc, index);
return err;
}
static void pdsc_qcq_intr_free(struct pdsc *pdsc, struct pdsc_qcq *qcq)
{
if (!(qcq->flags & PDS_CORE_QCQ_F_INTR) ||
qcq->intx == PDS_CORE_INTR_INDEX_NOT_ASSIGNED)
return;
pdsc_intr_free(pdsc, qcq->intx);
}
static int pdsc_qcq_intr_alloc(struct pdsc *pdsc, struct pdsc_qcq *qcq)
{
char name[PDSC_INTR_NAME_MAX_SZ];
int index;
if (!(qcq->flags & PDS_CORE_QCQ_F_INTR)) {
qcq->intx = PDS_CORE_INTR_INDEX_NOT_ASSIGNED;
return 0;
}
snprintf(name, sizeof(name), "%s-%d-%s",
PDS_CORE_DRV_NAME, pdsc->pdev->bus->number, qcq->q.name);
index = pdsc_intr_alloc(pdsc, name, pdsc_adminq_isr, pdsc);
if (index < 0)
return index;
qcq->intx = index;
qcq->cq.bound_intr = &pdsc->intr_info[index];
return 0;
}
void pdsc_qcq_free(struct pdsc *pdsc, struct pdsc_qcq *qcq)
{
struct device *dev = pdsc->dev;
if (!(qcq && qcq->pdsc))
return;
pdsc_debugfs_del_qcq(qcq);
pdsc_qcq_intr_free(pdsc, qcq);
/* Drain any work queued by ISR before it was freed above */
if (qcq->work.func)
cancel_work_sync(&qcq->work);
qcq->intx = PDS_CORE_INTR_INDEX_NOT_ASSIGNED;
if (qcq->q_base)
dma_free_coherent(dev, qcq->q_size,
qcq->q_base, qcq->q_base_pa);
if (qcq->cq_base)
dma_free_coherent(dev, qcq->cq_size,
qcq->cq_base, qcq->cq_base_pa);
vfree(qcq->cq.info);
vfree(qcq->q.info);
memset(qcq, 0, sizeof(*qcq));
}
static void pdsc_q_map(struct pdsc_queue *q, void *base, dma_addr_t base_pa)
{
struct pdsc_q_info *cur;
unsigned int i;
q->base = base;
q->base_pa = base_pa;
for (i = 0, cur = q->info; i < q->num_descs; i++, cur++) {
cur->desc = base + (i * q->desc_size);
init_completion(&cur->completion);
}
}
static void pdsc_cq_map(struct pdsc_cq *cq, void *base, dma_addr_t base_pa)
{
struct pdsc_cq_info *cur;
unsigned int i;
cq->base = base;
cq->base_pa = base_pa;
for (i = 0, cur = cq->info; i < cq->num_descs; i++, cur++)
cur->comp = base + (i * cq->desc_size);
}
int pdsc_qcq_alloc(struct pdsc *pdsc, unsigned int type, unsigned int index,
const char *name, unsigned int flags, unsigned int num_descs,
unsigned int desc_size, unsigned int cq_desc_size,
unsigned int pid, struct pdsc_qcq *qcq)
{
struct device *dev = pdsc->dev;
void *q_base, *cq_base;
dma_addr_t cq_base_pa;
dma_addr_t q_base_pa;
int err;
qcq->q.info = vcalloc(num_descs, sizeof(*qcq->q.info));
if (!qcq->q.info) {
err = -ENOMEM;
goto err_out;
}
qcq->pdsc = pdsc;
qcq->flags = flags;
INIT_WORK(&qcq->work, pdsc_work_thread);
qcq->q.type = type;
qcq->q.index = index;
qcq->q.num_descs = num_descs;
qcq->q.desc_size = desc_size;
qcq->q.tail_idx = 0;
qcq->q.head_idx = 0;
qcq->q.pid = pid;
snprintf(qcq->q.name, sizeof(qcq->q.name), "%s%u", name, index);
err = pdsc_qcq_intr_alloc(pdsc, qcq);
if (err)
goto err_out_free_q_info;
qcq->cq.info = vcalloc(num_descs, sizeof(*qcq->cq.info));
if (!qcq->cq.info) {
err = -ENOMEM;
goto err_out_free_irq;
}
qcq->cq.num_descs = num_descs;
qcq->cq.desc_size = cq_desc_size;
qcq->cq.tail_idx = 0;
qcq->cq.done_color = 1;
if (flags & PDS_CORE_QCQ_F_NOTIFYQ) {
/* q & cq need to be contiguous in case of notifyq */
qcq->q_size = PDS_PAGE_SIZE +
ALIGN(num_descs * desc_size, PDS_PAGE_SIZE) +
ALIGN(num_descs * cq_desc_size, PDS_PAGE_SIZE);
qcq->q_base = dma_alloc_coherent(dev,
qcq->q_size + qcq->cq_size,
&qcq->q_base_pa,
GFP_KERNEL);
if (!qcq->q_base) {
err = -ENOMEM;
goto err_out_free_cq_info;
}
q_base = PTR_ALIGN(qcq->q_base, PDS_PAGE_SIZE);
q_base_pa = ALIGN(qcq->q_base_pa, PDS_PAGE_SIZE);
pdsc_q_map(&qcq->q, q_base, q_base_pa);
cq_base = PTR_ALIGN(q_base +
ALIGN(num_descs * desc_size, PDS_PAGE_SIZE),
PDS_PAGE_SIZE);
cq_base_pa = ALIGN(qcq->q_base_pa +
ALIGN(num_descs * desc_size, PDS_PAGE_SIZE),
PDS_PAGE_SIZE);
} else {
/* q DMA descriptors */
qcq->q_size = PDS_PAGE_SIZE + (num_descs * desc_size);
qcq->q_base = dma_alloc_coherent(dev, qcq->q_size,
&qcq->q_base_pa,
GFP_KERNEL);
if (!qcq->q_base) {
err = -ENOMEM;
goto err_out_free_cq_info;
}
q_base = PTR_ALIGN(qcq->q_base, PDS_PAGE_SIZE);
q_base_pa = ALIGN(qcq->q_base_pa, PDS_PAGE_SIZE);
pdsc_q_map(&qcq->q, q_base, q_base_pa);
/* cq DMA descriptors */
qcq->cq_size = PDS_PAGE_SIZE + (num_descs * cq_desc_size);
qcq->cq_base = dma_alloc_coherent(dev, qcq->cq_size,
&qcq->cq_base_pa,
GFP_KERNEL);
if (!qcq->cq_base) {
err = -ENOMEM;
goto err_out_free_q;
}
cq_base = PTR_ALIGN(qcq->cq_base, PDS_PAGE_SIZE);
cq_base_pa = ALIGN(qcq->cq_base_pa, PDS_PAGE_SIZE);
}
pdsc_cq_map(&qcq->cq, cq_base, cq_base_pa);
qcq->cq.bound_q = &qcq->q;
pdsc_debugfs_add_qcq(pdsc, qcq);
return 0;
err_out_free_q:
dma_free_coherent(dev, qcq->q_size, qcq->q_base, qcq->q_base_pa);
err_out_free_cq_info:
vfree(qcq->cq.info);
err_out_free_irq:
pdsc_qcq_intr_free(pdsc, qcq);
err_out_free_q_info:
vfree(qcq->q.info);
memset(qcq, 0, sizeof(*qcq));
err_out:
dev_err(dev, "qcq alloc of %s%d failed %d\n", name, index, err);
return err;
}
static void pdsc_core_uninit(struct pdsc *pdsc)
{
/* Free adminqcq first: its work accesses notifyqcq, so we must
* disable its IRQ and drain its work before freeing notifyqcq.
*/
pdsc_qcq_free(pdsc, &pdsc->adminqcq);
pdsc_qcq_free(pdsc, &pdsc->notifyqcq);
if (pdsc->kern_dbpage) {
iounmap(pdsc->kern_dbpage);
pdsc->kern_dbpage = NULL;
}
}
static int pdsc_core_init(struct pdsc *pdsc)
{
union pds_core_dev_comp comp = {};
union pds_core_dev_cmd cmd = {
.init.opcode = PDS_CORE_CMD_INIT,
};
struct pds_core_dev_init_data_out cido;
struct pds_core_dev_init_data_in cidi;
u32 dbid_count;
u32 dbpage_num;
int numdescs;
size_t sz;
int err;
numdescs = PDSC_ADMINQ_MAX_LENGTH;
err = pdsc_qcq_alloc(pdsc, PDS_CORE_QTYPE_ADMINQ, 0, "adminq",
PDS_CORE_QCQ_F_CORE | PDS_CORE_QCQ_F_INTR,
numdescs,
sizeof(union pds_core_adminq_cmd),
sizeof(union pds_core_adminq_comp),
0, &pdsc->adminqcq);
if (err)
return err;
err = pdsc_qcq_alloc(pdsc, PDS_CORE_QTYPE_NOTIFYQ, 0, "notifyq",
PDS_CORE_QCQ_F_NOTIFYQ,
PDSC_NOTIFYQ_LENGTH,
sizeof(struct pds_core_notifyq_cmd),
sizeof(union pds_core_notifyq_comp),
0, &pdsc->notifyqcq);
if (err)
goto err_out_uninit;
cidi.adminq_q_base = cpu_to_le64(pdsc->adminqcq.q_base_pa);
cidi.adminq_cq_base = cpu_to_le64(pdsc->adminqcq.cq_base_pa);
cidi.notifyq_cq_base = cpu_to_le64(pdsc->notifyqcq.cq.base_pa);
cidi.flags = cpu_to_le32(PDS_CORE_QINIT_F_IRQ | PDS_CORE_QINIT_F_ENA);
cidi.intr_index = cpu_to_le16(pdsc->adminqcq.intx);
cidi.adminq_ring_size = ilog2(pdsc->adminqcq.q.num_descs);
cidi.notifyq_ring_size = ilog2(pdsc->notifyqcq.q.num_descs);
mutex_lock(&pdsc->devcmd_lock);
sz = min_t(size_t, sizeof(cidi), sizeof(pdsc->cmd_regs->data));
memcpy_toio(&pdsc->cmd_regs->data, &cidi, sz);
err = pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
if (!err) {
sz = min_t(size_t, sizeof(cido), sizeof(pdsc->cmd_regs->data));
memcpy_fromio(&cido, &pdsc->cmd_regs->data, sz);
}
mutex_unlock(&pdsc->devcmd_lock);
if (err) {
dev_err(pdsc->dev, "Device init command failed: %pe\n",
ERR_PTR(err));
goto err_out_uninit;
}
pdsc->hw_index = le32_to_cpu(cido.core_hw_index);
dbid_count = le32_to_cpu(pdsc->dev_ident.ndbpgs_per_lif);
dbpage_num = pdsc->hw_index * dbid_count;
pdsc->kern_dbpage = pdsc_map_dbpage(pdsc, dbpage_num);
if (!pdsc->kern_dbpage) {
dev_err(pdsc->dev, "Cannot map dbpage, aborting\n");
err = -ENOMEM;
goto err_out_uninit;
}
pdsc->adminqcq.q.hw_type = cido.adminq_hw_type;
pdsc->adminqcq.q.hw_index = le32_to_cpu(cido.adminq_hw_index);
pdsc->adminqcq.q.dbval = PDS_CORE_DBELL_QID(pdsc->adminqcq.q.hw_index);
pdsc->notifyqcq.q.hw_type = cido.notifyq_hw_type;
pdsc->notifyqcq.q.hw_index = le32_to_cpu(cido.notifyq_hw_index);
pdsc->notifyqcq.q.dbval = PDS_CORE_DBELL_QID(pdsc->notifyqcq.q.hw_index);
pdsc->last_eid = 0;
return 0;
err_out_uninit:
pdsc_core_uninit(pdsc);
return err;
}
static struct pdsc_viftype pdsc_viftype_defaults[] = {
[PDS_DEV_TYPE_FWCTL] = { .name = PDS_DEV_TYPE_FWCTL_STR,
.enabled = true,
.vif_id = PDS_DEV_TYPE_FWCTL,
.dl_id = -1 },
[PDS_DEV_TYPE_VDPA] = { .name = PDS_DEV_TYPE_VDPA_STR,
.vif_id = PDS_DEV_TYPE_VDPA,
.dl_id = DEVLINK_PARAM_GENERIC_ID_ENABLE_VNET },
[PDS_DEV_TYPE_MAX] = {}
};
static int pdsc_viftypes_init(struct pdsc *pdsc)
{
enum pds_core_vif_types vt;
pdsc->viftype_status = kzalloc_objs(*pdsc->viftype_status,
ARRAY_SIZE(pdsc_viftype_defaults));
if (!pdsc->viftype_status)
return -ENOMEM;
for (vt = 0; vt < PDS_DEV_TYPE_MAX; vt++) {
bool vt_support;
if (!pdsc_viftype_defaults[vt].name)
continue;
/* Grab the defaults */
pdsc->viftype_status[vt] = pdsc_viftype_defaults[vt];
/* See what the Core device has for support */
vt_support = !!le16_to_cpu(pdsc->dev_ident.vif_types[vt]);
dev_dbg(pdsc->dev, "VIF %s is %ssupported\n",
pdsc->viftype_status[vt].name,
vt_support ? "" : "not ");
pdsc->viftype_status[vt].supported = vt_support;
}
return 0;
}
int pdsc_setup(struct pdsc *pdsc, bool init)
{
int err;
pci_set_master(pdsc->pdev);
err = pdsc_dev_init(pdsc);
if (err)
return err;
/* Set up the Core with the AdminQ and NotifyQ info */
err = pdsc_core_init(pdsc);
if (err)
goto err_out_teardown;
/* Set up the VIFs */
if (init) {
err = pdsc_viftypes_init(pdsc);
if (err)
goto err_out_teardown;
pdsc_debugfs_add_viftype(pdsc);
}
refcount_set(&pdsc->adminq_refcnt, 1);
clear_bit(PDSC_S_FW_DEAD, &pdsc->state);
return 0;
err_out_teardown:
pdsc_teardown(pdsc, init);
return err;
}
void pdsc_teardown(struct pdsc *pdsc, bool removing)
{
if (!pdsc->pdev->is_virtfn)
pdsc_devcmd_reset(pdsc);
pci_clear_master(pdsc->pdev);
pdsc_core_uninit(pdsc);
if (removing) {
kfree(pdsc->viftype_status);
pdsc->viftype_status = NULL;
}
pdsc_dev_uninit(pdsc);
set_bit(PDSC_S_FW_DEAD, &pdsc->state);
}
int pdsc_start(struct pdsc *pdsc)
{
pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx],
PDS_CORE_INTR_MASK_CLEAR);
return 0;
}
void pdsc_stop(struct pdsc *pdsc)
{
int i;
if (!pdsc->intr_info)
return;
/* Mask interrupts that are in use */
for (i = 0; i < pdsc->nintrs; i++)
if (pdsc->intr_info[i].vector)
pds_core_intr_mask(&pdsc->intr_ctrl[i],
PDS_CORE_INTR_MASK_SET);
}
static void pdsc_adminq_wait_and_dec_once_unused(struct pdsc *pdsc)
{
/* The driver initializes the adminq_refcnt to 1 when the adminq is
* allocated and ready for use. Other users/requesters will increment
* the refcnt while in use. If the refcnt is down to 1 then the adminq
* is not in use and the refcnt can be cleared and adminq freed. Before
* calling this function the driver will set PDSC_S_FW_DEAD, which
* prevent subsequent attempts to use the adminq and increment the
* refcnt to fail. This guarantees that this function will eventually
* exit.
*/
while (!refcount_dec_if_one(&pdsc->adminq_refcnt)) {
dev_dbg_ratelimited(pdsc->dev, "%s: adminq in use\n",
__func__);
cpu_relax();
cond_resched();
}
}
void pdsc_fw_down(struct pdsc *pdsc)
{
union pds_core_notifyq_comp reset_event = {
.reset.ecode = cpu_to_le16(PDS_EVENT_RESET),
.reset.state = 0,
};
if (test_and_set_bit(PDSC_S_FW_DEAD, &pdsc->state)) {
dev_warn(pdsc->dev, "%s: already happening\n", __func__);
return;
}
if (pdsc->pdev->is_virtfn)
return;
pdsc_adminq_wait_and_dec_once_unused(pdsc);
/* Notify clients of fw_down */
if (pdsc->fw_reporter)
devlink_health_report(pdsc->fw_reporter, "FW down reported", pdsc);
pdsc_notify(PDS_EVENT_RESET, &reset_event);
pdsc_stop(pdsc);
pdsc_teardown(pdsc, PDSC_TEARDOWN_RECOVERY);
}
void pdsc_fw_up(struct pdsc *pdsc)
{
union pds_core_notifyq_comp reset_event = {
.reset.ecode = cpu_to_le16(PDS_EVENT_RESET),
.reset.state = 1,
};
int err;
if (!test_bit(PDSC_S_FW_DEAD, &pdsc->state)) {
dev_err(pdsc->dev, "%s: fw not dead\n", __func__);
return;
}
if (pdsc->pdev->is_virtfn) {
clear_bit(PDSC_S_FW_DEAD, &pdsc->state);
return;
}
err = pdsc_setup(pdsc, PDSC_SETUP_RECOVERY);
if (err)
goto err_out;
err = pdsc_start(pdsc);
if (err)
goto err_out;
/* Notify clients of fw_up */
pdsc->fw_recoveries++;
if (pdsc->fw_reporter)
devlink_health_reporter_state_update(pdsc->fw_reporter,
DEVLINK_HEALTH_REPORTER_STATE_HEALTHY);
pdsc_notify(PDS_EVENT_RESET, &reset_event);
return;
err_out:
pdsc_teardown(pdsc, PDSC_TEARDOWN_RECOVERY);
}
void pdsc_pci_reset_thread(struct work_struct *work)
{
struct pdsc *pdsc = container_of(work, struct pdsc, pci_reset_work);
struct pci_dev *pdev = pdsc->pdev;
/* Use try variant to avoid deadlock with pdsc_remove().
* If lock is contended, the watchdog timer will retry.
*/
pci_try_reset_function(pdev);
}
static void pdsc_check_pci_health(struct pdsc *pdsc)
{
u8 fw_status;
/* some sort of teardown already in progress */
if (!pdsc->info_regs)
return;
fw_status = ioread8(&pdsc->info_regs->fw_status);
/* is PCI broken? */
if (fw_status != PDS_RC_BAD_PCI)
return;
/* prevent deadlock between pdsc_reset_prepare and pdsc_health_thread */
queue_work(pdsc->wq, &pdsc->pci_reset_work);
}
void pdsc_health_thread(struct work_struct *work)
{
struct pdsc *pdsc = container_of(work, struct pdsc, health_work);
unsigned long mask;
bool healthy;
mutex_lock(&pdsc->config_lock);
/* Don't do a check when in a transition state */
mask = BIT_ULL(PDSC_S_INITING_DRIVER) |
BIT_ULL(PDSC_S_STOPPING_DRIVER);
if (pdsc->state & mask)
goto out_unlock;
healthy = pdsc_is_fw_good(pdsc);
dev_dbg(pdsc->dev, "%s: health %d fw_status %#02x fw_heartbeat %d\n",
__func__, healthy, pdsc->fw_status, pdsc->last_hb);
if (test_bit(PDSC_S_FW_DEAD, &pdsc->state)) {
if (healthy)
pdsc_fw_up(pdsc);
} else {
if (!healthy)
pdsc_fw_down(pdsc);
}
pdsc_check_pci_health(pdsc);
pdsc->fw_generation = pdsc->fw_status & PDS_CORE_FW_STS_F_GENERATION;
out_unlock:
mutex_unlock(&pdsc->config_lock);
}
@@ -0,0 +1,326 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#ifndef _PDSC_H_
#define _PDSC_H_
#include <linux/debugfs.h>
#include <net/devlink.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include <linux/pds/pds_intr.h>
#define PDSC_DRV_DESCRIPTION "AMD/Pensando Core Driver"
#define PDSC_WATCHDOG_SECS 5
#define PDSC_QUEUE_NAME_MAX_SZ 16
#define PDSC_ADMINQ_MAX_LENGTH 16 /* must be a power of two */
#define PDSC_NOTIFYQ_LENGTH 64 /* must be a power of two */
#define PDSC_TEARDOWN_RECOVERY false
#define PDSC_TEARDOWN_REMOVING true
#define PDSC_SETUP_RECOVERY false
#define PDSC_SETUP_INIT true
struct pdsc_dev_bar {
void __iomem *vaddr;
phys_addr_t bus_addr;
unsigned long len;
int res_index;
};
struct pdsc;
struct pdsc_vf {
struct pds_auxiliary_dev *padev;
struct pdsc *vf;
u16 index;
__le16 vif_types[PDS_DEV_TYPE_MAX];
};
struct pdsc_devinfo {
u8 asic_type;
u8 asic_rev;
char fw_version[PDS_CORE_DEVINFO_FWVERS_BUFLEN + 1];
char serial_num[PDS_CORE_DEVINFO_SERIAL_BUFLEN + 1];
};
struct pdsc_queue {
struct pdsc_q_info *info;
u64 dbval;
u16 head_idx;
u16 tail_idx;
u8 hw_type;
unsigned int index;
unsigned int num_descs;
u64 dbell_count;
u64 features;
unsigned int type;
unsigned int hw_index;
union {
void *base;
struct pds_core_admin_cmd *adminq;
};
dma_addr_t base_pa; /* must be page aligned */
unsigned int desc_size;
unsigned int pid;
char name[PDSC_QUEUE_NAME_MAX_SZ];
};
#define PDSC_INTR_NAME_MAX_SZ 32
struct pdsc_intr_info {
char name[PDSC_INTR_NAME_MAX_SZ];
unsigned int index;
unsigned int vector;
void *data;
};
struct pdsc_cq_info {
void *comp;
};
struct pdsc_buf_info {
struct page *page;
dma_addr_t dma_addr;
u32 page_offset;
u32 len;
};
struct pdsc_q_info {
union {
void *desc;
struct pdsc_admin_cmd *adminq_desc;
};
unsigned int bytes;
unsigned int nbufs;
struct pdsc_buf_info bufs[PDS_CORE_MAX_FRAGS];
struct completion completion;
void *dest;
};
struct pdsc_cq {
struct pdsc_cq_info *info;
struct pdsc_queue *bound_q;
struct pdsc_intr_info *bound_intr;
u16 tail_idx;
bool done_color;
unsigned int num_descs;
unsigned int desc_size;
void *base;
dma_addr_t base_pa; /* must be page aligned */
} ____cacheline_aligned_in_smp;
struct pdsc_qcq {
struct pdsc *pdsc;
void *q_base;
dma_addr_t q_base_pa; /* might not be page aligned */
void *cq_base;
dma_addr_t cq_base_pa; /* might not be page aligned */
u32 q_size;
u32 cq_size;
bool armed;
unsigned int flags;
struct work_struct work;
struct pdsc_queue q;
struct pdsc_cq cq;
int intx;
u32 accum_work;
struct dentry *dentry;
};
struct pdsc_viftype {
char *name;
bool supported;
bool enabled;
int dl_id;
int vif_id;
struct pds_auxiliary_dev *padev;
};
/* No state flags set means we are in a steady running state */
enum pdsc_state_flags {
PDSC_S_FW_DEAD, /* stopped, wait on startup or recovery */
PDSC_S_INITING_DRIVER, /* initial startup from probe */
PDSC_S_STOPPING_DRIVER, /* driver remove */
/* leave this as last */
PDSC_S_STATE_SIZE
};
struct pdsc {
struct pci_dev *pdev;
struct dentry *dentry;
struct device *dev;
struct pdsc_dev_bar bars[PDS_CORE_BARS_MAX];
struct pds_auxiliary_dev *padev;
struct pdsc_vf *vfs;
int num_vfs;
int vf_id;
int hw_index;
int uid;
unsigned long state;
u8 fw_status;
u8 fw_generation;
unsigned long last_fw_time;
u32 last_hb;
struct timer_list wdtimer;
unsigned int wdtimer_period;
struct work_struct health_work;
bool health_stopped;
struct devlink_health_reporter *fw_reporter;
u32 fw_recoveries;
struct pdsc_devinfo dev_info;
struct pds_core_dev_identity dev_ident;
unsigned int nintrs;
struct pdsc_intr_info *intr_info; /* array of nintrs elements */
struct workqueue_struct *wq;
unsigned int devcmd_timeout;
struct mutex devcmd_lock; /* lock for dev_cmd operations */
struct mutex config_lock; /* lock for configuration operations */
spinlock_t adminq_lock; /* lock for adminq operations */
refcount_t adminq_refcnt;
struct pds_core_dev_info_regs __iomem *info_regs;
struct pds_core_dev_cmd_regs __iomem *cmd_regs;
struct pds_core_intr __iomem *intr_ctrl;
u64 __iomem *intr_status;
u64 __iomem *db_pages;
dma_addr_t phy_db_pages;
u64 __iomem *kern_dbpage;
struct pdsc_qcq adminqcq;
struct pdsc_qcq notifyqcq;
u64 last_eid;
struct pdsc_viftype *viftype_status;
struct work_struct pci_reset_work;
};
/** enum pds_core_dbell_bits - bitwise composition of dbell values.
*
* @PDS_CORE_DBELL_QID_MASK: unshifted mask of valid queue id bits.
* @PDS_CORE_DBELL_QID_SHIFT: queue id shift amount in dbell value.
* @PDS_CORE_DBELL_QID: macro to build QID component of dbell value.
*
* @PDS_CORE_DBELL_RING_MASK: unshifted mask of valid ring bits.
* @PDS_CORE_DBELL_RING_SHIFT: ring shift amount in dbell value.
* @PDS_CORE_DBELL_RING: macro to build ring component of dbell value.
*
* @PDS_CORE_DBELL_RING_0: ring zero dbell component value.
* @PDS_CORE_DBELL_RING_1: ring one dbell component value.
* @PDS_CORE_DBELL_RING_2: ring two dbell component value.
* @PDS_CORE_DBELL_RING_3: ring three dbell component value.
*
* @PDS_CORE_DBELL_INDEX_MASK: bit mask of valid index bits, no shift needed.
*/
enum pds_core_dbell_bits {
PDS_CORE_DBELL_QID_MASK = 0xffffff,
PDS_CORE_DBELL_QID_SHIFT = 24,
#define PDS_CORE_DBELL_QID(n) \
(((u64)(n) & PDS_CORE_DBELL_QID_MASK) << PDS_CORE_DBELL_QID_SHIFT)
PDS_CORE_DBELL_RING_MASK = 0x7,
PDS_CORE_DBELL_RING_SHIFT = 16,
#define PDS_CORE_DBELL_RING(n) \
(((u64)(n) & PDS_CORE_DBELL_RING_MASK) << PDS_CORE_DBELL_RING_SHIFT)
PDS_CORE_DBELL_RING_0 = 0,
PDS_CORE_DBELL_RING_1 = PDS_CORE_DBELL_RING(1),
PDS_CORE_DBELL_RING_2 = PDS_CORE_DBELL_RING(2),
PDS_CORE_DBELL_RING_3 = PDS_CORE_DBELL_RING(3),
PDS_CORE_DBELL_INDEX_MASK = 0xffff,
};
static inline void pds_core_dbell_ring(u64 __iomem *db_page,
enum pds_core_logical_qtype qtype,
u64 val)
{
writeq(val, &db_page[qtype]);
}
int pdsc_fw_reporter_diagnose(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg,
struct netlink_ext_ack *extack);
int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
struct netlink_ext_ack *extack);
int pdsc_dl_flash_update(struct devlink *dl,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack);
int pdsc_dl_enable_get(struct devlink *dl, u32 id,
struct devlink_param_gset_ctx *ctx,
struct netlink_ext_ack *extack);
int pdsc_dl_enable_set(struct devlink *dl, u32 id,
struct devlink_param_gset_ctx *ctx,
struct netlink_ext_ack *extack);
int pdsc_dl_enable_validate(struct devlink *dl, u32 id,
union devlink_param_value *val,
struct netlink_ext_ack *extack);
void __iomem *pdsc_map_dbpage(struct pdsc *pdsc, int page_num);
void pdsc_debugfs_create(void);
void pdsc_debugfs_destroy(void);
void pdsc_debugfs_add_dev(struct pdsc *pdsc);
void pdsc_debugfs_del_dev(struct pdsc *pdsc);
void pdsc_debugfs_add_ident(struct pdsc *pdsc);
void pdsc_debugfs_add_viftype(struct pdsc *pdsc);
void pdsc_debugfs_add_irqs(struct pdsc *pdsc);
void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq);
void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq);
int pdsc_err_to_errno(enum pds_core_status_code code);
bool pdsc_is_fw_running(struct pdsc *pdsc);
bool pdsc_is_fw_good(struct pdsc *pdsc);
int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
union pds_core_dev_comp *comp, int max_seconds);
int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
union pds_core_dev_comp *comp, int max_seconds);
int pdsc_devcmd_init(struct pdsc *pdsc);
int pdsc_devcmd_reset(struct pdsc *pdsc);
int pdsc_dev_init(struct pdsc *pdsc);
void pdsc_dev_uninit(struct pdsc *pdsc);
int pdsc_intr_alloc(struct pdsc *pdsc, char *name,
irq_handler_t handler, void *data);
void pdsc_intr_free(struct pdsc *pdsc, int index);
void pdsc_qcq_free(struct pdsc *pdsc, struct pdsc_qcq *qcq);
int pdsc_qcq_alloc(struct pdsc *pdsc, unsigned int type, unsigned int index,
const char *name, unsigned int flags, unsigned int num_descs,
unsigned int desc_size, unsigned int cq_desc_size,
unsigned int pid, struct pdsc_qcq *qcq);
int pdsc_setup(struct pdsc *pdsc, bool init);
void pdsc_teardown(struct pdsc *pdsc, bool removing);
int pdsc_start(struct pdsc *pdsc);
void pdsc_stop(struct pdsc *pdsc);
void pdsc_health_thread(struct work_struct *work);
int pdsc_register_notify(struct notifier_block *nb);
void pdsc_unregister_notify(struct notifier_block *nb);
void pdsc_notify(unsigned long event, void *data);
int pdsc_auxbus_dev_add(struct pdsc *cf, struct pdsc *pf,
enum pds_core_vif_types vt,
struct pds_auxiliary_dev **pd_ptr);
void pdsc_auxbus_dev_del(struct pdsc *cf, struct pdsc *pf,
struct pds_auxiliary_dev **pd_ptr);
void pdsc_process_adminq(struct pdsc_qcq *qcq);
void pdsc_work_thread(struct work_struct *work);
irqreturn_t pdsc_adminq_isr(int irq, void *data);
int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
struct netlink_ext_ack *extack);
void pdsc_fw_down(struct pdsc *pdsc);
void pdsc_fw_up(struct pdsc *pdsc);
void pdsc_pci_reset_thread(struct work_struct *work);
#endif /* _PDSC_H_ */
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/pci.h>
#include "core.h"
static struct dentry *pdsc_dir;
void pdsc_debugfs_create(void)
{
pdsc_dir = debugfs_create_dir(PDS_CORE_DRV_NAME, NULL);
}
void pdsc_debugfs_destroy(void)
{
debugfs_remove_recursive(pdsc_dir);
}
void pdsc_debugfs_add_dev(struct pdsc *pdsc)
{
pdsc->dentry = debugfs_create_dir(pci_name(pdsc->pdev), pdsc_dir);
debugfs_create_ulong("state", 0400, pdsc->dentry, &pdsc->state);
}
void pdsc_debugfs_del_dev(struct pdsc *pdsc)
{
debugfs_remove_recursive(pdsc->dentry);
pdsc->dentry = NULL;
}
static int identity_show(struct seq_file *seq, void *v)
{
struct pds_core_dev_identity *ident;
struct pdsc *pdsc = seq->private;
int vt;
ident = &pdsc->dev_ident;
seq_printf(seq, "fw_heartbeat: 0x%x\n",
ioread32(&pdsc->info_regs->fw_heartbeat));
seq_printf(seq, "nlifs: %d\n",
le32_to_cpu(ident->nlifs));
seq_printf(seq, "nintrs: %d\n",
le32_to_cpu(ident->nintrs));
seq_printf(seq, "ndbpgs_per_lif: %d\n",
le32_to_cpu(ident->ndbpgs_per_lif));
seq_printf(seq, "intr_coal_mult: %d\n",
le32_to_cpu(ident->intr_coal_mult));
seq_printf(seq, "intr_coal_div: %d\n",
le32_to_cpu(ident->intr_coal_div));
seq_puts(seq, "vif_types: ");
for (vt = 0; vt < PDS_DEV_TYPE_MAX; vt++)
seq_printf(seq, "%d ",
le16_to_cpu(pdsc->dev_ident.vif_types[vt]));
seq_puts(seq, "\n");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(identity);
void pdsc_debugfs_add_ident(struct pdsc *pdsc)
{
struct dentry *dentry;
/* This file will already exist in the reset flow */
dentry = debugfs_lookup("identity", pdsc->dentry);
if (!IS_ERR_OR_NULL(dentry)) {
dput(dentry);
return;
}
debugfs_create_file("identity", 0400, pdsc->dentry,
pdsc, &identity_fops);
}
static int viftype_show(struct seq_file *seq, void *v)
{
struct pdsc *pdsc = seq->private;
int vt;
for (vt = 0; vt < PDS_DEV_TYPE_MAX; vt++) {
if (!pdsc->viftype_status[vt].name)
continue;
seq_printf(seq, "%s\t%d supported %d enabled\n",
pdsc->viftype_status[vt].name,
pdsc->viftype_status[vt].supported,
pdsc->viftype_status[vt].enabled);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(viftype);
void pdsc_debugfs_add_viftype(struct pdsc *pdsc)
{
debugfs_create_file("viftypes", 0400, pdsc->dentry,
pdsc, &viftype_fops);
}
static const struct debugfs_reg32 intr_ctrl_regs[] = {
{ .name = "coal_init", .offset = 0, },
{ .name = "mask", .offset = 4, },
{ .name = "credits", .offset = 8, },
{ .name = "mask_on_assert", .offset = 12, },
{ .name = "coal_timer", .offset = 16, },
};
void pdsc_debugfs_add_qcq(struct pdsc *pdsc, struct pdsc_qcq *qcq)
{
struct dentry *qcq_dentry, *q_dentry, *cq_dentry, *intr_dentry;
struct debugfs_regset32 *intr_ctrl_regset;
struct pdsc_queue *q = &qcq->q;
struct pdsc_cq *cq = &qcq->cq;
qcq_dentry = debugfs_create_dir(q->name, pdsc->dentry);
if (IS_ERR(qcq_dentry))
return;
qcq->dentry = qcq_dentry;
debugfs_create_x64("q_base_pa", 0400, qcq_dentry, &qcq->q_base_pa);
debugfs_create_x32("q_size", 0400, qcq_dentry, &qcq->q_size);
debugfs_create_x64("cq_base_pa", 0400, qcq_dentry, &qcq->cq_base_pa);
debugfs_create_x32("cq_size", 0400, qcq_dentry, &qcq->cq_size);
debugfs_create_x32("accum_work", 0400, qcq_dentry, &qcq->accum_work);
q_dentry = debugfs_create_dir("q", qcq->dentry);
if (IS_ERR(q_dentry))
return;
debugfs_create_u32("index", 0400, q_dentry, &q->index);
debugfs_create_u32("num_descs", 0400, q_dentry, &q->num_descs);
debugfs_create_u32("desc_size", 0400, q_dentry, &q->desc_size);
debugfs_create_u32("pid", 0400, q_dentry, &q->pid);
debugfs_create_u16("tail", 0400, q_dentry, &q->tail_idx);
debugfs_create_u16("head", 0400, q_dentry, &q->head_idx);
cq_dentry = debugfs_create_dir("cq", qcq->dentry);
if (IS_ERR(cq_dentry))
return;
debugfs_create_x64("base_pa", 0400, cq_dentry, &cq->base_pa);
debugfs_create_u32("num_descs", 0400, cq_dentry, &cq->num_descs);
debugfs_create_u32("desc_size", 0400, cq_dentry, &cq->desc_size);
debugfs_create_bool("done_color", 0400, cq_dentry, &cq->done_color);
debugfs_create_u16("tail", 0400, cq_dentry, &cq->tail_idx);
if (qcq->flags & PDS_CORE_QCQ_F_INTR) {
struct pdsc_intr_info *intr = &pdsc->intr_info[qcq->intx];
intr_dentry = debugfs_create_dir("intr", qcq->dentry);
if (IS_ERR(intr_dentry))
return;
debugfs_create_u32("index", 0400, intr_dentry, &intr->index);
debugfs_create_u32("vector", 0400, intr_dentry, &intr->vector);
intr_ctrl_regset = devm_kzalloc(pdsc->dev,
sizeof(*intr_ctrl_regset),
GFP_KERNEL);
if (!intr_ctrl_regset)
return;
intr_ctrl_regset->regs = intr_ctrl_regs;
intr_ctrl_regset->nregs = ARRAY_SIZE(intr_ctrl_regs);
intr_ctrl_regset->base = &pdsc->intr_ctrl[intr->index];
debugfs_create_regset32("intr_ctrl", 0400, intr_dentry,
intr_ctrl_regset);
}
};
void pdsc_debugfs_del_qcq(struct pdsc_qcq *qcq)
{
debugfs_remove_recursive(qcq->dentry);
qcq->dentry = NULL;
}
@@ -0,0 +1,389 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/utsname.h>
#include "core.h"
int pdsc_err_to_errno(enum pds_core_status_code code)
{
switch (code) {
case PDS_RC_SUCCESS:
return 0;
case PDS_RC_EVERSION:
case PDS_RC_EQTYPE:
case PDS_RC_EQID:
case PDS_RC_EINVAL:
case PDS_RC_ENOSUPP:
return -EINVAL;
case PDS_RC_EPERM:
return -EPERM;
case PDS_RC_ENOENT:
return -ENOENT;
case PDS_RC_EAGAIN:
return -EAGAIN;
case PDS_RC_ENOMEM:
return -ENOMEM;
case PDS_RC_EFAULT:
return -EFAULT;
case PDS_RC_EBUSY:
return -EBUSY;
case PDS_RC_EEXIST:
return -EEXIST;
case PDS_RC_EVFID:
return -ENODEV;
case PDS_RC_ECLIENT:
return -ECHILD;
case PDS_RC_ENOSPC:
return -ENOSPC;
case PDS_RC_ERANGE:
return -ERANGE;
case PDS_RC_BAD_ADDR:
return -EFAULT;
case PDS_RC_BAD_PCI:
return -ENXIO;
case PDS_RC_EOPCODE:
case PDS_RC_EINTR:
case PDS_RC_DEV_CMD:
case PDS_RC_ERROR:
case PDS_RC_ERDMA:
case PDS_RC_EIO:
default:
return -EIO;
}
}
bool pdsc_is_fw_running(struct pdsc *pdsc)
{
if (!pdsc->info_regs)
return false;
pdsc->fw_status = ioread8(&pdsc->info_regs->fw_status);
pdsc->last_fw_time = jiffies;
pdsc->last_hb = ioread32(&pdsc->info_regs->fw_heartbeat);
/* Firmware is useful only if the running bit is set and
* fw_status != 0xff (bad PCI read)
*/
return (pdsc->fw_status != PDS_RC_BAD_PCI) &&
(pdsc->fw_status & PDS_CORE_FW_STS_F_RUNNING);
}
bool pdsc_is_fw_good(struct pdsc *pdsc)
{
bool fw_running = pdsc_is_fw_running(pdsc);
u8 gen;
/* Make sure to update the cached fw_status by calling
* pdsc_is_fw_running() before getting the generation
*/
gen = pdsc->fw_status & PDS_CORE_FW_STS_F_GENERATION;
return fw_running && gen == pdsc->fw_generation;
}
static u8 pdsc_devcmd_status(struct pdsc *pdsc)
{
return ioread8(&pdsc->cmd_regs->comp.status);
}
static bool pdsc_devcmd_done(struct pdsc *pdsc)
{
return ioread32(&pdsc->cmd_regs->done) & PDS_CORE_DEV_CMD_DONE;
}
static void pdsc_devcmd_dbell(struct pdsc *pdsc)
{
iowrite32(0, &pdsc->cmd_regs->done);
iowrite32(1, &pdsc->cmd_regs->doorbell);
}
static void pdsc_devcmd_clean(struct pdsc *pdsc)
{
iowrite32(0, &pdsc->cmd_regs->doorbell);
memset_io(&pdsc->cmd_regs->cmd, 0, sizeof(pdsc->cmd_regs->cmd));
}
static const char *pdsc_devcmd_str(int opcode)
{
switch (opcode) {
case PDS_CORE_CMD_NOP:
return "PDS_CORE_CMD_NOP";
case PDS_CORE_CMD_IDENTIFY:
return "PDS_CORE_CMD_IDENTIFY";
case PDS_CORE_CMD_RESET:
return "PDS_CORE_CMD_RESET";
case PDS_CORE_CMD_INIT:
return "PDS_CORE_CMD_INIT";
case PDS_CORE_CMD_FW_DOWNLOAD:
return "PDS_CORE_CMD_FW_DOWNLOAD";
case PDS_CORE_CMD_FW_CONTROL:
return "PDS_CORE_CMD_FW_CONTROL";
default:
return "PDS_CORE_CMD_UNKNOWN";
}
}
static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds)
{
struct device *dev = pdsc->dev;
unsigned long start_time;
unsigned long max_wait;
unsigned long duration;
int timeout = 0;
bool running;
int done = 0;
int err = 0;
int status;
start_time = jiffies;
max_wait = start_time + (max_seconds * HZ);
while (!done && !timeout) {
running = pdsc_is_fw_running(pdsc);
if (!running)
break;
done = pdsc_devcmd_done(pdsc);
if (done)
break;
timeout = time_after(jiffies, max_wait);
if (timeout)
break;
usleep_range(100, 200);
}
duration = jiffies - start_time;
if (done && duration > HZ)
dev_dbg(dev, "DEVCMD %d %s after %ld secs\n",
opcode, pdsc_devcmd_str(opcode), duration / HZ);
if (!running) {
dev_err(dev, "DEVCMD %d %s fw not running\n",
opcode, pdsc_devcmd_str(opcode));
pdsc_devcmd_clean(pdsc);
return -ENXIO;
}
if (!done || timeout) {
dev_err(dev, "DEVCMD %d %s timeout, done %d timeout %d max_seconds=%d\n",
opcode, pdsc_devcmd_str(opcode), done, timeout,
max_seconds);
pdsc_devcmd_clean(pdsc);
return -ETIMEDOUT;
}
status = pdsc_devcmd_status(pdsc);
err = pdsc_err_to_errno(status);
if (err && err != -EAGAIN)
dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n",
opcode, pdsc_devcmd_str(opcode), status, err,
ERR_PTR(err));
return err;
}
int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
union pds_core_dev_comp *comp, int max_seconds)
{
int err;
if (!pdsc->cmd_regs)
return -ENXIO;
memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd));
pdsc_devcmd_dbell(pdsc);
err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds);
if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq)
queue_work(pdsc->wq, &pdsc->health_work);
else
memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp));
return err;
}
int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd,
union pds_core_dev_comp *comp, int max_seconds)
{
int err;
mutex_lock(&pdsc->devcmd_lock);
err = pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds);
mutex_unlock(&pdsc->devcmd_lock);
return err;
}
int pdsc_devcmd_init(struct pdsc *pdsc)
{
union pds_core_dev_comp comp = {};
union pds_core_dev_cmd cmd = {
.opcode = PDS_CORE_CMD_INIT,
};
return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
}
int pdsc_devcmd_reset(struct pdsc *pdsc)
{
union pds_core_dev_comp comp = {};
union pds_core_dev_cmd cmd = {
.reset.opcode = PDS_CORE_CMD_RESET,
};
if (!pdsc_is_fw_running(pdsc))
return 0;
return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
}
static int pdsc_devcmd_identify_locked(struct pdsc *pdsc)
{
union pds_core_dev_comp comp = {};
union pds_core_dev_cmd cmd = {
.identify.opcode = PDS_CORE_CMD_IDENTIFY,
.identify.ver = PDS_CORE_IDENTITY_VERSION_1,
};
return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
}
static void pdsc_init_devinfo(struct pdsc *pdsc)
{
pdsc->dev_info.asic_type = ioread8(&pdsc->info_regs->asic_type);
pdsc->dev_info.asic_rev = ioread8(&pdsc->info_regs->asic_rev);
pdsc->fw_generation = PDS_CORE_FW_STS_F_GENERATION &
ioread8(&pdsc->info_regs->fw_status);
memcpy_fromio(pdsc->dev_info.fw_version,
pdsc->info_regs->fw_version,
PDS_CORE_DEVINFO_FWVERS_BUFLEN);
pdsc->dev_info.fw_version[PDS_CORE_DEVINFO_FWVERS_BUFLEN] = 0;
memcpy_fromio(pdsc->dev_info.serial_num,
pdsc->info_regs->serial_num,
PDS_CORE_DEVINFO_SERIAL_BUFLEN);
pdsc->dev_info.serial_num[PDS_CORE_DEVINFO_SERIAL_BUFLEN] = 0;
dev_dbg(pdsc->dev, "fw_version %s\n", pdsc->dev_info.fw_version);
}
static int pdsc_identify(struct pdsc *pdsc)
{
struct pds_core_drv_identity drv = {};
size_t sz;
int err;
int n;
drv.drv_type = cpu_to_le32(PDS_DRIVER_LINUX);
/* Catching the return quiets a Wformat-truncation complaint */
n = snprintf(drv.driver_ver_str, sizeof(drv.driver_ver_str),
"%s %s", PDS_CORE_DRV_NAME, utsname()->release);
if (n > sizeof(drv.driver_ver_str))
dev_dbg(pdsc->dev, "release name truncated, don't care\n");
/* Next let's get some info about the device
* We use the devcmd_lock at this level in order to
* get safe access to the cmd_regs->data before anyone
* else can mess it up
*/
mutex_lock(&pdsc->devcmd_lock);
sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data));
memcpy_toio(&pdsc->cmd_regs->data, &drv, sz);
err = pdsc_devcmd_identify_locked(pdsc);
if (!err) {
sz = min_t(size_t, sizeof(pdsc->dev_ident),
sizeof(pdsc->cmd_regs->data));
memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz);
}
mutex_unlock(&pdsc->devcmd_lock);
if (err) {
dev_err(pdsc->dev, "Cannot identify device: %pe\n",
ERR_PTR(err));
return err;
}
if (isprint(pdsc->dev_info.fw_version[0]) &&
isascii(pdsc->dev_info.fw_version[0]))
dev_info(pdsc->dev, "FW: %.*s\n",
(int)(sizeof(pdsc->dev_info.fw_version) - 1),
pdsc->dev_info.fw_version);
else
dev_info(pdsc->dev, "FW: (invalid string) 0x%02x 0x%02x 0x%02x 0x%02x ...\n",
(u8)pdsc->dev_info.fw_version[0],
(u8)pdsc->dev_info.fw_version[1],
(u8)pdsc->dev_info.fw_version[2],
(u8)pdsc->dev_info.fw_version[3]);
return 0;
}
void pdsc_dev_uninit(struct pdsc *pdsc)
{
if (pdsc->intr_info) {
int i;
for (i = 0; i < pdsc->nintrs; i++)
pdsc_intr_free(pdsc, i);
kfree(pdsc->intr_info);
pdsc->intr_info = NULL;
pdsc->nintrs = 0;
}
pci_free_irq_vectors(pdsc->pdev);
}
int pdsc_dev_init(struct pdsc *pdsc)
{
unsigned int nintrs;
int err;
/* Initial init and reset of device */
pdsc_init_devinfo(pdsc);
pdsc->devcmd_timeout = PDS_CORE_DEVCMD_TIMEOUT;
err = pdsc_devcmd_reset(pdsc);
if (err)
return err;
err = pdsc_identify(pdsc);
if (err)
return err;
pdsc_debugfs_add_ident(pdsc);
/* Now we can reserve interrupts */
nintrs = le32_to_cpu(pdsc->dev_ident.nintrs);
nintrs = min_t(unsigned int, num_online_cpus(), nintrs);
/* Get intr_info struct array for tracking */
pdsc->intr_info = kzalloc_objs(*pdsc->intr_info, nintrs);
if (!pdsc->intr_info)
return -ENOMEM;
err = pci_alloc_irq_vectors(pdsc->pdev, nintrs, nintrs, PCI_IRQ_MSIX);
if (err != nintrs) {
dev_err(pdsc->dev, "Can't get %d intrs from OS: %pe\n",
nintrs, ERR_PTR(err));
err = -ENOSPC;
goto err_out;
}
pdsc->nintrs = nintrs;
return 0;
err_out:
kfree(pdsc->intr_info);
pdsc->intr_info = NULL;
return err;
}
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include "core.h"
#include <linux/pds/pds_auxbus.h>
static struct
pdsc_viftype *pdsc_dl_find_viftype_by_id(struct pdsc *pdsc,
enum devlink_param_type dl_id)
{
int vt;
if (!pdsc->viftype_status)
return NULL;
for (vt = 0; vt < PDS_DEV_TYPE_MAX; vt++) {
if (pdsc->viftype_status[vt].dl_id == dl_id)
return &pdsc->viftype_status[vt];
}
return NULL;
}
int pdsc_dl_enable_get(struct devlink *dl, u32 id,
struct devlink_param_gset_ctx *ctx,
struct netlink_ext_ack *extack)
{
struct pdsc *pdsc = devlink_priv(dl);
struct pdsc_viftype *vt_entry;
vt_entry = pdsc_dl_find_viftype_by_id(pdsc, id);
if (!vt_entry)
return -ENOENT;
ctx->val.vbool = vt_entry->enabled;
return 0;
}
int pdsc_dl_enable_set(struct devlink *dl, u32 id,
struct devlink_param_gset_ctx *ctx,
struct netlink_ext_ack *extack)
{
struct pdsc *pdsc = devlink_priv(dl);
struct pdsc_viftype *vt_entry;
int err = 0;
int vf_id;
vt_entry = pdsc_dl_find_viftype_by_id(pdsc, id);
if (!vt_entry || !vt_entry->supported)
return -EOPNOTSUPP;
if (vt_entry->enabled == ctx->val.vbool)
return 0;
vt_entry->enabled = ctx->val.vbool;
for (vf_id = 0; vf_id < pdsc->num_vfs; vf_id++) {
struct pdsc *vf = pdsc->vfs[vf_id].vf;
if (ctx->val.vbool)
err = pdsc_auxbus_dev_add(vf, pdsc, vt_entry->vif_id,
&pdsc->vfs[vf_id].padev);
else
pdsc_auxbus_dev_del(vf, pdsc, &pdsc->vfs[vf_id].padev);
}
return err;
}
int pdsc_dl_enable_validate(struct devlink *dl, u32 id,
union devlink_param_value *val,
struct netlink_ext_ack *extack)
{
struct pdsc *pdsc = devlink_priv(dl);
struct pdsc_viftype *vt_entry;
vt_entry = pdsc_dl_find_viftype_by_id(pdsc, id);
if (!vt_entry || !vt_entry->supported)
return -EOPNOTSUPP;
if (!pdsc->viftype_status[vt_entry->vif_id].supported)
return -ENODEV;
return 0;
}
int pdsc_dl_flash_update(struct devlink *dl,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack)
{
struct pdsc *pdsc = devlink_priv(dl);
if (params->component) {
NL_SET_ERR_MSG_MOD(extack,
"Component update not supported by this device");
return -EOPNOTSUPP;
}
return pdsc_firmware_update(pdsc, params->fw, extack);
}
static char *fw_slotnames[] = {
"fw.goldfw",
"fw.mainfwa",
"fw.mainfwb",
};
int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
union pds_core_dev_cmd cmd = {
.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
.fw_control.oper = PDS_CORE_FW_GET_LIST,
};
struct pds_core_fw_list_info fw_list = {};
struct pdsc *pdsc = devlink_priv(dl);
union pds_core_dev_comp comp;
char buf[32];
int listlen;
int err;
int i;
mutex_lock(&pdsc->devcmd_lock);
err = pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout * 2);
if (!err)
memcpy_fromio(&fw_list, pdsc->cmd_regs->data, sizeof(fw_list));
mutex_unlock(&pdsc->devcmd_lock);
listlen = min(fw_list.num_fw_slots, ARRAY_SIZE(fw_list.fw_names));
for (i = 0; i < listlen; i++) {
char *fw_ver = fw_list.fw_names[i].fw_version;
if (i < ARRAY_SIZE(fw_slotnames))
strscpy(buf, fw_slotnames[i], sizeof(buf));
else
snprintf(buf, sizeof(buf), "fw.slot_%d", i);
fw_ver[sizeof(fw_list.fw_names[i].fw_version) - 1] = '\0';
err = devlink_info_version_stored_put(req, buf, fw_ver);
if (err)
return err;
}
err = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW,
pdsc->dev_info.fw_version);
if (err)
return err;
snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_type);
err = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
buf);
if (err)
return err;
snprintf(buf, sizeof(buf), "0x%x", pdsc->dev_info.asic_rev);
err = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_REV,
buf);
if (err)
return err;
return devlink_info_serial_number_put(req, pdsc->dev_info.serial_num);
}
int pdsc_fw_reporter_diagnose(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg,
struct netlink_ext_ack *extack)
{
struct pdsc *pdsc = devlink_health_reporter_priv(reporter);
mutex_lock(&pdsc->config_lock);
if (test_bit(PDSC_S_FW_DEAD, &pdsc->state))
devlink_fmsg_string_pair_put(fmsg, "Status", "dead");
else if (!pdsc_is_fw_good(pdsc))
devlink_fmsg_string_pair_put(fmsg, "Status", "unhealthy");
else
devlink_fmsg_string_pair_put(fmsg, "Status", "healthy");
mutex_unlock(&pdsc->config_lock);
devlink_fmsg_u32_pair_put(fmsg, "State",
pdsc->fw_status & ~PDS_CORE_FW_STS_F_GENERATION);
devlink_fmsg_u32_pair_put(fmsg, "Generation", pdsc->fw_generation >> 4);
devlink_fmsg_u32_pair_put(fmsg, "Recoveries", pdsc->fw_recoveries);
return 0;
}
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include "core.h"
/* The worst case wait for the install activity is about 25 minutes when
* installing a new CPLD, which is very seldom. Normal is about 30-35
* seconds. Since the driver can't tell if a CPLD update will happen we
* set the timeout for the ugly case.
*/
#define PDSC_FW_INSTALL_TIMEOUT (25 * 60)
#define PDSC_FW_SELECT_TIMEOUT 30
/* Number of periodic log updates during fw file download */
#define PDSC_FW_INTERVAL_FRACTION 32
static int pdsc_devcmd_fw_download_locked(struct pdsc *pdsc, u64 addr,
u32 offset, u32 length)
{
union pds_core_dev_cmd cmd = {
.fw_download.opcode = PDS_CORE_CMD_FW_DOWNLOAD,
.fw_download.offset = cpu_to_le32(offset),
.fw_download.addr = cpu_to_le64(addr),
.fw_download.length = cpu_to_le32(length),
};
union pds_core_dev_comp comp;
return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
}
static int pdsc_devcmd_fw_install(struct pdsc *pdsc)
{
union pds_core_dev_cmd cmd = {
.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
.fw_control.oper = PDS_CORE_FW_INSTALL_ASYNC
};
union pds_core_dev_comp comp;
int err;
err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
if (err < 0)
return err;
return comp.fw_control.slot;
}
static int pdsc_devcmd_fw_activate(struct pdsc *pdsc,
enum pds_core_fw_slot slot)
{
union pds_core_dev_cmd cmd = {
.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
.fw_control.oper = PDS_CORE_FW_ACTIVATE_ASYNC,
.fw_control.slot = slot
};
union pds_core_dev_comp comp;
return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
}
static int pdsc_fw_status_long_wait(struct pdsc *pdsc,
const char *label,
unsigned long timeout,
u8 fw_cmd,
struct netlink_ext_ack *extack)
{
union pds_core_dev_cmd cmd = {
.fw_control.opcode = PDS_CORE_CMD_FW_CONTROL,
.fw_control.oper = fw_cmd,
};
union pds_core_dev_comp comp;
unsigned long start_time;
unsigned long end_time;
int err;
/* Ping on the status of the long running async install
* command. We get EAGAIN while the command is still
* running, else we get the final command status.
*/
start_time = jiffies;
end_time = start_time + (timeout * HZ);
do {
err = pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
msleep(20);
} while (time_before(jiffies, end_time) &&
(err == -EAGAIN || err == -ETIMEDOUT));
if (err == -EAGAIN || err == -ETIMEDOUT) {
NL_SET_ERR_MSG_MOD(extack, "Firmware wait timed out");
dev_err(pdsc->dev, "DEV_CMD firmware wait %s timed out\n",
label);
} else if (err) {
NL_SET_ERR_MSG_MOD(extack, "Firmware wait failed");
}
return err;
}
int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
struct netlink_ext_ack *extack)
{
u32 buf_sz, copy_sz, offset;
struct devlink *dl;
int next_interval;
u64 data_addr;
int err = 0;
int fw_slot;
dev_info(pdsc->dev, "Installing firmware\n");
if (!pdsc->cmd_regs)
return -ENXIO;
dl = priv_to_devlink(pdsc);
devlink_flash_update_status_notify(dl, "Preparing to flash",
NULL, 0, 0);
buf_sz = sizeof(pdsc->cmd_regs->data);
dev_dbg(pdsc->dev,
"downloading firmware - size %d part_sz %d nparts %lu\n",
(int)fw->size, buf_sz, DIV_ROUND_UP(fw->size, buf_sz));
offset = 0;
next_interval = 0;
data_addr = offsetof(struct pds_core_dev_cmd_regs, data);
while (offset < fw->size) {
if (offset >= next_interval) {
devlink_flash_update_status_notify(dl, "Downloading",
NULL, offset,
fw->size);
next_interval = offset +
(fw->size / PDSC_FW_INTERVAL_FRACTION);
}
copy_sz = min_t(unsigned int, buf_sz, fw->size - offset);
mutex_lock(&pdsc->devcmd_lock);
memcpy_toio(&pdsc->cmd_regs->data, fw->data + offset, copy_sz);
err = pdsc_devcmd_fw_download_locked(pdsc, data_addr,
offset, copy_sz);
mutex_unlock(&pdsc->devcmd_lock);
if (err) {
dev_err(pdsc->dev,
"download failed offset 0x%x addr 0x%llx len 0x%x: %pe\n",
offset, data_addr, copy_sz, ERR_PTR(err));
NL_SET_ERR_MSG_MOD(extack, "Segment download failed");
goto err_out;
}
offset += copy_sz;
}
devlink_flash_update_status_notify(dl, "Downloading", NULL,
fw->size, fw->size);
devlink_flash_update_timeout_notify(dl, "Installing", NULL,
PDSC_FW_INSTALL_TIMEOUT);
fw_slot = pdsc_devcmd_fw_install(pdsc);
if (fw_slot < 0) {
err = fw_slot;
dev_err(pdsc->dev, "install failed: %pe\n", ERR_PTR(err));
NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware install");
goto err_out;
}
err = pdsc_fw_status_long_wait(pdsc, "Installing",
PDSC_FW_INSTALL_TIMEOUT,
PDS_CORE_FW_INSTALL_STATUS,
extack);
if (err)
goto err_out;
devlink_flash_update_timeout_notify(dl, "Selecting", NULL,
PDSC_FW_SELECT_TIMEOUT);
err = pdsc_devcmd_fw_activate(pdsc, fw_slot);
if (err) {
NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware select");
goto err_out;
}
err = pdsc_fw_status_long_wait(pdsc, "Selecting",
PDSC_FW_SELECT_TIMEOUT,
PDS_CORE_FW_ACTIVATE_STATUS,
extack);
if (err)
goto err_out;
dev_info(pdsc->dev, "Firmware update completed, slot %d\n", fw_slot);
err_out:
if (err)
devlink_flash_update_status_notify(dl, "Flash failed",
NULL, 0, 0);
else
devlink_flash_update_status_notify(dl, "Flash done",
NULL, 0, 0);
return err;
}
@@ -0,0 +1,624 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/pci.h>
#include <linux/pds/pds_common.h>
#include "core.h"
MODULE_DESCRIPTION(PDSC_DRV_DESCRIPTION);
MODULE_AUTHOR("Advanced Micro Devices, Inc");
MODULE_LICENSE("GPL");
/* Supported devices */
static const struct pci_device_id pdsc_id_table[] = {
{ PCI_VDEVICE(PENSANDO, PCI_DEVICE_ID_PENSANDO_CORE_PF) },
{ PCI_VDEVICE(PENSANDO, PCI_DEVICE_ID_PENSANDO_VDPA_VF) },
{ 0, } /* end of table */
};
MODULE_DEVICE_TABLE(pci, pdsc_id_table);
static void pdsc_wdtimer_cb(struct timer_list *t)
{
struct pdsc *pdsc = timer_container_of(pdsc, t, wdtimer);
dev_dbg(pdsc->dev, "%s: jiffies %ld\n", __func__, jiffies);
mod_timer(&pdsc->wdtimer,
round_jiffies(jiffies + pdsc->wdtimer_period));
queue_work(pdsc->wq, &pdsc->health_work);
}
static void pdsc_unmap_bars(struct pdsc *pdsc)
{
struct pdsc_dev_bar *bars = pdsc->bars;
unsigned int i;
pdsc->info_regs = NULL;
pdsc->cmd_regs = NULL;
pdsc->intr_status = NULL;
pdsc->intr_ctrl = NULL;
for (i = 0; i < PDS_CORE_BARS_MAX; i++) {
if (bars[i].vaddr)
pci_iounmap(pdsc->pdev, bars[i].vaddr);
bars[i].vaddr = NULL;
}
}
static int pdsc_map_bars(struct pdsc *pdsc)
{
struct pdsc_dev_bar *bar = pdsc->bars;
struct pci_dev *pdev = pdsc->pdev;
struct device *dev = pdsc->dev;
struct pdsc_dev_bar *bars;
unsigned int i, j;
int num_bars = 0;
int err;
u32 sig;
bars = pdsc->bars;
/* Since the PCI interface in the hardware is configurable,
* we need to poke into all the bars to find the set we're
* expecting.
*/
for (i = 0, j = 0; i < PDS_CORE_BARS_MAX; i++) {
if (!(pci_resource_flags(pdev, i) & IORESOURCE_MEM))
continue;
bars[j].len = pci_resource_len(pdev, i);
bars[j].bus_addr = pci_resource_start(pdev, i);
bars[j].res_index = i;
/* only map the whole bar 0 */
if (j > 0) {
bars[j].vaddr = NULL;
} else {
bars[j].vaddr = pci_iomap(pdev, i, bars[j].len);
if (!bars[j].vaddr) {
dev_err(dev, "Cannot map BAR %d, aborting\n", i);
return -ENODEV;
}
}
j++;
}
num_bars = j;
/* BAR0: dev_cmd and interrupts */
if (num_bars < 1) {
dev_err(dev, "No bars found\n");
err = -EFAULT;
goto err_out;
}
if (bar->len < PDS_CORE_BAR0_SIZE) {
dev_err(dev, "Resource bar size %lu too small\n", bar->len);
err = -EFAULT;
goto err_out;
}
pdsc->info_regs = bar->vaddr + PDS_CORE_BAR0_DEV_INFO_REGS_OFFSET;
pdsc->cmd_regs = bar->vaddr + PDS_CORE_BAR0_DEV_CMD_REGS_OFFSET;
pdsc->intr_status = bar->vaddr + PDS_CORE_BAR0_INTR_STATUS_OFFSET;
pdsc->intr_ctrl = bar->vaddr + PDS_CORE_BAR0_INTR_CTRL_OFFSET;
sig = ioread32(&pdsc->info_regs->signature);
if (sig != PDS_CORE_DEV_INFO_SIGNATURE) {
dev_err(dev, "Incompatible firmware signature %x", sig);
err = -EFAULT;
goto err_out;
}
/* BAR1: doorbells */
bar++;
if (num_bars < 2) {
dev_err(dev, "Doorbell bar missing\n");
err = -EFAULT;
goto err_out;
}
pdsc->db_pages = bar->vaddr;
pdsc->phy_db_pages = bar->bus_addr;
return 0;
err_out:
pdsc_unmap_bars(pdsc);
return err;
}
void __iomem *pdsc_map_dbpage(struct pdsc *pdsc, int page_num)
{
return pci_iomap_range(pdsc->pdev,
pdsc->bars[PDS_CORE_PCI_BAR_DBELL].res_index,
(u64)page_num << PAGE_SHIFT, PAGE_SIZE);
}
static int pdsc_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
struct pdsc *pdsc = pci_get_drvdata(pdev);
struct device *dev = pdsc->dev;
int ret = 0;
if (num_vfs > 0) {
pdsc->vfs = kzalloc_objs(struct pdsc_vf, num_vfs);
if (!pdsc->vfs)
return -ENOMEM;
pdsc->num_vfs = num_vfs;
ret = pci_enable_sriov(pdev, num_vfs);
if (ret) {
dev_err(dev, "Cannot enable SRIOV: %pe\n",
ERR_PTR(ret));
goto no_vfs;
}
return num_vfs;
}
no_vfs:
pci_disable_sriov(pdev);
kfree(pdsc->vfs);
pdsc->vfs = NULL;
pdsc->num_vfs = 0;
return ret;
}
static int pdsc_init_vf(struct pdsc *vf)
{
struct devlink *dl;
struct pdsc *pf;
int err;
pf = pdsc_get_pf_struct(vf->pdev);
if (IS_ERR_OR_NULL(pf))
return PTR_ERR(pf) ?: -1;
vf->vf_id = pci_iov_vf_id(vf->pdev);
dl = priv_to_devlink(vf);
devl_lock(dl);
devl_register(dl);
devl_unlock(dl);
pf->vfs[vf->vf_id].vf = vf;
err = pdsc_auxbus_dev_add(vf, pf, PDS_DEV_TYPE_VDPA,
&pf->vfs[vf->vf_id].padev);
if (err) {
devl_lock(dl);
devl_unregister(dl);
devl_unlock(dl);
}
return err;
}
static const struct devlink_health_reporter_ops pdsc_fw_reporter_ops = {
.name = "fw",
.diagnose = pdsc_fw_reporter_diagnose,
};
static const struct devlink_param pdsc_dl_params[] = {
DEVLINK_PARAM_GENERIC(ENABLE_VNET,
BIT(DEVLINK_PARAM_CMODE_RUNTIME),
pdsc_dl_enable_get,
pdsc_dl_enable_set,
pdsc_dl_enable_validate),
};
#define PDSC_WQ_NAME_LEN 24
static int pdsc_init_pf(struct pdsc *pdsc)
{
struct devlink_health_reporter *hr;
char wq_name[PDSC_WQ_NAME_LEN];
struct devlink *dl;
int err;
pcie_print_link_status(pdsc->pdev);
err = pci_request_regions(pdsc->pdev, PDS_CORE_DRV_NAME);
if (err) {
dev_err(pdsc->dev, "Cannot request PCI regions: %pe\n",
ERR_PTR(err));
return err;
}
err = pdsc_map_bars(pdsc);
if (err)
goto err_out_release_regions;
/* General workqueue and timer, but don't start timer yet */
snprintf(wq_name, sizeof(wq_name), "%s.%d", PDS_CORE_DRV_NAME, pdsc->uid);
pdsc->wq = create_singlethread_workqueue(wq_name);
if (!pdsc->wq) {
err = -ENOMEM;
goto err_out_unmap_bars;
}
INIT_WORK(&pdsc->health_work, pdsc_health_thread);
INIT_WORK(&pdsc->pci_reset_work, pdsc_pci_reset_thread);
timer_setup(&pdsc->wdtimer, pdsc_wdtimer_cb, 0);
pdsc->wdtimer_period = PDSC_WATCHDOG_SECS * HZ;
mutex_init(&pdsc->devcmd_lock);
mutex_init(&pdsc->config_lock);
spin_lock_init(&pdsc->adminq_lock);
mutex_lock(&pdsc->config_lock);
set_bit(PDSC_S_FW_DEAD, &pdsc->state);
err = pdsc_setup(pdsc, PDSC_SETUP_INIT);
if (err) {
mutex_unlock(&pdsc->config_lock);
goto err_out_shutdown_timer;
}
err = pdsc_start(pdsc);
if (err) {
mutex_unlock(&pdsc->config_lock);
goto err_out_teardown;
}
mutex_unlock(&pdsc->config_lock);
err = pdsc_auxbus_dev_add(pdsc, pdsc, PDS_DEV_TYPE_FWCTL, &pdsc->padev);
if (err)
goto err_out_stop;
dl = priv_to_devlink(pdsc);
devl_lock(dl);
err = devl_params_register(dl, pdsc_dl_params,
ARRAY_SIZE(pdsc_dl_params));
if (err) {
devl_unlock(dl);
dev_warn(pdsc->dev, "Failed to register devlink params: %pe\n",
ERR_PTR(err));
goto err_out_del_dev;
}
hr = devl_health_reporter_create(dl, &pdsc_fw_reporter_ops, pdsc);
if (IS_ERR(hr)) {
devl_unlock(dl);
dev_warn(pdsc->dev, "Failed to create fw reporter: %pe\n", hr);
err = PTR_ERR(hr);
goto err_out_unreg_params;
}
pdsc->fw_reporter = hr;
devl_register(dl);
devl_unlock(dl);
/* Lastly, start the health check timer */
mod_timer(&pdsc->wdtimer, round_jiffies(jiffies + pdsc->wdtimer_period));
return 0;
err_out_unreg_params:
devlink_params_unregister(dl, pdsc_dl_params,
ARRAY_SIZE(pdsc_dl_params));
err_out_del_dev:
pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
err_out_stop:
pdsc_stop(pdsc);
err_out_teardown:
pdsc_teardown(pdsc, PDSC_TEARDOWN_REMOVING);
err_out_shutdown_timer:
timer_shutdown_sync(&pdsc->wdtimer);
if (pdsc->wq)
destroy_workqueue(pdsc->wq);
mutex_destroy(&pdsc->config_lock);
mutex_destroy(&pdsc->devcmd_lock);
pci_free_irq_vectors(pdsc->pdev);
err_out_unmap_bars:
pdsc_unmap_bars(pdsc);
err_out_release_regions:
pci_release_regions(pdsc->pdev);
return err;
}
static const struct devlink_ops pdsc_dl_ops = {
.info_get = pdsc_dl_info_get,
.flash_update = pdsc_dl_flash_update,
};
static const struct devlink_ops pdsc_dl_vf_ops = {
};
static DEFINE_IDA(pdsc_ida);
static int pdsc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct device *dev = &pdev->dev;
const struct devlink_ops *ops;
struct devlink *dl;
struct pdsc *pdsc;
bool is_pf;
int err;
is_pf = !pdev->is_virtfn;
ops = is_pf ? &pdsc_dl_ops : &pdsc_dl_vf_ops;
dl = devlink_alloc(ops, sizeof(struct pdsc), dev);
if (!dl)
return -ENOMEM;
pdsc = devlink_priv(dl);
pdsc->pdev = pdev;
pdsc->dev = &pdev->dev;
set_bit(PDSC_S_INITING_DRIVER, &pdsc->state);
pci_set_drvdata(pdev, pdsc);
pdsc_debugfs_add_dev(pdsc);
err = ida_alloc(&pdsc_ida, GFP_KERNEL);
if (err < 0) {
dev_err(pdsc->dev, "%s: id alloc failed: %pe\n",
__func__, ERR_PTR(err));
goto err_out_free_devlink;
}
pdsc->uid = err;
/* Query system for DMA addressing limitation for the device. */
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(PDS_CORE_ADDR_LEN));
if (err) {
dev_err(dev, "Unable to obtain 64-bit DMA for consistent allocations, aborting: %pe\n",
ERR_PTR(err));
goto err_out_free_ida;
}
err = pci_enable_device(pdev);
if (err) {
dev_err(dev, "Cannot enable PCI device: %pe\n", ERR_PTR(err));
goto err_out_free_ida;
}
pci_set_master(pdev);
if (is_pf)
err = pdsc_init_pf(pdsc);
else
err = pdsc_init_vf(pdsc);
if (err) {
dev_err(dev, "Cannot init device: %pe\n", ERR_PTR(err));
goto err_out_disable_device;
}
clear_bit(PDSC_S_INITING_DRIVER, &pdsc->state);
return 0;
err_out_disable_device:
pci_disable_device(pdev);
err_out_free_ida:
ida_free(&pdsc_ida, pdsc->uid);
err_out_free_devlink:
pdsc_debugfs_del_dev(pdsc);
devlink_free(dl);
return err;
}
static void pdsc_remove(struct pci_dev *pdev)
{
struct pdsc *pdsc = pci_get_drvdata(pdev);
struct devlink *dl;
/* Unhook the registrations first to be sure there
* are no requests while we're stopping.
*/
dl = priv_to_devlink(pdsc);
devl_lock(dl);
devl_unregister(dl);
if (!pdev->is_virtfn) {
if (pdsc->fw_reporter) {
devl_health_reporter_destroy(pdsc->fw_reporter);
pdsc->fw_reporter = NULL;
}
devl_params_unregister(dl, pdsc_dl_params,
ARRAY_SIZE(pdsc_dl_params));
}
devl_unlock(dl);
if (pdev->is_virtfn) {
struct pdsc *pf;
pf = pdsc_get_pf_struct(pdsc->pdev);
if (!IS_ERR(pf)) {
pdsc_auxbus_dev_del(pdsc, pf, &pf->vfs[pdsc->vf_id].padev);
pf->vfs[pdsc->vf_id].vf = NULL;
}
} else {
/* Remove the VFs and their aux_bus connections before other
* cleanup so that the clients can use the AdminQ to cleanly
* shut themselves down.
*/
pdsc_sriov_configure(pdev, 0);
pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
timer_shutdown_sync(&pdsc->wdtimer);
mutex_lock(&pdsc->config_lock);
set_bit(PDSC_S_STOPPING_DRIVER, &pdsc->state);
pdsc_stop(pdsc);
pdsc_teardown(pdsc, PDSC_TEARDOWN_REMOVING);
mutex_unlock(&pdsc->config_lock);
if (pdsc->wq)
destroy_workqueue(pdsc->wq);
mutex_destroy(&pdsc->config_lock);
mutex_destroy(&pdsc->devcmd_lock);
pdsc_unmap_bars(pdsc);
pci_release_regions(pdev);
}
pci_disable_device(pdev);
ida_free(&pdsc_ida, pdsc->uid);
pdsc_debugfs_del_dev(pdsc);
devlink_free(dl);
}
static void pdsc_stop_health_thread(struct pdsc *pdsc)
{
if (pdsc->pdev->is_virtfn)
return;
timer_shutdown_sync(&pdsc->wdtimer);
if (pdsc->health_work.func && !pdsc->health_stopped) {
disable_work_sync(&pdsc->health_work);
pdsc->health_stopped = true;
}
}
static void pdsc_restart_health_thread(struct pdsc *pdsc)
{
if (pdsc->pdev->is_virtfn)
return;
if (pdsc->health_stopped) {
enable_work(&pdsc->health_work);
pdsc->health_stopped = false;
}
timer_setup(&pdsc->wdtimer, pdsc_wdtimer_cb, 0);
mod_timer(&pdsc->wdtimer, jiffies + 1);
}
static void pdsc_reset_prepare(struct pci_dev *pdev)
{
struct pdsc *pdsc = pci_get_drvdata(pdev);
pdsc_stop_health_thread(pdsc);
pdsc_fw_down(pdsc);
if (pdev->is_virtfn) {
struct pdsc *pf;
pf = pdsc_get_pf_struct(pdsc->pdev);
if (!IS_ERR(pf))
pdsc_auxbus_dev_del(pdsc, pf,
&pf->vfs[pdsc->vf_id].padev);
} else {
pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
}
pdsc_unmap_bars(pdsc);
pci_release_regions(pdev);
if (pci_is_enabled(pdev))
pci_disable_device(pdev);
}
static void pdsc_reset_done(struct pci_dev *pdev)
{
struct pdsc *pdsc = pci_get_drvdata(pdev);
struct device *dev = pdsc->dev;
int err;
err = pci_enable_device(pdev);
if (err) {
dev_err(dev, "Cannot enable PCI device: %pe\n", ERR_PTR(err));
return;
}
pci_set_master(pdev);
if (!pdev->is_virtfn) {
pcie_print_link_status(pdsc->pdev);
err = pci_request_regions(pdsc->pdev, PDS_CORE_DRV_NAME);
if (err) {
dev_err(pdsc->dev, "Cannot request PCI regions: %pe\n",
ERR_PTR(err));
return;
}
err = pdsc_map_bars(pdsc);
if (err)
return;
}
pdsc_fw_up(pdsc);
pdsc_restart_health_thread(pdsc);
if (pdev->is_virtfn) {
struct pdsc *pf;
pf = pdsc_get_pf_struct(pdsc->pdev);
if (!IS_ERR(pf))
pdsc_auxbus_dev_add(pdsc, pf, PDS_DEV_TYPE_VDPA,
&pf->vfs[pdsc->vf_id].padev);
} else {
pdsc_auxbus_dev_add(pdsc, pdsc, PDS_DEV_TYPE_FWCTL,
&pdsc->padev);
}
}
static pci_ers_result_t pdsc_pci_error_detected(struct pci_dev *pdev,
pci_channel_state_t error)
{
if (error == pci_channel_io_frozen) {
struct pdsc *pdsc = pci_get_drvdata(pdev);
pdsc_reset_prepare(pdev);
if (!pdev->is_virtfn)
cancel_work_sync(&pdsc->pci_reset_work);
return PCI_ERS_RESULT_NEED_RESET;
}
return PCI_ERS_RESULT_NONE;
}
static void pdsc_pci_error_resume(struct pci_dev *pdev)
{
struct pdsc *pdsc = pci_get_drvdata(pdev);
if (test_bit(PDSC_S_FW_DEAD, &pdsc->state))
pci_reset_function_locked(pdev);
}
static const struct pci_error_handlers pdsc_err_handler = {
/* FLR handling */
.reset_prepare = pdsc_reset_prepare,
.reset_done = pdsc_reset_done,
/* AER handling */
.error_detected = pdsc_pci_error_detected,
.resume = pdsc_pci_error_resume,
};
static struct pci_driver pdsc_driver = {
.name = PDS_CORE_DRV_NAME,
.id_table = pdsc_id_table,
.probe = pdsc_probe,
.remove = pdsc_remove,
.sriov_configure = pdsc_sriov_configure,
.err_handler = &pdsc_err_handler,
};
void *pdsc_get_pf_struct(struct pci_dev *vf_pdev)
{
return pci_iov_get_pf_drvdata(vf_pdev, &pdsc_driver);
}
EXPORT_SYMBOL_GPL(pdsc_get_pf_struct);
static int __init pdsc_init_module(void)
{
if (strcmp(KBUILD_MODNAME, PDS_CORE_DRV_NAME))
return -EINVAL;
pdsc_debugfs_create();
return pci_register_driver(&pdsc_driver);
}
static void __exit pdsc_cleanup_module(void)
{
pci_unregister_driver(&pdsc_driver);
pdsc_debugfs_destroy();
}
module_init(pdsc_init_module);
module_exit(pdsc_cleanup_module);
@@ -0,0 +1,939 @@
/* sun3lance.c: Ethernet driver for SUN3 Lance chip */
/*
Sun3 Lance ethernet driver, by Sam Creasey (sammy@users.qual.net).
This driver is a part of the linux kernel, and is thus distributed
under the GNU General Public License.
The values used in LANCE_OBIO and LANCE_IRQ seem to be empirically
true for the correct IRQ and address of the lance registers. They
have not been widely tested, however. What we probably need is a
"proper" way to search for a device in the sun3's prom, but, alas,
linux has no such thing.
This driver is largely based on atarilance.c, by Roman Hodek. Other
sources of inspiration were the NetBSD sun3 am7990 driver, and the
linux sparc lance driver (sunlance.c).
There are more assumptions made throughout this driver, it almost
certainly still needs work, but it does work at least for RARP/BOOTP and
mounting the root NFS filesystem.
*/
static const char version[] =
"sun3lance.c: v1.2 1/12/2001 Sam Creasey (sammy@sammy.net)\n";
#include <linux/module.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/bitops.h>
#include <linux/pgtable.h>
#include <asm/cacheflush.h>
#include <asm/setup.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/dvma.h>
#include <asm/idprom.h>
#include <asm/machines.h>
#ifdef CONFIG_SUN3
#include <asm/sun3mmu.h>
#else
#include <asm/sun3xprom.h>
#endif
/* sun3/60 addr/irq for the lance chip. If your sun is different,
change this. */
#define LANCE_OBIO 0x120000
#define LANCE_IRQ IRQ_AUTO_3
/* Debug level:
* 0 = silent, print only serious errors
* 1 = normal, print error messages
* 2 = debug, print debug infos
* 3 = debug, print even more debug infos (packet data)
*/
#define LANCE_DEBUG 0
#ifdef LANCE_DEBUG
static int lance_debug = LANCE_DEBUG;
#else
static int lance_debug = 1;
#endif
module_param(lance_debug, int, 0);
MODULE_PARM_DESC(lance_debug, "SUN3 Lance debug level (0-3)");
MODULE_DESCRIPTION("Sun3/Sun3x on-board LANCE Ethernet driver");
MODULE_LICENSE("GPL");
#define DPRINTK(n,a) \
do { \
if (lance_debug >= n) \
printk a; \
} while( 0 )
/* we're only using 32k of memory, so we use 4 TX
buffers and 16 RX buffers. These values are expressed as log2. */
#define TX_LOG_RING_SIZE 3
#define RX_LOG_RING_SIZE 5
/* These are the derived values */
#define TX_RING_SIZE (1 << TX_LOG_RING_SIZE)
#define TX_RING_LEN_BITS (TX_LOG_RING_SIZE << 5)
#define TX_RING_MOD_MASK (TX_RING_SIZE - 1)
#define RX_RING_SIZE (1 << RX_LOG_RING_SIZE)
#define RX_RING_LEN_BITS (RX_LOG_RING_SIZE << 5)
#define RX_RING_MOD_MASK (RX_RING_SIZE - 1)
/* Definitions for packet buffer access: */
#define PKT_BUF_SZ 1544
/* Get the address of a packet buffer corresponding to a given buffer head */
#define PKTBUF_ADDR(head) (void *)((unsigned long)(MEM) | (head)->base)
/* The LANCE Rx and Tx ring descriptors. */
struct lance_rx_head {
unsigned short base; /* Low word of base addr */
volatile unsigned char flag;
unsigned char base_hi; /* High word of base addr (unused) */
short buf_length; /* This length is 2s complement! */
volatile short msg_length; /* This length is "normal". */
};
struct lance_tx_head {
unsigned short base; /* Low word of base addr */
volatile unsigned char flag;
unsigned char base_hi; /* High word of base addr (unused) */
short length; /* Length is 2s complement! */
volatile short misc;
};
/* The LANCE initialization block, described in databook. */
struct lance_init_block {
unsigned short mode; /* Pre-set mode */
unsigned char hwaddr[6]; /* Physical ethernet address */
unsigned int filter[2]; /* Multicast filter (unused). */
/* Receive and transmit ring base, along with length bits. */
unsigned short rdra;
unsigned short rlen;
unsigned short tdra;
unsigned short tlen;
unsigned short pad[4]; /* is thie needed? */
};
/* The whole layout of the Lance shared memory */
struct lance_memory {
struct lance_init_block init;
struct lance_tx_head tx_head[TX_RING_SIZE];
struct lance_rx_head rx_head[RX_RING_SIZE];
char rx_data[RX_RING_SIZE][PKT_BUF_SZ];
char tx_data[TX_RING_SIZE][PKT_BUF_SZ];
};
/* The driver's private device structure */
struct lance_private {
volatile unsigned short *iobase;
struct lance_memory *mem;
int new_rx, new_tx; /* The next free ring entry */
int old_tx, old_rx; /* ring entry to be processed */
/* These two must be longs for set_bit() */
long tx_full;
long lock;
};
/* I/O register access macros */
#define MEM lp->mem
#define DREG lp->iobase[0]
#define AREG lp->iobase[1]
#define REGA(a) (*( AREG = (a), &DREG ))
/* Definitions for the Lance */
/* tx_head flags */
#define TMD1_ENP 0x01 /* end of packet */
#define TMD1_STP 0x02 /* start of packet */
#define TMD1_DEF 0x04 /* deferred */
#define TMD1_ONE 0x08 /* one retry needed */
#define TMD1_MORE 0x10 /* more than one retry needed */
#define TMD1_ERR 0x40 /* error summary */
#define TMD1_OWN 0x80 /* ownership (set: chip owns) */
#define TMD1_OWN_CHIP TMD1_OWN
#define TMD1_OWN_HOST 0
/* tx_head misc field */
#define TMD3_TDR 0x03FF /* Time Domain Reflectometry counter */
#define TMD3_RTRY 0x0400 /* failed after 16 retries */
#define TMD3_LCAR 0x0800 /* carrier lost */
#define TMD3_LCOL 0x1000 /* late collision */
#define TMD3_UFLO 0x4000 /* underflow (late memory) */
#define TMD3_BUFF 0x8000 /* buffering error (no ENP) */
/* rx_head flags */
#define RMD1_ENP 0x01 /* end of packet */
#define RMD1_STP 0x02 /* start of packet */
#define RMD1_BUFF 0x04 /* buffer error */
#define RMD1_CRC 0x08 /* CRC error */
#define RMD1_OFLO 0x10 /* overflow */
#define RMD1_FRAM 0x20 /* framing error */
#define RMD1_ERR 0x40 /* error summary */
#define RMD1_OWN 0x80 /* ownership (set: ship owns) */
#define RMD1_OWN_CHIP RMD1_OWN
#define RMD1_OWN_HOST 0
/* register names */
#define CSR0 0 /* mode/status */
#define CSR1 1 /* init block addr (low) */
#define CSR2 2 /* init block addr (high) */
#define CSR3 3 /* misc */
#define CSR8 8 /* address filter */
#define CSR15 15 /* promiscuous mode */
/* CSR0 */
/* (R=readable, W=writeable, S=set on write, C=clear on write) */
#define CSR0_INIT 0x0001 /* initialize (RS) */
#define CSR0_STRT 0x0002 /* start (RS) */
#define CSR0_STOP 0x0004 /* stop (RS) */
#define CSR0_TDMD 0x0008 /* transmit demand (RS) */
#define CSR0_TXON 0x0010 /* transmitter on (R) */
#define CSR0_RXON 0x0020 /* receiver on (R) */
#define CSR0_INEA 0x0040 /* interrupt enable (RW) */
#define CSR0_INTR 0x0080 /* interrupt active (R) */
#define CSR0_IDON 0x0100 /* initialization done (RC) */
#define CSR0_TINT 0x0200 /* transmitter interrupt (RC) */
#define CSR0_RINT 0x0400 /* receiver interrupt (RC) */
#define CSR0_MERR 0x0800 /* memory error (RC) */
#define CSR0_MISS 0x1000 /* missed frame (RC) */
#define CSR0_CERR 0x2000 /* carrier error (no heartbeat :-) (RC) */
#define CSR0_BABL 0x4000 /* babble: tx-ed too many bits (RC) */
#define CSR0_ERR 0x8000 /* error (RC) */
/* CSR3 */
#define CSR3_BCON 0x0001 /* byte control */
#define CSR3_ACON 0x0002 /* ALE control */
#define CSR3_BSWP 0x0004 /* byte swap (1=big endian) */
/***************************** Prototypes *****************************/
static int lance_probe( struct net_device *dev);
static int lance_open( struct net_device *dev );
static void lance_init_ring( struct net_device *dev );
static netdev_tx_t lance_start_xmit(struct sk_buff *skb,
struct net_device *dev);
static irqreturn_t lance_interrupt( int irq, void *dev_id);
static int lance_rx( struct net_device *dev );
static int lance_close( struct net_device *dev );
static void set_multicast_list( struct net_device *dev );
/************************* End of Prototypes **************************/
static struct net_device * __init sun3lance_probe(void)
{
struct net_device *dev;
static int found;
int err = -ENODEV;
if (!MACH_IS_SUN3 && !MACH_IS_SUN3X)
return ERR_PTR(-ENODEV);
/* check that this machine has an onboard lance */
switch(idprom->id_machtype) {
case SM_SUN3|SM_3_50:
case SM_SUN3|SM_3_60:
case SM_SUN3X|SM_3_80:
/* these machines have lance */
break;
default:
return ERR_PTR(-ENODEV);
}
if (found)
return ERR_PTR(-ENODEV);
dev = alloc_etherdev(sizeof(struct lance_private));
if (!dev)
return ERR_PTR(-ENOMEM);
if (!lance_probe(dev))
goto out;
err = register_netdev(dev);
if (err)
goto out1;
found = 1;
return dev;
out1:
#ifdef CONFIG_SUN3
iounmap((void __iomem *)dev->base_addr);
#endif
out:
free_netdev(dev);
return ERR_PTR(err);
}
static const struct net_device_ops lance_netdev_ops = {
.ndo_open = lance_open,
.ndo_stop = lance_close,
.ndo_start_xmit = lance_start_xmit,
.ndo_set_rx_mode = set_multicast_list,
.ndo_set_mac_address = NULL,
.ndo_validate_addr = eth_validate_addr,
};
static int __init lance_probe( struct net_device *dev)
{
unsigned long ioaddr;
struct lance_private *lp;
static int did_version;
volatile unsigned short *ioaddr_probe;
unsigned short tmp1, tmp2;
#ifdef CONFIG_SUN3
ioaddr = (unsigned long)ioremap(LANCE_OBIO, PAGE_SIZE);
if (!ioaddr)
return 0;
#else
ioaddr = SUN3X_LANCE;
#endif
/* test to see if there's really a lance here */
/* (CSRO_INIT shouldn't be readable) */
ioaddr_probe = (volatile unsigned short *)ioaddr;
tmp1 = ioaddr_probe[0];
tmp2 = ioaddr_probe[1];
ioaddr_probe[1] = CSR0;
ioaddr_probe[0] = CSR0_INIT | CSR0_STOP;
if(ioaddr_probe[0] != CSR0_STOP) {
ioaddr_probe[0] = tmp1;
ioaddr_probe[1] = tmp2;
#ifdef CONFIG_SUN3
iounmap((void __iomem *)ioaddr);
#endif
return 0;
}
lp = netdev_priv(dev);
/* XXX - leak? */
MEM = dvma_malloc_align(sizeof(struct lance_memory), 0x10000);
if (!MEM) {
#ifdef CONFIG_SUN3
iounmap((void __iomem *)ioaddr);
#endif
printk(KERN_WARNING "SUN3 Lance couldn't allocate DVMA memory\n");
return 0;
}
lp->iobase = (volatile unsigned short *)ioaddr;
dev->base_addr = (unsigned long)ioaddr; /* informational only */
REGA(CSR0) = CSR0_STOP;
if (request_irq(LANCE_IRQ, lance_interrupt, 0, "SUN3 Lance", dev) < 0) {
#ifdef CONFIG_SUN3
iounmap((void __iomem *)ioaddr);
#endif
dvma_free((void *)MEM);
printk(KERN_WARNING "SUN3 Lance unable to allocate IRQ\n");
return 0;
}
dev->irq = (unsigned short)LANCE_IRQ;
printk("%s: SUN3 Lance at io %#lx, mem %#lx, irq %d, hwaddr ",
dev->name,
(unsigned long)ioaddr,
(unsigned long)MEM,
dev->irq);
/* copy in the ethernet address from the prom */
eth_hw_addr_set(dev, idprom->id_ethaddr);
/* tell the card it's ether address, bytes swapped */
MEM->init.hwaddr[0] = dev->dev_addr[1];
MEM->init.hwaddr[1] = dev->dev_addr[0];
MEM->init.hwaddr[2] = dev->dev_addr[3];
MEM->init.hwaddr[3] = dev->dev_addr[2];
MEM->init.hwaddr[4] = dev->dev_addr[5];
MEM->init.hwaddr[5] = dev->dev_addr[4];
printk("%pM\n", dev->dev_addr);
MEM->init.mode = 0x0000;
MEM->init.filter[0] = 0x00000000;
MEM->init.filter[1] = 0x00000000;
MEM->init.rdra = dvma_vtob(MEM->rx_head);
MEM->init.rlen = (RX_LOG_RING_SIZE << 13) |
(dvma_vtob(MEM->rx_head) >> 16);
MEM->init.tdra = dvma_vtob(MEM->tx_head);
MEM->init.tlen = (TX_LOG_RING_SIZE << 13) |
(dvma_vtob(MEM->tx_head) >> 16);
DPRINTK(2, ("initaddr: %08lx rx_ring: %08lx tx_ring: %08lx\n",
dvma_vtob(&(MEM->init)), dvma_vtob(MEM->rx_head),
(dvma_vtob(MEM->tx_head))));
if (did_version++ == 0)
printk( version );
dev->netdev_ops = &lance_netdev_ops;
// KLUDGE -- REMOVE ME
set_bit(__LINK_STATE_PRESENT, &dev->state);
return 1;
}
static int lance_open( struct net_device *dev )
{
struct lance_private *lp = netdev_priv(dev);
int i;
DPRINTK( 2, ( "%s: lance_open()\n", dev->name ));
REGA(CSR0) = CSR0_STOP;
lance_init_ring(dev);
/* From now on, AREG is kept to point to CSR0 */
REGA(CSR0) = CSR0_INIT;
i = 1000000;
while (--i > 0)
if (DREG & CSR0_IDON)
break;
if (i <= 0 || (DREG & CSR0_ERR)) {
DPRINTK( 2, ( "lance_open(): opening %s failed, i=%d, csr0=%04x\n",
dev->name, i, DREG ));
DREG = CSR0_STOP;
return -EIO;
}
DREG = CSR0_IDON | CSR0_STRT | CSR0_INEA;
netif_start_queue(dev);
DPRINTK( 2, ( "%s: LANCE is open, csr0 %04x\n", dev->name, DREG ));
return 0;
}
/* Initialize the LANCE Rx and Tx rings. */
static void lance_init_ring( struct net_device *dev )
{
struct lance_private *lp = netdev_priv(dev);
int i;
lp->lock = 0;
lp->tx_full = 0;
lp->new_rx = lp->new_tx = 0;
lp->old_rx = lp->old_tx = 0;
for( i = 0; i < TX_RING_SIZE; i++ ) {
MEM->tx_head[i].base = dvma_vtob(MEM->tx_data[i]);
MEM->tx_head[i].flag = 0;
MEM->tx_head[i].base_hi =
(dvma_vtob(MEM->tx_data[i])) >>16;
MEM->tx_head[i].length = 0;
MEM->tx_head[i].misc = 0;
}
for( i = 0; i < RX_RING_SIZE; i++ ) {
MEM->rx_head[i].base = dvma_vtob(MEM->rx_data[i]);
MEM->rx_head[i].flag = RMD1_OWN_CHIP;
MEM->rx_head[i].base_hi =
(dvma_vtob(MEM->rx_data[i])) >> 16;
MEM->rx_head[i].buf_length = -PKT_BUF_SZ | 0xf000;
MEM->rx_head[i].msg_length = 0;
}
/* tell the card it's ether address, bytes swapped */
MEM->init.hwaddr[0] = dev->dev_addr[1];
MEM->init.hwaddr[1] = dev->dev_addr[0];
MEM->init.hwaddr[2] = dev->dev_addr[3];
MEM->init.hwaddr[3] = dev->dev_addr[2];
MEM->init.hwaddr[4] = dev->dev_addr[5];
MEM->init.hwaddr[5] = dev->dev_addr[4];
MEM->init.mode = 0x0000;
MEM->init.filter[0] = 0x00000000;
MEM->init.filter[1] = 0x00000000;
MEM->init.rdra = dvma_vtob(MEM->rx_head);
MEM->init.rlen = (RX_LOG_RING_SIZE << 13) |
(dvma_vtob(MEM->rx_head) >> 16);
MEM->init.tdra = dvma_vtob(MEM->tx_head);
MEM->init.tlen = (TX_LOG_RING_SIZE << 13) |
(dvma_vtob(MEM->tx_head) >> 16);
/* tell the lance the address of its init block */
REGA(CSR1) = dvma_vtob(&(MEM->init));
REGA(CSR2) = dvma_vtob(&(MEM->init)) >> 16;
#ifdef CONFIG_SUN3X
REGA(CSR3) = CSR3_BSWP | CSR3_ACON | CSR3_BCON;
#else
REGA(CSR3) = CSR3_BSWP;
#endif
}
static netdev_tx_t
lance_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct lance_private *lp = netdev_priv(dev);
int entry, len;
struct lance_tx_head *head;
unsigned long flags;
DPRINTK( 1, ( "%s: transmit start.\n",
dev->name));
/* Transmitter timeout, serious problems. */
if (netif_queue_stopped(dev)) {
int tickssofar = jiffies - dev_trans_start(dev);
if (tickssofar < HZ/5)
return NETDEV_TX_BUSY;
DPRINTK( 1, ( "%s: transmit timed out, status %04x, resetting.\n",
dev->name, DREG ));
DREG = CSR0_STOP;
/*
* Always set BSWP after a STOP as STOP puts it back into
* little endian mode.
*/
REGA(CSR3) = CSR3_BSWP;
dev->stats.tx_errors++;
if(lance_debug >= 2) {
int i;
printk("Ring data: old_tx %d new_tx %d%s new_rx %d\n",
lp->old_tx, lp->new_tx,
lp->tx_full ? " (full)" : "",
lp->new_rx );
for( i = 0 ; i < RX_RING_SIZE; i++ )
printk( "rx #%d: base=%04x blen=%04x mlen=%04x\n",
i, MEM->rx_head[i].base,
-MEM->rx_head[i].buf_length,
MEM->rx_head[i].msg_length);
for( i = 0 ; i < TX_RING_SIZE; i++ )
printk("tx #%d: base=%04x len=%04x misc=%04x\n",
i, MEM->tx_head[i].base,
-MEM->tx_head[i].length,
MEM->tx_head[i].misc );
}
lance_init_ring(dev);
REGA( CSR0 ) = CSR0_INEA | CSR0_INIT | CSR0_STRT;
netif_start_queue(dev);
return NETDEV_TX_OK;
}
/* Block a timer-based transmit from overlapping. This could better be
done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */
/* Block a timer-based transmit from overlapping with us by
stopping the queue for a bit... */
netif_stop_queue(dev);
if (test_and_set_bit( 0, (void*)&lp->lock ) != 0) {
printk( "%s: tx queue lock!.\n", dev->name);
/* don't clear dev->tbusy flag. */
return NETDEV_TX_BUSY;
}
AREG = CSR0;
DPRINTK( 2, ( "%s: lance_start_xmit() called, csr0 %4.4x.\n",
dev->name, DREG ));
#ifdef CONFIG_SUN3X
/* this weirdness doesn't appear on sun3... */
if(!(DREG & CSR0_INIT)) {
DPRINTK( 1, ("INIT not set, reinitializing...\n"));
REGA( CSR0 ) = CSR0_STOP;
lance_init_ring(dev);
REGA( CSR0 ) = CSR0_INIT | CSR0_STRT;
}
#endif
/* Fill in a Tx ring entry */
#if 0
if (lance_debug >= 2) {
printk( "%s: TX pkt %d type 0x%04x"
" from %s to %s"
" data at 0x%08x len %d\n",
dev->name, lp->new_tx, ((u_short *)skb->data)[6],
DEV_ADDR(&skb->data[6]), DEV_ADDR(skb->data),
(int)skb->data, (int)skb->len );
}
#endif
/* We're not prepared for the int until the last flags are set/reset.
* And the int may happen already after setting the OWN_CHIP... */
local_irq_save(flags);
/* Mask to ring buffer boundary. */
entry = lp->new_tx;
head = &(MEM->tx_head[entry]);
/* Caution: the write order is important here, set the "ownership" bits
* last.
*/
/* the sun3's lance needs it's buffer padded to the minimum
size */
len = (ETH_ZLEN < skb->len) ? skb->len : ETH_ZLEN;
// head->length = -len;
head->length = (-len) | 0xf000;
head->misc = 0;
skb_copy_from_linear_data(skb, PKTBUF_ADDR(head), skb->len);
if (len != skb->len)
memset(PKTBUF_ADDR(head) + skb->len, 0, len-skb->len);
head->flag = TMD1_OWN_CHIP | TMD1_ENP | TMD1_STP;
lp->new_tx = (lp->new_tx + 1) & TX_RING_MOD_MASK;
dev->stats.tx_bytes += skb->len;
/* Trigger an immediate send poll. */
REGA(CSR0) = CSR0_INEA | CSR0_TDMD | CSR0_STRT;
AREG = CSR0;
DPRINTK( 2, ( "%s: lance_start_xmit() exiting, csr0 %4.4x.\n",
dev->name, DREG ));
dev_kfree_skb(skb);
lp->lock = 0;
if ((MEM->tx_head[(entry+1) & TX_RING_MOD_MASK].flag & TMD1_OWN) ==
TMD1_OWN_HOST)
netif_start_queue(dev);
local_irq_restore(flags);
return NETDEV_TX_OK;
}
/* The LANCE interrupt handler. */
static irqreturn_t lance_interrupt( int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct lance_private *lp = netdev_priv(dev);
int csr0;
still_more:
flush_cache_all();
AREG = CSR0;
csr0 = DREG;
/* ack interrupts */
DREG = csr0 & (CSR0_TINT | CSR0_RINT | CSR0_IDON);
/* clear errors */
if(csr0 & CSR0_ERR)
DREG = CSR0_BABL | CSR0_MERR | CSR0_CERR | CSR0_MISS;
DPRINTK( 2, ( "%s: interrupt csr0=%04x new csr=%04x.\n",
dev->name, csr0, DREG ));
if (csr0 & CSR0_TINT) { /* Tx-done interrupt */
int old_tx = lp->old_tx;
// if(lance_debug >= 3) {
// int i;
//
// printk("%s: tx int\n", dev->name);
//
// for(i = 0; i < TX_RING_SIZE; i++)
// printk("ring %d flag=%04x\n", i,
// MEM->tx_head[i].flag);
// }
while( old_tx != lp->new_tx) {
struct lance_tx_head *head = &(MEM->tx_head[old_tx]);
DPRINTK(3, ("on tx_ring %d\n", old_tx));
if (head->flag & TMD1_OWN_CHIP)
break; /* It still hasn't been Txed */
if (head->flag & TMD1_ERR) {
int status = head->misc;
dev->stats.tx_errors++;
if (status & TMD3_RTRY) dev->stats.tx_aborted_errors++;
if (status & TMD3_LCAR) dev->stats.tx_carrier_errors++;
if (status & TMD3_LCOL) dev->stats.tx_window_errors++;
if (status & (TMD3_UFLO | TMD3_BUFF)) {
dev->stats.tx_fifo_errors++;
printk("%s: Tx FIFO error\n",
dev->name);
REGA(CSR0) = CSR0_STOP;
REGA(CSR3) = CSR3_BSWP;
lance_init_ring(dev);
REGA(CSR0) = CSR0_STRT | CSR0_INEA;
return IRQ_HANDLED;
}
} else if(head->flag & (TMD1_ENP | TMD1_STP)) {
head->flag &= ~(TMD1_ENP | TMD1_STP);
if(head->flag & (TMD1_ONE | TMD1_MORE))
dev->stats.collisions++;
dev->stats.tx_packets++;
DPRINTK(3, ("cleared tx ring %d\n", old_tx));
}
old_tx = (old_tx +1) & TX_RING_MOD_MASK;
}
lp->old_tx = old_tx;
}
if (netif_queue_stopped(dev)) {
/* The ring is no longer full, clear tbusy. */
netif_start_queue(dev);
netif_wake_queue(dev);
}
if (csr0 & CSR0_RINT) /* Rx interrupt */
lance_rx( dev );
/* Log misc errors. */
if (csr0 & CSR0_BABL) dev->stats.tx_errors++; /* Tx babble. */
if (csr0 & CSR0_MISS) dev->stats.rx_errors++; /* Missed a Rx frame. */
if (csr0 & CSR0_MERR) {
DPRINTK( 1, ( "%s: Bus master arbitration failure (?!?), "
"status %04x.\n", dev->name, csr0 ));
/* Restart the chip. */
REGA(CSR0) = CSR0_STOP;
REGA(CSR3) = CSR3_BSWP;
lance_init_ring(dev);
REGA(CSR0) = CSR0_STRT | CSR0_INEA;
}
/* Clear any other interrupt, and set interrupt enable. */
// DREG = CSR0_BABL | CSR0_CERR | CSR0_MISS | CSR0_MERR |
// CSR0_IDON | CSR0_INEA;
REGA(CSR0) = CSR0_INEA;
if(DREG & (CSR0_RINT | CSR0_TINT)) {
DPRINTK(2, ("restarting interrupt, csr0=%#04x\n", DREG));
goto still_more;
}
DPRINTK( 2, ( "%s: exiting interrupt, csr0=%#04x.\n",
dev->name, DREG ));
return IRQ_HANDLED;
}
/* get packet, toss into skbuff */
static int lance_rx( struct net_device *dev )
{
struct lance_private *lp = netdev_priv(dev);
int entry = lp->new_rx;
/* If we own the next entry, it's a new packet. Send it up. */
while( (MEM->rx_head[entry].flag & RMD1_OWN) == RMD1_OWN_HOST ) {
struct lance_rx_head *head = &(MEM->rx_head[entry]);
int status = head->flag;
if (status != (RMD1_ENP|RMD1_STP)) { /* There was an error. */
/* There is a tricky error noted by John Murphy,
<murf@perftech.com> to Russ Nelson: Even with
full-sized buffers it's possible for a jabber packet to use two
buffers, with only the last correctly noting the error. */
if (status & RMD1_ENP) /* Only count a general error at the */
dev->stats.rx_errors++; /* end of a packet.*/
if (status & RMD1_FRAM) dev->stats.rx_frame_errors++;
if (status & RMD1_OFLO) dev->stats.rx_over_errors++;
if (status & RMD1_CRC) dev->stats.rx_crc_errors++;
if (status & RMD1_BUFF) dev->stats.rx_fifo_errors++;
head->flag &= (RMD1_ENP|RMD1_STP);
} else {
/* Malloc up new buffer, compatible with net-3. */
// short pkt_len = head->msg_length;// & 0xfff;
short pkt_len = (head->msg_length & 0xfff) - 4;
struct sk_buff *skb;
if (pkt_len < 60) {
printk( "%s: Runt packet!\n", dev->name );
dev->stats.rx_errors++;
}
else {
skb = netdev_alloc_skb(dev, pkt_len + 2);
if (!skb) {
dev->stats.rx_dropped++;
head->msg_length = 0;
head->flag |= RMD1_OWN_CHIP;
lp->new_rx = (lp->new_rx+1) &
RX_RING_MOD_MASK;
}
#if 0
if (lance_debug >= 3) {
u_char *data = PKTBUF_ADDR(head);
printk("%s: RX pkt %d type 0x%04x"
" from %pM to %pM",
dev->name, lp->new_tx, ((u_short *)data)[6],
&data[6], data);
printk(" data %02x %02x %02x %02x %02x %02x %02x %02x "
"len %d at %08x\n",
data[15], data[16], data[17], data[18],
data[19], data[20], data[21], data[22],
pkt_len, data);
}
#endif
if (lance_debug >= 3) {
u_char *data = PKTBUF_ADDR(head);
printk( "%s: RX pkt %d type 0x%04x len %d\n ", dev->name, entry, ((u_short *)data)[6], pkt_len);
}
skb_reserve( skb, 2 ); /* 16 byte align */
skb_put( skb, pkt_len ); /* Make room */
skb_copy_to_linear_data(skb,
PKTBUF_ADDR(head),
pkt_len);
skb->protocol = eth_type_trans( skb, dev );
netif_rx( skb );
dev->stats.rx_packets++;
dev->stats.rx_bytes += pkt_len;
}
}
// head->buf_length = -PKT_BUF_SZ | 0xf000;
head->msg_length = 0;
head->flag = RMD1_OWN_CHIP;
entry = lp->new_rx = (lp->new_rx +1) & RX_RING_MOD_MASK;
}
/* From lance.c (Donald Becker): */
/* We should check that at least two ring entries are free.
If not, we should free one and mark stats->rx_dropped++. */
return 0;
}
static int lance_close( struct net_device *dev )
{
struct lance_private *lp = netdev_priv(dev);
netif_stop_queue(dev);
AREG = CSR0;
DPRINTK( 2, ( "%s: Shutting down ethercard, status was %2.2x.\n",
dev->name, DREG ));
/* We stop the LANCE here -- it occasionally polls
memory if we don't. */
DREG = CSR0_STOP;
return 0;
}
/* Set or clear the multicast filter for this adaptor.
num_addrs == -1 Promiscuous mode, receive all packets
num_addrs == 0 Normal mode, clear multicast list
num_addrs > 0 Multicast mode, receive normal and MC packets, and do
best-effort filtering.
*/
/* completely untested on a sun3 */
static void set_multicast_list( struct net_device *dev )
{
struct lance_private *lp = netdev_priv(dev);
if(netif_queue_stopped(dev))
/* Only possible if board is already started */
return;
/* We take the simple way out and always enable promiscuous mode. */
DREG = CSR0_STOP; /* Temporarily stop the lance. */
if (dev->flags & IFF_PROMISC) {
/* Log any net taps. */
DPRINTK( 3, ( "%s: Promiscuous mode enabled.\n", dev->name ));
REGA( CSR15 ) = 0x8000; /* Set promiscuous mode */
} else {
short multicast_table[4];
int num_addrs = netdev_mc_count(dev);
int i;
/* We don't use the multicast table, but rely on upper-layer
* filtering. */
memset( multicast_table, (num_addrs == 0) ? 0 : -1,
sizeof(multicast_table) );
for( i = 0; i < 4; i++ )
REGA( CSR8+i ) = multicast_table[i];
REGA( CSR15 ) = 0; /* Unset promiscuous mode */
}
/*
* Always set BSWP after a STOP as STOP puts it back into
* little endian mode.
*/
REGA( CSR3 ) = CSR3_BSWP;
/* Resume normal operation and reset AREG to CSR0 */
REGA( CSR0 ) = CSR0_IDON | CSR0_INEA | CSR0_STRT;
}
static struct net_device *sun3lance_dev;
static int __init sun3lance_init(void)
{
sun3lance_dev = sun3lance_probe();
return PTR_ERR_OR_ZERO(sun3lance_dev);
}
module_init(sun3lance_init);
static void __exit sun3lance_cleanup(void)
{
unregister_netdev(sun3lance_dev);
#ifdef CONFIG_SUN3
iounmap((void __iomem *)sun3lance_dev->base_addr);
#endif
free_netdev(sun3lance_dev);
}
module_exit(sun3lance_cleanup);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_AMD_XGBE) += amd-xgbe.o
amd-xgbe-objs := xgbe-main.o xgbe-drv.o xgbe-dev.o \
xgbe-desc.o xgbe-ethtool.o xgbe-mdio.o \
xgbe-hwtstamp.o xgbe-ptp.o xgbe-pps.o \
xgbe-i2c.o xgbe-phy-v1.o xgbe-phy-v2.o \
xgbe-platform.o xgbe-selftest.o
amd-xgbe-$(CONFIG_PCI) += xgbe-pci.o
amd-xgbe-$(CONFIG_AMD_XGBE_DCB) += xgbe-dcb.o
amd-xgbe-$(CONFIG_DEBUG_FS) += xgbe-debugfs.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/netdevice.h>
#include <net/dcbnl.h>
#include "xgbe.h"
#include "xgbe-common.h"
static int xgbe_dcb_ieee_getets(struct net_device *netdev,
struct ieee_ets *ets)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
/* Set number of supported traffic classes */
ets->ets_cap = pdata->hw_feat.tc_cnt;
if (pdata->ets) {
ets->cbs = pdata->ets->cbs;
memcpy(ets->tc_tx_bw, pdata->ets->tc_tx_bw,
sizeof(ets->tc_tx_bw));
memcpy(ets->tc_tsa, pdata->ets->tc_tsa,
sizeof(ets->tc_tsa));
memcpy(ets->prio_tc, pdata->ets->prio_tc,
sizeof(ets->prio_tc));
}
return 0;
}
static int xgbe_dcb_ieee_setets(struct net_device *netdev,
struct ieee_ets *ets)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
unsigned int i, tc_ets, tc_ets_weight;
u8 max_tc = 0;
tc_ets = 0;
tc_ets_weight = 0;
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
netif_dbg(pdata, drv, netdev,
"TC%u: tx_bw=%hhu, rx_bw=%hhu, tsa=%hhu\n", i,
ets->tc_tx_bw[i], ets->tc_rx_bw[i],
ets->tc_tsa[i]);
netif_dbg(pdata, drv, netdev, "PRIO%u: TC=%hhu\n", i,
ets->prio_tc[i]);
max_tc = max_t(u8, max_tc, ets->prio_tc[i]);
if ((ets->tc_tx_bw[i] || ets->tc_tsa[i]))
max_tc = max_t(u8, max_tc, i);
switch (ets->tc_tsa[i]) {
case IEEE_8021QAZ_TSA_STRICT:
break;
case IEEE_8021QAZ_TSA_ETS:
tc_ets = 1;
tc_ets_weight += ets->tc_tx_bw[i];
break;
default:
netif_err(pdata, drv, netdev,
"unsupported TSA algorithm (%hhu)\n",
ets->tc_tsa[i]);
return -EINVAL;
}
}
/* Check maximum traffic class requested */
if (max_tc >= pdata->hw_feat.tc_cnt) {
netif_err(pdata, drv, netdev,
"exceeded number of supported traffic classes\n");
return -EINVAL;
}
/* Weights must add up to 100% */
if (tc_ets && (tc_ets_weight != 100)) {
netif_err(pdata, drv, netdev,
"sum of ETS algorithm weights is not 100 (%u)\n",
tc_ets_weight);
return -EINVAL;
}
if (!pdata->ets) {
pdata->ets = devm_kzalloc(pdata->dev, sizeof(*pdata->ets),
GFP_KERNEL);
if (!pdata->ets)
return -ENOMEM;
}
pdata->num_tcs = max_tc + 1;
memcpy(pdata->ets, ets, sizeof(*pdata->ets));
pdata->hw_if.config_dcb_tc(pdata);
return 0;
}
static int xgbe_dcb_ieee_getpfc(struct net_device *netdev,
struct ieee_pfc *pfc)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
/* Set number of supported PFC traffic classes */
pfc->pfc_cap = pdata->hw_feat.tc_cnt;
if (pdata->pfc) {
pfc->pfc_en = pdata->pfc->pfc_en;
pfc->mbc = pdata->pfc->mbc;
pfc->delay = pdata->pfc->delay;
}
return 0;
}
static int xgbe_dcb_ieee_setpfc(struct net_device *netdev,
struct ieee_pfc *pfc)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
netif_dbg(pdata, drv, netdev,
"cap=%d, en=%#x, mbc=%d, delay=%d\n",
pfc->pfc_cap, pfc->pfc_en, pfc->mbc, pfc->delay);
/* Check PFC for supported number of traffic classes */
if (pfc->pfc_en & ~((1 << pdata->hw_feat.tc_cnt) - 1)) {
netif_err(pdata, drv, netdev,
"PFC requested for unsupported traffic class\n");
return -EINVAL;
}
if (!pdata->pfc) {
pdata->pfc = devm_kzalloc(pdata->dev, sizeof(*pdata->pfc),
GFP_KERNEL);
if (!pdata->pfc)
return -ENOMEM;
}
memcpy(pdata->pfc, pfc, sizeof(*pdata->pfc));
pdata->hw_if.config_dcb_pfc(pdata);
return 0;
}
static u8 xgbe_dcb_getdcbx(struct net_device *netdev)
{
return DCB_CAP_DCBX_HOST | DCB_CAP_DCBX_VER_IEEE;
}
static u8 xgbe_dcb_setdcbx(struct net_device *netdev, u8 dcbx)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
u8 support = xgbe_dcb_getdcbx(netdev);
netif_dbg(pdata, drv, netdev, "DCBX=%#hhx\n", dcbx);
if (dcbx & ~support)
return 1;
if ((dcbx & support) != support)
return 1;
return 0;
}
static const struct dcbnl_rtnl_ops xgbe_dcbnl_ops = {
/* IEEE 802.1Qaz std */
.ieee_getets = xgbe_dcb_ieee_getets,
.ieee_setets = xgbe_dcb_ieee_setets,
.ieee_getpfc = xgbe_dcb_ieee_getpfc,
.ieee_setpfc = xgbe_dcb_ieee_setpfc,
/* DCBX configuration */
.getdcbx = xgbe_dcb_getdcbx,
.setdcbx = xgbe_dcb_setdcbx,
};
const struct dcbnl_rtnl_ops *xgbe_get_dcbnl_ops(void)
{
return &xgbe_dcbnl_ops;
}
@@ -0,0 +1,401 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "xgbe.h"
#include "xgbe-common.h"
static ssize_t xgbe_common_read(char __user *buffer, size_t count,
loff_t *ppos, unsigned int value)
{
char *buf;
ssize_t len;
if (*ppos != 0)
return 0;
buf = kasprintf(GFP_KERNEL, "0x%08x\n", value);
if (!buf)
return -ENOMEM;
if (count < strlen(buf)) {
kfree(buf);
return -ENOSPC;
}
len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
kfree(buf);
return len;
}
static ssize_t xgbe_common_write(const char __user *buffer, size_t count,
loff_t *ppos, unsigned int *value)
{
char workarea[32];
ssize_t len;
int ret;
if (*ppos != 0)
return -EINVAL;
if (count >= sizeof(workarea))
return -ENOSPC;
len = simple_write_to_buffer(workarea, sizeof(workarea) - 1, ppos,
buffer, count);
if (len < 0)
return len;
workarea[len] = '\0';
ret = kstrtouint(workarea, 16, value);
if (ret)
return -EIO;
return len;
}
static ssize_t xgmac_reg_addr_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_read(buffer, count, ppos, pdata->debugfs_xgmac_reg);
}
static ssize_t xgmac_reg_addr_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_write(buffer, count, ppos,
&pdata->debugfs_xgmac_reg);
}
static ssize_t xgmac_reg_value_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
value = XGMAC_IOREAD(pdata, pdata->debugfs_xgmac_reg);
return xgbe_common_read(buffer, count, ppos, value);
}
static ssize_t xgmac_reg_value_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
ssize_t len;
len = xgbe_common_write(buffer, count, ppos, &value);
if (len < 0)
return len;
XGMAC_IOWRITE(pdata, pdata->debugfs_xgmac_reg, value);
return len;
}
static const struct file_operations xgmac_reg_addr_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xgmac_reg_addr_read,
.write = xgmac_reg_addr_write,
};
static const struct file_operations xgmac_reg_value_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xgmac_reg_value_read,
.write = xgmac_reg_value_write,
};
static ssize_t xpcs_mmd_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_read(buffer, count, ppos, pdata->debugfs_xpcs_mmd);
}
static ssize_t xpcs_mmd_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_write(buffer, count, ppos,
&pdata->debugfs_xpcs_mmd);
}
static ssize_t xpcs_reg_addr_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_read(buffer, count, ppos, pdata->debugfs_xpcs_reg);
}
static ssize_t xpcs_reg_addr_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_write(buffer, count, ppos,
&pdata->debugfs_xpcs_reg);
}
static ssize_t xpcs_reg_value_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
value = XMDIO_READ(pdata, pdata->debugfs_xpcs_mmd,
pdata->debugfs_xpcs_reg);
return xgbe_common_read(buffer, count, ppos, value);
}
static ssize_t xpcs_reg_value_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
ssize_t len;
len = xgbe_common_write(buffer, count, ppos, &value);
if (len < 0)
return len;
XMDIO_WRITE(pdata, pdata->debugfs_xpcs_mmd, pdata->debugfs_xpcs_reg,
value);
return len;
}
static const struct file_operations xpcs_mmd_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xpcs_mmd_read,
.write = xpcs_mmd_write,
};
static const struct file_operations xpcs_reg_addr_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xpcs_reg_addr_read,
.write = xpcs_reg_addr_write,
};
static const struct file_operations xpcs_reg_value_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xpcs_reg_value_read,
.write = xpcs_reg_value_write,
};
static ssize_t xprop_reg_addr_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_read(buffer, count, ppos, pdata->debugfs_xprop_reg);
}
static ssize_t xprop_reg_addr_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_write(buffer, count, ppos,
&pdata->debugfs_xprop_reg);
}
static ssize_t xprop_reg_value_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
value = XP_IOREAD(pdata, pdata->debugfs_xprop_reg);
return xgbe_common_read(buffer, count, ppos, value);
}
static ssize_t xprop_reg_value_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
ssize_t len;
len = xgbe_common_write(buffer, count, ppos, &value);
if (len < 0)
return len;
XP_IOWRITE(pdata, pdata->debugfs_xprop_reg, value);
return len;
}
static const struct file_operations xprop_reg_addr_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xprop_reg_addr_read,
.write = xprop_reg_addr_write,
};
static const struct file_operations xprop_reg_value_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xprop_reg_value_read,
.write = xprop_reg_value_write,
};
static ssize_t xi2c_reg_addr_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_read(buffer, count, ppos, pdata->debugfs_xi2c_reg);
}
static ssize_t xi2c_reg_addr_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
return xgbe_common_write(buffer, count, ppos,
&pdata->debugfs_xi2c_reg);
}
static ssize_t xi2c_reg_value_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
value = XI2C_IOREAD(pdata, pdata->debugfs_xi2c_reg);
return xgbe_common_read(buffer, count, ppos, value);
}
static ssize_t xi2c_reg_value_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *ppos)
{
struct xgbe_prv_data *pdata = filp->private_data;
unsigned int value;
ssize_t len;
len = xgbe_common_write(buffer, count, ppos, &value);
if (len < 0)
return len;
XI2C_IOWRITE(pdata, pdata->debugfs_xi2c_reg, value);
return len;
}
static const struct file_operations xi2c_reg_addr_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xi2c_reg_addr_read,
.write = xi2c_reg_addr_write,
};
static const struct file_operations xi2c_reg_value_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = xi2c_reg_value_read,
.write = xi2c_reg_value_write,
};
void xgbe_debugfs_init(struct xgbe_prv_data *pdata)
{
char *buf;
/* Set defaults */
pdata->debugfs_xgmac_reg = 0;
pdata->debugfs_xpcs_mmd = 1;
pdata->debugfs_xpcs_reg = 0;
buf = kasprintf(GFP_KERNEL, "amd-xgbe-%s", pdata->netdev->name);
if (!buf)
return;
pdata->xgbe_debugfs = debugfs_create_dir(buf, NULL);
debugfs_create_file("xgmac_register", 0600, pdata->xgbe_debugfs, pdata,
&xgmac_reg_addr_fops);
debugfs_create_file("xgmac_register_value", 0600, pdata->xgbe_debugfs,
pdata, &xgmac_reg_value_fops);
debugfs_create_file("xpcs_mmd", 0600, pdata->xgbe_debugfs, pdata,
&xpcs_mmd_fops);
debugfs_create_file("xpcs_register", 0600, pdata->xgbe_debugfs, pdata,
&xpcs_reg_addr_fops);
debugfs_create_file("xpcs_register_value", 0600, pdata->xgbe_debugfs,
pdata, &xpcs_reg_value_fops);
if (pdata->xprop_regs) {
debugfs_create_file("xprop_register", 0600, pdata->xgbe_debugfs,
pdata, &xprop_reg_addr_fops);
debugfs_create_file("xprop_register_value", 0600,
pdata->xgbe_debugfs, pdata,
&xprop_reg_value_fops);
}
if (pdata->xi2c_regs) {
debugfs_create_file("xi2c_register", 0600, pdata->xgbe_debugfs,
pdata, &xi2c_reg_addr_fops);
debugfs_create_file("xi2c_register_value", 0600,
pdata->xgbe_debugfs, pdata,
&xi2c_reg_value_fops);
}
if (pdata->vdata->an_cdr_workaround) {
debugfs_create_bool("an_cdr_workaround", 0600,
pdata->xgbe_debugfs,
&pdata->debugfs_an_cdr_workaround);
debugfs_create_bool("an_cdr_track_early", 0600,
pdata->xgbe_debugfs,
&pdata->debugfs_an_cdr_track_early);
}
kfree(buf);
}
void xgbe_debugfs_exit(struct xgbe_prv_data *pdata)
{
debugfs_remove_recursive(pdata->xgbe_debugfs);
pdata->xgbe_debugfs = NULL;
}
void xgbe_debugfs_rename(struct xgbe_prv_data *pdata)
{
debugfs_change_name(pdata->xgbe_debugfs,
"amd-xgbe-%s", pdata->netdev->name);
}
@@ -0,0 +1,572 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include "xgbe.h"
#include "xgbe-common.h"
static void xgbe_unmap_rdata(struct xgbe_prv_data *, struct xgbe_ring_data *);
static void xgbe_free_ring(struct xgbe_prv_data *pdata,
struct xgbe_ring *ring)
{
struct xgbe_ring_data *rdata;
unsigned int i;
if (!ring)
return;
if (ring->rdata) {
for (i = 0; i < ring->rdesc_count; i++) {
rdata = XGBE_GET_DESC_DATA(ring, i);
xgbe_unmap_rdata(pdata, rdata);
}
kfree(ring->rdata);
ring->rdata = NULL;
}
if (ring->rx_hdr_pa.pages) {
dma_unmap_page(pdata->dev, ring->rx_hdr_pa.pages_dma,
ring->rx_hdr_pa.pages_len, DMA_FROM_DEVICE);
put_page(ring->rx_hdr_pa.pages);
ring->rx_hdr_pa.pages = NULL;
ring->rx_hdr_pa.pages_len = 0;
ring->rx_hdr_pa.pages_offset = 0;
ring->rx_hdr_pa.pages_dma = 0;
}
if (ring->rx_buf_pa.pages) {
dma_unmap_page(pdata->dev, ring->rx_buf_pa.pages_dma,
ring->rx_buf_pa.pages_len, DMA_FROM_DEVICE);
put_page(ring->rx_buf_pa.pages);
ring->rx_buf_pa.pages = NULL;
ring->rx_buf_pa.pages_len = 0;
ring->rx_buf_pa.pages_offset = 0;
ring->rx_buf_pa.pages_dma = 0;
}
if (ring->rdesc) {
dma_free_coherent(pdata->dev,
(sizeof(struct xgbe_ring_desc) *
ring->rdesc_count),
ring->rdesc, ring->rdesc_dma);
ring->rdesc = NULL;
}
}
static void xgbe_free_ring_resources(struct xgbe_prv_data *pdata)
{
struct xgbe_channel *channel;
unsigned int i;
DBGPR("-->xgbe_free_ring_resources\n");
for (i = 0; i < pdata->channel_count; i++) {
channel = pdata->channel[i];
xgbe_free_ring(pdata, channel->tx_ring);
xgbe_free_ring(pdata, channel->rx_ring);
}
DBGPR("<--xgbe_free_ring_resources\n");
}
static void *xgbe_alloc_node(size_t size, int node)
{
void *mem;
mem = kzalloc_node(size, GFP_KERNEL, node);
if (!mem)
mem = kzalloc(size, GFP_KERNEL);
return mem;
}
static void *xgbe_dma_alloc_node(struct device *dev, size_t size,
dma_addr_t *dma, int node)
{
void *mem;
int cur_node = dev_to_node(dev);
set_dev_node(dev, node);
mem = dma_alloc_coherent(dev, size, dma, GFP_KERNEL);
set_dev_node(dev, cur_node);
if (!mem)
mem = dma_alloc_coherent(dev, size, dma, GFP_KERNEL);
return mem;
}
static int xgbe_init_ring(struct xgbe_prv_data *pdata,
struct xgbe_ring *ring, unsigned int rdesc_count)
{
size_t size;
if (!ring)
return 0;
/* Descriptors */
size = rdesc_count * sizeof(struct xgbe_ring_desc);
ring->rdesc_count = rdesc_count;
ring->rdesc = xgbe_dma_alloc_node(pdata->dev, size, &ring->rdesc_dma,
ring->node);
if (!ring->rdesc)
return -ENOMEM;
/* Descriptor information */
size = rdesc_count * sizeof(struct xgbe_ring_data);
ring->rdata = xgbe_alloc_node(size, ring->node);
if (!ring->rdata)
return -ENOMEM;
netif_dbg(pdata, drv, pdata->netdev,
"rdesc=%p, rdesc_dma=%pad, rdata=%p, node=%d\n",
ring->rdesc, &ring->rdesc_dma, ring->rdata, ring->node);
return 0;
}
static int xgbe_alloc_ring_resources(struct xgbe_prv_data *pdata)
{
struct xgbe_channel *channel;
unsigned int i;
int ret;
for (i = 0; i < pdata->channel_count; i++) {
channel = pdata->channel[i];
netif_dbg(pdata, drv, pdata->netdev, "%s - Tx ring:\n",
channel->name);
ret = xgbe_init_ring(pdata, channel->tx_ring,
pdata->tx_desc_count);
if (ret) {
netdev_alert(pdata->netdev,
"error initializing Tx ring\n");
goto err_ring;
}
netif_dbg(pdata, drv, pdata->netdev, "%s - Rx ring:\n",
channel->name);
ret = xgbe_init_ring(pdata, channel->rx_ring,
pdata->rx_desc_count);
if (ret) {
netdev_alert(pdata->netdev,
"error initializing Rx ring\n");
goto err_ring;
}
}
return 0;
err_ring:
xgbe_free_ring_resources(pdata);
return ret;
}
static int xgbe_alloc_pages(struct xgbe_prv_data *pdata,
struct xgbe_page_alloc *pa, int alloc_order,
int node)
{
struct page *pages = NULL;
dma_addr_t pages_dma;
gfp_t gfp;
int order;
again:
order = alloc_order;
/* Try to obtain pages, decreasing order if necessary */
gfp = GFP_ATOMIC | __GFP_COMP | __GFP_NOWARN;
while (order >= 0) {
pages = alloc_pages_node(node, gfp, order);
if (pages)
break;
order--;
}
/* If we couldn't get local pages, try getting from anywhere */
if (!pages && (node != NUMA_NO_NODE)) {
node = NUMA_NO_NODE;
goto again;
}
if (!pages)
return -ENOMEM;
/* Map the pages */
pages_dma = dma_map_page(pdata->dev, pages, 0,
PAGE_SIZE << order, DMA_FROM_DEVICE);
if (dma_mapping_error(pdata->dev, pages_dma)) {
put_page(pages);
return -ENOMEM;
}
pa->pages = pages;
pa->pages_len = PAGE_SIZE << order;
pa->pages_offset = 0;
pa->pages_dma = pages_dma;
return 0;
}
static void xgbe_set_buffer_data(struct xgbe_buffer_data *bd,
struct xgbe_page_alloc *pa,
unsigned int len)
{
get_page(pa->pages);
bd->pa = *pa;
bd->dma_base = pa->pages_dma;
bd->dma_off = pa->pages_offset;
bd->dma_len = len;
pa->pages_offset += len;
if ((pa->pages_offset + len) > pa->pages_len) {
/* This data descriptor is responsible for unmapping page(s) */
bd->pa_unmap = *pa;
/* Get a new allocation next time */
pa->pages = NULL;
pa->pages_len = 0;
pa->pages_offset = 0;
pa->pages_dma = 0;
}
}
static int xgbe_map_rx_buffer(struct xgbe_prv_data *pdata,
struct xgbe_ring *ring,
struct xgbe_ring_data *rdata)
{
int ret;
if (!ring->rx_hdr_pa.pages) {
ret = xgbe_alloc_pages(pdata, &ring->rx_hdr_pa, 0, ring->node);
if (ret)
return ret;
}
if (!ring->rx_buf_pa.pages) {
ret = xgbe_alloc_pages(pdata, &ring->rx_buf_pa,
PAGE_ALLOC_COSTLY_ORDER, ring->node);
if (ret)
return ret;
}
/* Set up the header page info */
if (pdata->netdev->features & NETIF_F_RXCSUM) {
xgbe_set_buffer_data(&rdata->rx.hdr, &ring->rx_hdr_pa,
XGBE_SKB_ALLOC_SIZE);
} else {
xgbe_set_buffer_data(&rdata->rx.hdr, &ring->rx_hdr_pa,
pdata->rx_buf_size);
}
/* Set up the buffer page info */
xgbe_set_buffer_data(&rdata->rx.buf, &ring->rx_buf_pa,
pdata->rx_buf_size);
return 0;
}
static void xgbe_wrapper_tx_descriptor_init(struct xgbe_prv_data *pdata)
{
struct xgbe_hw_if *hw_if = &pdata->hw_if;
struct xgbe_channel *channel;
struct xgbe_ring *ring;
struct xgbe_ring_data *rdata;
struct xgbe_ring_desc *rdesc;
dma_addr_t rdesc_dma;
unsigned int i, j;
DBGPR("-->xgbe_wrapper_tx_descriptor_init\n");
for (i = 0; i < pdata->channel_count; i++) {
channel = pdata->channel[i];
ring = channel->tx_ring;
if (!ring)
break;
rdesc = ring->rdesc;
rdesc_dma = ring->rdesc_dma;
for (j = 0; j < ring->rdesc_count; j++) {
rdata = XGBE_GET_DESC_DATA(ring, j);
rdata->rdesc = rdesc;
rdata->rdesc_dma = rdesc_dma;
rdesc++;
rdesc_dma += sizeof(struct xgbe_ring_desc);
}
ring->cur = 0;
ring->dirty = 0;
memset(&ring->tx, 0, sizeof(ring->tx));
hw_if->tx_desc_init(channel);
}
DBGPR("<--xgbe_wrapper_tx_descriptor_init\n");
}
static void xgbe_wrapper_rx_descriptor_init(struct xgbe_prv_data *pdata)
{
struct xgbe_hw_if *hw_if = &pdata->hw_if;
struct xgbe_channel *channel;
struct xgbe_ring *ring;
struct xgbe_ring_desc *rdesc;
struct xgbe_ring_data *rdata;
dma_addr_t rdesc_dma;
unsigned int i, j;
DBGPR("-->xgbe_wrapper_rx_descriptor_init\n");
for (i = 0; i < pdata->channel_count; i++) {
channel = pdata->channel[i];
ring = channel->rx_ring;
if (!ring)
break;
rdesc = ring->rdesc;
rdesc_dma = ring->rdesc_dma;
for (j = 0; j < ring->rdesc_count; j++) {
rdata = XGBE_GET_DESC_DATA(ring, j);
rdata->rdesc = rdesc;
rdata->rdesc_dma = rdesc_dma;
if (xgbe_map_rx_buffer(pdata, ring, rdata))
break;
rdesc++;
rdesc_dma += sizeof(struct xgbe_ring_desc);
}
ring->cur = 0;
ring->dirty = 0;
hw_if->rx_desc_init(channel);
}
DBGPR("<--xgbe_wrapper_rx_descriptor_init\n");
}
static void xgbe_unmap_rdata(struct xgbe_prv_data *pdata,
struct xgbe_ring_data *rdata)
{
if (rdata->skb_dma) {
if (rdata->mapped_as_page) {
dma_unmap_page(pdata->dev, rdata->skb_dma,
rdata->skb_dma_len, DMA_TO_DEVICE);
} else {
dma_unmap_single(pdata->dev, rdata->skb_dma,
rdata->skb_dma_len, DMA_TO_DEVICE);
}
rdata->skb_dma = 0;
rdata->skb_dma_len = 0;
}
if (rdata->skb) {
dev_kfree_skb_any(rdata->skb);
rdata->skb = NULL;
}
if (rdata->rx.hdr.pa.pages)
put_page(rdata->rx.hdr.pa.pages);
if (rdata->rx.hdr.pa_unmap.pages) {
dma_unmap_page(pdata->dev, rdata->rx.hdr.pa_unmap.pages_dma,
rdata->rx.hdr.pa_unmap.pages_len,
DMA_FROM_DEVICE);
put_page(rdata->rx.hdr.pa_unmap.pages);
}
if (rdata->rx.buf.pa.pages)
put_page(rdata->rx.buf.pa.pages);
if (rdata->rx.buf.pa_unmap.pages) {
dma_unmap_page(pdata->dev, rdata->rx.buf.pa_unmap.pages_dma,
rdata->rx.buf.pa_unmap.pages_len,
DMA_FROM_DEVICE);
put_page(rdata->rx.buf.pa_unmap.pages);
}
memset(&rdata->tx, 0, sizeof(rdata->tx));
memset(&rdata->rx, 0, sizeof(rdata->rx));
rdata->mapped_as_page = 0;
if (rdata->state_saved) {
rdata->state_saved = 0;
rdata->state.skb = NULL;
rdata->state.len = 0;
rdata->state.error = 0;
}
}
static int xgbe_map_tx_skb(struct xgbe_channel *channel, struct sk_buff *skb)
{
struct xgbe_prv_data *pdata = channel->pdata;
struct xgbe_ring *ring = channel->tx_ring;
struct xgbe_ring_data *rdata;
struct xgbe_packet_data *packet;
skb_frag_t *frag;
dma_addr_t skb_dma;
unsigned int start_index, cur_index;
unsigned int offset, tso, vlan, datalen, len;
unsigned int i;
DBGPR("-->xgbe_map_tx_skb: cur = %d\n", ring->cur);
offset = 0;
start_index = ring->cur;
cur_index = ring->cur;
packet = &ring->packet_data;
packet->rdesc_count = 0;
packet->length = 0;
tso = XGMAC_GET_BITS(packet->attributes, TX_PACKET_ATTRIBUTES,
TSO_ENABLE);
vlan = XGMAC_GET_BITS(packet->attributes, TX_PACKET_ATTRIBUTES,
VLAN_CTAG);
/* Save space for a context descriptor if needed */
if ((tso && (packet->mss != ring->tx.cur_mss)) ||
(vlan && (packet->vlan_ctag != ring->tx.cur_vlan_ctag)))
cur_index++;
rdata = XGBE_GET_DESC_DATA(ring, cur_index);
if (tso) {
/* Map the TSO header */
skb_dma = dma_map_single(pdata->dev, skb->data,
packet->header_len, DMA_TO_DEVICE);
if (dma_mapping_error(pdata->dev, skb_dma)) {
netdev_alert(pdata->netdev, "dma_map_single failed\n");
goto err_out;
}
rdata->skb_dma = skb_dma;
rdata->skb_dma_len = packet->header_len;
netif_dbg(pdata, tx_queued, pdata->netdev,
"skb header: index=%u, dma=%pad, len=%u\n",
cur_index, &skb_dma, packet->header_len);
offset = packet->header_len;
packet->length += packet->header_len;
cur_index++;
rdata = XGBE_GET_DESC_DATA(ring, cur_index);
}
/* Map the (remainder of the) packet */
for (datalen = skb_headlen(skb) - offset; datalen; ) {
len = min_t(unsigned int, datalen, XGBE_TX_MAX_BUF_SIZE);
skb_dma = dma_map_single(pdata->dev, skb->data + offset, len,
DMA_TO_DEVICE);
if (dma_mapping_error(pdata->dev, skb_dma)) {
netdev_alert(pdata->netdev, "dma_map_single failed\n");
goto err_out;
}
rdata->skb_dma = skb_dma;
rdata->skb_dma_len = len;
netif_dbg(pdata, tx_queued, pdata->netdev,
"skb data: index=%u, dma=%pad, len=%u\n",
cur_index, &skb_dma, len);
datalen -= len;
offset += len;
packet->length += len;
cur_index++;
rdata = XGBE_GET_DESC_DATA(ring, cur_index);
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
netif_dbg(pdata, tx_queued, pdata->netdev,
"mapping frag %u\n", i);
frag = &skb_shinfo(skb)->frags[i];
offset = 0;
for (datalen = skb_frag_size(frag); datalen; ) {
len = min_t(unsigned int, datalen,
XGBE_TX_MAX_BUF_SIZE);
skb_dma = skb_frag_dma_map(pdata->dev, frag, offset,
len, DMA_TO_DEVICE);
if (dma_mapping_error(pdata->dev, skb_dma)) {
netdev_alert(pdata->netdev,
"skb_frag_dma_map failed\n");
goto err_out;
}
rdata->skb_dma = skb_dma;
rdata->skb_dma_len = len;
rdata->mapped_as_page = 1;
netif_dbg(pdata, tx_queued, pdata->netdev,
"skb frag: index=%u, dma=%pad, len=%u\n",
cur_index, &skb_dma, len);
datalen -= len;
offset += len;
packet->length += len;
cur_index++;
rdata = XGBE_GET_DESC_DATA(ring, cur_index);
}
}
/* Save the skb address in the last entry. We always have some data
* that has been mapped so rdata is always advanced past the last
* piece of mapped data - use the entry pointed to by cur_index - 1.
*/
rdata = XGBE_GET_DESC_DATA(ring, cur_index - 1);
rdata->skb = skb;
/* Save the number of descriptor entries used */
packet->rdesc_count = cur_index - start_index;
DBGPR("<--xgbe_map_tx_skb: count=%u\n", packet->rdesc_count);
return packet->rdesc_count;
err_out:
while (start_index < cur_index) {
rdata = XGBE_GET_DESC_DATA(ring, start_index++);
xgbe_unmap_rdata(pdata, rdata);
}
DBGPR("<--xgbe_map_tx_skb: count=0\n");
return 0;
}
void xgbe_init_function_ptrs_desc(struct xgbe_desc_if *desc_if)
{
DBGPR("-->xgbe_init_function_ptrs_desc\n");
desc_if->alloc_ring_resources = xgbe_alloc_ring_resources;
desc_if->free_ring_resources = xgbe_free_ring_resources;
desc_if->map_tx_skb = xgbe_map_tx_skb;
desc_if->map_rx_buffer = xgbe_map_rx_buffer;
desc_if->unmap_rdata = xgbe_unmap_rdata;
desc_if->wrapper_tx_desc_init = xgbe_wrapper_tx_descriptor_init;
desc_if->wrapper_rx_desc_init = xgbe_wrapper_rx_descriptor_init;
DBGPR("<--xgbe_init_function_ptrs_desc\n");
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,771 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/spinlock.h>
#include <linux/phy.h>
#include <linux/net_tstamp.h>
#include "xgbe.h"
#include "xgbe-common.h"
struct xgbe_stats {
char stat_string[ETH_GSTRING_LEN];
int stat_size;
int stat_offset;
};
#define XGMAC_MMC_STAT(_string, _var) \
{ _string, \
sizeof_field(struct xgbe_mmc_stats, _var), \
offsetof(struct xgbe_prv_data, mmc_stats._var), \
}
#define XGMAC_EXT_STAT(_string, _var) \
{ _string, \
sizeof_field(struct xgbe_ext_stats, _var), \
offsetof(struct xgbe_prv_data, ext_stats._var), \
}
static const struct xgbe_stats xgbe_gstring_stats[] = {
XGMAC_MMC_STAT("tx_bytes", txoctetcount_gb),
XGMAC_MMC_STAT("tx_packets", txframecount_gb),
XGMAC_MMC_STAT("tx_unicast_packets", txunicastframes_gb),
XGMAC_MMC_STAT("tx_broadcast_packets", txbroadcastframes_gb),
XGMAC_MMC_STAT("tx_multicast_packets", txmulticastframes_gb),
XGMAC_MMC_STAT("tx_vlan_packets", txvlanframes_g),
XGMAC_EXT_STAT("tx_vxlan_packets", tx_vxlan_packets),
XGMAC_EXT_STAT("tx_tso_packets", tx_tso_packets),
XGMAC_MMC_STAT("tx_64_byte_packets", tx64octets_gb),
XGMAC_MMC_STAT("tx_65_to_127_byte_packets", tx65to127octets_gb),
XGMAC_MMC_STAT("tx_128_to_255_byte_packets", tx128to255octets_gb),
XGMAC_MMC_STAT("tx_256_to_511_byte_packets", tx256to511octets_gb),
XGMAC_MMC_STAT("tx_512_to_1023_byte_packets", tx512to1023octets_gb),
XGMAC_MMC_STAT("tx_1024_to_max_byte_packets", tx1024tomaxoctets_gb),
XGMAC_MMC_STAT("tx_underflow_errors", txunderflowerror),
XGMAC_MMC_STAT("tx_pause_frames", txpauseframes),
XGMAC_MMC_STAT("rx_bytes", rxoctetcount_gb),
XGMAC_MMC_STAT("rx_packets", rxframecount_gb),
XGMAC_MMC_STAT("rx_unicast_packets", rxunicastframes_g),
XGMAC_MMC_STAT("rx_broadcast_packets", rxbroadcastframes_g),
XGMAC_MMC_STAT("rx_multicast_packets", rxmulticastframes_g),
XGMAC_MMC_STAT("rx_vlan_packets", rxvlanframes_gb),
XGMAC_EXT_STAT("rx_vxlan_packets", rx_vxlan_packets),
XGMAC_MMC_STAT("rx_64_byte_packets", rx64octets_gb),
XGMAC_MMC_STAT("rx_65_to_127_byte_packets", rx65to127octets_gb),
XGMAC_MMC_STAT("rx_128_to_255_byte_packets", rx128to255octets_gb),
XGMAC_MMC_STAT("rx_256_to_511_byte_packets", rx256to511octets_gb),
XGMAC_MMC_STAT("rx_512_to_1023_byte_packets", rx512to1023octets_gb),
XGMAC_MMC_STAT("rx_1024_to_max_byte_packets", rx1024tomaxoctets_gb),
XGMAC_MMC_STAT("rx_undersize_packets", rxundersize_g),
XGMAC_MMC_STAT("rx_oversize_packets", rxoversize_g),
XGMAC_MMC_STAT("rx_crc_errors", rxcrcerror),
XGMAC_MMC_STAT("rx_crc_errors_small_packets", rxrunterror),
XGMAC_MMC_STAT("rx_crc_errors_giant_packets", rxjabbererror),
XGMAC_MMC_STAT("rx_length_errors", rxlengtherror),
XGMAC_MMC_STAT("rx_out_of_range_errors", rxoutofrangetype),
XGMAC_MMC_STAT("rx_fifo_overflow_errors", rxfifooverflow),
XGMAC_MMC_STAT("rx_watchdog_errors", rxwatchdogerror),
XGMAC_EXT_STAT("rx_csum_errors", rx_csum_errors),
XGMAC_EXT_STAT("rx_vxlan_csum_errors", rx_vxlan_csum_errors),
XGMAC_MMC_STAT("rx_pause_frames", rxpauseframes),
XGMAC_EXT_STAT("rx_split_header_packets", rx_split_header_packets),
XGMAC_EXT_STAT("rx_buffer_unavailable", rx_buffer_unavailable),
};
#define XGBE_STATS_COUNT ARRAY_SIZE(xgbe_gstring_stats)
static void xgbe_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
int i;
switch (stringset) {
case ETH_SS_TEST:
xgbe_selftest_get_strings(pdata, data);
break;
case ETH_SS_STATS:
for (i = 0; i < XGBE_STATS_COUNT; i++)
ethtool_puts(&data, xgbe_gstring_stats[i].stat_string);
for (i = 0; i < pdata->tx_ring_count; i++) {
ethtool_sprintf(&data, "txq_%u_packets", i);
ethtool_sprintf(&data, "txq_%u_bytes", i);
}
for (i = 0; i < pdata->rx_ring_count; i++) {
ethtool_sprintf(&data, "rxq_%u_packets", i);
ethtool_sprintf(&data, "rxq_%u_bytes", i);
}
break;
}
}
static void xgbe_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *stats, u64 *data)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
u8 *stat;
int i;
pdata->hw_if.read_mmc_stats(pdata);
for (i = 0; i < XGBE_STATS_COUNT; i++) {
stat = (u8 *)pdata + xgbe_gstring_stats[i].stat_offset;
*data++ = *(u64 *)stat;
}
for (i = 0; i < pdata->tx_ring_count; i++) {
*data++ = pdata->ext_stats.txq_packets[i];
*data++ = pdata->ext_stats.txq_bytes[i];
}
for (i = 0; i < pdata->rx_ring_count; i++) {
*data++ = pdata->ext_stats.rxq_packets[i];
*data++ = pdata->ext_stats.rxq_bytes[i];
}
}
static int xgbe_get_sset_count(struct net_device *netdev, int stringset)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
int ret;
switch (stringset) {
case ETH_SS_TEST:
ret = xgbe_selftest_get_count(pdata);
break;
case ETH_SS_STATS:
ret = XGBE_STATS_COUNT +
(pdata->tx_ring_count * 2) +
(pdata->rx_ring_count * 2);
break;
default:
ret = -EOPNOTSUPP;
}
return ret;
}
static void xgbe_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
pause->autoneg = pdata->phy.pause_autoneg;
pause->tx_pause = pdata->phy.tx_pause;
pause->rx_pause = pdata->phy.rx_pause;
}
static int xgbe_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
struct ethtool_link_ksettings *lks = &pdata->phy.lks;
int ret = 0;
if (pause->autoneg && (pdata->phy.autoneg != AUTONEG_ENABLE)) {
netdev_err(netdev,
"autoneg disabled, pause autoneg not available\n");
return -EINVAL;
}
pdata->phy.pause_autoneg = pause->autoneg;
pdata->phy.tx_pause = pause->tx_pause;
pdata->phy.rx_pause = pause->rx_pause;
XGBE_CLR_ADV(lks, Pause);
XGBE_CLR_ADV(lks, Asym_Pause);
if (pause->rx_pause) {
XGBE_SET_ADV(lks, Pause);
XGBE_SET_ADV(lks, Asym_Pause);
}
if (pause->tx_pause) {
/* Equivalent to XOR of Asym_Pause */
if (XGBE_ADV(lks, Asym_Pause))
XGBE_CLR_ADV(lks, Asym_Pause);
else
XGBE_SET_ADV(lks, Asym_Pause);
}
if (netif_running(netdev))
ret = pdata->phy_if.phy_config_aneg(pdata);
return ret;
}
static int xgbe_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
struct ethtool_link_ksettings *lks = &pdata->phy.lks;
cmd->base.phy_address = pdata->phy.address;
if (netif_carrier_ok(netdev)) {
cmd->base.speed = pdata->phy.speed;
cmd->base.duplex = pdata->phy.duplex;
} else {
cmd->base.speed = SPEED_UNKNOWN;
cmd->base.duplex = DUPLEX_UNKNOWN;
}
cmd->base.autoneg = pdata->phy.autoneg;
cmd->base.port = PORT_NONE;
XGBE_LM_COPY(cmd, supported, lks, supported);
XGBE_LM_COPY(cmd, advertising, lks, advertising);
XGBE_LM_COPY(cmd, lp_advertising, lks, lp_advertising);
return 0;
}
static int xgbe_set_link_ksettings(struct net_device *netdev,
const struct ethtool_link_ksettings *cmd)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
struct ethtool_link_ksettings *lks = &pdata->phy.lks;
__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
u32 speed;
int ret;
speed = cmd->base.speed;
if (cmd->base.phy_address != pdata->phy.address) {
netdev_err(netdev, "invalid phy address %hhu\n",
cmd->base.phy_address);
return -EINVAL;
}
if ((cmd->base.autoneg != AUTONEG_ENABLE) &&
(cmd->base.autoneg != AUTONEG_DISABLE)) {
netdev_err(netdev, "unsupported autoneg %hhu\n",
cmd->base.autoneg);
return -EINVAL;
}
if (cmd->base.autoneg == AUTONEG_DISABLE) {
if (!pdata->phy_if.phy_valid_speed(pdata, speed)) {
netdev_err(netdev, "unsupported speed %u\n", speed);
return -EINVAL;
}
if (cmd->base.duplex != DUPLEX_FULL) {
netdev_err(netdev, "unsupported duplex %hhu\n",
cmd->base.duplex);
return -EINVAL;
}
}
netif_dbg(pdata, link, netdev,
"requested advertisement 0x%*pb, phy supported 0x%*pb\n",
__ETHTOOL_LINK_MODE_MASK_NBITS, cmd->link_modes.advertising,
__ETHTOOL_LINK_MODE_MASK_NBITS, lks->link_modes.supported);
linkmode_and(advertising, cmd->link_modes.advertising,
lks->link_modes.supported);
if ((cmd->base.autoneg == AUTONEG_ENABLE) &&
bitmap_empty(advertising, __ETHTOOL_LINK_MODE_MASK_NBITS)) {
netdev_err(netdev,
"unsupported requested advertisement\n");
return -EINVAL;
}
ret = 0;
pdata->phy.autoneg = cmd->base.autoneg;
pdata->phy.speed = speed;
pdata->phy.duplex = cmd->base.duplex;
linkmode_copy(lks->link_modes.advertising, advertising);
if (cmd->base.autoneg == AUTONEG_ENABLE)
XGBE_SET_ADV(lks, Autoneg);
else
XGBE_CLR_ADV(lks, Autoneg);
if (netif_running(netdev))
ret = pdata->phy_if.phy_config_aneg(pdata);
return ret;
}
static void xgbe_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *drvinfo)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
struct xgbe_hw_features *hw_feat = &pdata->hw_feat;
strscpy(drvinfo->driver, XGBE_DRV_NAME, sizeof(drvinfo->driver));
strscpy(drvinfo->bus_info, dev_name(pdata->dev),
sizeof(drvinfo->bus_info));
snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), "%d.%d.%d",
XGMAC_GET_BITS(hw_feat->version, MAC_VR, USERVER),
XGMAC_GET_BITS(hw_feat->version, MAC_VR, DEVID),
XGMAC_GET_BITS(hw_feat->version, MAC_VR, SNPSVER));
}
static u32 xgbe_get_msglevel(struct net_device *netdev)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
return pdata->msg_enable;
}
static void xgbe_set_msglevel(struct net_device *netdev, u32 msglevel)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
pdata->msg_enable = msglevel;
}
static int xgbe_get_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
memset(ec, 0, sizeof(struct ethtool_coalesce));
ec->rx_coalesce_usecs = pdata->rx_usecs;
ec->rx_max_coalesced_frames = pdata->rx_frames;
ec->tx_coalesce_usecs = pdata->tx_usecs;
ec->tx_max_coalesced_frames = pdata->tx_frames;
return 0;
}
static int xgbe_set_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
struct xgbe_hw_if *hw_if = &pdata->hw_if;
unsigned int rx_frames, rx_riwt, rx_usecs;
unsigned int tx_frames, tx_usecs;
unsigned int jiffy_us = jiffies_to_usecs(1);
rx_riwt = hw_if->usec_to_riwt(pdata, ec->rx_coalesce_usecs);
rx_usecs = ec->rx_coalesce_usecs;
rx_frames = ec->rx_max_coalesced_frames;
/* Use smallest possible value if conversion resulted in zero */
if (rx_usecs && !rx_riwt)
rx_riwt = 1;
/* Check the bounds of values for Rx */
if (rx_riwt > XGMAC_MAX_DMA_RIWT) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"rx-usec is limited to %d usecs",
hw_if->riwt_to_usec(pdata,
XGMAC_MAX_DMA_RIWT));
return -EINVAL;
}
if (rx_frames > pdata->rx_desc_count) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"rx-frames is limited to %d frames",
pdata->rx_desc_count);
return -EINVAL;
}
tx_usecs = ec->tx_coalesce_usecs;
tx_frames = ec->tx_max_coalesced_frames;
/* Check the bounds of values for Tx */
if (!tx_usecs) {
NL_SET_ERR_MSG_MOD(extack, "tx-usecs must not be 0");
return -EINVAL;
}
if (tx_usecs > XGMAC_MAX_COAL_TX_TICK) {
NL_SET_ERR_MSG_FMT_MOD(extack, "tx-usecs is limited to %d usec",
XGMAC_MAX_COAL_TX_TICK);
return -EINVAL;
}
if (tx_frames > pdata->tx_desc_count) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"tx-frames is limited to %d frames",
pdata->tx_desc_count);
return -EINVAL;
}
/* Round tx-usecs to nearest multiple of jiffy granularity */
if (tx_usecs % jiffy_us) {
tx_usecs = rounddown(tx_usecs, jiffy_us);
if (!tx_usecs)
tx_usecs = jiffy_us;
NL_SET_ERR_MSG_FMT_MOD(extack,
"tx-usecs rounded to %u usec due to jiffy granularity (%u usec)",
tx_usecs, jiffy_us);
}
pdata->rx_riwt = rx_riwt;
pdata->rx_usecs = rx_usecs;
pdata->rx_frames = rx_frames;
hw_if->config_rx_coalesce(pdata);
pdata->tx_usecs = tx_usecs;
pdata->tx_frames = tx_frames;
hw_if->config_tx_coalesce(pdata);
return 0;
}
static u32 xgbe_get_rx_ring_count(struct net_device *netdev)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
return pdata->rx_ring_count;
}
static u32 xgbe_get_rxfh_key_size(struct net_device *netdev)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
return sizeof(pdata->rss_key);
}
static u32 xgbe_get_rxfh_indir_size(struct net_device *netdev)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
return ARRAY_SIZE(pdata->rss_table);
}
static int xgbe_get_rxfh(struct net_device *netdev,
struct ethtool_rxfh_param *rxfh)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
unsigned int i;
if (rxfh->indir) {
for (i = 0; i < ARRAY_SIZE(pdata->rss_table); i++)
rxfh->indir[i] = XGMAC_GET_BITS(pdata->rss_table[i],
MAC_RSSDR, DMCH);
}
if (rxfh->key)
memcpy(rxfh->key, pdata->rss_key, sizeof(pdata->rss_key));
rxfh->hfunc = ETH_RSS_HASH_TOP;
return 0;
}
static int xgbe_set_rxfh(struct net_device *netdev,
struct ethtool_rxfh_param *rxfh,
struct netlink_ext_ack *extack)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
struct xgbe_hw_if *hw_if = &pdata->hw_if;
int ret;
if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
rxfh->hfunc != ETH_RSS_HASH_TOP) {
NL_SET_ERR_MSG_MOD(extack, "unsupported hash function");
return -EOPNOTSUPP;
}
if (rxfh->indir) {
ret = hw_if->set_rss_lookup_table(pdata, rxfh->indir);
if (ret)
return ret;
}
if (rxfh->key) {
ret = hw_if->set_rss_hash_key(pdata, rxfh->key);
if (ret)
return ret;
}
return 0;
}
static int xgbe_get_ts_info(struct net_device *netdev,
struct kernel_ethtool_ts_info *ts_info)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
ts_info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
if (pdata->ptp_clock)
ts_info->phc_index = ptp_clock_index(pdata->ptp_clock);
ts_info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON);
ts_info->rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V1_L4_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_ALL);
return 0;
}
static int xgbe_get_module_info(struct net_device *netdev,
struct ethtool_modinfo *modinfo)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
return pdata->phy_if.module_info(pdata, modinfo);
}
static int xgbe_get_module_eeprom(struct net_device *netdev,
struct ethtool_eeprom *eeprom, u8 *data)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
return pdata->phy_if.module_eeprom(pdata, eeprom, data);
}
static void
xgbe_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ringparam,
struct kernel_ethtool_ringparam *kernel_ringparam,
struct netlink_ext_ack *extack)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
ringparam->rx_max_pending = XGBE_RX_DESC_CNT_MAX;
ringparam->tx_max_pending = XGBE_TX_DESC_CNT_MAX;
ringparam->rx_pending = pdata->rx_desc_count;
ringparam->tx_pending = pdata->tx_desc_count;
}
static int xgbe_set_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ringparam,
struct kernel_ethtool_ringparam *kernel_ringparam,
struct netlink_ext_ack *extack)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
unsigned int rx, tx;
if (ringparam->rx_mini_pending || ringparam->rx_jumbo_pending) {
NL_SET_ERR_MSG_MOD(extack, "unsupported ring parameter");
return -EINVAL;
}
if ((ringparam->rx_pending < XGBE_RX_DESC_CNT_MIN) ||
(ringparam->rx_pending > XGBE_RX_DESC_CNT_MAX)) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"rx ring parameter must be between %u and %u",
XGBE_RX_DESC_CNT_MIN,
XGBE_RX_DESC_CNT_MAX);
return -EINVAL;
}
if ((ringparam->tx_pending < XGBE_TX_DESC_CNT_MIN) ||
(ringparam->tx_pending > XGBE_TX_DESC_CNT_MAX)) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"tx ring parameter must be between %u and %u",
XGBE_TX_DESC_CNT_MIN,
XGBE_TX_DESC_CNT_MAX);
return -EINVAL;
}
rx = __rounddown_pow_of_two(ringparam->rx_pending);
if (rx != ringparam->rx_pending)
NL_SET_ERR_MSG_FMT_MOD(extack,
"rx ring parameter rounded to power of two: %u",
rx);
tx = __rounddown_pow_of_two(ringparam->tx_pending);
if (tx != ringparam->tx_pending)
NL_SET_ERR_MSG_FMT_MOD(extack,
"tx ring parameter rounded to power of two: %u",
tx);
if ((rx == pdata->rx_desc_count) &&
(tx == pdata->tx_desc_count))
goto out;
pdata->rx_desc_count = rx;
pdata->tx_desc_count = tx;
xgbe_restart_dev(pdata);
out:
return 0;
}
static void xgbe_get_channels(struct net_device *netdev,
struct ethtool_channels *channels)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
unsigned int rx, tx, combined;
/* Calculate maximums allowed:
* - Take into account the number of available IRQs
* - Do not take into account the number of online CPUs so that
* the user can over-subscribe if desired
* - Tx is additionally limited by the number of hardware queues
*/
rx = min(pdata->hw_feat.rx_ch_cnt, pdata->rx_max_channel_count);
rx = min(rx, pdata->channel_irq_count);
tx = min(pdata->hw_feat.tx_ch_cnt, pdata->tx_max_channel_count);
tx = min(tx, pdata->channel_irq_count);
tx = min(tx, pdata->tx_max_q_count);
combined = min(rx, tx);
channels->max_combined = combined;
channels->max_rx = rx ? rx - 1 : 0;
channels->max_tx = tx ? tx - 1 : 0;
/* Get current settings based on device state */
rx = pdata->new_rx_ring_count ? : pdata->rx_ring_count;
tx = pdata->new_tx_ring_count ? : pdata->tx_ring_count;
combined = min(rx, tx);
rx -= combined;
tx -= combined;
channels->combined_count = combined;
channels->rx_count = rx;
channels->tx_count = tx;
}
static void xgbe_print_set_channels_input(struct net_device *netdev,
struct ethtool_channels *channels)
{
netdev_err(netdev, "channel inputs: combined=%u, rx-only=%u, tx-only=%u\n",
channels->combined_count, channels->rx_count,
channels->tx_count);
}
static int xgbe_set_channels(struct net_device *netdev,
struct ethtool_channels *channels)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
unsigned int rx, rx_curr, tx, tx_curr, combined;
/* Calculate maximums allowed:
* - Take into account the number of available IRQs
* - Do not take into account the number of online CPUs so that
* the user can over-subscribe if desired
* - Tx is additionally limited by the number of hardware queues
*/
rx = min(pdata->hw_feat.rx_ch_cnt, pdata->rx_max_channel_count);
rx = min(rx, pdata->channel_irq_count);
tx = min(pdata->hw_feat.tx_ch_cnt, pdata->tx_max_channel_count);
tx = min(tx, pdata->tx_max_q_count);
tx = min(tx, pdata->channel_irq_count);
combined = min(rx, tx);
/* Should not be setting other count */
if (channels->other_count) {
netdev_err(netdev,
"other channel count must be zero\n");
return -EINVAL;
}
/* Require at least one Combined (Rx and Tx) channel */
if (!channels->combined_count) {
netdev_err(netdev,
"at least one combined Rx/Tx channel is required\n");
xgbe_print_set_channels_input(netdev, channels);
return -EINVAL;
}
/* Check combined channels */
if (channels->combined_count > combined) {
netdev_err(netdev,
"combined channel count cannot exceed %u\n",
combined);
xgbe_print_set_channels_input(netdev, channels);
return -EINVAL;
}
/* Can have some Rx-only or Tx-only channels, but not both */
if (channels->rx_count && channels->tx_count) {
netdev_err(netdev,
"cannot specify both Rx-only and Tx-only channels\n");
xgbe_print_set_channels_input(netdev, channels);
return -EINVAL;
}
/* Check that we don't exceed the maximum number of channels */
if ((channels->combined_count + channels->rx_count) > rx) {
netdev_err(netdev,
"total Rx channels (%u) requested exceeds maximum available (%u)\n",
channels->combined_count + channels->rx_count, rx);
xgbe_print_set_channels_input(netdev, channels);
return -EINVAL;
}
if ((channels->combined_count + channels->tx_count) > tx) {
netdev_err(netdev,
"total Tx channels (%u) requested exceeds maximum available (%u)\n",
channels->combined_count + channels->tx_count, tx);
xgbe_print_set_channels_input(netdev, channels);
return -EINVAL;
}
rx = channels->combined_count + channels->rx_count;
tx = channels->combined_count + channels->tx_count;
rx_curr = pdata->new_rx_ring_count ? : pdata->rx_ring_count;
tx_curr = pdata->new_tx_ring_count ? : pdata->tx_ring_count;
if ((rx == rx_curr) && (tx == tx_curr))
goto out;
pdata->new_rx_ring_count = rx;
pdata->new_tx_ring_count = tx;
xgbe_full_restart_dev(pdata);
out:
return 0;
}
static const struct ethtool_ops xgbe_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES,
.get_drvinfo = xgbe_get_drvinfo,
.get_msglevel = xgbe_get_msglevel,
.set_msglevel = xgbe_set_msglevel,
.get_link = ethtool_op_get_link,
.get_coalesce = xgbe_get_coalesce,
.set_coalesce = xgbe_set_coalesce,
.get_pauseparam = xgbe_get_pauseparam,
.set_pauseparam = xgbe_set_pauseparam,
.get_strings = xgbe_get_strings,
.get_ethtool_stats = xgbe_get_ethtool_stats,
.get_sset_count = xgbe_get_sset_count,
.get_rx_ring_count = xgbe_get_rx_ring_count,
.get_rxfh_key_size = xgbe_get_rxfh_key_size,
.get_rxfh_indir_size = xgbe_get_rxfh_indir_size,
.get_rxfh = xgbe_get_rxfh,
.set_rxfh = xgbe_set_rxfh,
.get_ts_info = xgbe_get_ts_info,
.get_link_ksettings = xgbe_get_link_ksettings,
.set_link_ksettings = xgbe_set_link_ksettings,
.get_module_info = xgbe_get_module_info,
.get_module_eeprom = xgbe_get_module_eeprom,
.get_ringparam = xgbe_get_ringparam,
.set_ringparam = xgbe_set_ringparam,
.get_channels = xgbe_get_channels,
.set_channels = xgbe_set_channels,
.self_test = xgbe_selftest_run,
};
const struct ethtool_ops *xgbe_get_ethtool_ops(void)
{
return &xgbe_ethtool_ops;
}
@@ -0,0 +1,399 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*
* Author: Raju Rangoju <Raju.Rangoju@amd.com>
*/
#include "xgbe.h"
#include "xgbe-common.h"
void xgbe_update_tstamp_time(struct xgbe_prv_data *pdata,
unsigned int sec, unsigned int nsec)
{
int count;
/* Set the time values and tell the device */
XGMAC_IOWRITE(pdata, MAC_STSUR, sec);
XGMAC_IOWRITE(pdata, MAC_STNUR, nsec);
/* issue command to update the system time value */
XGMAC_IOWRITE(pdata, MAC_TSCR,
XGMAC_IOREAD(pdata, MAC_TSCR) |
(1 << MAC_TSCR_TSUPDT_INDEX));
/* Wait for the time adjust/update to complete */
count = 10000;
while (--count && XGMAC_IOREAD_BITS(pdata, MAC_TSCR, TSUPDT))
udelay(5);
if (count < 0)
netdev_err(pdata->netdev,
"timed out updating system timestamp\n");
}
void xgbe_update_tstamp_addend(struct xgbe_prv_data *pdata,
unsigned int addend)
{
unsigned int count = 10000;
/* Set the addend register value and tell the device */
XGMAC_IOWRITE(pdata, MAC_TSAR, addend);
XGMAC_IOWRITE_BITS(pdata, MAC_TSCR, TSADDREG, 1);
/* Wait for addend update to complete */
while (--count && XGMAC_IOREAD_BITS(pdata, MAC_TSCR, TSADDREG))
udelay(5);
if (!count)
netdev_err(pdata->netdev,
"timed out updating timestamp addend register\n");
}
void xgbe_set_tstamp_time(struct xgbe_prv_data *pdata, unsigned int sec,
unsigned int nsec)
{
unsigned int count = 10000;
/* Set the time values and tell the device */
XGMAC_IOWRITE(pdata, MAC_STSUR, sec);
XGMAC_IOWRITE(pdata, MAC_STNUR, nsec);
XGMAC_IOWRITE_BITS(pdata, MAC_TSCR, TSINIT, 1);
/* Wait for time update to complete */
while (--count && XGMAC_IOREAD_BITS(pdata, MAC_TSCR, TSINIT))
udelay(5);
if (!count)
netdev_err(pdata->netdev, "timed out initializing timestamp\n");
}
u64 xgbe_get_tstamp_time(struct xgbe_prv_data *pdata)
{
u64 nsec;
nsec = XGMAC_IOREAD(pdata, MAC_STSR);
nsec *= NSEC_PER_SEC;
nsec += XGMAC_IOREAD(pdata, MAC_STNR);
return nsec;
}
u64 xgbe_get_tx_tstamp(struct xgbe_prv_data *pdata)
{
unsigned int tx_snr, tx_ssr;
u64 nsec;
if (pdata->vdata->tx_tstamp_workaround) {
tx_snr = XGMAC_IOREAD(pdata, MAC_TXSNR);
tx_ssr = XGMAC_IOREAD(pdata, MAC_TXSSR);
} else {
tx_ssr = XGMAC_IOREAD(pdata, MAC_TXSSR);
tx_snr = XGMAC_IOREAD(pdata, MAC_TXSNR);
}
if (XGMAC_GET_BITS(tx_snr, MAC_TXSNR, TXTSSTSMIS))
return 0;
nsec = tx_ssr;
nsec *= NSEC_PER_SEC;
nsec += tx_snr;
return nsec;
}
void xgbe_get_rx_tstamp(struct xgbe_packet_data *packet,
struct xgbe_ring_desc *rdesc)
{
u64 nsec;
if (XGMAC_GET_BITS_LE(rdesc->desc3, RX_CONTEXT_DESC3, TSA) &&
!XGMAC_GET_BITS_LE(rdesc->desc3, RX_CONTEXT_DESC3, TSD)) {
nsec = le32_to_cpu(rdesc->desc1);
nsec *= NSEC_PER_SEC;
nsec += le32_to_cpu(rdesc->desc0);
if (nsec != 0xffffffffffffffffULL) {
packet->rx_tstamp = nsec;
XGMAC_SET_BITS(packet->attributes, RX_PACKET_ATTRIBUTES,
RX_TSTAMP, 1);
}
}
}
void xgbe_config_tstamp(struct xgbe_prv_data *pdata, unsigned int mac_tscr)
{
unsigned int value = 0;
value = XGMAC_IOREAD(pdata, MAC_TSCR);
value |= mac_tscr;
XGMAC_IOWRITE(pdata, MAC_TSCR, value);
}
void xgbe_tx_tstamp(struct work_struct *work)
{
struct xgbe_prv_data *pdata = container_of(work,
struct xgbe_prv_data,
tx_tstamp_work);
struct skb_shared_hwtstamps hwtstamps;
unsigned long flags;
spin_lock_irqsave(&pdata->tstamp_lock, flags);
if (!pdata->tx_tstamp_skb)
goto unlock;
if (pdata->tx_tstamp) {
memset(&hwtstamps, 0, sizeof(hwtstamps));
hwtstamps.hwtstamp = ns_to_ktime(pdata->tx_tstamp);
skb_tstamp_tx(pdata->tx_tstamp_skb, &hwtstamps);
}
dev_kfree_skb_any(pdata->tx_tstamp_skb);
pdata->tx_tstamp_skb = NULL;
unlock:
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
}
int xgbe_get_hwtstamp_settings(struct net_device *netdev,
struct kernel_hwtstamp_config *config)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
*config = pdata->tstamp_config;
return 0;
}
int xgbe_set_hwtstamp_settings(struct net_device *netdev,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
unsigned int mac_tscr = 0;
switch (config->tx_type) {
case HWTSTAMP_TX_OFF:
break;
case HWTSTAMP_TX_ON:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
default:
return -ERANGE;
}
switch (config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_NTP_ALL:
case HWTSTAMP_FILTER_ALL:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENALL, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* PTP v2, UDP, any kind of event packet */
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSVER2ENA, 1);
fallthrough; /* to PTP v1, UDP, any kind of event packet */
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV4ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV6ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, SNAPTYPSEL, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* PTP v2, UDP, Sync packet */
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSVER2ENA, 1);
fallthrough; /* to PTP v1, UDP, Sync packet */
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV4ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV6ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSEVNTENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* PTP v2, UDP, Delay_req packet */
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSVER2ENA, 1);
fallthrough; /* to PTP v1, UDP, Delay_req packet */
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV4ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV6ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSEVNTENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSMSTRENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* 802.AS1, Ethernet, any kind of event packet */
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, AV8021ASMEN, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, SNAPTYPSEL, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* 802.AS1, Ethernet, Sync packet */
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, AV8021ASMEN, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSEVNTENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* 802.AS1, Ethernet, Delay_req packet */
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, AV8021ASMEN, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSMSTRENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSEVNTENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* PTP v2/802.AS1, any layer, any kind of event packet */
case HWTSTAMP_FILTER_PTP_V2_EVENT:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSVER2ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV4ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV6ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, SNAPTYPSEL, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* PTP v2/802.AS1, any layer, Sync packet */
case HWTSTAMP_FILTER_PTP_V2_SYNC:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSVER2ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV4ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV6ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSEVNTENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
/* PTP v2/802.AS1, any layer, Delay_req packet */
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSVER2ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV4ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSIPV6ENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSMSTRENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSEVNTENA, 1);
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
break;
default:
return -ERANGE;
}
xgbe_config_tstamp(pdata, mac_tscr);
pdata->tstamp_config = *config;
return 0;
}
void xgbe_prep_tx_tstamp(struct xgbe_prv_data *pdata,
struct sk_buff *skb,
struct xgbe_packet_data *packet)
{
unsigned long flags;
if (XGMAC_GET_BITS(packet->attributes, TX_PACKET_ATTRIBUTES, PTP)) {
spin_lock_irqsave(&pdata->tstamp_lock, flags);
if (pdata->tx_tstamp_skb) {
/* Another timestamp in progress, ignore this one */
XGMAC_SET_BITS(packet->attributes,
TX_PACKET_ATTRIBUTES, PTP, 0);
} else {
pdata->tx_tstamp_skb = skb_get(skb);
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
}
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
}
skb_tx_timestamp(skb);
}
int xgbe_init_ptp(struct xgbe_prv_data *pdata)
{
unsigned int mac_tscr = 0;
struct timespec64 now;
u64 dividend;
/* Register Settings to be done based on the link speed. */
switch (pdata->phy.speed) {
case SPEED_1000:
XGMAC_IOWRITE(pdata, MAC_TICNR, MAC_TICNR_1G_INITVAL);
XGMAC_IOWRITE(pdata, MAC_TECNR, MAC_TECNR_1G_INITVAL);
break;
case SPEED_2500:
case SPEED_10000:
XGMAC_IOWRITE_BITS(pdata, MAC_TICSNR, TSICSNS,
MAC_TICSNR_10G_INITVAL);
XGMAC_IOWRITE(pdata, MAC_TECNR, MAC_TECNR_10G_INITVAL);
XGMAC_IOWRITE_BITS(pdata, MAC_TECSNR, TSECSNS,
MAC_TECSNR_10G_INITVAL);
break;
case SPEED_UNKNOWN:
default:
break;
}
/* Enable IEEE1588 PTP clock. */
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSENA, 1);
/* Overwrite earlier timestamps */
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TXTSSTSM, 1);
/* Set one nano-second accuracy */
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSCTRLSSR, 1);
/* Set fine timestamp update */
XGMAC_SET_BITS(mac_tscr, MAC_TSCR, TSCFUPDT, 1);
xgbe_config_tstamp(pdata, mac_tscr);
/* Exit if timestamping is not enabled */
if (!XGMAC_GET_BITS(mac_tscr, MAC_TSCR, TSENA))
return -EOPNOTSUPP;
if (pdata->vdata->tstamp_ptp_clock_freq) {
/* Initialize time registers based on
* 125MHz PTP Clock Frequency
*/
XGMAC_IOWRITE_BITS(pdata, MAC_SSIR, SSINC,
XGBE_V2_TSTAMP_SSINC);
XGMAC_IOWRITE_BITS(pdata, MAC_SSIR, SNSINC,
XGBE_V2_TSTAMP_SNSINC);
} else {
/* Initialize time registers based on
* 50MHz PTP Clock Frequency
*/
XGMAC_IOWRITE_BITS(pdata, MAC_SSIR, SSINC, XGBE_TSTAMP_SSINC);
XGMAC_IOWRITE_BITS(pdata, MAC_SSIR, SNSINC, XGBE_TSTAMP_SNSINC);
}
/* Calculate the addend:
* addend = 2^32 / (PTP ref clock / (PTP clock based on SSINC))
* = (2^32 * (PTP clock based on SSINC)) / PTP ref clock
*/
if (pdata->vdata->tstamp_ptp_clock_freq)
dividend = XGBE_V2_PTP_ACT_CLK_FREQ;
else
dividend = XGBE_PTP_ACT_CLK_FREQ;
dividend = (u64)(dividend << 32);
pdata->tstamp_addend = div_u64(dividend, pdata->ptpclk_rate);
xgbe_update_tstamp_addend(pdata, pdata->tstamp_addend);
dma_wmb();
/* initialize system time */
ktime_get_real_ts64(&now);
/* lower 32 bits of tv_sec are safe until y2106 */
xgbe_set_tstamp_time(pdata, (u32)now.tv_sec, now.tv_nsec);
return 0;
}
@@ -0,0 +1,405 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/kmod.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include "xgbe.h"
#include "xgbe-common.h"
#define XGBE_ABORT_COUNT 500
#define XGBE_DISABLE_COUNT 1000
#define XGBE_STD_SPEED 1
#define XGBE_INTR_RX_FULL BIT(IC_RAW_INTR_STAT_RX_FULL_INDEX)
#define XGBE_INTR_TX_EMPTY BIT(IC_RAW_INTR_STAT_TX_EMPTY_INDEX)
#define XGBE_INTR_TX_ABRT BIT(IC_RAW_INTR_STAT_TX_ABRT_INDEX)
#define XGBE_INTR_STOP_DET BIT(IC_RAW_INTR_STAT_STOP_DET_INDEX)
#define XGBE_DEFAULT_INT_MASK (XGBE_INTR_RX_FULL | \
XGBE_INTR_TX_EMPTY | \
XGBE_INTR_TX_ABRT | \
XGBE_INTR_STOP_DET)
#define XGBE_I2C_READ BIT(8)
#define XGBE_I2C_STOP BIT(9)
static int xgbe_i2c_abort(struct xgbe_prv_data *pdata)
{
unsigned int wait = XGBE_ABORT_COUNT;
/* Must be enabled to recognize the abort request */
XI2C_IOWRITE_BITS(pdata, IC_ENABLE, EN, 1);
/* Issue the abort */
XI2C_IOWRITE_BITS(pdata, IC_ENABLE, ABORT, 1);
while (wait--) {
if (!XI2C_IOREAD_BITS(pdata, IC_ENABLE, ABORT))
return 0;
usleep_range(500, 600);
}
return -EBUSY;
}
static int xgbe_i2c_set_enable(struct xgbe_prv_data *pdata, bool enable)
{
unsigned int wait = XGBE_DISABLE_COUNT;
unsigned int mode = enable ? 1 : 0;
while (wait--) {
XI2C_IOWRITE_BITS(pdata, IC_ENABLE, EN, mode);
if (XI2C_IOREAD_BITS(pdata, IC_ENABLE_STATUS, EN) == mode)
return 0;
usleep_range(100, 110);
}
return -EBUSY;
}
static int xgbe_i2c_disable(struct xgbe_prv_data *pdata)
{
int ret;
ret = xgbe_i2c_set_enable(pdata, false);
if (ret) {
/* Disable failed, try an abort */
ret = xgbe_i2c_abort(pdata);
if (ret)
return ret;
/* Abort succeeded, try to disable again */
ret = xgbe_i2c_set_enable(pdata, false);
}
return ret;
}
static int xgbe_i2c_enable(struct xgbe_prv_data *pdata)
{
return xgbe_i2c_set_enable(pdata, true);
}
static void xgbe_i2c_clear_all_interrupts(struct xgbe_prv_data *pdata)
{
XI2C_IOREAD(pdata, IC_CLR_INTR);
}
static void xgbe_i2c_disable_interrupts(struct xgbe_prv_data *pdata)
{
XI2C_IOWRITE(pdata, IC_INTR_MASK, 0);
}
static void xgbe_i2c_enable_interrupts(struct xgbe_prv_data *pdata)
{
XI2C_IOWRITE(pdata, IC_INTR_MASK, XGBE_DEFAULT_INT_MASK);
}
static void xgbe_i2c_write(struct xgbe_prv_data *pdata)
{
struct xgbe_i2c_op_state *state = &pdata->i2c.op_state;
unsigned int tx_slots;
unsigned int cmd;
/* Configured to never receive Rx overflows, so fill up Tx fifo */
tx_slots = pdata->i2c.tx_fifo_size - XI2C_IOREAD(pdata, IC_TXFLR);
while (tx_slots && state->tx_len) {
if (state->op->cmd == XGBE_I2C_CMD_READ)
cmd = XGBE_I2C_READ;
else
cmd = *state->tx_buf++;
if (state->tx_len == 1)
XI2C_SET_BITS(cmd, IC_DATA_CMD, STOP, 1);
XI2C_IOWRITE(pdata, IC_DATA_CMD, cmd);
tx_slots--;
state->tx_len--;
}
/* No more Tx operations, so ignore TX_EMPTY and return */
if (!state->tx_len)
XI2C_IOWRITE_BITS(pdata, IC_INTR_MASK, TX_EMPTY, 0);
}
static void xgbe_i2c_read(struct xgbe_prv_data *pdata)
{
struct xgbe_i2c_op_state *state = &pdata->i2c.op_state;
unsigned int rx_slots;
/* Anything to be read? */
if (state->op->cmd != XGBE_I2C_CMD_READ)
return;
rx_slots = XI2C_IOREAD(pdata, IC_RXFLR);
while (rx_slots && state->rx_len) {
*state->rx_buf++ = XI2C_IOREAD(pdata, IC_DATA_CMD);
state->rx_len--;
rx_slots--;
}
}
static void xgbe_i2c_clear_isr_interrupts(struct xgbe_prv_data *pdata,
unsigned int isr)
{
struct xgbe_i2c_op_state *state = &pdata->i2c.op_state;
if (isr & XGBE_INTR_TX_ABRT) {
state->tx_abort_source = XI2C_IOREAD(pdata, IC_TX_ABRT_SOURCE);
XI2C_IOREAD(pdata, IC_CLR_TX_ABRT);
}
if (isr & XGBE_INTR_STOP_DET)
XI2C_IOREAD(pdata, IC_CLR_STOP_DET);
}
static void xgbe_i2c_isr_bh_work(struct work_struct *work)
{
struct xgbe_prv_data *pdata = from_work(pdata, work, i2c_bh_work);
struct xgbe_i2c_op_state *state = &pdata->i2c.op_state;
unsigned int isr;
isr = XI2C_IOREAD(pdata, IC_RAW_INTR_STAT);
if (!isr)
goto reissue_check;
netif_dbg(pdata, intr, pdata->netdev,
"I2C interrupt received: status=%#010x\n", isr);
xgbe_i2c_clear_isr_interrupts(pdata, isr);
if (isr & XGBE_INTR_TX_ABRT) {
netif_dbg(pdata, link, pdata->netdev,
"I2C TX_ABRT received (%#010x) for target %#04x\n",
state->tx_abort_source, state->op->target);
xgbe_i2c_disable_interrupts(pdata);
state->ret = -EIO;
goto out;
}
/* Check for data in the Rx fifo */
xgbe_i2c_read(pdata);
/* Fill up the Tx fifo next */
xgbe_i2c_write(pdata);
out:
/* Complete on an error or STOP condition */
if (state->ret || XI2C_GET_BITS(isr, IC_RAW_INTR_STAT, STOP_DET))
complete(&pdata->i2c_complete);
reissue_check:
/* Reissue interrupt if status is not clear */
if (pdata->vdata->irq_reissue_support)
XP_IOWRITE(pdata, XP_INT_REISSUE_EN, 1 << 2);
}
static irqreturn_t xgbe_i2c_isr(int irq, void *data)
{
struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)data;
if (pdata->isr_as_bh_work)
queue_work(system_bh_wq, &pdata->i2c_bh_work);
else
xgbe_i2c_isr_bh_work(&pdata->i2c_bh_work);
return IRQ_HANDLED;
}
static void xgbe_i2c_set_mode(struct xgbe_prv_data *pdata)
{
unsigned int reg;
reg = XI2C_IOREAD(pdata, IC_CON);
XI2C_SET_BITS(reg, IC_CON, MASTER_MODE, 1);
XI2C_SET_BITS(reg, IC_CON, SLAVE_DISABLE, 1);
XI2C_SET_BITS(reg, IC_CON, RESTART_EN, 1);
XI2C_SET_BITS(reg, IC_CON, SPEED, XGBE_STD_SPEED);
XI2C_SET_BITS(reg, IC_CON, RX_FIFO_FULL_HOLD, 1);
XI2C_IOWRITE(pdata, IC_CON, reg);
}
static void xgbe_i2c_get_features(struct xgbe_prv_data *pdata)
{
struct xgbe_i2c *i2c = &pdata->i2c;
unsigned int reg;
reg = XI2C_IOREAD(pdata, IC_COMP_PARAM_1);
i2c->max_speed_mode = XI2C_GET_BITS(reg, IC_COMP_PARAM_1,
MAX_SPEED_MODE);
i2c->rx_fifo_size = XI2C_GET_BITS(reg, IC_COMP_PARAM_1,
RX_BUFFER_DEPTH);
i2c->tx_fifo_size = XI2C_GET_BITS(reg, IC_COMP_PARAM_1,
TX_BUFFER_DEPTH);
if (netif_msg_probe(pdata))
dev_dbg(pdata->dev, "I2C features: %s=%u, %s=%u, %s=%u\n",
"MAX_SPEED_MODE", i2c->max_speed_mode,
"RX_BUFFER_DEPTH", i2c->rx_fifo_size,
"TX_BUFFER_DEPTH", i2c->tx_fifo_size);
}
static void xgbe_i2c_set_target(struct xgbe_prv_data *pdata, unsigned int addr)
{
XI2C_IOWRITE(pdata, IC_TAR, addr);
}
static irqreturn_t xgbe_i2c_combined_isr(struct xgbe_prv_data *pdata)
{
xgbe_i2c_isr_bh_work(&pdata->i2c_bh_work);
return IRQ_HANDLED;
}
static int xgbe_i2c_xfer(struct xgbe_prv_data *pdata, struct xgbe_i2c_op *op)
{
struct xgbe_i2c_op_state *state = &pdata->i2c.op_state;
int ret;
mutex_lock(&pdata->i2c_mutex);
reinit_completion(&pdata->i2c_complete);
ret = xgbe_i2c_disable(pdata);
if (ret) {
netdev_err(pdata->netdev, "failed to disable i2c master\n");
goto unlock;
}
xgbe_i2c_set_target(pdata, op->target);
memset(state, 0, sizeof(*state));
state->op = op;
state->tx_len = op->len;
state->tx_buf = op->buf;
state->rx_len = op->len;
state->rx_buf = op->buf;
xgbe_i2c_clear_all_interrupts(pdata);
ret = xgbe_i2c_enable(pdata);
if (ret) {
netdev_err(pdata->netdev, "failed to enable i2c master\n");
goto unlock;
}
/* Enabling the interrupts will cause the TX FIFO empty interrupt to
* fire and begin to process the command via the ISR.
*/
xgbe_i2c_enable_interrupts(pdata);
if (!wait_for_completion_timeout(&pdata->i2c_complete, HZ)) {
netdev_err(pdata->netdev, "i2c operation timed out\n");
ret = -ETIMEDOUT;
goto disable;
}
ret = state->ret;
if (ret) {
if (state->tx_abort_source & IC_TX_ABRT_7B_ADDR_NOACK)
ret = -ENOTCONN;
else if (state->tx_abort_source & IC_TX_ABRT_ARB_LOST)
ret = -EAGAIN;
}
disable:
xgbe_i2c_disable_interrupts(pdata);
xgbe_i2c_disable(pdata);
unlock:
mutex_unlock(&pdata->i2c_mutex);
return ret;
}
static void xgbe_i2c_stop(struct xgbe_prv_data *pdata)
{
if (!pdata->i2c.started)
return;
netif_dbg(pdata, link, pdata->netdev, "stopping I2C\n");
pdata->i2c.started = 0;
xgbe_i2c_disable_interrupts(pdata);
xgbe_i2c_disable(pdata);
xgbe_i2c_clear_all_interrupts(pdata);
if (pdata->dev_irq != pdata->i2c_irq) {
devm_free_irq(pdata->dev, pdata->i2c_irq, pdata);
cancel_work_sync(&pdata->i2c_bh_work);
}
}
static int xgbe_i2c_start(struct xgbe_prv_data *pdata)
{
int ret;
if (pdata->i2c.started)
return 0;
netif_dbg(pdata, link, pdata->netdev, "starting I2C\n");
/* If we have a separate I2C irq, enable it */
if (pdata->dev_irq != pdata->i2c_irq) {
INIT_WORK(&pdata->i2c_bh_work, xgbe_i2c_isr_bh_work);
ret = devm_request_irq(pdata->dev, pdata->i2c_irq,
xgbe_i2c_isr, 0, pdata->i2c_name,
pdata);
if (ret) {
netdev_err(pdata->netdev, "i2c irq request failed\n");
return ret;
}
}
pdata->i2c.started = 1;
return 0;
}
static int xgbe_i2c_init(struct xgbe_prv_data *pdata)
{
int ret;
xgbe_i2c_disable_interrupts(pdata);
ret = xgbe_i2c_disable(pdata);
if (ret) {
dev_err(pdata->dev, "failed to disable i2c master\n");
return ret;
}
xgbe_i2c_get_features(pdata);
xgbe_i2c_set_mode(pdata);
xgbe_i2c_clear_all_interrupts(pdata);
return 0;
}
void xgbe_init_function_ptrs_i2c(struct xgbe_i2c_if *i2c_if)
{
i2c_if->i2c_init = xgbe_i2c_init;
i2c_if->i2c_start = xgbe_i2c_start;
i2c_if->i2c_stop = xgbe_i2c_stop;
i2c_if->i2c_xfer = xgbe_i2c_xfer;
i2c_if->i2c_isr = xgbe_i2c_combined_isr;
}
@@ -0,0 +1,380 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/io.h>
#include <linux/notifier.h>
#include "xgbe.h"
#include "xgbe-common.h"
MODULE_AUTHOR("Tom Lendacky <thomas.lendacky@amd.com>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION(XGBE_DRV_DESC);
static int debug = -1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, " Network interface message level setting");
static const u32 default_msg_level = (NETIF_MSG_LINK | NETIF_MSG_IFDOWN |
NETIF_MSG_IFUP);
static void xgbe_default_config(struct xgbe_prv_data *pdata)
{
DBGPR("-->xgbe_default_config\n");
pdata->blen = DMA_SBMR_BLEN_64;
pdata->pbl = DMA_PBL_128;
pdata->aal = 1;
pdata->rd_osr_limit = 8;
pdata->wr_osr_limit = 8;
pdata->tx_sf_mode = MTL_TSF_ENABLE;
pdata->tx_threshold = MTL_TX_THRESHOLD_64;
pdata->tx_osp_mode = DMA_OSP_ENABLE;
pdata->rx_sf_mode = MTL_RSF_DISABLE;
pdata->rx_threshold = MTL_RX_THRESHOLD_64;
pdata->pause_autoneg = 1;
pdata->tx_pause = 1;
pdata->rx_pause = 1;
pdata->phy_speed = SPEED_UNKNOWN;
pdata->power_down = 0;
DBGPR("<--xgbe_default_config\n");
}
static void xgbe_init_all_fptrs(struct xgbe_prv_data *pdata)
{
xgbe_init_function_ptrs_dev(&pdata->hw_if);
xgbe_init_function_ptrs_phy(&pdata->phy_if);
xgbe_init_function_ptrs_i2c(&pdata->i2c_if);
xgbe_init_function_ptrs_desc(&pdata->desc_if);
pdata->vdata->init_function_ptrs_phy_impl(&pdata->phy_if);
}
struct xgbe_prv_data *xgbe_alloc_pdata(struct device *dev)
{
struct xgbe_prv_data *pdata;
struct net_device *netdev;
netdev = alloc_etherdev_mq(sizeof(struct xgbe_prv_data),
XGBE_MAX_DMA_CHANNELS);
if (!netdev) {
dev_err(dev, "alloc_etherdev_mq failed\n");
return ERR_PTR(-ENOMEM);
}
SET_NETDEV_DEV(netdev, dev);
pdata = netdev_priv(netdev);
pdata->netdev = netdev;
pdata->dev = dev;
spin_lock_init(&pdata->xpcs_lock);
mutex_init(&pdata->rss_mutex);
spin_lock_init(&pdata->tstamp_lock);
mutex_init(&pdata->i2c_mutex);
init_completion(&pdata->i2c_complete);
init_completion(&pdata->mdio_complete);
pdata->msg_enable = netif_msg_init(debug, default_msg_level);
set_bit(XGBE_DOWN, &pdata->dev_state);
set_bit(XGBE_STOPPED, &pdata->dev_state);
return pdata;
}
void xgbe_free_pdata(struct xgbe_prv_data *pdata)
{
struct net_device *netdev = pdata->netdev;
free_netdev(netdev);
}
void xgbe_set_counts(struct xgbe_prv_data *pdata)
{
/* Set all the function pointers */
xgbe_init_all_fptrs(pdata);
/* Populate the hardware features */
xgbe_get_all_hw_features(pdata);
/* Set default max values if not provided */
if (!pdata->tx_max_channel_count)
pdata->tx_max_channel_count = pdata->hw_feat.tx_ch_cnt;
if (!pdata->rx_max_channel_count)
pdata->rx_max_channel_count = pdata->hw_feat.rx_ch_cnt;
if (!pdata->tx_max_q_count)
pdata->tx_max_q_count = pdata->hw_feat.tx_q_cnt;
if (!pdata->rx_max_q_count)
pdata->rx_max_q_count = pdata->hw_feat.rx_q_cnt;
/* Calculate the number of Tx and Rx rings to be created
* -Tx (DMA) Channels map 1-to-1 to Tx Queues so set
* the number of Tx queues to the number of Tx channels
* enabled
* -Rx (DMA) Channels do not map 1-to-1 so use the actual
* number of Rx queues or maximum allowed
*/
pdata->tx_ring_count = min_t(unsigned int, num_online_cpus(),
pdata->hw_feat.tx_ch_cnt);
pdata->tx_ring_count = min_t(unsigned int, pdata->tx_ring_count,
pdata->tx_max_channel_count);
pdata->tx_ring_count = min_t(unsigned int, pdata->tx_ring_count,
pdata->tx_max_q_count);
pdata->tx_q_count = pdata->tx_ring_count;
pdata->rx_ring_count = min_t(unsigned int, num_online_cpus(),
pdata->hw_feat.rx_ch_cnt);
pdata->rx_ring_count = min_t(unsigned int, pdata->rx_ring_count,
pdata->rx_max_channel_count);
pdata->rx_q_count = min_t(unsigned int, pdata->hw_feat.rx_q_cnt,
pdata->rx_max_q_count);
if (netif_msg_probe(pdata)) {
dev_dbg(pdata->dev, "TX/RX DMA channel count = %u/%u\n",
pdata->tx_ring_count, pdata->rx_ring_count);
dev_dbg(pdata->dev, "TX/RX hardware queue count = %u/%u\n",
pdata->tx_q_count, pdata->rx_q_count);
}
}
int xgbe_config_netdev(struct xgbe_prv_data *pdata)
{
struct net_device *netdev = pdata->netdev;
struct device *dev = pdata->dev;
int ret;
netdev->irq = pdata->dev_irq;
netdev->base_addr = (unsigned long)pdata->xgmac_regs;
eth_hw_addr_set(netdev, pdata->mac_addr);
/* Initialize ECC timestamps */
pdata->tx_sec_period = jiffies;
pdata->tx_ded_period = jiffies;
pdata->rx_sec_period = jiffies;
pdata->rx_ded_period = jiffies;
pdata->desc_sec_period = jiffies;
pdata->desc_ded_period = jiffies;
/* Issue software reset to device */
ret = pdata->hw_if.exit(pdata);
if (ret) {
dev_err(dev, "software reset failed\n");
return ret;
}
/* Set default configuration data */
xgbe_default_config(pdata);
/* Set the DMA mask */
ret = dma_set_mask_and_coherent(dev,
DMA_BIT_MASK(pdata->hw_feat.dma_width));
if (ret) {
dev_err(dev, "dma_set_mask_and_coherent failed\n");
return ret;
}
/* Set default max values if not provided */
if (!pdata->tx_max_fifo_size)
pdata->tx_max_fifo_size = pdata->hw_feat.tx_fifo_size;
if (!pdata->rx_max_fifo_size)
pdata->rx_max_fifo_size = pdata->hw_feat.rx_fifo_size;
/* Set and validate the number of descriptors for a ring */
BUILD_BUG_ON_NOT_POWER_OF_2(XGBE_TX_DESC_CNT);
pdata->tx_desc_count = XGBE_TX_DESC_CNT;
BUILD_BUG_ON_NOT_POWER_OF_2(XGBE_RX_DESC_CNT);
pdata->rx_desc_count = XGBE_RX_DESC_CNT;
/* Adjust the number of queues based on interrupts assigned */
if (pdata->channel_irq_count) {
pdata->tx_ring_count = min_t(unsigned int, pdata->tx_ring_count,
pdata->channel_irq_count);
pdata->rx_ring_count = min_t(unsigned int, pdata->rx_ring_count,
pdata->channel_irq_count);
if (netif_msg_probe(pdata))
dev_dbg(pdata->dev,
"adjusted TX/RX DMA channel count = %u/%u\n",
pdata->tx_ring_count, pdata->rx_ring_count);
}
/* Initialize RSS hash key */
netdev_rss_key_fill(pdata->rss_key, sizeof(pdata->rss_key));
XGMAC_SET_BITS(pdata->rss_options, MAC_RSSCR, IP2TE, 1);
XGMAC_SET_BITS(pdata->rss_options, MAC_RSSCR, TCP4TE, 1);
XGMAC_SET_BITS(pdata->rss_options, MAC_RSSCR, UDP4TE, 1);
/* Call MDIO/PHY initialization routine */
pdata->debugfs_an_cdr_workaround = pdata->vdata->an_cdr_workaround;
ret = pdata->phy_if.phy_init(pdata);
if (ret)
return ret;
/* Set device operations */
netdev->netdev_ops = xgbe_get_netdev_ops();
netdev->ethtool_ops = xgbe_get_ethtool_ops();
#ifdef CONFIG_AMD_XGBE_DCB
netdev->dcbnl_ops = xgbe_get_dcbnl_ops();
#endif
/* Set device features */
netdev->hw_features = NETIF_F_SG |
NETIF_F_IP_CSUM |
NETIF_F_IPV6_CSUM |
NETIF_F_RXCSUM |
NETIF_F_TSO |
NETIF_F_TSO6 |
NETIF_F_GRO |
NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_CTAG_FILTER;
if (pdata->hw_feat.rss)
netdev->hw_features |= NETIF_F_RXHASH;
if (pdata->hw_feat.vxn) {
netdev->hw_enc_features = NETIF_F_SG |
NETIF_F_IP_CSUM |
NETIF_F_IPV6_CSUM |
NETIF_F_RXCSUM |
NETIF_F_TSO |
NETIF_F_TSO6 |
NETIF_F_GRO |
NETIF_F_GSO_UDP_TUNNEL |
NETIF_F_GSO_UDP_TUNNEL_CSUM;
netdev->hw_features |= NETIF_F_GSO_UDP_TUNNEL |
NETIF_F_GSO_UDP_TUNNEL_CSUM;
netdev->udp_tunnel_nic_info = xgbe_get_udp_tunnel_info();
}
netdev->vlan_features |= NETIF_F_SG |
NETIF_F_IP_CSUM |
NETIF_F_IPV6_CSUM |
NETIF_F_TSO |
NETIF_F_TSO6;
netdev->features |= netdev->hw_features;
pdata->netdev_features = netdev->features;
netdev->priv_flags |= IFF_UNICAST_FLT;
netdev->min_mtu = 0;
netdev->max_mtu = XGMAC_GIANT_PACKET_MTU - XGBE_ETH_FRAME_HDR;
/* Use default watchdog timeout */
netdev->watchdog_timeo = 0;
xgbe_init_rx_coalesce(pdata);
xgbe_init_tx_coalesce(pdata);
netif_carrier_off(netdev);
ret = register_netdev(netdev);
if (ret) {
dev_err(dev, "net device registration failed\n");
return ret;
}
if (IS_REACHABLE(CONFIG_PTP_1588_CLOCK))
xgbe_ptp_register(pdata);
xgbe_debugfs_init(pdata);
netif_dbg(pdata, drv, pdata->netdev, "%u Tx software queues\n",
pdata->tx_ring_count);
netif_dbg(pdata, drv, pdata->netdev, "%u Rx software queues\n",
pdata->rx_ring_count);
return 0;
}
void xgbe_deconfig_netdev(struct xgbe_prv_data *pdata)
{
struct net_device *netdev = pdata->netdev;
xgbe_debugfs_exit(pdata);
if (IS_REACHABLE(CONFIG_PTP_1588_CLOCK))
xgbe_ptp_unregister(pdata);
unregister_netdev(netdev);
pdata->phy_if.phy_exit(pdata);
}
static int xgbe_netdev_event(struct notifier_block *nb, unsigned long event,
void *data)
{
struct net_device *netdev = netdev_notifier_info_to_dev(data);
struct xgbe_prv_data *pdata = netdev_priv(netdev);
if (netdev->netdev_ops != xgbe_get_netdev_ops())
goto out;
switch (event) {
case NETDEV_CHANGENAME:
xgbe_debugfs_rename(pdata);
break;
default:
break;
}
out:
return NOTIFY_DONE;
}
static struct notifier_block xgbe_netdev_notifier = {
.notifier_call = xgbe_netdev_event,
};
static int __init xgbe_mod_init(void)
{
int ret;
ret = register_netdevice_notifier(&xgbe_netdev_notifier);
if (ret)
return ret;
ret = xgbe_platform_init();
if (ret)
goto err_platform_init;
ret = xgbe_pci_init();
if (ret)
goto err_pci_init;
return 0;
err_pci_init:
xgbe_platform_exit();
err_platform_init:
unregister_netdevice_notifier(&xgbe_netdev_notifier);
return ret;
}
static void __exit xgbe_mod_exit(void)
{
xgbe_pci_exit();
xgbe_platform_exit();
unregister_netdevice_notifier(&xgbe_netdev_notifier);
}
module_init(xgbe_mod_init);
module_exit(xgbe_mod_exit);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,551 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/log2.h>
#include "xgbe-smn.h"
#include "xgbe.h"
#include "xgbe-common.h"
static int xgbe_config_multi_msi(struct xgbe_prv_data *pdata)
{
unsigned int vector_count;
unsigned int i, j;
int ret;
vector_count = XGBE_MSI_BASE_COUNT;
vector_count += max(pdata->rx_ring_count,
pdata->tx_ring_count);
ret = pci_alloc_irq_vectors(pdata->pcidev, XGBE_MSI_MIN_COUNT,
vector_count, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (ret < 0) {
dev_info(pdata->dev, "multi MSI/MSI-X enablement failed\n");
return ret;
}
pdata->isr_as_bh_work = 1;
pdata->irq_count = ret;
pdata->dev_irq = pci_irq_vector(pdata->pcidev, 0);
pdata->ecc_irq = pci_irq_vector(pdata->pcidev, 1);
pdata->i2c_irq = pci_irq_vector(pdata->pcidev, 2);
pdata->an_irq = pci_irq_vector(pdata->pcidev, 3);
for (i = XGBE_MSI_BASE_COUNT, j = 0; i < ret; i++, j++)
pdata->channel_irq[j] = pci_irq_vector(pdata->pcidev, i);
pdata->channel_irq_count = j;
pdata->per_channel_irq = 1;
pdata->channel_irq_mode = XGBE_IRQ_MODE_LEVEL;
if (netif_msg_probe(pdata))
dev_dbg(pdata->dev, "multi %s interrupts enabled\n",
pdata->pcidev->msix_enabled ? "MSI-X" : "MSI");
return 0;
}
static int xgbe_config_irqs(struct xgbe_prv_data *pdata)
{
int ret;
ret = xgbe_config_multi_msi(pdata);
if (!ret)
goto out;
ret = pci_alloc_irq_vectors(pdata->pcidev, 1, 1,
PCI_IRQ_INTX | PCI_IRQ_MSI);
if (ret < 0) {
dev_info(pdata->dev, "single IRQ enablement failed\n");
return ret;
}
pdata->isr_as_bh_work = pdata->pcidev->msi_enabled ? 1 : 0;
pdata->irq_count = 1;
pdata->channel_irq_count = 1;
pdata->dev_irq = pci_irq_vector(pdata->pcidev, 0);
pdata->ecc_irq = pci_irq_vector(pdata->pcidev, 0);
pdata->i2c_irq = pci_irq_vector(pdata->pcidev, 0);
pdata->an_irq = pci_irq_vector(pdata->pcidev, 0);
if (netif_msg_probe(pdata))
dev_dbg(pdata->dev, "single %s interrupt enabled\n",
pdata->pcidev->msi_enabled ? "MSI" : "legacy");
out:
if (netif_msg_probe(pdata)) {
unsigned int i;
dev_dbg(pdata->dev, " dev irq=%d\n", pdata->dev_irq);
dev_dbg(pdata->dev, " ecc irq=%d\n", pdata->ecc_irq);
dev_dbg(pdata->dev, " i2c irq=%d\n", pdata->i2c_irq);
dev_dbg(pdata->dev, " an irq=%d\n", pdata->an_irq);
for (i = 0; i < pdata->channel_irq_count; i++)
dev_dbg(pdata->dev, " dma%u irq=%d\n",
i, pdata->channel_irq[i]);
}
return 0;
}
static int xgbe_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
void __iomem * const *iomap_table;
unsigned int port_addr_size, reg;
struct device *dev = &pdev->dev;
struct xgbe_prv_data *pdata;
unsigned int ma_lo, ma_hi;
struct pci_dev *rdev;
int bar_mask, ret;
u32 address;
pdata = xgbe_alloc_pdata(dev);
if (IS_ERR(pdata)) {
ret = PTR_ERR(pdata);
goto err_alloc;
}
pdata->pcidev = pdev;
pci_set_drvdata(pdev, pdata);
/* Get the version data */
pdata->vdata = (struct xgbe_version_data *)id->driver_data;
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(dev, "pcim_enable_device failed\n");
goto err_pci_enable;
}
/* Obtain the mmio areas for the device */
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
ret = pcim_iomap_regions(pdev, bar_mask, XGBE_DRV_NAME);
if (ret) {
dev_err(dev, "pcim_iomap_regions failed\n");
goto err_pci_enable;
}
iomap_table = pcim_iomap_table(pdev);
if (!iomap_table) {
dev_err(dev, "pcim_iomap_table failed\n");
ret = -ENOMEM;
goto err_pci_enable;
}
pdata->xgmac_regs = iomap_table[XGBE_XGMAC_BAR];
if (!pdata->xgmac_regs) {
dev_err(dev, "xgmac ioremap failed\n");
ret = -ENOMEM;
goto err_pci_enable;
}
pdata->xprop_regs = pdata->xgmac_regs + XGBE_MAC_PROP_OFFSET;
pdata->xi2c_regs = pdata->xgmac_regs + XGBE_I2C_CTRL_OFFSET;
if (netif_msg_probe(pdata)) {
dev_dbg(dev, "xgmac_regs = %p\n", pdata->xgmac_regs);
dev_dbg(dev, "xprop_regs = %p\n", pdata->xprop_regs);
dev_dbg(dev, "xi2c_regs = %p\n", pdata->xi2c_regs);
}
pdata->xpcs_regs = iomap_table[XGBE_XPCS_BAR];
if (!pdata->xpcs_regs) {
dev_err(dev, "xpcs ioremap failed\n");
ret = -ENOMEM;
goto err_pci_enable;
}
if (netif_msg_probe(pdata))
dev_dbg(dev, "xpcs_regs = %p\n", pdata->xpcs_regs);
/* Set the PCS indirect addressing definition registers */
rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
if (rdev && rdev->vendor == PCI_VENDOR_ID_AMD) {
switch (rdev->device) {
case XGBE_P100a_PCI_DEVICE_ID:
pdata->xpcs_window_def_reg = PCS_P100a_WINDOW_DEF;
pdata->xpcs_window_sel_reg = PCS_P100a_WINDOW_SELECT;
/* P100a devices do not need rrc and cdr workaround */
pdata->vdata->an_cdr_workaround = 0;
pdata->vdata->enable_rrc = 0;
break;
case XGBE_RV_PCI_DEVICE_ID:
pdata->xpcs_window_def_reg = PCS_V2_RV_WINDOW_DEF;
pdata->xpcs_window_sel_reg = PCS_V2_RV_WINDOW_SELECT;
break;
case XGBE_YC_PCI_DEVICE_ID:
pdata->xpcs_window_def_reg = PCS_V2_YC_WINDOW_DEF;
pdata->xpcs_window_sel_reg = PCS_V2_YC_WINDOW_SELECT;
/* Yellow Carp devices do not need cdr workaround */
pdata->vdata->an_cdr_workaround = 0;
/* Yellow Carp devices do not need rrc */
pdata->vdata->enable_rrc = 0;
break;
case XGBE_RN_PCI_DEVICE_ID:
pdata->xpcs_window_def_reg = PCS_V3_RN_WINDOW_DEF;
pdata->xpcs_window_sel_reg = PCS_V3_RN_WINDOW_SELECT;
break;
default:
pdata->xpcs_window_def_reg = PCS_V2_WINDOW_DEF;
pdata->xpcs_window_sel_reg = PCS_V2_WINDOW_SELECT;
break;
}
} else {
pdata->xpcs_window_def_reg = PCS_V2_WINDOW_DEF;
pdata->xpcs_window_sel_reg = PCS_V2_WINDOW_SELECT;
}
pci_dev_put(rdev);
/* Configure the PCS indirect addressing support */
if (pdata->vdata->xpcs_access == XGBE_XPCS_ACCESS_V3) {
reg = XP_IOREAD(pdata, XP_PROP_0);
port_addr_size = PCS_RN_PORT_ADDR_SIZE *
XP_GET_BITS(reg, XP_PROP_0, PORT_ID);
pdata->smn_base = PCS_RN_SMN_BASE_ADDR + port_addr_size;
address = pdata->smn_base + (pdata->xpcs_window_def_reg);
ret = amd_smn_read(0, address, &reg);
if (ret) {
pci_err(pdata->pcidev, "Failed to read data\n");
goto err_pci_enable;
}
} else {
reg = XPCS32_IOREAD(pdata, pdata->xpcs_window_def_reg);
}
pdata->xpcs_window = XPCS_GET_BITS(reg, PCS_V2_WINDOW_DEF, OFFSET);
pdata->xpcs_window <<= 6;
pdata->xpcs_window_size = XPCS_GET_BITS(reg, PCS_V2_WINDOW_DEF, SIZE);
pdata->xpcs_window_size = 1 << (pdata->xpcs_window_size + 7);
pdata->xpcs_window_mask = pdata->xpcs_window_size - 1;
if (netif_msg_probe(pdata)) {
dev_dbg(dev, "xpcs window def = %#010x\n",
pdata->xpcs_window_def_reg);
dev_dbg(dev, "xpcs window sel = %#010x\n",
pdata->xpcs_window_sel_reg);
dev_dbg(dev, "xpcs window = %#010x\n",
pdata->xpcs_window);
dev_dbg(dev, "xpcs window size = %#010x\n",
pdata->xpcs_window_size);
dev_dbg(dev, "xpcs window mask = %#010x\n",
pdata->xpcs_window_mask);
}
pci_set_master(pdev);
/* Enable all interrupts in the hardware */
XP_IOWRITE(pdata, XP_INT_EN, 0x1fffff);
/* Retrieve the MAC address */
ma_lo = XP_IOREAD(pdata, XP_MAC_ADDR_LO);
ma_hi = XP_IOREAD(pdata, XP_MAC_ADDR_HI);
pdata->mac_addr[0] = ma_lo & 0xff;
pdata->mac_addr[1] = (ma_lo >> 8) & 0xff;
pdata->mac_addr[2] = (ma_lo >> 16) & 0xff;
pdata->mac_addr[3] = (ma_lo >> 24) & 0xff;
pdata->mac_addr[4] = ma_hi & 0xff;
pdata->mac_addr[5] = (ma_hi >> 8) & 0xff;
if (!XP_GET_BITS(ma_hi, XP_MAC_ADDR_HI, VALID) ||
!is_valid_ether_addr(pdata->mac_addr)) {
dev_err(dev, "invalid mac address\n");
ret = -EINVAL;
goto err_pci_enable;
}
/* Clock settings */
pdata->sysclk_rate = XGBE_V2_DMA_CLOCK_FREQ;
pdata->ptpclk_rate = XGBE_V2_PTP_CLOCK_FREQ;
/* Set the DMA coherency values */
pdata->coherent = 1;
pdata->arcr = XGBE_DMA_PCI_ARCR;
pdata->awcr = XGBE_DMA_PCI_AWCR;
pdata->awarcr = XGBE_DMA_PCI_AWARCR;
/* Read the port property registers */
pdata->pp0 = XP_IOREAD(pdata, XP_PROP_0);
pdata->pp1 = XP_IOREAD(pdata, XP_PROP_1);
pdata->pp2 = XP_IOREAD(pdata, XP_PROP_2);
pdata->pp3 = XP_IOREAD(pdata, XP_PROP_3);
pdata->pp4 = XP_IOREAD(pdata, XP_PROP_4);
if (netif_msg_probe(pdata)) {
dev_dbg(dev, "port property 0 = %#010x\n", pdata->pp0);
dev_dbg(dev, "port property 1 = %#010x\n", pdata->pp1);
dev_dbg(dev, "port property 2 = %#010x\n", pdata->pp2);
dev_dbg(dev, "port property 3 = %#010x\n", pdata->pp3);
dev_dbg(dev, "port property 4 = %#010x\n", pdata->pp4);
}
/* Set the maximum channels and queues */
pdata->tx_max_channel_count = XP_GET_BITS(pdata->pp1, XP_PROP_1,
MAX_TX_DMA);
pdata->rx_max_channel_count = XP_GET_BITS(pdata->pp1, XP_PROP_1,
MAX_RX_DMA);
pdata->tx_max_q_count = XP_GET_BITS(pdata->pp1, XP_PROP_1,
MAX_TX_QUEUES);
pdata->rx_max_q_count = XP_GET_BITS(pdata->pp1, XP_PROP_1,
MAX_RX_QUEUES);
if (netif_msg_probe(pdata)) {
dev_dbg(dev, "max tx/rx channel count = %u/%u\n",
pdata->tx_max_channel_count,
pdata->rx_max_channel_count);
dev_dbg(dev, "max tx/rx hw queue count = %u/%u\n",
pdata->tx_max_q_count, pdata->rx_max_q_count);
}
/* Set the hardware channel and queue counts */
xgbe_set_counts(pdata);
/* Set the maximum fifo amounts */
pdata->tx_max_fifo_size = XP_GET_BITS(pdata->pp2, XP_PROP_2,
TX_FIFO_SIZE);
pdata->tx_max_fifo_size *= 16384;
pdata->tx_max_fifo_size = min(pdata->tx_max_fifo_size,
pdata->vdata->tx_max_fifo_size);
pdata->rx_max_fifo_size = XP_GET_BITS(pdata->pp2, XP_PROP_2,
RX_FIFO_SIZE);
pdata->rx_max_fifo_size *= 16384;
pdata->rx_max_fifo_size = min(pdata->rx_max_fifo_size,
pdata->vdata->rx_max_fifo_size);
if (netif_msg_probe(pdata))
dev_dbg(dev, "max tx/rx max fifo size = %u/%u\n",
pdata->tx_max_fifo_size, pdata->rx_max_fifo_size);
/* Configure interrupt support */
ret = xgbe_config_irqs(pdata);
if (ret)
goto err_pci_enable;
/* Configure the netdev resource */
ret = xgbe_config_netdev(pdata);
if (ret)
goto err_irq_vectors;
netdev_notice(pdata->netdev, "net device enabled\n");
return 0;
err_irq_vectors:
pci_free_irq_vectors(pdata->pcidev);
err_pci_enable:
xgbe_free_pdata(pdata);
err_alloc:
dev_notice(dev, "net device not enabled\n");
return ret;
}
static void xgbe_pci_remove(struct pci_dev *pdev)
{
struct xgbe_prv_data *pdata = pci_get_drvdata(pdev);
xgbe_deconfig_netdev(pdata);
pci_free_irq_vectors(pdata->pcidev);
/* Disable all interrupts in the hardware */
XP_IOWRITE(pdata, XP_INT_EN, 0x0);
xgbe_free_pdata(pdata);
}
static void xgbe_pci_synchronize_irqs(struct xgbe_prv_data *pdata)
{
unsigned int i;
/* Synchronize main device interrupt */
synchronize_irq(pdata->dev_irq);
/* Synchronize ECC interrupt if separate from main device interrupt */
if (pdata->vdata->ecc_support && pdata->dev_irq != pdata->ecc_irq)
synchronize_irq(pdata->ecc_irq);
/* Synchronize I2C interrupt if separate from main device interrupt */
if (pdata->vdata->i2c_support && pdata->dev_irq != pdata->i2c_irq)
synchronize_irq(pdata->i2c_irq);
/* Synchronize AN interrupt if separate from main device interrupt */
if (pdata->dev_irq != pdata->an_irq)
synchronize_irq(pdata->an_irq);
/* Synchronize per-channel DMA interrupts */
if (pdata->per_channel_irq) {
for (i = 0; i < pdata->channel_count; i++)
synchronize_irq(pdata->channel[i]->dma_irq);
}
}
static int xgbe_pci_suspend(struct device *dev)
{
struct xgbe_prv_data *pdata = dev_get_drvdata(dev);
struct net_device *netdev = pdata->netdev;
struct pci_dev *pdev = to_pci_dev(dev);
int ret = 0;
if (netif_running(netdev))
ret = xgbe_powerdown(netdev);
/* Disable all device interrupts to prevent spurious wakeups */
XP_IOWRITE(pdata, XP_INT_EN, 0x0);
/* Ensure no IRQ handlers are still executing before powering down.
* This prevents race conditions where an IRQ handler could access
* invalid register state after the device is disabled.
*/
xgbe_pci_synchronize_irqs(pdata);
/* Set PHY to low-power mode */
pdata->lpm_ctrl = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
pdata->lpm_ctrl |= MDIO_CTRL1_LPOWER;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, pdata->lpm_ctrl);
/* Disable bus mastering to prevent DMA activity */
pci_clear_master(pdev);
/* Save PCI configuration state and disable device */
pci_save_state(pdev);
pci_disable_device(pdev);
/* Disable wake from D3 - required for S0i3 deep sleep */
pci_wake_from_d3(pdev, false);
pci_set_power_state(pdev, PCI_D3hot);
return ret;
}
static int xgbe_pci_resume(struct device *dev)
{
struct xgbe_prv_data *pdata = dev_get_drvdata(dev);
struct net_device *netdev = pdata->netdev;
struct pci_dev *pdev = to_pci_dev(dev);
int ret = 0;
/* Restore PCI power state */
pci_set_power_state(pdev, PCI_D0);
/* Restore PCI configuration state */
pci_restore_state(pdev);
/* Enable PCI device */
ret = pci_enable_device(pdev);
if (ret) {
dev_err(dev, "pci_enable_device failed: %d\n", ret);
return ret;
}
/* Re-enable bus mastering */
pci_set_master(pdev);
/* Re-enable all device interrupts */
XP_IOWRITE(pdata, XP_INT_EN, 0x1fffff);
/* Clear PHY low-power mode */
pdata->lpm_ctrl &= ~MDIO_CTRL1_LPOWER;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, pdata->lpm_ctrl);
if (netif_running(netdev)) {
ret = xgbe_powerup(netdev);
/* Schedule a restart in case the link or phy state changed
* while we were powered down.
*/
schedule_work(&pdata->restart_work);
}
return ret;
}
static struct xgbe_version_data xgbe_v3 = {
.init_function_ptrs_phy_impl = xgbe_init_function_ptrs_phy_v2,
.xpcs_access = XGBE_XPCS_ACCESS_V3,
.mmc_64bit = 1,
.tx_max_fifo_size = 65536,
.rx_max_fifo_size = 65536,
.tx_tstamp_workaround = 1,
.ecc_support = 1,
.i2c_support = 1,
.irq_reissue_support = 1,
.tx_desc_prefetch = 5,
.rx_desc_prefetch = 5,
.an_cdr_workaround = 0,
.enable_rrc = 0,
};
static struct xgbe_version_data xgbe_v2a = {
.init_function_ptrs_phy_impl = xgbe_init_function_ptrs_phy_v2,
.xpcs_access = XGBE_XPCS_ACCESS_V2,
.mmc_64bit = 1,
.tx_max_fifo_size = 229376,
.rx_max_fifo_size = 229376,
.tx_tstamp_workaround = 1,
.tstamp_ptp_clock_freq = 1,
.ecc_support = 1,
.i2c_support = 1,
.irq_reissue_support = 1,
.tx_desc_prefetch = 5,
.rx_desc_prefetch = 5,
.an_cdr_workaround = 1,
.enable_rrc = 1,
};
static struct xgbe_version_data xgbe_v2b = {
.init_function_ptrs_phy_impl = xgbe_init_function_ptrs_phy_v2,
.xpcs_access = XGBE_XPCS_ACCESS_V2,
.mmc_64bit = 1,
.tx_max_fifo_size = 65536,
.rx_max_fifo_size = 65536,
.tx_tstamp_workaround = 1,
.tstamp_ptp_clock_freq = 1,
.ecc_support = 1,
.i2c_support = 1,
.irq_reissue_support = 1,
.tx_desc_prefetch = 5,
.rx_desc_prefetch = 5,
.an_cdr_workaround = 1,
.enable_rrc = 1,
};
static const struct pci_device_id xgbe_pci_table[] = {
{ PCI_VDEVICE(AMD, 0x1458),
.driver_data = (kernel_ulong_t)&xgbe_v2a },
{ PCI_VDEVICE(AMD, 0x1459),
.driver_data = (kernel_ulong_t)&xgbe_v2b },
{ PCI_VDEVICE(AMD, 0x1641),
.driver_data = (kernel_ulong_t)&xgbe_v3 },
/* Last entry must be zero */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, xgbe_pci_table);
static DEFINE_SIMPLE_DEV_PM_OPS(xgbe_pci_pm_ops,
xgbe_pci_suspend,
xgbe_pci_resume);
static struct pci_driver xgbe_driver = {
.name = XGBE_DRV_NAME,
.id_table = xgbe_pci_table,
.probe = xgbe_pci_probe,
.remove = xgbe_pci_remove,
.driver.pm = pm_sleep_ptr(&xgbe_pci_pm_ops),
};
int xgbe_pci_init(void)
{
return pci_register_driver(&xgbe_driver);
}
void xgbe_pci_exit(void)
{
pci_unregister_driver(&xgbe_driver);
}
@@ -0,0 +1,744 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/device.h>
#include <linux/property.h>
#include <linux/mdio.h>
#include <linux/phy.h>
#include "xgbe.h"
#include "xgbe-common.h"
#define XGBE_BLWC_PROPERTY "amd,serdes-blwc"
#define XGBE_CDR_RATE_PROPERTY "amd,serdes-cdr-rate"
#define XGBE_PQ_SKEW_PROPERTY "amd,serdes-pq-skew"
#define XGBE_TX_AMP_PROPERTY "amd,serdes-tx-amp"
#define XGBE_DFE_CFG_PROPERTY "amd,serdes-dfe-tap-config"
#define XGBE_DFE_ENA_PROPERTY "amd,serdes-dfe-tap-enable"
/* Default SerDes settings */
#define XGBE_SPEED_1000_BLWC 1
#define XGBE_SPEED_1000_CDR 0x2
#define XGBE_SPEED_1000_PLL 0x0
#define XGBE_SPEED_1000_PQ 0xa
#define XGBE_SPEED_1000_RATE 0x3
#define XGBE_SPEED_1000_TXAMP 0xf
#define XGBE_SPEED_1000_WORD 0x1
#define XGBE_SPEED_1000_DFE_TAP_CONFIG 0x3
#define XGBE_SPEED_1000_DFE_TAP_ENABLE 0x0
#define XGBE_SPEED_2500_BLWC 1
#define XGBE_SPEED_2500_CDR 0x2
#define XGBE_SPEED_2500_PLL 0x0
#define XGBE_SPEED_2500_PQ 0xa
#define XGBE_SPEED_2500_RATE 0x1
#define XGBE_SPEED_2500_TXAMP 0xf
#define XGBE_SPEED_2500_WORD 0x1
#define XGBE_SPEED_2500_DFE_TAP_CONFIG 0x3
#define XGBE_SPEED_2500_DFE_TAP_ENABLE 0x0
#define XGBE_SPEED_10000_BLWC 0
#define XGBE_SPEED_10000_CDR 0x7
#define XGBE_SPEED_10000_PLL 0x1
#define XGBE_SPEED_10000_PQ 0x12
#define XGBE_SPEED_10000_RATE 0x0
#define XGBE_SPEED_10000_TXAMP 0xa
#define XGBE_SPEED_10000_WORD 0x7
#define XGBE_SPEED_10000_DFE_TAP_CONFIG 0x1
#define XGBE_SPEED_10000_DFE_TAP_ENABLE 0x7f
/* Rate-change complete wait/retry count */
#define XGBE_RATECHANGE_COUNT 500
static const u32 xgbe_phy_blwc[] = {
XGBE_SPEED_1000_BLWC,
XGBE_SPEED_2500_BLWC,
XGBE_SPEED_10000_BLWC,
};
static const u32 xgbe_phy_cdr_rate[] = {
XGBE_SPEED_1000_CDR,
XGBE_SPEED_2500_CDR,
XGBE_SPEED_10000_CDR,
};
static const u32 xgbe_phy_pq_skew[] = {
XGBE_SPEED_1000_PQ,
XGBE_SPEED_2500_PQ,
XGBE_SPEED_10000_PQ,
};
static const u32 xgbe_phy_tx_amp[] = {
XGBE_SPEED_1000_TXAMP,
XGBE_SPEED_2500_TXAMP,
XGBE_SPEED_10000_TXAMP,
};
static const u32 xgbe_phy_dfe_tap_cfg[] = {
XGBE_SPEED_1000_DFE_TAP_CONFIG,
XGBE_SPEED_2500_DFE_TAP_CONFIG,
XGBE_SPEED_10000_DFE_TAP_CONFIG,
};
static const u32 xgbe_phy_dfe_tap_ena[] = {
XGBE_SPEED_1000_DFE_TAP_ENABLE,
XGBE_SPEED_2500_DFE_TAP_ENABLE,
XGBE_SPEED_10000_DFE_TAP_ENABLE,
};
struct xgbe_phy_data {
/* 1000/10000 vs 2500/10000 indicator */
unsigned int speed_set;
/* SerDes UEFI configurable settings.
* Switching between modes/speeds requires new values for some
* SerDes settings. The values can be supplied as device
* properties in array format. The first array entry is for
* 1GbE, second for 2.5GbE and third for 10GbE
*/
u32 blwc[XGBE_SPEEDS];
u32 cdr_rate[XGBE_SPEEDS];
u32 pq_skew[XGBE_SPEEDS];
u32 tx_amp[XGBE_SPEEDS];
u32 dfe_tap_cfg[XGBE_SPEEDS];
u32 dfe_tap_ena[XGBE_SPEEDS];
};
static void xgbe_phy_kr_training_pre(struct xgbe_prv_data *pdata)
{
XSIR0_IOWRITE_BITS(pdata, SIR0_KR_RT_1, RESET, 1);
}
static void xgbe_phy_kr_training_post(struct xgbe_prv_data *pdata)
{
XSIR0_IOWRITE_BITS(pdata, SIR0_KR_RT_1, RESET, 0);
}
static enum xgbe_mode xgbe_phy_an_outcome(struct xgbe_prv_data *pdata)
{
struct ethtool_link_ksettings *lks = &pdata->phy.lks;
struct xgbe_phy_data *phy_data = pdata->phy_data;
enum xgbe_mode mode;
unsigned int ad_reg, lp_reg;
XGBE_SET_LP_ADV(lks, Autoneg);
XGBE_SET_LP_ADV(lks, Backplane);
/* Compare Advertisement and Link Partner register 1 */
ad_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE);
lp_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_LPA);
if (lp_reg & 0x400)
XGBE_SET_LP_ADV(lks, Pause);
if (lp_reg & 0x800)
XGBE_SET_LP_ADV(lks, Asym_Pause);
if (pdata->phy.pause_autoneg) {
/* Set flow control based on auto-negotiation result */
pdata->phy.tx_pause = 0;
pdata->phy.rx_pause = 0;
if (ad_reg & lp_reg & 0x400) {
pdata->phy.tx_pause = 1;
pdata->phy.rx_pause = 1;
} else if (ad_reg & lp_reg & 0x800) {
if (ad_reg & 0x400)
pdata->phy.rx_pause = 1;
else if (lp_reg & 0x400)
pdata->phy.tx_pause = 1;
}
}
/* Compare Advertisement and Link Partner register 2 */
ad_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1);
lp_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_LPA + 1);
if (lp_reg & 0x80)
XGBE_SET_LP_ADV(lks, 10000baseKR_Full);
if (lp_reg & 0x20) {
if (phy_data->speed_set == XGBE_SPEEDSET_2500_10000)
XGBE_SET_LP_ADV(lks, 2500baseX_Full);
else
XGBE_SET_LP_ADV(lks, 1000baseKX_Full);
}
ad_reg &= lp_reg;
if (ad_reg & 0x80) {
mode = XGBE_MODE_KR;
} else if (ad_reg & 0x20) {
if (phy_data->speed_set == XGBE_SPEEDSET_2500_10000)
mode = XGBE_MODE_KX_2500;
else
mode = XGBE_MODE_KX_1000;
} else {
mode = XGBE_MODE_UNKNOWN;
}
/* Compare Advertisement and Link Partner register 3 */
ad_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2);
lp_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_LPA + 2);
if (lp_reg & 0xc000)
XGBE_SET_LP_ADV(lks, 10000baseR_FEC);
return mode;
}
static void xgbe_phy_an_advertising(struct xgbe_prv_data *pdata,
struct ethtool_link_ksettings *dlks)
{
struct ethtool_link_ksettings *slks = &pdata->phy.lks;
XGBE_LM_COPY(dlks, advertising, slks, advertising);
}
static int xgbe_phy_an_config(struct xgbe_prv_data *pdata)
{
/* Nothing uniquely required for an configuration */
return 0;
}
static enum xgbe_an_mode xgbe_phy_an_mode(struct xgbe_prv_data *pdata)
{
return XGBE_AN_MODE_CL73;
}
static void xgbe_phy_pcs_power_cycle(struct xgbe_prv_data *pdata)
{
unsigned int reg;
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
reg |= MDIO_CTRL1_LPOWER;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, reg);
usleep_range(75, 100);
reg &= ~MDIO_CTRL1_LPOWER;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, reg);
}
static void xgbe_phy_start_ratechange(struct xgbe_prv_data *pdata)
{
/* Assert Rx and Tx ratechange */
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, RATECHANGE, 1);
}
static void xgbe_phy_complete_ratechange(struct xgbe_prv_data *pdata)
{
unsigned int wait;
u16 status;
/* Release Rx and Tx ratechange */
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, RATECHANGE, 0);
/* Wait for Rx and Tx ready */
wait = XGBE_RATECHANGE_COUNT;
while (wait--) {
usleep_range(50, 75);
status = XSIR0_IOREAD(pdata, SIR0_STATUS);
if (XSIR_GET_BITS(status, SIR0_STATUS, RX_READY) &&
XSIR_GET_BITS(status, SIR0_STATUS, TX_READY))
goto rx_reset;
}
netif_dbg(pdata, link, pdata->netdev, "SerDes rx/tx not ready (%#hx)\n",
status);
rx_reset:
/* Perform Rx reset for the DFE changes */
XRXTX_IOWRITE_BITS(pdata, RXTX_REG6, RESETB_RXD, 0);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG6, RESETB_RXD, 1);
}
static void xgbe_phy_kr_mode(struct xgbe_prv_data *pdata)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
unsigned int reg;
/* Set PCS to KR/10G speed */
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL2);
reg &= ~MDIO_PCS_CTRL2_TYPE;
reg |= MDIO_PCS_CTRL2_10GBR;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL2, reg);
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
reg &= ~MDIO_CTRL1_SPEEDSEL;
reg |= MDIO_CTRL1_SPEED10G;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, reg);
xgbe_phy_pcs_power_cycle(pdata);
/* Set SerDes to 10G speed */
xgbe_phy_start_ratechange(pdata);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, DATARATE, XGBE_SPEED_10000_RATE);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, WORDMODE, XGBE_SPEED_10000_WORD);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, PLLSEL, XGBE_SPEED_10000_PLL);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, CDR_RATE,
phy_data->cdr_rate[XGBE_SPEED_10000]);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, TXAMP,
phy_data->tx_amp[XGBE_SPEED_10000]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG20, BLWC_ENA,
phy_data->blwc[XGBE_SPEED_10000]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG114, PQ_REG,
phy_data->pq_skew[XGBE_SPEED_10000]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG129, RXDFE_CONFIG,
phy_data->dfe_tap_cfg[XGBE_SPEED_10000]);
XRXTX_IOWRITE(pdata, RXTX_REG22,
phy_data->dfe_tap_ena[XGBE_SPEED_10000]);
xgbe_phy_complete_ratechange(pdata);
netif_dbg(pdata, link, pdata->netdev, "10GbE KR mode set\n");
}
static void xgbe_phy_kx_2500_mode(struct xgbe_prv_data *pdata)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
unsigned int reg;
/* Set PCS to KX/1G speed */
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL2);
reg &= ~MDIO_PCS_CTRL2_TYPE;
reg |= MDIO_PCS_CTRL2_10GBX;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL2, reg);
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
reg &= ~MDIO_CTRL1_SPEEDSEL;
reg |= MDIO_CTRL1_SPEED1G;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, reg);
xgbe_phy_pcs_power_cycle(pdata);
/* Set SerDes to 2.5G speed */
xgbe_phy_start_ratechange(pdata);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, DATARATE, XGBE_SPEED_2500_RATE);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, WORDMODE, XGBE_SPEED_2500_WORD);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, PLLSEL, XGBE_SPEED_2500_PLL);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, CDR_RATE,
phy_data->cdr_rate[XGBE_SPEED_2500]);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, TXAMP,
phy_data->tx_amp[XGBE_SPEED_2500]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG20, BLWC_ENA,
phy_data->blwc[XGBE_SPEED_2500]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG114, PQ_REG,
phy_data->pq_skew[XGBE_SPEED_2500]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG129, RXDFE_CONFIG,
phy_data->dfe_tap_cfg[XGBE_SPEED_2500]);
XRXTX_IOWRITE(pdata, RXTX_REG22,
phy_data->dfe_tap_ena[XGBE_SPEED_2500]);
xgbe_phy_complete_ratechange(pdata);
netif_dbg(pdata, link, pdata->netdev, "2.5GbE KX mode set\n");
}
static void xgbe_phy_kx_1000_mode(struct xgbe_prv_data *pdata)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
unsigned int reg;
/* Set PCS to KX/1G speed */
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL2);
reg &= ~MDIO_PCS_CTRL2_TYPE;
reg |= MDIO_PCS_CTRL2_10GBX;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL2, reg);
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
reg &= ~MDIO_CTRL1_SPEEDSEL;
reg |= MDIO_CTRL1_SPEED1G;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, reg);
xgbe_phy_pcs_power_cycle(pdata);
/* Set SerDes to 1G speed */
xgbe_phy_start_ratechange(pdata);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, DATARATE, XGBE_SPEED_1000_RATE);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, WORDMODE, XGBE_SPEED_1000_WORD);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, PLLSEL, XGBE_SPEED_1000_PLL);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, CDR_RATE,
phy_data->cdr_rate[XGBE_SPEED_1000]);
XSIR1_IOWRITE_BITS(pdata, SIR1_SPEED, TXAMP,
phy_data->tx_amp[XGBE_SPEED_1000]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG20, BLWC_ENA,
phy_data->blwc[XGBE_SPEED_1000]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG114, PQ_REG,
phy_data->pq_skew[XGBE_SPEED_1000]);
XRXTX_IOWRITE_BITS(pdata, RXTX_REG129, RXDFE_CONFIG,
phy_data->dfe_tap_cfg[XGBE_SPEED_1000]);
XRXTX_IOWRITE(pdata, RXTX_REG22,
phy_data->dfe_tap_ena[XGBE_SPEED_1000]);
xgbe_phy_complete_ratechange(pdata);
netif_dbg(pdata, link, pdata->netdev, "1GbE KX mode set\n");
}
static enum xgbe_mode xgbe_phy_cur_mode(struct xgbe_prv_data *pdata)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
enum xgbe_mode mode;
unsigned int reg;
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL2);
reg &= MDIO_PCS_CTRL2_TYPE;
if (reg == MDIO_PCS_CTRL2_10GBR) {
mode = XGBE_MODE_KR;
} else {
if (phy_data->speed_set == XGBE_SPEEDSET_2500_10000)
mode = XGBE_MODE_KX_2500;
else
mode = XGBE_MODE_KX_1000;
}
return mode;
}
static enum xgbe_mode xgbe_phy_switch_mode(struct xgbe_prv_data *pdata)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
enum xgbe_mode mode;
/* If we are in KR switch to KX, and vice-versa */
if (xgbe_phy_cur_mode(pdata) == XGBE_MODE_KR) {
if (phy_data->speed_set == XGBE_SPEEDSET_2500_10000)
mode = XGBE_MODE_KX_2500;
else
mode = XGBE_MODE_KX_1000;
} else {
mode = XGBE_MODE_KR;
}
return mode;
}
static enum xgbe_mode xgbe_phy_get_mode(struct xgbe_prv_data *pdata,
int speed)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
switch (speed) {
case SPEED_1000:
return (phy_data->speed_set == XGBE_SPEEDSET_1000_10000)
? XGBE_MODE_KX_1000 : XGBE_MODE_UNKNOWN;
case SPEED_2500:
return (phy_data->speed_set == XGBE_SPEEDSET_2500_10000)
? XGBE_MODE_KX_2500 : XGBE_MODE_UNKNOWN;
case SPEED_10000:
return XGBE_MODE_KR;
default:
return XGBE_MODE_UNKNOWN;
}
}
static void xgbe_phy_set_mode(struct xgbe_prv_data *pdata, enum xgbe_mode mode)
{
switch (mode) {
case XGBE_MODE_KX_1000:
xgbe_phy_kx_1000_mode(pdata);
break;
case XGBE_MODE_KX_2500:
xgbe_phy_kx_2500_mode(pdata);
break;
case XGBE_MODE_KR:
xgbe_phy_kr_mode(pdata);
break;
default:
break;
}
}
static bool xgbe_phy_check_mode(struct xgbe_prv_data *pdata,
enum xgbe_mode mode, bool advert)
{
if (pdata->phy.autoneg == AUTONEG_ENABLE) {
return advert;
} else {
enum xgbe_mode cur_mode;
cur_mode = xgbe_phy_get_mode(pdata, pdata->phy.speed);
if (cur_mode == mode)
return true;
}
return false;
}
static bool xgbe_phy_use_mode(struct xgbe_prv_data *pdata, enum xgbe_mode mode)
{
struct ethtool_link_ksettings *lks = &pdata->phy.lks;
switch (mode) {
case XGBE_MODE_KX_1000:
return xgbe_phy_check_mode(pdata, mode,
XGBE_ADV(lks, 1000baseKX_Full));
case XGBE_MODE_KX_2500:
return xgbe_phy_check_mode(pdata, mode,
XGBE_ADV(lks, 2500baseX_Full));
case XGBE_MODE_KR:
return xgbe_phy_check_mode(pdata, mode,
XGBE_ADV(lks, 10000baseKR_Full));
default:
return false;
}
}
static bool xgbe_phy_valid_speed(struct xgbe_prv_data *pdata, int speed)
{
struct xgbe_phy_data *phy_data = pdata->phy_data;
switch (speed) {
case SPEED_1000:
if (phy_data->speed_set != XGBE_SPEEDSET_1000_10000)
return false;
return true;
case SPEED_2500:
if (phy_data->speed_set != XGBE_SPEEDSET_2500_10000)
return false;
return true;
case SPEED_10000:
return true;
default:
return false;
}
}
static int xgbe_phy_link_status(struct xgbe_prv_data *pdata, int *an_restart)
{
unsigned int reg;
*an_restart = 0;
/* Link status is latched low, so read once to clear
* and then read again to get current state
*/
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_STAT1);
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_STAT1);
return (reg & MDIO_STAT1_LSTATUS) ? 1 : 0;
}
static void xgbe_phy_stop(struct xgbe_prv_data *pdata)
{
/* Nothing uniquely required for stop */
}
static int xgbe_phy_start(struct xgbe_prv_data *pdata)
{
/* Nothing uniquely required for start */
return 0;
}
static int xgbe_phy_reset(struct xgbe_prv_data *pdata)
{
unsigned int reg, count;
/* Perform a software reset of the PCS */
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
reg |= MDIO_CTRL1_RESET;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, reg);
count = 50;
do {
msleep(20);
reg = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
} while ((reg & MDIO_CTRL1_RESET) && --count);
if (reg & MDIO_CTRL1_RESET)
return -ETIMEDOUT;
return 0;
}
static void xgbe_phy_exit(struct xgbe_prv_data *pdata)
{
/* Nothing uniquely required for exit */
}
static int xgbe_phy_init(struct xgbe_prv_data *pdata)
{
struct ethtool_link_ksettings *lks = &pdata->phy.lks;
struct xgbe_phy_data *phy_data;
int ret;
phy_data = devm_kzalloc(pdata->dev, sizeof(*phy_data), GFP_KERNEL);
if (!phy_data)
return -ENOMEM;
/* Retrieve the PHY speedset */
ret = device_property_read_u32(pdata->phy_dev, XGBE_SPEEDSET_PROPERTY,
&phy_data->speed_set);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_SPEEDSET_PROPERTY);
return ret;
}
switch (phy_data->speed_set) {
case XGBE_SPEEDSET_1000_10000:
case XGBE_SPEEDSET_2500_10000:
break;
default:
dev_err(pdata->dev, "invalid %s property\n",
XGBE_SPEEDSET_PROPERTY);
return -EINVAL;
}
/* Retrieve the PHY configuration properties */
if (device_property_present(pdata->phy_dev, XGBE_BLWC_PROPERTY)) {
ret = device_property_read_u32_array(pdata->phy_dev,
XGBE_BLWC_PROPERTY,
phy_data->blwc,
XGBE_SPEEDS);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_BLWC_PROPERTY);
return ret;
}
} else {
memcpy(phy_data->blwc, xgbe_phy_blwc,
sizeof(phy_data->blwc));
}
if (device_property_present(pdata->phy_dev, XGBE_CDR_RATE_PROPERTY)) {
ret = device_property_read_u32_array(pdata->phy_dev,
XGBE_CDR_RATE_PROPERTY,
phy_data->cdr_rate,
XGBE_SPEEDS);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_CDR_RATE_PROPERTY);
return ret;
}
} else {
memcpy(phy_data->cdr_rate, xgbe_phy_cdr_rate,
sizeof(phy_data->cdr_rate));
}
if (device_property_present(pdata->phy_dev, XGBE_PQ_SKEW_PROPERTY)) {
ret = device_property_read_u32_array(pdata->phy_dev,
XGBE_PQ_SKEW_PROPERTY,
phy_data->pq_skew,
XGBE_SPEEDS);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_PQ_SKEW_PROPERTY);
return ret;
}
} else {
memcpy(phy_data->pq_skew, xgbe_phy_pq_skew,
sizeof(phy_data->pq_skew));
}
if (device_property_present(pdata->phy_dev, XGBE_TX_AMP_PROPERTY)) {
ret = device_property_read_u32_array(pdata->phy_dev,
XGBE_TX_AMP_PROPERTY,
phy_data->tx_amp,
XGBE_SPEEDS);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_TX_AMP_PROPERTY);
return ret;
}
} else {
memcpy(phy_data->tx_amp, xgbe_phy_tx_amp,
sizeof(phy_data->tx_amp));
}
if (device_property_present(pdata->phy_dev, XGBE_DFE_CFG_PROPERTY)) {
ret = device_property_read_u32_array(pdata->phy_dev,
XGBE_DFE_CFG_PROPERTY,
phy_data->dfe_tap_cfg,
XGBE_SPEEDS);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_DFE_CFG_PROPERTY);
return ret;
}
} else {
memcpy(phy_data->dfe_tap_cfg, xgbe_phy_dfe_tap_cfg,
sizeof(phy_data->dfe_tap_cfg));
}
if (device_property_present(pdata->phy_dev, XGBE_DFE_ENA_PROPERTY)) {
ret = device_property_read_u32_array(pdata->phy_dev,
XGBE_DFE_ENA_PROPERTY,
phy_data->dfe_tap_ena,
XGBE_SPEEDS);
if (ret) {
dev_err(pdata->dev, "invalid %s property\n",
XGBE_DFE_ENA_PROPERTY);
return ret;
}
} else {
memcpy(phy_data->dfe_tap_ena, xgbe_phy_dfe_tap_ena,
sizeof(phy_data->dfe_tap_ena));
}
/* Initialize supported features */
XGBE_ZERO_SUP(lks);
XGBE_SET_SUP(lks, Autoneg);
XGBE_SET_SUP(lks, Pause);
XGBE_SET_SUP(lks, Asym_Pause);
XGBE_SET_SUP(lks, Backplane);
XGBE_SET_SUP(lks, 10000baseKR_Full);
switch (phy_data->speed_set) {
case XGBE_SPEEDSET_1000_10000:
XGBE_SET_SUP(lks, 1000baseKX_Full);
break;
case XGBE_SPEEDSET_2500_10000:
XGBE_SET_SUP(lks, 2500baseX_Full);
break;
}
if (pdata->fec_ability & MDIO_PMA_10GBR_FECABLE_ABLE)
XGBE_SET_SUP(lks, 10000baseR_FEC);
pdata->phy_data = phy_data;
return 0;
}
void xgbe_init_function_ptrs_phy_v1(struct xgbe_phy_if *phy_if)
{
struct xgbe_phy_impl_if *phy_impl = &phy_if->phy_impl;
phy_impl->init = xgbe_phy_init;
phy_impl->exit = xgbe_phy_exit;
phy_impl->reset = xgbe_phy_reset;
phy_impl->start = xgbe_phy_start;
phy_impl->stop = xgbe_phy_stop;
phy_impl->link_status = xgbe_phy_link_status;
phy_impl->valid_speed = xgbe_phy_valid_speed;
phy_impl->use_mode = xgbe_phy_use_mode;
phy_impl->set_mode = xgbe_phy_set_mode;
phy_impl->get_mode = xgbe_phy_get_mode;
phy_impl->switch_mode = xgbe_phy_switch_mode;
phy_impl->cur_mode = xgbe_phy_cur_mode;
phy_impl->an_mode = xgbe_phy_an_mode;
phy_impl->an_config = xgbe_phy_an_config;
phy_impl->an_advertising = xgbe_phy_an_advertising;
phy_impl->an_outcome = xgbe_phy_an_outcome;
phy_impl->kr_training_pre = xgbe_phy_kr_training_pre;
phy_impl->kr_training_post = xgbe_phy_kr_training_post;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,466 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/clk.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/mdio.h>
#include "xgbe.h"
#include "xgbe-common.h"
#ifdef CONFIG_ACPI
static int xgbe_acpi_support(struct xgbe_prv_data *pdata)
{
struct device *dev = pdata->dev;
u32 property;
int ret;
/* Obtain the system clock setting */
ret = device_property_read_u32(dev, XGBE_ACPI_DMA_FREQ, &property);
if (ret) {
dev_err(dev, "unable to obtain %s property\n",
XGBE_ACPI_DMA_FREQ);
return ret;
}
pdata->sysclk_rate = property;
/* Obtain the PTP clock setting */
ret = device_property_read_u32(dev, XGBE_ACPI_PTP_FREQ, &property);
if (ret) {
dev_err(dev, "unable to obtain %s property\n",
XGBE_ACPI_PTP_FREQ);
return ret;
}
pdata->ptpclk_rate = property;
return 0;
}
#else /* CONFIG_ACPI */
static int xgbe_acpi_support(struct xgbe_prv_data *pdata)
{
return -EINVAL;
}
#endif /* CONFIG_ACPI */
#ifdef CONFIG_OF
static int xgbe_of_support(struct xgbe_prv_data *pdata)
{
struct device *dev = pdata->dev;
/* Obtain the system clock setting */
pdata->sysclk = devm_clk_get(dev, XGBE_DMA_CLOCK);
if (IS_ERR(pdata->sysclk)) {
dev_err(dev, "dma devm_clk_get failed\n");
return PTR_ERR(pdata->sysclk);
}
pdata->sysclk_rate = clk_get_rate(pdata->sysclk);
/* Obtain the PTP clock setting */
pdata->ptpclk = devm_clk_get(dev, XGBE_PTP_CLOCK);
if (IS_ERR(pdata->ptpclk)) {
dev_err(dev, "ptp devm_clk_get failed\n");
return PTR_ERR(pdata->ptpclk);
}
pdata->ptpclk_rate = clk_get_rate(pdata->ptpclk);
return 0;
}
static struct platform_device *xgbe_of_get_phy_pdev(struct xgbe_prv_data *pdata)
{
struct device *dev = pdata->dev;
struct device_node *phy_node;
struct platform_device *phy_pdev;
phy_node = of_parse_phandle(dev->of_node, "phy-handle", 0);
if (phy_node) {
/* Old style device tree:
* The XGBE and PHY resources are separate
*/
phy_pdev = of_find_device_by_node(phy_node);
of_node_put(phy_node);
} else {
/* New style device tree:
* The XGBE and PHY resources are grouped together with
* the PHY resources listed last
*/
get_device(dev);
phy_pdev = pdata->platdev;
}
return phy_pdev;
}
#else /* CONFIG_OF */
static int xgbe_of_support(struct xgbe_prv_data *pdata)
{
return -EINVAL;
}
static struct platform_device *xgbe_of_get_phy_pdev(struct xgbe_prv_data *pdata)
{
return NULL;
}
#endif /* CONFIG_OF */
static unsigned int xgbe_resource_count(struct platform_device *pdev,
unsigned int type)
{
unsigned int count;
int i;
for (i = 0, count = 0; i < pdev->num_resources; i++) {
struct resource *res = &pdev->resource[i];
if (type == resource_type(res))
count++;
}
return count;
}
static struct platform_device *xgbe_get_phy_pdev(struct xgbe_prv_data *pdata)
{
struct platform_device *phy_pdev;
if (pdata->use_acpi) {
get_device(pdata->dev);
phy_pdev = pdata->platdev;
} else {
phy_pdev = xgbe_of_get_phy_pdev(pdata);
}
return phy_pdev;
}
static int xgbe_platform_probe(struct platform_device *pdev)
{
struct xgbe_prv_data *pdata;
struct device *dev = &pdev->dev;
struct platform_device *phy_pdev;
const char *phy_mode;
unsigned int phy_memnum, phy_irqnum;
unsigned int dma_irqnum, dma_irqend;
enum dev_dma_attr attr;
int ret;
pdata = xgbe_alloc_pdata(dev);
if (IS_ERR(pdata)) {
ret = PTR_ERR(pdata);
goto err_alloc;
}
pdata->platdev = pdev;
pdata->adev = ACPI_COMPANION(dev);
platform_set_drvdata(pdev, pdata);
/* Check if we should use ACPI or DT */
pdata->use_acpi = dev->of_node ? 0 : 1;
/* Get the version data */
pdata->vdata = (struct xgbe_version_data *)device_get_match_data(dev);
phy_pdev = xgbe_get_phy_pdev(pdata);
if (!phy_pdev) {
dev_err(dev, "unable to obtain phy device\n");
ret = -EINVAL;
goto err_phydev;
}
pdata->phy_platdev = phy_pdev;
pdata->phy_dev = &phy_pdev->dev;
if (pdev == phy_pdev) {
/* New style device tree or ACPI:
* The XGBE and PHY resources are grouped together with
* the PHY resources listed last
*/
phy_memnum = xgbe_resource_count(pdev, IORESOURCE_MEM) - 3;
phy_irqnum = platform_irq_count(pdev) - 1;
dma_irqnum = 1;
dma_irqend = phy_irqnum;
} else {
/* Old style device tree:
* The XGBE and PHY resources are separate
*/
phy_memnum = 0;
phy_irqnum = 0;
dma_irqnum = 1;
dma_irqend = platform_irq_count(pdev);
}
/* Obtain the mmio areas for the device */
pdata->xgmac_regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pdata->xgmac_regs)) {
dev_err(dev, "xgmac ioremap failed\n");
ret = PTR_ERR(pdata->xgmac_regs);
goto err_io;
}
if (netif_msg_probe(pdata))
dev_dbg(dev, "xgmac_regs = %p\n", pdata->xgmac_regs);
pdata->xpcs_regs = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(pdata->xpcs_regs)) {
dev_err(dev, "xpcs ioremap failed\n");
ret = PTR_ERR(pdata->xpcs_regs);
goto err_io;
}
if (netif_msg_probe(pdata))
dev_dbg(dev, "xpcs_regs = %p\n", pdata->xpcs_regs);
pdata->rxtx_regs = devm_platform_ioremap_resource(phy_pdev,
phy_memnum++);
if (IS_ERR(pdata->rxtx_regs)) {
dev_err(dev, "rxtx ioremap failed\n");
ret = PTR_ERR(pdata->rxtx_regs);
goto err_io;
}
if (netif_msg_probe(pdata))
dev_dbg(dev, "rxtx_regs = %p\n", pdata->rxtx_regs);
pdata->sir0_regs = devm_platform_ioremap_resource(phy_pdev,
phy_memnum++);
if (IS_ERR(pdata->sir0_regs)) {
dev_err(dev, "sir0 ioremap failed\n");
ret = PTR_ERR(pdata->sir0_regs);
goto err_io;
}
if (netif_msg_probe(pdata))
dev_dbg(dev, "sir0_regs = %p\n", pdata->sir0_regs);
pdata->sir1_regs = devm_platform_ioremap_resource(phy_pdev,
phy_memnum++);
if (IS_ERR(pdata->sir1_regs)) {
dev_err(dev, "sir1 ioremap failed\n");
ret = PTR_ERR(pdata->sir1_regs);
goto err_io;
}
if (netif_msg_probe(pdata))
dev_dbg(dev, "sir1_regs = %p\n", pdata->sir1_regs);
/* Retrieve the MAC address */
ret = device_get_mac_address(dev, pdata->mac_addr);
if (ret) {
dev_err(dev, "invalid MAC address property\n");
goto err_io;
}
/* Retrieve the PHY mode - it must be "xgmii" */
ret = device_property_read_string(dev, XGBE_PHY_MODE_PROPERTY,
&phy_mode);
if (ret || strcmp(phy_mode, phy_modes(PHY_INTERFACE_MODE_XGMII))) {
dev_err(dev, "invalid %s property\n", XGBE_PHY_MODE_PROPERTY);
if (!ret)
ret = -EINVAL;
goto err_io;
}
pdata->phy_mode = PHY_INTERFACE_MODE_XGMII;
/* Check for per channel interrupt support */
if (device_property_present(dev, XGBE_DMA_IRQS_PROPERTY)) {
pdata->per_channel_irq = 1;
pdata->channel_irq_mode = XGBE_IRQ_MODE_EDGE;
}
/* Obtain device settings unique to ACPI/OF */
if (pdata->use_acpi)
ret = xgbe_acpi_support(pdata);
else
ret = xgbe_of_support(pdata);
if (ret)
goto err_io;
/* Set the DMA coherency values */
attr = device_get_dma_attr(dev);
if (attr == DEV_DMA_NOT_SUPPORTED) {
dev_err(dev, "DMA is not supported");
ret = -ENODEV;
goto err_io;
}
pdata->coherent = (attr == DEV_DMA_COHERENT);
if (pdata->coherent) {
pdata->arcr = XGBE_DMA_OS_ARCR;
pdata->awcr = XGBE_DMA_OS_AWCR;
} else {
pdata->arcr = XGBE_DMA_SYS_ARCR;
pdata->awcr = XGBE_DMA_SYS_AWCR;
}
/* Set the maximum fifo amounts */
pdata->tx_max_fifo_size = pdata->vdata->tx_max_fifo_size;
pdata->rx_max_fifo_size = pdata->vdata->rx_max_fifo_size;
/* Set the hardware channel and queue counts */
xgbe_set_counts(pdata);
/* Always have XGMAC and XPCS (auto-negotiation) interrupts */
pdata->irq_count = 2;
/* Get the device interrupt */
ret = platform_get_irq(pdev, 0);
if (ret < 0)
goto err_io;
pdata->dev_irq = ret;
/* Get the per channel DMA interrupts */
if (pdata->per_channel_irq) {
unsigned int i, max = ARRAY_SIZE(pdata->channel_irq);
for (i = 0; (i < max) && (dma_irqnum < dma_irqend); i++) {
ret = platform_get_irq(pdata->platdev, dma_irqnum++);
if (ret < 0)
goto err_io;
pdata->channel_irq[i] = ret;
}
pdata->channel_irq_count = max;
pdata->irq_count += max;
}
/* Get the auto-negotiation interrupt */
ret = platform_get_irq(phy_pdev, phy_irqnum++);
if (ret < 0)
goto err_io;
pdata->an_irq = ret;
/* Configure the netdev resource */
ret = xgbe_config_netdev(pdata);
if (ret)
goto err_io;
netdev_notice(pdata->netdev, "net device enabled\n");
return 0;
err_io:
platform_device_put(phy_pdev);
err_phydev:
xgbe_free_pdata(pdata);
err_alloc:
dev_notice(dev, "net device not enabled\n");
return ret;
}
static void xgbe_platform_remove(struct platform_device *pdev)
{
struct xgbe_prv_data *pdata = platform_get_drvdata(pdev);
xgbe_deconfig_netdev(pdata);
platform_device_put(pdata->phy_platdev);
xgbe_free_pdata(pdata);
}
#ifdef CONFIG_PM_SLEEP
static int xgbe_platform_suspend(struct device *dev)
{
struct xgbe_prv_data *pdata = dev_get_drvdata(dev);
struct net_device *netdev = pdata->netdev;
int ret = 0;
DBGPR("-->xgbe_suspend\n");
if (netif_running(netdev))
ret = xgbe_powerdown(netdev);
pdata->lpm_ctrl = XMDIO_READ(pdata, MDIO_MMD_PCS, MDIO_CTRL1);
pdata->lpm_ctrl |= MDIO_CTRL1_LPOWER;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, pdata->lpm_ctrl);
DBGPR("<--xgbe_suspend\n");
return ret;
}
static int xgbe_platform_resume(struct device *dev)
{
struct xgbe_prv_data *pdata = dev_get_drvdata(dev);
struct net_device *netdev = pdata->netdev;
int ret = 0;
DBGPR("-->xgbe_resume\n");
pdata->lpm_ctrl &= ~MDIO_CTRL1_LPOWER;
XMDIO_WRITE(pdata, MDIO_MMD_PCS, MDIO_CTRL1, pdata->lpm_ctrl);
if (netif_running(netdev)) {
ret = xgbe_powerup(netdev);
/* Schedule a restart in case the link or phy state changed
* while we were powered down.
*/
schedule_work(&pdata->restart_work);
}
DBGPR("<--xgbe_resume\n");
return ret;
}
#endif /* CONFIG_PM_SLEEP */
static const struct xgbe_version_data xgbe_v1 = {
.init_function_ptrs_phy_impl = xgbe_init_function_ptrs_phy_v1,
.xpcs_access = XGBE_XPCS_ACCESS_V1,
.tx_max_fifo_size = 81920,
.rx_max_fifo_size = 81920,
.tx_tstamp_workaround = 1,
};
static const struct acpi_device_id xgbe_acpi_match[] = {
{ .id = "AMDI8001",
.driver_data = (kernel_ulong_t)&xgbe_v1 },
{},
};
MODULE_DEVICE_TABLE(acpi, xgbe_acpi_match);
static const struct of_device_id xgbe_of_match[] = {
{ .compatible = "amd,xgbe-seattle-v1a",
.data = &xgbe_v1 },
{},
};
MODULE_DEVICE_TABLE(of, xgbe_of_match);
static SIMPLE_DEV_PM_OPS(xgbe_platform_pm_ops,
xgbe_platform_suspend, xgbe_platform_resume);
static struct platform_driver xgbe_driver = {
.driver = {
.name = XGBE_DRV_NAME,
.acpi_match_table = xgbe_acpi_match,
.of_match_table = xgbe_of_match,
.pm = &xgbe_platform_pm_ops,
},
.probe = xgbe_platform_probe,
.remove = xgbe_platform_remove,
};
int xgbe_platform_init(void)
{
return platform_driver_register(&xgbe_driver);
}
void xgbe_platform_exit(void)
{
platform_driver_unregister(&xgbe_driver);
}
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*
* Author: Raju Rangoju <Raju.Rangoju@amd.com>
*/
#include "xgbe.h"
#include "xgbe-common.h"
static u32 get_pps_mask(unsigned int x)
{
return GENMASK(PPS_MAXIDX(x), PPS_MINIDX(x));
}
static u32 get_pps_cmd(unsigned int x, u32 val)
{
return (val & GENMASK(3, 0)) << PPS_MINIDX(x);
}
static u32 get_target_mode_sel(unsigned int x, u32 val)
{
return (val & GENMASK(1, 0)) << (PPS_MAXIDX(x) - 2);
}
int xgbe_pps_config(struct xgbe_prv_data *pdata,
struct xgbe_pps_config *cfg, int index, bool on)
{
unsigned int ppscr = 0;
unsigned int tnsec;
u64 period;
/* Check if target time register is busy */
tnsec = XGMAC_IOREAD(pdata, MAC_PPSx_TTNSR(index));
if (XGMAC_GET_BITS(tnsec, MAC_PPSx_TTNSR, TRGTBUSY0))
return -EBUSY;
ppscr = XGMAC_IOREAD(pdata, MAC_PPSCR);
ppscr &= ~get_pps_mask(index);
if (!on) {
/* Disable PPS output */
ppscr |= get_pps_cmd(index, XGBE_PPSCMD_STOP);
ppscr |= PPSEN0;
XGMAC_IOWRITE(pdata, MAC_PPSCR, ppscr);
return 0;
}
/* Configure start time */
XGMAC_IOWRITE(pdata, MAC_PPSx_TTSR(index), cfg->start.tv_sec);
XGMAC_IOWRITE(pdata, MAC_PPSx_TTNSR(index), cfg->start.tv_nsec);
period = cfg->period.tv_sec * NSEC_PER_SEC + cfg->period.tv_nsec;
period = div_u64(period, XGBE_V2_TSTAMP_SSINC);
if (period < 4)
return -EINVAL;
/* Configure interval and pulse width (50% duty cycle) */
XGMAC_IOWRITE(pdata, MAC_PPSx_INTERVAL(index), period - 1);
XGMAC_IOWRITE(pdata, MAC_PPSx_WIDTH(index), (period >> 1) - 1);
/* Enable PPS with pulse train mode */
ppscr |= get_pps_cmd(index, XGBE_PPSCMD_START);
ppscr |= get_target_mode_sel(index, XGBE_PPSTARGET_PULSE);
ppscr |= PPSEN0;
XGMAC_IOWRITE(pdata, MAC_PPSCR, ppscr);
return 0;
}
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*/
#include <linux/clk.h>
#include <linux/clocksource.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/net_tstamp.h>
#include "xgbe.h"
#include "xgbe-common.h"
static int xgbe_adjfine(struct ptp_clock_info *info, long scaled_ppm)
{
struct xgbe_prv_data *pdata = container_of(info,
struct xgbe_prv_data,
ptp_clock_info);
unsigned long flags;
u64 addend;
addend = adjust_by_scaled_ppm(pdata->tstamp_addend, scaled_ppm);
spin_lock_irqsave(&pdata->tstamp_lock, flags);
xgbe_update_tstamp_addend(pdata, addend);
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
return 0;
}
static int xgbe_adjtime(struct ptp_clock_info *info, s64 delta)
{
struct xgbe_prv_data *pdata = container_of(info,
struct xgbe_prv_data,
ptp_clock_info);
unsigned int neg_adjust = 0;
unsigned int sec, nsec;
u32 quotient, reminder;
unsigned long flags;
if (delta < 0) {
neg_adjust = 1;
delta = -delta;
}
quotient = div_u64_rem(delta, 1000000000ULL, &reminder);
sec = quotient;
nsec = reminder;
/* Negative adjustment for Hw timer register. */
if (neg_adjust) {
sec = -sec;
if (XGMAC_IOREAD_BITS(pdata, MAC_TSCR, TSCTRLSSR))
nsec = (1000000000UL - nsec);
else
nsec = (0x80000000UL - nsec);
}
nsec = (neg_adjust << 31) | nsec;
spin_lock_irqsave(&pdata->tstamp_lock, flags);
xgbe_update_tstamp_time(pdata, sec, nsec);
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
return 0;
}
static int xgbe_gettimex(struct ptp_clock_info *info, struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct xgbe_prv_data *pdata = container_of(info,
struct xgbe_prv_data,
ptp_clock_info);
unsigned long flags;
u64 nsec;
spin_lock_irqsave(&pdata->tstamp_lock, flags);
ptp_read_system_prets(sts);
nsec = xgbe_get_tstamp_time(pdata);
ptp_read_system_postts(sts);
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
*ts = ns_to_timespec64(nsec);
return 0;
}
static int xgbe_settime(struct ptp_clock_info *info,
const struct timespec64 *ts)
{
struct xgbe_prv_data *pdata = container_of(info,
struct xgbe_prv_data,
ptp_clock_info);
unsigned long flags;
spin_lock_irqsave(&pdata->tstamp_lock, flags);
xgbe_set_tstamp_time(pdata, ts->tv_sec, ts->tv_nsec);
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
return 0;
}
static int xgbe_enable(struct ptp_clock_info *info,
struct ptp_clock_request *request, int on)
{
struct xgbe_prv_data *pdata = container_of(info, struct xgbe_prv_data,
ptp_clock_info);
struct xgbe_pps_config *pps_cfg;
unsigned long flags;
int ret;
dev_dbg(pdata->dev, "rq->type %d on %d\n", request->type, on);
if (request->type != PTP_CLK_REQ_PEROUT)
return -EOPNOTSUPP;
pps_cfg = &pdata->pps[request->perout.index];
pps_cfg->start.tv_sec = request->perout.start.sec;
pps_cfg->start.tv_nsec = request->perout.start.nsec;
pps_cfg->period.tv_sec = request->perout.period.sec;
pps_cfg->period.tv_nsec = request->perout.period.nsec;
spin_lock_irqsave(&pdata->tstamp_lock, flags);
ret = xgbe_pps_config(pdata, pps_cfg, request->perout.index, on);
spin_unlock_irqrestore(&pdata->tstamp_lock, flags);
return ret;
}
void xgbe_ptp_register(struct xgbe_prv_data *pdata)
{
struct ptp_clock_info *info = &pdata->ptp_clock_info;
struct ptp_clock *clock;
snprintf(info->name, sizeof(info->name), "%s",
netdev_name(pdata->netdev));
info->owner = THIS_MODULE;
info->max_adj = pdata->ptpclk_rate;
info->adjfine = xgbe_adjfine;
info->adjtime = xgbe_adjtime;
info->gettimex64 = xgbe_gettimex;
info->settime64 = xgbe_settime;
info->n_per_out = pdata->hw_feat.pps_out_num;
info->n_ext_ts = pdata->hw_feat.aux_snap_num;
info->enable = xgbe_enable;
clock = ptp_clock_register(info, pdata->dev);
if (IS_ERR(clock)) {
dev_err(pdata->dev, "ptp_clock_register failed\n");
return;
}
pdata->ptp_clock = clock;
/* Disable all timestamping to start */
XGMAC_IOWRITE(pdata, MAC_TSCR, 0);
pdata->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
pdata->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
}
void xgbe_ptp_unregister(struct xgbe_prv_data *pdata)
{
if (pdata->ptp_clock)
ptp_clock_unregister(pdata->ptp_clock);
}
@@ -0,0 +1,346 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*
* Author: Raju Rangoju <Raju.Rangoju@amd.com>
*/
#include <linux/crc32.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/checksum.h>
#include <net/selftests.h>
#include "xgbe.h"
#include "xgbe-common.h"
#define XGBE_LOOPBACK_NONE 0
#define XGBE_LOOPBACK_MAC 1
#define XGBE_LOOPBACK_PHY 2
struct xgbe_test {
char name[ETH_GSTRING_LEN];
int lb;
int (*fn)(struct xgbe_prv_data *pdata);
};
static u8 xgbe_test_id;
static int xgbe_test_loopback_validate(struct sk_buff *skb,
struct net_device *ndev,
struct packet_type *pt,
struct net_device *orig_ndev)
{
struct net_test_priv *tdata = pt->af_packet_priv;
const unsigned char *dst = tdata->packet->dst;
const unsigned char *src = tdata->packet->src;
struct netsfhdr *hdr;
struct ethhdr *eh;
struct tcphdr *th;
struct udphdr *uh;
struct iphdr *ih;
int eat;
skb = skb_unshare(skb, GFP_ATOMIC);
if (!skb)
goto out;
eat = (skb->tail + skb->data_len) - skb->end;
if (eat > 0 && skb_shared(skb)) {
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb)
goto out;
}
if (skb_linearize(skb))
goto out;
if (skb_headlen(skb) < (NET_TEST_PKT_SIZE - ETH_HLEN))
goto out;
eh = (struct ethhdr *)skb_mac_header(skb);
if (dst) {
if (!ether_addr_equal_unaligned(eh->h_dest, dst))
goto out;
}
if (src) {
if (!ether_addr_equal_unaligned(eh->h_source, src))
goto out;
}
ih = ip_hdr(skb);
if (tdata->packet->tcp) {
if (ih->protocol != IPPROTO_TCP)
goto out;
th = (struct tcphdr *)((u8 *)ih + 4 * ih->ihl);
if (th->dest != htons(tdata->packet->dport))
goto out;
hdr = (struct netsfhdr *)((u8 *)th + sizeof(*th));
} else {
if (ih->protocol != IPPROTO_UDP)
goto out;
uh = (struct udphdr *)((u8 *)ih + 4 * ih->ihl);
if (uh->dest != htons(tdata->packet->dport))
goto out;
hdr = (struct netsfhdr *)((u8 *)uh + sizeof(*uh));
}
if (hdr->magic != cpu_to_be64(NET_TEST_PKT_MAGIC))
goto out;
if (tdata->packet->id != hdr->id)
goto out;
tdata->ok = true;
complete(&tdata->comp);
out:
kfree_skb(skb);
return 0;
}
static int __xgbe_test_loopback(struct xgbe_prv_data *pdata,
struct net_packet_attrs *attr)
{
struct net_test_priv *tdata;
struct sk_buff *skb = NULL;
int ret = 0;
tdata = kzalloc_obj(*tdata);
if (!tdata)
return -ENOMEM;
tdata->ok = false;
init_completion(&tdata->comp);
tdata->pt.type = htons(ETH_P_IP);
tdata->pt.func = xgbe_test_loopback_validate;
tdata->pt.dev = pdata->netdev;
tdata->pt.af_packet_priv = tdata;
tdata->packet = attr;
dev_add_pack(&tdata->pt);
skb = net_test_get_skb(pdata->netdev, xgbe_test_id, attr);
if (!skb) {
ret = -ENOMEM;
goto cleanup;
}
xgbe_test_id++;
ret = dev_direct_xmit(skb, attr->queue_mapping);
if (ret)
goto cleanup;
if (!attr->timeout)
attr->timeout = NET_LB_TIMEOUT;
wait_for_completion_timeout(&tdata->comp, attr->timeout);
ret = tdata->ok ? 0 : -ETIMEDOUT;
if (ret)
netdev_err(pdata->netdev, "Response timedout: ret %d\n", ret);
cleanup:
dev_remove_pack(&tdata->pt);
kfree(tdata);
return ret;
}
static int xgbe_test_mac_loopback(struct xgbe_prv_data *pdata)
{
struct net_packet_attrs attr = {};
attr.dst = pdata->netdev->dev_addr;
return __xgbe_test_loopback(pdata, &attr);
}
static int xgbe_test_phy_loopback(struct xgbe_prv_data *pdata)
{
struct net_packet_attrs attr = {};
int ret;
if (!pdata->netdev->phydev) {
netdev_err(pdata->netdev, "phydev not found: cannot start PHY loopback test\n");
return -EOPNOTSUPP;
}
ret = phy_loopback(pdata->netdev->phydev, true, 0);
if (ret)
return ret;
attr.dst = pdata->netdev->dev_addr;
ret = __xgbe_test_loopback(pdata, &attr);
phy_loopback(pdata->netdev->phydev, false, 0);
return ret;
}
static int xgbe_test_sph(struct xgbe_prv_data *pdata)
{
struct net_packet_attrs attr = {};
unsigned long cnt_end, cnt_start;
int ret;
cnt_start = pdata->ext_stats.rx_split_header_packets;
if (!pdata->sph) {
netdev_err(pdata->netdev, "Split Header not enabled\n");
return -EOPNOTSUPP;
}
/* UDP test */
attr.dst = pdata->netdev->dev_addr;
attr.tcp = false;
ret = __xgbe_test_loopback(pdata, &attr);
if (ret)
return ret;
cnt_end = pdata->ext_stats.rx_split_header_packets;
if (cnt_end <= cnt_start)
return -EINVAL;
/* TCP test */
cnt_start = cnt_end;
attr.dst = pdata->netdev->dev_addr;
attr.tcp = true;
ret = __xgbe_test_loopback(pdata, &attr);
if (ret)
return ret;
cnt_end = pdata->ext_stats.rx_split_header_packets;
if (cnt_end <= cnt_start)
return -EINVAL;
return 0;
}
static int xgbe_test_jumbo(struct xgbe_prv_data *pdata)
{
struct net_packet_attrs attr = {};
int size = pdata->rx_buf_size;
attr.dst = pdata->netdev->dev_addr;
attr.max_size = size - ETH_FCS_LEN;
return __xgbe_test_loopback(pdata, &attr);
}
static const struct xgbe_test xgbe_selftests[] = {
{
.name = "MAC Loopback ",
.lb = XGBE_LOOPBACK_MAC,
.fn = xgbe_test_mac_loopback,
}, {
.name = "PHY Loopback ",
.lb = XGBE_LOOPBACK_NONE,
.fn = xgbe_test_phy_loopback,
}, {
.name = "Split Header ",
.lb = XGBE_LOOPBACK_PHY,
.fn = xgbe_test_sph,
}, {
.name = "Jumbo Frame ",
.lb = XGBE_LOOPBACK_PHY,
.fn = xgbe_test_jumbo,
},
};
void xgbe_selftest_run(struct net_device *dev,
struct ethtool_test *etest, u64 *buf)
{
struct xgbe_prv_data *pdata = netdev_priv(dev);
int count = xgbe_selftest_get_count(pdata);
int i, ret;
memset(buf, 0, sizeof(*buf) * count);
xgbe_test_id = 0;
if (etest->flags != ETH_TEST_FL_OFFLINE) {
netdev_err(pdata->netdev, "Only offline tests are supported\n");
etest->flags |= ETH_TEST_FL_FAILED;
return;
} else if (!netif_carrier_ok(dev)) {
netdev_err(pdata->netdev,
"Invalid link, cannot execute tests\n");
etest->flags |= ETH_TEST_FL_FAILED;
return;
}
/* Wait for queues drain */
msleep(200);
for (i = 0; i < count; i++) {
ret = 0;
switch (xgbe_selftests[i].lb) {
case XGBE_LOOPBACK_PHY:
ret = -EOPNOTSUPP;
if (dev->phydev)
ret = phy_loopback(dev->phydev, true, 0);
if (!ret)
break;
fallthrough;
case XGBE_LOOPBACK_MAC:
ret = xgbe_enable_mac_loopback(pdata);
break;
case XGBE_LOOPBACK_NONE:
break;
default:
ret = -EOPNOTSUPP;
break;
}
/*
* First tests will always be MAC / PHY loopback.
* If any of them is not supported we abort earlier.
*/
if (ret) {
netdev_err(pdata->netdev, "Loopback not supported\n");
etest->flags |= ETH_TEST_FL_FAILED;
break;
}
ret = xgbe_selftests[i].fn(pdata);
if (ret && (ret != -EOPNOTSUPP))
etest->flags |= ETH_TEST_FL_FAILED;
buf[i] = ret;
switch (xgbe_selftests[i].lb) {
case XGBE_LOOPBACK_PHY:
ret = -EOPNOTSUPP;
if (dev->phydev)
ret = phy_loopback(dev->phydev, false, 0);
if (!ret)
break;
fallthrough;
case XGBE_LOOPBACK_MAC:
xgbe_disable_mac_loopback(pdata);
break;
default:
break;
}
}
}
void xgbe_selftest_get_strings(struct xgbe_prv_data *pdata, u8 *data)
{
u8 *p = data;
int i;
for (i = 0; i < xgbe_selftest_get_count(pdata); i++)
ethtool_puts(&p, xgbe_selftests[i].name);
}
int xgbe_selftest_get_count(struct xgbe_prv_data *pdata)
{
return ARRAY_SIZE(xgbe_selftests);
}
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause) */
/*
* Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
* Copyright (c) 2014, Synopsys, Inc.
* All rights reserved
*
* Author: Raju Rangoju <Raju.Rangoju@amd.com>
*/
#ifndef __SMN_H__
#define __SMN_H__
#ifdef CONFIG_AMD_NB
#include <asm/amd/nb.h>
#else
static inline int amd_smn_write(u16 node, u32 address, u32 value)
{
return -ENODEV;
}
static inline int amd_smn_read(u16 node, u32 address, u32 *value)
{
return -ENODEV;
}
#endif
#endif
File diff suppressed because it is too large Load Diff