kernel_samsung_a34x-permissive/drivers/misc/mediatek/eccci/fsm/ccci_fsm.c

1064 lines
27 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016 MediaTek Inc.
*/
/*
* Author: Xiao Wang <xiao.wang@mediatek.com>
*/
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#endif
#include <memory/mediatek/emi.h>
#include "ccci_config.h"
#if (MD_GENERATION >= 6297)
#include <mt-plat/mtk_ccci_common.h>
#include "modem_secure_base.h"
#endif
#ifdef CCCI_PLATFORM_MT6781
#include "modem_sys.h"
#include "md_sys1_platform.h"
#include "modem_reg_base.h"
#endif
#include "ccci_fsm_internal.h"
#include "ccci_platform.h"
static struct ccci_fsm_ctl *ccci_fsm_entries[MAX_MD_NUM];
static void fsm_finish_command(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_command *cmd, int result);
static void fsm_finish_event(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_event *event);
static int needforcestop;
static int s_is_normal_mdee;
static int s_devapc_dump_counter;
static void (*s_md_state_cb)(enum MD_STATE old_state,
enum MD_STATE new_state);
int mtk_ccci_register_md_state_cb(
void (*md_state_cb)(
enum MD_STATE old_state,
enum MD_STATE new_state))
{
s_md_state_cb = md_state_cb;
return 0;
}
EXPORT_SYMBOL(mtk_ccci_register_md_state_cb);
int force_md_stop(struct ccci_fsm_monitor *monitor_ctl)
{
int ret = -1;
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(monitor_ctl->md_id);
needforcestop = 1;
if (!ctl) {
CCCI_ERROR_LOG(-1, FSM,
"%s:fsm_get_entity_by_md_id fail\n",
__func__);
return -1;
}
ret = fsm_append_command(ctl, CCCI_COMMAND_STOP, 0);
CCCI_NORMAL_LOG(monitor_ctl->md_id, FSM,
"force md stop\n");
return ret;
}
unsigned long __weak BAT_Get_Battery_Voltage(int polling_mode)
{
pr_debug("[ccci/dummy] %s is not supported!\n", __func__);
return 0;
}
void mdee_set_ex_time_str(unsigned char md_id, unsigned int type, char *str)
{
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(md_id);
if (!ctl) {
CCCI_ERROR_LOG(-1, FSM,
"%s:fsm_get_entity_by_md_id fail\n",
__func__);
return;
}
mdee_set_ex_start_str(&ctl->ee_ctl, type, str);
}
static struct ccci_fsm_command *fsm_check_for_ee(struct ccci_fsm_ctl *ctl,
int xip)
{
struct ccci_fsm_command *cmd = NULL;
struct ccci_fsm_command *next = NULL;
unsigned long flags;
spin_lock_irqsave(&ctl->command_lock, flags);
if (!list_empty(&ctl->command_queue)) {
cmd = list_first_entry(&ctl->command_queue,
struct ccci_fsm_command, entry);
if (cmd->cmd_id == CCCI_COMMAND_EE) {
if (xip)
list_del(&cmd->entry);
next = cmd;
}
}
spin_unlock_irqrestore(&ctl->command_lock, flags);
return next;
}
static inline int fsm_broadcast_state(struct ccci_fsm_ctl *ctl,
enum MD_STATE state)
{
enum MD_STATE old_state;
if (unlikely(ctl->md_state != BOOT_WAITING_FOR_HS2 && state == READY)) {
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"ignore HS2 when md_state=%d\n",
ctl->md_state);
return 0;
}
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"md_state change from %d to %d\n",
ctl->md_state, state);
old_state = ctl->md_state;
ctl->md_state = state;
/* update to port first,
* otherwise send message on HS2 may fail
*/
ccci_port_md_status_notify(ctl->md_id, state);
ccci_hif_state_notification(ctl->md_id, state);
#ifdef FEATURE_SCP_CCCI_SUPPORT
schedule_work(&ctl->scp_ctl.scp_md_state_sync_work);
#endif
if (old_state != state &&
s_md_state_cb != NULL)
s_md_state_cb(old_state, state);
return 0;
}
static void fsm_routine_zombie(struct ccci_fsm_ctl *ctl)
{
struct ccci_fsm_event *event = NULL;
struct ccci_fsm_event *evt_next = NULL;
struct ccci_fsm_command *cmd = NULL;
struct ccci_fsm_command *cmd_next = NULL;
unsigned long flags;
CCCI_ERROR_LOG(ctl->md_id, FSM,
"unexpected FSM state %d->%d, from %ps\n",
ctl->last_state, ctl->curr_state,
__builtin_return_address(0));
spin_lock_irqsave(&ctl->command_lock, flags);
list_for_each_entry_safe(cmd,
cmd_next, &ctl->command_queue, entry) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"unhandled command %d\n", cmd->cmd_id);
list_del(&cmd->entry);
fsm_finish_command(ctl, cmd, -1);
}
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) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"unhandled event %d\n", event->event_id);
fsm_finish_event(ctl, event);
}
spin_unlock_irqrestore(&ctl->event_lock, flags);
}
int ccci_fsm_is_normal_mdee(void)
{
return s_is_normal_mdee;
}
int ccci_fsm_increase_devapc_dump_counter(void)
{
return (++ s_devapc_dump_counter);
}
void __weak mtk_clear_md_violation(void)
{
CCCI_ERROR_LOG(-1, FSM, "[%s] is not supported!\n", __func__);
}
/* cmd is not NULL only when reason is ordinary EE */
static void fsm_routine_exception(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_command *cmd, enum CCCI_EE_REASON reason)
{
int count = 0, ex_got = 0;
int rec_ok_got = 0, pass_got = 0;
struct ccci_fsm_event *event = NULL;
unsigned long flags;
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"exception %d, from %ps\n",
reason, __builtin_return_address(0));
fsm_monitor_send_message(ctl->md_id,
CCCI_MD_MSG_EXCEPTION, 0);
/* 1. state sanity check */
if (ctl->curr_state == CCCI_FSM_GATED) {
if (cmd)
fsm_finish_command(ctl, cmd, -1);
fsm_routine_zombie(ctl);
return;
}
ctl->last_state = ctl->curr_state;
ctl->curr_state = CCCI_FSM_EXCEPTION;
if (reason == EXCEPTION_WDT
|| reason == EXCEPTION_HS1_TIMEOUT
|| reason == EXCEPTION_HS2_TIMEOUT)
mdee_set_ex_start_str(&ctl->ee_ctl, 0, NULL);
/* 2. check EE reason */
switch (reason) {
case EXCEPTION_HS1_TIMEOUT:
CCCI_ERROR_LOG(ctl->md_id, FSM,
"MD_BOOT_HS1_FAIL!\n");
fsm_md_bootup_timeout_handler(&ctl->ee_ctl);
break;
case EXCEPTION_HS2_TIMEOUT:
CCCI_ERROR_LOG(ctl->md_id, FSM,
"MD_BOOT_HS2_FAIL!\n");
fsm_md_bootup_timeout_handler(&ctl->ee_ctl);
break;
case EXCEPTION_MD_NO_RESPONSE:
CCCI_ERROR_LOG(ctl->md_id, FSM,
"MD_NO_RESPONSE!\n");
fsm_broadcast_state(ctl, EXCEPTION);
fsm_md_no_response_handler(&ctl->ee_ctl);
break;
case EXCEPTION_WDT:
fsm_broadcast_state(ctl, EXCEPTION);
CCCI_ERROR_LOG(ctl->md_id, FSM,
"MD_WDT!\n");
fsm_md_wdt_handler(&ctl->ee_ctl);
break;
case EXCEPTION_EE:
fsm_broadcast_state(ctl, EXCEPTION);
/* no need to implement another
* event polling in EE_CTRL,
* so we do it here
*/
ccci_md_exception_handshake(ctl->md_id,
MD_EX_CCIF_TIMEOUT);
#if (MD_GENERATION >= 6297)
#ifndef MTK_EMI_MPU_DISABLE
mtk_clear_md_violation();
#endif
#endif
count = 0;
while (count < MD_EX_REC_OK_TIMEOUT/EVENT_POLL_INTEVAL) {
spin_lock_irqsave(&ctl->event_lock, flags);
if (!list_empty(&ctl->event_queue)) {
event = list_first_entry(&ctl->event_queue,
struct ccci_fsm_event, entry);
if (event->event_id == CCCI_EVENT_MD_EX) {
ex_got = 1;
fsm_finish_event(ctl, event);
} else if (event->event_id ==
CCCI_EVENT_MD_EX_REC_OK) {
rec_ok_got = 1;
fsm_finish_event(ctl, event);
}
}
spin_unlock_irqrestore(&ctl->event_lock, flags);
if (rec_ok_got)
break;
count++;
msleep(EVENT_POLL_INTEVAL);
}
fsm_md_exception_stage(&ctl->ee_ctl, 1);
count = 0;
while (count < MD_EX_PASS_TIMEOUT/EVENT_POLL_INTEVAL) {
spin_lock_irqsave(&ctl->event_lock, flags);
if (!list_empty(&ctl->event_queue)) {
event = list_first_entry(&ctl->event_queue,
struct ccci_fsm_event, entry);
if (event->event_id == CCCI_EVENT_MD_EX_PASS) {
pass_got = 1;
fsm_finish_event(ctl, event);
}
}
spin_unlock_irqrestore(&ctl->event_lock, flags);
if (pass_got)
break;
count++;
msleep(EVENT_POLL_INTEVAL);
}
fsm_md_exception_stage(&ctl->ee_ctl, 2);
/*wait until modem memory dump done*/
fsm_check_ee_done(&ctl->ee_ctl, EE_DONE_TIMEOUT);
break;
default:
break;
}
/* 3. always end in exception state */
if (cmd)
fsm_finish_command(ctl, cmd, 1);
}
#if (MD_GENERATION >= 6297)
static void fsm_dump_boot_status(int md_id)
{
struct arm_smccc_res res;
arm_smccc_smc(MTK_SIP_KERNEL_CCCI_CONTROL,
MD_POWER_CONFIG, MD_BOOT_STATUS,
0, 0, 0, 0, 0, &res);
CCCI_NORMAL_LOG(md_id, FSM,
"[%s] AP: boot_ret=%lu, boot_status_0=%lX, boot_status_1=%lX\n",
__func__, res.a0, res.a1, res.a2);
}
#endif
static void fsm_routine_start(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_command *cmd)
{
int ret;
int count = 0, user_exit = 0, hs1_got = 0, hs2_got = 0;
struct ccci_fsm_event *event = NULL;
struct ccci_fsm_event *next = NULL;
unsigned long flags;
/* 1. state sanity check */
if (ctl->curr_state == CCCI_FSM_READY)
goto success;
if (ctl->curr_state != CCCI_FSM_GATED) {
fsm_finish_command(ctl, cmd, -1);
fsm_routine_zombie(ctl);
return;
}
ctl->last_state = ctl->curr_state;
ctl->curr_state = CCCI_FSM_STARTING;
__pm_stay_awake(ctl->wakelock);
/* 2. poll for critical users exit */
while (count < BOOT_TIMEOUT/EVENT_POLL_INTEVAL && !needforcestop) {
if (ccci_port_check_critical_user(ctl->md_id) == 0 ||
ccci_port_critical_user_only_fsd(ctl->md_id)) {
user_exit = 1;
break;
}
count++;
msleep(EVENT_POLL_INTEVAL);
}
/* what if critical user still alive:
* we can't wait for ever since this may be
* an illegal sequence (enter flight mode -> force start),
* and we must be able to recover from it.
* we'd better not entering exception state as
* start operation is not allowed in exception state.
* so we tango on...
*/
if (!user_exit)
CCCI_ERROR_LOG(ctl->md_id, FSM, "critical user alive %d\n",
ccci_port_check_critical_user(ctl->md_id));
spin_lock_irqsave(&ctl->event_lock, flags);
list_for_each_entry_safe(event, next, &ctl->event_queue, entry) {
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"drop event %d before start\n", event->event_id);
fsm_finish_event(ctl, event);
}
spin_unlock_irqrestore(&ctl->event_lock, flags);
/* 3. action and poll event queue */
ccci_md_pre_start(ctl->md_id);
fsm_broadcast_state(ctl, BOOT_WAITING_FOR_HS1);
ret = ccci_md_start(ctl->md_id);
if (ret)
goto fail;
ctl->boot_count++;
count = 0;
while (count < BOOT_TIMEOUT/EVENT_POLL_INTEVAL && !needforcestop) {
spin_lock_irqsave(&ctl->event_lock, flags);
if (!list_empty(&ctl->event_queue)) {
event = list_first_entry(&ctl->event_queue,
struct ccci_fsm_event, entry);
if (event->event_id == CCCI_EVENT_HS1) {
hs1_got = 1;
#if (MD_GENERATION >= 6297)
fsm_dump_boot_status(ctl->md_id);
#endif
fsm_broadcast_state(ctl, BOOT_WAITING_FOR_HS2);
if (event->length
== sizeof(struct md_query_ap_feature)
+ sizeof(struct ccci_header))
ccci_md_prepare_runtime_data(ctl->md_id,
event->data, event->length);
else if (event->length
== sizeof(struct ccci_header))
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"old handshake1 message\n");
else
CCCI_ERROR_LOG(ctl->md_id, FSM,
"invalid MD_QUERY_MSG %d\n",
event->length);
#ifdef SET_EMI_STEP_BY_STAGE
ccci_set_mem_access_protection_second_stage(
ctl->md_id);
#endif
ccci_md_dump_info(ctl->md_id,
DUMP_MD_BOOTUP_STATUS, NULL, 0);
fsm_finish_event(ctl, event);
spin_unlock_irqrestore(&ctl->event_lock, flags);
/* this API would alloc skb */
ret = ccci_md_send_runtime_data(ctl->md_id);
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"send runtime data %d\n", ret);
spin_lock_irqsave(&ctl->event_lock, flags);
} else if (event->event_id == CCCI_EVENT_HS2) {
hs2_got = 1;
fsm_broadcast_state(ctl, READY);
fsm_finish_event(ctl, event);
}
}
spin_unlock_irqrestore(&ctl->event_lock, flags);
if (fsm_check_for_ee(ctl, 0)) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"early exception detected\n");
goto fail_ee;
}
if (hs2_got)
goto success;
/* defeatured for now, just enlarge BOOT_TIMEOUT */
if (atomic_read(&ctl->fs_ongoing))
count = 0;
else
count++;
msleep(EVENT_POLL_INTEVAL);
}
if (needforcestop) {
fsm_finish_command(ctl, cmd, -1);
return;
}
/* 4. check result, finish command */
fail:
if (hs1_got)
fsm_routine_exception(ctl, NULL, EXCEPTION_HS2_TIMEOUT);
else
fsm_routine_exception(ctl, NULL, EXCEPTION_HS1_TIMEOUT);
fsm_finish_command(ctl, cmd, -1);
__pm_relax(ctl->wakelock);
return;
fail_ee:
/* exit imediately,
* let md_init have chance to start MD logger service
*/
fsm_finish_command(ctl, cmd, -1);
__pm_relax(ctl->wakelock);
return;
success:
ctl->last_state = ctl->curr_state;
ctl->curr_state = CCCI_FSM_READY;
ccci_md_post_start(ctl->md_id);
fsm_finish_command(ctl, cmd, 1);
__pm_relax(ctl->wakelock);
__pm_wakeup_event(ctl->wakelock, jiffies_to_msecs(10 * HZ));
}
static void fsm_routine_stop(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_command *cmd)
{
struct ccci_fsm_event *event = NULL;
struct ccci_fsm_event *next = NULL;
struct ccci_fsm_command *ee_cmd = NULL;
struct port_t *port = NULL;
struct sk_buff *skb = NULL;
unsigned long flags;
/* 1. state sanity check */
if (ctl->curr_state == CCCI_FSM_GATED)
goto success;
if (ctl->curr_state != CCCI_FSM_READY && !needforcestop
&& ctl->curr_state != CCCI_FSM_EXCEPTION) {
fsm_finish_command(ctl, cmd, -1);
fsm_routine_zombie(ctl);
return;
}
__pm_stay_awake(ctl->wakelock);
ctl->last_state = ctl->curr_state;
ctl->curr_state = CCCI_FSM_STOPPING;
/* 2. pre-stop: polling MD for infinit sleep mode */
ccci_md_pre_stop(ctl->md_id,
cmd->flag & FSM_CMD_FLAG_FLIGHT_MODE
?
MD_FLIGHT_MODE_ENTER
:
MD_FLIGHT_MODE_NONE);
/* 3. check for EE */
ee_cmd = fsm_check_for_ee(ctl, 1);
if (ee_cmd) {
fsm_routine_exception(ctl, ee_cmd, EXCEPTION_EE);
fsm_check_ee_done(&ctl->ee_ctl, EE_DONE_TIMEOUT);
}
/* to block port's write operation, must after EE flow done */
fsm_broadcast_state(ctl, WAITING_TO_STOP);
/*reset fsm poller*/
ctl->poller_ctl.poller_state = FSM_POLLER_RECEIVED_RESPONSE;
wake_up(&ctl->poller_ctl.status_rx_wq);
/* 4. hardware stop */
ccci_md_stop(ctl->md_id,
cmd->flag & FSM_CMD_FLAG_FLIGHT_MODE
?
MD_FLIGHT_MODE_ENTER
:
MD_FLIGHT_MODE_NONE);
/* 5. clear event queue */
spin_lock_irqsave(&ctl->event_lock, flags);
list_for_each_entry_safe(event, next,
&ctl->event_queue, entry) {
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"drop event %d after stop\n",
event->event_id);
fsm_finish_event(ctl, event);
}
spin_unlock_irqrestore(&ctl->event_lock, flags);
__pm_relax(ctl->wakelock);
/* 6. always end in stopped state */
success:
needforcestop = 0;
/* when MD is stopped, the skb list of ccci_fs should be clean */
port = port_get_by_channel(ctl->md_id, CCCI_FS_RX);
if (port == NULL) {
CCCI_ERROR_LOG(ctl->md_id, FSM, "port_get_by_channel fail");
return;
}
if (port->flags & PORT_F_CLEAN) {
spin_lock_irqsave(&port->rx_skb_list.lock, flags);
while ((skb = __skb_dequeue(&port->rx_skb_list)) != NULL)
ccci_free_skb(skb);
spin_unlock_irqrestore(&port->rx_skb_list.lock, flags);
}
ctl->last_state = ctl->curr_state;
ctl->curr_state = CCCI_FSM_GATED;
fsm_broadcast_state(ctl, GATED);
fsm_finish_command(ctl, cmd, 1);
}
static void fsm_routine_wdt(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_command *cmd)
{
int reset_md = 0;
int is_epon_set = 0;
struct device_node *node;
unsigned int offset_apon_md1 = 0;
struct ccci_smem_region *mdss_dbg
= ccci_md_get_smem_by_user_id(ctl->md_id,
SMEM_USER_RAW_MDSS_DBG);
int ret;
#ifdef CCCI_PLATFORM_MT6781
struct ccci_modem *md = NULL;
struct md_sys1_info *md_info = NULL;
struct md_pll_reg *md_reg = NULL;
#endif
node = of_find_compatible_node(NULL, NULL, "mediatek,mddriver");
if (node) {
ret = of_property_read_u32(node,
"mediatek,offset_apon_md1", &offset_apon_md1);
if (ret < 0)
CCCI_ERROR_LOG(ctl->md_id, FSM,
"[%s] not found: mediatek,offset_apon_md1\n",
__func__);
} else
CCCI_ERROR_LOG(ctl->md_id, FSM,
"[%s] error: not found the mediatek,mddriver\n",
__func__);
#ifdef CCCI_PLATFORM_MT6781
md = ccci_md_get_modem_by_id(ctl->md_id);
if (md)
md_info = (struct md_sys1_info *)md->private_data;
else {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"%s: get md fail\n", __func__);
return;
}
if (md_info)
md_reg = md_info->md_pll_base;
else {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"%s: get md private_data fail\n", __func__);
return;
}
if (!md_reg) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"%s: get md_reg fail\n", __func__);
return;
}
if (!md_reg->md_l2sram_base) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"%s: get md_l2sram_base fail\n", __func__);
return;
}
#endif
if (ctl->md_id == MD_SYS1)
#ifdef CCCI_PLATFORM_MT6781
is_epon_set =
*((int *)(md_reg->md_l2sram_base
+ CCCI_EE_OFFSET_EPON_MD1)) == 0xBAEBAE10;
#else
is_epon_set =
*((int *)(mdss_dbg->base_ap_view_vir
+ offset_apon_md1)) == 0xBAEBAE10;
#endif
else if (ctl->md_id == MD_SYS3)
is_epon_set = *((int *)(mdss_dbg->base_ap_view_vir
+ CCCI_EE_OFFSET_EPON_MD3))
== 0xBAEBAE10;
if (is_epon_set) {
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"reset MD after WDT\n");
reset_md = 1;
} else {
if (ccci_port_get_critical_user(ctl->md_id,
CRIT_USR_MDLOG) == 0) {
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"mdlogger closed, reset MD after WDT\n");
reset_md = 1;
} else {
fsm_routine_exception(ctl, NULL, EXCEPTION_WDT);
}
}
if (reset_md) {
fsm_monitor_send_message(ctl->md_id,
CCCI_MD_MSG_RESET_REQUEST, 0);
fsm_monitor_send_message(GET_OTHER_MD_ID(ctl->md_id),
CCCI_MD_MSG_RESET_REQUEST, 0);
}
fsm_finish_command(ctl, cmd, 1);
}
static int fsm_main_thread(void *data)
{
struct ccci_fsm_ctl *ctl = (struct ccci_fsm_ctl *)data;
struct ccci_fsm_command *cmd = NULL;
unsigned long flags;
int ret;
while (!kthread_should_stop()) {
ret = wait_event_interruptible(ctl->command_wq,
!list_empty(&ctl->command_queue));
if (ret == -ERESTARTSYS)
continue;
spin_lock_irqsave(&ctl->command_lock, flags);
cmd = list_first_entry(&ctl->command_queue,
struct ccci_fsm_command, entry);
/* delete first, otherwise hard to peek
* next command in routines
*/
list_del(&cmd->entry);
spin_unlock_irqrestore(&ctl->command_lock, flags);
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"command %d process\n", cmd->cmd_id);
s_is_normal_mdee = 0;
s_devapc_dump_counter = 0;
switch (cmd->cmd_id) {
case CCCI_COMMAND_START:
fsm_routine_start(ctl, cmd);
break;
case CCCI_COMMAND_STOP:
fsm_routine_stop(ctl, cmd);
break;
case CCCI_COMMAND_WDT:
fsm_routine_wdt(ctl, cmd);
break;
case CCCI_COMMAND_EE:
s_is_normal_mdee = 1;
fsm_routine_exception(ctl, cmd, EXCEPTION_EE);
break;
case CCCI_COMMAND_MD_HANG:
fsm_routine_exception(ctl, cmd,
EXCEPTION_MD_NO_RESPONSE);
break;
default:
fsm_finish_command(ctl, cmd, -1);
fsm_routine_zombie(ctl);
break;
};
}
return 0;
}
int fsm_append_command(struct ccci_fsm_ctl *ctl,
enum CCCI_FSM_COMMAND cmd_id, unsigned int flag)
{
struct ccci_fsm_command *cmd = NULL;
int result = 0;
unsigned long flags;
int ret;
if (cmd_id <= CCCI_COMMAND_INVALID
|| cmd_id >= CCCI_COMMAND_MAX) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"invalid command %d\n", cmd_id);
return -CCCI_ERR_INVALID_PARAM;
}
cmd = kmalloc(sizeof(struct ccci_fsm_command),
(in_irq() || in_softirq()
|| irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL);
if (!cmd) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"fail to alloc command %d\n", cmd_id);
return -CCCI_ERR_GET_MEM_FAIL;
}
INIT_LIST_HEAD(&cmd->entry);
init_waitqueue_head(&cmd->complete_wq);
cmd->cmd_id = cmd_id;
cmd->complete = 0;
if (in_irq() || irqs_disabled())
flag &= ~FSM_CMD_FLAG_WAIT_FOR_COMPLETE;
cmd->flag = flag;
spin_lock_irqsave(&ctl->command_lock, flags);
list_add_tail(&cmd->entry, &ctl->command_queue);
spin_unlock_irqrestore(&ctl->command_lock, flags);
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"command %d is appended %x from %ps\n",
cmd_id, flag,
__builtin_return_address(0));
/* after this line, only dereference cmd
* when "wait-for-complete"
*/
wake_up(&ctl->command_wq);
if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETE) {
while (1) {
ret = wait_event_interruptible(cmd->complete_wq,
cmd->complete != 0);
if (ret == -ERESTARTSYS)
continue;
if (cmd->complete != 1)
result = -1;
spin_lock_irqsave(&ctl->cmd_complete_lock, flags);
kfree(cmd);
spin_unlock_irqrestore(&ctl->cmd_complete_lock, flags);
break;
}
}
return result;
}
static void fsm_finish_command(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_command *cmd, int result)
{
unsigned long flags;
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"command %d is completed %d by %ps\n",
cmd->cmd_id, result,
__builtin_return_address(0));
if (cmd->flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETE) {
spin_lock_irqsave(&ctl->cmd_complete_lock, flags);
cmd->complete = result;
/* do not dereference cmd after this line */
wake_up_all(&cmd->complete_wq);
/* after cmd in list,
* processing thread may see it
* without being waked up,
* so spinlock is needed
*/
spin_unlock_irqrestore(&ctl->cmd_complete_lock, flags);
} else {
/* no one is waiting for this cmd, free to free */
kfree(cmd);
}
}
int fsm_append_event(struct ccci_fsm_ctl *ctl, enum CCCI_FSM_EVENT event_id,
unsigned char *data, unsigned int length)
{
struct ccci_fsm_event *event = NULL;
unsigned long flags;
if (event_id <= CCCI_EVENT_INVALID || event_id >= CCCI_EVENT_MAX) {
CCCI_ERROR_LOG(ctl->md_id, FSM, "invalid event %d\n", event_id);
return -CCCI_ERR_INVALID_PARAM;
}
if (event_id == CCCI_EVENT_FS_IN) {
atomic_set(&(ctl->fs_ongoing), 1);
return 0;
} else if (event_id == CCCI_EVENT_FS_OUT) {
atomic_set(&(ctl->fs_ongoing), 0);
return 0;
}
event = kmalloc(sizeof(struct ccci_fsm_event) + length,
in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
if (!event) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"fail to alloc event%d\n", event_id);
return -CCCI_ERR_GET_MEM_FAIL;
}
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);
/* do not derefence event after here */
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"event %d is appended from %ps\n", event_id,
__builtin_return_address(0));
return 0;
}
/* must be called within protection of event_lock */
static void fsm_finish_event(struct ccci_fsm_ctl *ctl,
struct ccci_fsm_event *event)
{
list_del(&event->entry);
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"event %d is completed by %ps\n", event->event_id,
__builtin_return_address(0));
kfree(event);
}
struct ccci_fsm_ctl *fsm_get_entity_by_device_number(dev_t dev_n)
{
int i;
for (i = 0; i < ARRAY_SIZE(ccci_fsm_entries); i++) {
if (ccci_fsm_entries[i]
&& ccci_fsm_entries[i]->monitor_ctl.dev_n
== dev_n)
return ccci_fsm_entries[i];
}
return NULL;
}
struct ccci_fsm_ctl *fsm_get_entity_by_md_id(int md_id)
{
int i;
for (i = 0; i < ARRAY_SIZE(ccci_fsm_entries); i++) {
if (ccci_fsm_entries[i]
&& ccci_fsm_entries[i]->md_id == md_id)
return ccci_fsm_entries[i];
}
return NULL;
}
int ccci_fsm_init(int md_id)
{
struct ccci_fsm_ctl *ctl = NULL;
int ret = 0;
if (md_id < 0 || md_id >= ARRAY_SIZE(ccci_fsm_entries))
return -CCCI_ERR_INVALID_PARAM;
ctl = kzalloc(sizeof(struct ccci_fsm_ctl), GFP_KERNEL);
if (ctl == NULL) {
CCCI_ERROR_LOG(md_id, FSM,
"%s kzalloc ccci_fsm_ctl fail\n",
__func__);
return -1;
}
ctl->md_id = md_id;
ctl->last_state = CCCI_FSM_INVALID;
ctl->curr_state = CCCI_FSM_GATED;
INIT_LIST_HEAD(&ctl->command_queue);
INIT_LIST_HEAD(&ctl->event_queue);
init_waitqueue_head(&ctl->command_wq);
spin_lock_init(&ctl->event_lock);
spin_lock_init(&ctl->command_lock);
spin_lock_init(&ctl->cmd_complete_lock);
atomic_set(&ctl->fs_ongoing, 0);
ret = snprintf(ctl->wakelock_name, sizeof(ctl->wakelock_name),
"md%d_wakelock", ctl->md_id + 1);
if (ret < 0 || ret >= sizeof(ctl->wakelock_name)) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"%s-%d:snprintf fail,ret=%d\n", __func__, __LINE__, ret);
return -1;
}
ctl->wakelock = wakeup_source_register(NULL, ctl->wakelock_name);
if (!ctl->wakelock) {
CCCI_ERROR_LOG(ctl->md_id, FSM,
"%s %d: init wakeup source fail",
__func__, __LINE__);
return -1;
}
ctl->fsm_thread = kthread_run(fsm_main_thread, ctl,
"ccci_fsm%d", md_id + 1);
#ifdef FEATURE_SCP_CCCI_SUPPORT
fsm_scp_init(&ctl->scp_ctl);
#endif
fsm_poller_init(&ctl->poller_ctl);
fsm_ee_init(&ctl->ee_ctl);
fsm_monitor_init(&ctl->monitor_ctl);
fsm_sys_init();
ccci_fsm_entries[md_id] = ctl;
return 0;
}
enum MD_STATE ccci_fsm_get_md_state(int md_id)
{
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(md_id);
if (ctl)
return ctl->md_state;
else
return INVALID;
}
EXPORT_SYMBOL(ccci_fsm_get_md_state);
enum MD_STATE_FOR_USER ccci_fsm_get_md_state_for_user(int md_id)
{
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(md_id);
if (!ctl)
return MD_STATE_INVALID;
switch (ctl->md_state) {
case INVALID:
case WAITING_TO_STOP:
case GATED:
return MD_STATE_INVALID;
case RESET:
case BOOT_WAITING_FOR_HS1:
case BOOT_WAITING_FOR_HS2:
return MD_STATE_BOOTING;
case READY:
return MD_STATE_READY;
case EXCEPTION:
return MD_STATE_EXCEPTION;
default:
CCCI_ERROR_LOG(ctl->md_id, FSM,
"Invalid md_state %d\n", ctl->md_state);
return MD_STATE_INVALID;
}
}
EXPORT_SYMBOL(ccci_fsm_get_md_state_for_user);
int ccci_fsm_recv_md_interrupt(int md_id, enum MD_IRQ_TYPE type)
{
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(md_id);
if (!ctl)
return -CCCI_ERR_INVALID_PARAM;
__pm_wakeup_event(ctl->wakelock, jiffies_to_msecs(10 * HZ));
if (type == MD_IRQ_WDT) {
fsm_append_command(ctl, CCCI_COMMAND_WDT, 0);
} else if (type == MD_IRQ_CCIF_EX) {
fsm_md_exception_stage(&ctl->ee_ctl, 0);
fsm_append_command(ctl, CCCI_COMMAND_EE, 0);
}
return 0;
}
int ccci_fsm_recv_control_packet(int md_id, struct sk_buff *skb)
{
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(md_id);
struct ccci_header *ccci_h = (struct ccci_header *)skb->data;
int ret = 0, free_skb = 1;
struct c2k_ctrl_port_msg *c2k_ctl_msg = NULL;
struct ccci_per_md *per_md_data = ccci_get_per_md_data(md_id);
if (!ctl)
return -CCCI_ERR_INVALID_PARAM;
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"control message 0x%X,0x%X\n",
ccci_h->data[1], ccci_h->reserved);
switch (ccci_h->data[1]) {
case MD_INIT_START_BOOT: /* also MD_NORMAL_BOOT */
if (ccci_h->reserved == MD_INIT_CHK_ID)
fsm_append_event(ctl, CCCI_EVENT_HS1,
skb->data, skb->len);
else
fsm_append_event(ctl, CCCI_EVENT_HS2, NULL, 0);
break;
case MD_EX:
case MD_EX_REC_OK:
case MD_EX_PASS:
case CCCI_DRV_VER_ERROR:
fsm_ee_message_handler(&ctl->ee_ctl, skb);
break;
case C2K_HB_MSG:
free_skb = 0;
ccci_fsm_recv_status_packet(ctl->md_id, skb);
break;
case C2K_STATUS_IND_MSG:
case C2K_STATUS_QUERY_MSG:
c2k_ctl_msg = (struct c2k_ctrl_port_msg *)&ccci_h->reserved;
CCCI_NORMAL_LOG(ctl->md_id, FSM,
"C2K line status %d: 0x%02x\n",
ccci_h->data[1], c2k_ctl_msg->option);
if (c2k_ctl_msg->option & 0x80)
per_md_data->dtr_state = 1; /*connect */
else
per_md_data->dtr_state = 0; /*disconnect */
break;
case C2K_CCISM_SHM_INIT_ACK:
fsm_ccism_init_ack_handler(ctl->md_id, ccci_h->reserved);
break;
case C2K_FLOW_CTRL_MSG:
ccci_hif_start_queue(ctl->md_id, ccci_h->reserved, OUT);
break;
default:
CCCI_ERROR_LOG(ctl->md_id, FSM,
"unknown control message %x\n", ccci_h->data[1]);
break;
}
if (free_skb)
ccci_free_skb(skb);
return ret;
}
/* requested by throttling feature */
unsigned long ccci_get_md_boot_count(int md_id)
{
struct ccci_fsm_ctl *ctl = fsm_get_entity_by_md_id(md_id);
if (ctl)
return ctl->boot_count;
else
return 0;
}