root/drivers/net/wwan/t7xx/t7xx_state_monitor.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2021, MediaTek Inc.
 * Copyright (c) 2021-2022, Intel Corporation.
 *
 * Authors:
 *  Haijun Liu <haijun.liu@mediatek.com>
 *  Eliot Lee <eliot.lee@intel.com>
 *  Moises Veleta <moises.veleta@intel.com>
 *  Ricardo Martinez <ricardo.martinez@linux.intel.com>
 *
 * Contributors:
 *  Amir Hanania <amir.hanania@intel.com>
 *  Sreehari Kancharla <sreehari.kancharla@intel.com>
 */

#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/iopoll.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/wait.h>

#include "t7xx_hif_cldma.h"
#include "t7xx_mhccif.h"
#include "t7xx_modem_ops.h"
#include "t7xx_pci.h"
#include "t7xx_pcie_mac.h"
#include "t7xx_port_proxy.h"
#include "t7xx_reg.h"
#include "t7xx_state_monitor.h"

#define FSM_DRM_DISABLE_DELAY_MS                200
#define FSM_EVENT_POLL_INTERVAL_MS              20
#define FSM_MD_EX_REC_OK_TIMEOUT_MS             10000
#define FSM_MD_EX_PASS_TIMEOUT_MS               45000
#define FSM_CMD_TIMEOUT_MS                      2000

#define wait_for_expected_dev_stage(status)     \
        read_poll_timeout(ioread32, status,     \
                          ((status & MISC_STAGE_MASK) == T7XX_DEV_STAGE_LINUX) ||       \
                          ((status & MISC_STAGE_MASK) == T7XX_DEV_STAGE_LK), 100000,    \
                          20000000, false, IREG_BASE(md->t7xx_dev) +    \
                          T7XX_PCIE_MISC_DEV_STATUS)

void t7xx_fsm_notifier_register(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
{
        struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
        unsigned long flags;

        spin_lock_irqsave(&ctl->notifier_lock, flags);
        list_add_tail(&notifier->entry, &ctl->notifier_list);
        spin_unlock_irqrestore(&ctl->notifier_lock, flags);
}

void t7xx_fsm_notifier_unregister(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
{
        struct t7xx_fsm_notifier *notifier_cur, *notifier_next;
        struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
        unsigned long flags;

        spin_lock_irqsave(&ctl->notifier_lock, flags);
        list_for_each_entry_safe(notifier_cur, notifier_next, &ctl->notifier_list, entry) {
                if (notifier_cur == notifier)
                        list_del(&notifier->entry);
        }
        spin_unlock_irqrestore(&ctl->notifier_lock, flags);
}

static void fsm_state_notify(struct t7xx_modem *md, enum md_state state)
{
        struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
        struct t7xx_fsm_notifier *notifier;
        unsigned long flags;

        spin_lock_irqsave(&ctl->notifier_lock, flags);
        list_for_each_entry(notifier, &ctl->notifier_list, entry) {
                spin_unlock_irqrestore(&ctl->notifier_lock, flags);
                if (notifier->notifier_fn)
                        notifier->notifier_fn(state, notifier->data);

                spin_lock_irqsave(&ctl->notifier_lock, flags);
        }
        spin_unlock_irqrestore(&ctl->notifier_lock, flags);
}

void t7xx_fsm_broadcast_state(struct t7xx_fsm_ctl *ctl, enum md_state state)
{
        ctl->md_state = state;

        /* Update to port first, otherwise sending message on HS2 may fail */
        t7xx_port_proxy_md_status_notify(ctl->md->port_prox, state);
        fsm_state_notify(ctl->md, state);
}

static void fsm_release_command(struct kref *ref)
{
        struct t7xx_fsm_command *cmd = container_of(ref, typeof(*cmd), refcnt);

        kfree(cmd);
}

static void fsm_finish_command(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd, int result)
{
        if (cmd->flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
                cmd->result = result;
                complete_all(&cmd->done);
        }

        kref_put(&cmd->refcnt, fsm_release_command);
}

static void fsm_del_kf_event(struct t7xx_fsm_event *event)
{
        list_del(&event->entry);
        kfree(event);
}

static void fsm_flush_event_cmd_qs(struct t7xx_fsm_ctl *ctl)
{
        struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
        struct t7xx_fsm_event *event, *evt_next;
        struct t7xx_fsm_command *cmd, *cmd_next;
        unsigned long flags;

        spin_lock_irqsave(&ctl->command_lock, flags);
        list_for_each_entry_safe(cmd, cmd_next, &ctl->command_queue, entry) {
                dev_warn(dev, "Unhandled command %d\n", cmd->cmd_id);
                list_del(&cmd->entry);
                fsm_finish_command(ctl, cmd, -EINVAL);
        }
        spin_unlock_irqrestore(&ctl->command_lock, flags);

        spin_lock_irqsave(&ctl->event_lock, flags);
        list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
                dev_warn(dev, "Unhandled event %d\n", event->event_id);
                fsm_del_kf_event(event);
        }
        spin_unlock_irqrestore(&ctl->event_lock, flags);
}

static void fsm_wait_for_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_expected,
                               enum t7xx_fsm_event_state event_ignore, int retries)
{
        struct t7xx_fsm_event *event;
        bool event_received = false;
        unsigned long flags;
        int cnt = 0;

        while (cnt++ < retries && !event_received) {
                bool sleep_required = true;

                if (kthread_should_stop())
                        return;

                spin_lock_irqsave(&ctl->event_lock, flags);
                event = list_first_entry_or_null(&ctl->event_queue, struct t7xx_fsm_event, entry);
                if (event) {
                        event_received = event->event_id == event_expected;
                        if (event_received || event->event_id == event_ignore) {
                                fsm_del_kf_event(event);
                                sleep_required = false;
                        }
                }
                spin_unlock_irqrestore(&ctl->event_lock, flags);

                if (sleep_required)
                        msleep(FSM_EVENT_POLL_INTERVAL_MS);
        }
}

static void fsm_routine_exception(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd,
                                  enum t7xx_ex_reason reason)
{
        struct device *dev = &ctl->md->t7xx_dev->pdev->dev;

        if (ctl->curr_state != FSM_STATE_READY && ctl->curr_state != FSM_STATE_STARTING) {
                if (cmd)
                        fsm_finish_command(ctl, cmd, -EINVAL);

                return;
        }

        ctl->curr_state = FSM_STATE_EXCEPTION;

        switch (reason) {
        case EXCEPTION_HS_TIMEOUT:
                dev_err(dev, "Boot Handshake failure\n");
                break;

        case EXCEPTION_EVENT:
                dev_err(dev, "Exception event\n");
                t7xx_fsm_broadcast_state(ctl, MD_STATE_EXCEPTION);
                t7xx_pci_pm_exp_detected(ctl->md->t7xx_dev);
                t7xx_md_exception_handshake(ctl->md);

                fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_REC_OK, FSM_EVENT_MD_EX,
                                   FSM_MD_EX_REC_OK_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS);
                fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_PASS, FSM_EVENT_INVALID,
                                   FSM_MD_EX_PASS_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS);
                break;

        default:
                dev_err(dev, "Exception %d\n", reason);
                break;
        }

        if (cmd)
                fsm_finish_command(ctl, cmd, 0);
}

static void t7xx_lk_stage_event_handling(struct t7xx_fsm_ctl *ctl, unsigned int status)
{
        struct t7xx_modem *md = ctl->md;
        struct cldma_ctrl *md_ctrl;
        enum lk_event_id lk_event;
        struct device *dev;
        struct t7xx_port *port;

        dev = &md->t7xx_dev->pdev->dev;
        lk_event = FIELD_GET(MISC_LK_EVENT_MASK, status);
        switch (lk_event) {
        case LK_EVENT_NORMAL:
        case LK_EVENT_RESET:
                break;

        case LK_EVENT_CREATE_PD_PORT:
        case LK_EVENT_CREATE_POST_DL_PORT:
                md_ctrl = md->md_ctrl[CLDMA_ID_AP];
                t7xx_cldma_hif_hw_init(md_ctrl);
                t7xx_cldma_stop(md_ctrl);
                t7xx_cldma_switch_cfg(md_ctrl, CLDMA_DEDICATED_Q_CFG);

                port = &ctl->md->port_prox->ports[0];
                port->port_conf->ops->enable_chl(port);

                t7xx_cldma_start(md_ctrl);

                if (lk_event == LK_EVENT_CREATE_POST_DL_PORT)
                        t7xx_mode_update(md->t7xx_dev, T7XX_FASTBOOT_DOWNLOAD);
                else
                        t7xx_mode_update(md->t7xx_dev, T7XX_FASTBOOT_DUMP);
                break;

        default:
                dev_err(dev, "Invalid LK event %d\n", lk_event);
                break;
        }
}

static int fsm_stopped_handler(struct t7xx_fsm_ctl *ctl)
{
        enum t7xx_mode mode;

        ctl->curr_state = FSM_STATE_STOPPED;

        mode = READ_ONCE(ctl->md->t7xx_dev->mode);
        if (mode == T7XX_FASTBOOT_DOWNLOAD || mode == T7XX_FASTBOOT_DUMP)
                return 0;

        t7xx_fsm_broadcast_state(ctl, MD_STATE_STOPPED);
        return t7xx_md_reset(ctl->md->t7xx_dev);
}

static void fsm_routine_stopped(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
{
        if (ctl->curr_state == FSM_STATE_STOPPED) {
                fsm_finish_command(ctl, cmd, -EINVAL);
                return;
        }

        fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl));
}

static void fsm_routine_stopping(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
{
        struct cldma_ctrl *md_ctrl = ctl->md->md_ctrl[CLDMA_ID_MD];
        struct t7xx_pci_dev *t7xx_dev = ctl->md->t7xx_dev;

        if (ctl->curr_state == FSM_STATE_STOPPED || ctl->curr_state == FSM_STATE_STOPPING) {
                fsm_finish_command(ctl, cmd, -EINVAL);
                return;
        }

        ctl->curr_state = FSM_STATE_STOPPING;
        t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_TO_STOP);
        t7xx_cldma_stop(md_ctrl);

        t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DRM_DISABLE_AP);
        /* Wait for the DRM disable to take effect */
        msleep(FSM_DRM_DISABLE_DELAY_MS);

        fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl));
}

static void t7xx_fsm_broadcast_ready_state(struct t7xx_fsm_ctl *ctl)
{
        if (ctl->md_state != MD_STATE_WAITING_FOR_HS2)
                return;

        ctl->md_state = MD_STATE_READY;

        fsm_state_notify(ctl->md, MD_STATE_READY);
        t7xx_port_proxy_md_status_notify(ctl->md->port_prox, MD_STATE_READY);
}

static void fsm_routine_ready(struct t7xx_fsm_ctl *ctl)
{
        struct t7xx_modem *md = ctl->md;

        ctl->curr_state = FSM_STATE_READY;
        t7xx_fsm_broadcast_ready_state(ctl);
        t7xx_mode_update(md->t7xx_dev, T7XX_READY);
        t7xx_md_event_notify(md, FSM_READY);
}

static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl)
{
        struct t7xx_modem *md = ctl->md;
        struct device *dev;

        ctl->curr_state = FSM_STATE_STARTING;

        t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS1);
        t7xx_md_event_notify(md, FSM_START);

        wait_event_interruptible_timeout(ctl->async_hk_wq,
                                         (md->core_md.ready && md->core_ap.ready) ||
                                          ctl->exp_flg, HZ * 60);
        dev = &md->t7xx_dev->pdev->dev;

        if (ctl->exp_flg)
                dev_err(dev, "MD exception is captured during handshake\n");

        if (!md->core_md.ready) {
                dev_err(dev, "MD handshake timeout\n");
                if (md->core_md.handshake_ongoing)
                        t7xx_fsm_append_event(ctl, FSM_EVENT_MD_HS2_EXIT, NULL, 0);

                fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
                return -ETIMEDOUT;
        } else if (!md->core_ap.ready) {
                dev_err(dev, "AP handshake timeout\n");
                if (md->core_ap.handshake_ongoing)
                        t7xx_fsm_append_event(ctl, FSM_EVENT_AP_HS2_EXIT, NULL, 0);

                fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
                return -ETIMEDOUT;
        }

        t7xx_pci_pm_init_late(md->t7xx_dev);
        fsm_routine_ready(ctl);
        return 0;
}

static void fsm_routine_start(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
{
        struct t7xx_modem *md = ctl->md;
        struct device *dev;
        u32 status;
        int ret;

        if (!md)
                return;

        if (ctl->curr_state != FSM_STATE_INIT && ctl->curr_state != FSM_STATE_PRE_START &&
            ctl->curr_state != FSM_STATE_STOPPED) {
                fsm_finish_command(ctl, cmd, -EINVAL);
                return;
        }

        dev = &md->t7xx_dev->pdev->dev;
        ctl->curr_state = FSM_STATE_PRE_START;
        t7xx_md_event_notify(md, FSM_PRE_START);

        ret = wait_for_expected_dev_stage(status);

        if (ret) {
                dev_err(dev, "read poll timeout %d\n", ret);
                goto finish_command;
        }

        if (status != ctl->status || cmd->flag != 0) {
                u32 stage = FIELD_GET(MISC_STAGE_MASK, status);

                switch (stage) {
                case T7XX_DEV_STAGE_INIT:
                case T7XX_DEV_STAGE_BROM_PRE:
                case T7XX_DEV_STAGE_BROM_POST:
                        dev_dbg(dev, "BROM_STAGE Entered\n");
                        ret = t7xx_fsm_append_cmd(ctl, FSM_CMD_START, 0);
                        break;

                case T7XX_DEV_STAGE_LK:
                        dev_dbg(dev, "LK_STAGE Entered\n");
                        t7xx_port_proxy_set_cfg(md, PORT_CFG_ID_EARLY);
                        t7xx_lk_stage_event_handling(ctl, status);

                        break;

                case T7XX_DEV_STAGE_LINUX:
                        dev_dbg(dev, "LINUX_STAGE Entered\n");
                        t7xx_mhccif_mask_clr(md->t7xx_dev, D2H_INT_PORT_ENUM |
                                             D2H_INT_ASYNC_MD_HK | D2H_INT_ASYNC_AP_HK);
                        if (cmd->flag == 0)
                                break;
                        t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_AP]);
                        t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_MD]);
                        t7xx_port_proxy_set_cfg(md, PORT_CFG_ID_NORMAL);
                        ret = fsm_routine_starting(ctl);
                        break;

                default:
                        break;
                }
                ctl->status = status;
        }

finish_command:
        if (ret)
                t7xx_mode_update(md->t7xx_dev, T7XX_UNKNOWN);

        fsm_finish_command(ctl, cmd, ret);
}

static int fsm_main_thread(void *data)
{
        struct t7xx_fsm_ctl *ctl = data;
        struct t7xx_fsm_command *cmd;
        unsigned long flags;

        while (!kthread_should_stop()) {
                if (wait_event_interruptible(ctl->command_wq, !list_empty(&ctl->command_queue) ||
                                             kthread_should_stop()))
                        continue;

                if (kthread_should_stop())
                        break;

                spin_lock_irqsave(&ctl->command_lock, flags);
                cmd = list_first_entry(&ctl->command_queue, struct t7xx_fsm_command, entry);
                list_del(&cmd->entry);
                spin_unlock_irqrestore(&ctl->command_lock, flags);

                switch (cmd->cmd_id) {
                case FSM_CMD_START:
                        fsm_routine_start(ctl, cmd);
                        break;

                case FSM_CMD_EXCEPTION:
                        fsm_routine_exception(ctl, cmd, FIELD_GET(FSM_CMD_EX_REASON, cmd->flag));
                        break;

                case FSM_CMD_PRE_STOP:
                        fsm_routine_stopping(ctl, cmd);
                        break;

                case FSM_CMD_STOP:
                        fsm_routine_stopped(ctl, cmd);
                        break;

                default:
                        fsm_finish_command(ctl, cmd, -EINVAL);
                        fsm_flush_event_cmd_qs(ctl);
                        break;
                }
        }

        return 0;
}

int t7xx_fsm_append_cmd(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_cmd_state cmd_id, unsigned int flag)
{
        struct t7xx_fsm_command *cmd;
        unsigned long flags;
        int ret;

        cmd = kzalloc_obj(*cmd,
                          flag & FSM_CMD_FLAG_IN_INTERRUPT ? GFP_ATOMIC : GFP_KERNEL);
        if (!cmd)
                return -ENOMEM;

        INIT_LIST_HEAD(&cmd->entry);
        cmd->cmd_id = cmd_id;
        cmd->flag = flag;
        kref_init(&cmd->refcnt);
        if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
                init_completion(&cmd->done);
                kref_get(&cmd->refcnt);
        }

        kref_get(&cmd->refcnt);
        spin_lock_irqsave(&ctl->command_lock, flags);
        list_add_tail(&cmd->entry, &ctl->command_queue);
        spin_unlock_irqrestore(&ctl->command_lock, flags);

        wake_up(&ctl->command_wq);

        if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
                unsigned long wait_ret;

                wait_ret = wait_for_completion_timeout(&cmd->done,
                                                       msecs_to_jiffies(FSM_CMD_TIMEOUT_MS));

                ret = wait_ret ? cmd->result : -ETIMEDOUT;
                kref_put(&cmd->refcnt, fsm_release_command);
                return ret;
        }

        return 0;
}

int t7xx_fsm_append_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id,
                          unsigned char *data, unsigned int length)
{
        struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
        struct t7xx_fsm_event *event;
        unsigned long flags;

        if (event_id <= FSM_EVENT_INVALID || event_id >= FSM_EVENT_MAX) {
                dev_err(dev, "Invalid event %d\n", event_id);
                return -EINVAL;
        }

        event = kmalloc_flex(*event, data, length,
                             in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
        if (!event)
                return -ENOMEM;

        INIT_LIST_HEAD(&event->entry);
        event->event_id = event_id;
        event->length = length;

        if (data && length)
                memcpy(event->data, data, length);

        spin_lock_irqsave(&ctl->event_lock, flags);
        list_add_tail(&event->entry, &ctl->event_queue);
        spin_unlock_irqrestore(&ctl->event_lock, flags);

        wake_up_all(&ctl->event_wq);
        return 0;
}

void t7xx_fsm_clr_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id)
{
        struct t7xx_fsm_event *event, *evt_next;
        unsigned long flags;

        spin_lock_irqsave(&ctl->event_lock, flags);
        list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
                if (event->event_id == event_id)
                        fsm_del_kf_event(event);
        }
        spin_unlock_irqrestore(&ctl->event_lock, flags);
}

enum md_state t7xx_fsm_get_md_state(struct t7xx_fsm_ctl *ctl)
{
        if (ctl)
                return ctl->md_state;

        return MD_STATE_INVALID;
}

unsigned int t7xx_fsm_get_ctl_state(struct t7xx_fsm_ctl *ctl)
{
        if (ctl)
                return ctl->curr_state;

        return FSM_STATE_STOPPED;
}

int t7xx_fsm_recv_md_intr(struct t7xx_fsm_ctl *ctl, enum t7xx_md_irq_type type)
{
        unsigned int cmd_flags = FSM_CMD_FLAG_IN_INTERRUPT;

        if (type == MD_IRQ_PORT_ENUM) {
                return t7xx_fsm_append_cmd(ctl, FSM_CMD_START, cmd_flags);
        } else if (type == MD_IRQ_CCIF_EX) {
                ctl->exp_flg = true;
                wake_up(&ctl->async_hk_wq);
                cmd_flags |= FIELD_PREP(FSM_CMD_EX_REASON, EXCEPTION_EVENT);
                return t7xx_fsm_append_cmd(ctl, FSM_CMD_EXCEPTION, cmd_flags);
        }

        return -EINVAL;
}

void t7xx_fsm_reset(struct t7xx_modem *md)
{
        struct t7xx_fsm_ctl *ctl = md->fsm_ctl;

        fsm_flush_event_cmd_qs(ctl);
        ctl->curr_state = FSM_STATE_STOPPED;
        ctl->exp_flg = false;
        ctl->status = T7XX_DEV_STAGE_INIT;
}

int t7xx_fsm_init(struct t7xx_modem *md)
{
        struct device *dev = &md->t7xx_dev->pdev->dev;
        struct t7xx_fsm_ctl *ctl;

        ctl = devm_kzalloc(dev, sizeof(*ctl), GFP_KERNEL);
        if (!ctl)
                return -ENOMEM;

        md->fsm_ctl = ctl;
        ctl->md = md;
        ctl->curr_state = FSM_STATE_INIT;
        INIT_LIST_HEAD(&ctl->command_queue);
        INIT_LIST_HEAD(&ctl->event_queue);
        init_waitqueue_head(&ctl->async_hk_wq);
        init_waitqueue_head(&ctl->event_wq);
        INIT_LIST_HEAD(&ctl->notifier_list);
        init_waitqueue_head(&ctl->command_wq);
        spin_lock_init(&ctl->event_lock);
        spin_lock_init(&ctl->command_lock);
        ctl->exp_flg = false;
        spin_lock_init(&ctl->notifier_lock);

        ctl->fsm_thread = kthread_run(fsm_main_thread, ctl, "t7xx_fsm");
        return PTR_ERR_OR_ZERO(ctl->fsm_thread);
}

void t7xx_fsm_uninit(struct t7xx_modem *md)
{
        struct t7xx_fsm_ctl *ctl = md->fsm_ctl;

        if (!ctl)
                return;

        if (ctl->fsm_thread)
                kthread_stop(ctl->fsm_thread);

        fsm_flush_event_cmd_qs(ctl);
}