diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,nord-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,nord-pas.yaml index e90d2953ba692..58d88cb07e525 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,nord-pas.yaml +++ b/Documentation/devicetree/bindings/remoteproc/qcom,nord-pas.yaml @@ -17,6 +17,8 @@ properties: compatible: enum: - qcom,nord-adsp-pas + - qcom,nord-adsp1-pas + - qcom,nord-adsp2-pas - qcom,nord-cdsp0-pas - qcom,nord-cdsp1-pas - qcom,nord-cdsp2-pas @@ -89,6 +91,19 @@ properties: maxItems: 1 description: The names of the state bits used for SMP2P output + qcom,cluster-root: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Reference to the Peripheral Authentication Service instance that owns the + resources shared across this DSP's cluster. HPASS shares clock/reset/NoC + resources between its ADSP0/1/2 QDSP6 instances, and the owning instance + (ADSP0) has to boot first to initialize them before ADSP1/ADSP2 can cold + boot. Every member of a cluster carries this property, the owning instance + included, which references itself; instances whose property references the + same node form one cluster. A cluster is always brought down as a single + unit; any member crashing or being stopped forces every other member to + crash or stop with it. + required: - compatible - reg @@ -101,6 +116,8 @@ allOf: compatible: enum: - qcom,nord-adsp-pas + - qcom,nord-adsp1-pas + - qcom,nord-adsp2-pas then: properties: power-domains: diff --git a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi index 0d1c561b1f9e0..cdb48310a54a5 100644 --- a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi +++ b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi @@ -577,6 +577,28 @@ "mx"; }; +&remoteproc_adsp1 { + clocks = <&rpmhcc RPMH_CXO_CLK>; + clock-names = "xo"; + interconnects = <&hpass_ag_noc MASTER_HPASS_PROC_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + power-domains = <&rpmhpd RPMHPD_CX>, + <&rpmhpd RPMHPD_MX>; + power-domain-names = "cx", + "mx"; +}; + +&remoteproc_adsp2 { + clocks = <&rpmhcc RPMH_CXO_CLK>; + clock-names = "xo"; + interconnects = <&hpass_ag_noc MASTER_HPASS_PROC_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + power-domains = <&rpmhpd RPMHPD_CX>, + <&rpmhpd RPMHPD_MX>; + power-domain-names = "cx", + "mx"; +}; + &remoteproc_cdsp0 { clocks = <&rpmhcc RPMH_CXO_CLK>; clock-names = "xo"; diff --git a/arch/arm64/boot/dts/qcom/nord-ride-sx.dts b/arch/arm64/boot/dts/qcom/nord-ride-sx.dts index b8839e1b86421..922749cc7ad2a 100644 --- a/arch/arm64/boot/dts/qcom/nord-ride-sx.dts +++ b/arch/arm64/boot/dts/qcom/nord-ride-sx.dts @@ -594,6 +594,20 @@ status = "okay"; }; +&remoteproc_adsp1 { + firmware-name = "qcom/nord/adsp1.mbn", + "qcom/nord/adsp1_dtbs.elf"; + + status = "okay"; +}; + +&remoteproc_adsp2 { + firmware-name = "qcom/nord/adsp2.mbn", + "qcom/nord/adsp2_dtbs.elf"; + + status = "okay"; +}; + &remoteproc_cdsp0 { firmware-name = "qcom/nord/cdsp.mbn", "qcom/nord/cdsp_dtbs.elf"; diff --git a/arch/arm64/boot/dts/qcom/nord-rrd.dts b/arch/arm64/boot/dts/qcom/nord-rrd.dts index ddc534e2214b4..12047eaf081b1 100644 --- a/arch/arm64/boot/dts/qcom/nord-rrd.dts +++ b/arch/arm64/boot/dts/qcom/nord-rrd.dts @@ -59,6 +59,14 @@ regulator-max-microvolt = <3300000>; regulator-always-on; }; + + vreg_hub_1p2: regulator-hub-1p2 { + compatible = "regulator-fixed"; + regulator-name = "vreg_hub_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; }; &apps_rsc { @@ -519,6 +527,14 @@ vdd1v8-supply = <&vreg_l3l_1p8>; vdd3v3-supply = <&vreg_pre_reg_3p3>; }; + + eusb2_redriver_usb1: redriver@4f { + compatible = "nxp,ptn3222"; + reg = <0x4f>; + #phy-cells = <0>; + vdd1v8-supply = <&vreg_l3l_1p8>; + vdd3v3-supply = <&vreg_pre_reg_3p3>; + }; }; &qupv3_0 { @@ -546,6 +562,20 @@ status = "okay"; }; +&remoteproc_adsp1 { + firmware-name = "qcom/nord/adsp1.mbn", + "qcom/nord/adsp1_dtbs.elf"; + + status = "okay"; +}; + +&remoteproc_adsp2 { + firmware-name = "qcom/nord/adsp2.mbn", + "qcom/nord/adsp2_dtbs.elf"; + + status = "okay"; +}; + &remoteproc_cdsp0 { firmware-name = "qcom/nord/cdsp.mbn", "qcom/nord/cdsp_dtbs.elf"; @@ -626,3 +656,44 @@ status = "okay"; }; + +&usb_1 { + dr_mode = "host"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + hub_hs: hub@1 { + compatible = "usb5e3,610"; + reg = <1>; + reset-gpios = <&tlmm 170 GPIO_ACTIVE_LOW>; + vdd-supply = <&vreg_pre_reg_3p3>; + peer-hub = <&hub_ss>; + }; + + hub_ss: hub@2 { + compatible = "usb5e3,625"; + reg = <2>; + vdd-supply = <&vreg_pre_reg_3p3>; + vdd12-supply = <&vreg_hub_1p2>; + peer-hub = <&hub_hs>; + }; +}; + +&usb_1_hsphy { + vdd-supply = <&vreg_l1e_0p9>; + vdda12-supply = <&vreg_l2i_1p2>; + + phys = <&eusb2_redriver_usb1>; + + status = "okay"; +}; + +&usb_1_qmpphy { + vdda-phy-supply = <&vreg_l1h_0p9>; + vdda-pll-supply = <&vreg_l2h_1p2>; + + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/nord.dtsi b/arch/arm64/boot/dts/qcom/nord.dtsi index 3d472d2434b64..9ba96d479a39d 100644 --- a/arch/arm64/boot/dts/qcom/nord.dtsi +++ b/arch/arm64/boot/dts/qcom/nord.dtsi @@ -991,6 +991,52 @@ }; }; + smp2p-adsp1 { + compatible = "qcom,smp2p"; + interrupts-extended = <&ipcc IPCC_MPROC_ADSP1 + IPCC_MPROC_SIGNAL_SMP2P + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_MPROC_ADSP1 + IPCC_MPROC_SIGNAL_SMP2P>; + qcom,smem = <617>, <616>; + qcom,local-pid = <0>; + qcom,remote-pid = <66>; + + smp2p_adsp1_out: master-kernel { + qcom,entry-name = "master-kernel"; + #qcom,smem-state-cells = <1>; + }; + + smp2p_adsp1_in: slave-kernel { + qcom,entry-name = "slave-kernel"; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; + + smp2p-adsp2 { + compatible = "qcom,smp2p"; + interrupts-extended = <&ipcc IPCC_MPROC_ADSP2 + IPCC_MPROC_SIGNAL_SMP2P + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_MPROC_ADSP2 + IPCC_MPROC_SIGNAL_SMP2P>; + qcom,smem = <617>, <616>; + qcom,local-pid = <0>; + qcom,remote-pid = <130>; + + smp2p_adsp2_out: master-kernel { + qcom,entry-name = "master-kernel"; + #qcom,smem-state-cells = <1>; + }; + + smp2p_adsp2_in: slave-kernel { + qcom,entry-name = "slave-kernel"; + interrupt-controller; + #interrupt-cells = <2>; + }; + }; + smp2p-cdsp0 { compatible = "qcom,smp2p"; qcom,smem = <94>, <432>; @@ -1798,6 +1844,8 @@ memory-region = <&hpass_dsp0_mem>, <&hpass_dsp0_dtb_mem>; + qcom,cluster-root = <&remoteproc_adsp>; + qcom,qmp = <&aoss_qmp>; qcom,smem-states = <&smp2p_adsp_out 0>; @@ -1865,6 +1913,184 @@ }; }; + remoteproc_adsp1: remoteproc@6000000 { + compatible = "qcom,nord-adsp1-pas"; + reg = <0x0 0x06000000 0x0 0x10000>; + + interrupts-extended = <&intc GIC_SPI 155 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp1_in 0 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp1_in 1 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp1_in 2 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp1_in 3 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp1_in 7 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "wdog", + "fatal", + "ready", + "handover", + "stop-ack", + "shutdown-ack"; + + memory-region = <&hpass_dsp1_mem>, + <&hpass_dsp1_dtb_mem>; + + qcom,cluster-root = <&remoteproc_adsp>; + + qcom,qmp = <&aoss_qmp>; + + qcom,smem-states = <&smp2p_adsp1_out 0>; + qcom,smem-state-names = "stop"; + + status = "disabled"; + + glink-edge { + interrupts-extended = <&ipcc IPCC_MPROC_ADSP1 + IPCC_MPROC_SIGNAL_GLINK_QMP + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_MPROC_ADSP1 + IPCC_MPROC_SIGNAL_GLINK_QMP>; + qcom,remote-pid = <66>; + label = "adsp1"; + + fastrpc { + compatible = "qcom,nord-fastrpc", + "qcom,kaanapali-fastrpc"; + qcom,glink-channels = "fastrpcglink-apps-dsp"; + label = "adsp1"; + #address-cells = <1>; + #size-cells = <0>; + + compute-cb@11 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <11>; + iommus = <&apps_smmu_0 0x0a0b 0x0040>, + <&apps_smmu_0 0x0a4b 0x0040>, + <&apps_smmu_0 0x0a6b 0x0000>, + <&apps_smmu_0 0x0a8b 0x0000>; + dma-coherent; + }; + + compute-cb@12 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <12>; + iommus = <&apps_smmu_0 0x0a0c 0x0080>, + <&apps_smmu_0 0x0a4c 0x0020>, + <&apps_smmu_0 0x0a6c 0x0020>, + <&apps_smmu_0 0x0a8c 0x0080>; + dma-coherent; + }; + + compute-cb@13 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <13>; + iommus = <&apps_smmu_0 0x0a0d 0x0080>, + <&apps_smmu_0 0x0a4d 0x0020>, + <&apps_smmu_0 0x0a6d 0x0020>, + <&apps_smmu_0 0x0a8d 0x0080>; + dma-coherent; + }; + + compute-cb@14 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <14>; + iommus = <&apps_smmu_0 0x0a0e 0x0080>, + <&apps_smmu_0 0x0a4e 0x0020>, + <&apps_smmu_0 0x0a6e 0x0020>, + <&apps_smmu_0 0x0a8e 0x0080>; + dma-coherent; + }; + }; + }; + }; + + remoteproc_adsp2: remoteproc@6100000 { + compatible = "qcom,nord-adsp2-pas"; + reg = <0x0 0x06100000 0x0 0x10000>; + + interrupts-extended = <&intc GIC_SPI 178 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp2_in 0 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp2_in 1 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp2_in 2 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp2_in 3 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp2_in 7 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "wdog", + "fatal", + "ready", + "handover", + "stop-ack", + "shutdown-ack"; + + memory-region = <&hpass_dsp2_mem>, + <&hpass_dsp2_dtb_mem>; + + qcom,cluster-root = <&remoteproc_adsp>; + + qcom,qmp = <&aoss_qmp>; + + qcom,smem-states = <&smp2p_adsp2_out 0>; + qcom,smem-state-names = "stop"; + + status = "disabled"; + + glink-edge { + interrupts-extended = <&ipcc IPCC_MPROC_ADSP2 + IPCC_MPROC_SIGNAL_GLINK_QMP + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_MPROC_ADSP2 + IPCC_MPROC_SIGNAL_GLINK_QMP>; + qcom,remote-pid = <130>; + label = "adsp2"; + + fastrpc { + compatible = "qcom,nord-fastrpc", + "qcom,kaanapali-fastrpc"; + qcom,glink-channels = "fastrpcglink-apps-dsp"; + label = "adsp2"; + #address-cells = <1>; + #size-cells = <0>; + + compute-cb@3 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <3>; + iommus = <&apps_smmu_0 0x0a33 0x0040>, + <&apps_smmu_0 0x0a53 0x0000>, + <&apps_smmu_0 0x0a73 0x0040>, + <&apps_smmu_0 0x0a93 0x0000>; + dma-coherent; + }; + + compute-cb@4 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <4>; + iommus = <&apps_smmu_0 0x0a34 0x0040>, + <&apps_smmu_0 0x0a54 0x0000>, + <&apps_smmu_0 0x0a74 0x0040>, + <&apps_smmu_0 0x0a94 0x0000>; + dma-coherent; + }; + + compute-cb@5 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <5>; + iommus = <&apps_smmu_0 0x0a35 0x0000>, + <&apps_smmu_0 0x0a55 0x0020>, + <&apps_smmu_0 0x0a75 0x0020>, + <&apps_smmu_0 0x0a95 0x0000>; + dma-coherent; + }; + + compute-cb@6 { + compatible = "qcom,fastrpc-compute-cb"; + reg = <6>; + iommus = <&apps_smmu_0 0x0a36 0x0000>, + <&apps_smmu_0 0x0a56 0x0020>, + <&apps_smmu_0 0x0a76 0x0020>, + <&apps_smmu_0 0x0a96 0x0000>; + dma-coherent; + }; + }; + }; + }; + ipcc: mailbox@9007000 { compatible = "qcom,nord-ipcc", "qcom,ipcc"; diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c index 1f406bfcb1f59..ec151cfd853a9 100644 --- a/drivers/remoteproc/qcom_common.c +++ b/drivers/remoteproc/qcom_common.c @@ -217,7 +217,8 @@ static int glink_subdev_start(struct rproc_subdev *subdev) { struct qcom_rproc_glink *glink = to_glink_subdev(subdev); - glink->edge = qcom_glink_smem_register(glink->dev, glink->node); + glink->edge = qcom_glink_smem_register(&glink->rproc->dev, glink->node, + glink->rproc->cluster); return PTR_ERR_OR_ZERO(glink->edge); } @@ -237,7 +238,7 @@ static void glink_subdev_unprepare(struct rproc_subdev *subdev) { struct qcom_rproc_glink *glink = to_glink_subdev(subdev); - qcom_glink_ssr_notify(glink->ssr_name); + qcom_glink_ssr_notify(glink->ssr_name, glink->rproc->cluster); } /** @@ -259,7 +260,7 @@ void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink, if (!glink->ssr_name) return; - glink->dev = dev; + glink->rproc = rproc; glink->subdev.start = glink_subdev_start; glink->subdev.stop = glink_subdev_stop; glink->subdev.unprepare = glink_subdev_unprepare; diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h index b0e7e336d363e..7136494cbfbaa 100644 --- a/drivers/remoteproc/qcom_common.h +++ b/drivers/remoteproc/qcom_common.h @@ -14,7 +14,7 @@ struct qcom_rproc_glink { const char *ssr_name; - struct device *dev; + struct rproc *rproc; struct device_node *node; struct qcom_glink_smem *edge; }; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 37084a038c519..3010ae256db8b 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "qcom_common.h" #include "qcom_pil_info.h" @@ -66,6 +67,8 @@ struct qcom_pas_data { bool needs_tzmem; }; +struct qcom_pas_cluster; + struct qcom_pas { struct device *dev; struct rproc *rproc; @@ -124,8 +127,449 @@ struct qcom_pas { struct qcom_scm_pas_context *dtb_pas_ctx; struct qmi_tmd_client *tmd_inst; + + struct qcom_pas_cluster *cluster; + struct list_head cluster_node; + struct work_struct stop_work; + bool is_cluster_root; + bool in_cluster_stop; +}; + +/** + * struct qcom_pas_cluster - state shared by clustered PAS instances + * @node: device_node of the cluster root, the key into the global list + * @list: linkage in qcom_pas_cluster_list + * @lock: protects @members and @root + * @members: list of struct qcom_pas, linked via cluster_node + * @root: the member whose "qcom,cluster-root" points at itself + * @root_booted: signaled once @root has started, cleared when it goes down + * @cascade_work: deferred work that restarts the cluster after a crash + * @cascade_origin: member whose stop or crash triggered the current round + * @cascade_crashed: true if @cascade_origin crashed, rather than being stopped + * @stop_in_progress: re-entrancy guard: a coordinated stop is in flight + * @stop_pending: participants still owing a phase-1 graceful-ack attempt + * @stop_barrier: released once @stop_pending reaches 0 + * @stop_done_pending: participants still owing a phase-2 hardware power-off + * @refcount: number of members currently attached to this cluster + * + * PAS instances whose "qcom,cluster-root" phandle points at the same node + * share one of these. + */ +struct qcom_pas_cluster { + struct device_node *node; + struct list_head list; + + struct mutex lock; + struct list_head members; + struct qcom_pas *root; + struct completion root_booted; + + struct work_struct cascade_work; + struct qcom_pas *cascade_origin; + bool cascade_crashed; + bool stop_in_progress; + + int stop_pending; + struct completion stop_barrier; + int stop_done_pending; + + int refcount; }; +/* + * How long a dependent member waits for its root to finish booting before + * giving up, e.g. when it is racing the root through the crash-restart + * cascade. + */ +#define QCOM_PAS_CLUSTER_ROOT_BOOT_TIMEOUT (1 * HZ) + +/* + * How long a member waits at the phase-1 barrier for the rest of the cluster, + * in case a participant never reaches it at all (e.g. rproc_stop() bails out + * before calling ops->stop). + */ +#define QCOM_PAS_CLUSTER_STOP_TIMEOUT (20 * HZ) + +static LIST_HEAD(qcom_pas_cluster_list); +static DEFINE_MUTEX(qcom_pas_cluster_list_lock); + +static void qcom_pas_stop_work_fn(struct work_struct *work) +{ + struct qcom_pas *pas = container_of(work, struct qcom_pas, stop_work); + + rproc_shutdown(pas->rproc); +} + +/** + * qcom_pas_cluster_cascade_work() - restart a crashed cluster, root first + * @work: the cluster's cascade_work + * + * Only ever scheduled from qcom_pas_cluster_stop_complete(), i.e. only after + * every participant has finished its own phase-2 hardware power-off, and only + * when the round that just finished was a crash; explicit stops never + * auto-restart. + */ +static void qcom_pas_cluster_cascade_work(struct work_struct *work) +{ + struct qcom_pas_cluster *cluster = container_of(work, struct qcom_pas_cluster, + cascade_work); + struct qcom_pas *pas, *origin, *root; + + mutex_lock(&cluster->lock); + origin = cluster->cascade_origin; + root = cluster->root; + mutex_unlock(&cluster->lock); + + /* + * Membership is stable here: qcom_pas_cluster_exit() always + * cancel_work_sync()s this work before touching cluster->members, so no + * member can join or leave while this work item is running. + * + * If @origin is itself the root, its own crash-recovery thread + * (rproc_boot_recovery()) is already booting it directly -- never call + * rproc_boot() on @origin from here. The root has to be booted before + * any other member, since a dependent member's qcom_pas_start() blocks + * on cluster->root_booted: were we to boot it first from this + * single-threaded work item, it would wait out its timeout on a root + * boot this same thread has not issued yet. + */ + if (root && root != origin) { + int ret; + + ret = rproc_boot(root->rproc); + if (ret) { + dev_err(root->dev, "failed to restart cluster root: %d\n", ret); + return; + } + } + + list_for_each_entry(pas, &cluster->members, cluster_node) { + int ret; + + if (pas == origin || pas == root) + continue; + + ret = rproc_boot(pas->rproc); + if (ret) + dev_err(pas->dev, "failed to restart cluster sibling: %d\n", ret); + } +} + +static struct qcom_pas_cluster *qcom_pas_cluster_get(struct device_node *node) +{ + struct qcom_pas_cluster *cluster; + + mutex_lock(&qcom_pas_cluster_list_lock); + + list_for_each_entry(cluster, &qcom_pas_cluster_list, list) { + if (cluster->node == node) { + cluster->refcount++; + goto out; + } + } + + cluster = kzalloc(sizeof(*cluster), GFP_KERNEL); + if (!cluster) + goto out; + + cluster->node = of_node_get(node); + mutex_init(&cluster->lock); + INIT_LIST_HEAD(&cluster->members); + init_completion(&cluster->root_booted); + init_completion(&cluster->stop_barrier); + INIT_WORK(&cluster->cascade_work, qcom_pas_cluster_cascade_work); + /* + * Cluster members may be attached to already-running firmware at + * probe, so the root is presumed up until its own qcom_pas_stop() + * reinit_completion()s this the first time it actually goes down. + */ + complete_all(&cluster->root_booted); + cluster->refcount = 1; + list_add_tail(&cluster->list, &qcom_pas_cluster_list); + +out: + mutex_unlock(&qcom_pas_cluster_list_lock); + return cluster; +} + +static void qcom_pas_cluster_put(struct qcom_pas_cluster *cluster) +{ + mutex_lock(&qcom_pas_cluster_list_lock); + if (--cluster->refcount == 0) { + list_del(&cluster->list); + mutex_unlock(&qcom_pas_cluster_list_lock); + of_node_put(cluster->node); + kfree(cluster); + return; + } + mutex_unlock(&qcom_pas_cluster_list_lock); +} + +/** + * qcom_pas_cluster_init() - join the cluster referenced by @np, if any + * @pas: PAS instance being probed + * @np: of_node of @pas's platform device + * + * Devices without a "qcom,cluster-root" property are not part of a cluster; + * @pas->cluster is left NULL and this is a no-op (e.g. cdsp0-3). Cluster + * members all carry the property, the root included, whose phandle points + * back at itself. + * + * Return: 0 on success, negative errno on failure. + */ +static int qcom_pas_cluster_init(struct qcom_pas *pas, struct device_node *np) +{ + struct device_node *root_node; + bool is_root; + + root_node = of_parse_phandle(np, "qcom,cluster-root", 0); + if (!root_node) + return 0; + + is_root = root_node == np; + + /* + * A non-root member is useless without its root: it can never be + * booted, since its boot has to be sequenced after the root's. Reject + * it here rather than at first boot, so that a DT enabling a dependent + * DSP but not the one owning the shared resources fails loudly and + * early. + */ + if (!is_root && !of_device_is_available(root_node)) { + dev_err(pas->dev, "cluster root %pOF is not enabled\n", root_node); + of_node_put(root_node); + return -ENODEV; + } + + pas->cluster = qcom_pas_cluster_get(root_node); + of_node_put(root_node); + if (!pas->cluster) + return -ENOMEM; + + pas->is_cluster_root = is_root; + INIT_WORK(&pas->stop_work, qcom_pas_stop_work_fn); + + mutex_lock(&pas->cluster->lock); + list_add_tail(&pas->cluster_node, &pas->cluster->members); + if (is_root) + pas->cluster->root = pas; + mutex_unlock(&pas->cluster->lock); + + return 0; +} + +static void qcom_pas_cluster_exit(struct qcom_pas *pas) +{ + struct qcom_pas_cluster *cluster = pas->cluster; + + if (!cluster) + return; + + cancel_work_sync(&pas->stop_work); + cancel_work_sync(&cluster->cascade_work); + + mutex_lock(&cluster->lock); + list_del(&pas->cluster_node); + if (cluster->root == pas) + cluster->root = NULL; + mutex_unlock(&cluster->lock); + + qcom_pas_cluster_put(cluster); + pas->cluster = NULL; +} + +/** + * qcom_pas_cluster_wait_for_root() - gate a dependent member's boot on its root + * @pas: the non-root cluster member being started + * + * The cluster root owns the resources its siblings need, and initializes them + * as part of its own boot, so a dependent member can only be started once the + * root is up. + * + * Return: 0 if the root is up, negative errno otherwise. + */ +static int qcom_pas_cluster_wait_for_root(struct qcom_pas *pas) +{ + struct qcom_pas_cluster *cluster = pas->cluster; + struct rproc *root; + + /* + * The root's PAS instance is enabled in DT (checked in + * qcom_pas_cluster_init()) but has not necessarily bound yet, and may + * have unbound again. Without it there is nothing to sequence this + * member's boot against. + * + * Note that nothing refcounts members across unbind, so a root freed + * under a concurrent sibling boot remains unhandled. + */ + mutex_lock(&cluster->lock); + root = cluster->root ? cluster->root->rproc : NULL; + mutex_unlock(&cluster->lock); + + if (!root) { + dev_err(pas->dev, "cluster root not bound\n"); + return -ENODEV; + } + + if (root->state == RPROC_RUNNING || root->state == RPROC_ATTACHED) + return 0; + + if (!wait_for_completion_timeout(&cluster->root_booted, + QCOM_PAS_CLUSTER_ROOT_BOOT_TIMEOUT)) { + dev_err(pas->dev, "cluster root not started\n"); + return -ENODEV; + } + + return 0; +} + +/* + * A member takes part in a coordinated stop if its hardware is still powered: + * either it is running or attached, or it is the crashed member that triggered + * the round, whose rproc->state is RPROC_CRASHED and only becomes + * RPROC_OFFLINE once its ops->stop() has returned. + * + * A sibling that crashes concurrently is deliberately not a participant: it is + * already being torn down by its own recovery, and pulling it into this round + * would leave the two rounds fighting over the same counters. + */ +static bool qcom_pas_cluster_member_stops(struct qcom_pas *member, + struct qcom_pas *origin) +{ + if (member == origin) + return true; + + return member->rproc->state == RPROC_RUNNING || + member->rproc->state == RPROC_ATTACHED; +} + +/** + * qcom_pas_cluster_trigger_stop() - begin a coordinated cluster stop + * @pas: the member that is being stopped or has crashed + * @crashed: true if @pas crashed, rather than being stopped explicitly + * + * Marks every member whose hardware is still powered as a participant of this + * round and fires off each *other* participant's own full stop concurrently + * via its stop_work, instead of one after another. This lets every + * participant's phase-1 graceful-ack attempt (see qcom_pas_stop()) run while + * all of them are still fully powered, so nobody is asking firmware to ack a + * shutdown after a sibling's hardware is already gone. + * + * A no-op if a coordinated stop is already in flight, e.g. when @pas is a + * sibling whose own stop was itself triggered by this same round: @pas is + * already marked as a participant and just goes on to take part in it. + * + * Must not call rproc_shutdown()/rproc_boot() directly from here: this runs + * from inside qcom_pas_stop(), which the remoteproc core calls with @pas's own + * rproc->lock held, and taking a sibling's rproc->lock synchronously from + * within that critical section would risk an ABBA deadlock against a + * concurrent operation on the sibling. + */ +static void qcom_pas_cluster_trigger_stop(struct qcom_pas *pas, bool crashed) +{ + struct qcom_pas_cluster *cluster = pas->cluster; + struct qcom_pas *member; + int active = 0; + + mutex_lock(&cluster->lock); + if (cluster->stop_in_progress) { + mutex_unlock(&cluster->lock); + return; + } + + cluster->stop_in_progress = true; + cluster->cascade_origin = pas; + cluster->cascade_crashed = crashed; + + list_for_each_entry(member, &cluster->members, cluster_node) { + member->in_cluster_stop = qcom_pas_cluster_member_stops(member, pas); + if (member->in_cluster_stop) + active++; + } + + cluster->stop_pending = active; + cluster->stop_done_pending = active; + reinit_completion(&cluster->stop_barrier); + + /* + * Fan out while still holding the lock, so that membership cannot + * change between counting the participants and scheduling them, and so + * that a stop_work running immediately blocks in + * qcom_pas_cluster_stop_barrier() until the counters above are in + * place. schedule_work() does not sleep, so it is safe from here. + */ + list_for_each_entry(member, &cluster->members, cluster_node) { + if (member == pas || !member->in_cluster_stop) + continue; + + schedule_work(&member->stop_work); + } + mutex_unlock(&cluster->lock); +} + +/** + * qcom_pas_cluster_stop_barrier() - wait for the whole cluster's phase-1 ack + * @pas: the member calling this from inside its own qcom_pas_stop() + * + * Blocks this member's own hardware power-off until every other participant + * has also finished its phase-1 graceful-ack attempt (see qcom_pas_stop()). + * Bounded by QCOM_PAS_CLUSTER_STOP_TIMEOUT, proceeding to phase 2 anyway + * rather than hanging forever. + * + * A member that is not a participant of the current round, having already been + * powered off before it started, has no ack to contribute and must not touch + * the counters. + */ +static void qcom_pas_cluster_stop_barrier(struct qcom_pas *pas) +{ + struct qcom_pas_cluster *cluster = pas->cluster; + + mutex_lock(&cluster->lock); + if (!pas->in_cluster_stop) { + mutex_unlock(&cluster->lock); + return; + } + if (--cluster->stop_pending == 0) + complete_all(&cluster->stop_barrier); + mutex_unlock(&cluster->lock); + + if (!wait_for_completion_timeout(&cluster->stop_barrier, + QCOM_PAS_CLUSTER_STOP_TIMEOUT)) + dev_warn(pas->dev, "timed out waiting for cluster stop barrier\n"); +} + +/** + * qcom_pas_cluster_stop_complete() - record this member's phase-2 completion + * @pas: the member calling this from inside its own qcom_pas_stop() + * + * The last participant to call this, i.e. the last to finish powering off its + * own hardware, ends the round and, if it was triggered by a crash, schedules + * the root-first restart cascade. + */ +static void qcom_pas_cluster_stop_complete(struct qcom_pas *pas) +{ + struct qcom_pas_cluster *cluster = pas->cluster; + bool crashed; + + mutex_lock(&cluster->lock); + if (!pas->in_cluster_stop) { + mutex_unlock(&cluster->lock); + return; + } + pas->in_cluster_stop = false; + + if (--cluster->stop_done_pending != 0) { + mutex_unlock(&cluster->lock); + return; + } + cluster->stop_in_progress = false; + crashed = cluster->cascade_crashed; + mutex_unlock(&cluster->lock); + + if (crashed) + schedule_work(&cluster->cascade_work); +} + static void qcom_pas_segment_dump(struct rproc *rproc, struct rproc_dump_segment *segment, void *dest, size_t offset, size_t size) @@ -304,6 +748,12 @@ static int qcom_pas_start(struct rproc *rproc) struct qcom_pas *pas = rproc->priv; int ret; + if (pas->cluster && !pas->is_cluster_root) { + ret = qcom_pas_cluster_wait_for_root(pas); + if (ret) + return ret; + } + ret = qcom_q6v5_prepare(&pas->q6v5); if (ret) return ret; @@ -374,6 +824,9 @@ static int qcom_pas_start(struct rproc *rproc) if (pas->dtb_pas_id) qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); + if (pas->cluster && pas->is_cluster_root) + complete_all(&pas->cluster->root_booted); + /* firmware is used to pass reference from qcom_pas_start(), drop it now */ pas->firmware = NULL; @@ -429,10 +882,24 @@ static int qcom_pas_stop(struct rproc *rproc) int handover; int ret; + if (pas->cluster && pas->is_cluster_root) { + mutex_lock(&pas->cluster->lock); + reinit_completion(&pas->cluster->root_booted); + mutex_unlock(&pas->cluster->lock); + } + + if (pas->cluster) + qcom_pas_cluster_trigger_stop(pas, rproc->state == RPROC_CRASHED); + + /* Phase 1: request and await this member's own graceful ack */ ret = qcom_q6v5_request_stop(&pas->q6v5, pas->sysmon); if (ret == -ETIMEDOUT) dev_err(pas->dev, "timed out on wait\n"); + if (pas->cluster) + qcom_pas_cluster_stop_barrier(pas); + + /* Phase 2: the whole cluster has acked, power the hardware off */ ret = qcom_scm_pas_shutdown(pas->pas_id); if (ret && pas->decrypt_shutdown) ret = qcom_pas_shutdown_poll_decrypt(pas); @@ -463,6 +930,9 @@ static int qcom_pas_stop(struct rproc *rproc) if (pas->smem_host_id) ret = qcom_smem_bust_hwspin_lock_by_host(pas->smem_host_id); + if (pas->cluster) + qcom_pas_cluster_stop_complete(pas); + return ret; } @@ -939,6 +1409,12 @@ static int qcom_pas_probe(struct platform_device *pdev) pas = rproc->priv; pas->dev = &pdev->dev; pas->rproc = rproc; + + ret = qcom_pas_cluster_init(pas, pdev->dev.of_node); + if (ret) + return ret; + rproc->cluster = pas->cluster; + pas->minidump_id = desc->minidump_id; pas->pas_id = desc->pas_id; pas->lite_pas_id = desc->lite_pas_id; @@ -1046,6 +1522,7 @@ static int qcom_pas_probe(struct platform_device *pdev) qcom_pas_unassign_memory_region(pas); free_rproc: device_init_wakeup(pas->dev, false); + qcom_pas_cluster_exit(pas); return ret; } @@ -1059,6 +1536,9 @@ static void qcom_pas_remove(struct platform_device *pdev) rproc_del(pas->rproc); + pas->rproc->cluster = NULL; + qcom_pas_cluster_exit(pas); + qcom_q6v5_deinit(&pas->q6v5); qcom_pas_unassign_memory_region(pas); qcom_remove_glink_subdev(pas->rproc, &pas->glink_subdev); @@ -1552,6 +2032,48 @@ static const struct qcom_pas_data nord_adsp_resource = { .smem_host_id = 2, }; +static const struct qcom_pas_data nord_adsp1_resource = { + .crash_reason_smem = 663, + .firmware_name = "adsp1.mbn", + .dtb_firmware_name = "adsp1_dtb.mbn", + .pas_id = 53, + .dtb_pas_id = 55, + .minidump_id = 21, + .auto_boot = true, + .early_boot = true, + .proxy_pd_names = (char*[]){ + "cx", + "mx", + NULL + }, + .load_state = "adsp1", + .ssr_name = "lpass1", + .sysmon_name = "adsp1", + .ssctl_id = 0x1d, + .smem_host_id = 66, +}; + +static const struct qcom_pas_data nord_adsp2_resource = { + .crash_reason_smem = 664, + .firmware_name = "adsp2.mbn", + .dtb_firmware_name = "adsp2_dtb.mbn", + .pas_id = 54, + .dtb_pas_id = 56, + .minidump_id = 22, + .auto_boot = true, + .early_boot = true, + .proxy_pd_names = (char*[]){ + "cx", + "mx", + NULL + }, + .load_state = "adsp2", + .ssr_name = "lpass2", + .sysmon_name = "adsp2", + .ssctl_id = 0x1e, + .smem_host_id = 130, +}; + static const struct qcom_pas_data nord_cdsp0_resource = { .crash_reason_smem = 601, .firmware_name = "cdsp.mbn", @@ -1898,6 +2420,8 @@ static const struct of_device_id qcom_pas_of_match[] = { { .compatible = "qcom,milos-mpss-pas", .data = &sm8450_mpss_resource }, { .compatible = "qcom,milos-wpss-pas", .data = &sc7280_wpss_resource }, { .compatible = "qcom,nord-adsp-pas", .data = &nord_adsp_resource }, + { .compatible = "qcom,nord-adsp1-pas", .data = &nord_adsp1_resource }, + { .compatible = "qcom,nord-adsp2-pas", .data = &nord_adsp2_resource }, { .compatible = "qcom,nord-cdsp0-pas", .data = &nord_cdsp0_resource }, { .compatible = "qcom,nord-cdsp1-pas", .data = &nord_cdsp1_resource }, { .compatible = "qcom,nord-cdsp2-pas", .data = &nord_cdsp2_resource }, diff --git a/drivers/remoteproc/qcom_sysmon.c b/drivers/remoteproc/qcom_sysmon.c index a0830a48b1f40..4af05f940a491 100644 --- a/drivers/remoteproc/qcom_sysmon.c +++ b/drivers/remoteproc/qcom_sysmon.c @@ -67,6 +67,7 @@ static const char * const sysmon_state_string[] = { struct sysmon_event { const char *subsys_name; u32 ssr_event; + void *cluster; }; static DEFINE_MUTEX(sysmon_lock); @@ -473,7 +474,8 @@ static int sysmon_prepare(struct rproc_subdev *subdev) subdev); struct sysmon_event event = { .subsys_name = sysmon->name, - .ssr_event = SSCTL_SSR_EVENT_BEFORE_POWERUP + .ssr_event = SSCTL_SSR_EVENT_BEFORE_POWERUP, + .cluster = sysmon->rproc->cluster, }; mutex_lock(&sysmon->state_lock); @@ -500,7 +502,8 @@ static int sysmon_start(struct rproc_subdev *subdev) struct qcom_sysmon *target; struct sysmon_event event = { .subsys_name = sysmon->name, - .ssr_event = SSCTL_SSR_EVENT_AFTER_POWERUP + .ssr_event = SSCTL_SSR_EVENT_AFTER_POWERUP, + .cluster = sysmon->rproc->cluster, }; reinit_completion(&sysmon->ssctl_comp); @@ -536,7 +539,8 @@ static void sysmon_stop(struct rproc_subdev *subdev, bool crashed) struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon, subdev); struct sysmon_event event = { .subsys_name = sysmon->name, - .ssr_event = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN + .ssr_event = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN, + .cluster = sysmon->rproc->cluster, }; sysmon->shutdown_acked = false; @@ -567,7 +571,8 @@ static void sysmon_unprepare(struct rproc_subdev *subdev) subdev); struct sysmon_event event = { .subsys_name = sysmon->name, - .ssr_event = SSCTL_SSR_EVENT_AFTER_SHUTDOWN + .ssr_event = SSCTL_SSR_EVENT_AFTER_SHUTDOWN, + .cluster = sysmon->rproc->cluster, }; mutex_lock(&sysmon->state_lock); @@ -588,6 +593,11 @@ static int sysmon_notify(struct notifier_block *nb, unsigned long event, struct qcom_sysmon *sysmon = container_of(nb, struct qcom_sysmon, nb); struct sysmon_event *sysmon_event = data; + /* Cluster siblings' firmware can't handle peer SSR notify; skip it */ + if (sysmon->rproc->cluster && + sysmon->rproc->cluster == sysmon_event->cluster) + return NOTIFY_DONE; + /* Skip non-running rprocs and the originating instance */ if (sysmon->state != SSCTL_SSR_EVENT_AFTER_POWERUP || !strcmp(sysmon_event->subsys_name, sysmon->name)) { diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c index d9d4468e4cbdf..a15d7e6a52423 100644 --- a/drivers/rpmsg/qcom_glink_native.c +++ b/drivers/rpmsg/qcom_glink_native.c @@ -122,6 +122,8 @@ struct qcom_glink { unsigned long features; bool intentless; + /* Cluster identifier of the remote processor behind this edge, or NULL */ + void *cluster; wait_queue_head_t tx_avail_notify; bool sent_read_notify; @@ -1892,7 +1894,8 @@ struct qcom_glink *qcom_glink_native_probe(struct device *dev, unsigned long features, struct qcom_glink_pipe *rx, struct qcom_glink_pipe *tx, - bool intentless) + bool intentless, + void *cluster) { int ret; struct qcom_glink *glink; @@ -1907,6 +1910,7 @@ struct qcom_glink *qcom_glink_native_probe(struct device *dev, glink->features = features; glink->intentless = intentless; + glink->cluster = cluster; spin_lock_init(&glink->tx_lock); spin_lock_init(&glink->rx_lock); @@ -1940,6 +1944,20 @@ struct qcom_glink *qcom_glink_native_probe(struct device *dev, } EXPORT_SYMBOL_GPL(qcom_glink_native_probe); +/** + * qcom_glink_ept_cluster() - cluster identifier of an endpoint's edge + * @ept: endpoint to query + * + * Return: the opaque cluster identifier of the remote processor @ept talks to, + * or NULL if it is not part of a cluster. + */ +void *qcom_glink_ept_cluster(struct rpmsg_endpoint *ept) +{ + struct glink_channel *channel = to_glink_channel(ept); + + return channel->glink->cluster; +} + static int qcom_glink_remove_device(struct device *dev, void *data) { device_unregister(dev); diff --git a/drivers/rpmsg/qcom_glink_native.h b/drivers/rpmsg/qcom_glink_native.h index 8dbec24de23e0..8544f4f5c5450 100644 --- a/drivers/rpmsg/qcom_glink_native.h +++ b/drivers/rpmsg/qcom_glink_native.h @@ -29,12 +29,15 @@ struct qcom_glink_pipe { struct device; struct qcom_glink; +struct rpmsg_endpoint; struct qcom_glink *qcom_glink_native_probe(struct device *dev, unsigned long features, struct qcom_glink_pipe *rx, struct qcom_glink_pipe *tx, - bool intentless); + bool intentless, + void *cluster); +void *qcom_glink_ept_cluster(struct rpmsg_endpoint *ept); void qcom_glink_native_remove(struct qcom_glink *glink); void qcom_glink_native_rx(struct qcom_glink *glink); diff --git a/drivers/rpmsg/qcom_glink_rpm.c b/drivers/rpmsg/qcom_glink_rpm.c index e3ba2c63a5fce..9c1ff05d4a10c 100644 --- a/drivers/rpmsg/qcom_glink_rpm.c +++ b/drivers/rpmsg/qcom_glink_rpm.c @@ -346,7 +346,7 @@ static int glink_rpm_probe(struct platform_device *pdev) 0, &rpm->rx_pipe.native, &rpm->tx_pipe.native, - true); + true, NULL); if (IS_ERR(glink)) { mbox_free_channel(rpm->mbox_chan); return PTR_ERR(glink); diff --git a/drivers/rpmsg/qcom_glink_smem.c b/drivers/rpmsg/qcom_glink_smem.c index 62adc4db23170..f7874bbcf7fcf 100644 --- a/drivers/rpmsg/qcom_glink_smem.c +++ b/drivers/rpmsg/qcom_glink_smem.c @@ -218,7 +218,8 @@ static void qcom_glink_smem_release(struct device *dev) } struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent, - struct device_node *node) + struct device_node *node, + void *cluster) { struct glink_smem_pipe *rx_pipe; struct glink_smem_pipe *tx_pipe; @@ -338,7 +339,7 @@ struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent, glink = qcom_glink_native_probe(dev, GLINK_FEATURE_INTENT_REUSE, &rx_pipe->native, &tx_pipe->native, - false); + false, cluster); if (IS_ERR(glink)) { ret = PTR_ERR(glink); goto err_free_mbox; diff --git a/drivers/rpmsg/qcom_glink_ssr.c b/drivers/rpmsg/qcom_glink_ssr.c index e71d3716c55c5..74c671a19af03 100644 --- a/drivers/rpmsg/qcom_glink_ssr.c +++ b/drivers/rpmsg/qcom_glink_ssr.c @@ -11,6 +11,8 @@ #include #include +#include "qcom_glink_native.h" + /** * struct do_cleanup_msg - The data structure for an SSR do_cleanup message * @version: The G-Link SSR protocol version @@ -55,16 +57,34 @@ struct glink_ssr { struct completion completion; }; +/** + * struct glink_ssr_notify_data - payload of an SSR notification + * @ssr_name: name of the remoteproc that has been stopped + * @cluster: cluster identifier of the remoteproc that has been stopped, + * or NULL if it is not part of a cluster + */ +struct glink_ssr_notify_data { + const char *ssr_name; + void *cluster; +}; + /* Notifier list for all registered glink_ssr instances */ static BLOCKING_NOTIFIER_HEAD(ssr_notifiers); /** * qcom_glink_ssr_notify() - notify GLINK SSR about stopped remoteproc * @ssr_name: name of the remoteproc that has been stopped + * @cluster: cluster identifier of the remoteproc that has been stopped, + * or NULL if it is not part of a cluster */ -void qcom_glink_ssr_notify(const char *ssr_name) +void qcom_glink_ssr_notify(const char *ssr_name, void *cluster) { - blocking_notifier_call_chain(&ssr_notifiers, 0, (void *)ssr_name); + struct glink_ssr_notify_data data = { + .ssr_name = ssr_name, + .cluster = cluster, + }; + + blocking_notifier_call_chain(&ssr_notifiers, 0, &data); } EXPORT_SYMBOL_GPL(qcom_glink_ssr_notify); @@ -100,18 +120,24 @@ static int qcom_glink_ssr_notifier_call(struct notifier_block *nb, void *data) { struct glink_ssr *ssr = container_of(nb, struct glink_ssr, nb); + struct glink_ssr_notify_data *notify_data = data; struct do_cleanup_msg msg; - char *ssr_name = data; + void *cluster; int ret; + /* Cluster siblings' firmware can't handle peer SSR notify; skip it */ + cluster = qcom_glink_ept_cluster(ssr->ept); + if (cluster && cluster == notify_data->cluster) + return NOTIFY_DONE; + ssr->seq_num++; reinit_completion(&ssr->completion); memset(&msg, 0, sizeof(msg)); msg.command = cpu_to_le32(GLINK_SSR_DO_CLEANUP); msg.seq_num = cpu_to_le32(ssr->seq_num); - msg.name_len = cpu_to_le32(strlen(ssr_name)); - strscpy(msg.name, ssr_name, sizeof(msg.name)); + msg.name_len = cpu_to_le32(strlen(notify_data->ssr_name)); + strscpy(msg.name, notify_data->ssr_name, sizeof(msg.name)); ret = rpmsg_send(ssr->ept, &msg, sizeof(msg)); if (ret < 0) diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 7c1546d480082..67169ade60ce4 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -278,6 +278,8 @@ enum rproc_features { * @cdev: character device of the rproc * @cdev_put_on_release: flag to indicate if remoteproc should be shutdown on @char_dev release * @features: indicate remoteproc features + * @cluster: opaque identifier shared by remoteprocs whose SSR notifications + * should be coalesced (set by the owning rproc driver), or NULL */ struct rproc { struct list_head node; @@ -319,6 +321,7 @@ struct rproc { struct cdev cdev; bool cdev_put_on_release; DECLARE_BITMAP(features, RPROC_MAX_FEATURES); + void *cluster; }; /** diff --git a/include/linux/rpmsg/qcom_glink.h b/include/linux/rpmsg/qcom_glink.h index bfbd48f435fa5..6591d016e8cbb 100644 --- a/include/linux/rpmsg/qcom_glink.h +++ b/include/linux/rpmsg/qcom_glink.h @@ -8,22 +8,24 @@ struct qcom_glink_smem; #if IS_ENABLED(CONFIG_RPMSG_QCOM_GLINK) -void qcom_glink_ssr_notify(const char *ssr_name); +void qcom_glink_ssr_notify(const char *ssr_name, void *cluster); #else -static inline void qcom_glink_ssr_notify(const char *ssr_name) {} +static inline void qcom_glink_ssr_notify(const char *ssr_name, void *cluster) {} #endif #if IS_ENABLED(CONFIG_RPMSG_QCOM_GLINK_SMEM) struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent, - struct device_node *node); + struct device_node *node, + void *cluster); void qcom_glink_smem_unregister(struct qcom_glink_smem *glink); #else static inline struct qcom_glink_smem * qcom_glink_smem_register(struct device *parent, - struct device_node *node) + struct device_node *node, + void *cluster) { return NULL; }