From nobody Thu Sep 24 16:07:47 2026 Received: from out28-146.mail.aliyun.com (out28-146.mail.aliyun.com [115.124.28.146]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 31C5B51C073; Tue, 22 Sep 2026 12:03:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.146 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078607; cv=none; b=V1EalvFjTqEEIKXGrWEOOgBwgJZ3078AciEYMBWz1Lj/Qk+/Y0Q0cD0ZbDtra27mZvGxMvO9XyTkkChhzIVYP7x4QUBF63Q1s10qOV1mfTps08IipqBjx8aax7+9djpeFAZeDLsouRc/c6WLiWmxq1rM9TSqPtUChvNIO0LqPrA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078607; c=relaxed/simple; bh=NuatXUygrFcZ+8Ic433D/6k1sIzusRCGYwGTdSRds9I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=KBwc8eQBIZNXnE8kainBNhz3i8M5Vz/mPpgAr4husREAj8wficEK9BFX9SagqH0z5e/qzAzFMODaYu03eAG3b91AfnqlLOSjyEtQVCMlWupMS4ZO0HVpwUsAawDxGSK6Z1em3bfU/Qmkf1RchD4U8KBaRK+a1YfoiWwd/X97yyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.146 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_system_inform|0.0306048-0.00127899-0.968116;FP=16335519543958524978|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033032053168;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPQ2_1790078594; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPQ2_1790078594 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:15 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 1/8] net/nebula-matrix: add channel layer Date: Tue, 22 Sep 2026 20:02:58 +0800 Message-ID: <20260922120311.86593-2-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: illusion wang Add generic channel management layer for Nebula Matrix network adapter to implement core inter-PF communication capability. This patch introduces a dedicated mailbox-based channel layer as the foundation of PF0 <-> other PFs inter-PF communication. It provides standardized message transmission, lifecycle-based queue management and hardware abstraction, supporting reliable control message interaction between physical functions. Core functional implementations: 1. Dynamic message handling framework - Implement xarray-based dynamic message handler management with O(1) lookup by message type - Support two transmission modes: fire-and-forget and synchronous send with ACK waiting - Dedicated ACK message processing path to match request-response semantics - TX slot concurrency control, return -EAGAIN under full slot pressure - Dual transmission format: small embedded payload in TX descriptor, large payload via external DMA buffer - Complete message handler cleanup logic in driver teardown 2. Mailbox queue lifecycle management - Coherent DMA allocation for TX/RX descriptor rings and data buffers with devm automatic resource management - Full queue lifecycle: hardware init/config/stop/teardown - Dual RX processing mode (interrupt/polling), offload RX cleanup to dedicated workqueue to reduce IRQ latency - Inflight TX traffic draining and pending ACK abortion during channel teardown - Reserved hole mechanism to precisely distinguish RX ring empty/full state and avoid overflow - One-shot DMA buffer allocation in probe phase, no dynamic runtime reinit 3. Hardware abstraction layer (hw_ops) - Decouple channel business logic from hardware implementation via independent hw_ops layer - Provide hardware queue config, tail pointer doorbell update and PF mailbox route table configuration interfaces - Add reg_lock to protect BAR0 read-modify-write register operations - Optimize BAR2 register access without lock for fast-path performance, avoid CPU soft lockup under stress test 4. Common infrastructure & utility - Implement thread-safe xarray management for message handlers - Create device-independent dedicated workqueue for RX cleanup tasks - Supplement channel state management, bitmask control and auxiliary tool interfaces Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/Makefile | 4 +- .../nbl/nbl_channel/nbl_channel.c | 1421 +++++++++++++++++ .../nbl/nbl_channel/nbl_channel.h | 181 +++ .../nebula-matrix/nbl/nbl_common/nbl_common.c | 31 + .../nebula-matrix/nbl/nbl_common/nbl_common.h | 14 + .../net/ethernet/nebula-matrix/nbl/nbl_core.h | 7 + .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c | 250 +++ .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h | 42 + .../nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h | 33 + .../nbl/nbl_include/nbl_def_channel.h | 125 ++ .../nbl/nbl_include/nbl_def_common.h | 4 + .../nbl/nbl_include/nbl_def_hw.h | 38 + .../nbl/nbl_include/nbl_include.h | 3 + .../net/ethernet/nebula-matrix/nbl/nbl_main.c | 7 + 14 files changed, 2159 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_= channel.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_= channel.h create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_c= ommon.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_c= ommon.h create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_= def_channel.h diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/= ethernet/nebula-matrix/nbl/Makefile index cc060cf8bf75..04e1aa1fb4bd 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/Makefile +++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile @@ -3,5 +3,7 @@ =20 obj-$(CONFIG_NBL) :=3D nbl.o =20 -nbl-objs +=3D nbl_hw/nbl_hw_leonis/nbl_hw_leonis.o \ +nbl-objs +=3D nbl_common/nbl_common.o \ + nbl_channel/nbl_channel.o \ + nbl_hw/nbl_hw_leonis/nbl_hw_leonis.o \ nbl_main.o diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel= .c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c new file mode 100644 index 000000000000..706875751836 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.c @@ -0,0 +1,1421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nbl_channel.h" + +static int nbl_chan_add_msg_handler(struct nbl_channel_mgt *chan_mgt, + u16 msg_type, nbl_chan_resp func, + void *priv) +{ + struct nbl_chan_msg_node_data *handler; + int ret; + + handler =3D kzalloc_obj(*handler, GFP_KERNEL); + if (!handler) + return -ENOMEM; + + handler->func =3D func; + handler->priv =3D priv; + + /* Each msg_type is registered at most once; reject duplicates. */ + mutex_lock(&chan_mgt->handler_lock); + ret =3D xa_insert(&chan_mgt->handler_xa, msg_type, handler, GFP_KERNEL); + mutex_unlock(&chan_mgt->handler_lock); + if (ret) + kfree(handler); + + return ret; +} + +static int nbl_chan_init_msg_handler(struct nbl_channel_mgt *chan_mgt) +{ + int ret; + + ret =3D devm_mutex_init(chan_mgt->common->dev, &chan_mgt->handler_lock); + if (ret) + return ret; + + xa_init(&chan_mgt->handler_xa); + + return 0; +} + +static void nbl_chan_remove_msg_handler(struct nbl_channel_mgt *chan_mgt) +{ + struct nbl_chan_msg_node_data *handler; + unsigned long msg_type; + + mutex_lock(&chan_mgt->handler_lock); + xa_for_each(&chan_mgt->handler_xa, msg_type, handler) { + xa_erase(&chan_mgt->handler_xa, msg_type); + kfree(handler); + } + mutex_unlock(&chan_mgt->handler_lock); + xa_destroy(&chan_mgt->handler_xa); +} + +static void nbl_chan_init_queue_param(struct nbl_chan_info *chan_info, + u16 num_txq_entries, u16 num_rxq_entries, + u16 txq_buf_size, u16 rxq_buf_size) +{ + chan_info->num_txq_entries =3D num_txq_entries; + chan_info->num_rxq_entries =3D num_rxq_entries; + chan_info->txq_buf_size =3D txq_buf_size; + chan_info->rxq_buf_size =3D rxq_buf_size; + atomic_set(&chan_info->inflight_tx_cnt, 0); + WRITE_ONCE(chan_info->shutdn, false); + WRITE_ONCE(chan_info->active, false); + WRITE_ONCE(chan_info->wait_head_index, 0); + memset(chan_info->state, 0, sizeof(chan_info->state)); + init_waitqueue_head(&chan_info->inflight_wait); +} + +static int nbl_chan_init_tx_queue(struct nbl_common_info *common, + struct nbl_chan_info *chan_info) +{ + struct nbl_chan_ring *txq =3D &chan_info->txq; + struct device *dev =3D common->dev; + size_t size =3D + chan_info->num_txq_entries * sizeof(struct nbl_chan_tx_desc); + u16 i; + + txq->desc.tx_desc =3D + dmam_alloc_coherent(dev, size, &txq->dma, GFP_KERNEL); + if (!txq->desc.tx_desc) + return -ENOMEM; + + chan_info->wait =3D devm_kcalloc(dev, chan_info->num_txq_entries, + sizeof(*chan_info->wait), GFP_KERNEL); + if (!chan_info->wait) + return -ENOMEM; + for (i =3D 0; i < chan_info->num_txq_entries; i++) { + init_waitqueue_head(&chan_info->wait[i].wait_queue); + WRITE_ONCE(chan_info->wait[i].status, NBL_MBX_STATUS_IDLE); + WRITE_ONCE(chan_info->wait[i].acked, 0); + WRITE_ONCE(chan_info->wait[i].ack_data, NULL); + WRITE_ONCE(chan_info->wait[i].ack_data_len, 0); + WRITE_ONCE(chan_info->wait[i].ack_err, 0); + WRITE_ONCE(chan_info->wait[i].msg_type, 0); + WRITE_ONCE(chan_info->wait[i].msg_index, 0); + WRITE_ONCE(chan_info->wait[i].dstid, 0); + } + + txq->buf =3D devm_kcalloc(dev, chan_info->num_txq_entries, + sizeof(*txq->buf), GFP_KERNEL); + if (!txq->buf) + return -ENOMEM; + + return 0; +} + +static int nbl_chan_init_rx_queue(struct nbl_common_info *common, + struct nbl_chan_info *chan_info) +{ + struct nbl_chan_ring *rxq =3D &chan_info->rxq; + struct device *dev =3D common->dev; + size_t size =3D + chan_info->num_rxq_entries * sizeof(struct nbl_chan_rx_desc); + + rxq->desc.rx_desc =3D + dmam_alloc_coherent(dev, size, &rxq->dma, GFP_KERNEL); + if (!rxq->desc.rx_desc) { + dev_err_ratelimited(dev, + "Allocate DMA for chan rx descriptor ring failed\n"); + return -ENOMEM; + } + + rxq->buf =3D devm_kcalloc(dev, chan_info->num_rxq_entries, + sizeof(*rxq->buf), GFP_KERNEL); + if (!rxq->buf) + return -ENOMEM; + + return 0; +} + +static int nbl_chan_init_queue(struct nbl_common_info *common, + struct nbl_chan_info *chan_info) +{ + int err; + + err =3D nbl_chan_init_tx_queue(common, chan_info); + if (err) + return err; + + err =3D nbl_chan_init_rx_queue(common, chan_info); + + return err; +} + +static void nbl_chan_config_queue(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info, bool tx) +{ + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + struct nbl_hw_mgt *p =3D chan_mgt->hw_ops_tbl->priv; + struct nbl_chan_ring *ring; + dma_addr_t addr; + int size_bwid; + + if (tx) + ring =3D &chan_info->txq; + else + ring =3D &chan_info->rxq; + addr =3D ring->dma; + if (tx) { + size_bwid =3D ilog2(chan_info->num_txq_entries); + hw_ops->config_mailbox_txq(p, addr, size_bwid); + } else { + size_bwid =3D ilog2(chan_info->num_rxq_entries); + hw_ops->config_mailbox_rxq(p, addr, size_bwid); + } +} + +static int nbl_chan_alloc_all_tx_bufs(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info) +{ + struct nbl_chan_ring *txq =3D &chan_info->txq; + struct device *dev =3D chan_mgt->common->dev; + struct nbl_chan_buf *buf; + u16 i; + + for (i =3D 0; i < chan_info->num_txq_entries; i++) { + buf =3D &txq->buf[i]; + buf->va =3D dmam_alloc_coherent(dev, chan_info->txq_buf_size, + &buf->pa, GFP_KERNEL); + if (!buf->va) { + dev_err_ratelimited(dev, + "Allocate buffer for chan tx queue failed\n"); + return -ENOMEM; + } + } + + txq->next_to_clean =3D 0; + txq->next_to_use =3D 0; + txq->tail_ptr =3D 0; + + return 0; +} + +static void nbl_chan_cfg_qinfo_map_table(struct nbl_channel_mgt *chan_mgt, + u8 bus, u8 devid) +{ + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + struct nbl_hw_mgt *p =3D chan_mgt->hw_ops_tbl->priv; + u32 pf_mask =3D 0; + u8 func_id; + + /* + * k_pf_mask rule: bit N =3D=3D 0 means PF#N enabled, bit N =3D=3D 1 mask= ed out. + * Program mailbox QINFO entry for each hardware-active PF func_id. + * + * Note: This loop iterates over raw hardware PF func_id. + * Product constraints limit supported PF counts to contiguous sets: + * PF0 only, PF0~1, or PF0~3. Non-contiguous or unsupported PF count + * will be rejected in resource initialization logic. + */ + hw_ops->get_host_pf_mask(p, &pf_mask); + for (func_id =3D 0; func_id < NBL_MAX_PF; func_id++) { + if (!(pf_mask & (1 << func_id))) + hw_ops->cfg_mailbox_qinfo(p, func_id, bus, + devid, func_id); + } +} + +static int nbl_chan_alloc_all_rx_bufs(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info) +{ + struct nbl_chan_ring *rxq =3D &chan_info->rxq; + struct device *dev =3D chan_mgt->common->dev; + struct nbl_chan_rx_desc *desc; + struct nbl_chan_buf *buf; + u16 i; + + for (i =3D 0; i < chan_info->num_rxq_entries; i++) { + buf =3D &rxq->buf[i]; + buf->va =3D dmam_alloc_coherent(dev, chan_info->rxq_buf_size, + &buf->pa, GFP_KERNEL); + if (!buf->va) { + dev_err_ratelimited(dev, + "Allocate buffer for chan rx queue failed\n"); + goto err; + } + } + + desc =3D rxq->desc.rx_desc; + /* + * Initially leave one RX descriptor unused so that + * next_to_clean and next_to_use can distinguish an empty + * ring from a full ring. + * + * The unused slot is replenished as RX descriptors are + * consumed and recycled. + */ + for (i =3D 0; i < chan_info->num_rxq_entries - 1; i++) { + buf =3D &rxq->buf[i]; + desc[i].buf_addr =3D cpu_to_le64(buf->pa); + desc[i].buf_len =3D cpu_to_le32(chan_info->rxq_buf_size); + desc[i].flags =3D cpu_to_le16(BIT(NBL_CHAN_RX_DESC_AVAIL)); + } + + rxq->next_to_clean =3D 0; + rxq->next_to_use =3D chan_info->num_rxq_entries - 1; + rxq->tail_ptr =3D chan_info->num_rxq_entries - 1; + + return 0; +err: + return -ENOMEM; +} + +static int nbl_chan_alloc_all_bufs(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info) +{ + int err; + + err =3D nbl_chan_alloc_all_tx_bufs(chan_mgt, chan_info); + if (err) + return err; + err =3D nbl_chan_alloc_all_rx_bufs(chan_mgt, chan_info); + + return err; +} + +static void nbl_chan_stop_queue(struct nbl_channel_mgt *chan_mgt) +{ + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + + hw_ops->stop_mailbox_rxq(chan_mgt->hw_ops_tbl->priv); + hw_ops->stop_mailbox_txq(chan_mgt->hw_ops_tbl->priv); +} + +static void nbl_chan_reset_wait_head(struct nbl_chan_info *chan_info, + struct nbl_chan_waitqueue_head *wait_head) +{ + lockdep_assert_held(&chan_info->pending_lock); + + WRITE_ONCE(wait_head->acked, 0); + WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_IDLE); + WRITE_ONCE(wait_head->ack_data, NULL); + WRITE_ONCE(wait_head->ack_data_len, 0); + WRITE_ONCE(wait_head->ack_err, 0); + WRITE_ONCE(wait_head->msg_type, 0); + WRITE_ONCE(wait_head->dstid, 0); +} + +static int nbl_chan_teardown_queue(struct nbl_channel_mgt *chan_mgt, + u8 chan_type) +{ + struct nbl_chan_info *chan_info =3D chan_mgt->chan_info[chan_type]; + struct nbl_chan_waitqueue_head *wait_head; + struct work_struct *task; + bool err; + int ret =3D 0; + u16 i; + + if (!READ_ONCE(chan_info->active)) { + dev_warn(chan_mgt->common->dev, "channel not active, skip duplicate tear= down\n"); + return 0; + } + /* + * Step1: + * block new sender + */ + mutex_lock(&chan_info->state_lock); + WRITE_ONCE(chan_info->shutdn, true); + task =3D READ_ONCE(chan_info->clean_task); + WRITE_ONCE(chan_info->clean_task, NULL); + mutex_unlock(&chan_info->state_lock); + /* + * Step2: + * abort pending ACK waiters + */ + + mutex_lock(&chan_info->pending_lock); + for (i =3D 0; i < chan_info->num_txq_entries; i++) { + wait_head =3D &chan_info->wait[i]; + nbl_chan_reset_wait_head(chan_info, wait_head); + WRITE_ONCE(wait_head->ack_err, (s32)-EIO); + WRITE_ONCE(wait_head->acked, 1); + wake_up(&wait_head->wait_queue); + } + mutex_unlock(&chan_info->pending_lock); + /* + * Drain strategy mirrors mlx5 command interface teardown: + * set shutdown flag first, abort all pending waiters, then + * block until inflight_tx_cnt reaches zero. + * + * After shutdn is set every sender exits promptly at its next + * checkpoint: + * - interrupt-driven senders wake on shutdn immediately + * (it is part of the wait_event condition); + * - polling senders re-check shutdn every 100-120us; + * - TX recovery never re-arms a shutting-down queue (gates in + * nbl_chan_kick_tx_ring()/nbl_chan_quiesce_and_reclaim_tx()). + * + * A timeout therefore means a sender is stuck where no flag can + * reach it - almost certainly an MMIO read against a wedged PCIe + * link. rmmod must not hang in that case, so proceed, but the + * hardware stop below is trylock-protected instead of blocking. + */ + err =3D !wait_event_timeout(chan_info->inflight_wait, + atomic_read(&chan_info->inflight_tx_cnt) =3D=3D 0, + msecs_to_jiffies(5000)); + if (err) { + dev_warn(chan_mgt->common->dev, + "teardown: inflight tx drain timeout\n"); + ret =3D -ETIMEDOUT; + } + + /* + * Stop the hardware queue under txq_lock so this is the sole + * writer of the BAR2 QINFO block (sender-side stop/config/doorbell + * sequences all run under txq_lock). + * + * Successful drain: no sender can hold txq_lock, plain lock. + * Zombie case: never block - a holder may be trapped in an MMIO + * read. Such a holder observes shutdn and emits at most the QUEUE + * reset table (identical 16 bytes to our stop, so even interleaved + * writes are harmless), never QUEUE_EN. If the lock is busy we skip + * our own stop and leave the queue in the holder's reset state. + */ + if (!err) { + mutex_lock(&chan_info->txq_lock); + nbl_chan_stop_queue(chan_mgt); + mutex_unlock(&chan_info->txq_lock); + } else if (mutex_trylock(&chan_info->txq_lock)) { + nbl_chan_stop_queue(chan_mgt); + mutex_unlock(&chan_info->txq_lock); + } else { + dev_crit(chan_mgt->common->dev, + "zombie channel: inflight sender holds txq_lock; HW stop skipped, queu= e left in QUEUE_RST; device requires reset before re-bind\n"); + } + + /* + * Join the RX clean work before active=3Dfalse and before devres + * releases the rings. IRQ is already freed and clean_task is + * cleared under state_lock, so no new instance can be queued. + */ + if (task) + cancel_work_sync(task); + WRITE_ONCE(chan_info->active, false); + return ret; +} + +static int nbl_chan_setup_queue(struct nbl_channel_mgt *chan_mgt, u8 chan_= type) +{ + struct nbl_chan_info *chan_info =3D chan_mgt->chan_info[chan_type]; + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + struct nbl_common_info *common =3D chan_mgt->common; + struct nbl_chan_ring *rxq =3D &chan_info->rxq; + int err; + + if (READ_ONCE(chan_info->active)) { + dev_warn(common->dev, "channel already active, reject duplicate setup\n"= ); + return -EBUSY; + } + /* + * DMA resources are allocated once and released only by devres + * at device detach. A teardown does not free them, so a second + * setup would orphan ~2 MiB of coherent DMA per channel. + */ + if (chan_info->dma_allocated) { + dev_warn(common->dev, + "channel DMA already allocated, re-setup not supported\n"); + return -EBUSY; + } + nbl_chan_init_queue_param(chan_info, NBL_CHAN_QUEUE_LEN, + NBL_CHAN_QUEUE_LEN, NBL_CHAN_BUF_LEN, + NBL_CHAN_BUF_LEN); + err =3D nbl_chan_init_queue(common, chan_info); + if (err) + return err; + err =3D nbl_chan_alloc_all_bufs(chan_mgt, chan_info); + if (err) + return err; + nbl_chan_config_queue(chan_mgt, chan_info, true); /* tx */ + nbl_chan_config_queue(chan_mgt, chan_info, false); /* rx */ + nbl_chan_update_tail_ptr(hw_ops, chan_mgt->hw_ops_tbl->priv, + rxq->tail_ptr, NBL_MB_RX_QID); + WRITE_ONCE(chan_info->active, true); + chan_info->dma_allocated =3D true; + return 0; +} + +static bool nbl_chan_txq_full(struct nbl_chan_ring *txq, + u16 num_entries) +{ + return NBL_NEXT_ID(txq->next_to_use, num_entries - 1) =3D=3D + txq->next_to_clean; +} + +static int nbl_chan_update_txqueue(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info, + struct nbl_chan_tx_param *param) +{ + struct nbl_chan_ring *txq =3D &chan_info->txq; + struct nbl_chan_tx_desc *tx_desc; + struct nbl_chan_buf *tx_buf; + + if (nbl_chan_txq_full(txq, chan_info->num_txq_entries)) + return -EBUSY; + if (param->arg_len > NBL_CHAN_BUF_LEN - sizeof(*tx_desc)) + return -EINVAL; + tx_desc =3D + NBL_CHAN_TX_RING_TO_DESC(txq, txq->next_to_use); + tx_buf =3D + NBL_CHAN_TX_RING_TO_BUF(txq, txq->next_to_use); + tx_desc->dstid =3D cpu_to_le16(param->dstid); + tx_desc->msg_type =3D cpu_to_le16(param->msg_type); + tx_desc->msgid =3D cpu_to_le16(param->msgid); + + /* + * srcid field is filled by mailbox hardware after peer receives this + * packet, driver producer never writes srcid; reused descriptor slots + * will contain stale srcid value temporarily until hardware overwrites + * it. + */ + if (param->arg_len > NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN) { + if (param->arg) + memcpy(tx_buf->va, param->arg, param->arg_len); + tx_desc->buf_addr =3D cpu_to_le64(tx_buf->pa); + tx_desc->buf_len =3D cpu_to_le16(param->arg_len); + tx_desc->data_len =3D 0; + memset(tx_desc->data, 0, sizeof(tx_desc->data)); + } else { + memset(tx_desc->data, 0, sizeof(tx_desc->data)); + memset(&tx_desc->buf_addr, 0, sizeof(tx_desc->buf_addr)); + if (param->arg && param->arg_len > 0) + memcpy(tx_desc->data, param->arg, param->arg_len); + tx_desc->buf_len =3D 0; + tx_desc->data_len =3D cpu_to_le16(param->arg_len); + } + /* Ensure descriptor data visible to device before AVAIL flag */ + dma_wmb(); + tx_desc->flags =3D cpu_to_le16(BIT(NBL_CHAN_TX_DESC_AVAIL)); + + txq->next_to_use =3D + NBL_NEXT_ID(txq->next_to_use, chan_info->num_txq_entries - 1); + txq->tail_ptr++; + + return 0; +} + +/* + * Quiesce the TX mailbox queue and reclaim all outstanding + * descriptors. Called from the timeout path of nbl_chan_kick_tx_ring() + * with txq_lock held. + * + * The device failed to fetch/complete the current descriptor within the + * polling window. We assert QUEUE_RST to stop further DMA fetches, + * reclaim every descriptor between next_to_clean and next_to_use, + * reset the software tail_ptr counter to match the hardware reset state, + * and re-enable the queue so subsequent sends can proceed. + */ +static void nbl_chan_quiesce_and_reclaim_tx(struct nbl_channel_mgt *chan_m= gt, + struct nbl_chan_info *chan_info) +{ + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + struct nbl_hw_mgt *hw_priv =3D chan_mgt->hw_ops_tbl->priv; + struct nbl_chan_ring *txq =3D &chan_info->txq; + struct nbl_chan_tx_desc *tx_desc; + + /* + * Assert QUEUE_RST to stop hardware fetching new descriptors. + * stop_mailbox_txq() flushes through the mailbox BAR itself, so + * the reset has reached the device when it returns; do NOT call + * hw_ops->flush_write() here, that op reads the control-PF-only + * MEMORY BAR aperture and is unsafe on sibling PFs. + */ + hw_ops->stop_mailbox_txq(hw_priv); + + /* + * Reclaim all outstanding descriptors between next_to_clean and + * next_to_use. Under txq_lock there is at most one in-flight + * descriptor, but iterate the full range for robustness. + */ + while (txq->next_to_clean !=3D txq->next_to_use) { + tx_desc =3D NBL_CHAN_TX_RING_TO_DESC(txq, + txq->next_to_clean); + WRITE_ONCE(tx_desc->flags, 0); + txq->next_to_clean =3D + NBL_NEXT_ID(txq->next_to_clean, + chan_info->num_txq_entries - 1); + } + + /* + * Hardware tail_ptr counter is cleared by QUEUE_RST. Reset + * software counter to match so the next doorbell update does + * not produce a false 16-bit wrap delta. + */ + txq->tail_ptr =3D 0; + txq->next_to_use =3D 0; + txq->next_to_clean =3D 0; + + /* + * Authoritative re-arm gate, evaluated while txq_lock is held: + * teardown sets shutdn before draining senders and then owns + * hardware state. If teardown started while we were stopping/ + * reclaiming, leave the queue in QUEUE_RST and never program + * QUEUE_EN, otherwise the queue could be armed again pointing + * at rings that are about to be freed. + */ + if (READ_ONCE(chan_info->shutdn)) + return; + + /* Re-enable queue with current ring base and size */ + nbl_chan_config_queue(chan_mgt, chan_info, true); +} + +static int nbl_chan_kick_tx_ring(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info) +{ + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + struct nbl_chan_ring *txq =3D &chan_info->txq; + struct device *dev =3D chan_mgt->common->dev; + int max_retries =3D NBL_CHAN_TX_WAIT_TIMES; + struct nbl_chan_tx_desc *tx_desc; + int retry_count =3D 0; + u16 msg_type; + + nbl_chan_update_tail_ptr(hw_ops, chan_mgt->hw_ops_tbl->priv, + txq->tail_ptr, NBL_MB_TX_QID); + + tx_desc =3D NBL_CHAN_TX_RING_TO_DESC(txq, txq->next_to_clean); + /* + * Poll for HW to mark descriptor as USED. + * Mailbox is a low-speed control channel for management commands. + * We avoid enabling dedicated per-TX interrupt for single control + * message to reduce interrupt overhead, so use bounded polling + * with small delay instead. + */ + while (retry_count < max_retries) { + if (READ_ONCE(chan_info->shutdn)) + return -ESHUTDOWN; + + if (le16_to_cpu(READ_ONCE(tx_desc->flags)) & + BIT(NBL_CHAN_TX_DESC_USED)) { + /* + * Order reads of other device-written descriptor + * fields after observing USED. Matches the RX side + * pattern in nbl_chan_clean_queue(). + */ + dma_rmb(); + break; + } + + retry_count++; + if (retry_count =3D=3D max_retries) { + msg_type =3D le16_to_cpu(READ_ONCE(tx_desc->msg_type)); + dev_err_ratelimited(dev, "chan send msg type: %d timeout\n", + msg_type); + /* + * Teardown may have started after the loop-top + * check on this final iteration. Do not run lock + * recovery then: it would stop/reclaim the queue + * and, without the in-quiesce gate, re-arm it. + * nbl_chan_teardown_queue() owns hardware state. + */ + if (READ_ONCE(chan_info->shutdn)) + return -ESHUTDOWN; + /* + * Device failed to complete this descriptor. + * Quiesce the queue, reclaim the timed-out + * descriptor, and re-enable so future sends can + * proceed instead of stalling the ring full. + */ + nbl_chan_quiesce_and_reclaim_tx(chan_mgt, + chan_info); + return -ETIMEDOUT; + } + usleep_range(NBL_CHAN_TX_WAIT_US, NBL_CHAN_TX_WAIT_US_MAX); + } + + txq->next_to_clean =3D txq->next_to_use; + + return 0; +} + +static void nbl_chan_recv_ack_msg(void *priv, u16 srcid, u16 msgid, void *= data, + u32 data_len) +{ + struct nbl_channel_mgt *chan_mgt =3D (struct nbl_channel_mgt *)priv; + struct nbl_chan_waitqueue_head *wait_head =3D NULL; + struct device *dev =3D chan_mgt->common->dev; + struct nbl_chan_info *chan_info =3D + chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX]; + u16 w_dstid, w_msgtype, w_msgidx; + u32 *payload =3D data; + u16 ack_msgtype =3D 0; + u16 ack_msgid =3D 0; + u32 ack_datalen; + void *ack_data; + u32 copy_len; + int w_status; + s32 raw_err; + + if (READ_ONCE(chan_info->shutdn)) + return; + if (data_len > NBL_CHAN_BUF_LEN || + data_len < NBL_CHAN_ACK_HEAD_LEN * sizeof(u32)) { + dev_err_ratelimited(dev, "Invalid ACK data_len: %u\n", + data_len); + return; + } + ack_datalen =3D data_len - NBL_CHAN_ACK_HEAD_LEN * sizeof(u32); + ack_msgtype =3D le16_to_cpu(*(__le16 *)(payload + NBL_CHAN_MSG_TYPE_POS)); + ack_msgid =3D le16_to_cpu(*(__le16 *)(payload + NBL_CHAN_MSG_ID_POS)); + if (FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, ack_msgid) >=3D + chan_info->num_txq_entries) { + dev_err_ratelimited(dev, "chan recv msg id: %u err\n", + ack_msgid); + return; + } + wait_head =3D + &chan_info->wait[FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, ack_msgid)]; + + mutex_lock(&chan_info->pending_lock); + + /* Cache repeated READ_ONCE values */ + w_dstid =3D READ_ONCE(wait_head->dstid); + w_status =3D READ_ONCE(wait_head->status); + w_msgtype =3D READ_ONCE(wait_head->msg_type); + w_msgidx =3D READ_ONCE(wait_head->msg_index); + + if (srcid !=3D w_dstid) { + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, "ACK srcid=3D%u !=3D dstid=3D%u, rejecting\n", + srcid, w_dstid); + return; + } + if (w_status !=3D NBL_MBX_STATUS_WAITING) { + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, + "Skip ack invalid status, wait msgtype:%u idx:%u status:%d ack msg= type:%u msgid:%u datalen:%u\n", + w_msgtype, w_msgidx, w_status, + ack_msgtype, ack_msgid, ack_datalen); + return; + } + + if (w_msgtype !=3D ack_msgtype) { + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, + "Skip ack msgtype mismatch, wait msgtype:%u idx:%u ack msgtype:%u = msgid:%u\n", + w_msgtype, w_msgidx, ack_msgtype, + ack_msgid); + return; + } + if (FIELD_GET(NBL_CHAN_MSGID_INDEX_MASK, ack_msgid) !=3D w_msgidx) { + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, + "Stale ACK: expected index=3D%u, got msgid=3D%u\n", + w_msgidx, ack_msgid); + return; + } + + raw_err =3D (s32)le32_to_cpu(*(__le32 *)&payload[NBL_CHAN_ACK_RET_POS]); + if (raw_err > 0 || raw_err < -MAX_ERRNO) + raw_err =3D -EREMOTEIO; + + WRITE_ONCE(wait_head->ack_err, raw_err); + + copy_len =3D min_t(u32, READ_ONCE(wait_head->ack_data_len), ack_datalen); + if (READ_ONCE(wait_head->ack_err) >=3D 0 && copy_len > 0) { + ack_data =3D READ_ONCE(wait_head->ack_data); + if (!ack_data) { + dev_err_ratelimited(dev, "ACK payload dropped: ack_data is NULL\n"); + WRITE_ONCE(wait_head->ack_data_len, 0); + goto ack_done; + } + memcpy((char *)ack_data, + payload + NBL_CHAN_ACK_HEAD_LEN, copy_len); + WRITE_ONCE(wait_head->ack_data_len, (u16)copy_len); + } else { + WRITE_ONCE(wait_head->ack_data_len, 0); + } +ack_done: + /* Guarantee payload data finished before acked flag visible */ + smp_wmb(); + WRITE_ONCE(wait_head->acked, 1); + WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_ACKD); + mutex_unlock(&chan_info->pending_lock); + wake_up(&wait_head->wait_queue); +} + +static void nbl_chan_recv_msg(struct nbl_channel_mgt *chan_mgt, void *data) +{ + struct device *dev =3D chan_mgt->common->dev; + struct nbl_chan_msg_node_data *msg_handler; + u16 msg_type, payload_len, srcid, msgid; + struct nbl_chan_info *chan_info =3D + chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX]; + struct nbl_chan_tx_desc *tx_desc; + void *payload; + size_t avail_space; + u16 data_len_fw; + + if (READ_ONCE(chan_info->shutdn)) + return; + + tx_desc =3D data; + msg_type =3D le16_to_cpu(READ_ONCE(tx_desc->msg_type)); + dev_dbg(dev, "recv msg_type: %d\n", msg_type); + + srcid =3D le16_to_cpu(READ_ONCE(tx_desc->srcid)); + msgid =3D le16_to_cpu(READ_ONCE(tx_desc->msgid)); + + if (msg_type >=3D NBL_CHAN_MSG_MAILBOX_MAX) + return; + + data_len_fw =3D le16_to_cpu(READ_ONCE(tx_desc->data_len)); + if (data_len_fw) { + payload_len =3D data_len_fw; + + if (payload_len > NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN) { + dev_err_ratelimited(dev, + "data_len=3D%u exceeds embedded buffer size=3D%u\n", + payload_len, + NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN); + return; + } + /* Small pkt: payload stored inside descriptor data[] array */ + payload =3D tx_desc->data; + } else { + payload_len =3D le16_to_cpu(READ_ONCE(tx_desc->buf_len)); + + avail_space =3D NBL_CHAN_BUF_LEN - sizeof(*tx_desc); + if (payload_len > avail_space) { + dev_err_ratelimited(dev, + "buf_len=3D%u exceeds external buffer size=3D%zu\n", + payload_len, avail_space); + return; + } + /* Large pkt: payload follows immediately after tx_desc */ + payload =3D tx_desc + 1; + } + + msg_handler =3D xa_load(&chan_mgt->handler_xa, msg_type); + if (!msg_handler || !msg_handler->func) { + dev_err_ratelimited(dev, + "No handler for msg_type: %u (srcid=3D%u, msgid=3D%u)\n", + msg_type, srcid, msgid); + return; + } + + msg_handler->func(msg_handler->priv, srcid, msgid, payload, + payload_len); +} + +static void nbl_chan_advance_rx_ring(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info, + struct nbl_chan_ring *rxq) +{ + struct nbl_hw_ops *hw_ops =3D chan_mgt->hw_ops_tbl->ops; + struct nbl_chan_rx_desc *rx_desc; + struct nbl_chan_buf *rx_buf; + u16 next_to_use; + + next_to_use =3D rxq->next_to_use; + rx_desc =3D NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_use); + rx_buf =3D NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_use); + + /* + * Recycle the RX descriptor at next_to_use. The initial + * unused slot is intentionally recycled after the first + * RX descriptor is consumed, allowing the ring to become + * fully populated while next_to_clean tracks the consumer. + */ + rx_desc->buf_addr =3D cpu_to_le64(rx_buf->pa); + rx_desc->buf_len =3D cpu_to_le32(chan_info->rxq_buf_size); + + /* + * DMA Write Memory Barrier: + * Ensures all previous DMA-mapped writes (buffer address/length) + * are completed before the descriptor flags are updated. + * This prevents hardware from seeing a partially updated descriptor + * where flags are set but buffer info isn't ready yet. + */ + dma_wmb(); + + rx_desc->flags =3D cpu_to_le16(BIT(NBL_CHAN_RX_DESC_AVAIL)); + + rxq->next_to_use++; + if (rxq->next_to_use =3D=3D chan_info->num_rxq_entries) + rxq->next_to_use =3D 0; + rxq->tail_ptr++; + + nbl_chan_update_tail_ptr(hw_ops, chan_mgt->hw_ops_tbl->priv, + rxq->tail_ptr, NBL_MB_RX_QID); +} + +static void nbl_chan_clean_queue(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_info *chan_info) +{ + struct nbl_common_info *common =3D chan_mgt->common; + struct nbl_chan_ring *rxq =3D &chan_info->rxq; + struct device *dev =3D chan_mgt->common->dev; + u32 budget =3D NBL_CHAN_RX_CLEAN_BUDGET; + struct nbl_chan_rx_desc *rx_desc; + struct nbl_chan_buf *rx_buf; + struct work_struct *task; + bool more_work =3D false; + u16 next_to_clean; + u16 flags; + + next_to_clean =3D rxq->next_to_clean; + rx_desc =3D NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_clean); + rx_buf =3D NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_clean); + while (le16_to_cpu(READ_ONCE(rx_desc->flags)) & + BIT(NBL_CHAN_RX_DESC_USED)) { + flags =3D le16_to_cpu(READ_ONCE(rx_desc->flags)); + + if (READ_ONCE(chan_info->shutdn)) + break; + if (!(flags & BIT(NBL_CHAN_RX_DESC_WRITE))) + dev_dbg(dev, + "mailbox rx flag 0x%x missing NBL_CHAN_RX_DESC_WRITE\n", + flags); + + /* Make sure hardware written descriptor visible to CPU */ + dma_rmb(); + nbl_chan_recv_msg(chan_mgt, rx_buf->va); + nbl_chan_advance_rx_ring(chan_mgt, chan_info, rxq); + next_to_clean++; + if (next_to_clean =3D=3D chan_info->num_rxq_entries) + next_to_clean =3D 0; + rx_desc =3D NBL_CHAN_RX_RING_TO_DESC(rxq, next_to_clean); + rx_buf =3D NBL_CHAN_RX_RING_TO_BUF(rxq, next_to_clean); + if (--budget =3D=3D 0) { + more_work =3D true; + break; + } + cond_resched(); + } + rxq->next_to_clean =3D next_to_clean; + + mutex_lock(&chan_info->state_lock); + /* Prevent queue_work after teardown clears clean_task */ + if (READ_ONCE(chan_info->shutdn)) { + mutex_unlock(&chan_info->state_lock); + return; + } + if (common->wq && more_work) { + task =3D READ_ONCE(chan_info->clean_task); + if (task) + queue_work(common->wq, task); + } + mutex_unlock(&chan_info->state_lock); +} + +static void nbl_chan_clean_queue_subtask(struct nbl_channel_mgt *chan_mgt, + u8 chan_type) +{ + struct nbl_chan_info *chan_info =3D chan_mgt->chan_info[chan_type]; + + nbl_chan_clean_queue(chan_mgt, chan_info); +} + +static int nbl_chan_get_msg_id(struct nbl_chan_info *chan_info, + u16 *msgid) +{ + int search_loc =3D READ_ONCE(chan_info->wait_head_index), i; + struct nbl_chan_waitqueue_head *wait =3D NULL; + int status; + int next; + + lockdep_assert_held(&chan_info->pending_lock); + for (i =3D 0; i < chan_info->num_txq_entries; i++) { + wait =3D &chan_info->wait[search_loc]; + status =3D READ_ONCE(wait->status); + if (status =3D=3D NBL_MBX_STATUS_IDLE || + status =3D=3D NBL_MBX_STATUS_TIMEOUT) { + WRITE_ONCE(wait->msg_index, + NBL_NEXT_ID(wait->msg_index, + NBL_CHAN_MSG_INDEX_MAX)); + + *msgid =3D FIELD_PREP(NBL_CHAN_MSGID_INDEX_MASK, + wait->msg_index) | + FIELD_PREP(NBL_CHAN_MSGID_LOC_MASK, + search_loc); + + /* Advance starting search position for next caller */ + next =3D NBL_NEXT_ID(search_loc, + chan_info->num_txq_entries - 1); + WRITE_ONCE(chan_info->wait_head_index, next); + return 0; + } + + search_loc =3D NBL_NEXT_ID(search_loc, + chan_info->num_txq_entries - 1); + } + + /* + * All tx slots are occupied. May happen under high transmit load + * or delayed remote ACK responses. Caller should retry later. + */ + return -EAGAIN; +} + +static int nbl_chan_send_msg(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_send_info *chan_send) +{ + struct nbl_common_info *common =3D chan_mgt->common; + struct nbl_chan_waitqueue_head *wait_head =3D NULL; + struct nbl_chan_tx_param tx_param =3D { 0 }; + int i =3D NBL_CHAN_TX_WAIT_ACK_TIMES; + struct nbl_chan_info *chan_info =3D + chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX]; + struct device *dev =3D common->dev; + struct work_struct *task; + u16 msgid =3D 0; + int ret; + + if (chan_send->resp_len > NBL_CHAN_BUF_LEN) { + dev_err_ratelimited(dev, "resp_len %zu exceeds max %d\n", + chan_send->resp_len, NBL_CHAN_BUF_LEN); + return -EINVAL; + } + + mutex_lock(&chan_info->state_lock); + if (READ_ONCE(chan_info->shutdn)) { + mutex_unlock(&chan_info->state_lock); + return -ESHUTDOWN; + } + atomic_inc(&chan_info->inflight_tx_cnt); + mutex_unlock(&chan_info->state_lock); + + tx_param.msg_type =3D chan_send->msg_type; + tx_param.arg =3D chan_send->arg; + tx_param.arg_len =3D chan_send->arg_len; + tx_param.dstid =3D chan_send->dstid; + tx_param.msgid =3D msgid; + if (chan_send->ack) { + mutex_lock(&chan_info->pending_lock); + + ret =3D nbl_chan_get_msg_id(chan_info, &msgid); + if (ret) { + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, + "Channel tx wait head full, send msgtype:%u to dstid:%u failed\n", + chan_send->msg_type, + chan_send->dstid); + goto out_clean_inflight; + } + wait_head =3D + &chan_info->wait[FIELD_GET(NBL_CHAN_MSGID_LOC_MASK, + msgid)]; + WRITE_ONCE(wait_head->acked, 0); + WRITE_ONCE(wait_head->ack_data, chan_send->resp); + WRITE_ONCE(wait_head->ack_data_len, chan_send->resp_len); + WRITE_ONCE(wait_head->msg_type, chan_send->msg_type); + WRITE_ONCE(wait_head->msg_index, + FIELD_GET(NBL_CHAN_MSGID_INDEX_MASK, msgid)); + WRITE_ONCE(wait_head->dstid, chan_send->dstid); + + WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_WAITING); + mutex_unlock(&chan_info->pending_lock); + + tx_param.msgid =3D msgid; + } + + mutex_lock(&chan_info->txq_lock); + ret =3D nbl_chan_update_txqueue(chan_mgt, chan_info, &tx_param); + if (ret) { + mutex_unlock(&chan_info->txq_lock); + dev_err_ratelimited(dev, + "Channel tx queue full, send msgtype:%u to dstid:%u failed\n", + chan_send->msg_type, chan_send->dstid); + if (wait_head) + goto out_clear_wait_slot; + goto out_clean_inflight; + } + + ret =3D nbl_chan_kick_tx_ring(chan_mgt, chan_info); + mutex_unlock(&chan_info->txq_lock); + if (ret) { + if (wait_head) + goto out_clear_wait_slot; + goto out_clean_inflight; + } + + if (!chan_send->ack) { + ret =3D 0; + goto out_clean_inflight; + } + + if (test_bit(NBL_CHAN_IRQ_RDY, chan_info->state)) { + while (!READ_ONCE(wait_head->acked)) { + /* + * avoids long task blocking when interrupt mode is + * disabled mid-wait. Cannot guarantee subsequent ACK + * delivery after interrupt mask off, only prevents + * infinite blocking. Spurious timeout is possible. + */ + ret =3D wait_event_timeout(wait_head->wait_queue, + READ_ONCE(wait_head->acked) || + READ_ONCE(chan_info->shutdn) || + !test_bit(NBL_CHAN_IRQ_RDY, + chan_info->state), + NBL_CHAN_ACK_WAIT_TIME); + + if (READ_ONCE(chan_info->shutdn)) { + ret =3D -ESHUTDOWN; + goto out_clear_wait_slot; + } + if (!test_bit(NBL_CHAN_IRQ_RDY, chan_info->state)) { + ret =3D -EIO; + goto out_clear_wait_slot; + } + if (ret =3D=3D 0) { + mutex_lock(&chan_info->pending_lock); + if (READ_ONCE(wait_head->status) =3D=3D + NBL_MBX_STATUS_WAITING) { + WRITE_ONCE(wait_head->status, + NBL_MBX_STATUS_TIMEOUT); + WRITE_ONCE(wait_head->acked, 0); + WRITE_ONCE(wait_head->ack_data, NULL); + WRITE_ONCE(wait_head->ack_data_len, 0); + /* + * Ensure all status/ack slot + * updates are visible before subsequent + * readers observe acked =3D=3D 0 + */ + smp_wmb(); + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, + "Channel waiting ack failed, message type: %d, msg id: %u\n", + chan_send->msg_type, + msgid); + ret =3D -ETIMEDOUT; + /* + * TIMEOUT slots can be reused by + * another sender. A late ACK is + * rejected by the receive path, + * which only accepts WAITING slots + * (and by the msg_index generation + * check after reallocation). The + * current sender no longer owns the + * slot, so skip the IDLE reset. + */ + goto out_clean_inflight; + } + /* + * ACK won the timeout race: between the + * final condition check and this lock the + * receiver published acked=3D1/ACKD and + * copied the payload into our response + * buffer. The slot is still exclusively + * ours (only IDLE/TIMEOUT slots are ever + * reallocated), so consume the completion: + * unlock and continue to the loop-bottom + * acked check, which enters the normal + * success readout and IDLE reset. Leaving + * here via the timeout path would strand + * the slot in ACKD forever, permanently + * shrinking the 256-entry pool, and would + * report a false -ETIMEDOUT for a request + * whose response already arrived. + */ + mutex_unlock(&chan_info->pending_lock); + } + + if (READ_ONCE(wait_head->acked)) + break; + } + if (READ_ONCE(wait_head->acked)) { + /* + * Load ordering: observe acked flag before + * reading ACK payload metadata. + */ + smp_rmb(); + chan_send->ack_len =3D READ_ONCE(wait_head->ack_data_len); + ret =3D READ_ONCE(wait_head->ack_err); + } + } else { + /* Polling path for synchronous ACK */ + while (i--) { + if (READ_ONCE(chan_info->shutdn)) { + ret =3D -ESHUTDOWN; + goto out_clear_wait_slot; + } + + mutex_lock(&chan_info->state_lock); + task =3D READ_ONCE(chan_info->clean_task); + if (common->wq && task && + !READ_ONCE(chan_info->shutdn) && + !work_pending(task)) + queue_work(common->wq, task); + mutex_unlock(&chan_info->state_lock); + if (READ_ONCE(wait_head->acked)) { + /* + * Guarantee load order: observe acked + * flag before reading ack payload metadata. + */ + smp_rmb(); + chan_send->ack_len =3D + READ_ONCE(wait_head->ack_data_len); + ret =3D READ_ONCE(wait_head->ack_err); + goto out_clear_wait_slot; + } + + usleep_range(NBL_CHAN_TX_WAIT_ACK_US_MIN, + NBL_CHAN_TX_WAIT_ACK_US_MAX); + cond_resched(); + } + mutex_lock(&chan_info->pending_lock); + if (READ_ONCE(wait_head->status) =3D=3D NBL_MBX_STATUS_ACKD) { + chan_send->ack_len =3D READ_ONCE(wait_head->ack_data_len); + ret =3D READ_ONCE(wait_head->ack_err); + mutex_unlock(&chan_info->pending_lock); + goto out_clear_wait_slot; + } + /* + * Polling timed out without receiving an ACK. Transition + * the slot to TIMEOUT so nbl_chan_get_msg_id() can reuse + * it. Must not proceed to out_clean_inflight with + * the slot still in WAITING state =E2=80=94 that would leak the + * slot permanently and eventually exhaust the 256-entry + * channel queue. + */ + if (READ_ONCE(wait_head->status) =3D=3D NBL_MBX_STATUS_WAITING) { + WRITE_ONCE(wait_head->status, NBL_MBX_STATUS_TIMEOUT); + WRITE_ONCE(wait_head->acked, 0); + WRITE_ONCE(wait_head->ack_data, NULL); + WRITE_ONCE(wait_head->ack_data_len, 0); + } + mutex_unlock(&chan_info->pending_lock); + dev_err_ratelimited(dev, + "Channel polling ack failed, message type: %d msg id: %u\n", + chan_send->msg_type, msgid); + ret =3D -ETIMEDOUT; + goto out_clean_inflight; + } + +out_clear_wait_slot: + mutex_lock(&chan_info->pending_lock); + nbl_chan_reset_wait_head(chan_info, wait_head); + mutex_unlock(&chan_info->pending_lock); + +out_clean_inflight: + mutex_lock(&chan_info->state_lock); + if (atomic_dec_and_test(&chan_info->inflight_tx_cnt)) + wake_up(&chan_info->inflight_wait); + mutex_unlock(&chan_info->state_lock); + return ret; +} + +static int nbl_chan_send_ack(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_ack_info *chan_ack) +{ + size_t head_len =3D NBL_CHAN_ACK_HEAD_LEN * sizeof(u32); + size_t data_len =3D chan_ack->data_len; + struct nbl_chan_send_info chan_send; + __le32 *tmp; + size_t len; + int ret; + + if (data_len > + NBL_CHAN_BUF_LEN - sizeof(struct nbl_chan_tx_desc) - head_len) + return -EINVAL; + + len =3D head_len + data_len; + tmp =3D kzalloc(len, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + *(__le16 *)&tmp[NBL_CHAN_MSG_TYPE_POS] =3D + cpu_to_le16(chan_ack->msg_type); + *(__le16 *)&tmp[NBL_CHAN_MSG_ID_POS] =3D cpu_to_le16(chan_ack->msgid); + tmp[NBL_CHAN_ACK_RET_POS] =3D cpu_to_le32(chan_ack->err); + if (chan_ack->data && chan_ack->data_len) + memcpy(&tmp[NBL_CHAN_ACK_HEAD_LEN], chan_ack->data, + chan_ack->data_len); + + nbl_chan_fill_send_info(&chan_send, chan_ack->dstid, NBL_CHAN_MSG_ACK, + tmp, len, NULL, 0, 0); + ret =3D nbl_chan_send_msg(chan_mgt, &chan_send); + kfree(tmp); + + return ret; +} + +static int nbl_chan_register_msg(struct nbl_channel_mgt *chan_mgt, u16 msg= _type, + nbl_chan_resp func, void *callback) +{ + return nbl_chan_add_msg_handler(chan_mgt, msg_type, func, callback); +} + +static bool nbl_chan_check_queue_exist(struct nbl_channel_mgt *chan_mgt, + u8 chan_type) +{ + struct nbl_chan_info *chan_info =3D chan_mgt->chan_info[chan_type]; + + return chan_info ? true : false; +} + +static void nbl_chan_register_chan_task(struct nbl_channel_mgt *chan_mgt, + u8 chan_type, struct work_struct *task) +{ + struct nbl_chan_info *chan_info =3D chan_mgt->chan_info[chan_type]; + + mutex_lock(&chan_info->state_lock); + if (!READ_ONCE(chan_info->shutdn)) + WRITE_ONCE(chan_info->clean_task, task); + mutex_unlock(&chan_info->state_lock); +} + +static void nbl_chan_set_queue_state(struct nbl_channel_mgt *chan_mgt, + enum nbl_chan_state state, u8 chan_type, + u8 set) +{ + struct nbl_chan_info *chan_info =3D chan_mgt->chan_info[chan_type]; + int i; + + if (set) + set_bit(state, chan_info->state); + else + clear_bit(state, chan_info->state); + /* + * When clearing IRQ_RDY, wake all per-slot wait queues so + * sleeping senders observe the condition immediately and + * return -EIO instead of waiting out the 3s timeout and + * reporting -ETIMEDOUT. + */ + if (!set && state =3D=3D NBL_CHAN_IRQ_RDY) { + for (i =3D 0; i < chan_info->num_txq_entries; i++) + wake_up_all(&chan_info->wait[i].wait_queue); + } +} + +static struct nbl_channel_ops chan_ops =3D { + .send_msg =3D nbl_chan_send_msg, + .send_ack =3D nbl_chan_send_ack, + .register_msg =3D nbl_chan_register_msg, + .cfg_chan_qinfo_map_table =3D nbl_chan_cfg_qinfo_map_table, + .check_queue_exist =3D nbl_chan_check_queue_exist, + .setup_queue =3D nbl_chan_setup_queue, + .teardown_queue =3D nbl_chan_teardown_queue, + .clean_queue_subtask =3D nbl_chan_clean_queue_subtask, + .register_chan_task =3D nbl_chan_register_chan_task, + .set_queue_state =3D nbl_chan_set_queue_state, +}; + +static struct nbl_channel_mgt * +nbl_chan_setup_chan_mgt(struct nbl_adapter *adapter) +{ + struct nbl_hw_ops_tbl *hw_ops_tbl =3D adapter->intf.hw_ops_tbl; + struct nbl_common_info *common =3D &adapter->common; + struct device *dev =3D &adapter->pdev->dev; + struct nbl_channel_mgt *chan_mgt; + struct nbl_chan_info *mailbox; + int ret; + + chan_mgt =3D devm_kzalloc(dev, sizeof(*chan_mgt), GFP_KERNEL); + if (!chan_mgt) + return ERR_PTR(-ENOMEM); + + chan_mgt->common =3D common; + chan_mgt->hw_ops_tbl =3D hw_ops_tbl; + + mailbox =3D devm_kzalloc(dev, sizeof(*mailbox), GFP_KERNEL); + if (!mailbox) + return ERR_PTR(-ENOMEM); + mailbox->chan_type =3D NBL_CHAN_TYPE_MAILBOX; + chan_mgt->chan_info[NBL_CHAN_TYPE_MAILBOX] =3D mailbox; + + ret =3D nbl_chan_init_msg_handler(chan_mgt); + if (ret) + return ERR_PTR(ret); + ret =3D devm_mutex_init(common->dev, &mailbox->txq_lock); + if (ret) + return ERR_PTR(ret); + ret =3D devm_mutex_init(common->dev, &mailbox->state_lock); + if (ret) + return ERR_PTR(ret); + ret =3D devm_mutex_init(common->dev, &mailbox->pending_lock); + if (ret) + return ERR_PTR(ret); + return chan_mgt; +} + +static struct nbl_channel_ops_tbl * +nbl_chan_setup_ops(struct device *dev, struct nbl_channel_mgt *chan_mgt) +{ + struct nbl_channel_ops_tbl *chan_ops_tbl; + int ret; + + chan_ops_tbl =3D devm_kzalloc(dev, sizeof(*chan_ops_tbl), GFP_KERNEL); + if (!chan_ops_tbl) + return ERR_PTR(-ENOMEM); + if (!chan_ops.send_msg || !chan_ops.send_ack || + !chan_ops.register_msg || !chan_ops.cfg_chan_qinfo_map_table || + !chan_ops.check_queue_exist || !chan_ops.setup_queue || + !chan_ops.teardown_queue || !chan_ops.clean_queue_subtask || + !chan_ops.register_chan_task || !chan_ops.set_queue_state) + return ERR_PTR(-EINVAL); + + chan_ops_tbl->ops =3D &chan_ops; + chan_ops_tbl->priv =3D chan_mgt; + + ret =3D nbl_chan_register_msg(chan_mgt, NBL_CHAN_MSG_ACK, + nbl_chan_recv_ack_msg, chan_mgt); + if (ret) + return ERR_PTR(ret); + + return chan_ops_tbl; +} + +int nbl_chan_init_common(struct nbl_adapter *adap) +{ + struct nbl_channel_ops_tbl *chan_ops_tbl; + struct device *dev =3D &adap->pdev->dev; + struct nbl_channel_mgt *chan_mgt; + int ret; + + chan_mgt =3D nbl_chan_setup_chan_mgt(adap); + if (IS_ERR(chan_mgt)) { + ret =3D PTR_ERR(chan_mgt); + goto exit; + } + + chan_ops_tbl =3D nbl_chan_setup_ops(dev, chan_mgt); + if (IS_ERR(chan_ops_tbl)) { + ret =3D PTR_ERR(chan_ops_tbl); + goto cleanup_mgt; + } + + adap->intf.channel_ops_tbl =3D chan_ops_tbl; + adap->core.chan_mgt =3D chan_mgt; + ret =3D nbl_common_create_wq(&adap->common); + if (ret) + goto cleanup_mgt; + return 0; + +cleanup_mgt: + nbl_chan_remove_msg_handler(chan_mgt); +exit: + return ret; +} + +void nbl_chan_remove_common(struct nbl_adapter *adap) +{ + struct nbl_channel_mgt *chan_mgt =3D adap->core.chan_mgt; + + if (!chan_mgt) + return; + nbl_common_destroy_wq(&adap->common); + /* + * All channel queues shall be torn down earlier in remove path + * to drain inflight tx workers and stop hardware before destroying + * message handler xarray. + */ + nbl_chan_remove_msg_handler(chan_mgt); + adap->core.chan_mgt =3D NULL; +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h new file mode 100644 index 000000000000..c03efdae878e --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_channel/nbl_channel.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_CHANNEL_H_ +#define _NBL_CHANNEL_H_ + +#include +#include + +#include "../nbl_include/nbl_include.h" +#include "../nbl_include/nbl_def_channel.h" +#include "../nbl_include/nbl_def_hw.h" +#include "../nbl_include/nbl_def_common.h" +#include "../nbl_core.h" + +#define NBL_CHAN_TX_RING_TO_DESC(tx_ring, i) \ + (&((((tx_ring)->desc.tx_desc))[i])) +#define NBL_CHAN_RX_RING_TO_DESC(rx_ring, i) \ + (&((((rx_ring)->desc.rx_desc))[i])) +#define NBL_CHAN_TX_RING_TO_BUF(tx_ring, i) (&(((tx_ring)->buf)[i])) +#define NBL_CHAN_RX_RING_TO_BUF(rx_ring, i) (&(((rx_ring)->buf)[i])) + +#define NBL_CHAN_TX_WAIT_US 100 +#define NBL_CHAN_TX_WAIT_US_MAX 120 +#define NBL_CHAN_TX_WAIT_TIMES 100 +#define NBL_CHAN_TX_WAIT_ACK_US_MIN 1000 +#define NBL_CHAN_TX_WAIT_ACK_US_MAX 1200 +#define NBL_CHAN_TX_WAIT_ACK_TIMES 5000 +#define NBL_CHAN_QUEUE_LEN 256 +#define NBL_CHAN_BUF_LEN 4096 +#define NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN 16 + +#define NBL_CHAN_TX_DESC_AVAIL 0 +#define NBL_CHAN_TX_DESC_USED 1 +#define NBL_CHAN_RX_DESC_WRITE 1 +#define NBL_CHAN_RX_DESC_AVAIL 3 +#define NBL_CHAN_RX_DESC_USED 4 + +#define NBL_CHAN_ACK_HEAD_LEN 3 +#define NBL_CHAN_ACK_RET_POS 2 +#define NBL_CHAN_MSG_ID_POS 1 +#define NBL_CHAN_MSG_TYPE_POS 0 + +#define NBL_CHAN_ACK_WAIT_TIME (3 * HZ) +#define NBL_CHAN_RX_CLEAN_BUDGET 64 + +enum { + NBL_MB_RX_QID =3D 0, + NBL_MB_TX_QID =3D 1, +}; + +enum { + NBL_MBX_STATUS_IDLE =3D 0, + NBL_MBX_STATUS_WAITING, + NBL_MBX_STATUS_ACKD, + NBL_MBX_STATUS_TIMEOUT, +}; + +struct nbl_chan_tx_param { + enum nbl_chan_msg_type msg_type; + void *arg; + size_t arg_len; + u16 dstid; + u16 msgid; +}; + +struct nbl_chan_buf { + void *va; + dma_addr_t pa; + size_t size; +}; + +struct nbl_chan_tx_desc { + __le16 flags; + __le16 srcid; + __le16 dstid; + __le16 data_len; + __le16 buf_len; + __le64 buf_addr; + __le16 msg_type; + u8 data[NBL_CHAN_TX_DESC_EMBEDDED_DATA_LEN]; + __le16 msgid; + u8 rsv[26]; +} __packed; + +struct nbl_chan_rx_desc { + __le16 flags; + __le32 buf_len; + __le16 buf_id; + __le64 buf_addr; +} __packed; + +union nbl_chan_desc_ptr { + struct nbl_chan_tx_desc *tx_desc; + struct nbl_chan_rx_desc *rx_desc; +}; + +struct nbl_chan_ring { + union nbl_chan_desc_ptr desc; + struct nbl_chan_buf *buf; + u16 next_to_use; + u16 tail_ptr; /* hardware does modulo ring size internally */ + u16 next_to_clean; + dma_addr_t dma; +}; + +#define NBL_CHAN_MSG_INDEX_MAX 63 + +#define NBL_CHAN_MSGID_INDEX_MASK GENMASK(5, 0) +#define NBL_CHAN_MSGID_LOC_MASK GENMASK(13, 6) + +static inline void nbl_chan_update_tail_ptr(struct nbl_hw_ops *hw_ops, + void *hw_priv, u32 tail_ptr, u8 qid) +{ + hw_ops->update_mailbox_queue_tail_ptr(hw_priv, tail_ptr, qid); +} + +struct nbl_chan_waitqueue_head { + struct wait_queue_head wait_queue; + char *ack_data; + int acked; + s32 ack_err; + u16 ack_data_len; + u16 msg_type; + int status; + u8 msg_index; + u16 dstid; +}; + +struct nbl_chan_info { + wait_queue_head_t inflight_wait; + struct nbl_chan_ring txq; + struct nbl_chan_ring rxq; + struct nbl_chan_waitqueue_head *wait; + /* + *Protects access to the TX queue (txq) and related metadata. + *This mutex ensures exclusive access when updating the TX queue + */ + struct mutex txq_lock; + /* Guards channel state bitmap, active and shutdn flags */ + struct mutex state_lock; + /* Guards pending requests and pending work list operations */ + struct mutex pending_lock; + struct work_struct *clean_task; + u16 wait_head_index; + u16 num_txq_entries; + u16 num_rxq_entries; + u16 txq_buf_size; + u16 rxq_buf_size; + DECLARE_BITMAP(state, NBL_CHAN_STATE_NBITS); + u8 chan_type; + atomic_t inflight_tx_cnt; + bool shutdn; + bool active; + /* + * One-shot lifecycle flag: DMA ring/buffer allocations use devm/ + * dmam and are only released at device detach. Teardown only + * stops the hardware and clears active; it does not free memory. + * setup must never run twice on the same chan_info or the first + * allocation set would be orphaned. + */ + bool dma_allocated; +}; + +struct nbl_chan_msg_node_data { + nbl_chan_resp func; + void *priv; +}; + +struct nbl_channel_mgt { + struct nbl_common_info *common; + struct nbl_hw_ops_tbl *hw_ops_tbl; + struct nbl_chan_info *chan_info[NBL_CHAN_TYPE_MAX]; + /* Serializes handler_xa registration and teardown */ + struct mutex handler_lock; + struct xarray handler_xa; +}; + +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c new file mode 100644 index 000000000000..ce0ed2869af4 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#include +#include "nbl_common.h" + +void nbl_common_destroy_wq(struct nbl_common_info *common) +{ + if (!common || !common->wq) + return; + + destroy_workqueue(common->wq); + common->wq =3D NULL; +} + +int nbl_common_create_wq(struct nbl_common_info *common) +{ + char wq_name[32]; + + snprintf(wq_name, sizeof(wq_name), "nbl_wq_%s", pci_name(common->pdev)); + common->wq =3D alloc_workqueue(wq_name, WQ_UNBOUND, 0); + if (!common->wq) { + dev_err(common->dev, "Failed to alloc workqueue %s\n", wq_name); + return -ENOMEM; + } + + return 0; +} + diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h new file mode 100644 index 000000000000..129073b08eb9 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_COMMON_H_ +#define _NBL_COMMON_H_ + +#include + +#include "../nbl_include/nbl_include.h" +#include "../nbl_include/nbl_def_common.h" + +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_core.h index 1cd6587a8fcb..f998a2b44e5c 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h @@ -14,13 +14,20 @@ enum { NBL_CAP_HAS_NET_BIT, }; =20 +struct nbl_interface { + struct nbl_hw_ops_tbl *hw_ops_tbl; + struct nbl_channel_ops_tbl *channel_ops_tbl; +}; + struct nbl_core { struct nbl_hw_mgt *hw_mgt; + struct nbl_channel_mgt *chan_mgt; }; =20 struct nbl_adapter { struct pci_dev *pdev; struct nbl_core core; + struct nbl_interface intf; struct nbl_common_info common; }; =20 diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.c index cf40ddc45192..ae9965bbc425 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c @@ -6,8 +6,229 @@ #include #include #include +#include +#include #include "nbl_hw_leonis.h" =20 +static void nbl_hw_read_mbx_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *= data, + u32 len) +{ + u32 i; + + if (len % 4) + return; + if (reg >=3D (u64)hw_mgt->mailbox_bar_size || + reg + len > (u64)hw_mgt->mailbox_bar_size) { + dev_err_once(hw_mgt->common->dev, + "mbx read out of range: reg=3D0x%llx len=3D%u bar_size=3D%pa\n", + reg, len, &hw_mgt->mailbox_bar_size); + return; + } + for (i =3D 0; i < len / 4; i++) + data[i] =3D nbl_mbx_rd32(hw_mgt, reg + i * sizeof(u32)); +} + +static void nbl_hw_write_mbx_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, + const u32 *data, u32 len) +{ + u32 i; + + if (len % 4) + return; + if (reg >=3D (u64)hw_mgt->mailbox_bar_size || + reg + len > (u64)hw_mgt->mailbox_bar_size) { + dev_err_once(hw_mgt->common->dev, + "mbx write out of range: reg=3D0x%llx len=3D%u bar_size=3D%pa\n", + reg, len, &hw_mgt->mailbox_bar_size); + return; + } + for (i =3D 0; i < len / 4; i++) + nbl_mbx_wr32(hw_mgt, reg + i * sizeof(u32), data[i]); +} + +/* + * Flush posted mailbox-BAR writes by reading back through the same + * BAR. A read to the same PCI function completes only after prior + * posted writes targeting it have been accepted, so this write-then- + * read-back pair gives the same guarantee as nbl_flush_writes(). + * + * This must be used instead of nbl_flush_writes() in any path that can + * run on a non-management PF: the mailbox BAR is fully mapped on every + * function, while the MEMORY BAR dummy register used by + * nbl_flush_writes() lies inside the 64 MiB control-PF-only aperture. + */ +static void nbl_hw_flush_mbx_write(struct nbl_hw_mgt *hw_mgt, u64 reg) +{ + u32 data; + + nbl_hw_read_mbx_regs(hw_mgt, reg, &data, sizeof(data)); +} + +static void nbl_hw_rd_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *data, + u32 len) +{ + u32 size =3D len / 4; + u32 i; + + if (len % 4) + return; + for (i =3D 0; i < size; i++) + data[i] =3D rd32(hw_mgt->hw_addr, reg + i * sizeof(u32)); +} + +static void nbl_hw_wr_regs(struct nbl_hw_mgt *hw_mgt, u64 reg, const u32 *= data, + u32 len) +{ + u32 size =3D len / 4; + u32 i; + + if (len % 4) + return; + for (i =3D 0; i < size; i++) + wr32(hw_mgt->hw_addr, reg + i * sizeof(u32), data[i]); +} + +static void nbl_hw_rd_regs_lock(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 *d= ata, + u32 len) +{ + u32 size =3D len / 4; + u32 i; + + if (len % 4) + return; + + spin_lock(&hw_mgt->reg_lock); + + for (i =3D 0; i < size; i++) + data[i] =3D rd32(hw_mgt->hw_addr, reg + i * sizeof(u32)); + spin_unlock(&hw_mgt->reg_lock); +} + +static void nbl_hw_update_mailbox_queue_tail_ptr(struct nbl_hw_mgt *hw_mgt, + u16 tail_ptr, u8 txrx) +{ + /* local_qid 0 and 1 denote rx and tx queue respectively */ + u32 local_qid =3D txrx; + u32 value =3D ((u32)tail_ptr << 16) | local_qid; + + /* wmb for doorbell */ + wmb(); + nbl_mbx_wr32(hw_mgt, NBL_MAILBOX_NOTIFY_ADDR, value); +} + +static void nbl_hw_config_mailbox_rxq(struct nbl_hw_mgt *hw_mgt, + dma_addr_t dma_addr, int size_bwid) +{ + struct nbl_mailbox_qinfo_cfg_table cfg_tbl; + + memset(&cfg_tbl, 0, sizeof(cfg_tbl)); + cfg_tbl.data[3] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1); + nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR, + cfg_tbl.data, sizeof(cfg_tbl)); + + cfg_tbl.data[0] =3D lower_32_bits(dma_addr); + cfg_tbl.data[1] =3D upper_32_bits(dma_addr); + cfg_tbl.data[2] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK, + size_bwid); + cfg_tbl.data[3] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 0) | + FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK, 1); + nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR, + cfg_tbl.data, sizeof(cfg_tbl)); +} + +static void nbl_hw_config_mailbox_txq(struct nbl_hw_mgt *hw_mgt, + dma_addr_t dma_addr, int size_bwid) +{ + struct nbl_mailbox_qinfo_cfg_table cfg_tbl; + + memset(&cfg_tbl, 0, sizeof(cfg_tbl)); + cfg_tbl.data[3] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1); + nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR, + cfg_tbl.data, sizeof(cfg_tbl)); + + cfg_tbl.data[0] =3D lower_32_bits(dma_addr); + cfg_tbl.data[1] =3D upper_32_bits(dma_addr); + cfg_tbl.data[2] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK, + size_bwid); + cfg_tbl.data[3] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 0) | + FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK, 1); + nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR, + cfg_tbl.data, sizeof(cfg_tbl)); +} + +static void nbl_hw_stop_mailbox_rxq(struct nbl_hw_mgt *hw_mgt) +{ + struct nbl_mailbox_qinfo_cfg_table cfg_tbl; + + memset(&cfg_tbl, 0, sizeof(cfg_tbl)); + cfg_tbl.data[3] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1); + nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR, + cfg_tbl.data, sizeof(cfg_tbl)); + /* Ensure QUEUE_RST has reached the device before caller proceeds */ + nbl_hw_flush_mbx_write(hw_mgt, + NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR); +} + +static void nbl_hw_stop_mailbox_txq(struct nbl_hw_mgt *hw_mgt) +{ + struct nbl_mailbox_qinfo_cfg_table cfg_tbl; + + memset(&cfg_tbl, 0, sizeof(cfg_tbl)); + cfg_tbl.data[3] =3D FIELD_PREP(NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK, 1); + nbl_hw_write_mbx_regs(hw_mgt, NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR, + cfg_tbl.data, sizeof(cfg_tbl)); + /* Ensure QUEUE_RST has reached the device before caller proceeds */ + nbl_hw_flush_mbx_write(hw_mgt, + NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR); +} + +static void nbl_hw_get_host_pf_mask(struct nbl_hw_mgt *hw_mgt, u32 *pf_mas= k) +{ + nbl_hw_rd_regs_lock(hw_mgt, NBL_PCIE_HOST_K_PF_MASK_REG, pf_mask, + sizeof(*pf_mask)); +} + +static void nbl_hw_cfg_mailbox_qinfo(struct nbl_hw_mgt *hw_mgt, u16 func_i= d, + u8 bus, u8 devid, u8 function) +{ + u32 data =3D 0; + + /* + * Clear MSIX_IDX/MSIX_IDX_VALID together with the BDF fields: + * these registers survive kexec or a forced unload without FLR, + * so a VALID bit left over from a previous instance would keep + * mailbox interrupts routed to a global vector index that this + * instance may hand to a different function via cfg_msix_map(). + * Routing is re-armed per PF by set_mailbox_irq() during each + * PF's own init (and disarmed again by intr_mgt_stop teardown). + */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), + &data, sizeof(data)); + data &=3D ~(NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK | + NBL_MAILBOX_QINFO_MAP_DEVID_MASK | + NBL_MAILBOX_QINFO_MAP_BUS_MASK | + NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK | + NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK); + data |=3D FIELD_PREP(NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK, function) | + FIELD_PREP(NBL_MAILBOX_QINFO_MAP_DEVID_MASK, devid) | + FIELD_PREP(NBL_MAILBOX_QINFO_MAP_BUS_MASK, bus); + nbl_hw_wr_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), + &data, sizeof(data)); + spin_unlock(&hw_mgt->reg_lock); +} + +static struct nbl_hw_ops hw_ops =3D { + .update_mailbox_queue_tail_ptr =3D nbl_hw_update_mailbox_queue_tail_ptr, + .config_mailbox_rxq =3D nbl_hw_config_mailbox_rxq, + .config_mailbox_txq =3D nbl_hw_config_mailbox_txq, + .stop_mailbox_rxq =3D nbl_hw_stop_mailbox_rxq, + .stop_mailbox_txq =3D nbl_hw_stop_mailbox_txq, + .get_host_pf_mask =3D nbl_hw_get_host_pf_mask, + .cfg_mailbox_qinfo =3D nbl_hw_cfg_mailbox_qinfo, + +}; + /* Structure starts here, adding an op should not modify anything below */ static struct nbl_hw_mgt *nbl_hw_setup_hw_mgt(struct nbl_common_info *comm= on) { @@ -23,6 +244,27 @@ static struct nbl_hw_mgt *nbl_hw_setup_hw_mgt(struct nb= l_common_info *common) return hw_mgt; } =20 +static struct nbl_hw_ops_tbl *nbl_hw_setup_ops(struct nbl_common_info *com= mon, + struct nbl_hw_mgt *hw_mgt) +{ + struct nbl_hw_ops_tbl *hw_ops_tbl; + struct device *dev; + + dev =3D common->dev; + hw_ops_tbl =3D devm_kzalloc(dev, sizeof(*hw_ops_tbl), GFP_KERNEL); + if (!hw_ops_tbl) + return ERR_PTR(-ENOMEM); + if (!hw_ops.update_mailbox_queue_tail_ptr || + !hw_ops.config_mailbox_rxq || !hw_ops.config_mailbox_txq || + !hw_ops.stop_mailbox_rxq || !hw_ops.stop_mailbox_txq || + !hw_ops.get_host_pf_mask || !hw_ops.cfg_mailbox_qinfo) + return ERR_PTR(-EINVAL); + hw_ops_tbl->ops =3D &hw_ops; + hw_ops_tbl->priv =3D hw_mgt; + + return hw_ops_tbl; +} + static int nbl_pcim_request_selected_bars(struct pci_dev *pdev, u32 mask, const char *name) { @@ -43,6 +285,7 @@ int nbl_hw_init_leonis(struct nbl_adapter *adapter) { resource_size_t expect_sz =3D NBL_MEM_BAR_TOTAL_SIZE; struct nbl_common_info *common =3D &adapter->common; + struct nbl_hw_ops_tbl *hw_ops_tbl =3D NULL; struct pci_dev *pdev =3D common->pdev; struct nbl_hw_mgt *hw_mgt =3D NULL; resource_size_t bar_len; @@ -136,7 +379,14 @@ int nbl_hw_init_leonis(struct nbl_adapter *adapter) } =20 hw_mgt->mailbox_bar_size =3D bar_len; + spin_lock_init(&hw_mgt->reg_lock); =20 + hw_ops_tbl =3D nbl_hw_setup_ops(common, hw_mgt); + if (IS_ERR(hw_ops_tbl)) { + ret =3D PTR_ERR(hw_ops_tbl); + goto setup_mgt_fail; + } + adapter->intf.hw_ops_tbl =3D hw_ops_tbl; adapter->core.hw_mgt =3D hw_mgt; =20 return 0; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.h index 1f9e509dc631..9cb611d42e8e 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h @@ -11,5 +11,47 @@ #include "../../nbl_include/nbl_include.h" #include "../nbl_hw_reg.h" =20 +/* ---------- REG BASE ADDR ---------- */ +/* Interface modules base addr */ +#define NBL_INTF_HOST_PCOMPLETER_BASE 0x00f08000 +#define NBL_INTF_HOST_PADPT_BASE 0x00f4c000 +#define NBL_INTF_HOST_MAILBOX_BASE 0x00fb0000 +#define NBL_INTF_HOST_PCIE_BASE 0X01504000 +/* -------- MAILBOX BAR2 ----- */ +#define NBL_MAILBOX_NOTIFY_ADDR 0x00000000 +#define NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR 0x10 +#define NBL_MAILBOX_QINFO_CFG_TX_TABLE_ADDR 0x20 + +/* -------- MAILBOX -------- */ + +/* mailbox BAR qinfo_cfg_table */ +#define MAILBOX_QINFO_CFG_TABLE_DWLEN 4 +/* data[2] */ +#define NBL_MAILBOX_QINFO_CFG_QUEUE_SIZE_BWID_MASK GENMASK(3, 0) +/* data[3] */ +#define NBL_MAILBOX_QINFO_CFG_QUEUE_RST_MASK BIT(0) +#define NBL_MAILBOX_QINFO_CFG_QUEUE_EN_MASK BIT(1) +#define NBL_MAILBOX_QINFO_CFG_DIF_ERR_MASK BIT(2) +#define NBL_MAILBOX_QINFO_CFG_PTR_ERR_MASK BIT(3) +struct nbl_mailbox_qinfo_cfg_table { + u32 data[MAILBOX_QINFO_CFG_TABLE_DWLEN]; +}; + +/* -------- MAILBOX BAR0 ----- */ +/* mailbox qinfo_map_table */ +#define NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id) \ + (NBL_INTF_HOST_MAILBOX_BASE + 0x00001000 + (func_id) * sizeof(u32)) + +/* MAILBOX qinfo_map_table */ +#define NBL_MAILBOX_QINFO_MAP_FUNCTION_MASK GENMASK(2, 0) +#define NBL_MAILBOX_QINFO_MAP_DEVID_MASK GENMASK(7, 3) +#define NBL_MAILBOX_QINFO_MAP_BUS_MASK GENMASK(15, 8) +#define NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK GENMASK(28, 16) +#define NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK BIT(29) + +/* -------- HOST_PCIE -------- */ +#define NBL_PCIE_HOST_K_PF_MASK_REG (NBL_INTF_HOST_PCIE_BASE + 0x00001004) +#define NBL_PCIE_HOST_TL_CFG_BUSDEV (NBL_INTF_HOST_PCIE_BASE + 0x11040) + #define NBL_BAR2_MAX_LEN 0x300 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h b/d= rivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h index e281109f502e..00fa33eeabaa 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_reg.h @@ -8,6 +8,7 @@ =20 #include =20 +#include "../nbl_include/nbl_def_channel.h" #include "../nbl_include/nbl_def_hw.h" #include "../nbl_include/nbl_def_common.h" #include "../nbl_core.h" @@ -16,6 +17,7 @@ #define NBL_MAILBOX_BAR 2 #define NBL_RDMA_NOTIFY_LEN (8ULL << 10) #define NBL_REG_NET_ONLY_LEN (8ULL << 10) +#define NBL_HW_DUMMY_REG 0x1300904 /* * PCI MEMORY BAR total size: 64MiB. */ @@ -26,6 +28,37 @@ struct nbl_hw_mgt { u8 __iomem *hw_addr; u8 __iomem *mailbox_bar_hw_addr; resource_size_t mailbox_bar_size; + spinlock_t reg_lock; /* Protect reg access */ }; =20 +static inline u32 rd32(u8 __iomem *addr, u64 reg) +{ + return readl(addr + reg); +} + +static inline void wr32(u8 __iomem *addr, u64 reg, u32 value) +{ + writel(value, addr + reg); +} + +static inline void nbl_hw_wr32(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 val= ue) +{ + wr32(hw_mgt->hw_addr, reg, value); +} + +static inline u32 nbl_hw_rd32(struct nbl_hw_mgt *hw_mgt, u64 reg) +{ + return rd32(hw_mgt->hw_addr, reg); +} + +static inline void nbl_mbx_wr32(struct nbl_hw_mgt *hw_mgt, u64 reg, u32 va= lue) +{ + writel(value, hw_mgt->mailbox_bar_hw_addr + reg); +} + +static inline u32 nbl_mbx_rd32(struct nbl_hw_mgt *hw_mgt, u64 reg) +{ + return readl(hw_mgt->mailbox_bar_hw_addr + reg); +} + #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_cha= nnel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel= .h new file mode 100644 index 000000000000..d6faa0bc4026 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_DEF_CHANNEL_H_ +#define _NBL_DEF_CHANNEL_H_ + +#include + +struct nbl_channel_mgt; +struct nbl_adapter; + +typedef void (*nbl_chan_resp)(void *, u16, u16, void *, u32); + +/* + * Mailbox wire opcodes, stable wire ABI shared between driver and firmwar= e. + * Each opcode has a fixed assigned number to preserve compatibility. + * ABI compatibility rules: + * 1. New opcodes shall only be appended before NBL_CHAN_MSG_MAILBOX_MAX; + * 2. Reordering, inserting or deleting existing enumerators breaks driver- + * firmware interoperability and must be avoided; + * 3. Modifications to existing opcodes require synchronized firmware ABI + * updates. + * + * Only opcodes currently used by in-tree driver logic are defined here. + * Unimplemented feature opcodes (KTLS, IPsec, vDPA, mirror etc.) will be + * added incrementally together with their corresponding driver + * implementation patches. + */ +enum nbl_chan_msg_type { + NBL_CHAN_MSG_ACK =3D 0, + /* mailbox msg end */ + NBL_CHAN_MSG_MAILBOX_MAX, +}; + +enum nbl_chan_state { + NBL_CHAN_IRQ_RDY, + NBL_CHAN_STATE_NBITS +}; + +struct nbl_chan_send_info { + void *arg; + size_t arg_len; + void *resp; + size_t resp_len; + u16 dstid; + u16 msg_type; + u16 ack; + u16 ack_len; +}; + +struct nbl_chan_ack_info { + void *data; + int err; + u32 data_len; + u16 dstid; + u16 msg_type; + u16 msgid; +}; + +enum nbl_channel_type { + NBL_CHAN_TYPE_MAILBOX, + NBL_CHAN_TYPE_MAX +}; + +static inline void +nbl_chan_fill_send_info(struct nbl_chan_send_info *info, + u16 dst_id, u16 msg_type, + void *argument, u32 arg_length, + void *response, u32 resp_length, + bool need_ack) +{ + info->dstid =3D dst_id; + info->msg_type =3D msg_type; + info->arg =3D argument; + info->arg_len =3D arg_length; + info->resp =3D response; + info->resp_len =3D resp_length; + info->ack =3D need_ack; +} + +static inline void +nbl_chan_fill_ack_info(struct nbl_chan_ack_info *info, + u16 dst_id, u16 msg_type, u16 msg_id, + int err_code, void *ack_data, u32 data_length) +{ + info->dstid =3D dst_id; + info->msg_type =3D msg_type; + info->msgid =3D msg_id; + info->err =3D err_code; + info->data =3D ack_data; + info->data_len =3D data_length; +} + +struct nbl_channel_ops { + int (*send_msg)(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_send_info *chan_send); + int (*send_ack)(struct nbl_channel_mgt *chan_mgt, + struct nbl_chan_ack_info *chan_ack); + int (*register_msg)(struct nbl_channel_mgt *chan_mgt, u16 msg_type, + nbl_chan_resp func, void *callback_priv); + void (*cfg_chan_qinfo_map_table)(struct nbl_channel_mgt *chan_mgt, + u8 bus, u8 devid); + bool (*check_queue_exist)(struct nbl_channel_mgt *chan_mgt, + u8 chan_type); + int (*setup_queue)(struct nbl_channel_mgt *chan_mgt, u8 chan_type); + int (*teardown_queue)(struct nbl_channel_mgt *chan_mgt, u8 chan_type); + void (*clean_queue_subtask)(struct nbl_channel_mgt *chan_mgt, + u8 chan_type); + void (*register_chan_task)(struct nbl_channel_mgt *chan_mgt, + u8 chan_type, struct work_struct *task); + void (*set_queue_state)(struct nbl_channel_mgt *chan_mgt, + enum nbl_chan_state state, u8 chan_type, + u8 set); +}; + +struct nbl_channel_ops_tbl { + struct nbl_channel_ops *ops; + struct nbl_channel_mgt *priv; +}; + +int nbl_chan_init_common(struct nbl_adapter *adapter); +void nbl_chan_remove_common(struct nbl_adapter *adapter); +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_com= mon.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h index ea646d1efc2d..26a364e3a194 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h @@ -12,6 +12,7 @@ #include "nbl_include.h" =20 struct nbl_common_info { + struct workqueue_struct *wq; struct pci_dev *pdev; struct device *dev; u16 vsi_id; @@ -28,4 +29,7 @@ struct nbl_common_info { u8 has_net; }; =20 +void nbl_common_destroy_wq(struct nbl_common_info *common); +int nbl_common_create_wq(struct nbl_common_info *common); + #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.= h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h index ecbf440e4366..ce092005d767 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h @@ -10,6 +10,44 @@ =20 struct nbl_hw_mgt; struct nbl_adapter; +struct nbl_hw_ops { + void (*update_mailbox_queue_tail_ptr)(struct nbl_hw_mgt *hw_mgt, + u16 tail_ptr, u8 txrx); + void (*config_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt, + dma_addr_t dma_addr, int size_bwid); + void (*config_mailbox_txq)(struct nbl_hw_mgt *hw_mgt, + dma_addr_t dma_addr, int size_bwid); + void (*stop_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt); + void (*stop_mailbox_txq)(struct nbl_hw_mgt *hw_mgt); + /** + * get_host_pf_mask - Fetch host PF mask from firmware k_pf_mask reg + * @hw_mgt: hardware management context + * @pf_mask: output pointer for PF mask value + * + * k_pf_mask register rule: + * bit N =3D=3D 0 -> PF#N enabled; bit N =3D=3D 1 -> PF#N masked out. + * bit0 is PF0's mask bit (not reserved); PF0 can be masked but + * the driver requires at least PF0 enabled. + * Only 1/2/4 PFs are supported: + * 1 PF (PF0): mask =3D 0xfe + * 2 PFs (PF0,PF1): mask =3D 0xfc + * 4 PFs (PF0~PF3): mask =3D 0xf0 + * All-zero mask (0x00) means all 8 PFs enabled, which is + * unsupported by the driver and rejected with -EINVAL. + * + * Firmware contract: number of unmasked PFs MUST equal + * get_board_info()->eth_num. + */ + void (*get_host_pf_mask)(struct nbl_hw_mgt *hw_mgt, u32 *pf_mask); + + void (*cfg_mailbox_qinfo)(struct nbl_hw_mgt *hw_mgt, u16 func_id, + u8 bus, u8 devid, u8 function); +}; + +struct nbl_hw_ops_tbl { + struct nbl_hw_ops *ops; + struct nbl_hw_mgt *priv; +}; =20 int nbl_hw_init_leonis(struct nbl_adapter *adapter); void nbl_hw_remove_leonis(struct nbl_adapter *adapter); diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h index 14e7b19f9a4c..f2d802397d98 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h @@ -10,6 +10,9 @@ =20 /* ------ Basic definitions ------- */ #define NBL_DRIVER_NAME "nbl" +#define NBL_MAX_PF 8 +#define NBL_NEXT_ID(id, max) (((id) + 1) % ((max) + 1)) + struct nbl_func_caps { u32 has_ctrl:1; u32 has_net:1; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_main.c index f2552bc73293..b7c80ea54c8d 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c @@ -8,6 +8,7 @@ #include #include #include "nbl_include/nbl_include.h" +#include "nbl_include/nbl_def_channel.h" #include "nbl_include/nbl_def_hw.h" #include "nbl_include/nbl_def_common.h" #include "nbl_core.h" @@ -38,13 +39,19 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, if (ret) goto hw_init_fail; =20 + ret =3D nbl_chan_init_common(adapter); + if (ret) + goto chan_init_fail; return adapter; +chan_init_fail: + nbl_hw_remove_leonis(adapter); hw_init_fail: return ERR_PTR(ret); } =20 void nbl_core_remove(struct nbl_adapter *adapter) { + nbl_chan_remove_common(adapter); nbl_hw_remove_leonis(adapter); } =20 --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-197.mail.aliyun.com (out28-197.mail.aliyun.com [115.124.28.197]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 540595372D1; Tue, 22 Sep 2026 12:03:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.197 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078607; cv=none; b=FaKT0dj7fc6Cg8Fh0cGnE1DAIK5NjbrNV9Z82XZMAnHFiAHlDNRyFLFtLxrMaihR9/UWINVTDlFkk44NHQgOfLCr+/gJwpjuTnvADoo2MAKVGgvEyfjbZRD3sxg3dofGjFFm+90pdEg64BJJiJgvWfOlQYna+KsyxX3yrkX3C4w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078607; c=relaxed/simple; bh=RVSnBnKy2zyZgScZts3RhePDKQ0neFjTHHcDOEU1+a4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=o61nV4OxsYOfnYT1bUqc1J6a5rQJ+uksVmSF5KRVuYf5gXZVZMb6zTTiAr3qBSkBXRGnjCUVFUFoRqyh65p5EA+PrHDLhX7Uj9R8jRKAfAwlGj3ILVas653MqjpM9eeJ3FBBDTtdTIvvBYENlsuYoULD+FmsubQjsr61W7a77OQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.197 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_system_inform|0.0534534-0.0015194-0.945027;FP=13211288054999293586|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033032062159;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPR8_1790078595; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPR8_1790078595 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:16 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 2/8] net/nebula-matrix: add common resource implementation Date: Tue, 22 Sep 2026 20:02:59 +0800 Message-ID: <20260922120311.86593-3-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: illusion wang The Resource layer owns chip-agnostic resource entries and identity mappings, providing lookup and conversion helpers between VSI ID, func ID, eth ID and PF ID for upper dispatch and device layers. This patch adds the common (chip-independent) resource layer: - nbl_res_init_pf_num(): reads firmware k_pf_mask and validates the enabled PF topology. Only 1/2/4 contiguous PFs starting from PF0 are accepted; non-contiguous layouts, unsupported counts (0, 3, 5-8) and the all-zero mask (all 8 PFs enabled) are rejected with -EINVAL, causing probe to fail early. - nbl_common_func_id_to_rel_pf_id(): translate absolute PF ID to relative PF index for resource table lookup. - nbl_res_ctrl_dev_sriov_info_init(): derives each PF's BDF from the hardware bus number (read via get_real_bus) and the function offset, stored in sriov_info[] for MSI-X map programming. - nbl_res_ctrl_dev_setup_eth_info(): validates that firmware port count (get_board_info()->eth_num) matches max_pf and the eth bitmap reported by get_fw_eth_map(), then builds the per-PF eth_id / logic_eth_id lookup tables. - nbl_res_ctrl_dev_vsi_info_init(): assigns per-PF VSI base IDs. VSI ID gap is 1024 for 1 port, 512 for 2 ports, 256 for 4 ports respectively. - Conversion helpers nbl_res_func_id_to_vsi_id(), nbl_res_vsi_id_to_pf_id() and nbl_res_get_eth_id() with full range and has_ctrl guards. All initialization above runs only on the control PF (common->has_ctrl =3D=3D true). Resource tables are built once during control PF probe and remain read-only afterwards, so lookup helpers require no internal locking. The resource conversion helpers themselves do not include locking; all resource operations must be serialized by upper dispatch layer via ops_mutex_lock. Non-control PF requests are routed to control PF via mailbox RPC, and these helpers are only executed on control PF. All resource allocations use devm managed memory, no explicit cleanup needed in remove path. Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/Makefile | 2 + .../nebula-matrix/nbl/nbl_common/nbl_common.c | 22 ++ .../net/ethernet/nebula-matrix/nbl/nbl_core.h | 2 + .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c | 67 +++- .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h | 14 + .../nbl_hw_leonis/nbl_resource_leonis.c | 329 ++++++++++++++++++ .../nbl_hw_leonis/nbl_resource_leonis.h | 10 + .../nebula-matrix/nbl/nbl_hw/nbl_resource.c | 125 +++++++ .../nebula-matrix/nbl/nbl_hw/nbl_resource.h | 69 ++++ .../nbl/nbl_include/nbl_def_channel.h | 10 + .../nbl/nbl_include/nbl_def_common.h | 20 ++ .../nbl/nbl_include/nbl_def_hw.h | 15 + .../nbl/nbl_include/nbl_def_resource.h | 29 ++ .../nbl/nbl_include/nbl_include.h | 6 + .../net/ethernet/nebula-matrix/nbl/nbl_main.c | 9 + 15 files changed, 727 insertions(+), 2 deletions(-) create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_le= onis/nbl_resource_leonis.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_le= onis/nbl_resource_leonis.h create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resou= rce.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resou= rce.h create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_= def_resource.h diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/= ethernet/nebula-matrix/nbl/Makefile index 04e1aa1fb4bd..3dab9519a277 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/Makefile +++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile @@ -6,4 +6,6 @@ obj-$(CONFIG_NBL) :=3D nbl.o nbl-objs +=3D nbl_common/nbl_common.o \ nbl_channel/nbl_channel.o \ nbl_hw/nbl_hw_leonis/nbl_hw_leonis.o \ + nbl_hw/nbl_hw_leonis/nbl_resource_leonis.o \ + nbl_hw/nbl_resource.o \ nbl_main.o diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c index ce0ed2869af4..7c664195db7f 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c @@ -29,3 +29,25 @@ int nbl_common_create_wq(struct nbl_common_info *common) return 0; } =20 +/** + * nbl_common_func_id_to_rel_pf_id - convert absolute PF id to relative PF= id + * @common: common device info + * @pf_id: absolute PF identifier + * @rel_pf_id: output relative pf id + * + * Leonis uses fixed mgt_pf =3D 0. Support future non-zero management PF. + * + * Return: 0 on success, -EINVAL on invalid arguments. + */ +int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf= _id, + u32 *rel_pf_id) +{ + if (!rel_pf_id) + return -EINVAL; + + if (pf_id < common->mgt_pf) + return -EINVAL; + *rel_pf_id =3D pf_id - common->mgt_pf; + return 0; +} + diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_core.h index f998a2b44e5c..dd24ebec0171 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h @@ -16,11 +16,13 @@ enum { =20 struct nbl_interface { struct nbl_hw_ops_tbl *hw_ops_tbl; + struct nbl_resource_ops_tbl *resource_ops_tbl; struct nbl_channel_ops_tbl *channel_ops_tbl; }; =20 struct nbl_core { struct nbl_hw_mgt *hw_mgt; + struct nbl_resource_mgt *res_mgt; struct nbl_channel_mgt *chan_mgt; }; =20 diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.c index ae9965bbc425..82efe2b40a8c 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c @@ -104,6 +104,32 @@ static void nbl_hw_rd_regs_lock(struct nbl_hw_mgt *hw_= mgt, u64 reg, u32 *data, spin_unlock(&hw_mgt->reg_lock); } =20 +/* + * Only call this when has_ctrl=3Dtrue, which maps enough space + * (bar_len - 8192) to cover NBL_HW_DUMMY_REG (0x1300904). + * The flow/design guarantees this is only called in the + * has_ctrl path. + */ +static void nbl_flush_writes(struct nbl_hw_mgt *hw_mgt) +{ + nbl_hw_rd32(hw_mgt, NBL_HW_DUMMY_REG); +} + +/* + * Registers reset to zero after cold boot / FLR / bus reset. Firmware + * programs valid values before driver probe, so zero is only seen on + * hardware fault or register read failure. Initialize data=3D0 to guard + * against nbl_hw_read_mbx_regs() early-return on bounds-check failure. + */ +static void nbl_hw_get_fw_eth_map(struct nbl_hw_mgt *hw_mgt, u32 *eth_map) +{ + u32 data =3D 0; + + nbl_hw_read_mbx_regs(hw_mgt, NBL_FW_BOARD_DW6_OFFSET, &data, + sizeof(data)); + *eth_map =3D FIELD_GET(NBL_FW_BOARD_DW6_ETH_BITMAP_MASK, data); +} + static void nbl_hw_update_mailbox_queue_tail_ptr(struct nbl_hw_mgt *hw_mgt, u16 tail_ptr, u8 txrx) { @@ -188,6 +214,15 @@ static void nbl_hw_get_host_pf_mask(struct nbl_hw_mgt = *hw_mgt, u32 *pf_mask) sizeof(*pf_mask)); } =20 +static void nbl_hw_get_real_bus(struct nbl_hw_mgt *hw_mgt, u8 *bus) +{ + u32 data =3D 0; + + nbl_hw_rd_regs_lock(hw_mgt, NBL_PCIE_HOST_TL_CFG_BUSDEV, &data, + sizeof(data)); + *bus =3D FIELD_GET(NBL_PCIE_BUS_MASK, data); +} + static void nbl_hw_cfg_mailbox_qinfo(struct nbl_hw_mgt *hw_mgt, u16 func_i= d, u8 bus, u8 devid, u8 function) { @@ -218,15 +253,41 @@ static void nbl_hw_cfg_mailbox_qinfo(struct nbl_hw_mg= t *hw_mgt, u16 func_id, spin_unlock(&hw_mgt->reg_lock); } =20 +/* + * Registers reset to zero after cold boot / FLR / bus reset. Firmware + * programs valid values before driver probe, so zero is only seen on + * hardware fault or register read failure. Initialize data=3D0 to guard + * against nbl_hw_read_mbx_regs() early-return on bounds-check failure. + */ +static void nbl_hw_get_board_info(struct nbl_hw_mgt *hw_mgt, + struct nbl_board_port_info *board_info) +{ + u32 data =3D 0; + + nbl_hw_read_mbx_regs(hw_mgt, NBL_FW_BOARD_DW3_OFFSET, &data, + sizeof(data)); + board_info->eth_num =3D FIELD_GET(NBL_FW_BOARD_DW3_PORT_NUM_MASK, data); + board_info->eth_speed =3D + FIELD_GET(NBL_FW_BOARD_DW3_PORT_SPEED_MASK, data); + board_info->p4_version =3D + FIELD_GET(NBL_FW_BOARD_DW3_P4_VERSION_MASK, data); +} + static struct nbl_hw_ops hw_ops =3D { + .flush_write =3D nbl_flush_writes, + .update_mailbox_queue_tail_ptr =3D nbl_hw_update_mailbox_queue_tail_ptr, .config_mailbox_rxq =3D nbl_hw_config_mailbox_rxq, .config_mailbox_txq =3D nbl_hw_config_mailbox_txq, .stop_mailbox_rxq =3D nbl_hw_stop_mailbox_rxq, .stop_mailbox_txq =3D nbl_hw_stop_mailbox_txq, .get_host_pf_mask =3D nbl_hw_get_host_pf_mask, + .get_real_bus =3D nbl_hw_get_real_bus, + .cfg_mailbox_qinfo =3D nbl_hw_cfg_mailbox_qinfo, =20 + .get_fw_eth_map =3D nbl_hw_get_fw_eth_map, + .get_board_info =3D nbl_hw_get_board_info, }; =20 /* Structure starts here, adding an op should not modify anything below */ @@ -254,10 +315,12 @@ static struct nbl_hw_ops_tbl *nbl_hw_setup_ops(struct= nbl_common_info *common, hw_ops_tbl =3D devm_kzalloc(dev, sizeof(*hw_ops_tbl), GFP_KERNEL); if (!hw_ops_tbl) return ERR_PTR(-ENOMEM); - if (!hw_ops.update_mailbox_queue_tail_ptr || + if (!hw_ops.flush_write || !hw_ops.update_mailbox_queue_tail_ptr || !hw_ops.config_mailbox_rxq || !hw_ops.config_mailbox_txq || !hw_ops.stop_mailbox_rxq || !hw_ops.stop_mailbox_txq || - !hw_ops.get_host_pf_mask || !hw_ops.cfg_mailbox_qinfo) + !hw_ops.get_host_pf_mask || !hw_ops.get_real_bus || + !hw_ops.cfg_mailbox_qinfo || + !hw_ops.get_fw_eth_map || !hw_ops.get_board_info) return ERR_PTR(-EINVAL); hw_ops_tbl->ops =3D &hw_ops; hw_ops_tbl->priv =3D hw_mgt; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.h index 9cb611d42e8e..251dd68d0721 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h @@ -53,5 +53,19 @@ struct nbl_mailbox_qinfo_cfg_table { #define NBL_PCIE_HOST_K_PF_MASK_REG (NBL_INTF_HOST_PCIE_BASE + 0x00001004) #define NBL_PCIE_HOST_TL_CFG_BUSDEV (NBL_INTF_HOST_PCIE_BASE + 0x11040) =20 +#define NBL_PCIE_BUS_MASK GENMASK(12, 5) +#define NBL_FW_BOARD_CONFIG 0x200 +#define NBL_FW_BOARD_DW3_OFFSET (NBL_FW_BOARD_CONFIG + 12) +#define NBL_FW_BOARD_DW6_OFFSET (NBL_FW_BOARD_CONFIG + 24) + +#define NBL_FW_BOARD_DW3_PORT_TYPE_MASK BIT(0) +#define NBL_FW_BOARD_DW3_PORT_NUM_MASK GENMASK(7, 1) +#define NBL_FW_BOARD_DW3_PORT_SPEED_MASK GENMASK(9, 8) +#define NBL_FW_BOARD_DW3_GPIO_TYPE_MASK GENMASK(12, 10) +#define NBL_FW_BOARD_DW3_P4_VERSION_MASK GENMASK(13, 13) + +#define NBL_FW_BOARD_DW6_LANE_BITMAP_MASK GENMASK(7, 0) +#define NBL_FW_BOARD_DW6_ETH_BITMAP_MASK GENMASK(15, 8) + #define NBL_BAR2_MAX_LEN 0x300 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_resource_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_= leonis/nbl_resource_leonis.c new file mode 100644 index 000000000000..7804762a96e0 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ +#include +#include +#include +#include "nbl_resource_leonis.h" + +static struct nbl_resource_ops res_ops =3D { + .get_vsi_id =3D nbl_res_func_id_to_vsi_id, + .get_eth_id =3D nbl_res_get_eth_id, +}; + +static struct nbl_resource_mgt * +nbl_res_setup_res_mgt(struct nbl_common_info *common) +{ + struct nbl_resource_info *resource_info; + struct nbl_resource_mgt *res_mgt; + struct device *dev =3D common->dev; + + res_mgt =3D devm_kzalloc(dev, sizeof(*res_mgt), GFP_KERNEL); + if (!res_mgt) + return ERR_PTR(-ENOMEM); + res_mgt->common =3D common; + + resource_info =3D + devm_kzalloc(dev, sizeof(*resource_info), GFP_KERNEL); + if (!resource_info) + return ERR_PTR(-ENOMEM); + res_mgt->resource_info =3D resource_info; + + return res_mgt; +} + +static struct nbl_resource_ops_tbl * +nbl_res_setup_ops(struct device *dev, struct nbl_resource_mgt *res_mgt) +{ + struct nbl_resource_ops_tbl *res_ops_tbl; + + res_ops_tbl =3D devm_kzalloc(dev, sizeof(*res_ops_tbl), GFP_KERNEL); + if (!res_ops_tbl) + return ERR_PTR(-ENOMEM); + if (!res_ops.get_vsi_id || !res_ops.get_eth_id) + return ERR_PTR(-EINVAL); + res_ops_tbl->ops =3D &res_ops; + res_ops_tbl->priv =3D res_mgt; + + return res_ops_tbl; +} + +static int nbl_res_ctrl_dev_setup_eth_info(struct nbl_resource_mgt *res_mg= t) +{ + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct device *dev =3D res_mgt->common->dev; + struct nbl_eth_info *eth_info; + u32 eth_bitmap =3D 0; + u32 eth_num =3D 0; + u32 fw_port_num; + int i; + + eth_info =3D devm_kzalloc(dev, sizeof(*eth_info), GFP_KERNEL); + if (!eth_info) + return -ENOMEM; + + res_mgt->resource_info->eth_info =3D eth_info; + + fw_port_num =3D res_mgt->resource_info->board_info.eth_num; + + hw_ops->get_fw_eth_map(res_mgt->hw_ops_tbl->priv, ð_bitmap); + if (eth_bitmap & ~((1 << NBL_MAX_ETHERNET) - 1)) { + dev_err(dev, "FW reported invalid eth_bitmap 0x%x\n", + eth_bitmap); + return -EINVAL; + } + if (fw_port_num !=3D hweight32(eth_bitmap)) { + dev_err(dev, "FW inconsistency: port_num=3D%u, bitmap=3D0x%x\n", + fw_port_num, eth_bitmap); + return -EINVAL; + } + /* + * Firmware is ready before probe. Valid port counts are 1/2/4; + * 0 (invalid config), 3 (unsupported topology), and >4 (exceeds + * hardware max) are all rejected with -EINVAL. + */ + if (fw_port_num =3D=3D 0 || fw_port_num =3D=3D 3 || + fw_port_num > NBL_MAX_ETHERNET) { + dev_err(dev, "FW reports %u Ethernet ports, unsupported (valid: 1/2/4)\n= ", + fw_port_num); + return -EINVAL; + } + eth_info->eth_num =3D fw_port_num; + /* Intentional design constraint: each PF maps to exactly one + * Ethernet port. This couples PF identity to port identity + * and is required by nbl_res_get_eth_id() which indexes + * eth_info->eth_id[] by relative PF id. + */ + if (res_mgt->common->max_pf !=3D eth_info->eth_num) { + dev_err(dev, "Invalid PF-to-port topology: max_pf=3D%u, eth_num=3D%u\n", + res_mgt->common->max_pf, eth_info->eth_num); + return -EINVAL; + } + + /* + * Any subset of valid bitmap bits is accepted (e.g. 0/1, 0/2, + * 1/3, etc.). Firmware only needs to report the correct count + * of active ports; no hard-coded fixed bit positions required. + * eth_id[] is filled in ascending bitmap-bit order, so the Nth + * relative PF owns the Nth logical port (max_pf =3D=3D eth_num is + * enforced above). + */ + for (i =3D 0; i < NBL_MAX_ETHERNET; i++) { + if ((1 << i) & eth_bitmap) { + eth_info->eth_id[eth_num] =3D i; + eth_num++; + } + } + + return 0; +} + +static int nbl_res_ctrl_dev_sriov_info_init(struct nbl_resource_mgt *res_m= gt) +{ + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct nbl_hw_mgt *p =3D res_mgt->hw_ops_tbl->priv; + struct nbl_common_info *common =3D res_mgt->common; + struct nbl_sriov_info *sriov_info; + struct device *dev =3D common->dev; + u8 hw_bus =3D 0; + u16 function; + u16 func_id; + + hw_ops->get_real_bus(p, &hw_bus); + if (common->function + common->max_pf > NBL_MAX_PF) { + dev_err(dev, "PF count exceeds available function space\n"); + return -EINVAL; + } + sriov_info =3D devm_kcalloc(dev, common->max_pf, + sizeof(*sriov_info), GFP_KERNEL); + if (!sriov_info) + return -ENOMEM; + + res_mgt->resource_info->sriov_info =3D sriov_info; + /* + * Real bus number of the control PF; + * the BDF table below is built from it. + */ + common->hw_bus =3D hw_bus; + + for (func_id =3D 0; func_id < common->max_pf; func_id++) { + sriov_info =3D res_mgt->resource_info->sriov_info + func_id; + function =3D common->function + func_id; + sriov_info->bdf =3D PCI_DEVID(common->hw_bus, + PCI_DEVFN(common->devid, function)); + } + + return 0; +} + +static int nbl_res_ctrl_dev_vsi_info_init(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_eth_info *eth_info =3D res_mgt->resource_info->eth_info; + struct nbl_common_info *common =3D res_mgt->common; + struct device *dev =3D common->dev; + struct nbl_vsi_info *vsi_info; + int i; + + vsi_info =3D devm_kzalloc(dev, sizeof(*vsi_info), GFP_KERNEL); + if (!vsi_info) + return -ENOMEM; + + res_mgt->resource_info->vsi_info =3D vsi_info; + /* + * case 1 one port(1pf) + * pf0 (NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0 + * case 2 two port(2pf) + * pf0,pf1(NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0,512 + * case 3 four port(4pf) + * pf0,pf1,pf2,pf3(NBL_VSI_SERV_PF_DATA_TYPE) vsi is 0,256,512,768 + */ + + vsi_info->num =3D eth_info->eth_num; + /* + * eth_num can be 1/2/4: + * - 2/4 ports use dedicated gap constants; + * - 1 port falls back to NBL_DEFAULT_VSI_ID_GAP (1024). + * All three values produce valid base_id offsets. + */ + for (i =3D 0; i < vsi_info->num; i++) { + vsi_info->serv_info[i][NBL_VSI_SERV_PF_DATA_TYPE].base_id =3D + i * nbl_vsi_id_gap(vsi_info->num); + vsi_info->serv_info[i][NBL_VSI_SERV_PF_DATA_TYPE].num =3D 1; + } + + return 0; +} + +static int nbl_res_init_pf_num(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + u32 exp_contiguous_mask =3D 0; + u32 pf_mask =3D 0; + u32 pf_num =3D 0; + int i; + + hw_ops->get_host_pf_mask(res_mgt->hw_ops_tbl->priv, &pf_mask); + + /* + * k_pf_mask register rule: + * bit N =3D=3D 0 -> PF#N enabled; bit N =3D=3D 1 -> PF#N masked out. + * Hardware constraint: bit0 is PF0's mask bit; driver requires + * PF0 enabled as management PF, so bit0 must be clear. + * All-zero pf_mask means all PF0~PF7 are enabled, which is unsupported + * by the driver + * + * Product firmware constraint: only 3 valid configurations supported: + * 1 PF (PF0 only): pf_num =3D 1, mask =3D 0xfe + * 2 PFs (PF0,PF1): pf_num =3D 2, mask =3D 0xfc + * 4 PFs (PF0~PF3): pf_num =3D 4, mask =3D 0xf0 + * No other PF count or sparse/non-contiguous PF layout is allowed. + */ + for (i =3D 0; i < NBL_MAX_PF; i++) { + if (!(pf_mask & (1 << i))) + pf_num++; + } + + /* + * Sanity check: enabled PFs must be contiguous starting from PF0. + * Current resource framework uses relative PF id, sparse PF layout + * will cause mismatch between resource layer and hardware func_id. + */ + for (i =3D 0; i < pf_num; i++) + exp_contiguous_mask |=3D BIT(i); + if ((pf_mask & exp_contiguous_mask) !=3D 0) { + dev_err(res_mgt->common->dev, + "pf_mask 0x%08x: non-contiguous enabled PF, unsupported\n", + pf_mask); + return -EINVAL; + } + + /* Only allow product-specified PF count: 1 / 2 / 4 */ + if (pf_num !=3D 1 && pf_num !=3D 2 && pf_num !=3D 4) { + dev_err(res_mgt->common->dev, + "Invalid pf_num=3D%u (mask=3D0x%08x), only 1/2/4 PFs supported\n", + pf_num, pf_mask); + return -EINVAL; + } + + res_mgt->common->max_pf =3D pf_num; + + return 0; +} + +static void nbl_res_init_board_info(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + + hw_ops->get_board_info(res_mgt->hw_ops_tbl->priv, + &res_mgt->resource_info->board_info); +} + +static int nbl_res_start(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_common_info *common =3D res_mgt->common; + int ret =3D 0; + + if (common->has_ctrl) { + nbl_res_init_board_info(res_mgt); + + ret =3D nbl_res_init_pf_num(res_mgt); + if (ret) + return ret; + + ret =3D nbl_res_ctrl_dev_sriov_info_init(res_mgt); + if (ret) + return ret; + + ret =3D nbl_res_ctrl_dev_setup_eth_info(res_mgt); + if (ret) + return ret; + + ret =3D nbl_res_ctrl_dev_vsi_info_init(res_mgt); + if (ret) + return ret; + } + + return 0; +} + +int nbl_res_init_leonis(struct nbl_adapter *adap) +{ + struct nbl_channel_ops_tbl *chan_ops_tbl =3D adap->intf.channel_ops_tbl; + struct nbl_hw_ops_tbl *hw_ops_tbl =3D adap->intf.hw_ops_tbl; + struct nbl_common_info *common =3D &adap->common; + struct nbl_resource_ops_tbl *res_ops_tbl; + struct device *dev =3D &adap->pdev->dev; + struct nbl_resource_mgt *res_mgt; + int ret; + + res_mgt =3D nbl_res_setup_res_mgt(common); + if (IS_ERR(res_mgt)) { + ret =3D PTR_ERR(res_mgt); + return ret; + } + res_mgt->chan_ops_tbl =3D chan_ops_tbl; + res_mgt->hw_ops_tbl =3D hw_ops_tbl; + + ret =3D nbl_res_start(res_mgt); + if (ret) + return ret; + + res_ops_tbl =3D nbl_res_setup_ops(dev, res_mgt); + if (IS_ERR(res_ops_tbl)) { + ret =3D PTR_ERR(res_ops_tbl); + return ret; + } + adap->intf.resource_ops_tbl =3D res_ops_tbl; + adap->core.res_mgt =3D res_mgt; + + return 0; +} + +void nbl_res_remove_leonis(struct nbl_adapter *adap) +{ + /* + * No resource release here because all memory uses devm managed + * allocation + */ +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_resource_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_= leonis/nbl_resource_leonis.h new file mode 100644 index 000000000000..b9355262c00d --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_RESOURCE_LEONIS_H_ +#define _NBL_RESOURCE_LEONIS_H_ + +#include "../nbl_resource.h" +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c b= /drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c new file mode 100644 index 000000000000..b316fb8e7051 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#include +#include "nbl_resource.h" + +int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_i= d, + u16 type, u16 *vsi_id) +{ + struct nbl_vsi_info *vsi_info =3D res_mgt->resource_info->vsi_info; + enum nbl_vsi_serv_type dst_type =3D NBL_VSI_SERV_PF_DATA_TYPE; + struct nbl_common_info *common =3D res_mgt->common; + struct device *dev =3D res_mgt->common->dev; + int pfid =3D func_id; + u32 rel_pf_id; + int ret; + + if (!common->has_ctrl || !vsi_id) { + dev_dbg(dev, "No control plane or null vsi output ptr\n"); + return -EINVAL; + } + ret =3D nbl_common_func_id_to_rel_pf_id(common, pfid, &rel_pf_id); + if (ret) + return ret; + if (rel_pf_id >=3D vsi_info->num) { + dev_err(dev, "PF %d (diff=3D%u) exceeds vsi_info->num (%u)\n", + pfid, rel_pf_id, vsi_info->num); + return -EINVAL; + } + + ret =3D nbl_res_pf_dev_vsi_type_to_hw_vsi_type(res_mgt, type, &dst_type); + if (ret) { + dev_err(dev, "Invalid vsi type %u func_id %u\n", type, func_id); + return ret; + } + *vsi_id =3D vsi_info->serv_info[rel_pf_id][dst_type].base_id; + return 0; +} + +int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id) +{ + struct nbl_vsi_info *vsi_info =3D res_mgt->resource_info->vsi_info; + struct nbl_common_info *common =3D res_mgt->common; + struct device *dev =3D res_mgt->common->dev; + int j =3D NBL_VSI_SERV_PF_DATA_TYPE; + int pf_id, i; + + if (!common->has_ctrl) { + dev_dbg(dev, "No control plane available\n"); + return -EINVAL; + } + for (i =3D 0; i < vsi_info->num; i++) { + if (vsi_id >=3D vsi_info->serv_info[i][j].base_id && + (vsi_id < vsi_info->serv_info[i][j].base_id + + vsi_info->serv_info[i][j].num)) { + pf_id =3D i + common->mgt_pf; + if (pf_id >=3D NBL_MAX_PF) { + dev_err(dev, "PF ID overflow\n"); + return -ERANGE; + } + return pf_id; + } + } + + dev_dbg(dev, "VSI ID %u not found\n", vsi_id); + return -ENOENT; +} + +int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id, + u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id) +{ + struct nbl_eth_info *eth_info =3D res_mgt->resource_info->eth_info; + struct nbl_common_info *common =3D res_mgt->common; + struct device *dev =3D res_mgt->common->dev; + int pfid =3D func_id; + int rel_pf_id; + int abs_pf_id; + + if (!common->has_ctrl || !eth_num || !eth_id || !logic_eth_id) + return -EINVAL; + abs_pf_id =3D nbl_res_vsi_id_to_pf_id(res_mgt, vsi_id); + if (abs_pf_id < 0) { + dev_err(dev, "Failed to get PF ID from VSI ID %u\n", vsi_id); + return -EINVAL; + } + if (abs_pf_id !=3D pfid) { + dev_err(dev, "func_id %u does not match pf derived from vsi_id %u\n", + pfid, vsi_id); + return -EINVAL; + } + rel_pf_id =3D abs_pf_id - common->mgt_pf; + + if (rel_pf_id >=3D eth_info->eth_num) { + dev_err(dev, "rel_pf_id %d out of range [0, %u)\n", + rel_pf_id, eth_info->eth_num); + return -ERANGE; + } + + *eth_num =3D eth_info->eth_num; + *eth_id =3D eth_info->eth_id[rel_pf_id]; + /* + * Logical eth id equals the relative PF id: eth_info setup enforces + * max_pf =3D=3D eth_num and fills eth_id[] in ascending bitmap-bit + * order, so the Nth PF owns the Nth logical port. + */ + *logic_eth_id =3D rel_pf_id; + return 0; +} + +int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mg= t, + u16 src_type, + enum nbl_vsi_serv_type *dst_type) +{ + switch (src_type) { + case NBL_VSI_DATA: + *dst_type =3D NBL_VSI_SERV_PF_DATA_TYPE; + return 0; + default: + dev_err_once(res_mgt->common->dev, + "Unsupported vsi src_type %u\n", src_type); + return -EINVAL; + } +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h b= /drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h new file mode 100644 index 000000000000..ae0a3d33198d --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_RESOURCE_H_ +#define _NBL_RESOURCE_H_ + +#include + +#include "../nbl_include/nbl_include.h" +#include "../nbl_include/nbl_def_channel.h" +#include "../nbl_include/nbl_def_hw.h" +#include "../nbl_include/nbl_def_resource.h" +#include "../nbl_include/nbl_def_common.h" +#include "../nbl_core.h" + +struct nbl_resource_mgt; + +/* --------- INFO ---------- */ +struct nbl_sriov_info { + unsigned int bdf; +}; + +struct nbl_eth_info { + u8 eth_num; + u8 resv[3]; + u8 eth_id[NBL_MAX_ETHERNET]; +}; + +enum nbl_vsi_serv_type { + NBL_VSI_SERV_PF_DATA_TYPE, + NBL_VSI_SERV_MAX_TYPE, +}; + +struct nbl_vsi_serv_info { + u16 base_id; + u16 num; +}; + +struct nbl_vsi_info { + u16 num; + struct nbl_vsi_serv_info serv_info[NBL_MAX_ETHERNET] + [NBL_VSI_SERV_MAX_TYPE]; +}; + +struct nbl_resource_info { + struct nbl_sriov_info *sriov_info; + struct nbl_eth_info *eth_info; + struct nbl_vsi_info *vsi_info; + struct nbl_board_port_info board_info; +}; + +struct nbl_resource_mgt { + struct nbl_common_info *common; + struct nbl_resource_info *resource_info; + struct nbl_channel_ops_tbl *chan_ops_tbl; + struct nbl_hw_ops_tbl *hw_ops_tbl; +}; + +int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id); +int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_i= d, + u16 type, u16 *vsi_id); +int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id, + u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id); +int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mg= t, + u16 src_type, + enum nbl_vsi_serv_type *dst_type); +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_cha= nnel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel= .h index d6faa0bc4026..bf971121d2ec 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h @@ -39,6 +39,16 @@ enum nbl_chan_state { NBL_CHAN_STATE_NBITS }; =20 +struct nbl_board_port_info { + u8 eth_num; + u8 eth_speed; + u8 p4_version; + u8 rsv[5]; +}; + +static_assert(sizeof(struct nbl_board_port_info) =3D=3D 8, + "nbl_board_port_info size must be 8 bytes"); + struct nbl_chan_send_info { void *arg; size_t arg_len; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_com= mon.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h index 26a364e3a194..fa76dd6c5323 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h @@ -11,6 +11,22 @@ #include #include "nbl_include.h" =20 +#define NBL_TWO_ETHERNET_PORT 2 +#define NBL_FOUR_ETHERNET_PORT 4 +#define NBL_DEFAULT_VSI_ID_GAP 1024 +#define NBL_TWO_ETHERNET_VSI_ID_GAP 512 +#define NBL_FOUR_ETHERNET_VSI_ID_GAP 256 + +static inline u32 nbl_vsi_id_gap(u32 m) +{ + if (m =3D=3D NBL_FOUR_ETHERNET_PORT) + return NBL_FOUR_ETHERNET_VSI_ID_GAP; + else if (m =3D=3D NBL_TWO_ETHERNET_PORT) + return NBL_TWO_ETHERNET_VSI_ID_GAP; + + return NBL_DEFAULT_VSI_ID_GAP; +} + struct nbl_common_info { struct workqueue_struct *wq; struct pci_dev *pdev; @@ -24,12 +40,16 @@ struct nbl_common_info { u8 devid; u8 bus; u8 hw_bus; + u16 mgt_pf; =20 u8 has_ctrl; u8 has_net; + u8 max_pf; }; =20 void nbl_common_destroy_wq(struct nbl_common_info *common); int nbl_common_create_wq(struct nbl_common_info *common); +int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf= _id, + u32 *rel_pf_id); =20 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.= h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h index ce092005d767..095ff548c4a7 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h @@ -8,9 +8,11 @@ =20 #include =20 +struct nbl_board_port_info; struct nbl_hw_mgt; struct nbl_adapter; struct nbl_hw_ops { + void (*flush_write)(struct nbl_hw_mgt *hw_mgt); void (*update_mailbox_queue_tail_ptr)(struct nbl_hw_mgt *hw_mgt, u16 tail_ptr, u8 txrx); void (*config_mailbox_rxq)(struct nbl_hw_mgt *hw_mgt, @@ -39,9 +41,22 @@ struct nbl_hw_ops { * get_board_info()->eth_num. */ void (*get_host_pf_mask)(struct nbl_hw_mgt *hw_mgt, u32 *pf_mask); + void (*get_real_bus)(struct nbl_hw_mgt *hw_mgt, u8 *bus); =20 void (*cfg_mailbox_qinfo)(struct nbl_hw_mgt *hw_mgt, u16 func_id, u8 bus, u8 devid, u8 function); + void (*get_fw_eth_map)(struct nbl_hw_mgt *hw_mgt, u32 *eth_map); + /** + * get_board_info - Fetch board info from firmware + * @hw_mgt: hardware management context + * @board_info: output pointer for board info structure + * + * Firmware contract: board_info.eth_num MUST equal the number of + * unmasked PFs from get_host_pf_mask(). See get_host_pf_mask for + * details. + */ + void (*get_board_info)(struct nbl_hw_mgt *hw_mgt, + struct nbl_board_port_info *board_info); }; =20 struct nbl_hw_ops_tbl { diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_res= ource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resour= ce.h new file mode 100644 index 000000000000..7136b282fb80 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_DEF_RESOURCE_H_ +#define _NBL_DEF_RESOURCE_H_ + +#include + +struct nbl_resource_mgt; +struct nbl_adapter; + +struct nbl_resource_ops { + int (*get_vsi_id)(struct nbl_resource_mgt *res_mgt, u16 func_id, + u16 type, u16 *vsi_id); + int (*get_eth_id)(struct nbl_resource_mgt *res_mgt, u16 func_id, + u16 vsi_id, u8 *eth_num, u8 *eth_id, + u8 *logic_eth_id); +}; + +struct nbl_resource_ops_tbl { + struct nbl_resource_ops *ops; + struct nbl_resource_mgt *priv; +}; + +int nbl_res_init_leonis(struct nbl_adapter *adapter); +void nbl_res_remove_leonis(struct nbl_adapter *adapter); +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h index f2d802397d98..59e44feab44f 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h @@ -13,6 +13,12 @@ #define NBL_MAX_PF 8 #define NBL_NEXT_ID(id, max) (((id) + 1) % ((max) + 1)) =20 +#define NBL_MAX_ETHERNET 4 + +enum { + NBL_VSI_DATA =3D 0, +}; + struct nbl_func_caps { u32 has_ctrl:1; u32 has_net:1; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_main.c index b7c80ea54c8d..1aafed2d46d7 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c @@ -10,6 +10,7 @@ #include "nbl_include/nbl_include.h" #include "nbl_include/nbl_def_channel.h" #include "nbl_include/nbl_def_hw.h" +#include "nbl_include/nbl_def_resource.h" #include "nbl_include/nbl_def_common.h" #include "nbl_core.h" =20 @@ -27,6 +28,7 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, adapter->pdev =3D pdev; common =3D &adapter->common; =20 + common->mgt_pf =3D 0; common->pdev =3D pdev; common->dev =3D &pdev->dev; common->has_ctrl =3D param->caps.has_ctrl; @@ -42,7 +44,13 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, ret =3D nbl_chan_init_common(adapter); if (ret) goto chan_init_fail; + + ret =3D nbl_res_init_leonis(adapter); + if (ret) + goto res_init_fail; return adapter; +res_init_fail: + nbl_chan_remove_common(adapter); chan_init_fail: nbl_hw_remove_leonis(adapter); hw_init_fail: @@ -51,6 +59,7 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, =20 void nbl_core_remove(struct nbl_adapter *adapter) { + nbl_res_remove_leonis(adapter); nbl_chan_remove_common(adapter); nbl_hw_remove_leonis(adapter); } --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-97.mail.aliyun.com (out28-97.mail.aliyun.com [115.124.28.97]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1E9C5535FBD; Tue, 22 Sep 2026 12:03:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.97 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078611; cv=none; b=HKc7q57Z9TX/GKevN+anVtii3+UltiAsyoKwIMvam5dXJznOJUnt8261ZV7NmYA/jZXS3SMNcE0L23Ush3SHdRLjkd4fN36DmLBiLfLeStqNUU8mfVebU8IbnMUvEaK63nPMNBGBNMS68u28MXfL/mRx/QFY7BIBTJtiew67gak= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078611; c=relaxed/simple; bh=8VRxkVQmYqwUCgZibHVENukrtlOa8aMdVKC90TrZiVY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=quAk79B6Spo1lN22gH+gZodEl3Hl8mZv/oxybGUbSUUg1UODKieTTEGc6WFaZZkNywGoxDusrEz+vBn8AZYkjW17Pq5AdKEffM91kH1oiRYJmTyymvaAlWPwhvMDF6Fq9rpJporE1H+RQ+54AJ2g/GLhwjVPzq2hJleLgl6eH1o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.97 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.9171291|0.8893988;CH=green;DM=|SPAM|false|;DS=CONTINUE|ham_system_inform|0.0815216-0.00237242-0.916106;FP=9463164790942289067|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033037021217;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPSF_1790078596; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPSF_1790078596 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:17 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 3/8] net/nebula-matrix: add intr resource implementation Date: Tue, 22 Sep 2026 20:03:00 +0800 Message-ID: <20260922120311.86593-4-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: illusion wang Add dedicated nbl_interrupt module to manage chip-internal MSI-X interrupt = resource and hardware mapping for Nebula Matrix Ethernet driver. This module manages driver-wide global hardware MSI-X vector index space, split into independent network and control interrupt bitmaps (intr_net_bmap/ intr_other_bmap), and handles programming of chip-internal MSI-X mapping registers. It explicitly does not manage physical PCI MSI-X entries; physical vector allocation via pci_alloc_irq_vectors() will be implemented in a follow-up patch via nbl_dev_init_interrupt_scheme(). Core functional interfaces: 1. cfg_msix_map: Allocate global hardware MSI-X vectors from separate net/o= ther interrupt bitmaps. Reuse per-function coherent DMA map table on reconfig, eliminating free/realloc cycles, DMA race windows and redundant quiesce sleeps. Only tear down old hardware state after new allocation succeeds to avoid interrupt loss, and program MSI-X table DMA address + control-PF BDF into NBL_PCOMPLETER_FUNCTION_MSIX_MAP. 2. destroy_msix_map: Recycle global vector indices, clear hardware MSI-X ma= ppings, and release DMA/descriptor resources. Implements two-stage teardown: disable mailbox IRQ routing first, mask vectors, clear hardware VALID bits while retaining live DMA addresses, wait ~1ms for hardware DMA quiescence, then zero table entries and free coherent memory to prevent torn reads. 3. set_mailbox_irq: Toggle PF-specific mailbox MSI-X routing by updating NBL_MAILBOX_QINFO_MAP_REG_ARR. The disable path works without a configured MSI-X map, enabling safe routing cleanup before vector release. 4. cfg_msix_info: Program PADPT_HOST_MSIX_INFO and PCOMPLETER_HOST_MSIX_FID= _TABLE with strict hardware-defined programming order (forward for enable, reverse for teardown) to avoid inconsistent hardware state. Key design & safety features: - Self-contained intr_mgt->lock protects global bitmaps and per-function state; all public APIs internally hold the lock, no upper-layer locking required by callers. - Deferred vector recycling mechanism: retire reconfigured vectors to a timed list and recycle only after hardware pipeline quiesces. - Global cleanup via nbl_intr_mgt_stop(): iterate all 520 function IDs to clean up leftover MSI-X maps (including remote PF maps via mailbox RPC) before devres releases coherent resources. - PF-only support: explicitly reject VF function IDs with -EOPNOTSUPP. Instantiate the interrupt manager via nbl_intr_mgt_start() during device resource initialization, and attach it to the resource management context. Add corresponding hardware register definitions, helper functions, resource ops callbacks, and Makefile entries to wire up the new module. Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/Makefile | 1 + .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c | 142 +++- .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h | 42 + .../nbl_hw_leonis/nbl_resource_leonis.c | 33 +- .../nbl_hw_leonis/nbl_resource_leonis.h | 1 + .../nebula-matrix/nbl/nbl_hw/nbl_interrupt.c | 742 ++++++++++++++++++ .../nebula-matrix/nbl/nbl_hw/nbl_interrupt.h | 21 + .../nebula-matrix/nbl/nbl_hw/nbl_resource.c | 32 + .../nebula-matrix/nbl/nbl_hw/nbl_resource.h | 66 ++ .../nbl/nbl_include/nbl_def_hw.h | 9 + .../nbl/nbl_include/nbl_def_resource.h | 6 + .../nbl/nbl_include/nbl_include.h | 1 + 12 files changed, 1091 insertions(+), 5 deletions(-) create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_inter= rupt.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_inter= rupt.h diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/= ethernet/nebula-matrix/nbl/Makefile index 3dab9519a277..5aec8e44f5d7 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/Makefile +++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile @@ -8,4 +8,5 @@ nbl-objs +=3D nbl_common/nbl_common.o \ nbl_hw/nbl_hw_leonis/nbl_hw_leonis.o \ nbl_hw/nbl_hw_leonis/nbl_resource_leonis.o \ nbl_hw/nbl_resource.o \ + nbl_hw/nbl_interrupt.o \ nbl_main.o diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.c index 82efe2b40a8c..457e0212a7bf 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c @@ -104,6 +104,20 @@ static void nbl_hw_rd_regs_lock(struct nbl_hw_mgt *hw_= mgt, u64 reg, u32 *data, spin_unlock(&hw_mgt->reg_lock); } =20 +static void nbl_hw_wr_regs_lock(struct nbl_hw_mgt *hw_mgt, u64 reg, + const u32 *data, u32 len) +{ + u32 size =3D len / 4; + u32 i; + + if (len % 4) + return; + spin_lock(&hw_mgt->reg_lock); + for (i =3D 0; i < size; i++) + wr32(hw_mgt->hw_addr, reg + i * sizeof(u32), data[i]); + spin_unlock(&hw_mgt->reg_lock); +} + /* * Only call this when has_ctrl=3Dtrue, which maps enough space * (bar_len - 8192) to cover NBL_HW_DUMMY_REG (0x1300904). @@ -130,6 +144,126 @@ static void nbl_hw_get_fw_eth_map(struct nbl_hw_mgt *= hw_mgt, u32 *eth_map) *eth_map =3D FIELD_GET(NBL_FW_BOARD_DW6_ETH_BITMAP_MASK, data); } =20 +/* + * nbl_hw_set_mailbox_irq - read-modify-write NBL_MAILBOX_QINFO_MAP_REG_ARR + * + * The full RMW sequence is wrapped by reg_lock, so concurrent register + * access from different CPUs is already serialized safely. + * nbl_hw_cfg_mailbox_qinfo() programs the BDF fields during control-PF + * init and clears MSIX_IDX/MSIX_IDX_VALID at the same time (they survive + * kexec/forced unload without FLR), so mailbox MSIX routing for a PF + * starts disarmed at init and is armed only by an explicit en_msix=3Dtrue + * call here. + */ +static void nbl_hw_set_mailbox_irq(struct nbl_hw_mgt *hw_mgt, u16 func_id, + bool en_msix, u16 gvec) +{ + u32 data =3D 0; + + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), &data, + sizeof(data)); + data &=3D ~(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK | + NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK); + if (en_msix) + data |=3D FIELD_PREP(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_MASK, + gvec) | + FIELD_PREP(NBL_MAILBOX_QINFO_MAP_MSIX_IDX_VALID_MASK, + 1); + + nbl_hw_wr_regs(hw_mgt, NBL_MAILBOX_QINFO_MAP_REG_ARR(func_id), &data, + sizeof(data)); + spin_unlock(&hw_mgt->reg_lock); + nbl_flush_writes(hw_mgt); +} + +static void nbl_hw_cfg_msix_map(struct nbl_hw_mgt *hw_mgt, u16 func_id, + bool valid, dma_addr_t dma_addr, u8 bus, + u8 devid, u8 function) +{ + struct nbl_function_msix_map function_msix_map; + + memset(&function_msix_map, 0, sizeof(function_msix_map)); + if (valid) { + function_msix_map.data[0] =3D lower_32_bits(dma_addr); + function_msix_map.data[1] =3D upper_32_bits(dma_addr); + /* use ctrl dev's bdf, because the dma memory was + * allocated by it + */ + function_msix_map.data[2] =3D + FIELD_PREP(NBL_FUNCTION_MSIX_MAP_FUNCTION_MASK, + function) | + FIELD_PREP(NBL_FUNCTION_MSIX_MAP_DEVID_MASK, devid) | + FIELD_PREP(NBL_FUNCTION_MSIX_MAP_BUS_MASK, bus) | + FIELD_PREP(NBL_FUNCTION_MSIX_MAP_VALID_MASK, 1); + } else { + /* + * reg_lock prevents concurrent CPU writes to the same + * function's MSIX entry, but cannot synchronize hardware DMA + * reads. Upper layer uses two-stage destruction + sync sleep + * to avoid torn hardware read of partial MSIX entry. + * Keep valid live dma address here, only clear VALID flag. + */ + function_msix_map.data[0] =3D lower_32_bits(dma_addr); + function_msix_map.data[1] =3D upper_32_bits(dma_addr); + function_msix_map.data[2] =3D 0; + } + + nbl_hw_wr_regs_lock(hw_mgt, + NBL_PCOMPLETER_FUNCTION_MSIX_MAP_REG_ARR(func_id), + function_msix_map.data, sizeof(function_msix_map)); +} + +static void nbl_hw_cfg_msix_info(struct nbl_hw_mgt *hw_mgt, u16 func_id, + bool valid, u16 interrupt_id, u8 bus, + u8 devid, u8 function, bool msix_mask_en) +{ + u32 host_msix_fid =3D 0; + struct nbl_host_msix_info msix_info; + + memset(&msix_info, 0, sizeof(msix_info)); + if (valid) { + host_msix_fid =3D + FIELD_PREP(NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_FID_MASK, + func_id) | + FIELD_PREP(NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_VLD_MASK, + 1); + + msix_info.data[1] =3D + FIELD_PREP(NBL_HOST_MSIX_INFO_FUNCTION_MASK, function) | + FIELD_PREP(NBL_HOST_MSIX_INFO_DEVID_MASK, devid) | + FIELD_PREP(NBL_HOST_MSIX_INFO_BUS_MASK, bus) | + FIELD_PREP(NBL_HOST_MSIX_INFO_VALID_MASK, 1); + + if (msix_mask_en) + msix_info.data[1] |=3D + FIELD_PREP(NBL_HOST_MSIX_INFO_MSIX_MASK_EN_MASK, 1); + } + spin_lock(&hw_mgt->reg_lock); + /* + * Programming order rule: + * Enable: PADPT_HOST_MSIX_INFO -> PCOMPLETER_HOST_MSIX_FID_TABLE + * Teardown: reverse order, clear FID VLD first to avoid inconsistent + * state + */ + if (valid) { + nbl_hw_wr_regs(hw_mgt, + NBL_PADPT_HOST_MSIX_INFO_REG_ARR(interrupt_id), + msix_info.data, sizeof(msix_info)); + nbl_hw_wr_regs(hw_mgt, + NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(interrupt_id), + &host_msix_fid, sizeof(host_msix_fid)); + } else { + nbl_hw_wr_regs(hw_mgt, + NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(interrupt_id), + &host_msix_fid, sizeof(host_msix_fid)); + nbl_hw_wr_regs(hw_mgt, + NBL_PADPT_HOST_MSIX_INFO_REG_ARR(interrupt_id), + msix_info.data, sizeof(msix_info)); + } + spin_unlock(&hw_mgt->reg_lock); +} + static void nbl_hw_update_mailbox_queue_tail_ptr(struct nbl_hw_mgt *hw_mgt, u16 tail_ptr, u8 txrx) { @@ -274,6 +408,8 @@ static void nbl_hw_get_board_info(struct nbl_hw_mgt *hw= _mgt, } =20 static struct nbl_hw_ops hw_ops =3D { + .cfg_msix_map =3D nbl_hw_cfg_msix_map, + .cfg_msix_info =3D nbl_hw_cfg_msix_info, .flush_write =3D nbl_flush_writes, =20 .update_mailbox_queue_tail_ptr =3D nbl_hw_update_mailbox_queue_tail_ptr, @@ -285,6 +421,7 @@ static struct nbl_hw_ops hw_ops =3D { .get_real_bus =3D nbl_hw_get_real_bus, =20 .cfg_mailbox_qinfo =3D nbl_hw_cfg_mailbox_qinfo, + .set_mailbox_irq =3D nbl_hw_set_mailbox_irq, =20 .get_fw_eth_map =3D nbl_hw_get_fw_eth_map, .get_board_info =3D nbl_hw_get_board_info, @@ -315,11 +452,12 @@ static struct nbl_hw_ops_tbl *nbl_hw_setup_ops(struct= nbl_common_info *common, hw_ops_tbl =3D devm_kzalloc(dev, sizeof(*hw_ops_tbl), GFP_KERNEL); if (!hw_ops_tbl) return ERR_PTR(-ENOMEM); - if (!hw_ops.flush_write || !hw_ops.update_mailbox_queue_tail_ptr || + if (!hw_ops.cfg_msix_map || !hw_ops.cfg_msix_info || + !hw_ops.flush_write || !hw_ops.update_mailbox_queue_tail_ptr || !hw_ops.config_mailbox_rxq || !hw_ops.config_mailbox_txq || !hw_ops.stop_mailbox_rxq || !hw_ops.stop_mailbox_txq || !hw_ops.get_host_pf_mask || !hw_ops.get_real_bus || - !hw_ops.cfg_mailbox_qinfo || + !hw_ops.cfg_mailbox_qinfo || !hw_ops.set_mailbox_irq || !hw_ops.get_fw_eth_map || !hw_ops.get_board_info) return ERR_PTR(-EINVAL); hw_ops_tbl->ops =3D &hw_ops; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.h index 251dd68d0721..18e3e7da4f9d 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h @@ -54,6 +54,48 @@ struct nbl_mailbox_qinfo_cfg_table { #define NBL_PCIE_HOST_TL_CFG_BUSDEV (NBL_INTF_HOST_PCIE_BASE + 0x11040) =20 #define NBL_PCIE_BUS_MASK GENMASK(12, 5) + +/* -------- HOST_PADPT -------- */ +/* host_padpt host_msix_info */ +#define NBL_PADPT_HOST_MSIX_INFO_REG_ARR(vector_id) \ + (NBL_INTF_HOST_PADPT_BASE + 0x00010000 + \ + (vector_id) * sizeof(struct nbl_host_msix_info)) + +#define NBL_HOST_MSIX_INFO_DWLEN 2 +/* data[0] */ +#define NBL_HOST_MSIX_INFO_INTRL_PNUM_MASK GENMASK(15, 0) +#define NBL_HOST_MSIX_INFO_INTRL_RATE_MASK GENMASK(31, 16) +/* data[1] */ +#define NBL_HOST_MSIX_INFO_FUNCTION_MASK GENMASK(2, 0) +#define NBL_HOST_MSIX_INFO_DEVID_MASK GENMASK(7, 3) +#define NBL_HOST_MSIX_INFO_BUS_MASK GENMASK(15, 8) +#define NBL_HOST_MSIX_INFO_VALID_MASK BIT(16) +#define NBL_HOST_MSIX_INFO_MSIX_MASK_EN_MASK BIT(17) +struct nbl_host_msix_info { + u32 data[NBL_HOST_MSIX_INFO_DWLEN]; +}; + +/* -------- HOST_PCOMPLETER -------- */ +/* pcompleter_host function_msix_map_table */ +#define NBL_PCOMPLETER_FUNCTION_MSIX_MAP_REG_ARR(i) \ + (NBL_INTF_HOST_PCOMPLETER_BASE + 0x00004000 + \ + (i) * sizeof(struct nbl_function_msix_map)) +#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE(i) \ + (NBL_INTF_HOST_PCOMPLETER_BASE + 0x0003a000 + (i) * sizeof(u32)) + +#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_FID_MASK GENMASK(9, 0) +#define NBL_PCOMPLETER_HOST_MSIX_FID_TABLE_VLD_MASK BIT(10) + +#define NBL_FUNC_MSIX_MAP_DWLEN 4 +/* data[2] */ +#define NBL_FUNCTION_MSIX_MAP_FUNCTION_MASK GENMASK(2, 0) +#define NBL_FUNCTION_MSIX_MAP_DEVID_MASK GENMASK(7, 3) +#define NBL_FUNCTION_MSIX_MAP_BUS_MASK GENMASK(15, 8) +#define NBL_FUNCTION_MSIX_MAP_VALID_MASK BIT(16) +struct nbl_function_msix_map { + u32 data[NBL_FUNC_MSIX_MAP_DWLEN]; +}; + #define NBL_FW_BOARD_CONFIG 0x200 #define NBL_FW_BOARD_DW3_OFFSET (NBL_FW_BOARD_CONFIG + 12) #define NBL_FW_BOARD_DW6_OFFSET (NBL_FW_BOARD_CONFIG + 24) diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_resource_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_= leonis/nbl_resource_leonis.c index 7804762a96e0..62af1738e2fd 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.c @@ -10,6 +10,9 @@ static struct nbl_resource_ops res_ops =3D { .get_vsi_id =3D nbl_res_func_id_to_vsi_id, .get_eth_id =3D nbl_res_get_eth_id, + .cfg_msix_map =3D nbl_res_intr_cfg_msix_map, + .destroy_msix_map =3D nbl_res_intr_destroy_msix_map, + .set_mailbox_irq =3D nbl_res_intr_set_mailbox_irq, }; =20 static struct nbl_resource_mgt * @@ -41,7 +44,9 @@ nbl_res_setup_ops(struct device *dev, struct nbl_resource= _mgt *res_mgt) res_ops_tbl =3D devm_kzalloc(dev, sizeof(*res_ops_tbl), GFP_KERNEL); if (!res_ops_tbl) return ERR_PTR(-ENOMEM); - if (!res_ops.get_vsi_id || !res_ops.get_eth_id) + if (!res_ops.get_vsi_id || !res_ops.get_eth_id || + !res_ops.cfg_msix_map || !res_ops.destroy_msix_map || + !res_ops.set_mailbox_irq) return ERR_PTR(-EINVAL); res_ops_tbl->ops =3D &res_ops; res_ops_tbl->priv =3D res_mgt; @@ -282,6 +287,10 @@ static int nbl_res_start(struct nbl_resource_mgt *res_= mgt) ret =3D nbl_res_ctrl_dev_vsi_info_init(res_mgt); if (ret) return ret; + + ret =3D nbl_intr_mgt_start(res_mgt); + if (ret) + return ret; } =20 return 0; @@ -322,8 +331,26 @@ int nbl_res_init_leonis(struct nbl_adapter *adap) =20 void nbl_res_remove_leonis(struct nbl_adapter *adap) { + struct nbl_resource_mgt *res_mgt =3D adap->core.res_mgt; + struct nbl_common_info *common =3D &adap->common; + + if (!res_mgt) + return; + /* - * No resource release here because all memory uses devm managed - * allocation + * Tear down all MSI-X maps before devres releases the coherent + * tables. This is critical on the control PF, which may hold + * maps for remote PFs that are still bound. + */ + if (common->has_ctrl && res_mgt->intr_mgt) + nbl_intr_mgt_stop(res_mgt); + + /* Note: + * per-function interrupts arrays (kcalloc) are freed by + * nbl_intr_mgt_stop(). + * MSIX coherent tables are explicitly freed by dmam_free_coherent() + * inside the intr destroy path, before nbl_intr_mgt_stop() returns. + * intr_mgt itself (devm_kzalloc) is released by devres after this + * function returns */ } diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_resource_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_= leonis/nbl_resource_leonis.h index b9355262c00d..6eb4dc9e695a 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.h @@ -7,4 +7,5 @@ #define _NBL_RESOURCE_LEONIS_H_ =20 #include "../nbl_resource.h" +#include "../nbl_interrupt.h" #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c = b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c new file mode 100644 index 000000000000..e99a9d65c758 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ +#include +#include +#include +#include +#include +#include "nbl_interrupt.h" + +#define NBL_MSIX_DMA_SYNC_MIN_US 1000 /* us */ +#define NBL_MSIX_DMA_SYNC_MAX_US 1200 /* us */ + +/* + * Release global vector IDs back to intr_net_bmap / intr_other_bmap. + * Caller must hold intr_mgt->lock. + */ +static void nbl_intr_release_bitmap(struct nbl_resource_mgt *res_mgt, + u16 *vec_buf, u16 cnt) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + u16 bit; + u16 i; + + lockdep_assert_held(&intr_mgt->lock); + + if (!vec_buf || cnt =3D=3D 0) + return; + + for (i =3D 0; i < cnt; i++) { + u16 intr_index =3D vec_buf[i]; + + if (intr_index >=3D NBL_NET_INTR_BASE) { + bit =3D intr_index - NBL_NET_INTR_BASE; + if (bit < NBL_MAX_NET_INTERRUPT) + clear_bit(bit, intr_mgt->intr_net_bmap); + else + dev_warn(res_mgt->common->dev, + "invalid net intr index %u\n", + intr_index); + } else { + if (intr_index < NBL_MAX_OTHER_INTERRUPT) + clear_bit(intr_index, + intr_mgt->intr_other_bmap); + else + dev_warn(res_mgt->common->dev, + "invalid other intr index %u\n", + intr_index); + } + } +} + +/* + * Defer recycling of vectors removed during reconfiguration until the + * hardware interrupt/DMA pipeline has quiesced. Caller must hold + * intr_mgt->lock. + */ +static void nbl_intr_retire_vectors_locked(struct nbl_resource_mgt *res_mg= t, + u16 *vectors, u16 cnt) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_intr_retired_vectors *retired; + + retired =3D kzalloc_obj(*retired, GFP_KERNEL); + if (retired) { + retired->vectors =3D vectors; + retired->cnt =3D cnt; + retired->expires =3D jiffies + + usecs_to_jiffies(NBL_MSIX_DMA_SYNC_MIN_US); + list_add_tail(&retired->node, &intr_mgt->retired_list); + return; + } + + /* + * The retired tracking node cannot be allocated; fall back to + * immediate recycling, matching the pre-reconfig behavior. + */ + dev_warn(res_mgt->common->dev, + "cannot defer vector recycling, recycling immediately\n"); + nbl_intr_release_bitmap(res_mgt, vectors, cnt); + kfree(vectors); +} + +/* + * Recycle expired retired vectors. Entries are appended in time order, + * so stop at the first unexpired entry. Caller must hold intr_mgt->lock. + */ +static void nbl_intr_sweep_retired_locked(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_intr_retired_vectors *retired; + + lockdep_assert_held(&intr_mgt->lock); + + while (!list_empty(&intr_mgt->retired_list)) { + retired =3D list_first_entry(&intr_mgt->retired_list, + struct nbl_intr_retired_vectors, + node); + if (!time_after_eq(jiffies, retired->expires)) + return; + list_del(&retired->node); + nbl_intr_release_bitmap(res_mgt, retired->vectors, + retired->cnt); + kfree(retired->vectors); + kfree(retired); + } +} + +/* + * Recycle all retired vectors regardless of expiry. Used on module + * teardown after the global hardware quiesce window has elapsed. + * Caller must hold intr_mgt->lock. + */ +static void nbl_intr_drain_retired_locked(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_intr_retired_vectors *retired; + struct nbl_intr_retired_vectors *tmp; + + lockdep_assert_held(&intr_mgt->lock); + + list_for_each_entry_safe(retired, tmp, &intr_mgt->retired_list, node) { + list_del(&retired->node); + nbl_intr_release_bitmap(res_mgt, retired->vectors, + retired->cnt); + kfree(retired->vectors); + kfree(retired); + } +} + +/* + * Internal (unlocked) mailbox IRQ bind. Caller must hold + * intr_mgt->lock. The disable path does not require a configured + * MSI-X map because the hardware op ignores gvec when + * en_msix=3Dfalse. + */ +static int __nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt, + u16 func_id, u16 vector_id, + bool en_msix) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct nbl_common_info *common =3D res_mgt->common; + struct device *dev =3D common->dev; + u16 gvec; + + lockdep_assert_held(&intr_mgt->lock); + + if (func_id >=3D NBL_MAX_FUNC) { + dev_err(dev, "func_id %u out of range\n", func_id); + return -EINVAL; + } + + if (!en_msix) { + hw_ops->set_mailbox_irq(res_mgt->hw_ops_tbl->priv, + func_id, false, 0); + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + return 0; + } + + if (!intr_mgt->func_intr_res[func_id].interrupts) { + dev_err(dev, "func %u MSIX map not configured\n", func_id); + return -ENODEV; + } + + if (vector_id >=3D intr_mgt->func_intr_res[func_id].num_interrupts) { + dev_err(dev, "vector_id %u out of range (max %u)\n", + vector_id, + intr_mgt->func_intr_res[func_id].num_interrupts - 1); + return -EINVAL; + } + + gvec =3D intr_mgt->func_intr_res[func_id].interrupts[vector_id]; + hw_ops->set_mailbox_irq(res_mgt->hw_ops_tbl->priv, func_id, + en_msix, gvec); + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + + return 0; +} + +/* + * Internal (unlocked) MSI-X map teardown prepare phase: only hardware + * register operations. The DMA address is retained and only the VALID + * bit is cleared; zeroing the address (Stage 2) is deferred to the + * complete phase after the hardware-DMA quiesce window. + * + * Caller must hold intr_mgt->lock. + */ +static int +__nbl_res_intr_prepare_destroy_msix_map(struct nbl_resource_mgt *res_mgt, + u16 func) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct nbl_func_interrupt_resource_mng *func_res; + u16 *interrupts; + u16 intr_num, i; + int ret; + + lockdep_assert_held(&intr_mgt->lock); + + if (func >=3D NBL_MAX_FUNC) { + dev_err(res_mgt->common->dev, "Invalid func_id %u\n", func); + return -EINVAL; + } + + nbl_intr_sweep_retired_locked(res_mgt); + + func_res =3D &intr_mgt->func_intr_res[func]; + if (func_res->state !=3D NBL_INTR_FUNC_CONFIGURED) + return 0; + + interrupts =3D func_res->interrupts; + intr_num =3D func_res->num_interrupts; + + /* Step 0: disable mailbox IRQ routing before tearing down map */ + ret =3D __nbl_res_intr_set_mailbox_irq(res_mgt, func, 0, false); + if (ret) { + dev_err(res_mgt->common->dev, + "disable mailbox irq failed, func=3D%u ret=3D%d\n", + func, ret); + return ret; + } + + /* Step 1: invalidate each MSIX info entry in hardware first */ + for (i =3D 0; i < intr_num; i++) { + hw_ops->cfg_msix_info(res_mgt->hw_ops_tbl->priv, + func, false, interrupts[i], + 0, 0, 0, false); + } + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + + /* + * Stage 1: retain the DMA address, only clear the VALID bit. + * Stage 2 runs after the quiesce window in the complete phase. + */ + hw_ops->cfg_msix_map(res_mgt->hw_ops_tbl->priv, func, + false, func_res->msix_map_table.dma, + 0, 0, 0); + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + + func_res->state =3D NBL_INTR_FUNC_DESTROYING; + + return 0; +} + +/* + * __nbl_res_intr_complete_destroy_msix_map - finish hardware teardown and + * release vector bitmap, DMA memory and interrupt buffer after the + * hardware quiesce window has elapsed. + * + * Caller must hold intr_mgt->lock. + */ +static int +__nbl_res_intr_complete_destroy_msix_map(struct nbl_resource_mgt *res_mgt, + u16 func_id) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct nbl_func_interrupt_resource_mng *func_res; + struct nbl_msix_map_table *msix_map_table; + struct device *dev =3D res_mgt->common->dev; + u16 *interrupts; + u16 intr_num; + + lockdep_assert_held(&intr_mgt->lock); + + if (func_id >=3D NBL_MAX_FUNC) { + dev_err(dev, "Invalid func_id %u\n", func_id); + return -EINVAL; + } + + func_res =3D &intr_mgt->func_intr_res[func_id]; + if (func_res->state !=3D NBL_INTR_FUNC_DESTROYING) + return 0; + + /* + * Stage 2: the quiesce window has elapsed, it is now safe to + * zero the DMA base address in the hardware map register. + */ + hw_ops->cfg_msix_map(res_mgt->hw_ops_tbl->priv, func_id, + false, 0, 0, 0, 0); + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + + interrupts =3D func_res->interrupts; + intr_num =3D func_res->num_interrupts; + msix_map_table =3D &func_res->msix_map_table; + + if (interrupts) { + nbl_intr_release_bitmap(res_mgt, interrupts, intr_num); + kfree(interrupts); + } + + /* + * Release the coherent table independently of interrupts so a + * partially built config (table allocated, vectors never + * published) cannot leak coherent DMA memory. + */ + if (msix_map_table->base_addr) { + dma_free_coherent(dev, msix_map_table->size, + msix_map_table->base_addr, + msix_map_table->dma); + msix_map_table->base_addr =3D NULL; + msix_map_table->dma =3D 0; + msix_map_table->size =3D 0; + } + + func_res->interrupts =3D NULL; + func_res->num_interrupts =3D 0; + func_res->num_net_interrupts =3D 0; + func_res->state =3D NBL_INTR_FUNC_IDLE; + + return 0; +} + +/* + * Internal (unlocked) MSI-X map teardown. Caller must hold + * intr_mgt->lock on entry. + * This is used for single function synchronous destroy path. + */ +static int __nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mg= t, + u16 func_id) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + int ret; + + lockdep_assert_held(&intr_mgt->lock); + + ret =3D __nbl_res_intr_prepare_destroy_msix_map(res_mgt, func_id); + if (ret) + return ret; + mutex_unlock(&intr_mgt->lock); + usleep_range(NBL_MSIX_DMA_SYNC_MIN_US, NBL_MSIX_DMA_SYNC_MAX_US); + mutex_lock(&intr_mgt->lock); + + return __nbl_res_intr_complete_destroy_msix_map(res_mgt, func_id); +} + +int nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mgt, + u16 func_id) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + int ret; + + if (!intr_mgt) + return -EINVAL; + + mutex_lock(&intr_mgt->lock); + ret =3D __nbl_res_intr_destroy_msix_map(res_mgt, func_id); + mutex_unlock(&intr_mgt->lock); + + return ret; +} + +/** + * nbl_res_intr_cfg_msix_map - allocate & program MSI-X mapping table + * @res_mgt: resource management instance + * @func_id: target function identifier + * @num_net_msix: required net data interrupt vectors + * @num_others_msix: required control interrupt vectors + * @net_msix_mask_en: enable mask for net interrupt entries + * + * Allocate interrupt vectors; MSIX coherent DMA table is allocated once + * per function on first configuration, entries are updated in-place on + * subsequent reconfigurations. No free/realloc of DMA table on vector + * count changes. This removes the DMA table free/realloc cycle and the + * per-reconfig timed sleep. Vectors removed by a reconfiguration are + * deferred to the retired list until the hardware pipeline has quiesced. + * + * Serialization: this function takes intr_mgt->lock internally to + * protect the global vector bitmaps and per-function state against + * concurrent callers. + * + * Return: 0 on success, negative errno on failure + */ +int nbl_res_intr_cfg_msix_map(struct nbl_resource_mgt *res_mgt, + u16 func_id, u16 num_net_msix, + u16 num_others_msix, + bool net_msix_mask_en) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct nbl_common_info *common =3D res_mgt->common; + struct nbl_msix_map_table *official_tbl; + struct nbl_msix_map *msix_map_entries; + struct device *dev =3D common->dev; + u16 requested, intr_index; + u8 bus, devid, function; + bool entry_masked =3D false; + u16 *tmp_interrupts =3D NULL; + u16 allocated_cnt =3D 0; + u16 *old_interrupts; + u16 old_num; + bool had_config; + int ret =3D 0; + u16 gvec; + u16 i, j; + + if (!intr_mgt) + return -EINVAL; + + if (!common->has_ctrl) + return -EINVAL; + + if (func_id >=3D NBL_MAX_FUNC) { + dev_err(dev, "Invalid func_id %u\n", func_id); + return -EINVAL; + } + + if (num_net_msix =3D=3D 0 && num_others_msix =3D=3D 0) { + dev_err(dev, "MSI-X vector count cannot both be zero\n"); + return -EINVAL; + } + + if (num_net_msix > NBL_MSIX_MAP_TABLE_MAX_ENTRIES || + num_others_msix > NBL_MSIX_MAP_TABLE_MAX_ENTRIES) { + dev_err(dev, "MSI-X count out of limit: net=3D%u, others=3D%u\n", + num_net_msix, num_others_msix); + return -EINVAL; + } + + if (check_add_overflow(num_net_msix, num_others_msix, &requested) || + requested > NBL_MSIX_MAP_TABLE_MAX_ENTRIES) { + dev_err(dev, "Total MSI-X vectors %u exceeds maximum %u\n", + requested, NBL_MSIX_MAP_TABLE_MAX_ENTRIES); + return -EINVAL; + } + + ret =3D nbl_res_func_id_to_bdf(res_mgt, func_id, &bus, &devid, &function); + if (ret) { + if (ret =3D=3D -EOPNOTSUPP) + dev_err(dev, + "MSI-X mapping for VF func_id=3D%u is not supported\n", + func_id); + return ret; + } + + mutex_lock(&intr_mgt->lock); + official_tbl =3D &intr_mgt->func_intr_res[func_id].msix_map_table; + + /* Reject new configs during teardown or while func is mid-destroy */ + if (intr_mgt->stopping) { + ret =3D -ESHUTDOWN; + goto out_unlock; + } + if (intr_mgt->func_intr_res[func_id].state =3D=3D + NBL_INTR_FUNC_DESTROYING) { + ret =3D -EBUSY; + goto out_unlock; + } + + nbl_intr_sweep_retired_locked(res_mgt); + had_config =3D intr_mgt->func_intr_res[func_id].state =3D=3D + NBL_INTR_FUNC_CONFIGURED; + + /* + * Phase1: allocate global vector array first. + * Allocate the fixed-size MSIX DMA table only ONCE for this function. + */ + tmp_interrupts =3D kcalloc(requested, sizeof(*tmp_interrupts), + GFP_KERNEL); + if (!tmp_interrupts) { + ret =3D -ENOMEM; + goto out_unlock; + } + /* Allocate MSIX DMA table once per function */ + if (!official_tbl->base_addr) { + official_tbl->size =3D + sizeof(struct nbl_msix_map) * + NBL_MSIX_MAP_TABLE_MAX_ENTRIES; + official_tbl->base_addr =3D dma_alloc_coherent(dev, + official_tbl->size, + &official_tbl->dma, + GFP_KERNEL); + if (!official_tbl->base_addr) { + dev_err(dev, "Failed to allocate DMA memory for MSIX table\n"); + ret =3D -ENOMEM; + goto release_vecs_unlock; + } + } + + /* Allocate net interrupt vectors */ + for (i =3D 0; i < num_net_msix; i++) { + intr_index =3D find_first_zero_bit(intr_mgt->intr_net_bmap, + NBL_MAX_NET_INTERRUPT); + if (intr_index =3D=3D NBL_MAX_NET_INTERRUPT) { + dev_err(dev, "No free net interrupt vectors left\n"); + ret =3D -EAGAIN; + goto release_vecs_unlock; + } + tmp_interrupts[i] =3D intr_index + NBL_NET_INTR_BASE; + set_bit(intr_index, intr_mgt->intr_net_bmap); + allocated_cnt++; + } + + /* Allocate other interrupt vectors */ + for (; i < requested; i++) { + intr_index =3D + find_first_zero_bit(intr_mgt->intr_other_bmap, + NBL_MAX_OTHER_INTERRUPT); + if (intr_index =3D=3D NBL_MAX_OTHER_INTERRUPT) { + dev_err(dev, "No free control interrupt vectors left\n"); + ret =3D -EAGAIN; + goto release_vecs_unlock; + } + tmp_interrupts[i] =3D intr_index; + set_bit(intr_index, intr_mgt->intr_other_bmap); + allocated_cnt++; + } + + /* + * Phase2: tear down old hardware MSIX config. Old vectors are + * moved to the retired list and recycled only after the + * hardware pipeline has quiesced. + * NOTE: NO DMA table free here, only invalidate HW entries. + */ + if (intr_mgt->func_intr_res[func_id].interrupts) { + old_interrupts =3D intr_mgt->func_intr_res[func_id].interrupts; + old_num =3D intr_mgt->func_intr_res[func_id].num_interrupts; + + ret =3D __nbl_res_intr_set_mailbox_irq(res_mgt, func_id, 0, + false); + if (ret) { + dev_err(dev, "%s: disable old mailbox irq failed, keep old config\n", + __func__); + goto release_vecs_unlock; + } + for (j =3D 0; j < old_num; j++) { + hw_ops->cfg_msix_info(res_mgt->hw_ops_tbl->priv, + func_id, false, old_interrupts[j], + 0, 0, 0, false); + } + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + nbl_intr_retire_vectors_locked(res_mgt, old_interrupts, + old_num); + intr_mgt->func_intr_res[func_id].interrupts =3D NULL; + intr_mgt->func_intr_res[func_id].num_interrupts =3D 0; + intr_mgt->func_intr_res[func_id].num_net_interrupts =3D 0; + } + + /* Swap new vector array into func state */ + intr_mgt->func_intr_res[func_id].interrupts =3D tmp_interrupts; + intr_mgt->func_intr_res[func_id].num_interrupts =3D requested; + intr_mgt->func_intr_res[func_id].num_net_interrupts =3D num_net_msix; + tmp_interrupts =3D NULL; + + /* + * Fill MSIX map table IN-PLACE in the pre-allocated DMA buffer. + * Only zero entries beyond requested count to avoid full table + * memset overhead. + */ + msix_map_entries =3D official_tbl->base_addr; + memset(msix_map_entries + requested, 0, + (NBL_MSIX_MAP_TABLE_MAX_ENTRIES - requested) * + sizeof(*msix_map_entries)); + + for (i =3D 0; i < requested; i++) { + gvec =3D intr_mgt->func_intr_res[func_id].interrupts[i]; + msix_map_entries[i].data =3D + cpu_to_le16(FIELD_PREP(NBL_MSIX_MAP_VALID_MASK, 1) | + FIELD_PREP(NBL_MSIX_MAP_INDEX_MASK, + gvec)); + entry_masked =3D (i < num_net_msix && net_msix_mask_en); + hw_ops->cfg_msix_info(res_mgt->hw_ops_tbl->priv, + func_id, true, gvec, + bus, devid, function, + entry_masked); + } + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + + /* Ensure coherent memory writes are visible before HW DMA fetch */ + dma_wmb(); + + /* + * cfg_msix_map uses the control PF's own BDF (common->hw_bus etc.), + * not the target function's BDF. This BDF tags the pcompler DMA + * read of the MSI-X map table as originating from the control PF. + * The target function's BDF (bus/devid/function from + * nbl_res_func_id_to_bdf) is used only in cfg_msix_info for the + * host_msix_ctrl table entry BDF filtering. + */ + hw_ops->cfg_msix_map(res_mgt->hw_ops_tbl->priv, func_id, + true, official_tbl->dma, common->hw_bus, + common->devid, common->function); + hw_ops->flush_write(res_mgt->hw_ops_tbl->priv); + + intr_mgt->func_intr_res[func_id].state =3D NBL_INTR_FUNC_CONFIGURED; + mutex_unlock(&intr_mgt->lock); + return 0; + +release_vecs_unlock: + nbl_intr_release_bitmap(res_mgt, tmp_interrupts, allocated_cnt); + kfree(tmp_interrupts); + /* + * On a failed fresh configuration, release the DMA table + * allocated during this call. On a failed reconfiguration the + * old configuration is still intact (it is only torn down + * after all vector allocations succeed) and owns the table. + */ + if (!had_config && official_tbl->base_addr) { + dma_free_coherent(dev, official_tbl->size, + official_tbl->base_addr, + official_tbl->dma); + official_tbl->base_addr =3D NULL; + official_tbl->dma =3D 0; + official_tbl->size =3D 0; + } +out_unlock: + mutex_unlock(&intr_mgt->lock); + return ret; +} + +/** + * nbl_res_intr_set_mailbox_irq - bind mailbox IRQ to specified vector + * @res_mgt: resource management instance + * @func_id: target function identifier + * @vector_id: index inside local interrupt array + * @en_msix: enable/disable mailbox interrupt + * + * Serialization: takes intr_mgt->lock internally. + * + * Return: 0 on success, negative errno on parameter or state check + * failure. The hardware op is void and cannot report failure. + */ +int nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt, + u16 func_id, u16 vector_id, + bool en_msix) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + struct nbl_common_info *common =3D res_mgt->common; + int ret; + + if (!intr_mgt) + return -EINVAL; + + if (!common->has_ctrl) + return -EINVAL; + + mutex_lock(&intr_mgt->lock); + ret =3D __nbl_res_intr_set_mailbox_irq(res_mgt, func_id, + vector_id, en_msix); + mutex_unlock(&intr_mgt->lock); + + return ret; +} + +static struct nbl_interrupt_mgt *nbl_intr_setup_mgt(struct device *dev) +{ + struct nbl_interrupt_mgt *intr_mgt; + int err; + + intr_mgt =3D devm_kzalloc(dev, sizeof(*intr_mgt), GFP_KERNEL); + if (!intr_mgt) + return ERR_PTR(-ENOMEM); + + err =3D devm_mutex_init(dev, &intr_mgt->lock); + if (err) + return ERR_PTR(err); + + INIT_LIST_HEAD(&intr_mgt->retired_list); + intr_mgt->stopping =3D false; + bitmap_zero(intr_mgt->intr_net_bmap, NBL_MAX_NET_INTERRUPT); + bitmap_zero(intr_mgt->intr_other_bmap, NBL_MAX_OTHER_INTERRUPT); + + return intr_mgt; +} + +int nbl_intr_mgt_start(struct nbl_resource_mgt *res_mgt) +{ + struct device *dev =3D res_mgt->common->dev; + struct nbl_interrupt_mgt *intr_mgt; + int ret; + + intr_mgt =3D nbl_intr_setup_mgt(dev); + if (IS_ERR(intr_mgt)) { + ret =3D PTR_ERR(intr_mgt); + return ret; + } + + res_mgt->intr_mgt =3D intr_mgt; + return 0; +} + +void nbl_intr_mgt_stop(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_interrupt_mgt *intr_mgt =3D res_mgt->intr_mgt; + u16 func_id; + int ret; + + if (!intr_mgt) + return; + + /* + * Phase 1: batch invalidate all hardware MSIX map entries. + * stopping is set under the lock so any configuration racing + * with the unlocked quiesce window below fails with -ESHUTDOWN + * instead of installing a map that Phase 2 would free. + */ + mutex_lock(&intr_mgt->lock); + intr_mgt->stopping =3D true; + for (func_id =3D 0; func_id < NBL_MAX_FUNC; func_id++) { + if (intr_mgt->func_intr_res[func_id].state =3D=3D + NBL_INTR_FUNC_CONFIGURED) { + dev_info(res_mgt->common->dev, + "intr_mgt_stop: preparing destroy map for func %u\n", + func_id); + ret =3D __nbl_res_intr_prepare_destroy_msix_map(res_mgt, + func_id); + if (ret) + dev_warn(res_mgt->common->dev, + "intr_mgt_stop: prepare destroy map for func %u failed: %d\n", + func_id, ret); + } + } + mutex_unlock(&intr_mgt->lock); + + /* + * Global quiesce: wait for straggler DMA table reads after all + * MSIX map entries have been invalidated in hardware, before + * freeing coherent memory. Best-effort only. + */ + usleep_range(NBL_MSIX_DMA_SYNC_MIN_US, NBL_MSIX_DMA_SYNC_MAX_US); + + /* Phase2: safely release MSIX coherent memory and intr resources */ + mutex_lock(&intr_mgt->lock); + nbl_intr_drain_retired_locked(res_mgt); + for (func_id =3D 0; func_id < NBL_MAX_FUNC; func_id++) { + if (intr_mgt->func_intr_res[func_id].state =3D=3D + NBL_INTR_FUNC_DESTROYING) { + ret =3D __nbl_res_intr_complete_destroy_msix_map(res_mgt, + func_id); + if (ret) + dev_warn(res_mgt->common->dev, + "intr_mgt_stop: complete destroy map for func %u failed: %d\n", + func_id, ret); + } + } + mutex_unlock(&intr_mgt->lock); + + res_mgt->intr_mgt =3D NULL; +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h = b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h new file mode 100644 index 000000000000..9f66f5e19c98 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_interrupt.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_INTERRUPT_H_ +#define _NBL_INTERRUPT_H_ + +#include "nbl_resource.h" + +#define NBL_MSIX_MAP_TABLE_MAX_ENTRIES 1024 +int nbl_res_intr_destroy_msix_map(struct nbl_resource_mgt *res_mgt, + u16 func_id); +int nbl_res_intr_cfg_msix_map(struct nbl_resource_mgt *res_mgt, + u16 func_id, u16 num_net_msix, + u16 num_others_msix, + bool net_msix_mask_en); +int nbl_res_intr_set_mailbox_irq(struct nbl_resource_mgt *res_mgt, + u16 func_id, u16 vector_id, + bool en_msix); +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c b= /drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c index b316fb8e7051..635f34312c56 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.c @@ -68,6 +68,38 @@ int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res= _mgt, u16 vsi_id) return -ENOENT; } =20 +int nbl_res_func_id_to_bdf(struct nbl_resource_mgt *res_mgt, u16 func_id, + u8 *bus, u8 *dev, u8 *function) +{ + struct nbl_common_info *common =3D res_mgt->common; + struct nbl_sriov_info *sriov_info; + int pfid =3D func_id; + u8 pf_bus, devfn; + u32 rel_pf_id; + int ret; + + if (!common->has_ctrl || !bus || !dev || !function) + return -EINVAL; + ret =3D nbl_common_func_id_to_rel_pf_id(common, pfid, &rel_pf_id); + if (ret) + return ret; + if (rel_pf_id >=3D common->max_pf) { + dev_err(common->dev, + "func_id=3D%u rel_pf_id=3D%u exceeds max_pf=3D%u, VF BDF unsupported\n", + pfid, rel_pf_id, + common->max_pf); + return -EOPNOTSUPP; + } + sriov_info =3D res_mgt->resource_info->sriov_info + rel_pf_id; + pf_bus =3D PCI_BUS_NUM(sriov_info->bdf); + devfn =3D sriov_info->bdf & 0xff; + *bus =3D pf_bus; + *dev =3D PCI_SLOT(devfn); + *function =3D PCI_FUNC(devfn); + + return 0; +} + int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id, u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id) { diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h b= /drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h index ae0a3d33198d..c69d6f2ca321 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_resource.h @@ -7,6 +7,7 @@ #define _NBL_RESOURCE_H_ =20 #include +#include =20 #include "../nbl_include/nbl_include.h" #include "../nbl_include/nbl_def_channel.h" @@ -17,6 +18,66 @@ =20 struct nbl_resource_mgt; =20 +/* --------- INTERRUPT ---------- */ +#define NBL_MAX_OTHER_INTERRUPT 1024 +#define NBL_MAX_NET_INTERRUPT 4096 +#define NBL_NET_INTR_BASE NBL_MAX_OTHER_INTERRUPT + +#define NBL_MSIX_MAP_VALID_MASK BIT(0) +#define NBL_MSIX_MAP_INDEX_MASK GENMASK(13, 1) +#define NBL_MSIX_MAP_RSV_MASK GENMASK(15, 14) + +struct nbl_msix_map { + __le16 data; +}; + +struct nbl_msix_map_table { + struct nbl_msix_map *base_addr; + dma_addr_t dma; + size_t size; +}; + +/* + * Per-function MSI-X resource state. The DESTROYING state spans the + * unlocked hardware-DMA quiesce window between prepare and complete so + * a concurrent configuration cannot install a map that the in-flight + * teardown would free. + */ +enum nbl_intr_func_state { + NBL_INTR_FUNC_IDLE =3D 0, + NBL_INTR_FUNC_CONFIGURED, + NBL_INTR_FUNC_DESTROYING, +}; + +struct nbl_func_interrupt_resource_mng { + u16 num_interrupts; + u16 num_net_interrupts; + u16 *interrupts; + struct nbl_msix_map_table msix_map_table; + u8 state; /* enum nbl_intr_func_state */ +}; + +/* + * Vectors removed from a reconfigured function are not recycled + * immediately: they stay on the retired list until the hardware + * interrupt/DMA pipeline has had time to quiesce. + */ +struct nbl_intr_retired_vectors { + struct list_head node; + unsigned long expires; /* jiffies at which recycling is safe */ + u16 *vectors; + u16 cnt; +}; + +struct nbl_interrupt_mgt { + struct mutex lock; /* Protects bitmap + func_intr_res[] */ + DECLARE_BITMAP(intr_net_bmap, NBL_MAX_NET_INTERRUPT); + DECLARE_BITMAP(intr_other_bmap, NBL_MAX_OTHER_INTERRUPT); + struct list_head retired_list; + bool stopping; /* set on teardown, rejects new configurations */ + struct nbl_func_interrupt_resource_mng func_intr_res[NBL_MAX_FUNC]; +}; + /* --------- INFO ---------- */ struct nbl_sriov_info { unsigned int bdf; @@ -56,14 +117,19 @@ struct nbl_resource_mgt { struct nbl_resource_info *resource_info; struct nbl_channel_ops_tbl *chan_ops_tbl; struct nbl_hw_ops_tbl *hw_ops_tbl; + struct nbl_interrupt_mgt *intr_mgt; }; =20 int nbl_res_vsi_id_to_pf_id(struct nbl_resource_mgt *res_mgt, u16 vsi_id); int nbl_res_func_id_to_vsi_id(struct nbl_resource_mgt *res_mgt, u16 func_i= d, u16 type, u16 *vsi_id); +int nbl_res_func_id_to_bdf(struct nbl_resource_mgt *res_mgt, u16 func_id, + u8 *bus, u8 *dev, u8 *function); int nbl_res_get_eth_id(struct nbl_resource_mgt *res_mgt, u16 func_id, u16 vsi_id, u8 *eth_num, u8 *eth_id, u8 *logic_eth_id); +int nbl_intr_mgt_start(struct nbl_resource_mgt *res_mgt); int nbl_res_pf_dev_vsi_type_to_hw_vsi_type(struct nbl_resource_mgt *res_mg= t, u16 src_type, enum nbl_vsi_serv_type *dst_type); +void nbl_intr_mgt_stop(struct nbl_resource_mgt *res_mgt); #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.= h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h index 095ff548c4a7..b9b92c42f276 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h @@ -12,6 +12,13 @@ struct nbl_board_port_info; struct nbl_hw_mgt; struct nbl_adapter; struct nbl_hw_ops { + void (*cfg_msix_map)(struct nbl_hw_mgt *hw_mgt, u16 func_id, + bool valid, dma_addr_t dma_addr, u8 bus, + u8 devid, u8 function); + void (*cfg_msix_info)(struct nbl_hw_mgt *hw_mgt, u16 func_id, + bool valid, u16 interrupt_id, u8 bus, + u8 devid, u8 function, + bool net_msix_mask_en); void (*flush_write)(struct nbl_hw_mgt *hw_mgt); void (*update_mailbox_queue_tail_ptr)(struct nbl_hw_mgt *hw_mgt, u16 tail_ptr, u8 txrx); @@ -45,6 +52,8 @@ struct nbl_hw_ops { =20 void (*cfg_mailbox_qinfo)(struct nbl_hw_mgt *hw_mgt, u16 func_id, u8 bus, u8 devid, u8 function); + void (*set_mailbox_irq)(struct nbl_hw_mgt *hw_mgt, u16 func_id, + bool en_msix, u16 gvec); void (*get_fw_eth_map)(struct nbl_hw_mgt *hw_mgt, u32 *eth_map); /** * get_board_info - Fetch board info from firmware diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_res= ource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resour= ce.h index 7136b282fb80..e718ea41a816 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h @@ -12,6 +12,12 @@ struct nbl_resource_mgt; struct nbl_adapter; =20 struct nbl_resource_ops { + int (*cfg_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id, + u16 num_net_msix, u16 num_others_msix, + bool net_msix_mask_en); + int (*destroy_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id); + int (*set_mailbox_irq)(struct nbl_resource_mgt *res_mgt, u16 func_id, + u16 vector_id, bool en_msix); int (*get_vsi_id)(struct nbl_resource_mgt *res_mgt, u16 func_id, u16 type, u16 *vsi_id); int (*get_eth_id)(struct nbl_resource_mgt *res_mgt, u16 func_id, diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h index 59e44feab44f..2c959832c32f 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h @@ -13,6 +13,7 @@ #define NBL_MAX_PF 8 #define NBL_NEXT_ID(id, max) (((id) + 1) % ((max) + 1)) =20 +#define NBL_MAX_FUNC 520 #define NBL_MAX_ETHERNET 4 =20 enum { --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-2.mail.aliyun.com (out28-2.mail.aliyun.com [115.124.28.2]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 25A3B47AF56; Tue, 22 Sep 2026 12:03:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078615; cv=none; b=ua2/Hc8PfBZVOULUFBuBBQOee6wlWJmtuxVhcM2pSPDyuFpS0dhJZKf51rMV773CDRy/uFeig7sPvqGidk2SZ/aSj42Xn5qxjdH6QD2DNr6P6RQvGOQ8QKGRVbhi/rZMb12UoqKjNtGeB1oTCMRUVv3DuHiY3oufWTvV7nysIt4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078615; c=relaxed/simple; bh=I/Qhem4ktGtu18KdPjKWyNH1V6DFJZjIlCjNKQzNANk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=S9MAkhDC7bjgMNgArTzEl4bYThDtrblnj99Hu77w842EXqAqEsUfC85sqf8sIrqPZC+IATJoK1NQ/m6ORdDCLLFvqgQ2czGbvMZYjE9xJga+ttr3Kk66f2d8seUebJmQwA5kVejct8pIT6VwSugXYASqTFwhbniWL1ZP0jCMkic= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.2 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.0025803-0.000603671-0.996816;FP=17535847315963206290|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033037071049;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPT1_1790078597; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPT1_1790078597 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:18 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 4/8] net/nebula-matrix: add chip-wide hardware init/deinit implementation Date: Tue, 22 Sep 2026 20:03:01 +0800 Message-ID: <20260922120311.86593-5-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: illusion wang Add Leonis chip-wide hardware init/deinit logic for Nebula Matrix Ethernet = driver, providing full datapath pipeline configuration and orderly chip-level resou= rce teardown infrastructure. This patch implements complete chip hardware lifecycle management to initia= lize core datapath components including packet engines, scheduler, buffer stores, queue manager, per-port traffic shaping and flow control. It also maintains the firmware-synced driver_status flag to coordinate driver-firmware state. Core framework enhancements: 1. Add hw_ops.init_module/deinit_module and resource_ops entry points for chip-level hardware lifecycle management. Callback invocation path will be added in a subsequent patch, only ops registration here. 2. Implement PCI domain/bus/slot based chip-wide PF registry to track active driver-bound PFs, preventing premature chip-global firmware deinitializatio= n. 3. Add device link dependency for non-management PFs to enforce determinist= ic teardown order, ensuring consumer PFs release before management PF (func 0). Datapath sub-module initialization covers all core hardware units: - dped/uped: Configure packet engine registers, checksum offload and TCP pr= ofiles - dsch: Setup scheduler VN quanta and host QID maximum threshold - ustore/dstore: Tune Tx/Rx buffer thresholds, drop control and flow control - dvn/uvn: Configure descriptor timeout, PCIe RO and prefetch with firmware= quirks - uqm: Reset queue counters and initialize queue working mode - shaping: Apply port-speed tailored rate shaping and PSHA scheduler config Complete chip initialization flow: 1. Initialize all datapath sub-modules via unified nbl_dp_init() entry 2. Configure host adapter PADPT layer flow control via nbl_intf_init() 3. Set driver_status active flag and flush all hardware register writes Key safety and design guarantees: - Restrict all chip-global operations exclusively to management PF via has_= ctrl - Only driver register programming; full chip reset is managed by firmware - Explicit register flushing ensures consistency with async firmware cleanup - Strict teardown ordering blocks new DMA and in-flight mailbox transactions - Support UVN descriptor prefetch alignment firmware quirk optimization Deinit behavior note: Chip-wide datapath registers are not auto-restored by firmware on deinit. The deinit path only clears driver_status and flushes writes, retaining hardware state until chip reset or explicit overwriting. Add corresponding register definitions, firmware ABI enums, helper routines, resource callbacks and Makefile entries to wire up the new chip management. Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/Makefile | 1 + .../nebula-matrix/nbl/nbl_common/nbl_common.c | 1 - .../nebula-matrix/nbl/nbl_hw/nbl_chip.c | 32 + .../nebula-matrix/nbl/nbl_hw/nbl_chip.h | 12 + .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.c | 633 +++++++++++++++++- .../nbl/nbl_hw/nbl_hw_leonis/nbl_hw_leonis.h | 222 ++++++ .../nbl_hw_leonis/nbl_resource_leonis.c | 5 +- .../nbl_hw_leonis/nbl_resource_leonis.h | 1 + .../nbl/nbl_include/nbl_def_common.h | 1 - .../nbl/nbl_include/nbl_def_hw.h | 3 + .../nbl/nbl_include/nbl_def_resource.h | 3 + .../nbl/nbl_include/nbl_include.h | 19 + .../net/ethernet/nebula-matrix/nbl/nbl_main.c | 56 ++ 13 files changed, 985 insertions(+), 4 deletions(-) create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/= ethernet/nebula-matrix/nbl/Makefile index 5aec8e44f5d7..be314b909d66 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/Makefile +++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile @@ -9,4 +9,5 @@ nbl-objs +=3D nbl_common/nbl_common.o \ nbl_hw/nbl_hw_leonis/nbl_resource_leonis.o \ nbl_hw/nbl_resource.o \ nbl_hw/nbl_interrupt.o \ + nbl_hw/nbl_chip.o \ nbl_main.o diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c index 7c664195db7f..302c3c48dc4d 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_common/nbl_common.c @@ -50,4 +50,3 @@ int nbl_common_func_id_to_rel_pf_id(struct nbl_common_inf= o *common, u32 pf_id, *rel_pf_id =3D pf_id - common->mgt_pf; return 0; } - diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c b/dri= vers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c new file mode 100644 index 000000000000..419eb6392ada --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ +#include +#include "nbl_chip.h" + +void nbl_res_chip_deinit_module(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_hw_ops *hw_ops =3D res_mgt->hw_ops_tbl->ops; + struct nbl_common_info *common =3D res_mgt->common; + + if (!common->has_ctrl) + return; + hw_ops->deinit_module(res_mgt->hw_ops_tbl->priv); +} + +int nbl_res_chip_init_module(struct nbl_resource_mgt *res_mgt) +{ + struct nbl_common_info *common =3D res_mgt->common; + struct nbl_hw_ops *hw_ops; + u8 eth_speed, eth_num; + struct nbl_hw_mgt *p; + + if (!common->has_ctrl) + return -EINVAL; + eth_speed =3D res_mgt->resource_info->board_info.eth_speed; + eth_num =3D res_mgt->resource_info->board_info.eth_num; + hw_ops =3D res_mgt->hw_ops_tbl->ops; + p =3D res_mgt->hw_ops_tbl->priv; + return hw_ops->init_module(p, eth_speed, eth_num); +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h b/dri= vers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h new file mode 100644 index 000000000000..d14093ba916c --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_chip.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_CHIP_H_ +#define _NBL_CHIP_H_ + +#include "nbl_resource.h" +int nbl_res_chip_init_module(struct nbl_resource_mgt *res_mgt); +void nbl_res_chip_deinit_module(struct nbl_resource_mgt *res_mgt); +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.c index 457e0212a7bf..c19c60c9dd99 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.c @@ -144,6 +144,633 @@ static void nbl_hw_get_fw_eth_map(struct nbl_hw_mgt *= hw_mgt, u32 *eth_map) *eth_map =3D FIELD_GET(NBL_FW_BOARD_DW6_ETH_BITMAP_MASK, data); } =20 +static u32 nbl_hw_get_quirks(struct nbl_hw_mgt *hw_mgt) +{ + u32 quirks =3D 0; + + /* + * Read quirk bits from mailbox register. + * All supported firmware implement the quirk ABI, + * firmware always populates NBL_LEONIS_QUIRKS_OFFSET. + * Value ~0U indicates no active quirks. + */ + nbl_hw_read_mbx_regs(hw_mgt, NBL_LEONIS_QUIRKS_OFFSET, &quirks, + sizeof(u32)); + + if (quirks =3D=3D ~0u) + return 0; + + return quirks; +} + +static void nbl_configure_dped_checksum(struct nbl_hw_mgt *hw_mgt) +{ + u32 data =3D 0; + + /* DPED dped_l4_ck_cmd_40 for sctp */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_DPED_L4_CK_CMD_40_ADDR, &data, sizeof(data)); + data |=3D FIELD_PREP(NBL_DPED_L4_CK_CMD_40_EN_MASK, 1); + nbl_hw_wr_regs(hw_mgt, NBL_DPED_L4_CK_CMD_40_ADDR, &data, sizeof(data)); + spin_unlock(&hw_mgt->reg_lock); +} + +static void nbl_dped_init(struct nbl_hw_mgt *hw_mgt) +{ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_wr32(hw_mgt, NBL_DPED_VLAN_OFFSET, 0xC); + nbl_hw_wr32(hw_mgt, NBL_DPED_DSCP_OFFSET_0, 0x8); + nbl_hw_wr32(hw_mgt, NBL_DPED_DSCP_OFFSET_1, 0x4); + spin_unlock(&hw_mgt->reg_lock); + /* dped checksum offload */ + nbl_configure_dped_checksum(hw_mgt); +} + +static void nbl_uped_init(struct nbl_hw_mgt *hw_mgt) +{ + u32 hw_edit =3D 0; + + /* V4 TCP: l3_len =3D 0 */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V4_TCP_IDX), + &hw_edit, sizeof(hw_edit)); + hw_edit &=3D ~NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK; + nbl_hw_wr_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V4_TCP_IDX), + &hw_edit, sizeof(hw_edit)); + + /* V6 TCP: l3_len =3D 1 */ + nbl_hw_rd_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V6_TCP_IDX), + &hw_edit, sizeof(hw_edit)); + hw_edit =3D (hw_edit & ~NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK) | + FIELD_PREP(NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK, 1); + nbl_hw_wr_regs(hw_mgt, NBL_UPED_HW_EDT_PROF_TABLE(NBL_UPED_V6_TCP_IDX), + &hw_edit, sizeof(hw_edit)); + spin_unlock(&hw_mgt->reg_lock); +} + +static int nbl_shaping_eth_init(struct nbl_hw_mgt *hw_mgt, u8 eth_id, u8 s= peed) +{ + struct nbl_shaping_dvn_dport_u dvn_dport =3D { 0 }; + struct nbl_shaping_dport_u dport =3D { 0 }; + u32 rate, half_rate; + u32 depth; + u64 low_val, high_val; + + switch (speed) { + case NBL_FW_PORT_SPEED_100G: + rate =3D 100000; + break; + case NBL_FW_PORT_SPEED_50G: + rate =3D 50000; + break; + case NBL_FW_PORT_SPEED_25G: + rate =3D 25000; + break; + case NBL_FW_PORT_SPEED_10G: + rate =3D 10000; + break; + default: + dev_err(hw_mgt->common->dev, + "Unsupported port speed %u for eth%u\n", speed, eth_id); + return -EINVAL; + } + + half_rate =3D rate / 2; + depth =3D max_t(u32, rate * 2, NBL_LR_LEONIS_NET_BUCKET_DEPTH); + + /* 1. clear valid first + * dport and dvn_dport are zero-initialised above, so VALID=3D0 already + */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data, + sizeof(dport)); + nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id), + dvn_dport.data, sizeof(dvn_dport)); + + /* 2. write config words (valid=3D0, safe) */ + low_val =3D FIELD_PREP(NBL_DPORT_CIR_MASK, rate) | + FIELD_PREP(NBL_DPORT_PIR_MASK, rate) | + FIELD_PREP(NBL_DPORT_DEPTH_MASK, depth) | + FIELD_PREP(NBL_DPORT_CBS_MASK_LOW, depth & 0x3F); + high_val =3D FIELD_PREP(NBL_DPORT_CBS_MASK_HIGH, depth >> 6) | + FIELD_PREP(NBL_DPORT_PBS_MASK, depth); + /* Fixed split, independent of host endian */ + dport.data[0] =3D lower_32_bits(low_val); + dport.data[1] =3D upper_32_bits(low_val); + dport.data[2] =3D lower_32_bits(high_val); + dport.data[3] =3D upper_32_bits(high_val); + + low_val =3D FIELD_PREP(NBL_DPORT_CIR_MASK, half_rate) | + FIELD_PREP(NBL_DPORT_PIR_MASK, rate) | + FIELD_PREP(NBL_DPORT_DEPTH_MASK, depth) | + FIELD_PREP(NBL_DPORT_CBS_MASK_LOW, depth & 0x3F); + high_val =3D FIELD_PREP(NBL_DPORT_CBS_MASK_HIGH, depth >> 6) | + FIELD_PREP(NBL_DPORT_PBS_MASK, depth); + dvn_dport.data[0] =3D lower_32_bits(low_val); + dvn_dport.data[1] =3D upper_32_bits(low_val); + dvn_dport.data[2] =3D lower_32_bits(high_val); + dvn_dport.data[3] =3D upper_32_bits(high_val); + + nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data, + sizeof(dport)); + nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id), + dvn_dport.data, sizeof(dvn_dport)); + + /* 3. commit: set valid last */ + low_val =3D FIELD_PREP(NBL_DPORT_VALID_MASK, 1); + dport.data[0] |=3D lower_32_bits(low_val); + + low_val =3D FIELD_PREP(NBL_DPORT_VALID_MASK, 1); + dvn_dport.data[0] |=3D lower_32_bits(low_val); + + nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DPORT_REG(eth_id), dport.data, + sizeof(dport)); + nbl_hw_wr_regs(hw_mgt, NBL_SHAPING_DVN_DPORT_REG(eth_id), + dvn_dport.data, sizeof(dvn_dport)); + spin_unlock(&hw_mgt->reg_lock); + return 0; +} + +static int nbl_shaping_init(struct nbl_hw_mgt *hw_mgt, u8 speed) +{ +#define NBL_SHAPING_FLUSH_INTERVAL 128 + struct nbl_shaping_net_u net_shaping =3D { 0 }; + u32 eth_bitmap =3D 0; + u32 reg_val; + int ret; + int i; + + nbl_hw_get_fw_eth_map(hw_mgt, ð_bitmap); + for (i =3D 0; i < NBL_MAX_ETHERNET; i++) { + if (!(eth_bitmap & BIT(i))) + continue; + ret =3D nbl_shaping_eth_init(hw_mgt, i, speed); + if (ret) + return ret; + } + nbl_hw_rd_regs_lock(hw_mgt, NBL_DSCH_PSHA_EN_ADDR, ®_val, + sizeof(reg_val)); + reg_val &=3D ~NBL_DSCH_PSHA_EN_MASK; + reg_val |=3D FIELD_PREP(NBL_DSCH_PSHA_EN_MASK, + eth_bitmap & GENMASK(3, 0)); + nbl_hw_wr_regs_lock(hw_mgt, NBL_DSCH_PSHA_EN_ADDR, ®_val, + sizeof(reg_val)); + + for (i =3D 0; i < NBL_MAX_FUNC; i++) { + nbl_hw_wr_regs_lock(hw_mgt, NBL_SHAPING_NET_REG(i), + net_shaping.data, + sizeof(net_shaping)); + if ((i + 1) % NBL_SHAPING_FLUSH_INTERVAL =3D=3D 0) + nbl_flush_writes(hw_mgt); + } + nbl_flush_writes(hw_mgt); + return 0; +} + +static void nbl_dsch_qid_max_init(struct nbl_hw_mgt *hw_mgt) +{ + u32 quanta =3D 0; + u32 qid_max =3D 0; + + quanta =3D FIELD_PREP(NBL_DSCH_VN_QUANTA_H_QUA_MASK, NBL_HOST_QUANTA) | + FIELD_PREP(NBL_DSCH_VN_QUANTA_E_QUA_MASK, NBL_ECPU_QUANTA); + spin_lock(&hw_mgt->reg_lock); + nbl_hw_wr_regs(hw_mgt, NBL_DSCH_VN_QUANTA_ADDR, &quanta, + sizeof(quanta)); + nbl_hw_rd_regs(hw_mgt, NBL_DSCH_HOST_QID_MAX, &qid_max, + sizeof(qid_max)); + qid_max &=3D ~NBL_DSCH_HOST_QID_MAX_MASK; + qid_max |=3D FIELD_PREP(NBL_DSCH_HOST_QID_MAX_MASK, NBL_MAX_QUEUE_ID); + nbl_hw_wr_regs(hw_mgt, NBL_DSCH_HOST_QID_MAX, &qid_max, + sizeof(qid_max)); + spin_unlock(&hw_mgt->reg_lock); +} + +static int nbl_ustore_init(struct nbl_hw_mgt *hw_mgt, u8 eth_num) +{ + u32 eth_bitmap =3D 0; + u32 drop_th =3D 0; + u32 pkt_len =3D 0; + u32 reg_val =3D 0; + int i; + + /* + * eth_num is validated in the resource layer: + * nbl_res_init_pf_num() requires 1/2/4 PFs, and + * nbl_res_ctrl_dev_setup_eth_info() requires max_pf =3D=3D eth_num. + * This is a defensive check only; if it triggers, the resource + * layer validation was bypassed, which is a bug. + */ + if (WARN_ON(eth_num !=3D 1 && eth_num !=3D 2 && eth_num !=3D 4)) + return -EINVAL; + /* Read current packet length config + *(to preserve other fields while updating 'min') + */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_USTORE_PKT_LEN_ADDR, &pkt_len, + sizeof(pkt_len)); + /* min arp packet length 42 (14 + 28) */ + pkt_len &=3D ~NBL_USTORE_PKT_LEN_MIN_MASK; + pkt_len |=3D FIELD_PREP(NBL_USTORE_PKT_LEN_MIN_MASK, 42); + nbl_hw_wr_regs(hw_mgt, NBL_USTORE_PKT_LEN_ADDR, &pkt_len, + sizeof(pkt_len)); + + drop_th |=3D FIELD_PREP(NBL_USTORE_PORT_DROP_TH_EN_MASK, 1); + if (eth_num =3D=3D 1) + drop_th |=3D FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK, + NBL_USTORE_SINGLE_ETH_DROP_TH); + else if (eth_num =3D=3D 2) + drop_th |=3D FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK, + NBL_USTORE_DUAL_ETH_DROP_TH); + else + drop_th |=3D FIELD_PREP(NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK, + NBL_USTORE_QUAD_ETH_DROP_TH); + nbl_hw_get_fw_eth_map(hw_mgt, ð_bitmap); + for (i =3D 0; i < NBL_MAX_ETHERNET; i++) { + if (!(eth_bitmap & BIT(i))) + continue; + nbl_hw_rd_regs(hw_mgt, NBL_USTORE_PORT_DROP_TH_REG_ARR(i), + ®_val, sizeof(reg_val)); + reg_val &=3D ~(NBL_USTORE_PORT_DROP_TH_EN_MASK | + NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK); + reg_val |=3D drop_th; + nbl_hw_wr_regs(hw_mgt, NBL_USTORE_PORT_DROP_TH_REG_ARR(i), + ®_val, sizeof(reg_val)); + } + + /* Clear port drop/truncate counters by reading them + * (hardware has read-to-clear behavior for these registers) + */ + for (i =3D 0; i < NBL_MAX_ETHERNET; i++) { + if (!(eth_bitmap & BIT(i))) + continue; + nbl_hw_rd32(hw_mgt, NBL_USTORE_BUF_PORT_DROP_PKT(i)); + nbl_hw_rd32(hw_mgt, NBL_USTORE_BUF_PORT_TRUN_PKT(i)); + } + spin_unlock(&hw_mgt->reg_lock); + return 0; +} + +static void nbl_dstore_init(struct nbl_hw_mgt *hw_mgt, u8 speed) +{ + u32 eth_bitmap =3D 0; + u32 drop_th =3D 0; + u32 fc_th =3D 0; + u32 bp_th =3D 0; + int i; + + for (i =3D 0; i < NBL_DSTORE_PORT_DROP_TH_DEPTH; i++) { + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_DSTORE_PORT_DROP_TH_REG(i), &drop_th, + sizeof(drop_th)); + drop_th &=3D ~NBL_DSTORE_PORT_DROP_EN_MASK; + nbl_hw_wr_regs(hw_mgt, NBL_DSTORE_PORT_DROP_TH_REG(i), &drop_th, + sizeof(drop_th)); + spin_unlock(&hw_mgt->reg_lock); + } + + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_DSTORE_DISC_BP_TH, &bp_th, sizeof(bp_th)); + bp_th |=3D FIELD_PREP(NBL_DSTORE_DISC_BP_TH_EN_MASK, 1); + nbl_hw_wr_regs(hw_mgt, NBL_DSTORE_DISC_BP_TH, &bp_th, sizeof(bp_th)); + spin_unlock(&hw_mgt->reg_lock); + + nbl_hw_get_fw_eth_map(hw_mgt, ð_bitmap); + for (i =3D 0; i < NBL_MAX_ETHERNET; i++) { + if (!(eth_bitmap & BIT(i))) + continue; + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_DSTORE_D_DPORT_FC_TH_REG(i), &fc_th, + sizeof(fc_th)); + fc_th &=3D ~(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK | + NBL_DSTORE_D_DPORT_FC_XON_TH_MASK); + if (speed =3D=3D NBL_FW_PORT_SPEED_100G) { + fc_th |=3D + FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK, + NBL_DSTORE_DROP_XOFF_TH_100G) | + FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XON_TH_MASK, + NBL_DSTORE_DROP_XON_TH_100G); + } else { + fc_th |=3D + FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK, + NBL_DSTORE_DROP_XOFF_TH) | + FIELD_PREP(NBL_DSTORE_D_DPORT_FC_XON_TH_MASK, + NBL_DSTORE_DROP_XON_TH); + } + + fc_th |=3D FIELD_PREP(NBL_DSTORE_D_DPORT_FC_FC_EN_MASK, 1); + nbl_hw_wr_regs(hw_mgt, NBL_DSTORE_D_DPORT_FC_TH_REG(i), &fc_th, + sizeof(fc_th)); + spin_unlock(&hw_mgt->reg_lock); + } +} + +static void nbl_dvn_descreq_num_cfg(struct nbl_hw_mgt *hw_mgt, u8 descreq_= num) +{ + u8 split_ring_num =3D (descreq_num >> 3) & 0x1; + u8 ring_num =3D descreq_num & 0x7; + u32 num_cfg; + u32 reg_val; + + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_DVN_DESCREQ_NUM_CFG, ®_val, + sizeof(reg_val)); + + num_cfg =3D FIELD_PREP(NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK, + split_ring_num) | + FIELD_PREP(NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK, + ring_num); + reg_val &=3D ~(NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK | + NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK); + reg_val |=3D num_cfg; + nbl_hw_wr_regs(hw_mgt, NBL_DVN_DESCREQ_NUM_CFG, ®_val, + sizeof(reg_val)); + spin_unlock(&hw_mgt->reg_lock); +} + +static void nbl_dvn_init(struct nbl_hw_mgt *hw_mgt, u8 speed) +{ + u32 timeout =3D 0; + u32 ro_flag =3D 0; + + nbl_hw_wr32(hw_mgt, NBL_DVN_ECPU_QUEUE_NUM, 0); + timeout =3D FIELD_PREP(NBL_DVN_DESC_WR_MERGE_TIMEOUT_CFG_CYCLE_MASK, + DEFAULT_DVN_DESC_WR_MERGE_TIMEOUT_MAX); + nbl_hw_wr_regs_lock(hw_mgt, NBL_DVN_DESC_WR_MERGE_TIMEOUT, &timeout, + sizeof(timeout)); + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_DVN_DIF_REQ_RD_RO_FLAG, &ro_flag, + sizeof(ro_flag)); + if (pcie_relaxed_ordering_enabled(hw_mgt->common->pdev)) { + ro_flag |=3D + FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK, + 1) | + FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK, + 1) | + FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK, + 1); + } else { + ro_flag &=3D + ~(FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK, + 1) | + FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK, + 1) | + FIELD_PREP(NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK, + 1)); + } + nbl_hw_wr_regs(hw_mgt, NBL_DVN_DIF_REQ_RD_RO_FLAG, &ro_flag, + sizeof(ro_flag)); + spin_unlock(&hw_mgt->reg_lock); + if (speed =3D=3D NBL_FW_PORT_SPEED_100G) + nbl_dvn_descreq_num_cfg(hw_mgt, + DEFAULT_DVN_100G_DESCREQ_NUMCFG); + else + nbl_dvn_descreq_num_cfg(hw_mgt, DEFAULT_DVN_DESCREQ_NUMCFG); +} + +static void nbl_uvn_init(struct nbl_hw_mgt *hw_mgt) +{ + u16 wr_timeout =3D NBL_UVN_DESC_WR_TIMEOUT_VAL; + u32 timeout =3D NBL_UVN_DESC_RD_WAIT_TICKS; + u32 prefetch_init =3D 0; + bool ro_enabled; + u32 flag =3D 0; + u32 mask =3D 0; + u32 quirks; + u32 reg_val; + + spin_lock(&hw_mgt->reg_lock); + nbl_hw_wr32(hw_mgt, NBL_UVN_ECPU_QUEUE_NUM, 0); + nbl_hw_wr32(hw_mgt, NBL_UVN_DESC_RD_WAIT, timeout); + nbl_hw_rd_regs(hw_mgt, NBL_UVN_DESC_WR_TIMEOUT, + ®_val, sizeof(reg_val)); + reg_val &=3D ~NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK; + reg_val |=3D FIELD_PREP(NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK, wr_timeout); + nbl_hw_wr_regs(hw_mgt, NBL_UVN_DESC_WR_TIMEOUT, ®_val, + sizeof(reg_val)); + ro_enabled =3D pcie_relaxed_ordering_enabled(hw_mgt->common->pdev); + + nbl_hw_rd_regs(hw_mgt, NBL_UVN_DIF_REQ_RO_FLAG, &flag, sizeof(flag)); + if (ro_enabled) { + flag |=3D FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK, 1) | + FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK, 1) | + FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK, 1); + flag &=3D ~FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK, 1); + } else { + flag &=3D ~(FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK, 1) | + FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK, 1) | + FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK, 1) | + FIELD_PREP(NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK, 1)); + } + nbl_hw_wr_regs(hw_mgt, NBL_UVN_DIF_REQ_RO_FLAG, &flag, sizeof(flag)); + + nbl_hw_rd_regs(hw_mgt, NBL_UVN_QUEUE_ERR_MASK, &mask, sizeof(mask)); + mask |=3D FIELD_PREP(NBL_UVN_QUEUE_ERR_MASK_DIF_ERR_MASK, 1); + + nbl_hw_wr_regs(hw_mgt, NBL_UVN_QUEUE_ERR_MASK, &mask, sizeof(mask)); + + spin_unlock(&hw_mgt->reg_lock); + quirks =3D nbl_hw_get_quirks(hw_mgt); + /* + * sel=3D0: use configured num; sel=3D1: use internal calc (max 32) + * Default is sel=3D1, unless NBL_QUIRK_UVN_PREFETCH_ALIGN is set, + * in which case override to sel=3D0. + */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_rd_regs(hw_mgt, NBL_UVN_DESC_PREFETCH_INIT, ®_val, + sizeof(reg_val)); + prefetch_init =3D + FIELD_PREP(NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK, + NBL_UVN_DESC_PREFETCH_NUM) | + FIELD_PREP(NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK, + (quirks & NBL_QUIRK_UVN_PREFETCH_ALIGN) ? 0 : 1); + reg_val &=3D ~(NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK | + NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK); + reg_val |=3D prefetch_init; + nbl_hw_wr_regs(hw_mgt, NBL_UVN_DESC_PREFETCH_INIT, ®_val, + sizeof(reg_val)); + spin_unlock(&hw_mgt->reg_lock); +} + +static void nbl_uqm_init(struct nbl_hw_mgt *hw_mgt) +{ + u32 que_type =3D 0; + u32 cnt =3D 0; + int i; + + spin_lock(&hw_mgt->reg_lock); + nbl_hw_wr_regs(hw_mgt, NBL_UQM_FWD_DROP_CNT, &cnt, sizeof(cnt)); + + nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_CNT, &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_SLICE_CNT, &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_PKT_LEN_ADD_CNT, &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_HEAD_PNTR_ADD_CNT, &cnt, + sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, NBL_UQM_DROP_WEIGHT_ADD_CNT, &cnt, sizeof(cnt)); + + for (i =3D 0; i < NBL_UQM_PORT_DROP_DEPTH; i++) { + nbl_hw_wr_regs(hw_mgt, + NBL_UQM_PORT_DROP_PKT_CNT + (sizeof(cnt) * i), + &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, + NBL_UQM_PORT_DROP_PKT_SLICE_CNT + + (sizeof(cnt) * i), + &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, + NBL_UQM_PORT_DROP_PKT_LEN_ADD_CNT + + (sizeof(cnt) * i), + &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, + NBL_UQM_PORT_DROP_HEAD_PNTR_ADD_CNT + + (sizeof(cnt) * i), + &cnt, sizeof(cnt)); + nbl_hw_wr_regs(hw_mgt, + NBL_UQM_PORT_DROP_WEIGHT_ADD_CNT + + (sizeof(cnt) * i), + &cnt, sizeof(cnt)); + } + + for (i =3D 0; i < NBL_UQM_DPORT_DROP_DEPTH; i++) + nbl_hw_wr_regs(hw_mgt, + NBL_UQM_DPORT_DROP_CNT + (sizeof(cnt) * i), &cnt, + sizeof(cnt)); + /* bit0: 0=3Dbp mode, 1=3Ddrop mode, resv bit1-31 */ + nbl_hw_wr_regs(hw_mgt, NBL_UQM_QUE_TYPE, &que_type, sizeof(que_type)); + spin_unlock(&hw_mgt->reg_lock); +} + +static int nbl_dp_init(struct nbl_hw_mgt *hw_mgt, u8 speed, u8 eth_num) +{ + int ret; + + nbl_dped_init(hw_mgt); + nbl_uped_init(hw_mgt); + ret =3D nbl_shaping_init(hw_mgt, speed); + if (ret) + return ret; + nbl_dsch_qid_max_init(hw_mgt); + ret =3D nbl_ustore_init(hw_mgt, eth_num); + if (ret) + return ret; + nbl_dstore_init(hw_mgt, speed); + nbl_dvn_init(hw_mgt, speed); + nbl_uvn_init(hw_mgt); + nbl_uqm_init(hw_mgt); + return 0; +} + +static void nbl_host_padpt_init(struct nbl_hw_mgt *hw_mgt) +{ + /* padpt flow control register */ + spin_lock(&hw_mgt->reg_lock); + nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_CPLH_UP, + NBL_HOST_PADPT_CFG_FC_CPLH_UP_VAL); + nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_PD_DN, + NBL_HOST_PADPT_CFG_FC_PD_DN_VAL); + nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_PH_DN, + NBL_HOST_PADPT_CFG_FC_PH_DN_VAL); + nbl_hw_wr32(hw_mgt, NBL_HOST_PADPT_HOST_CFG_FC_NPH_DN, + NBL_HOST_PADPT_CFG_FC_NPH_DN_VAL); + spin_unlock(&hw_mgt->reg_lock); +} + +static void nbl_intf_init(struct nbl_hw_mgt *hw_mgt) +{ + nbl_host_padpt_init(hw_mgt); +} + +static void nbl_hw_set_driver_status(struct nbl_hw_mgt *hw_mgt, bool activ= e) +{ + u32 status; + + spin_lock(&hw_mgt->reg_lock); + status =3D nbl_hw_rd32(hw_mgt, NBL_DRIVER_STATUS_REG); + + status &=3D ~BIT(NBL_DRIVER_STATUS_BIT); + status |=3D FIELD_PREP(BIT(NBL_DRIVER_STATUS_BIT), active); + + nbl_hw_wr32(hw_mgt, NBL_DRIVER_STATUS_REG, status); + spin_unlock(&hw_mgt->reg_lock); +} + +/* + * Setting driver status to false notifies firmware to clean up per-PF + * hardware state such as qinfo registers. + * + * Note: firmware does NOT automatically revert chip-wide registers + * configured in this init flow. Those chip-wide settings remain valid + * until chip reset or explicitly overwritten by driver. + * + * This deinit_module only clears driver active status and flush writes. + * It does NOT reset or restore chip-wide datapath registers. + * + * Caller must ensure no new DMA is initiated after this point. + * The mailbox channel is stopped by nbl_chan_teardown_queue() + * before this function is called, so no in-flight mailbox DMA + * remains. + */ +static void nbl_hw_deinit_module(struct nbl_hw_mgt *hw_mgt) +{ + nbl_hw_set_driver_status(hw_mgt, false); + /* ensure registers written */ + nbl_flush_writes(hw_mgt); + /* + * Firmware cleanup is asynchronous: there is no cleanup-complete + * status register in the current hardware revision. The posted + * write flush above only ensures driver_status reaches the chip; + * firmware may still be performing per-PF state cleanup when this + * function returns. + */ +} + +static bool nbl_hw_eth_speed_valid(u8 speed) +{ + switch (speed) { + case NBL_FW_PORT_SPEED_10G: + case NBL_FW_PORT_SPEED_25G: + case NBL_FW_PORT_SPEED_50G: + case NBL_FW_PORT_SPEED_100G: + return true; + default: + return false; + } +} + +static bool nbl_hw_eth_num_valid(u8 eth_num) +{ + return eth_num =3D=3D 1 || eth_num =3D=3D 2 || eth_num =3D=3D 4; +} + +/* + * Full chip hardware initialization is handled by firmware. + * This function only configures driver-level table entries and registers. + */ +static int nbl_hw_init_module(struct nbl_hw_mgt *hw_mgt, u8 eth_speed, + u8 eth_num) +{ + int ret; + + if (!nbl_hw_eth_speed_valid(eth_speed)) { + dev_err(hw_mgt->common->dev, "Invalid eth_speed %u\n", + eth_speed); + return -EINVAL; + } + if (!nbl_hw_eth_num_valid(eth_num)) { + dev_err(hw_mgt->common->dev, "Invalid eth_num %u\n", eth_num); + return -EINVAL; + } + + ret =3D nbl_dp_init(hw_mgt, eth_speed, eth_num); + if (ret) + return ret; + nbl_intf_init(hw_mgt); + nbl_hw_set_driver_status(hw_mgt, true); + /* ensure registers written */ + nbl_flush_writes(hw_mgt); + + return 0; +} + /* * nbl_hw_set_mailbox_irq - read-modify-write NBL_MAILBOX_QINFO_MAP_REG_ARR * @@ -408,6 +1035,9 @@ static void nbl_hw_get_board_info(struct nbl_hw_mgt *h= w_mgt, } =20 static struct nbl_hw_ops hw_ops =3D { + .init_module =3D nbl_hw_init_module, + .deinit_module =3D nbl_hw_deinit_module, + .cfg_msix_map =3D nbl_hw_cfg_msix_map, .cfg_msix_info =3D nbl_hw_cfg_msix_info, .flush_write =3D nbl_flush_writes, @@ -458,7 +1088,8 @@ static struct nbl_hw_ops_tbl *nbl_hw_setup_ops(struct = nbl_common_info *common, !hw_ops.stop_mailbox_rxq || !hw_ops.stop_mailbox_txq || !hw_ops.get_host_pf_mask || !hw_ops.get_real_bus || !hw_ops.cfg_mailbox_qinfo || !hw_ops.set_mailbox_irq || - !hw_ops.get_fw_eth_map || !hw_ops.get_board_info) + !hw_ops.get_fw_eth_map || !hw_ops.get_board_info || + !hw_ops.init_module || !hw_ops.deinit_module) return ERR_PTR(-EINVAL); hw_ops_tbl->ops =3D &hw_ops; hw_ops_tbl->priv =3D hw_mgt; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_hw_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis= /nbl_hw_leonis.h index 18e3e7da4f9d..8c9ce8b793e9 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_hw_le= onis.h @@ -11,12 +11,25 @@ #include "../../nbl_include/nbl_include.h" #include "../nbl_hw_reg.h" =20 +#define NBL_DRIVER_STATUS_REG 0x1300444 +#define NBL_DRIVER_STATUS_BIT 16 + /* ---------- REG BASE ADDR ---------- */ /* Interface modules base addr */ #define NBL_INTF_HOST_PCOMPLETER_BASE 0x00f08000 #define NBL_INTF_HOST_PADPT_BASE 0x00f4c000 #define NBL_INTF_HOST_MAILBOX_BASE 0x00fb0000 #define NBL_INTF_HOST_PCIE_BASE 0X01504000 +/* DP modules base addr */ +#define NBL_DP_USTORE_BASE 0x00104000 +#define NBL_DP_UQM_BASE 0x00114000 +#define NBL_DP_UPED_BASE 0x0015c000 +#define NBL_DP_UVN_BASE 0x00244000 +#define NBL_DP_DSCH_BASE 0x00404000 +#define NBL_DP_SHAPING_BASE 0x00504000 +#define NBL_DP_DVN_BASE 0x00514000 +#define NBL_DP_DSTORE_BASE 0x00704000 +#define NBL_DP_DPED_BASE 0x0075c000 /* -------- MAILBOX BAR2 ----- */ #define NBL_MAILBOX_NOTIFY_ADDR 0x00000000 #define NBL_MAILBOX_QINFO_CFG_RX_TABLE_ADDR 0x10 @@ -56,6 +69,17 @@ struct nbl_mailbox_qinfo_cfg_table { #define NBL_PCIE_BUS_MASK GENMASK(12, 5) =20 /* -------- HOST_PADPT -------- */ +#define NBL_HOST_PADPT_HOST_CFG_FC_PD_DN (NBL_INTF_HOST_PADPT_BASE + 0x000= 00160) +#define NBL_HOST_PADPT_HOST_CFG_FC_PH_DN (NBL_INTF_HOST_PADPT_BASE + 0x000= 00164) +#define NBL_HOST_PADPT_HOST_CFG_FC_NPH_DN \ + (NBL_INTF_HOST_PADPT_BASE + 0x0000016C) +#define NBL_HOST_PADPT_HOST_CFG_FC_CPLH_UP \ + (NBL_INTF_HOST_PADPT_BASE + 0x00000170) + +#define NBL_HOST_PADPT_CFG_FC_CPLH_UP_VAL 0x10400 +#define NBL_HOST_PADPT_CFG_FC_PD_DN_VAL 0x10080 +#define NBL_HOST_PADPT_CFG_FC_PH_DN_VAL 0x10010 +#define NBL_HOST_PADPT_CFG_FC_NPH_DN_VAL 0x10010 /* host_padpt host_msix_info */ #define NBL_PADPT_HOST_MSIX_INFO_REG_ARR(vector_id) \ (NBL_INTF_HOST_PADPT_BASE + 0x00010000 + \ @@ -96,6 +120,203 @@ struct nbl_function_msix_map { u32 data[NBL_FUNC_MSIX_MAP_DWLEN]; }; =20 +/* ---------- DPED ---------- */ +#define NBL_DPED_VLAN_OFFSET (NBL_DP_DPED_BASE + 0x000003F4) +#define NBL_DPED_DSCP_OFFSET_0 (NBL_DP_DPED_BASE + 0x000003F8) +#define NBL_DPED_DSCP_OFFSET_1 (NBL_DP_DPED_BASE + 0x000003FC) +/* DPED hw_edt_prof/ UPED hw_edt_prof */ + +#define NBL_DPED_L4_CK_CMD_40_ADDR (NBL_DP_DPED_BASE + 0x00000338) + +#define NBL_DPED_L4_CK_CMD_40_EN_MASK BIT(31) + +/* ---------- UPED ---------- */ +/* UPED uped_hw_edt_prof */ +#define NBL_UPED_HW_EDT_PROF_TABLE(i) \ + (NBL_DP_UPED_BASE + 0x00001000 + (i) * sizeof(u32)) +#define NBL_UPED_V4_TCP_IDX 5 +#define NBL_UPED_V6_TCP_IDX 6 +#define NBL_PED_HW_EDIT_PROFILE_L3_LEN_MASK GENMASK(3, 2) + +/* ---------- DSCH ---------- */ +#define NBL_DSCH_PSHA_EN_MASK GENMASK(3, 0) +/* DSCH dsch maxqid */ +#define NBL_DSCH_HOST_QID_MAX (NBL_DP_DSCH_BASE + 0x00000118) +#define NBL_DSCH_HOST_QID_MAX_MASK GENMASK(10, 0) +#define NBL_DSCH_VN_QUANTA_ADDR (NBL_DP_DSCH_BASE + 0x00000134) + +#define NBL_MAX_QUEUE_ID 0x7ff +#define NBL_HOST_QUANTA 0x8000 +#define NBL_ECPU_QUANTA 0x1000 + +#define NBL_DSCH_VN_QUANTA_H_QUA_MASK GENMASK(15, 0) +#define NBL_DSCH_VN_QUANTA_E_QUA_MASK GENMASK(31, 16) + +/* ---------- DVN ---------- */ +/* DVN dvn_queue_table */ +#define NBL_DVN_ECPU_QUEUE_NUM (NBL_DP_DVN_BASE + 0x0000041C) +#define NBL_DVN_DESCREQ_NUM_CFG (NBL_DP_DVN_BASE + 0x00000430) +#define NBL_DVN_DESC_WR_MERGE_TIMEOUT (NBL_DP_DVN_BASE + 0x00000480) +#define NBL_DVN_DIF_REQ_RD_RO_FLAG (NBL_DP_DVN_BASE + 0x0000045C) + +#define DEFAULT_DVN_DESCREQ_NUMCFG 0x03 +#define DEFAULT_DVN_100G_DESCREQ_NUMCFG 0x07 + +#define DEFAULT_DVN_DESC_WR_MERGE_TIMEOUT_MAX 0x3FF + +/* spilit ring descreq_num 0:8,1:16 */ +#define NBL_DVN_DESCREQ_NUM_CFG_AVRING_DESREQ_NUM_CFG_MASK BIT(0) +/* packet ring descreq_num + * 0:8,1:12,2:16;3:20,4:24,5:26;6:32,7:32 + */ +#define NBL_DVN_DESCREQ_NUM_CFG_PACKED_L1_NUM_MASK GENMASK(6, 4) + +#define NBL_DVN_DESC_WR_MERGE_TIMEOUT_CFG_CYCLE_MASK GENMASK(9, 0) + +#define NBL_DVN_DIF_REQ_RD_RO_FLAG_DESC_RO_EN_MASK BIT(0) +#define NBL_DVN_DIF_REQ_RD_RO_FLAG_DATA_RO_EN_MASK BIT(1) +#define NBL_DVN_DIF_REQ_RD_RO_FLAG_AVRING_RO_EN_MASK BIT(2) + +/* ---------- UVN ---------- */ +/* UVN uvn_queue_table */ + +#define NBL_UVN_DESC_RD_WAIT (NBL_DP_UVN_BASE + 0x0000020C) +#define NBL_UVN_QUEUE_ERR_MASK (NBL_DP_UVN_BASE + 0x00000224) +#define NBL_UVN_ECPU_QUEUE_NUM (NBL_DP_UVN_BASE + 0x0000023C) +#define NBL_UVN_DESC_WR_TIMEOUT (NBL_DP_UVN_BASE + 0x00000214) +#define NBL_UVN_DIF_REQ_RO_FLAG (NBL_DP_UVN_BASE + 0x00000250) +#define NBL_UVN_DESC_PREFETCH_INIT (NBL_DP_UVN_BASE + 0x00000204) +#define NBL_UVN_DESC_PREFETCH_NUM 4 + +#define NBL_UVN_DIF_REQ_RO_FLAG_AVAIL_RD_MASK BIT(0) +#define NBL_UVN_DIF_REQ_RO_FLAG_DESC_RD_MASK BIT(1) +#define NBL_UVN_DIF_REQ_RO_FLAG_PKT_WR_MASK BIT(2) +#define NBL_UVN_DIF_REQ_RO_FLAG_DESC_WR_MASK BIT(3) + +#define NBL_UVN_DESC_WR_TIMEOUT_NUM_MASK GENMASK(14, 0) + +#define NBL_UVN_QUEUE_ERR_MASK_DIF_ERR_MASK BIT(5) + +#define NBL_UVN_DESC_PREFETCH_INIT_NUM_MASK GENMASK(7, 0) +#define NBL_UVN_DESC_PREFETCH_INIT_SEL_MASK BIT(16) + +#define NBL_UVN_DESC_WR_TIMEOUT_VAL 0x12c +/* 200us =3D 200000ns / 1.67ns per tick =3D 119760 ticks */ +#define NBL_UVN_DESC_RD_WAIT_TICKS 119760 + +/* -------- USTORE -------- */ +#define NBL_USTORE_PKT_LEN_ADDR (NBL_DP_USTORE_BASE + 0x00000108) +#define NBL_USTORE_PORT_DROP_TH_REG_ARR(port_id) \ + (NBL_DP_USTORE_BASE + 0x00000150 + (port_id) * sizeof(u32)) +#define NBL_USTORE_BUF_PORT_DROP_PKT(eth_id) \ + (NBL_DP_USTORE_BASE + 0x00002500 + (eth_id) * sizeof(u32)) +#define NBL_USTORE_BUF_PORT_TRUN_PKT(eth_id) \ + (NBL_DP_USTORE_BASE + 0x00002540 + (eth_id) * sizeof(u32)) + +#define NBL_USTORE_SINGLE_ETH_DROP_TH 0xC80 +#define NBL_USTORE_DUAL_ETH_DROP_TH 0x640 +#define NBL_USTORE_QUAD_ETH_DROP_TH 0x320 + +/* USTORE pkt_len */ +#define NBL_USTORE_PKT_LEN_MIN_MASK GENMASK(6, 0) + +/* USTORE port_drop_th */ +#define NBL_USTORE_PORT_DROP_TH_DISC_TH_MASK GENMASK(11, 0) +#define NBL_USTORE_PORT_DROP_TH_EN_MASK BIT(31) + +/* UQM*/ +#define NBL_UQM_QUE_TYPE (NBL_DP_UQM_BASE + 0x0000013c) +#define NBL_UQM_DROP_PKT_CNT (NBL_DP_UQM_BASE + 0x000009C0) +#define NBL_UQM_DROP_PKT_SLICE_CNT (NBL_DP_UQM_BASE + 0x000009C4) +#define NBL_UQM_DROP_PKT_LEN_ADD_CNT (NBL_DP_UQM_BASE + 0x000009C8) +#define NBL_UQM_DROP_HEAD_PNTR_ADD_CNT (NBL_DP_UQM_BASE + 0x000009CC) +#define NBL_UQM_DROP_WEIGHT_ADD_CNT (NBL_DP_UQM_BASE + 0x000009D0) +#define NBL_UQM_PORT_DROP_PKT_CNT (NBL_DP_UQM_BASE + 0x000009D4) +#define NBL_UQM_PORT_DROP_PKT_SLICE_CNT (NBL_DP_UQM_BASE + 0x000009F4) +#define NBL_UQM_PORT_DROP_PKT_LEN_ADD_CNT (NBL_DP_UQM_BASE + 0x00000A14) +#define NBL_UQM_PORT_DROP_HEAD_PNTR_ADD_CNT (NBL_DP_UQM_BASE + 0x00000A34) +#define NBL_UQM_PORT_DROP_WEIGHT_ADD_CNT (NBL_DP_UQM_BASE + 0x00000A54) +#define NBL_UQM_FWD_DROP_CNT (NBL_DP_UQM_BASE + 0x00000A80) +#define NBL_UQM_DPORT_DROP_CNT (NBL_DP_UQM_BASE + 0x00000B74) + +#define NBL_UQM_PORT_DROP_DEPTH 6 +#define NBL_UQM_DPORT_DROP_DEPTH 16 + +/* --------- SHAPING --------- */ + +/* Shaping rate unit: 1 =3D 1 Mbit/s. + * e.g. 100000 =3D 100 Gbit/s, 25000 =3D 25 Gbit/s. + */ +#define NBL_LR_LEONIS_NET_BUCKET_DEPTH 9600 +#define NBL_SHAPING_DPORT_ADDR (NBL_DP_SHAPING_BASE + 0x700) +#define NBL_SHAPING_DPORT_DWLEN 4 +#define NBL_SHAPING_DPORT_REG(r) \ + (NBL_SHAPING_DPORT_ADDR + (NBL_SHAPING_DPORT_DWLEN * 4) * (r)) +#define NBL_SHAPING_DVN_DPORT_ADDR (NBL_DP_SHAPING_BASE + 0x750) +#define NBL_SHAPING_DVN_DPORT_DWLEN 4 +#define NBL_SHAPING_DVN_DPORT_REG(r) \ + (NBL_SHAPING_DVN_DPORT_ADDR + (NBL_SHAPING_DVN_DPORT_DWLEN * 4) * (r)) +#define NBL_DSCH_PSHA_EN_ADDR (NBL_DP_DSCH_BASE + 0x00000314) +#define NBL_SHAPING_NET_ADDR (NBL_DP_SHAPING_BASE + 0x1800) +#define NBL_SHAPING_NET_DWLEN 4 +#define NBL_SHAPING_NET_REG(r) \ + (NBL_SHAPING_NET_ADDR + (NBL_SHAPING_NET_DWLEN * 4) * (r)) + +#define NBL_DPORT_VALID_MASK GENMASK_ULL(0, 0) +#define NBL_DPORT_DEPTH_MASK GENMASK_ULL(19, 1) +#define NBL_DPORT_CIR_MASK GENMASK_ULL(38, 20) +#define NBL_DPORT_PIR_MASK GENMASK_ULL(57, 39) +#define NBL_DPORT_CBS_MASK_LOW GENMASK_ULL(63, 58) +#define NBL_DPORT_CBS_MASK_HIGH GENMASK_ULL(14, 0) +#define NBL_DPORT_PBS_MASK GENMASK_ULL(35, 15) + +/* SHAPING shaping_net */ +struct nbl_shaping_net_u { + u32 data[NBL_SHAPING_NET_DWLEN]; +}; + +struct nbl_shaping_dport_u { + u32 data[NBL_SHAPING_DPORT_DWLEN]; +}; + +struct nbl_shaping_dvn_dport_u { + u32 data[NBL_SHAPING_DVN_DPORT_DWLEN]; +}; + +/* -------- DSTORE -------- */ +#define NBL_DSTORE_D_DPORT_FC_TH_ADDR (NBL_DP_DSTORE_BASE + 0x00000600) +#define NBL_DSTORE_D_DPORT_FC_TH_DEPTH 5 +#define NBL_DSTORE_D_DPORT_FC_TH_WIDTH 32 +#define NBL_DSTORE_D_DPORT_FC_TH_DWLEN 1 + +#define NBL_DSTORE_D_DPORT_FC_XOFF_TH_MASK GENMASK(10, 0) +#define NBL_DSTORE_D_DPORT_FC_XON_TH_MASK GENMASK(26, 16) +#define NBL_DSTORE_D_DPORT_FC_FC_EN_MASK BIT(31) + +#define NBL_DSTORE_D_DPORT_FC_TH_REG(r) \ + (NBL_DSTORE_D_DPORT_FC_TH_ADDR + \ + (NBL_DSTORE_D_DPORT_FC_TH_DWLEN * 4) * (r)) +#define NBL_DSTORE_PORT_DROP_TH_ADDR (NBL_DP_DSTORE_BASE + 0x00000150) +#define NBL_DSTORE_PORT_DROP_TH_DEPTH 6 +#define NBL_DSTORE_PORT_DROP_TH_WIDTH 32 +#define NBL_DSTORE_PORT_DROP_TH_DWLEN 1 + +#define NBL_DSTORE_PORT_DROP_EN_MASK BIT(31) + +#define NBL_DSTORE_DROP_XOFF_TH 0xC8 +#define NBL_DSTORE_DROP_XON_TH 0x64 + +#define NBL_DSTORE_DROP_XOFF_TH_100G 0x1F4 +#define NBL_DSTORE_DROP_XON_TH_100G 0x12C + +#define NBL_DSTORE_DISC_BP_TH (NBL_DP_DSTORE_BASE + 0x00000630) + +#define NBL_DSTORE_DISC_BP_TH_EN_MASK BIT(31) + +#define NBL_DSTORE_PORT_DROP_TH_REG(r) \ + (NBL_DSTORE_PORT_DROP_TH_ADDR + \ + (NBL_DSTORE_PORT_DROP_TH_DWLEN * 4) * (r)) + #define NBL_FW_BOARD_CONFIG 0x200 #define NBL_FW_BOARD_DW3_OFFSET (NBL_FW_BOARD_CONFIG + 12) #define NBL_FW_BOARD_DW6_OFFSET (NBL_FW_BOARD_CONFIG + 24) @@ -109,5 +330,6 @@ struct nbl_function_msix_map { #define NBL_FW_BOARD_DW6_LANE_BITMAP_MASK GENMASK(7, 0) #define NBL_FW_BOARD_DW6_ETH_BITMAP_MASK GENMASK(15, 8) =20 +#define NBL_LEONIS_QUIRKS_OFFSET 0x00000140 #define NBL_BAR2_MAX_LEN 0x300 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_resource_leonis.c b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_= leonis/nbl_resource_leonis.c index 62af1738e2fd..18ff6de793ba 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.c @@ -13,6 +13,8 @@ static struct nbl_resource_ops res_ops =3D { .cfg_msix_map =3D nbl_res_intr_cfg_msix_map, .destroy_msix_map =3D nbl_res_intr_destroy_msix_map, .set_mailbox_irq =3D nbl_res_intr_set_mailbox_irq, + .init_module =3D nbl_res_chip_init_module, + .deinit_module =3D nbl_res_chip_deinit_module, }; =20 static struct nbl_resource_mgt * @@ -46,7 +48,8 @@ nbl_res_setup_ops(struct device *dev, struct nbl_resource= _mgt *res_mgt) return ERR_PTR(-ENOMEM); if (!res_ops.get_vsi_id || !res_ops.get_eth_id || !res_ops.cfg_msix_map || !res_ops.destroy_msix_map || - !res_ops.set_mailbox_irq) + !res_ops.set_mailbox_irq || !res_ops.init_module || + !res_ops.deinit_module) return ERR_PTR(-EINVAL); res_ops_tbl->ops =3D &res_ops; res_ops_tbl->priv =3D res_mgt; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nb= l_resource_leonis.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_= leonis/nbl_resource_leonis.h index 6eb4dc9e695a..f1cb0f23240b 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_hw/nbl_hw_leonis/nbl_resou= rce_leonis.h @@ -8,4 +8,5 @@ =20 #include "../nbl_resource.h" #include "../nbl_interrupt.h" +#include "../nbl_chip.h" #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_com= mon.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h index fa76dd6c5323..3e18a2a97850 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_common.h @@ -51,5 +51,4 @@ void nbl_common_destroy_wq(struct nbl_common_info *common= ); int nbl_common_create_wq(struct nbl_common_info *common); int nbl_common_func_id_to_rel_pf_id(struct nbl_common_info *common, u32 pf= _id, u32 *rel_pf_id); - #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.= h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h index b9b92c42f276..6aff1b10eb7f 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_hw.h @@ -12,6 +12,9 @@ struct nbl_board_port_info; struct nbl_hw_mgt; struct nbl_adapter; struct nbl_hw_ops { + int (*init_module)(struct nbl_hw_mgt *hw_mgt, u8 eth_speed, u8 eth_num); + void (*deinit_module)(struct nbl_hw_mgt *hw_mgt); + void (*cfg_msix_map)(struct nbl_hw_mgt *hw_mgt, u16 func_id, bool valid, dma_addr_t dma_addr, u8 bus, u8 devid, u8 function); diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_res= ource.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resour= ce.h index e718ea41a816..8dc64e806c1e 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_resource.h @@ -12,6 +12,9 @@ struct nbl_resource_mgt; struct nbl_adapter; =20 struct nbl_resource_ops { + int (*init_module)(struct nbl_resource_mgt *res_mgt); + void (*deinit_module)(struct nbl_resource_mgt *res_mgt); + int (*cfg_msix_map)(struct nbl_resource_mgt *res_mgt, u16 func_id, u16 num_net_msix, u16 num_others_msix, bool net_msix_mask_en); diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h index 2c959832c32f..5ae9fc2ea53b 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_include.h @@ -30,4 +30,23 @@ struct nbl_init_param { struct nbl_func_caps caps; }; =20 +/* + * Firmware ABI defines port speed enum fixed, value 0 represents 10G, can= not + * reassign 0 to INVALID for compatibility + */ +enum nbl_fw_port_speed { + NBL_FW_PORT_SPEED_10G, + NBL_FW_PORT_SPEED_25G, + NBL_FW_PORT_SPEED_50G, + NBL_FW_PORT_SPEED_100G, +}; + +/* + * Firmware quirk word @ NBL_LEONIS_QUIRKS_OFFSET (0x140) + * Sentinel value: ~0U (0xFFFFFFFF) =3D firmware reports no active quirks + * BIT(1): NBL_QUIRK_UVN_PREFETCH_ALIGN =E2=80=93 control UVN descriptor p= refetch + * selection + */ +#define NBL_QUIRK_UVN_PREFETCH_ALIGN BIT(1) + #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_main.c index 1aafed2d46d7..6df91c751bc5 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c @@ -83,6 +83,56 @@ static void nbl_get_func_param(struct pci_dev *pdev, ker= nel_ulong_t driver_data, param->caps.has_ctrl =3D 1; } =20 +/* + * Establish chip-wide dependencies for this PF: + * - register it in the chip registry so control PF teardown can detect + * siblings that are still bound; + * - for every non-management PF, add a consumer->management PF device + * link. The driver core then guarantees (sysfs unbind, driver + * unregister, hot-unplug alike) that this consumer is released + * BEFORE the func 0 supplier, which is the only teardown order in + * which the chip-global firmware deinit is safe. + * + * The management PF is addressed by the deterministic identity + * (domain, bus, slot, func 0) instead of any name-based scan: hardware + * guarantees PFs are contiguous from func 0 in the same slot. + * + * The link uses DL_FLAG_AUTOREMOVE_CONSUMER, so it is dropped by the + * driver core when this PF detaches; it must not be removed manually. + * A DORMANT link (func 0 not bound yet) activates automatically once + * the management PF driver binds. + * + * Return: 0 on success, negative errno on failure. On failure the chip + * registry entry has already been rolled back. + */ +static int nbl_probe_chip_deps(struct pci_dev *pdev, bool has_ctrl) +{ + struct pci_dev *mgt; + int err =3D 0; + + if (has_ctrl) + return 0; + + mgt =3D pci_get_domain_bus_and_slot(pci_domain_nr(pdev->bus), + pdev->bus->number, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 0)); + if (!mgt) { + dev_err(&pdev->dev, + "management PF (func 0) not found on this chip\n"); + return -ENODEV; + } + + if (!device_link_add(&pdev->dev, &mgt->dev, + DL_FLAG_AUTOREMOVE_CONSUMER)) { + dev_err(&pdev->dev, + "failed to create device link to management PF %s\n", + pci_name(mgt)); + err =3D -ENOMEM; + } + pci_dev_put(mgt); + return err; +} + static int nbl_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -103,6 +153,10 @@ static int nbl_probe(struct pci_dev *pdev, =20 pci_set_master(pdev); =20 + err =3D nbl_probe_chip_deps(pdev, param.caps.has_ctrl); + if (err) + goto chip_deps_err; + adapter =3D nbl_core_init(pdev, ¶m); if (IS_ERR(adapter)) { dev_err(dev, "Nbl adapter init fail: %pe\n", adapter); @@ -112,6 +166,7 @@ static int nbl_probe(struct pci_dev *pdev, pci_set_drvdata(pdev, adapter); return 0; adapter_init_err: +chip_deps_err: pci_clear_master(pdev); return err; } @@ -122,6 +177,7 @@ static void nbl_remove(struct pci_dev *pdev) =20 if (!adapter) return; + pci_set_drvdata(pdev, NULL); nbl_core_remove(adapter); =20 --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-101.mail.aliyun.com (out28-101.mail.aliyun.com [115.124.28.101]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 83B1653E2FD; Tue, 22 Sep 2026 12:03:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.101 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078612; cv=none; b=npPgHo2TU9S8fxzqP16zU4QlA2iVLlmCfQ+WfEnHc3VdIagnVlXC2cHt1L4sJ2LMgE+K2ueMRBE0aApLu750LKU68x0KH/Tgcghl19pwx64BeSyHqT35PdVoSDpGHZ2S65nlXl6dJ3IMVAsSAhliuT+ETpAbr8LsmIm9tumvgiQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078612; c=relaxed/simple; bh=AgZPb6y3WdhLC7+pFKcBbi0mqFmmjo4N0Edmh1+vZxk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Uo1tJi6KgZoidAR8Iow8JzP66xZfa5eu4fT040Dc0wvPGj+Pjz4xv0zIwG2nhuVHAEt/A5DRmQpKG3oTlBd98vJpYRjtiCIb1NwQCWuJ1j46ajUr38JuJdiPawfOTrdQqh3BxYtpPvE1Rd3cx6mpNsiZwl/lSSRt+oqDhX1V1VE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.101 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.0296641-0.00568381-0.964652;FP=16597853964287346194|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam011083013073;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPU-_1790078598; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPU-_1790078598 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:19 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 5/8] net/nebula-matrix: dispatch: add control-level routing core infrastructure Date: Tue, 22 Sep 2026 20:03:02 +0800 Message-ID: <20260922120311.86593-6-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: illusion wang Implement core dispatch layer infrastructure for control-plane routing: Allocate dispatch management structure and dispatch ops table Provide init_module/deinit_module wrapper callbacks for chip resource ops Introduce ctrl_lvl bitmask tracking; enable MGT level only for Control PF Regular PF NET level routing will be added in a subsequent patch. Kerneldoc on nbl_dispatch_ops documents that init_module/deinit_module must only be invoked on Control PF behind has_ctrl guard. This skeleton establishes dispatch management flow. It adds forward definitions of channel wire ABI structures and response enums required for later channel RPC framework, without implementing message handling. Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/Makefile | 1 + .../net/ethernet/nebula-matrix/nbl/nbl_core.h | 2 + .../nebula-matrix/nbl/nbl_core/nbl_dispatch.c | 117 ++++++++++++++++++ .../nebula-matrix/nbl/nbl_core/nbl_dispatch.h | 23 ++++ .../nbl/nbl_include/nbl_def_channel.h | 35 ++++++ .../nbl/nbl_include/nbl_def_dispatch.h | 39 ++++++ .../net/ethernet/nebula-matrix/nbl/nbl_main.c | 8 ++ 7 files changed, 225 insertions(+) create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dis= patch.c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dis= patch.h create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_= def_dispatch.h diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/= ethernet/nebula-matrix/nbl/Makefile index be314b909d66..b7eebd89b4d1 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/Makefile +++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile @@ -10,4 +10,5 @@ nbl-objs +=3D nbl_common/nbl_common.o \ nbl_hw/nbl_resource.o \ nbl_hw/nbl_interrupt.o \ nbl_hw/nbl_chip.o \ + nbl_core/nbl_dispatch.o \ nbl_main.o diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_core.h index dd24ebec0171..4d8cea8d8ab3 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h @@ -17,12 +17,14 @@ enum { struct nbl_interface { struct nbl_hw_ops_tbl *hw_ops_tbl; struct nbl_resource_ops_tbl *resource_ops_tbl; + struct nbl_dispatch_ops_tbl *dispatch_ops_tbl; struct nbl_channel_ops_tbl *channel_ops_tbl; }; =20 struct nbl_core { struct nbl_hw_mgt *hw_mgt; struct nbl_resource_mgt *res_mgt; + struct nbl_dispatch_mgt *disp_mgt; struct nbl_channel_mgt *chan_mgt; }; =20 diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c new file mode 100644 index 000000000000..966fee2dec8b --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ +#include +#include +#include "nbl_dispatch.h" + +static void nbl_disp_deinit_module(struct nbl_dispatch_mgt *disp_mgt) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + + if (res_ops->deinit_module) + res_ops->deinit_module(p); +} + +static int nbl_disp_init_module(struct nbl_dispatch_mgt *disp_mgt) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + + if (res_ops->init_module) + return res_ops->init_module(p); + return -EOPNOTSUPP; +} + +static void nbl_disp_set_ctrl_bit(struct nbl_dispatch_mgt *disp_mgt, u32 l= vl) +{ + set_bit(lvl, disp_mgt->ctrl_lvl); +} + +static void nbl_disp_refresh_ctrl_ops(struct nbl_dispatch_mgt *disp_mgt) +{ + struct nbl_dispatch_ops *disp_ops =3D disp_mgt->disp_ops_tbl->ops; + + if (test_bit(NBL_DISP_CTRL_LVL_MGT, disp_mgt->ctrl_lvl)) { + disp_ops->init_module =3D nbl_disp_init_module; + disp_ops->deinit_module =3D nbl_disp_deinit_module; + } +} + +static struct nbl_dispatch_mgt * +nbl_disp_setup_disp_mgt(struct nbl_common_info *common) +{ + struct nbl_dispatch_mgt *disp_mgt; + struct device *dev =3D common->dev; + + disp_mgt =3D devm_kzalloc(dev, sizeof(*disp_mgt), GFP_KERNEL); + if (!disp_mgt) + return ERR_PTR(-ENOMEM); + + disp_mgt->common =3D common; + return disp_mgt; +} + +static struct nbl_dispatch_ops_tbl * +nbl_disp_setup_ops(struct device *dev, struct nbl_dispatch_mgt *disp_mgt) +{ + struct nbl_dispatch_ops_tbl *disp_ops_tbl; + struct nbl_dispatch_ops *disp_ops; + + disp_ops_tbl =3D devm_kzalloc(dev, sizeof(*disp_ops_tbl), GFP_KERNEL); + if (!disp_ops_tbl) + return ERR_PTR(-ENOMEM); + + disp_ops =3D devm_kzalloc(dev, sizeof(*disp_ops), GFP_KERNEL); + if (!disp_ops) + return ERR_PTR(-ENOMEM); + + disp_ops_tbl->ops =3D disp_ops; + disp_ops_tbl->priv =3D disp_mgt; + + return disp_ops_tbl; +} + +int nbl_disp_init(struct nbl_adapter *adapter) +{ + struct nbl_common_info *common =3D &adapter->common; + struct nbl_dispatch_ops_tbl *disp_ops_tbl; + struct nbl_resource_ops_tbl *res_ops_tbl =3D + adapter->intf.resource_ops_tbl; + struct nbl_channel_ops_tbl *chan_ops_tbl =3D + adapter->intf.channel_ops_tbl; + struct device *dev =3D &adapter->pdev->dev; + struct nbl_dispatch_mgt *disp_mgt; + int ret; + + disp_mgt =3D nbl_disp_setup_disp_mgt(common); + if (IS_ERR(disp_mgt)) { + ret =3D PTR_ERR(disp_mgt); + return ret; + } + + disp_ops_tbl =3D nbl_disp_setup_ops(dev, disp_mgt); + if (IS_ERR(disp_ops_tbl)) { + ret =3D PTR_ERR(disp_ops_tbl); + return ret; + } + + disp_mgt->res_ops_tbl =3D res_ops_tbl; + disp_mgt->chan_ops_tbl =3D chan_ops_tbl; + disp_mgt->disp_ops_tbl =3D disp_ops_tbl; + adapter->core.disp_mgt =3D disp_mgt; + adapter->intf.dispatch_ops_tbl =3D disp_ops_tbl; + + if (common->has_ctrl) + nbl_disp_set_ctrl_bit(disp_mgt, NBL_DISP_CTRL_LVL_MGT); + + nbl_disp_refresh_ctrl_ops(disp_mgt); + return 0; +} + +void nbl_disp_remove(struct nbl_adapter *adapter) +{ + /* Dispatch structures are allocated via devm */ +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h new file mode 100644 index 000000000000..a7e5802344b4 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_DISPATCH_H_ +#define _NBL_DISPATCH_H_ +#include "../nbl_include/nbl_include.h" +#include "../nbl_include/nbl_def_channel.h" +#include "../nbl_include/nbl_def_resource.h" +#include "../nbl_include/nbl_def_dispatch.h" +#include "../nbl_include/nbl_def_common.h" +#include "../nbl_core.h" + +struct nbl_dispatch_mgt { + struct nbl_common_info *common; + struct nbl_resource_ops_tbl *res_ops_tbl; + struct nbl_channel_ops_tbl *chan_ops_tbl; + struct nbl_dispatch_ops_tbl *disp_ops_tbl; + DECLARE_BITMAP(ctrl_lvl, NBL_DISP_CTRL_LVL_MAX); +}; + +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_cha= nnel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel= .h index bf971121d2ec..c9c99c967037 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h @@ -7,6 +7,7 @@ #define _NBL_DEF_CHANNEL_H_ =20 #include +#include =20 struct nbl_channel_mgt; struct nbl_adapter; @@ -39,6 +40,32 @@ enum nbl_chan_state { NBL_CHAN_STATE_NBITS }; =20 +struct nbl_chan_param_cfg_msix_map { + __le16 num_net_msix; + __le16 num_others_msix; + __le16 msix_mask_en; + __le16 rsvd; +}; + +struct nbl_chan_param_set_mailbox_irq { + __le16 vector_id; + u8 en_msix; + u8 rsvd; +}; + +struct nbl_chan_param_get_vsi_id { + __le16 vsi_id; + __le16 type; +}; + +struct nbl_chan_param_get_eth_id { + __le16 vsi_id; + u8 eth_num; + u8 eth_id; + u8 logic_eth_id; + u8 rsvd[3]; +}; + struct nbl_board_port_info { u8 eth_num; u8 eth_speed; @@ -46,6 +73,14 @@ struct nbl_board_port_info { u8 rsv[5]; }; =20 +static_assert(sizeof(struct nbl_chan_param_cfg_msix_map) =3D=3D 8, + "nbl_chan_param_cfg_msix_map size must be 8 bytes"); +static_assert(sizeof(struct nbl_chan_param_set_mailbox_irq) =3D=3D 4, + "nbl_chan_param_set_mailbox_irq size must be 4 bytes"); +static_assert(sizeof(struct nbl_chan_param_get_vsi_id) =3D=3D 4, + "nbl_chan_param_get_vsi_id size must be 4 bytes"); +static_assert(sizeof(struct nbl_chan_param_get_eth_id) =3D=3D 8, + "nbl_chan_param_get_eth_id size must be 8 bytes"); static_assert(sizeof(struct nbl_board_port_info) =3D=3D 8, "nbl_board_port_info size must be 8 bytes"); =20 diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dis= patch.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispat= ch.h new file mode 100644 index 000000000000..b0daabb05d39 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_DEF_DISPATCH_H_ +#define _NBL_DEF_DISPATCH_H_ + +#include + +struct nbl_dispatch_mgt; +struct nbl_adapter; +enum { + NBL_DISP_CTRL_LVL_MGT, + NBL_DISP_CTRL_LVL_MAX, +}; + +/** + * struct nbl_dispatch_ops - dispatch control plane operation callbacks + * @init_module: dispatch layer initialization, ONLY valid on Control PF, + * caller must check has_ctrl guard + * @deinit_module: dispatch layer cleanup, ONLY valid on Control PF, + * caller must check has_ctrl guard + * Warning: All ops except init_module/deinit_module can be safely called + * on PF/VF; init/deinit hooks are control-PF exclusive to prevent NULL pt= r. + */ +struct nbl_dispatch_ops { + int (*init_module)(struct nbl_dispatch_mgt *disp_mgt); + void (*deinit_module)(struct nbl_dispatch_mgt *disp_mgt); +}; + +struct nbl_dispatch_ops_tbl { + struct nbl_dispatch_ops *ops; + struct nbl_dispatch_mgt *priv; +}; + +int nbl_disp_init(struct nbl_adapter *adapter); +void nbl_disp_remove(struct nbl_adapter *adapter); +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_main.c index 6df91c751bc5..5979f62d7f81 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c @@ -11,6 +11,7 @@ #include "nbl_include/nbl_def_channel.h" #include "nbl_include/nbl_def_hw.h" #include "nbl_include/nbl_def_resource.h" +#include "nbl_include/nbl_def_dispatch.h" #include "nbl_include/nbl_def_common.h" #include "nbl_core.h" =20 @@ -48,7 +49,13 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, ret =3D nbl_res_init_leonis(adapter); if (ret) goto res_init_fail; + + ret =3D nbl_disp_init(adapter); + if (ret) + goto disp_init_fail; return adapter; +disp_init_fail: + nbl_res_remove_leonis(adapter); res_init_fail: nbl_chan_remove_common(adapter); chan_init_fail: @@ -59,6 +66,7 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, =20 void nbl_core_remove(struct nbl_adapter *adapter) { + nbl_disp_remove(adapter); nbl_res_remove_leonis(adapter); nbl_chan_remove_common(adapter); nbl_hw_remove_leonis(adapter); --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-172.mail.aliyun.com (out28-172.mail.aliyun.com [115.124.28.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 08B0B53D9EF; Tue, 22 Sep 2026 12:03:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078613; cv=none; b=ebqPN35dBjFZJlIx1XsZ352WbMqFRriKiBW6Fh63NKfFjcZZ5nOfjp9yDDCLwYeJH4kLUYowcoQBLJutymVeQwXCDjqaxcC7exJrAEzRkmTBO4OoB9+UMHLdDkEkj3dwXuVde2M3ICgnlzuBZ17FOxkkGakc58GIUs5gFIi3G6A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078613; c=relaxed/simple; bh=45J+KLpvlZjAJxAx9hLRL5c2mNI9C+49Jux0z+8TIdw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dSA4nuJaLycofeoFJXeOqvmqR4GOvTWV71csHcxCHGHuc74f1s7mD7U0eodia5JRdltkSuA/qJOTEQXBMgMaJgDX8gtKSTJj/KtLNQKGU2SY3p0QF+oyC9J/ZqRkOIqLfB28oGtmxxNxOd8vyiEy20mY1Lqk215tuRJkT6yUmCw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_social|0.0899838-0.00233551-0.907681;FP=17748942668922799670|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033037028158;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPV5_1790078599; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPV5_1790078599 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:20 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 6/8] net/nebula-matrix: dispatch: implement channel RPC framework and serialize hardware ops Date: Tue, 22 Sep 2026 20:03:03 +0800 Message-ID: <20260922120311.86593-7-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: illusion wang Add bidirectional mailbox RPC for cross-PF resource coordination, implementing request/response handlers for five operations: configure_msix_map, destroy_msix_map, set_mailbox_irq, get_vsi_id, get_eth_id. Dispatch operations are resolved dynamically based on PF control capability via nbl_disp_refresh_ctrl_ops(): - Control PF (NBL_DISP_CTRL_LVL_MGT): invokes local hardware ops directly. - Non-control PF with net capability (NBL_DISP_CTRL_LVL_NET): forwards requests via mailbox RPC to the manager PF. - Neither level set: all dispatch ops are NULL. Introduce per-dispatch mutex ops_mutex_lock to serialize mutable hardware operations (cfg_msix_map, destroy_msix_map, set_mailbox_irq). The mutex protects both local control-PF call paths and asynchronous remote mailbox RPC response handlers to eliminate race windows. Read-only get_vsi_id() / get_eth_id() access static init-time metadata; disp_ops table updates only happen during probe, so no runtime concurrent modification. nbl_disp_setup_msg() registers five dispatch-layer wire message response handlers with the channel layer: NBL_CHAN_MSG_CONFIGURE_MSIX_MAP, NBL_CHAN_MSG_DESTROY_MSIX_MAP, NBL_CHAN_MSG_MAILBOX_SET_IRQ, NBL_CHAN_MSG_GET_VSI_ID, NBL_CHAN_MSG_GET_ETH_ID. Message IDs are non-contiguous to preserve compatibility with reserved and deprecated message entries. Extend wire protocol with new message types. Every responder validates: - src_id against common->max_pf at runtime; rejects VF and out-of-range function IDs with -EPERM; - incoming payload length against the expected parameter struct size before parsing; - unimplemented operation via NULL function pointer check before dispatch. Remote responders return standard Linux errnos directly on the wire. Transport errors are returned unchanged from chan_ops->send_msg(). get_vsi_id_req() and get_eth_id_req() verify chan_send.ack_len matches sizeof(result) before dereferencing the response, rejecting truncated ACKs with -EREMOTEIO to prevent adoption of zeroed identifiers from a short reply. destroy_msix_map responder clears the mailbox QINFO MSIX routing (MSIX_IDX / MSIX_IDX_VALID) as Step 0 before tearing down the MSI-X map, ensuring a recycled global vector cannot trigger an interrupt owned by a different function. cfg_msix_map pre-allocates all coherent DMA buffers and global vector indices before destroying the old configuration, then swaps the new resources into the per-function entry to avoid interrupt loss during reconfiguration. RPC caller precondition: disable mailbox IRQ_RDY and switch send_msg to polling path before invoking cfg_msix_map / destroy_msix_map RPC, because responder modifies mailbox MSIX routing and ACK cannot rely on interrupt wakeup. Signed-off-by: illusion wang --- .../nebula-matrix/nbl/nbl_core/nbl_dispatch.c | 538 +++++++++++++++++- .../nebula-matrix/nbl/nbl_core/nbl_dispatch.h | 2 + .../nbl/nbl_include/nbl_def_channel.h | 5 + .../nbl/nbl_include/nbl_def_dispatch.h | 17 + 4 files changed, 561 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c index 966fee2dec8b..d12ef8683528 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.c @@ -3,9 +3,176 @@ * Copyright (c) 2026 Nebula Matrix Limited. */ #include +#include #include #include "nbl_dispatch.h" =20 +static int nbl_disp_chan_get_vsi_id_req(struct nbl_dispatch_mgt *disp_mgt, + u16 type, u16 *vsi_id) +{ + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_common_info *common =3D disp_mgt->common; + struct nbl_chan_param_get_vsi_id result =3D { 0 }; + struct nbl_chan_param_get_vsi_id param =3D { 0 }; + struct nbl_chan_send_info chan_send =3D {0}; + int ret; + + param.type =3D cpu_to_le16(type); + + nbl_chan_fill_send_info(&chan_send, common->mgt_pf, + NBL_CHAN_MSG_GET_VSI_ID, + ¶m, sizeof(param), &result, + sizeof(result), 1); + ret =3D chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send); + if (ret) + return ret; + if (chan_send.ack_len !=3D sizeof(result)) { + dev_err(disp_mgt->common->dev, + "get_vsi_id: short ACK, ack_len=3D%u expected %zu\n", + chan_send.ack_len, sizeof(result)); + return -EREMOTEIO; + } + *vsi_id =3D le16_to_cpu(result.vsi_id); + return 0; +} + +static void nbl_disp_chan_get_vsi_id_resp(void *priv, u16 src_id, u16 msg_= id, + void *data, u32 data_len) +{ + struct nbl_dispatch_mgt *disp_mgt =3D (struct nbl_dispatch_mgt *)priv; + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct device *dev =3D disp_mgt->common->dev; + struct nbl_chan_param_get_vsi_id result =3D { 0 }; + struct nbl_chan_param_get_vsi_id param =3D { 0 }; + struct nbl_chan_ack_info chan_ack; + int err =3D 0; + u16 vsi_id =3D 0; + u32 rel_pf_id; + int ret; + + ret =3D nbl_common_func_id_to_rel_pf_id(disp_mgt->common, src_id, + &rel_pf_id); + if (ret) { + err =3D -EPERM; + goto ack_out; + } + if (rel_pf_id >=3D disp_mgt->common->max_pf) { + err =3D -EPERM; + goto ack_out; + } + if (data_len < sizeof(param)) { + err =3D -EREMOTEIO; + goto ack_out; + } + memcpy(¶m, data, sizeof(param)); + + if (res_ops->get_vsi_id) { + ret =3D res_ops->get_vsi_id(p, src_id, le16_to_cpu(param.type), + &vsi_id); + if (ret) + err =3D -EREMOTEIO; + } else { + err =3D -EOPNOTSUPP; + } + + result.vsi_id =3D cpu_to_le16(vsi_id); +ack_out: + nbl_chan_fill_ack_info(&chan_ack, src_id, + NBL_CHAN_MSG_GET_VSI_ID, msg_id, err, + &result, sizeof(result)); + ret =3D chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack); + if (ret) + dev_err(dev, + "channel send ack failed with ret: %d, msg_type: %d\n", + ret, NBL_CHAN_MSG_GET_VSI_ID); +} + +static int nbl_disp_chan_get_eth_id_req(struct nbl_dispatch_mgt *disp_mgt, + u16 vsi_id, u8 *eth_num, u8 *eth_id, + u8 *logic_eth_id) +{ + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_common_info *common =3D disp_mgt->common; + struct nbl_chan_param_get_eth_id result =3D { 0 }; + struct nbl_chan_param_get_eth_id param =3D { 0 }; + struct nbl_chan_send_info chan_send =3D {0}; + int ret; + + param.vsi_id =3D cpu_to_le16(vsi_id); + + nbl_chan_fill_send_info(&chan_send, common->mgt_pf, + NBL_CHAN_MSG_GET_ETH_ID, + ¶m, sizeof(param), &result, + sizeof(result), 1); + ret =3D chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send); + if (ret) + return ret; + if (chan_send.ack_len !=3D sizeof(result)) { + dev_err(disp_mgt->common->dev, + "get_eth_id: short ACK, ack_len=3D%u expected %zu\n", + chan_send.ack_len, sizeof(result)); + return -EREMOTEIO; + } + *eth_num =3D result.eth_num; + *eth_id =3D result.eth_id; + *logic_eth_id =3D result.logic_eth_id; + + return 0; +} + +static void nbl_disp_chan_get_eth_id_resp(void *priv, u16 src_id, u16 msg_= id, + void *data, u32 data_len) +{ + struct nbl_dispatch_mgt *disp_mgt =3D (struct nbl_dispatch_mgt *)priv; + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_chan_param_get_eth_id result =3D { 0 }; + struct nbl_chan_param_get_eth_id param =3D { 0 }; + struct device *dev =3D disp_mgt->common->dev; + struct nbl_chan_ack_info chan_ack; + int err =3D 0; + u32 rel_pf_id; + int ret; + + ret =3D nbl_common_func_id_to_rel_pf_id(disp_mgt->common, src_id, + &rel_pf_id); + if (ret) { + err =3D -EPERM; + goto ack_out; + } + if (rel_pf_id >=3D disp_mgt->common->max_pf) { + err =3D -EPERM; + goto ack_out; + } + if (data_len < sizeof(param)) { + err =3D -EREMOTEIO; + goto ack_out; + } + memcpy(¶m, data, sizeof(param)); + + if (res_ops->get_eth_id) { + ret =3D res_ops->get_eth_id(p, src_id, le16_to_cpu(param.vsi_id), + &result.eth_num, &result.eth_id, + &result.logic_eth_id); + if (ret) + err =3D -EREMOTEIO; + } else { + err =3D -EOPNOTSUPP; + } +ack_out: + nbl_chan_fill_ack_info(&chan_ack, src_id, + NBL_CHAN_MSG_GET_ETH_ID, msg_id, err, + &result, sizeof(result)); + ret =3D chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack); + if (ret) + dev_err(dev, + "channel send ack failed with ret: %d, msg_type: %d\n", + ret, NBL_CHAN_MSG_GET_ETH_ID); +} + static void nbl_disp_deinit_module(struct nbl_dispatch_mgt *disp_mgt) { struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; @@ -25,6 +192,347 @@ static int nbl_disp_init_module(struct nbl_dispatch_mg= t *disp_mgt) return -EOPNOTSUPP; } =20 +static int nbl_disp_cfg_msix_map(struct nbl_dispatch_mgt *disp_mgt, + u16 num_net_msix, u16 num_others_msix, + bool net_msix_mask_en) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_common_info *common =3D disp_mgt->common; + int ret; + + if (!res_ops->cfg_msix_map) + return -EOPNOTSUPP; + mutex_lock(&disp_mgt->ops_mutex_lock); + ret =3D res_ops->cfg_msix_map(p, common->mgt_pf, num_net_msix, + num_others_msix, net_msix_mask_en); + mutex_unlock(&disp_mgt->ops_mutex_lock); + return ret; +} + +static int +nbl_disp_chan_cfg_msix_map_req(struct nbl_dispatch_mgt *disp_mgt, + u16 num_net_msix, u16 num_others_msix, + bool net_msix_mask_en) +{ + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_common_info *common =3D disp_mgt->common; + struct nbl_chan_param_cfg_msix_map param =3D { 0 }; + struct nbl_chan_send_info chan_send =3D {0}; + int ret; + + param.num_net_msix =3D cpu_to_le16(num_net_msix); + param.num_others_msix =3D cpu_to_le16(num_others_msix); + param.msix_mask_en =3D cpu_to_le16(!!net_msix_mask_en); + + nbl_chan_fill_send_info(&chan_send, common->mgt_pf, + NBL_CHAN_MSG_CONFIGURE_MSIX_MAP, + ¶m, sizeof(param), + NULL, 0, 1); + ret =3D chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send); + if (ret) + return ret; + return 0; +} + +/* + * Precondition: caller must disable mailbox IRQ_RDY and switch send_msg + * to polling path before issuing cfg_msix_map RPC. + * The responder will disable mailbox MSIX routing during resource ops, + * so ACK cannot rely on interrupt wakeup. + */ +static void nbl_disp_chan_cfg_msix_map_resp(void *priv, u16 src_id, u16 ms= g_id, + void *data, u32 data_len) +{ + struct nbl_dispatch_mgt *disp_mgt =3D (struct nbl_dispatch_mgt *)priv; + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct device *dev =3D disp_mgt->common->dev; + struct nbl_chan_param_cfg_msix_map param =3D { 0 }; + struct nbl_chan_ack_info chan_ack; + int err =3D 0; + u32 rel_pf_id; + int ret; + + ret =3D nbl_common_func_id_to_rel_pf_id(disp_mgt->common, src_id, + &rel_pf_id); + if (ret) { + err =3D -EPERM; + goto ack_out; + } + if (rel_pf_id >=3D disp_mgt->common->max_pf) { + err =3D -EPERM; + goto ack_out; + } + if (data_len < sizeof(param)) { + err =3D -EREMOTEIO; + goto ack_out; + } + memcpy(¶m, data, sizeof(param)); + + if (res_ops->cfg_msix_map) { + mutex_lock(&disp_mgt->ops_mutex_lock); + ret =3D res_ops->cfg_msix_map(p, src_id, + le16_to_cpu(param.num_net_msix), + le16_to_cpu(param.num_others_msix), + !!le16_to_cpu(param.msix_mask_en)); + mutex_unlock(&disp_mgt->ops_mutex_lock); + if (ret) + err =3D -EREMOTEIO; + } else { + err =3D -EOPNOTSUPP; + } +ack_out: + nbl_chan_fill_ack_info(&chan_ack, src_id, + NBL_CHAN_MSG_CONFIGURE_MSIX_MAP, msg_id, + err, NULL, 0); + ret =3D chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack); + if (ret) + dev_err(dev, + "channel send ack failed with ret: %d, msg_type: %d\n", + ret, NBL_CHAN_MSG_CONFIGURE_MSIX_MAP); +} + +static int nbl_disp_chan_destroy_msix_map_req(struct nbl_dispatch_mgt *dis= p_mgt) +{ + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_common_info *common =3D disp_mgt->common; + struct nbl_chan_send_info chan_send =3D {0}; + int ret; + + nbl_chan_fill_send_info(&chan_send, common->mgt_pf, + NBL_CHAN_MSG_DESTROY_MSIX_MAP, + NULL, 0, NULL, 0, 1); + ret =3D chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send); + if (ret) + return ret; + return 0; +} + +/* + * Precondition: caller must disable mailbox IRQ_RDY and switch send_msg + * to polling path before issuing destroy_msix_map. + * The responder will disable mailbox MSIX routing during resource ops, + * so ACK cannot rely on interrupt wakeup. + */ +static void nbl_disp_chan_destroy_msix_map_resp(void *priv, u16 src_id, + u16 msg_id, void *data, + u32 data_len) +{ + struct nbl_dispatch_mgt *disp_mgt =3D (struct nbl_dispatch_mgt *)priv; + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct device *dev =3D disp_mgt->common->dev; + struct nbl_chan_ack_info chan_ack; + int err =3D 0; + u32 rel_pf_id; + int ret; + + ret =3D nbl_common_func_id_to_rel_pf_id(disp_mgt->common, src_id, + &rel_pf_id); + if (ret) { + err =3D -EPERM; + goto ack_out; + } + if (rel_pf_id >=3D disp_mgt->common->max_pf) { + err =3D -EPERM; + goto ack_out; + } + if (res_ops->destroy_msix_map) { + mutex_lock(&disp_mgt->ops_mutex_lock); + ret =3D res_ops->destroy_msix_map(p, src_id); + mutex_unlock(&disp_mgt->ops_mutex_lock); + if (ret) + err =3D -EREMOTEIO; + } else { + err =3D -EOPNOTSUPP; + } +ack_out: + nbl_chan_fill_ack_info(&chan_ack, src_id, + NBL_CHAN_MSG_DESTROY_MSIX_MAP, msg_id, + err, NULL, 0); + ret =3D chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack); + if (ret) + dev_err(dev, + "channel send ack failed with ret: %d, msg_type: %d\n", + ret, NBL_CHAN_MSG_DESTROY_MSIX_MAP); +} + +static int nbl_disp_chan_set_mailbox_irq_req(struct nbl_dispatch_mgt *disp= _mgt, + u16 vector_id, bool en_msix) +{ + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_chan_param_set_mailbox_irq param =3D { 0 }; + struct nbl_common_info *common =3D disp_mgt->common; + struct nbl_chan_send_info chan_send =3D {0}; + int ret; + + param.vector_id =3D cpu_to_le16(vector_id); + param.en_msix =3D !!en_msix; + + nbl_chan_fill_send_info(&chan_send, common->mgt_pf, + NBL_CHAN_MSG_MAILBOX_SET_IRQ, + ¶m, sizeof(param), NULL, 0, 1); + ret =3D chan_ops->send_msg(disp_mgt->chan_ops_tbl->priv, &chan_send); + if (ret) + return ret; + return 0; +} + +static void nbl_disp_chan_set_mailbox_irq_resp(void *priv, u16 src_id, + u16 msg_id, void *data, + u32 data_len) +{ + struct nbl_dispatch_mgt *disp_mgt =3D (struct nbl_dispatch_mgt *)priv; + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_chan_param_set_mailbox_irq param =3D { 0 }; + struct device *dev =3D disp_mgt->common->dev; + struct nbl_chan_ack_info chan_ack; + int err =3D 0; + u16 vector_id; + u32 rel_pf_id; + bool en_msix; + int ret; + + ret =3D nbl_common_func_id_to_rel_pf_id(disp_mgt->common, src_id, + &rel_pf_id); + if (ret) { + err =3D -EPERM; + goto ack_out; + } + if (rel_pf_id >=3D disp_mgt->common->max_pf) { + err =3D -EPERM; + goto ack_out; + } + if (data_len < sizeof(param)) { + err =3D -EREMOTEIO; + goto ack_out; + } + memcpy(¶m, data, sizeof(param)); + vector_id =3D le16_to_cpu(param.vector_id); + en_msix =3D !!param.en_msix; + + if (res_ops->set_mailbox_irq) { + mutex_lock(&disp_mgt->ops_mutex_lock); + ret =3D res_ops->set_mailbox_irq(p, src_id, vector_id, en_msix); + mutex_unlock(&disp_mgt->ops_mutex_lock); + if (ret) + err =3D -EREMOTEIO; + } else { + err =3D -EOPNOTSUPP; + } + +ack_out: + nbl_chan_fill_ack_info(&chan_ack, src_id, + NBL_CHAN_MSG_MAILBOX_SET_IRQ, msg_id, + err, NULL, 0); + ret =3D chan_ops->send_ack(disp_mgt->chan_ops_tbl->priv, &chan_ack); + if (ret) + dev_err(dev, + "channel send ack failed with ret: %d, msg_type: %d\n", + ret, NBL_CHAN_MSG_MAILBOX_SET_IRQ); +} + +static int nbl_disp_destroy_msix_map(struct nbl_dispatch_mgt *disp_mgt) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_common_info *common =3D disp_mgt->common; + int ret; + + if (!res_ops->destroy_msix_map) + return -EOPNOTSUPP; + mutex_lock(&disp_mgt->ops_mutex_lock); + ret =3D res_ops->destroy_msix_map(p, common->mgt_pf); + mutex_unlock(&disp_mgt->ops_mutex_lock); + return ret; +} + +static int nbl_disp_set_mailbox_irq(struct nbl_dispatch_mgt *disp_mgt, + u16 vector_id, bool en_msix) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_common_info *common =3D disp_mgt->common; + int ret; + + if (!res_ops->set_mailbox_irq) + return -EOPNOTSUPP; + mutex_lock(&disp_mgt->ops_mutex_lock); + ret =3D res_ops->set_mailbox_irq(p, common->mgt_pf, vector_id, en_msix); + mutex_unlock(&disp_mgt->ops_mutex_lock); + return ret; +} + +static int nbl_disp_get_vsi_id(struct nbl_dispatch_mgt *disp_mgt, u16 type, + u16 *vsi_id) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_common_info *common =3D disp_mgt->common; + + if (res_ops->get_vsi_id) + return res_ops->get_vsi_id(p, common->mgt_pf, type, vsi_id); + return -EOPNOTSUPP; +} + +static int nbl_disp_get_eth_id(struct nbl_dispatch_mgt *disp_mgt, u16 vsi_= id, + u8 *eth_num, u8 *eth_id, u8 *logic_eth_id) +{ + struct nbl_resource_ops *res_ops =3D disp_mgt->res_ops_tbl->ops; + struct nbl_resource_mgt *p =3D disp_mgt->res_ops_tbl->priv; + struct nbl_common_info *common =3D disp_mgt->common; + + if (res_ops->get_eth_id) + return res_ops->get_eth_id(p, common->mgt_pf, vsi_id, + eth_num, eth_id, logic_eth_id); + return -EOPNOTSUPP; +} + +static int nbl_disp_setup_msg(struct nbl_dispatch_mgt *disp_mgt) +{ + struct nbl_channel_ops *chan_ops =3D disp_mgt->chan_ops_tbl->ops; + struct nbl_channel_mgt *p =3D disp_mgt->chan_ops_tbl->priv; + int ret =3D 0; + int _ret; + + _ret =3D chan_ops->register_msg(p, NBL_CHAN_MSG_CONFIGURE_MSIX_MAP, + nbl_disp_chan_cfg_msix_map_resp, + disp_mgt); + if (_ret < 0 && !ret) + ret =3D _ret; + + _ret =3D chan_ops->register_msg(p, NBL_CHAN_MSG_DESTROY_MSIX_MAP, + nbl_disp_chan_destroy_msix_map_resp, + disp_mgt); + if (_ret < 0 && !ret) + ret =3D _ret; + + _ret =3D chan_ops->register_msg(p, NBL_CHAN_MSG_MAILBOX_SET_IRQ, + nbl_disp_chan_set_mailbox_irq_resp, + disp_mgt); + if (_ret < 0 && !ret) + ret =3D _ret; + + _ret =3D chan_ops->register_msg(p, NBL_CHAN_MSG_GET_VSI_ID, + nbl_disp_chan_get_vsi_id_resp, + disp_mgt); + if (_ret < 0 && !ret) + ret =3D _ret; + + _ret =3D chan_ops->register_msg(p, NBL_CHAN_MSG_GET_ETH_ID, + nbl_disp_chan_get_eth_id_resp, + disp_mgt); + if (_ret < 0 && !ret) + ret =3D _ret; + + return ret; +} + static void nbl_disp_set_ctrl_bit(struct nbl_dispatch_mgt *disp_mgt, u32 l= vl) { set_bit(lvl, disp_mgt->ctrl_lvl); @@ -34,9 +542,22 @@ static void nbl_disp_refresh_ctrl_ops(struct nbl_dispat= ch_mgt *disp_mgt) { struct nbl_dispatch_ops *disp_ops =3D disp_mgt->disp_ops_tbl->ops; =20 + memset(disp_ops, 0, sizeof(*disp_ops)); if (test_bit(NBL_DISP_CTRL_LVL_MGT, disp_mgt->ctrl_lvl)) { disp_ops->init_module =3D nbl_disp_init_module; disp_ops->deinit_module =3D nbl_disp_deinit_module; + disp_ops->cfg_msix_map =3D nbl_disp_cfg_msix_map; + disp_ops->destroy_msix_map =3D nbl_disp_destroy_msix_map; + disp_ops->set_mailbox_irq =3D nbl_disp_set_mailbox_irq; + disp_ops->get_vsi_id =3D nbl_disp_get_vsi_id; + disp_ops->get_eth_id =3D nbl_disp_get_eth_id; + } else if (test_bit(NBL_DISP_CTRL_LVL_NET, disp_mgt->ctrl_lvl)) { + disp_ops->cfg_msix_map =3D + nbl_disp_chan_cfg_msix_map_req; + disp_ops->destroy_msix_map =3D nbl_disp_chan_destroy_msix_map_req; + disp_ops->set_mailbox_irq =3D nbl_disp_chan_set_mailbox_irq_req; + disp_ops->get_vsi_id =3D nbl_disp_chan_get_vsi_id_req; + disp_ops->get_eth_id =3D nbl_disp_chan_get_eth_id_req; } } =20 @@ -45,12 +566,16 @@ nbl_disp_setup_disp_mgt(struct nbl_common_info *common) { struct nbl_dispatch_mgt *disp_mgt; struct device *dev =3D common->dev; + int err; =20 disp_mgt =3D devm_kzalloc(dev, sizeof(*disp_mgt), GFP_KERNEL); if (!disp_mgt) return ERR_PTR(-ENOMEM); =20 disp_mgt->common =3D common; + err =3D devm_mutex_init(common->dev, &disp_mgt->ops_mutex_lock); + if (err) + return ERR_PTR(err); return disp_mgt; } =20 @@ -104,14 +629,25 @@ int nbl_disp_init(struct nbl_adapter *adapter) adapter->core.disp_mgt =3D disp_mgt; adapter->intf.dispatch_ops_tbl =3D disp_ops_tbl; =20 + ret =3D nbl_disp_setup_msg(disp_mgt); + if (ret) + return ret; + if (common->has_ctrl) nbl_disp_set_ctrl_bit(disp_mgt, NBL_DISP_CTRL_LVL_MGT); =20 + if (common->has_net) + nbl_disp_set_ctrl_bit(disp_mgt, NBL_DISP_CTRL_LVL_NET); nbl_disp_refresh_ctrl_ops(disp_mgt); return 0; } =20 void nbl_disp_remove(struct nbl_adapter *adapter) { - /* Dispatch structures are allocated via devm */ + /* + * Dispatch structures are allocated via devm. + * Message handlers registered by nbl_disp_setup_msg() are owned by + * channel layer; they are unregistered in nbl_chan_remove_common(), + * not here. + */ } diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h= b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h index a7e5802344b4..8549048f76e9 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dispatch.h @@ -18,6 +18,8 @@ struct nbl_dispatch_mgt { struct nbl_channel_ops_tbl *chan_ops_tbl; struct nbl_dispatch_ops_tbl *disp_ops_tbl; DECLARE_BITMAP(ctrl_lvl, NBL_DISP_CTRL_LVL_MAX); + /* use for the caller not in interrupt */ + struct mutex ops_mutex_lock; }; =20 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_cha= nnel.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel= .h index c9c99c967037..5aa16d4cd432 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_channel.h @@ -31,6 +31,11 @@ typedef void (*nbl_chan_resp)(void *, u16, u16, void *, = u32); */ enum nbl_chan_msg_type { NBL_CHAN_MSG_ACK =3D 0, + NBL_CHAN_MSG_CONFIGURE_MSIX_MAP =3D 17, + NBL_CHAN_MSG_DESTROY_MSIX_MAP =3D 18, + NBL_CHAN_MSG_MAILBOX_SET_IRQ =3D 19, + NBL_CHAN_MSG_GET_VSI_ID =3D 21, + NBL_CHAN_MSG_GET_ETH_ID =3D 67, /* mailbox msg end */ NBL_CHAN_MSG_MAILBOX_MAX, }; diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dis= patch.h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispat= ch.h index b0daabb05d39..61083a750da4 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dispatch.h @@ -12,6 +12,7 @@ struct nbl_dispatch_mgt; struct nbl_adapter; enum { NBL_DISP_CTRL_LVL_MGT, + NBL_DISP_CTRL_LVL_NET, NBL_DISP_CTRL_LVL_MAX, }; =20 @@ -21,12 +22,28 @@ enum { * caller must check has_ctrl guard * @deinit_module: dispatch layer cleanup, ONLY valid on Control PF, * caller must check has_ctrl guard + * @cfg_msix_map: configure function msix mapping table + * @destroy_msix_map: tear down msix mapping resource + * @set_mailbox_irq: bind mailbox interrupt to specified msix vector + * @get_vsi_id: resolve VSI ID by type + * @get_eth_id: resolve eth port info from VSI ID + * * Warning: All ops except init_module/deinit_module can be safely called * on PF/VF; init/deinit hooks are control-PF exclusive to prevent NULL pt= r. */ struct nbl_dispatch_ops { int (*init_module)(struct nbl_dispatch_mgt *disp_mgt); void (*deinit_module)(struct nbl_dispatch_mgt *disp_mgt); + int (*cfg_msix_map)(struct nbl_dispatch_mgt *disp_mgt, + u16 num_net_msix, u16 num_others_msix, + bool net_msix_mask_en); + int (*destroy_msix_map)(struct nbl_dispatch_mgt *disp_mgt); + int (*set_mailbox_irq)(struct nbl_dispatch_mgt *disp_mgt, + u16 vector_id, bool en_msix); + int (*get_vsi_id)(struct nbl_dispatch_mgt *disp_mgt, u16 type, + u16 *vsi_id); + int (*get_eth_id)(struct nbl_dispatch_mgt *disp_mgt, u16 vsi_id, + u8 *eth_num, u8 *eth_id, u8 *logic_eth_id); }; =20 struct nbl_dispatch_ops_tbl { --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-123.mail.aliyun.com (out28-123.mail.aliyun.com [115.124.28.123]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CB8DF53D0C5; Tue, 22 Sep 2026 12:03:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.123 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078614; cv=none; b=iFn5+mchdsKL6l3Zwomvz1D2arDwzBwJgB9ta161wAzVRC/QtwM1eSOxaz1+Vxjwh7fS3uFb6twlEowk+Tg9IyQJy9oMh2l112C/I5K9sIFQQkLIr2TvePEaBimDRwMXytmWlFfpEGOYeKs8sscEAmpuXQJIM6uH8MFGU3sX/v8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078614; c=relaxed/simple; bh=1Y4xHZkaOnnpWmXdsaPo06WyDphbp+14Ql/B1YGaq4E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ULLWflhOxJzX++G0P1ZLzjpr3M63MN8qjuiEw0SaLz6qJtdQyNIHbvokgcn4wJKauuQQ4PhmtCs0wxVITI+rpKMc97hp4ifbw6ng6/ZNHYgIJDI4DPQKXYH4XeyApgmZxcfeN8rLZr5oiKJy9NvvgjTRieqVIIxDbxl57q5n+AY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.123 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.0113905-0.00586475-0.982745;FP=16884940708758703762|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033037031241;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPVy_1790078600; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPVy_1790078600 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:20 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 7/8] net/nebula-matrix: add common/ctrl dev init/remove operation Date: Tue, 22 Sep 2026 20:03:04 +0800 Message-ID: <20260922120311.86593-8-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: illusion wang Add nbl_dev core infrastructure. Create nbl_dev.c / nbl_dev.h and nbl_def_dev.h, introducing nbl_dev_setup_common_dev() and nbl_dev_setup_ctrl_dev() initialization helpers with their teardown counterparts, and wire them into nbl_dev_init() / nbl_dev_remove() called from nbl_core_init() / nbl_core_remove(). Common device setup (nbl_dev_setup_common_dev): - Allocate per-device state (nbl_dev_common) - Set up mailbox channel queue and register cleanup work - Initialize MSI-X service counts (mailbox =3D 1 vector; actual allocation deferred to nbl_dev_start() in the next patch; this patch only populates service vector counts) - VSI/ETH identity fetch is deferred to nbl_dev_start() for both control and non-control PFs. This ensures mailbox channel and control PF responder are fully ready before RPC lookup, and keeps every intermediate commit bisect-bootable on multi-PF systems. Control device setup (nbl_dev_setup_ctrl_dev, control PF only): - disp_ops->init_module(): chip-wide hardware initialization callback (datapath pipeline setup, driver_status flag) - Program mailbox QINFO routing map (bus/devid for each PF) Chip core hardware initialization is done by firmware at power-on. The driver only programs functional table entries after hardware is ready; common_dev setup before ctrl_dev setup is safe. Non-control PFs create device links to the control PF during probe. The device link guarantees the kernel unbinds all non-control PFs before the control PF can be removed, preventing broken cross-PF mailbox RPC. A registry check is added as defense-in-depth, to skip global chip deinit if sibling PFs remain bound. Deinit order: ctrl dev cleanup after common dev teardown. nbl_dev_remove_ctrl_dev() calls deinit_module(), which sets driver_status=3Dfalse to notify firmware to asynchronously clean all per-PF hardware state including qinfo registers. The qinfo map is not explicitly cleared by the driver; firmware handles it on driver_status change. No completion handshake exists in the current hardware revision. Teardown ordering guarantee: stop mailbox channel and drain all inflight DMA first, then invoke deinit_module to notify firmware. This intentionally breaks strict init/teardown mirror symmetry due to hardware constraint: firmware may perform asynchronous global hardware cleanup once driver_status=3Dfalse is set. We must guarantee no ongoing mailbox DMA before deinit_module to avoid invalid DMA write. Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/Makefile | 1 + .../net/ethernet/nebula-matrix/nbl/nbl_core.h | 1 + .../nebula-matrix/nbl/nbl_core/nbl_dev.c | 259 ++++++++++++++++++ .../nebula-matrix/nbl/nbl_core/nbl_dev.h | 55 ++++ .../nbl/nbl_include/nbl_def_dev.h | 14 + .../net/ethernet/nebula-matrix/nbl/nbl_main.c | 9 + 6 files changed, 339 insertions(+) create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev= .c create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev= .h create mode 100644 drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_= def_dev.h diff --git a/drivers/net/ethernet/nebula-matrix/nbl/Makefile b/drivers/net/= ethernet/nebula-matrix/nbl/Makefile index b7eebd89b4d1..71fbe3ee7e62 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/Makefile +++ b/drivers/net/ethernet/nebula-matrix/nbl/Makefile @@ -11,4 +11,5 @@ nbl-objs +=3D nbl_common/nbl_common.o \ nbl_hw/nbl_interrupt.o \ nbl_hw/nbl_chip.o \ nbl_core/nbl_dispatch.o \ + nbl_core/nbl_dev.o \ nbl_main.o diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_core.h index 4d8cea8d8ab3..c3c4dd685bf6 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h @@ -25,6 +25,7 @@ struct nbl_core { struct nbl_hw_mgt *hw_mgt; struct nbl_resource_mgt *res_mgt; struct nbl_dispatch_mgt *disp_mgt; + struct nbl_dev_mgt *dev_mgt; struct nbl_channel_mgt *chan_mgt; }; =20 diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c b/dr= ivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c new file mode 100644 index 000000000000..35410c6f1fb9 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ +#include +#include +#include "nbl_dev.h" + +static void nbl_dev_init_msix_cnt(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + + /* mailbox vector allocated in nbl_dev_start() via + * nbl_dev_init_interrupt_scheme(); nbl_dev_request_mailbox_irq() + * only attaches the irq handler to pre-allocated vectors. + */ + msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num =3D 1; +} + +/* ---------- Channel config ---------- */ +static void nbl_dev_setup_chan_qinfo(struct nbl_dev_mgt *dev_mgt, u8 chan_= type) +{ + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + struct nbl_channel_mgt *priv =3D dev_mgt->chan_ops_tbl->priv; + struct nbl_common_info *common =3D dev_mgt->common; + + if (!chan_ops->check_queue_exist(priv, chan_type)) + return; + + /* + * common->hw_bus is the control PF's real bus number, captured in + * nbl_res_ctrl_dev_sriov_info_init() during nbl_res_init_leonis(). + * nbl_core_init() runs resource init before nbl_dev_init(), so the + * value is always initialized when this control-PF-only path runs; + * nbl_res_intr_cfg_msix_map() consumes it for cfg_msix_map() the + * same way. + */ + chan_ops->cfg_chan_qinfo_map_table(priv, common->hw_bus, common->devid); +} + +static int nbl_dev_setup_chan_queue(struct nbl_dev_mgt *dev_mgt, u8 chan_t= ype) +{ + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + struct nbl_channel_mgt *priv =3D dev_mgt->chan_ops_tbl->priv; + int ret =3D 0; + + if (chan_ops->check_queue_exist(priv, chan_type)) + ret =3D chan_ops->setup_queue(priv, chan_type); + + return ret; +} + +static int nbl_dev_remove_chan_queue(struct nbl_dev_mgt *dev_mgt, u8 chan_= type) +{ + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + struct nbl_channel_mgt *priv =3D dev_mgt->chan_ops_tbl->priv; + int ret =3D 0; + + if (chan_ops->check_queue_exist(priv, chan_type)) + ret =3D chan_ops->teardown_queue(priv, chan_type); + + return ret; +} + +static void nbl_dev_register_chan_task(struct nbl_dev_mgt *dev_mgt, + u8 chan_type, struct work_struct *task) +{ + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + + if (chan_ops->check_queue_exist(dev_mgt->chan_ops_tbl->priv, chan_type)) + chan_ops->register_chan_task(dev_mgt->chan_ops_tbl->priv, + chan_type, task); +} + +/* ---------- Tasks config ---------- */ +static void nbl_dev_clean_mailbox_task(struct work_struct *work) +{ + struct nbl_dev_common *common_dev =3D + container_of(work, struct nbl_dev_common, clean_mbx_task); + struct nbl_dev_mgt *dev_mgt =3D common_dev->dev_mgt; + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + + chan_ops->clean_queue_subtask(dev_mgt->chan_ops_tbl->priv, + NBL_CHAN_TYPE_MAILBOX); +} + +/* ---------- Dev init process ---------- */ +static int nbl_dev_setup_common_dev(struct nbl_adapter *adapter) +{ + struct nbl_dev_mgt *dev_mgt =3D adapter->core.dev_mgt; + struct nbl_dev_common *common_dev; + int ret; + + common_dev =3D devm_kzalloc(&adapter->pdev->dev, sizeof(*common_dev), + GFP_KERNEL); + if (!common_dev) + return -ENOMEM; + common_dev->dev_mgt =3D dev_mgt; + + /* + * Initialize clean_mbx_task before any operation that could jump + * to err_cleanup, so cancel_work_sync() is always safe there. + */ + INIT_WORK(&common_dev->clean_mbx_task, nbl_dev_clean_mailbox_task); + + ret =3D nbl_dev_setup_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX); + if (ret) + goto err_cleanup; + + nbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, + &common_dev->clean_mbx_task); + /* + * VSI/ETH identity fetch moved to nbl_dev_start(). + * This avoids cross-PF probe race when manager PF is not ready. + */ + dev_mgt->common_dev =3D common_dev; + nbl_dev_init_msix_cnt(dev_mgt); + + return 0; +err_cleanup: + cancel_work_sync(&common_dev->clean_mbx_task); + nbl_dev_remove_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX); + nbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, NULL); + return ret; +} + +static void nbl_dev_remove_common_dev(struct nbl_adapter *adapter) +{ + struct nbl_dev_mgt *dev_mgt =3D adapter->core.dev_mgt; + struct nbl_dev_common *common_dev =3D dev_mgt->common_dev; + int ret; + + if (!common_dev) + return; + cancel_work_sync(&common_dev->clean_mbx_task); + ret =3D nbl_dev_remove_chan_queue(dev_mgt, NBL_CHAN_TYPE_MAILBOX); + if (ret) + dev_warn(&adapter->pdev->dev, "mailbox queue teardown failed, inflight D= MA may exist: %d\n", + ret); + nbl_dev_register_chan_task(dev_mgt, NBL_CHAN_TYPE_MAILBOX, NULL); +} + +static int nbl_dev_setup_ctrl_dev(struct nbl_adapter *adapter) +{ + struct nbl_dev_mgt *dev_mgt =3D adapter->core.dev_mgt; + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + int ret; + + ret =3D disp_ops->init_module(dev_mgt->disp_ops_tbl->priv); + if (ret) + return ret; + + return 0; +} + +/* + * Tear down control device: deinit_module sets driver_status=3Dfalse + * to notify firmware to clean all per-PF hardware state (including + * qinfo registers). The qinfo map programmed in setup_ctrl_dev is + * not explicitly cleared; firmware handles it on driver_status change. + * + * Teardown ordering guarantee: every non-management PF creates a + * consumer->control PF device link in its probe path, so the driver + * core always unbinds all siblings before allowing the control PF to + * be detached (sysfs unbind, driver unregister and hot-unplug alike). + * + * Safety net: this registry check additionally blocks the chip-global + * deinit if a sibling PF is somehow still bound on the same chip + * (defense in depth for paths that bypass device-link ordering). + * Skipping deinit leaves firmware/qinfo routing alive for the siblings; + * their control plane stays functional at the cost of deferring the + * global firmware cleanup until the next control PF init_module(). + * Direct control-PF FLR (which never runs driver teardown) cannot be + * guarded here. + */ +static void nbl_dev_remove_ctrl_dev(struct nbl_adapter *adapter) +{ + struct nbl_dev_mgt *dev_mgt =3D adapter->core.dev_mgt; + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + + disp_ops->deinit_module(dev_mgt->disp_ops_tbl->priv); +} + +static struct nbl_dev_mgt *nbl_dev_setup_dev_mgt(struct nbl_common_info *c= ommon) +{ + struct nbl_dev_mgt *dev_mgt; + + dev_mgt =3D devm_kzalloc(common->dev, sizeof(*dev_mgt), GFP_KERNEL); + if (!dev_mgt) + return ERR_PTR(-ENOMEM); + + dev_mgt->common =3D common; + return dev_mgt; +} + +int nbl_dev_init(struct nbl_adapter *adapter) +{ + struct nbl_common_info *common =3D &adapter->common; + struct nbl_dispatch_ops_tbl *disp_ops_tbl =3D + adapter->intf.dispatch_ops_tbl; + struct nbl_channel_ops_tbl *chan_ops_tbl =3D + adapter->intf.channel_ops_tbl; + struct nbl_dev_mgt *dev_mgt; + int ret; + + dev_mgt =3D nbl_dev_setup_dev_mgt(common); + if (IS_ERR(dev_mgt)) { + ret =3D PTR_ERR(dev_mgt); + return ret; + } + + dev_mgt->disp_ops_tbl =3D disp_ops_tbl; + dev_mgt->chan_ops_tbl =3D chan_ops_tbl; + adapter->core.dev_mgt =3D dev_mgt; + if (common->has_ctrl) + nbl_dev_setup_chan_qinfo(dev_mgt, NBL_CHAN_TYPE_MAILBOX); + /* + * Chip hardware initialization is completed by firmware at power-up. + * Only driver functional table/register config follows here, safe to + * access hardware registers before ctrl dev setup. + */ + ret =3D nbl_dev_setup_common_dev(adapter); + if (ret) + goto setup_err; + + if (common->has_ctrl) { + ret =3D nbl_dev_setup_ctrl_dev(adapter); + if (ret) + goto setup_ctrl_dev_fail; + } + + return 0; +setup_ctrl_dev_fail: + nbl_dev_remove_common_dev(adapter); +setup_err: + return ret; +} + +/* + * Teardown order: Stop mailbox channel and drain all inflight DMA first, + * then invoke deinit_module to notify firmware. + * + * This intentionally breaks strict init/teardown mirror symmetry due to + * hardware constraint: firmware may perform asynchronous global hardware + * cleanup once driver_status=3Dfalse is set. We must guarantee no ongoing + * mailbox DMA before deinit_module to avoid invalid DMA write. + * + * Init order: create mailbox(common_dev) =E2=86=92 ctrl dev init + * Teardown order: destroy mailbox(common_dev) =E2=86=92 ctrl dev deinit + */ +void nbl_dev_remove(struct nbl_adapter *adapter) +{ + struct nbl_common_info *common =3D &adapter->common; + + nbl_dev_remove_common_dev(adapter); + if (common->has_ctrl) + nbl_dev_remove_ctrl_dev(adapter); +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h b/dr= ivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h new file mode 100644 index 000000000000..24e890fd8987 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_DEV_H_ +#define _NBL_DEV_H_ + +#include + +#include "../nbl_include/nbl_include.h" +#include "../nbl_include/nbl_def_channel.h" +#include "../nbl_include/nbl_def_hw.h" +#include "../nbl_include/nbl_def_resource.h" +#include "../nbl_include/nbl_def_dispatch.h" +#include "../nbl_include/nbl_def_dev.h" +#include "../nbl_include/nbl_def_common.h" +#include "../nbl_core.h" + +#define NBL_STRING_NAME_LEN 32 + +enum nbl_msix_serv_type { + NBL_MSIX_NET_TYPE, + NBL_MSIX_MAILBOX_TYPE, + NBL_MSIX_TYPE_MAX +}; + +struct nbl_msix_serv_info { + char irq_name[NBL_STRING_NAME_LEN]; + u16 num; + u16 base_vector_id; + /* true: hw report msix, hw need to mask actively */ + bool hw_self_mask_en; +}; + +struct nbl_msix_info { + struct nbl_msix_serv_info serv_info[NBL_MSIX_TYPE_MAX]; +}; + +struct nbl_dev_common { + struct nbl_dev_mgt *dev_mgt; + struct nbl_msix_info msix_info; + char mailbox_name[NBL_STRING_NAME_LEN]; + /* for ctrl-dev/net-dev mailbox recv msg */ + struct work_struct clean_mbx_task; +}; + +struct nbl_dev_mgt { + struct nbl_common_info *common; + struct nbl_dispatch_ops_tbl *disp_ops_tbl; + struct nbl_channel_ops_tbl *chan_ops_tbl; + struct nbl_dev_common *common_dev; +}; + +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h new file mode 100644 index 000000000000..51cf04e4c552 --- /dev/null +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 Nebula Matrix Limited. + */ + +#ifndef _NBL_DEF_DEV_H_ +#define _NBL_DEF_DEV_H_ + +struct nbl_adapter; + +int nbl_dev_init(struct nbl_adapter *adapter); +void nbl_dev_remove(struct nbl_adapter *adapter); + +#endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_main.c index 5979f62d7f81..d362f1270d83 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c @@ -12,6 +12,7 @@ #include "nbl_include/nbl_def_hw.h" #include "nbl_include/nbl_def_resource.h" #include "nbl_include/nbl_def_dispatch.h" +#include "nbl_include/nbl_def_dev.h" #include "nbl_include/nbl_def_common.h" #include "nbl_core.h" =20 @@ -53,7 +54,14 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, ret =3D nbl_disp_init(adapter); if (ret) goto disp_init_fail; + + ret =3D nbl_dev_init(adapter); + if (ret) + goto dev_init_fail; return adapter; + +dev_init_fail: + nbl_disp_remove(adapter); disp_init_fail: nbl_res_remove_leonis(adapter); res_init_fail: @@ -66,6 +74,7 @@ struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, =20 void nbl_core_remove(struct nbl_adapter *adapter) { + nbl_dev_remove(adapter); nbl_disp_remove(adapter); nbl_res_remove_leonis(adapter); nbl_chan_remove_common(adapter); --=20 2.47.3 From nobody Thu Sep 24 16:07:47 2026 Received: from out28-220.mail.aliyun.com (out28-220.mail.aliyun.com [115.124.28.220]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2C1D144F570; Tue, 22 Sep 2026 12:03:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.220 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078614; cv=none; b=L/gY5iid8AbhI067Wq3/GQ0P5t5vPxo0x/poaxVIelkK3ysw+//mYDTz3kckHdzOvems1VBLqV10forr3JFyDAOmzVENP//rAyocCRh0w5NpbvwcBuLNIlfLSiWuCi4Y71bZmqeAdjxVJpOOMVetEVTbikZf+y4eD+6hHMldbNo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790078614; c=relaxed/simple; bh=ldd9mi0YLqF/0JSnpUjot5thQq3ZpMjJ/bhmZ2B1Ers=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=i3zxKsGcqj3ovWYGbou72yzGXx0IFGNyJNf9+Q2lLmw40Oh8nxRXrQlOlAYPqf1p+YZKd8lPTH+adbvSiVEcbkBfSfhS2D8P8actiSzqiI7syriuK5QtQMXNfEcWoh5luVZySKbiAr68gUOZLKimXgw/l6MFzwuEy82QE61Qs88= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com; spf=pass smtp.mailfrom=nebula-matrix.com; arc=none smtp.client-ip=115.124.28.220 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=nebula-matrix.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebula-matrix.com X-Alimail-AntiSpam: AC=CONTINUE;BC=0.06712908|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.00481734-0.000760873-0.994422;FP=15732195813479701746|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033022149254;MF=illusion.wang@nebula-matrix.com;NM=1;PH=DS;RN=9;RT=9;SR=0;TI=SMTPD_---.jK4DPWq_1790078601; Received: from localhost.localdomain(mailfrom:illusion.wang@nebula-matrix.com fp:SMTPD_---.jK4DPWq_1790078601 cluster:ay29) by smtp.aliyun-inc.com; Tue, 22 Sep 2026 20:03:21 +0800 From: "illusion.wang" To: dimon.zhao@nebula-matrix.com, illusion.wang@nebula-matrix.com, alvin.wang@nebula-matrix.com, sam.chen@nebula-matrix.com, netdev@vger.kernel.org Cc: kuba@kernel.org, edumazet@google.com, horms@kernel.org, linux-kernel@vger.kernel.org (open list) Subject: [PATCH v29 net-next 8/8] net/nebula-matrix: add common dev start/stop operation Date: Tue, 22 Sep 2026 20:03:05 +0800 Message-ID: <20260922120311.86593-9-illusion.wang@nebula-matrix.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> References: <20260922120311.86593-1-illusion.wang@nebula-matrix.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: illusion wang Implement nbl_dev_start() and nbl_dev_stop() to manage MSI-X hardware mapping and mailbox interrupt setup/teardown. nbl_dev_start() performs device startup in strict order: 1. Configure hardware MSI-X mapping table (cfg_msix_map) 2. Fetch VSI and ETH identity information via dispatch RPC For non-control PF, if mailbox RPC times out because control PF is not ready yet, return -EPROBE_DEFER to trigger deferred probing, so kernel will retry later once PF0 comes up. 3. Allocate MSI-X IRQ vectors via pci_alloc_irq_vectors() 4. Request mailbox interrupt and attach handler (nbl_dev_clean_mailbox -> schedule cleanup work) 5. Enable hardware mailbox interrupt and mark channel interrupt ready (NBL_CHAN_IRQ_RDY) The VSI/ETH identity fetch for all PFs happens inside nbl_dev_start(), after the MSI-X mapping is configured. Non-control PFs rely on the control PF's mailbox responder being fully operational when this RPC runs. If control PF is not ready, return -EPROBE_DEFER for retry. nbl_dev_stop() tears down resources in reverse order, with device-side mapping destroyed before kernel-side vectors are released: 1. Switch channel to polling mode (clear NBL_CHAN_IRQ_RDY) before masking hardware mailbox interrupt. This ordering avoids discarding in-flight ACKs that would trigger "Channel waiting ack failed" errors. 2. Release mailbox IRQ via free_irq() 3. Destroy hardware MSI-X mapping table (masks all device vectors and clears the completer map entry) 4. Kernel MSI-X vectors are managed by devres/pcim, so explicit pci_free_irq_vectors() is skipped to prevent double-free. 5. Drain mailbox cleanup work via cancel_work_sync() The start error-unwind path uses reverse ordering: disable mailbox IRQ and free IRQ handler, destroy MSI-X map, then drain work via cancel_work_sync, so a failure at any stage leaves the device and kernel in a consistent state. Rollback failures are logged. This teardown is best-effort: destroy_msix_map uses mailbox RPC and may fail. Stale hardware MSI-X entries are reclaimed by firmware on chip reset. pci_clear_master() in nbl_remove() provides the final safety net if a teardown RPC to the manager PF cannot complete. Add thin wrappers nbl_core_start() / nbl_core_stop() and hook them into PCI probe and remove paths to control the device lifecycle. This patch completes the dev lifecycle series. Signed-off-by: illusion wang --- .../net/ethernet/nebula-matrix/nbl/nbl_core.h | 2 + .../nebula-matrix/nbl/nbl_core/nbl_dev.c | 342 ++++++++++++++++++ .../nbl/nbl_include/nbl_def_dev.h | 2 + .../net/ethernet/nebula-matrix/nbl/nbl_main.c | 18 + 4 files changed, 364 insertions(+) diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_core.h index c3c4dd685bf6..56872c8ca9a7 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core.h @@ -39,5 +39,7 @@ struct nbl_adapter { struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, struct nbl_init_param *param); void nbl_core_remove(struct nbl_adapter *adapter); +int nbl_core_start(struct nbl_adapter *adapter); +void nbl_core_stop(struct nbl_adapter *adapter); =20 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c b/dr= ivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c index 35410c6f1fb9..e672478f6114 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_core/nbl_dev.c @@ -6,6 +6,17 @@ #include #include "nbl_dev.h" =20 +static void nbl_dev_clean_mailbox_schedule(struct nbl_dev_mgt *dev_mgt); + +/* ---------- Interrupt config ---------- */ +static irqreturn_t nbl_dev_clean_mailbox(int irq __always_unused, void *da= ta) +{ + struct nbl_dev_mgt *dev_mgt =3D (struct nbl_dev_mgt *)data; + + nbl_dev_clean_mailbox_schedule(dev_mgt); + return IRQ_HANDLED; +} + static void nbl_dev_init_msix_cnt(struct nbl_dev_mgt *dev_mgt) { struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; @@ -18,6 +29,206 @@ static void nbl_dev_init_msix_cnt(struct nbl_dev_mgt *d= ev_mgt) msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num =3D 1; } =20 +static int nbl_dev_request_mailbox_irq(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + struct nbl_common_info *common =3D dev_mgt->common; + u16 lvec; + int irq_num; + int err; + + if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num) + return 0; + + lvec =3D msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id; + irq_num =3D pci_irq_vector(common->pdev, lvec); + if (irq_num < 0) { + dev_err(common->dev, "Failed to get mailbox IRQ vector: %d\n", + irq_num); + return irq_num; + } + + snprintf(dev_common->mailbox_name, sizeof(dev_common->mailbox_name), + "nbl_mailbox@pci:%s", pci_name(common->pdev)); + err =3D request_irq(irq_num, nbl_dev_clean_mailbox, 0, + dev_common->mailbox_name, dev_mgt); + if (err) + return err; + + return 0; +} + +static void nbl_dev_free_mailbox_irq(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + struct nbl_common_info *common =3D dev_mgt->common; + u16 lvec; + int irq_num; + + if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num) + return; + + lvec =3D msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id; + irq_num =3D pci_irq_vector(common->pdev, lvec); + if (irq_num >=3D 0) + free_irq(irq_num, dev_mgt); +} + +static int nbl_dev_enable_mailbox_irq(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + u16 lvec; + int ret; + + if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num) + return 0; + + lvec =3D msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id; + /* + * Enable sequence: perform set_mailbox_irq RPC in polling mode first. + * Only set NBL_CHAN_IRQ_RDY after RPC succeeds, mirroring disable path. + * This avoids waiting for an interrupt which has not been armed yet. + */ + ret =3D disp_ops->set_mailbox_irq(dev_mgt->disp_ops_tbl->priv, + lvec, true); + if (ret) + return ret; + chan_ops->set_queue_state(dev_mgt->chan_ops_tbl->priv, + NBL_CHAN_IRQ_RDY, + NBL_CHAN_TYPE_MAILBOX, true); + return 0; +} + +static int nbl_dev_disable_mailbox_irq(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + struct nbl_channel_ops *chan_ops =3D dev_mgt->chan_ops_tbl->ops; + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + u16 lvec; + + if (!msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].num) + return 0; + + lvec =3D msix_info->serv_info[NBL_MSIX_MAILBOX_TYPE].base_vector_id; + /* + * Disable sequence invariant: update software state first, then mask + * hardware interrupt. Must not reverse the order. + * + * If hardware interrupt is masked before clearing INTERRUPT_READY, + * the hardware may still transmit outstanding ACK packets for in-flight + * messages. Subsequent switch to polling mode discards pending ACK + * processing, triggering "Channel waiting ack failed" and "Skip ack + * with invalid status" errors. + * + * By entering polling mode first, any late hardware interrupts are + * ignored without pending ACK expectations, then hardware interrupt + * can be safely disabled. + * + * This helper is invoked in two paths: + * 1. Error unwind path of nbl_dev_start(): followed immediately by + * nbl_dev_free_mailbox_irq() and full channel teardown. No new mailbox + * interrupts can fire afterwards, and subsequent cancel_work_sync() + * drains pending cleanup work before resources are released. + * 2. Normal device stop path nbl_dev_stop(): free_irq() blocks until + * any in-flight hardirq handler completes and prevents new interrupts. + * cancel_work_sync() then waits for any already running mailbox cleanup + * work to finish, or cancels queued but unstarted work items before + * final channel destruction. No stuck descriptors linger in either + * scenario. + */ + chan_ops->set_queue_state(dev_mgt->chan_ops_tbl->priv, + NBL_CHAN_IRQ_RDY, + NBL_CHAN_TYPE_MAILBOX, false); + + return disp_ops->set_mailbox_irq(dev_mgt->disp_ops_tbl->priv, + lvec, false); +} + +static int nbl_dev_cfg_msix_map(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + bool mask_en =3D msix_info->serv_info[NBL_MSIX_NET_TYPE].hw_self_mask_en; + u16 msix_net_num =3D msix_info->serv_info[NBL_MSIX_NET_TYPE].num; + u16 msix_not_net_num =3D 0; + int err, i; + + msix_info->serv_info[NBL_MSIX_NET_TYPE].base_vector_id =3D 0; + /* + * Calculate base_vector_id for each MSIX service type. + * This relies on NBL_MSIX_TYPE enum being ordered sequentially, + * starting from NBL_MSIX_NET_TYPE. + */ + for (i =3D NBL_MSIX_NET_TYPE + 1; i < NBL_MSIX_TYPE_MAX; i++) + msix_info->serv_info[i].base_vector_id =3D + msix_info->serv_info[i - 1].base_vector_id + + msix_info->serv_info[i - 1].num; + + for (i =3D 0; i < NBL_MSIX_TYPE_MAX; i++) { + if (i =3D=3D NBL_MSIX_NET_TYPE) + continue; + msix_not_net_num +=3D msix_info->serv_info[i].num; + } + + err =3D disp_ops->cfg_msix_map(dev_mgt->disp_ops_tbl->priv, + msix_net_num, msix_not_net_num, + mask_en); + + return err; +} + +static int nbl_dev_destroy_msix_map(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + + return disp_ops->destroy_msix_map(dev_mgt->disp_ops_tbl->priv); +} + +static int nbl_dev_init_interrupt_scheme(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dev_common *dev_common =3D dev_mgt->common_dev; + struct nbl_msix_info *msix_info =3D &dev_common->msix_info; + struct nbl_common_info *common =3D dev_mgt->common; + int needed =3D 0; + int err; + int i; + + for (i =3D 0; i < NBL_MSIX_TYPE_MAX; i++) + needed +=3D msix_info->serv_info[i].num; + + err =3D pci_alloc_irq_vectors(common->pdev, needed, needed, + PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); + if (err < 0) { + dev_err(common->dev, + "pci_alloc_irq_vectors failed, err =3D %d\n", err); + return err; + } + if (err !=3D needed) { + dev_err(common->dev, "pci_alloc_irq_vectors got %d vecs, need %d\n", + err, needed); + return -ENOSPC; + } + return 0; +} + +static void nbl_dev_clear_interrupt_scheme(struct nbl_dev_mgt *dev_mgt) +{ + /* + * pcim_enable_device() is used in nbl_probe(). + * pci_alloc_irq_vectors() registers pcim_msi_release devres callback, + * which invokes pci_free_irq_vectors() automatically on device detach. + * Do NOT call pci_free_irq_vectors() explicitly here to avoid + * double-free. + */ +} + /* ---------- Channel config ---------- */ static void nbl_dev_setup_chan_qinfo(struct nbl_dev_mgt *dev_mgt, u8 chan_= type) { @@ -85,6 +296,14 @@ static void nbl_dev_clean_mailbox_task(struct work_stru= ct *work) NBL_CHAN_TYPE_MAILBOX); } =20 +static void nbl_dev_clean_mailbox_schedule(struct nbl_dev_mgt *dev_mgt) +{ + struct nbl_dev_common *common_dev =3D dev_mgt->common_dev; + struct nbl_common_info *common =3D dev_mgt->common; + + queue_work(common->wq, &common_dev->clean_mbx_task); +} + /* ---------- Dev init process ---------- */ static int nbl_dev_setup_common_dev(struct nbl_adapter *adapter) { @@ -257,3 +476,126 @@ void nbl_dev_remove(struct nbl_adapter *adapter) if (common->has_ctrl) nbl_dev_remove_ctrl_dev(adapter); } + +/* ---------- Dev start process ---------- */ +int nbl_dev_start(struct nbl_adapter *adapter) +{ + struct nbl_dev_mgt *dev_mgt =3D adapter->core.dev_mgt; + struct nbl_dispatch_ops *disp_ops =3D dev_mgt->disp_ops_tbl->ops; + struct nbl_dispatch_mgt *priv =3D dev_mgt->disp_ops_tbl->priv; + struct nbl_dev_common *common_dev =3D dev_mgt->common_dev; + struct nbl_common_info *common =3D dev_mgt->common; + int cleanup_ret; + int ret; + + ret =3D nbl_dev_cfg_msix_map(dev_mgt); + if (ret) + goto err_destroy_map; + + /* Fetch VSI/ETH identity after cfg_msix_map */ + ret =3D disp_ops->get_vsi_id(priv, NBL_VSI_DATA, &common->vsi_id); + if (ret) { + if (!common->has_ctrl && ret =3D=3D -ETIMEDOUT) + ret =3D -EPROBE_DEFER; + goto err_destroy_map; + } + ret =3D disp_ops->get_eth_id(priv, common->vsi_id, &common->eth_num, + &common->eth_id, &common->logic_eth_id); + if (ret) { + if (!common->has_ctrl && ret =3D=3D -ETIMEDOUT) + ret =3D -EPROBE_DEFER; + goto err_destroy_map; + } + + ret =3D nbl_dev_init_interrupt_scheme(dev_mgt); + if (ret) + goto err_destroy_map; + + ret =3D nbl_dev_request_mailbox_irq(dev_mgt); + if (ret) + goto err_destroy_map; + + ret =3D nbl_dev_enable_mailbox_irq(dev_mgt); + if (ret) + goto err_disable_irq; + + return 0; + +err_disable_irq: + cleanup_ret =3D nbl_dev_disable_mailbox_irq(dev_mgt); + if (cleanup_ret) + dev_err(dev_mgt->common->dev, + "rollback: disable mailbox IRQ failed: %d\n", + cleanup_ret); + nbl_dev_free_mailbox_irq(dev_mgt); +err_destroy_map: + /* + * Destroy device-side MSI-X map BEFORE releasing kernel-side + * vectors. This masks all hardware vectors and clears the + * pcompleter map entry, so no MSI-X message can fire after + * vector release. + * + * For non-control PFs this is a polling-mode mailbox RPC + * (IRQ_RDY already cleared by disable above, or never set). + * If the RPC fails the device may remain armed. + * + * This is best-effort teardown: we still release kernel vectors + * even if remote RPC fails. The hardware entry will be reclaimed + * by firmware on chip reset. + * + * Note: pci_clear_master() runs in nbl_probe() core_start_err path, + * Also pci_clear_master() on non-control PF + * cannot stop DMA using the manager PF's BDF; this is a known + * limitation. Stale hardware entries will be reclaimed by firmware + * on chip reset. + */ + cleanup_ret =3D nbl_dev_destroy_msix_map(dev_mgt); + if (cleanup_ret) + dev_err(dev_mgt->common->dev, + "rollback: destroy MSI-X map failed: %d\n", + cleanup_ret); + nbl_dev_clear_interrupt_scheme(dev_mgt); + + cancel_work_sync(&common_dev->clean_mbx_task); + return ret; +} + +void nbl_dev_stop(struct nbl_adapter *adapter) +{ + struct nbl_dev_mgt *dev_mgt =3D adapter->core.dev_mgt; + struct nbl_dev_common *common_dev =3D dev_mgt->common_dev; + int ret; + + ret =3D nbl_dev_disable_mailbox_irq(dev_mgt); + if (ret) + dev_err(dev_mgt->common->dev, + "Failed to disable mailbox IRQ: %d\n", ret); + nbl_dev_free_mailbox_irq(dev_mgt); + + /* + * Destroy hardware MSI-X map BEFORE releasing kernel-side + * vectors. Masks all device vectors and clears pcompleter + * map entry first. + * + * This is best-effort teardown: if destroy_msix_map RPC fails, + * hardware MSI-X map remains valid. We proceed to release + * kernel vectors anyway. Hardware stale entries rely on + * firmware cleanup on chip reset. + * + * pci_clear_master() on non-control PF cannot stop DMA using manager + * PF's BDF. + */ + ret =3D nbl_dev_destroy_msix_map(dev_mgt); + if (ret) + dev_err(dev_mgt->common->dev, + "Failed to destroy MSI-X map: %d\n", ret); + + nbl_dev_clear_interrupt_scheme(dev_mgt); + + /* + * destroy_msix_map() sends ack-requested messages which may + * requeue clean_mbx_task via polling send path. Drain work + * after the operation. + */ + cancel_work_sync(&common_dev->clean_mbx_task); +} diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev= .h b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h index 51cf04e4c552..a66c633a0e7a 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_include/nbl_def_dev.h @@ -10,5 +10,7 @@ struct nbl_adapter; =20 int nbl_dev_init(struct nbl_adapter *adapter); void nbl_dev_remove(struct nbl_adapter *adapter); +int nbl_dev_start(struct nbl_adapter *adapter); +void nbl_dev_stop(struct nbl_adapter *adapter); =20 #endif diff --git a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c b/drivers/ne= t/ethernet/nebula-matrix/nbl/nbl_main.c index d362f1270d83..170a4d13494b 100644 --- a/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c +++ b/drivers/net/ethernet/nebula-matrix/nbl/nbl_main.c @@ -16,6 +16,16 @@ #include "nbl_include/nbl_def_common.h" #include "nbl_core.h" =20 +int nbl_core_start(struct nbl_adapter *adapter) +{ + return nbl_dev_start(adapter); +} + +void nbl_core_stop(struct nbl_adapter *adapter) +{ + nbl_dev_stop(adapter); +} + struct nbl_adapter *nbl_core_init(struct pci_dev *pdev, struct nbl_init_param *param) { @@ -181,7 +191,13 @@ static int nbl_probe(struct pci_dev *pdev, goto adapter_init_err; } pci_set_drvdata(pdev, adapter); + err =3D nbl_core_start(adapter); + if (err) + goto core_start_err; + return 0; +core_start_err: + nbl_core_remove(adapter); adapter_init_err: chip_deps_err: pci_clear_master(pdev); @@ -196,6 +212,8 @@ static void nbl_remove(struct pci_dev *pdev) return; =20 pci_set_drvdata(pdev, NULL); + + nbl_core_stop(adapter); nbl_core_remove(adapter); =20 pci_clear_master(pdev); --=20 2.47.3