blob: db5e736a366718ab5285c0410cf759061ac1ccf5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018-2019, Samsung Electronics.
*
*/
#include <asm/cacheflush.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/ip6_checksum.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <soc/google/shm_ipc.h>
#include "modem_prj.h"
#include "modem_utils.h"
#include "link_device_memory.h"
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
#include "dit.h"
#endif
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
#include "link_device_pcie_iommu.h"
#endif
static struct pktproc_perftest_data perftest_data[PERFTEST_MODE_MAX] = {
{
/* empty */
},
{
/* PERFTEST_MODE_IPV4
* port: 5000 -> 5001
* payload: 1464 (0x5b8)
*/
.header = {
0x45, 0x00, 0x05, 0xB8, 0x00, 0x00, 0x40, 0x00,
0x80, 0x11, 0x71, 0xDF, 0xC0, 0xA8, 0x01, 0x03,
0xC0, 0xA8, 0x01, 0x02, 0x13, 0x88, 0x13, 0x89,
0x05, 0xA4, 0x00, 0x00
},
.header_len = 28,
.dst_port_offset = 22,
.packet_len = 1464
},
{
/* PERFTEST_MODE_CLAT
* port: 5000 -> 5001
* payload: 1444 (0x5a4)
*/
.header = {
0x60, 0x0a, 0xf8, 0x0c, 0x05, 0xa4, 0x11, 0x40,
0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x02,
0x20, 0x01, 0x02, 0xd8, 0xe1, 0x43, 0x7b, 0xfb,
0x1d, 0xda, 0x90, 0x9d, 0x8b, 0x8d, 0x05, 0xe7,
0x13, 0x88, 0x13, 0x89, 0x05, 0xa4, 0x00, 0x00,
},
.header_len = 48,
.dst_port_offset = 42,
.packet_len = 1484
},
{
/* PERFTEST_MODE_IPV6
* port: 5000 -> 5001
* payload: 1444 (0x5a4)
*/
.header = {
0x60, 0x0a, 0xf8, 0x0c, 0x05, 0x90, 0x11, 0x40,
0x20, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x20, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
0x7d, 0xae, 0x3d, 0x4e, 0xac, 0xf2, 0x8a, 0x2b,
0x13, 0x88, 0x13, 0x89, 0x05, 0x90, 0x00, 0x00,
},
.header_len = 48,
.dst_port_offset = 42,
.packet_len = 1464
},
};
static bool pktproc_check_hw_checksum(u8 status)
{
if (unlikely(status & PKTPROC_STATUS_IGNR))
return false;
if (unlikely(!(status & PKTPROC_STATUS_IPCS) || !(status & PKTPROC_STATUS_TCPC)))
return false;
if (unlikely((status & PKTPROC_STATUS_IPCSF) || (status & PKTPROC_STATUS_TCPCF)))
return false;
return true;
}
static void pktproc_set_pktgen_checksum(struct pktproc_queue *q, u8 *data)
{
unsigned int off;
struct udphdr *uh;
switch (data[0] & 0xF0) {
case 0x40:
off = sizeof(struct iphdr);
break;
case 0x60:
off = sizeof(struct ipv6hdr);
break;
default:
return;
}
uh = (struct udphdr *)(data + off);
uh->check = htons(0x1234);
}
static ssize_t pktgen_gro_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct modem_ctl *mc = dev_get_drvdata(dev);
struct link_device *ld = get_current_link(mc->iod);
struct mem_link_device *mld = to_mem_link_device(ld);
struct pktproc_adaptor *ppa = &mld->pktproc;
unsigned int gro;
int ret;
ret = kstrtoint(buf, 0, &gro);
if (ret)
return -EINVAL;
ppa->pktgen_gro = (gro > 0 ? true : false);
return count;
}
static ssize_t pktgen_gro_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct modem_ctl *mc = dev_get_drvdata(dev);
struct link_device *ld = get_current_link(mc->iod);
struct mem_link_device *mld = to_mem_link_device(ld);
struct pktproc_adaptor *ppa = &mld->pktproc;
ssize_t count = 0;
count += scnprintf(&buf[count], PAGE_SIZE - count, "pktgen gro:%d\n", ppa->pktgen_gro);
return count;
}
/*
* Get a packet: ringbuf mode
*/
static int pktproc_get_pkt_from_ringbuf_mode(struct pktproc_queue *q, struct sk_buff **new_skb)
{
int ret = 0;
u16 len;
u16 ch_id;
u8 *src;
struct sk_buff *skb = NULL;
struct link_device *ld = &q->mld->link_dev;
struct pktproc_desc_ringbuf *desc = q->desc_ringbuf;
if (!pktproc_check_active(q->ppa, q->q_idx)) {
mif_err_limited("Queue %d not activated\n", q->q_idx);
return -EACCES;
}
if (q->ppa->desc_mode != DESC_MODE_RINGBUF) {
mif_err_limited("Invalid desc_mode %d\n", q->ppa->desc_mode);
return -EINVAL;
}
/* Get data */
len = desc[*q->rear_ptr].length;
if (len > q->ppa->max_packet_size) {
mif_err_limited("Length is invalid:%d\n", len);
q->stat.err_len++;
ret = -EPERM;
goto rx_error_on_desc;
}
ch_id = desc[*q->rear_ptr].channel_id;
if (ch_id == SIPC5_CH_ID_MAX) {
mif_err_limited("Channel ID is invalid:%d\n", ch_id);
q->stat.err_chid++;
ret = -EPERM;
goto rx_error_on_desc;
}
src = desc[*q->rear_ptr].cp_data_paddr - q->cp_buff_pbase + q->q_buff_vbase;
if ((src < q->q_buff_vbase) || (src > q->q_buff_vbase + q->q_buff_size)) {
mif_err_limited("Data address is invalid:%pK q_buff_vbase:%pK size:0x%08x\n",
src, q->q_buff_vbase, q->q_buff_size);
q->stat.err_addr++;
ret = -EINVAL;
goto rx_error_on_desc;
}
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
if (q->ppa->buff_rgn_cached && !q->ppa->use_hw_iocc)
dma_sync_single_for_cpu(q->ppa->dev, virt_to_phys(src), len, DMA_FROM_DEVICE);
#endif
pp_debug("len:%d ch_id:%d src:%pK\n", len, ch_id, src);
/* Build skb */
skb = napi_alloc_skb(q->napi_ptr, len);
if (unlikely(!skb)) {
mif_err_limited("alloc_skb() error\n");
q->stat.err_nomem++;
ret = -ENOMEM;
goto rx_error;
}
skb_put(skb, len);
skb_copy_to_linear_data(skb, src, len);
#ifdef PKTPROC_DEBUG_PKT
pr_buffer("pktproc", (char *)skb->data, (size_t)len, (size_t)40);
#endif
/* Set priv */
skbpriv(skb)->lnk_hdr = 0;
skbpriv(skb)->sipc_ch = ch_id;
skbpriv(skb)->iod = link_get_iod_with_channel(ld, skbpriv(skb)->sipc_ch);
skbpriv(skb)->ld = ld;
skbpriv(skb)->napi = q->napi_ptr;
switch (q->ppa->version) {
case PKTPROC_V2:
if (pktproc_check_hw_checksum(desc[*q->rear_ptr].status))
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
q->stat.err_csum++;
break;
default:
break;
}
if (unlikely(q->ppa->pktgen_gro)) {
pktproc_set_pktgen_checksum(q, skb->data);
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
*new_skb = skb;
q->stat.pass_cnt++;
*q->rear_ptr = circ_new_ptr(q->num_desc, *q->rear_ptr, 1);
return 0;
rx_error_on_desc:
mif_err_limited("Skip invalid descriptor at %d\n", *q->rear_ptr);
*q->rear_ptr = circ_new_ptr(q->num_desc, *q->rear_ptr, 1);
rx_error:
if (skb)
dev_kfree_skb_any(skb);
return ret;
}
/*
* Get a packet : sktbuf mode on 32bit region
*/
static int pktproc_clear_data_addr(struct pktproc_queue *q)
{
struct pktproc_desc_sktbuf *desc = q->desc_sktbuf;
struct pktproc_adaptor *ppa = q->ppa;
if (ppa->desc_mode != DESC_MODE_SKTBUF) {
mif_err_limited("Invalid desc_mode %d\n", ppa->desc_mode);
return -EINVAL;
}
if (!ppa->use_netrx_mng) {
mif_err_limited("Buffer manager is not set\n");
return -EPERM;
}
mif_info("Unmap buffer from %d to %d\n", q->done_ptr, *q->fore_ptr);
while (*q->fore_ptr != q->done_ptr) {
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
if (ppa->buff_rgn_cached && !ppa->use_hw_iocc && q->dma_addr[q->done_ptr]) {
dma_unmap_single_attrs(ppa->dev, q->dma_addr[q->done_ptr],
ppa->max_packet_size, DMA_FROM_DEVICE, 0);
q->dma_addr[q->done_ptr] = 0;
}
#endif
desc[q->done_ptr].cp_data_paddr = 0;
q->done_ptr = circ_new_ptr(q->num_desc, q->done_ptr, 1);
}
cpif_init_netrx_mng(q->manager);
memset(desc, 0, q->desc_size);
return 0;
}
static int pktproc_clear_data_addr_without_bm(struct pktproc_queue *q)
{
struct pktproc_desc_sktbuf *desc = q->desc_sktbuf;
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->ppa->use_netrx_mng) {
mif_err_limited("Buffer manager is set\n");
return -EPERM;
}
#endif
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
mif_info("Unmap all buffers\n");
if (q->ppa->buff_rgn_cached && !q->ppa->use_hw_iocc) {
int i;
for (i = 0; i < q->num_desc; i++) {
if(q->dma_addr[i]) {
dma_unmap_single_attrs(q->ppa->dev, q->dma_addr[i],
q->ppa->max_packet_size, DMA_FROM_DEVICE, 0);
q->dma_addr[i] = 0;
}
}
}
#endif
memset(desc, 0, q->desc_size);
return 0;
}
static int pktproc_fill_data_addr(struct pktproc_queue *q)
{
struct pktproc_desc_sktbuf *desc = q->desc_sktbuf;
struct pktproc_adaptor *ppa = q->ppa;
u32 space;
u32 fore;
int i;
unsigned long flags;
if (ppa->desc_mode != DESC_MODE_SKTBUF) {
mif_err_limited("Invalid desc_mode %d\n", ppa->desc_mode);
return -EINVAL;
}
if (!ppa->use_netrx_mng) {
mif_err_limited("Buffer manager is not set\n");
return -EPERM;
}
spin_lock_irqsave(&q->lock, flags);
space = circ_get_space(q->num_desc, *q->fore_ptr, q->done_ptr);
pp_debug("Q%d:%d/%d/%d Space:%d\n",
q->q_idx, *q->fore_ptr, *q->rear_ptr, q->done_ptr, space);
fore = *q->fore_ptr;
for (i = 0; i < space; i++) {
struct cpif_addr_pair *addrpair = cpif_map_rx_buf(q->manager);
if (unlikely(!addrpair)) {
mif_err_limited("skb alloc error due to no memory\n");
q->stat.err_bm_nomem++;
spin_unlock_irqrestore(&q->lock, flags);
return -ENOMEM;
}
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
if (ppa->buff_rgn_cached && !ppa->use_hw_iocc) {
q->dma_addr[fore] = dma_map_single_attrs(ppa->dev,
(u8 *)addrpair->ap_addr + ppa->skb_padding_size,
ppa->max_packet_size, DMA_FROM_DEVICE, 0);
if (dma_mapping_error(ppa->dev, q->dma_addr[fore])) {
mif_err_limited("dma_map_single_attrs() failed\n");
q->dma_addr[fore] = 0;
spin_unlock_irqrestore(&q->lock, flags);
return -ENOMEM;
}
}
#endif
desc[fore].cp_data_paddr = addrpair->cp_addr + ppa->skb_padding_size;
if (fore == 0)
desc[fore].control |= (1 << 7); /* HEAD */
if (fore == (q->num_desc - 1))
desc[fore].control |= (1 << 3); /* RINGEND */
if (unlikely(desc[fore].reserved0 != 0)) { /* W/A to detect mem poison */
mif_err("mem poison:0x%llX r0:%d c:%d s:%d l%d cl%d r1:%d\n",
desc[fore].cp_data_paddr, desc[fore].reserved0,
desc[fore].control, desc[fore].status,
desc[fore].lro, desc[fore].clat, desc[fore].reserved1);
panic("memory poison\n");
}
*q->fore_ptr = circ_new_ptr(q->num_desc, *q->fore_ptr, 1);
fore = circ_new_ptr(q->num_desc, fore, 1);
}
pp_debug("Q:%d fore/rear/done:%d/%d/%d\n",
q->q_idx, *q->fore_ptr, *q->rear_ptr, q->done_ptr);
spin_unlock_irqrestore(&q->lock, flags);
return 0;
}
static int pktproc_fill_data_addr_without_bm(struct pktproc_queue *q)
{
struct pktproc_desc_sktbuf *desc = q->desc_sktbuf;
unsigned long dst_paddr;
u32 fore;
int i;
unsigned long flags;
u32 space;
u32 fore_inc = 1;
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->ppa->use_netrx_mng) {
mif_err_limited("Buffer manager is set\n");
return -EPERM;
}
#endif
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
fore = q->ioc.curr_fore;
#else
fore = *q->fore_ptr;
#endif
/* The fore pointer is passed by CP from shared memory. Check the
* range to avoid OOB access */
if ((fore < 0) || (fore >= q->num_desc)) {
mif_err("Invalid fore_ptr (%d) passed by CP on queue(%d)!\n",
fore, q->q_idx);
return -EINVAL;
}
pp_debug("Q%d:%d/%d/%d\n",
q->q_idx, fore, *q->rear_ptr, q->done_ptr);
spin_lock_irqsave(&q->lock, flags);
if (q->ppa->buff_rgn_cached) {
space = circ_get_space(q->num_desc, fore, q->done_ptr);
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
if (space > q->ppa->space_margin)
space -= q->ppa->space_margin;
else
space = 0;
#endif
} else {
space = q->num_desc;
}
for (i = 0; i < space; i++) {
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU) || !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
u8 *dst_vaddr = NULL;
#endif
dst_paddr = q->q_buff_pbase + (q->ppa->true_packet_size * fore);
if (dst_paddr > (q->q_buff_pbase + q->q_buff_size))
mif_err_limited("dst_paddr:0x%lx is over 0x%lx\n",
dst_paddr, q->q_buff_pbase + q->q_buff_size);
pp_debug("Q:%d fore_ptr:%d dst_paddr:0x%lx\n",
q->q_idx, fore, dst_paddr);
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
dst_vaddr = cpif_pcie_iommu_map_va(q, dst_paddr, fore, &fore_inc);
if (!dst_vaddr) {
mif_err_limited("cpif_pcie_iommu_get_va() failed\n");
spin_unlock_irqrestore(&q->lock, flags);
return -ENOMEM;
}
#endif
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
if (q->ppa->buff_rgn_cached && !q->ppa->use_hw_iocc) {
if (dst_vaddr)
goto dma_map;
dst_vaddr = q->q_buff_vbase + (q->ppa->true_packet_size * fore);
if (dst_vaddr > (q->q_buff_vbase + q->q_buff_size))
mif_err_limited("dst_vaddr:%pK is over %pK\n",
dst_vaddr, q->q_buff_vbase + q->q_buff_size);
dma_map:
q->dma_addr[fore] =
dma_map_single_attrs(q->ppa->dev,
dst_vaddr + q->ppa->skb_padding_size,
q->ppa->max_packet_size, DMA_FROM_DEVICE, 0);
if (dma_mapping_error(q->ppa->dev, q->dma_addr[fore])) {
mif_err_limited("dma_map_single_attrs() failed\n");
q->dma_addr[fore] = 0;
spin_unlock_irqrestore(&q->lock, flags);
return -ENOMEM;
}
}
#endif
desc[fore].cp_data_paddr = (dst_paddr - q->q_buff_pbase) +
q->cp_buff_pbase +
q->ppa->skb_padding_size;
if (fore == 0)
desc[fore].control |= (1 << 7); /* HEAD */
if (fore == (q->num_desc - 1)) {
desc[fore].control |= (1 << 3); /* RINGEND */
if (!q->ppa->buff_rgn_cached)
continue;
}
if (fore_inc)
*q->fore_ptr = circ_new_ptr(q->num_desc, *q->fore_ptr, fore_inc);
fore = circ_new_ptr(q->num_desc, fore, 1);
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
q->ioc.curr_fore = fore;
#endif
}
pp_debug("Q:%d fore/rear/done:%d/%d/%d\n",
q->q_idx, *q->fore_ptr, *q->rear_ptr, q->done_ptr);
spin_unlock_irqrestore(&q->lock, flags);
return 0;
}
static int pktproc_update_fore_ptr(struct pktproc_queue *q, u32 count)
{
int ret = 0;
unsigned long flags;
if (!count)
return 0;
if (q->ppa->buff_rgn_cached) {
ret = q->alloc_rx_buf(q);
if (ret)
mif_err_limited("alloc_rx_buf() error %d Q%d\n", ret, q->q_idx);
} else {
spin_lock_irqsave(&q->lock, flags);
*q->fore_ptr = circ_new_ptr(q->num_desc, *q->fore_ptr, count);
spin_unlock_irqrestore(&q->lock, flags);
}
return ret;
}
static bool is_desc_valid(struct pktproc_queue *q, struct pktproc_desc_sktbuf *desc)
{
if (desc->length > q->ppa->max_packet_size) {
mif_err_limited("Length is invalid:%d\n", desc->length);
q->stat.err_len++;
return false;
}
if (desc->channel_id == SIPC5_CH_ID_MAX) {
mif_err_limited("Channel ID is invalid:%d\n", desc->channel_id);
q->stat.err_chid++;
return false;
}
return true;
}
static u8 *get_packet_vaddr(struct pktproc_queue *q, struct pktproc_desc_sktbuf *desc)
{
u8 *ret;
struct pktproc_adaptor *ppa = q->ppa;
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->manager) {
ret = (u8 *)cpif_unmap_rx_buf(q->manager,
desc->cp_data_paddr -
ppa->skb_padding_size, false);
if (!ret) {
mif_err_limited("invalid data address. null given\n");
q->stat.err_addr++;
return NULL;
}
} else
#endif
{
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
unsigned long src_paddr = desc->cp_data_paddr - q->cp_buff_pbase +
q->q_buff_pbase - ppa->skb_padding_size;
ret = (u8 *)q->ioc.pf_buf[q->done_ptr];
cpif_pcie_iommu_try_ummap_va(q, src_paddr, ret, q->done_ptr);
#else
ret = desc->cp_data_paddr - q->cp_buff_pbase +
q->q_buff_vbase - ppa->skb_padding_size;
if ((ret < q->q_buff_vbase) || (ret > q->q_buff_vbase + q->q_buff_size)) {
mif_err_limited("Data address is invalid:%pK data:%pK size:0x%08x\n",
ret, q->q_buff_vbase, q->q_buff_size);
q->stat.err_addr++;
return NULL;
}
#endif
}
ret += ppa->skb_padding_size;
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
if (ppa->buff_rgn_cached && !ppa->use_hw_iocc && q->dma_addr[q->done_ptr]) {
dma_unmap_single_attrs(ppa->dev, q->dma_addr[q->done_ptr],
ppa->max_packet_size, DMA_FROM_DEVICE, 0);
q->dma_addr[q->done_ptr] = 0;
}
#endif
return ret;
}
static struct sk_buff *cpif_build_skb_single(struct pktproc_queue *q, u8 *src, u16 len,
u16 front_pad_size, u16 rear_pad_size, int *buffer_count)
{
struct sk_buff *skb;
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->manager) {
skb = build_skb(src - front_pad_size, q->manager->frag_size);
if (unlikely(!skb))
goto error;
skb_reserve(skb, front_pad_size);
} else
#endif
{
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
skb = build_skb(src - front_pad_size, q->ppa->true_packet_size);
if (unlikely(!skb))
goto error;
skb_reserve(skb, front_pad_size);
#else
skb = napi_alloc_skb(q->napi_ptr, len);
if (unlikely(!skb))
goto error;
skb_copy_to_linear_data(skb, src, len);
#endif
}
skb_put(skb, len);
*buffer_count += 1;
q->done_ptr = circ_new_ptr(q->num_desc, q->done_ptr, 1);
return skb;
error:
mif_err_limited("getting skb failed\n");
q->stat.err_nomem++;
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->manager && !q->manager->already_retrieved)
q->manager->already_retrieved = src;
#endif
return NULL;
}
#if IS_ENABLED(CONFIG_CP_PKTPROC_LRO)
static u32 cpif_prepare_lro_and_get_headlen(struct sk_buff *skb, bool *is_udp)
{
u32 headlen = 0;
struct iphdr *iph = (struct iphdr *)skb->data;
if (iph->version == 6) {
struct ipv6hdr *ipv6h = (struct ipv6hdr *)skb->data;
headlen += sizeof(struct ipv6hdr);
if (ipv6h->nexthdr == NEXTHDR_TCP) {
struct tcphdr *th = (struct tcphdr *)(skb->data + headlen);
headlen += th->doff * 4;
skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV6;
} else {
struct udphdr *uh = (struct udphdr *)(skb->data + headlen);
__be16 backup_len = uh->len;
uh->check = 0;
uh->len = htons(skb->len - headlen);
uh->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
ntohs(uh->len), IPPROTO_UDP,
csum_partial(uh, ntohs(uh->len), 0));
uh->len = backup_len;
headlen += sizeof(struct udphdr);
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_L4 | SKB_GSO_FRAGLIST;
*is_udp = true;
}
} else { /* ipv4 */
headlen += sizeof(struct iphdr);
if (iph->protocol == IPPROTO_TCP) {
struct tcphdr *th = (struct tcphdr *)(skb->data + headlen);
headlen += th->doff * 4;
skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV4;
} else {
headlen += sizeof(struct udphdr);
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_L4 | SKB_GSO_FRAGLIST;
*is_udp = true;
}
}
return headlen;
}
static struct sk_buff *cpif_build_skb_gro(struct pktproc_queue *q, u8 *src, u16 len,
int *buffer_count, bool *nomem)
{
struct sk_buff *skb_head, *skb, *last;
struct pktproc_desc_sktbuf *desc;
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct udphdr *uh;
u32 hdr_len;
bool is_udp = false;
skb_head = cpif_build_skb_single(q, src, len, q->ppa->skb_padding_size,
sizeof(struct skb_shared_info), buffer_count);
if (unlikely(!skb_head))
goto gro_fail_nomem;
hdr_len = cpif_prepare_lro_and_get_headlen(skb_head, &is_udp);
skb_shinfo(skb_head)->gso_size = skb_head->len - hdr_len;
skb_shinfo(skb_head)->gso_segs = 1;
skb_frag_list_init(skb_head);
skb_head->csum_level = 1;
last = NULL;
while (q->desc_sktbuf[q->done_ptr].lro & (LRO_MID_SEG | LRO_LAST_SEG)) {
u8 *tmp_src;
u16 tmp_len;
bool last_seg = (q->desc_sktbuf[q->done_ptr].lro & LRO_LAST_SEG) ?
true : false;
desc = &q->desc_sktbuf[q->done_ptr];
if (!is_desc_valid(q, desc)) {
mif_err_limited("Err! invalid desc. HW GRO failed\n");
goto gro_fail_inval;
}
tmp_src = get_packet_vaddr(q, desc);
if (!tmp_src) {
mif_err_limited("Err! invalid packet vaddr. HW GRO failed\n");
goto gro_fail_inval;
}
tmp_len = desc->length;
skb = cpif_build_skb_single(q, tmp_src, tmp_len, q->ppa->skb_padding_size,
sizeof(struct skb_shared_info), buffer_count);
if (unlikely(!skb))
goto gro_fail_nomem;
skb->transport_header = q->ppa->skb_padding_size - hdr_len;
skb->network_header = q->ppa->skb_padding_size - hdr_len;
skb->mac_header = q->ppa->skb_padding_size - hdr_len;
if (is_udp) { /* need to generate header including checksum */
u8 *hdr_start = skb->data - hdr_len;
skb_copy_from_linear_data(skb_head, hdr_start, hdr_len);
iph = (struct iphdr *)hdr_start;
if (iph->version == 4) {
uh = (struct udphdr *)(hdr_start + sizeof(struct iphdr));
iph->tot_len = htons(tmp_len + hdr_len);
iph->check = ip_fast_csum((unsigned char *)iph,
iph->ihl);
} else { /* ipv6 */
uh = (struct udphdr *)(hdr_start + sizeof(struct ipv6hdr));
ipv6h = (struct ipv6hdr *)hdr_start;
ipv6h->payload_len = htons(tmp_len + sizeof(struct udphdr));
}
uh->len = htons(tmp_len + sizeof(struct udphdr));
if (iph->version == 6) { /* checksum required for udp v6 only */
uh->check = 0;
uh->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
ntohs(uh->len), IPPROTO_UDP,
csum_partial(uh, ntohs(uh->len), 0));
}
}
if (last)
last->next = skb;
else
skb_shinfo(skb_head)->frag_list = skb;
last = skb;
skb_head->data_len += skb->len;
skb_head->truesize += skb->truesize;
skb_head->len += skb->len;
skb_shinfo(skb_head)->gso_segs += 1;
if (last_seg)
break;
}
iph = (struct iphdr *)skb_head->data;
if (iph->version == 4)
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
return skb_head;
gro_fail_nomem:
*nomem = true;
gro_fail_inval:
if (skb_head)
dev_kfree_skb_any(skb_head);
return NULL;
}
#endif
static int pktproc_get_pkt_from_sktbuf_mode(struct pktproc_queue *q, struct sk_buff **new_skb)
{
int ret = 0;
int buffer_count = 0;
u16 len;
u8 ch_id;
/* It will be the start of skb->data */
u8 *src;
struct pktproc_adaptor *ppa = q->ppa;
struct sk_buff *skb = NULL;
struct pktproc_desc_sktbuf desc_done_ptr = q->desc_sktbuf[q->done_ptr];
struct link_device *ld = &q->mld->link_dev;
bool csum = false;
if (!is_desc_valid(q, &desc_done_ptr)) {
ret = -EINVAL;
goto rx_error_on_desc;
}
src = get_packet_vaddr(q, &desc_done_ptr);
if (!src) {
ret = -EINVAL;
goto rx_error_on_desc;
}
len = desc_done_ptr.length;
ch_id = desc_done_ptr.channel_id;
csum = pktproc_check_hw_checksum(desc_done_ptr.status);
if (!csum)
q->stat.err_csum++;
if (unlikely(ppa->pktgen_gro)) {
pktproc_set_pktgen_checksum(q, src);
csum = true;
}
pp_debug("Q:%d done_ptr:%d len:%d ch_id:%d src:%pK csum:%d\n",
q->q_idx, q->done_ptr, len, ch_id, src, csum);
#ifdef PKTPROC_DEBUG_PKT
pr_buffer("pktproc", (char *)src + ppa->skb_padding_size, (size_t)len, (size_t)40);
#endif
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
if (dit_check_dir_use_queue(DIT_DIR_RX, q->q_idx)) {
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
unsigned long src_paddr = 0;
#else
unsigned long src_paddr = desc_done_ptr.cp_data_paddr - q->cp_buff_pbase +
q->q_buff_pbase;
#endif
ret = dit_enqueue_src_desc_ring(DIT_DIR_RX,
src, src_paddr, len, ch_id, csum);
if (ret < 0) {
mif_err_limited("Enqueue failed at fore/rear/done:%d/%d/%d, ret: %d\n",
*q->fore_ptr, *q->rear_ptr, q->done_ptr, ret);
q->stat.err_enqueue_dit++;
goto rx_error;
}
q->stat.pass_cnt++;
q->done_ptr = circ_new_ptr(q->num_desc, q->done_ptr, 1);
return 1; /* dit cannot support HW GRO packets */
}
#endif
#if IS_ENABLED(CONFIG_CP_PKTPROC_LRO)
/* guaranteed that only TCP/IP, UDP/IP in this case */
if (desc_done_ptr.lro == (LRO_MODE_ON | LRO_FIRST_SEG)) {
bool nomem = false;
if (!csum)
mif_info("CSUM error on LRO: 0x%X\n", desc_done_ptr.status);
skb = cpif_build_skb_gro(q, src, len, &buffer_count, &nomem);
if (unlikely(!skb)) {
if (nomem) {
ret = -ENOMEM;
if (buffer_count != 0) /* intermediate seg */
q->done_ptr = circ_new_ptr(q->num_desc, q->done_ptr, 1);
goto rx_error;
} else {
ret = -EINVAL;
goto rx_error_on_desc;
}
}
q->stat.lro_cnt++;
} else {
skb = cpif_build_skb_single(q, src, len, ppa->skb_padding_size,
sizeof(struct skb_shared_info), &buffer_count);
if (unlikely(!skb)) {
ret = -ENOMEM;
goto rx_error;
}
}
#else
skb = cpif_build_skb_single(q, src, len, ppa->skb_padding_size,
sizeof(struct skb_shared_info), &buffer_count);
if (unlikely(!skb)) {
ret = -ENOMEM;
goto rx_error;
}
#endif
if (csum)
skb->ip_summed = CHECKSUM_UNNECESSARY;
if (ppa->use_exclusive_irq)
skb_record_rx_queue(skb, q->q_idx);
/* Set priv */
skbpriv(skb)->lnk_hdr = 0;
skbpriv(skb)->sipc_ch = ch_id;
skbpriv(skb)->iod = link_get_iod_with_channel(ld, ch_id);
skbpriv(skb)->ld = ld;
skbpriv(skb)->napi = q->napi_ptr;
#if IS_ENABLED(CONFIG_CP_PKTPROC_CLAT)
/* CLAT[1:0] = {CLAT On, CLAT Pkt} */
if (desc_done_ptr.clat == 0x03)
skbpriv(skb)->rx_clat = 1;
#endif
*new_skb = skb;
q->stat.pass_cnt += buffer_count;
return buffer_count;
rx_error_on_desc:
mif_err_limited("Skip invalid descriptor at %d and crash\n", q->done_ptr);
ld->link_trigger_cp_crash(q->mld, CRASH_REASON_MIF_RX_BAD_DATA,
"invalid descriptor given");
q->done_ptr = circ_new_ptr(q->num_desc, q->done_ptr, 1);
rx_error:
if (skb)
dev_kfree_skb_any(skb);
return ret;
}
/*
* Clean RX ring
*/
int pktproc_get_usage(struct pktproc_queue *q)
{
u32 usage = 0;
switch (q->ppa->desc_mode) {
case DESC_MODE_RINGBUF:
usage = circ_get_usage(q->num_desc, *q->fore_ptr, *q->rear_ptr);
break;
case DESC_MODE_SKTBUF:
usage = circ_get_usage(q->num_desc, *q->rear_ptr, q->done_ptr);
break;
default:
usage = 0;
break;
}
return usage;
}
int pktproc_get_usage_fore_rear(struct pktproc_queue *q)
{
u32 usage = 0;
switch (q->ppa->desc_mode) {
case DESC_MODE_RINGBUF:
usage = circ_get_usage(q->num_desc, *q->fore_ptr, *q->rear_ptr);
break;
case DESC_MODE_SKTBUF:
usage = circ_get_usage(q->num_desc, *q->rear_ptr, *q->fore_ptr);
break;
default:
usage = 0;
break;
}
return usage;
}
static int pktproc_clean_rx_ring(struct pktproc_queue *q, int budget, int *work_done)
{
int ret = 0;
u32 num_frames = 0;
u32 rcvd_total = 0;
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
u32 rcvd_dit = 0;
#endif
u32 budget_used = 0;
num_frames = pktproc_get_usage(q);
if (!num_frames)
return 0;
pp_debug("Q%d num_frames:%d fore/rear/done: %d/%d/%d\n",
q->q_idx, num_frames,
*q->fore_ptr, *q->rear_ptr, q->done_ptr);
while (rcvd_total < num_frames && budget_used < budget) {
struct sk_buff *skb = NULL;
ret = q->get_packet(q, &skb);
if (unlikely(ret < 0)) {
mif_err_limited("get_packet() error %d\n", ret);
break;
}
rcvd_total += ret;
budget_used++;
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
/* skb will be null if dit fills the skb */
if (!skb) {
rcvd_dit += ret; /* ret will be always 1 */
continue;
}
#endif
ret = q->mld->pass_skb_to_net(q->mld, skb);
if (ret < 0)
break;
}
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
if (rcvd_dit) {
dit_kick(DIT_DIR_RX, false);
/* dit processed every packets*/
if (rcvd_dit == rcvd_total)
goto out;
}
#endif
#if IS_ENABLED(CONFIG_CPIF_TP_MONITOR)
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
if (rcvd_total - rcvd_dit > 0)
#else
if (rcvd_total > 0)
#endif
tpmon_start();
#endif
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
q->update_fore_ptr(q, rcvd_total - rcvd_dit);
#else
q->update_fore_ptr(q, rcvd_total);
#endif
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
out:
#endif
*work_done = rcvd_total;
return ret;
}
/*
* perftest
*/
static void pktproc_perftest_napi_schedule(void *arg)
{
struct pktproc_queue *q = (struct pktproc_queue *)arg;
if (!q) {
mif_err_limited("q is null\n");
return;
}
if (!pktproc_get_usage(q))
return;
if (napi_schedule_prep(q->napi_ptr)) {
q->disable_irq(q);
__napi_schedule(q->napi_ptr);
}
}
static unsigned int pktproc_perftest_gen_rx_packet_sktbuf_mode(
struct pktproc_queue *q, int packet_num, int session)
{
struct pktproc_desc_sktbuf *desc = q->desc_sktbuf;
struct pktproc_perftest *perf = &q->ppa->perftest;
u32 header_len = perftest_data[perf->mode].header_len;
u32 rear_ptr;
unsigned int space, loop_count;
u8 *src;
u32 *seq;
u16 *dst_port;
u16 *dst_addr;
int i, j;
rear_ptr = *q->rear_ptr;
space = circ_get_space(q->num_desc, rear_ptr, *q->fore_ptr);
loop_count = min_t(unsigned int, space, packet_num);
for (i = 0 ; i < loop_count ; i++) {
/* set desc */
desc[rear_ptr].status =
PKTPROC_STATUS_DONE | PKTPROC_STATUS_TCPC | PKTPROC_STATUS_IPCS;
desc[rear_ptr].length = perftest_data[perf->mode].packet_len;
desc[rear_ptr].filter_result = 0x9;
desc[rear_ptr].channel_id = perf->ch;
/* set data */
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
src = q->ioc.pf_buf[rear_ptr] + q->ppa->skb_padding_size;
#else
src = desc[rear_ptr].cp_data_paddr -
q->cp_buff_pbase + q->q_buff_vbase;
#endif
memset(src, 0x0, desc[rear_ptr].length);
memcpy(src, perftest_data[perf->mode].header, header_len);
seq = (u32 *)(src + header_len);
*seq = htonl(perf->seq_counter[session]++);
dst_port = (u16 *)(src + perftest_data[perf->mode].dst_port_offset);
*dst_port = htons(5001 + session);
if (perf->mode == PERFTEST_MODE_CLAT) {
for (j = 0 ; j < 8 ; j++) {
dst_addr = (u16 *)(src + 24 + (j * 2));
*dst_addr = htons(perf->clat_ipv6[j]);
}
}
rear_ptr = circ_new_ptr(q->num_desc, rear_ptr, 1);
}
*q->rear_ptr = rear_ptr;
return loop_count;
}
static int pktproc_perftest_thread(void *arg)
{
struct mem_link_device *mld = (struct mem_link_device *) arg;
struct pktproc_adaptor *ppa = &mld->pktproc;
struct pktproc_queue *q = ppa->q[0];
struct pktproc_perftest *perf = &ppa->perftest;
bool session_queue = false;
int i, pkts;
if (ppa->use_exclusive_irq && (perf->session > 1) && (perf->session <= ppa->num_queue))
session_queue = true;
/* max 1023 packets per 1ms for 12Gbps */
pkts = (perf->session > 0 ? (1023 / perf->session) : 0);
do {
for (i = 0 ; i < perf->session ; i++) {
int napi_cpu = perf->ipi_cpu[0];
if (session_queue)
q = ppa->q[i];
if (!pktproc_perftest_gen_rx_packet_sktbuf_mode(q, pkts, i))
continue;
if (session_queue)
napi_cpu = perf->ipi_cpu[i];
if (napi_cpu >= 0 && cpu_online(napi_cpu)) {
smp_call_function_single(napi_cpu,
pktproc_perftest_napi_schedule,
(void *)q, 0);
} else {
pktproc_perftest_napi_schedule(q);
}
}
udelay(perf->udelay);
if (kthread_should_stop())
break;
} while (perf->test_run);
return 0;
}
static ssize_t perftest_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct modem_ctl *mc = dev_get_drvdata(dev);
struct link_device *ld = get_current_link(mc->iod);
struct mem_link_device *mld = to_mem_link_device(ld);
struct pktproc_adaptor *ppa = &mld->pktproc;
struct pktproc_perftest new_perf;
struct pktproc_perftest *perf = &ppa->perftest;
static struct task_struct *worker_task;
int ret;
perf->ipi_cpu[0] = -1;
if (ppa->use_exclusive_irq) {
perf->ipi_cpu[0] = 4;
perf->ipi_cpu[1] = 4;
perf->ipi_cpu[2] = 4;
perf->ipi_cpu[3] = 4;
}
memcpy((void *)&new_perf, (void *)perf, sizeof(struct pktproc_perftest));
switch (perf->mode) {
case PERFTEST_MODE_CLAT:
ret = sscanf(buf,
"%u %hu %hu %hu %hu %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx %hu %hu %hu %hu",
&new_perf.mode, &new_perf.session, &new_perf.ch,
&new_perf.cpu, &new_perf.udelay,
&new_perf.clat_ipv6[0], &new_perf.clat_ipv6[1], &new_perf.clat_ipv6[2],
&new_perf.clat_ipv6[3], &new_perf.clat_ipv6[4], &new_perf.clat_ipv6[5],
&new_perf.clat_ipv6[6], &new_perf.clat_ipv6[7],
&new_perf.ipi_cpu[0], &new_perf.ipi_cpu[1], &new_perf.ipi_cpu[2],
&new_perf.ipi_cpu[3]);
break;
default:
ret = sscanf(buf, "%u %hu %hu %hu %hu %hu %hu %hu %hu",
&new_perf.mode, &new_perf.session, &new_perf.ch,
&new_perf.cpu, &new_perf.udelay,
&new_perf.ipi_cpu[0], &new_perf.ipi_cpu[1], &new_perf.ipi_cpu[2],
&new_perf.ipi_cpu[3]);
break;
}
if (ret < 1)
return -EINVAL;
if (new_perf.mode >= PERFTEST_MODE_MAX)
new_perf.mode = PERFTEST_MODE_STOP;
if (new_perf.session > PKTPROC_MAX_QUEUE)
new_perf.session = PKTPROC_MAX_QUEUE;
if (new_perf.ch > SIPC5_CH_ID_MAX)
new_perf.ch = SIPC5_CH_ID_MAX;
memcpy((void *)perf, (void *)&new_perf, sizeof(struct pktproc_perftest));
switch (perf->mode) {
case PERFTEST_MODE_STOP:
if (perf->test_run)
kthread_stop(worker_task);
perf->seq_counter[0] = 0;
perf->seq_counter[1] = 0;
perf->seq_counter[2] = 0;
perf->seq_counter[3] = 0;
perf->test_run = false;
break;
case PERFTEST_MODE_IPV4:
case PERFTEST_MODE_CLAT:
case PERFTEST_MODE_IPV6:
if (perf->test_run)
kthread_stop(worker_task);
perf->test_run = true;
worker_task = kthread_create_on_node(pktproc_perftest_thread,
mld, cpu_to_node(perf->cpu), "perftest/%d", perf->cpu);
kthread_bind(worker_task, perf->cpu);
wake_up_process(worker_task);
break;
default:
break;
}
return count;
}
static ssize_t perftest_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct modem_ctl *mc = dev_get_drvdata(dev);
struct link_device *ld = get_current_link(mc->iod);
struct mem_link_device *mld = to_mem_link_device(ld);
struct pktproc_adaptor *ppa = &mld->pktproc;
struct pktproc_perftest *perf = &ppa->perftest;
ssize_t count = 0;
count += scnprintf(&buf[count], PAGE_SIZE - count, "test_run:%d\n", perf->test_run);
count += scnprintf(&buf[count], PAGE_SIZE - count, "mode:%d\n", perf->mode);
count += scnprintf(&buf[count], PAGE_SIZE - count, "session:%d\n", perf->session);
count += scnprintf(&buf[count], PAGE_SIZE - count, "ch:%d\n", perf->ch);
count += scnprintf(&buf[count], PAGE_SIZE - count, "udelay:%d\n", perf->udelay);
count += scnprintf(&buf[count], PAGE_SIZE - count, "cpu:%d\n", perf->cpu);
if (ppa->use_exclusive_irq)
count += scnprintf(&buf[count], PAGE_SIZE - count, "ipi cpu:%d %d %d %d\n",
perf->ipi_cpu[0], perf->ipi_cpu[1], perf->ipi_cpu[2], perf->ipi_cpu[3]);
if (perf->mode == PERFTEST_MODE_CLAT)
count += scnprintf(&buf[count], PAGE_SIZE - count, "clat %x:%x:%x:%x:%x:%x:%x:%x\n",
perf->clat_ipv6[0], perf->clat_ipv6[1], perf->clat_ipv6[2],
perf->clat_ipv6[3], perf->clat_ipv6[4], perf->clat_ipv6[5],
perf->clat_ipv6[6], perf->clat_ipv6[7]);
return count;
}
/*
* NAPI
*/
static void pktproc_enable_irq(struct pktproc_queue *q)
{
#if IS_ENABLED(CONFIG_MCU_IPC)
cp_mbox_enable_handler(q->irq_idx, q->mld->irq_cp2ap_msg);
#endif
}
static void pktproc_disable_irq(struct pktproc_queue *q)
{
#if IS_ENABLED(CONFIG_MCU_IPC)
cp_mbox_disable_handler(q->irq_idx, q->mld->irq_cp2ap_msg);
#endif
}
static int pktproc_poll(struct napi_struct *napi, int budget)
{
struct pktproc_queue *q = container_of(napi, struct pktproc_queue, napi);
struct mem_link_device *mld = q->mld;
struct link_device *ld = &mld->link_dev;
struct modem_ctl *mc = ld->mc;
int ret;
u32 rcvd = 0;
if (unlikely(!cp_online(mc)))
goto poll_exit;
if (!pktproc_check_active(q->ppa, q->q_idx))
goto poll_exit;
ret = q->clean_rx_ring(q, budget, &rcvd);
if ((ret == -EBUSY) || (ret == -ENOMEM))
goto poll_retry;
if (rcvd < budget) {
napi_complete_done(napi, rcvd);
q->enable_irq(q);
return rcvd;
}
poll_retry:
return budget;
poll_exit:
napi_complete(napi);
q->enable_irq(q);
return 0;
}
/*
* IRQ handler
*/
static irqreturn_t pktproc_irq_handler(int irq, void *arg)
{
struct pktproc_queue *q = (struct pktproc_queue *)arg;
if (!q) {
mif_err_limited("q is null\n");
return IRQ_HANDLED;
}
if (!pktproc_get_usage(q))
return IRQ_HANDLED;
#if IS_ENABLED(CONFIG_CPIF_TP_MONITOR)
tpmon_start();
#endif
if (napi_schedule_prep(q->napi_ptr)) {
q->disable_irq(q);
__napi_schedule(q->napi_ptr);
}
return IRQ_HANDLED;
}
/*
* Debug
*/
static ssize_t region_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct modem_ctl *mc = dev_get_drvdata(dev);
struct link_device *ld = get_current_link(mc->iod);
struct mem_link_device *mld = to_mem_link_device(ld);
struct pktproc_adaptor *ppa = &mld->pktproc;
ssize_t count = 0;
int i;
count += scnprintf(&buf[count], PAGE_SIZE - count, "Version:%d\n", ppa->version);
count += scnprintf(&buf[count], PAGE_SIZE - count, "CP base:0x%08llx\n", ppa->cp_base);
count += scnprintf(&buf[count], PAGE_SIZE - count, "Descriptor mode:%d\n", ppa->desc_mode);
count += scnprintf(&buf[count], PAGE_SIZE - count, "Num of queue:%d\n", ppa->num_queue);
count += scnprintf(&buf[count], PAGE_SIZE - count, "NetRX manager:%d\n",
ppa->use_netrx_mng);
count += scnprintf(&buf[count], PAGE_SIZE - count, "Exclusive interrupt:%d\n",
ppa->use_exclusive_irq);
count += scnprintf(&buf[count], PAGE_SIZE - count, "HW cache coherency:%d\n",
ppa->use_hw_iocc);
count += scnprintf(&buf[count], PAGE_SIZE - count, "Max packet size:%d\n",
ppa->max_packet_size);
if (ppa->true_packet_size != ppa->max_packet_size) {
count += scnprintf(&buf[count], PAGE_SIZE - count, "True packet size:%d\n",
ppa->true_packet_size);
}
count += scnprintf(&buf[count], PAGE_SIZE - count, "Padding size:%d\n",
ppa->skb_padding_size);
count += scnprintf(&buf[count], PAGE_SIZE - count, "Dedicated BAAW:%d\n",
ppa->use_dedicated_baaw);
count += scnprintf(&buf[count], PAGE_SIZE - count, "info:%s desc:%s/buff:%s\n",
ppa->info_rgn_cached ? "C" : "NC",
ppa->desc_rgn_cached ? "C" : "NC",
ppa->buff_rgn_cached ? "C" : "NC");
count += scnprintf(&buf[count], PAGE_SIZE - count, "\n");
for (i = 0; i < ppa->num_queue; i++) {
struct pktproc_queue *q = ppa->q[i];
if (!pktproc_check_active(ppa, q->q_idx)) {
count += scnprintf(&buf[count], PAGE_SIZE - count,
"Queue %d is not active\n", i);
continue;
}
count += scnprintf(&buf[count], PAGE_SIZE - count, "Queue%d\n", i);
count += scnprintf(&buf[count], PAGE_SIZE - count, " num_desc:%d(0x%08x)\n",
q->q_info_ptr->num_desc, q->q_info_ptr->num_desc);
count += scnprintf(&buf[count], PAGE_SIZE - count, " cp_desc_pbase:0x%08x\n",
q->q_info_ptr->cp_desc_pbase);
count += scnprintf(&buf[count], PAGE_SIZE - count, " desc_size:0x%08x\n",
q->desc_size);
count += scnprintf(&buf[count], PAGE_SIZE - count, " cp_buff_pbase:0x%08x\n",
q->q_info_ptr->cp_buff_pbase);
count += scnprintf(&buf[count], PAGE_SIZE - count, " q_buff_size:0x%08x\n",
q->q_buff_size);
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
count += scnprintf(&buf[count], PAGE_SIZE - count, " DIT:%d\n",
dit_check_dir_use_queue(DIT_DIR_RX, q->q_idx));
#endif
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->manager) {
count += scnprintf(&buf[count], PAGE_SIZE - count,
"Buffer manager\n");
count += scnprintf(&buf[count], PAGE_SIZE - count,
" total number of packets:%llu\n",
q->manager->num_packet);
count += scnprintf(&buf[count], PAGE_SIZE - count,
" frag size:%llu\n",
q->manager->frag_size);
count += scnprintf(&buf[count], PAGE_SIZE - count, "\n");
}
#endif
}
return count;
}
static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct modem_ctl *mc = dev_get_drvdata(dev);
struct link_device *ld = get_current_link(mc->iod);
struct mem_link_device *mld = to_mem_link_device(ld);
struct pktproc_adaptor *ppa = &mld->pktproc;
ssize_t count = 0;
int i;
for (i = 0; i < ppa->num_queue; i++) {
struct pktproc_queue *q = ppa->q[i];
if (!pktproc_check_active(ppa, q->q_idx)) {
count += scnprintf(&buf[count], PAGE_SIZE - count,
"Queue %d is not active\n", i);
continue;
}
count += scnprintf(&buf[count], PAGE_SIZE - count, "Queue%d\n", i);
count += scnprintf(&buf[count], PAGE_SIZE - count, " num_desc:%d\n",
q->num_desc);
switch (ppa->desc_mode) {
case DESC_MODE_RINGBUF:
count += scnprintf(&buf[count], PAGE_SIZE - count, " fore/rear:%d/%d\n",
*q->fore_ptr, *q->rear_ptr);
count += scnprintf(&buf[count], PAGE_SIZE - count, " fore~rear:%d\n",
circ_get_usage(q->num_desc, *q->fore_ptr, *q->rear_ptr));
break;
case DESC_MODE_SKTBUF:
count += scnprintf(&buf[count], PAGE_SIZE - count,
" fore/rear/done:%d/%d/%d\n",
*q->fore_ptr, *q->rear_ptr, q->done_ptr);
count += scnprintf(&buf[count], PAGE_SIZE - count,
" fore~rear:%d rear~done:%d rear~fore:%d\n",
circ_get_usage(q->num_desc, *q->fore_ptr, *q->rear_ptr),
circ_get_usage(q->num_desc, *q->rear_ptr, q->done_ptr),
circ_get_usage(q->num_desc, *q->rear_ptr, *q->fore_ptr));
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
count += scnprintf(&buf[count], PAGE_SIZE - count,
" iommu_mapped cnt:%u size:0x%llX\n",
q->ioc.mapped_cnt, q->ioc.mapped_size);
#endif
break;
default:
break;
}
count += scnprintf(&buf[count], PAGE_SIZE - count, " pass:%lld lro:%lld\n",
q->stat.pass_cnt, q->stat.lro_cnt);
count += scnprintf(&buf[count], PAGE_SIZE - count,
" fail:len%lld chid%lld addr%lld nomem%lld bmnomem%lld csum%lld\n",
q->stat.err_len, q->stat.err_chid, q->stat.err_addr,
q->stat.err_nomem, q->stat.err_bm_nomem, q->stat.err_csum);
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
if (dit_check_dir_use_queue(DIT_DIR_RX, q->q_idx))
count += scnprintf(&buf[count], PAGE_SIZE - count,
" fail:enqueue_dit%lld\n",
q->stat.err_enqueue_dit);
#endif
}
return count;
}
static DEVICE_ATTR_RO(region);
static DEVICE_ATTR_RO(status);
static DEVICE_ATTR_RW(perftest);
static DEVICE_ATTR_RW(pktgen_gro);
static struct attribute *pktproc_attrs[] = {
&dev_attr_region.attr,
&dev_attr_status.attr,
&dev_attr_perftest.attr,
&dev_attr_pktgen_gro.attr,
NULL,
};
static const struct attribute_group pktproc_group = {
.attrs = pktproc_attrs,
.name = "pktproc",
};
/*
* Initialize PktProc
*/
int pktproc_init(struct pktproc_adaptor *ppa)
{
int i;
int ret = 0;
struct mem_link_device *mld;
if (!ppa) {
mif_err("ppa is null\n");
return -EPERM;
}
mld = container_of(ppa, struct mem_link_device, pktproc);
mif_info("version:%d cp_base:0x%08llx desc_mode:%d num_queue:%d\n",
ppa->version, ppa->cp_base, ppa->desc_mode, ppa->num_queue);
mif_info("interrupt:%d iocc:%d max_packet_size:%d\n",
ppa->use_exclusive_irq, ppa->use_hw_iocc, ppa->max_packet_size);
for (i = 0; i < ppa->num_queue; i++) {
struct pktproc_queue *q = ppa->q[i];
mif_info("Q%d\n", i);
napi_synchronize(&q->napi);
switch (ppa->desc_mode) {
case DESC_MODE_SKTBUF:
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
cpif_pcie_iommu_reset(q);
#endif
if (pktproc_check_active(q->ppa, q->q_idx))
q->clear_data_addr(q);
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)
if (q->manager)
mif_info("num packets:%llu frag size:%llu\n",
q->manager->num_packet,
q->manager->frag_size);
#endif
break;
default:
break;
}
*q->fore_ptr = 0;
*q->rear_ptr = 0;
q->done_ptr = 0;
if (mld->pktproc_use_36bit_addr) {
q->q_info_ptr->cp_desc_pbase = q->cp_desc_pbase >> 4;
q->q_info_ptr->cp_buff_pbase = q->cp_buff_pbase >> 4;
} else {
q->q_info_ptr->cp_desc_pbase = q->cp_desc_pbase;
q->q_info_ptr->cp_buff_pbase = q->cp_buff_pbase;
}
q->q_info_ptr->num_desc = q->num_desc;
memset(&q->stat, 0, sizeof(struct pktproc_statistics));
switch (ppa->desc_mode) {
case DESC_MODE_SKTBUF:
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
ret = cpif_pcie_iommu_init(q);
if (ret) {
mif_err("cpif_pcie_iommu_init() error %d Q%d\n", ret, q->q_idx);
continue;
}
#endif
ret = q->alloc_rx_buf(q);
if (ret) {
mif_err("alloc_rx_buf() error %d Q%d\n", ret, q->q_idx);
continue;
}
break;
default:
break;
}
mif_info("num_desc:0x%08x cp_desc_pbase:0x%08x desc_size:0x%08x\n",
q->q_info_ptr->num_desc, q->q_info_ptr->cp_desc_pbase,
q->desc_size);
mif_info("cp_buff_pbase:0x%08llx q_buff_size:0x%08x\n",
q->cp_buff_pbase, q->q_buff_size);
mif_info("fore:%d rear:%d done:%d\n",
*q->fore_ptr, *q->rear_ptr, q->done_ptr);
atomic_set(&q->active, 1);
}
return 0;
}
/*
* Create PktProc
*/
static int pktproc_create_buffer_manager(struct pktproc_queue *q, u64 ap_desc_pbase)
{
struct pktproc_adaptor *ppa;
unsigned int desc_total_size = 0;
struct cpif_addr_pair desc_addr_pair;
u64 frag_size = 0; /* size of fragmenting a page */
if (!q) {
mif_err("q is null\n");
return -EINVAL;
}
desc_total_size = q->num_desc * sizeof(struct pktproc_desc_sktbuf);
ppa = q->ppa;
if (!ppa) {
mif_err("ppa is null\n");
return -EINVAL;
}
if (!ppa->use_netrx_mng) {
mif_err("use_netrx_mng is not set\n");
return -EINVAL;
}
if (q->manager != NULL) {
mif_info("buffer manager is already initialized\n");
return 0;
}
desc_addr_pair.cp_addr = q->cp_desc_pbase;
desc_addr_pair.ap_addr = phys_to_virt(ap_desc_pbase);
frag_size = SKB_DATA_ALIGN(ppa->max_packet_size + ppa->skb_padding_size)
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
mif_info("about to init netrx mng: cp_addr: 0x%llX ap_addr: %pK frag_size: %llu\n",
q->cp_desc_pbase, q->desc_sktbuf, frag_size);
mif_info("desc_total_size:%d cp_buff_pbase: 0x%llX num_desc: %d\n",
desc_total_size, q->cp_buff_pbase, q->num_desc);
q->manager = cpif_create_netrx_mng(&desc_addr_pair, desc_total_size,
q->cp_buff_pbase, frag_size,
q->num_desc);
if (!q->manager) {
mif_err("cpif_create_netrx_mng() error\n");
return -ENOMEM;
}
return 0;
}
static void pktproc_adjust_size(struct pktproc_adaptor *ppa)
{
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
ppa->skb_padding_size = SKB_FRONT_PADDING;
#else
if (ppa->use_netrx_mng)
ppa->skb_padding_size = SKB_FRONT_PADDING;
else
ppa->skb_padding_size = 0;
#endif
ppa->true_packet_size = ppa->max_packet_size;
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
ppa->true_packet_size += ppa->skb_padding_size;
ppa->true_packet_size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
mif_info("adjusted iommu required:%u true_packet_size:%lu\n",
ppa->true_packet_size, roundup_pow_of_two(ppa->true_packet_size));
ppa->true_packet_size = roundup_pow_of_two(ppa->true_packet_size);
ppa->space_margin = PAGE_FRAG_CACHE_MAX_SIZE / ppa->true_packet_size;
#endif
}
static int pktproc_get_info(struct pktproc_adaptor *ppa, struct device_node *np)
{
mif_dt_read_u64(np, "pktproc_cp_base", ppa->cp_base);
mif_dt_read_u32(np, "pktproc_dl_version", ppa->version);
switch (ppa->version) {
case PKTPROC_V1:
ppa->desc_mode = DESC_MODE_RINGBUF;
ppa->num_queue = 1;
ppa->use_exclusive_irq = 0;
break;
case PKTPROC_V2:
mif_dt_read_u32(np, "pktproc_dl_desc_mode", ppa->desc_mode);
mif_dt_read_u32(np, "pktproc_dl_num_queue", ppa->num_queue);
#if IS_ENABLED(CONFIG_EXYNOS_CPIF_IOMMU)
mif_dt_read_u32(np, "pktproc_dl_use_netrx_mng", ppa->use_netrx_mng);
mif_dt_read_u32(np, "pktproc_dl_netrx_capacity", ppa->netrx_capacity);
/* Check if config and dt are consistent */
if(ppa->use_netrx_mng != IS_ENABLED(CONFIG_EXYNOS_CPIF_NETRX_MGR)) {
mif_err("netrx mgr config and dt are inconsistent\n");
panic("netrx mgr config and dt are inconsistent\n");
return -EINVAL;
}
#else
ppa->use_netrx_mng = 0;
ppa->netrx_capacity = 0;
#endif
mif_dt_read_u32(np, "pktproc_dl_use_exclusive_irq", ppa->use_exclusive_irq);
#if IS_ENABLED(CONFIG_MCU_IPC)
if (ppa->use_exclusive_irq) {
int ret;
ret = of_property_read_u32_array(np, "pktproc_dl_exclusive_irq_idx",
ppa->exclusive_irq_idx, ppa->num_queue);
if (ret) {
mif_err("pktproc_dl_exclusive_irq_idx error:%d\n", ret);
return ret;
}
}
#endif
break;
default:
mif_err("Unsupported version:%d\n", ppa->version);
return -EINVAL;
}
mif_info("version:%d cp_base:0x%08llx mode:%d num_queue:%d\n",
ppa->version, ppa->cp_base, ppa->desc_mode, ppa->num_queue);
mif_info("use_netrx_mng:%d netrx_capacity:%d exclusive_irq:%d\n",
ppa->use_netrx_mng, ppa->netrx_capacity, ppa->use_exclusive_irq);
mif_dt_read_u32(np, "pktproc_dl_use_hw_iocc", ppa->use_hw_iocc);
mif_dt_read_u32(np, "pktproc_dl_max_packet_size", ppa->max_packet_size);
mif_dt_read_u32(np, "pktproc_dl_use_dedicated_baaw", ppa->use_dedicated_baaw);
mif_info("iocc:%d max_packet_size:%d baaw:%d\n",
ppa->use_hw_iocc, ppa->max_packet_size, ppa->use_dedicated_baaw);
mif_dt_read_u32(np, "pktproc_dl_info_rgn_offset", ppa->info_rgn_offset);
mif_dt_read_u32(np, "pktproc_dl_info_rgn_size", ppa->info_rgn_size);
mif_dt_read_u32(np, "pktproc_dl_desc_rgn_offset", ppa->desc_rgn_offset);
mif_dt_read_u32(np, "pktproc_dl_desc_rgn_size", ppa->desc_rgn_size);
mif_dt_read_u32(np, "pktproc_dl_buff_rgn_offset", ppa->buff_rgn_offset);
mif_dt_read_u32(np, "pktproc_dl_buff_rgn_size", ppa->buff_rgn_size);
mif_info("info_rgn 0x%08x 0x%08x desc_rgn 0x%08x 0x%08x %u buff_rgn 0x%08x 0x%08x\n",
ppa->info_rgn_offset, ppa->info_rgn_size,
ppa->desc_rgn_offset, ppa->desc_rgn_size,
ppa->desc_num_ratio_percent,
ppa->buff_rgn_offset, ppa->buff_rgn_size);
mif_dt_read_u32(np, "pktproc_dl_info_rgn_cached", ppa->info_rgn_cached);
mif_dt_read_u32(np, "pktproc_dl_desc_rgn_cached", ppa->desc_rgn_cached);
mif_dt_read_u32(np, "pktproc_dl_buff_rgn_cached", ppa->buff_rgn_cached);
mif_info("cached:%d/%d/%d\n", ppa->info_rgn_cached, ppa->desc_rgn_cached,
ppa->buff_rgn_cached);
if (ppa->use_netrx_mng && !ppa->buff_rgn_cached) {
mif_err("Buffer manager requires cached buff region\n");
return -EINVAL;
}
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
if (ppa->use_netrx_mng || !ppa->buff_rgn_cached || ppa->desc_mode != DESC_MODE_SKTBUF) {
mif_err("not compatible with pcie iommu\n");
return -EINVAL;
}
#endif
/* Check if config and dt are consistent */
if (ppa->use_hw_iocc != IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)) {
mif_err("PCIe IOCC config and dt are inconsistent\n");
panic("PCIe IOCC config and dt are inconsistent\n");
return -EINVAL;
}
pktproc_adjust_size(ppa);
return 0;
}
int pktproc_create(struct platform_device *pdev, struct mem_link_device *mld,
unsigned long memaddr, u32 memsize)
{
struct device_node *np = pdev->dev.of_node;
struct pktproc_adaptor *ppa = &mld->pktproc;
u32 buff_size_by_q, accum_buff_size;
u32 alloc_size;
int i;
int ret = 0;
if (!np) {
mif_err("of_node is null\n");
return -EINVAL;
}
if (!ppa) {
mif_err("ppa is null\n");
return -EINVAL;
}
ppa->dev = &pdev->dev;
mif_dt_read_u32_noerr(np, "pktproc_dl_support", ppa->support);
if (!ppa->support) {
mif_err("pktproc_support is 0.\n");
panic("pktproc_support is 0\n");
return 0;
}
/* Get info */
ret = pktproc_get_info(ppa, np);
if (ret != 0) {
mif_err("pktproc_get_dt() error %d\n", ret);
return ret;
}
if (!ppa->use_hw_iocc && ppa->info_rgn_cached) {
mif_err("cannot support sw iocc based caching on info region\n");
return -EINVAL;
}
if (!ppa->use_hw_iocc && ppa->desc_rgn_cached) {
mif_err("cannot support sw iocc based caching on desc region\n");
return -EINVAL;
}
/* Get base addr */
mif_info("memaddr:0x%lx memsize:0x%08x\n", memaddr, memsize);
if (ppa->info_rgn_cached)
ppa->info_vbase = phys_to_virt(memaddr + ppa->info_rgn_offset);
else {
ppa->info_vbase = cp_shmem_get_nc_region(memaddr + ppa->info_rgn_offset,
ppa->info_rgn_size);
if (!ppa->info_vbase) {
mif_err("ppa->info_base error\n");
return -ENOMEM;
}
}
if (ppa->desc_rgn_cached)
ppa->desc_vbase = phys_to_virt(memaddr + ppa->desc_rgn_offset);
else {
ppa->desc_vbase = cp_shmem_get_nc_region(memaddr + ppa->desc_rgn_offset,
ppa->desc_rgn_size);
if (!ppa->desc_vbase) {
mif_err("ppa->desc_base error\n");
return -ENOMEM;
}
}
memset(ppa->info_vbase, 0, ppa->info_rgn_size);
memset(ppa->desc_vbase, 0, ppa->desc_rgn_size);
mif_info("info + desc size:0x%08x\n", ppa->info_rgn_size + ppa->desc_rgn_size);
if (!ppa->use_netrx_mng) {
buff_size_by_q = ppa->buff_rgn_size / ppa->num_queue;
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
mif_info("Rounded down queue size from 0x%08x to 0x%08x\n",
buff_size_by_q, rounddown(buff_size_by_q, SZ_4K));
buff_size_by_q = rounddown(buff_size_by_q, SZ_4K);
#endif
ppa->buff_pbase = memaddr + ppa->buff_rgn_offset;
if (ppa->buff_rgn_cached) {
ppa->buff_vbase = phys_to_virt(ppa->buff_pbase);
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOMMU)
mif_info("release iommu buffer region offset:0x%08x\n",
ppa->buff_rgn_offset);
cp_shmem_release_rmem(mld->link_dev.mdm_data->cp_num,
SHMEM_PKTPROC, ppa->buff_rgn_offset);
#endif
} else {
ppa->buff_vbase = cp_shmem_get_nc_region(ppa->buff_pbase,
ppa->buff_rgn_size);
}
mif_info("Total buff buffer size:0x%08x Queue:%d Size by queue:0x%08x\n",
ppa->buff_rgn_size, ppa->num_queue, buff_size_by_q);
} else
accum_buff_size = 0;
/* Create queue */
for (i = 0; i < ppa->num_queue; i++) {
struct pktproc_queue *q;
mif_info("Create queue %d\n", i);
ppa->q[i] = kzalloc(sizeof(struct pktproc_queue), GFP_ATOMIC);
if (ppa->q[i] == NULL) {
mif_err_limited("kzalloc() error %d\n", i);
ret = -ENOMEM;
goto create_error;
}
q = ppa->q[i];
q->ppa = ppa;
atomic_set(&q->active, 0);
/* Info region */
switch (ppa->version) {
case PKTPROC_V1:
q->info_v1 = (struct pktproc_info_v1 *)ppa->info_vbase;
q->q_info_ptr = &q->info_v1->q_info;
break;
case PKTPROC_V2:
q->info_v2 = (struct pktproc_info_v2 *)ppa->info_vbase;
q->info_v2->num_queues = ppa->num_queue;
q->info_v2->desc_mode = ppa->desc_mode;
q->info_v2->irq_mode = ppa->use_exclusive_irq;
q->info_v2->max_packet_size = ppa->max_packet_size;
q->q_info_ptr = &q->info_v2->q_info[i];
break;
default:
mif_err("Unsupported version:%d\n", ppa->version);
ret = -EINVAL;
goto create_error;
}
/* Descriptor, data buffer region */
switch (ppa->desc_mode) {
case DESC_MODE_RINGBUF:
q->q_buff_pbase = ppa->buff_pbase + (i * buff_size_by_q);
q->q_buff_vbase = ppa->buff_vbase + (i * buff_size_by_q);
q->cp_buff_pbase = ppa->cp_base + ppa->buff_rgn_offset +
(i * buff_size_by_q);
if (mld->pktproc_use_36bit_addr)
q->q_info_ptr->cp_buff_pbase = q->cp_buff_pbase >> 4;
else
q->q_info_ptr->cp_buff_pbase = q->cp_buff_pbase;
q->q_buff_size = buff_size_by_q;
#if !IS_ENABLED(CONFIG_LINK_DEVICE_PCIE_IOCC)
if (ppa->buff_rgn_cached && !ppa->use_hw_iocc)
dma_sync_single_for_device(ppa->dev,
q->q_buff_pbase, q->q_buff_size, DMA_FROM_DEVICE);
#endif
q->num_desc = buff_size_by_q / ppa->true_packet_size;
q->q_info_ptr->num_desc = q->num_desc;
q->desc_ringbuf = ppa->desc_vbase +
(i * sizeof(struct pktproc_desc_ringbuf) *
q->num_desc);
q->cp_desc_pbase = ppa->cp_base + ppa->desc_rgn_offset +
(i * sizeof(struct pktproc_desc_ringbuf) *
q->num_desc);
if (mld->pktproc_use_36bit_addr)
q->q_info_ptr->cp_desc_pbase = q->cp_desc_pbase >> 4;
else
q->q_info_ptr->cp_desc_pbase = q->cp_desc_pbase;
q->desc_size = sizeof(struct pktproc_desc_ringbuf) * q->num_desc;
q->get_packet = pktproc_get_pkt_from_ringbuf_mode;
q->irq_handler = pktproc_irq_handler;
break;
case DESC_MODE_SKTBUF:
if (ppa->use_netrx_mng) {
q->num_desc = ppa->netrx_capacity;
q->alloc_rx_buf = pktproc_fill_data_addr;
q->clear_data_addr = pktproc_clear_data_addr;
q->cp_buff_pbase = ppa->cp_base + ppa->buff_rgn_offset
+ accum_buff_size;
} else {
q->q_buff_pbase = ppa->buff_pbase + (i * buff_size_by_q);
q->q_buff_vbase = ppa->buff_vbase + (i * buff_size_by_q);
q->cp_buff_pbase = ppa->cp_base + ppa->buff_rgn_offset +
(i * buff_size_by_q);
q->q_buff_size = buff_size_by_q;
q->num_desc = buff_size_by_q / ppa->true_packet_size;
q->alloc_rx_buf = pktproc_fill_data_addr_without_bm;
q->clear_data_addr = pktproc_clear_data_addr_without_bm;
}
if (mld->pktproc_use_36bit_addr)
q->q_info_ptr->cp_buff_pbase = q->cp_buff_pbase >> 4;
else
q->q_info_ptr->cp_buff_pbase = q->cp_buff_pbase;
q->q_info_ptr->num_desc = q->num_desc;
q->desc_sktbuf = ppa->desc_vbase +
(i * sizeof(struct pktproc_desc_sktbuf) *
q->num_desc);
q->cp_desc_pbase = ppa->cp_base + ppa->desc_rgn_offset +
(i * sizeof(struct pktproc_desc_sktbuf) *
q->num_desc);
if (mld->pktproc_use_36bit_addr)
q->q_info_ptr->cp_desc_pbase = q->cp_desc_pbase >> 4;
else
q->q_info_ptr->cp_desc_pbase = q->cp_desc_pbase;
mif_info("cp_desc_pbase - 36bit addr: 0x%08llx, 32bit addr: 0x%08x\n",
q->cp_desc_pbase, q->q_info_ptr->cp_desc_pbase);
q->desc_size = sizeof(struct pktproc_desc_sktbuf) * q->num_desc;
alloc_size = sizeof(dma_addr_t) * q->num_desc;
q->dma_addr = kzalloc(alloc_size, GFP_KERNEL);
if (!q->dma_addr) {
mif_err("kzalloc() dma_addr failed\n");
ret = -ENOMEM;
goto create_error;
}
if (ppa->use_netrx_mng) {
/* to make phys_to_virt macro operable */
u64 ap_desc_pbase = memaddr + ppa->desc_rgn_offset +
(i * sizeof(struct pktproc_desc_sktbuf)
* q->num_desc);
mif_info("create buffer manager\n");
ret = pktproc_create_buffer_manager(q, ap_desc_pbase);
if (ret < 0) {
mif_err("failed to create netrx mng:%d\n", ret);
goto create_error;
}
accum_buff_size += q->manager->total_buf_size;
}
q->get_packet = pktproc_get_pkt_from_sktbuf_mode;
q->irq_handler = pktproc_irq_handler;
q->update_fore_ptr = pktproc_update_fore_ptr;
break;
default:
mif_err("Unsupported version:%d\n", ppa->version);
ret = -EINVAL;
goto create_error;
}
if ((!q->manager) &&
(q->cp_desc_pbase + q->desc_size) > q->cp_buff_pbase) {
mif_err("Descriptor overflow:0x%08llx 0x%08x 0x%08llx\n",
q->cp_desc_pbase, q->desc_size, q->cp_buff_pbase);
ret = -EINVAL;
goto create_error;
}
spin_lock_init(&q->lock);
q->clean_rx_ring = pktproc_clean_rx_ring;
q->q_idx = i;
q->mld = mld;
/* NAPI */
if (ppa->use_exclusive_irq) {
init_dummy_netdev(&q->netdev);
netif_napi_add(&q->netdev, &q->napi, pktproc_poll, NAPI_POLL_WEIGHT);
napi_enable(&q->napi);
q->napi_ptr = &q->napi;
} else {
q->napi_ptr = &q->mld->mld_napi;
}
/* IRQ handler */
q->enable_irq = pktproc_enable_irq;
q->disable_irq = pktproc_disable_irq;
if (ppa->use_exclusive_irq) {
#if IS_ENABLED(CONFIG_MCU_IPC)
q->irq_idx = ppa->exclusive_irq_idx[q->q_idx];
ret = cp_mbox_register_handler(q->irq_idx,
mld->irq_cp2ap_msg, q->irq_handler, q);
if (ret) {
mif_err("cp_mbox_register_handler() error:%d\n", ret);
goto create_error;
}
#endif
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE)
/* Set by request_pcie_msi_int() */
#endif
}
q->q_info_ptr->fore_ptr = 0;
q->q_info_ptr->rear_ptr = 0;
q->fore_ptr = &q->q_info_ptr->fore_ptr;
q->rear_ptr = &q->q_info_ptr->rear_ptr;
q->done_ptr = *q->rear_ptr;
mif_info("num_desc:%d cp_desc_pbase:0x%08llx desc_size:0x%08x\n",
q->num_desc, q->cp_desc_pbase, q->desc_size);
if (!q->manager)
mif_info("cp_buff_pbase:0x%08llx buff_size:0x%08x\n",
q->cp_buff_pbase, q->q_buff_size);
#if IS_ENABLED(CONFIG_EXYNOS_DIT)
if (q->q_idx == 0) {
ret = dit_set_pktproc_queue_num(DIT_DIR_RX, q->q_idx);
if (ret)
mif_err("dit_set_buf_size() error:%d\n", ret);
ret = dit_set_buf_size(DIT_DIR_RX, ppa->max_packet_size);
if (ret)
mif_err("dit_set_buf_size() error:%d\n", ret);
ret = dit_set_desc_ring_len(DIT_DIR_RX, q->num_desc - 1);
if (ret)
mif_err("dit_set_desc_ring_len() error:%d\n", ret);
}
#endif
}
/* Debug */
ret = sysfs_create_group(&pdev->dev.kobj, &pktproc_group);
if (ret != 0) {
mif_err("sysfs_create_group() error %d\n", ret);
goto create_error;
}
return 0;
create_error:
for (i = 0; i < ppa->num_queue; i++) {
if (!ppa->q[i])
continue;
if (ppa->q[i]->manager)
cpif_exit_netrx_mng(ppa->q[i]->manager);
kfree(ppa->q[i]->dma_addr);
kfree(ppa->q[i]);
}
if (!ppa->info_rgn_cached && ppa->info_vbase)
vunmap(ppa->info_vbase);
if (!ppa->desc_rgn_cached && ppa->desc_vbase)
vunmap(ppa->desc_vbase);
if (!ppa->buff_rgn_cached && ppa->buff_vbase)
vunmap(ppa->buff_vbase);
return ret;
}