<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Selim Arda Çevik — English</title><description>Notes on software, tools, and things worth writing down.</description><link>https://www.selimardacevik.com/</link><language>en</language><atom:link href="https://www.selimardacevik.com/rss.xml" rel="self" type="application/rss+xml"/><item><title>Tracking down a lost interrupt: NVMe, Intel VMD and 30 seconds</title><link>https://www.selimardacevik.com/blog/nvme-timeout-intel-vmd/</link><guid isPermaLink="true">https://www.selimardacevik.com/blog/nvme-timeout-intel-vmd/</guid><description>A 53-second boot and a browser that froze on first launch. Two complaints that looked unrelated had one cause: the drive completion interrupts were never reaching the kernel.</description><pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I had two complaints about a new laptop, and I was treating them as separate
problems.&lt;/p&gt;
&lt;p&gt;The first: booting took 53 seconds. An absurd number for a machine with an
NVMe SSD, 16 cores and 32 GB of RAM. The second: the first Chrome launch of
the day took forever. You click the icon, nothing happens. You click again,
still nothing. Then two windows open at once.&lt;/p&gt;
&lt;p&gt;There was one cause, and it was not a failing disk.&lt;/p&gt;
&lt;h2&gt;The kernel was already saying it&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;nvme nvme0: I/O tag 77 (104d) QID 1 timeout, completion polled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read that line as follows. The kernel sent a command to the drive. The drive
completed it. But the completion interrupt &lt;strong&gt;never arrived&lt;/strong&gt;. The kernel waited
out the full 30-second timeout (&lt;code&gt;nvme_core.io_timeout&lt;/code&gt;), then gave up, polled
the queue by hand — &lt;code&gt;completion polled&lt;/code&gt; — and found the command had finished
long ago.&lt;/p&gt;
&lt;p&gt;No data loss, no failing hardware. The only thing lost is an interrupt. The
price is 30 seconds, and during those 30 seconds &lt;strong&gt;everything&lt;/strong&gt; that touches
the disk freezes.&lt;/p&gt;
&lt;h2&gt;Evidence 1: 31 seconds of the boot were one stall&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;systemd-analyze&lt;/code&gt; almost handed me the culprit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ systemd-analyze
Startup finished in 7.093s (firmware) + 2.384s (loader) + 1.568s (kernel)
                  + 3.244s (initrd) + 38.753s (userspace) = 53.044s

$ systemd-analyze blame | grep -v &apos;\.device$&apos; | head -3
30.836s initrd-switch-root.service
22.381s fwupd.service
 6.370s NetworkManager-wait-online.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;30.8 of the 38.7 userspace seconds sat in a single unit. But the real evidence
was in the journal, and it took the form of &lt;strong&gt;an absence&lt;/strong&gt;: between 4.74 and
35.66 seconds, not one line was written. Thirty-one seconds of complete
silence. The line that broke the silence was the timeout itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[    4.741257] systemd[1]: Closed systemd-udevd-control.socket
[   35.663220] kernel: nvme nvme0: I/O tag 77 QID 1 timeout, completion polled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;30.92 seconds apart. &lt;code&gt;nvme_core.io_timeout&lt;/code&gt; is 30 seconds. The boot&apos;s lost time
matched the length of a single timeout &lt;strong&gt;exactly&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;i915 GSC proxy component didn&apos;t bind within the expected timeout&lt;/code&gt; error
sitting right next to it distracted me for a while. It was a consequence, not a
separate problem: &lt;code&gt;mei_gsc_proxy&lt;/code&gt; bound at 36.24s, the moment the disk came
back.&lt;/p&gt;
&lt;h2&gt;Evidence 2: the Chrome freeze was the same stall&lt;/h2&gt;
&lt;p&gt;I went looking for the same pattern in the Chrome complaint, and found it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[  340.004125] kernel: nvme0: I/O tag 735 QID 1 timeout, completion polled
[  340.167394] chrome: ERROR:process_singleton_posix.cc:347]
               Failed to create .../SingletonLock: File exists (17)
[  341.149448] chrome: Opening in existing browser session.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chrome had been started around 310s and stalled while reading its profile. When
nothing appeared, I clicked a second time — the &lt;code&gt;SingletonLock: File exists&lt;/code&gt;
line is exactly the fingerprint of that second click. Both came alive at
340.00, the second the timeout resolved.&lt;/p&gt;
&lt;p&gt;Chrome itself was not slow. With the same profile (394 MB, 16 extensions) and a
warm page cache, it measured 0.28 seconds. The whole difference was the disk.&lt;/p&gt;
&lt;h2&gt;Evidence 3: it happened on every boot&lt;/h2&gt;
&lt;p&gt;I checked earlier boots to see whether this was a one-off:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for b in 0 -1 -2 -3 -4; do
  echo &quot;boot $b: $(journalctl -b $b | grep -c &apos;completion polled&apos;)&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;boot  0: 2
boot -1: 7
boot -2: 2
boot -3: 4
boot -4: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seven times in one session. Seven times thirty seconds.&lt;/p&gt;
&lt;h2&gt;The wrong hypothesis: APST&lt;/h2&gt;
&lt;p&gt;My first theory was APST (Autonomous Power State Transition): the drive drops
into a low-power state when idle and misses an interrupt on the way back up.
Plausible, and the measurement backed it — the drive really was sleeping
aggressively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ sudo nvme get-feature /dev/nvme0 -f 0x0c -H
Autonomous Power State Transition Enable (APSTE): Enabled
Entry[0]  Idle Time Prior to Transition: 100 ms   -&amp;gt; power state 3
Entry[3]  Idle Time Prior to Transition: 2000 ms  -&amp;gt; power state 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A drive that sleeps after 100 milliseconds of idle. So I turned it off:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo grubby --update-kernel=ALL \
  --args=&quot;nvme_core.default_ps_max_latency_us=0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;It did nothing.&lt;/strong&gt; The boot with that parameter stalled exactly as before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[   35.684191] nvme0: I/O tag 0   QID 6 timeout, completion polled
[   96.870108] nvme0: I/O tag 257 QID 5 timeout, completion polled
[  127.078093] nvme0: I/O tag 256 QID 5 timeout, completion polled
[  227.942038] nvme0: I/O tag 930 QID 3 timeout, completion polled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Worse, on that boot the &lt;strong&gt;login screen never appeared&lt;/strong&gt;. Black screen. My first
reaction was that I had broken the system with the kernel parameter, but the
previous boot&apos;s journal said something else:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[  127.078] nvme0: I/O tag 256 QID 5 timeout, completion polled
[  128.163] plasma-login-kwin_wayland.service: Failed with result &apos;timeout&apos;
[  128.216] plasma-login-greeter: no Qt platform plugin could be initialized
[  128.314] systemd-coredump: Process 1386 (plasma-login-wa) dumped core
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The chain reads cleanly: this time the stall landed on the display manager&apos;s
startup path. &lt;code&gt;plasma-login-kwin_wayland&lt;/code&gt; exceeded systemd&apos;s start timeout and
was killed. With no Wayland compositor, the greeter could not initialize a Qt
platform plugin and crashed. The screen stayed black.&lt;/p&gt;
&lt;p&gt;The lesson there has nothing to do with the parameter: &lt;strong&gt;&quot;it won&apos;t boot&quot; is not
always a boot failure.&lt;/strong&gt; The system had booted; what was missing was the login
screen. When that happens, reading the previous boot&apos;s journal with
&lt;code&gt;journalctl -b -1&lt;/code&gt; beats guessing. The cause was sitting there in plain text.&lt;/p&gt;
&lt;h2&gt;The actual cause: Intel VMD&lt;/h2&gt;
&lt;p&gt;The next question was where the interrupt was getting lost.
&lt;code&gt;/proc/interrupts&lt;/code&gt; answered directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;178: ... VMD-PCI-MSIX-10000:e1:00.0    0  nvme0q0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things in that line. First, &lt;code&gt;VMD-PCI-MSIX&lt;/code&gt;: the drive was not attached
directly to PCIe but through &lt;strong&gt;Intel VMD&lt;/strong&gt; (Volume Management Device), a layer
that sits between NVMe controllers and the CPU and multiplexes their
interrupts.&lt;/p&gt;
&lt;p&gt;Second, and more striking, the counter: &lt;strong&gt;zero&lt;/strong&gt;. The drive had served tens of
thousands of I/Os and not one interrupt had been counted. They were being
aggregated in the VMD layer, and occasionally dropped there.&lt;/p&gt;
&lt;p&gt;The drive itself made things worse — it is a &lt;strong&gt;DRAM-less&lt;/strong&gt; model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Micron 2500 NVMe SSD (DRAM-less) [1344:5425]
kernel: nvme nvme0: allocated 64 MiB host memory buffer (16 segments)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It has no cache of its own; it borrows 64 MiB of system RAM to hold its mapping
tables (HMB, Host Memory Buffer). So the drive is constantly doing DMA into
system memory. Pushing that traffic through VMD&apos;s address and interrupt
remapping is where things went missing.&lt;/p&gt;
&lt;h2&gt;The fix is in the BIOS&lt;/h2&gt;
&lt;p&gt;In the ASUS BIOS: &lt;strong&gt;Advanced → VMD Configuration → Enable VMD controller:
Disabled&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The drive now sits directly on PCIe:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ lspci -nn | grep -i non-volatile
01:00.0 Non-Volatile memory controller: Micron 2500 NVMe SSD (DRAM-less)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And interrupts are actually counted:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep nvme0q /proc/interrupts | head -2
159: ... IR-PCI-MSIX-0000:01:00.0    0-edge   nvme0q0
160: ... IR-PCI-MSIX-0000:01:00.0    1-edge   nvme0q1
total: 34663 interrupts      (with VMD: 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One warning. Sources recommend generalizing the initramfs &lt;strong&gt;before&lt;/strong&gt; making
this change (&lt;code&gt;sudo dracut --regenerate-all --force --no-hostonly&lt;/code&gt;), because if
the initramfs has no &lt;code&gt;nvme&lt;/code&gt; driver the system will not boot once VMD is off. I
did not run it and the machine came up fine — Fedora&apos;s initramfs already
includes &lt;code&gt;nvme&lt;/code&gt;. Preparing the initramfs first is still the risk-free path. If
you want to go back, setting VMD to &lt;code&gt;Enabled&lt;/code&gt; again is enough; the root
filesystem is mounted by &lt;code&gt;UUID=&lt;/code&gt;, so the device path changing does not matter.&lt;/p&gt;
&lt;p&gt;One small win found along the way: &lt;code&gt;NetworkManager-wait-online&lt;/code&gt; was blocking
the critical chain on wifi DHCP. Pointless on a desktop, and it cost 6.4
seconds.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl disable NetworkManager-wait-online.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ systemd-analyze
Startup finished in 7.190s (firmware) + 2.745s (loader) + 1.587s (kernel)
                  + 3.738s (initrd) + 2.879s (userspace) = 18.141s

$ journalctl -b | grep -c &quot;completion polled&quot;
0
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;firmware&lt;/td&gt;
&lt;td&gt;7.1 s&lt;/td&gt;
&lt;td&gt;7.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loader&lt;/td&gt;
&lt;td&gt;2.4 s&lt;/td&gt;
&lt;td&gt;2.7 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kernel + initrd&lt;/td&gt;
&lt;td&gt;4.8 s&lt;/td&gt;
&lt;td&gt;5.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;userspace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38.8 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.9 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53.0 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18.1 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stalls per boot&lt;/td&gt;
&lt;td&gt;1–7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note that only userspace moved: firmware and loader stayed the same. That is
what you would expect — the problem was not where the disk is first read, but
where the system starts hammering it.&lt;/p&gt;
&lt;p&gt;Chrome was measured after a fresh boot with no prior launch, so the cache was
genuinely cold:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GPU process : 0.051 s
Renderer    : 0.091 s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A launch that used to freeze for 30 seconds now takes under a tenth of a
second.&lt;/p&gt;
&lt;h2&gt;Reverting the APST parameter&lt;/h2&gt;
&lt;p&gt;The kernel parameter left behind by the wrong hypothesis was still there. I
already knew it did nothing, but leaving it had a cost: with APST off, the
drive never idles down and burns power for no reason.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo grubby --update-kernel=ALL \
  --remove-args=&quot;nvme_core.default_ps_max_latency_us&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The boot without it was identical to the boot with it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -c nvme_core /proc/cmdline
0
$ cat /sys/module/nvme_core/parameters/default_ps_max_latency_us
100000                        # APST on again
$ journalctl -b | grep -c &quot;completion polled&quot;
0
$ systemd-analyze
... = 18.147s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;18.147 against 18.141 is measurement noise. So disabling VMD was the only thing
that ended the stalls; the drive can keep sleeping when idle, at no cost.&lt;/p&gt;
&lt;p&gt;Skipping this step would have been easy — the problem was already fixed. But a
parameter that has been &lt;em&gt;proven&lt;/em&gt; not to work, left sitting on the kernel command
line, becomes a variable you will stare at six months from now while debugging
something else, wondering why it is there.&lt;/p&gt;
&lt;h2&gt;What I took away&lt;/h2&gt;
&lt;p&gt;Three things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Silence is data too.&lt;/strong&gt; The single observation that cracked this was that the
journal had no lines at all for 31 seconds. We are trained to read what logs
say; here the information was in what they did not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An unmeasured hypothesis is not a fix.&lt;/strong&gt; The APST theory was plausible, the
measurement supported it, and it was wrong. The &lt;code&gt;nvme get-feature&lt;/code&gt; output said
&quot;this drive sleeps aggressively&quot; — true, and irrelevant. The question is not
&quot;is this finding real&quot; but &quot;is this finding the cause of &lt;em&gt;this&lt;/em&gt; problem&quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When two things look broken at once, one thing is probably broken.&lt;/strong&gt; A slow
boot and a frozen browser were two unrelated complaints; the common cause was
that both were waiting out the same 30 seconds.&lt;/p&gt;
&lt;p&gt;The note in my own files is longer and carries every command output. I write
these down routinely now, because researching the same problem a second time
from scratch is more annoying than the first.&lt;/p&gt;
</content:encoded><category>linux</category><category>fedora</category><category>hardware</category><category>debugging</category></item><item><title>Hello world</title><link>https://www.selimardacevik.com/blog/hello-world/</link><guid isPermaLink="true">https://www.selimardacevik.com/blog/hello-world/</guid><description>The first post on this blog: who I am, what I will write here, and how the site is built.</description><pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the first post on the blog. Rather than open with a long introduction,
here is what you will find here.&lt;/p&gt;
&lt;p&gt;I am a platform engineer. Half the job is the path a team ships through — CI
pipelines, containers, environments — and the other half is the application
code that runs on it. I work in finance, on systems where throughput and
failures are taken seriously.&lt;/p&gt;
&lt;h2&gt;What I will write about&lt;/h2&gt;
&lt;p&gt;This is not a journal. What I find worth writing down is this: when a problem
takes longer than it should have, the cause is usually a small detail that was
not written down anywhere. Those go here.&lt;/p&gt;
&lt;p&gt;Likely subjects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes and container behaviour that the docs cover in one line and that
cost me three hours&lt;/li&gt;
&lt;li&gt;Decisions made while building a delivery path with Jenkins and Argo CD, and
the ones I later regretted&lt;/li&gt;
&lt;li&gt;Patterns I keep running into on the Spring Boot side&lt;/li&gt;
&lt;li&gt;Development on Fedora: what works and what does not&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not every post will end with &quot;here is how you should do it&quot;. Sometimes &quot;so
that is how it behaves&quot; is the whole finding.&lt;/p&gt;
&lt;h2&gt;About the site&lt;/h2&gt;
&lt;p&gt;The site is static, built with Astro, and posts live in the repository as MDX
files. Publishing is a commit, which means corrections stay in the history too.&lt;/p&gt;
&lt;p&gt;It is bilingual: English at the root, Turkish under &lt;code&gt;/tr&lt;/code&gt;, with both versions
of a post sharing one filename. When a post has no translation, the language
switcher takes you to that language&apos;s blog index rather than a dead link.&lt;/p&gt;
&lt;p&gt;Every heading carries its own link, longer posts get a table of contents above
the body, and the search page covers posts in both languages. You can also read
everything over RSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://selimardacevik.com/rss.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What&apos;s next&lt;/h2&gt;
&lt;p&gt;The first technical post is coming soon. Until then, have a look at
&lt;a href=&quot;https://www.selimardacevik.com/projects/&quot;&gt;projects&lt;/a&gt; and the &lt;a href=&quot;https://www.selimardacevik.com/cv/&quot;&gt;CV&lt;/a&gt;, and send an email if you want to ask
about something.&lt;/p&gt;
</content:encoded><category>meta</category></item></channel></rss>