Commit a31eb63a authored by David S. Miller's avatar David S. Miller

Merge branch 'thunderx-next'

Sunil Goutham says:

====================
net: thunderx: Support for newer chips and miscellaneous patches

This patch series adds support for VNIC on 81xx and 83xx SOCs.
81xx/83xx is different from 88xx in terms of capabilities and new type
of interfaces supported (eg: QSGMII, RGMII) and have DLMs instead of
QLMs which allows single BGX to have interfaces of different LMAC types.

Also included some patches which are common for all 88xx/81xx/83xx
SOCs like using netdev's name while registering irqs, reset receive
queue stats and some changes to use standard API for split buffer Rx
packets, generating RSS key e.t.c

PS: Most of the patches were submitted earlier under different series but
for some reason were not picked up by patchwork. Since new patches have been
added in the meantime, resubmitting all as a new patchset.

Changes from v1:
- Incorporated Yuval Mintz's suggestion to use generic API to set minimum
  queue count i.e by using netif_get_num_default_rss_queues().
- Resolved a compilation issue reported by test robot while compiling
  patch 'Add support for 16 LMACs of 83xx'
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 2ce66f9c 93db2cf8
......@@ -36,10 +36,20 @@ config THUNDER_NIC_BGX
depends on 64BIT
select PHYLIB
select MDIO_THUNDER
select THUNDER_NIC_RGX
---help---
This driver supports programming and controlling of MAC
interface from NIC physical function driver.
config THUNDER_NIC_RGX
tristate "Thunder MAC interface driver (RGX)"
depends on 64BIT
select PHYLIB
select MDIO_THUNDER
---help---
This driver supports configuring XCV block of RGX interface
present on CN81XX chip.
config LIQUIDIO
tristate "Cavium LiquidIO support"
depends on 64BIT
......
......@@ -2,6 +2,7 @@
# Makefile for Cavium's Thunder ethernet device
#
obj-$(CONFIG_THUNDER_NIC_RGX) += thunder_xcv.o
obj-$(CONFIG_THUNDER_NIC_BGX) += thunder_bgx.o
obj-$(CONFIG_THUNDER_NIC_PF) += nicpf.o
obj-$(CONFIG_THUNDER_NIC_VF) += nicvf.o
......
......@@ -20,6 +20,17 @@
#define PCI_DEVICE_ID_THUNDER_NIC_VF 0xA034
#define PCI_DEVICE_ID_THUNDER_BGX 0xA026
/* Subsystem device IDs */
#define PCI_SUBSYS_DEVID_88XX_NIC_PF 0xA11E
#define PCI_SUBSYS_DEVID_81XX_NIC_PF 0xA21E
#define PCI_SUBSYS_DEVID_83XX_NIC_PF 0xA31E
#define PCI_SUBSYS_DEVID_88XX_PASS1_NIC_VF 0xA11E
#define PCI_SUBSYS_DEVID_88XX_NIC_VF 0xA134
#define PCI_SUBSYS_DEVID_81XX_NIC_VF 0xA234
#define PCI_SUBSYS_DEVID_83XX_NIC_VF 0xA334
/* PCI BAR nos */
#define PCI_CFG_REG_BAR_NUM 0
#define PCI_MSIX_REG_BAR_NUM 4
......@@ -41,40 +52,8 @@
/* Max pkinds */
#define NIC_MAX_PKIND 16
/* Rx Channels */
/* Receive channel configuration in TNS bypass mode
* Below is configuration in TNS bypass mode
* BGX0-LMAC0-CHAN0 - VNIC CHAN0
* BGX0-LMAC1-CHAN0 - VNIC CHAN16
* ...
* BGX1-LMAC0-CHAN0 - VNIC CHAN128
* ...
* BGX1-LMAC3-CHAN0 - VNIC CHAN174
*/
#define NIC_INTF_COUNT 2 /* Interfaces btw VNIC and TNS/BGX */
#define NIC_CHANS_PER_INF 128
#define NIC_MAX_CHANS (NIC_INTF_COUNT * NIC_CHANS_PER_INF)
#define NIC_CPI_COUNT 2048 /* No of channel parse indices */
/* TNS bypass mode: 1-1 mapping between VNIC and BGX:LMAC */
#define NIC_MAX_BGX MAX_BGX_PER_CN88XX
#define NIC_CPI_PER_BGX (NIC_CPI_COUNT / NIC_MAX_BGX)
#define NIC_MAX_CPI_PER_LMAC 64 /* Max when CPI_ALG is IP diffserv */
#define NIC_RSSI_PER_BGX (NIC_RSSI_COUNT / NIC_MAX_BGX)
/* Tx scheduling */
#define NIC_MAX_TL4 1024
#define NIC_MAX_TL4_SHAPERS 256 /* 1 shaper for 4 TL4s */
#define NIC_MAX_TL3 256
#define NIC_MAX_TL3_SHAPERS 64 /* 1 shaper for 4 TL3s */
#define NIC_MAX_TL2 64
#define NIC_MAX_TL2_SHAPERS 2 /* 1 shaper for 32 TL2s */
#define NIC_MAX_TL1 2
/* TNS bypass mode */
#define NIC_TL2_PER_BGX 32
#define NIC_TL4_PER_BGX (NIC_MAX_TL4 / NIC_MAX_BGX)
#define NIC_TL4_PER_LMAC (NIC_MAX_TL4 / NIC_CHANS_PER_INF)
/* Max when CPI_ALG is IP diffserv */
#define NIC_MAX_CPI_PER_LMAC 64
/* NIC VF Interrupts */
#define NICVF_INTR_CQ 0
......@@ -148,7 +127,6 @@ struct nicvf_cq_poll {
struct napi_struct napi;
};
#define NIC_RSSI_COUNT 4096 /* Total no of RSS indices */
#define NIC_MAX_RSS_HASH_BITS 8
#define NIC_MAX_RSS_IDR_TBL_SIZE (1 << NIC_MAX_RSS_HASH_BITS)
#define RSS_HASH_KEY_SIZE 5 /* 320 bit key */
......@@ -273,6 +251,7 @@ struct nicvf {
struct net_device *netdev;
struct pci_dev *pdev;
void __iomem *reg_base;
#define MAX_QUEUES_PER_QSET 8
struct queue_set *qs;
struct nicvf_cq_poll *napi[8];
u8 vf_id;
......@@ -368,6 +347,7 @@ struct nicvf {
#define NIC_MBOX_MSG_PNICVF_PTR 0x14 /* Get primary qset nicvf ptr */
#define NIC_MBOX_MSG_SNICVF_PTR 0x15 /* Send sqet nicvf ptr to PVF */
#define NIC_MBOX_MSG_LOOPBACK 0x16 /* Set interface in loopback */
#define NIC_MBOX_MSG_RESET_STAT_COUNTER 0x17 /* Reset statistics counters */
#define NIC_MBOX_MSG_CFG_DONE 0xF0 /* VF configuration done */
#define NIC_MBOX_MSG_SHUTDOWN 0xF1 /* VF is being shutdown */
......@@ -484,6 +464,31 @@ struct set_loopback {
bool enable;
};
/* Reset statistics counters */
struct reset_stat_cfg {
u8 msg;
/* Bitmap to select NIC_PF_VNIC(vf_id)_RX_STAT(0..13) */
u16 rx_stat_mask;
/* Bitmap to select NIC_PF_VNIC(vf_id)_TX_STAT(0..4) */
u8 tx_stat_mask;
/* Bitmap to select NIC_PF_QS(0..127)_RQ(0..7)_STAT(0..1)
* bit14, bit15 NIC_PF_QS(vf_id)_RQ7_STAT(0..1)
* bit12, bit13 NIC_PF_QS(vf_id)_RQ6_STAT(0..1)
* ..
* bit2, bit3 NIC_PF_QS(vf_id)_RQ1_STAT(0..1)
* bit0, bit1 NIC_PF_QS(vf_id)_RQ0_STAT(0..1)
*/
u16 rq_stat_mask;
/* Bitmap to select NIC_PF_QS(0..127)_SQ(0..7)_STAT(0..1)
* bit14, bit15 NIC_PF_QS(vf_id)_SQ7_STAT(0..1)
* bit12, bit13 NIC_PF_QS(vf_id)_SQ6_STAT(0..1)
* ..
* bit2, bit3 NIC_PF_QS(vf_id)_SQ1_STAT(0..1)
* bit0, bit1 NIC_PF_QS(vf_id)_SQ0_STAT(0..1)
*/
u16 sq_stat_mask;
};
/* 128 bit shared memory between PF and each VF */
union nic_mbx {
struct { u8 msg; } msg;
......@@ -501,6 +506,7 @@ union nic_mbx {
struct sqs_alloc sqs_alloc;
struct nicvf_ptr nicvf;
struct set_loopback lbk;
struct reset_stat_cfg reset_stat;
};
#define NIC_NODE_ID_MASK 0x03
......@@ -514,7 +520,14 @@ static inline int nic_get_node_id(struct pci_dev *pdev)
static inline bool pass1_silicon(struct pci_dev *pdev)
{
return pdev->revision < 8;
return (pdev->revision < 8) &&
(pdev->subsystem_device == PCI_SUBSYS_DEVID_88XX_NIC_PF);
}
static inline bool pass2_silicon(struct pci_dev *pdev)
{
return (pdev->revision >= 8) &&
(pdev->subsystem_device == PCI_SUBSYS_DEVID_88XX_NIC_PF);
}
int nicvf_set_real_num_queues(struct net_device *netdev,
......
......@@ -20,8 +20,25 @@
#define DRV_NAME "thunder-nic"
#define DRV_VERSION "1.0"
struct hw_info {
u8 bgx_cnt;
u8 chans_per_lmac;
u8 chans_per_bgx; /* Rx/Tx chans */
u8 chans_per_rgx;
u8 chans_per_lbk;
u16 cpi_cnt;
u16 rssi_cnt;
u16 rss_ind_tbl_size;
u16 tl4_cnt;
u16 tl3_cnt;
u8 tl2_cnt;
u8 tl1_cnt;
bool tl1_per_bgx; /* TL1 per BGX or per LMAC */
};
struct nicpf {
struct pci_dev *pdev;
struct hw_info *hw;
u8 node;
unsigned int flags;
u8 num_vf_en; /* No of VF enabled */
......@@ -36,22 +53,22 @@ struct nicpf {
#define NIC_SET_VF_LMAC_MAP(bgx, lmac) (((bgx & 0xF) << 4) | (lmac & 0xF))
#define NIC_GET_BGX_FROM_VF_LMAC_MAP(map) ((map >> 4) & 0xF)
#define NIC_GET_LMAC_FROM_VF_LMAC_MAP(map) (map & 0xF)
u8 vf_lmac_map[MAX_LMAC];
u8 *vf_lmac_map;
struct delayed_work dwork;
struct workqueue_struct *check_link;
u8 link[MAX_LMAC];
u8 duplex[MAX_LMAC];
u32 speed[MAX_LMAC];
u8 *link;
u8 *duplex;
u32 *speed;
u16 cpi_base[MAX_NUM_VFS_SUPPORTED];
u16 rssi_base[MAX_NUM_VFS_SUPPORTED];
u16 rss_ind_tbl_size;
bool mbx_lock[MAX_NUM_VFS_SUPPORTED];
/* MSI-X */
bool msix_enabled;
u8 num_vec;
struct msix_entry msix_entries[NIC_PF_MSIX_VECTORS];
struct msix_entry *msix_entries;
bool irq_allocated[NIC_PF_MSIX_VECTORS];
char irq_name[NIC_PF_MSIX_VECTORS][20];
};
/* Supported devices */
......@@ -89,9 +106,22 @@ static u64 nic_reg_read(struct nicpf *nic, u64 offset)
/* PF -> VF mailbox communication APIs */
static void nic_enable_mbx_intr(struct nicpf *nic)
{
/* Enable mailbox interrupt for all 128 VFs */
nic_reg_write(nic, NIC_PF_MAILBOX_ENA_W1S, ~0ull);
nic_reg_write(nic, NIC_PF_MAILBOX_ENA_W1S + sizeof(u64), ~0ull);
int vf_cnt = pci_sriov_get_totalvfs(nic->pdev);
#define INTR_MASK(vfs) ((vfs < 64) ? (BIT_ULL(vfs) - 1) : (~0ull))
/* Clear it, to avoid spurious interrupts (if any) */
nic_reg_write(nic, NIC_PF_MAILBOX_INT, INTR_MASK(vf_cnt));
/* Enable mailbox interrupt for all VFs */
nic_reg_write(nic, NIC_PF_MAILBOX_ENA_W1S, INTR_MASK(vf_cnt));
/* One mailbox intr enable reg per 64 VFs */
if (vf_cnt > 64) {
nic_reg_write(nic, NIC_PF_MAILBOX_INT + sizeof(u64),
INTR_MASK(vf_cnt - 64));
nic_reg_write(nic, NIC_PF_MAILBOX_ENA_W1S + sizeof(u64),
INTR_MASK(vf_cnt - 64));
}
}
static void nic_clear_mbx_intr(struct nicpf *nic, int vf, int mbx_reg)
......@@ -144,7 +174,7 @@ static void nic_mbx_send_ready(struct nicpf *nic, int vf)
mbx.nic_cfg.tns_mode = NIC_TNS_BYPASS_MODE;
if (vf < MAX_LMAC) {
if (vf < nic->num_vf_en) {
bgx_idx = NIC_GET_BGX_FROM_VF_LMAC_MAP(nic->vf_lmac_map[vf]);
lmac = NIC_GET_LMAC_FROM_VF_LMAC_MAP(nic->vf_lmac_map[vf]);
......@@ -155,7 +185,7 @@ static void nic_mbx_send_ready(struct nicpf *nic, int vf)
mbx.nic_cfg.sqs_mode = (vf >= nic->num_vf_en) ? true : false;
mbx.nic_cfg.node_id = nic->node;
mbx.nic_cfg.loopback_supported = vf < MAX_LMAC;
mbx.nic_cfg.loopback_supported = vf < nic->num_vf_en;
nic_send_msg_to_vf(nic, vf, &mbx);
}
......@@ -248,14 +278,22 @@ static int nic_update_hw_frs(struct nicpf *nic, int new_frs, int vf)
/* Set minimum transmit packet size */
static void nic_set_tx_pkt_pad(struct nicpf *nic, int size)
{
int lmac;
int lmac, max_lmac;
u16 sdevid;
u64 lmac_cfg;
/* Max value that can be set is 60 */
if (size > 60)
size = 60;
for (lmac = 0; lmac < (MAX_BGX_PER_CN88XX * MAX_LMAC_PER_BGX); lmac++) {
pci_read_config_word(nic->pdev, PCI_SUBSYSTEM_ID, &sdevid);
/* 81xx's RGX has only one LMAC */
if (sdevid == PCI_SUBSYS_DEVID_81XX_NIC_PF)
max_lmac = ((nic->hw->bgx_cnt - 1) * MAX_LMAC_PER_BGX) + 1;
else
max_lmac = nic->hw->bgx_cnt * MAX_LMAC_PER_BGX;
for (lmac = 0; lmac < max_lmac; lmac++) {
lmac_cfg = nic_reg_read(nic, NIC_PF_LMAC_0_7_CFG | (lmac << 3));
lmac_cfg &= ~(0xF << 2);
lmac_cfg |= ((size / 4) << 2);
......@@ -275,7 +313,7 @@ static void nic_set_lmac_vf_mapping(struct nicpf *nic)
nic->num_vf_en = 0;
for (bgx = 0; bgx < NIC_MAX_BGX; bgx++) {
for (bgx = 0; bgx < nic->hw->bgx_cnt; bgx++) {
if (!(bgx_map & (1 << bgx)))
continue;
lmac_cnt = bgx_get_lmac_count(nic->node, bgx);
......@@ -295,28 +333,125 @@ static void nic_set_lmac_vf_mapping(struct nicpf *nic)
nic_reg_write(nic,
NIC_PF_LMAC_0_7_CREDIT + (lmac * 8),
lmac_credit);
/* On CN81XX there are only 8 VFs but max possible no of
* interfaces are 9.
*/
if (nic->num_vf_en >= pci_sriov_get_totalvfs(nic->pdev)) {
nic->num_vf_en = pci_sriov_get_totalvfs(nic->pdev);
break;
}
}
}
static void nic_free_lmacmem(struct nicpf *nic)
{
kfree(nic->vf_lmac_map);
kfree(nic->link);
kfree(nic->duplex);
kfree(nic->speed);
}
static int nic_get_hw_info(struct nicpf *nic)
{
u8 max_lmac;
u16 sdevid;
struct hw_info *hw = nic->hw;
pci_read_config_word(nic->pdev, PCI_SUBSYSTEM_ID, &sdevid);
switch (sdevid) {
case PCI_SUBSYS_DEVID_88XX_NIC_PF:
hw->bgx_cnt = MAX_BGX_PER_CN88XX;
hw->chans_per_lmac = 16;
hw->chans_per_bgx = 128;
hw->cpi_cnt = 2048;
hw->rssi_cnt = 4096;
hw->rss_ind_tbl_size = NIC_MAX_RSS_IDR_TBL_SIZE;
hw->tl3_cnt = 256;
hw->tl2_cnt = 64;
hw->tl1_cnt = 2;
hw->tl1_per_bgx = true;
break;
case PCI_SUBSYS_DEVID_81XX_NIC_PF:
hw->bgx_cnt = MAX_BGX_PER_CN81XX;
hw->chans_per_lmac = 8;
hw->chans_per_bgx = 32;
hw->chans_per_rgx = 8;
hw->chans_per_lbk = 24;
hw->cpi_cnt = 512;
hw->rssi_cnt = 256;
hw->rss_ind_tbl_size = 32; /* Max RSSI / Max interfaces */
hw->tl3_cnt = 64;
hw->tl2_cnt = 16;
hw->tl1_cnt = 10;
hw->tl1_per_bgx = false;
break;
case PCI_SUBSYS_DEVID_83XX_NIC_PF:
hw->bgx_cnt = MAX_BGX_PER_CN83XX;
hw->chans_per_lmac = 8;
hw->chans_per_bgx = 32;
hw->chans_per_lbk = 64;
hw->cpi_cnt = 2048;
hw->rssi_cnt = 1024;
hw->rss_ind_tbl_size = 64; /* Max RSSI / Max interfaces */
hw->tl3_cnt = 256;
hw->tl2_cnt = 64;
hw->tl1_cnt = 18;
hw->tl1_per_bgx = false;
break;
}
hw->tl4_cnt = MAX_QUEUES_PER_QSET * pci_sriov_get_totalvfs(nic->pdev);
/* Allocate memory for LMAC tracking elements */
max_lmac = hw->bgx_cnt * MAX_LMAC_PER_BGX;
nic->vf_lmac_map = kmalloc_array(max_lmac, sizeof(u8), GFP_KERNEL);
if (!nic->vf_lmac_map)
goto error;
nic->link = kmalloc_array(max_lmac, sizeof(u8), GFP_KERNEL);
if (!nic->link)
goto error;
nic->duplex = kmalloc_array(max_lmac, sizeof(u8), GFP_KERNEL);
if (!nic->duplex)
goto error;
nic->speed = kmalloc_array(max_lmac, sizeof(u32), GFP_KERNEL);
if (!nic->speed)
goto error;
return 0;
error:
nic_free_lmacmem(nic);
return -ENOMEM;
}
#define BGX0_BLOCK 8
#define BGX1_BLOCK 9
static void nic_init_hw(struct nicpf *nic)
static int nic_init_hw(struct nicpf *nic)
{
int i;
int i, err;
u64 cqm_cfg;
/* Get HW capability info */
err = nic_get_hw_info(nic);
if (err)
return err;
/* Enable NIC HW block */
nic_reg_write(nic, NIC_PF_CFG, 0x3);
/* Enable backpressure */
nic_reg_write(nic, NIC_PF_BP_CFG, (1ULL << 6) | 0x03);
/* TNS and TNS bypass modes are present only on 88xx */
if (nic->pdev->subsystem_device == PCI_SUBSYS_DEVID_88XX_NIC_PF) {
/* Disable TNS mode on both interfaces */
nic_reg_write(nic, NIC_PF_INTF_0_1_SEND_CFG,
(NIC_TNS_BYPASS_MODE << 7) | BGX0_BLOCK);
nic_reg_write(nic, NIC_PF_INTF_0_1_SEND_CFG | (1 << 8),
(NIC_TNS_BYPASS_MODE << 7) | BGX1_BLOCK);
}
nic_reg_write(nic, NIC_PF_INTF_0_1_BP_CFG,
(1ULL << 63) | BGX0_BLOCK);
nic_reg_write(nic, NIC_PF_INTF_0_1_BP_CFG + (1 << 8),
......@@ -346,11 +481,14 @@ static void nic_init_hw(struct nicpf *nic)
cqm_cfg = nic_reg_read(nic, NIC_PF_CQM_CFG);
if (cqm_cfg < NICPF_CQM_MIN_DROP_LEVEL)
nic_reg_write(nic, NIC_PF_CQM_CFG, NICPF_CQM_MIN_DROP_LEVEL);
return 0;
}
/* Channel parse index configuration */
static void nic_config_cpi(struct nicpf *nic, struct cpi_cfg_msg *cfg)
{
struct hw_info *hw = nic->hw;
u32 vnic, bgx, lmac, chan;
u32 padd, cpi_count = 0;
u64 cpi_base, cpi, rssi_base, rssi;
......@@ -360,9 +498,9 @@ static void nic_config_cpi(struct nicpf *nic, struct cpi_cfg_msg *cfg)
bgx = NIC_GET_BGX_FROM_VF_LMAC_MAP(nic->vf_lmac_map[vnic]);
lmac = NIC_GET_LMAC_FROM_VF_LMAC_MAP(nic->vf_lmac_map[vnic]);
chan = (lmac * MAX_BGX_CHANS_PER_LMAC) + (bgx * NIC_CHANS_PER_INF);
cpi_base = (lmac * NIC_MAX_CPI_PER_LMAC) + (bgx * NIC_CPI_PER_BGX);
rssi_base = (lmac * nic->rss_ind_tbl_size) + (bgx * NIC_RSSI_PER_BGX);
chan = (lmac * hw->chans_per_lmac) + (bgx * hw->chans_per_bgx);
cpi_base = vnic * NIC_MAX_CPI_PER_LMAC;
rssi_base = vnic * hw->rss_ind_tbl_size;
/* Rx channel configuration */
nic_reg_write(nic, NIC_PF_CHAN_0_255_RX_BP_CFG | (chan << 3),
......@@ -434,7 +572,7 @@ static void nic_send_rss_size(struct nicpf *nic, int vf)
msg = (u64 *)&mbx;
mbx.rss_size.msg = NIC_MBOX_MSG_RSS_SIZE;
mbx.rss_size.ind_tbl_size = nic->rss_ind_tbl_size;
mbx.rss_size.ind_tbl_size = nic->hw->rss_ind_tbl_size;
nic_send_msg_to_vf(nic, vf, &mbx);
}
......@@ -481,7 +619,7 @@ static void nic_config_rss(struct nicpf *nic, struct rss_cfg_msg *cfg)
/* 4 level transmit side scheduler configutation
* for TNS bypass mode
*
* Sample configuration for SQ0
* Sample configuration for SQ0 on 88xx
* VNIC0-SQ0 -> TL4(0) -> TL3[0] -> TL2[0] -> TL1[0] -> BGX0
* VNIC1-SQ0 -> TL4(8) -> TL3[2] -> TL2[0] -> TL1[0] -> BGX0
* VNIC2-SQ0 -> TL4(16) -> TL3[4] -> TL2[1] -> TL1[0] -> BGX0
......@@ -494,6 +632,7 @@ static void nic_config_rss(struct nicpf *nic, struct rss_cfg_msg *cfg)
static void nic_tx_channel_cfg(struct nicpf *nic, u8 vnic,
struct sq_cfg_msg *sq)
{
struct hw_info *hw = nic->hw;
u32 bgx, lmac, chan;
u32 tl2, tl3, tl4;
u32 rr_quantum;
......@@ -512,21 +651,28 @@ static void nic_tx_channel_cfg(struct nicpf *nic, u8 vnic,
/* 24 bytes for FCS, IPG and preamble */
rr_quantum = ((NIC_HW_MAX_FRS + 24) / 4);
/* For 88xx 0-511 TL4 transmits via BGX0 and
* 512-1023 TL4s transmit via BGX1.
*/
if (hw->tl1_per_bgx) {
tl4 = bgx * (hw->tl4_cnt / hw->bgx_cnt);
if (!sq->sqs_mode) {
tl4 = (lmac * NIC_TL4_PER_LMAC) + (bgx * NIC_TL4_PER_BGX);
tl4 += (lmac * MAX_QUEUES_PER_QSET);
} else {
for (svf = 0; svf < MAX_SQS_PER_VF; svf++) {
if (nic->vf_sqs[pqs_vnic][svf] == vnic)
break;
}
tl4 = (MAX_LMAC_PER_BGX * NIC_TL4_PER_LMAC);
tl4 += (lmac * NIC_TL4_PER_LMAC * MAX_SQS_PER_VF);
tl4 += (svf * NIC_TL4_PER_LMAC);
tl4 += (bgx * NIC_TL4_PER_BGX);
tl4 += (MAX_LMAC_PER_BGX * MAX_QUEUES_PER_QSET);
tl4 += (lmac * MAX_QUEUES_PER_QSET * MAX_SQS_PER_VF);
tl4 += (svf * MAX_QUEUES_PER_QSET);
}
} else {
tl4 = (vnic * MAX_QUEUES_PER_QSET);
}
tl4 += sq_idx;
tl3 = tl4 / (NIC_MAX_TL4 / NIC_MAX_TL3);
tl3 = tl4 / (hw->tl4_cnt / hw->tl3_cnt);
nic_reg_write(nic, NIC_PF_QSET_0_127_SQ_0_7_CFG2 |
((u64)vnic << NIC_QS_ID_SHIFT) |
((u32)sq_idx << NIC_Q_NUM_SHIFT), tl4);
......@@ -534,8 +680,19 @@ static void nic_tx_channel_cfg(struct nicpf *nic, u8 vnic,
((u64)vnic << 27) | ((u32)sq_idx << 24) | rr_quantum);
nic_reg_write(nic, NIC_PF_TL3_0_255_CFG | (tl3 << 3), rr_quantum);
chan = (lmac * MAX_BGX_CHANS_PER_LMAC) + (bgx * NIC_CHANS_PER_INF);
/* On 88xx 0-127 channels are for BGX0 and
* 127-255 channels for BGX1.
*
* On 81xx/83xx TL3_CHAN reg should be configured with channel
* within LMAC i.e 0-7 and not the actual channel number like on 88xx
*/
chan = (lmac * hw->chans_per_lmac) + (bgx * hw->chans_per_bgx);
if (hw->tl1_per_bgx)
nic_reg_write(nic, NIC_PF_TL3_0_255_CHAN | (tl3 << 3), chan);
else
nic_reg_write(nic, NIC_PF_TL3_0_255_CHAN | (tl3 << 3), 0);
/* Enable backpressure on the channel */
nic_reg_write(nic, NIC_PF_CHAN_0_255_TX_CFG | (chan << 3), 1);
......@@ -544,6 +701,16 @@ static void nic_tx_channel_cfg(struct nicpf *nic, u8 vnic,
nic_reg_write(nic, NIC_PF_TL2_0_63_CFG | (tl2 << 3), rr_quantum);
/* No priorities as of now */
nic_reg_write(nic, NIC_PF_TL2_0_63_PRI | (tl2 << 3), 0x00);
/* Unlike 88xx where TL2s 0-31 transmits to TL1 '0' and rest to TL1 '1'
* on 81xx/83xx TL2 needs to be configured to transmit to one of the
* possible LMACs.
*
* This register doesn't exist on 88xx.
*/
if (!hw->tl1_per_bgx)
nic_reg_write(nic, NIC_PF_TL2_LMAC | (tl2 << 3),
lmac + (bgx * MAX_LMAC_PER_BGX));
}
/* Send primary nicvf pointer to secondary QS's VF */
......@@ -615,7 +782,7 @@ static int nic_config_loopback(struct nicpf *nic, struct set_loopback *lbk)
{
int bgx_idx, lmac_idx;
if (lbk->vf_id > MAX_LMAC)
if (lbk->vf_id >= nic->num_vf_en)
return -1;
bgx_idx = NIC_GET_BGX_FROM_VF_LMAC_MAP(nic->vf_lmac_map[lbk->vf_id]);
......@@ -626,6 +793,67 @@ static int nic_config_loopback(struct nicpf *nic, struct set_loopback *lbk)
return 0;
}
/* Reset statistics counters */
static int nic_reset_stat_counters(struct nicpf *nic,
int vf, struct reset_stat_cfg *cfg)
{
int i, stat, qnum;
u64 reg_addr;
for (i = 0; i < RX_STATS_ENUM_LAST; i++) {
if (cfg->rx_stat_mask & BIT(i)) {
reg_addr = NIC_PF_VNIC_0_127_RX_STAT_0_13 |
(vf << NIC_QS_ID_SHIFT) |
(i << 3);
nic_reg_write(nic, reg_addr, 0);
}
}
for (i = 0; i < TX_STATS_ENUM_LAST; i++) {
if (cfg->tx_stat_mask & BIT(i)) {
reg_addr = NIC_PF_VNIC_0_127_TX_STAT_0_4 |
(vf << NIC_QS_ID_SHIFT) |
(i << 3);
nic_reg_write(nic, reg_addr, 0);
}
}
for (i = 0; i <= 15; i++) {
qnum = i >> 1;
stat = i & 1 ? 1 : 0;
reg_addr = (vf << NIC_QS_ID_SHIFT) |
(qnum << NIC_Q_NUM_SHIFT) | (stat << 3);
if (cfg->rq_stat_mask & BIT(i)) {
reg_addr |= NIC_PF_QSET_0_127_RQ_0_7_STAT_0_1;
nic_reg_write(nic, reg_addr, 0);
}
if (cfg->sq_stat_mask & BIT(i)) {
reg_addr |= NIC_PF_QSET_0_127_SQ_0_7_STAT_0_1;
nic_reg_write(nic, reg_addr, 0);
}
}
return 0;
}
static void nic_enable_tunnel_parsing(struct nicpf *nic, int vf)
{
u64 prot_def = (IPV6_PROT << 32) | (IPV4_PROT << 16) | ET_PROT;
u64 vxlan_prot_def = (IPV6_PROT_DEF << 32) |
(IPV4_PROT_DEF) << 16 | ET_PROT_DEF;
/* Configure tunnel parsing parameters */
nic_reg_write(nic, NIC_PF_RX_GENEVE_DEF,
(1ULL << 63 | UDP_GENEVE_PORT_NUM));
nic_reg_write(nic, NIC_PF_RX_GENEVE_PROT_DEF,
((7ULL << 61) | prot_def));
nic_reg_write(nic, NIC_PF_RX_NVGRE_PROT_DEF,
((7ULL << 61) | prot_def));
nic_reg_write(nic, NIC_PF_RX_VXLAN_DEF_0_1,
((1ULL << 63) | UDP_VXLAN_PORT_NUM));
nic_reg_write(nic, NIC_PF_RX_VXLAN_PROT_DEF,
((0xfULL << 60) | vxlan_prot_def));
}
static void nic_enable_vf(struct nicpf *nic, int vf, bool enable)
{
int bgx, lmac;
......@@ -664,18 +892,17 @@ static void nic_handle_mbx_intr(struct nicpf *nic, int vf)
mbx_addr += sizeof(u64);
}
dev_dbg(&nic->pdev->dev, "%s: Mailbox msg %d from VF%d\n",
dev_dbg(&nic->pdev->dev, "%s: Mailbox msg 0x%02x from VF%d\n",
__func__, mbx.msg.msg, vf);
switch (mbx.msg.msg) {
case NIC_MBOX_MSG_READY:
nic_mbx_send_ready(nic, vf);
if (vf < MAX_LMAC) {
if (vf < nic->num_vf_en) {
nic->link[vf] = 0;
nic->duplex[vf] = 0;
nic->speed[vf] = 0;
}
ret = 1;
break;
goto unlock;
case NIC_MBOX_MSG_QS_CFG:
reg_addr = NIC_PF_QSET_0_127_CFG |
(mbx.qs.num << NIC_QS_ID_SHIFT);
......@@ -693,6 +920,15 @@ static void nic_handle_mbx_intr(struct nicpf *nic, int vf)
(mbx.rq.qs_num << NIC_QS_ID_SHIFT) |
(mbx.rq.rq_num << NIC_Q_NUM_SHIFT);
nic_reg_write(nic, reg_addr, mbx.rq.cfg);
/* Enable CQE_RX2_S extension in CQE_RX descriptor.
* This gets appended by default on 81xx/83xx chips,
* for consistency enabling the same on 88xx pass2
* where this is introduced.
*/
if (pass2_silicon(nic->pdev))
nic_reg_write(nic, NIC_PF_RX_CFG, 0x01);
if (!pass1_silicon(nic->pdev))
nic_enable_tunnel_parsing(nic, vf);
break;
case NIC_MBOX_MSG_RQ_BP_CFG:
reg_addr = NIC_PF_QSET_0_127_RQ_0_7_BP_CFG |
......@@ -717,8 +953,10 @@ static void nic_handle_mbx_intr(struct nicpf *nic, int vf)
nic_tx_channel_cfg(nic, mbx.qs.num, &mbx.sq);
break;
case NIC_MBOX_MSG_SET_MAC:
if (vf >= nic->num_vf_en)
if (vf >= nic->num_vf_en) {
ret = -1; /* NACK */
break;
}
lmac = mbx.mac.vf_id;
bgx = NIC_GET_BGX_FROM_VF_LMAC_MAP(nic->vf_lmac_map[lmac]);
lmac = NIC_GET_LMAC_FROM_VF_LMAC_MAP(nic->vf_lmac_map[lmac]);
......@@ -767,25 +1005,38 @@ static void nic_handle_mbx_intr(struct nicpf *nic, int vf)
case NIC_MBOX_MSG_LOOPBACK:
ret = nic_config_loopback(nic, &mbx.lbk);
break;
case NIC_MBOX_MSG_RESET_STAT_COUNTER:
ret = nic_reset_stat_counters(nic, vf, &mbx.reset_stat);
break;
default:
dev_err(&nic->pdev->dev,
"Invalid msg from VF%d, msg 0x%x\n", vf, mbx.msg.msg);
break;
}
if (!ret)
if (!ret) {
nic_mbx_send_ack(nic, vf);
else if (mbx.msg.msg != NIC_MBOX_MSG_READY)
} else if (mbx.msg.msg != NIC_MBOX_MSG_READY) {
dev_err(&nic->pdev->dev, "NACK for MBOX 0x%02x from VF %d\n",
mbx.msg.msg, vf);
nic_mbx_send_nack(nic, vf);
}
unlock:
nic->mbx_lock[vf] = false;
}
static void nic_mbx_intr_handler (struct nicpf *nic, int mbx)
static irqreturn_t nic_mbx_intr_handler(int irq, void *nic_irq)
{
struct nicpf *nic = (struct nicpf *)nic_irq;
int mbx;
u64 intr;
u8 vf, vf_per_mbx_reg = 64;
if (irq == nic->msix_entries[NIC_PF_INTR_ID_MBOX0].vector)
mbx = 0;
else
mbx = 1;
intr = nic_reg_read(nic, NIC_PF_MAILBOX_INT + (mbx << 3));
dev_dbg(&nic->pdev->dev, "PF interrupt Mbox%d 0x%llx\n", mbx, intr);
for (vf = 0; vf < vf_per_mbx_reg; vf++) {
......@@ -797,23 +1048,6 @@ static void nic_mbx_intr_handler (struct nicpf *nic, int mbx)
nic_clear_mbx_intr(nic, vf, mbx);
}
}
}
static irqreturn_t nic_mbx0_intr_handler (int irq, void *nic_irq)
{
struct nicpf *nic = (struct nicpf *)nic_irq;
nic_mbx_intr_handler(nic, 0);
return IRQ_HANDLED;
}
static irqreturn_t nic_mbx1_intr_handler (int irq, void *nic_irq)
{
struct nicpf *nic = (struct nicpf *)nic_irq;
nic_mbx_intr_handler(nic, 1);
return IRQ_HANDLED;
}
......@@ -821,7 +1055,13 @@ static int nic_enable_msix(struct nicpf *nic)
{
int i, ret;
nic->num_vec = NIC_PF_MSIX_VECTORS;
nic->num_vec = pci_msix_vec_count(nic->pdev);
nic->msix_entries = kmalloc_array(nic->num_vec,
sizeof(struct msix_entry),
GFP_KERNEL);
if (!nic->msix_entries)
return -ENOMEM;
for (i = 0; i < nic->num_vec; i++)
nic->msix_entries[i].entry = i;
......@@ -829,8 +1069,9 @@ static int nic_enable_msix(struct nicpf *nic)
ret = pci_enable_msix(nic->pdev, nic->msix_entries, nic->num_vec);
if (ret) {
dev_err(&nic->pdev->dev,
"Request for #%d msix vectors failed\n",
nic->num_vec);
"Request for #%d msix vectors failed, returned %d\n",
nic->num_vec, ret);
kfree(nic->msix_entries);
return ret;
}
......@@ -842,6 +1083,7 @@ static void nic_disable_msix(struct nicpf *nic)
{
if (nic->msix_enabled) {
pci_disable_msix(nic->pdev);
kfree(nic->msix_entries);
nic->msix_enabled = 0;
nic->num_vec = 0;
}
......@@ -860,27 +1102,26 @@ static void nic_free_all_interrupts(struct nicpf *nic)
static int nic_register_interrupts(struct nicpf *nic)
{
int ret;
int i, ret;
/* Enable MSI-X */
ret = nic_enable_msix(nic);
if (ret)
return ret;
/* Register mailbox interrupt handlers */
ret = request_irq(nic->msix_entries[NIC_PF_INTR_ID_MBOX0].vector,
nic_mbx0_intr_handler, 0, "NIC Mbox0", nic);
if (ret)
goto fail;
/* Register mailbox interrupt handler */
for (i = NIC_PF_INTR_ID_MBOX0; i < nic->num_vec; i++) {
sprintf(nic->irq_name[i],
"NICPF Mbox%d", (i - NIC_PF_INTR_ID_MBOX0));
nic->irq_allocated[NIC_PF_INTR_ID_MBOX0] = true;
ret = request_irq(nic->msix_entries[NIC_PF_INTR_ID_MBOX1].vector,
nic_mbx1_intr_handler, 0, "NIC Mbox1", nic);
ret = request_irq(nic->msix_entries[i].vector,
nic_mbx_intr_handler, 0,
nic->irq_name[i], nic);
if (ret)
goto fail;
nic->irq_allocated[NIC_PF_INTR_ID_MBOX1] = true;
nic->irq_allocated[i] = true;
}
/* Enable mailbox interrupt */
nic_enable_mbx_intr(nic);
......@@ -889,6 +1130,7 @@ static int nic_register_interrupts(struct nicpf *nic)
fail:
dev_err(&nic->pdev->dev, "Request irq failed\n");
nic_free_all_interrupts(nic);
nic_disable_msix(nic);
return ret;
}
......@@ -903,6 +1145,12 @@ static int nic_num_sqs_en(struct nicpf *nic, int vf_en)
int pos, sqs_per_vf = MAX_SQS_PER_VF_SINGLE_NODE;
u16 total_vf;
/* Secondary Qsets are needed only if CPU count is
* morethan MAX_QUEUES_PER_QSET.
*/
if (num_online_cpus() <= MAX_QUEUES_PER_QSET)
return 0;
/* Check if its a multi-node environment */
if (nr_node_ids > 1)
sqs_per_vf = MAX_SQS_PER_VF;
......@@ -1008,6 +1256,12 @@ static int nic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
if (!nic)
return -ENOMEM;
nic->hw = devm_kzalloc(dev, sizeof(struct hw_info), GFP_KERNEL);
if (!nic->hw) {
devm_kfree(dev, nic);
return -ENOMEM;
}
pci_set_drvdata(pdev, nic);
nic->pdev = pdev;
......@@ -1047,13 +1301,12 @@ static int nic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
nic->node = nic_get_node_id(pdev);
nic_set_lmac_vf_mapping(nic);
/* Initialize hardware */
nic_init_hw(nic);
err = nic_init_hw(nic);
if (err)
goto err_release_regions;
/* Set RSS TBL size for each VF */
nic->rss_ind_tbl_size = NIC_MAX_RSS_IDR_TBL_SIZE;
nic_set_lmac_vf_mapping(nic);
/* Register interrupts */
err = nic_register_interrupts(nic);
......@@ -1086,6 +1339,9 @@ static int nic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err_release_regions:
pci_release_regions(pdev);
err_disable_device:
nic_free_lmacmem(nic);
devm_kfree(dev, nic->hw);
devm_kfree(dev, nic);
pci_disable_device(pdev);
pci_set_drvdata(pdev, NULL);
return err;
......@@ -1106,6 +1362,11 @@ static void nic_remove(struct pci_dev *pdev)
nic_unregister_interrupts(nic);
pci_release_regions(pdev);
nic_free_lmacmem(nic);
devm_kfree(&pdev->dev, nic->hw);
devm_kfree(&pdev->dev, nic);
pci_disable_device(pdev);
pci_set_drvdata(pdev, NULL);
}
......
......@@ -36,6 +36,20 @@
#define NIC_PF_MAILBOX_ENA_W1C (0x0450)
#define NIC_PF_MAILBOX_ENA_W1S (0x0470)
#define NIC_PF_RX_ETYPE_0_7 (0x0500)
#define NIC_PF_RX_GENEVE_DEF (0x0580)
#define UDP_GENEVE_PORT_NUM 0x17C1ULL
#define NIC_PF_RX_GENEVE_PROT_DEF (0x0588)
#define IPV6_PROT 0x86DDULL
#define IPV4_PROT 0x800ULL
#define ET_PROT 0x6558ULL
#define NIC_PF_RX_NVGRE_PROT_DEF (0x0598)
#define NIC_PF_RX_VXLAN_DEF_0_1 (0x05A0)
#define UDP_VXLAN_PORT_NUM 0x12B5
#define NIC_PF_RX_VXLAN_PROT_DEF (0x05B0)
#define IPV6_PROT_DEF 0x2ULL
#define IPV4_PROT_DEF 0x1ULL
#define ET_PROT_DEF 0x3ULL
#define NIC_PF_RX_CFG (0x05D0)
#define NIC_PF_PKIND_0_15_CFG (0x0600)
#define NIC_PF_ECC0_FLIP0 (0x1000)
#define NIC_PF_ECC1_FLIP0 (0x1008)
......@@ -103,6 +117,7 @@
#define NIC_PF_SW_SYNC_RX_DONE (0x490008)
#define NIC_PF_TL2_0_63_CFG (0x500000)
#define NIC_PF_TL2_0_63_PRI (0x520000)
#define NIC_PF_TL2_LMAC (0x540000)
#define NIC_PF_TL2_0_63_SH_STATUS (0x580000)
#define NIC_PF_TL3A_0_63_CFG (0x5F0000)
#define NIC_PF_TL3_0_255_CFG (0x600000)
......
......@@ -29,10 +29,20 @@
static const struct pci_device_id nicvf_id_table[] = {
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM,
PCI_DEVICE_ID_THUNDER_NIC_VF,
PCI_VENDOR_ID_CAVIUM, 0xA134) },
PCI_VENDOR_ID_CAVIUM,
PCI_SUBSYS_DEVID_88XX_NIC_VF) },
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM,
PCI_DEVICE_ID_THUNDER_PASS1_NIC_VF,
PCI_VENDOR_ID_CAVIUM, 0xA11E) },
PCI_VENDOR_ID_CAVIUM,
PCI_SUBSYS_DEVID_88XX_PASS1_NIC_VF) },
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM,
PCI_DEVICE_ID_THUNDER_NIC_VF,
PCI_VENDOR_ID_CAVIUM,
PCI_SUBSYS_DEVID_81XX_NIC_VF) },
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM,
PCI_DEVICE_ID_THUNDER_NIC_VF,
PCI_VENDOR_ID_CAVIUM,
PCI_SUBSYS_DEVID_83XX_NIC_VF) },
{ 0, } /* end of table */
};
......@@ -134,15 +144,19 @@ int nicvf_send_msg_to_pf(struct nicvf *nic, union nic_mbx *mbx)
/* Wait for previous message to be acked, timeout 2sec */
while (!nic->pf_acked) {
if (nic->pf_nacked)
if (nic->pf_nacked) {
netdev_err(nic->netdev,
"PF NACK to mbox msg 0x%02x from VF%d\n",
(mbx->msg.msg & 0xFF), nic->vf_id);
return -EINVAL;
}
msleep(sleep);
if (nic->pf_acked)
break;
timeout -= sleep;
if (!timeout) {
netdev_err(nic->netdev,
"PF didn't ack to mbox msg %d from VF%d\n",
"PF didn't ACK to mbox msg 0x%02x from VF%d\n",
(mbx->msg.msg & 0xFF), nic->vf_id);
return -EBUSY;
}
......@@ -352,13 +366,7 @@ static int nicvf_rss_init(struct nicvf *nic)
rss->enable = true;
/* Using the HW reset value for now */
rss->key[0] = 0xFEED0BADFEED0BADULL;
rss->key[1] = 0xFEED0BADFEED0BADULL;
rss->key[2] = 0xFEED0BADFEED0BADULL;
rss->key[3] = 0xFEED0BADFEED0BADULL;
rss->key[4] = 0xFEED0BADFEED0BADULL;
netdev_rss_key_fill(rss->key, RSS_HASH_KEY_SIZE * sizeof(u64));
nicvf_set_rss_key(nic);
rss->cfg = RSS_IP_HASH_ENA | RSS_TCP_HASH_ENA | RSS_UDP_HASH_ENA;
......@@ -507,7 +515,8 @@ static int nicvf_init_resources(struct nicvf *nic)
static void nicvf_snd_pkt_handler(struct net_device *netdev,
struct cmp_queue *cq,
struct cqe_send_t *cqe_tx, int cqe_type)
struct cqe_send_t *cqe_tx,
int cqe_type, int budget)
{
struct sk_buff *skb = NULL;
struct nicvf *nic = netdev_priv(netdev);
......@@ -531,7 +540,7 @@ static void nicvf_snd_pkt_handler(struct net_device *netdev,
if (skb) {
nicvf_put_sq_desc(sq, hdr->subdesc_cnt + 1);
prefetch(skb);
dev_consume_skb_any(skb);
napi_consume_skb(skb, budget);
sq->skbuff[cqe_tx->sqe_ptr] = (u64)NULL;
} else {
/* In case of HW TSO, HW sends a CQE for each segment of a TSO
......@@ -686,7 +695,8 @@ static int nicvf_cq_intr_handler(struct net_device *netdev, u8 cq_idx,
break;
case CQE_TYPE_SEND:
nicvf_snd_pkt_handler(netdev, cq,
(void *)cq_desc, CQE_TYPE_SEND);
(void *)cq_desc, CQE_TYPE_SEND,
budget);
tx_done++;
break;
case CQE_TYPE_INVALID:
......@@ -928,16 +938,19 @@ static int nicvf_register_interrupts(struct nicvf *nic)
int vector;
for_each_cq_irq(irq)
sprintf(nic->irq_name[irq], "NICVF%d CQ%d",
nic->vf_id, irq);
sprintf(nic->irq_name[irq], "%s-rxtx-%d",
nic->pnicvf->netdev->name,
nicvf_netdev_qidx(nic, irq));
for_each_sq_irq(irq)
sprintf(nic->irq_name[irq], "NICVF%d SQ%d",
nic->vf_id, irq - NICVF_INTR_ID_SQ);
sprintf(nic->irq_name[irq], "%s-sq-%d",
nic->pnicvf->netdev->name,
nicvf_netdev_qidx(nic, irq - NICVF_INTR_ID_SQ));
for_each_rbdr_irq(irq)
sprintf(nic->irq_name[irq], "NICVF%d RBDR%d",
nic->vf_id, irq - NICVF_INTR_ID_RBDR);
sprintf(nic->irq_name[irq], "%s-rbdr-%d",
nic->pnicvf->netdev->name,
nic->sqs_mode ? (nic->sqs_id + 1) : 0);
/* Register CQ interrupts */
for (irq = 0; irq < nic->qs->cq_cnt; irq++) {
......@@ -961,8 +974,9 @@ static int nicvf_register_interrupts(struct nicvf *nic)
}
/* Register QS error interrupt */
sprintf(nic->irq_name[NICVF_INTR_ID_QS_ERR],
"NICVF%d Qset error", nic->vf_id);
sprintf(nic->irq_name[NICVF_INTR_ID_QS_ERR], "%s-qset-err-%d",
nic->pnicvf->netdev->name,
nic->sqs_mode ? (nic->sqs_id + 1) : 0);
irq = NICVF_INTR_ID_QS_ERR;
ret = request_irq(nic->msix_entries[irq].vector,
nicvf_qs_err_intr_handler,
......@@ -1191,7 +1205,7 @@ int nicvf_open(struct net_device *netdev)
}
/* Check if we got MAC address from PF or else generate a radom MAC */
if (is_zero_ether_addr(netdev->dev_addr)) {
if (!nic->sqs_mode && is_zero_ether_addr(netdev->dev_addr)) {
eth_hw_addr_random(netdev);
nicvf_hw_set_mac_addr(nic, netdev);
}
......@@ -1527,13 +1541,12 @@ static int nicvf_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto err_release_regions;
}
qcount = MAX_CMP_QUEUES_PER_QS;
qcount = netif_get_num_default_rss_queues();
/* Restrict multiqset support only for host bound VFs */
if (pdev->is_virtfn) {
/* Set max number of queues per VF */
qcount = roundup(num_online_cpus(), MAX_CMP_QUEUES_PER_QS);
qcount = min(qcount,
qcount = min_t(int, num_online_cpus(),
(MAX_SQS_PER_VF + 1) * MAX_CMP_QUEUES_PER_QS);
}
......
......@@ -479,6 +479,16 @@ void nicvf_config_vlan_stripping(struct nicvf *nic, netdev_features_t features)
NIC_QSET_RQ_GEN_CFG, 0, rq_cfg);
}
static void nicvf_reset_rcv_queue_stats(struct nicvf *nic)
{
union nic_mbx mbx = {};
/* Reset all RXQ's stats */
mbx.reset_stat.msg = NIC_MBOX_MSG_RESET_STAT_COUNTER;
mbx.reset_stat.rq_stat_mask = 0xFFFF;
nicvf_send_msg_to_pf(nic, &mbx);
}
/* Configures receive queue */
static void nicvf_rcv_queue_config(struct nicvf *nic, struct queue_set *qs,
int qidx, bool enable)
......@@ -762,10 +772,10 @@ int nicvf_set_qset_resources(struct nicvf *nic)
nic->qs = qs;
/* Set count of each queue */
qs->rbdr_cnt = RBDR_CNT;
qs->rq_cnt = RCV_QUEUE_CNT;
qs->sq_cnt = SND_QUEUE_CNT;
qs->cq_cnt = CMP_QUEUE_CNT;
qs->rbdr_cnt = DEFAULT_RBDR_CNT;
qs->rq_cnt = min_t(u8, MAX_RCV_QUEUES_PER_QS, num_online_cpus());
qs->sq_cnt = min_t(u8, MAX_SND_QUEUES_PER_QS, num_online_cpus());
qs->cq_cnt = max_t(u8, qs->rq_cnt, qs->sq_cnt);
/* Set queue lengths */
qs->rbdr_len = RCV_BUF_COUNT;
......@@ -812,6 +822,11 @@ int nicvf_config_data_transfer(struct nicvf *nic, bool enable)
nicvf_free_resources(nic);
}
/* Reset RXQ's stats.
* SQ's stats will get reset automatically once SQ is reset.
*/
nicvf_reset_rcv_queue_stats(nic);
return 0;
}
......@@ -1184,13 +1199,23 @@ struct sk_buff *nicvf_get_rcv_skb(struct nicvf *nic, struct cqe_rx_t *cqe_rx)
int frag;
int payload_len = 0;
struct sk_buff *skb = NULL;
struct sk_buff *skb_frag = NULL;
struct sk_buff *prev_frag = NULL;
struct page *page;
int offset;
u16 *rb_lens = NULL;
u64 *rb_ptrs = NULL;
rb_lens = (void *)cqe_rx + (3 * sizeof(u64));
/* Except 88xx pass1 on all other chips CQE_RX2_S is added to
* CQE_RX at word6, hence buffer pointers move by word
*
* Use existing 'hw_tso' flag which will be set for all chips
* except 88xx pass1 instead of a additional cache line
* access (or miss) by using pci dev's revision.
*/
if (!nic->hw_tso)
rb_ptrs = (void *)cqe_rx + (6 * sizeof(u64));
else
rb_ptrs = (void *)cqe_rx + (7 * sizeof(u64));
netdev_dbg(nic->netdev, "%s rb_cnt %d rb0_ptr %llx rb0_sz %d\n",
__func__, cqe_rx->rb_cnt, cqe_rx->rb0_ptr, cqe_rx->rb0_sz);
......@@ -1208,22 +1233,10 @@ struct sk_buff *nicvf_get_rcv_skb(struct nicvf *nic, struct cqe_rx_t *cqe_rx)
skb_put(skb, payload_len);
} else {
/* Add fragments */
skb_frag = nicvf_rb_ptr_to_skb(nic, *rb_ptrs,
payload_len);
if (!skb_frag) {
dev_kfree_skb(skb);
return NULL;
}
if (!skb_shinfo(skb)->frag_list)
skb_shinfo(skb)->frag_list = skb_frag;
else
prev_frag->next = skb_frag;
prev_frag = skb_frag;
skb->len += payload_len;
skb->data_len += payload_len;
skb_frag->len = payload_len;
page = virt_to_page(phys_to_virt(*rb_ptrs));
offset = phys_to_virt(*rb_ptrs) - page_address(page);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
offset, payload_len, RCV_FRAG_LEN);
}
/* Next buffer pointer */
rb_ptrs++;
......
......@@ -57,10 +57,7 @@
#define CMP_QUEUE_SIZE6 6ULL /* 64K entries */
/* Default queue count per QS, its lengths and threshold values */
#define RBDR_CNT 1
#define RCV_QUEUE_CNT 8
#define SND_QUEUE_CNT 8
#define CMP_QUEUE_CNT 8 /* Max of RCV and SND qcount */
#define DEFAULT_RBDR_CNT 1
#define SND_QSIZE SND_QUEUE_SIZE2
#define SND_QUEUE_LEN (1ULL << (SND_QSIZE + 10))
......
......@@ -28,6 +28,9 @@ struct lmac {
struct bgx *bgx;
int dmac;
u8 mac[ETH_ALEN];
u8 lmac_type;
u8 lane_to_sds;
bool use_training;
bool link_up;
int lmacid; /* ID within BGX */
int lmacid_bd; /* ID on board */
......@@ -43,14 +46,13 @@ struct lmac {
struct bgx {
u8 bgx_id;
u8 qlm_mode;
struct lmac lmac[MAX_LMAC_PER_BGX];
int lmac_count;
int lmac_type;
int lane_to_sds;
int use_training;
u8 max_lmac;
void __iomem *reg_base;
struct pci_dev *pdev;
bool is_dlm;
bool is_rgx;
};
static struct bgx *bgx_vnic[MAX_BGX_THUNDER];
......@@ -61,6 +63,7 @@ static int bgx_xaui_check_link(struct lmac *lmac);
/* Supported devices */
static const struct pci_device_id bgx_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_THUNDER_BGX) },
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_THUNDER_RGX) },
{ 0, } /* end of table */
};
......@@ -124,8 +127,8 @@ unsigned bgx_get_map(int node)
int i;
unsigned map = 0;
for (i = 0; i < MAX_BGX_PER_CN88XX; i++) {
if (bgx_vnic[(node * MAX_BGX_PER_CN88XX) + i])
for (i = 0; i < MAX_BGX_PER_NODE; i++) {
if (bgx_vnic[(node * MAX_BGX_PER_NODE) + i])
map |= (1 << i);
}
......@@ -138,7 +141,7 @@ int bgx_get_lmac_count(int node, int bgx_idx)
{
struct bgx *bgx;
bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (bgx)
return bgx->lmac_count;
......@@ -153,7 +156,7 @@ void bgx_get_lmac_link_state(int node, int bgx_idx, int lmacid, void *status)
struct bgx *bgx;
struct lmac *lmac;
bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (!bgx)
return;
......@@ -166,7 +169,7 @@ EXPORT_SYMBOL(bgx_get_lmac_link_state);
const u8 *bgx_get_lmac_mac(int node, int bgx_idx, int lmacid)
{
struct bgx *bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
struct bgx *bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (bgx)
return bgx->lmac[lmacid].mac;
......@@ -177,7 +180,7 @@ EXPORT_SYMBOL(bgx_get_lmac_mac);
void bgx_set_lmac_mac(int node, int bgx_idx, int lmacid, const u8 *mac)
{
struct bgx *bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
struct bgx *bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (!bgx)
return;
......@@ -188,11 +191,13 @@ EXPORT_SYMBOL(bgx_set_lmac_mac);
void bgx_lmac_rx_tx_enable(int node, int bgx_idx, int lmacid, bool enable)
{
struct bgx *bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
struct bgx *bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
struct lmac *lmac;
u64 cfg;
if (!bgx)
return;
lmac = &bgx->lmac[lmacid];
cfg = bgx_reg_read(bgx, lmacid, BGX_CMRX_CFG);
if (enable)
......@@ -200,6 +205,9 @@ void bgx_lmac_rx_tx_enable(int node, int bgx_idx, int lmacid, bool enable)
else
cfg &= ~(CMR_PKT_RX_EN | CMR_PKT_TX_EN);
bgx_reg_write(bgx, lmacid, BGX_CMRX_CFG, cfg);
if (bgx->is_rgx)
xcv_setup_link(enable ? lmac->link_up : 0, lmac->last_speed);
}
EXPORT_SYMBOL(bgx_lmac_rx_tx_enable);
......@@ -266,9 +274,12 @@ static void bgx_sgmii_change_link_state(struct lmac *lmac)
port_cfg = bgx_reg_read(bgx, lmac->lmacid, BGX_GMP_GMI_PRTX_CFG);
/* renable lmac */
/* Re-enable lmac */
cmr_cfg |= CMR_EN;
bgx_reg_write(bgx, lmac->lmacid, BGX_CMRX_CFG, cmr_cfg);
if (bgx->is_rgx && (cmr_cfg & (CMR_PKT_RX_EN | CMR_PKT_TX_EN)))
xcv_setup_link(lmac->link_up, lmac->last_speed);
}
static void bgx_lmac_handler(struct net_device *netdev)
......@@ -314,7 +325,7 @@ u64 bgx_get_rx_stats(int node, int bgx_idx, int lmac, int idx)
{
struct bgx *bgx;
bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (!bgx)
return 0;
......@@ -328,7 +339,7 @@ u64 bgx_get_tx_stats(int node, int bgx_idx, int lmac, int idx)
{
struct bgx *bgx;
bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (!bgx)
return 0;
......@@ -356,7 +367,7 @@ void bgx_lmac_internal_loopback(int node, int bgx_idx,
struct lmac *lmac;
u64 cfg;
bgx = bgx_vnic[(node * MAX_BGX_PER_CN88XX) + bgx_idx];
bgx = bgx_vnic[(node * MAX_BGX_PER_NODE) + bgx_idx];
if (!bgx)
return;
......@@ -379,8 +390,9 @@ void bgx_lmac_internal_loopback(int node, int bgx_idx,
}
EXPORT_SYMBOL(bgx_lmac_internal_loopback);
static int bgx_lmac_sgmii_init(struct bgx *bgx, int lmacid)
static int bgx_lmac_sgmii_init(struct bgx *bgx, struct lmac *lmac)
{
int lmacid = lmac->lmacid;
u64 cfg;
bgx_reg_modify(bgx, lmacid, BGX_GMP_GMI_TXX_THRESH, 0x30);
......@@ -409,18 +421,29 @@ static int bgx_lmac_sgmii_init(struct bgx *bgx, int lmacid)
cfg |= (PCS_MRX_CTL_RST_AN | PCS_MRX_CTL_AN_EN);
bgx_reg_write(bgx, lmacid, BGX_GMP_PCS_MRX_CTL, cfg);
if (lmac->lmac_type == BGX_MODE_QSGMII) {
/* Disable disparity check for QSGMII */
cfg = bgx_reg_read(bgx, lmacid, BGX_GMP_PCS_MISCX_CTL);
cfg &= ~PCS_MISC_CTL_DISP_EN;
bgx_reg_write(bgx, lmacid, BGX_GMP_PCS_MISCX_CTL, cfg);
return 0;
}
if (lmac->lmac_type == BGX_MODE_SGMII) {
if (bgx_poll_reg(bgx, lmacid, BGX_GMP_PCS_MRX_STATUS,
PCS_MRX_STATUS_AN_CPT, false)) {
dev_err(&bgx->pdev->dev, "BGX AN_CPT not completed\n");
return -1;
}
}
return 0;
}
static int bgx_lmac_xaui_init(struct bgx *bgx, int lmacid, int lmac_type)
static int bgx_lmac_xaui_init(struct bgx *bgx, struct lmac *lmac)
{
u64 cfg;
int lmacid = lmac->lmacid;
/* Reset SPU */
bgx_reg_modify(bgx, lmacid, BGX_SPUX_CONTROL1, SPU_CTL_RESET);
......@@ -436,12 +459,14 @@ static int bgx_lmac_xaui_init(struct bgx *bgx, int lmacid, int lmac_type)
bgx_reg_modify(bgx, lmacid, BGX_SPUX_CONTROL1, SPU_CTL_LOW_POWER);
/* Set interleaved running disparity for RXAUI */
if (bgx->lmac_type != BGX_MODE_RXAUI)
bgx_reg_modify(bgx, lmacid,
BGX_SPUX_MISC_CONTROL, SPU_MISC_CTL_RX_DIS);
else
if (lmac->lmac_type == BGX_MODE_RXAUI)
bgx_reg_modify(bgx, lmacid, BGX_SPUX_MISC_CONTROL,
SPU_MISC_CTL_RX_DIS | SPU_MISC_CTL_INTLV_RDISP);
SPU_MISC_CTL_INTLV_RDISP);
/* Clear receive packet disable */
cfg = bgx_reg_read(bgx, lmacid, BGX_SPUX_MISC_CONTROL);
cfg &= ~SPU_MISC_CTL_RX_DIS;
bgx_reg_write(bgx, lmacid, BGX_SPUX_MISC_CONTROL, cfg);
/* clear all interrupts */
cfg = bgx_reg_read(bgx, lmacid, BGX_SMUX_RX_INT);
......@@ -451,7 +476,7 @@ static int bgx_lmac_xaui_init(struct bgx *bgx, int lmacid, int lmac_type)
cfg = bgx_reg_read(bgx, lmacid, BGX_SPUX_INT);
bgx_reg_write(bgx, lmacid, BGX_SPUX_INT, cfg);
if (bgx->use_training) {
if (lmac->use_training) {
bgx_reg_write(bgx, lmacid, BGX_SPUX_BR_PMD_LP_CUP, 0x00);
bgx_reg_write(bgx, lmacid, BGX_SPUX_BR_PMD_LD_CUP, 0x00);
bgx_reg_write(bgx, lmacid, BGX_SPUX_BR_PMD_LD_REP, 0x00);
......@@ -474,9 +499,9 @@ static int bgx_lmac_xaui_init(struct bgx *bgx, int lmacid, int lmac_type)
bgx_reg_write(bgx, lmacid, BGX_SPUX_AN_CONTROL, cfg);
cfg = bgx_reg_read(bgx, lmacid, BGX_SPUX_AN_ADV);
if (bgx->lmac_type == BGX_MODE_10G_KR)
if (lmac->lmac_type == BGX_MODE_10G_KR)
cfg |= (1 << 23);
else if (bgx->lmac_type == BGX_MODE_40G_KR)
else if (lmac->lmac_type == BGX_MODE_40G_KR)
cfg |= (1 << 24);
else
cfg &= ~((1 << 23) | (1 << 24));
......@@ -511,11 +536,10 @@ static int bgx_xaui_check_link(struct lmac *lmac)
{
struct bgx *bgx = lmac->bgx;
int lmacid = lmac->lmacid;
int lmac_type = bgx->lmac_type;
int lmac_type = lmac->lmac_type;
u64 cfg;
bgx_reg_modify(bgx, lmacid, BGX_SPUX_MISC_CONTROL, SPU_MISC_CTL_RX_DIS);
if (bgx->use_training) {
if (lmac->use_training) {
cfg = bgx_reg_read(bgx, lmacid, BGX_SPUX_INT);
if (!(cfg & (1ull << 13))) {
cfg = (1ull << 13) | (1ull << 14);
......@@ -556,7 +580,7 @@ static int bgx_xaui_check_link(struct lmac *lmac)
BGX_SPUX_STATUS2, SPU_STATUS2_RCVFLT);
if (bgx_reg_read(bgx, lmacid, BGX_SPUX_STATUS2) & SPU_STATUS2_RCVFLT) {
dev_err(&bgx->pdev->dev, "Receive fault, retry training\n");
if (bgx->use_training) {
if (lmac->use_training) {
cfg = bgx_reg_read(bgx, lmacid, BGX_SPUX_INT);
if (!(cfg & (1ull << 13))) {
cfg = (1ull << 13) | (1ull << 14);
......@@ -584,11 +608,6 @@ static int bgx_xaui_check_link(struct lmac *lmac)
return -1;
}
/* Clear receive packet disable */
cfg = bgx_reg_read(bgx, lmacid, BGX_SPUX_MISC_CONTROL);
cfg &= ~SPU_MISC_CTL_RX_DIS;
bgx_reg_write(bgx, lmacid, BGX_SPUX_MISC_CONTROL, cfg);
/* Check for MAC RX faults */
cfg = bgx_reg_read(bgx, lmacid, BGX_SMUX_RX_CTL);
/* 0 - Link is okay, 1 - Local fault, 2 - Remote fault */
......@@ -599,7 +618,7 @@ static int bgx_xaui_check_link(struct lmac *lmac)
/* Rx local/remote fault seen.
* Do lmac reinit to see if condition recovers
*/
bgx_lmac_xaui_init(bgx, lmacid, bgx->lmac_type);
bgx_lmac_xaui_init(bgx, lmac);
return -1;
}
......@@ -623,7 +642,7 @@ static void bgx_poll_for_link(struct work_struct *work)
if ((spu_link & SPU_STATUS1_RCV_LNK) &&
!(smu_link & SMU_RX_CTL_STATUS)) {
lmac->link_up = 1;
if (lmac->bgx->lmac_type == BGX_MODE_XLAUI)
if (lmac->lmac_type == BGX_MODE_XLAUI)
lmac->last_speed = 40000;
else
lmac->last_speed = 10000;
......@@ -649,6 +668,16 @@ static void bgx_poll_for_link(struct work_struct *work)
queue_delayed_work(lmac->check_link, &lmac->dwork, HZ * 2);
}
static int phy_interface_mode(u8 lmac_type)
{
if (lmac_type == BGX_MODE_QSGMII)
return PHY_INTERFACE_MODE_QSGMII;
if (lmac_type == BGX_MODE_RGMII)
return PHY_INTERFACE_MODE_RGMII;
return PHY_INTERFACE_MODE_SGMII;
}
static int bgx_lmac_enable(struct bgx *bgx, u8 lmacid)
{
struct lmac *lmac;
......@@ -657,13 +686,15 @@ static int bgx_lmac_enable(struct bgx *bgx, u8 lmacid)
lmac = &bgx->lmac[lmacid];
lmac->bgx = bgx;
if (bgx->lmac_type == BGX_MODE_SGMII) {
if ((lmac->lmac_type == BGX_MODE_SGMII) ||
(lmac->lmac_type == BGX_MODE_QSGMII) ||
(lmac->lmac_type == BGX_MODE_RGMII)) {
lmac->is_sgmii = 1;
if (bgx_lmac_sgmii_init(bgx, lmacid))
if (bgx_lmac_sgmii_init(bgx, lmac))
return -1;
} else {
lmac->is_sgmii = 0;
if (bgx_lmac_xaui_init(bgx, lmacid, bgx->lmac_type))
if (bgx_lmac_xaui_init(bgx, lmac))
return -1;
}
......@@ -685,10 +716,10 @@ static int bgx_lmac_enable(struct bgx *bgx, u8 lmacid)
/* Restore default cfg, incase low level firmware changed it */
bgx_reg_write(bgx, lmacid, BGX_CMRX_RX_DMAC_CTL, 0x03);
if ((bgx->lmac_type != BGX_MODE_XFI) &&
(bgx->lmac_type != BGX_MODE_XLAUI) &&
(bgx->lmac_type != BGX_MODE_40G_KR) &&
(bgx->lmac_type != BGX_MODE_10G_KR)) {
if ((lmac->lmac_type != BGX_MODE_XFI) &&
(lmac->lmac_type != BGX_MODE_XLAUI) &&
(lmac->lmac_type != BGX_MODE_40G_KR) &&
(lmac->lmac_type != BGX_MODE_10G_KR)) {
if (!lmac->phydev)
return -ENODEV;
......@@ -696,7 +727,7 @@ static int bgx_lmac_enable(struct bgx *bgx, u8 lmacid)
if (phy_connect_direct(&lmac->netdev, lmac->phydev,
bgx_lmac_handler,
PHY_INTERFACE_MODE_SGMII))
phy_interface_mode(lmac->lmac_type)))
return -ENODEV;
phy_start_aneg(lmac->phydev);
......@@ -753,76 +784,19 @@ static void bgx_lmac_disable(struct bgx *bgx, u8 lmacid)
bgx_flush_dmac_addrs(bgx, lmacid);
if ((bgx->lmac_type != BGX_MODE_XFI) &&
(bgx->lmac_type != BGX_MODE_XLAUI) &&
(bgx->lmac_type != BGX_MODE_40G_KR) &&
(bgx->lmac_type != BGX_MODE_10G_KR) && lmac->phydev)
if ((lmac->lmac_type != BGX_MODE_XFI) &&
(lmac->lmac_type != BGX_MODE_XLAUI) &&
(lmac->lmac_type != BGX_MODE_40G_KR) &&
(lmac->lmac_type != BGX_MODE_10G_KR) && lmac->phydev)
phy_disconnect(lmac->phydev);
lmac->phydev = NULL;
}
static void bgx_set_num_ports(struct bgx *bgx)
{
u64 lmac_count;
switch (bgx->qlm_mode) {
case QLM_MODE_SGMII:
bgx->lmac_count = 4;
bgx->lmac_type = BGX_MODE_SGMII;
bgx->lane_to_sds = 0;
break;
case QLM_MODE_XAUI_1X4:
bgx->lmac_count = 1;
bgx->lmac_type = BGX_MODE_XAUI;
bgx->lane_to_sds = 0xE4;
break;
case QLM_MODE_RXAUI_2X2:
bgx->lmac_count = 2;
bgx->lmac_type = BGX_MODE_RXAUI;
bgx->lane_to_sds = 0xE4;
break;
case QLM_MODE_XFI_4X1:
bgx->lmac_count = 4;
bgx->lmac_type = BGX_MODE_XFI;
bgx->lane_to_sds = 0;
break;
case QLM_MODE_XLAUI_1X4:
bgx->lmac_count = 1;
bgx->lmac_type = BGX_MODE_XLAUI;
bgx->lane_to_sds = 0xE4;
break;
case QLM_MODE_10G_KR_4X1:
bgx->lmac_count = 4;
bgx->lmac_type = BGX_MODE_10G_KR;
bgx->lane_to_sds = 0;
bgx->use_training = 1;
break;
case QLM_MODE_40G_KR4_1X4:
bgx->lmac_count = 1;
bgx->lmac_type = BGX_MODE_40G_KR;
bgx->lane_to_sds = 0xE4;
bgx->use_training = 1;
break;
default:
bgx->lmac_count = 0;
break;
}
/* Check if low level firmware has programmed LMAC count
* based on board type, if yes consider that otherwise
* the default static values
*/
lmac_count = bgx_reg_read(bgx, 0, BGX_CMR_RX_LMACS) & 0x7;
if (lmac_count != 4)
bgx->lmac_count = lmac_count;
}
static void bgx_init_hw(struct bgx *bgx)
{
int i;
bgx_set_num_ports(bgx);
struct lmac *lmac;
bgx_reg_modify(bgx, 0, BGX_CMR_GLOBAL_CFG, CMR_GLOBAL_CFG_FCS_STRIP);
if (bgx_reg_read(bgx, 0, BGX_CMR_BIST_STATUS))
......@@ -830,17 +804,9 @@ static void bgx_init_hw(struct bgx *bgx)
/* Set lmac type and lane2serdes mapping */
for (i = 0; i < bgx->lmac_count; i++) {
if (bgx->lmac_type == BGX_MODE_RXAUI) {
if (i)
bgx->lane_to_sds = 0x0e;
else
bgx->lane_to_sds = 0x04;
bgx_reg_write(bgx, i, BGX_CMRX_CFG,
(bgx->lmac_type << 8) | bgx->lane_to_sds);
continue;
}
lmac = &bgx->lmac[i];
bgx_reg_write(bgx, i, BGX_CMRX_CFG,
(bgx->lmac_type << 8) | (bgx->lane_to_sds + i));
(lmac->lmac_type << 8) | lmac->lane_to_sds);
bgx->lmac[i].lmacid_bd = lmac_count;
lmac_count++;
}
......@@ -863,55 +829,212 @@ static void bgx_init_hw(struct bgx *bgx)
bgx_reg_write(bgx, 0, BGX_CMR_RX_STREERING + (i * 8), 0x00);
}
static void bgx_get_qlm_mode(struct bgx *bgx)
static u8 bgx_get_lane2sds_cfg(struct bgx *bgx, struct lmac *lmac)
{
return (u8)(bgx_reg_read(bgx, lmac->lmacid, BGX_CMRX_CFG) & 0xFF);
}
static void bgx_print_qlm_mode(struct bgx *bgx, u8 lmacid)
{
struct device *dev = &bgx->pdev->dev;
int lmac_type;
int train_en;
struct lmac *lmac;
char str[20];
u8 dlm;
/* Read LMAC0 type to figure out QLM mode
* This is configured by low level firmware
*/
lmac_type = bgx_reg_read(bgx, 0, BGX_CMRX_CFG);
lmac_type = (lmac_type >> 8) & 0x07;
if (lmacid > bgx->max_lmac)
return;
train_en = bgx_reg_read(bgx, 0, BGX_SPUX_BR_PMD_CRTL) &
SPU_PMD_CRTL_TRAIN_EN;
lmac = &bgx->lmac[lmacid];
dlm = (lmacid / 2) + (bgx->bgx_id * 2);
if (!bgx->is_dlm)
sprintf(str, "BGX%d QLM mode", bgx->bgx_id);
else
sprintf(str, "BGX%d DLM%d mode", bgx->bgx_id, dlm);
switch (lmac_type) {
switch (lmac->lmac_type) {
case BGX_MODE_SGMII:
bgx->qlm_mode = QLM_MODE_SGMII;
dev_info(dev, "BGX%d QLM mode: SGMII\n", bgx->bgx_id);
dev_info(dev, "%s: SGMII\n", (char *)str);
break;
case BGX_MODE_XAUI:
bgx->qlm_mode = QLM_MODE_XAUI_1X4;
dev_info(dev, "BGX%d QLM mode: XAUI\n", bgx->bgx_id);
dev_info(dev, "%s: XAUI\n", (char *)str);
break;
case BGX_MODE_RXAUI:
bgx->qlm_mode = QLM_MODE_RXAUI_2X2;
dev_info(dev, "BGX%d QLM mode: RXAUI\n", bgx->bgx_id);
dev_info(dev, "%s: RXAUI\n", (char *)str);
break;
case BGX_MODE_XFI:
if (!train_en) {
bgx->qlm_mode = QLM_MODE_XFI_4X1;
dev_info(dev, "BGX%d QLM mode: XFI\n", bgx->bgx_id);
} else {
bgx->qlm_mode = QLM_MODE_10G_KR_4X1;
dev_info(dev, "BGX%d QLM mode: 10G_KR\n", bgx->bgx_id);
}
if (!lmac->use_training)
dev_info(dev, "%s: XFI\n", (char *)str);
else
dev_info(dev, "%s: 10G_KR\n", (char *)str);
break;
case BGX_MODE_XLAUI:
if (!train_en) {
bgx->qlm_mode = QLM_MODE_XLAUI_1X4;
dev_info(dev, "BGX%d QLM mode: XLAUI\n", bgx->bgx_id);
} else {
bgx->qlm_mode = QLM_MODE_40G_KR4_1X4;
dev_info(dev, "BGX%d QLM mode: 40G_KR4\n", bgx->bgx_id);
if (!lmac->use_training)
dev_info(dev, "%s: XLAUI\n", (char *)str);
else
dev_info(dev, "%s: 40G_KR4\n", (char *)str);
break;
case BGX_MODE_QSGMII:
if ((lmacid == 0) &&
(bgx_get_lane2sds_cfg(bgx, lmac) != lmacid))
return;
if ((lmacid == 2) &&
(bgx_get_lane2sds_cfg(bgx, lmac) == lmacid))
return;
dev_info(dev, "%s: QSGMII\n", (char *)str);
break;
case BGX_MODE_RGMII:
dev_info(dev, "%s: RGMII\n", (char *)str);
break;
case BGX_MODE_INVALID:
/* Nothing to do */
break;
}
}
static void lmac_set_lane2sds(struct bgx *bgx, struct lmac *lmac)
{
switch (lmac->lmac_type) {
case BGX_MODE_SGMII:
case BGX_MODE_XFI:
lmac->lane_to_sds = lmac->lmacid;
break;
case BGX_MODE_XAUI:
case BGX_MODE_XLAUI:
case BGX_MODE_RGMII:
lmac->lane_to_sds = 0xE4;
break;
case BGX_MODE_RXAUI:
lmac->lane_to_sds = (lmac->lmacid) ? 0xE : 0x4;
break;
case BGX_MODE_QSGMII:
/* There is no way to determine if DLM0/2 is QSGMII or
* DLM1/3 is configured to QSGMII as bootloader will
* configure all LMACs, so take whatever is configured
* by low level firmware.
*/
lmac->lane_to_sds = bgx_get_lane2sds_cfg(bgx, lmac);
break;
default:
bgx->qlm_mode = QLM_MODE_SGMII;
dev_info(dev, "BGX%d QLM default mode: SGMII\n", bgx->bgx_id);
lmac->lane_to_sds = 0;
break;
}
}
static void lmac_set_training(struct bgx *bgx, struct lmac *lmac, int lmacid)
{
if ((lmac->lmac_type != BGX_MODE_10G_KR) &&
(lmac->lmac_type != BGX_MODE_40G_KR)) {
lmac->use_training = 0;
return;
}
lmac->use_training = bgx_reg_read(bgx, lmacid, BGX_SPUX_BR_PMD_CRTL) &
SPU_PMD_CRTL_TRAIN_EN;
}
static void bgx_set_lmac_config(struct bgx *bgx, u8 idx)
{
struct lmac *lmac;
struct lmac *olmac;
u64 cmr_cfg;
u8 lmac_type;
u8 lane_to_sds;
lmac = &bgx->lmac[idx];
if (!bgx->is_dlm || bgx->is_rgx) {
/* Read LMAC0 type to figure out QLM mode
* This is configured by low level firmware
*/
cmr_cfg = bgx_reg_read(bgx, 0, BGX_CMRX_CFG);
lmac->lmac_type = (cmr_cfg >> 8) & 0x07;
if (bgx->is_rgx)
lmac->lmac_type = BGX_MODE_RGMII;
lmac_set_training(bgx, lmac, 0);
lmac_set_lane2sds(bgx, lmac);
return;
}
/* On 81xx BGX can be split across 2 DLMs
* firmware programs lmac_type of LMAC0 and LMAC2
*/
if ((idx == 0) || (idx == 2)) {
cmr_cfg = bgx_reg_read(bgx, idx, BGX_CMRX_CFG);
lmac_type = (u8)((cmr_cfg >> 8) & 0x07);
lane_to_sds = (u8)(cmr_cfg & 0xFF);
/* Check if config is not reset value */
if ((lmac_type == 0) && (lane_to_sds == 0xE4))
lmac->lmac_type = BGX_MODE_INVALID;
else
lmac->lmac_type = lmac_type;
lmac_set_training(bgx, lmac, lmac->lmacid);
lmac_set_lane2sds(bgx, lmac);
/* Set LMAC type of other lmac on same DLM i.e LMAC 1/3 */
olmac = &bgx->lmac[idx + 1];
olmac->lmac_type = lmac->lmac_type;
lmac_set_training(bgx, olmac, olmac->lmacid);
lmac_set_lane2sds(bgx, olmac);
}
}
static bool is_dlm0_in_bgx_mode(struct bgx *bgx)
{
struct lmac *lmac;
if (!bgx->is_dlm)
return true;
lmac = &bgx->lmac[0];
if (lmac->lmac_type == BGX_MODE_INVALID)
return false;
return true;
}
static void bgx_get_qlm_mode(struct bgx *bgx)
{
struct lmac *lmac;
struct lmac *lmac01;
struct lmac *lmac23;
u8 idx;
/* Init all LMAC's type to invalid */
for (idx = 0; idx < bgx->max_lmac; idx++) {
lmac = &bgx->lmac[idx];
lmac->lmacid = idx;
lmac->lmac_type = BGX_MODE_INVALID;
lmac->use_training = false;
}
/* It is assumed that low level firmware sets this value */
bgx->lmac_count = bgx_reg_read(bgx, 0, BGX_CMR_RX_LMACS) & 0x7;
if (bgx->lmac_count > bgx->max_lmac)
bgx->lmac_count = bgx->max_lmac;
for (idx = 0; idx < bgx->max_lmac; idx++)
bgx_set_lmac_config(bgx, idx);
if (!bgx->is_dlm || bgx->is_rgx) {
bgx_print_qlm_mode(bgx, 0);
return;
}
if (bgx->lmac_count) {
bgx_print_qlm_mode(bgx, 0);
bgx_print_qlm_mode(bgx, 2);
}
/* If DLM0 is not in BGX mode then LMAC0/1 have
* to be configured with serdes lanes of DLM1
*/
if (is_dlm0_in_bgx_mode(bgx) || (bgx->lmac_count > 2))
return;
for (idx = 0; idx < bgx->lmac_count; idx++) {
lmac01 = &bgx->lmac[idx];
lmac23 = &bgx->lmac[idx + 2];
lmac01->lmac_type = lmac23->lmac_type;
lmac01->lane_to_sds = lmac23->lane_to_sds;
}
}
......@@ -1042,7 +1165,7 @@ static int bgx_init_of_phy(struct bgx *bgx)
}
lmac++;
if (lmac == MAX_LMAC_PER_BGX) {
if (lmac == bgx->max_lmac) {
of_node_put(node);
break;
}
......@@ -1087,6 +1210,7 @@ static int bgx_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
struct device *dev = &pdev->dev;
struct bgx *bgx = NULL;
u8 lmac;
u16 sdevid;
bgx = devm_kzalloc(dev, sizeof(*bgx), GFP_KERNEL);
if (!bgx)
......@@ -1115,10 +1239,30 @@ static int bgx_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = -ENOMEM;
goto err_release_regions;
}
bgx->bgx_id = (pci_resource_start(pdev, PCI_CFG_REG_BAR_NUM) >> 24) & 1;
bgx->bgx_id += nic_get_node_id(pdev) * MAX_BGX_PER_CN88XX;
pci_read_config_word(pdev, PCI_DEVICE_ID, &sdevid);
if (sdevid != PCI_DEVICE_ID_THUNDER_RGX) {
bgx->bgx_id =
(pci_resource_start(pdev, PCI_CFG_REG_BAR_NUM) >> 24) & 1;
bgx->bgx_id += nic_get_node_id(pdev) * MAX_BGX_PER_NODE;
bgx->max_lmac = MAX_LMAC_PER_BGX;
bgx_vnic[bgx->bgx_id] = bgx;
} else {
bgx->is_rgx = true;
bgx->max_lmac = 1;
bgx->bgx_id = MAX_BGX_PER_CN81XX - 1;
bgx_vnic[bgx->bgx_id] = bgx;
xcv_init_hw();
}
/* On 81xx all are DLMs and on 83xx there are 3 BGX QLMs and one
* BGX i.e BGX2 can be split across 2 DLMs.
*/
pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &sdevid);
if ((sdevid == PCI_SUBSYS_DEVID_81XX_BGX) ||
((sdevid == PCI_SUBSYS_DEVID_83XX_BGX) && (bgx->bgx_id == 2)))
bgx->is_dlm = true;
bgx_get_qlm_mode(bgx);
err = bgx_init_phy(bgx);
......@@ -1133,6 +1277,8 @@ static int bgx_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
if (err) {
dev_err(dev, "BGX%d failed to enable lmac%d\n",
bgx->bgx_id, lmac);
while (lmac)
bgx_lmac_disable(bgx, --lmac);
goto err_enable;
}
}
......
......@@ -9,8 +9,20 @@
#ifndef THUNDER_BGX_H
#define THUNDER_BGX_H
#define MAX_BGX_THUNDER 8 /* Max 4 nodes, 2 per node */
/* PCI device ID */
#define PCI_DEVICE_ID_THUNDER_BGX 0xA026
#define PCI_DEVICE_ID_THUNDER_RGX 0xA054
/* Subsystem device IDs */
#define PCI_SUBSYS_DEVID_88XX_BGX 0xA126
#define PCI_SUBSYS_DEVID_81XX_BGX 0xA226
#define PCI_SUBSYS_DEVID_83XX_BGX 0xA326
#define MAX_BGX_THUNDER 8 /* Max 2 nodes, 4 per node */
#define MAX_BGX_PER_CN88XX 2
#define MAX_BGX_PER_CN81XX 3 /* 2 BGXs + 1 RGX */
#define MAX_BGX_PER_CN83XX 4
#define MAX_BGX_PER_NODE 4
#define MAX_LMAC_PER_BGX 4
#define MAX_BGX_CHANS_PER_LMAC 16
#define MAX_DMAC_PER_LMAC 8
......@@ -18,8 +30,6 @@
#define MAX_DMAC_PER_LMAC_TNS_BYPASS_MODE 2
#define MAX_LMAC (MAX_BGX_PER_CN88XX * MAX_LMAC_PER_BGX)
/* Registers */
#define BGX_CMRX_CFG 0x00
#define CMR_PKT_TX_EN BIT_ULL(13)
......@@ -136,6 +146,7 @@
#define BGX_GMP_PCS_ANX_AN_RESULTS 0x30020
#define BGX_GMP_PCS_SGM_AN_ADV 0x30068
#define BGX_GMP_PCS_MISCX_CTL 0x30078
#define PCS_MISC_CTL_DISP_EN BIT_ULL(13)
#define PCS_MISC_CTL_GMX_ENO BIT_ULL(11)
#define PCS_MISC_CTL_SAMP_PT_MASK 0x7Full
#define BGX_GMP_GMI_PRTX_CFG 0x38020
......@@ -194,6 +205,9 @@ void bgx_set_lmac_mac(int node, int bgx_idx, int lmacid, const u8 *mac);
void bgx_get_lmac_link_state(int node, int bgx_idx, int lmacid, void *status);
void bgx_lmac_internal_loopback(int node, int bgx_idx,
int lmac_idx, bool enable);
void xcv_init_hw(void);
void xcv_setup_link(bool link_up, int link_speed);
u64 bgx_get_rx_stats(int node, int bgx_idx, int lmac, int idx);
u64 bgx_get_tx_stats(int node, int bgx_idx, int lmac, int idx);
#define BGX_RX_STATS_COUNT 11
......@@ -213,16 +227,9 @@ enum LMAC_TYPE {
BGX_MODE_XLAUI = 4, /* 4 lanes, 10.3125 Gbaud */
BGX_MODE_10G_KR = 3,/* 1 lane, 10.3125 Gbaud */
BGX_MODE_40G_KR = 4,/* 4 lanes, 10.3125 Gbaud */
};
enum qlm_mode {
QLM_MODE_SGMII, /* SGMII, each lane independent */
QLM_MODE_XAUI_1X4, /* 1 XAUI or DXAUI, 4 lanes */
QLM_MODE_RXAUI_2X2, /* 2 RXAUI, 2 lanes each */
QLM_MODE_XFI_4X1, /* 4 XFI, 1 lane each */
QLM_MODE_XLAUI_1X4, /* 1 XLAUI, 4 lanes each */
QLM_MODE_10G_KR_4X1, /* 4 10GBASE-KR, 1 lane each */
QLM_MODE_40G_KR4_1X4, /* 1 40GBASE-KR4, 4 lanes each */
BGX_MODE_RGMII = 5,
BGX_MODE_QSGMII = 6,
BGX_MODE_INVALID = 7,
};
#endif /* THUNDER_BGX_H */
/*
* Copyright (C) 2016 Cavium, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*/
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/phy.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include "nic.h"
#include "thunder_bgx.h"
#define DRV_NAME "thunder-xcv"
#define DRV_VERSION "1.0"
/* Register offsets */
#define XCV_RESET 0x00
#define PORT_EN BIT_ULL(63)
#define CLK_RESET BIT_ULL(15)
#define DLL_RESET BIT_ULL(11)
#define COMP_EN BIT_ULL(7)
#define TX_PKT_RESET BIT_ULL(3)
#define TX_DATA_RESET BIT_ULL(2)
#define RX_PKT_RESET BIT_ULL(1)
#define RX_DATA_RESET BIT_ULL(0)
#define XCV_DLL_CTL 0x10
#define CLKRX_BYP BIT_ULL(23)
#define CLKTX_BYP BIT_ULL(15)
#define XCV_COMP_CTL 0x20
#define DRV_BYP BIT_ULL(63)
#define XCV_CTL 0x30
#define XCV_INT 0x40
#define XCV_INT_W1S 0x48
#define XCV_INT_ENA_W1C 0x50
#define XCV_INT_ENA_W1S 0x58
#define XCV_INBND_STATUS 0x80
#define XCV_BATCH_CRD_RET 0x100
struct xcv {
void __iomem *reg_base;
struct pci_dev *pdev;
};
static struct xcv *xcv;
/* Supported devices */
static const struct pci_device_id xcv_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA056) },
{ 0, } /* end of table */
};
MODULE_AUTHOR("Cavium Inc");
MODULE_DESCRIPTION("Cavium Thunder RGX/XCV Driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRV_VERSION);
MODULE_DEVICE_TABLE(pci, xcv_id_table);
void xcv_init_hw(void)
{
u64 cfg;
/* Take DLL out of reset */
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg &= ~DLL_RESET;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
/* Take clock tree out of reset */
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg &= ~CLK_RESET;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
/* Wait for DLL to lock */
msleep(1);
/* Configure DLL - enable or bypass
* TX no bypass, RX bypass
*/
cfg = readq_relaxed(xcv->reg_base + XCV_DLL_CTL);
cfg &= ~0xFF03;
cfg |= CLKRX_BYP;
writeq_relaxed(cfg, xcv->reg_base + XCV_DLL_CTL);
/* Enable compensation controller and force the
* write to be visible to HW by readig back.
*/
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg |= COMP_EN;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
readq_relaxed(xcv->reg_base + XCV_RESET);
/* Wait for compensation state machine to lock */
msleep(10);
/* enable the XCV block */
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg |= PORT_EN;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg |= CLK_RESET;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
}
EXPORT_SYMBOL(xcv_init_hw);
void xcv_setup_link(bool link_up, int link_speed)
{
u64 cfg;
int speed = 2;
if (!xcv) {
dev_err(&xcv->pdev->dev,
"XCV init not done, probe may have failed\n");
return;
}
if (link_speed == 100)
speed = 1;
else if (link_speed == 10)
speed = 0;
if (link_up) {
/* set operating speed */
cfg = readq_relaxed(xcv->reg_base + XCV_CTL);
cfg &= ~0x03;
cfg |= speed;
writeq_relaxed(cfg, xcv->reg_base + XCV_CTL);
/* Reset datapaths */
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg |= TX_DATA_RESET | RX_DATA_RESET;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
/* Enable the packet flow */
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg |= TX_PKT_RESET | RX_PKT_RESET;
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
/* Return credits to RGX */
writeq_relaxed(0x01, xcv->reg_base + XCV_BATCH_CRD_RET);
} else {
/* Disable packet flow */
cfg = readq_relaxed(xcv->reg_base + XCV_RESET);
cfg &= ~(TX_PKT_RESET | RX_PKT_RESET);
writeq_relaxed(cfg, xcv->reg_base + XCV_RESET);
readq_relaxed(xcv->reg_base + XCV_RESET);
}
}
EXPORT_SYMBOL(xcv_setup_link);
static int xcv_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int err;
struct device *dev = &pdev->dev;
xcv = devm_kzalloc(dev, sizeof(struct xcv), GFP_KERNEL);
if (!xcv)
return -ENOMEM;
xcv->pdev = pdev;
pci_set_drvdata(pdev, xcv);
err = pci_enable_device(pdev);
if (err) {
dev_err(dev, "Failed to enable PCI device\n");
goto err_kfree;
}
err = pci_request_regions(pdev, DRV_NAME);
if (err) {
dev_err(dev, "PCI request regions failed 0x%x\n", err);
goto err_disable_device;
}
/* MAP configuration registers */
xcv->reg_base = pcim_iomap(pdev, PCI_CFG_REG_BAR_NUM, 0);
if (!xcv->reg_base) {
dev_err(dev, "XCV: Cannot map CSR memory space, aborting\n");
err = -ENOMEM;
goto err_release_regions;
}
return 0;
err_release_regions:
pci_release_regions(pdev);
err_disable_device:
pci_disable_device(pdev);
err_kfree:
pci_set_drvdata(pdev, NULL);
devm_kfree(dev, xcv);
xcv = NULL;
return err;
}
static void xcv_remove(struct pci_dev *pdev)
{
struct device *dev = &pdev->dev;
if (xcv) {
devm_kfree(dev, xcv);
xcv = NULL;
}
pci_release_regions(pdev);
pci_disable_device(pdev);
pci_set_drvdata(pdev, NULL);
}
static struct pci_driver xcv_driver = {
.name = DRV_NAME,
.id_table = xcv_id_table,
.probe = xcv_probe,
.remove = xcv_remove,
};
static int __init xcv_init_module(void)
{
pr_info("%s, ver %s\n", DRV_NAME, DRV_VERSION);
return pci_register_driver(&xcv_driver);
}
static void __exit xcv_cleanup_module(void)
{
pci_unregister_driver(&xcv_driver);
}
module_init(xcv_init_module);
module_exit(xcv_cleanup_module);
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