be2net: event queue re-design
v2: Fixed up the bad typecasting pointed out by David... In the current design 8 TXQs are serviced by 1 EQ, while each RSS queue is serviced by a separate EQ. This is being changed as follows: - Upto 8 EQs will be used (based on the availabilty of msix vectors). Each EQ will handle 1 RSS and 1 TX ring. The default non-RSS RX queue and MCC queue are handled by the last EQ. - On cards which provide support, upto 8 RSS rings will be used, instead of the current limit of 4. The new design allows spreading the TX multi-queue completion processing across multiple CPUs unlike the previous design. Signed-off-by: Sathya Perla <sathya.perla@emulex.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Родитель
23677ce317
Коммит
10ef9ab432
|
@ -81,7 +81,7 @@ static inline char *nic_name(struct pci_dev *pdev)
|
|||
#define BE_MIN_MTU 256
|
||||
|
||||
#define BE_NUM_VLANS_SUPPORTED 64
|
||||
#define BE_MAX_EQD 96
|
||||
#define BE_MAX_EQD 96u
|
||||
#define BE_MAX_TX_FRAG_COUNT 30
|
||||
|
||||
#define EVNT_Q_LEN 1024
|
||||
|
@ -92,12 +92,16 @@ static inline char *nic_name(struct pci_dev *pdev)
|
|||
#define MCC_Q_LEN 128 /* total size not to exceed 8 pages */
|
||||
#define MCC_CQ_LEN 256
|
||||
|
||||
#define MAX_RSS_QS 4 /* BE limit is 4 queues/port */
|
||||
#define BE3_MAX_RSS_QS 8
|
||||
#define BE2_MAX_RSS_QS 4
|
||||
#define MAX_RSS_QS BE3_MAX_RSS_QS
|
||||
#define MAX_RX_QS (MAX_RSS_QS + 1) /* RSS qs + 1 def Rx */
|
||||
|
||||
#define MAX_TX_QS 8
|
||||
#define BE_MAX_MSIX_VECTORS (MAX_RX_QS + 1)/* RX + TX */
|
||||
#define MAX_MSIX_VECTORS MAX_RSS_QS
|
||||
#define BE_TX_BUDGET 256
|
||||
#define BE_NAPI_WEIGHT 64
|
||||
#define MAX_RX_POST BE_NAPI_WEIGHT /* Frags posted at a time */
|
||||
#define MAX_RX_POST BE_NAPI_WEIGHT /* Frags posted at a time */
|
||||
#define RX_FRAGS_REFILL_WM (RX_Q_LEN - MAX_RX_POST)
|
||||
|
||||
#define FW_VER_LEN 32
|
||||
|
@ -165,13 +169,16 @@ struct be_eq_obj {
|
|||
|
||||
/* Adaptive interrupt coalescing (AIC) info */
|
||||
bool enable_aic;
|
||||
u16 min_eqd; /* in usecs */
|
||||
u16 max_eqd; /* in usecs */
|
||||
u16 cur_eqd; /* in usecs */
|
||||
u8 eq_idx;
|
||||
u32 min_eqd; /* in usecs */
|
||||
u32 max_eqd; /* in usecs */
|
||||
u32 eqd; /* configured val when aic is off */
|
||||
u32 cur_eqd; /* in usecs */
|
||||
|
||||
u8 idx; /* array index */
|
||||
u16 tx_budget;
|
||||
struct napi_struct napi;
|
||||
};
|
||||
struct be_adapter *adapter;
|
||||
} ____cacheline_aligned_in_smp;
|
||||
|
||||
struct be_mcc_obj {
|
||||
struct be_queue_info q;
|
||||
|
@ -197,7 +204,7 @@ struct be_tx_obj {
|
|||
/* Remember the skbs that were transmitted */
|
||||
struct sk_buff *sent_skb_list[TX_Q_LEN];
|
||||
struct be_tx_stats stats;
|
||||
};
|
||||
} ____cacheline_aligned_in_smp;
|
||||
|
||||
/* Struct to remember the pages posted for rx frags */
|
||||
struct be_rx_page_info {
|
||||
|
@ -215,8 +222,6 @@ struct be_rx_stats {
|
|||
u32 rx_drops_no_skbs; /* skb allocation errors */
|
||||
u32 rx_drops_no_frags; /* HW has no fetched frags */
|
||||
u32 rx_post_fail; /* page post alloc failures */
|
||||
u32 rx_polls; /* NAPI calls */
|
||||
u32 rx_events;
|
||||
u32 rx_compl;
|
||||
u32 rx_mcast_pkts;
|
||||
u32 rx_compl_err; /* completions with err set */
|
||||
|
@ -249,16 +254,13 @@ struct be_rx_obj {
|
|||
struct be_queue_info cq;
|
||||
struct be_rx_compl_info rxcp;
|
||||
struct be_rx_page_info page_info_tbl[RX_Q_LEN];
|
||||
struct be_eq_obj rx_eq;
|
||||
struct be_rx_stats stats;
|
||||
u8 rss_id;
|
||||
bool rx_post_starved; /* Zero rx frags have been posted to BE */
|
||||
u32 cache_line_barrier[16];
|
||||
};
|
||||
} ____cacheline_aligned_in_smp;
|
||||
|
||||
struct be_drv_stats {
|
||||
u32 be_on_die_temperature;
|
||||
u32 tx_events;
|
||||
u32 eth_red_drops;
|
||||
u32 rx_drops_no_pbuf;
|
||||
u32 rx_drops_no_txpb;
|
||||
|
@ -320,20 +322,19 @@ struct be_adapter {
|
|||
spinlock_t mcc_lock; /* For serializing mcc cmds to BE card */
|
||||
spinlock_t mcc_cq_lock;
|
||||
|
||||
struct msix_entry msix_entries[BE_MAX_MSIX_VECTORS];
|
||||
u32 num_msix_vec;
|
||||
u32 num_evt_qs;
|
||||
struct be_eq_obj eq_obj[MAX_MSIX_VECTORS];
|
||||
struct msix_entry msix_entries[MAX_MSIX_VECTORS];
|
||||
bool isr_registered;
|
||||
|
||||
/* TX Rings */
|
||||
struct be_eq_obj tx_eq;
|
||||
u32 num_tx_qs;
|
||||
struct be_tx_obj tx_obj[MAX_TX_QS];
|
||||
u8 num_tx_qs;
|
||||
|
||||
u32 cache_line_break[8];
|
||||
|
||||
/* Rx rings */
|
||||
struct be_rx_obj rx_obj[MAX_RX_QS];
|
||||
u32 num_rx_qs;
|
||||
struct be_rx_obj rx_obj[MAX_RX_QS];
|
||||
u32 big_page_size; /* Compounded page size shared by rx wrbs */
|
||||
|
||||
u8 eq_next_idx;
|
||||
|
@ -404,24 +405,34 @@ struct be_adapter {
|
|||
extern const struct ethtool_ops be_ethtool_ops;
|
||||
|
||||
#define msix_enabled(adapter) (adapter->num_msix_vec > 0)
|
||||
#define tx_stats(txo) (&txo->stats)
|
||||
#define rx_stats(rxo) (&rxo->stats)
|
||||
#define num_irqs(adapter) (msix_enabled(adapter) ? \
|
||||
adapter->num_msix_vec : 1)
|
||||
#define tx_stats(txo) (&(txo)->stats)
|
||||
#define rx_stats(rxo) (&(rxo)->stats)
|
||||
|
||||
#define BE_SET_NETDEV_OPS(netdev, ops) (netdev->netdev_ops = ops)
|
||||
/* The default RXQ is the last RXQ */
|
||||
#define default_rxo(adpt) (&adpt->rx_obj[adpt->num_rx_qs - 1])
|
||||
|
||||
#define for_all_rx_queues(adapter, rxo, i) \
|
||||
for (i = 0, rxo = &adapter->rx_obj[i]; i < adapter->num_rx_qs; \
|
||||
i++, rxo++)
|
||||
|
||||
/* Just skip the first default non-rss queue */
|
||||
/* Skip the default non-rss queue (last one)*/
|
||||
#define for_all_rss_queues(adapter, rxo, i) \
|
||||
for (i = 0, rxo = &adapter->rx_obj[i+1]; i < (adapter->num_rx_qs - 1);\
|
||||
for (i = 0, rxo = &adapter->rx_obj[i]; i < (adapter->num_rx_qs - 1);\
|
||||
i++, rxo++)
|
||||
|
||||
#define for_all_tx_queues(adapter, txo, i) \
|
||||
for (i = 0, txo = &adapter->tx_obj[i]; i < adapter->num_tx_qs; \
|
||||
i++, txo++)
|
||||
|
||||
#define for_all_evt_queues(adapter, eqo, i) \
|
||||
for (i = 0, eqo = &adapter->eq_obj[i]; i < adapter->num_evt_qs; \
|
||||
i++, eqo++)
|
||||
|
||||
#define is_mcc_eqo(eqo) (eqo->idx == 0)
|
||||
#define mcc_eqo(adapter) (&adapter->eq_obj[0])
|
||||
|
||||
#define PAGE_SHIFT_4K 12
|
||||
#define PAGE_SIZE_4K (1 << PAGE_SHIFT_4K)
|
||||
|
||||
|
|
|
@ -235,10 +235,10 @@ void be_async_mcc_disable(struct be_adapter *adapter)
|
|||
adapter->mcc_obj.rearm_cq = false;
|
||||
}
|
||||
|
||||
int be_process_mcc(struct be_adapter *adapter, int *status)
|
||||
int be_process_mcc(struct be_adapter *adapter)
|
||||
{
|
||||
struct be_mcc_compl *compl;
|
||||
int num = 0;
|
||||
int num = 0, status = 0;
|
||||
struct be_mcc_obj *mcc_obj = &adapter->mcc_obj;
|
||||
|
||||
spin_lock_bh(&adapter->mcc_cq_lock);
|
||||
|
@ -252,32 +252,32 @@ int be_process_mcc(struct be_adapter *adapter, int *status)
|
|||
be_async_grp5_evt_process(adapter,
|
||||
compl->flags, compl);
|
||||
} else if (compl->flags & CQE_FLAGS_COMPLETED_MASK) {
|
||||
*status = be_mcc_compl_process(adapter, compl);
|
||||
status = be_mcc_compl_process(adapter, compl);
|
||||
atomic_dec(&mcc_obj->q.used);
|
||||
}
|
||||
be_mcc_compl_use(compl);
|
||||
num++;
|
||||
}
|
||||
|
||||
if (num)
|
||||
be_cq_notify(adapter, mcc_obj->cq.id, mcc_obj->rearm_cq, num);
|
||||
|
||||
spin_unlock_bh(&adapter->mcc_cq_lock);
|
||||
return num;
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Wait till no more pending mcc requests are present */
|
||||
static int be_mcc_wait_compl(struct be_adapter *adapter)
|
||||
{
|
||||
#define mcc_timeout 120000 /* 12s timeout */
|
||||
int i, num, status = 0;
|
||||
int i, status = 0;
|
||||
struct be_mcc_obj *mcc_obj = &adapter->mcc_obj;
|
||||
|
||||
for (i = 0; i < mcc_timeout; i++) {
|
||||
if (be_error(adapter))
|
||||
return -EIO;
|
||||
|
||||
num = be_process_mcc(adapter, &status);
|
||||
if (num)
|
||||
be_cq_notify(adapter, mcc_obj->cq.id,
|
||||
mcc_obj->rearm_cq, num);
|
||||
status = be_process_mcc(adapter);
|
||||
|
||||
if (atomic_read(&mcc_obj->q.used) == 0)
|
||||
break;
|
||||
|
@ -726,9 +726,8 @@ err:
|
|||
}
|
||||
|
||||
/* Uses Mbox */
|
||||
int be_cmd_cq_create(struct be_adapter *adapter,
|
||||
struct be_queue_info *cq, struct be_queue_info *eq,
|
||||
bool sol_evts, bool no_delay, int coalesce_wm)
|
||||
int be_cmd_cq_create(struct be_adapter *adapter, struct be_queue_info *cq,
|
||||
struct be_queue_info *eq, bool no_delay, int coalesce_wm)
|
||||
{
|
||||
struct be_mcc_wrb *wrb;
|
||||
struct be_cmd_req_cq_create *req;
|
||||
|
@ -759,7 +758,6 @@ int be_cmd_cq_create(struct be_adapter *adapter,
|
|||
ctxt, 1);
|
||||
AMAP_SET_BITS(struct amap_cq_context_lancer, eqid,
|
||||
ctxt, eq->id);
|
||||
AMAP_SET_BITS(struct amap_cq_context_lancer, armed, ctxt, 1);
|
||||
} else {
|
||||
AMAP_SET_BITS(struct amap_cq_context_be, coalescwm, ctxt,
|
||||
coalesce_wm);
|
||||
|
@ -768,11 +766,8 @@ int be_cmd_cq_create(struct be_adapter *adapter,
|
|||
AMAP_SET_BITS(struct amap_cq_context_be, count, ctxt,
|
||||
__ilog2_u32(cq->len/256));
|
||||
AMAP_SET_BITS(struct amap_cq_context_be, valid, ctxt, 1);
|
||||
AMAP_SET_BITS(struct amap_cq_context_be, solevent,
|
||||
ctxt, sol_evts);
|
||||
AMAP_SET_BITS(struct amap_cq_context_be, eventable, ctxt, 1);
|
||||
AMAP_SET_BITS(struct amap_cq_context_be, eqid, ctxt, eq->id);
|
||||
AMAP_SET_BITS(struct amap_cq_context_be, armed, ctxt, 1);
|
||||
}
|
||||
|
||||
be_dws_cpu_to_le(ctxt, sizeof(req->context));
|
||||
|
@ -973,7 +968,7 @@ err:
|
|||
/* Uses MCC */
|
||||
int be_cmd_rxq_create(struct be_adapter *adapter,
|
||||
struct be_queue_info *rxq, u16 cq_id, u16 frag_size,
|
||||
u16 max_frame_size, u32 if_id, u32 rss, u8 *rss_id)
|
||||
u32 if_id, u32 rss, u8 *rss_id)
|
||||
{
|
||||
struct be_mcc_wrb *wrb;
|
||||
struct be_cmd_req_eth_rx_create *req;
|
||||
|
@ -997,7 +992,7 @@ int be_cmd_rxq_create(struct be_adapter *adapter,
|
|||
req->num_pages = 2;
|
||||
be_cmd_page_addrs_prepare(req->pages, ARRAY_SIZE(req->pages), q_mem);
|
||||
req->interface_id = cpu_to_le32(if_id);
|
||||
req->max_frame_size = cpu_to_le16(max_frame_size);
|
||||
req->max_frame_size = cpu_to_le16(BE_MAX_JUMBO_FRAME_SIZE);
|
||||
req->rss_queue = cpu_to_le32(rss);
|
||||
|
||||
status = be_mcc_notify_wait(adapter);
|
||||
|
|
|
@ -1506,8 +1506,7 @@ extern int be_cmd_eq_create(struct be_adapter *adapter,
|
|||
struct be_queue_info *eq, int eq_delay);
|
||||
extern int be_cmd_cq_create(struct be_adapter *adapter,
|
||||
struct be_queue_info *cq, struct be_queue_info *eq,
|
||||
bool sol_evts, bool no_delay,
|
||||
int num_cqe_dma_coalesce);
|
||||
bool no_delay, int num_cqe_dma_coalesce);
|
||||
extern int be_cmd_mccq_create(struct be_adapter *adapter,
|
||||
struct be_queue_info *mccq,
|
||||
struct be_queue_info *cq);
|
||||
|
@ -1516,8 +1515,7 @@ extern int be_cmd_txq_create(struct be_adapter *adapter,
|
|||
struct be_queue_info *cq);
|
||||
extern int be_cmd_rxq_create(struct be_adapter *adapter,
|
||||
struct be_queue_info *rxq, u16 cq_id,
|
||||
u16 frag_size, u16 max_frame_size, u32 if_id,
|
||||
u32 rss, u8 *rss_id);
|
||||
u16 frag_size, u32 if_id, u32 rss, u8 *rss_id);
|
||||
extern int be_cmd_q_destroy(struct be_adapter *adapter, struct be_queue_info *q,
|
||||
int type);
|
||||
extern int be_cmd_rxq_destroy(struct be_adapter *adapter,
|
||||
|
@ -1546,7 +1544,7 @@ extern int be_cmd_query_fw_cfg(struct be_adapter *adapter,
|
|||
extern int be_cmd_reset_function(struct be_adapter *adapter);
|
||||
extern int be_cmd_rss_config(struct be_adapter *adapter, u8 *rsstable,
|
||||
u16 table_size);
|
||||
extern int be_process_mcc(struct be_adapter *adapter, int *status);
|
||||
extern int be_process_mcc(struct be_adapter *adapter);
|
||||
extern int be_cmd_set_beacon_state(struct be_adapter *adapter,
|
||||
u8 port_num, u8 beacon, u8 status, u8 state);
|
||||
extern int be_cmd_get_beacon_state(struct be_adapter *adapter,
|
||||
|
|
|
@ -37,7 +37,6 @@ enum {DRVSTAT_TX, DRVSTAT_RX, DRVSTAT};
|
|||
FIELDINFO(struct be_drv_stats, field)
|
||||
|
||||
static const struct be_ethtool_stat et_stats[] = {
|
||||
{DRVSTAT_INFO(tx_events)},
|
||||
{DRVSTAT_INFO(rx_crc_errors)},
|
||||
{DRVSTAT_INFO(rx_alignment_symbol_errors)},
|
||||
{DRVSTAT_INFO(rx_pause_frames)},
|
||||
|
@ -126,8 +125,6 @@ static const struct be_ethtool_stat et_stats[] = {
|
|||
static const struct be_ethtool_stat et_rx_stats[] = {
|
||||
{DRVSTAT_RX_INFO(rx_bytes)},/* If moving this member see above note */
|
||||
{DRVSTAT_RX_INFO(rx_pkts)}, /* If moving this member see above note */
|
||||
{DRVSTAT_RX_INFO(rx_polls)},
|
||||
{DRVSTAT_RX_INFO(rx_events)},
|
||||
{DRVSTAT_RX_INFO(rx_compl)},
|
||||
{DRVSTAT_RX_INFO(rx_mcast_pkts)},
|
||||
/* Number of page allocation failures while posting receive buffers
|
||||
|
@ -154,7 +151,6 @@ static const struct be_ethtool_stat et_tx_stats[] = {
|
|||
{DRVSTAT_TX_INFO(tx_reqs)},
|
||||
/* Number of TX work request blocks DMAed to HW */
|
||||
{DRVSTAT_TX_INFO(tx_wrbs)},
|
||||
{DRVSTAT_TX_INFO(tx_compl)},
|
||||
/* Number of times the TX queue was stopped due to lack
|
||||
* of spaces in the TXQ.
|
||||
*/
|
||||
|
@ -290,86 +286,42 @@ be_get_regs(struct net_device *netdev, struct ethtool_regs *regs, void *buf)
|
|||
}
|
||||
}
|
||||
|
||||
static int
|
||||
be_get_coalesce(struct net_device *netdev, struct ethtool_coalesce *coalesce)
|
||||
static int be_get_coalesce(struct net_device *netdev,
|
||||
struct ethtool_coalesce *et)
|
||||
{
|
||||
struct be_adapter *adapter = netdev_priv(netdev);
|
||||
struct be_eq_obj *rx_eq = &adapter->rx_obj[0].rx_eq;
|
||||
struct be_eq_obj *tx_eq = &adapter->tx_eq;
|
||||
struct be_eq_obj *eqo = &adapter->eq_obj[0];
|
||||
|
||||
coalesce->rx_coalesce_usecs = rx_eq->cur_eqd;
|
||||
coalesce->rx_coalesce_usecs_high = rx_eq->max_eqd;
|
||||
coalesce->rx_coalesce_usecs_low = rx_eq->min_eqd;
|
||||
|
||||
coalesce->tx_coalesce_usecs = tx_eq->cur_eqd;
|
||||
coalesce->tx_coalesce_usecs_high = tx_eq->max_eqd;
|
||||
coalesce->tx_coalesce_usecs_low = tx_eq->min_eqd;
|
||||
et->rx_coalesce_usecs = eqo->cur_eqd;
|
||||
et->rx_coalesce_usecs_high = eqo->max_eqd;
|
||||
et->rx_coalesce_usecs_low = eqo->min_eqd;
|
||||
|
||||
coalesce->use_adaptive_rx_coalesce = rx_eq->enable_aic;
|
||||
coalesce->use_adaptive_tx_coalesce = tx_eq->enable_aic;
|
||||
et->tx_coalesce_usecs = eqo->cur_eqd;
|
||||
et->tx_coalesce_usecs_high = eqo->max_eqd;
|
||||
et->tx_coalesce_usecs_low = eqo->min_eqd;
|
||||
|
||||
et->use_adaptive_rx_coalesce = eqo->enable_aic;
|
||||
et->use_adaptive_tx_coalesce = eqo->enable_aic;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This routine is used to set interrup coalescing delay
|
||||
/* TX attributes are ignored. Only RX attributes are considered
|
||||
* eqd cmd is issued in the worker thread.
|
||||
*/
|
||||
static int
|
||||
be_set_coalesce(struct net_device *netdev, struct ethtool_coalesce *coalesce)
|
||||
static int be_set_coalesce(struct net_device *netdev,
|
||||
struct ethtool_coalesce *et)
|
||||
{
|
||||
struct be_adapter *adapter = netdev_priv(netdev);
|
||||
struct be_rx_obj *rxo;
|
||||
struct be_eq_obj *rx_eq;
|
||||
struct be_eq_obj *tx_eq = &adapter->tx_eq;
|
||||
u32 rx_max, rx_min, rx_cur;
|
||||
int status = 0, i;
|
||||
u32 tx_cur;
|
||||
struct be_eq_obj *eqo;
|
||||
int i;
|
||||
|
||||
if (coalesce->use_adaptive_tx_coalesce == 1)
|
||||
return -EINVAL;
|
||||
|
||||
for_all_rx_queues(adapter, rxo, i) {
|
||||
rx_eq = &rxo->rx_eq;
|
||||
|
||||
if (!rx_eq->enable_aic && coalesce->use_adaptive_rx_coalesce)
|
||||
rx_eq->cur_eqd = 0;
|
||||
rx_eq->enable_aic = coalesce->use_adaptive_rx_coalesce;
|
||||
|
||||
rx_max = coalesce->rx_coalesce_usecs_high;
|
||||
rx_min = coalesce->rx_coalesce_usecs_low;
|
||||
rx_cur = coalesce->rx_coalesce_usecs;
|
||||
|
||||
if (rx_eq->enable_aic) {
|
||||
if (rx_max > BE_MAX_EQD)
|
||||
rx_max = BE_MAX_EQD;
|
||||
if (rx_min > rx_max)
|
||||
rx_min = rx_max;
|
||||
rx_eq->max_eqd = rx_max;
|
||||
rx_eq->min_eqd = rx_min;
|
||||
if (rx_eq->cur_eqd > rx_max)
|
||||
rx_eq->cur_eqd = rx_max;
|
||||
if (rx_eq->cur_eqd < rx_min)
|
||||
rx_eq->cur_eqd = rx_min;
|
||||
} else {
|
||||
if (rx_cur > BE_MAX_EQD)
|
||||
rx_cur = BE_MAX_EQD;
|
||||
if (rx_eq->cur_eqd != rx_cur) {
|
||||
status = be_cmd_modify_eqd(adapter, rx_eq->q.id,
|
||||
rx_cur);
|
||||
if (!status)
|
||||
rx_eq->cur_eqd = rx_cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx_cur = coalesce->tx_coalesce_usecs;
|
||||
|
||||
if (tx_cur > BE_MAX_EQD)
|
||||
tx_cur = BE_MAX_EQD;
|
||||
if (tx_eq->cur_eqd != tx_cur) {
|
||||
status = be_cmd_modify_eqd(adapter, tx_eq->q.id, tx_cur);
|
||||
if (!status)
|
||||
tx_eq->cur_eqd = tx_cur;
|
||||
for_all_evt_queues(adapter, eqo, i) {
|
||||
eqo->enable_aic = et->use_adaptive_rx_coalesce;
|
||||
eqo->max_eqd = min(et->rx_coalesce_usecs_high, BE_MAX_EQD);
|
||||
eqo->min_eqd = min(et->rx_coalesce_usecs_low, eqo->max_eqd);
|
||||
eqo->eqd = et->rx_coalesce_usecs;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче