DEV Community

Yutaka Hirata
Yutaka Hirata

Posted on

[4/4] A Familiar Pattern in rtw88 and a Return to Linux

Realtek's rtw88 keeps its state check and interrupt operation under a shared lock. I examine both possible lock orders, then follow the 2021 patch that introduced this shutdown pattern.

The gap in igb made me look for an implementation that kept the state check and interrupt enable together. In Realtek's wireless driver, rtw88, I found a familiar pattern.

Sometimes a short if makes me stop reading. Its history explains why someone needed it.

That happened while reviewing rtw88, Realtek's wireless driver, in my local Linux 7.1.3 and 7.3 trees.

Keeping the decision with the action

NAPI processes packets in batches. At the end, its poll function can enable interrupts for the next arrival. Shutdown must prevent a late enable from undoing its interrupt mask.

In rtw_pci_napi_poll(), the relevant excerpt is:

spin_lock_bh(&rtwpci->irq_lock);
if (rtwpci->running)
        rtw_pci_enable_interrupt(rtwdev, rtwpci, false);
spin_unlock_bh(&rtwpci->irq_lock);
Enter fullscreen mode Exit fullscreen mode

rtw_pci_stop() uses the same lock:

spin_lock_bh(&rtwpci->irq_lock);
rtwpci->running = false;
rtw_pci_disable_interrupt(rtwdev, rtwpci);
spin_unlock_bh(&rtwpci->irq_lock);
Enter fullscreen mode Exit fullscreen mode

The spinlock prevents these protected sections from overlapping. The flag check and interrupt enable stay together, as do the flag update and interrupt disable.

I used this combination of a spinlock and a state flag as a standard pattern when developing HP-UX device drivers. That experience later helped me when I moved into Linux driver development. Finding it here felt like meeting an old acquaintance.

Either side can arrive first

If shutdown wins the lock, poll later sees running == false and skips the enable. If poll wins, it may enable interrupts, but shutdown acquires the lock afterward and disables them.

Both lock orders leave shutdown's interrupt mask in place

Shutdown then waits for interrupt handlers with synchronize_irq() and stops NAPI, outside the lock. This prevents the re-enable race without the wait used by r8169.

A broader wait has a cost

Why not add synchronize_net() everywhere? Because it participates in a system-wide RCU grace period, rather than waiting only for one NIC. An unrelated earlier reader on another CPU can affect the caller's wait.

This does not stop every CPU at a barrier. CPUs continue working while RCU obtains the required quiescent states; the calling task waits. CPUs already known to satisfy the condition need not all be disturbed.

Linux 7.3 selects synchronize_rcu_expedited() when called from network-namespace cleanup or when the RTNL network-configuration mutex is locked. Otherwise it calls synchronize_rcu(), which can itself select expedited operation under configuration policy. The RTNL test checks lock state, not ownership by the current task.

The lock establishes the ordering locally. But faster grace periods have costs of their own, too.

What expedited synchronization costs

Expedited RCU can use inter-processor interrupts to obtain the necessary boundaries sooner. It does not skip readers that must finish. Its source comment in kernel/rcu/tree_exp.h warns about interference with real-time workloads and discourages common-case use. For repeated updates, it recommends batching and one normal synchronization instead.

The shared lock in rtw88 addresses this particular check-and-enable race locally. It has costs of its own and is not a universal replacement for RCU. A shutdown-time wait and an operation on every packet deserve different performance decisions.

The reason was in the history

The local trees had no Git history, so I checked upstream. Po-Hao Huang's 7bd3760c71f7, “rtw88: refine napi deinit flow” has an author date of April 15, 2021, and a committer date of April 21.

It describes trouble when stopping the device as an interrupt arrives. The fix changes the shutdown order and adds running checks in both poll and the interrupt thread. That second location matters: a shutdown review must find every path that can enable interrupts again.

This explains one race; it does not certify the entire driver.

Returning to the Linux kernel source after a long time away brought back the work I did when I was younger: loading and unloading modules overnight, tracing two CPUs on paper. Back then, I wanted to get the job finished. Now I find myself coming back voluntarily. Funny how that works.

I no longer use Linux in my day job. Still, nothing is quite as interesting as Linux. According to my own research, anyway. The sample size was one.

I think I will keep enjoying a little kernel reading now and then. Just one line, I tell myself. Then its caller, then the caller's caller. Apparently, moderation is the difficult part.


Volume 3 also covers the NAPI shutdown races discussed here. This English-language Kindle series progresses from kernel fundamentals through PCI Express, interrupts and DMA to Ethernet driver development.

Getting Started with Linux Device Drivers 1

Linux Kernel Fundamentals and PCI Express

Getting Started with Linux Device Drivers 1(https://www.amazon.com/dp/B0HGMHF5P1)

Amazon Kindle

Getting Started with Linux Device Drivers, Volume 2

PCI Express, Interrupts, and DMA

Getting Started with Linux Device Drivers, Volume 2(https://www.amazon.com/dp/B0HJTWQBDH)

Amazon Kindle

Getting Started with Linux Device Drivers, Volume 3

Practical Ethernet Network Driver Development

Getting Started with Linux Device Drivers, Volume 3(https://www.amazon.com/dp/B0HLST4J4Y)

Amazon Kindle

Top comments (0)