Skip to content

Commit b8e36e1

Browse files
Alex Elderdavem330
authored andcommitted
net: ipa: fix TX queue race
Jakub Kicinski pointed out a race condition in ipa_start_xmit() in a recently-accepted series of patches: https://lore.kernel.org/netdev/[email protected]/ We are stopping the modem TX queue in that function if the power state is not active. We restart the TX queue again once hardware resume is complete. TX path Power Management ------- ---------------- pm_runtime_get(); no power Start resume Stop TX queue ... pm_runtime_put() Resume complete return NETDEV_TX_BUSY Start TX queue pm_runtime_get() Power present, transmit pm_runtime_put() (auto-suspend) The issue is that the power management (resume) activity and the network transmit activity can occur concurrently, and there's a chance the queue will be stopped *after* it has been started again. TX path Power Management ------- ---------------- Resume underway pm_runtime_get(); no power ... Resume complete Start TX queue Stop TX queue <-- No more transmits after this pm_runtime_put() return NETDEV_TX_BUSY We address this using a STARTED flag to indicate when the TX queue has been started from the resume path, and a spinlock to make the flag and queue updates happen atomically. TX path Power Management ------- ---------------- Resume underway pm_runtime_get(); no power Resume complete start TX queue \ If STARTED flag is *not* set: > atomic Stop TX queue set STARTED flag / pm_runtime_put() return NETDEV_TX_BUSY A second flag is used to address a different race that involves another path requesting power. TX path Other path Power Management ------- ---------- ---------------- pm_runtime_get_sync() Resume Start TX queue \ atomic Set STARTED flag / (do its thing) pm_runtime_put() (auto-suspend) pm_runtime_get() Mark delayed resume STARTED *is* set, so do *not* stop TX queue <-- Queue should be stopped here pm_runtime_put() return NETDEV_TX_BUSY Suspend done, resume Resume complete pm_runtime_get() Stop TX queue (STARTED is *not* set) Start TX queue \ atomic pm_runtime_put() Set STARTED flag / return NETDEV_TX_BUSY So a STOPPED flag is set in the transmit path when it has stopped the TX queue, and this pair of operations is also protected by the spinlock. The resume path only restarts the TX queue if the STOPPED flag is set. This case isn't a major problem, but it avoids the "non-trivial amount of useless work" done by the networking stack when NETDEV_TX_BUSY is returned. Fixes: 6b51f80 ("net: ipa: ensure hardware has power in ipa_start_xmit()") Signed-off-by: Alex Elder <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 6505782 commit b8e36e1

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

drivers/net/ipa/ipa_clock.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,31 @@ struct ipa_interconnect {
4848
* enum ipa_power_flag - IPA power flags
4949
* @IPA_POWER_FLAG_RESUMED: Whether resume from suspend has been signaled
5050
* @IPA_POWER_FLAG_SYSTEM: Hardware is system (not runtime) suspended
51+
* @IPA_POWER_FLAG_STOPPED: Modem TX is disabled by ipa_start_xmit()
52+
* @IPA_POWER_FLAG_STARTED: Modem TX was enabled by ipa_runtime_resume()
5153
* @IPA_POWER_FLAG_COUNT: Number of defined power flags
5254
*/
5355
enum ipa_power_flag {
5456
IPA_POWER_FLAG_RESUMED,
5557
IPA_POWER_FLAG_SYSTEM,
58+
IPA_POWER_FLAG_STOPPED,
59+
IPA_POWER_FLAG_STARTED,
5660
IPA_POWER_FLAG_COUNT, /* Last; not a flag */
5761
};
5862

5963
/**
6064
* struct ipa_clock - IPA clocking information
6165
* @dev: IPA device pointer
6266
* @core: IPA core clock
67+
* @spinlock: Protects modem TX queue enable/disable
6368
* @flags: Boolean state flags
6469
* @interconnect_count: Number of elements in interconnect[]
6570
* @interconnect: Interconnect array
6671
*/
6772
struct ipa_clock {
6873
struct device *dev;
6974
struct clk *core;
75+
spinlock_t spinlock; /* used with STOPPED/STARTED power flags */
7076
DECLARE_BITMAP(flags, IPA_POWER_FLAG_COUNT);
7177
u32 interconnect_count;
7278
struct ipa_interconnect *interconnect;
@@ -334,6 +340,70 @@ static void ipa_suspend_handler(struct ipa *ipa, enum ipa_irq_id irq_id)
334340
ipa_interrupt_suspend_clear_all(ipa->interrupt);
335341
}
336342

343+
/* The next few functions coordinate stopping and starting the modem
344+
* network device transmit queue.
345+
*
346+
* Transmit can be running concurrent with power resume, and there's a
347+
* chance the resume completes before the transmit path stops the queue,
348+
* leaving the queue in a stopped state. The next two functions are used
349+
* to avoid this: ipa_power_modem_queue_stop() is used by ipa_start_xmit()
350+
* to conditionally stop the TX queue; and ipa_power_modem_queue_start()
351+
* is used by ipa_runtime_resume() to conditionally restart it.
352+
*
353+
* Two flags and a spinlock are used. If the queue is stopped, the STOPPED
354+
* power flag is set. And if the queue is started, the STARTED flag is set.
355+
* The queue is only started on resume if the STOPPED flag is set. And the
356+
* queue is only started in ipa_start_xmit() if the STARTED flag is *not*
357+
* set. As a result, the queue remains operational if the two activites
358+
* happen concurrently regardless of the order they complete. The spinlock
359+
* ensures the flag and TX queue operations are done atomically.
360+
*
361+
* The first function stops the modem netdev transmit queue, but only if
362+
* the STARTED flag is *not* set. That flag is cleared if it was set.
363+
* If the queue is stopped, the STOPPED flag is set. This is called only
364+
* from the power ->runtime_resume operation.
365+
*/
366+
void ipa_power_modem_queue_stop(struct ipa *ipa)
367+
{
368+
struct ipa_clock *clock = ipa->clock;
369+
unsigned long flags;
370+
371+
spin_lock_irqsave(&clock->spinlock, flags);
372+
373+
if (!__test_and_clear_bit(IPA_POWER_FLAG_STARTED, clock->flags)) {
374+
netif_stop_queue(ipa->modem_netdev);
375+
__set_bit(IPA_POWER_FLAG_STOPPED, clock->flags);
376+
}
377+
378+
spin_unlock_irqrestore(&clock->spinlock, flags);
379+
}
380+
381+
/* This function starts the modem netdev transmit queue, but only if the
382+
* STOPPED flag is set. That flag is cleared if it was set. If the queue
383+
* was restarted, the STARTED flag is set; this allows ipa_start_xmit()
384+
* to skip stopping the queue in the event of a race.
385+
*/
386+
void ipa_power_modem_queue_wake(struct ipa *ipa)
387+
{
388+
struct ipa_clock *clock = ipa->clock;
389+
unsigned long flags;
390+
391+
spin_lock_irqsave(&clock->spinlock, flags);
392+
393+
if (__test_and_clear_bit(IPA_POWER_FLAG_STOPPED, clock->flags)) {
394+
__set_bit(IPA_POWER_FLAG_STARTED, clock->flags);
395+
netif_wake_queue(ipa->modem_netdev);
396+
}
397+
398+
spin_unlock_irqrestore(&clock->spinlock, flags);
399+
}
400+
401+
/* This function clears the STARTED flag once the TX queue is operating */
402+
void ipa_power_modem_queue_active(struct ipa *ipa)
403+
{
404+
clear_bit(IPA_POWER_FLAG_STARTED, ipa->clock->flags);
405+
}
406+
337407
int ipa_power_setup(struct ipa *ipa)
338408
{
339409
int ret;
@@ -383,6 +453,7 @@ ipa_clock_init(struct device *dev, const struct ipa_clock_data *data)
383453
}
384454
clock->dev = dev;
385455
clock->core = clk;
456+
spin_lock_init(&clock->spinlock);
386457
clock->interconnect_count = data->interconnect_count;
387458

388459
ret = ipa_interconnect_init(clock, dev, data->interconnect_data);

drivers/net/ipa/ipa_clock.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ extern const struct dev_pm_ops ipa_pm_ops;
2222
*/
2323
u32 ipa_clock_rate(struct ipa *ipa);
2424

25+
/**
26+
* ipa_power_modem_queue_stop() - Possibly stop the modem netdev TX queue
27+
* @ipa: IPA pointer
28+
*/
29+
void ipa_power_modem_queue_stop(struct ipa *ipa);
30+
31+
/**
32+
* ipa_power_modem_queue_wake() - Possibly wake the modem netdev TX queue
33+
* @ipa: IPA pointer
34+
*/
35+
void ipa_power_modem_queue_wake(struct ipa *ipa);
36+
37+
/**
38+
* ipa_power_modem_queue_active() - Report modem netdev TX queue active
39+
* @ipa: IPA pointer
40+
*/
41+
void ipa_power_modem_queue_active(struct ipa *ipa);
42+
2543
/**
2644
* ipa_power_setup() - Set up IPA power management
2745
* @ipa: IPA pointer

drivers/net/ipa/ipa_modem.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ ipa_start_xmit(struct sk_buff *skb, struct net_device *netdev)
130130
if (ret < 1) {
131131
/* If a resume won't happen, just drop the packet */
132132
if (ret < 0 && ret != -EINPROGRESS) {
133+
ipa_power_modem_queue_active(ipa);
133134
pm_runtime_put_noidle(dev);
134135
goto err_drop_skb;
135136
}
@@ -138,13 +139,15 @@ ipa_start_xmit(struct sk_buff *skb, struct net_device *netdev)
138139
* until we're resumed; ipa_modem_resume() arranges for the
139140
* TX queue to be started again.
140141
*/
141-
netif_stop_queue(netdev);
142+
ipa_power_modem_queue_stop(ipa);
142143

143144
(void)pm_runtime_put(dev);
144145

145146
return NETDEV_TX_BUSY;
146147
}
147148

149+
ipa_power_modem_queue_active(ipa);
150+
148151
ret = ipa_endpoint_skb_tx(endpoint, skb);
149152

150153
(void)pm_runtime_put(dev);
@@ -241,7 +244,7 @@ static void ipa_modem_wake_queue_work(struct work_struct *work)
241244
{
242245
struct ipa_priv *priv = container_of(work, struct ipa_priv, work);
243246

244-
netif_wake_queue(priv->ipa->modem_netdev);
247+
ipa_power_modem_queue_wake(priv->ipa);
245248
}
246249

247250
/** ipa_modem_resume() - resume callback for runtime_pm

0 commit comments

Comments
 (0)