晚上好 世界

晚上好 世界

早安

本文由voyage200的 hermes-agent 代其撰写,不过voyage200本人仍对文章负责。

火影 T5G AMD(搭载 AMD Ryzen 5 5600H + NVIDIA RTX 3050 Mobile)本质上基于蓝天(Clevo)NH50/NH55(如 NH55Ax)公模。这类模具性价比极高,但官方往往只提供 Windows 版驱动(即官网上 270MB 的「综合控制中心-CV」,Clevo_ControlCenter_7.057.zip)。

在 Linux(我的主力机运行 Arch Linux + Linux 7.2 内核 + GNOME Wayland)环境下,默认没有专有控制程序,通常会面临两个烦人问题:

  1. 风扇策略不可控:默认保守的 EC 策略在负载升高时容易迟滞或突然起飞咆哮,日常码字又希望能有一套更安静的温控曲线;
  2. 键盘 RGB 无法调节:单区 RGB 背光无法改色,无法调暗甚至关不掉。

为了彻底解决火影 T5G 在 Linux 下的综合控制问题,我们逆向解包了官方 Windows 驱动包,理清了底层协议,并成功基于 Linux 开源生态打通了整套风扇曲线与键盘调色方案。以下是完整的逆向探查与配置排坑记录。


一、 官方 Windows 驱动包逆向剖析

官方下载的 Clevo_ControlCenter_7.057.zip 解压后是一套标准的 InstallShield 打包工程(包含 data1.cabdata2.cabsetup.exe)。通过 Linux 下的 unshield 工具解包 CAB 后,可以提取出其内部真正的核心载荷:

  1. ACPI 与 WMI 硬件通信桥

    • 包含 AcpiBridge.sysAcpiBridge1.sysInsydeDCHU.dll
    • INF 文件显示硬件 ID 为 ACPI\CLV0001ACPI\CLV0002
    • 底层通过 \\root\WMI 命名空间以及 ACPI DSM 方法与主板 EC 交互:

      • 0x63 / 0x64 / 0x6E:读取 CPU、GPU 及系统风扇的 RPM 转速与 PWM 占空比;
      • 0x68 / 0x69:设定手动风扇转速 / 恢复自动风扇策略;
      • 0x79(子命令 0x19):切换蓝天官方性能模式(Quiet / Power / Performance / Entertainment);
      • 0x67:控制键盘 RGB 颜色与亮度掩码。
  2. 键盘模块(Keyboard.appxbundle / LedKeyboardSetting.exe

    • 针对外挂的单键 RGB(Per-Key),驱动内置了 ITE Tech(048d:8297 / 048d:8910)USB HID 驱动;
    • 针对主板自带的整区/三区背光,直接走上述 ACPI 0x67 接口下发。

二、 Linux 方案选型:TUXEDO Control Center (TCC)

德国知名 Linux 笔记本厂商 TUXEDO Computers(以及欧洲贴牌商 XMG / Schenker)旗下大量游戏本均采购蓝天模具。他们官方开源并重金维护了一整套成熟的 Linux 方案:

  • tuxedo-drivers:内核 DKMS 模块,内置了 clevo_wmiclevo_acpituxedo_keyboard 以及风扇读写接口 tuxedo_io
  • tuxedo-control-center(TCC):图形化桌面控制中心与后台守护进程 tccd,提供无级风扇曲线调节、键盘 RGB 调色盘、性能模式切换等完整功能。

唯一的障碍与社区补丁

Tuxedo 原厂驱动在 tuxedo_compatibility_check.c 中加了 DMI 厂商白名单校验(若非 TUXEDO 机器直接退出)。
Arch Linux AUR 社区的 clevo-drivers-dkms-git 已经为我们打了补丁,强制 tuxedo_is_compatible() => true,完美移除了厂商硬件锁。


三、 Arch Linux 实装与排坑记录

1. 安装基础包

sudo pacman -S --needed base-devel linux-headers dkms
yay -S clevo-drivers-dkms-git tuxedo-control-center-bin

2. 踩坑一:模块加载语法误区导致 TCC 界面留白

在安装完成后启动 tccd.service,打开 TUXEDO Control Center 发现界面只有 Tux 图标,下方空白,风扇转速也无法调节。查看后台日志提示:

FanControlWorker: onStart: Fan API not available
KeyboardBacklightListener: Detected no keyboard backlight

原因分析
Linux 下 modprobe 的语法是 modprobe [模块名] [参数...]。如果敲成:
sudo modprobe tuxedo_keyboard tuxedo_io clevo_wmi clevo_acpi
内核会把后面三个名字全部当作参数传给 tuxedo_keyboard 并全部忽略!导致关键的 tuxedo_io(提供 /dev/tuxedo_io 节点)和 clevo_acpi 根本没进入内核。

正确做法:逐个单独加载核心模块:

sudo modprobe tuxedo_io
sudo modprobe clevo_wmi
sudo modprobe clevo_acpi

模块加载后,重启后台服务:

sudo systemctl restart tccd.service

再看日志,风扇与性能模式瞬间满血激活:

FanControlWorker: initializeFanControl: tuxedo-io available
FanControlWorker: initializeFanControl: Detected 3 fans
FanControlLogic: Setting tableCPU / tableGPU
ODMProfileWorker: Setting ODM profile 'performance'

在 TCC 界面点击 Profiles,已经可以随心所欲自定义 CPU / GPU 的温度-风扇转速阶梯曲线了!


四、 键盘背光探秘:单区 RGB 到底能不能硬件呼吸?

在排查键盘背光时,驱动成功握手并在内核日志中打印:

tuxedo_keyboard: Set keyboard enabled to: 1
tuxedo_keyboard: Set keyboard backlight mode on CUSTOM

在驱动加载的瞬间,我的键盘背光熄灭并重置为纯白色,同时 /sys/class/leds/rgb:kbd_backlight/ 成功出现!在控制中心也可以自由拖动调色板改色。

但当我尝试测试动效时,发现了一个经典疑问:为什么执行 echo 1 | sudo tee /sys/devices/platform/tuxedo_keyboard/kbd_backlight_mode 会报“权限不够”?火影 T5G 单区键盘到底支不支持硬件呼吸?

为了彻底搞清这个问题,我们直接进行了双向源码反编译:

1. Linux 内核驱动源码求证

查看 clevo_keyboard.h 驱动源码发现:

static void clevo_keyboard_init_device_interface(struct platform_device *dev)
{
    // 只有 3 区 RGB (0x02) 才会创建该 sysfs 动效节点!
    if (clevo_leds_get_backlight_type() == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) {
        device_create_file(&dev->dev, &dev_attr_kbd_backlight_mode);
    }
}

报“权限不够”不是因为 sudo 权限不足,而是虚拟文件系统里压根就没有这个文件! 驱动作者在识别到单区 RGB(1_ZONE_RGB,类型值 0x06)时直接跳过了动效节点的创建。

2. 官方 Windows 控制中心反编译验证

我们利用 dnfiledncil 深入反编译了火影官方 LedKeyboardSetting.exe 的 CIL 字节码,在 Page_LED_Device.Open_UI 中找到了答案:

ldfld this.kbType
ldc.i4.2            ; 若为 3 区 RGB (Type 2)
call Init_UI_RGB()  ; 加载包含呼吸(Breath)、波浪(Wave)、循环(Cycle)的特效面板

ldfld this.kbType
ldc.i4.6            ; 若为 单区 RGB (Type 6, 即火影 T5G AMD)
call Init_UI_ALL()  ; 仅加载单色调色板,动效面板直接隐藏剥离!

终极结论
可以彻底死心,不必再折腾固件硬件呼吸了。 火影 T5G AMD 的单区 RGB 在主板 EC 固件中压根就没有烧录硬件呼吸波形微码,即便在 Windows 官方系统下也只有静态纯色。

但这丝毫不影响我们在 Linux 下的使用体验:

  • 原生调色与控光:直接使用 Linux 标准 Multi-Color LED 子系统,在 TUXEDO Control Center 图形界面点选即变色,或者终端一行命令秒切:

    # 变冰蓝
    echo "0 255 255" | sudo tee /sys/class/leds/rgb:kbd_backlight/multi_intensity
    # 调暗亮度 (0 - 255)
    echo 120 | sudo tee /sys/class/leds/rgb:kbd_backlight/brightness
  • 软件呼吸(甚至比硬件更细腻):如果真的喜欢呼吸效果,在 Linux 下写个极轻量的 Python 脚本循环调节 brightness,正弦波插值平滑呼吸,CPU 占用不到 0.1%,刷新率和过渡甚至优于硬件自带微码。

五、 功耗与温控协同:ryzenadj 与 TCC 是否冲突?

很多 AMD 笔记本用户(包括我自己)会使用 ryzenadj 限制温度墙,比如配置开机自启 systemd 单元:

[Unit]
Description=RyzenAdj Power and Thermal Limit
After=multi-user.target suspend.target hibernate.target hybrid-sleep.target

[Service]
Type=oneshot
ExecStart=/usr/bin/ryzenadj --tctl-temp=75

[Install]
WantedBy=multi-user.target suspend.target hibernate.target hybrid-sleep.target

两者冲突吗?答案是:完全不冲突,天作之合。

  • ryzenadj --tctl-temp=75:工作在物理硬件最底层,直接通过 PCI 写入 AMD 芯片内部的 SMU(系统管理单元协处理器),属于硬件级熔断兜底(一旦触碰 75°C 强制限流);
  • TUXEDO Control Center:工作在操作系统与 EC 层,只负责 Linux 内核 CPU 调度器(cpufreq governor)以及蓝天 EC 的风扇 PWM 曲线映射。

两者搭配时:ryzenadj 负责守住芯片温度上限,TCC 负责在温控范围内根据自定义曲线安安静静地排热,互不干涉,相得益彰。


六、 固化开机自动加载

最后,为了确保每次重启或休眠唤醒后驱动自动就绪,添加自动加载配置文件:

sudo tee /etc/modules-load.d/tuxedo.conf << 'EOF'
tuxedo_compatibility_check
tuxedo_keyboard
clevo_acpi
clevo_wmi
tuxedo_io
EOF

并确保守护进程开机启动:

sudo systemctl enable tccd.service

至此,火影 T5G AMD 游戏本在 Arch Linux 下成功获得了比 Windows 原版更轻巧、更自由、更安静的「综合控制中心」完整体验!


文章转载并翻译自:https://heikoseeberger.de/2022/10/20/2022-10-21-tower-1/

第一讲:将服务视为函数

作者:Heiko Seeberger 2022年10月21日 标签:Rust, Tokio, Tower

当我第一次接触Tower库(Tokio技术栈的一部分)时,注意到它的标题行写着:

async fn(Request) -> Result<Response, Error>

文档中对此解释如下:

Tower提供了一个简单的核心抽象——Service特性,它表示一个异步函数,接收请求并返回响应或错误。这个抽象可用于建模客户端和服务器。

这立即让我这个经验丰富的Scala开发者想起了Finagle论文《Your Server as a Function》,其中定义"系统边界由称为服务的异步函数表示"。看到新项目利用已有知识总是件好事。

虽然Tower和Finagle服务共享相同的核心抽象——异步函数,但有两点不同。

首先,Tower不一定将服务解释为系统边界。相反,服务也可以"本地"组合,这更符合Finagle的"过滤器":

通用组件(如超时、速率限制和负载平衡)可以建模为包装某些内部服务并在调用内部服务前后应用附加行为的服务。这允许以协议无关、可组合的方式实现这些组件。通常,此类服务被称为中间件。

其次,Tower通过就绪概念丰富了异步函数的核心抽象,正如我们从Service特性的定义中看到的:

pub trait Service<Request> {
    type Response;
    type Error;
    type Future: Future<Output = Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
    fn call(&mut self, req: Request) -> Self::Future;
}

其核心在于这些服务方法的调用契约如下:

  1. 必须先调用poll_ready(可能需要多次)直到返回Poll::Ready
  2. 然后才能调用call
  3. 如果不遵守上述规则,call可能会panic

在下一讲深入探讨调用服务的细节之前,我们先看一个简单的服务示例:

pub struct EchoRequest(String);
pub struct EchoResponse(String);
pub struct EchoService;

/// 为[EchoService]实现Tower的`Service`特性:
/// 总是就绪,永不失败,立即返回回显响应(即内容与请求相同的响应)
impl Service<EchoRequest> for EchoService {
    type Response = EchoResponse;
    type Error = Infallible; // 此服务永不失败
    type Future = Ready<Result<Self::Response, Self::Error>>; // 立即响应

    /// 总是返回`Poll::Ready`:此服务总是就绪
    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    /// 总是返回回显,即内容与请求相同的响应
    fn call(&mut self, req: EchoRequest) -> Self::Future {
        ready(Ok(EchoResponse(req.0)))
    }
}

由于此服务总是就绪的,因此如果之前没有调用poll_readycall也不需要panic。

完整代码可在GitHub的tower-experiments仓库找到。下一讲我们将探讨服务客户端。

第二讲:调用Tower服务

作者:Heiko Seeberger 2022年10月23日 标签:Rust, Tokio, Tower

在上一讲中我们了解到,Tower服务由poll_readycall两个方法组成,它们遵循这样的契约:调用者必须先持续调用poll_ready直到返回Poll::Ready,然后才能调用call,否则call可能会panic。

poll_ready的签名如下:

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;

要调用poll_ready,我们需要一个std::task::Context。如果你熟悉Future,会发现它的poll方法也有这样的参数。那么我们是否必须实现一个Future才能调用服务呢?

幸运的是不必,因为Tower已经提供了ServiceExt特性,其中包含一些辅助方法,可以按照契约要求调用poll_ready,甚至链式调用poll_readycall

首先我们来看ServiceExt::oneshot。根据文档说明,它会"消费此服务,在就绪时使用提供的请求调用一次":

let mut service = EchoService;
let response = service.oneshot("Hello, Tower!".into()).await?;
println!("Echo service responded with: {response}");

oneshot返回的Future在被轮询时首先调用poll_ready,当服务返回Poll::Ready时再调用call。当然,由于oneshot通过self消费了服务,所以不可能再次调用该服务。但这实际上是Tower提供的唯一能强制执行poll_readycall之间契约的方式。

接下来看看ServiceExt::ready,根据文档说明,它"在服务准备好接受请求时返回一个可变引用":

let mut service = EchoService;

let service = service.ready().await?;
let response = service.call("Hello, Tower!".into()).await?;
println!("Echo service responded with: {response}");

// 再次调用call前应该先调用ready!
// service.ready().await?;
let response = service.call("Hello again, Tower!".into()).await?;
println!("Echo service responded with: {response}");

ready返回的Future会返回服务的可变引用,然后可以用来调用它。如你所见,我们甚至可以不检查就绪状态就多次调用服务。当然这违反了契约,我们应该在每次调用call之前再次调用ready,即在上面的代码片段中应该取消第8行的注释。

最后还有ServiceExt::ready_oneshot。它像ready一样工作,但会消费服务,然后又将其返回:

let service = EchoService;

let mut service = service.ready_oneshot().await?;
let response = service.call("Hello, Tower!".into()).await?;
println!("Echo service responded with: {response}");

// 再次调用call前应该先调用ready_oneshot!
// let mut service = service.ready_oneshot().await?;
let response = service.call("Hello again, Tower!".into()).await?;
println!("Echo service responded with: {response}");

ready类似,我们可以不检查就绪状态就多次调用返回的服务。因此我认为这个方法命名不当,因为"oneshot"对我来说意味着最多只能调用一次call

现在你可能会问,这种总是先调用poll_ready再调用call的服务契约是否真的那么重要。对于无害的EchoService来说确实不重要,因为它总是就绪的,所以它的call方法永远不会panic。我们将在下一讲中看看其他表现不那么好的服务。完整代码照例可在GitHub的tower-experiments仓库找到。

第三讲:就绪状态

作者:Heiko Seeberger 2022年11月8日 标签:Rust, Tokio, Tower

在上一讲中,我们学习了如何调用Tower服务。特别讨论了poll_readycall之间的契约关系:调用者必须首先持续调用poll_ready直到返回Poll::Ready,才能调用call方法,否则call可能会触发panic。

当然,某些服务(例如第一讲中介绍的EchoService)始终处于就绪状态,因此无需调用poll_ready直接调用call也能正常工作。

但通常情况下,服务的内部状态或外部依赖(例如数据库连接)会影响其就绪状态。此外,将领域服务与中间件(如限流器或熔断器)组合时,自然也会影响就绪状态。

为了演示这一点,我们来看一个在"就绪"与"未就绪"状态间交替切换的服务:

/// 交替就绪服务,接收[AlternatingReadyRequest]并返回[AlternatingReadyResponse]
#[derive(Debug, Clone)]
pub struct AlternatingReadyService {
    ready: bool,
}

/// 为[AlternatingReadyService]实现Tower的`Service`特性:
/// 就绪状态在Pending和Ready间交替切换,调用永不失败且响应立即就绪
impl Service<AlternatingReadyRequest> for AlternatingReadyService {
    type Response = AlternatingReadyResponse;
    type Error = Infallible; // 该服务永不失败
    type Future = Ready<Result<Self::Response, Self::Error>>; // 响应立即就绪

    /// 交替返回`Poll::Pending`和`Poll::Ready`
    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        if self.ready {
            // 唤醒任务确保执行器再次调用poll_ready
            cx.waker().wake_by_ref();
            self.ready = false;
            Poll::Pending
        } else {
            self.ready = true;
            Poll::Ready(Ok(()))
        }
    }

    /// 始终返回[AlternatingReadyResponse]
    /// 
    /// # Panics
    /// 若未先调用poll_ready就直接调用本方法会panic
    fn call(&mut self, _req: AlternatingReadyRequest) -> Self::Future {
        if !self.ready {
            panic!("服务未就绪,必须先调用poll_ready");
        }
        self.ready = false;
        ready(Ok(AlternatingReadyResponse))
    }
}

AlternatingReadyService通过ready字段跟踪就绪状态。poll_ready的实现逻辑是:若服务之前处于就绪状态,则将其设为未就绪并返回Poll::Pending;反之则设为就绪并返回Poll::Ready。同时,call方法会检查ready字段,若服务未就绪时调用将触发panic。

虽然这个实现是刻意设计的,但它很好地演示了poll_readycall之间契约的重要性:

let mut service = AlternatingReadyService::new();

let service = service.ready().await?;
let _response = service.call(AlternatingReadyRequest).await?;

// 再次调用call前应该先调用ready!
// service.ready().await?;
let _response = service.call(AlternatingReadyRequest).await?;

在这个例子中,第二次call调用会因为服务处于未就绪状态而panic(由于第一次ready调用后服务状态变为未就绪)。如果你想亲自验证,可以在GitHub的tower-experiments仓库查看完整代码。


关系数据库规范化理论 是数据库设计中的核心理论,旨在通过分解关系模式来消除数据冗余和操作异常,从而提高数据库的一致性、完整性和性能。规范化理论由 Edgar F. Codd 提出,并逐步发展为一套完整的体系,包括多个范式(Normal Forms,NF)。

More...


曾经,我很爱用 fishshell ,直到我用上了不支持 fishshell 的 guix

上链接: https://github.com/sorin-ionescu/prezto

我在用的主题:powerlevel10k ,首次使用这个主题会主动询问你几个问题,再根据你的回答向你推荐合适的主题。


看官网:https://volta.sh/

为什么推荐用 volta ?因为 volta 给自己的定位不仅仅是 nodejs 版本管理工具,而是 “The Hassle-Free JavaScript Tool Manager” ,秉持着让开发者省心的设计思路,对我这种懒人特别友好。

例如,
npm i -g @angular/cli @angular/language-server typescript-language-server vscode-langservers-extracted,你要是用fnm安装的node 22.04就会报错,说 @angular/language-server 不支持22.04版本的nodejs。

到了 volta 这边,你运行上面的npm i -g命令时,这个命令会被 volta 接管:

被 volta 接管

volta会依次用合适版本的nodejs安装上面的工具,没有烦人的nodejs版本问题,爽爆啦!!