| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* Copyright(c) 2019-2020 Realtek Corporation |
| */ |
| |
| #include <linux/devcoredump.h> |
| |
| #include "cam.h" |
| #include "chan.h" |
| #include "debug.h" |
| #include "fw.h" |
| #include "mac.h" |
| #include "ps.h" |
| #include "reg.h" |
| #include "ser.h" |
| #include "util.h" |
| |
| #define SER_RECFG_TIMEOUT 1000 |
| |
| enum ser_evt { |
| SER_EV_NONE, |
| SER_EV_STATE_IN, |
| SER_EV_STATE_OUT, |
| SER_EV_L1_RESET_PREPARE, /* pre-M0 */ |
| SER_EV_L1_RESET, /* M1 */ |
| SER_EV_DO_RECOVERY, /* M3 */ |
| SER_EV_MAC_RESET_DONE, /* M5 */ |
| SER_EV_L2_RESET, |
| SER_EV_L2_RECFG_DONE, |
| SER_EV_L2_RECFG_TIMEOUT, |
| SER_EV_M1_TIMEOUT, |
| SER_EV_M3_TIMEOUT, |
| SER_EV_FW_M5_TIMEOUT, |
| SER_EV_L0_RESET, |
| SER_EV_MAXX |
| }; |
| |
| enum ser_state { |
| SER_IDLE_ST, |
| SER_L1_RESET_PRE_ST, |
| SER_RESET_TRX_ST, |
| SER_DO_HCI_ST, |
| SER_L2_RESET_ST, |
| SER_ST_MAX_ST |
| }; |
| |
| struct ser_msg { |
| struct list_head list; |
| u8 event; |
| }; |
| |
| struct state_ent { |
| u8 state; |
| char *name; |
| void (*st_func)(struct rtw89_ser *ser, u8 event); |
| }; |
| |
| struct event_ent { |
| u8 event; |
| char *name; |
| }; |
| |
| static char *ser_ev_name(struct rtw89_ser *ser, u8 event) |
| { |
| if (event < SER_EV_MAXX) |
| return ser->ev_tbl[event].name; |
| |
| return "err_ev_name"; |
| } |
| |
| static char *ser_st_name(struct rtw89_ser *ser) |
| { |
| if (ser->state < SER_ST_MAX_ST) |
| return ser->st_tbl[ser->state].name; |
| |
| return "err_st_name"; |
| } |
| |
| #define RTW89_DEF_SER_CD_TYPE(_name, _type, _size) \ |
| struct ser_cd_ ## _name { \ |
| u32 type; \ |
| u32 type_size; \ |
| u64 padding; \ |
| u8 data[_size]; \ |
| } __packed; \ |
| static void ser_cd_ ## _name ## _init(struct ser_cd_ ## _name *p) \ |
| { \ |
| p->type = _type; \ |
| p->type_size = sizeof(p->data); \ |
| p->padding = 0x0123456789abcdef; \ |
| } |
| |
| enum rtw89_ser_cd_type { |
| RTW89_SER_CD_FW_RSVD_PLE = 0, |
| RTW89_SER_CD_FW_BACKTRACE = 1, |
| }; |
| |
| RTW89_DEF_SER_CD_TYPE(fw_rsvd_ple, |
| RTW89_SER_CD_FW_RSVD_PLE, |
| RTW89_FW_RSVD_PLE_SIZE); |
| |
| RTW89_DEF_SER_CD_TYPE(fw_backtrace, |
| RTW89_SER_CD_FW_BACKTRACE, |
| RTW89_FW_BACKTRACE_MAX_SIZE); |
| |
| struct rtw89_ser_cd_buffer { |
| struct ser_cd_fw_rsvd_ple fwple; |
| struct ser_cd_fw_backtrace fwbt; |
| } __packed; |
| |
| static struct rtw89_ser_cd_buffer *rtw89_ser_cd_prep(struct rtw89_dev *rtwdev) |
| { |
| struct rtw89_ser_cd_buffer *buf; |
| |
| buf = vzalloc(sizeof(*buf)); |
| if (!buf) |
| return NULL; |
| |
| ser_cd_fw_rsvd_ple_init(&buf->fwple); |
| ser_cd_fw_backtrace_init(&buf->fwbt); |
| |
| return buf; |
| } |
| |
| static void rtw89_ser_cd_send(struct rtw89_dev *rtwdev, |
| struct rtw89_ser_cd_buffer *buf) |
| { |
| rtw89_debug(rtwdev, RTW89_DBG_SER, "SER sends core dump\n"); |
| |
| /* After calling dev_coredump, buf's lifetime is supposed to be |
| * handled by the device coredump framework. Note that a new dump |
| * will be discarded if a previous one hasn't been released by |
| * framework yet. |
| */ |
| dev_coredumpv(rtwdev->dev, buf, sizeof(*buf), GFP_KERNEL); |
| } |
| |
| static void rtw89_ser_cd_free(struct rtw89_dev *rtwdev, |
| struct rtw89_ser_cd_buffer *buf, bool free_self) |
| { |
| if (!free_self) |
| return; |
| |
| rtw89_debug(rtwdev, RTW89_DBG_SER, "SER frees core dump by self\n"); |
| |
| /* When some problems happen during filling data of core dump, |
| * we won't send it to device coredump framework. Instead, we |
| * free buf by ourselves. |
| */ |
| vfree(buf); |
| } |
| |
| static void ser_state_run(struct rtw89_ser *ser, u8 evt) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| rtw89_debug(rtwdev, RTW89_DBG_SER, "ser: %s receive %s\n", |
| ser_st_name(ser), ser_ev_name(ser, evt)); |
| |
| mutex_lock(&rtwdev->mutex); |
| rtw89_leave_lps(rtwdev); |
| mutex_unlock(&rtwdev->mutex); |
| |
| ser->st_tbl[ser->state].st_func(ser, evt); |
| } |
| |
| static void ser_state_goto(struct rtw89_ser *ser, u8 new_state) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| if (ser->state == new_state || new_state >= SER_ST_MAX_ST) |
| return; |
| ser_state_run(ser, SER_EV_STATE_OUT); |
| |
| rtw89_debug(rtwdev, RTW89_DBG_SER, "ser: %s goto -> %s\n", |
| ser_st_name(ser), ser->st_tbl[new_state].name); |
| |
| ser->state = new_state; |
| ser_state_run(ser, SER_EV_STATE_IN); |
| } |
| |
| static struct ser_msg *__rtw89_ser_dequeue_msg(struct rtw89_ser *ser) |
| { |
| struct ser_msg *msg; |
| |
| spin_lock_irq(&ser->msg_q_lock); |
| msg = list_first_entry_or_null(&ser->msg_q, struct ser_msg, list); |
| if (msg) |
| list_del(&msg->list); |
| spin_unlock_irq(&ser->msg_q_lock); |
| |
| return msg; |
| } |
| |
| static void rtw89_ser_hdl_work(struct work_struct *work) |
| { |
| struct ser_msg *msg; |
| struct rtw89_ser *ser = container_of(work, struct rtw89_ser, |
| ser_hdl_work); |
| |
| while ((msg = __rtw89_ser_dequeue_msg(ser))) { |
| ser_state_run(ser, msg->event); |
| kfree(msg); |
| } |
| } |
| |
| static int ser_send_msg(struct rtw89_ser *ser, u8 event) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| struct ser_msg *msg = NULL; |
| |
| if (test_bit(RTW89_SER_DRV_STOP_RUN, ser->flags)) |
| return -EIO; |
| |
| msg = kmalloc(sizeof(*msg), GFP_ATOMIC); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->event = event; |
| |
| spin_lock_irq(&ser->msg_q_lock); |
| list_add(&msg->list, &ser->msg_q); |
| spin_unlock_irq(&ser->msg_q_lock); |
| |
| ieee80211_queue_work(rtwdev->hw, &ser->ser_hdl_work); |
| return 0; |
| } |
| |
| static void rtw89_ser_alarm_work(struct work_struct *work) |
| { |
| struct rtw89_ser *ser = container_of(work, struct rtw89_ser, |
| ser_alarm_work.work); |
| |
| ser_send_msg(ser, ser->alarm_event); |
| ser->alarm_event = SER_EV_NONE; |
| } |
| |
| static void ser_set_alarm(struct rtw89_ser *ser, u32 ms, u8 event) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| if (test_bit(RTW89_SER_DRV_STOP_RUN, ser->flags)) |
| return; |
| |
| ser->alarm_event = event; |
| ieee80211_queue_delayed_work(rtwdev->hw, &ser->ser_alarm_work, |
| msecs_to_jiffies(ms)); |
| } |
| |
| static void ser_del_alarm(struct rtw89_ser *ser) |
| { |
| cancel_delayed_work(&ser->ser_alarm_work); |
| ser->alarm_event = SER_EV_NONE; |
| } |
| |
| /* driver function */ |
| static void drv_stop_tx(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| ieee80211_stop_queues(rtwdev->hw); |
| set_bit(RTW89_SER_DRV_STOP_TX, ser->flags); |
| } |
| |
| static void drv_stop_rx(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| clear_bit(RTW89_FLAG_RUNNING, rtwdev->flags); |
| set_bit(RTW89_SER_DRV_STOP_RX, ser->flags); |
| } |
| |
| static void drv_trx_reset(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| rtw89_hci_reset(rtwdev); |
| } |
| |
| static void drv_resume_tx(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| if (!test_bit(RTW89_SER_DRV_STOP_TX, ser->flags)) |
| return; |
| |
| ieee80211_wake_queues(rtwdev->hw); |
| clear_bit(RTW89_SER_DRV_STOP_TX, ser->flags); |
| } |
| |
| static void drv_resume_rx(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| if (!test_bit(RTW89_SER_DRV_STOP_RX, ser->flags)) |
| return; |
| |
| set_bit(RTW89_FLAG_RUNNING, rtwdev->flags); |
| clear_bit(RTW89_SER_DRV_STOP_RX, ser->flags); |
| } |
| |
| static void ser_reset_vif(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif) |
| { |
| rtw89_core_release_bit_map(rtwdev->hw_port, rtwvif->port); |
| rtwvif->net_type = RTW89_NET_TYPE_NO_LINK; |
| rtwvif->trigger = false; |
| rtwvif->tdls_peer = 0; |
| } |
| |
| static void ser_sta_deinit_cam_iter(void *data, struct ieee80211_sta *sta) |
| { |
| struct rtw89_vif *target_rtwvif = (struct rtw89_vif *)data; |
| struct rtw89_sta *rtwsta = (struct rtw89_sta *)sta->drv_priv; |
| struct rtw89_vif *rtwvif = rtwsta->rtwvif; |
| struct rtw89_dev *rtwdev = rtwvif->rtwdev; |
| |
| if (rtwvif != target_rtwvif) |
| return; |
| |
| if (rtwvif->net_type == RTW89_NET_TYPE_AP_MODE || sta->tdls) |
| rtw89_cam_deinit_addr_cam(rtwdev, &rtwsta->addr_cam); |
| if (sta->tdls) |
| rtw89_cam_deinit_bssid_cam(rtwdev, &rtwsta->bssid_cam); |
| |
| INIT_LIST_HEAD(&rtwsta->ba_cam_list); |
| } |
| |
| static void ser_deinit_cam(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif) |
| { |
| ieee80211_iterate_stations_atomic(rtwdev->hw, |
| ser_sta_deinit_cam_iter, |
| rtwvif); |
| |
| rtw89_cam_deinit(rtwdev, rtwvif); |
| |
| bitmap_zero(rtwdev->cam_info.ba_cam_map, RTW89_MAX_BA_CAM_NUM); |
| } |
| |
| static void ser_reset_mac_binding(struct rtw89_dev *rtwdev) |
| { |
| struct rtw89_vif *rtwvif; |
| |
| rtw89_cam_reset_keys(rtwdev); |
| rtw89_for_each_rtwvif(rtwdev, rtwvif) |
| ser_deinit_cam(rtwdev, rtwvif); |
| |
| rtw89_core_release_all_bits_map(rtwdev->mac_id_map, RTW89_MAX_MAC_ID_NUM); |
| rtw89_for_each_rtwvif(rtwdev, rtwvif) |
| ser_reset_vif(rtwdev, rtwvif); |
| |
| rtwdev->total_sta_assoc = 0; |
| } |
| |
| /* hal function */ |
| static int hal_enable_dma(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| int ret; |
| |
| if (!test_bit(RTW89_SER_HAL_STOP_DMA, ser->flags)) |
| return 0; |
| |
| if (!rtwdev->hci.ops->mac_lv1_rcvy) |
| return -EIO; |
| |
| ret = rtwdev->hci.ops->mac_lv1_rcvy(rtwdev, RTW89_LV1_RCVY_STEP_2); |
| if (!ret) |
| clear_bit(RTW89_SER_HAL_STOP_DMA, ser->flags); |
| else |
| rtw89_debug(rtwdev, RTW89_DBG_SER, |
| "lv1 rcvy fail to start dma: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int hal_stop_dma(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| int ret; |
| |
| if (!rtwdev->hci.ops->mac_lv1_rcvy) |
| return -EIO; |
| |
| ret = rtwdev->hci.ops->mac_lv1_rcvy(rtwdev, RTW89_LV1_RCVY_STEP_1); |
| if (!ret) |
| set_bit(RTW89_SER_HAL_STOP_DMA, ser->flags); |
| else |
| rtw89_debug(rtwdev, RTW89_DBG_SER, |
| "lv1 rcvy fail to stop dma: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static void hal_send_post_m0_event(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| rtw89_mac_set_err_status(rtwdev, MAC_AX_ERR_L1_RESET_START_DMAC); |
| } |
| |
| static void hal_send_m2_event(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| rtw89_mac_set_err_status(rtwdev, MAC_AX_ERR_L1_DISABLE_EN); |
| } |
| |
| static void hal_send_m4_event(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| rtw89_mac_set_err_status(rtwdev, MAC_AX_ERR_L1_RCVY_EN); |
| } |
| |
| /* state handler */ |
| static void ser_idle_st_hdl(struct rtw89_ser *ser, u8 evt) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| switch (evt) { |
| case SER_EV_STATE_IN: |
| rtw89_hci_recovery_complete(rtwdev); |
| clear_bit(RTW89_FLAG_SER_HANDLING, rtwdev->flags); |
| clear_bit(RTW89_FLAG_CRASH_SIMULATING, rtwdev->flags); |
| break; |
| case SER_EV_L1_RESET_PREPARE: |
| ser_state_goto(ser, SER_L1_RESET_PRE_ST); |
| break; |
| case SER_EV_L1_RESET: |
| ser_state_goto(ser, SER_RESET_TRX_ST); |
| break; |
| case SER_EV_L2_RESET: |
| ser_state_goto(ser, SER_L2_RESET_ST); |
| break; |
| case SER_EV_STATE_OUT: |
| set_bit(RTW89_FLAG_SER_HANDLING, rtwdev->flags); |
| rtw89_hci_recovery_start(rtwdev); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void ser_l1_reset_pre_st_hdl(struct rtw89_ser *ser, u8 evt) |
| { |
| switch (evt) { |
| case SER_EV_STATE_IN: |
| ser->prehandle_l1 = true; |
| hal_send_post_m0_event(ser); |
| ser_set_alarm(ser, 1000, SER_EV_M1_TIMEOUT); |
| break; |
| case SER_EV_L1_RESET: |
| ser_state_goto(ser, SER_RESET_TRX_ST); |
| break; |
| case SER_EV_M1_TIMEOUT: |
| ser_state_goto(ser, SER_L2_RESET_ST); |
| break; |
| case SER_EV_STATE_OUT: |
| ser_del_alarm(ser); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void ser_reset_trx_st_hdl(struct rtw89_ser *ser, u8 evt) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| switch (evt) { |
| case SER_EV_STATE_IN: |
| cancel_delayed_work_sync(&rtwdev->track_work); |
| drv_stop_tx(ser); |
| |
| if (hal_stop_dma(ser)) { |
| ser_state_goto(ser, SER_L2_RESET_ST); |
| break; |
| } |
| |
| drv_stop_rx(ser); |
| drv_trx_reset(ser); |
| |
| /* wait m3 */ |
| hal_send_m2_event(ser); |
| |
| /* set alarm to prevent FW response timeout */ |
| ser_set_alarm(ser, 1000, SER_EV_M3_TIMEOUT); |
| break; |
| |
| case SER_EV_DO_RECOVERY: |
| ser_state_goto(ser, SER_DO_HCI_ST); |
| break; |
| |
| case SER_EV_M3_TIMEOUT: |
| ser_state_goto(ser, SER_L2_RESET_ST); |
| break; |
| |
| case SER_EV_STATE_OUT: |
| ser_del_alarm(ser); |
| hal_enable_dma(ser); |
| drv_resume_rx(ser); |
| drv_resume_tx(ser); |
| ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->track_work, |
| RTW89_TRACK_WORK_PERIOD); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static void ser_do_hci_st_hdl(struct rtw89_ser *ser, u8 evt) |
| { |
| switch (evt) { |
| case SER_EV_STATE_IN: |
| /* wait m5 */ |
| hal_send_m4_event(ser); |
| |
| /* prevent FW response timeout */ |
| ser_set_alarm(ser, 1000, SER_EV_FW_M5_TIMEOUT); |
| break; |
| |
| case SER_EV_FW_M5_TIMEOUT: |
| ser_state_goto(ser, SER_L2_RESET_ST); |
| break; |
| |
| case SER_EV_MAC_RESET_DONE: |
| ser_state_goto(ser, SER_IDLE_ST); |
| break; |
| |
| case SER_EV_STATE_OUT: |
| ser_del_alarm(ser); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static void ser_mac_mem_dump(struct rtw89_dev *rtwdev, u8 *buf, |
| u8 sel, u32 start_addr, u32 len) |
| { |
| const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; |
| u32 filter_model_addr = mac->filter_model_addr; |
| u32 indir_access_addr = mac->indir_access_addr; |
| u32 *ptr = (u32 *)buf; |
| u32 base_addr, start_page, residue; |
| u32 cnt = 0; |
| u32 i; |
| |
| start_page = start_addr / MAC_MEM_DUMP_PAGE_SIZE; |
| residue = start_addr % MAC_MEM_DUMP_PAGE_SIZE; |
| base_addr = mac->mem_base_addrs[sel]; |
| base_addr += start_page * MAC_MEM_DUMP_PAGE_SIZE; |
| |
| while (cnt < len) { |
| rtw89_write32(rtwdev, filter_model_addr, base_addr); |
| |
| for (i = indir_access_addr + residue; |
| i < indir_access_addr + MAC_MEM_DUMP_PAGE_SIZE; |
| i += 4, ptr++) { |
| *ptr = rtw89_read32(rtwdev, i); |
| cnt += 4; |
| if (cnt >= len) |
| break; |
| } |
| |
| residue = 0; |
| base_addr += MAC_MEM_DUMP_PAGE_SIZE; |
| } |
| } |
| |
| static void rtw89_ser_fw_rsvd_ple_dump(struct rtw89_dev *rtwdev, u8 *buf) |
| { |
| u32 start_addr = rtwdev->chip->rsvd_ple_ofst; |
| |
| rtw89_debug(rtwdev, RTW89_DBG_SER, |
| "dump mem for fw rsvd payload engine (start addr: 0x%x)\n", |
| start_addr); |
| ser_mac_mem_dump(rtwdev, buf, RTW89_MAC_MEM_SHARED_BUF, start_addr, |
| RTW89_FW_RSVD_PLE_SIZE); |
| } |
| |
| struct __fw_backtrace_entry { |
| u32 wcpu_addr; |
| u32 size; |
| u32 key; |
| } __packed; |
| |
| struct __fw_backtrace_info { |
| u32 ra; |
| u32 sp; |
| } __packed; |
| |
| static_assert(RTW89_FW_BACKTRACE_INFO_SIZE == |
| sizeof(struct __fw_backtrace_info)); |
| |
| static u32 convert_addr_from_wcpu(u32 wcpu_addr) |
| { |
| if (wcpu_addr < 0x30000000) |
| return wcpu_addr; |
| |
| return wcpu_addr & GENMASK(28, 0); |
| } |
| |
| static int rtw89_ser_fw_backtrace_dump(struct rtw89_dev *rtwdev, u8 *buf, |
| const struct __fw_backtrace_entry *ent) |
| { |
| struct __fw_backtrace_info *ptr = (struct __fw_backtrace_info *)buf; |
| const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; |
| u32 filter_model_addr = mac->filter_model_addr; |
| u32 indir_access_addr = mac->indir_access_addr; |
| u32 fwbt_addr = convert_addr_from_wcpu(ent->wcpu_addr); |
| u32 fwbt_size = ent->size; |
| u32 fwbt_key = ent->key; |
| u32 i; |
| |
| if (fwbt_addr == 0) { |
| rtw89_warn(rtwdev, "FW backtrace invalid address: 0x%x\n", |
| fwbt_addr); |
| return -EINVAL; |
| } |
| |
| if (fwbt_key != RTW89_FW_BACKTRACE_KEY) { |
| rtw89_warn(rtwdev, "FW backtrace invalid key: 0x%x\n", |
| fwbt_key); |
| return -EINVAL; |
| } |
| |
| if (fwbt_size == 0 || !RTW89_VALID_FW_BACKTRACE_SIZE(fwbt_size) || |
| fwbt_size > RTW89_FW_BACKTRACE_MAX_SIZE) { |
| rtw89_warn(rtwdev, "FW backtrace invalid size: 0x%x\n", |
| fwbt_size); |
| return -EINVAL; |
| } |
| |
| rtw89_debug(rtwdev, RTW89_DBG_SER, "dump fw backtrace start\n"); |
| rtw89_write32(rtwdev, filter_model_addr, fwbt_addr); |
| |
| for (i = indir_access_addr; |
| i < indir_access_addr + fwbt_size; |
| i += RTW89_FW_BACKTRACE_INFO_SIZE, ptr++) { |
| *ptr = (struct __fw_backtrace_info){ |
| .ra = rtw89_read32(rtwdev, i), |
| .sp = rtw89_read32(rtwdev, i + 4), |
| }; |
| rtw89_debug(rtwdev, RTW89_DBG_SER, |
| "next sp: 0x%x, next ra: 0x%x\n", |
| ptr->sp, ptr->ra); |
| } |
| |
| rtw89_debug(rtwdev, RTW89_DBG_SER, "dump fw backtrace end\n"); |
| return 0; |
| } |
| |
| static void ser_l2_reset_st_pre_hdl(struct rtw89_ser *ser) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| struct rtw89_ser_cd_buffer *buf; |
| struct __fw_backtrace_entry fwbt_ent; |
| int ret = 0; |
| |
| buf = rtw89_ser_cd_prep(rtwdev); |
| if (!buf) { |
| ret = -ENOMEM; |
| goto bottom; |
| } |
| |
| rtw89_ser_fw_rsvd_ple_dump(rtwdev, buf->fwple.data); |
| |
| fwbt_ent = *(struct __fw_backtrace_entry *)buf->fwple.data; |
| ret = rtw89_ser_fw_backtrace_dump(rtwdev, buf->fwbt.data, &fwbt_ent); |
| if (ret) |
| goto bottom; |
| |
| rtw89_ser_cd_send(rtwdev, buf); |
| |
| bottom: |
| rtw89_ser_cd_free(rtwdev, buf, !!ret); |
| |
| ser_reset_mac_binding(rtwdev); |
| rtw89_core_stop(rtwdev); |
| rtw89_entity_init(rtwdev); |
| rtw89_fw_release_general_pkt_list(rtwdev, false); |
| INIT_LIST_HEAD(&rtwdev->rtwvifs_list); |
| } |
| |
| static void ser_l2_reset_st_hdl(struct rtw89_ser *ser, u8 evt) |
| { |
| struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); |
| |
| switch (evt) { |
| case SER_EV_STATE_IN: |
| mutex_lock(&rtwdev->mutex); |
| ser_l2_reset_st_pre_hdl(ser); |
| mutex_unlock(&rtwdev->mutex); |
| |
| ieee80211_restart_hw(rtwdev->hw); |
| ser_set_alarm(ser, SER_RECFG_TIMEOUT, SER_EV_L2_RECFG_TIMEOUT); |
| break; |
| |
| case SER_EV_L2_RECFG_TIMEOUT: |
| rtw89_info(rtwdev, "Err: ser L2 re-config timeout\n"); |
| fallthrough; |
| case SER_EV_L2_RECFG_DONE: |
| ser_state_goto(ser, SER_IDLE_ST); |
| break; |
| |
| case SER_EV_STATE_OUT: |
| ser_del_alarm(ser); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static const struct event_ent ser_ev_tbl[] = { |
| {SER_EV_NONE, "SER_EV_NONE"}, |
| {SER_EV_STATE_IN, "SER_EV_STATE_IN"}, |
| {SER_EV_STATE_OUT, "SER_EV_STATE_OUT"}, |
| {SER_EV_L1_RESET_PREPARE, "SER_EV_L1_RESET_PREPARE pre-m0"}, |
| {SER_EV_L1_RESET, "SER_EV_L1_RESET m1"}, |
| {SER_EV_DO_RECOVERY, "SER_EV_DO_RECOVERY m3"}, |
| {SER_EV_MAC_RESET_DONE, "SER_EV_MAC_RESET_DONE m5"}, |
| {SER_EV_L2_RESET, "SER_EV_L2_RESET"}, |
| {SER_EV_L2_RECFG_DONE, "SER_EV_L2_RECFG_DONE"}, |
| {SER_EV_L2_RECFG_TIMEOUT, "SER_EV_L2_RECFG_TIMEOUT"}, |
| {SER_EV_M1_TIMEOUT, "SER_EV_M1_TIMEOUT"}, |
| {SER_EV_M3_TIMEOUT, "SER_EV_M3_TIMEOUT"}, |
| {SER_EV_FW_M5_TIMEOUT, "SER_EV_FW_M5_TIMEOUT"}, |
| {SER_EV_L0_RESET, "SER_EV_L0_RESET"}, |
| {SER_EV_MAXX, "SER_EV_MAX"} |
| }; |
| |
| static const struct state_ent ser_st_tbl[] = { |
| {SER_IDLE_ST, "SER_IDLE_ST", ser_idle_st_hdl}, |
| {SER_L1_RESET_PRE_ST, "SER_L1_RESET_PRE_ST", ser_l1_reset_pre_st_hdl}, |
| {SER_RESET_TRX_ST, "SER_RESET_TRX_ST", ser_reset_trx_st_hdl}, |
| {SER_DO_HCI_ST, "SER_DO_HCI_ST", ser_do_hci_st_hdl}, |
| {SER_L2_RESET_ST, "SER_L2_RESET_ST", ser_l2_reset_st_hdl} |
| }; |
| |
| int rtw89_ser_init(struct rtw89_dev *rtwdev) |
| { |
| struct rtw89_ser *ser = &rtwdev->ser; |
| |
| memset(ser, 0, sizeof(*ser)); |
| INIT_LIST_HEAD(&ser->msg_q); |
| ser->state = SER_IDLE_ST; |
| ser->st_tbl = ser_st_tbl; |
| ser->ev_tbl = ser_ev_tbl; |
| |
| bitmap_zero(ser->flags, RTW89_NUM_OF_SER_FLAGS); |
| spin_lock_init(&ser->msg_q_lock); |
| INIT_WORK(&ser->ser_hdl_work, rtw89_ser_hdl_work); |
| INIT_DELAYED_WORK(&ser->ser_alarm_work, rtw89_ser_alarm_work); |
| return 0; |
| } |
| |
| int rtw89_ser_deinit(struct rtw89_dev *rtwdev) |
| { |
| struct rtw89_ser *ser = (struct rtw89_ser *)&rtwdev->ser; |
| |
| set_bit(RTW89_SER_DRV_STOP_RUN, ser->flags); |
| cancel_delayed_work_sync(&ser->ser_alarm_work); |
| cancel_work_sync(&ser->ser_hdl_work); |
| clear_bit(RTW89_SER_DRV_STOP_RUN, ser->flags); |
| return 0; |
| } |
| |
| void rtw89_ser_recfg_done(struct rtw89_dev *rtwdev) |
| { |
| ser_send_msg(&rtwdev->ser, SER_EV_L2_RECFG_DONE); |
| } |
| |
| int rtw89_ser_notify(struct rtw89_dev *rtwdev, u32 err) |
| { |
| u8 event = SER_EV_NONE; |
| |
| rtw89_info(rtwdev, "SER catches error: 0x%x\n", err); |
| |
| switch (err) { |
| case MAC_AX_ERR_L1_PREERR_DMAC: /* pre-M0 */ |
| event = SER_EV_L1_RESET_PREPARE; |
| break; |
| case MAC_AX_ERR_L1_ERR_DMAC: |
| case MAC_AX_ERR_L0_PROMOTE_TO_L1: |
| event = SER_EV_L1_RESET; /* M1 */ |
| break; |
| case MAC_AX_ERR_L1_RESET_DISABLE_DMAC_DONE: |
| event = SER_EV_DO_RECOVERY; /* M3 */ |
| break; |
| case MAC_AX_ERR_L1_RESET_RECOVERY_DONE: |
| event = SER_EV_MAC_RESET_DONE; /* M5 */ |
| break; |
| case MAC_AX_ERR_L0_ERR_CMAC0: |
| case MAC_AX_ERR_L0_ERR_CMAC1: |
| case MAC_AX_ERR_L0_RESET_DONE: |
| event = SER_EV_L0_RESET; |
| break; |
| default: |
| if (err == MAC_AX_ERR_L1_PROMOTE_TO_L2 || |
| (err >= MAC_AX_ERR_L2_ERR_AH_DMA && |
| err <= MAC_AX_GET_ERR_MAX)) |
| event = SER_EV_L2_RESET; |
| break; |
| } |
| |
| if (event == SER_EV_NONE) { |
| rtw89_warn(rtwdev, "SER cannot recognize error: 0x%x\n", err); |
| return -EINVAL; |
| } |
| |
| ser_send_msg(&rtwdev->ser, event); |
| return 0; |
| } |
| EXPORT_SYMBOL(rtw89_ser_notify); |