Commit eceb024e authored by Kalle Valo's avatar Kalle Valo

Merge ath-next from git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git

ath.git patches for v6.2. Major changes:

ath10k

* store WLAN firmware version in SMEM image table
parents 823092a5 7256f287
...@@ -44,6 +44,7 @@ config ATH10K_SNOC ...@@ -44,6 +44,7 @@ config ATH10K_SNOC
tristate "Qualcomm ath10k SNOC support" tristate "Qualcomm ath10k SNOC support"
depends on ATH10K depends on ATH10K
depends on ARCH_QCOM || COMPILE_TEST depends on ARCH_QCOM || COMPILE_TEST
select QCOM_SMEM
select QCOM_SCM select QCOM_SCM
select QCOM_QMI_HELPERS select QCOM_QMI_HELPERS
help help
......
...@@ -1379,7 +1379,7 @@ static void ath10k_process_rx(struct ath10k *ar, struct sk_buff *skb) ...@@ -1379,7 +1379,7 @@ static void ath10k_process_rx(struct ath10k *ar, struct sk_buff *skb)
ath10k_get_tid(hdr, tid, sizeof(tid)), ath10k_get_tid(hdr, tid, sizeof(tid)),
is_multicast_ether_addr(ieee80211_get_DA(hdr)) ? is_multicast_ether_addr(ieee80211_get_DA(hdr)) ?
"mcast" : "ucast", "mcast" : "ucast",
(__le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4, IEEE80211_SEQ_TO_SN(__le16_to_cpu(hdr->seq_ctrl)),
(status->encoding == RX_ENC_LEGACY) ? "legacy" : "", (status->encoding == RX_ENC_LEGACY) ? "legacy" : "",
(status->encoding == RX_ENC_HT) ? "ht" : "", (status->encoding == RX_ENC_HT) ? "ht" : "",
(status->encoding == RX_ENC_VHT) ? "vht" : "", (status->encoding == RX_ENC_VHT) ? "vht" : "",
...@@ -1844,15 +1844,14 @@ static void ath10k_htt_rx_h_csum_offload(struct ath10k_hw_params *hw, ...@@ -1844,15 +1844,14 @@ static void ath10k_htt_rx_h_csum_offload(struct ath10k_hw_params *hw,
} }
static u64 ath10k_htt_rx_h_get_pn(struct ath10k *ar, struct sk_buff *skb, static u64 ath10k_htt_rx_h_get_pn(struct ath10k *ar, struct sk_buff *skb,
u16 offset,
enum htt_rx_mpdu_encrypt_type enctype) enum htt_rx_mpdu_encrypt_type enctype)
{ {
struct ieee80211_hdr *hdr; struct ieee80211_hdr *hdr;
u64 pn = 0; u64 pn = 0;
u8 *ehdr; u8 *ehdr;
hdr = (struct ieee80211_hdr *)(skb->data + offset); hdr = (struct ieee80211_hdr *)skb->data;
ehdr = skb->data + offset + ieee80211_hdrlen(hdr->frame_control); ehdr = skb->data + ieee80211_hdrlen(hdr->frame_control);
if (enctype == HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2) { if (enctype == HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2) {
pn = ehdr[0]; pn = ehdr[0];
...@@ -1866,19 +1865,17 @@ static u64 ath10k_htt_rx_h_get_pn(struct ath10k *ar, struct sk_buff *skb, ...@@ -1866,19 +1865,17 @@ static u64 ath10k_htt_rx_h_get_pn(struct ath10k *ar, struct sk_buff *skb,
} }
static bool ath10k_htt_rx_h_frag_multicast_check(struct ath10k *ar, static bool ath10k_htt_rx_h_frag_multicast_check(struct ath10k *ar,
struct sk_buff *skb, struct sk_buff *skb)
u16 offset)
{ {
struct ieee80211_hdr *hdr; struct ieee80211_hdr *hdr;
hdr = (struct ieee80211_hdr *)(skb->data + offset); hdr = (struct ieee80211_hdr *)skb->data;
return !is_multicast_ether_addr(hdr->addr1); return !is_multicast_ether_addr(hdr->addr1);
} }
static bool ath10k_htt_rx_h_frag_pn_check(struct ath10k *ar, static bool ath10k_htt_rx_h_frag_pn_check(struct ath10k *ar,
struct sk_buff *skb, struct sk_buff *skb,
u16 peer_id, u16 peer_id,
u16 offset,
enum htt_rx_mpdu_encrypt_type enctype) enum htt_rx_mpdu_encrypt_type enctype)
{ {
struct ath10k_peer *peer; struct ath10k_peer *peer;
...@@ -1893,16 +1890,16 @@ static bool ath10k_htt_rx_h_frag_pn_check(struct ath10k *ar, ...@@ -1893,16 +1890,16 @@ static bool ath10k_htt_rx_h_frag_pn_check(struct ath10k *ar,
return false; return false;
} }
hdr = (struct ieee80211_hdr *)(skb->data + offset); hdr = (struct ieee80211_hdr *)skb->data;
if (ieee80211_is_data_qos(hdr->frame_control)) if (ieee80211_is_data_qos(hdr->frame_control))
tid = ieee80211_get_tid(hdr); tid = ieee80211_get_tid(hdr);
else else
tid = ATH10K_TXRX_NON_QOS_TID; tid = ATH10K_TXRX_NON_QOS_TID;
last_pn = &peer->frag_tids_last_pn[tid]; last_pn = &peer->frag_tids_last_pn[tid];
new_pn.pn48 = ath10k_htt_rx_h_get_pn(ar, skb, offset, enctype); new_pn.pn48 = ath10k_htt_rx_h_get_pn(ar, skb, enctype);
frag_number = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG; frag_number = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG;
seq = (__le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; seq = IEEE80211_SEQ_TO_SN(__le16_to_cpu(hdr->seq_ctrl));
if (frag_number == 0) { if (frag_number == 0) {
last_pn->pn48 = new_pn.pn48; last_pn->pn48 = new_pn.pn48;
...@@ -2059,13 +2056,11 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar, ...@@ -2059,13 +2056,11 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar,
frag_pn_check = ath10k_htt_rx_h_frag_pn_check(ar, frag_pn_check = ath10k_htt_rx_h_frag_pn_check(ar,
msdu, msdu,
peer_id, peer_id,
0,
enctype); enctype);
if (frag) if (frag)
multicast_check = ath10k_htt_rx_h_frag_multicast_check(ar, multicast_check = ath10k_htt_rx_h_frag_multicast_check(ar,
msdu, msdu);
0);
if (!frag_pn_check || !multicast_check) { if (!frag_pn_check || !multicast_check) {
/* Discard the fragment with invalid PN or multicast DA /* Discard the fragment with invalid PN or multicast DA
...@@ -2824,7 +2819,7 @@ static bool ath10k_htt_rx_proc_rx_frag_ind_hl(struct ath10k_htt *htt, ...@@ -2824,7 +2819,7 @@ static bool ath10k_htt_rx_proc_rx_frag_ind_hl(struct ath10k_htt *htt,
hdr_space = ieee80211_hdrlen(hdr->frame_control); hdr_space = ieee80211_hdrlen(hdr->frame_control);
sc = __le16_to_cpu(hdr->seq_ctrl); sc = __le16_to_cpu(hdr->seq_ctrl);
seq = (sc & IEEE80211_SCTL_SEQ) >> 4; seq = IEEE80211_SEQ_TO_SN(sc);
frag = sc & IEEE80211_SCTL_FRAG; frag = sc & IEEE80211_SCTL_FRAG;
sec_index = MS(rx_desc_info, HTT_RX_DESC_HL_INFO_MCAST_BCAST) ? sec_index = MS(rx_desc_info, HTT_RX_DESC_HL_INFO_MCAST_BCAST) ?
......
...@@ -3792,18 +3792,22 @@ static struct pci_driver ath10k_pci_driver = { ...@@ -3792,18 +3792,22 @@ static struct pci_driver ath10k_pci_driver = {
static int __init ath10k_pci_init(void) static int __init ath10k_pci_init(void)
{ {
int ret; int ret1, ret2;
ret = pci_register_driver(&ath10k_pci_driver); ret1 = pci_register_driver(&ath10k_pci_driver);
if (ret) if (ret1)
printk(KERN_ERR "failed to register ath10k pci driver: %d\n", printk(KERN_ERR "failed to register ath10k pci driver: %d\n",
ret); ret1);
ret = ath10k_ahb_init(); ret2 = ath10k_ahb_init();
if (ret) if (ret2)
printk(KERN_ERR "ahb init failed: %d\n", ret); printk(KERN_ERR "ahb init failed: %d\n", ret2);
return ret; if (ret1 && ret2)
return ret1;
/* registered to at least one bus */
return 0;
} }
module_init(ath10k_pci_init); module_init(ath10k_pci_init);
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/net.h> #include <linux/net.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/qcom_scm.h> #include <linux/qcom_scm.h>
#include <linux/soc/qcom/smem.h>
#include <linux/string.h> #include <linux/string.h>
#include <net/sock.h> #include <net/sock.h>
...@@ -22,6 +23,10 @@ ...@@ -22,6 +23,10 @@
#define ATH10K_QMI_CLIENT_ID 0x4b4e454c #define ATH10K_QMI_CLIENT_ID 0x4b4e454c
#define ATH10K_QMI_TIMEOUT 30 #define ATH10K_QMI_TIMEOUT 30
#define SMEM_IMAGE_VERSION_TABLE 469
#define SMEM_IMAGE_TABLE_CNSS_INDEX 13
#define SMEM_IMAGE_VERSION_ENTRY_SIZE 128
#define SMEM_IMAGE_VERSION_NAME_SIZE 75
static int ath10k_qmi_map_msa_permission(struct ath10k_qmi *qmi, static int ath10k_qmi_map_msa_permission(struct ath10k_qmi *qmi,
struct ath10k_msa_mem_info *mem_info) struct ath10k_msa_mem_info *mem_info)
...@@ -536,6 +541,33 @@ int ath10k_qmi_wlan_disable(struct ath10k *ar) ...@@ -536,6 +541,33 @@ int ath10k_qmi_wlan_disable(struct ath10k *ar)
return ath10k_qmi_mode_send_sync_msg(ar, QMI_WLFW_OFF_V01); return ath10k_qmi_mode_send_sync_msg(ar, QMI_WLFW_OFF_V01);
} }
static void ath10k_qmi_add_wlan_ver_smem(struct ath10k *ar, const char *fw_build_id)
{
u8 *table_ptr;
size_t smem_item_size;
const u32 smem_img_idx_wlan = SMEM_IMAGE_TABLE_CNSS_INDEX *
SMEM_IMAGE_VERSION_ENTRY_SIZE;
table_ptr = qcom_smem_get(QCOM_SMEM_HOST_ANY,
SMEM_IMAGE_VERSION_TABLE,
&smem_item_size);
if (IS_ERR(table_ptr)) {
ath10k_err(ar, "smem image version table not found\n");
return;
}
if (smem_img_idx_wlan + SMEM_IMAGE_VERSION_ENTRY_SIZE >
smem_item_size) {
ath10k_err(ar, "smem block size too small: %zu\n",
smem_item_size);
return;
}
strscpy(table_ptr + smem_img_idx_wlan, fw_build_id,
SMEM_IMAGE_VERSION_NAME_SIZE);
}
static int ath10k_qmi_cap_send_sync_msg(struct ath10k_qmi *qmi) static int ath10k_qmi_cap_send_sync_msg(struct ath10k_qmi *qmi)
{ {
struct wlfw_cap_resp_msg_v01 *resp; struct wlfw_cap_resp_msg_v01 *resp;
...@@ -606,6 +638,9 @@ static int ath10k_qmi_cap_send_sync_msg(struct ath10k_qmi *qmi) ...@@ -606,6 +638,9 @@ static int ath10k_qmi_cap_send_sync_msg(struct ath10k_qmi *qmi)
qmi->fw_version, qmi->fw_build_timestamp, qmi->fw_build_id); qmi->fw_version, qmi->fw_build_timestamp, qmi->fw_build_id);
} }
if (resp->fw_build_id_valid)
ath10k_qmi_add_wlan_ver_smem(ar, qmi->fw_build_id);
kfree(resp); kfree(resp);
return 0; return 0;
...@@ -618,7 +653,7 @@ static int ath10k_qmi_host_cap_send_sync(struct ath10k_qmi *qmi) ...@@ -618,7 +653,7 @@ static int ath10k_qmi_host_cap_send_sync(struct ath10k_qmi *qmi)
{ {
struct wlfw_host_cap_resp_msg_v01 resp = {}; struct wlfw_host_cap_resp_msg_v01 resp = {};
struct wlfw_host_cap_req_msg_v01 req = {}; struct wlfw_host_cap_req_msg_v01 req = {};
struct qmi_elem_info *req_ei; const struct qmi_elem_info *req_ei;
struct ath10k *ar = qmi->ar; struct ath10k *ar = qmi->ar;
struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
struct qmi_txn txn; struct qmi_txn txn;
......
...@@ -195,6 +195,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { ...@@ -195,6 +195,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.tcl_ring_retry = true, .tcl_ring_retry = true,
.tx_ring_size = DP_TCL_DATA_RING_SIZE, .tx_ring_size = DP_TCL_DATA_RING_SIZE,
.smp2p_wow_exit = false, .smp2p_wow_exit = false,
.support_fw_mac_sequence = false,
}, },
{ {
.name = "qca6390 hw2.0", .name = "qca6390 hw2.0",
...@@ -277,6 +278,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { ...@@ -277,6 +278,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.tcl_ring_retry = true, .tcl_ring_retry = true,
.tx_ring_size = DP_TCL_DATA_RING_SIZE, .tx_ring_size = DP_TCL_DATA_RING_SIZE,
.smp2p_wow_exit = false, .smp2p_wow_exit = false,
.support_fw_mac_sequence = true,
}, },
{ {
.name = "qcn9074 hw1.0", .name = "qcn9074 hw1.0",
...@@ -356,6 +358,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { ...@@ -356,6 +358,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.tcl_ring_retry = true, .tcl_ring_retry = true,
.tx_ring_size = DP_TCL_DATA_RING_SIZE, .tx_ring_size = DP_TCL_DATA_RING_SIZE,
.smp2p_wow_exit = false, .smp2p_wow_exit = false,
.support_fw_mac_sequence = false,
}, },
{ {
.name = "wcn6855 hw2.0", .name = "wcn6855 hw2.0",
...@@ -438,6 +441,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { ...@@ -438,6 +441,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.tcl_ring_retry = true, .tcl_ring_retry = true,
.tx_ring_size = DP_TCL_DATA_RING_SIZE, .tx_ring_size = DP_TCL_DATA_RING_SIZE,
.smp2p_wow_exit = false, .smp2p_wow_exit = false,
.support_fw_mac_sequence = true,
}, },
{ {
.name = "wcn6855 hw2.1", .name = "wcn6855 hw2.1",
...@@ -519,6 +523,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { ...@@ -519,6 +523,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.tcl_ring_retry = true, .tcl_ring_retry = true,
.tx_ring_size = DP_TCL_DATA_RING_SIZE, .tx_ring_size = DP_TCL_DATA_RING_SIZE,
.smp2p_wow_exit = false, .smp2p_wow_exit = false,
.support_fw_mac_sequence = true,
}, },
{ {
.name = "wcn6750 hw1.0", .name = "wcn6750 hw1.0",
...@@ -597,6 +602,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { ...@@ -597,6 +602,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.tcl_ring_retry = false, .tcl_ring_retry = false,
.tx_ring_size = DP_TCL_DATA_RING_SIZE_WCN6750, .tx_ring_size = DP_TCL_DATA_RING_SIZE_WCN6750,
.smp2p_wow_exit = true, .smp2p_wow_exit = true,
.support_fw_mac_sequence = true,
}, },
}; };
......
...@@ -219,6 +219,7 @@ struct ath11k_hw_params { ...@@ -219,6 +219,7 @@ struct ath11k_hw_params {
bool tcl_ring_retry; bool tcl_ring_retry;
u32 tx_ring_size; u32 tx_ring_size;
bool smp2p_wow_exit; bool smp2p_wow_exit;
bool support_fw_mac_sequence;
}; };
struct ath11k_hw_ops { struct ath11k_hw_ops {
......
...@@ -8010,6 +8010,7 @@ ath11k_mac_op_reconfig_complete(struct ieee80211_hw *hw, ...@@ -8010,6 +8010,7 @@ ath11k_mac_op_reconfig_complete(struct ieee80211_hw *hw,
struct ath11k *ar = hw->priv; struct ath11k *ar = hw->priv;
struct ath11k_base *ab = ar->ab; struct ath11k_base *ab = ar->ab;
int recovery_count; int recovery_count;
struct ath11k_vif *arvif;
if (reconfig_type != IEEE80211_RECONFIG_TYPE_RESTART) if (reconfig_type != IEEE80211_RECONFIG_TYPE_RESTART)
return; return;
...@@ -8045,6 +8046,12 @@ ath11k_mac_op_reconfig_complete(struct ieee80211_hw *hw, ...@@ -8045,6 +8046,12 @@ ath11k_mac_op_reconfig_complete(struct ieee80211_hw *hw,
ath11k_dbg(ab, ATH11K_DBG_BOOT, "reset success\n"); ath11k_dbg(ab, ATH11K_DBG_BOOT, "reset success\n");
} }
} }
if (ar->ab->hw_params.support_fw_mac_sequence) {
list_for_each_entry(arvif, &ar->arvifs, list) {
if (arvif->is_up && arvif->vdev_type == WMI_VDEV_TYPE_STA)
ieee80211_hw_restart_disconnect(arvif->vif);
}
}
} }
mutex_unlock(&ar->conf_mutex); mutex_unlock(&ar->conf_mutex);
......
...@@ -163,7 +163,7 @@ void ath11k_mac_drain_tx(struct ath11k *ar); ...@@ -163,7 +163,7 @@ void ath11k_mac_drain_tx(struct ath11k *ar);
void ath11k_mac_peer_cleanup_all(struct ath11k *ar); void ath11k_mac_peer_cleanup_all(struct ath11k *ar);
int ath11k_mac_tx_mgmt_pending_free(int buf_id, void *skb, void *ctx); int ath11k_mac_tx_mgmt_pending_free(int buf_id, void *skb, void *ctx);
u8 ath11k_mac_bw_to_mac80211_bw(u8 bw); u8 ath11k_mac_bw_to_mac80211_bw(u8 bw);
u32 ath11k_mac_he_gi_to_nl80211_he_gi(u8 sgi); enum nl80211_he_gi ath11k_mac_he_gi_to_nl80211_he_gi(u8 sgi);
enum nl80211_he_ru_alloc ath11k_mac_phy_he_ru_to_nl80211_he_ru_alloc(u16 ru_phy); enum nl80211_he_ru_alloc ath11k_mac_phy_he_ru_to_nl80211_he_ru_alloc(u16 ru_phy);
enum nl80211_he_ru_alloc ath11k_mac_he_ru_tones_to_nl80211_he_ru_alloc(u16 ru_tones); enum nl80211_he_ru_alloc ath11k_mac_he_ru_tones_to_nl80211_he_ru_alloc(u16 ru_tones);
enum ath11k_supported_bw ath11k_mac_mac80211_bw_to_ath11k_bw(enum rate_info_bw bw); enum ath11k_supported_bw ath11k_mac_mac80211_bw_to_ath11k_bw(enum rate_info_bw bw);
......
This diff is collapsed.
...@@ -585,7 +585,7 @@ static u32 ar9003_mci_wait_for_gpm(struct ath_hw *ah, u8 gpm_type, ...@@ -585,7 +585,7 @@ static u32 ar9003_mci_wait_for_gpm(struct ath_hw *ah, u8 gpm_type,
{ {
struct ath_common *common = ath9k_hw_common(ah); struct ath_common *common = ath9k_hw_common(ah);
struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci;
u32 *p_gpm = NULL, mismatch = 0, more_data; u32 *p_gpm = NULL, more_data;
u32 offset; u32 offset;
u8 recv_type = 0, recv_opcode = 0; u8 recv_type = 0, recv_opcode = 0;
bool b_is_bt_cal_done = (gpm_type == MCI_GPM_BT_CAL_DONE); bool b_is_bt_cal_done = (gpm_type == MCI_GPM_BT_CAL_DONE);
...@@ -656,7 +656,6 @@ static u32 ar9003_mci_wait_for_gpm(struct ath_hw *ah, u8 gpm_type, ...@@ -656,7 +656,6 @@ static u32 ar9003_mci_wait_for_gpm(struct ath_hw *ah, u8 gpm_type,
} else { } else {
ath_dbg(common, MCI, "MCI GPM subtype not match 0x%x\n", ath_dbg(common, MCI, "MCI GPM subtype not match 0x%x\n",
*(p_gpm + 1)); *(p_gpm + 1));
mismatch++;
ar9003_mci_process_gpm_extra(ah, recv_type, ar9003_mci_process_gpm_extra(ah, recv_type,
recv_opcode, p_gpm); recv_opcode, p_gpm);
} }
......
...@@ -1678,7 +1678,6 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw, ...@@ -1678,7 +1678,6 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
struct ieee80211_tx_info *info; struct ieee80211_tx_info *info;
struct list_head bf_q; struct list_head bf_q;
struct ath_buf *bf_tail = NULL, *bf = NULL; struct ath_buf *bf_tail = NULL, *bf = NULL;
int sent = 0;
int i, ret; int i, ret;
INIT_LIST_HEAD(&bf_q); INIT_LIST_HEAD(&bf_q);
...@@ -1707,7 +1706,6 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw, ...@@ -1707,7 +1706,6 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
bf_tail = bf; bf_tail = bf;
nframes--; nframes--;
sent++;
TX_STAT_INC(sc, txq->axq_qnum, a_queued_hw); TX_STAT_INC(sc, txq->axq_qnum, a_queued_hw);
if (an->sta && skb_queue_empty(&tid->retry_q)) if (an->sta && skb_queue_empty(&tid->retry_q))
......
...@@ -118,10 +118,10 @@ struct carl9170_reg_list { ...@@ -118,10 +118,10 @@ struct carl9170_reg_list {
} __packed; } __packed;
struct carl9170_write_reg { struct carl9170_write_reg {
struct { DECLARE_FLEX_ARRAY(struct {
__le32 addr; __le32 addr;
__le32 val; __le32 val;
} regs[0] __packed; } __packed, regs);
} __packed; } __packed;
struct carl9170_write_reg_byte { struct carl9170_write_reg_byte {
......
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