firmware: qcom_scm: Handle Waitqueue operations iteratively

Instead of calling __scm_smc_call() recursively to make the
waitqueue-specific SMC calls, make them directly by populating the SMC
call arguments by hand and calling __scm_smc_do_quirk().

Motivating points for this change:
1. With the exception of get_wq_ctx(), the other two waitqueue-specific
   SMC calls wq_wake_ack() and wq_resume() can return WAIT_SLEEP, and
   this cycle of attempting to resume and sleeping can take place
   several times. With recursive logic, this can potentially lead to
   stack overflow.
2. If an SMC client call has received WAITQ_SLEEP, it must prevent other
   fresh client calls from being made until it has reached completion
   (success or error). Therefore, handle the waitqueue operations within
   the mutex qcom_scm_lock.

Use a do-while loop to iteratively check the return values of
wq_resume() and wq_wake_ack() for WAITQ_SLEEP or WAITQ_WAKE.

Change-Id: I937eb00c753903885fb211abb2bfeec5c431ee35
Signed-off-by: Guru Das Srinagesh <quic_gurus@quicinc.com>
This commit is contained in:
Guru Das Srinagesh 2021-10-27 11:48:36 -07:00
parent edd26f2e68
commit 8b32f7b699
3 changed files with 172 additions and 173 deletions

View File

@ -53,23 +53,135 @@ static void __scm_smc_do_quirk(const struct arm_smccc_args *smc,
} while (res->a0 == QCOM_SCM_INTERRUPTED);
}
static void __scm_smc_do(const struct arm_smccc_args *smc,
#define IS_WAITQ_SLEEP_OR_WAKE(res) \
(res->a0 == QCOM_SCM_WAITQ_SLEEP || res->a0 == QCOM_SCM_WAITQ_WAKE)
static void fill_wq_resume_args(struct arm_smccc_args *resume, u32 smc_call_ctx)
{
memset(resume->args, 0, ARRAY_SIZE(resume->args));
resume->args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_RESUME));
resume->args[1] = QCOM_SCM_ARGS(1);
resume->args[2] = smc_call_ctx;
}
static void fill_wq_wake_ack_args(struct arm_smccc_args *wake_ack, u32 smc_call_ctx)
{
memset(wake_ack->args, 0, ARRAY_SIZE(wake_ack->args));
wake_ack->args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_ACK));
wake_ack->args[1] = QCOM_SCM_ARGS(1);
wake_ack->args[2] = smc_call_ctx;
}
static void fill_get_wq_ctx_args(struct arm_smccc_args *get_wq_ctx)
{
memset(get_wq_ctx->args, 0, ARRAY_SIZE(get_wq_ctx->args));
get_wq_ctx->args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_GET_WQ_CTX));
}
int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending)
{
int ret;
struct arm_smccc_args get_wq_ctx = {0};
struct arm_smccc_res get_wq_res;
fill_get_wq_ctx_args(&get_wq_ctx);
__scm_smc_do_quirk(&get_wq_ctx, &get_wq_res);
/* Guaranteed to return only success or error, no WAITQ_* */
ret = get_wq_res.a0;
if (ret)
return ret;
*wq_ctx = get_wq_res.a1;
*flags = get_wq_res.a2;
*more_pending = get_wq_res.a3;
return 0;
}
static int scm_smc_do_quirk(struct device *dev, struct arm_smccc_args *smc,
struct arm_smccc_res *res)
{
struct completion *wq = NULL;
struct qcom_scm *qscm;
u32 wq_ctx, smc_call_ctx, flags;
do {
__scm_smc_do_quirk(smc, res);
if (IS_WAITQ_SLEEP_OR_WAKE(res)) {
wq_ctx = res->a1;
smc_call_ctx = res->a2;
flags = res->a3;
if (!dev)
return -EPROBE_DEFER;
qscm = dev_get_drvdata(dev);
wq = qcom_scm_lookup_wq(qscm, wq_ctx);
if (IS_ERR_OR_NULL(wq)) {
pr_err("No waitqueue found for wq_ctx %d: %d\n",
wq_ctx, PTR_ERR(wq));
return PTR_ERR(wq);
}
if (res->a0 == QCOM_SCM_WAITQ_SLEEP) {
wait_for_completion(wq);
fill_wq_resume_args(smc, smc_call_ctx);
wq = NULL;
continue;
} else {
fill_wq_wake_ack_args(smc, smc_call_ctx);
continue;
}
} else if ((long)res->a0 < 0) {
/* Error, simply return to caller */
break;
} else {
/*
* Success.
* wq will be set only if a prior WAKE happened.
* Its value will be the one from the prior WAKE.
*/
if (wq)
scm_waitq_flag_handler(wq, flags);
break;
}
} while (IS_WAITQ_SLEEP_OR_WAKE(res));
return 0;
}
static int __scm_smc_do(struct device *dev, struct arm_smccc_args *smc,
struct arm_smccc_res *res,
enum qcom_scm_call_type call_type)
{
int retry_count = 0;
int ret, retry_count = 0;
if (call_type == QCOM_SCM_CALL_ATOMIC) {
__scm_smc_do_quirk(smc, res);
return;
return 0;
}
do {
mutex_lock(&qcom_scm_lock);
__scm_smc_do_quirk(smc, res);
ret = scm_smc_do_quirk(dev, smc, res);
mutex_unlock(&qcom_scm_lock);
if (ret)
return ret;
if (res->a0 == QCOM_SCM_V2_EBUSY) {
if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY ||
@ -78,6 +190,8 @@ static void __scm_smc_do(const struct arm_smccc_args *smc,
msleep(QCOM_SCM_EBUSY_WAIT_MS);
}
} while (res->a0 == QCOM_SCM_V2_EBUSY);
return 0;
}
int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
@ -96,7 +210,6 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
ARM_SMCCC_SMC_32 : ARM_SMCCC_SMC_64;
struct arm_smccc_res smc_res;
struct arm_smccc_args smc = {0};
struct qcom_scm_res wait_res;
smc.args[0] = ARM_SMCCC_CALL_VAL(
smccc_call_type,
@ -152,7 +265,8 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
smc.args[SCM_SMC_LAST_REG_IDX] = shm.paddr;
}
__scm_smc_do(&smc, &smc_res, call_type);
ret = __scm_smc_do(dev, &smc, &smc_res, call_type);
/* ret error check follows shm cleanup */
if (shm.vaddr) {
dma_unmap_single(dev, shm.paddr, alloc_len, DMA_TO_DEVICE);
@ -162,31 +276,16 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
kfree(shm.vaddr);
}
if (smc_res.a0 == QCOM_SCM_WAITQ_SLEEP ||
smc_res.a0 == QCOM_SCM_WAITQ_WAKE) {
/* Atomic calls should not wait */
BUG_ON(call_type == QCOM_SCM_CALL_ATOMIC);
if (ret)
return ret;
if (!dev)
return -EPROBE_DEFER;
wait_res.result[0] = smc_res.a1;
wait_res.result[1] = smc_res.a2;
wait_res.result[2] = smc_res.a3;
ret = qcom_scm_handle_wait(dev, smc_res.a0, &wait_res);
if (res)
*res = wait_res;
} else {
if (res) {
res->result[0] = smc_res.a1;
res->result[1] = smc_res.a2;
res->result[2] = smc_res.a3;
}
ret = (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0;
if (res) {
res->result[0] = smc_res.a1;
res->result[1] = smc_res.a2;
res->result[2] = smc_res.a3;
}
ret = (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0;
return ret;
}

View File

@ -41,6 +41,12 @@ module_param(pas_shutdown_retry_delay, uint, 0644);
#define SCM_HAS_IFACE_CLK BIT(1)
#define SCM_HAS_BUS_CLK BIT(2)
struct qcom_scm_waitq {
struct idr idr;
spinlock_t idr_lock;
struct work_struct scm_irq_work;
};
struct qcom_scm {
struct device *dev;
struct clk *core_clk;
@ -48,9 +54,7 @@ struct qcom_scm {
struct clk *bus_clk;
struct reset_controller_dev reset;
struct notifier_block restart_nb;
struct work_struct scm_irq_work;
struct idr wq_idr;
spinlock_t wq_idr_lock;
struct qcom_scm_waitq waitq;
u64 dload_mode_addr;
};
@ -2197,53 +2201,6 @@ int qcom_scm_camera_protect_phy_lanes(bool protect, u64 regmask)
}
EXPORT_SYMBOL(qcom_scm_camera_protect_phy_lanes);
static int qcom_scm_wq_wake_ack(u32 smc_call_ctx, struct qcom_scm_res *res)
{
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_WAITQ,
.cmd = QCOM_SCM_WAITQ_ACK,
.owner = ARM_SMCCC_OWNER_SIP,
.args[0] = smc_call_ctx,
.arginfo = QCOM_SCM_ARGS(1),
};
return qcom_scm_call(__scm->dev, &desc, res);
}
static int qcom_scm_wq_resume(u32 smc_call_ctx, struct qcom_scm_res *res)
{
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_WAITQ,
.cmd = QCOM_SCM_WAITQ_RESUME,
.owner = ARM_SMCCC_OWNER_SIP,
.args[0] = smc_call_ctx,
.arginfo = QCOM_SCM_ARGS(1),
};
return qcom_scm_call(__scm->dev, &desc, res);
}
static int qcom_scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending)
{
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_WAITQ,
.cmd = QCOM_SCM_WAITQ_GET_WQ_CTX,
.owner = ARM_SMCCC_OWNER_SIP,
};
struct qcom_scm_res res;
int ret;
ret = qcom_scm_call(__scm->dev, &desc, &res);
if (ret)
return ret;
*wq_ctx = res.result[0];
*flags = res.result[1];
*more_pending = res.result[2];
return ret;
}
int qcom_scm_tsens_reinit(int *tsens_ret)
{
unsigned int ret;
@ -2548,39 +2505,39 @@ static int qcom_scm_do_restart(struct notifier_block *this, unsigned long event,
return NOTIFY_OK;
}
static struct completion *qcom_scm_lookup_wq(struct qcom_scm *scm,
u32 wq_ctx)
struct completion *qcom_scm_lookup_wq(struct qcom_scm *scm, u32 wq_ctx)
{
struct completion *wq = NULL;
u32 wq_ctx_idr = wq_ctx;
unsigned long flags;
int err;
spin_lock(&scm->wq_idr_lock);
wq = idr_find(&scm->wq_idr, wq_ctx);
spin_unlock(&scm->wq_idr_lock);
spin_lock_irqsave(&scm->waitq.idr_lock, flags);
wq = idr_find(&scm->waitq.idr, wq_ctx);
if (wq)
return wq;
goto out;
wq = devm_kzalloc(scm->dev, sizeof(*wq), GFP_ATOMIC);
if (!wq)
return ERR_PTR(-ENOMEM);
if (!wq) {
wq = ERR_PTR(-ENOMEM);
goto out;
}
init_completion(wq);
spin_lock(&scm->wq_idr_lock);
err = idr_alloc_u32(&scm->wq_idr, wq, &wq_ctx_idr,
err = idr_alloc_u32(&scm->waitq.idr, wq, &wq_ctx_idr,
(wq_ctx_idr < U32_MAX ? : U32_MAX), GFP_ATOMIC);
spin_unlock(&scm->wq_idr_lock);
if (err < 0) {
devm_kfree(scm->dev, wq);
return ERR_PTR(err);
wq = ERR_PTR(err);
}
out:
spin_unlock_irqrestore(&scm->waitq.idr_lock, flags);
return wq;
}
static void qcom_scm_waitq_flag_handler(struct completion *wq, u32 flags)
void scm_waitq_flag_handler(struct completion *wq, u32 flags)
{
switch (flags) {
case QCOM_SMC_WAITQ_FLAG_WAKE_ONE:
@ -2594,84 +2551,21 @@ static void qcom_scm_waitq_flag_handler(struct completion *wq, u32 flags)
}
}
static int qcom_scm_handle_wait_sleep(struct qcom_scm *scm,
u32 wq_ctx)
{
struct completion *wq;
int rc;
wq = qcom_scm_lookup_wq(scm, wq_ctx);
if (IS_ERR_OR_NULL(wq)) {
pr_err("No waitqueue found\n");
return PTR_ERR(wq);
}
reinit_completion(wq);
rc = wait_for_completion_timeout(wq, msecs_to_jiffies(200));
if (!rc)
pr_err("Timed out\n");
return 0;
}
static int qcom_scm_handle_wait_wake(struct qcom_scm *scm, u32 wq_ctx,
u32 smc_call_ctx, u32 flags,
struct qcom_scm_res *res)
{
struct completion *wq;
wq = qcom_scm_lookup_wq(scm, wq_ctx);
if (IS_ERR_OR_NULL(wq)) {
pr_err("No waitqueue found\n");
return PTR_ERR(wq);
}
/* Allow resume to occur first */
qcom_scm_waitq_flag_handler(wq, flags);
return qcom_scm_wq_wake_ack(smc_call_ctx, res);
}
int qcom_scm_handle_wait(struct device *dev, int scm_ret,
struct qcom_scm_res *res)
{
struct qcom_scm *scm = dev_get_drvdata(dev);
u32 wq_ctx = res->result[0], smc_call_ctx = res->result[1];
u32 flags = res->result[2];
int ret;
switch (scm_ret) {
case QCOM_SCM_WAITQ_WAKE:
/* Exit in order to let another thread waiting on the same wq
* to proceed and send wq_resume().
*/
return qcom_scm_handle_wait_wake(scm, wq_ctx, smc_call_ctx,
flags, res);
case QCOM_SCM_WAITQ_SLEEP:
ret = qcom_scm_handle_wait_sleep(scm, wq_ctx);
if (ret)
return ret;
break;
default:
pr_err("Invalid return code\n");
}
return qcom_scm_wq_resume(smc_call_ctx, res);
}
static void scm_irq_work(struct work_struct *work)
{
int ret;
u32 wq_ctx, flags, more_pending = 0;
struct completion *wq_to_wake;
struct qcom_scm *scm = container_of(work, struct qcom_scm,
scm_irq_work);
struct qcom_scm_waitq *w = container_of(work, struct qcom_scm_waitq, scm_irq_work);
struct qcom_scm *scm = container_of(w, struct qcom_scm, waitq);
if (qcom_scm_convention != SMC_CONVENTION_ARM_64) {
/* Unsupported */
return;
}
do {
ret = qcom_scm_get_wq_ctx(&wq_ctx, &flags, &more_pending);
ret = scm_get_wq_ctx(&wq_ctx, &flags, &more_pending);
if (ret) {
pr_err("GET_WQ_CTX SMC call failed: %d\n", ret);
return;
@ -2679,11 +2573,12 @@ static void scm_irq_work(struct work_struct *work)
wq_to_wake = qcom_scm_lookup_wq(scm, wq_ctx);
if (IS_ERR_OR_NULL(wq_to_wake)) {
pr_err("No waitqueue found for wq_ctx %d\n", wq_ctx);
pr_err("No waitqueue found for wq_ctx %d: %d\n",
wq_ctx, PTR_ERR(wq_to_wake));
return;
}
qcom_scm_waitq_flag_handler(wq_to_wake, flags);
scm_waitq_flag_handler(wq_to_wake, flags);
} while (more_pending);
}
@ -2691,7 +2586,7 @@ static irqreturn_t qcom_scm_irq_handler(int irq, void *p)
{
struct qcom_scm *scm = p;
schedule_work(&scm->scm_irq_work);
schedule_work(&scm->waitq.scm_irq_work);
return IRQ_HANDLED;
}
@ -2772,10 +2667,10 @@ static int qcom_scm_probe(struct platform_device *pdev)
__scm = scm;
__scm->dev = &pdev->dev;
spin_lock_init(&__scm->wq_idr_lock);
idr_init(&__scm->wq_idr);
spin_lock_init(&__scm->waitq.idr_lock);
idr_init(&__scm->waitq.idr);
if (of_device_is_compatible(__scm->dev->of_node, "qcom,scm-v1.1")) {
INIT_WORK(&__scm->scm_irq_work, scm_irq_work);
INIT_WORK(&__scm->waitq.scm_irq_work, scm_irq_work);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
@ -2810,7 +2705,7 @@ static int qcom_scm_probe(struct platform_device *pdev)
static void qcom_scm_shutdown(struct platform_device *pdev)
{
idr_destroy(&__scm->wq_idr);
idr_destroy(&__scm->waitq.idr);
qcom_scm_disable_sdi();
qcom_scm_halt_spmi_pmic_arbiter();
/* Clean shutdown, disable download mode to allow normal restart */

View File

@ -66,6 +66,11 @@ enum qcom_scm_call_type {
QCOM_SCM_CALL_NORETRY,
};
struct qcom_scm;
extern struct completion *qcom_scm_lookup_wq(struct qcom_scm *scm, u32 wq_ctx);
extern void scm_waitq_flag_handler(struct completion *wq, u32 flags);
extern int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending);
#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF))
extern int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
enum qcom_scm_convention qcom_convention,