From: Alex Elder <hidden> Date: 2021-08-12 19:50:42
This series contains a few remaining changes needed before fully
switching over to using runtime power management rather than the
previous "IPA clock" mechanism.
The first patch moves the calls to enable and disable the IPA
interrupt as a system wakeup interrupt into "ipa_clock.c" with the
rest of the power-related code.
The second adds a flag to make it possible to distinguish runtime
suspend from system suspend.
The third and fourth patches arrange for the ->start_xmit path to
resume hardware if necessary, to ensure it is powered. If power is
not active, the TX queue is stopped, and arrangements are made for
the queue to be restarted once hardware power is active again.
The fifth patch keeps the TX queue active during suspend. This
isn't necessary for system suspend but it's important for runtime
suspend.
And the last patch makes it so we don't hold the hardware active
while the modem network device is open.
-Alex
Alex Elder (6):
net: ipa: enable wakeup in ipa_power_setup()
net: ipa: distinguish system from runtime suspend
net: ipa: re-enable transmit in PM WQ context
net: ipa: ensure hardware has power in ipa_start_xmit()
net: ipa: don't stop TX on suspend
net: ipa: don't hold clock reference while netdev open
drivers/net/ipa/ipa_clock.c | 49 ++++++++++++++++++++-----
drivers/net/ipa/ipa_clock.h | 4 ++-
drivers/net/ipa/ipa_main.c | 6 +---
drivers/net/ipa/ipa_modem.c | 72 +++++++++++++++++++++++++++++++++----
4 files changed, 111 insertions(+), 20 deletions(-)
--
2.27.0
From: Alex Elder <hidden> Date: 2021-08-12 19:50:44
Move the call to enable the IPA interrupt as a wakeup interrupt into
ipa_power_setup(), disable it in ipa_power_teardown().
Signed-off-by: Alex Elder <redacted>
---
drivers/net/ipa/ipa_clock.c | 11 ++++++++++-
drivers/net/ipa/ipa_clock.h | 4 +++-
drivers/net/ipa/ipa_main.c | 6 +-----
3 files changed, 14 insertions(+), 7 deletions(-)
From: Alex Elder <hidden> Date: 2021-08-12 19:50:46
Create a new work structure in the modem private data, and use it to
re-enable the modem network device transmit queue when resuming.
This is needed by the next patch, which stops the TX queue if IPA
power isn't active when a transmit request arrives. Packets will
start arriving the instant the TX queue is enabled, but resuming
isn't complete until ipa_modem_resume() returns. This way we're
sure to be resumed before transmits are allowed again.
Cancel it before calling ipa_stop() in ipa_modem_stop() to ensure
the transmit queue restart completes before it gets stopped there.
Signed-off-by: Alex Elder <redacted>
---
drivers/net/ipa/ipa_modem.c | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
@@ -205,7 +226,8 @@ void ipa_modem_resume(struct net_device *netdev)ipa_endpoint_resume_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]);ipa_endpoint_resume_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]);-netif_wake_queue(netdev);+/* Arrange for the TX queue to be restarted */+(void)queue_pm_work(&priv->work);}intipa_modem_start(structipa*ipa)
@@ -233,6 +255,7 @@ int ipa_modem_start(struct ipa *ipa)SET_NETDEV_DEV(netdev,&ipa->pdev->dev);priv=netdev_priv(netdev);priv->ipa=ipa;+INIT_WORK(&priv->work,ipa_modem_wake_queue_work);ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]->netdev=netdev;ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]->netdev=netdev;ipa->modem_netdev=netdev;
@@ -277,6 +300,9 @@ int ipa_modem_stop(struct ipa *ipa)/* Clean up the netdev and endpoints if it was started */if(netdev){+structipa_priv*priv=netdev_priv(netdev);++cancel_work_sync(&priv->work);/* If it was opened, stop it first */if(netdev->flags&IFF_UP)(void)ipa_stop(netdev);
From: Alex Elder <hidden> Date: 2021-08-12 19:50:53
Add a new flag that is set when the hardware is suspended due to a
system suspend operation, distingishing it from runtime suspend.
Use it in the SUSPEND IPA interrupt handler to determine whether to
trigger a system resume because of the event. Define new suspend
and resume power management callback functions to set and clear the
new flag, respectively.
Signed-off-by: Alex Elder <redacted>
---
drivers/net/ipa/ipa_clock.c | 38 ++++++++++++++++++++++++++++++-------
1 file changed, 31 insertions(+), 7 deletions(-)
@@ -47,10 +47,12 @@ struct ipa_interconnect {/***enumipa_power_flag-IPApowerflags*@IPA_POWER_FLAG_RESUMED:Whetherresumefromsuspendhasbeensignaled+*@IPA_POWER_FLAG_SYSTEM:Hardwareissystem(notruntime)suspended*@IPA_POWER_FLAG_COUNT:Numberofdefinedpowerflags*/enumipa_power_flag{IPA_POWER_FLAG_RESUMED,+IPA_POWER_FLAG_SYSTEM,IPA_POWER_FLAG_COUNT,/* Last; not a flag */};
@@ -281,6 +283,27 @@ int ipa_clock_put(struct ipa *ipa)returnpm_runtime_put(&ipa->pdev->dev);}+staticintipa_suspend(structdevice*dev)+{+structipa*ipa=dev_get_drvdata(dev);++__set_bit(IPA_POWER_FLAG_SYSTEM,ipa->clock->flags);++returnpm_runtime_force_suspend(dev);+}++staticintipa_resume(structdevice*dev)+{+structipa*ipa=dev_get_drvdata(dev);+intret;++ret=pm_runtime_force_resume(dev);++__clear_bit(IPA_POWER_FLAG_SYSTEM,ipa->clock->flags);++returnret;+}+/* Return the current IPA core clock rate */u32ipa_clock_rate(structipa*ipa){
@@ -299,12 +322,13 @@ u32 ipa_clock_rate(struct ipa *ipa)*/staticvoidipa_suspend_handler(structipa*ipa,enumipa_irq_idirq_id){-/* Just report the event, and let system resume handle the rest.-*Morethanoneendpointcouldsignalthis;ifso,ignore-*allbutthefirst.+/* To handle an IPA interrupt we will have resumed the hardware+*justtohandletheinterrupt,sowe'redone.Ifweareina+*systemsuspend,triggerasystemresume.*/-if(!test_and_set_bit(IPA_POWER_FLAG_RESUMED,ipa->clock->flags))-pm_wakeup_dev_event(&ipa->pdev->dev,0,true);+if(!__test_and_set_bit(IPA_POWER_FLAG_RESUMED,ipa->clock->flags))+if(test_bit(IPA_POWER_FLAG_SYSTEM,ipa->clock->flags))+pm_wakeup_dev_event(&ipa->pdev->dev,0,true);/* Acknowledge/clear the suspend interrupt on all endpoints */ipa_interrupt_suspend_clear_all(ipa->interrupt);
From: Alex Elder <hidden> Date: 2021-08-12 19:50:58
Currently we stop the modem netdev transmit queue when suspending
the hardware. For system suspend this ensured we'd never attempt
to transmit while attempting to suspend the modem endpoints.
For runtime suspend, the IPA hardware might get suspended while the
system is operating. In that case we want an attempt to transmit a
packet to cause the hardware to resume if necessary. But if we
disable the queue this cannot happen.
So stop disabling the queue on suspend. In case we end up disabling
it in ipa_start_xmit() (see the previous commit), we still arrange
to start the TX queue on resume.
Signed-off-by: Alex Elder <redacted>
---
drivers/net/ipa/ipa_modem.c | 2 --
1 file changed, 2 deletions(-)
From: Alex Elder <hidden> Date: 2021-08-12 19:51:01
Currently a clock reference is taken whenever the ->ndo_open
callback for the modem netdev is called. That reference is dropped
when the device is closed, in ipa_stop().
We no longer need this, because ipa_start_xmit() now handles the
situation where the hardware power state is not active.
Drop the clock reference in ipa_open() when we're done, and take a
new reference in ipa_stop() before we begin closing the interface.
Finally (and unrelated, but trivial), change the return type of
ipa_start_xmit() to be netdev_tx_t instead of int.
Signed-off-by: Alex Elder <redacted>
---
drivers/net/ipa/ipa_modem.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
From: Alex Elder <hidden> Date: 2021-08-12 19:51:04
We need to ensure the hardware is powered when we transmit a packet.
But if it's not, we can't block to wait for it. So asynchronously
request power in ipa_start_xmit(), and only proceed if the return
value indicates the power state is active.
If the hardware is not active, a runtime resume request will have
been initiated. In that case, stop the network stack from further
transmit attempts until the resume completes. Return NETDEV_TX_BUSY,
to retry sending the packet once the queue is restarted.
If the power request returns an error (other than -EINPROGRESS,
which just means a resume requested elsewhere isn't complete), just
drop the packet.
Signed-off-by: Alex Elder <redacted>
---
drivers/net/ipa/ipa_modem.c | 30 +++++++++++++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
@@ -115,7 +116,31 @@ static int ipa_start_xmit(struct sk_buff *skb, struct net_device *netdev)if(endpoint->data->qmap&&skb->protocol!=htons(ETH_P_MAP))gotoerr_drop_skb;+/* The hardware must be powered for us to transmit */+dev=&ipa->pdev->dev;+ret=pm_runtime_get(dev);+if(ret<1){+/* If a resume won't happen, just drop the packet */+if(ret<0&&ret!=-EINPROGRESS){+pm_runtime_put_noidle(dev);+gotoerr_drop_skb;+}++/* No power (yet). Stop the network stack from transmitting+*untilwe'reresumed;ipa_modem_resume()arrangesforthe+*TXqueuetobestartedagain.+*/+netif_stop_queue(netdev);++(void)pm_runtime_put(dev);++returnNETDEV_TX_BUSY;+}+ret=ipa_endpoint_skb_tx(endpoint,skb);++(void)pm_runtime_put(dev);+if(ret){if(ret!=-E2BIG)returnNETDEV_TX_BUSY;
From: Jakub Kicinski <kuba@kernel.org> Date: 2021-08-14 00:46:58
On Thu, 12 Aug 2021 14:50:33 -0500 Alex Elder wrote:
+ /* The hardware must be powered for us to transmit */
+ dev = &ipa->pdev->dev;
+ ret = pm_runtime_get(dev);
+ if (ret < 1) {
+ /* If a resume won't happen, just drop the packet */
+ if (ret < 0 && ret != -EINPROGRESS) {
+ pm_runtime_put_noidle(dev);
+ goto err_drop_skb;
+ }
This is racy, what if the pm work gets scheduled on another CPU and
calls wake right here (i.e. before you call netif_stop_queue())?
The queue may never get woken up?
+ /* No power (yet). Stop the network stack from transmitting
+ * until we're resumed; ipa_modem_resume() arranges for the
+ * TX queue to be started again.
+ */
+ netif_stop_queue(netdev);
+
+ (void)pm_runtime_put(dev);
+
+ return NETDEV_TX_BUSY;
From: Alex Elder <hidden> Date: 2021-08-14 02:25:27
On 8/13/21 7:46 PM, Jakub Kicinski wrote:
On Thu, 12 Aug 2021 14:50:33 -0500 Alex Elder wrote:
quoted
+ /* The hardware must be powered for us to transmit */
+ dev = &ipa->pdev->dev;
+ ret = pm_runtime_get(dev);
+ if (ret < 1) {
+ /* If a resume won't happen, just drop the packet */
+ if (ret < 0 && ret != -EINPROGRESS) {
+ pm_runtime_put_noidle(dev);
+ goto err_drop_skb;
+ }
This is racy, what if the pm work gets scheduled on another CPU and
calls wake right here (i.e. before you call netif_stop_queue())?
The queue may never get woken up?
I haven't been seeing this happen but I think you may be right.
I did think about this race, but I think I was relying on the
PM work queue to somehow avoid the problem. I need to think
about this again after a good night's sleep. I might need
to add an atomic flag or something.
-Alex
quoted
+ /* No power (yet). Stop the network stack from transmitting
+ * until we're resumed; ipa_modem_resume() arranges for the
+ * TX queue to be started again.
+ */
+ netif_stop_queue(netdev);
+
+ (void)pm_runtime_put(dev);
+
+ return NETDEV_TX_BUSY;
From: Alex Elder <hidden> Date: 2021-08-14 02:32:34
On 8/13/21 7:44 PM, Jakub Kicinski wrote:
On Thu, 12 Aug 2021 14:50:32 -0500 Alex Elder wrote:
quoted
+/**
+ * ipa_modem_wake_queue_work() - enable modem netdev queue
+ * @work: Work structure
+ *
+ * Re-enable transmit on the modem network device. This is called
+ * in (power management) work queue context, scheduled when resuming
+ * the modem.
+ */
+static void ipa_modem_wake_queue_work(struct work_struct *work)
+{
+ struct ipa_priv *priv = container_of(work, struct ipa_priv, work);
+
+ netif_wake_queue(priv->ipa->modem_netdev);
+}
+
/** ipa_modem_resume() - resume callback for runtime_pm
* @dev: pointer to device
*
@@ -205,7 +226,8 @@ void ipa_modem_resume(struct net_device *netdev) ipa_endpoint_resume_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]); ipa_endpoint_resume_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]);- netif_wake_queue(netdev);+ /* Arrange for the TX queue to be restarted */+ (void)queue_pm_work(&priv->work); }
Why move the wake call to a work queue, tho? It's okay to call it
from any context.
The issue isn't about the context in which is run (well, not
really, not in the sense you're talking about).
The issue has to do with the PM ->runtime_resume function
running concurrent with the network ->start_xmit function.
We need the hardware powered in ipa_start_xmit(). So we
call pm_runtime_get(), which will not block and which will
indicate in its return value whether power: is active
(return is 1); will be active once the resume underway
completes (return is -EINPROGRESS); will be active once
suspend underway and a delayed resume completes (return
is 0); or will be active once the newly-scheduled resume
completes (return is 0, scheduled on PM work queue).
We don't expect any other error, but if we get one we
drop the packet.
If the return value is 1, power is active and we transmit
the packet. If the return value indicates power is not
active, but will be, we stop the TX queue. No other packets
should be passed to ->start_xmit until TX is started again.
We wish to restart the TX queue when the ipa_runtime_resume()
completes. Here is the call path:
ipa_runtime_resume() This is the ->runtime_resume PM op
ipa_endpoint_resume()
ipa_modem_resume()
netif_wake_queue() Without this patch
The instant netif_wake_queue() is called, we start getting
calls to ipa_start_xmit(), which again attempts to transmit
the SKB that caused the queue to be stopped. And there is a
good chance that when that is called, the ipa_runtime_resume()
PM callback is still executing, and not complete. In that case,
we'll *again* get an -EINPROGRESS back from pm_runtime_get() in
ipa_start_xmit(), and we stop the TX queue again. Basically,
we're stuck.
All we need is for the TX queue to be started *after* the
PM ->runtime_resume callback completes and marks the the
PM runtime status ACTIVE. Scheduling this on the PM
workqueue ensures this will happen then, if we happen
to be running ipa_runtime_resume() via that workqueue.
If not, there's a bit of a race but it should resolve
(but I think here lies the specific race you mentioned
in the other message).
I'm open to other suggestions, but my hope was to at least
explain why I did it this way. I'll think about it over
the weekend and will send a new version of the series when
I come up with a solution.
Thank you very much for the review.
-Alex
Hello:
This series was applied to netdev/net-next.git (refs/heads/master):
On Thu, 12 Aug 2021 14:50:29 -0500 you wrote:
This series contains a few remaining changes needed before fully
switching over to using runtime power management rather than the
previous "IPA clock" mechanism.
The first patch moves the calls to enable and disable the IPA
interrupt as a system wakeup interrupt into "ipa_clock.c" with the
rest of the power-related code.
[...]
From: Jakub Kicinski <kuba@kernel.org> Date: 2021-08-16 14:15:52
On Fri, 13 Aug 2021 21:25:23 -0500 Alex Elder wrote:
quoted
This is racy, what if the pm work gets scheduled on another CPU and
calls wake right here (i.e. before you call netif_stop_queue())?
The queue may never get woken up?
I haven't been seeing this happen but I think you may be right.
I did think about this race, but I think I was relying on the
PM work queue to somehow avoid the problem. I need to think
about this again after a good night's sleep. I might need
to add an atomic flag or something.
Maybe add a spin lock? Seems like the whole wake up path will be
expensive enough for a spin lock to be in the noise. You can always
add complexity later.
From: Alex Elder <hidden> Date: 2021-08-16 14:20:45
On 8/16/21 9:15 AM, Jakub Kicinski wrote:
On Fri, 13 Aug 2021 21:25:23 -0500 Alex Elder wrote:
quoted
quoted
This is racy, what if the pm work gets scheduled on another CPU and
calls wake right here (i.e. before you call netif_stop_queue())?
The queue may never get woken up?
I haven't been seeing this happen but I think you may be right.
I did think about this race, but I think I was relying on the
PM work queue to somehow avoid the problem. I need to think
about this again after a good night's sleep. I might need
to add an atomic flag or something.
Maybe add a spin lock? Seems like the whole wake up path will be
expensive enough for a spin lock to be in the noise. You can always
add complexity later.
Exactly what I just decided after trying to work out a
clever way without using a spinlock... I'll be sending
out a fix today. Thanks.
-Alex
From: Alex Elder <hidden> Date: 2021-08-16 17:56:46
On 8/16/21 9:20 AM, Alex Elder wrote:
On 8/16/21 9:15 AM, Jakub Kicinski wrote:
quoted
On Fri, 13 Aug 2021 21:25:23 -0500 Alex Elder wrote:
quoted
quoted
This is racy, what if the pm work gets scheduled on another CPU and
calls wake right here (i.e. before you call netif_stop_queue())?
The queue may never get woken up?
I haven't been seeing this happen but I think you may be right.
I did think about this race, but I think I was relying on the
PM work queue to somehow avoid the problem. I need to think
about this again after a good night's sleep. I might need
to add an atomic flag or something.
Maybe add a spin lock? Seems like the whole wake up path will be
expensive enough for a spin lock to be in the noise. You can always
add complexity later.
Exactly what I just decided after trying to work out a
clever way without using a spinlock... I'll be sending
out a fix today. Thanks.
I'm finding this isn't an easy problem to solve (or even think
about). While I ponder the best course of action I'm going
to send out another series (i.e., *before* I send a fix for
this issue) because I'd like to get everything I have out
for review this week. I *will* address this potential race
one way or another, possibly later today.
-Alex
From: Jakub Kicinski <kuba@kernel.org> Date: 2021-08-16 20:19:40
On Mon, 16 Aug 2021 12:56:40 -0500 Alex Elder wrote:
I'm finding this isn't an easy problem to solve (or even think
about). While I ponder the best course of action I'm going
to send out another series (i.e., *before* I send a fix for
this issue) because I'd like to get everything I have out
for review this week. I *will* address this potential race
one way or another, possibly later today.