linux/drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c
Alexander Lobakin 291515c764 net: gro: decouple GRO from the NAPI layer
In fact, these two are not tied closely to each other. The only
requirements to GRO are to use it in the BH context and have some
sane limits on the packet batches, e.g. NAPI has a limit of its
budget (64/8/etc.).
Move purely GRO fields into a new structure, &gro_node. Embed it
into &napi_struct and adjust all the references.
gro_node::cached_napi_id is effectively the same as
napi_struct::napi_id, but to be used on GRO hotpath to mark skbs.
napi_struct::napi_id is now a fully control path field.

Three Ethernet drivers use napi_gro_flush() not really meant to be
exported, so move it to <net/gro.h> and add that include there.
napi_gro_receive() is used in more than 100 drivers, keep it
in <linux/netdevice.h>.
This does not make GRO ready to use outside of the NAPI context
yet.

Tested-by: Daniel Xu <dxu@dxuuu.xyz>
Acked-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Signed-off-by: Alexander Lobakin <aleksander.lobakin@intel.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
2025-02-27 14:03:14 +01:00

1170 lines
32 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, MediaTek Inc.
* Copyright (c) 2021-2022, Intel Corporation.
*
* Authors:
* Amir Hanania <amir.hanania@intel.com>
* Haijun Liu <haijun.liu@mediatek.com>
* Eliot Lee <eliot.lee@intel.com>
* Moises Veleta <moises.veleta@intel.com>
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
*
* Contributors:
* Andy Shevchenko <andriy.shevchenko@linux.intel.com>
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
* Sreehari Kancharla <sreehari.kancharla@intel.com>
*/
#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
#include <linux/gfp.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/netdevice.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <net/gro.h>
#include "t7xx_dpmaif.h"
#include "t7xx_hif_dpmaif.h"
#include "t7xx_hif_dpmaif_rx.h"
#include "t7xx_netdev.h"
#include "t7xx_pci.h"
#define DPMAIF_BAT_COUNT 8192
#define DPMAIF_FRG_COUNT 4814
#define DPMAIF_PIT_COUNT (DPMAIF_BAT_COUNT * 2)
#define DPMAIF_BAT_CNT_THRESHOLD 30
#define DPMAIF_PIT_CNT_THRESHOLD 60
#define DPMAIF_RX_PUSH_THRESHOLD_MASK GENMASK(2, 0)
#define DPMAIF_NOTIFY_RELEASE_COUNT 128
#define DPMAIF_POLL_PIT_TIME_US 20
#define DPMAIF_POLL_PIT_MAX_TIME_US 2000
#define DPMAIF_WQ_TIME_LIMIT_MS 2
#define DPMAIF_CS_RESULT_PASS 0
/* Packet type */
#define DES_PT_PD 0
#define DES_PT_MSG 1
/* Buffer type */
#define PKT_BUF_FRAG 1
static unsigned int t7xx_normal_pit_bid(const struct dpmaif_pit *pit_info)
{
u32 value;
value = FIELD_GET(PD_PIT_H_BID, le32_to_cpu(pit_info->pd.footer));
value <<= 13;
value += FIELD_GET(PD_PIT_BUFFER_ID, le32_to_cpu(pit_info->header));
return value;
}
static int t7xx_dpmaif_update_bat_wr_idx(struct dpmaif_ctrl *dpmaif_ctrl,
const unsigned int q_num, const unsigned int bat_cnt)
{
struct dpmaif_rx_queue *rxq = &dpmaif_ctrl->rxq[q_num];
struct dpmaif_bat_request *bat_req = rxq->bat_req;
unsigned int old_rl_idx, new_wr_idx, old_wr_idx;
if (!rxq->que_started) {
dev_err(dpmaif_ctrl->dev, "RX queue %d has not been started\n", rxq->index);
return -EINVAL;
}
old_rl_idx = bat_req->bat_release_rd_idx;
old_wr_idx = bat_req->bat_wr_idx;
new_wr_idx = old_wr_idx + bat_cnt;
if (old_rl_idx > old_wr_idx && new_wr_idx >= old_rl_idx)
goto err_flow;
if (new_wr_idx >= bat_req->bat_size_cnt) {
new_wr_idx -= bat_req->bat_size_cnt;
if (new_wr_idx >= old_rl_idx)
goto err_flow;
}
bat_req->bat_wr_idx = new_wr_idx;
return 0;
err_flow:
dev_err(dpmaif_ctrl->dev, "RX BAT flow check fail\n");
return -EINVAL;
}
static bool t7xx_alloc_and_map_skb_info(const struct dpmaif_ctrl *dpmaif_ctrl,
const unsigned int size, struct dpmaif_bat_skb *cur_skb)
{
dma_addr_t data_bus_addr;
struct sk_buff *skb;
skb = __dev_alloc_skb(size, GFP_KERNEL);
if (!skb)
return false;
data_bus_addr = dma_map_single(dpmaif_ctrl->dev, skb->data, size, DMA_FROM_DEVICE);
if (dma_mapping_error(dpmaif_ctrl->dev, data_bus_addr)) {
dev_err_ratelimited(dpmaif_ctrl->dev, "DMA mapping error\n");
dev_kfree_skb_any(skb);
return false;
}
cur_skb->skb = skb;
cur_skb->data_bus_addr = data_bus_addr;
cur_skb->data_len = size;
return true;
}
static void t7xx_unmap_bat_skb(struct device *dev, struct dpmaif_bat_skb *bat_skb_base,
unsigned int index)
{
struct dpmaif_bat_skb *bat_skb = bat_skb_base + index;
if (bat_skb->skb) {
dma_unmap_single(dev, bat_skb->data_bus_addr, bat_skb->data_len, DMA_FROM_DEVICE);
dev_kfree_skb(bat_skb->skb);
bat_skb->skb = NULL;
}
}
/**
* t7xx_dpmaif_rx_buf_alloc() - Allocate buffers for the BAT ring.
* @dpmaif_ctrl: Pointer to DPMAIF context structure.
* @bat_req: Pointer to BAT request structure.
* @q_num: Queue number.
* @buf_cnt: Number of buffers to allocate.
* @initial: Indicates if the ring is being populated for the first time.
*
* Allocate skb and store the start address of the data buffer into the BAT ring.
* If this is not the initial call, notify the HW about the new entries.
*
* Return:
* * 0 - Success.
* * -ERROR - Error code.
*/
int t7xx_dpmaif_rx_buf_alloc(struct dpmaif_ctrl *dpmaif_ctrl,
const struct dpmaif_bat_request *bat_req,
const unsigned int q_num, const unsigned int buf_cnt,
const bool initial)
{
unsigned int i, bat_cnt, bat_max_cnt, bat_start_idx;
int ret;
if (!buf_cnt || buf_cnt > bat_req->bat_size_cnt)
return -EINVAL;
/* Check BAT buffer space */
bat_max_cnt = bat_req->bat_size_cnt;
bat_cnt = t7xx_ring_buf_rd_wr_count(bat_max_cnt, bat_req->bat_release_rd_idx,
bat_req->bat_wr_idx, DPMAIF_WRITE);
if (buf_cnt > bat_cnt)
return -ENOMEM;
bat_start_idx = bat_req->bat_wr_idx;
for (i = 0; i < buf_cnt; i++) {
unsigned int cur_bat_idx = bat_start_idx + i;
struct dpmaif_bat_skb *cur_skb;
struct dpmaif_bat *cur_bat;
if (cur_bat_idx >= bat_max_cnt)
cur_bat_idx -= bat_max_cnt;
cur_skb = (struct dpmaif_bat_skb *)bat_req->bat_skb + cur_bat_idx;
if (!cur_skb->skb &&
!t7xx_alloc_and_map_skb_info(dpmaif_ctrl, bat_req->pkt_buf_sz, cur_skb))
break;
cur_bat = (struct dpmaif_bat *)bat_req->bat_base + cur_bat_idx;
cur_bat->buffer_addr_ext = upper_32_bits(cur_skb->data_bus_addr);
cur_bat->p_buffer_addr = lower_32_bits(cur_skb->data_bus_addr);
}
if (!i)
return -ENOMEM;
ret = t7xx_dpmaif_update_bat_wr_idx(dpmaif_ctrl, q_num, i);
if (ret)
goto err_unmap_skbs;
if (!initial) {
unsigned int hw_wr_idx;
ret = t7xx_dpmaif_dl_snd_hw_bat_cnt(&dpmaif_ctrl->hw_info, i);
if (ret)
goto err_unmap_skbs;
hw_wr_idx = t7xx_dpmaif_dl_get_bat_wr_idx(&dpmaif_ctrl->hw_info,
DPF_RX_QNO_DFT);
if (hw_wr_idx != bat_req->bat_wr_idx) {
ret = -EFAULT;
dev_err(dpmaif_ctrl->dev, "Write index mismatch in RX ring\n");
goto err_unmap_skbs;
}
}
return 0;
err_unmap_skbs:
while (i--)
t7xx_unmap_bat_skb(dpmaif_ctrl->dev, bat_req->bat_skb, i);
return ret;
}
static int t7xx_dpmaifq_release_pit_entry(struct dpmaif_rx_queue *rxq,
const unsigned int rel_entry_num)
{
struct dpmaif_hw_info *hw_info = &rxq->dpmaif_ctrl->hw_info;
unsigned int old_rel_idx, new_rel_idx, hw_wr_idx;
int ret;
if (!rxq->que_started)
return 0;
if (rel_entry_num >= rxq->pit_size_cnt) {
dev_err(rxq->dpmaif_ctrl->dev, "Invalid PIT release index\n");
return -EINVAL;
}
old_rel_idx = rxq->pit_release_rd_idx;
new_rel_idx = old_rel_idx + rel_entry_num;
hw_wr_idx = rxq->pit_wr_idx;
if (hw_wr_idx < old_rel_idx && new_rel_idx >= rxq->pit_size_cnt)
new_rel_idx -= rxq->pit_size_cnt;
ret = t7xx_dpmaif_dlq_add_pit_remain_cnt(hw_info, rxq->index, rel_entry_num);
if (ret) {
dev_err(rxq->dpmaif_ctrl->dev, "PIT release failure: %d\n", ret);
return ret;
}
rxq->pit_release_rd_idx = new_rel_idx;
return 0;
}
static void t7xx_dpmaif_set_bat_mask(struct dpmaif_bat_request *bat_req, unsigned int idx)
{
unsigned long flags;
spin_lock_irqsave(&bat_req->mask_lock, flags);
set_bit(idx, bat_req->bat_bitmap);
spin_unlock_irqrestore(&bat_req->mask_lock, flags);
}
static int t7xx_frag_bat_cur_bid_check(struct dpmaif_rx_queue *rxq,
const unsigned int cur_bid)
{
struct dpmaif_bat_request *bat_frag = rxq->bat_frag;
struct dpmaif_bat_page *bat_page;
if (cur_bid >= DPMAIF_FRG_COUNT)
return -EINVAL;
bat_page = bat_frag->bat_skb + cur_bid;
if (!bat_page->page)
return -EINVAL;
return 0;
}
static void t7xx_unmap_bat_page(struct device *dev, struct dpmaif_bat_page *bat_page_base,
unsigned int index)
{
struct dpmaif_bat_page *bat_page = bat_page_base + index;
if (bat_page->page) {
dma_unmap_page(dev, bat_page->data_bus_addr, bat_page->data_len, DMA_FROM_DEVICE);
put_page(bat_page->page);
bat_page->page = NULL;
}
}
/**
* t7xx_dpmaif_rx_frag_alloc() - Allocates buffers for the Fragment BAT ring.
* @dpmaif_ctrl: Pointer to DPMAIF context structure.
* @bat_req: Pointer to BAT request structure.
* @buf_cnt: Number of buffers to allocate.
* @initial: Indicates if the ring is being populated for the first time.
*
* Fragment BAT is used when the received packet does not fit in a normal BAT entry.
* This function allocates a page fragment and stores the start address of the page
* into the Fragment BAT ring.
* If this is not the initial call, notify the HW about the new entries.
*
* Return:
* * 0 - Success.
* * -ERROR - Error code.
*/
int t7xx_dpmaif_rx_frag_alloc(struct dpmaif_ctrl *dpmaif_ctrl, struct dpmaif_bat_request *bat_req,
const unsigned int buf_cnt, const bool initial)
{
unsigned int buf_space, cur_bat_idx = bat_req->bat_wr_idx;
struct dpmaif_bat_page *bat_skb = bat_req->bat_skb;
int ret = 0, i;
if (!buf_cnt || buf_cnt > bat_req->bat_size_cnt)
return -EINVAL;
buf_space = t7xx_ring_buf_rd_wr_count(bat_req->bat_size_cnt,
bat_req->bat_release_rd_idx, bat_req->bat_wr_idx,
DPMAIF_WRITE);
if (buf_cnt > buf_space) {
dev_err(dpmaif_ctrl->dev,
"Requested more buffers than the space available in RX frag ring\n");
return -EINVAL;
}
for (i = 0; i < buf_cnt; i++) {
struct dpmaif_bat_page *cur_page = bat_skb + cur_bat_idx;
struct dpmaif_bat *cur_bat;
dma_addr_t data_base_addr;
if (!cur_page->page) {
unsigned long offset;
struct page *page;
void *data;
data = netdev_alloc_frag(bat_req->pkt_buf_sz);
if (!data)
break;
page = virt_to_head_page(data);
offset = data - page_address(page);
data_base_addr = dma_map_page(dpmaif_ctrl->dev, page, offset,
bat_req->pkt_buf_sz, DMA_FROM_DEVICE);
if (dma_mapping_error(dpmaif_ctrl->dev, data_base_addr)) {
put_page(virt_to_head_page(data));
dev_err(dpmaif_ctrl->dev, "DMA mapping fail\n");
break;
}
cur_page->page = page;
cur_page->data_bus_addr = data_base_addr;
cur_page->offset = offset;
cur_page->data_len = bat_req->pkt_buf_sz;
}
data_base_addr = cur_page->data_bus_addr;
cur_bat = (struct dpmaif_bat *)bat_req->bat_base + cur_bat_idx;
cur_bat->buffer_addr_ext = upper_32_bits(data_base_addr);
cur_bat->p_buffer_addr = lower_32_bits(data_base_addr);
cur_bat_idx = t7xx_ring_buf_get_next_wr_idx(bat_req->bat_size_cnt, cur_bat_idx);
}
bat_req->bat_wr_idx = cur_bat_idx;
if (!initial)
t7xx_dpmaif_dl_snd_hw_frg_cnt(&dpmaif_ctrl->hw_info, i);
if (i < buf_cnt) {
ret = -ENOMEM;
if (initial) {
while (--i > 0)
t7xx_unmap_bat_page(dpmaif_ctrl->dev, bat_req->bat_skb, i);
}
}
return ret;
}
static int t7xx_dpmaif_set_frag_to_skb(const struct dpmaif_rx_queue *rxq,
const struct dpmaif_pit *pkt_info,
struct sk_buff *skb)
{
unsigned long long data_bus_addr, data_base_addr;
struct device *dev = rxq->dpmaif_ctrl->dev;
struct dpmaif_bat_page *page_info;
unsigned int data_len;
int data_offset;
page_info = rxq->bat_frag->bat_skb;
page_info += t7xx_normal_pit_bid(pkt_info);
dma_unmap_page(dev, page_info->data_bus_addr, page_info->data_len, DMA_FROM_DEVICE);
if (!page_info->page)
return -EINVAL;
data_bus_addr = le32_to_cpu(pkt_info->pd.data_addr_h);
data_bus_addr = (data_bus_addr << 32) + le32_to_cpu(pkt_info->pd.data_addr_l);
data_base_addr = page_info->data_bus_addr;
data_offset = data_bus_addr - data_base_addr;
data_offset += page_info->offset;
data_len = FIELD_GET(PD_PIT_DATA_LEN, le32_to_cpu(pkt_info->header));
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page_info->page,
data_offset, data_len, page_info->data_len);
page_info->page = NULL;
page_info->offset = 0;
page_info->data_len = 0;
return 0;
}
static int t7xx_dpmaif_get_frag(struct dpmaif_rx_queue *rxq,
const struct dpmaif_pit *pkt_info,
const struct dpmaif_cur_rx_skb_info *skb_info)
{
unsigned int cur_bid = t7xx_normal_pit_bid(pkt_info);
int ret;
ret = t7xx_frag_bat_cur_bid_check(rxq, cur_bid);
if (ret < 0)
return ret;
ret = t7xx_dpmaif_set_frag_to_skb(rxq, pkt_info, skb_info->cur_skb);
if (ret < 0) {
dev_err(rxq->dpmaif_ctrl->dev, "Failed to set frag data to skb: %d\n", ret);
return ret;
}
t7xx_dpmaif_set_bat_mask(rxq->bat_frag, cur_bid);
return 0;
}
static int t7xx_bat_cur_bid_check(struct dpmaif_rx_queue *rxq, const unsigned int cur_bid)
{
struct dpmaif_bat_skb *bat_skb = rxq->bat_req->bat_skb;
bat_skb += cur_bid;
if (cur_bid >= DPMAIF_BAT_COUNT || !bat_skb->skb)
return -EINVAL;
return 0;
}
static int t7xx_dpmaif_read_pit_seq(const struct dpmaif_pit *pit)
{
return FIELD_GET(PD_PIT_PIT_SEQ, le32_to_cpu(pit->pd.footer));
}
static int t7xx_dpmaif_check_pit_seq(struct dpmaif_rx_queue *rxq,
const struct dpmaif_pit *pit)
{
unsigned int cur_pit_seq, expect_pit_seq = rxq->expect_pit_seq;
if (read_poll_timeout_atomic(t7xx_dpmaif_read_pit_seq, cur_pit_seq,
cur_pit_seq == expect_pit_seq, DPMAIF_POLL_PIT_TIME_US,
DPMAIF_POLL_PIT_MAX_TIME_US, false, pit))
return -EFAULT;
rxq->expect_pit_seq++;
if (rxq->expect_pit_seq >= DPMAIF_DL_PIT_SEQ_VALUE)
rxq->expect_pit_seq = 0;
return 0;
}
static unsigned int t7xx_dpmaif_avail_pkt_bat_cnt(struct dpmaif_bat_request *bat_req)
{
unsigned int zero_index;
unsigned long flags;
spin_lock_irqsave(&bat_req->mask_lock, flags);
zero_index = find_next_zero_bit(bat_req->bat_bitmap, bat_req->bat_size_cnt,
bat_req->bat_release_rd_idx);
if (zero_index < bat_req->bat_size_cnt) {
spin_unlock_irqrestore(&bat_req->mask_lock, flags);
return zero_index - bat_req->bat_release_rd_idx;
}
/* limiting the search till bat_release_rd_idx */
zero_index = find_first_zero_bit(bat_req->bat_bitmap, bat_req->bat_release_rd_idx);
spin_unlock_irqrestore(&bat_req->mask_lock, flags);
return bat_req->bat_size_cnt - bat_req->bat_release_rd_idx + zero_index;
}
static int t7xx_dpmaif_release_bat_entry(const struct dpmaif_rx_queue *rxq,
const unsigned int rel_entry_num,
const enum bat_type buf_type)
{
struct dpmaif_hw_info *hw_info = &rxq->dpmaif_ctrl->hw_info;
unsigned int old_rel_idx, new_rel_idx, hw_rd_idx, i;
struct dpmaif_bat_request *bat;
unsigned long flags;
if (!rxq->que_started || !rel_entry_num)
return -EINVAL;
if (buf_type == BAT_TYPE_FRAG) {
bat = rxq->bat_frag;
hw_rd_idx = t7xx_dpmaif_dl_get_frg_rd_idx(hw_info, rxq->index);
} else {
bat = rxq->bat_req;
hw_rd_idx = t7xx_dpmaif_dl_get_bat_rd_idx(hw_info, rxq->index);
}
if (rel_entry_num >= bat->bat_size_cnt)
return -EINVAL;
old_rel_idx = bat->bat_release_rd_idx;
new_rel_idx = old_rel_idx + rel_entry_num;
/* Do not need to release if the queue is empty */
if (bat->bat_wr_idx == old_rel_idx)
return 0;
if (hw_rd_idx >= old_rel_idx) {
if (new_rel_idx > hw_rd_idx)
return -EINVAL;
}
if (new_rel_idx >= bat->bat_size_cnt) {
new_rel_idx -= bat->bat_size_cnt;
if (new_rel_idx > hw_rd_idx)
return -EINVAL;
}
spin_lock_irqsave(&bat->mask_lock, flags);
for (i = 0; i < rel_entry_num; i++) {
unsigned int index = bat->bat_release_rd_idx + i;
if (index >= bat->bat_size_cnt)
index -= bat->bat_size_cnt;
clear_bit(index, bat->bat_bitmap);
}
spin_unlock_irqrestore(&bat->mask_lock, flags);
bat->bat_release_rd_idx = new_rel_idx;
return rel_entry_num;
}
static int t7xx_dpmaif_pit_release_and_add(struct dpmaif_rx_queue *rxq)
{
int ret;
if (rxq->pit_remain_release_cnt < DPMAIF_PIT_CNT_THRESHOLD)
return 0;
ret = t7xx_dpmaifq_release_pit_entry(rxq, rxq->pit_remain_release_cnt);
if (ret)
return ret;
rxq->pit_remain_release_cnt = 0;
return 0;
}
static int t7xx_dpmaif_bat_release_and_add(const struct dpmaif_rx_queue *rxq)
{
unsigned int bid_cnt;
int ret;
bid_cnt = t7xx_dpmaif_avail_pkt_bat_cnt(rxq->bat_req);
if (bid_cnt < DPMAIF_BAT_CNT_THRESHOLD)
return 0;
ret = t7xx_dpmaif_release_bat_entry(rxq, bid_cnt, BAT_TYPE_NORMAL);
if (ret <= 0) {
dev_err(rxq->dpmaif_ctrl->dev, "Release PKT BAT failed: %d\n", ret);
return ret;
}
ret = t7xx_dpmaif_rx_buf_alloc(rxq->dpmaif_ctrl, rxq->bat_req, rxq->index, bid_cnt, false);
if (ret < 0)
dev_err(rxq->dpmaif_ctrl->dev, "Allocate new RX buffer failed: %d\n", ret);
return ret;
}
static int t7xx_dpmaif_frag_bat_release_and_add(const struct dpmaif_rx_queue *rxq)
{
unsigned int bid_cnt;
int ret;
bid_cnt = t7xx_dpmaif_avail_pkt_bat_cnt(rxq->bat_frag);
if (bid_cnt < DPMAIF_BAT_CNT_THRESHOLD)
return 0;
ret = t7xx_dpmaif_release_bat_entry(rxq, bid_cnt, BAT_TYPE_FRAG);
if (ret <= 0) {
dev_err(rxq->dpmaif_ctrl->dev, "Release BAT entry failed: %d\n", ret);
return ret;
}
return t7xx_dpmaif_rx_frag_alloc(rxq->dpmaif_ctrl, rxq->bat_frag, bid_cnt, false);
}
static void t7xx_dpmaif_parse_msg_pit(const struct dpmaif_rx_queue *rxq,
const struct dpmaif_pit *msg_pit,
struct dpmaif_cur_rx_skb_info *skb_info)
{
int header = le32_to_cpu(msg_pit->header);
skb_info->cur_chn_idx = FIELD_GET(MSG_PIT_CHANNEL_ID, header);
skb_info->check_sum = FIELD_GET(MSG_PIT_CHECKSUM, header);
skb_info->pit_dp = FIELD_GET(MSG_PIT_DP, header);
skb_info->pkt_type = FIELD_GET(MSG_PIT_IP, le32_to_cpu(msg_pit->msg.params_3));
}
static int t7xx_dpmaif_set_data_to_skb(const struct dpmaif_rx_queue *rxq,
const struct dpmaif_pit *pkt_info,
struct dpmaif_cur_rx_skb_info *skb_info)
{
unsigned long long data_bus_addr, data_base_addr;
struct device *dev = rxq->dpmaif_ctrl->dev;
struct dpmaif_bat_skb *bat_skb;
unsigned int data_len;
struct sk_buff *skb;
int data_offset;
bat_skb = rxq->bat_req->bat_skb;
bat_skb += t7xx_normal_pit_bid(pkt_info);
dma_unmap_single(dev, bat_skb->data_bus_addr, bat_skb->data_len, DMA_FROM_DEVICE);
data_bus_addr = le32_to_cpu(pkt_info->pd.data_addr_h);
data_bus_addr = (data_bus_addr << 32) + le32_to_cpu(pkt_info->pd.data_addr_l);
data_base_addr = bat_skb->data_bus_addr;
data_offset = data_bus_addr - data_base_addr;
data_len = FIELD_GET(PD_PIT_DATA_LEN, le32_to_cpu(pkt_info->header));
skb = bat_skb->skb;
skb->len = 0;
skb_reset_tail_pointer(skb);
skb_reserve(skb, data_offset);
if (skb->tail + data_len > skb->end) {
dev_err(dev, "No buffer space available\n");
return -ENOBUFS;
}
skb_put(skb, data_len);
skb_info->cur_skb = skb;
bat_skb->skb = NULL;
return 0;
}
static int t7xx_dpmaif_get_rx_pkt(struct dpmaif_rx_queue *rxq,
const struct dpmaif_pit *pkt_info,
struct dpmaif_cur_rx_skb_info *skb_info)
{
unsigned int cur_bid = t7xx_normal_pit_bid(pkt_info);
int ret;
ret = t7xx_bat_cur_bid_check(rxq, cur_bid);
if (ret < 0)
return ret;
ret = t7xx_dpmaif_set_data_to_skb(rxq, pkt_info, skb_info);
if (ret < 0) {
dev_err(rxq->dpmaif_ctrl->dev, "RX set data to skb failed: %d\n", ret);
return ret;
}
t7xx_dpmaif_set_bat_mask(rxq->bat_req, cur_bid);
return 0;
}
static int t7xx_dpmaifq_rx_notify_hw(struct dpmaif_rx_queue *rxq)
{
struct dpmaif_ctrl *dpmaif_ctrl = rxq->dpmaif_ctrl;
int ret;
queue_work(dpmaif_ctrl->bat_release_wq, &dpmaif_ctrl->bat_release_work);
ret = t7xx_dpmaif_pit_release_and_add(rxq);
if (ret < 0)
dev_err(dpmaif_ctrl->dev, "RXQ%u update PIT failed: %d\n", rxq->index, ret);
return ret;
}
static void t7xx_dpmaif_rx_skb(struct dpmaif_rx_queue *rxq,
struct dpmaif_cur_rx_skb_info *skb_info)
{
struct dpmaif_ctrl *dpmaif_ctrl = rxq->dpmaif_ctrl;
struct sk_buff *skb = skb_info->cur_skb;
struct t7xx_skb_cb *skb_cb;
u8 netif_id;
skb_info->cur_skb = NULL;
if (skb_info->pit_dp) {
dev_kfree_skb_any(skb);
return;
}
skb->ip_summed = skb_info->check_sum == DPMAIF_CS_RESULT_PASS ? CHECKSUM_UNNECESSARY :
CHECKSUM_NONE;
netif_id = FIELD_GET(NETIF_MASK, skb_info->cur_chn_idx);
skb_cb = T7XX_SKB_CB(skb);
skb_cb->netif_idx = netif_id;
skb_cb->rx_pkt_type = skb_info->pkt_type;
dpmaif_ctrl->callbacks->recv_skb(dpmaif_ctrl->t7xx_dev->ccmni_ctlb, skb, &rxq->napi);
}
static int t7xx_dpmaif_rx_start(struct dpmaif_rx_queue *rxq, const unsigned int pit_cnt,
const unsigned int budget, int *once_more)
{
unsigned int cur_pit, pit_len, rx_cnt, recv_skb_cnt = 0;
struct device *dev = rxq->dpmaif_ctrl->dev;
struct dpmaif_cur_rx_skb_info *skb_info;
int ret = 0;
pit_len = rxq->pit_size_cnt;
skb_info = &rxq->rx_data_info;
cur_pit = rxq->pit_rd_idx;
for (rx_cnt = 0; rx_cnt < pit_cnt; rx_cnt++) {
struct dpmaif_pit *pkt_info;
u32 val;
if (!skb_info->msg_pit_received && recv_skb_cnt >= budget)
break;
pkt_info = (struct dpmaif_pit *)rxq->pit_base + cur_pit;
if (t7xx_dpmaif_check_pit_seq(rxq, pkt_info)) {
dev_err_ratelimited(dev, "RXQ%u checks PIT SEQ fail\n", rxq->index);
*once_more = 1;
return recv_skb_cnt;
}
val = FIELD_GET(PD_PIT_PACKET_TYPE, le32_to_cpu(pkt_info->header));
if (val == DES_PT_MSG) {
if (skb_info->msg_pit_received)
dev_err(dev, "RXQ%u received repeated PIT\n", rxq->index);
skb_info->msg_pit_received = true;
t7xx_dpmaif_parse_msg_pit(rxq, pkt_info, skb_info);
} else { /* DES_PT_PD */
val = FIELD_GET(PD_PIT_BUFFER_TYPE, le32_to_cpu(pkt_info->header));
if (val != PKT_BUF_FRAG)
ret = t7xx_dpmaif_get_rx_pkt(rxq, pkt_info, skb_info);
else if (!skb_info->cur_skb)
ret = -EINVAL;
else
ret = t7xx_dpmaif_get_frag(rxq, pkt_info, skb_info);
if (ret < 0) {
skb_info->err_payload = 1;
dev_err_ratelimited(dev, "RXQ%u error payload\n", rxq->index);
}
val = FIELD_GET(PD_PIT_CONT, le32_to_cpu(pkt_info->header));
if (!val) {
if (!skb_info->err_payload) {
t7xx_dpmaif_rx_skb(rxq, skb_info);
} else if (skb_info->cur_skb) {
dev_kfree_skb_any(skb_info->cur_skb);
skb_info->cur_skb = NULL;
}
memset(skb_info, 0, sizeof(*skb_info));
recv_skb_cnt++;
}
}
cur_pit = t7xx_ring_buf_get_next_wr_idx(pit_len, cur_pit);
rxq->pit_rd_idx = cur_pit;
rxq->pit_remain_release_cnt++;
if (rx_cnt > 0 && !(rx_cnt % DPMAIF_NOTIFY_RELEASE_COUNT)) {
ret = t7xx_dpmaifq_rx_notify_hw(rxq);
if (ret < 0)
break;
}
}
if (!ret)
ret = t7xx_dpmaifq_rx_notify_hw(rxq);
if (ret)
return ret;
return recv_skb_cnt;
}
static unsigned int t7xx_dpmaifq_poll_pit(struct dpmaif_rx_queue *rxq)
{
unsigned int hw_wr_idx, pit_cnt;
if (!rxq->que_started)
return 0;
hw_wr_idx = t7xx_dpmaif_dl_dlq_pit_get_wr_idx(&rxq->dpmaif_ctrl->hw_info, rxq->index);
pit_cnt = t7xx_ring_buf_rd_wr_count(rxq->pit_size_cnt, rxq->pit_rd_idx, hw_wr_idx,
DPMAIF_READ);
rxq->pit_wr_idx = hw_wr_idx;
return pit_cnt;
}
static int t7xx_dpmaif_napi_rx_data_collect(struct dpmaif_ctrl *dpmaif_ctrl,
const unsigned int q_num,
const unsigned int budget, int *once_more)
{
struct dpmaif_rx_queue *rxq = &dpmaif_ctrl->rxq[q_num];
unsigned int cnt;
int ret = 0;
cnt = t7xx_dpmaifq_poll_pit(rxq);
if (!cnt)
return ret;
ret = t7xx_dpmaif_rx_start(rxq, cnt, budget, once_more);
if (ret < 0)
dev_err(dpmaif_ctrl->dev, "dlq%u rx ERR:%d\n", rxq->index, ret);
return ret;
}
int t7xx_dpmaif_napi_rx_poll(struct napi_struct *napi, const int budget)
{
struct dpmaif_rx_queue *rxq = container_of(napi, struct dpmaif_rx_queue, napi);
struct t7xx_pci_dev *t7xx_dev = rxq->dpmaif_ctrl->t7xx_dev;
int ret, once_more = 0, work_done = 0;
atomic_set(&rxq->rx_processing, 1);
/* Ensure rx_processing is changed to 1 before actually begin RX flow */
smp_mb();
if (!rxq->que_started) {
atomic_set(&rxq->rx_processing, 0);
pm_runtime_put_autosuspend(rxq->dpmaif_ctrl->dev);
dev_err(rxq->dpmaif_ctrl->dev, "Work RXQ: %d has not been started\n", rxq->index);
return work_done;
}
if (!rxq->sleep_lock_pending)
t7xx_pci_disable_sleep(t7xx_dev);
ret = try_wait_for_completion(&t7xx_dev->sleep_lock_acquire);
if (!ret) {
napi_complete_done(napi, work_done);
rxq->sleep_lock_pending = true;
napi_schedule(napi);
return work_done;
}
rxq->sleep_lock_pending = false;
while (work_done < budget) {
int each_budget = budget - work_done;
int rx_cnt = t7xx_dpmaif_napi_rx_data_collect(rxq->dpmaif_ctrl, rxq->index,
each_budget, &once_more);
if (rx_cnt > 0)
work_done += rx_cnt;
else
break;
}
if (once_more) {
napi_gro_flush(napi, false);
work_done = budget;
t7xx_dpmaif_clr_ip_busy_sts(&rxq->dpmaif_ctrl->hw_info);
} else if (work_done < budget) {
napi_complete_done(napi, work_done);
t7xx_dpmaif_clr_ip_busy_sts(&rxq->dpmaif_ctrl->hw_info);
t7xx_dpmaif_dlq_unmask_rx_done(&rxq->dpmaif_ctrl->hw_info, rxq->index);
t7xx_pci_enable_sleep(rxq->dpmaif_ctrl->t7xx_dev);
pm_runtime_mark_last_busy(rxq->dpmaif_ctrl->dev);
pm_runtime_put_autosuspend(rxq->dpmaif_ctrl->dev);
atomic_set(&rxq->rx_processing, 0);
} else {
t7xx_dpmaif_clr_ip_busy_sts(&rxq->dpmaif_ctrl->hw_info);
}
return work_done;
}
void t7xx_dpmaif_irq_rx_done(struct dpmaif_ctrl *dpmaif_ctrl, const unsigned int que_mask)
{
struct dpmaif_rx_queue *rxq;
struct dpmaif_ctrl *ctrl;
int qno, ret;
qno = ffs(que_mask) - 1;
if (qno < 0 || qno > DPMAIF_RXQ_NUM - 1) {
dev_err(dpmaif_ctrl->dev, "Invalid RXQ number: %u\n", qno);
return;
}
rxq = &dpmaif_ctrl->rxq[qno];
ctrl = rxq->dpmaif_ctrl;
/* We need to make sure that the modem has been resumed before
* calling napi. This can't be done inside the polling function
* as we could be blocked waiting for device to be resumed,
* which can't be done from softirq context the poll function
* is running in.
*/
ret = pm_runtime_resume_and_get(ctrl->dev);
if (ret < 0 && ret != -EACCES) {
dev_err(ctrl->dev, "Failed to resume device: %d\n", ret);
return;
}
napi_schedule(&rxq->napi);
}
static void t7xx_dpmaif_base_free(const struct dpmaif_ctrl *dpmaif_ctrl,
const struct dpmaif_bat_request *bat_req)
{
if (bat_req->bat_base)
dma_free_coherent(dpmaif_ctrl->dev,
bat_req->bat_size_cnt * sizeof(struct dpmaif_bat),
bat_req->bat_base, bat_req->bat_bus_addr);
}
/**
* t7xx_dpmaif_bat_alloc() - Allocate the BAT ring buffer.
* @dpmaif_ctrl: Pointer to DPMAIF context structure.
* @bat_req: Pointer to BAT request structure.
* @buf_type: BAT ring type.
*
* This function allocates the BAT ring buffer shared with the HW device, also allocates
* a buffer used to store information about the BAT skbs for further release.
*
* Return:
* * 0 - Success.
* * -ERROR - Error code.
*/
int t7xx_dpmaif_bat_alloc(const struct dpmaif_ctrl *dpmaif_ctrl, struct dpmaif_bat_request *bat_req,
const enum bat_type buf_type)
{
int sw_buf_size;
if (buf_type == BAT_TYPE_FRAG) {
sw_buf_size = sizeof(struct dpmaif_bat_page);
bat_req->bat_size_cnt = DPMAIF_FRG_COUNT;
bat_req->pkt_buf_sz = DPMAIF_HW_FRG_PKTBUF;
} else {
sw_buf_size = sizeof(struct dpmaif_bat_skb);
bat_req->bat_size_cnt = DPMAIF_BAT_COUNT;
bat_req->pkt_buf_sz = DPMAIF_HW_BAT_PKTBUF;
}
bat_req->type = buf_type;
bat_req->bat_wr_idx = 0;
bat_req->bat_release_rd_idx = 0;
bat_req->bat_base = dma_alloc_coherent(dpmaif_ctrl->dev,
bat_req->bat_size_cnt * sizeof(struct dpmaif_bat),
&bat_req->bat_bus_addr, GFP_KERNEL | __GFP_ZERO);
if (!bat_req->bat_base)
return -ENOMEM;
/* For AP SW to record skb information */
bat_req->bat_skb = devm_kzalloc(dpmaif_ctrl->dev, bat_req->bat_size_cnt * sw_buf_size,
GFP_KERNEL);
if (!bat_req->bat_skb)
goto err_free_dma_mem;
bat_req->bat_bitmap = bitmap_zalloc(bat_req->bat_size_cnt, GFP_KERNEL);
if (!bat_req->bat_bitmap)
goto err_free_dma_mem;
spin_lock_init(&bat_req->mask_lock);
atomic_set(&bat_req->refcnt, 0);
return 0;
err_free_dma_mem:
t7xx_dpmaif_base_free(dpmaif_ctrl, bat_req);
return -ENOMEM;
}
void t7xx_dpmaif_bat_free(const struct dpmaif_ctrl *dpmaif_ctrl, struct dpmaif_bat_request *bat_req)
{
if (!bat_req || !atomic_dec_and_test(&bat_req->refcnt))
return;
bitmap_free(bat_req->bat_bitmap);
bat_req->bat_bitmap = NULL;
if (bat_req->bat_skb) {
unsigned int i;
for (i = 0; i < bat_req->bat_size_cnt; i++) {
if (bat_req->type == BAT_TYPE_FRAG)
t7xx_unmap_bat_page(dpmaif_ctrl->dev, bat_req->bat_skb, i);
else
t7xx_unmap_bat_skb(dpmaif_ctrl->dev, bat_req->bat_skb, i);
}
}
t7xx_dpmaif_base_free(dpmaif_ctrl, bat_req);
}
static int t7xx_dpmaif_rx_alloc(struct dpmaif_rx_queue *rxq)
{
rxq->pit_size_cnt = DPMAIF_PIT_COUNT;
rxq->pit_rd_idx = 0;
rxq->pit_wr_idx = 0;
rxq->pit_release_rd_idx = 0;
rxq->expect_pit_seq = 0;
rxq->pit_remain_release_cnt = 0;
memset(&rxq->rx_data_info, 0, sizeof(rxq->rx_data_info));
rxq->pit_base = dma_alloc_coherent(rxq->dpmaif_ctrl->dev,
rxq->pit_size_cnt * sizeof(struct dpmaif_pit),
&rxq->pit_bus_addr, GFP_KERNEL | __GFP_ZERO);
if (!rxq->pit_base)
return -ENOMEM;
rxq->bat_req = &rxq->dpmaif_ctrl->bat_req;
atomic_inc(&rxq->bat_req->refcnt);
rxq->bat_frag = &rxq->dpmaif_ctrl->bat_frag;
atomic_inc(&rxq->bat_frag->refcnt);
return 0;
}
static void t7xx_dpmaif_rx_buf_free(const struct dpmaif_rx_queue *rxq)
{
if (!rxq->dpmaif_ctrl)
return;
t7xx_dpmaif_bat_free(rxq->dpmaif_ctrl, rxq->bat_req);
t7xx_dpmaif_bat_free(rxq->dpmaif_ctrl, rxq->bat_frag);
if (rxq->pit_base)
dma_free_coherent(rxq->dpmaif_ctrl->dev,
rxq->pit_size_cnt * sizeof(struct dpmaif_pit),
rxq->pit_base, rxq->pit_bus_addr);
}
int t7xx_dpmaif_rxq_init(struct dpmaif_rx_queue *queue)
{
int ret;
ret = t7xx_dpmaif_rx_alloc(queue);
if (ret < 0)
dev_err(queue->dpmaif_ctrl->dev, "Failed to allocate RX buffers: %d\n", ret);
return ret;
}
void t7xx_dpmaif_rxq_free(struct dpmaif_rx_queue *queue)
{
t7xx_dpmaif_rx_buf_free(queue);
}
static void t7xx_dpmaif_bat_release_work(struct work_struct *work)
{
struct dpmaif_ctrl *dpmaif_ctrl = container_of(work, struct dpmaif_ctrl, bat_release_work);
struct dpmaif_rx_queue *rxq;
int ret;
ret = pm_runtime_resume_and_get(dpmaif_ctrl->dev);
if (ret < 0 && ret != -EACCES)
return;
t7xx_pci_disable_sleep(dpmaif_ctrl->t7xx_dev);
/* ALL RXQ use one BAT table, so choose DPF_RX_QNO_DFT */
rxq = &dpmaif_ctrl->rxq[DPF_RX_QNO_DFT];
if (t7xx_pci_sleep_disable_complete(dpmaif_ctrl->t7xx_dev)) {
t7xx_dpmaif_bat_release_and_add(rxq);
t7xx_dpmaif_frag_bat_release_and_add(rxq);
}
t7xx_pci_enable_sleep(dpmaif_ctrl->t7xx_dev);
pm_runtime_mark_last_busy(dpmaif_ctrl->dev);
pm_runtime_put_autosuspend(dpmaif_ctrl->dev);
}
int t7xx_dpmaif_bat_rel_wq_alloc(struct dpmaif_ctrl *dpmaif_ctrl)
{
dpmaif_ctrl->bat_release_wq = alloc_workqueue("dpmaif_bat_release_work_queue",
WQ_MEM_RECLAIM, 1);
if (!dpmaif_ctrl->bat_release_wq)
return -ENOMEM;
INIT_WORK(&dpmaif_ctrl->bat_release_work, t7xx_dpmaif_bat_release_work);
return 0;
}
void t7xx_dpmaif_bat_wq_rel(struct dpmaif_ctrl *dpmaif_ctrl)
{
flush_work(&dpmaif_ctrl->bat_release_work);
if (dpmaif_ctrl->bat_release_wq) {
destroy_workqueue(dpmaif_ctrl->bat_release_wq);
dpmaif_ctrl->bat_release_wq = NULL;
}
}
/**
* t7xx_dpmaif_rx_stop() - Suspend RX flow.
* @dpmaif_ctrl: Pointer to data path control struct dpmaif_ctrl.
*
* Wait for all the RX work to finish executing and mark the RX queue as paused.
*/
void t7xx_dpmaif_rx_stop(struct dpmaif_ctrl *dpmaif_ctrl)
{
unsigned int i;
for (i = 0; i < DPMAIF_RXQ_NUM; i++) {
struct dpmaif_rx_queue *rxq = &dpmaif_ctrl->rxq[i];
int timeout, value;
timeout = readx_poll_timeout_atomic(atomic_read, &rxq->rx_processing, value,
!value, 0, DPMAIF_CHECK_INIT_TIMEOUT_US);
if (timeout)
dev_err(dpmaif_ctrl->dev, "Stop RX SW failed\n");
/* Ensure RX processing has stopped before we set rxq->que_started to false */
smp_mb();
rxq->que_started = false;
}
}
static void t7xx_dpmaif_stop_rxq(struct dpmaif_rx_queue *rxq)
{
int cnt, j = 0;
rxq->que_started = false;
do {
cnt = t7xx_ring_buf_rd_wr_count(rxq->pit_size_cnt, rxq->pit_rd_idx,
rxq->pit_wr_idx, DPMAIF_READ);
if (++j >= DPMAIF_MAX_CHECK_COUNT) {
dev_err(rxq->dpmaif_ctrl->dev, "Stop RX SW failed, %d\n", cnt);
break;
}
} while (cnt);
memset(rxq->pit_base, 0, rxq->pit_size_cnt * sizeof(struct dpmaif_pit));
memset(rxq->bat_req->bat_base, 0, rxq->bat_req->bat_size_cnt * sizeof(struct dpmaif_bat));
bitmap_zero(rxq->bat_req->bat_bitmap, rxq->bat_req->bat_size_cnt);
memset(&rxq->rx_data_info, 0, sizeof(rxq->rx_data_info));
rxq->pit_rd_idx = 0;
rxq->pit_wr_idx = 0;
rxq->pit_release_rd_idx = 0;
rxq->expect_pit_seq = 0;
rxq->pit_remain_release_cnt = 0;
rxq->bat_req->bat_release_rd_idx = 0;
rxq->bat_req->bat_wr_idx = 0;
rxq->bat_frag->bat_release_rd_idx = 0;
rxq->bat_frag->bat_wr_idx = 0;
}
void t7xx_dpmaif_rx_clear(struct dpmaif_ctrl *dpmaif_ctrl)
{
int i;
for (i = 0; i < DPMAIF_RXQ_NUM; i++)
t7xx_dpmaif_stop_rxq(&dpmaif_ctrl->rxq[i]);
}