Commit 55f7c9b0 authored by Felix Fietkau's avatar Felix Fietkau

mt76: mt7915: add 802.11 encap offload support

It is currently limited to 3-address mode AP and STA interfaces
Signed-off-by: default avatarFelix Fietkau <nbd@nbd.name>
parent 6a618acb
...@@ -261,6 +261,7 @@ mt7915_init_wiphy(struct ieee80211_hw *hw) ...@@ -261,6 +261,7 @@ mt7915_init_wiphy(struct ieee80211_hw *hw)
wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS);
ieee80211_hw_set(hw, HAS_RATE_CONTROL); ieee80211_hw_set(hw, HAS_RATE_CONTROL);
ieee80211_hw_set(hw, SUPPORTS_TX_ENCAP_OFFLOAD);
hw->max_tx_fragments = 4; hw->max_tx_fragments = 4;
} }
......
...@@ -562,21 +562,131 @@ int mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb) ...@@ -562,21 +562,131 @@ int mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb)
return 0; return 0;
} }
static void
mt7915_mac_write_txwi_8023(struct mt7915_dev *dev, __le32 *txwi,
struct sk_buff *skb, struct mt76_wcid *wcid)
{
u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
u8 fc_type, fc_stype;
bool wmm = false;
u32 val;
if (wcid->sta) {
struct ieee80211_sta *sta;
sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
wmm = sta->wme;
}
val = FIELD_PREP(MT_TXD1_HDR_FORMAT, MT_HDR_FORMAT_802_3) |
FIELD_PREP(MT_TXD1_TID, tid);
if (be16_to_cpu(skb->protocol) >= ETH_P_802_3_MIN)
val |= MT_TXD1_ETH_802_3;
txwi[1] |= cpu_to_le32(val);
fc_type = IEEE80211_FTYPE_DATA >> 2;
fc_stype = wmm ? IEEE80211_STYPE_QOS_DATA >> 4 : 0;
val = FIELD_PREP(MT_TXD2_FRAME_TYPE, fc_type) |
FIELD_PREP(MT_TXD2_SUB_TYPE, fc_stype);
txwi[2] |= cpu_to_le32(val);
val = FIELD_PREP(MT_TXD7_TYPE, fc_type) |
FIELD_PREP(MT_TXD7_SUB_TYPE, fc_stype);
txwi[7] |= cpu_to_le32(val);
}
static void
mt7915_mac_write_txwi_80211(struct mt7915_dev *dev, __le32 *txwi,
struct sk_buff *skb, struct ieee80211_key_conf *key)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
bool multicast = is_multicast_ether_addr(hdr->addr1);
u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
__le16 fc = hdr->frame_control;
u8 fc_type, fc_stype;
u32 val;
if (ieee80211_is_action(fc) &&
mgmt->u.action.category == WLAN_CATEGORY_BACK &&
mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ) {
u16 capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab);
txwi[5] |= cpu_to_le32(MT_TXD5_ADD_BA);
tid = (capab >> 2) & IEEE80211_QOS_CTL_TID_MASK;
} else if (ieee80211_is_back_req(hdr->frame_control)) {
struct ieee80211_bar *bar = (struct ieee80211_bar *)hdr;
u16 control = le16_to_cpu(bar->control);
tid = FIELD_GET(IEEE80211_BAR_CTRL_TID_INFO_MASK, control);
}
val = FIELD_PREP(MT_TXD1_HDR_FORMAT, MT_HDR_FORMAT_802_11) |
FIELD_PREP(MT_TXD1_HDR_INFO,
ieee80211_get_hdrlen_from_skb(skb) / 2) |
FIELD_PREP(MT_TXD1_TID, tid);
txwi[1] |= cpu_to_le32(val);
fc_type = (le16_to_cpu(fc) & IEEE80211_FCTL_FTYPE) >> 2;
fc_stype = (le16_to_cpu(fc) & IEEE80211_FCTL_STYPE) >> 4;
val = FIELD_PREP(MT_TXD2_FRAME_TYPE, fc_type) |
FIELD_PREP(MT_TXD2_SUB_TYPE, fc_stype) |
FIELD_PREP(MT_TXD2_MULTICAST, multicast);
if (key && multicast && ieee80211_is_robust_mgmt_frame(skb) &&
key->cipher == WLAN_CIPHER_SUITE_AES_CMAC) {
val |= MT_TXD2_BIP;
txwi[3] &= ~cpu_to_le32(MT_TXD3_PROTECT_FRAME);
}
if (!ieee80211_is_data(fc) || multicast)
val |= MT_TXD2_FIX_RATE;
txwi[2] |= cpu_to_le32(val);
if (ieee80211_is_beacon(fc)) {
txwi[3] &= ~cpu_to_le32(MT_TXD3_SW_POWER_MGMT);
txwi[3] |= cpu_to_le32(MT_TXD3_REM_TX_COUNT);
}
if (info->flags & IEEE80211_TX_CTL_INJECTED) {
u16 seqno = le16_to_cpu(hdr->seq_ctrl);
if (ieee80211_is_back_req(hdr->frame_control)) {
struct ieee80211_bar *bar;
bar = (struct ieee80211_bar *)skb->data;
seqno = le16_to_cpu(bar->start_seq_num);
}
val = MT_TXD3_SN_VALID |
FIELD_PREP(MT_TXD3_SEQ, IEEE80211_SEQ_TO_SN(seqno));
txwi[3] |= cpu_to_le32(val);
}
val = FIELD_PREP(MT_TXD7_TYPE, fc_type) |
FIELD_PREP(MT_TXD7_SUB_TYPE, fc_stype);
txwi[7] |= cpu_to_le32(val);
}
void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi, void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi,
struct sk_buff *skb, struct mt76_wcid *wcid, struct sk_buff *skb, struct mt76_wcid *wcid,
struct ieee80211_key_conf *key, bool beacon) struct ieee80211_key_conf *key, bool beacon)
{ {
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
bool multicast = is_multicast_ether_addr(hdr->addr1);
struct ieee80211_vif *vif = info->control.vif; struct ieee80211_vif *vif = info->control.vif;
struct mt76_phy *mphy = &dev->mphy; struct mt76_phy *mphy = &dev->mphy;
bool ext_phy = info->hw_queue & MT_TX_HW_QUEUE_EXT_PHY; bool ext_phy = info->hw_queue & MT_TX_HW_QUEUE_EXT_PHY;
u8 fc_type, fc_stype, p_fmt, q_idx, omac_idx = 0, wmm_idx = 0; u8 p_fmt, q_idx, omac_idx = 0, wmm_idx = 0;
__le16 fc = hdr->frame_control; bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP;
u16 tx_count = 15, seqno = 0; u16 tx_count = 15;
u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
u32 val; u32 val;
if (vif) { if (vif) {
...@@ -589,13 +699,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi, ...@@ -589,13 +699,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi,
if (ext_phy && dev->mt76.phy2) if (ext_phy && dev->mt76.phy2)
mphy = dev->mt76.phy2; mphy = dev->mt76.phy2;
fc_type = (le16_to_cpu(fc) & IEEE80211_FCTL_FTYPE) >> 2;
fc_stype = (le16_to_cpu(fc) & IEEE80211_FCTL_STYPE) >> 4;
txwi[4] = 0;
txwi[5] = 0;
txwi[6] = 0;
if (beacon) { if (beacon) {
p_fmt = MT_TX_TYPE_FW; p_fmt = MT_TX_TYPE_FW;
q_idx = MT_LMAC_BCN0; q_idx = MT_LMAC_BCN0;
...@@ -608,20 +711,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi, ...@@ -608,20 +711,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi,
mt7915_lmac_mapping(dev, skb_get_queue_mapping(skb)); mt7915_lmac_mapping(dev, skb_get_queue_mapping(skb));
} }
if (ieee80211_is_action(fc) &&
mgmt->u.action.category == WLAN_CATEGORY_BACK &&
mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ) {
u16 capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab);
txwi[5] |= cpu_to_le32(MT_TXD5_ADD_BA);
tid = (capab >> 2) & IEEE80211_QOS_CTL_TID_MASK;
} else if (ieee80211_is_back_req(hdr->frame_control)) {
struct ieee80211_bar *bar = (struct ieee80211_bar *)hdr;
u16 control = le16_to_cpu(bar->control);
tid = FIELD_GET(IEEE80211_BAR_CTRL_TID_INFO_MASK, control);
}
val = FIELD_PREP(MT_TXD0_TX_BYTES, skb->len + MT_TXD_SIZE) | val = FIELD_PREP(MT_TXD0_TX_BYTES, skb->len + MT_TXD_SIZE) |
FIELD_PREP(MT_TXD0_PKT_FMT, p_fmt) | FIELD_PREP(MT_TXD0_PKT_FMT, p_fmt) |
FIELD_PREP(MT_TXD0_Q_IDX, q_idx); FIELD_PREP(MT_TXD0_Q_IDX, q_idx);
...@@ -629,10 +718,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi, ...@@ -629,10 +718,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi,
val = MT_TXD1_LONG_FORMAT | val = MT_TXD1_LONG_FORMAT |
FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) |
FIELD_PREP(MT_TXD1_HDR_FORMAT, MT_HDR_FORMAT_802_11) |
FIELD_PREP(MT_TXD1_HDR_INFO,
ieee80211_get_hdrlen_from_skb(skb) / 2) |
FIELD_PREP(MT_TXD1_TID, tid) |
FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx);
if (ext_phy && q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0) if (ext_phy && q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0)
...@@ -640,27 +725,31 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi, ...@@ -640,27 +725,31 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi,
txwi[1] = cpu_to_le32(val); txwi[1] = cpu_to_le32(val);
val = FIELD_PREP(MT_TXD2_FRAME_TYPE, fc_type) | txwi[2] = 0;
FIELD_PREP(MT_TXD2_SUB_TYPE, fc_stype) |
FIELD_PREP(MT_TXD2_MULTICAST, multicast);
if (key) {
if (multicast && ieee80211_is_robust_mgmt_frame(skb) &&
key->cipher == WLAN_CIPHER_SUITE_AES_CMAC) {
val |= MT_TXD2_BIP;
txwi[3] = 0;
} else {
txwi[3] = cpu_to_le32(MT_TXD3_PROTECT_FRAME);
}
} else {
txwi[3] = 0;
}
txwi[2] = cpu_to_le32(val);
if (!ieee80211_is_data(fc) || multicast) { val = MT_TXD3_SW_POWER_MGMT |
FIELD_PREP(MT_TXD3_REM_TX_COUNT, tx_count);
if (key)
val |= MT_TXD3_PROTECT_FRAME;
if (info->flags & IEEE80211_TX_CTL_NO_ACK)
val |= MT_TXD3_NO_ACK;
txwi[3] = cpu_to_le32(val);
txwi[4] = 0;
txwi[5] = 0;
txwi[6] = 0;
txwi[7] = wcid->amsdu ? cpu_to_le32(MT_TXD7_HW_AMSDU) : 0;
if (is_8023)
mt7915_mac_write_txwi_8023(dev, txwi, skb, wcid);
else
mt7915_mac_write_txwi_80211(dev, txwi, skb, key);
if (txwi[2] & cpu_to_le32(MT_TXD2_FIX_RATE)) {
u16 rate; u16 rate;
/* hardware won't add HTC for mgmt/ctrl frame */ /* hardware won't add HTC for mgmt/ctrl frame */
txwi[2] |= cpu_to_le32(MT_TXD2_FIX_RATE | MT_TXD2_HTC_VLD); txwi[2] |= cpu_to_le32(MT_TXD2_HTC_VLD);
if (mphy->chandef.chan->band == NL80211_BAND_5GHZ) if (mphy->chandef.chan->band == NL80211_BAND_5GHZ)
rate = MT7915_5G_RATE_DEFAULT; rate = MT7915_5G_RATE_DEFAULT;
...@@ -672,36 +761,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi, ...@@ -672,36 +761,6 @@ void mt7915_mac_write_txwi(struct mt7915_dev *dev, __le32 *txwi,
txwi[6] |= cpu_to_le32(val); txwi[6] |= cpu_to_le32(val);
txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE); txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE);
} }
if (!ieee80211_is_beacon(fc))
txwi[3] |= cpu_to_le32(MT_TXD3_SW_POWER_MGMT);
else
tx_count = 0x1f;
if (info->flags & IEEE80211_TX_CTL_NO_ACK)
txwi[3] |= cpu_to_le32(MT_TXD3_NO_ACK);
val = FIELD_PREP(MT_TXD7_TYPE, fc_type) |
FIELD_PREP(MT_TXD7_SUB_TYPE, fc_stype);
if (wcid->amsdu)
val |= MT_TXD7_HW_AMSDU;
txwi[7] = cpu_to_le32(val);
val = FIELD_PREP(MT_TXD3_REM_TX_COUNT, tx_count);
if (info->flags & IEEE80211_TX_CTL_INJECTED) {
seqno = le16_to_cpu(hdr->seq_ctrl);
if (ieee80211_is_back_req(hdr->frame_control)) {
struct ieee80211_bar *bar;
bar = (struct ieee80211_bar *)skb->data;
seqno = le16_to_cpu(bar->start_seq_num);
}
val |= MT_TXD3_SN_VALID |
FIELD_PREP(MT_TXD3_SEQ, IEEE80211_SEQ_TO_SN(seqno));
}
txwi[3] |= cpu_to_le32(val);
} }
int mt7915_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, int mt7915_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
...@@ -723,11 +782,11 @@ int mt7915_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, ...@@ -723,11 +782,11 @@ int mt7915_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
if (!wcid) if (!wcid)
wcid = &dev->mt76.global_wcid; wcid = &dev->mt76.global_wcid;
cb->wcid = wcid->idx;
mt7915_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key, mt7915_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key,
false); false);
cb->wcid = wcid->idx;
txp = (struct mt7915_txp *)(txwi + MT_TXD_SIZE); txp = (struct mt7915_txp *)(txwi + MT_TXD_SIZE);
for (i = 0; i < nbuf; i++) { for (i = 0; i < nbuf; i++) {
txp->buf[i] = cpu_to_le32(tx_info->buf[i + 1].addr); txp->buf[i] = cpu_to_le32(tx_info->buf[i + 1].addr);
...@@ -745,7 +804,8 @@ int mt7915_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, ...@@ -745,7 +804,8 @@ int mt7915_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
if (!key) if (!key)
txp->flags |= cpu_to_le16(MT_CT_INFO_NONE_CIPHER_FRAME); txp->flags |= cpu_to_le16(MT_CT_INFO_NONE_CIPHER_FRAME);
if (ieee80211_is_mgmt(hdr->frame_control)) if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) &&
ieee80211_is_mgmt(hdr->frame_control))
txp->flags |= cpu_to_le16(MT_CT_INFO_MGMT_FRAME); txp->flags |= cpu_to_le16(MT_CT_INFO_MGMT_FRAME);
if (vif) { if (vif) {
...@@ -838,7 +898,8 @@ mt7915_tx_complete_status(struct mt76_dev *mdev, struct sk_buff *skb, ...@@ -838,7 +898,8 @@ mt7915_tx_complete_status(struct mt76_dev *mdev, struct sk_buff *skb,
info->status.tx_time = 0; info->status.tx_time = 0;
if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) { if (info->flags & (IEEE80211_TX_CTL_REQ_TX_STATUS |
IEEE80211_TX_CTL_HW_80211_ENCAP)) {
mt7915_tx_status(sta, hw, info, skb); mt7915_tx_status(sta, hw, info, skb);
return; return;
} }
......
...@@ -176,6 +176,7 @@ enum tx_mcu_port_q_idx { ...@@ -176,6 +176,7 @@ enum tx_mcu_port_q_idx {
#define MT_TXD1_HDR_PAD GENMASK(19, 18) #define MT_TXD1_HDR_PAD GENMASK(19, 18)
#define MT_TXD1_HDR_FORMAT GENMASK(17, 16) #define MT_TXD1_HDR_FORMAT GENMASK(17, 16)
#define MT_TXD1_HDR_INFO GENMASK(15, 11) #define MT_TXD1_HDR_INFO GENMASK(15, 11)
#define MT_TXD1_ETH_802_3 BIT(15)
#define MT_TXD1_VTA BIT(10) #define MT_TXD1_VTA BIT(10)
#define MT_TXD1_WLAN_IDX GENMASK(9, 0) #define MT_TXD1_WLAN_IDX GENMASK(9, 0)
......
...@@ -1688,6 +1688,24 @@ mt7915_mcu_wtbl_ht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta, ...@@ -1688,6 +1688,24 @@ mt7915_mcu_wtbl_ht_tlv(struct sk_buff *skb, struct ieee80211_sta *sta,
mt7915_mcu_wtbl_smps_tlv(skb, sta, sta_wtbl, wtbl_tlv); mt7915_mcu_wtbl_smps_tlv(skb, sta, sta_wtbl, wtbl_tlv);
} }
static void
mt7915_mcu_wtbl_hdr_trans_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
void *sta_wtbl, void *wtbl_tlv)
{
struct wtbl_hdr_trans *htr = NULL;
struct tlv *tlv;
tlv = mt7915_mcu_add_nested_tlv(skb, WTBL_HDR_TRANS, sizeof(*htr),
wtbl_tlv, sta_wtbl);
htr = (struct wtbl_hdr_trans *)tlv;
htr->no_rx_trans = true;
if (vif->type == NL80211_IFTYPE_STATION)
htr->to_ds = true;
else
htr->from_ds = true;
}
int mt7915_mcu_add_smps(struct mt7915_dev *dev, struct ieee80211_vif *vif, int mt7915_mcu_add_smps(struct mt7915_dev *dev, struct ieee80211_vif *vif,
struct ieee80211_sta *sta) struct ieee80211_sta *sta)
{ {
...@@ -2277,6 +2295,7 @@ int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, ...@@ -2277,6 +2295,7 @@ int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif,
sta_wtbl, &skb); sta_wtbl, &skb);
if (enable) { if (enable) {
mt7915_mcu_wtbl_generic_tlv(skb, vif, sta, sta_wtbl, wtbl_hdr); mt7915_mcu_wtbl_generic_tlv(skb, vif, sta, sta_wtbl, wtbl_hdr);
mt7915_mcu_wtbl_hdr_trans_tlv(skb, vif, sta, sta_wtbl, wtbl_hdr);
if (sta) if (sta)
mt7915_mcu_wtbl_ht_tlv(skb, sta, sta_wtbl, wtbl_hdr); mt7915_mcu_wtbl_ht_tlv(skb, sta, sta_wtbl, wtbl_hdr);
} }
......
...@@ -551,6 +551,15 @@ struct wtbl_vht { ...@@ -551,6 +551,15 @@ struct wtbl_vht {
u8 rsv[4]; u8 rsv[4];
} __packed; } __packed;
struct wtbl_hdr_trans {
__le16 tag;
__le16 len;
u8 to_ds;
u8 from_ds;
u8 no_rx_trans;
u8 _rsv;
};
enum { enum {
MT_BA_TYPE_INVALID, MT_BA_TYPE_INVALID,
MT_BA_TYPE_ORIGINATOR, MT_BA_TYPE_ORIGINATOR,
...@@ -972,6 +981,7 @@ enum { ...@@ -972,6 +981,7 @@ enum {
sizeof(struct wtbl_rx) + \ sizeof(struct wtbl_rx) + \
sizeof(struct wtbl_ht) + \ sizeof(struct wtbl_ht) + \
sizeof(struct wtbl_vht) + \ sizeof(struct wtbl_vht) + \
sizeof(struct wtbl_hdr_trans) +\
sizeof(struct wtbl_ba) + \ sizeof(struct wtbl_ba) + \
sizeof(struct wtbl_smps)) sizeof(struct wtbl_smps))
......
...@@ -272,6 +272,7 @@ mt76_tx(struct mt76_phy *phy, struct ieee80211_sta *sta, ...@@ -272,6 +272,7 @@ mt76_tx(struct mt76_phy *phy, struct ieee80211_sta *sta,
} }
if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) &&
!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) &&
!ieee80211_is_data(hdr->frame_control) && !ieee80211_is_data(hdr->frame_control) &&
!ieee80211_is_bufferable_mmpdu(hdr->frame_control)) { !ieee80211_is_bufferable_mmpdu(hdr->frame_control)) {
qid = MT_TXQ_PSD; qid = MT_TXQ_PSD;
......
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