diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af6611..9ec4114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project (partially) adheres to [Semantic Versioning](https://semver.org ## [Unreleased] ### Added +- `GPU` support for Raspberry Pi - `Model` support for Raspberry Pi 5+ - `none` logo style to completely hide distribution logo - AppArmor confinement profile (included in Debian and AUR packages) diff --git a/apparmor.profile b/apparmor.profile index e64cdd2..51432ad 100644 --- a/apparmor.profile +++ b/apparmor.profile @@ -57,6 +57,7 @@ profile archey4 /usr/{,local/}bin/archey{,4} { # [GPU] entry /{,usr/}bin/lspci PUx, + @{sys}/kernel/debug/dri/[0-9]*/{name,v3d_ident} r, # [Hostname] entry /etc/hostname r, diff --git a/archey/entries/gpu.py b/archey/entries/gpu.py index bcb1f4d..9b274f8 100644 --- a/archey/entries/gpu.py +++ b/archey/entries/gpu.py @@ -1,13 +1,29 @@ """GPU information detection class""" +import contextlib +import os import platform import re +from pathlib import Path from shlex import split from subprocess import DEVNULL, CalledProcessError, check_output from typing import List from archey.entry import Entry +LINUX_DRI_DEBUGFS_PATH = Path("/sys/kernel/debug/dri") + +# See +LINUX_DRM_DEV_MAJOR = 226 +LINUX_MAX_DRM_MINOR_PRIMARY = 63 + +# From +V3D_REV_TO_VC_VERSION = { + "1": "VideoCore IV", # Raspberry Pi 1 -> 3 + "4.2": "VideoCore VI", # Raspberry Pi 4 + "7.1": "VideoCore VII", # Raspberry Pi 5 +} + class GPU(Entry): """Relies on `lspci` or `pciconf` to retrieve graphical device(s) information""" @@ -18,7 +34,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if platform.system() == "Linux": - self.value = self._parse_lspci_output() + self.value = self._parse_lspci_output() + self._videocore_chipsets() else: # Darwin or any other BSD-based system. self.value = self._parse_system_profiler() or self._parse_pciconf_output() @@ -49,6 +65,54 @@ def _parse_lspci_output() -> List[str]: return gpus_list + def _videocore_chipsets(self) -> List[str]: + """ + Browse /dev/dri/card* to look for devices managed by vc4/v3d driver. From there, infer + VideoCore chipset version from v3d driver revision (through kernel debugfs). + + See . + """ + videocore_chipsets = [] + + for device in Path("/dev/dri").glob("card*"): + # safety checks to make sure DRI cards are primary interface node devices + # Python < 3.10, can't use `follow_symlinks=False` with `stat` + st_rdev = device.stat().st_rdev + if ( + os.major(st_rdev) != LINUX_DRM_DEV_MAJOR + or os.minor(st_rdev) > LINUX_MAX_DRM_MINOR_PRIMARY + ): + continue + + with contextlib.suppress(OSError): + # assert device card driver is vc4 or v3d + if (LINUX_DRI_DEBUGFS_PATH / str(os.minor(st_rdev)) / "name").read_text().split()[ + 0 + ] not in ("vc4", "v3d"): + continue + + # retrieve driver (major.minor SemVer segments) revision from v3d_ident file + # Note : there seems to be multiple "Revision" fields for v3d driver (1 global + 1 + # per core). `re.search` will return the first match. + v3d_revision = re.search( + r"Revision:\s*(\d(?:\.\d)?)", + (LINUX_DRI_DEBUGFS_PATH / str(os.minor(st_rdev)) / "v3d_ident").read_text(), + ) + if v3d_revision is None: + self._logger.warning("could not find v3d driver revision for %s", device) + continue + + try: + videocore_chipsets.append(V3D_REV_TO_VC_VERSION[v3d_revision.group(1)]) + except KeyError: + self._logger.warning( + "could not infer VideoCore version from %s driver version %s", + device, + v3d_revision.group(1), + ) + + return videocore_chipsets + @staticmethod def _parse_system_profiler() -> List[str]: """Based on `system_profiler` output, return a list of video chipsets model names""" diff --git a/archey/test/entries/test_archey_gpu.py b/archey/test/entries/test_archey_gpu.py index 353e21a..291d28a 100644 --- a/archey/test/entries/test_archey_gpu.py +++ b/archey/test/entries/test_archey_gpu.py @@ -1,6 +1,9 @@ """Test module for Archey's GPU detection module""" +import sys +import tempfile import unittest +from pathlib import Path from unittest.mock import MagicMock, call, patch from archey.configuration import DEFAULT_CONFIG @@ -56,6 +59,100 @@ def test_parse_lspci_output(self, _): self.assertListEqual(GPU._parse_lspci_output(), ["GPU-Manufacturer GPU-MODEL-NAME"]) # pylint: enable=protected-access + @unittest.skipUnless( + sys.platform.startswith("linux"), + "major/minor device types differ between UNIX implementations", + ) + @patch("archey.entries.gpu.Path") + def test_videocore_chipsets(self, path_mock): + """Check `_videocore_chipsets` behavior""" + gpu_instance_mock = HelperMethods.entry_mock(GPU) + + # Prepare mocks for `Path("...").glob("...")` and device major/minor checks. + device_dri_card_path_mock = MagicMock() + device_dri_card_path_mock.stat.return_value = MagicMock( + st_rdev=57856 # device type: 226, 0 + ) + path_mock.return_value.glob.return_value = [device_dri_card_path_mock] + + # pylint: disable=protected-access + + # create a fake debugfs kernel tree + with tempfile.TemporaryDirectory() as temp_dir, patch( + "archey.entries.gpu.LINUX_DRI_DEBUGFS_PATH", Path(temp_dir) + ): + debugfs_dri_0 = Path(temp_dir) / "0" + debugfs_dri_0.mkdir(parents=True, exist_ok=True) + + # There is a /dev/dri/card0, but its driver information are (currently) missing + self.assertListEmpty(gpu_instance_mock._videocore_chipsets(gpu_instance_mock)) + + # VideoCore IV (Raspberry Pi 1 -> 3) + (debugfs_dri_0 / "name").write_text( + "vc4 dev=XXXX:YY:ZZ.T master=pci:XXXX:YY:ZZ.T unique=XXXX:YY:ZZ.T\n" + ) + (debugfs_dri_0 / "v3d_ident").write_text( + """\ +Revision: 1 +Slices: 3 +TMUs: 6 +QPUs: 12 +Semaphores: 16 +""" + ) + self.assertListEqual( + gpu_instance_mock._videocore_chipsets(gpu_instance_mock), ["VideoCore IV"] + ) + + # VideoCore VI (Raspberry Pi 4) + (debugfs_dri_0 / "name").write_text( + "v3d dev=XXXX:YY:ZZ.T master=pci:XXXX:YY:ZZ.T unique=XXXX:YY:ZZ.T\n" + ) + (debugfs_dri_0 / "v3d_ident").write_text( + """\ +Revision: 4.2.14.0 +MMU: yes +TFU: yes +TSY: yes +MSO: yes +L3C: no (0kb) +Core 0: + Revision: 4.2 + Slices: 2 + TMUs: 2 + QPUs: 8 + Semaphores: 0 + BCG int: 0 + Override TMU: 0 +""" + ) + self.assertListEqual( + gpu_instance_mock._videocore_chipsets(gpu_instance_mock), ["VideoCore VI"] + ) + + # VideoCore VII (Raspberry Pi 5) + (debugfs_dri_0 / "name").write_text( + "v3d dev=XXXX:YY:ZZ.T master=pci:XXXX:YY:ZZ.T unique=XXXX:YY:ZZ.T\n" + ) + (debugfs_dri_0 / "v3d_ident").write_text( + """\ +Revision: 7.1.7.0 +MMU: yes +TFU: no +MSO: yes +L3C: no (0kb) +Core 0: + Revision: 7.1 + Slices: 4 + TMUs: 4 + QPUs: 16 + Semaphores: 0 +""" + ) + self.assertListEqual( + gpu_instance_mock._videocore_chipsets(gpu_instance_mock), ["VideoCore VII"] + ) + @patch( "archey.entries.gpu.check_output", side_effect=[