summaryrefslogtreecommitdiffstatshomepage
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/19/index.md2
-rw-r--r--content/22/index.md52
-rw-r--r--content/23/index.md140
-rw-r--r--content/24/index.md68
-rw-r--r--content/25/index.md131
-rw-r--r--content/25/recent-okular.pngbin0 -> 44030 bytes
-rw-r--r--content/26/icon-day.pngbin0 -> 31082 bytes
-rw-r--r--content/26/icon-night.pngbin0 -> 25542 bytes
-rw-r--r--content/26/index.md67
-rw-r--r--content/27/index.md65
10 files changed, 524 insertions, 1 deletions
diff --git a/content/19/index.md b/content/19/index.md
index 792a6dd..419e7dc 100644
--- a/content/19/index.md
+++ b/content/19/index.md
@@ -4,7 +4,7 @@ date = 2024-09-18T21:16:12+02:00
title = "KDE Plasma 6 and two bugs"
[taxonomies]
-tags = ["bugs"]
+tags = ["bugs", "kde"]
[extra]
related = []
diff --git a/content/22/index.md b/content/22/index.md
new file mode 100644
index 0000000..c3ece23
--- /dev/null
+++ b/content/22/index.md
@@ -0,0 +1,52 @@
++++
+date = 2024-09-28T18:23:12+02:00
+title = "MIME type subclassing and its consequences"
+
+[taxonomies]
+tags = ["TIL"]
+
+[extra]
+related = []
++++
+
+The freedesktop.org [shared MIME-info database
+spec](https://specifications.freedesktop.org/shared-mime-info-spec/latest) says
+the following in [section
+2.11](https://specifications.freedesktop.org/shared-mime-info-spec/latest/ar01s02.html#subclassing):
+
+> A type is a subclass of another type if any instance of the first type is
+> also an instance of the second. For example, all `image/svg+xml` files are
+> also `application/xml`, `text/plain` and `application/octet-stream` files.
+> Subclassing is about the format, rather than the category of the data (for
+> example, there is no 'generic spreadsheet' class that all spreadsheets
+> inherit from).
+>
+> Some subclass rules are implicit:
+> - All `text/*` types are subclasses of `text/plain`.
+> - All streamable types (ie, everything except the `inode/*` types) are subclasses of application/octet-stream.
+
+So far so good; this makes intuitive sense and seems sensible enough. There is
+an interesting consequence of this rule when the MIME-info database is used by
+desktop systems for file associations, however: **An application associated with
+`application/octet-stream` will automatically be associated with all streamable
+types as well.**
+
+This means that if you associate `application/octet-stream` with your text
+editor, your desktop system will also suggest you open video and audio files
+with that same text editor. This behaviour can be quite surprising, especially
+if the association was added automatically when a file was opened through the
+"Open with..." dialog.
+
+What is even more confusing if you don't happen to know the subclassing rule is
+the fact that `~/.config/mimeapps.list` and applications interfacing with this
+file will not even list the editor as associated with any audio or video files.
+You might just skip over the entry it has for `application/octet-stream`, not
+realizing its significance. Perhaps you even assume (understandably) that
+`application/octet-stream` only specifies any file of "unknown" type.
+User-facing documentation on desktop systems (if it even exists) does not
+discuss this behaviour.
+
+Whilst looking into this I found an older KDE bug report with some [interesting
+thoughts](https://bugs.kde.org/show_bug.cgi?id=425154#c2) on how to explain this
+behaviour to the end user, but sadly as far as I have seen none of these have
+made it into the system setting's file association dialog.
diff --git a/content/23/index.md b/content/23/index.md
new file mode 100644
index 0000000..442f383
--- /dev/null
+++ b/content/23/index.md
@@ -0,0 +1,140 @@
++++
+date = 2024-10-02T18:42:07+02:00
+title = "musl and a curious Rust segfault"
+
+[taxonomies]
+tags = ["bugs"]
+
+[extra]
+related = []
++++
+
+About a week ago I noticed that [`fd(1)`](https://github.com/sharkdp/fd), a
+Rust-based alternative to [`find(1)`](https://www.gnu.org/software/findutils/),
+would suddenly segfault on my [musl](https://www.musl-libc.org/)-based server
+system. Usually a segfault is nothing particularly special to my eyes, but this
+one was different. Even just having `fd(1)` attempt to print its help text was
+enough to trigger it, and when I attempted to debug it with
+[`gdb(1)`](https://www.sourceware.org/gdb/), I saw the following:
+
+```
+(gdb) run
+Starting program: /usr/bin/fd
+
+Program received signal SIGSEGV, Segmentation fault.
+memcpy () at ../src_musl/src/string/x86_64/memcpy.s:18
+warning: 18 ../src_musl/src/string/x86_64/memcpy.s: No such file or directory
+(gdb) bt
+#0 memcpy () at ../src_musl/src/string/x86_64/memcpy.s:18
+#1 0x00007ffff7ab7177 in __copy_tls () at ../src_musl/src/env/__init_tls.c:66
+#2 0x00007ffff7ab730d in static_init_tls () at ../src_musl/src/env/__init_tls.c:149
+#3 0x00007ffff7aae89d in __init_libc () at ../src_musl/src/env/__libc_start_main.c:39
+#4 0x00007ffff7aae9c0 in __libc_start_main () at ../src_musl/src/env/__libc_start_main.c:80
+#5 0x00007ffff74107f6 in _start ()
+```
+
+So... the segfault is in musl, not in `fd`!?
+
+I immediately checked whether other basic programs on the system worked. *They
+did.* I checked when I last updated musl. *A couple of months ago, so that can't
+be it.* I checked specifically whether another Rust-based program worked. *It
+did.*
+
+`fd(1)` had been updated pretty recently, and I remembered it working correctly
+about a month ago, so maybe something specific to `fd(1)`'s usage of Rust
+triggered this segfault in musl? I wanted to make sure I could reproduce this in
+a development environment, so I cloned the `fd(1)` repository, built a debug
+release, and ran it...
+
+*It worked.* Huh!?
+
+I decided it was likely that [`portage`](https://wiki.gentoo.org/wiki/Portage),
+Gentoo's package manager, was building the program differently, so I took care
+to apply the same build flags to the development build. And what can I say:
+
+```
+error: failed to run custom build command for `crossbeam-utils v0.8.20`
+
+Caused by:
+ process didn't exit successfully: `fd/target/[...]/build-script-build`
+ (signal: 11, SIGSEGV: invalid memory reference)
+
+```
+
+... it didn't even get to build the `fd` binary proper. A segfault again, too.
+What on earth was going on? Why didn't this also happen in the `portage` build?
+
+Thankfully I now had a reproducer, so I did the only sensible thing and started
+removing random build flags until I got `fd` to build again. This was our
+culprit:
+
+```
+-Wl,-z,pack-relative-relocs
+```
+
+Already pretty out of my depth considering the fact that I couldn't fathom how
+`fd(1)` got musl to segfault on `memcpy`, I now also found that a piece of the
+puzzle required me to understand specific linker flags. *Oof.*
+
+Unsure what to do next I decided on a whim to compare the working and the
+broken binary with `readelf(1)`. The most obvious difference was that the
+working binary had its `.rela.dyn`
+[relocation](https://en.wikipedia.org/wiki/Relocation_(computing)) section
+populated with entries whilst the broken one was missing `.rela.dyn` but had
+`.relr.dyn` instead. At a loss, I stopped and went to do something else.
+
+The story would probably have ended here had I not mentioned this conundrum to
+[my partner](https://ahti.space/~nortti/) later in the day. We decided to have
+another look at the binaries. After some discussion we determined that the
+working binary was dynamically linked whilst the broken one wasn't. The other
+working Rust-based program, [`rg(1)`](https://github.com/BurntSushi/ripgrep),
+was also dynamically linked and had been built a while ago, so **at some point
+`portage` must have stopped producing Rust executables that were dynamically
+linked**. Finally some progress!
+
+At this point we need some background. Early on, Rust decided to use the
+`x86_64-unknown-linux-musl` target to provide statically-linked binaries that
+would run on a wide range of systems. Whilst support for dynamically linked
+executables on musl systems was [added back in
+2017](https://github.com/rust-lang/rust/pull/40113), the default behaviour was
+never changed, so Gentoo has to make sure to disable static linking by passing
+the `target-feature=-crt-static` flag.
+
+It does this in a system-wide fashion by setting an environment variable in
+[`/etc/env.d`](https://wiki.gentoo.org/wiki//etc/env.d):
+
+```
+$ cat /etc/env.d/50rust-bin-1.80.1
+LDPATH="/usr/lib/rust/lib"
+MANPATH="/usr/lib/rust/man"
+CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=-crt-static"
+```
+
+This setting should therefore be picked up by `portage` as well, but when I
+examined its build environment it was simply not there. So finally we come to
+the last piece of the puzzle: a [recent
+change](https://gitweb.gentoo.org/repo/gentoo.git/commit/eclass/cargo.eclass?id=27d469a2114b4ad0b3e682854c50c806753eb472)
+in how `RUSTFLAGS` are set within `portage`. Here's the important part:
+
+```bash
+local -x CARGO_TARGET_"${TRIPLE}"_RUSTFLAGS="-C strip=none -C linker=${LD_A[0]}"
+[[ ${#LD_A[@]} -gt 1 ]] && local CARGO_TARGET_"${TRIPLE}"_RUSTFLAGS+="$(printf -- ' -C link-arg=%s' "${LD_A[@]:1}")"
+local CARGO_TARGET_"${TRIPLE}"_RUSTFLAGS+=" ${RUSTFLAGS}"
+```
+
+Quoth the `bash(1)` manual:
+
+> Local variables "shadow" variables with the same name declared at previous
+> scopes. For instance, a local variable declared in a function hides a global
+> variable of the same name: references and assignments refer to the local
+> variable, leaving the global variable unmodified.
+
+When previously the `RUSTFLAGS` environment variable was only touched when
+cross-compiling, it was now overridden. To confirm, I edited the file in
+question to include the previous value, and both `fd(1)` and `rg(1)` worked
+again. Success!
+
+This whole saga was also [reported](https://bugs.gentoo.org/940197) to the
+Gentoo bug tracker and promptly fixed. A project for another day is figuring out
+exactly how a change from static linking to dynamic linking causes segfaults
+like this, because I sure would love to know the details.
diff --git a/content/24/index.md b/content/24/index.md
new file mode 100644
index 0000000..e1a0082
--- /dev/null
+++ b/content/24/index.md
@@ -0,0 +1,68 @@
++++
+date = 2024-10-14T20:33:27+02:00
+title = "A plethora of bug fixes"
+
+[taxonomies]
+tags = ["bugs", "kde", "git"]
+
+[extra]
+related = []
++++
+
+For whatever reason I've been uncovering software bugs at an unprecedented rate
+in the past 10 days. This is by no means a bad thing, I enjoy hunting down and
+fixing bugs, but it does mean that the additional overhead of drafting a post
+about each bug becomes a bit too much. So instead here's a quick overview - the
+linked patches and merge requests will have more information, if you are
+interested.
+
+### Trash size calculation in KIO
+
+I noticed this one pretty much right after starting to use
+[Dolphin](https://apps.kde.org/dolphin/) but did not end up looking into it
+until quite a bit later: when displaying the size of the items in the trash, the
+application would always show 0 bytes. This would also cause the automated
+cleanup of items to fail - Dolphin simply believed that the trash was empty.
+
+KDE uses the [KIO](https://invent.kde.org/frameworks/kio) framework to provide
+management of the trash. A [recent
+commit](https://invent.kde.org/frameworks/kio/-/commit/0ab81b6bac953b12b454bef4874946bb7fc8db85)
+had changed the construction of a
+[QDirIterator](https://doc.qt.io/qt-6/qdiriterator.html) in a way that would
+make it ignore all items when iterating over the trash directory. Thankfully the
+fix was straightforward and it was [merged
+quickly](https://invent.kde.org/frameworks/kio/-/merge_requests/1729).
+
+### git-shortlog(1) segfaults outside of a git repository
+
+This one I uncovered as I was writing a small script to give me an overview of
+commit authors in all the git repositories I had cloned locally. I was happily
+scanning through my source directory using the
+[`--author`](https://git-scm.com/docs/git-shortlog#Documentation/git-shortlog.txt---authorltpatterngt)
+flag for `git-shortlog(1)` to generate this, fully expecting git to complain
+about the few non-git directories I had. Instead of complaints, however, I got a
+segfault.
+
+Turns out that a [change back in
+May](https://github.com/git/git/commit/ab274909d4) stopped setting SHA1 as the
+default object hash. This was done to progress the slow-moving [transition to
+stronger hash functions](https://git-scm.com/docs/hash-function-transition) but
+inadvertently broke `git-shortlog(1)` whose argument parsing machinery expected
+a default hash algorithm to be set. I sent [a
+patch](https://public-inbox.org/git/20241011183445.229228-1-wolf@oriole.systems/T/#u)
+upstream.
+
+### An infinite loop in plasmashell
+
+I regularly use the
+[Activities](https://docs.kde.org/stable5/en/plasma-desktop/plasma-desktop/activities-interface.html)
+functionality in Plasma 6 and switch through my activities using Plasma's
+built-in activity manager. A couple of days ago I managed to make `plasmashell`,
+the provider for Plasma's desktop and task bar, freeze - I had hit the "up
+arrow" key in the activity filter text box when there were no results visible.
+This was perfectly reproducible, so I went to investigate.
+
+The cause of the issue was a do-while construct not handling a specific sentinel
+value, making it loop infinitely. For this one I also opened a [merge
+request](https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/2574)
+upstream.
diff --git a/content/25/index.md b/content/25/index.md
new file mode 100644
index 0000000..b17f3fe
--- /dev/null
+++ b/content/25/index.md
@@ -0,0 +1,131 @@
++++
+date = 2024-10-18T17:46:30+02:00
+title = "Understanding recent files on KDE Plasma 6"
+
+[taxonomies]
+tags = ["TIL", "kde"]
+
+[extra]
+related = []
++++
+
+Today, prompted by a question on the `#kde` channel on
+[libera](https://libera.chat/), I looked into how Plasma handles its registry of
+recently used folders and documents. Turns out it's way more complicated than I
+first thought.
+
+The question specifically was whether there was a way to programmatically add
+files to [Okular's](https://okular.kde.org/) recently opened documents, so
+that's where I started looking. I was already aware that some apps like Okular
+and [Gwenview](https://apps.kde.org/gwenview/) keep their own history
+independently from the system, and I quickly found out that Okular simply keeps
+a list of recently opened files in its configuration file `~/.config/okularrc`
+of all places.
+
+{{ img(path="recent-okular.png", format="png", alt="A screenshot of Okular,
+KDE's PDF document viewer. The application shows its welcome page, with a button
+to open a new document beside a list of recently opened documents. The latter
+contains an entry for a PDF of the POSIX Base Specifications.",
+caption="Okular's welcome page, with recent documents listed.") }}
+
+This got me thinking. Maybe it would be a decent idea to instead have Okular
+interact with the system history directly. For that I first had to understand
+how exactly it worked.
+
+### The Standard
+
+The way that I *thought* Plasma's system history worked was through
+freedesktop.org's [Desktop Bookmark
+Specification](https://www.freedesktop.org/wiki/Specifications/desktop-bookmark-spec/)
+. The gist of it is that applications read from and write to a well-known file
+`$XDG_DATA_DIR/recently-used.xbel`. Indeed that file existed and it even
+contained the relevant entry:
+
+```xml
+<bookmark href="IEEE%20Standard%20-%20POSIX%20Base%20Specifications,%20Issue%208,%202024.pdf">
+<info>
+ <metadata owner="http://freedesktop.org">
+ <mime:mime-type type="application/pdf"/>
+ <bookmark:applications>
+ <bookmark:application name="org.kde.okular" exec="okular %u" count="1"/>
+ </bookmark:applications>
+ </metadata>
+</info>
+</bookmark>
+```
+
+Okular then seemed to write to both `recently-used.xbel` and `okularrc` when
+instead it could simply access the former directly and keep all history entries
+out of its configuration file. What's more, having Okular forget its history
+would only clear the entry in `okularrc`.
+
+The most prominent place in which system history is displayed is in
+[Dolphin's](https://apps.kde.org/dolphin/) "Recent Files" panel. After clearing
+Okular's history entry I still found the document there, so it seemed obvious to
+assume that it uses `recently-used.xbel`. Dolphin lets you forget specific
+entries from history, so I confidently deleted the entry there and re-checked
+the file. Weirdly, the entry was still there even though Dolphin didn't show it
+anymore...
+
+It was time to delve into the code. Untangling all the interconnected parts took
+a while, but after a good 10 minutes, I finally knew what was going on: **There
+was another history provider.**
+
+### The Other "Standard"
+
+This is where I have to mention KDE's activities. Activities are a
+[somewhat](https://pointieststick.com/2024/02/06/whats-going-on-with-activities-in-plasma-6/)
+[ill-defined](https://invent.kde.org/plasma/kactivitymanagerd/-/issues/6)
+concept but they basically boil down to the idea of providing a different
+computing space depending on what you are doing at the moment. In reality the
+most obvious user-facing activity feature in Plasma 6 is that you can customize
+your task bar and wallpaper per activity so you could consider it an extension
+of virtual desktops - applications open on one activity won't be shown once you
+switch to another.
+
+Crucially, however, the activity subsystem
+[`kactivitymanagerd`](https://invent.kde.org/plasma/kactivitymanagerd/) is
+also used to manage recently opened files. I imagine the plan is (or was) to
+enable tracking file history per activity, but in all my testing I could not get
+this to work - history seems to be global. So what this essentially means is
+that an application might, and most probably will:
+
+1) Keep its own history, most of the time through
+[KRecentFilesAction](https://api.kde.org/frameworks/kconfigwidgets/html/classKRecentFilesAction.html)
+and a simplistic history implementation. The data here is exclusively accessed
+by the application itself.
+
+2) Keep its history in the desktop-agnostic `recently-used.xbel` file. In KDE's
+case this usually does not happen in the application itself but instead through
+its [KIO](https://invent.kde.org/frameworks/kio) framework. Other desktop
+systems might read and display this data, but KDE seems to be write-only:
+history is appended, but never shown to the user.
+
+3) Keep its history in an [SQLite](https://www.sqlite.org/) database under
+`~/.local/share/kactivitymanagerd`, managed by a daemon. This is what you see in
+Dolphin and what you can manage under "Recent Files" in the system settings.
+
+It also means that if you want to tweak history management, forget documents or
+folders, or turn the thing(s) off, you have to look in a multitude of places:
+
+1) If the application provides a setting to manage or disable its own history,
+use that. If that's not available (like in Okular) you're out of luck. Disabling
+an application's own history will not impact the other two history providers -
+you will still see recent files in Dolphin and elsewhere in the system.
+
+2) There's been
+[ongoing](https://invent.kde.org/frameworks/kio/-/merge_requests/1670)
+[work](https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/4560) to
+streamline management of entries in `recently-used.xbel`, spurred by [this
+bug](https://bugs.kde.org/show_bug.cgi?id=480276). You may also use the
+undocumented `UseRecent`, `MaxEntries`, and `IgnoreHidden`
+[options](https://invent.kde.org/frameworks/kio/-/blob/57342c46bf3789cd6f7b07ec33086a24f26223ad/src/core/krecentdocument.cpp#L512-515)
+read from `~/.config/kdeglobals`.
+
+3) Tweak `kactivitymanagerd` history in system settings under "Recent Files".
+
+### Forgetting History
+
+With all this in mind my immediate reaction is to shy away from the whole
+endeavour to have Okular interface with the system history - there's too many
+moving parts, some of which aren't even yet well-defined on KDE's side.
diff --git a/content/25/recent-okular.png b/content/25/recent-okular.png
new file mode 100644
index 0000000..08dc0bb
--- /dev/null
+++ b/content/25/recent-okular.png
Binary files differ
diff --git a/content/26/icon-day.png b/content/26/icon-day.png
new file mode 100644
index 0000000..494e5b2
--- /dev/null
+++ b/content/26/icon-day.png
Binary files differ
diff --git a/content/26/icon-night.png b/content/26/icon-night.png
new file mode 100644
index 0000000..ffd6d35
--- /dev/null
+++ b/content/26/icon-night.png
Binary files differ
diff --git a/content/26/index.md b/content/26/index.md
new file mode 100644
index 0000000..e284afc
--- /dev/null
+++ b/content/26/index.md
@@ -0,0 +1,67 @@
++++
+date = 2024-10-19T20:02:17+02:00
+title = "Night-time icons for Plasma's DWD backend"
+
+[taxonomies]
+tags = ["contrib", "kde"]
+
+[extra]
+related = []
++++
+
+For a couple of weeks now I have been using Plasma's built-in weather report
+system. It supports various different sources for weather observations and
+forecasts, one of which is my preferred service, [Deutscher
+Wetterdienst](https://www.dwd.de) (DWD). Once the report location is set up, Plasma
+displays the current conditions in the tray - clicking on it gives you an
+overview of the next 7 days.
+
+I've been quite pleased with this; a quick glance at the icon tells you
+everything you need to know and the forecast is only one click away. It even
+supports DWD's official warning system. There is one small problem with the tray
+icon, however. See if you can spot it:
+
+{{ img(path="icon-day.png", format="png", alt="A screenshot of Plasma's tray
+showing a clipboard icon, a media control icon, and a weather icon. The weather
+icon is displaying a sun with few clouds in front. The tray is also showing the
+time. It is 23:57.", caption="Something's not quite right...") }}
+
+That's right, I couldn't have possibly taken this image given this post's
+timestamp is before 23:57! Good catch. More importantly, however, the icon is
+showing the sun when realistically it's going to be dark outside. This has been
+annoying me to no end, so a couple of days ago I decided to fix it.
+
+Since Plasma already comes with weather icons suited for night-time use, I
+really only needed a reliable way to tell when to switch to those. After some
+spelunking in DWD's [two](https://dwd.api.bund.dev/)
+[APIs](https://listed.to/@DieSieben/7851/api-des-deutschen-wetterdienstes), I
+found that the latter contains sunrise and sunset times for its forecast
+endpoint. The weather report system was already using this endpoint to fetch
+forecast data, so it was a rather simple lookup of two values in the API and
+then comparing the current observation time to those.
+
+So, after some preparatory work to make date parsing more robust, I sent a
+[merge
+request](https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/4848)
+upstream which was promptly approved and merged. Finally I could rest easy after
+dusk, not having to worry about a sun chasing me around.
+
+{{ img(path="icon-night.png", format="png", alt="A screenshot of Plasma's tray
+showing a clipboard icon, a media control icon, and a weather icon. The weather
+icon is displaying a crescent moon with few clouds in front. The tray is also
+showing the time. It is 18:57.", caption="... much better!") }}
+
+There's some more stuff that could be improved in the DWD backend, like station
+availability. As far as I can tell, when using the open API, DWD limits access
+to weather data depending on whether or not they have the full rights to it. The
+API might therefore simply deny access to a station even though it is listed in
+in [the official index](https://www.dwd.de/DE/leistungen/klimadatendeutschland/statliste/statlex_html.html?view=nasPublication&nn=16102).
+There's no way to find out which station is accessible except to try them all.
+To get around this, the DWD backend uses a [very simple
+heuristic](https://invent.kde.org/plasma/plasma-workspace/-/blob/69a52cd6fbde80b087f59a633b4ff216c27ddff0/dataengines/weather/ions/dwd/ion_dwd.cpp#L396-403)
+based on the first digit of the station ID, since all stations starting with 0
+or 1 seem to work. However, this ignores a number of other, still accessible,
+stations.
+
+I'm planning to look into this further, hopefully finding a better solution so
+that users of the DWD backend have access to as many stations as possible.
diff --git a/content/27/index.md b/content/27/index.md
new file mode 100644
index 0000000..42084e3
--- /dev/null
+++ b/content/27/index.md
@@ -0,0 +1,65 @@
++++
+date = 2024-11-05T23:39:57+01:00
+title = "Adding Zero RPM controls to the RX 7000 series"
+
+[taxonomies]
+tags = ["contrib"]
+
+[extra]
+related = []
++++
+
+Last year I upgraded to an AMD RX 7900XTX, mainly to play Alan Wake II. Just
+like my previous card the XTX has a "Zero RPM" feature: it turns off its fans
+fully if the junction temperature, the hottest part of the GPU, is below a
+certain threshold. With the fans off, the GPU relies on its massive heatsink for
+passive cooling. Even in a very well-ventilated case, however, this will mean
+that the area around the GPU will heat up considerably. For me the fans turn off
+at around 55°C; the component closest to the GPU, an NVMe M.2 SSD, will usually
+slowly heat up to around 48°C whilst idling.
+
+Even under load the SSD never exceeds any temperature threshold, so
+realistically it should be fine, but I'm simply not happy with the amount of
+thermal energy sitting around in there if it could be expelled easily by turning
+on the fans. Worse still, the logic for toggling the fans is not very well
+thought-out, and in the worst case the fans are on for one minute only to be off
+for the next one, ad nauseam.
+
+With my previous GPU turning off "Zero RPM" was pretty simple. Using the
+[upp(1)](https://github.com/sibradzic/upp) tool you could toggle the feature in
+the GPU's so-called PowerPlay tables. It's a simple job, then, to write a
+systemd service to turn off "Zero RPM" on system boot.
+
+Sadly this is no longer possible on 7000 series cards as there is no more direct
+access to the PowerPlay tables. Instead a new framework using
+[sysfs](https://www.kernel.org/doc/html/latest/filesystems/sysfs.html) for
+managing PowerPlay features was introduced. [Fan
+curve](https://gitlab.freedesktop.org/drm/amd/-/issues/2402) controls were added
+after a while (and a lot of moaning by users), but there was no such knob for
+the "Zero RPM" feature. A couple of months ago a [feature
+request](https://gitlab.freedesktop.org/drm/amd/-/issues/3489) was opened for
+it, but nothing much happened on AMD's side.
+
+Initially hopeful for a reasonably quick resolution, I was getting more and more
+annoyed after a while by the lack of this seemingly simple toggle, so I finally
+caved and proceeded to have a look at it myself. The hardest part was getting
+started with reading
+[amdgpu](https://gitlab.freedesktop.org/agd5f/linux/-/tree/amd-staging-drm-next/drivers/gpu/drm/amd?ref_type=heads)
+code. The code base is absolutely massive and I had no real idea where to start.
+Since fan curve controls already existed I thought it best to find the commit
+that introduced them. After a quick search I found [the relevant
+commit](https://gitlab.freedesktop.org/agd5f/linux/-/commit/eedd5a343d22) and
+had a better understanding of which parts of the code to change.
+
+So, after a while of tweaking and twiddling I had a working prototype and I
+could finally have my GPU run its fans at all times. I knew a lot of people were
+also waiting for this feature, so I [sent a
+patch](https://lists.freedesktop.org/archives/amd-gfx/2024-October/115857.html)
+upstream. After some short feedback and [the addition of another
+feature](https://lists.freedesktop.org/archives/amd-gfx/2024-October/116274.html)
+the series was accepted, and is going to be part of the kernel sometime soon.
+
+With the fans now running at all times I can happily report that ambient
+temperatures have dropped by more than 10°C and the SSD usually does not exceed
+40°C when idling. Even better I do feel quite proud to have finally contributed
+code to the kernel.