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

Merge branch 'qlcnic'

Himanshu Madhani says:

====================
qlcnic: Bug fixes.

This series contains bug fixes for mailbox handling and multi Tx queue support
for all supported adapters.

changes from v1 -> v2
o updated patch to fix usage of netif_tx_{wake,stop} api during link change
  as per David Miller's suggestion.
o Dropped patch to use spinklock per tx queue for more work.
o Added reworked patch for memory allocation failures.
o Added patch to allow capturing of dump, when auto recovery is disabled in firmware.
o Added patches for mailbox interrupt handling and debugging data for mailbox failure.

Please apply to net.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 7022ef8b 0951c5c2
...@@ -447,7 +447,8 @@ irqreturn_t qlcnic_83xx_intr(int irq, void *data) ...@@ -447,7 +447,8 @@ irqreturn_t qlcnic_83xx_intr(int irq, void *data)
qlcnic_83xx_poll_process_aen(adapter); qlcnic_83xx_poll_process_aen(adapter);
if (ahw->diag_test == QLCNIC_INTERRUPT_TEST) { if (ahw->diag_test) {
if (ahw->diag_test == QLCNIC_INTERRUPT_TEST)
ahw->diag_cnt++; ahw->diag_cnt++;
qlcnic_83xx_enable_legacy_msix_mbx_intr(adapter); qlcnic_83xx_enable_legacy_msix_mbx_intr(adapter);
return IRQ_HANDLED; return IRQ_HANDLED;
...@@ -1345,11 +1346,6 @@ static int qlcnic_83xx_diag_alloc_res(struct net_device *netdev, int test, ...@@ -1345,11 +1346,6 @@ static int qlcnic_83xx_diag_alloc_res(struct net_device *netdev, int test,
} }
if (adapter->ahw->diag_test == QLCNIC_LOOPBACK_TEST) { if (adapter->ahw->diag_test == QLCNIC_LOOPBACK_TEST) {
/* disable and free mailbox interrupt */
if (!(adapter->flags & QLCNIC_MSIX_ENABLED)) {
qlcnic_83xx_enable_mbx_poll(adapter);
qlcnic_83xx_free_mbx_intr(adapter);
}
adapter->ahw->loopback_state = 0; adapter->ahw->loopback_state = 0;
adapter->ahw->hw_ops->setup_link_event(adapter, 1); adapter->ahw->hw_ops->setup_link_event(adapter, 1);
} }
...@@ -1363,33 +1359,20 @@ static void qlcnic_83xx_diag_free_res(struct net_device *netdev, ...@@ -1363,33 +1359,20 @@ static void qlcnic_83xx_diag_free_res(struct net_device *netdev,
{ {
struct qlcnic_adapter *adapter = netdev_priv(netdev); struct qlcnic_adapter *adapter = netdev_priv(netdev);
struct qlcnic_host_sds_ring *sds_ring; struct qlcnic_host_sds_ring *sds_ring;
int ring, err; int ring;
clear_bit(__QLCNIC_DEV_UP, &adapter->state); clear_bit(__QLCNIC_DEV_UP, &adapter->state);
if (adapter->ahw->diag_test == QLCNIC_INTERRUPT_TEST) { if (adapter->ahw->diag_test == QLCNIC_INTERRUPT_TEST) {
for (ring = 0; ring < adapter->drv_sds_rings; ring++) { for (ring = 0; ring < adapter->drv_sds_rings; ring++) {
sds_ring = &adapter->recv_ctx->sds_rings[ring]; sds_ring = &adapter->recv_ctx->sds_rings[ring];
if (adapter->flags & QLCNIC_MSIX_ENABLED)
qlcnic_83xx_disable_intr(adapter, sds_ring); qlcnic_83xx_disable_intr(adapter, sds_ring);
if (!(adapter->flags & QLCNIC_MSIX_ENABLED))
qlcnic_83xx_enable_mbx_poll(adapter);
} }
} }
qlcnic_fw_destroy_ctx(adapter); qlcnic_fw_destroy_ctx(adapter);
qlcnic_detach(adapter); qlcnic_detach(adapter);
if (adapter->ahw->diag_test == QLCNIC_LOOPBACK_TEST) {
if (!(adapter->flags & QLCNIC_MSIX_ENABLED)) {
err = qlcnic_83xx_setup_mbx_intr(adapter);
qlcnic_83xx_disable_mbx_poll(adapter);
if (err) {
dev_err(&adapter->pdev->dev,
"%s: failed to setup mbx interrupt\n",
__func__);
goto out;
}
}
}
adapter->ahw->diag_test = 0; adapter->ahw->diag_test = 0;
adapter->drv_sds_rings = drv_sds_rings; adapter->drv_sds_rings = drv_sds_rings;
...@@ -1399,9 +1382,6 @@ static void qlcnic_83xx_diag_free_res(struct net_device *netdev, ...@@ -1399,9 +1382,6 @@ static void qlcnic_83xx_diag_free_res(struct net_device *netdev,
if (netif_running(netdev)) if (netif_running(netdev))
__qlcnic_up(adapter, netdev); __qlcnic_up(adapter, netdev);
if (adapter->ahw->diag_test == QLCNIC_INTERRUPT_TEST &&
!(adapter->flags & QLCNIC_MSIX_ENABLED))
qlcnic_83xx_disable_mbx_poll(adapter);
out: out:
netif_device_attach(netdev); netif_device_attach(netdev);
} }
...@@ -3754,6 +3734,19 @@ static void qlcnic_83xx_decode_mbx_rsp(struct qlcnic_adapter *adapter, ...@@ -3754,6 +3734,19 @@ static void qlcnic_83xx_decode_mbx_rsp(struct qlcnic_adapter *adapter,
return; return;
} }
static inline void qlcnic_dump_mailbox_registers(struct qlcnic_adapter *adapter)
{
struct qlcnic_hardware_context *ahw = adapter->ahw;
u32 offset;
offset = QLCRDX(ahw, QLCNIC_DEF_INT_MASK);
dev_info(&adapter->pdev->dev, "Mbx interrupt mask=0x%x, Mbx interrupt enable=0x%x, Host mbx control=0x%x, Fw mbx control=0x%x",
readl(ahw->pci_base0 + offset),
QLCRDX(ahw, QLCNIC_MBX_INTR_ENBL),
QLCRDX(ahw, QLCNIC_HOST_MBX_CTRL),
QLCRDX(ahw, QLCNIC_FW_MBX_CTRL));
}
static void qlcnic_83xx_mailbox_worker(struct work_struct *work) static void qlcnic_83xx_mailbox_worker(struct work_struct *work)
{ {
struct qlcnic_mailbox *mbx = container_of(work, struct qlcnic_mailbox, struct qlcnic_mailbox *mbx = container_of(work, struct qlcnic_mailbox,
...@@ -3798,6 +3791,8 @@ static void qlcnic_83xx_mailbox_worker(struct work_struct *work) ...@@ -3798,6 +3791,8 @@ static void qlcnic_83xx_mailbox_worker(struct work_struct *work)
__func__, cmd->cmd_op, cmd->type, ahw->pci_func, __func__, cmd->cmd_op, cmd->type, ahw->pci_func,
ahw->op_mode); ahw->op_mode);
clear_bit(QLC_83XX_MBX_READY, &mbx->status); clear_bit(QLC_83XX_MBX_READY, &mbx->status);
qlcnic_dump_mailbox_registers(adapter);
qlcnic_83xx_get_mbx_data(adapter, cmd);
qlcnic_dump_mbx(adapter, cmd); qlcnic_dump_mbx(adapter, cmd);
qlcnic_83xx_idc_request_reset(adapter, qlcnic_83xx_idc_request_reset(adapter,
QLCNIC_FORCE_FW_DUMP_KEY); QLCNIC_FORCE_FW_DUMP_KEY);
......
...@@ -662,4 +662,5 @@ pci_ers_result_t qlcnic_83xx_io_error_detected(struct pci_dev *, ...@@ -662,4 +662,5 @@ pci_ers_result_t qlcnic_83xx_io_error_detected(struct pci_dev *,
pci_channel_state_t); pci_channel_state_t);
pci_ers_result_t qlcnic_83xx_io_slot_reset(struct pci_dev *); pci_ers_result_t qlcnic_83xx_io_slot_reset(struct pci_dev *);
void qlcnic_83xx_io_resume(struct pci_dev *); void qlcnic_83xx_io_resume(struct pci_dev *);
void qlcnic_83xx_stop_hw(struct qlcnic_adapter *);
#endif #endif
...@@ -740,6 +740,7 @@ static int qlcnic_83xx_idc_unknown_state(struct qlcnic_adapter *adapter) ...@@ -740,6 +740,7 @@ static int qlcnic_83xx_idc_unknown_state(struct qlcnic_adapter *adapter)
adapter->ahw->idc.err_code = -EIO; adapter->ahw->idc.err_code = -EIO;
dev_err(&adapter->pdev->dev, dev_err(&adapter->pdev->dev,
"%s: Device in unknown state\n", __func__); "%s: Device in unknown state\n", __func__);
clear_bit(__QLCNIC_RESETTING, &adapter->state);
return 0; return 0;
} }
...@@ -818,7 +819,6 @@ static int qlcnic_83xx_idc_ready_state(struct qlcnic_adapter *adapter) ...@@ -818,7 +819,6 @@ static int qlcnic_83xx_idc_ready_state(struct qlcnic_adapter *adapter)
struct qlcnic_hardware_context *ahw = adapter->ahw; struct qlcnic_hardware_context *ahw = adapter->ahw;
struct qlcnic_mailbox *mbx = ahw->mailbox; struct qlcnic_mailbox *mbx = ahw->mailbox;
int ret = 0; int ret = 0;
u32 owner;
u32 val; u32 val;
/* Perform NIC configuration based ready state entry actions */ /* Perform NIC configuration based ready state entry actions */
...@@ -848,9 +848,9 @@ static int qlcnic_83xx_idc_ready_state(struct qlcnic_adapter *adapter) ...@@ -848,9 +848,9 @@ static int qlcnic_83xx_idc_ready_state(struct qlcnic_adapter *adapter)
set_bit(__QLCNIC_RESETTING, &adapter->state); set_bit(__QLCNIC_RESETTING, &adapter->state);
qlcnic_83xx_idc_enter_need_reset_state(adapter, 1); qlcnic_83xx_idc_enter_need_reset_state(adapter, 1);
} else { } else {
owner = qlcnic_83xx_idc_find_reset_owner_id(adapter); netdev_info(adapter->netdev, "%s: Auto firmware recovery is disabled\n",
if (ahw->pci_func == owner) __func__);
qlcnic_dump_fw(adapter); qlcnic_83xx_idc_enter_failed_state(adapter, 1);
} }
return -EIO; return -EIO;
} }
...@@ -948,13 +948,26 @@ static int qlcnic_83xx_idc_need_quiesce_state(struct qlcnic_adapter *adapter) ...@@ -948,13 +948,26 @@ static int qlcnic_83xx_idc_need_quiesce_state(struct qlcnic_adapter *adapter)
return 0; return 0;
} }
static int qlcnic_83xx_idc_failed_state(struct qlcnic_adapter *adapter) static void qlcnic_83xx_idc_failed_state(struct qlcnic_adapter *adapter)
{ {
dev_err(&adapter->pdev->dev, "%s: please restart!!\n", __func__); struct qlcnic_hardware_context *ahw = adapter->ahw;
u32 val, owner;
val = QLCRDX(adapter->ahw, QLC_83XX_IDC_CTRL);
if (val & QLC_83XX_IDC_DISABLE_FW_RESET_RECOVERY) {
owner = qlcnic_83xx_idc_find_reset_owner_id(adapter);
if (ahw->pci_func == owner) {
qlcnic_83xx_stop_hw(adapter);
qlcnic_dump_fw(adapter);
}
}
netdev_warn(adapter->netdev, "%s: Reboot will be required to recover the adapter!!\n",
__func__);
clear_bit(__QLCNIC_RESETTING, &adapter->state); clear_bit(__QLCNIC_RESETTING, &adapter->state);
adapter->ahw->idc.err_code = -EIO; ahw->idc.err_code = -EIO;
return 0; return;
} }
static int qlcnic_83xx_idc_quiesce_state(struct qlcnic_adapter *adapter) static int qlcnic_83xx_idc_quiesce_state(struct qlcnic_adapter *adapter)
...@@ -1063,12 +1076,6 @@ void qlcnic_83xx_idc_poll_dev_state(struct work_struct *work) ...@@ -1063,12 +1076,6 @@ void qlcnic_83xx_idc_poll_dev_state(struct work_struct *work)
adapter->ahw->idc.prev_state = adapter->ahw->idc.curr_state; adapter->ahw->idc.prev_state = adapter->ahw->idc.curr_state;
qlcnic_83xx_periodic_tasks(adapter); qlcnic_83xx_periodic_tasks(adapter);
/* Do not reschedule if firmaware is in hanged state and auto
* recovery is disabled
*/
if ((adapter->flags & QLCNIC_FW_HANG) && !qlcnic_auto_fw_reset)
return;
/* Re-schedule the function */ /* Re-schedule the function */
if (test_bit(QLC_83XX_MODULE_LOADED, &adapter->ahw->idc.status)) if (test_bit(QLC_83XX_MODULE_LOADED, &adapter->ahw->idc.status))
qlcnic_schedule_work(adapter, qlcnic_83xx_idc_poll_dev_state, qlcnic_schedule_work(adapter, qlcnic_83xx_idc_poll_dev_state,
...@@ -1219,10 +1226,10 @@ void qlcnic_83xx_idc_request_reset(struct qlcnic_adapter *adapter, u32 key) ...@@ -1219,10 +1226,10 @@ void qlcnic_83xx_idc_request_reset(struct qlcnic_adapter *adapter, u32 key)
} }
val = QLCRDX(adapter->ahw, QLC_83XX_IDC_CTRL); val = QLCRDX(adapter->ahw, QLC_83XX_IDC_CTRL);
if ((val & QLC_83XX_IDC_DISABLE_FW_RESET_RECOVERY) || if (val & QLC_83XX_IDC_DISABLE_FW_RESET_RECOVERY) {
!qlcnic_auto_fw_reset) { netdev_info(adapter->netdev, "%s: Auto firmware recovery is disabled\n",
dev_err(&adapter->pdev->dev, __func__);
"%s:failed, device in non reset mode\n", __func__); qlcnic_83xx_idc_enter_failed_state(adapter, 0);
qlcnic_83xx_unlock_driver(adapter); qlcnic_83xx_unlock_driver(adapter);
return; return;
} }
...@@ -1254,24 +1261,24 @@ static int qlcnic_83xx_copy_bootloader(struct qlcnic_adapter *adapter) ...@@ -1254,24 +1261,24 @@ static int qlcnic_83xx_copy_bootloader(struct qlcnic_adapter *adapter)
if (size & 0xF) if (size & 0xF)
size = (size + 16) & ~0xF; size = (size + 16) & ~0xF;
p_cache = kzalloc(size, GFP_KERNEL); p_cache = vzalloc(size);
if (p_cache == NULL) if (p_cache == NULL)
return -ENOMEM; return -ENOMEM;
ret = qlcnic_83xx_lockless_flash_read32(adapter, src, p_cache, ret = qlcnic_83xx_lockless_flash_read32(adapter, src, p_cache,
size / sizeof(u32)); size / sizeof(u32));
if (ret) { if (ret) {
kfree(p_cache); vfree(p_cache);
return ret; return ret;
} }
/* 16 byte write to MS memory */ /* 16 byte write to MS memory */
ret = qlcnic_83xx_ms_mem_write128(adapter, dest, (u32 *)p_cache, ret = qlcnic_83xx_ms_mem_write128(adapter, dest, (u32 *)p_cache,
size / 16); size / 16);
if (ret) { if (ret) {
kfree(p_cache); vfree(p_cache);
return ret; return ret;
} }
kfree(p_cache); vfree(p_cache);
return ret; return ret;
} }
...@@ -1939,7 +1946,7 @@ static void qlcnic_83xx_exec_template_cmd(struct qlcnic_adapter *p_dev, ...@@ -1939,7 +1946,7 @@ static void qlcnic_83xx_exec_template_cmd(struct qlcnic_adapter *p_dev,
p_dev->ahw->reset.seq_index = index; p_dev->ahw->reset.seq_index = index;
} }
static void qlcnic_83xx_stop_hw(struct qlcnic_adapter *p_dev) void qlcnic_83xx_stop_hw(struct qlcnic_adapter *p_dev)
{ {
p_dev->ahw->reset.seq_index = 0; p_dev->ahw->reset.seq_index = 0;
...@@ -1994,6 +2001,14 @@ static int qlcnic_83xx_restart_hw(struct qlcnic_adapter *adapter) ...@@ -1994,6 +2001,14 @@ static int qlcnic_83xx_restart_hw(struct qlcnic_adapter *adapter)
val = QLCRDX(adapter->ahw, QLC_83XX_IDC_CTRL); val = QLCRDX(adapter->ahw, QLC_83XX_IDC_CTRL);
if (!(val & QLC_83XX_IDC_GRACEFULL_RESET)) if (!(val & QLC_83XX_IDC_GRACEFULL_RESET))
qlcnic_dump_fw(adapter); qlcnic_dump_fw(adapter);
if (val & QLC_83XX_IDC_DISABLE_FW_RESET_RECOVERY) {
netdev_info(adapter->netdev, "%s: Auto firmware recovery is disabled\n",
__func__);
qlcnic_83xx_idc_enter_failed_state(adapter, 1);
return err;
}
qlcnic_83xx_init_hw(adapter); qlcnic_83xx_init_hw(adapter);
if (qlcnic_83xx_copy_bootloader(adapter)) if (qlcnic_83xx_copy_bootloader(adapter))
...@@ -2073,8 +2088,8 @@ int qlcnic_83xx_configure_opmode(struct qlcnic_adapter *adapter) ...@@ -2073,8 +2088,8 @@ int qlcnic_83xx_configure_opmode(struct qlcnic_adapter *adapter)
ahw->nic_mode = QLCNIC_DEFAULT_MODE; ahw->nic_mode = QLCNIC_DEFAULT_MODE;
adapter->nic_ops->init_driver = qlcnic_83xx_init_default_driver; adapter->nic_ops->init_driver = qlcnic_83xx_init_default_driver;
ahw->idc.state_entry = qlcnic_83xx_idc_ready_state_entry; ahw->idc.state_entry = qlcnic_83xx_idc_ready_state_entry;
adapter->max_sds_rings = ahw->max_rx_ques; adapter->max_sds_rings = QLCNIC_MAX_SDS_RINGS;
adapter->max_tx_rings = ahw->max_tx_ques; adapter->max_tx_rings = QLCNIC_MAX_TX_RINGS;
} else { } else {
return -EIO; return -EIO;
} }
......
...@@ -667,30 +667,25 @@ qlcnic_set_ringparam(struct net_device *dev, ...@@ -667,30 +667,25 @@ qlcnic_set_ringparam(struct net_device *dev,
static int qlcnic_validate_ring_count(struct qlcnic_adapter *adapter, static int qlcnic_validate_ring_count(struct qlcnic_adapter *adapter,
u8 rx_ring, u8 tx_ring) u8 rx_ring, u8 tx_ring)
{ {
if (rx_ring == 0 || tx_ring == 0)
return -EINVAL;
if (rx_ring != 0) { if (rx_ring != 0) {
if (rx_ring > adapter->max_sds_rings) { if (rx_ring > adapter->max_sds_rings) {
netdev_err(adapter->netdev, "Invalid ring count, SDS ring count %d should not be greater than max %d driver sds rings.\n", netdev_err(adapter->netdev,
"Invalid ring count, SDS ring count %d should not be greater than max %d driver sds rings.\n",
rx_ring, adapter->max_sds_rings); rx_ring, adapter->max_sds_rings);
return -EINVAL; return -EINVAL;
} }
} }
if (tx_ring != 0) { if (tx_ring != 0) {
if (qlcnic_82xx_check(adapter) && if (tx_ring > adapter->max_tx_rings) {
(tx_ring > adapter->max_tx_rings)) {
netdev_err(adapter->netdev, netdev_err(adapter->netdev,
"Invalid ring count, Tx ring count %d should not be greater than max %d driver Tx rings.\n", "Invalid ring count, Tx ring count %d should not be greater than max %d driver Tx rings.\n",
tx_ring, adapter->max_tx_rings); tx_ring, adapter->max_tx_rings);
return -EINVAL; return -EINVAL;
} }
if (qlcnic_83xx_check(adapter) &&
(tx_ring > QLCNIC_SINGLE_RING)) {
netdev_err(adapter->netdev,
"Invalid ring count, Tx ring count %d should not be greater than %d driver Tx rings.\n",
tx_ring, QLCNIC_SINGLE_RING);
return -EINVAL;
}
} }
return 0; return 0;
...@@ -948,6 +943,7 @@ static int qlcnic_irq_test(struct net_device *netdev) ...@@ -948,6 +943,7 @@ static int qlcnic_irq_test(struct net_device *netdev)
struct qlcnic_hardware_context *ahw = adapter->ahw; struct qlcnic_hardware_context *ahw = adapter->ahw;
struct qlcnic_cmd_args cmd; struct qlcnic_cmd_args cmd;
int ret, drv_sds_rings = adapter->drv_sds_rings; int ret, drv_sds_rings = adapter->drv_sds_rings;
int drv_tx_rings = adapter->drv_tx_rings;
if (qlcnic_83xx_check(adapter)) if (qlcnic_83xx_check(adapter))
return qlcnic_83xx_interrupt_test(netdev); return qlcnic_83xx_interrupt_test(netdev);
...@@ -980,6 +976,7 @@ static int qlcnic_irq_test(struct net_device *netdev) ...@@ -980,6 +976,7 @@ static int qlcnic_irq_test(struct net_device *netdev)
clear_diag_irq: clear_diag_irq:
adapter->drv_sds_rings = drv_sds_rings; adapter->drv_sds_rings = drv_sds_rings;
adapter->drv_tx_rings = drv_tx_rings;
clear_bit(__QLCNIC_RESETTING, &adapter->state); clear_bit(__QLCNIC_RESETTING, &adapter->state);
return ret; return ret;
......
...@@ -687,17 +687,11 @@ void qlcnic_advert_link_change(struct qlcnic_adapter *adapter, int linkup) ...@@ -687,17 +687,11 @@ void qlcnic_advert_link_change(struct qlcnic_adapter *adapter, int linkup)
if (adapter->ahw->linkup && !linkup) { if (adapter->ahw->linkup && !linkup) {
netdev_info(netdev, "NIC Link is down\n"); netdev_info(netdev, "NIC Link is down\n");
adapter->ahw->linkup = 0; adapter->ahw->linkup = 0;
if (netif_running(netdev)) {
netif_carrier_off(netdev); netif_carrier_off(netdev);
netif_tx_stop_all_queues(netdev);
}
} else if (!adapter->ahw->linkup && linkup) { } else if (!adapter->ahw->linkup && linkup) {
netdev_info(netdev, "NIC Link is up\n"); netdev_info(netdev, "NIC Link is up\n");
adapter->ahw->linkup = 1; adapter->ahw->linkup = 1;
if (netif_running(netdev)) {
netif_carrier_on(netdev); netif_carrier_on(netdev);
netif_wake_queue(netdev);
}
} }
} }
......
...@@ -1178,6 +1178,7 @@ qlcnic_initialize_nic(struct qlcnic_adapter *adapter) ...@@ -1178,6 +1178,7 @@ qlcnic_initialize_nic(struct qlcnic_adapter *adapter)
} else { } else {
adapter->ahw->nic_mode = QLCNIC_DEFAULT_MODE; adapter->ahw->nic_mode = QLCNIC_DEFAULT_MODE;
adapter->max_tx_rings = QLCNIC_MAX_HW_TX_RINGS; adapter->max_tx_rings = QLCNIC_MAX_HW_TX_RINGS;
adapter->max_sds_rings = QLCNIC_MAX_SDS_RINGS;
adapter->flags &= ~QLCNIC_ESWITCH_ENABLED; adapter->flags &= ~QLCNIC_ESWITCH_ENABLED;
} }
...@@ -1940,7 +1941,6 @@ int qlcnic_diag_alloc_res(struct net_device *netdev, int test) ...@@ -1940,7 +1941,6 @@ int qlcnic_diag_alloc_res(struct net_device *netdev, int test)
qlcnic_detach(adapter); qlcnic_detach(adapter);
adapter->drv_sds_rings = QLCNIC_SINGLE_RING; adapter->drv_sds_rings = QLCNIC_SINGLE_RING;
adapter->drv_tx_rings = QLCNIC_SINGLE_RING;
adapter->ahw->diag_test = test; adapter->ahw->diag_test = test;
adapter->ahw->linkup = 0; adapter->ahw->linkup = 0;
......
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