Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Xuan Zhuo 1127 100.00% 1 100.00%
Total 1127 1


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Alibaba Elastic Ethernet Adapter.
 *
 * Copyright (C) 2025 Alibaba Inc.
 */

#include "eea_pci.h"
#include "eea_ring.h"

void eea_ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering)
{
	u64 value = 0, rx_idx, tx_idx;

	tx_idx = (u64)tx_ering->cq.hw_idx;
	rx_idx = (u64)ering->cq.hw_idx;

	value |= EEA_IRQ_UNMASK << EEA_DB_FLAGS_OFF;
	value |= tx_idx << EEA_DB_TX_CQ_HEAD_OFF;
	value |= rx_idx << EEA_DB_RX_CQ_HEAD_OFF;

	writeq(value, ering->db);
}

void *eea_ering_cq_get_desc(const struct eea_ring *ering)
{
	u8 phase;
	u8 *desc;

	desc = ering->cq.desc + (ering->cq.head << ering->cq.desc_size_shift);

	phase = READ_ONCE(*(u8 *)(desc + ering->cq.desc_size - 1));

	if ((phase & EEA_RING_DESC_F_CQ_PHASE) == ering->cq.phase) {
		dma_rmb();
		return desc;
	}

	return NULL;
}

/* sq api */
void *eea_ering_sq_alloc_desc(struct eea_ring *ering, u16 id, bool is_last,
			      u16 flags)
{
	struct eea_ring_sq *sq = &ering->sq;
	struct eea_common_desc *desc;

	if (!sq->shadow_num) {
		sq->shadow_idx = sq->head;
		sq->shadow_id = cpu_to_le16(id);
	}

	if (!is_last)
		flags |= EEA_RING_DESC_F_MORE;

	desc = sq->desc + (sq->shadow_idx << sq->desc_size_shift);

	desc->flags = cpu_to_le16(flags);
	desc->id = sq->shadow_id;

	if (unlikely(++sq->shadow_idx >= ering->num))
		sq->shadow_idx = 0;

	++sq->shadow_num;

	return desc;
}

/* This is an allocation API for admin Q. For each call to admin Q, only one
 * desc will be allocated.
 */
void *eea_ering_aq_alloc_desc(struct eea_ring *ering)
{
	struct eea_ring_sq *sq = &ering->sq;
	struct eea_common_desc *desc;

	if (!sq->shadow_num)
		sq->shadow_idx = sq->head;

	desc = sq->desc + (sq->shadow_idx << sq->desc_size_shift);

	if (unlikely(++sq->shadow_idx >= ering->num))
		sq->shadow_idx = 0;

	++sq->shadow_num;

	return desc;
}

void eea_ering_sq_commit_desc(struct eea_ring *ering)
{
	struct eea_ring_sq *sq = &ering->sq;
	int num;

	num = sq->shadow_num;

	ering->num_free -= num;

	sq->head       = sq->shadow_idx;
	sq->hw_idx     += num;
	sq->shadow_num = 0;
}

void eea_ering_sq_cancel(struct eea_ring *ering)
{
	ering->sq.shadow_num = 0;
}

/* cq api */
void eea_ering_cq_ack_desc(struct eea_ring *ering, u32 num)
{
	struct eea_ring_cq *cq = &ering->cq;

	cq->head += num;
	cq->hw_idx += num;

	if (unlikely(cq->head >= ering->num)) {
		cq->head -= ering->num;
		cq->phase ^= EEA_RING_DESC_F_CQ_PHASE;
	}

	ering->num_free += num;
}

/* notify */
void eea_ering_kick(struct eea_ring *ering)
{
	u64 value = 0, idx;

	idx = (u64)ering->sq.hw_idx;

	value |= EEA_IDX_PRESENT << EEA_DB_FLAGS_OFF;
	value |= idx << EEA_DB_IDX_OFF;

	writeq(value, ering->db);
}

/* ering alloc/free */
static void ering_free_queue(struct eea_device *edev, size_t size,
			     void *queue, dma_addr_t dma_handle)
{
	dma_free_coherent(edev->dma_dev, size, queue, dma_handle);
}

static void *ering_alloc_queue(struct eea_device *edev, size_t size,
			       dma_addr_t *dma_handle)
{
	gfp_t flags = GFP_KERNEL | __GFP_NOWARN;

	return dma_alloc_coherent(edev->dma_dev, size, dma_handle, flags);
}

static int ering_alloc_queues(struct eea_ring *ering, struct eea_device *edev,
			      size_t num, u8 sq_desc_size, u8 cq_desc_size)
{
	dma_addr_t addr;
	size_t size;
	void *ring;

	size = num * sq_desc_size;

	ring = ering_alloc_queue(edev, size, &addr);
	if (!ring)
		return -ENOMEM;

	ering->sq.desc     = ring;
	ering->sq.dma_addr = addr;
	ering->sq.dma_size = size;
	ering->sq.desc_size = sq_desc_size;
	ering->sq.desc_size_shift = fls(sq_desc_size) - 1;

	size = num * cq_desc_size;

	ring = ering_alloc_queue(edev, size, &addr);
	if (!ring)
		goto err_free_sq;

	ering->cq.desc     = ring;
	ering->cq.dma_addr = addr;
	ering->cq.dma_size = size;
	ering->cq.desc_size = cq_desc_size;
	ering->cq.desc_size_shift = fls(cq_desc_size) - 1;

	ering->num = num;

	return 0;

err_free_sq:
	ering_free_queue(ering->edev, ering->sq.dma_size,
			 ering->sq.desc, ering->sq.dma_addr);
	return -ENOMEM;
}

static void ering_init(struct eea_ring *ering)
{
	ering->cq.phase = EEA_RING_DESC_F_CQ_PHASE;
	ering->num_free = ering->num;
}

struct eea_ring *eea_ering_alloc(u32 index, u32 num, struct eea_device *edev,
				 u8 sq_desc_size, u8 cq_desc_size,
				 const char *name)
{
	struct eea_ring *ering;

	if (num > EEA_NET_IO_HW_RING_DEPTH_MAX ||
	    num < EEA_NET_IO_RING_DEPTH_MIN)
		return NULL;

	if (!is_power_of_2(num))
		return NULL;

	if (!sq_desc_size || !is_power_of_2(sq_desc_size))
		return NULL;

	if (!cq_desc_size || !is_power_of_2(cq_desc_size))
		return NULL;

	ering = kzalloc(sizeof(*ering), GFP_KERNEL);
	if (!ering)
		return NULL;

	ering->edev = edev;
	ering->name = name;
	ering->index = index;

	if (ering_alloc_queues(ering, edev, num, sq_desc_size, cq_desc_size))
		goto err_free;

	ering_init(ering);

	return ering;

err_free:
	kfree(ering);
	return NULL;
}

void eea_ering_free(struct eea_ring *ering)
{
	ering_free_queue(ering->edev, ering->cq.dma_size,
			 ering->cq.desc, ering->cq.dma_addr);

	ering_free_queue(ering->edev, ering->sq.dma_size,
			 ering->sq.desc, ering->sq.dma_addr);

	kfree(ering);
}