diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..52994bb --- /dev/null +++ b/docs/README.md @@ -0,0 +1,383 @@ +# Deepcool + +An unofficial Linux kernel module Provides early boot support and sysfs +interface for monitoring and controlling device display modes for Deepcool +Digital USB HID devices (CPU coolers, AIOs, cases). The module runs directly in +kernel space, providing early boot support and standard Linux interfaces for +controlling Deepcool Digital USB HID devices. + +## Motive + +[deepcool-digital-linux]: https://github.com/Nortank12/deepcool-digital-linux + +This project exists solely because I hoped to replace the userspace daemon with +a kernel module that is always loaded. Simply put, I don't need it to be an +userspace daemon, and I am under the impression that using the Linux kernel APIs +directly will yield be better performance, or at least less overhead. + +[deepcool-digital-linux] is an userspace daemon that does a little more than +this project, and is a more suitable replacement if you are not looking to taint +your kernel with out-of-tree modules. Not that a "tainted kernel" is inherently +bad, though. There are different usecases. See below for a better comparison +between **Deepcool** (this module) and **deepcool-digital-linux**, the userspace +daemon. + +## Features + +The primary feature of Deepcool is that it supports early boot, or in other +words, its functionality fully available _as soon as you reach the kernel_. Rest +of the functionality is almost identical with little caveats. + +- **Real-time Monitoring**: CPU temperature, usage, power, frequency +- **Display Modes**: Auto-cycling, CPU temp, CPU usage, CPU power, and more +- **Sysfs Interface**: Standard Linux interface for configuration +- **Automatic Device Detection**: Supports most Deepcool Digital product series +- **Multiple Device Series Support**: + - AK Series (AK400 DIGITAL, AK500 DIGITAL, AK620 DIGITAL) + - AK400 PRO, AK620 PRO + - LS Series + - AG Series + - LQ Series + - LD Series + - LP Series + - CH Series (Gen1, Gen2, CH510) + +> [!INFO] +> **For device support, please refer to the +> [device support matrix](#device-support-matrix) below** + +If you believe there is anything missing that belongs in a kernel module, please +feel free to let me know. I'd be happy to discuss. + +## Usage + +To use Deepcool, you must load it in your kernel. The installation steps may +differ based on your distro, but most distros should be supported out of the +box. + +### Requirements + +- Linux kernel 5.10 or later +- Kernel headers for your running kernel +- Build essentials (gcc, make) +- Root/sudo access for installation + +#### Kernel Configuration + +The following kernel options must be enabled: + +```plaintext +CONFIG_HID=y or m +CONFIG_HIDRAW=y or m +CONFIG_USB_HID=y or m +CONFIG_THERMAL=y +CONFIG_CPU_FREQ=y +CONFIG_CPUFREQ_STATS=y +CONFIG_SYSFS=y +``` + +Most of those features are enabled on most kernels, but is best to check before +you build. `zcat` can be used to check the configuration for your current kernel +configuration: + +```sh +$ zcat /proc/config.gz | grep CONFIG_HID +# => CONFIG_HIDRAW=y +``` + +For Intel RAPL power monitoring: + +```plaintext +CONFIG_POWERCAP=y +CONFIG_INTEL_RAPL=y or m +``` + +### Building + +Once you confirm that you meet the requirements, you may build the kernel +module. To build, navigate to the cloned directory and run `make`. + +```bash +$ make +#=> The module will be built as `deepcool.ko`. +``` + +## Installation + +### Manual Installation + +[DKMS framework]: https://wiki.archlinux.org/title/Dynamic_Kernel_Module_Support + +The Makefile provides a task to install Deepcool for Linux distributions using +the [DKMS framework]. NixOS should refer to the section below. + +```bash +# Build the module +make + +# Install to system +sudo make dkms-install + +# Load the module +sudo modprobe deepcool +``` + +## Automatic Loading on Boot + +Create a module configuration file: + +```bash +sudo tee /etc/modules-load.d/deepcool_digital.conf < mode + +# Available modes: +# - auto (cycle between supported metrics) +# - cpu_temp (CPU temperature) +# - cpu_usage (CPU usage percentage) +# - cpu_power (CPU power consumption) +# - cpu_freq (CPU frequency) +# - cpu_fan (CPU fan speed, if supported) +# - gpu_temp (GPU temperature, if supported) +# - gpu_usage (GPU usage, if supported) +# - gpu_power (GPU power, if supported) +``` + +**`update_interval`** - Update interval in milliseconds (100-2000) + +```bash +# View current interval +cat update_interval + +# Set to 500ms +echo 500 > update_interval +``` + +**`fahrenheit`** - Temperature unit (0=Celsius, 1=Fahrenheit) + +```bash +# Use Fahrenheit +echo 1 > fahrenheit + +# Use Celsius +echo 0 > fahrenheit +``` + +**`alarm`** - Temperature alarm (0=off, 1=on) + +```bash +# Enable alarm +echo 1 > alarm + +# Disable alarm +echo 0 > alarm +``` + +#### Sensor Attributes (Read-Only) + +**`cpu_temp`** - CPU temperature (°C or °F based on fahrenheit setting) + +```bash +cat cpu_temp +``` + +**`cpu_usage`** - CPU usage percentage (0-100) + +```bash +cat cpu_usage +``` + +**`cpu_power`** - CPU power consumption (Watts) + +```bash +cat cpu_power +``` + +**`cpu_freq`** - CPU frequency (MHz) + +```bash +cat cpu_freq +``` + +### Examples + +#### Set CPU temperature display with Fahrenheit + +```bash +echo "cpu_temp" > /sys/bus/hid/drivers/deepcool_digital/0003:3633:0007.XXXX/mode +echo 1 > fahrenheit +echo 1 > alarm +``` + +#### Auto-cycle between metrics at 1-second intervals + +```bash +echo "auto" > /sys/bus/hid/drivers/deepcool_digital/0003:3633:0007.XXXX/mode +echo 1000 > /sys/bus/hid/drivers/deepcool_digital/0003:3633:0007.XXXX/update_interval +``` + +#### Monitor CPU power consumption + +```bash +echo "cpu_power" > /sys/bus/hid/drivers/deepcool_digital/0003:3633:0007.XXXX/mode +``` + +#### Create a simple monitoring script + +```bash +#!/usr/bin/env bash +DEVICE="/sys/bus/hid/drivers/deepcool_digital/0003:3633:0007.XXXX" + +while true; do + echo "CPU Temp: $(cat $DEVICE/cpu_temp)°C" + echo "CPU Usage: $(cat $DEVICE/cpu_usage)%" + echo "CPU Power: $(cat $DEVICE/cpu_power)W" + echo "CPU Freq: $(cat $DEVICE/cpu_freq)MHz" + echo "---" + sleep 1 +done +``` + +## Device Support Matrix + + + +| Device Series | Auto Mode | Alarm | Fahrenheit | Secondary Display | Rotation | +| ------------- | --------- | ------ | ---------- | ----------------- | -------- | +| AK Series | ✓ | ✓ | ✓ | ✗ | ✗ | +| AK400 PRO | ✓ | HW | ✓ | ✗ | ✗ | +| AK620 PRO | ✓ | HW | ✓ | ✗ | ✗ | +| LS Series | ✓ | ✓ | ✓ | ✗ | ✗ | +| AG Series | ✓ | ✓ | ✗ | ✗ | ✗ | +| LP Series | ✓ | ✗ | ✓ | ✓ | ✓ | +| LQ Series | ✓ | ✓ | ✓ | ✗ | ✗ | +| CH Series | ✓ | varies | varies | varies | ✗ | + + + +**Legend:** + +| Supported | Not Supported | Hardware-Controlled (Fixed thresholds) | +| :-------: | :-----------: | :------------------------------------: | +| ✓ | ✗ | HW | + +## How It Works + +### Initialization + +1. Module loads and registers HID driver +2. Kernel calls `probe()` when device is detected +3. Driver identifies device series and capabilities +4. Sysfs attributes are created +5. Work queue is scheduled for periodic updates + +### Runtime Operation + +``` +Timer fires (every update_interval ms) + ↓ +Work queue handler executes + ↓ +Read CPU sensors: + * Temperature from thermal subsystem + * Usage from kernel CPU statistics + * Frequency from CPUFreq + * Power from Intel RAPL + ↓ +Build device-specific packet + ↓ +Send HID output report over USB + ↓ +Display updates + ↓ +Reschedule work queue +``` + +### User Interaction + +``` +User writes to sysfs attribute + ↓ +Kernel calls store() handler + ↓ +Validate input + ↓ +Update device configuration + ↓ +Next work cycle uses new configuration +``` + +## Contributing + +Contributions are always welcome! When submitting patches, please try to follow +the several recommendations: + +- Tabs and 8 character indentations are heretical. A cardinal sin. Please don't + use those. +- Testing on your own hardware is recommended, but not a hard-requirement +- If there is device-specific behaviour (or non-behaviour), please document it! +- Include dmesg output for new devices + +Refer to [BUILD.md](./BUILD.md) for additional notes on working with and +building Deepcool. + +## License + +GPL-2.0-only + +## Attributions + +[Nortank12]: https://github.com/Nortank12 +[zenpower]: https://github.com/ocerman/zenpower + +There are a few projects that I would like to give credit to. Namely I'd like to +thank the [deepcool-digital-linux] project for the inspiration and the device +data. This project would be incredibly annoying to do without the efforts of +[Nortank12]. Please make sure to give them some love! + +I'd like to extend my thanks to the [zenpower] project that provided insight +into developing Linux kernel modules, and the DKMS installation tasks. Thank you +:) + +## See Also + +- [deepcool-digital-linux]: an userspace daemon providing similar functionality, + with better GPU support. +- Linux HID documentation: `Documentation/hid/` in kernel source +- Sysfs documentation: `Documentation/filesystems/sysfs.txt` diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..934192c --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7fb4142 --- /dev/null +++ b/flake.nix @@ -0,0 +1,96 @@ +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + + outputs = { + self, + nixpkgs, + }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + lib = nixpkgs.lib; + in { + packages."${system}" = let + inherit (pkgs.linuxPackages_latest) kernel; + kmod = pkgs.callPackage ./nix/kmod.nix {inherit kernel;}; + in { + inherit kernel kmod; + }; + + nixosConfigurations."gamma" = let + inherit (lib) nixosSystem; + inherit (lib.modules) mkDefault; + in + nixosSystem { + system = null; + modules = [ + ({modulesPath, ...}: { + imports = [(modulesPath + "/profiles/qemu-guest.nix")]; + networking.hostName = "gamma"; + + boot = { + growPartition = false; + kernelParams = ["console=ttyAMA0,115200n8" "console=tty0"]; + consoleLogLevel = mkDefault 7; # ground control to kernel + }; + + # Empty password + # root can login without a password + users.extraUsers.root.initialHashedPassword = ""; + + # Nixpkgs options + nixpkgs.pkgs = pkgs; # discard everything else, follow flake `pkgs` + + # Packages + environment.systemPackages = [pkgs.microfetch]; + + # Bootable + fileSystems."/boot" = { + device = "/dev/vda1"; + fsType = "vfat"; + }; + + fileSystems."/" = { + device = "/dev/vda2"; + fsType = "ext4"; + }; + + boot.loader.grub = { + efiSupport = true; + efiInstallAsRemovable = true; + device = "nodev"; + }; + + # Kernel fun + boot = { + # Use kernel package defined in flake.nix + kernelPackages = pkgs.linuxPackagesFor self.packages.${system}.kernel; # exposed kernel + + # Get test module from flake outputs + extraModulePackages = [self.packages.${system}.kmod]; + + # Load module from package. + # Alternatively, `$ modprobe test` would work too + kernelModules = ["deepcool"]; + }; + + # Make it smaller + documentation = { + doc.enable = false; + man.enable = false; + nixos.enable = false; + info.enable = false; + }; + + # Get out. + programs = { + bash.completion.enable = false; + command-not-found.enable = false; + }; + + # Shut up. + system.stateVersion = "25.11"; + }) + ]; + }; + }; +} diff --git a/nix/kmod.nix b/nix/kmod.nix new file mode 100644 index 0000000..0d4dabb --- /dev/null +++ b/nix/kmod.nix @@ -0,0 +1,76 @@ +{ + lib, + stdenv, + linuxPackages_latest, + kernel ? linuxPackages_latest.kernel, + ... +}: let + pname = "deepcool"; + version = "0.1.0"; +in + stdenv.mkDerivation { + inherit pname version; + + src = let + fs = lib.fileset; + sp = ../.; + in + fs.toSource { + root = sp; + fileset = fs.unions [ + (sp + /deepcool.c) + (sp + /Makefile) + ]; + }; + + enableParallelBuilding = true; + nativeBuildInputs = kernel.moduleBuildDependencies; + hardeningDisable = ["pic" "format"]; + + env = { + KVERSION = kernel.modDirVersion; + INSTALL_MOD_PATH = builtins.placeholder "out"; + }; + + buildPhase = '' + runHook preBuild + echo "Kernel version: $KVERSION" + make -C ${kernel.dev}/lib/modules/${kernel.modDirVersion}/build \ + M=$(pwd) \ + modules + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + echo "Module Installation Path: $INSTALL_MOD_PATH" + make -C ${kernel.dev}/lib/modules/${kernel.modDirVersion}/build \ + M=$(pwd) \ + modules_install + runHook postInstall + ''; + + postInstall = '' + if [ -d $out/lib/modules/${kernel.modDirVersion}/extra ]; then + mkdir -p $out/lib/modules/${kernel.modDirVersion}/kernel/drivers/hid + find $out/lib/modules/${kernel.modDirVersion}/extra -name "*.ko*" \ + -exec mv {} $out/lib/modules/${kernel.modDirVersion}/kernel/drivers/hid/ \; + rmdir $out/lib/modules/${kernel.modDirVersion}/extra 2>/dev/null || true + fi + ''; + + passthru = { + inherit kernel; + kernelVersion = kernel.modDirVersion; + moduleName = "deepcool_digital"; + modulePath = "kernel/drivers/hid/deepcool_digital.ko"; + }; + + meta = { + description = "Linux kernel module for Deepcool Digital USB HID devices"; + license = lib.licenses.gpl2Plus; + maintainers = []; + platforms = lib.platforms.linux; + broken = lib.versionOlder kernel.version "5.10"; + }; + }