mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	xchg() is used to safely handle the event queue arming.
However SPARC xchg operates only 4B of variable.
Change variable type from bool to int.
Unverified Error/Warning (likely false positive, kindly check if interested):
    ERROR: modpost: "__xchg_called_with_bad_pointer" [drivers/infiniband/hw/ionic/ionic_rdma.ko] undefined!
Error/Warning ids grouped by kconfigs:
recent_errors
`-- sparc-allmodconfig
    `-- ERROR:__xchg_called_with_bad_pointer-drivers-infiniband-hw-ionic-ionic_rdma.ko-undefined
Fixes: f3bdbd4270 ("RDMA/ionic: Create device queues to support admin operations")
Reported-by: Leon Romanovsky <leon@kernel.org>
Closes: https://lore.kernel.org/lkml/20250918180750.GA135135@unreal/
Signed-off-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
Link: https://patch.msgid.link/20250919121301.1113759-1-abhijit.gangurde@amd.com
Signed-off-by: Leon Romanovsky <leon@kernel.org>
		
	
			
		
			
				
	
	
		
			1228 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1228 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
/* Copyright (C) 2018-2025, Advanced Micro Devices, Inc. */
 | 
						|
 | 
						|
#include <linux/interrupt.h>
 | 
						|
#include <linux/module.h>
 | 
						|
#include <linux/printk.h>
 | 
						|
 | 
						|
#include "ionic_fw.h"
 | 
						|
#include "ionic_ibdev.h"
 | 
						|
 | 
						|
#define IONIC_EQ_COUNT_MIN	4
 | 
						|
#define IONIC_AQ_COUNT_MIN	1
 | 
						|
 | 
						|
/* not a valid queue position or negative error status */
 | 
						|
#define IONIC_ADMIN_POSTED	0x10000
 | 
						|
 | 
						|
/* cpu can be held with irq disabled for COUNT * MS  (for create/destroy_ah) */
 | 
						|
#define IONIC_ADMIN_BUSY_RETRY_COUNT	2000
 | 
						|
#define IONIC_ADMIN_BUSY_RETRY_MS	1
 | 
						|
 | 
						|
/* admin queue will be considered failed if a command takes longer */
 | 
						|
#define IONIC_ADMIN_TIMEOUT	(HZ * 2)
 | 
						|
#define IONIC_ADMIN_WARN	(HZ / 8)
 | 
						|
 | 
						|
/* will poll for admin cq to tolerate and report from missed event */
 | 
						|
#define IONIC_ADMIN_DELAY	(HZ / 8)
 | 
						|
 | 
						|
/* work queue for polling the event queue and admin cq */
 | 
						|
struct workqueue_struct *ionic_evt_workq;
 | 
						|
 | 
						|
static void ionic_admin_timedout(struct ionic_aq *aq)
 | 
						|
{
 | 
						|
	struct ionic_ibdev *dev = aq->dev;
 | 
						|
	unsigned long irqflags;
 | 
						|
	u16 pos;
 | 
						|
 | 
						|
	spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
	if (ionic_queue_empty(&aq->q))
 | 
						|
		goto out;
 | 
						|
 | 
						|
	/* Reset ALL adminq if any one times out */
 | 
						|
	if (atomic_read(&aq->admin_state) < IONIC_ADMIN_KILLED)
 | 
						|
		queue_work(ionic_evt_workq, &dev->reset_work);
 | 
						|
 | 
						|
	ibdev_err(&dev->ibdev, "admin command timed out, aq %d after: %ums\n",
 | 
						|
		  aq->aqid, (u32)jiffies_to_msecs(jiffies - aq->stamp));
 | 
						|
 | 
						|
	pos = (aq->q.prod - 1) & aq->q.mask;
 | 
						|
	if (pos == aq->q.cons)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	ibdev_warn(&dev->ibdev, "admin pos %u (last posted)\n", pos);
 | 
						|
	print_hex_dump(KERN_WARNING, "cmd ", DUMP_PREFIX_OFFSET, 16, 1,
 | 
						|
		       ionic_queue_at(&aq->q, pos),
 | 
						|
		       BIT(aq->q.stride_log2), true);
 | 
						|
 | 
						|
out:
 | 
						|
	spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_reset_dwork(struct ionic_ibdev *dev)
 | 
						|
{
 | 
						|
	if (atomic_read(&dev->admin_state) == IONIC_ADMIN_KILLED)
 | 
						|
		return;
 | 
						|
 | 
						|
	queue_delayed_work(ionic_evt_workq, &dev->admin_dwork,
 | 
						|
			   IONIC_ADMIN_DELAY);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_reset_wdog(struct ionic_aq *aq)
 | 
						|
{
 | 
						|
	if (atomic_read(&aq->admin_state) == IONIC_ADMIN_KILLED)
 | 
						|
		return;
 | 
						|
 | 
						|
	aq->stamp = jiffies;
 | 
						|
	ionic_admin_reset_dwork(aq->dev);
 | 
						|
}
 | 
						|
 | 
						|
static bool ionic_admin_next_cqe(struct ionic_ibdev *dev, struct ionic_cq *cq,
 | 
						|
				 struct ionic_v1_cqe **cqe)
 | 
						|
{
 | 
						|
	struct ionic_v1_cqe *qcqe = ionic_queue_at_prod(&cq->q);
 | 
						|
 | 
						|
	if (unlikely(cq->color != ionic_v1_cqe_color(qcqe)))
 | 
						|
		return false;
 | 
						|
 | 
						|
	/* Prevent out-of-order reads of the CQE */
 | 
						|
	dma_rmb();
 | 
						|
	*cqe = qcqe;
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_poll_locked(struct ionic_aq *aq)
 | 
						|
{
 | 
						|
	struct ionic_cq *cq = &aq->vcq->cq[0];
 | 
						|
	struct ionic_admin_wr *wr, *wr_next;
 | 
						|
	struct ionic_ibdev *dev = aq->dev;
 | 
						|
	u32 wr_strides, avlbl_strides;
 | 
						|
	struct ionic_v1_cqe *cqe;
 | 
						|
	u32 qtf, qid;
 | 
						|
	u16 old_prod;
 | 
						|
	u8 type;
 | 
						|
 | 
						|
	lockdep_assert_held(&aq->lock);
 | 
						|
 | 
						|
	if (atomic_read(&aq->admin_state) == IONIC_ADMIN_KILLED) {
 | 
						|
		list_for_each_entry_safe(wr, wr_next, &aq->wr_prod, aq_ent) {
 | 
						|
			INIT_LIST_HEAD(&wr->aq_ent);
 | 
						|
			aq->q_wr[wr->status].wr = NULL;
 | 
						|
			wr->status = atomic_read(&aq->admin_state);
 | 
						|
			complete_all(&wr->work);
 | 
						|
		}
 | 
						|
		INIT_LIST_HEAD(&aq->wr_prod);
 | 
						|
 | 
						|
		list_for_each_entry_safe(wr, wr_next, &aq->wr_post, aq_ent) {
 | 
						|
			INIT_LIST_HEAD(&wr->aq_ent);
 | 
						|
			wr->status = atomic_read(&aq->admin_state);
 | 
						|
			complete_all(&wr->work);
 | 
						|
		}
 | 
						|
		INIT_LIST_HEAD(&aq->wr_post);
 | 
						|
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	old_prod = cq->q.prod;
 | 
						|
 | 
						|
	while (ionic_admin_next_cqe(dev, cq, &cqe)) {
 | 
						|
		qtf = ionic_v1_cqe_qtf(cqe);
 | 
						|
		qid = ionic_v1_cqe_qtf_qid(qtf);
 | 
						|
		type = ionic_v1_cqe_qtf_type(qtf);
 | 
						|
 | 
						|
		if (unlikely(type != IONIC_V1_CQE_TYPE_ADMIN)) {
 | 
						|
			ibdev_warn_ratelimited(&dev->ibdev,
 | 
						|
					       "bad cqe type %u\n", type);
 | 
						|
			goto cq_next;
 | 
						|
		}
 | 
						|
 | 
						|
		if (unlikely(qid != aq->aqid)) {
 | 
						|
			ibdev_warn_ratelimited(&dev->ibdev,
 | 
						|
					       "bad cqe qid %u\n", qid);
 | 
						|
			goto cq_next;
 | 
						|
		}
 | 
						|
 | 
						|
		if (unlikely(be16_to_cpu(cqe->admin.cmd_idx) != aq->q.cons)) {
 | 
						|
			ibdev_warn_ratelimited(&dev->ibdev,
 | 
						|
					       "bad idx %u cons %u qid %u\n",
 | 
						|
					       be16_to_cpu(cqe->admin.cmd_idx),
 | 
						|
					       aq->q.cons, qid);
 | 
						|
			goto cq_next;
 | 
						|
		}
 | 
						|
 | 
						|
		if (unlikely(ionic_queue_empty(&aq->q))) {
 | 
						|
			ibdev_warn_ratelimited(&dev->ibdev,
 | 
						|
					       "bad cqe for empty adminq\n");
 | 
						|
			goto cq_next;
 | 
						|
		}
 | 
						|
 | 
						|
		wr = aq->q_wr[aq->q.cons].wr;
 | 
						|
		if (wr) {
 | 
						|
			aq->q_wr[aq->q.cons].wr = NULL;
 | 
						|
			list_del_init(&wr->aq_ent);
 | 
						|
 | 
						|
			wr->cqe = *cqe;
 | 
						|
			wr->status = atomic_read(&aq->admin_state);
 | 
						|
			complete_all(&wr->work);
 | 
						|
		}
 | 
						|
 | 
						|
		ionic_queue_consume_entries(&aq->q,
 | 
						|
					    aq->q_wr[aq->q.cons].wqe_strides);
 | 
						|
 | 
						|
cq_next:
 | 
						|
		ionic_queue_produce(&cq->q);
 | 
						|
		cq->color = ionic_color_wrap(cq->q.prod, cq->color);
 | 
						|
	}
 | 
						|
 | 
						|
	if (old_prod != cq->q.prod) {
 | 
						|
		ionic_admin_reset_wdog(aq);
 | 
						|
		cq->q.cons = cq->q.prod;
 | 
						|
		ionic_dbell_ring(dev->lif_cfg.dbpage, dev->lif_cfg.cq_qtype,
 | 
						|
				 ionic_queue_dbell_val(&cq->q));
 | 
						|
		queue_work(ionic_evt_workq, &aq->work);
 | 
						|
	} else if (!aq->armed) {
 | 
						|
		aq->armed = true;
 | 
						|
		cq->arm_any_prod = ionic_queue_next(&cq->q, cq->arm_any_prod);
 | 
						|
		ionic_dbell_ring(dev->lif_cfg.dbpage, dev->lif_cfg.cq_qtype,
 | 
						|
				 cq->q.dbell | IONIC_CQ_RING_ARM |
 | 
						|
				 cq->arm_any_prod);
 | 
						|
		queue_work(ionic_evt_workq, &aq->work);
 | 
						|
	}
 | 
						|
 | 
						|
	if (atomic_read(&aq->admin_state) != IONIC_ADMIN_ACTIVE)
 | 
						|
		return;
 | 
						|
 | 
						|
	old_prod = aq->q.prod;
 | 
						|
 | 
						|
	if (ionic_queue_empty(&aq->q) && !list_empty(&aq->wr_post))
 | 
						|
		ionic_admin_reset_wdog(aq);
 | 
						|
 | 
						|
	if (list_empty(&aq->wr_post))
 | 
						|
		return;
 | 
						|
 | 
						|
	do {
 | 
						|
		u8 *src;
 | 
						|
		int i, src_len;
 | 
						|
		size_t stride_len;
 | 
						|
 | 
						|
		wr = list_first_entry(&aq->wr_post, struct ionic_admin_wr,
 | 
						|
				      aq_ent);
 | 
						|
		wr_strides = (le16_to_cpu(wr->wqe.len) + ADMIN_WQE_HDR_LEN +
 | 
						|
			     (ADMIN_WQE_STRIDE - 1)) >> aq->q.stride_log2;
 | 
						|
		avlbl_strides = ionic_queue_length_remaining(&aq->q);
 | 
						|
 | 
						|
		if (wr_strides > avlbl_strides)
 | 
						|
			break;
 | 
						|
 | 
						|
		list_move(&wr->aq_ent, &aq->wr_prod);
 | 
						|
		wr->status = aq->q.prod;
 | 
						|
		aq->q_wr[aq->q.prod].wr = wr;
 | 
						|
		aq->q_wr[aq->q.prod].wqe_strides = wr_strides;
 | 
						|
 | 
						|
		src_len = le16_to_cpu(wr->wqe.len);
 | 
						|
		src = (uint8_t *)&wr->wqe.cmd;
 | 
						|
 | 
						|
		/* First stride */
 | 
						|
		memcpy(ionic_queue_at_prod(&aq->q), &wr->wqe,
 | 
						|
		       ADMIN_WQE_HDR_LEN);
 | 
						|
		stride_len = ADMIN_WQE_STRIDE - ADMIN_WQE_HDR_LEN;
 | 
						|
		if (stride_len > src_len)
 | 
						|
			stride_len = src_len;
 | 
						|
		memcpy(ionic_queue_at_prod(&aq->q) + ADMIN_WQE_HDR_LEN,
 | 
						|
		       src, stride_len);
 | 
						|
		ibdev_dbg(&dev->ibdev, "post admin prod %u (%u strides)\n",
 | 
						|
			  aq->q.prod, wr_strides);
 | 
						|
		print_hex_dump_debug("wqe ", DUMP_PREFIX_OFFSET, 16, 1,
 | 
						|
				     ionic_queue_at_prod(&aq->q),
 | 
						|
				     BIT(aq->q.stride_log2), true);
 | 
						|
		ionic_queue_produce(&aq->q);
 | 
						|
 | 
						|
		/* Remaining strides */
 | 
						|
		for (i = stride_len; i < src_len; i += stride_len) {
 | 
						|
			stride_len = ADMIN_WQE_STRIDE;
 | 
						|
 | 
						|
			if (i + stride_len > src_len)
 | 
						|
				stride_len = src_len - i;
 | 
						|
 | 
						|
			memcpy(ionic_queue_at_prod(&aq->q), src + i,
 | 
						|
			       stride_len);
 | 
						|
			print_hex_dump_debug("wqe ", DUMP_PREFIX_OFFSET, 16, 1,
 | 
						|
					     ionic_queue_at_prod(&aq->q),
 | 
						|
					     BIT(aq->q.stride_log2), true);
 | 
						|
			ionic_queue_produce(&aq->q);
 | 
						|
		}
 | 
						|
	} while (!list_empty(&aq->wr_post));
 | 
						|
 | 
						|
	if (old_prod != aq->q.prod)
 | 
						|
		ionic_dbell_ring(dev->lif_cfg.dbpage, dev->lif_cfg.aq_qtype,
 | 
						|
				 ionic_queue_dbell_val(&aq->q));
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_dwork(struct work_struct *ws)
 | 
						|
{
 | 
						|
	struct ionic_ibdev *dev =
 | 
						|
		container_of(ws, struct ionic_ibdev, admin_dwork.work);
 | 
						|
	struct ionic_aq *aq, *bad_aq = NULL;
 | 
						|
	bool do_reschedule = false;
 | 
						|
	unsigned long irqflags;
 | 
						|
	bool do_reset = false;
 | 
						|
	u16 pos;
 | 
						|
	int i;
 | 
						|
 | 
						|
	for (i = 0; i < dev->lif_cfg.aq_count; i++) {
 | 
						|
		aq = dev->aq_vec[i];
 | 
						|
 | 
						|
		spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
 | 
						|
		if (ionic_queue_empty(&aq->q))
 | 
						|
			goto next_aq;
 | 
						|
 | 
						|
		/* Reschedule if any queue has outstanding work */
 | 
						|
		do_reschedule = true;
 | 
						|
 | 
						|
		if (time_is_after_eq_jiffies(aq->stamp + IONIC_ADMIN_WARN))
 | 
						|
			/* Warning threshold not met, nothing to do */
 | 
						|
			goto next_aq;
 | 
						|
 | 
						|
		/* See if polling now makes some progress */
 | 
						|
		pos = aq->q.cons;
 | 
						|
		ionic_admin_poll_locked(aq);
 | 
						|
		if (pos != aq->q.cons) {
 | 
						|
			ibdev_dbg(&dev->ibdev,
 | 
						|
				  "missed event for acq %d\n", aq->cqid);
 | 
						|
			goto next_aq;
 | 
						|
		}
 | 
						|
 | 
						|
		if (time_is_after_eq_jiffies(aq->stamp +
 | 
						|
					     IONIC_ADMIN_TIMEOUT)) {
 | 
						|
			/* Timeout threshold not met */
 | 
						|
			ibdev_dbg(&dev->ibdev, "no progress after %ums\n",
 | 
						|
				  (u32)jiffies_to_msecs(jiffies - aq->stamp));
 | 
						|
			goto next_aq;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Queue timed out */
 | 
						|
		bad_aq = aq;
 | 
						|
		do_reset = true;
 | 
						|
next_aq:
 | 
						|
		spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
	}
 | 
						|
 | 
						|
	if (do_reset)
 | 
						|
		/* Reset RDMA lif on a timeout */
 | 
						|
		ionic_admin_timedout(bad_aq);
 | 
						|
	else if (do_reschedule)
 | 
						|
		/* Try to poll again later */
 | 
						|
		ionic_admin_reset_dwork(dev);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_work(struct work_struct *ws)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq = container_of(ws, struct ionic_aq, work);
 | 
						|
	unsigned long irqflags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
	ionic_admin_poll_locked(aq);
 | 
						|
	spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_post_aq(struct ionic_aq *aq, struct ionic_admin_wr *wr)
 | 
						|
{
 | 
						|
	unsigned long irqflags;
 | 
						|
	bool poll;
 | 
						|
 | 
						|
	wr->status = IONIC_ADMIN_POSTED;
 | 
						|
	wr->aq = aq;
 | 
						|
 | 
						|
	spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
	poll = list_empty(&aq->wr_post);
 | 
						|
	list_add(&wr->aq_ent, &aq->wr_post);
 | 
						|
	if (poll)
 | 
						|
		ionic_admin_poll_locked(aq);
 | 
						|
	spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
}
 | 
						|
 | 
						|
void ionic_admin_post(struct ionic_ibdev *dev, struct ionic_admin_wr *wr)
 | 
						|
{
 | 
						|
	int aq_idx;
 | 
						|
 | 
						|
	/* Use cpu id for the adminq selection */
 | 
						|
	aq_idx = raw_smp_processor_id() % dev->lif_cfg.aq_count;
 | 
						|
	ionic_admin_post_aq(dev->aq_vec[aq_idx], wr);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_admin_cancel(struct ionic_admin_wr *wr)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq = wr->aq;
 | 
						|
	unsigned long irqflags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
 | 
						|
	if (!list_empty(&wr->aq_ent)) {
 | 
						|
		list_del(&wr->aq_ent);
 | 
						|
		if (wr->status != IONIC_ADMIN_POSTED)
 | 
						|
			aq->q_wr[wr->status].wr = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
}
 | 
						|
 | 
						|
static int ionic_admin_busy_wait(struct ionic_admin_wr *wr)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq = wr->aq;
 | 
						|
	unsigned long irqflags;
 | 
						|
	int try_i;
 | 
						|
 | 
						|
	for (try_i = 0; try_i < IONIC_ADMIN_BUSY_RETRY_COUNT; ++try_i) {
 | 
						|
		if (completion_done(&wr->work))
 | 
						|
			return 0;
 | 
						|
 | 
						|
		mdelay(IONIC_ADMIN_BUSY_RETRY_MS);
 | 
						|
 | 
						|
		spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
		ionic_admin_poll_locked(aq);
 | 
						|
		spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * we timed out. Initiate RDMA LIF reset and indicate
 | 
						|
	 * error to caller.
 | 
						|
	 */
 | 
						|
	ionic_admin_timedout(aq);
 | 
						|
	return -ETIMEDOUT;
 | 
						|
}
 | 
						|
 | 
						|
int ionic_admin_wait(struct ionic_ibdev *dev, struct ionic_admin_wr *wr,
 | 
						|
		     enum ionic_admin_flags flags)
 | 
						|
{
 | 
						|
	int rc, timo;
 | 
						|
 | 
						|
	if (flags & IONIC_ADMIN_F_BUSYWAIT) {
 | 
						|
		/* Spin */
 | 
						|
		rc = ionic_admin_busy_wait(wr);
 | 
						|
	} else if (flags & IONIC_ADMIN_F_INTERRUPT) {
 | 
						|
		/*
 | 
						|
		 * Interruptible sleep, 1s timeout
 | 
						|
		 * This is used for commands which are safe for the caller
 | 
						|
		 * to clean up without killing and resetting the adminq.
 | 
						|
		 */
 | 
						|
		timo = wait_for_completion_interruptible_timeout(&wr->work,
 | 
						|
								 HZ);
 | 
						|
		if (timo > 0)
 | 
						|
			rc = 0;
 | 
						|
		else if (timo == 0)
 | 
						|
			rc = -ETIMEDOUT;
 | 
						|
		else
 | 
						|
			rc = timo;
 | 
						|
	} else {
 | 
						|
		/*
 | 
						|
		 * Uninterruptible sleep
 | 
						|
		 * This is used for commands which are NOT safe for the
 | 
						|
		 * caller to clean up. Cleanup must be handled by the
 | 
						|
		 * adminq kill and reset process so that host memory is
 | 
						|
		 * not corrupted by the device.
 | 
						|
		 */
 | 
						|
		wait_for_completion(&wr->work);
 | 
						|
		rc = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (rc) {
 | 
						|
		ibdev_warn(&dev->ibdev, "wait status %d\n", rc);
 | 
						|
		ionic_admin_cancel(wr);
 | 
						|
	} else if (wr->status == IONIC_ADMIN_KILLED) {
 | 
						|
		ibdev_dbg(&dev->ibdev, "admin killed\n");
 | 
						|
 | 
						|
		/* No error if admin already killed during teardown */
 | 
						|
		rc = (flags & IONIC_ADMIN_F_TEARDOWN) ? 0 : -ENODEV;
 | 
						|
	} else if (ionic_v1_cqe_error(&wr->cqe)) {
 | 
						|
		ibdev_warn(&dev->ibdev, "opcode %u error %u\n",
 | 
						|
			   wr->wqe.op,
 | 
						|
			   be32_to_cpu(wr->cqe.status_length));
 | 
						|
		rc = -EINVAL;
 | 
						|
	}
 | 
						|
	return rc;
 | 
						|
}
 | 
						|
 | 
						|
static int ionic_rdma_devcmd(struct ionic_ibdev *dev,
 | 
						|
			     struct ionic_admin_ctx *admin)
 | 
						|
{
 | 
						|
	int rc;
 | 
						|
 | 
						|
	rc = ionic_adminq_post_wait(dev->lif_cfg.lif, admin);
 | 
						|
	if (rc)
 | 
						|
		return rc;
 | 
						|
 | 
						|
	return ionic_error_to_errno(admin->comp.comp.status);
 | 
						|
}
 | 
						|
 | 
						|
int ionic_rdma_reset_devcmd(struct ionic_ibdev *dev)
 | 
						|
{
 | 
						|
	struct ionic_admin_ctx admin = {
 | 
						|
		.work = COMPLETION_INITIALIZER_ONSTACK(admin.work),
 | 
						|
		.cmd.rdma_reset = {
 | 
						|
			.opcode = IONIC_CMD_RDMA_RESET_LIF,
 | 
						|
			.lif_index = cpu_to_le16(dev->lif_cfg.lif_index),
 | 
						|
		},
 | 
						|
	};
 | 
						|
 | 
						|
	return ionic_rdma_devcmd(dev, &admin);
 | 
						|
}
 | 
						|
 | 
						|
static int ionic_rdma_queue_devcmd(struct ionic_ibdev *dev,
 | 
						|
				   struct ionic_queue *q,
 | 
						|
				   u32 qid, u32 cid, u16 opcode)
 | 
						|
{
 | 
						|
	struct ionic_admin_ctx admin = {
 | 
						|
		.work = COMPLETION_INITIALIZER_ONSTACK(admin.work),
 | 
						|
		.cmd.rdma_queue = {
 | 
						|
			.opcode = opcode,
 | 
						|
			.lif_index = cpu_to_le16(dev->lif_cfg.lif_index),
 | 
						|
			.qid_ver = cpu_to_le32(qid),
 | 
						|
			.cid = cpu_to_le32(cid),
 | 
						|
			.dbid = cpu_to_le16(dev->lif_cfg.dbid),
 | 
						|
			.depth_log2 = q->depth_log2,
 | 
						|
			.stride_log2 = q->stride_log2,
 | 
						|
			.dma_addr = cpu_to_le64(q->dma),
 | 
						|
		},
 | 
						|
	};
 | 
						|
 | 
						|
	return ionic_rdma_devcmd(dev, &admin);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_rdma_admincq_comp(struct ib_cq *ibcq, void *cq_context)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq = cq_context;
 | 
						|
	unsigned long irqflags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&aq->lock, irqflags);
 | 
						|
	aq->armed = false;
 | 
						|
	if (atomic_read(&aq->admin_state) < IONIC_ADMIN_KILLED)
 | 
						|
		queue_work(ionic_evt_workq, &aq->work);
 | 
						|
	spin_unlock_irqrestore(&aq->lock, irqflags);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_rdma_admincq_event(struct ib_event *event, void *cq_context)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq = cq_context;
 | 
						|
 | 
						|
	ibdev_err(&aq->dev->ibdev, "admincq event %d\n", event->event);
 | 
						|
}
 | 
						|
 | 
						|
static struct ionic_vcq *ionic_create_rdma_admincq(struct ionic_ibdev *dev,
 | 
						|
						   int comp_vector)
 | 
						|
{
 | 
						|
	struct ib_cq_init_attr attr = {
 | 
						|
		.cqe = IONIC_AQ_DEPTH,
 | 
						|
		.comp_vector = comp_vector,
 | 
						|
	};
 | 
						|
	struct ionic_tbl_buf buf = {};
 | 
						|
	struct ionic_vcq *vcq;
 | 
						|
	struct ionic_cq *cq;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	vcq = kzalloc(sizeof(*vcq), GFP_KERNEL);
 | 
						|
	if (!vcq)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	vcq->ibcq.device = &dev->ibdev;
 | 
						|
	vcq->ibcq.comp_handler = ionic_rdma_admincq_comp;
 | 
						|
	vcq->ibcq.event_handler = ionic_rdma_admincq_event;
 | 
						|
	atomic_set(&vcq->ibcq.usecnt, 0);
 | 
						|
 | 
						|
	vcq->udma_mask = 1;
 | 
						|
	cq = &vcq->cq[0];
 | 
						|
 | 
						|
	rc = ionic_create_cq_common(vcq, &buf, &attr, NULL, NULL,
 | 
						|
				    NULL, NULL, 0);
 | 
						|
	if (rc)
 | 
						|
		goto err_init;
 | 
						|
 | 
						|
	rc = ionic_rdma_queue_devcmd(dev, &cq->q, cq->cqid, cq->eqid,
 | 
						|
				     IONIC_CMD_RDMA_CREATE_CQ);
 | 
						|
	if (rc)
 | 
						|
		goto err_cmd;
 | 
						|
 | 
						|
	return vcq;
 | 
						|
 | 
						|
err_cmd:
 | 
						|
	ionic_destroy_cq_common(dev, cq);
 | 
						|
err_init:
 | 
						|
	kfree(vcq);
 | 
						|
 | 
						|
	return ERR_PTR(rc);
 | 
						|
}
 | 
						|
 | 
						|
static struct ionic_aq *__ionic_create_rdma_adminq(struct ionic_ibdev *dev,
 | 
						|
						   u32 aqid, u32 cqid)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	aq = kzalloc(sizeof(*aq), GFP_KERNEL);
 | 
						|
	if (!aq)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	atomic_set(&aq->admin_state, IONIC_ADMIN_KILLED);
 | 
						|
	aq->dev = dev;
 | 
						|
	aq->aqid = aqid;
 | 
						|
	aq->cqid = cqid;
 | 
						|
	spin_lock_init(&aq->lock);
 | 
						|
 | 
						|
	rc = ionic_queue_init(&aq->q, dev->lif_cfg.hwdev, IONIC_EQ_DEPTH,
 | 
						|
			      ADMIN_WQE_STRIDE);
 | 
						|
	if (rc)
 | 
						|
		goto err_q;
 | 
						|
 | 
						|
	ionic_queue_dbell_init(&aq->q, aq->aqid);
 | 
						|
 | 
						|
	aq->q_wr = kcalloc((u32)aq->q.mask + 1, sizeof(*aq->q_wr), GFP_KERNEL);
 | 
						|
	if (!aq->q_wr) {
 | 
						|
		rc = -ENOMEM;
 | 
						|
		goto err_wr;
 | 
						|
	}
 | 
						|
 | 
						|
	INIT_LIST_HEAD(&aq->wr_prod);
 | 
						|
	INIT_LIST_HEAD(&aq->wr_post);
 | 
						|
 | 
						|
	INIT_WORK(&aq->work, ionic_admin_work);
 | 
						|
	aq->armed = false;
 | 
						|
 | 
						|
	return aq;
 | 
						|
 | 
						|
err_wr:
 | 
						|
	ionic_queue_destroy(&aq->q, dev->lif_cfg.hwdev);
 | 
						|
err_q:
 | 
						|
	kfree(aq);
 | 
						|
 | 
						|
	return ERR_PTR(rc);
 | 
						|
}
 | 
						|
 | 
						|
static void __ionic_destroy_rdma_adminq(struct ionic_ibdev *dev,
 | 
						|
					struct ionic_aq *aq)
 | 
						|
{
 | 
						|
	ionic_queue_destroy(&aq->q, dev->lif_cfg.hwdev);
 | 
						|
	kfree(aq);
 | 
						|
}
 | 
						|
 | 
						|
static struct ionic_aq *ionic_create_rdma_adminq(struct ionic_ibdev *dev,
 | 
						|
						 u32 aqid, u32 cqid)
 | 
						|
{
 | 
						|
	struct ionic_aq *aq;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	aq = __ionic_create_rdma_adminq(dev, aqid, cqid);
 | 
						|
	if (IS_ERR(aq))
 | 
						|
		return aq;
 | 
						|
 | 
						|
	rc = ionic_rdma_queue_devcmd(dev, &aq->q, aq->aqid, aq->cqid,
 | 
						|
				     IONIC_CMD_RDMA_CREATE_ADMINQ);
 | 
						|
	if (rc)
 | 
						|
		goto err_cmd;
 | 
						|
 | 
						|
	return aq;
 | 
						|
 | 
						|
err_cmd:
 | 
						|
	__ionic_destroy_rdma_adminq(dev, aq);
 | 
						|
 | 
						|
	return ERR_PTR(rc);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_flush_qs(struct ionic_ibdev *dev)
 | 
						|
{
 | 
						|
	struct ionic_qp *qp, *qp_tmp;
 | 
						|
	struct ionic_cq *cq, *cq_tmp;
 | 
						|
	LIST_HEAD(flush_list);
 | 
						|
	unsigned long index;
 | 
						|
 | 
						|
	WARN_ON(!irqs_disabled());
 | 
						|
 | 
						|
	/* Flush qp send and recv */
 | 
						|
	xa_lock(&dev->qp_tbl);
 | 
						|
	xa_for_each(&dev->qp_tbl, index, qp) {
 | 
						|
		kref_get(&qp->qp_kref);
 | 
						|
		list_add_tail(&qp->ibkill_flush_ent, &flush_list);
 | 
						|
	}
 | 
						|
	xa_unlock(&dev->qp_tbl);
 | 
						|
 | 
						|
	list_for_each_entry_safe(qp, qp_tmp, &flush_list, ibkill_flush_ent) {
 | 
						|
		ionic_flush_qp(dev, qp);
 | 
						|
		kref_put(&qp->qp_kref, ionic_qp_complete);
 | 
						|
		list_del(&qp->ibkill_flush_ent);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Notify completions */
 | 
						|
	xa_lock(&dev->cq_tbl);
 | 
						|
	xa_for_each(&dev->cq_tbl, index, cq) {
 | 
						|
		kref_get(&cq->cq_kref);
 | 
						|
		list_add_tail(&cq->ibkill_flush_ent, &flush_list);
 | 
						|
	}
 | 
						|
	xa_unlock(&dev->cq_tbl);
 | 
						|
 | 
						|
	list_for_each_entry_safe(cq, cq_tmp, &flush_list, ibkill_flush_ent) {
 | 
						|
		ionic_notify_flush_cq(cq);
 | 
						|
		kref_put(&cq->cq_kref, ionic_cq_complete);
 | 
						|
		list_del(&cq->ibkill_flush_ent);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_kill_ibdev(struct ionic_ibdev *dev, bool fatal_path)
 | 
						|
{
 | 
						|
	unsigned long irqflags;
 | 
						|
	bool do_flush = false;
 | 
						|
	int i;
 | 
						|
 | 
						|
	/* Mark AQs for drain and flush the QPs while irq is disabled */
 | 
						|
	local_irq_save(irqflags);
 | 
						|
 | 
						|
	/* Mark the admin queue, flushing at most once */
 | 
						|
	for (i = 0; i < dev->lif_cfg.aq_count; i++) {
 | 
						|
		struct ionic_aq *aq = dev->aq_vec[i];
 | 
						|
 | 
						|
		spin_lock(&aq->lock);
 | 
						|
		if (atomic_read(&aq->admin_state) != IONIC_ADMIN_KILLED) {
 | 
						|
			atomic_set(&aq->admin_state, IONIC_ADMIN_KILLED);
 | 
						|
			/* Flush incomplete admin commands */
 | 
						|
			ionic_admin_poll_locked(aq);
 | 
						|
			do_flush = true;
 | 
						|
		}
 | 
						|
		spin_unlock(&aq->lock);
 | 
						|
	}
 | 
						|
 | 
						|
	if (do_flush)
 | 
						|
		ionic_flush_qs(dev);
 | 
						|
 | 
						|
	local_irq_restore(irqflags);
 | 
						|
 | 
						|
	/* Post a fatal event if requested */
 | 
						|
	if (fatal_path) {
 | 
						|
		struct ib_event ev;
 | 
						|
 | 
						|
		ev.device = &dev->ibdev;
 | 
						|
		ev.element.port_num = 1;
 | 
						|
		ev.event = IB_EVENT_DEVICE_FATAL;
 | 
						|
 | 
						|
		ib_dispatch_event(&ev);
 | 
						|
	}
 | 
						|
 | 
						|
	atomic_set(&dev->admin_state, IONIC_ADMIN_KILLED);
 | 
						|
}
 | 
						|
 | 
						|
void ionic_kill_rdma_admin(struct ionic_ibdev *dev, bool fatal_path)
 | 
						|
{
 | 
						|
	enum ionic_admin_state old_state;
 | 
						|
	unsigned long irqflags = 0;
 | 
						|
	int i, rc;
 | 
						|
 | 
						|
	if (!dev->aq_vec)
 | 
						|
		return;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Admin queues are transitioned from active to paused to killed state.
 | 
						|
	 * When in paused state, no new commands are issued to the device,
 | 
						|
	 * nor are any completed locally. After resetting the lif, it will be
 | 
						|
	 * safe to resume the rdma admin queues in the killed state. Commands
 | 
						|
	 * will not be issued to the device, but will complete locally with status
 | 
						|
	 * IONIC_ADMIN_KILLED. Handling completion will ensure that creating or
 | 
						|
	 * modifying resources fails, but destroying resources succeeds.
 | 
						|
	 * If there was a failure resetting the lif using this strategy,
 | 
						|
	 * then the state of the device is unknown.
 | 
						|
	 */
 | 
						|
	old_state = atomic_cmpxchg(&dev->admin_state, IONIC_ADMIN_ACTIVE,
 | 
						|
				   IONIC_ADMIN_PAUSED);
 | 
						|
	if (old_state != IONIC_ADMIN_ACTIVE)
 | 
						|
		return;
 | 
						|
 | 
						|
	/* Pause all the AQs */
 | 
						|
	local_irq_save(irqflags);
 | 
						|
	for (i = 0; i < dev->lif_cfg.aq_count; i++) {
 | 
						|
		struct ionic_aq *aq = dev->aq_vec[i];
 | 
						|
 | 
						|
		spin_lock(&aq->lock);
 | 
						|
		/* pause rdma admin queues to reset lif */
 | 
						|
		if (atomic_read(&aq->admin_state) == IONIC_ADMIN_ACTIVE)
 | 
						|
			atomic_set(&aq->admin_state, IONIC_ADMIN_PAUSED);
 | 
						|
		spin_unlock(&aq->lock);
 | 
						|
	}
 | 
						|
	local_irq_restore(irqflags);
 | 
						|
 | 
						|
	rc = ionic_rdma_reset_devcmd(dev);
 | 
						|
	if (unlikely(rc)) {
 | 
						|
		ibdev_err(&dev->ibdev, "failed to reset rdma %d\n", rc);
 | 
						|
		ionic_request_rdma_reset(dev->lif_cfg.lif);
 | 
						|
	}
 | 
						|
 | 
						|
	ionic_kill_ibdev(dev, fatal_path);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_reset_work(struct work_struct *ws)
 | 
						|
{
 | 
						|
	struct ionic_ibdev *dev =
 | 
						|
		container_of(ws, struct ionic_ibdev, reset_work);
 | 
						|
 | 
						|
	ionic_kill_rdma_admin(dev, true);
 | 
						|
}
 | 
						|
 | 
						|
static bool ionic_next_eqe(struct ionic_eq *eq, struct ionic_v1_eqe *eqe)
 | 
						|
{
 | 
						|
	struct ionic_v1_eqe *qeqe;
 | 
						|
	bool color;
 | 
						|
 | 
						|
	qeqe = ionic_queue_at_prod(&eq->q);
 | 
						|
	color = ionic_v1_eqe_color(qeqe);
 | 
						|
 | 
						|
	/* cons is color for eq */
 | 
						|
	if (eq->q.cons != color)
 | 
						|
		return false;
 | 
						|
 | 
						|
	/* Prevent out-of-order reads of the EQE */
 | 
						|
	dma_rmb();
 | 
						|
 | 
						|
	ibdev_dbg(&eq->dev->ibdev, "poll eq prod %u\n", eq->q.prod);
 | 
						|
	print_hex_dump_debug("eqe ", DUMP_PREFIX_OFFSET, 16, 1,
 | 
						|
			     qeqe, BIT(eq->q.stride_log2), true);
 | 
						|
	*eqe = *qeqe;
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_cq_event(struct ionic_ibdev *dev, u32 cqid, u8 code)
 | 
						|
{
 | 
						|
	unsigned long irqflags;
 | 
						|
	struct ib_event ibev;
 | 
						|
	struct ionic_cq *cq;
 | 
						|
 | 
						|
	xa_lock_irqsave(&dev->cq_tbl, irqflags);
 | 
						|
	cq = xa_load(&dev->cq_tbl, cqid);
 | 
						|
	if (cq)
 | 
						|
		kref_get(&cq->cq_kref);
 | 
						|
	xa_unlock_irqrestore(&dev->cq_tbl, irqflags);
 | 
						|
 | 
						|
	if (!cq) {
 | 
						|
		ibdev_dbg(&dev->ibdev,
 | 
						|
			  "missing cqid %#x code %u\n", cqid, code);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (code) {
 | 
						|
	case IONIC_V1_EQE_CQ_NOTIFY:
 | 
						|
		if (cq->vcq->ibcq.comp_handler)
 | 
						|
			cq->vcq->ibcq.comp_handler(&cq->vcq->ibcq,
 | 
						|
						   cq->vcq->ibcq.cq_context);
 | 
						|
		break;
 | 
						|
 | 
						|
	case IONIC_V1_EQE_CQ_ERR:
 | 
						|
		if (cq->vcq->ibcq.event_handler) {
 | 
						|
			ibev.event = IB_EVENT_CQ_ERR;
 | 
						|
			ibev.device = &dev->ibdev;
 | 
						|
			ibev.element.cq = &cq->vcq->ibcq;
 | 
						|
 | 
						|
			cq->vcq->ibcq.event_handler(&ibev,
 | 
						|
						    cq->vcq->ibcq.cq_context);
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		ibdev_dbg(&dev->ibdev,
 | 
						|
			  "unrecognized cqid %#x code %u\n", cqid, code);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	kref_put(&cq->cq_kref, ionic_cq_complete);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_qp_event(struct ionic_ibdev *dev, u32 qpid, u8 code)
 | 
						|
{
 | 
						|
	unsigned long irqflags;
 | 
						|
	struct ib_event ibev;
 | 
						|
	struct ionic_qp *qp;
 | 
						|
 | 
						|
	xa_lock_irqsave(&dev->qp_tbl, irqflags);
 | 
						|
	qp = xa_load(&dev->qp_tbl, qpid);
 | 
						|
	if (qp)
 | 
						|
		kref_get(&qp->qp_kref);
 | 
						|
	xa_unlock_irqrestore(&dev->qp_tbl, irqflags);
 | 
						|
 | 
						|
	if (!qp) {
 | 
						|
		ibdev_dbg(&dev->ibdev,
 | 
						|
			  "missing qpid %#x code %u\n", qpid, code);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	ibev.device = &dev->ibdev;
 | 
						|
	ibev.element.qp = &qp->ibqp;
 | 
						|
 | 
						|
	switch (code) {
 | 
						|
	case IONIC_V1_EQE_SQ_DRAIN:
 | 
						|
		ibev.event = IB_EVENT_SQ_DRAINED;
 | 
						|
		break;
 | 
						|
 | 
						|
	case IONIC_V1_EQE_QP_COMM_EST:
 | 
						|
		ibev.event = IB_EVENT_COMM_EST;
 | 
						|
		break;
 | 
						|
 | 
						|
	case IONIC_V1_EQE_QP_LAST_WQE:
 | 
						|
		ibev.event = IB_EVENT_QP_LAST_WQE_REACHED;
 | 
						|
		break;
 | 
						|
 | 
						|
	case IONIC_V1_EQE_QP_ERR:
 | 
						|
		ibev.event = IB_EVENT_QP_FATAL;
 | 
						|
		break;
 | 
						|
 | 
						|
	case IONIC_V1_EQE_QP_ERR_REQUEST:
 | 
						|
		ibev.event = IB_EVENT_QP_REQ_ERR;
 | 
						|
		break;
 | 
						|
 | 
						|
	case IONIC_V1_EQE_QP_ERR_ACCESS:
 | 
						|
		ibev.event = IB_EVENT_QP_ACCESS_ERR;
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		ibdev_dbg(&dev->ibdev,
 | 
						|
			  "unrecognized qpid %#x code %u\n", qpid, code);
 | 
						|
		goto out;
 | 
						|
	}
 | 
						|
 | 
						|
	if (qp->ibqp.event_handler)
 | 
						|
		qp->ibqp.event_handler(&ibev, qp->ibqp.qp_context);
 | 
						|
 | 
						|
out:
 | 
						|
	kref_put(&qp->qp_kref, ionic_qp_complete);
 | 
						|
}
 | 
						|
 | 
						|
static u16 ionic_poll_eq(struct ionic_eq *eq, u16 budget)
 | 
						|
{
 | 
						|
	struct ionic_ibdev *dev = eq->dev;
 | 
						|
	struct ionic_v1_eqe eqe;
 | 
						|
	u16 npolled = 0;
 | 
						|
	u8 type, code;
 | 
						|
	u32 evt, qid;
 | 
						|
 | 
						|
	while (npolled < budget) {
 | 
						|
		if (!ionic_next_eqe(eq, &eqe))
 | 
						|
			break;
 | 
						|
 | 
						|
		ionic_queue_produce(&eq->q);
 | 
						|
 | 
						|
		/* cons is color for eq */
 | 
						|
		eq->q.cons = ionic_color_wrap(eq->q.prod, eq->q.cons);
 | 
						|
 | 
						|
		++npolled;
 | 
						|
 | 
						|
		evt = ionic_v1_eqe_evt(&eqe);
 | 
						|
		type = ionic_v1_eqe_evt_type(evt);
 | 
						|
		code = ionic_v1_eqe_evt_code(evt);
 | 
						|
		qid = ionic_v1_eqe_evt_qid(evt);
 | 
						|
 | 
						|
		switch (type) {
 | 
						|
		case IONIC_V1_EQE_TYPE_CQ:
 | 
						|
			ionic_cq_event(dev, qid, code);
 | 
						|
			break;
 | 
						|
 | 
						|
		case IONIC_V1_EQE_TYPE_QP:
 | 
						|
			ionic_qp_event(dev, qid, code);
 | 
						|
			break;
 | 
						|
 | 
						|
		default:
 | 
						|
			ibdev_dbg(&dev->ibdev,
 | 
						|
				  "unknown event %#x type %u\n", evt, type);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return npolled;
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_poll_eq_work(struct work_struct *work)
 | 
						|
{
 | 
						|
	struct ionic_eq *eq = container_of(work, struct ionic_eq, work);
 | 
						|
	u32 npolled;
 | 
						|
 | 
						|
	if (unlikely(!eq->enable) || WARN_ON(eq->armed))
 | 
						|
		return;
 | 
						|
 | 
						|
	npolled = ionic_poll_eq(eq, IONIC_EQ_WORK_BUDGET);
 | 
						|
	if (npolled == IONIC_EQ_WORK_BUDGET) {
 | 
						|
		ionic_intr_credits(eq->dev->lif_cfg.intr_ctrl, eq->intr,
 | 
						|
				   npolled, 0);
 | 
						|
		queue_work(ionic_evt_workq, &eq->work);
 | 
						|
	} else {
 | 
						|
		xchg(&eq->armed, 1);
 | 
						|
		ionic_intr_credits(eq->dev->lif_cfg.intr_ctrl, eq->intr,
 | 
						|
				   0, IONIC_INTR_CRED_UNMASK);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static irqreturn_t ionic_poll_eq_isr(int irq, void *eqptr)
 | 
						|
{
 | 
						|
	struct ionic_eq *eq = eqptr;
 | 
						|
	int was_armed;
 | 
						|
	u32 npolled;
 | 
						|
 | 
						|
	was_armed = xchg(&eq->armed, 0);
 | 
						|
 | 
						|
	if (unlikely(!eq->enable) || !was_armed)
 | 
						|
		return IRQ_HANDLED;
 | 
						|
 | 
						|
	npolled = ionic_poll_eq(eq, IONIC_EQ_ISR_BUDGET);
 | 
						|
	if (npolled == IONIC_EQ_ISR_BUDGET) {
 | 
						|
		ionic_intr_credits(eq->dev->lif_cfg.intr_ctrl, eq->intr,
 | 
						|
				   npolled, 0);
 | 
						|
		queue_work(ionic_evt_workq, &eq->work);
 | 
						|
	} else {
 | 
						|
		xchg(&eq->armed, 1);
 | 
						|
		ionic_intr_credits(eq->dev->lif_cfg.intr_ctrl, eq->intr,
 | 
						|
				   0, IONIC_INTR_CRED_UNMASK);
 | 
						|
	}
 | 
						|
 | 
						|
	return IRQ_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
static struct ionic_eq *ionic_create_eq(struct ionic_ibdev *dev, int eqid)
 | 
						|
{
 | 
						|
	struct ionic_intr_info intr_obj = { };
 | 
						|
	struct ionic_eq *eq;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	eq = kzalloc(sizeof(*eq), GFP_KERNEL);
 | 
						|
	if (!eq)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	eq->dev = dev;
 | 
						|
 | 
						|
	rc = ionic_queue_init(&eq->q, dev->lif_cfg.hwdev, IONIC_EQ_DEPTH,
 | 
						|
			      sizeof(struct ionic_v1_eqe));
 | 
						|
	if (rc)
 | 
						|
		goto err_q;
 | 
						|
 | 
						|
	eq->eqid = eqid;
 | 
						|
 | 
						|
	eq->armed = true;
 | 
						|
	eq->enable = false;
 | 
						|
	INIT_WORK(&eq->work, ionic_poll_eq_work);
 | 
						|
 | 
						|
	rc = ionic_intr_alloc(dev->lif_cfg.lif, &intr_obj);
 | 
						|
	if (rc < 0)
 | 
						|
		goto err_intr;
 | 
						|
 | 
						|
	eq->irq = intr_obj.vector;
 | 
						|
	eq->intr = intr_obj.index;
 | 
						|
 | 
						|
	ionic_queue_dbell_init(&eq->q, eq->eqid);
 | 
						|
 | 
						|
	/* cons is color for eq */
 | 
						|
	eq->q.cons = true;
 | 
						|
 | 
						|
	snprintf(eq->name, sizeof(eq->name), "%s-%d-%d-eq",
 | 
						|
		 "ionr", dev->lif_cfg.lif_index, eq->eqid);
 | 
						|
 | 
						|
	ionic_intr_mask(dev->lif_cfg.intr_ctrl, eq->intr, IONIC_INTR_MASK_SET);
 | 
						|
	ionic_intr_mask_assert(dev->lif_cfg.intr_ctrl, eq->intr, IONIC_INTR_MASK_SET);
 | 
						|
	ionic_intr_coal_init(dev->lif_cfg.intr_ctrl, eq->intr, 0);
 | 
						|
	ionic_intr_clean(dev->lif_cfg.intr_ctrl, eq->intr);
 | 
						|
 | 
						|
	eq->enable = true;
 | 
						|
 | 
						|
	rc = request_irq(eq->irq, ionic_poll_eq_isr, 0, eq->name, eq);
 | 
						|
	if (rc)
 | 
						|
		goto err_irq;
 | 
						|
 | 
						|
	rc = ionic_rdma_queue_devcmd(dev, &eq->q, eq->eqid, eq->intr,
 | 
						|
				     IONIC_CMD_RDMA_CREATE_EQ);
 | 
						|
	if (rc)
 | 
						|
		goto err_cmd;
 | 
						|
 | 
						|
	ionic_intr_mask(dev->lif_cfg.intr_ctrl, eq->intr, IONIC_INTR_MASK_CLEAR);
 | 
						|
 | 
						|
	return eq;
 | 
						|
 | 
						|
err_cmd:
 | 
						|
	eq->enable = false;
 | 
						|
	free_irq(eq->irq, eq);
 | 
						|
	flush_work(&eq->work);
 | 
						|
err_irq:
 | 
						|
	ionic_intr_free(dev->lif_cfg.lif, eq->intr);
 | 
						|
err_intr:
 | 
						|
	ionic_queue_destroy(&eq->q, dev->lif_cfg.hwdev);
 | 
						|
err_q:
 | 
						|
	kfree(eq);
 | 
						|
 | 
						|
	return ERR_PTR(rc);
 | 
						|
}
 | 
						|
 | 
						|
static void ionic_destroy_eq(struct ionic_eq *eq)
 | 
						|
{
 | 
						|
	struct ionic_ibdev *dev = eq->dev;
 | 
						|
 | 
						|
	eq->enable = false;
 | 
						|
	free_irq(eq->irq, eq);
 | 
						|
	flush_work(&eq->work);
 | 
						|
 | 
						|
	ionic_intr_free(dev->lif_cfg.lif, eq->intr);
 | 
						|
	ionic_queue_destroy(&eq->q, dev->lif_cfg.hwdev);
 | 
						|
	kfree(eq);
 | 
						|
}
 | 
						|
 | 
						|
int ionic_create_rdma_admin(struct ionic_ibdev *dev)
 | 
						|
{
 | 
						|
	int eq_i = 0, aq_i = 0, rc = 0;
 | 
						|
	struct ionic_vcq *vcq;
 | 
						|
	struct ionic_aq *aq;
 | 
						|
	struct ionic_eq *eq;
 | 
						|
 | 
						|
	dev->eq_vec = NULL;
 | 
						|
	dev->aq_vec = NULL;
 | 
						|
 | 
						|
	INIT_WORK(&dev->reset_work, ionic_reset_work);
 | 
						|
	INIT_DELAYED_WORK(&dev->admin_dwork, ionic_admin_dwork);
 | 
						|
	atomic_set(&dev->admin_state, IONIC_ADMIN_KILLED);
 | 
						|
 | 
						|
	if (dev->lif_cfg.aq_count > IONIC_AQ_COUNT) {
 | 
						|
		ibdev_dbg(&dev->ibdev, "limiting adminq count to %d\n",
 | 
						|
			  IONIC_AQ_COUNT);
 | 
						|
		dev->lif_cfg.aq_count = IONIC_AQ_COUNT;
 | 
						|
	}
 | 
						|
 | 
						|
	if (dev->lif_cfg.eq_count > IONIC_EQ_COUNT) {
 | 
						|
		dev_dbg(&dev->ibdev.dev, "limiting eventq count to %d\n",
 | 
						|
			IONIC_EQ_COUNT);
 | 
						|
		dev->lif_cfg.eq_count = IONIC_EQ_COUNT;
 | 
						|
	}
 | 
						|
 | 
						|
	/* need at least two eq and one aq */
 | 
						|
	if (dev->lif_cfg.eq_count < IONIC_EQ_COUNT_MIN ||
 | 
						|
	    dev->lif_cfg.aq_count < IONIC_AQ_COUNT_MIN) {
 | 
						|
		rc = -EINVAL;
 | 
						|
		goto out;
 | 
						|
	}
 | 
						|
 | 
						|
	dev->eq_vec = kmalloc_array(dev->lif_cfg.eq_count, sizeof(*dev->eq_vec),
 | 
						|
				    GFP_KERNEL);
 | 
						|
	if (!dev->eq_vec) {
 | 
						|
		rc = -ENOMEM;
 | 
						|
		goto out;
 | 
						|
	}
 | 
						|
 | 
						|
	for (eq_i = 0; eq_i < dev->lif_cfg.eq_count; ++eq_i) {
 | 
						|
		eq = ionic_create_eq(dev, eq_i + dev->lif_cfg.eq_base);
 | 
						|
		if (IS_ERR(eq)) {
 | 
						|
			rc = PTR_ERR(eq);
 | 
						|
 | 
						|
			if (eq_i < IONIC_EQ_COUNT_MIN) {
 | 
						|
				ibdev_err(&dev->ibdev,
 | 
						|
					  "fail create eq %d\n", rc);
 | 
						|
				goto out;
 | 
						|
			}
 | 
						|
 | 
						|
			/* ok, just fewer eq than device supports */
 | 
						|
			ibdev_dbg(&dev->ibdev, "eq count %d want %d rc %d\n",
 | 
						|
				  eq_i, dev->lif_cfg.eq_count, rc);
 | 
						|
 | 
						|
			rc = 0;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		dev->eq_vec[eq_i] = eq;
 | 
						|
	}
 | 
						|
 | 
						|
	dev->lif_cfg.eq_count = eq_i;
 | 
						|
 | 
						|
	dev->aq_vec = kmalloc_array(dev->lif_cfg.aq_count, sizeof(*dev->aq_vec),
 | 
						|
				    GFP_KERNEL);
 | 
						|
	if (!dev->aq_vec) {
 | 
						|
		rc = -ENOMEM;
 | 
						|
		goto out;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Create one CQ per AQ */
 | 
						|
	for (aq_i = 0; aq_i < dev->lif_cfg.aq_count; ++aq_i) {
 | 
						|
		vcq = ionic_create_rdma_admincq(dev, aq_i % eq_i);
 | 
						|
		if (IS_ERR(vcq)) {
 | 
						|
			rc = PTR_ERR(vcq);
 | 
						|
 | 
						|
			if (!aq_i) {
 | 
						|
				ibdev_err(&dev->ibdev,
 | 
						|
					  "failed to create acq %d\n", rc);
 | 
						|
				goto out;
 | 
						|
			}
 | 
						|
 | 
						|
			/* ok, just fewer adminq than device supports */
 | 
						|
			ibdev_dbg(&dev->ibdev, "acq count %d want %d rc %d\n",
 | 
						|
				  aq_i, dev->lif_cfg.aq_count, rc);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		aq = ionic_create_rdma_adminq(dev, aq_i + dev->lif_cfg.aq_base,
 | 
						|
					      vcq->cq[0].cqid);
 | 
						|
		if (IS_ERR(aq)) {
 | 
						|
			/* Clean up the dangling CQ */
 | 
						|
			ionic_destroy_cq_common(dev, &vcq->cq[0]);
 | 
						|
			kfree(vcq);
 | 
						|
 | 
						|
			rc = PTR_ERR(aq);
 | 
						|
 | 
						|
			if (!aq_i) {
 | 
						|
				ibdev_err(&dev->ibdev,
 | 
						|
					  "failed to create aq %d\n", rc);
 | 
						|
				goto out;
 | 
						|
			}
 | 
						|
 | 
						|
			/* ok, just fewer adminq than device supports */
 | 
						|
			ibdev_dbg(&dev->ibdev, "aq count %d want %d rc %d\n",
 | 
						|
				  aq_i, dev->lif_cfg.aq_count, rc);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		vcq->ibcq.cq_context = aq;
 | 
						|
		aq->vcq = vcq;
 | 
						|
 | 
						|
		atomic_set(&aq->admin_state, IONIC_ADMIN_ACTIVE);
 | 
						|
		dev->aq_vec[aq_i] = aq;
 | 
						|
	}
 | 
						|
 | 
						|
	atomic_set(&dev->admin_state, IONIC_ADMIN_ACTIVE);
 | 
						|
out:
 | 
						|
	dev->lif_cfg.eq_count = eq_i;
 | 
						|
	dev->lif_cfg.aq_count = aq_i;
 | 
						|
 | 
						|
	return rc;
 | 
						|
}
 | 
						|
 | 
						|
void ionic_destroy_rdma_admin(struct ionic_ibdev *dev)
 | 
						|
{
 | 
						|
	struct ionic_vcq *vcq;
 | 
						|
	struct ionic_aq *aq;
 | 
						|
	struct ionic_eq *eq;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Killing the admin before destroy makes sure all admin and
 | 
						|
	 * completions are flushed. admin_state = IONIC_ADMIN_KILLED
 | 
						|
	 * stops queueing up further works.
 | 
						|
	 */
 | 
						|
	cancel_delayed_work_sync(&dev->admin_dwork);
 | 
						|
	cancel_work_sync(&dev->reset_work);
 | 
						|
 | 
						|
	if (dev->aq_vec) {
 | 
						|
		while (dev->lif_cfg.aq_count > 0) {
 | 
						|
			aq = dev->aq_vec[--dev->lif_cfg.aq_count];
 | 
						|
			vcq = aq->vcq;
 | 
						|
 | 
						|
			cancel_work_sync(&aq->work);
 | 
						|
 | 
						|
			__ionic_destroy_rdma_adminq(dev, aq);
 | 
						|
			if (vcq) {
 | 
						|
				ionic_destroy_cq_common(dev, &vcq->cq[0]);
 | 
						|
				kfree(vcq);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		kfree(dev->aq_vec);
 | 
						|
	}
 | 
						|
 | 
						|
	if (dev->eq_vec) {
 | 
						|
		while (dev->lif_cfg.eq_count > 0) {
 | 
						|
			eq = dev->eq_vec[--dev->lif_cfg.eq_count];
 | 
						|
			ionic_destroy_eq(eq);
 | 
						|
		}
 | 
						|
 | 
						|
		kfree(dev->eq_vec);
 | 
						|
	}
 | 
						|
}
 |