ITADN

We are not using `Card._verify_card()`

#44Openmazunki 创建于 2025-07-25
M
mazunkicommented
I just got a new Radeon RX9060XT card, and it seems it's lacking the `pwm1_enable` endpoint. I only have `pwm1`, `pwm1_min` and `pwm1_max`, which is part of `0d:00.0` (which seems to match the `pcieport` driver; and no pwm endpoints for `0e:00.0` (controlled by `amdgpu`). I am not exactly sure why this card is split like this, but it's not really relevant to this report. We have a `_verify_card()` function, but this is only used in `tests/test_card.py`, and not employed during runtime. As a consequence, the controller crashes when attempting to enable pwm control during runtime. I believe we should test for this preemptively, and at the very least catch the error to avoid a python traceback. Furthermore I think it would be nice to suggest what paths are missing, so the user can know what to look for. What do you think? Do you think we should test for this as we start up (maybe right after we read the config?), or prior to doing any and all sysfs executions? I have a few weeks free, so I can look at making a PR (and also tackle the testfile sanity check during SIGHUP we talked about last time). Let me know what you prefer :) ``` ~ amdfan manage [22:40:27] INFO Launching the amdfan controller controller.py:349 INFO Found configuration file at /etc/amdfan.yml controller.py:364 INFO Loading configuration controller.py:260 INFO Configuration succesfully loaded controller.py:263 INFO Controller is running controller.py:279 ╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮ │ /usr/lib/python-exec/python3.13/amdfan:8 in <module> │ │ │ │ 5 from amdfan.__main__ import main │ │ 6 if __name__ == "__main__": │ │ 7 │ sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) │ │ ❱ 8 │ sys.exit(main()) │ │ 9 │ │ │ │ /usr/lib/python3.13/site-packages/click/core.py:1442 in __call__ │ │ │ │ 1439 │ │ │ 1440 │ def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: │ │ 1441 │ │ """Alias for :meth:`main`.""" │ │ ❱ 1442 │ │ return self.main(*args, **kwargs) │ │ 1443 │ │ 1444 │ │ 1445 class _FakeSubclassCheck(type): │ │ │ │ /usr/lib/python3.13/site-packages/click/core.py:1363 in main │ │ │ │ 1360 │ │ try: │ │ 1361 │ │ │ try: │ │ 1362 │ │ │ │ with self.make_context(prog_name, args, **extra) as ctx: │ │ ❱ 1363 │ │ │ │ │ rv = self.invoke(ctx) │ │ 1364 │ │ │ │ │ if not standalone_mode: │ │ 1365 │ │ │ │ │ │ return rv │ │ 1366 │ │ │ │ │ # it's not safe to `ctx.exit(rv)` here! │ │ │ │ /usr/lib/python3.13/site-packages/click/core.py:1830 in invoke │ │ │ │ 1827 │ │ │ │ super().invoke(ctx) │ │ 1828 │ │ │ │ sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) │ │ 1829 │ │ │ │ with sub_ctx: │ │ ❱ 1830 │ │ │ │ │ return _process_result(sub_ctx.command.invoke(sub_ctx)) │ │ 1831 │ │ │ │ 1832 │ │ # In chain mode we create the contexts step by step, but after the │ │ 1833 │ │ # base command has been invoked. Because at that point we do not │ │ │ │ /usr/lib/python3.13/site-packages/click/core.py:1226 in invoke │ │ │ │ 1223 │ │ │ echo(style(message, fg="red"), err=True) │ │ 1224 │ │ │ │ 1225 │ │ if self.callback is not None: │ │ ❱ 1226 │ │ │ return ctx.invoke(self.callback, **ctx.params) │ │ 1227 │ │ │ 1228 │ def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: │ │ 1229 │ │ """Return a list of completions for the incomplete value. Looks │ │ │ │ /usr/lib/python3.13/site-packages/click/core.py:794 in invoke │ │ │ │ 791 │ │ │ │ 792 │ │ with augment_usage_errors(self): │ │ 793 │ │ │ with ctx: │ │ ❱ 794 │ │ │ │ return callback(*args, **kwargs) │ │ 795 │ │ │ 796 │ def forward(self, cmd: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: │ │ 797 │ │ """Similar to :meth:`invoke` but fills in default keyword │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/commands.py:59 in run_manager │ │ │ │ 56 │ help="Run the controller in the foreground.\n\nThis command mostly exists for short- │ │ 57 ) │ │ 58 def run_manager(): │ │ ❱ 59 │ FanController.start_manager(daemon=False) │ │ 60 │ │ 61 │ │ 62 class FileDescriptorOpt(click.ParamType): │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/controller.py:377 in start_manager │ │ │ │ 374 │ │ signal.signal(signal.SIGHUP, controller.reload_config) │ │ 375 │ │ signal.signal(signal.SIGTERM, controller.terminate) │ │ 376 │ │ signal.signal(signal.SIGINT, controller.terminate) │ │ ❱ 377 │ │ controller.main() │ │ 378 │ │ LOGGER.info("Goodbye") │ │ 379 │ │ 380 │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/controller.py:283 in main │ │ │ │ 280 │ │ while self._running: │ │ 281 │ │ │ for name, card in self._scanner.cards.items(): │ │ 282 │ │ │ │ # print("refreshing card", name, card) │ │ ❱ 283 │ │ │ │ self.refresh_card(name, card) │ │ 284 │ │ │ │ │ 285 │ │ │ self._stop_event.wait(self._frequency) │ │ 286 │ │ LOGGER.info("Stopped controller") │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/controller.py:330 in refresh_card │ │ │ │ 327 │ │ │ │ return │ │ 328 │ │ │ │ 329 │ │ if apply: │ │ ❱ 330 │ │ │ card.set_fan_speed(speed) │ │ 331 │ │ │ self._last_temp = temp │ │ 332 │ │ │ 333 │ @classmethod │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/controller.py:200 in set_fan_speed │ │ │ │ 197 │ │ else: │ │ 198 │ │ │ speed = int((self.fan_max - self.fan_min) / 100 * speed + self.fan_min) │ │ 199 │ │ │ │ ❱ 200 │ │ self.set_system_controlled_fan(False) │ │ 201 │ │ return self.write_endpoint("pwm1", speed) │ │ 202 │ │ 203 │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/controller.py:188 in set_system_controlled_fan │ │ │ │ 185 │ │ system_controlled_fan = 2 │ │ 186 │ │ manual_control = 1 │ │ 187 │ │ │ │ ❱ 188 │ │ self.write_endpoint( │ │ 189 │ │ │ "pwm1_enable", system_controlled_fan if state else manual_control │ │ 190 │ │ ) │ │ 191 │ │ │ │ /usr/lib/python3.13/site-packages/amdfan/controller.py:158 in write_endpoint │ │ │ │ 155 │ │ # debug here, troubleshooting 7900xtx │ │ 156 │ │ # print("writing to endpoint", endpoint, "data", data) │ │ 157 │ │ try: │ │ ❱ 158 │ │ │ with open(self._endpoints[endpoint], "w", encoding="utf8") as endpoint_file: │ │ 159 │ │ │ │ return endpoint_file.write(str(data)) │ │ 160 │ │ except PermissionError: │ │ 161 │ │ │ LOGGER.error("Failed writing to devfs file, are you running as root?") │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ KeyError: 'pwm1_enable' [1] ~ ```
3 条评论