Skip to content

Commit 6bf3259

Browse files
Guru Das Srinageshandersson
authored andcommitted
firmware: qcom: scm: Add wait-queue handling logic
When the firmware (FW) supports multiple requests per VM, multiple requests from the same/different VM can reach the firmware at the same time. Since the firmware currently being used has limited resources, it guards them with a resource lock and puts requests on a wait-queue internally and signals to HLOS that it is doing so. It does this by returning a new return value in addition to success or error: SCM_WAITQ_SLEEP. A sleeping SCM call can be woken up by an interrupt that the FW raises. 1) SCM_WAITQ_SLEEP: When an SCM call receives this return value instead of success or error, FW has placed this call on a wait-queue and has signalled HLOS to put it to non-interruptible sleep. Along with this return value, FW also passes to HLOS `wq_ctx` - a unique number (UID) identifying the wait-queue that it has put the call on, internally. This is to help HLOS with its own bookkeeping to wake this sleeping call later. Additionally, FW also passes to HLOS `smc_call_ctx` - a UID identifying the SCM call thus being put to sleep. This is also for HLOS' bookkeeping to wake this call up later. These two additional values are passed via the a1 and a2 registers. N.B.: The "ctx" in the above UID names = "context". The handshake mechanism that HLOS uses to talk to FW about wait-queue operations involves two new SMC calls. 1) get_wq_ctx(): Arguments: None Returns: wq_ctx, flags, more_pending Get the wait-queue context, and wake up either one or all of the sleeping SCM calls associated with that wait-queue. Additionally, repeat this if there are more wait-queues that are ready to have their requests woken up (`more_pending`). 2) wq_resume(smc_call_ctx): Arguments: smc_call_ctx HLOS needs to issue this in response to receiving an IRQ, passing to FW the same smc_call_ctx that FW receives from HLOS via the get_wq_ctx() call. (The mechanism to wake a SMC call back up is described in detail below) VM_1 VM_2 Firmware │ │ │ │ │ │ │ │ │ │ │ │ │ REQUEST_1 │ │ ├────────────────────────┼─────────────────────────────────┤ │ │ │ │ │ ┌──┼──┐ │ │ │ │ │ │ │ REQUEST_2 │ │ │ │ ├──────────────────────────────┼──┤ │ │ │ │ │ │Resource │ │ │ │ │is busy │ │ {WQ_SLEEP} │ │ │ │ │◄─────────────────────────────┼──┤ │ │ │ wq_ctx, smc_call_ctx │ │ │ │ │ └──┼──┘ │ REQUEST_1 COMPLETE │ │ │◄───────────────────────┼─────────────────────────────────┤ │ │ │ │ │ IRQ │ │ │◄─-------------------------------│ │ │ │ │ │ get_wq_ctx() │ │ ├────────────────────────────────►│ │ │ │ │ │ │ │ │◄────────────────────────────────┤ │ │ wq_ctx, flags, and │ │ │ more_pending │ │ │ │ │ │ │ │ │ wq_resume(smc_call_ctx) │ │ ├────────────────────────────────►│ │ │ │ │ │ │ │ │ REQUEST_2 COMPLETE │ │ │◄────────────────────────────────┤ │ │ │ │ │ │ With the exception of get_wq_ctx(), the other SMC call wq_resume() can return WQ_SLEEP (these nested rounds of WQ_SLEEP are not shown in the above diagram for the sake of simplicity). Therefore, introduce a new do-while loop to handle multiple WQ_SLEEP return values for the same parent SCM call. Request Completion in the above diagram refers to either a success return value (zero) or error (and not SMC_WAITQ_SLEEP) Also add the interrupt handler that wakes up a sleeping SCM call. Signed-off-by: Guru Das Srinagesh <[email protected]> Co-developed-by: Sibi Sankar <[email protected]> Signed-off-by: Sibi Sankar <[email protected]> Reviewed-by: Guru Das Srinagesh <[email protected]> Signed-off-by: Bjorn Andersson <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent afb37e2 commit 6bf3259

File tree

3 files changed

+176
-8
lines changed

3 files changed

+176
-8
lines changed

drivers/firmware/qcom_scm-smc.c

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,97 @@ static void __scm_smc_do_quirk(const struct arm_smccc_args *smc,
5252
} while (res->a0 == QCOM_SCM_INTERRUPTED);
5353
}
5454

55-
static void __scm_smc_do(const struct arm_smccc_args *smc,
56-
struct arm_smccc_res *res, bool atomic)
55+
static void fill_wq_resume_args(struct arm_smccc_args *resume, u32 smc_call_ctx)
5756
{
58-
int retry_count = 0;
57+
memset(resume->args, 0, sizeof(resume->args[0]) * ARRAY_SIZE(resume->args));
58+
59+
resume->args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
60+
ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
61+
SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_RESUME));
62+
63+
resume->args[1] = QCOM_SCM_ARGS(1);
64+
65+
resume->args[2] = smc_call_ctx;
66+
}
67+
68+
int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending)
69+
{
70+
int ret;
71+
struct arm_smccc_res get_wq_res;
72+
struct arm_smccc_args get_wq_ctx = {0};
73+
74+
get_wq_ctx.args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
75+
ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
76+
SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_GET_WQ_CTX));
77+
78+
/* Guaranteed to return only success or error, no WAITQ_* */
79+
__scm_smc_do_quirk(&get_wq_ctx, &get_wq_res);
80+
ret = get_wq_res.a0;
81+
if (ret)
82+
return ret;
83+
84+
*wq_ctx = get_wq_res.a1;
85+
*flags = get_wq_res.a2;
86+
*more_pending = get_wq_res.a3;
87+
88+
return 0;
89+
}
90+
91+
static int __scm_smc_do_quirk_handle_waitq(struct device *dev, struct arm_smccc_args *waitq,
92+
struct arm_smccc_res *res)
93+
{
94+
int ret;
95+
u32 wq_ctx, smc_call_ctx;
96+
struct arm_smccc_args resume;
97+
struct arm_smccc_args *smc = waitq;
98+
99+
do {
100+
__scm_smc_do_quirk(smc, res);
101+
102+
if (res->a0 == QCOM_SCM_WAITQ_SLEEP) {
103+
wq_ctx = res->a1;
104+
smc_call_ctx = res->a2;
105+
106+
ret = qcom_scm_wait_for_wq_completion(wq_ctx);
107+
if (ret)
108+
return ret;
109+
110+
fill_wq_resume_args(&resume, smc_call_ctx);
111+
smc = &resume;
112+
}
113+
} while (res->a0 == QCOM_SCM_WAITQ_SLEEP);
114+
115+
return 0;
116+
}
117+
118+
static int __scm_smc_do(struct device *dev, struct arm_smccc_args *smc,
119+
struct arm_smccc_res *res, bool atomic)
120+
{
121+
int ret, retry_count = 0;
59122

60123
if (atomic) {
61124
__scm_smc_do_quirk(smc, res);
62-
return;
125+
return 0;
63126
}
64127

65128
do {
66129
mutex_lock(&qcom_scm_lock);
67130

68-
__scm_smc_do_quirk(smc, res);
131+
ret = __scm_smc_do_quirk_handle_waitq(dev, smc, res);
69132

70133
mutex_unlock(&qcom_scm_lock);
71134

135+
if (ret)
136+
return ret;
137+
72138
if (res->a0 == QCOM_SCM_V2_EBUSY) {
73139
if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY)
74140
break;
75141
msleep(QCOM_SCM_EBUSY_WAIT_MS);
76142
}
77143
} while (res->a0 == QCOM_SCM_V2_EBUSY);
144+
145+
return 0;
78146
}
79147

80148

@@ -83,7 +151,7 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
83151
struct qcom_scm_res *res, bool atomic)
84152
{
85153
int arglen = desc->arginfo & 0xf;
86-
int i;
154+
int i, ret;
87155
dma_addr_t args_phys = 0;
88156
void *args_virt = NULL;
89157
size_t alloc_len;
@@ -135,13 +203,17 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
135203
smc.args[SCM_SMC_LAST_REG_IDX] = args_phys;
136204
}
137205

138-
__scm_smc_do(&smc, &smc_res, atomic);
206+
/* ret error check follows after args_virt cleanup*/
207+
ret = __scm_smc_do(dev, &smc, &smc_res, atomic);
139208

140209
if (args_virt) {
141210
dma_unmap_single(dev, args_phys, alloc_len, DMA_TO_DEVICE);
142211
kfree(args_virt);
143212
}
144213

214+
if (ret)
215+
return ret;
216+
145217
if (res) {
146218
res->result[0] = smc_res.a1;
147219
res->result[1] = smc_res.a2;

drivers/firmware/qcom_scm.c

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
#include <linux/platform_device.h>
66
#include <linux/init.h>
7+
#include <linux/interrupt.h>
8+
#include <linux/completion.h>
79
#include <linux/cpumask.h>
810
#include <linux/export.h>
911
#include <linux/dma-mapping.h>
@@ -13,6 +15,7 @@
1315
#include <linux/qcom_scm.h>
1416
#include <linux/of.h>
1517
#include <linux/of_address.h>
18+
#include <linux/of_irq.h>
1619
#include <linux/of_platform.h>
1720
#include <linux/clk.h>
1821
#include <linux/reset-controller.h>
@@ -33,6 +36,7 @@ struct qcom_scm {
3336
struct clk *iface_clk;
3437
struct clk *bus_clk;
3538
struct icc_path *path;
39+
struct completion waitq_comp;
3640
struct reset_controller_dev reset;
3741

3842
/* control access to the interconnect path */
@@ -63,6 +67,9 @@ static const u8 qcom_scm_cpu_warm_bits[QCOM_SCM_BOOT_MAX_CPUS] = {
6367
BIT(2), BIT(1), BIT(4), BIT(6)
6468
};
6569

70+
#define QCOM_SMC_WAITQ_FLAG_WAKE_ONE BIT(0)
71+
#define QCOM_SMC_WAITQ_FLAG_WAKE_ALL BIT(1)
72+
6673
static const char * const qcom_scm_convention_names[] = {
6774
[SMC_CONVENTION_UNKNOWN] = "unknown",
6875
[SMC_CONVENTION_ARM_32] = "smc arm 32",
@@ -1325,11 +1332,79 @@ bool qcom_scm_is_available(void)
13251332
}
13261333
EXPORT_SYMBOL(qcom_scm_is_available);
13271334

1335+
static int qcom_scm_assert_valid_wq_ctx(u32 wq_ctx)
1336+
{
1337+
/* FW currently only supports a single wq_ctx (zero).
1338+
* TODO: Update this logic to include dynamic allocation and lookup of
1339+
* completion structs when FW supports more wq_ctx values.
1340+
*/
1341+
if (wq_ctx != 0) {
1342+
dev_err(__scm->dev, "Firmware unexpectedly passed non-zero wq_ctx\n");
1343+
return -EINVAL;
1344+
}
1345+
1346+
return 0;
1347+
}
1348+
1349+
int qcom_scm_wait_for_wq_completion(u32 wq_ctx)
1350+
{
1351+
int ret;
1352+
1353+
ret = qcom_scm_assert_valid_wq_ctx(wq_ctx);
1354+
if (ret)
1355+
return ret;
1356+
1357+
wait_for_completion(&__scm->waitq_comp);
1358+
1359+
return 0;
1360+
}
1361+
1362+
static int qcom_scm_waitq_wakeup(struct qcom_scm *scm, unsigned int wq_ctx)
1363+
{
1364+
int ret;
1365+
1366+
ret = qcom_scm_assert_valid_wq_ctx(wq_ctx);
1367+
if (ret)
1368+
return ret;
1369+
1370+
complete(&__scm->waitq_comp);
1371+
1372+
return 0;
1373+
}
1374+
1375+
static irqreturn_t qcom_scm_irq_handler(int irq, void *data)
1376+
{
1377+
int ret;
1378+
struct qcom_scm *scm = data;
1379+
u32 wq_ctx, flags, more_pending = 0;
1380+
1381+
do {
1382+
ret = scm_get_wq_ctx(&wq_ctx, &flags, &more_pending);
1383+
if (ret) {
1384+
dev_err(scm->dev, "GET_WQ_CTX SMC call failed: %d\n", ret);
1385+
goto out;
1386+
}
1387+
1388+
if (flags != QCOM_SMC_WAITQ_FLAG_WAKE_ONE &&
1389+
flags != QCOM_SMC_WAITQ_FLAG_WAKE_ALL) {
1390+
dev_err(scm->dev, "Invalid flags found for wq_ctx: %u\n", flags);
1391+
goto out;
1392+
}
1393+
1394+
ret = qcom_scm_waitq_wakeup(scm, wq_ctx);
1395+
if (ret)
1396+
goto out;
1397+
} while (more_pending);
1398+
1399+
out:
1400+
return IRQ_HANDLED;
1401+
}
1402+
13281403
static int qcom_scm_probe(struct platform_device *pdev)
13291404
{
13301405
struct qcom_scm *scm;
13311406
unsigned long clks;
1332-
int ret;
1407+
int irq, ret;
13331408

13341409
scm = devm_kzalloc(&pdev->dev, sizeof(*scm), GFP_KERNEL);
13351410
if (!scm)
@@ -1402,6 +1477,19 @@ static int qcom_scm_probe(struct platform_device *pdev)
14021477
__scm = scm;
14031478
__scm->dev = &pdev->dev;
14041479

1480+
init_completion(&__scm->waitq_comp);
1481+
1482+
irq = platform_get_irq(pdev, 0);
1483+
if (irq < 0) {
1484+
if (irq != -ENXIO)
1485+
return irq;
1486+
} else {
1487+
ret = devm_request_threaded_irq(__scm->dev, irq, NULL, qcom_scm_irq_handler,
1488+
IRQF_ONESHOT, "qcom-scm", __scm);
1489+
if (ret < 0)
1490+
return dev_err_probe(scm->dev, ret, "Failed to request qcom-scm irq\n");
1491+
}
1492+
14051493
__get_convention();
14061494

14071495
/*

drivers/firmware/qcom_scm.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ struct qcom_scm_res {
6060
u64 result[MAX_QCOM_SCM_RETS];
6161
};
6262

63+
int qcom_scm_wait_for_wq_completion(u32 wq_ctx);
64+
int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending);
65+
6366
#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF))
6467
extern int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
6568
enum qcom_scm_convention qcom_convention,
@@ -129,6 +132,10 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
129132
#define QCOM_SCM_SMMU_CONFIG_ERRATA1 0x03
130133
#define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL 0x02
131134

135+
#define QCOM_SCM_SVC_WAITQ 0x24
136+
#define QCOM_SCM_WAITQ_RESUME 0x02
137+
#define QCOM_SCM_WAITQ_GET_WQ_CTX 0x03
138+
132139
/* common error codes */
133140
#define QCOM_SCM_V2_EBUSY -12
134141
#define QCOM_SCM_ENOMEM -5
@@ -137,6 +144,7 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
137144
#define QCOM_SCM_EINVAL_ARG -2
138145
#define QCOM_SCM_ERROR -1
139146
#define QCOM_SCM_INTERRUPTED 1
147+
#define QCOM_SCM_WAITQ_SLEEP 2
140148

141149
static inline int qcom_scm_remap_error(int err)
142150
{

0 commit comments

Comments
 (0)