diff options
Diffstat (limited to 'drivers/net/wireless/intel/iwlwifi/iwl-trans.c')
-rw-r--r-- | drivers/net/wireless/intel/iwlwifi/iwl-trans.c | 293 |
1 files changed, 157 insertions, 136 deletions
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index e7b2e08645ef..3694b41d6621 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -14,8 +14,8 @@ #include "iwl-fh.h" #include <linux/dmapool.h> #include "fw/api/commands.h" -#include "pcie/internal.h" -#include "iwl-context-info-gen3.h" +#include "pcie/gen1_2/internal.h" +#include "pcie/iwl-context-info-v2.h" struct iwl_trans_dev_restart_data { struct list_head list; @@ -82,14 +82,14 @@ void iwl_trans_free_restart_list(void) struct iwl_trans_reprobe { struct device *dev; - struct work_struct work; + struct delayed_work work; }; static void iwl_trans_reprobe_wk(struct work_struct *wk) { struct iwl_trans_reprobe *reprobe; - reprobe = container_of(wk, typeof(*reprobe), work); + reprobe = container_of(wk, typeof(*reprobe), work.work); if (device_reprobe(reprobe->dev)) dev_err(reprobe->dev, "reprobe failed!\n"); @@ -98,6 +98,31 @@ static void iwl_trans_reprobe_wk(struct work_struct *wk) module_put(THIS_MODULE); } +static void iwl_trans_schedule_reprobe(struct iwl_trans *trans, + unsigned int delay_ms) +{ + struct iwl_trans_reprobe *reprobe; + + /* + * get a module reference to avoid doing this while unloading + * anyway and to avoid scheduling a work with code that's + * being removed. + */ + if (!try_module_get(THIS_MODULE)) { + IWL_ERR(trans, "Module is being unloaded - abort\n"); + return; + } + + reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL); + if (!reprobe) { + module_put(THIS_MODULE); + return; + } + reprobe->dev = get_device(trans->dev); + INIT_DELAYED_WORK(&reprobe->work, iwl_trans_reprobe_wk); + schedule_delayed_work(&reprobe->work, msecs_to_jiffies(delay_ms)); +} + #define IWL_TRANS_RESET_OK_TIME 7 /* seconds */ static enum iwl_reset_mode @@ -106,18 +131,46 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans) struct iwl_trans_dev_restart_data *data; enum iwl_reset_mode at_least = 0; unsigned int index; - static const enum iwl_reset_mode escalation_list[] = { + static const enum iwl_reset_mode escalation_list_old[] = { + IWL_RESET_MODE_SW_RESET, + IWL_RESET_MODE_REPROBE, + IWL_RESET_MODE_REPROBE, + IWL_RESET_MODE_FUNC_RESET, + IWL_RESET_MODE_PROD_RESET, + }; + static const enum iwl_reset_mode escalation_list_sc[] = { IWL_RESET_MODE_SW_RESET, IWL_RESET_MODE_REPROBE, IWL_RESET_MODE_REPROBE, IWL_RESET_MODE_FUNC_RESET, - /* FIXME: add TOP reset */ + IWL_RESET_MODE_TOP_RESET, IWL_RESET_MODE_PROD_RESET, - /* FIXME: add TOP reset */ + IWL_RESET_MODE_TOP_RESET, IWL_RESET_MODE_PROD_RESET, - /* FIXME: add TOP reset */ + IWL_RESET_MODE_TOP_RESET, IWL_RESET_MODE_PROD_RESET, }; + const enum iwl_reset_mode *escalation_list; + size_t escalation_list_size; + + /* used by TOP fatal error/TOP reset */ + if (trans->restart.mode.type == IWL_ERR_TYPE_TOP_RESET_FAILED) + return IWL_RESET_MODE_PROD_RESET; + + if (trans->request_top_reset) { + trans->request_top_reset = 0; + if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_SC) + return IWL_RESET_MODE_TOP_RESET; + return IWL_RESET_MODE_PROD_RESET; + } + + if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_SC) { + escalation_list = escalation_list_sc; + escalation_list_size = ARRAY_SIZE(escalation_list_sc); + } else { + escalation_list = escalation_list_old; + escalation_list_size = ARRAY_SIZE(escalation_list_old); + } if (trans->restart.during_reset) at_least = IWL_RESET_MODE_REPROBE; @@ -132,8 +185,8 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans) data->restart_count = 0; index = data->restart_count; - if (index >= ARRAY_SIZE(escalation_list)) { - index = ARRAY_SIZE(escalation_list) - 1; + if (index >= escalation_list_size) { + index = escalation_list_size - 1; if (!data->backoff) { data->backoff = true; return IWL_RESET_MODE_BACKOFF; @@ -144,15 +197,21 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans) return max(at_least, escalation_list[index]); } +#define IWL_TRANS_TOP_FOLLOWER_WAIT 180 /* ms */ + #define IWL_TRANS_RESET_DELAY (HZ * 60) static void iwl_trans_restart_wk(struct work_struct *wk) { struct iwl_trans *trans = container_of(wk, typeof(*trans), restart.wk.work); - struct iwl_trans_reprobe *reprobe; enum iwl_reset_mode mode; + if (trans->restart.mode.type == IWL_ERR_TYPE_TOP_RESET_BY_BT) { + iwl_trans_schedule_reprobe(trans, IWL_TRANS_TOP_FOLLOWER_WAIT); + return; + } + if (!trans->op_mode) return; @@ -187,31 +246,19 @@ static void iwl_trans_restart_wk(struct work_struct *wk) iwl_trans_inc_restart_count(trans->dev); switch (mode) { + case IWL_RESET_MODE_TOP_RESET: + trans->do_top_reset = 1; + IWL_ERR(trans, "Device error - TOP reset\n"); + fallthrough; case IWL_RESET_MODE_SW_RESET: - IWL_ERR(trans, "Device error - SW reset\n"); + if (mode == IWL_RESET_MODE_SW_RESET) + IWL_ERR(trans, "Device error - SW reset\n"); iwl_trans_opmode_sw_reset(trans, trans->restart.mode.type); break; case IWL_RESET_MODE_REPROBE: IWL_ERR(trans, "Device error - reprobe!\n"); - /* - * get a module reference to avoid doing this while unloading - * anyway and to avoid scheduling a work with code that's - * being removed. - */ - if (!try_module_get(THIS_MODULE)) { - IWL_ERR(trans, "Module is being unloaded - abort\n"); - return; - } - - reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL); - if (!reprobe) { - module_put(THIS_MODULE); - return; - } - reprobe->dev = get_device(trans->dev); - INIT_WORK(&reprobe->work, iwl_trans_reprobe_wk); - schedule_work(&reprobe->work); + iwl_trans_schedule_reprobe(trans, 0); break; default: iwl_trans_pcie_reset(trans, mode); @@ -221,7 +268,9 @@ static void iwl_trans_restart_wk(struct work_struct *wk) struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, struct device *dev, - const struct iwl_cfg_trans_params *cfg_trans) + const struct iwl_mac_cfg *mac_cfg, + unsigned int txcmd_size, + unsigned int txcmd_align) { struct iwl_trans *trans; #ifdef CONFIG_LOCKDEP @@ -232,7 +281,7 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, if (!trans) return NULL; - trans->trans_cfg = cfg_trans; + trans->mac_cfg = mac_cfg; #ifdef CONFIG_LOCKDEP lockdep_init_map(&trans->sync_cmd_lockdep_map, "sync_cmd_lockdep_map", @@ -240,35 +289,9 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, #endif trans->dev = dev; - trans->num_rx_queues = 1; INIT_DELAYED_WORK(&trans->restart.wk, iwl_trans_restart_wk); - return trans; -} - -int iwl_trans_init(struct iwl_trans *trans) -{ - int txcmd_size, txcmd_align; - - if (!trans->trans_cfg->gen2) { - txcmd_size = sizeof(struct iwl_tx_cmd); - txcmd_align = sizeof(void *); - } else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { - txcmd_size = sizeof(struct iwl_tx_cmd_gen2); - txcmd_align = 64; - } else { - txcmd_size = sizeof(struct iwl_tx_cmd_gen3); - txcmd_align = 128; - } - - txcmd_size += sizeof(struct iwl_cmd_header); - txcmd_size += 36; /* biggest possible 802.11 header */ - - /* Ensure device TX cmd cannot reach/cross a page boundary in gen2 */ - if (WARN_ON(trans->trans_cfg->gen2 && txcmd_size >= txcmd_align)) - return -EINVAL; - snprintf(trans->dev_cmd_pool_name, sizeof(trans->dev_cmd_pool_name), "iwl_cmd_pool:%s", dev_name(trans->dev)); trans->dev_cmd_pool = @@ -276,12 +299,9 @@ int iwl_trans_init(struct iwl_trans *trans) txcmd_size, txcmd_align, SLAB_HWCACHE_ALIGN, NULL); if (!trans->dev_cmd_pool) - return -ENOMEM; - - /* Initialize the wait queue for commands */ - init_waitqueue_head(&trans->wait_command_queue); + return NULL; - return 0; + return trans; } void iwl_trans_free(struct iwl_trans *trans) @@ -298,17 +318,7 @@ int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) test_bit(STATUS_RFKILL_OPMODE, &trans->status))) return -ERFKILL; - /* - * We can't test IWL_MVM_STATUS_IN_D3 in mvm->status because this - * bit is set early in the D3 flow, before we send all the commands - * that configure the firmware for D3 operation (power, patterns, ...) - * and we don't want to flag all those with CMD_SEND_IN_D3. - * So use the system_pm_mode instead. The only command sent after - * we set system_pm_mode is D3_CONFIG_CMD, which we now flag with - * CMD_SEND_IN_D3. - */ - if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 && - !(cmd->flags & CMD_SEND_IN_D3))) + if (unlikely(test_bit(STATUS_SUSPENDED, &trans->status))) return -EHOSTDOWN; if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status))) @@ -321,7 +331,7 @@ int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) if (!(cmd->flags & CMD_ASYNC)) lock_map_acquire_read(&trans->sync_cmd_lockdep_map); - if (trans->wide_cmd_header && !iwl_cmd_groupid(cmd->id)) { + if (trans->conf.wide_cmd_header && !iwl_cmd_groupid(cmd->id)) { if (cmd->id != REPLY_ERROR) cmd->id = DEF_ID(cmd->id); } @@ -365,11 +375,12 @@ const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id) grp = iwl_cmd_groupid(id); cmd = iwl_cmd_opcode(id); - if (!trans->command_groups || grp >= trans->command_groups_size || - !trans->command_groups[grp].arr) + if (!trans->conf.command_groups || + grp >= trans->conf.command_groups_size || + !trans->conf.command_groups[grp].arr) return "UNKNOWN"; - arr = &trans->command_groups[grp]; + arr = &trans->conf.command_groups[grp]; ret = bsearch(&cmd, arr->arr, arr->size, size, iwl_hcmd_names_cmp); if (!ret) return "UNKNOWN"; @@ -377,37 +388,29 @@ const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id) } IWL_EXPORT_SYMBOL(iwl_get_cmd_string); -int iwl_cmd_groups_verify_sorted(const struct iwl_trans_config *trans) +void iwl_trans_op_mode_enter(struct iwl_trans *trans, + struct iwl_op_mode *op_mode) { - int i, j; - const struct iwl_hcmd_arr *arr; + trans->op_mode = op_mode; - for (i = 0; i < trans->command_groups_size; i++) { - arr = &trans->command_groups[i]; - if (!arr->arr) - continue; - for (j = 0; j < arr->size - 1; j++) - if (arr->arr[j].cmd_id > arr->arr[j + 1].cmd_id) - return -1; - } - return 0; -} -IWL_EXPORT_SYMBOL(iwl_cmd_groups_verify_sorted); + if (WARN_ON(trans->conf.n_no_reclaim_cmds > MAX_NO_RECLAIM_CMDS)) + trans->conf.n_no_reclaim_cmds = + ARRAY_SIZE(trans->conf.no_reclaim_cmds); -void iwl_trans_configure(struct iwl_trans *trans, - const struct iwl_trans_config *trans_cfg) -{ - trans->op_mode = trans_cfg->op_mode; + WARN_ON_ONCE(!trans->conf.rx_mpdu_cmd); - iwl_trans_pcie_configure(trans, trans_cfg); - WARN_ON(iwl_cmd_groups_verify_sorted(trans_cfg)); + iwl_trans_pcie_op_mode_enter(trans); } -IWL_EXPORT_SYMBOL(iwl_trans_configure); +IWL_EXPORT_SYMBOL(iwl_trans_op_mode_enter); int iwl_trans_start_hw(struct iwl_trans *trans) { might_sleep(); + clear_bit(STATUS_TRANS_RESET_IN_PROGRESS, &trans->status); + /* opmode may not resume if it detects errors */ + clear_bit(STATUS_SUSPENDED, &trans->status); + return iwl_trans_pcie_start_hw(trans); } IWL_EXPORT_SYMBOL(iwl_trans_start_hw); @@ -416,11 +419,15 @@ void iwl_trans_op_mode_leave(struct iwl_trans *trans) { might_sleep(); - iwl_trans_pcie_op_mode_leave(trans); + if (trans->mac_cfg->gen2) + iwl_trans_pcie_gen2_op_mode_leave(trans); + else + iwl_trans_pcie_op_mode_leave(trans); cancel_delayed_work_sync(&trans->restart.wk); trans->op_mode = NULL; + memset(&trans->conf, 0, sizeof(trans->conf)); trans->state = IWL_TRANS_NO_FW; } @@ -430,31 +437,26 @@ void iwl_trans_write8(struct iwl_trans *trans, u32 ofs, u8 val) { iwl_trans_pcie_write8(trans, ofs, val); } -IWL_EXPORT_SYMBOL(iwl_trans_write8); void iwl_trans_write32(struct iwl_trans *trans, u32 ofs, u32 val) { iwl_trans_pcie_write32(trans, ofs, val); } -IWL_EXPORT_SYMBOL(iwl_trans_write32); u32 iwl_trans_read32(struct iwl_trans *trans, u32 ofs) { return iwl_trans_pcie_read32(trans, ofs); } -IWL_EXPORT_SYMBOL(iwl_trans_read32); u32 iwl_trans_read_prph(struct iwl_trans *trans, u32 ofs) { return iwl_trans_pcie_read_prph(trans, ofs); } -IWL_EXPORT_SYMBOL(iwl_trans_read_prph); void iwl_trans_write_prph(struct iwl_trans *trans, u32 ofs, u32 val) { return iwl_trans_pcie_write_prph(trans, ofs, val); } -IWL_EXPORT_SYMBOL(iwl_trans_write_prph); int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr, void *buf, int dwords) @@ -466,7 +468,19 @@ IWL_EXPORT_SYMBOL(iwl_trans_read_mem); int iwl_trans_write_mem(struct iwl_trans *trans, u32 addr, const void *buf, int dwords) { - return iwl_trans_pcie_write_mem(trans, addr, buf, dwords); + int offs, ret = 0; + const u32 *vals = buf; + + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, HBUS_TARG_MEM_WADDR, addr); + for (offs = 0; offs < dwords; offs++) + iwl_write32(trans, HBUS_TARG_MEM_WDAT, + vals ? vals[offs] : 0); + iwl_trans_release_nic_access(trans); + } else { + ret = -EBUSY; + } + return ret; } IWL_EXPORT_SYMBOL(iwl_trans_write_mem); @@ -479,11 +493,10 @@ void iwl_trans_set_pmi(struct iwl_trans *trans, bool state) } IWL_EXPORT_SYMBOL(iwl_trans_set_pmi); -int iwl_trans_sw_reset(struct iwl_trans *trans, bool retake_ownership) +int iwl_trans_sw_reset(struct iwl_trans *trans) { - return iwl_trans_pcie_sw_reset(trans, retake_ownership); + return iwl_trans_pcie_sw_reset(trans, true); } -IWL_EXPORT_SYMBOL(iwl_trans_sw_reset); struct iwl_trans_dump_data * iwl_trans_dump_data(struct iwl_trans *trans, u32 dump_mask, @@ -493,22 +506,34 @@ iwl_trans_dump_data(struct iwl_trans *trans, u32 dump_mask, return iwl_trans_pcie_dump_data(trans, dump_mask, sanitize_ops, sanitize_ctx); } -IWL_EXPORT_SYMBOL(iwl_trans_dump_data); int iwl_trans_d3_suspend(struct iwl_trans *trans, bool test, bool reset) { + int err; + might_sleep(); - return iwl_trans_pcie_d3_suspend(trans, test, reset); + err = iwl_trans_pcie_d3_suspend(trans, test, reset); + + if (!err) + set_bit(STATUS_SUSPENDED, &trans->status); + + return err; } IWL_EXPORT_SYMBOL(iwl_trans_d3_suspend); int iwl_trans_d3_resume(struct iwl_trans *trans, enum iwl_d3_status *status, bool test, bool reset) { + int err; + might_sleep(); - return iwl_trans_pcie_d3_resume(trans, status, test, reset); + err = iwl_trans_pcie_d3_resume(trans, status, test, reset); + + clear_bit(STATUS_SUSPENDED, &trans->status); + + return err; } IWL_EXPORT_SYMBOL(iwl_trans_d3_resume); @@ -516,20 +541,17 @@ void iwl_trans_interrupts(struct iwl_trans *trans, bool enable) { iwl_trans_pci_interrupts(trans, enable); } -IWL_EXPORT_SYMBOL(iwl_trans_interrupts); void iwl_trans_sync_nmi(struct iwl_trans *trans) { iwl_trans_pcie_sync_nmi(trans); } -IWL_EXPORT_SYMBOL(iwl_trans_sync_nmi); int iwl_trans_write_imr_mem(struct iwl_trans *trans, u32 dst_addr, u64 src_addr, u32 byte_cnt) { return iwl_trans_pcie_copy_imr(trans, dst_addr, src_addr, byte_cnt); } -IWL_EXPORT_SYMBOL(iwl_trans_write_imr_mem); void iwl_trans_set_bits_mask(struct iwl_trans *trans, u32 reg, u32 mask, u32 value) @@ -543,7 +565,6 @@ int iwl_trans_read_config32(struct iwl_trans *trans, u32 ofs, { return iwl_trans_pcie_read_config32(trans, ofs, val); } -IWL_EXPORT_SYMBOL(iwl_trans_read_config32); bool _iwl_trans_grab_nic_access(struct iwl_trans *trans) { @@ -558,34 +579,39 @@ iwl_trans_release_nic_access(struct iwl_trans *trans) } IWL_EXPORT_SYMBOL(iwl_trans_release_nic_access); -void iwl_trans_fw_alive(struct iwl_trans *trans, u32 scd_addr) +void iwl_trans_fw_alive(struct iwl_trans *trans) { might_sleep(); trans->state = IWL_TRANS_FW_ALIVE; - if (trans->trans_cfg->gen2) + if (trans->mac_cfg->gen2) iwl_trans_pcie_gen2_fw_alive(trans); else - iwl_trans_pcie_fw_alive(trans, scd_addr); + iwl_trans_pcie_fw_alive(trans); } IWL_EXPORT_SYMBOL(iwl_trans_fw_alive); -int iwl_trans_start_fw(struct iwl_trans *trans, const struct fw_img *fw, - bool run_in_rfkill) +int iwl_trans_start_fw(struct iwl_trans *trans, const struct iwl_fw *fw, + enum iwl_ucode_type ucode_type, bool run_in_rfkill) { + const struct fw_img *img; int ret; might_sleep(); - WARN_ON_ONCE(!trans->rx_mpdu_cmd); + img = iwl_get_ucode_image(fw, ucode_type); + if (!img) + return -EINVAL; clear_bit(STATUS_FW_ERROR, &trans->status); - if (trans->trans_cfg->gen2) - ret = iwl_trans_pcie_gen2_start_fw(trans, fw, run_in_rfkill); + if (trans->mac_cfg->gen2) + ret = iwl_trans_pcie_gen2_start_fw(trans, fw, img, + run_in_rfkill); else - ret = iwl_trans_pcie_start_fw(trans, fw, run_in_rfkill); + ret = iwl_trans_pcie_start_fw(trans, fw, img, + run_in_rfkill); if (ret == 0) trans->state = IWL_TRANS_FW_STARTED; @@ -626,7 +652,7 @@ void iwl_trans_stop_device(struct iwl_trans *trans) iwl_op_mode_dump_error(trans->op_mode, &mode); } - if (trans->trans_cfg->gen2) + if (trans->mac_cfg->gen2) iwl_trans_pcie_gen2_stop_device(trans); else iwl_trans_pcie_stop_device(trans); @@ -645,7 +671,7 @@ int iwl_trans_tx(struct iwl_trans *trans, struct sk_buff *skb, "bad state = %d\n", trans->state)) return -EIO; - if (trans->trans_cfg->gen2) + if (trans->mac_cfg->gen2) return iwl_txq_gen2_tx(trans, skb, dev_cmd, queue); return iwl_trans_pcie_tx(trans, skb, dev_cmd, queue); @@ -734,7 +760,6 @@ void iwl_trans_debugfs_cleanup(struct iwl_trans *trans) { iwl_trans_pcie_debugfs_cleanup(trans); } -IWL_EXPORT_SYMBOL(iwl_trans_debugfs_cleanup); #endif void iwl_trans_set_q_ptrs(struct iwl_trans *trans, int queue, int ptr) @@ -772,35 +797,31 @@ int iwl_trans_get_rxq_dma_data(struct iwl_trans *trans, int queue, { return iwl_trans_pcie_rxq_dma_data(trans, queue, data); } -IWL_EXPORT_SYMBOL(iwl_trans_get_rxq_dma_data); int iwl_trans_load_pnvm(struct iwl_trans *trans, const struct iwl_pnvm_image *pnvm_data, const struct iwl_ucode_capabilities *capa) { - return iwl_trans_pcie_ctx_info_gen3_load_pnvm(trans, pnvm_data, capa); + return iwl_trans_pcie_ctx_info_v2_load_pnvm(trans, pnvm_data, capa); } IWL_EXPORT_SYMBOL(iwl_trans_load_pnvm); void iwl_trans_set_pnvm(struct iwl_trans *trans, const struct iwl_ucode_capabilities *capa) { - iwl_trans_pcie_ctx_info_gen3_set_pnvm(trans, capa); + iwl_trans_pcie_ctx_info_v2_set_pnvm(trans, capa); } -IWL_EXPORT_SYMBOL(iwl_trans_set_pnvm); int iwl_trans_load_reduce_power(struct iwl_trans *trans, const struct iwl_pnvm_image *payloads, const struct iwl_ucode_capabilities *capa) { - return iwl_trans_pcie_ctx_info_gen3_load_reduce_power(trans, payloads, + return iwl_trans_pcie_ctx_info_v2_load_reduce_power(trans, payloads, capa); } -IWL_EXPORT_SYMBOL(iwl_trans_load_reduce_power); void iwl_trans_set_reduce_power(struct iwl_trans *trans, const struct iwl_ucode_capabilities *capa) { - iwl_trans_pcie_ctx_info_gen3_set_reduce_power(trans, capa); + iwl_trans_pcie_ctx_info_v2_set_reduce_power(trans, capa); } -IWL_EXPORT_SYMBOL(iwl_trans_set_reduce_power); |