#define HBG_RX_SKIP1 0x00
#define HBG_RX_SKIP2 0x01
#define HBG_VECTOR_NUM 4
+#define HBG_PCU_CACHE_LINE_SIZE 32
+#define HBG_TX_TIMEOUT_BUF_LEN 1024
+
+enum hbg_dir {
+ HBG_DIR_TX = 1 << 0,
+ HBG_DIR_RX = 1 << 1,
+ HBG_DIR_TX_RX = HBG_DIR_TX | HBG_DIR_RX,
+};
+
+enum hbg_tx_state {
+ HBG_TX_STATE_COMPLETE = 0, /* clear state, must fix to 0 */
+ HBG_TX_STATE_START,
+};
enum hbg_nic_state {
HBG_NIC_STATE_EVENT_HANDLING = 0,
};
+struct hbg_buffer {
+ u32 state;
+ dma_addr_t state_dma;
+
+ struct sk_buff *skb;
+ dma_addr_t skb_dma;
+ u32 skb_len;
+
+ enum hbg_dir dir;
+ struct hbg_ring *ring;
+ struct hbg_priv *priv;
+};
+
+struct hbg_ring {
+ struct hbg_buffer *queue;
+ dma_addr_t queue_dma;
+
+ union {
+ u32 head;
+ u32 ntc;
+ };
+ union {
+ u32 tail;
+ u32 ntu;
+ };
+ u32 len;
+
+ enum hbg_dir dir;
+ struct hbg_priv *priv;
+ struct napi_struct napi;
+ char *tout_log_buf; /* tx timeout log buffer */
+};
+
enum hbg_hw_event_type {
HBG_HW_EVENT_NONE = 0,
HBG_HW_EVENT_INIT, /* driver is loading */
+ HBG_HW_EVENT_RESET,
};
struct hbg_dev_specs {
unsigned long state;
struct hbg_mac mac;
struct hbg_vector vectors;
+ struct hbg_ring tx_ring;
};
#endif
if (!is_valid_ether_addr((u8 *)specs->mac_addr.sa_data))
return -EADDRNOTAVAIL;
+ specs->max_frame_len = HBG_PCU_CACHE_LINE_SIZE + specs->max_mtu;
return 0;
}
HBG_REG_PORT_ENABLE_RX_B, enable);
}
+u32 hbg_hw_get_fifo_used_num(struct hbg_priv *priv, enum hbg_dir dir)
+{
+ if (dir & HBG_DIR_TX)
+ return hbg_reg_read_field(priv, HBG_REG_CF_CFF_DATA_NUM_ADDR,
+ HBG_REG_CF_CFF_DATA_NUM_ADDR_TX_M);
+
+ return 0;
+}
+
+void hbg_hw_set_tx_desc(struct hbg_priv *priv, struct hbg_tx_desc *tx_desc)
+{
+ hbg_reg_write(priv, HBG_REG_TX_CFF_ADDR_0_ADDR, tx_desc->word0);
+ hbg_reg_write(priv, HBG_REG_TX_CFF_ADDR_1_ADDR, tx_desc->word1);
+ hbg_reg_write(priv, HBG_REG_TX_CFF_ADDR_2_ADDR, tx_desc->word2);
+ hbg_reg_write(priv, HBG_REG_TX_CFF_ADDR_3_ADDR, tx_desc->word3);
+}
+
void hbg_hw_adjust_link(struct hbg_priv *priv, u32 speed, u32 duplex)
{
hbg_reg_write_field(priv, HBG_REG_PORT_MODE_ADDR,
void hbg_hw_set_mtu(struct hbg_priv *priv, u16 mtu);
void hbg_hw_mac_enable(struct hbg_priv *priv, u32 enable);
void hbg_hw_set_uc_addr(struct hbg_priv *priv, u64 mac_addr);
+u32 hbg_hw_get_fifo_used_num(struct hbg_priv *priv, enum hbg_dir dir);
+void hbg_hw_set_tx_desc(struct hbg_priv *priv, struct hbg_tx_desc *tx_desc);
#endif
"receive error interrupt: %s\n", irq_info->name);
}
+static void hbg_irq_handle_tx(struct hbg_priv *priv,
+ struct hbg_irq_info *irq_info)
+{
+ napi_schedule(&priv->tx_ring.napi);
+}
+
#define HBG_TXRX_IRQ_I(name, handle) \
{#name, HBG_INT_MSK_##name##_B, false, false, 0, handle}
#define HBG_ERR_IRQ_I(name, need_print) \
static struct hbg_irq_info hbg_irqs[] = {
HBG_TXRX_IRQ_I(RX, NULL),
- HBG_TXRX_IRQ_I(TX, NULL),
+ HBG_TXRX_IRQ_I(TX, hbg_irq_handle_tx),
HBG_ERR_IRQ_I(MAC_MII_FIFO_ERR, true),
HBG_ERR_IRQ_I(MAC_PCS_RX_FIFO_ERR, true),
HBG_ERR_IRQ_I(MAC_PCS_TX_FIFO_ERR, true),
#include "hbg_hw.h"
#include "hbg_irq.h"
#include "hbg_mdio.h"
+#include "hbg_txrx.h"
+
+static void hbg_change_mtu(struct hbg_priv *priv, int new_mtu);
static void hbg_all_irq_enable(struct hbg_priv *priv, bool enabled)
{
static int hbg_net_open(struct net_device *netdev)
{
struct hbg_priv *priv = netdev_priv(netdev);
+ int ret;
+
+ ret = hbg_txrx_init(priv);
+ if (ret)
+ return ret;
hbg_all_irq_enable(priv, true);
hbg_hw_mac_enable(priv, HBG_STATUS_ENABLE);
return 0;
}
+/* This function only can be called after hbg_txrx_uninit() */
+static int hbg_hw_txrx_clear(struct hbg_priv *priv)
+{
+ int ret;
+
+ /* After ring buffers have been released,
+ * do a reset to release hw fifo rx ring buffer
+ */
+ ret = hbg_hw_event_notify(priv, HBG_HW_EVENT_RESET);
+ if (ret)
+ return ret;
+
+ /* After reset, regs need to be reconfigured */
+ hbg_hw_init(priv);
+ hbg_hw_set_uc_addr(priv, ether_addr_to_u64(priv->netdev->dev_addr));
+ hbg_change_mtu(priv, priv->netdev->mtu);
+
+ return 0;
+}
+
static int hbg_net_stop(struct net_device *netdev)
{
struct hbg_priv *priv = netdev_priv(netdev);
netif_stop_queue(netdev);
hbg_hw_mac_enable(priv, HBG_STATUS_DISABLE);
hbg_all_irq_enable(priv, false);
-
- return 0;
+ hbg_txrx_uninit(priv);
+ return hbg_hw_txrx_clear(priv);
}
static int hbg_net_set_mac_address(struct net_device *netdev, void *addr)
return 0;
}
+static void hbg_net_tx_timeout(struct net_device *netdev, unsigned int txqueue)
+{
+ struct hbg_priv *priv = netdev_priv(netdev);
+ struct hbg_ring *ring = &priv->tx_ring;
+ char *buf = ring->tout_log_buf;
+ u32 pos = 0;
+
+ pos += scnprintf(buf + pos, HBG_TX_TIMEOUT_BUF_LEN - pos,
+ "ring used num: %u, fifo used num: %u\n",
+ hbg_get_queue_used_num(ring),
+ hbg_hw_get_fifo_used_num(priv, HBG_DIR_TX));
+ pos += scnprintf(buf + pos, HBG_TX_TIMEOUT_BUF_LEN - pos,
+ "ntc: %u, ntu: %u, irq enabled: %u\n",
+ ring->ntc, ring->ntu,
+ hbg_hw_irq_is_enabled(priv, HBG_INT_MSK_TX_B));
+
+ netdev_info(netdev, "%s", buf);
+}
+
static const struct net_device_ops hbg_netdev_ops = {
.ndo_open = hbg_net_open,
.ndo_stop = hbg_net_stop,
+ .ndo_start_xmit = hbg_net_start_xmit,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = hbg_net_set_mac_address,
.ndo_change_mtu = hbg_net_change_mtu,
+ .ndo_tx_timeout = hbg_net_tx_timeout,
};
static int hbg_init(struct hbg_priv *priv)
netdev->max_mtu = priv->dev_specs.max_mtu;
netdev->min_mtu = priv->dev_specs.min_mtu;
netdev->netdev_ops = &hbg_netdev_ops;
+ netdev->watchdog_timeo = 5 * HZ;
hbg_change_mtu(priv, ETH_DATA_LEN);
hbg_net_set_mac_address(priv->netdev, &priv->dev_specs.mac_addr);
#define HBG_REG_CF_INTRPT_CLR_ADDR (HBG_REG_SGMII_BASE + 0x0438)
#define HBG_REG_MAX_FRAME_LEN_ADDR (HBG_REG_SGMII_BASE + 0x0444)
#define HBG_REG_MAX_FRAME_LEN_M GENMASK(15, 0)
+#define HBG_REG_CF_CFF_DATA_NUM_ADDR (HBG_REG_SGMII_BASE + 0x045C)
+#define HBG_REG_CF_CFF_DATA_NUM_ADDR_TX_M GENMASK(8, 0)
+#define HBG_REG_TX_CFF_ADDR_0_ADDR (HBG_REG_SGMII_BASE + 0x0488)
+#define HBG_REG_TX_CFF_ADDR_1_ADDR (HBG_REG_SGMII_BASE + 0x048C)
+#define HBG_REG_TX_CFF_ADDR_2_ADDR (HBG_REG_SGMII_BASE + 0x0490)
+#define HBG_REG_TX_CFF_ADDR_3_ADDR (HBG_REG_SGMII_BASE + 0x0494)
#define HBG_REG_RX_BUF_SIZE_ADDR (HBG_REG_SGMII_BASE + 0x04E4)
#define HBG_REG_RX_BUF_SIZE_M GENMASK(15, 0)
#define HBG_REG_BUS_CTRL_ADDR (HBG_REG_SGMII_BASE + 0x04E8)
HBG_PORT_MODE_SGMII_1000M = 0x8,
};
+struct hbg_tx_desc {
+ u32 word0;
+ u32 word1;
+ u32 word2; /* pkt_addr */
+ u32 word3; /* clear_addr */
+};
+
+#define HBG_TX_DESC_W0_IP_OFF_M GENMASK(30, 26)
+#define HBG_TX_DESC_W0_l3_CS_B BIT(2)
+#define HBG_TX_DESC_W0_WB_B BIT(1)
+#define HBG_TX_DESC_W0_l4_CS_B BIT(0)
+#define HBG_TX_DESC_W1_SEND_LEN_M GENMASK(19, 4)
+
#endif
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (c) 2024 Hisilicon Limited.
+
+#include <net/netdev_queues.h>
+#include "hbg_common.h"
+#include "hbg_irq.h"
+#include "hbg_reg.h"
+#include "hbg_txrx.h"
+
+#define netdev_get_tx_ring(netdev) \
+ (&(((struct hbg_priv *)netdev_priv(netdev))->tx_ring))
+
+#define buffer_to_dma_dir(buffer) (((buffer)->dir == HBG_DIR_RX) ? \
+ DMA_FROM_DEVICE : DMA_TO_DEVICE)
+
+#define hbg_queue_used_num(head, tail, ring) ({ \
+ typeof(ring) _ring = (ring); \
+ ((tail) + _ring->len - (head)) % _ring->len; })
+#define hbg_queue_left_num(head, tail, ring) ({ \
+ typeof(ring) _r = (ring); \
+ _r->len - hbg_queue_used_num((head), (tail), _r) - 1; })
+#define hbg_queue_is_empty(head, tail, ring) \
+ (hbg_queue_used_num((head), (tail), (ring)) == 0)
+#define hbg_queue_next_prt(p, ring) (((p) + 1) % (ring)->len)
+
+#define HBG_TX_STOP_THRS 2
+#define HBG_TX_START_THRS (2 * HBG_TX_STOP_THRS)
+
+static int hbg_dma_map(struct hbg_buffer *buffer)
+{
+ struct hbg_priv *priv = buffer->priv;
+
+ buffer->skb_dma = dma_map_single(&priv->pdev->dev,
+ buffer->skb->data, buffer->skb_len,
+ buffer_to_dma_dir(buffer));
+ if (unlikely(dma_mapping_error(&priv->pdev->dev, buffer->skb_dma)))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void hbg_dma_unmap(struct hbg_buffer *buffer)
+{
+ struct hbg_priv *priv = buffer->priv;
+
+ if (unlikely(!buffer->skb_dma))
+ return;
+
+ dma_unmap_single(&priv->pdev->dev, buffer->skb_dma, buffer->skb_len,
+ buffer_to_dma_dir(buffer));
+ buffer->skb_dma = 0;
+}
+
+static void hbg_init_tx_desc(struct hbg_buffer *buffer,
+ struct hbg_tx_desc *tx_desc)
+{
+ u32 ip_offset = buffer->skb->network_header - buffer->skb->mac_header;
+ u32 word0 = 0;
+
+ word0 |= FIELD_PREP(HBG_TX_DESC_W0_WB_B, HBG_STATUS_ENABLE);
+ word0 |= FIELD_PREP(HBG_TX_DESC_W0_IP_OFF_M, ip_offset);
+ if (likely(buffer->skb->ip_summed == CHECKSUM_PARTIAL)) {
+ word0 |= FIELD_PREP(HBG_TX_DESC_W0_l3_CS_B, HBG_STATUS_ENABLE);
+ word0 |= FIELD_PREP(HBG_TX_DESC_W0_l4_CS_B, HBG_STATUS_ENABLE);
+ }
+
+ tx_desc->word0 = word0;
+ tx_desc->word1 = FIELD_PREP(HBG_TX_DESC_W1_SEND_LEN_M,
+ buffer->skb->len);
+ tx_desc->word2 = buffer->skb_dma;
+ tx_desc->word3 = buffer->state_dma;
+}
+
+netdev_tx_t hbg_net_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+ struct hbg_ring *ring = netdev_get_tx_ring(netdev);
+ struct hbg_priv *priv = netdev_priv(netdev);
+ /* This smp_load_acquire() pairs with smp_store_release() in
+ * hbg_napi_tx_recycle() called in tx interrupt handle process.
+ */
+ u32 ntc = smp_load_acquire(&ring->ntc);
+ struct hbg_buffer *buffer;
+ struct hbg_tx_desc tx_desc;
+ u32 ntu = ring->ntu;
+
+ if (unlikely(!skb->len ||
+ skb->len > hbg_spec_max_frame_len(priv, HBG_DIR_TX))) {
+ dev_kfree_skb_any(skb);
+ netdev->stats.tx_errors++;
+ return NETDEV_TX_OK;
+ }
+
+ if (!netif_subqueue_maybe_stop(netdev, 0,
+ hbg_queue_left_num(ntc, ntu, ring),
+ HBG_TX_STOP_THRS, HBG_TX_START_THRS))
+ return NETDEV_TX_BUSY;
+
+ buffer = &ring->queue[ntu];
+ buffer->skb = skb;
+ buffer->skb_len = skb->len;
+ if (unlikely(hbg_dma_map(buffer))) {
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+
+ buffer->state = HBG_TX_STATE_START;
+ hbg_init_tx_desc(buffer, &tx_desc);
+ hbg_hw_set_tx_desc(priv, &tx_desc);
+
+ /* This smp_store_release() pairs with smp_load_acquire() in
+ * hbg_napi_tx_recycle() called in tx interrupt handle process.
+ */
+ smp_store_release(&ring->ntu, hbg_queue_next_prt(ntu, ring));
+ dev_sw_netstats_tx_add(netdev, 1, skb->len);
+ return NETDEV_TX_OK;
+}
+
+static void hbg_buffer_free_skb(struct hbg_buffer *buffer)
+{
+ if (unlikely(!buffer->skb))
+ return;
+
+ dev_kfree_skb_any(buffer->skb);
+ buffer->skb = NULL;
+}
+
+static void hbg_buffer_free(struct hbg_buffer *buffer)
+{
+ hbg_dma_unmap(buffer);
+ hbg_buffer_free_skb(buffer);
+}
+
+static int hbg_napi_tx_recycle(struct napi_struct *napi, int budget)
+{
+ struct hbg_ring *ring = container_of(napi, struct hbg_ring, napi);
+ /* This smp_load_acquire() pairs with smp_store_release() in
+ * hbg_net_start_xmit() called in xmit process.
+ */
+ u32 ntu = smp_load_acquire(&ring->ntu);
+ struct hbg_priv *priv = ring->priv;
+ struct hbg_buffer *buffer;
+ u32 ntc = ring->ntc;
+ int packet_done = 0;
+
+ /* We need do cleanup even if budget is 0.
+ * Per NAPI documentation budget is for Rx.
+ * So We hardcode the amount of work Tx NAPI does to 128.
+ */
+ budget = 128;
+ while (packet_done < budget) {
+ if (unlikely(hbg_queue_is_empty(ntc, ntu, ring)))
+ break;
+
+ /* make sure HW write desc complete */
+ dma_rmb();
+
+ buffer = &ring->queue[ntc];
+ if (buffer->state != HBG_TX_STATE_COMPLETE)
+ break;
+
+ hbg_buffer_free(buffer);
+ ntc = hbg_queue_next_prt(ntc, ring);
+ packet_done++;
+ }
+
+ /* This smp_store_release() pairs with smp_load_acquire() in
+ * hbg_net_start_xmit() called in xmit process.
+ */
+ smp_store_release(&ring->ntc, ntc);
+ netif_wake_queue(priv->netdev);
+
+ if (likely(packet_done < budget &&
+ napi_complete_done(napi, packet_done)))
+ hbg_hw_irq_enable(priv, HBG_INT_MSK_TX_B, true);
+
+ return packet_done;
+}
+
+static void hbg_ring_uninit(struct hbg_ring *ring)
+{
+ struct hbg_buffer *buffer;
+ u32 i;
+
+ if (!ring->queue)
+ return;
+
+ napi_disable(&ring->napi);
+ netif_napi_del(&ring->napi);
+
+ for (i = 0; i < ring->len; i++) {
+ buffer = &ring->queue[i];
+ hbg_buffer_free(buffer);
+ buffer->ring = NULL;
+ buffer->priv = NULL;
+ }
+
+ dma_free_coherent(&ring->priv->pdev->dev,
+ ring->len * sizeof(*ring->queue),
+ ring->queue, ring->queue_dma);
+ ring->queue = NULL;
+ ring->queue_dma = 0;
+ ring->len = 0;
+ ring->priv = NULL;
+}
+
+static int hbg_ring_init(struct hbg_priv *priv, struct hbg_ring *ring,
+ int (*napi_poll)(struct napi_struct *, int),
+ enum hbg_dir dir)
+{
+ struct hbg_buffer *buffer;
+ u32 i, len;
+
+ len = hbg_get_spec_fifo_max_num(priv, dir) + 1;
+ ring->queue = dma_alloc_coherent(&priv->pdev->dev,
+ len * sizeof(*ring->queue),
+ &ring->queue_dma, GFP_KERNEL);
+ if (!ring->queue)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++) {
+ buffer = &ring->queue[i];
+ buffer->skb_len = 0;
+ buffer->dir = dir;
+ buffer->ring = ring;
+ buffer->priv = priv;
+ buffer->state_dma = ring->queue_dma + (i * sizeof(*buffer));
+ }
+
+ ring->dir = dir;
+ ring->priv = priv;
+ ring->ntc = 0;
+ ring->ntu = 0;
+ ring->len = len;
+
+ netif_napi_add_tx(priv->netdev, &ring->napi, napi_poll);
+ napi_enable(&ring->napi);
+ return 0;
+}
+
+static int hbg_tx_ring_init(struct hbg_priv *priv)
+{
+ struct hbg_ring *tx_ring = &priv->tx_ring;
+
+ if (!tx_ring->tout_log_buf)
+ tx_ring->tout_log_buf = devm_kmalloc(&priv->pdev->dev,
+ HBG_TX_TIMEOUT_BUF_LEN,
+ GFP_KERNEL);
+
+ if (!tx_ring->tout_log_buf)
+ return -ENOMEM;
+
+ return hbg_ring_init(priv, tx_ring, hbg_napi_tx_recycle, HBG_DIR_TX);
+}
+
+int hbg_txrx_init(struct hbg_priv *priv)
+{
+ int ret;
+
+ ret = hbg_tx_ring_init(priv);
+ if (ret)
+ dev_err(&priv->pdev->dev,
+ "failed to init tx ring, ret = %d\n", ret);
+
+ return ret;
+}
+
+void hbg_txrx_uninit(struct hbg_priv *priv)
+{
+ hbg_ring_uninit(&priv->tx_ring);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2024 Hisilicon Limited. */
+
+#ifndef __HBG_TXRX_H
+#define __HBG_TXRX_H
+
+#include <linux/etherdevice.h>
+#include "hbg_hw.h"
+
+static inline u32 hbg_spec_max_frame_len(struct hbg_priv *priv,
+ enum hbg_dir dir)
+{
+ return (dir == HBG_DIR_TX) ? priv->dev_specs.max_frame_len :
+ priv->dev_specs.rx_buf_size;
+}
+
+static inline u32 hbg_get_spec_fifo_max_num(struct hbg_priv *priv,
+ enum hbg_dir dir)
+{
+ return (dir == HBG_DIR_TX) ? priv->dev_specs.tx_fifo_num :
+ priv->dev_specs.rx_fifo_num;
+}
+
+static inline bool hbg_fifo_is_full(struct hbg_priv *priv, enum hbg_dir dir)
+{
+ return hbg_hw_get_fifo_used_num(priv, dir) >=
+ hbg_get_spec_fifo_max_num(priv, dir);
+}
+
+static inline u32 hbg_get_queue_used_num(struct hbg_ring *ring)
+{
+ return (ring->ntu + ring->len - ring->ntc) % ring->len;
+}
+
+netdev_tx_t hbg_net_start_xmit(struct sk_buff *skb, struct net_device *netdev);
+int hbg_txrx_init(struct hbg_priv *priv);
+void hbg_txrx_uninit(struct hbg_priv *priv);
+
+#endif