diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h index b07fbaa091a06..b0e7e336d363e 100644 --- a/drivers/remoteproc/qcom_common.h +++ b/drivers/remoteproc/qcom_common.h @@ -68,6 +68,7 @@ struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, int ssctl_instance); void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon); bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon); +bool qcom_sysmon_shutdown_irq_state(struct qcom_sysmon *sysmon); #else static inline struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, const char *name, @@ -84,6 +85,11 @@ static inline bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon) { return false; } + +static inline bool qcom_sysmon_shutdown_irq_state(struct qcom_sysmon *sysmon) +{ + return false; +} #endif #endif diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c index 52247c17c38ac..99e4384f1b0e6 100644 --- a/drivers/remoteproc/qcom_q6v5.c +++ b/drivers/remoteproc/qcom_q6v5.c @@ -37,6 +37,40 @@ static int q6v5_load_state_toggle(struct qcom_q6v5 *q6v5, bool enable) return ret; } +static void q6v5_handover_irq_enable(struct qcom_q6v5 *q6v5) +{ + unsigned long flags; + bool enable = false; + + spin_lock_irqsave(&q6v5->handover_lock, flags); + if (!q6v5->handover_irq_enabled) { + q6v5->handover_irq_enabled = true; + enable = true; + } + spin_unlock_irqrestore(&q6v5->handover_lock, flags); + + if (enable) + enable_irq(q6v5->handover_irq); +} + +static void q6v5_handover_irq_disable(struct qcom_q6v5 *q6v5, bool sync) +{ + unsigned long flags; + bool disable = false; + + spin_lock_irqsave(&q6v5->handover_lock, flags); + if (q6v5->handover_irq_enabled) { + q6v5->handover_irq_enabled = false; + disable = true; + } + spin_unlock_irqrestore(&q6v5->handover_lock, flags); + + if (disable) + disable_irq_nosync(q6v5->handover_irq); + if (sync) + synchronize_irq(q6v5->handover_irq); +} + /** * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start * @q6v5: reference to qcom_q6v5 context to be reinitialized @@ -65,7 +99,7 @@ int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5) q6v5->running = true; q6v5->handover_issued = false; - enable_irq(q6v5->handover_irq); + q6v5_handover_irq_enable(q6v5); return 0; } @@ -79,7 +113,8 @@ EXPORT_SYMBOL_GPL(qcom_q6v5_prepare); */ int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5) { - disable_irq(q6v5->handover_irq); + q6v5_handover_irq_disable(q6v5, true); + q6v5_load_state_toggle(q6v5, false); /* Disable interconnect vote, in case handover never happened */ @@ -165,18 +200,15 @@ static irqreturn_t q6v5_handover_interrupt(int irq, void *data) { struct qcom_q6v5 *q6v5 = data; - if (q6v5->handover_issued) { - dev_err(q6v5->dev, "Handover signaled, but it already happened\n"); - return IRQ_HANDLED; - } + q6v5->handover_issued = true; + + q6v5_handover_irq_disable(q6v5, false); if (q6v5->handover) q6v5->handover(q6v5); icc_set_bw(q6v5->path, 0, 0); - q6v5->handover_issued = true; - return IRQ_HANDLED; } @@ -203,7 +235,8 @@ int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon) q6v5->running = false; /* Don't perform SMP2P dance if remote isn't running */ - if (q6v5->rproc->state != RPROC_RUNNING || qcom_sysmon_shutdown_acked(sysmon)) + if ((q6v5->rproc->state != RPROC_RUNNING && q6v5->rproc->state != RPROC_ATTACHED) || + qcom_sysmon_shutdown_acked(sysmon)) return 0; qcom_smem_state_update_bits(q6v5->state, @@ -325,6 +358,8 @@ int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev, q6v5->crash_reason = crash_reason; q6v5->handover = handover; + spin_lock_init(&q6v5->handover_lock); + init_completion(&q6v5->start_done); init_completion(&q6v5->stop_done); @@ -373,13 +408,13 @@ int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev, ret = devm_request_threaded_irq(&pdev->dev, q6v5->handover_irq, NULL, q6v5_handover_interrupt, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, + IRQF_TRIGGER_RISING | IRQF_ONESHOT | + IRQF_NO_AUTOEN, "q6v5 handover", q6v5); if (ret) { dev_err(&pdev->dev, "failed to acquire handover IRQ\n"); return ret; } - disable_irq(q6v5->handover_irq); q6v5->stop_irq = platform_get_irq_byname(pdev, "stop-ack"); if (q6v5->stop_irq < 0) diff --git a/drivers/remoteproc/qcom_q6v5.h b/drivers/remoteproc/qcom_q6v5.h index 5025ffc4dbe80..ad03f1907029a 100644 --- a/drivers/remoteproc/qcom_q6v5.h +++ b/drivers/remoteproc/qcom_q6v5.h @@ -5,6 +5,7 @@ #include #include +#include #include struct icc_path; @@ -32,6 +33,9 @@ struct qcom_q6v5 { int stop_irq; int pong_irq; + /* Protects handover_irq_enabled against stop/handover races. */ + spinlock_t handover_lock; + bool handover_irq_enabled; bool handover_issued; struct completion start_done; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 3a30cbd3ce2b6..0ca373a294625 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -561,15 +561,17 @@ static void qcom_pas_coredump(struct rproc *rproc) static int qcom_pas_attach(struct rproc *rproc) { - int ret; struct qcom_pas *pas = rproc->priv; bool ready_state; bool crash_state; + bool stop_state; + int ret; + + pas->q6v5.handover_issued = true; pas->q6v5.running = true; ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, IRQCHIP_STATE_LINE_LEVEL, &crash_state); - if (ret) goto disable_running; @@ -580,9 +582,18 @@ static int qcom_pas_attach(struct rproc *rproc) goto disable_running; } + ret = irq_get_irqchip_state(pas->q6v5.stop_irq, + IRQCHIP_STATE_LINE_LEVEL, &stop_state); + if (ret) + goto disable_running; + + if (stop_state || qcom_sysmon_shutdown_irq_state(pas->sysmon)) { + dev_info(pas->dev, "Subsystem found stop state set. Falling back to start.\n"); + goto unroll_attach; + } + ret = irq_get_irqchip_state(pas->q6v5.ready_irq, IRQCHIP_STATE_LINE_LEVEL, &ready_state); - if (ret) goto disable_running; @@ -593,23 +604,14 @@ static int qcom_pas_attach(struct rproc *rproc) * start the remoteproc. */ dev_err(pas->dev, "Failed to get subsystem ready interrupt\n"); - pas->rproc->state = RPROC_OFFLINE; - ret = -EINVAL; - goto disable_running; - } - - ret = qcom_q6v5_ping_subsystem(&pas->q6v5); - - if (ret) { - dev_err(pas->dev, "Failed to ping subsystem, assuming device crashed\n"); - rproc_report_crash(rproc, RPROC_FATAL_ERROR); - goto disable_running; + goto unroll_attach; } - pas->q6v5.handover_issued = true; - return 0; +unroll_attach: + pas->rproc->state = RPROC_OFFLINE; + ret = -EINVAL; disable_running: pas->q6v5.running = false; @@ -637,6 +639,7 @@ static const struct rproc_ops qcom_pas_minidump_ops = { .load = qcom_pas_load, .panic = qcom_pas_panic, .coredump = qcom_pas_minidump, + .attach = qcom_pas_attach, }; static int qcom_pas_init_clock(struct qcom_pas *pas) @@ -1020,7 +1023,6 @@ static int qcom_pas_probe(struct platform_device *pdev) pas->rproc->state = RPROC_DETACHED; } - ret = qcom_pas_setup_tmd(pas); if (ret) goto remove_ssr_sysmon; diff --git a/drivers/remoteproc/qcom_sysmon.c b/drivers/remoteproc/qcom_sysmon.c index 913e3b750a869..a0830a48b1f40 100644 --- a/drivers/remoteproc/qcom_sysmon.c +++ b/drivers/remoteproc/qcom_sysmon.c @@ -736,6 +736,25 @@ bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon) } EXPORT_SYMBOL_GPL(qcom_sysmon_shutdown_acked); +bool qcom_sysmon_shutdown_irq_state(struct qcom_sysmon *sysmon) +{ + bool shutdown_state; + int ret; + + if (!sysmon) + return false; + + ret = irq_get_irqchip_state(sysmon->shutdown_irq, + IRQCHIP_STATE_LINE_LEVEL, &shutdown_state); + if (ret) { + dev_warn(sysmon->dev, "failed to get shutdown_state: %d\n", ret); + return false; + } + + return shutdown_state; +} +EXPORT_SYMBOL_GPL(qcom_sysmon_shutdown_irq_state); + /** * sysmon_probe() - probe sys_mon channel * @rpdev: rpmsg device handle