|
|
1632d2 |
Date: Thu, 18 Jun 2020 19:42:38 -0400
|
|
|
1632d2 |
From: Jonathan Toppins <jtoppins@redhat.com>
|
|
|
1632d2 |
To: rhkernel-list@redhat.com
|
|
|
1632d2 |
Cc: darcari@redhat.com, nhorman@redhat.com, linville@redhat.com
|
|
|
1632d2 |
Subject: [PATCH RHEL-8.3 03/16] ionic: support longer tx sg lists
|
|
|
1632d2 |
|
|
|
1632d2 |
The version 1 Tx queues can use longer SG lists than the
|
|
|
1632d2 |
original version 0 queues, but we need to check to see if the
|
|
|
1632d2 |
firmware supports the v1 Tx queues. This implements the queue
|
|
|
1632d2 |
type query for all queue types, and uses the information to
|
|
|
1632d2 |
set up for using the longer Tx SG lists.
|
|
|
1632d2 |
|
|
|
1632d2 |
Because the Tx SG list can be longer, we need to limit the
|
|
|
1632d2 |
max ring length to be sure we stay inside the boundaries of a
|
|
|
1632d2 |
DMA allocation max size, so we lower the max Tx ring size.
|
|
|
1632d2 |
|
|
|
1632d2 |
The driver sets its highest known version in the Q_IDENTITY
|
|
|
1632d2 |
command, and the FW returns the highest version that it knows,
|
|
|
1632d2 |
bounded by the driver's version. The negotiated version number
|
|
|
1632d2 |
is later used in the Q_INIT commands.
|
|
|
1632d2 |
|
|
|
1632d2 |
Signed-off-by: Shannon Nelson <snelson@pensando.io>
|
|
|
1632d2 |
Signed-off-by: David S. Miller <davem@davemloft.net>
|
|
|
1632d2 |
(cherry picked from commit 5b3f3f2a71ed1cecf6fcf9e8c858a89589415449)
|
|
|
1632d2 |
Bugzilla: 1848149
|
|
|
1632d2 |
Build Info: https://brewweb.engineering.redhat.com/brew/taskinfo?taskID=29498383
|
|
|
1632d2 |
Tested: QE tested devel kernel as well as the partner
|
|
|
1632d2 |
Signed-off-by: Jonathan Toppins <jtoppins@redhat.com>
|
|
|
1632d2 |
---
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_dev.c | 14 +++
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_dev.h | 7 +-
|
|
|
1632d2 |
.../net/ethernet/pensando/ionic/ionic_ethtool.c | 4 +-
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_if.h | 112 +++++++++++++++++---
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_lif.c | 114 ++++++++++++++++++++-
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_lif.h | 13 +++
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_main.c | 2 +
|
|
|
1632d2 |
drivers/net/ethernet/pensando/ionic/ionic_txrx.c | 27 +++--
|
|
|
1632d2 |
8 files changed, 263 insertions(+), 30 deletions(-)
|
|
|
1632d2 |
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_dev.c b/drivers/net/ethernet/pensando/ionic/ionic_dev.c
|
|
|
1632d2 |
index f4ae40ae1e53..d83eff0ae0ac 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_dev.c
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_dev.c
|
|
|
1632d2 |
@@ -388,6 +388,19 @@ int ionic_set_vf_config(struct ionic *ionic, int vf, u8 attr, u8 *data)
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
/* LIF commands */
|
|
|
1632d2 |
+void ionic_dev_cmd_queue_identify(struct ionic_dev *idev,
|
|
|
1632d2 |
+ u16 lif_type, u8 qtype, u8 qver)
|
|
|
1632d2 |
+{
|
|
|
1632d2 |
+ union ionic_dev_cmd cmd = {
|
|
|
1632d2 |
+ .q_identify.opcode = IONIC_CMD_Q_IDENTIFY,
|
|
|
1632d2 |
+ .q_identify.lif_type = lif_type,
|
|
|
1632d2 |
+ .q_identify.type = qtype,
|
|
|
1632d2 |
+ .q_identify.ver = qver,
|
|
|
1632d2 |
+ };
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ ionic_dev_cmd_go(idev, &cmd);
|
|
|
1632d2 |
+}
|
|
|
1632d2 |
+
|
|
|
1632d2 |
void ionic_dev_cmd_lif_identify(struct ionic_dev *idev, u8 type, u8 ver)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
union ionic_dev_cmd cmd = {
|
|
|
1632d2 |
@@ -431,6 +444,7 @@ void ionic_dev_cmd_adminq_init(struct ionic_dev *idev, struct ionic_qcq *qcq,
|
|
|
1632d2 |
.q_init.opcode = IONIC_CMD_Q_INIT,
|
|
|
1632d2 |
.q_init.lif_index = cpu_to_le16(lif_index),
|
|
|
1632d2 |
.q_init.type = q->type,
|
|
|
1632d2 |
+ .q_init.ver = qcq->q.lif->qtype_info[q->type].version,
|
|
|
1632d2 |
.q_init.index = cpu_to_le32(q->index),
|
|
|
1632d2 |
.q_init.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
|
1632d2 |
IONIC_QINIT_F_ENA),
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_dev.h b/drivers/net/ethernet/pensando/ionic/ionic_dev.h
|
|
|
1632d2 |
index 587398b01997..33519a8765eb 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_dev.h
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_dev.h
|
|
|
1632d2 |
@@ -12,7 +12,8 @@
|
|
|
1632d2 |
|
|
|
1632d2 |
#define IONIC_MIN_MTU ETH_MIN_MTU
|
|
|
1632d2 |
#define IONIC_MAX_MTU 9194
|
|
|
1632d2 |
-#define IONIC_MAX_TXRX_DESC 16384
|
|
|
1632d2 |
+#define IONIC_MAX_TX_DESC 8192
|
|
|
1632d2 |
+#define IONIC_MAX_RX_DESC 16384
|
|
|
1632d2 |
#define IONIC_MIN_TXRX_DESC 16
|
|
|
1632d2 |
#define IONIC_DEF_TXRX_DESC 4096
|
|
|
1632d2 |
#define IONIC_LIFS_MAX 1024
|
|
|
1632d2 |
@@ -83,6 +84,8 @@ static_assert(sizeof(struct ionic_q_init_cmd) == 64);
|
|
|
1632d2 |
static_assert(sizeof(struct ionic_q_init_comp) == 16);
|
|
|
1632d2 |
static_assert(sizeof(struct ionic_q_control_cmd) == 64);
|
|
|
1632d2 |
static_assert(sizeof(ionic_q_control_comp) == 16);
|
|
|
1632d2 |
+static_assert(sizeof(struct ionic_q_identify_cmd) == 64);
|
|
|
1632d2 |
+static_assert(sizeof(struct ionic_q_identify_comp) == 16);
|
|
|
1632d2 |
|
|
|
1632d2 |
static_assert(sizeof(struct ionic_rx_mode_set_cmd) == 64);
|
|
|
1632d2 |
static_assert(sizeof(ionic_rx_mode_set_comp) == 16);
|
|
|
1632d2 |
@@ -283,6 +286,8 @@ void ionic_dev_cmd_port_fec(struct ionic_dev *idev, u8 fec_type);
|
|
|
1632d2 |
void ionic_dev_cmd_port_pause(struct ionic_dev *idev, u8 pause_type);
|
|
|
1632d2 |
|
|
|
1632d2 |
int ionic_set_vf_config(struct ionic *ionic, int vf, u8 attr, u8 *data);
|
|
|
1632d2 |
+void ionic_dev_cmd_queue_identify(struct ionic_dev *idev,
|
|
|
1632d2 |
+ u16 lif_type, u8 qtype, u8 qver);
|
|
|
1632d2 |
void ionic_dev_cmd_lif_identify(struct ionic_dev *idev, u8 type, u8 ver);
|
|
|
1632d2 |
void ionic_dev_cmd_lif_init(struct ionic_dev *idev, u16 lif_index,
|
|
|
1632d2 |
dma_addr_t addr);
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
|
|
|
1632d2 |
index 6996229facfd..3f9a73aaef61 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
|
|
|
1632d2 |
@@ -458,9 +458,9 @@ static void ionic_get_ringparam(struct net_device *netdev,
|
|
|
1632d2 |
{
|
|
|
1632d2 |
struct ionic_lif *lif = netdev_priv(netdev);
|
|
|
1632d2 |
|
|
|
1632d2 |
- ring->tx_max_pending = IONIC_MAX_TXRX_DESC;
|
|
|
1632d2 |
+ ring->tx_max_pending = IONIC_MAX_TX_DESC;
|
|
|
1632d2 |
ring->tx_pending = lif->ntxq_descs;
|
|
|
1632d2 |
- ring->rx_max_pending = IONIC_MAX_TXRX_DESC;
|
|
|
1632d2 |
+ ring->rx_max_pending = IONIC_MAX_RX_DESC;
|
|
|
1632d2 |
ring->rx_pending = lif->nrxq_descs;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_if.h b/drivers/net/ethernet/pensando/ionic/ionic_if.h
|
|
|
1632d2 |
index 2804a24a9d73..7b9ec07db363 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_if.h
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_if.h
|
|
|
1632d2 |
@@ -40,6 +40,7 @@ enum ionic_cmd_opcode {
|
|
|
1632d2 |
IONIC_CMD_RX_FILTER_DEL = 32,
|
|
|
1632d2 |
|
|
|
1632d2 |
/* Queue commands */
|
|
|
1632d2 |
+ IONIC_CMD_Q_IDENTIFY = 39,
|
|
|
1632d2 |
IONIC_CMD_Q_INIT = 40,
|
|
|
1632d2 |
IONIC_CMD_Q_CONTROL = 41,
|
|
|
1632d2 |
|
|
|
1632d2 |
@@ -469,6 +470,66 @@ struct ionic_lif_init_comp {
|
|
|
1632d2 |
u8 rsvd2[12];
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
+ /**
|
|
|
1632d2 |
+ * struct ionic_q_identify_cmd - queue identify command
|
|
|
1632d2 |
+ * @opcode: opcode
|
|
|
1632d2 |
+ * @lif_type: LIF type (enum ionic_lif_type)
|
|
|
1632d2 |
+ * @type: Logical queue type (enum ionic_logical_qtype)
|
|
|
1632d2 |
+ * @ver: Highest queue type version that the driver supports
|
|
|
1632d2 |
+ */
|
|
|
1632d2 |
+struct ionic_q_identify_cmd {
|
|
|
1632d2 |
+ u8 opcode;
|
|
|
1632d2 |
+ u8 rsvd;
|
|
|
1632d2 |
+ __le16 lif_type;
|
|
|
1632d2 |
+ u8 type;
|
|
|
1632d2 |
+ u8 ver;
|
|
|
1632d2 |
+ u8 rsvd2[58];
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+/**
|
|
|
1632d2 |
+ * struct ionic_q_identify_comp - queue identify command completion
|
|
|
1632d2 |
+ * @status: Status of the command (enum ionic_status_code)
|
|
|
1632d2 |
+ * @comp_index: Index in the descriptor ring for which this is the completion
|
|
|
1632d2 |
+ * @ver: Queue type version that can be used with FW
|
|
|
1632d2 |
+ */
|
|
|
1632d2 |
+struct ionic_q_identify_comp {
|
|
|
1632d2 |
+ u8 status;
|
|
|
1632d2 |
+ u8 rsvd;
|
|
|
1632d2 |
+ __le16 comp_index;
|
|
|
1632d2 |
+ u8 ver;
|
|
|
1632d2 |
+ u8 rsvd2[11];
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+/**
|
|
|
1632d2 |
+ * union ionic_q_identity - queue identity information
|
|
|
1632d2 |
+ * @version: Queue type version that can be used with FW
|
|
|
1632d2 |
+ * @supported: Bitfield of queue versions, first bit = ver 0
|
|
|
1632d2 |
+ * @features: Queue features
|
|
|
1632d2 |
+ * @desc_sz: Descriptor size
|
|
|
1632d2 |
+ * @comp_sz: Completion descriptor size
|
|
|
1632d2 |
+ * @sg_desc_sz: Scatter/Gather descriptor size
|
|
|
1632d2 |
+ * @max_sg_elems: Maximum number of Scatter/Gather elements
|
|
|
1632d2 |
+ * @sg_desc_stride: Number of Scatter/Gather elements per descriptor
|
|
|
1632d2 |
+ */
|
|
|
1632d2 |
+union ionic_q_identity {
|
|
|
1632d2 |
+ struct {
|
|
|
1632d2 |
+ u8 version;
|
|
|
1632d2 |
+ u8 supported;
|
|
|
1632d2 |
+ u8 rsvd[6];
|
|
|
1632d2 |
+#define IONIC_QIDENT_F_CQ 0x01 /* queue has completion ring */
|
|
|
1632d2 |
+#define IONIC_QIDENT_F_SG 0x02 /* queue has scatter/gather ring */
|
|
|
1632d2 |
+#define IONIC_QIDENT_F_EQ 0x04 /* queue can use event queue */
|
|
|
1632d2 |
+#define IONIC_QIDENT_F_CMB 0x08 /* queue is in cmb bar */
|
|
|
1632d2 |
+ __le64 features;
|
|
|
1632d2 |
+ __le16 desc_sz;
|
|
|
1632d2 |
+ __le16 comp_sz;
|
|
|
1632d2 |
+ __le16 sg_desc_sz;
|
|
|
1632d2 |
+ __le16 max_sg_elems;
|
|
|
1632d2 |
+ __le16 sg_desc_stride;
|
|
|
1632d2 |
+ };
|
|
|
1632d2 |
+ __le32 words[478];
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
/**
|
|
|
1632d2 |
* struct ionic_q_init_cmd - Queue init command
|
|
|
1632d2 |
* @opcode: opcode
|
|
|
1632d2 |
@@ -733,20 +794,31 @@ static inline void decode_txq_desc_cmd(u64 cmd, u8 *opcode, u8 *flags,
|
|
|
1632d2 |
*addr = (cmd >> IONIC_TXQ_DESC_ADDR_SHIFT) & IONIC_TXQ_DESC_ADDR_MASK;
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
-#define IONIC_TX_MAX_SG_ELEMS 8
|
|
|
1632d2 |
-#define IONIC_RX_MAX_SG_ELEMS 8
|
|
|
1632d2 |
-
|
|
|
1632d2 |
/**
|
|
|
1632d2 |
- * struct ionic_txq_sg_desc - Transmit scatter-gather (SG) list
|
|
|
1632d2 |
+ * struct ionic_txq_sg_elem - Transmit scatter-gather (SG) descriptor element
|
|
|
1632d2 |
* @addr: DMA address of SG element data buffer
|
|
|
1632d2 |
* @len: Length of SG element data buffer, in bytes
|
|
|
1632d2 |
*/
|
|
|
1632d2 |
+struct ionic_txq_sg_elem {
|
|
|
1632d2 |
+ __le64 addr;
|
|
|
1632d2 |
+ __le16 len;
|
|
|
1632d2 |
+ __le16 rsvd[3];
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+/**
|
|
|
1632d2 |
+ * struct ionic_txq_sg_desc - Transmit scatter-gather (SG) list
|
|
|
1632d2 |
+ * @elems: Scatter-gather elements
|
|
|
1632d2 |
+ */
|
|
|
1632d2 |
struct ionic_txq_sg_desc {
|
|
|
1632d2 |
- struct ionic_txq_sg_elem {
|
|
|
1632d2 |
- __le64 addr;
|
|
|
1632d2 |
- __le16 len;
|
|
|
1632d2 |
- __le16 rsvd[3];
|
|
|
1632d2 |
- } elems[IONIC_TX_MAX_SG_ELEMS];
|
|
|
1632d2 |
+#define IONIC_TX_MAX_SG_ELEMS 8
|
|
|
1632d2 |
+#define IONIC_TX_SG_DESC_STRIDE 8
|
|
|
1632d2 |
+ struct ionic_txq_sg_elem elems[IONIC_TX_MAX_SG_ELEMS];
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+struct ionic_txq_sg_desc_v1 {
|
|
|
1632d2 |
+#define IONIC_TX_MAX_SG_ELEMS_V1 15
|
|
|
1632d2 |
+#define IONIC_TX_SG_DESC_STRIDE_V1 16
|
|
|
1632d2 |
+ struct ionic_txq_sg_elem elems[IONIC_TX_SG_DESC_STRIDE_V1];
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
/**
|
|
|
1632d2 |
@@ -791,16 +863,24 @@ struct ionic_rxq_desc {
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
/**
|
|
|
1632d2 |
- * struct ionic_rxq_sg_desc - Receive scatter-gather (SG) list
|
|
|
1632d2 |
+ * struct ionic_rxq_sg_desc - Receive scatter-gather (SG) descriptor element
|
|
|
1632d2 |
* @addr: DMA address of SG element data buffer
|
|
|
1632d2 |
* @len: Length of SG element data buffer, in bytes
|
|
|
1632d2 |
*/
|
|
|
1632d2 |
+struct ionic_rxq_sg_elem {
|
|
|
1632d2 |
+ __le64 addr;
|
|
|
1632d2 |
+ __le16 len;
|
|
|
1632d2 |
+ __le16 rsvd[3];
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+/**
|
|
|
1632d2 |
+ * struct ionic_rxq_sg_desc - Receive scatter-gather (SG) list
|
|
|
1632d2 |
+ * @elems: Scatter-gather elements
|
|
|
1632d2 |
+ */
|
|
|
1632d2 |
struct ionic_rxq_sg_desc {
|
|
|
1632d2 |
- struct ionic_rxq_sg_elem {
|
|
|
1632d2 |
- __le64 addr;
|
|
|
1632d2 |
- __le16 len;
|
|
|
1632d2 |
- __le16 rsvd[3];
|
|
|
1632d2 |
- } elems[IONIC_RX_MAX_SG_ELEMS];
|
|
|
1632d2 |
+#define IONIC_RX_MAX_SG_ELEMS 8
|
|
|
1632d2 |
+#define IONIC_RX_SG_DESC_STRIDE 8
|
|
|
1632d2 |
+ struct ionic_rxq_sg_elem elems[IONIC_RX_SG_DESC_STRIDE];
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
/**
|
|
|
1632d2 |
@@ -2389,6 +2469,7 @@ union ionic_dev_cmd {
|
|
|
1632d2 |
struct ionic_qos_init_cmd qos_init;
|
|
|
1632d2 |
struct ionic_qos_reset_cmd qos_reset;
|
|
|
1632d2 |
|
|
|
1632d2 |
+ struct ionic_q_identify_cmd q_identify;
|
|
|
1632d2 |
struct ionic_q_init_cmd q_init;
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
@@ -2421,6 +2502,7 @@ union ionic_dev_cmd_comp {
|
|
|
1632d2 |
ionic_qos_init_comp qos_init;
|
|
|
1632d2 |
ionic_qos_reset_comp qos_reset;
|
|
|
1632d2 |
|
|
|
1632d2 |
+ struct ionic_q_identify_comp q_identify;
|
|
|
1632d2 |
struct ionic_q_init_comp q_init;
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.c b/drivers/net/ethernet/pensando/ionic/ionic_lif.c
|
|
|
1632d2 |
index f8a9c1bcffc9..d60ef816604a 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_lif.c
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.c
|
|
|
1632d2 |
@@ -17,6 +17,16 @@
|
|
|
1632d2 |
#include "ionic_ethtool.h"
|
|
|
1632d2 |
#include "ionic_debugfs.h"
|
|
|
1632d2 |
|
|
|
1632d2 |
+/* queuetype support level */
|
|
|
1632d2 |
+static const u8 ionic_qtype_versions[IONIC_QTYPE_MAX] = {
|
|
|
1632d2 |
+ [IONIC_QTYPE_ADMINQ] = 0, /* 0 = Base version with CQ support */
|
|
|
1632d2 |
+ [IONIC_QTYPE_NOTIFYQ] = 0, /* 0 = Base version */
|
|
|
1632d2 |
+ [IONIC_QTYPE_RXQ] = 0, /* 0 = Base version with CQ+SG support */
|
|
|
1632d2 |
+ [IONIC_QTYPE_TXQ] = 1, /* 0 = Base version with CQ+SG support
|
|
|
1632d2 |
+ * 1 = ... with Tx SG version 1
|
|
|
1632d2 |
+ */
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
static void ionic_lif_rx_mode(struct ionic_lif *lif, unsigned int rx_mode);
|
|
|
1632d2 |
static int ionic_lif_addr_add(struct ionic_lif *lif, const u8 *addr);
|
|
|
1632d2 |
static int ionic_lif_addr_del(struct ionic_lif *lif, const u8 *addr);
|
|
|
1632d2 |
@@ -27,6 +37,7 @@ static void ionic_lif_set_netdev_info(struct ionic_lif *lif);
|
|
|
1632d2 |
|
|
|
1632d2 |
static int ionic_start_queues(struct ionic_lif *lif);
|
|
|
1632d2 |
static void ionic_stop_queues(struct ionic_lif *lif);
|
|
|
1632d2 |
+static void ionic_lif_queue_identify(struct ionic_lif *lif);
|
|
|
1632d2 |
|
|
|
1632d2 |
static void ionic_lif_deferred_work(struct work_struct *work)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
@@ -597,6 +608,7 @@ static int ionic_lif_txq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
|
1632d2 |
.opcode = IONIC_CMD_Q_INIT,
|
|
|
1632d2 |
.lif_index = cpu_to_le16(lif->index),
|
|
|
1632d2 |
.type = q->type,
|
|
|
1632d2 |
+ .ver = lif->qtype_info[q->type].version,
|
|
|
1632d2 |
.index = cpu_to_le32(q->index),
|
|
|
1632d2 |
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
|
1632d2 |
IONIC_QINIT_F_SG),
|
|
|
1632d2 |
@@ -614,6 +626,8 @@ static int ionic_lif_txq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
|
1632d2 |
dev_dbg(dev, "txq_init.index %d\n", ctx.cmd.q_init.index);
|
|
|
1632d2 |
dev_dbg(dev, "txq_init.ring_base 0x%llx\n", ctx.cmd.q_init.ring_base);
|
|
|
1632d2 |
dev_dbg(dev, "txq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
|
|
|
1632d2 |
+ dev_dbg(dev, "txq_init.flags 0x%x\n", ctx.cmd.q_init.flags);
|
|
|
1632d2 |
+ dev_dbg(dev, "txq_init.ver %d\n", ctx.cmd.q_init.ver);
|
|
|
1632d2 |
|
|
|
1632d2 |
q->tail = q->info;
|
|
|
1632d2 |
q->head = q->tail;
|
|
|
1632d2 |
@@ -646,6 +660,7 @@ static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
|
1632d2 |
.opcode = IONIC_CMD_Q_INIT,
|
|
|
1632d2 |
.lif_index = cpu_to_le16(lif->index),
|
|
|
1632d2 |
.type = q->type,
|
|
|
1632d2 |
+ .ver = lif->qtype_info[q->type].version,
|
|
|
1632d2 |
.index = cpu_to_le32(q->index),
|
|
|
1632d2 |
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
|
1632d2 |
IONIC_QINIT_F_SG),
|
|
|
1632d2 |
@@ -663,6 +678,8 @@ static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
|
1632d2 |
dev_dbg(dev, "rxq_init.index %d\n", ctx.cmd.q_init.index);
|
|
|
1632d2 |
dev_dbg(dev, "rxq_init.ring_base 0x%llx\n", ctx.cmd.q_init.ring_base);
|
|
|
1632d2 |
dev_dbg(dev, "rxq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
|
|
|
1632d2 |
+ dev_dbg(dev, "rxq_init.flags 0x%x\n", ctx.cmd.q_init.flags);
|
|
|
1632d2 |
+ dev_dbg(dev, "rxq_init.ver %d\n", ctx.cmd.q_init.ver);
|
|
|
1632d2 |
|
|
|
1632d2 |
q->tail = q->info;
|
|
|
1632d2 |
q->head = q->tail;
|
|
|
1632d2 |
@@ -726,7 +743,7 @@ static bool ionic_notifyq_service(struct ionic_cq *cq,
|
|
|
1632d2 |
}
|
|
|
1632d2 |
break;
|
|
|
1632d2 |
default:
|
|
|
1632d2 |
- netdev_warn(netdev, "Notifyq unknown event ecode=%d eid=%lld\n",
|
|
|
1632d2 |
+ netdev_warn(netdev, "Notifyq event ecode=%d eid=%lld\n",
|
|
|
1632d2 |
comp->event.ecode, eid);
|
|
|
1632d2 |
break;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
@@ -1509,17 +1526,25 @@ static void ionic_txrx_free(struct ionic_lif *lif)
|
|
|
1632d2 |
|
|
|
1632d2 |
static int ionic_txrx_alloc(struct ionic_lif *lif)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
+ unsigned int sg_desc_sz;
|
|
|
1632d2 |
unsigned int flags;
|
|
|
1632d2 |
unsigned int i;
|
|
|
1632d2 |
int err = 0;
|
|
|
1632d2 |
|
|
|
1632d2 |
+ if (lif->qtype_info[IONIC_QTYPE_TXQ].version >= 1 &&
|
|
|
1632d2 |
+ lif->qtype_info[IONIC_QTYPE_TXQ].sg_desc_sz ==
|
|
|
1632d2 |
+ sizeof(struct ionic_txq_sg_desc_v1))
|
|
|
1632d2 |
+ sg_desc_sz = sizeof(struct ionic_txq_sg_desc_v1);
|
|
|
1632d2 |
+ else
|
|
|
1632d2 |
+ sg_desc_sz = sizeof(struct ionic_txq_sg_desc);
|
|
|
1632d2 |
+
|
|
|
1632d2 |
flags = IONIC_QCQ_F_TX_STATS | IONIC_QCQ_F_SG;
|
|
|
1632d2 |
for (i = 0; i < lif->nxqs; i++) {
|
|
|
1632d2 |
err = ionic_qcq_alloc(lif, IONIC_QTYPE_TXQ, i, "tx", flags,
|
|
|
1632d2 |
lif->ntxq_descs,
|
|
|
1632d2 |
sizeof(struct ionic_txq_desc),
|
|
|
1632d2 |
sizeof(struct ionic_txq_comp),
|
|
|
1632d2 |
- sizeof(struct ionic_txq_sg_desc),
|
|
|
1632d2 |
+ sg_desc_sz,
|
|
|
1632d2 |
lif->kern_pid, &lif->txqcqs[i].qcq);
|
|
|
1632d2 |
if (err)
|
|
|
1632d2 |
goto err_out;
|
|
|
1632d2 |
@@ -2065,9 +2090,17 @@ int ionic_lifs_alloc(struct ionic *ionic)
|
|
|
1632d2 |
|
|
|
1632d2 |
/* only build the first lif, others are for later features */
|
|
|
1632d2 |
set_bit(0, ionic->lifbits);
|
|
|
1632d2 |
+
|
|
|
1632d2 |
lif = ionic_lif_alloc(ionic, 0);
|
|
|
1632d2 |
+ if (IS_ERR_OR_NULL(lif)) {
|
|
|
1632d2 |
+ clear_bit(0, ionic->lifbits);
|
|
|
1632d2 |
+ return -ENOMEM;
|
|
|
1632d2 |
+ }
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ lif->lif_type = IONIC_LIF_TYPE_CLASSIC;
|
|
|
1632d2 |
+ ionic_lif_queue_identify(lif);
|
|
|
1632d2 |
|
|
|
1632d2 |
- return PTR_ERR_OR_ZERO(lif);
|
|
|
1632d2 |
+ return 0;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
static void ionic_lif_reset(struct ionic_lif *lif)
|
|
|
1632d2 |
@@ -2292,6 +2325,7 @@ static int ionic_lif_notifyq_init(struct ionic_lif *lif)
|
|
|
1632d2 |
.opcode = IONIC_CMD_Q_INIT,
|
|
|
1632d2 |
.lif_index = cpu_to_le16(lif->index),
|
|
|
1632d2 |
.type = q->type,
|
|
|
1632d2 |
+ .ver = lif->qtype_info[q->type].version,
|
|
|
1632d2 |
.index = cpu_to_le32(q->index),
|
|
|
1632d2 |
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
|
1632d2 |
IONIC_QINIT_F_ENA),
|
|
|
1632d2 |
@@ -2578,6 +2612,80 @@ void ionic_lifs_unregister(struct ionic *ionic)
|
|
|
1632d2 |
unregister_netdev(ionic->master_lif->netdev);
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
+static void ionic_lif_queue_identify(struct ionic_lif *lif)
|
|
|
1632d2 |
+{
|
|
|
1632d2 |
+ struct ionic *ionic = lif->ionic;
|
|
|
1632d2 |
+ union ionic_q_identity *q_ident;
|
|
|
1632d2 |
+ struct ionic_dev *idev;
|
|
|
1632d2 |
+ int qtype;
|
|
|
1632d2 |
+ int err;
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ idev = &lif->ionic->idev;
|
|
|
1632d2 |
+ q_ident = (union ionic_q_identity *)&idev->dev_cmd_regs->data;
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ for (qtype = 0; qtype < ARRAY_SIZE(ionic_qtype_versions); qtype++) {
|
|
|
1632d2 |
+ struct ionic_qtype_info *qti = &lif->qtype_info[qtype];
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ /* filter out the ones we know about */
|
|
|
1632d2 |
+ switch (qtype) {
|
|
|
1632d2 |
+ case IONIC_QTYPE_ADMINQ:
|
|
|
1632d2 |
+ case IONIC_QTYPE_NOTIFYQ:
|
|
|
1632d2 |
+ case IONIC_QTYPE_RXQ:
|
|
|
1632d2 |
+ case IONIC_QTYPE_TXQ:
|
|
|
1632d2 |
+ break;
|
|
|
1632d2 |
+ default:
|
|
|
1632d2 |
+ continue;
|
|
|
1632d2 |
+ }
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ memset(qti, 0, sizeof(*qti));
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ mutex_lock(&ionic->dev_cmd_lock);
|
|
|
1632d2 |
+ ionic_dev_cmd_queue_identify(idev, lif->lif_type, qtype,
|
|
|
1632d2 |
+ ionic_qtype_versions[qtype]);
|
|
|
1632d2 |
+ err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT);
|
|
|
1632d2 |
+ if (!err) {
|
|
|
1632d2 |
+ qti->version = q_ident->version;
|
|
|
1632d2 |
+ qti->supported = q_ident->supported;
|
|
|
1632d2 |
+ qti->features = le64_to_cpu(q_ident->features);
|
|
|
1632d2 |
+ qti->desc_sz = le16_to_cpu(q_ident->desc_sz);
|
|
|
1632d2 |
+ qti->comp_sz = le16_to_cpu(q_ident->comp_sz);
|
|
|
1632d2 |
+ qti->sg_desc_sz = le16_to_cpu(q_ident->sg_desc_sz);
|
|
|
1632d2 |
+ qti->max_sg_elems = le16_to_cpu(q_ident->max_sg_elems);
|
|
|
1632d2 |
+ qti->sg_desc_stride = le16_to_cpu(q_ident->sg_desc_stride);
|
|
|
1632d2 |
+ }
|
|
|
1632d2 |
+ mutex_unlock(&ionic->dev_cmd_lock);
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ if (err == -EINVAL) {
|
|
|
1632d2 |
+ dev_err(ionic->dev, "qtype %d not supported\n", qtype);
|
|
|
1632d2 |
+ continue;
|
|
|
1632d2 |
+ } else if (err == -EIO) {
|
|
|
1632d2 |
+ dev_err(ionic->dev, "q_ident failed, not supported on older FW\n");
|
|
|
1632d2 |
+ return;
|
|
|
1632d2 |
+ } else if (err) {
|
|
|
1632d2 |
+ dev_err(ionic->dev, "q_ident failed, qtype %d: %d\n",
|
|
|
1632d2 |
+ qtype, err);
|
|
|
1632d2 |
+ return;
|
|
|
1632d2 |
+ }
|
|
|
1632d2 |
+
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].version = %d\n",
|
|
|
1632d2 |
+ qtype, qti->version);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].supported = 0x%02x\n",
|
|
|
1632d2 |
+ qtype, qti->supported);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].features = 0x%04llx\n",
|
|
|
1632d2 |
+ qtype, qti->features);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].desc_sz = %d\n",
|
|
|
1632d2 |
+ qtype, qti->desc_sz);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].comp_sz = %d\n",
|
|
|
1632d2 |
+ qtype, qti->comp_sz);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].sg_desc_sz = %d\n",
|
|
|
1632d2 |
+ qtype, qti->sg_desc_sz);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].max_sg_elems = %d\n",
|
|
|
1632d2 |
+ qtype, qti->max_sg_elems);
|
|
|
1632d2 |
+ dev_dbg(ionic->dev, " qtype[%d].sg_desc_stride = %d\n",
|
|
|
1632d2 |
+ qtype, qti->sg_desc_stride);
|
|
|
1632d2 |
+ }
|
|
|
1632d2 |
+}
|
|
|
1632d2 |
+
|
|
|
1632d2 |
int ionic_lif_identify(struct ionic *ionic, u8 lif_type,
|
|
|
1632d2 |
union ionic_lif_identity *lid)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.h b/drivers/net/ethernet/pensando/ionic/ionic_lif.h
|
|
|
1632d2 |
index 5d4ffda5c05f..1a30f0fb20b9 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_lif.h
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.h
|
|
|
1632d2 |
@@ -133,6 +133,17 @@ enum ionic_lif_state_flags {
|
|
|
1632d2 |
IONIC_LIF_F_STATE_SIZE
|
|
|
1632d2 |
};
|
|
|
1632d2 |
|
|
|
1632d2 |
+struct ionic_qtype_info {
|
|
|
1632d2 |
+ u8 version;
|
|
|
1632d2 |
+ u8 supported;
|
|
|
1632d2 |
+ u64 features;
|
|
|
1632d2 |
+ u16 desc_sz;
|
|
|
1632d2 |
+ u16 comp_sz;
|
|
|
1632d2 |
+ u16 sg_desc_sz;
|
|
|
1632d2 |
+ u16 max_sg_elems;
|
|
|
1632d2 |
+ u16 sg_desc_stride;
|
|
|
1632d2 |
+};
|
|
|
1632d2 |
+
|
|
|
1632d2 |
#define IONIC_LIF_NAME_MAX_SZ 32
|
|
|
1632d2 |
struct ionic_lif {
|
|
|
1632d2 |
char name[IONIC_LIF_NAME_MAX_SZ];
|
|
|
1632d2 |
@@ -161,11 +172,13 @@ struct ionic_lif {
|
|
|
1632d2 |
bool mc_overflow;
|
|
|
1632d2 |
unsigned int nmcast;
|
|
|
1632d2 |
bool uc_overflow;
|
|
|
1632d2 |
+ u16 lif_type;
|
|
|
1632d2 |
unsigned int nucast;
|
|
|
1632d2 |
|
|
|
1632d2 |
struct ionic_lif_info *info;
|
|
|
1632d2 |
dma_addr_t info_pa;
|
|
|
1632d2 |
u32 info_sz;
|
|
|
1632d2 |
+ struct ionic_qtype_info qtype_info[IONIC_QTYPE_MAX];
|
|
|
1632d2 |
|
|
|
1632d2 |
u16 rss_types;
|
|
|
1632d2 |
u8 rss_hash_key[IONIC_RSS_HASH_KEY_SIZE];
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_main.c b/drivers/net/ethernet/pensando/ionic/ionic_main.c
|
|
|
1632d2 |
index 15911ee1af32..34ccb8f53cda 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_main.c
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_main.c
|
|
|
1632d2 |
@@ -152,6 +152,8 @@ static const char *ionic_opcode_to_str(enum ionic_cmd_opcode opcode)
|
|
|
1632d2 |
return "IONIC_CMD_RX_FILTER_ADD";
|
|
|
1632d2 |
case IONIC_CMD_RX_FILTER_DEL:
|
|
|
1632d2 |
return "IONIC_CMD_RX_FILTER_DEL";
|
|
|
1632d2 |
+ case IONIC_CMD_Q_IDENTIFY:
|
|
|
1632d2 |
+ return "IONIC_CMD_Q_IDENTIFY";
|
|
|
1632d2 |
case IONIC_CMD_Q_INIT:
|
|
|
1632d2 |
return "IONIC_CMD_Q_INIT";
|
|
|
1632d2 |
case IONIC_CMD_Q_CONTROL:
|
|
|
1632d2 |
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c
|
|
|
1632d2 |
index d233b6e77b1e..6b14e55a6780 100644
|
|
|
1632d2 |
--- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c
|
|
|
1632d2 |
+++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c
|
|
|
1632d2 |
@@ -10,8 +10,10 @@
|
|
|
1632d2 |
#include "ionic_lif.h"
|
|
|
1632d2 |
#include "ionic_txrx.h"
|
|
|
1632d2 |
|
|
|
1632d2 |
-static void ionic_rx_clean(struct ionic_queue *q, struct ionic_desc_info *desc_info,
|
|
|
1632d2 |
- struct ionic_cq_info *cq_info, void *cb_arg);
|
|
|
1632d2 |
+static void ionic_rx_clean(struct ionic_queue *q,
|
|
|
1632d2 |
+ struct ionic_desc_info *desc_info,
|
|
|
1632d2 |
+ struct ionic_cq_info *cq_info,
|
|
|
1632d2 |
+ void *cb_arg);
|
|
|
1632d2 |
|
|
|
1632d2 |
static inline void ionic_txq_post(struct ionic_queue *q, bool ring_dbell,
|
|
|
1632d2 |
ionic_desc_cb cb_func, void *cb_arg)
|
|
|
1632d2 |
@@ -140,8 +142,10 @@ static struct sk_buff *ionic_rx_copybreak(struct ionic_queue *q,
|
|
|
1632d2 |
return skb;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
-static void ionic_rx_clean(struct ionic_queue *q, struct ionic_desc_info *desc_info,
|
|
|
1632d2 |
- struct ionic_cq_info *cq_info, void *cb_arg)
|
|
|
1632d2 |
+static void ionic_rx_clean(struct ionic_queue *q,
|
|
|
1632d2 |
+ struct ionic_desc_info *desc_info,
|
|
|
1632d2 |
+ struct ionic_cq_info *cq_info,
|
|
|
1632d2 |
+ void *cb_arg)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
struct ionic_rxq_comp *comp = cq_info->cq_desc;
|
|
|
1632d2 |
struct ionic_qcq *qcq = q_to_qcq(q);
|
|
|
1632d2 |
@@ -475,7 +479,8 @@ int ionic_rx_napi(struct napi_struct *napi, int budget)
|
|
|
1632d2 |
return work_done;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
-static dma_addr_t ionic_tx_map_single(struct ionic_queue *q, void *data, size_t len)
|
|
|
1632d2 |
+static dma_addr_t ionic_tx_map_single(struct ionic_queue *q,
|
|
|
1632d2 |
+ void *data, size_t len)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
struct ionic_tx_stats *stats = q_to_tx_stats(q);
|
|
|
1632d2 |
struct device *dev = q->lif->ionic->dev;
|
|
|
1632d2 |
@@ -491,7 +496,8 @@ static dma_addr_t ionic_tx_map_single(struct ionic_queue *q, void *data, size_t
|
|
|
1632d2 |
return dma_addr;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
-static dma_addr_t ionic_tx_map_frag(struct ionic_queue *q, const skb_frag_t *frag,
|
|
|
1632d2 |
+static dma_addr_t ionic_tx_map_frag(struct ionic_queue *q,
|
|
|
1632d2 |
+ const skb_frag_t *frag,
|
|
|
1632d2 |
size_t offset, size_t len)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
struct ionic_tx_stats *stats = q_to_tx_stats(q);
|
|
|
1632d2 |
@@ -507,8 +513,10 @@ static dma_addr_t ionic_tx_map_frag(struct ionic_queue *q, const skb_frag_t *fra
|
|
|
1632d2 |
return dma_addr;
|
|
|
1632d2 |
}
|
|
|
1632d2 |
|
|
|
1632d2 |
-static void ionic_tx_clean(struct ionic_queue *q, struct ionic_desc_info *desc_info,
|
|
|
1632d2 |
- struct ionic_cq_info *cq_info, void *cb_arg)
|
|
|
1632d2 |
+static void ionic_tx_clean(struct ionic_queue *q,
|
|
|
1632d2 |
+ struct ionic_desc_info *desc_info,
|
|
|
1632d2 |
+ struct ionic_cq_info *cq_info,
|
|
|
1632d2 |
+ void *cb_arg)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
struct ionic_txq_sg_desc *sg_desc = desc_info->sg_desc;
|
|
|
1632d2 |
struct ionic_txq_sg_elem *elem = sg_desc->elems;
|
|
|
1632d2 |
@@ -989,6 +997,7 @@ static int ionic_tx(struct ionic_queue *q, struct sk_buff *skb)
|
|
|
1632d2 |
|
|
|
1632d2 |
static int ionic_tx_descs_needed(struct ionic_queue *q, struct sk_buff *skb)
|
|
|
1632d2 |
{
|
|
|
1632d2 |
+ int sg_elems = q->lif->qtype_info[IONIC_QTYPE_TXQ].max_sg_elems;
|
|
|
1632d2 |
struct ionic_tx_stats *stats = q_to_tx_stats(q);
|
|
|
1632d2 |
int err;
|
|
|
1632d2 |
|
|
|
1632d2 |
@@ -997,7 +1006,7 @@ static int ionic_tx_descs_needed(struct ionic_queue *q, struct sk_buff *skb)
|
|
|
1632d2 |
return (skb->len / skb_shinfo(skb)->gso_size) + 1;
|
|
|
1632d2 |
|
|
|
1632d2 |
/* If non-TSO, just need 1 desc and nr_frags sg elems */
|
|
|
1632d2 |
- if (skb_shinfo(skb)->nr_frags <= IONIC_TX_MAX_SG_ELEMS)
|
|
|
1632d2 |
+ if (skb_shinfo(skb)->nr_frags <= sg_elems)
|
|
|
1632d2 |
return 1;
|
|
|
1632d2 |
|
|
|
1632d2 |
/* Too many frags, so linearize */
|
|
|
1632d2 |
--
|
|
|
1632d2 |
2.16.4
|
|
|
1632d2 |
|
|
|
1632d2 |
|