Commit e2585ea7 authored by Aviad Krawczyk's avatar Aviad Krawczyk Committed by David S. Miller

net-next/hinic: Add Rx handler

Set the io resources in the nic and handle rx events by qp operations.
Signed-off-by: default avatarAviad Krawczyk <aviad.krawczyk@huawei.com>
Signed-off-by: default avatarZhao Chen <zhaochen6@huawei.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7ef37fe4
......@@ -43,6 +43,7 @@ struct hinic_dev {
struct hinic_hwdev *hwdev;
u32 msg_enable;
unsigned int rx_weight;
unsigned int flags;
......
......@@ -20,6 +20,7 @@
#define HINIC_CSR_FUNC_ATTR0_ADDR 0x0
#define HINIC_CSR_FUNC_ATTR1_ADDR 0x4
#define HINIC_CSR_FUNC_ATTR4_ADDR 0x10
#define HINIC_CSR_FUNC_ATTR5_ADDR 0x14
#define HINIC_DMA_ATTR_BASE 0xC80
......
......@@ -74,6 +74,76 @@ enum hinic_cb_state {
HINIC_CB_RUNNING = BIT(1),
};
enum hinic_res_state {
HINIC_RES_CLEAN = 0,
HINIC_RES_ACTIVE = 1,
};
struct hinic_cmd_fw_ctxt {
u8 status;
u8 version;
u8 rsvd0[6];
u16 func_idx;
u16 rx_buf_sz;
u32 rsvd1;
};
struct hinic_cmd_hw_ioctxt {
u8 status;
u8 version;
u8 rsvd0[6];
u16 func_idx;
u16 rsvd1;
u8 set_cmdq_depth;
u8 cmdq_depth;
u8 rsvd2;
u8 rsvd3;
u8 rsvd4;
u8 rsvd5;
u16 rq_depth;
u16 rx_buf_sz_idx;
u16 sq_depth;
};
struct hinic_cmd_io_status {
u8 status;
u8 version;
u8 rsvd0[6];
u16 func_idx;
u8 rsvd1;
u8 rsvd2;
u32 io_status;
};
struct hinic_cmd_clear_io_res {
u8 status;
u8 version;
u8 rsvd0[6];
u16 func_idx;
u8 rsvd1;
u8 rsvd2;
};
struct hinic_cmd_set_res_state {
u8 status;
u8 version;
u8 rsvd0[6];
u16 func_idx;
u8 state;
u8 rsvd1;
u32 rsvd2;
};
struct hinic_cmd_base_qpn {
u8 status;
u8 version;
......@@ -137,4 +207,11 @@ struct hinic_sq *hinic_hwdev_get_sq(struct hinic_hwdev *hwdev, int i);
struct hinic_rq *hinic_hwdev_get_rq(struct hinic_hwdev *hwdev, int i);
int hinic_hwdev_msix_cnt_set(struct hinic_hwdev *hwdev, u16 msix_index);
int hinic_hwdev_msix_set(struct hinic_hwdev *hwdev, u16 msix_index,
u8 pending_limit, u8 coalesc_timer,
u8 lli_timer_cfg, u8 lli_credit_limit,
u8 resend_timer);
#endif
......@@ -132,6 +132,42 @@ void hinic_set_pf_action(struct hinic_hwif *hwif, enum hinic_pf_action action)
hinic_hwif_write_reg(hwif, HINIC_CSR_FUNC_ATTR5_ADDR, attr5);
}
enum hinic_outbound_state hinic_outbound_state_get(struct hinic_hwif *hwif)
{
u32 attr4 = hinic_hwif_read_reg(hwif, HINIC_CSR_FUNC_ATTR4_ADDR);
return HINIC_FA4_GET(attr4, OUTBOUND_STATE);
}
void hinic_outbound_state_set(struct hinic_hwif *hwif,
enum hinic_outbound_state outbound_state)
{
u32 attr4 = hinic_hwif_read_reg(hwif, HINIC_CSR_FUNC_ATTR4_ADDR);
attr4 = HINIC_FA4_CLEAR(attr4, OUTBOUND_STATE);
attr4 |= HINIC_FA4_SET(outbound_state, OUTBOUND_STATE);
hinic_hwif_write_reg(hwif, HINIC_CSR_FUNC_ATTR4_ADDR, attr4);
}
enum hinic_db_state hinic_db_state_get(struct hinic_hwif *hwif)
{
u32 attr4 = hinic_hwif_read_reg(hwif, HINIC_CSR_FUNC_ATTR4_ADDR);
return HINIC_FA4_GET(attr4, DB_STATE);
}
void hinic_db_state_set(struct hinic_hwif *hwif,
enum hinic_db_state db_state)
{
u32 attr4 = hinic_hwif_read_reg(hwif, HINIC_CSR_FUNC_ATTR4_ADDR);
attr4 = HINIC_FA4_CLEAR(attr4, DB_STATE);
attr4 |= HINIC_FA4_SET(db_state, DB_STATE);
hinic_hwif_write_reg(hwif, HINIC_CSR_FUNC_ATTR4_ADDR, attr4);
}
/**
* hwif_ready - test if the HW is ready for use
* @hwif: the HW interface of a pci function device
......
......@@ -73,6 +73,21 @@
#define HINIC_FA1_GET(val, member) \
(((val) >> HINIC_FA1_##member##_SHIFT) & HINIC_FA1_##member##_MASK)
#define HINIC_FA4_OUTBOUND_STATE_SHIFT 0
#define HINIC_FA4_DB_STATE_SHIFT 1
#define HINIC_FA4_OUTBOUND_STATE_MASK 0x1
#define HINIC_FA4_DB_STATE_MASK 0x1
#define HINIC_FA4_GET(val, member) \
(((val) >> HINIC_FA4_##member##_SHIFT) & HINIC_FA4_##member##_MASK)
#define HINIC_FA4_SET(val, member) \
((((u32)val) & HINIC_FA4_##member##_MASK) << HINIC_FA4_##member##_SHIFT)
#define HINIC_FA4_CLEAR(val, member) \
((val) & (~(HINIC_FA4_##member##_MASK << HINIC_FA4_##member##_SHIFT)))
#define HINIC_FA5_PF_ACTION_SHIFT 0
#define HINIC_FA5_PF_ACTION_MASK 0xFFFF
......@@ -182,6 +197,16 @@ enum hinic_pf_action {
HINIC_PF_MGMT_ACTIVE = 0x11,
};
enum hinic_outbound_state {
HINIC_OUTBOUND_ENABLE = 0,
HINIC_OUTBOUND_DISABLE = 1,
};
enum hinic_db_state {
HINIC_DB_ENABLE = 0,
HINIC_DB_DISABLE = 1,
};
struct hinic_func_attr {
u16 func_idx;
u8 pf_idx;
......@@ -230,6 +255,16 @@ int hinic_msix_attr_cnt_clear(struct hinic_hwif *hwif, u16 msix_index);
void hinic_set_pf_action(struct hinic_hwif *hwif, enum hinic_pf_action action);
enum hinic_outbound_state hinic_outbound_state_get(struct hinic_hwif *hwif);
void hinic_outbound_state_set(struct hinic_hwif *hwif,
enum hinic_outbound_state outbound_state);
enum hinic_db_state hinic_db_state_get(struct hinic_hwif *hwif);
void hinic_db_state_set(struct hinic_hwif *hwif,
enum hinic_db_state db_state);
int hinic_init_hwif(struct hinic_hwif *hwif, struct pci_dev *pdev);
void hinic_free_hwif(struct hinic_hwif *hwif);
......
......@@ -69,8 +69,21 @@ enum hinic_cfg_cmd {
};
enum hinic_comm_cmd {
HINIC_COMM_CMD_IO_STATUS_GET = 0x3,
HINIC_COMM_CMD_CMDQ_CTXT_SET = 0x10,
HINIC_COMM_CMD_CMDQ_CTXT_GET = 0x11,
HINIC_COMM_CMD_HWCTXT_SET = 0x12,
HINIC_COMM_CMD_HWCTXT_GET = 0x13,
HINIC_COMM_CMD_SQ_HI_CI_SET = 0x14,
HINIC_COMM_CMD_RES_STATE_SET = 0x24,
HINIC_COMM_CMD_IO_RES_CLEAR = 0x29,
HINIC_COMM_CMD_MAX = 0x32,
};
enum hinic_mgmt_cb_state {
......
......@@ -22,10 +22,13 @@
#include <linux/errno.h>
#include <linux/sizes.h>
#include <linux/atomic.h>
#include <linux/skbuff.h>
#include <asm/barrier.h>
#include <asm/byteorder.h>
#include "hinic_common.h"
#include "hinic_hw_if.h"
#include "hinic_hw_wqe.h"
#include "hinic_hw_wq.h"
#include "hinic_hw_qp_ctxt.h"
#include "hinic_hw_qp.h"
......@@ -51,6 +54,13 @@
(max_sqs + (q_id)) * Q_CTXT_SIZE)
#define SIZE_16BYTES(size) (ALIGN(size, 16) >> 4)
#define SIZE_8BYTES(size) (ALIGN(size, 8) >> 3)
#define RQ_MASKED_IDX(rq, idx) ((idx) & (rq)->wq->mask)
enum rq_completion_fmt {
RQ_COMPLETE_SGE = 1
};
void hinic_qp_prepare_header(struct hinic_qp_ctxt_header *qp_ctxt_hdr,
enum hinic_qp_ctxt_type ctxt_type,
......@@ -424,3 +434,201 @@ void hinic_clean_rq(struct hinic_rq *rq)
free_rq_cqe(rq);
free_rq_skb_arr(rq);
}
/**
* hinic_get_rq_free_wqebbs - return number of free wqebbs for use
* @rq: recv queue
*
* Return number of free wqebbs
**/
int hinic_get_rq_free_wqebbs(struct hinic_rq *rq)
{
struct hinic_wq *wq = rq->wq;
return atomic_read(&wq->delta) - 1;
}
/**
* hinic_rq_get_wqe - get wqe ptr in the current pi and update the pi
* @rq: rq to get wqe from
* @wqe_size: wqe size
* @prod_idx: returned pi
*
* Return wqe pointer
**/
struct hinic_rq_wqe *hinic_rq_get_wqe(struct hinic_rq *rq,
unsigned int wqe_size, u16 *prod_idx)
{
struct hinic_hw_wqe *hw_wqe = hinic_get_wqe(rq->wq, wqe_size,
prod_idx);
if (IS_ERR(hw_wqe))
return NULL;
return &hw_wqe->rq_wqe;
}
/**
* hinic_rq_write_wqe - write the wqe to the rq
* @rq: recv queue
* @prod_idx: pi of the wqe
* @rq_wqe: the wqe to write
* @skb: skb to save
**/
void hinic_rq_write_wqe(struct hinic_rq *rq, u16 prod_idx,
struct hinic_rq_wqe *rq_wqe, struct sk_buff *skb)
{
struct hinic_hw_wqe *hw_wqe = (struct hinic_hw_wqe *)rq_wqe;
rq->saved_skb[prod_idx] = skb;
/* The data in the HW should be in Big Endian Format */
hinic_cpu_to_be32(rq_wqe, sizeof(*rq_wqe));
hinic_write_wqe(rq->wq, hw_wqe, sizeof(*rq_wqe));
}
/**
* hinic_rq_read_wqe - read wqe ptr in the current ci and update the ci
* @rq: recv queue
* @wqe_size: the size of the wqe
* @skb: return saved skb
* @cons_idx: consumer index of the wqe
*
* Return wqe in ci position
**/
struct hinic_rq_wqe *hinic_rq_read_wqe(struct hinic_rq *rq,
unsigned int wqe_size,
struct sk_buff **skb, u16 *cons_idx)
{
struct hinic_hw_wqe *hw_wqe;
struct hinic_rq_cqe *cqe;
int rx_done;
u32 status;
hw_wqe = hinic_read_wqe(rq->wq, wqe_size, cons_idx);
if (IS_ERR(hw_wqe))
return NULL;
cqe = rq->cqe[*cons_idx];
status = be32_to_cpu(cqe->status);
rx_done = HINIC_RQ_CQE_STATUS_GET(status, RXDONE);
if (!rx_done)
return NULL;
*skb = rq->saved_skb[*cons_idx];
return &hw_wqe->rq_wqe;
}
/**
* hinic_rq_read_next_wqe - increment ci and read the wqe in ci position
* @rq: recv queue
* @wqe_size: the size of the wqe
* @skb: return saved skb
* @cons_idx: consumer index in the wq
*
* Return wqe in incremented ci position
**/
struct hinic_rq_wqe *hinic_rq_read_next_wqe(struct hinic_rq *rq,
unsigned int wqe_size,
struct sk_buff **skb,
u16 *cons_idx)
{
struct hinic_wq *wq = rq->wq;
struct hinic_hw_wqe *hw_wqe;
unsigned int num_wqebbs;
wqe_size = ALIGN(wqe_size, wq->wqebb_size);
num_wqebbs = wqe_size / wq->wqebb_size;
*cons_idx = RQ_MASKED_IDX(rq, *cons_idx + num_wqebbs);
*skb = rq->saved_skb[*cons_idx];
hw_wqe = hinic_read_wqe_direct(wq, *cons_idx);
return &hw_wqe->rq_wqe;
}
/**
* hinic_put_wqe - release the ci for new wqes
* @rq: recv queue
* @cons_idx: consumer index of the wqe
* @wqe_size: the size of the wqe
**/
void hinic_rq_put_wqe(struct hinic_rq *rq, u16 cons_idx,
unsigned int wqe_size)
{
struct hinic_rq_cqe *cqe = rq->cqe[cons_idx];
u32 status = be32_to_cpu(cqe->status);
status = HINIC_RQ_CQE_STATUS_CLEAR(status, RXDONE);
/* Rx WQE size is 1 WQEBB, no wq shadow*/
cqe->status = cpu_to_be32(status);
wmb(); /* clear done flag */
hinic_put_wqe(rq->wq, wqe_size);
}
/**
* hinic_rq_get_sge - get sge from the wqe
* @rq: recv queue
* @rq_wqe: wqe to get the sge from its buf address
* @cons_idx: consumer index
* @sge: returned sge
**/
void hinic_rq_get_sge(struct hinic_rq *rq, struct hinic_rq_wqe *rq_wqe,
u16 cons_idx, struct hinic_sge *sge)
{
struct hinic_rq_cqe *cqe = rq->cqe[cons_idx];
u32 len = be32_to_cpu(cqe->len);
sge->hi_addr = be32_to_cpu(rq_wqe->buf_desc.hi_addr);
sge->lo_addr = be32_to_cpu(rq_wqe->buf_desc.lo_addr);
sge->len = HINIC_RQ_CQE_SGE_GET(len, LEN);
}
/**
* hinic_rq_prepare_wqe - prepare wqe before insert to the queue
* @rq: recv queue
* @prod_idx: pi value
* @rq_wqe: the wqe
* @sge: sge for use by the wqe for recv buf address
**/
void hinic_rq_prepare_wqe(struct hinic_rq *rq, u16 prod_idx,
struct hinic_rq_wqe *rq_wqe, struct hinic_sge *sge)
{
struct hinic_rq_cqe_sect *cqe_sect = &rq_wqe->cqe_sect;
struct hinic_rq_bufdesc *buf_desc = &rq_wqe->buf_desc;
struct hinic_rq_cqe *cqe = rq->cqe[prod_idx];
struct hinic_rq_ctrl *ctrl = &rq_wqe->ctrl;
dma_addr_t cqe_dma = rq->cqe_dma[prod_idx];
ctrl->ctrl_info =
HINIC_RQ_CTRL_SET(SIZE_8BYTES(sizeof(*ctrl)), LEN) |
HINIC_RQ_CTRL_SET(SIZE_8BYTES(sizeof(*cqe_sect)),
COMPLETE_LEN) |
HINIC_RQ_CTRL_SET(SIZE_8BYTES(sizeof(*buf_desc)),
BUFDESC_SECT_LEN) |
HINIC_RQ_CTRL_SET(RQ_COMPLETE_SGE, COMPLETE_FORMAT);
hinic_set_sge(&cqe_sect->sge, cqe_dma, sizeof(*cqe));
buf_desc->hi_addr = sge->hi_addr;
buf_desc->lo_addr = sge->lo_addr;
}
/**
* hinic_rq_update - update pi of the rq
* @rq: recv queue
* @prod_idx: pi value
**/
void hinic_rq_update(struct hinic_rq *rq, u16 prod_idx)
{
*rq->pi_virt_addr = cpu_to_be16(RQ_MASKED_IDX(rq, prod_idx + 1));
}
......@@ -21,6 +21,7 @@
#include <linux/pci.h>
#include <linux/skbuff.h>
#include "hinic_common.h"
#include "hinic_hw_if.h"
#include "hinic_hw_wqe.h"
#include "hinic_hw_wq.h"
......@@ -100,4 +101,32 @@ int hinic_init_rq(struct hinic_rq *rq, struct hinic_hwif *hwif,
void hinic_clean_rq(struct hinic_rq *rq);
int hinic_get_rq_free_wqebbs(struct hinic_rq *rq);
struct hinic_rq_wqe *hinic_rq_get_wqe(struct hinic_rq *rq,
unsigned int wqe_size, u16 *prod_idx);
void hinic_rq_write_wqe(struct hinic_rq *rq, u16 prod_idx,
struct hinic_rq_wqe *wqe, struct sk_buff *skb);
struct hinic_rq_wqe *hinic_rq_read_wqe(struct hinic_rq *rq,
unsigned int wqe_size,
struct sk_buff **skb, u16 *cons_idx);
struct hinic_rq_wqe *hinic_rq_read_next_wqe(struct hinic_rq *rq,
unsigned int wqe_size,
struct sk_buff **skb,
u16 *cons_idx);
void hinic_rq_put_wqe(struct hinic_rq *rq, u16 cons_idx,
unsigned int wqe_size);
void hinic_rq_get_sge(struct hinic_rq *rq, struct hinic_rq_wqe *wqe,
u16 cons_idx, struct hinic_sge *sge);
void hinic_rq_prepare_wqe(struct hinic_rq *rq, u16 prod_idx,
struct hinic_rq_wqe *wqe, struct hinic_sge *sge);
void hinic_rq_update(struct hinic_rq *rq, u16 prod_idx);
#endif
......@@ -826,6 +826,18 @@ struct hinic_hw_wqe *hinic_read_wqe(struct hinic_wq *wq, unsigned int wqe_size,
return WQ_PAGE_ADDR(wq, *cons_idx) + WQE_PAGE_OFF(wq, *cons_idx);
}
/**
* hinic_read_wqe_direct - read wqe directly from ci position
* @wq: wq
* @cons_idx: ci position
*
* Return wqe
**/
struct hinic_hw_wqe *hinic_read_wqe_direct(struct hinic_wq *wq, u16 cons_idx)
{
return WQ_PAGE_ADDR(wq, cons_idx) + WQE_PAGE_OFF(wq, cons_idx);
}
/**
* wqe_shadow - check if a wqe is shadow
* @wq: wq of the wqe
......
......@@ -109,6 +109,8 @@ void hinic_put_wqe(struct hinic_wq *wq, unsigned int wqe_size);
struct hinic_hw_wqe *hinic_read_wqe(struct hinic_wq *wq, unsigned int wqe_size,
u16 *cons_idx);
struct hinic_hw_wqe *hinic_read_wqe_direct(struct hinic_wq *wq, u16 cons_idx);
void hinic_write_wqe(struct hinic_wq *wq, struct hinic_hw_wqe *wqe,
unsigned int wqe_size);
......
......@@ -15,6 +15,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/errno.h>
......@@ -42,6 +43,10 @@ MODULE_AUTHOR("Huawei Technologies CO., Ltd");
MODULE_DESCRIPTION("Huawei Intelligent NIC driver");
MODULE_LICENSE("GPL");
static unsigned int rx_weight = 64;
module_param(rx_weight, uint, 0644);
MODULE_PARM_DESC(rx_weight, "Number Rx packets for NAPI budget (default=64)");
#define PCI_DEVICE_ID_HI1822_PF 0x1822
#define HINIC_WQ_NAME "hinic_dev"
......@@ -220,6 +225,13 @@ static int hinic_open(struct net_device *netdev)
goto err_port_state;
}
err = hinic_port_set_func_state(nic_dev, HINIC_FUNC_PORT_ENABLE);
if (err) {
netif_err(nic_dev, drv, netdev,
"Failed to set func port state\n");
goto err_func_port_state;
}
/* Wait up to 3 sec between port enable to link state */
msleep(3000);
......@@ -250,6 +262,12 @@ static int hinic_open(struct net_device *netdev)
err_port_link:
up(&nic_dev->mgmt_lock);
ret = hinic_port_set_func_state(nic_dev, HINIC_FUNC_PORT_DISABLE);
if (ret)
netif_warn(nic_dev, drv, netdev,
"Failed to revert func port state\n");
err_func_port_state:
ret = hinic_port_set_state(nic_dev, HINIC_PORT_DISABLE);
if (ret)
netif_warn(nic_dev, drv, netdev,
......@@ -283,6 +301,14 @@ static int hinic_close(struct net_device *netdev)
up(&nic_dev->mgmt_lock);
err = hinic_port_set_func_state(nic_dev, HINIC_FUNC_PORT_DISABLE);
if (err) {
netif_err(nic_dev, drv, netdev,
"Failed to set func port state\n");
nic_dev->flags |= (flags & HINIC_INTF_UP);
return err;
}
err = hinic_port_set_state(nic_dev, HINIC_PORT_DISABLE);
if (err) {
netif_err(nic_dev, drv, netdev, "Failed to set port state\n");
......@@ -664,6 +690,7 @@ static int nic_dev_init(struct pci_dev *pdev)
nic_dev->flags = 0;
nic_dev->txqs = NULL;
nic_dev->rxqs = NULL;
nic_dev->rx_weight = rx_weight;
sema_init(&nic_dev->mgmt_lock, 1);
......
......@@ -314,3 +314,35 @@ int hinic_port_set_state(struct hinic_dev *nic_dev, enum hinic_port_state state)
return 0;
}
/**
* hinic_port_set_func_state- set func device state
* @nic_dev: nic device
* @state: the state to set
*
* Return 0 - Success, negative - Failure
**/
int hinic_port_set_func_state(struct hinic_dev *nic_dev,
enum hinic_func_port_state state)
{
struct hinic_port_func_state_cmd func_state;
struct hinic_hwdev *hwdev = nic_dev->hwdev;
struct hinic_hwif *hwif = hwdev->hwif;
struct pci_dev *pdev = hwif->pdev;
u16 out_size;
int err;
func_state.func_idx = HINIC_HWIF_FUNC_IDX(hwif);
func_state.state = state;
err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_SET_FUNC_STATE,
&func_state, sizeof(func_state),
&func_state, &out_size);
if (err || (out_size != sizeof(func_state)) || func_state.status) {
dev_err(&pdev->dev, "Failed to set port func state, ret = %d\n",
func_state.status);
return -EFAULT;
}
return 0;
}
......@@ -40,6 +40,11 @@ enum hinic_port_state {
HINIC_PORT_ENABLE = 3,
};
enum hinic_func_port_state {
HINIC_FUNC_PORT_DISABLE = 0,
HINIC_FUNC_PORT_ENABLE = 2,
};
struct hinic_port_mac_cmd {
u8 status;
u8 version;
......@@ -109,6 +114,17 @@ struct hinic_port_link_status {
u8 rsvd2;
};
struct hinic_port_func_state_cmd {
u8 status;
u8 version;
u8 rsvd0[6];
u16 func_idx;
u16 rsvd1;
u8 state;
u8 rsvd2[3];
};
int hinic_port_add_mac(struct hinic_dev *nic_dev, const u8 *addr,
u16 vlan_id);
......@@ -131,4 +147,7 @@ int hinic_port_link_state(struct hinic_dev *nic_dev,
int hinic_port_set_state(struct hinic_dev *nic_dev,
enum hinic_port_state state);
int hinic_port_set_func_state(struct hinic_dev *nic_dev,
enum hinic_func_port_state state);
#endif
......@@ -19,6 +19,7 @@
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/u64_stats_sync.h>
#include <linux/interrupt.h>
#include "hinic_hw_qp.h"
......@@ -34,6 +35,12 @@ struct hinic_rxq {
struct hinic_rq *rq;
struct hinic_rxq_stats rxq_stats;
char *irq_name;
struct tasklet_struct rx_task;
struct napi_struct napi;
};
void hinic_rxq_clean_stats(struct hinic_rxq *rxq);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment