Skip to content

Commit

Permalink
[GPU] Adds support for Raspberry Pi (VideoCore chipsets)
Browse files Browse the repository at this point in the history
> closes #130
  • Loading branch information
HorlogeSkynet committed Sep 29, 2024
1 parent e11cbfc commit 26af175
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions apparmor.profile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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,
Expand Down
66 changes: 65 additions & 1 deletion archey/entries/gpu.py
Original file line number Diff line number Diff line change
@@ -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 <https://unix.stackexchange.com/a/725968>
LINUX_DRM_DEV_MAJOR = 226
LINUX_MAX_DRM_MINOR_PRIMARY = 63

# From <https://www.lambda-v.com/texts/programming/gpu/gpu_raspi.html#id_sec_vcdisasm_examples>
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"""
Expand All @@ -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()
Expand Down Expand Up @@ -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 <https://unix.stackexchange.com/a/393324>.
"""
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"""
Expand Down
97 changes: 97 additions & 0 deletions archey/test/entries/test_archey_gpu.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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=[
Expand Down

0 comments on commit 26af175

Please sign in to comment.