香橙派 6(CIX CD8180)装昇腾 310P 驱动:从 BIOS 认卡到 DKMS 编译失败的完整排查

手上一块香橙派 6(Orange Pi 6,CIX P1 / CD8180),刷的是 Radxa 官方的 CIX 镜像(所以本机主机名还叫 orion-o6)。
给它插上华为昇腾 310P NPU,前后踩了两个坑:
一是主板 BIOS 的 PCIe 链路超时太短,压根认不到卡;
二是 CIX 定制内核与驱动对 enum profile_type 的定义打架,DKMS 编译直接失败。
本文把这两处的定位过程、补丁写法和重打包步骤完整记录下来,希望能帮到遇到同样问题的朋友。

关键词:昇腾 310P | Ascend 驱动 | 香橙派 6 | Orange Pi 6 | CIX CD8180 | Radxa 镜像 | DKMS 编译失败 | aarch64 | PCIe link up timeout | enum profile_type


一、环境信息

项目
开发板香橙派 6(Orange Pi 6),90×90mm
SoCCIX P1(代号 CD8180,12 核 aarch64)
系统镜像Radxa 官方 CIX 镜像(Radxa Orion O6 版),故本机主机名为 orion-o6
架构aarch64
操作系统Debian GNU/Linux 12 (bookworm)
内核6.6.89-1-sky1(CIX 厂商定制内核)
内核头文件linux-headers-6.6.89-1-sky1(含 Module.symvers
编译器gcc 12.2.0
DKMS3.0.10-8+deb12u1
页大小4096
外接加速卡(本文主角)华为昇腾 310P3(PCIe 插卡,非 CIX 板载 NPU)
加速卡 PCIe ID19e5:d500,Bus-Id 0000:C1:00.0
驱动包Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64.run

驱动装好后的软件栈版本(/usr/local/Ascend/driver/version.info):

Version=25.5.2
ascendhal_version=7.35.23
Innerversion=V100R001C23SPC007B220
compatible_version=[V100R001C19]...[V100R001C23]
compatible_version_fw=[6.4.0,6.4.99],[7.0.0,8.9.9]

先摆结论,方便赶时间的朋友直接对号入座:

阶段问题解决方式
插卡后BIOS 认不到卡,系统里 lspci19e5:d500BIOS 中把 PCIe link up timeout 由 300ms 改为 500ms
装驱动DKMS 编译失败,enum profile_type 重复定义给驱动打补丁(探测内核头文件),并重新打包 .run
装完后普通用户 npu-smi 报错 ret=-8005用户加入 HwHiAiUser 组,注意 tmux 会继承旧组

二、第一步:先让主板认到卡(BIOS 改 PCIe 链路超时)

硬件装好、上电开机,结果系统里根本看不到这张卡:

lspci -nn | grep -i 19e5
# 无任何输出
lspci -nn | grep -i d500
# 无任何输出

连设备都没枚举出来,驱动自然无从谈起。这时候先别怀疑驱动、也别怀疑插槽坏了——先在 BIOS 里确认 PCIe 相关的超时设置

2.1 现象与原因

香橙派 6 的 BIOS(本机跑的是 Radxa 那套 CIX 固件)里有一个 PCIe link up timeout(链路建立超时) 选项,默认值是 300ms

这个参数的物理含义是:BIOS 在枚举 PCIe 插槽时,会等待插槽上的设备完成链路训练(LTSSM 走到 L0)并返回配置空间响应;超过这个时间还没等到,就直接判定该插槽无设备,跳过枚举

而 310P 这类加速卡自带较重的固件与上电初始化流程,从供电稳定到链路训练完成所需的时间,在这块板子上会超过 300ms,于是 BIOS 一侧"等不及了",把卡当成不存在。表现出来就是:卡插着、供电正常、但系统里连 PCIe 设备节点都没有。

2.2 解决办法

进入 BIOS 设置界面,找到 PCIe 相关设置项,把 PCIe link up timeout 从 300ms 改为 500ms(不同 BIOS 版本该项所在的菜单层级、名称可能略有差异,一般在 PCIe / Advanced 相关的配置页里,找带 link uplink trainingtimeout 字样的项)。

改完保存退出、重新上电,卡就应该能被正常枚举了。

注意两点

  1. 这是主板 BIOS 设置,不是驱动或内核的改动,因此不需要重新编译任何东西;
  2. 这个改动只解决"认不到卡"。认到卡之后,驱动安装还会再卡一次——就是下面要讲的 DKMS 编译失败。

改完之后确认卡已经被识别:

lspci -nn | grep -i 19e5
# 示例输出(形态如下,Bus-Id 视插槽而定)
# 0000:c1:00.0 Processing accelerators [1200]: Huawei Technologies Co., Ltd. Device [19e5:d500]

能看到 19e5:d500,才说明硬件这一层通了,可以进入下一步装驱动。


三、第二步:装驱动,DKMS 编译直接失败

硬件认到之后,按官方方式全量安装:

./Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64.run --full

归档校验通过(说明包本身没坏),但到了 DKMS 编译阶段就挂了:

Uncompressing ASCEND DRIVER RUN PACKAGE  100%
[Driver] [INFO]driver install type: DKMS
[Driver] [INFO]upgradePercentage:10%
[Driver] [INFO]upgradePercentage:30%
[Driver] [INFO]upgradePercentage:40%
[Driver] [ERROR]Dkms install failed, details in : /var/log/ascend_seclog/ascend_install.log
[Driver] [ERROR]Driver_ko_install failed, details in : /var/log/ascend_seclog/ascend_install.log
[Driver] [INFO]Failed to install driver package, please retry after uninstall and reboot!

安装日志里的 DKMS 部分只说"构建失败",没给具体原因:

run dkms build command...
'make' all KERNEL_UNAME=6.6.89-1-sky1 DAVINCI_HIAI_DKMS=y TARGET_PRODUCT=mini \
    Driver_Install_Mode=normal....................(bad exit status: 2)
Error! Bad return status for module build on kernel: 6.6.89-1-sky1 (aarch64)

先排掉一个干扰项
安装日志开头还有这么一行,看起来很像"真凶",其实完全无关:

sed:无法读取 /root/selfgz639415650/driver/kernel/dvpp_cmdlist/Makefile*:没有那个文件或目录

它来自包内的 driver/script/os_adapt.sh,脚本会去 sed 设备侧源码里的 dvpp_cmdlist 目录,
而主机侧(host)包里根本没有这个目录,所以 sed 报"文件不存在"。
该命令带着 >>/dev/null 2>&1 且不影响返回值,与 DKMS 失败没有因果关系
改不改补丁,这行都会出现——别被它带偏。

3.1 找到真正有用的编译日志

安装日志太粗,真正详细的编译输出在 DKMS 的构建目录里:

# 需要 root
cat /var/lib/dkms/davinci_ascend/1.0/build/make.log

直接过滤错误行效率更高:

sudo grep -nE "error:" /var/lib/dkms/davinci_ascend/1.0/build/make.log

3.2 关键报错:redeclaration of 'enum profile_type'

In file included from .../dbl/uda/uda_access.c:35:
.../dev_inc/inc/dbl/kernel_adapt.h:23:6: error: redeclaration of 'enum profile_type'
   23 | enum profile_type {
      |      ^~~~~~~~~~~~
In file included from .../dbl/uda/uda_access.c:18:
./include/linux/profile.h:33:6: note: originally defined here
   33 | enum profile_type {
      |      ^~~~~~~~~~~~

.../kernel_adapt.h:24:5: error: redeclaration of enumerator 'PROFILE_TASK_EXIT'
.../kernel_adapt.h:25:5: error: redeclaration of enumerator 'PROFILE_MUNMAP'

make 随之失败:

make[4]: *** [scripts/Makefile.build:243:.../dbl/uda/uda_access.o] 错误 1
make[3]: *** [scripts/Makefile.build:480:.../dbl/uda] 错误 2
make[2]: *** [/usr/src/linux-headers-6.6.89-1-sky1/Makefile:1927:.../build] 错误 2
make: *** [Makefile:93:all] 错误 2

报错很明确:同一个枚举被定义了两次。一次来自内核头文件 include/linux/profile.h,一次来自驱动自己的 dev_inc/inc/dbl/kernel_adapt.h

3.3 两边到底各写了什么

内核侧/usr/src/linux-headers-6.6.89-1-sky1/include/linux/profile.h 第 33 行):

enum profile_type {
	PROFILE_TASK_EXIT,
	PROFILE_MUNMAP
};

驱动侧dev_inc/inc/dbl/kernel_adapt.h 第 22–30 行):

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
enum profile_type {
    PROFILE_TASK_EXIT,
    PROFILE_MUNMAP
};

int profile_event_register(enum profile_type type, struct notifier_block *n);
int profile_event_unregister(enum profile_type type, struct notifier_block *n);
#endif

注意驱动那句 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) —— 这就是整件事的起点。


四、根因:内核"版本号新、接口旧"

这是一次典型的内核版本判断与厂商内核实际实现不一致导致的冲突:

  1. 主线内核在 v5.17 删除了 enum profile_type 以及配套的 profile_event_register/unregister
    include/linux/profile.h:v5.16 还有该枚举,v6.2 / v6.6 已删除)。

  2. 驱动于是写下判断:当 LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)自行定义该枚举,
    以便在这些新内核上继续提供 profile_event_register() 接口。
    这个接口被 uda_access.cdevdrv_manager.cdms_init.csoft_fault.c 等多处使用。

  3. 但这台机器跑的是 CIX 厂商定制内核 6.6.89-1-sky1:版本号 ≥ 5.17,
    却把 enum profile_type 保留了下来(厂商回移 backport 保留了旧接口)。

  4. 两边都定义 → 编译时 redeclaration 报错;而驱动编译带了 -Werror
    EXTRA_CFLAGS += -Wall -Werror),警告升级为错误,DKMS 构建直接失败。

一句话概括:

驱动假设"内核 ≥ 5.17 就没有 profile_type",而 CIX 6.6 定制内核实为"版本号新、接口旧",
假设不成立,于是同一个枚举被定义了两次。

4.1 先排除掉"权限/环境问题"的可能

  • 内核头文件、Module.symvers、gcc、dkms 全都齐全,dkms build 能正常跑到编译阶段;
  • 编译跑了数分钟,成功编译了大量 .oka_task.oka_fs.osvm_*.o 等),
    只在 dbl/uda/uda_access.o 处失败。

也就是说,这是确定的源码级错误,跟权限、环境、头文件缺失都没关系。


五、补丁方案

5.1 思路:不能简单删掉那几行

最直觉的做法是把驱动里那段 enum profile_type 定义删掉,但这是错的:
真正的主线内核(≥ 5.17、确实没有该枚举)上,删掉后就会变成
enum profile_type 未定义,编译同样失败。

正确做法是在构建时探测内核头文件

内核 profile.h 中的情况处理
enum profile_type定义宏 DAVINCI_KERNEL_HAS_PROFILE_TYPE_ENUM,驱动改为 #include <linux/profile.h>,复用内核定义
没有该枚举不定义宏,保持驱动原有行为(自己定义)

这样补丁对两类内核都正确,而不是只针对手上这一台打一个"专属补丁"。

5.2 补丁一:驱动头文件

文件:driver/kernel/dev_inc/inc/dbl/kernel_adapt.h

 int task_exit_notify_register(struct notifier_block *n);
 int task_exit_notify_unregister(struct notifier_block *n);
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
+/*
+ * Mainline removed enum profile_type in v5.17, but some vendor kernels
+ * (e.g. CIX 6.6 shipped with the Orange Pi 6) keep it in
+ * include/linux/profile.h. The build system probes the kernel headers and
+ * defines DAVINCI_KERNEL_HAS_PROFILE_TYPE_ENUM in that case, so that the
+ * enum is taken from the kernel instead of being declared twice.
+ */
+#ifdef DAVINCI_KERNEL_HAS_PROFILE_TYPE_ENUM
+#include <linux/profile.h>
+#else
 enum profile_type {
     PROFILE_TASK_EXIT,
     PROFILE_MUNMAP
 };
+#endif
 
 int profile_event_register(enum profile_type type, struct notifier_block *n);
 int profile_event_unregister(enum profile_type type, struct notifier_block *n);
 #endif

为什么光加 #ifndef 不够,还要 #include <linux/profile.h>

kernel_adapt.h 会被多个 .c 文件包含,其中 soft_fault.cdevdrv_manager.c
dms_init.c并没有包含 <linux/profile.h>。如果只是跳过枚举定义而不把内核头文件引进来,
enum profile_type 在函数参数列表里就成了"新的不完整类型",触发:

error: 'enum profile_type' declared inside parameter list ... [-Werror]

所以必须显式包含内核头文件。

5.3 补丁二:构建探测

文件:driver/kernel/Makefile_mini1910pMakefile 是指向它的软链接)

 ifneq ($(KERNELRELEASE),)
 	# start
+	# Some vendor kernels (e.g. CIX 6.6 on the Orange Pi 6) still keep
+	# enum profile_type in include/linux/profile.h although mainline
+	# removed it in v5.17. Probe the headers so that the driver only
+	# declares it when the kernel does not.
+	KERNEL_PROFILE_HAS_TYPE := $(shell [ -f $(srctree)/include/linux/profile.h ] && grep -q "enum profile_type {" $(srctree)/include/linux/profile.h && echo y)
+	ifneq ($(KERNEL_PROFILE_HAS_TYPE),)
+		subdir-ccflags-y += -DDAVINCI_KERNEL_HAS_PROFILE_TYPE_ENUM
+	endif
 	ifeq ($(Driver_Install_Mode),vnpu_guest)
 		obj-m := svmdrv/vmaster/

几个要点:

  • $(srctree) 在内核构建上下文中指向内核源码/头文件树,本机即 /lib/modules/6.6.89-1-sky1/build
  • 先用 -f 判断文件存在,避免头文件缺失时误判;
  • grep -q "enum profile_type {" 精确匹配,避开注释里的字样误命中;
  • subdir-ccflags-y 而不是 EXTRA_CFLAGS一处生效于全部子模块
    uda_access.c 位于 dbl/uda/ 子目录下)。本机实测该宏确实传递到了子目录。

踩坑记录:第一次实现时只用了 #ifndef(跳过定义但不包含头文件),
结果报 declared inside parameter list。这反而证明了宏已经成功传递到子目录
只是补丁逻辑本身不完整;改成条件包含后即通过。


六、关键坑:补丁必须打进 .run 包,不是改 /usr/src

这是整件事里最容易白干的一点,务必看完再动手。

很多人(包括我第一反应)会直接去改 /usr/src/davinci_ascend-1.0/ 里的源码然后重新 dkms build
这样确实能编译过,但重装一次就全白费了

原因在安装脚本 driver/script/run_driver_dkms_install.shdkms_copy_source() 里:

dkms_copy_source() {
    sources="$targetdir"/driver/kernel          # /usr/local/Ascend/driver/kernel
    ...
    dkms_remove $module_name

    src_dir=/usr/src/$module_name-$module_version   # /usr/src/davinci_ascend-1.0
    if [ -d $src_dir ]; then
        rm -rf $src_dir                             # ← 先删掉旧源码树
    fi
    mkdir -p $src_dir
    ...
    cp -rf "${sources}"/* ${src_dir}/               # ← 从安装目录重新拷贝
}

也就是说,每次安装都会

  1. .run 包里解出 driver/kernel/*$targetdir/driver/kernel(即 /usr/local/Ascend/driver/kernel);
  2. rm -rf /usr/src/davinci_ascend-1.0
  3. 再把 $targetdir/driver/kernel/* 拷进 /usr/src/davinci_ascend-1.0

结论:

只改 /usr/src/davinci_ascend-1.0,或只改 /usr/local/Ascend/driver/kernel
都不持久
——重装时会被包内源码覆盖。
补丁必须打进 .run 包本身。


七、重新打包 .run

厂商的 .run 包本身就是 makeself 自解压包,包内自带打包脚本,直接复用即可,不需要自己写 repack。

7.1 准备

# pigz 是重打包必需的压缩器(包内 repack 脚本会检查它)
sudo apt-get install -y pigz

cd /home/radxa/Downloads

# 解出原始包(只解包、不执行安装脚本)
./Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64.run \
    --noexec --extract=/home/radxa/Downloads/ascend_extract

7.2 应用补丁

按第五节的 diff,修改解包目录中的这两个文件:

ascend_extract/driver/kernel/dev_inc/inc/dbl/kernel_adapt.h
ascend_extract/driver/kernel/Makefile_mini1910p

7.3 重新打包

复用包内自带的 makeself.sh / makeself-header.sh / help.info
保持与原包完全相同的打包参数--pigz --complevel 4 --nomd5 --sha256):

cd /home/radxa/Downloads/ascend_extract

bash driver/script/makeself.sh \
    --header driver/script/makeself-header.sh \
    --help-header driver/script/help.info \
    --pigz --complevel 4 --nomd5 --sha256 \
    "$PWD" \
    /home/radxa/Downloads/Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64-fixed.run \
    "ASCEND DRIVER RUN PACKAGE" \
    driver/script/install.sh

成功时会输出:

Self-extractable archive ".../Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64-fixed.run" successfully created.

7.4 校验新包(这一步别省)

# 1) 归档完整性
./Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64-fixed.run --check
# → SHA256 checksums are OK. All good.

# 2) 确认补丁确实进包了(重要!)
rm -rf /tmp/verify_run && mkdir -p /tmp/verify_run
./Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64-fixed.run \
    --noexec --extract=/tmp/verify_run
grep -n "DAVINCI_KERNEL_HAS_PROFILE_TYPE_ENUM" \
    /tmp/verify_run/driver/kernel/Makefile_mini1910p \
    /tmp/verify_run/driver/kernel/dev_inc/inc/dbl/kernel_adapt.h

第 2 步务必执行——它验证的正是第六节那个"补丁必须进包"的前提。
两条 grep 都要有命中,才说明补丁真的打进去了。


八、安装打补丁后的驱动

cd /home/radxa/Downloads
sudo ./Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64-fixed.run --full --quiet

安装日志(/var/log/ascend_seclog/ascend_install.log)关键片段:

[INFO]create /usr/src/davinci_ascend-1.0 success
[INFO]copy source (/usr/local/Ascend/driver/kernel/ -> /usr/src/davinci_ascend-1.0/) success
[INFO]run dkms add command...
[INFO]run dkms build command...
'make' all KERNEL_UNAME=6.6.89-1-sky1 DAVINCI_HIAI_DKMS=y TARGET_PRODUCT=mini \
    Driver_Install_Mode=normal.....................        ← 编译通过(没有 bad exit status 了)
[INFO]run copy-modules command...
[INFO]dkms output files check success
[INFO]run install path add /usr/local/Ascend/driver/host/README.txt success
[Server] [INFO]server(/etc/init.d/host_sys_init.sh) setup success
[Server] [INFO]auto start scripts setuped on Debian 12
[Driver] [INFO]upgradePercentage:100%
[Driver] [INFO]Installation status updated successfully

对比第三节那份失败日志,关键差别就是 make 那一行不再有 (bad exit status: 2)

确认状态:

grep Driver_Install_Status /etc/ascend_install.info
# Driver_Install_Status=complete

dkms status | grep davinci
# davinci_ascend/1.0, 6.6.89-1-sky1, aarch64: built

小提示--quiet 模式下如果通过管道执行,sudo 的密码提示可能把输出吞掉,
看起来像"没输出但其实是成功了"。请以 /var/log/ascend_seclog/ascend_install.log
/etc/ascend_install.info 的状态为准
,别只看终端。


九、加载模块与验证

9.1 手动加载内核模块

DKMS 方式安装只编译并安装 .ko/lib/modules/.../updates/,不会自动 modprobe
(安装日志里的 install ko, hot reset status: ko_abort, first_time:y 表示自动加载分支被跳过,
原因见第十一节)。首次验证手动加载即可:

sudo modprobe drv_pcie_host      # 主驱动,其余模块按依赖自动拉起

如需完整加载全部模块:

for m in drv_pcie_host drv_pcie_hdc_host drv_pcie_vnic_host drv_devmng_host \
         drv_devdrv_host drv_devmm_host drv_tsdrv_platform_host drv_soft_fault \
         drv_vascend_stub drv_virtmng_host drv_vpc_host drv_dp_proc_mng_host \
         ascend_queue ascend_event_sched_host dbl_algorithm dbl_dev_identity \
         dbl_runenv_config ascend_urd ascend_soc_resmng ascend_dms_dtm \
         ascend_dms_smf ascend_xsmem ts_agent debug_switch; do
    sudo modprobe "$m" 2>/dev/null || true
done

9.2 检查设备节点

ls -l /dev/davinci* /dev/devmm_svm /dev/hisi_hdc

期望输出:

crw-rw---- 1 HwHiAiUser HwHiAiUser 501, 0 /dev/davinci0
crw-rw---- 1 HwHiAiUser HwHiAiUser 502, 0 /dev/davinci_manager
crw-rw---- 1 HwHiAiUser HwHiAiUser 500, 0 /dev/devmm_svm
crw-rw---- 1 HwHiAiUser HwHiAiUser 499, 0 /dev/hisi_hdc

9.3 npu-smi info

sudo npu-smi info

本机实测输出:

+-------------------------------+-----------------+--------------------------------------------+
| npu-smi 25.5.2                | Version: 25.5.2 |                                            |
+-------------------------------+-----------------+--------------------------------------------+
| NPU     Name                  | Health          | Power(W)   Temp(C)   Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)  Memory-Usage(MB)                |
+===============================+=================+============================================+
| 49152   310P3                 | OK              | NA         49        0     / 0             |
| 0       0                     | 0000:C1:00.0    | 0          1904 / 44213                    |
+===============================+=================+============================================+

成功的三个标志:识别出 310P3Health=OK、显存 44213MB,且 Bus-Id 与 lspci 一致。

9.4 内核日志

sudo dmesg | grep -iE "davinci|devdrv|ascend" | tail

正常示例:

devdrv_device_driver 0000:c1:00.0: Adding to iommu group 8
[ascend] [ascend_logdrv] [log_drv_module_init 340] Host logdrv module init successfully.
[dbl_runenv_config] [recfg_init 82] Module init success.

十、非 root 用户权限问题(重要)

驱动装好只是第一步,普通用户能不能用是另一个坑。

10.1 现象

安装成功后,普通用户执行 npu-smi info 直接报错,而 sudo npu-smi info 一切正常:

$ npu-smi info
DrvMngGetConsoleLogLevel failed. (ret=4)
dcmi module initialize failed. ret is -8005

$ sudo npu-smi info
# 正常输出

10.2 原因

驱动创建的设备节点属于 HwHiAiUser 组、权限为 0660

$ ls -l /dev/davinci* /dev/devmm_svm /dev/hisi_hdc
crw-rw---- 1 HwHiAiUser HwHiAiUser 501, 0 /dev/davinci0
crw-rw---- 1 HwHiAiUser HwHiAiUser 502, 0 /dev/davinci_manager
crw-rw---- 1 HwHiAiUser HwHiAiUser 500, 0 /dev/devmm_svm
crw-rw---- 1 HwHiAiUser HwHiAiUser 499, 0 /dev/hisi_hdc

而当前用户 radxa 不在 HwHiAiUser 组里:

$ id radxa
uid=1000(radxa) gid=1000(radxa)=1000(radxa),27(sudo),29(audio),44(video),46(plugdev),
100(users),105(render),106(gpio),107(spidev),108(pwm),113(i2c)

0660 表示仅属主和属组可读写,radxa 两者都不是,自然访问不了设备节点,
npu-smi 在初始化 DCMI 时就失败(ret=-8005)。

补充:此时 ls -l 显示的属主是 HwHiAiUser:HwHiAiUser(而不是 root),
这是 host_sys_init.sh start 执行后 udev 按规则重新设权的结果,属组依然是 HwHiAiUser,结论不变。

10.3 解决:加入 HwHiAiUser

sudo usermod -aG HwHiAiUser radxa

然后必须重新登录(或新开一个会话)让组权限生效:

id radxa
# ... 1002(HwHiAiUser)     ← 出现即表示组关系已写入

验证(注意不要加 sudo):

npu-smi info

两个提醒

  • usermod -aG 里的 -a 千万不能省,否则会覆盖用户原有的附加组;
  • 这个改动只影响新建的会话,已经存在的进程不会自动获得新组——下一小节就是这里翻的车

10.4 踩坑:注销重登都没用,因为 tmux 是"长生不老"的

本机真实遇到的情况:id radxa 已经显示 1002(HwHiAiUser)
getent initgroups radxa 也正确返回 1002,
但终端里 npu-smi info 依然失败,而且注销重登也无效

关键证据:看进程,别看 id
$ id radxa
... 113(i2c),1002(HwHiAiUser)          # 看起来已经生效了

$ npu-smi info
DrvMngGetConsoleLogLevel failed. (ret=4)
dcmi module initialize failed. ret is -8005     # 实际仍然失败

检查进程实际持有的组(而不是 id 命令的输出):

$ pgrep -u1000 | while read p; do grep ^Groups /proc/$p/status; done | sort -u
Groups: 27 29 44 46 100 105 106 107 108 113 1000

没有 1002。这说明 id 查的是 NSS 数据库(已更新),
而进程持有的是登录时写死在凭据里的组列表(尚未更新)——这是两回事。

继续定位到具体进程:

$ ps -eo pid,lstart,cmd | grep -E "tmux|systemd --user"
2458  Tue Sep 15 13:45:33 2026  /lib/systemd/systemd --user
4870  Tue Sep 15 14:03:37 2026  tmux

$ grep ^Groups /proc/4870/status
Groups: 27 29 44 46 100 105 106 107 108 113 1000   # 缺 1002

$ stat -c %y /etc/group
2026-09-15 13:48:04                              # 改组的时间
根因

时间线摊开就很清楚了:

时刻事件
13:45:33systemd --user 启动(持有组列表)
13:48:04usermod -aG HwHiAiUser radxa 生效(NSS 更新)
14:03:37tmux server 启动 —— 继承自那个旧的 systemd --user

两个关键机制:

  1. 组凭据在进程创建时就固化了。子进程从父进程继承组列表;
    所有派生自 13:45 那个 systemd --user 的进程,组列表里永远不会有 1002。

  2. 注销不会杀掉 tmuxsystemd-logind 的默认配置是:

    $ systemctl show systemd-logind -p KillUserProcesses
    KillUserProcesses=no        # 默认值
    

    即用户注销时保留其进程。tmux server(及其中的 shell)因此长期存活,
    跨越了多次"注销 + 重新登录",每次都把旧组列表原样传给新开的窗口。

佐证:注销后重新登录,shell 仍显示 pts/3 ... tmux(4870).%0 —— 复用了同一个 tmux server。
另外 gnome-terminal-server(6285) 的父进程正是 systemd --user(2458),同样继承了旧组列表。

解决办法

方案 A:立即生效(推荐,无需重启)

sg HwHiAiUser -c 'npu-smi info'      # 单条命令以 HwHiAiUser 为主组执行
# 或
newgrp HwHiAiUser                    # 进入一个新 shell,之后的命令都带上该组

方案 B:彻底生效 —— 必须结束旧的 tmux / 用户会话

# 1) 杀掉所有旧 tmux(关键!)
tmux kill-server

# 2) 结束用户级 systemd 会话(下次登录时重建)
sudo systemctl restart user@1000.service

# 3) 完全注销并重新登录(或直接重启)

只注销 GUI 是不够的KillUserProcesses=no 会保留 tmux 等进程,
重登后又接回旧的 tmux server。必须先 tmux kill-server
否则新会话依旧继承旧组列表。

方案 C:重启(最省心)

重启后所有进程重建,组列表自然正确。

方案 D:ACL 授权(当前会话立即生效,本机已实测)

不想注销、不想杀 tmux 的话,可以直接对设备节点授权:

sudo setfacl -m u:radxa:rw \
    /dev/davinci0 /dev/davinci_manager /dev/devmm_svm /dev/hisi_hdc

组列表陈旧的 shell 中验证(本机实测通过):

$ setpriv --reuid=1000 --regid=1000 --groups=1000 --inh-caps=-all \
    /usr/local/bin/npu-smi info
| 49152   310P3   | OK | NA  40 | 0  1904 / 44213 |

⚠️ ACL 是临时手段:设备节点会在驱动加载 / host_sys_init.sh start 时被重新创建,
届时 ACL 会丢失(本机没有对应的 udev 规则来保留它)。
所以 ACL 适合"先让当前终端立刻能用";
长效做法还是重启(重启后组成员关系已正确,无需 ACL)。
若希望 ACL 持久,需自行添加 udev 规则,例如:

# /etc/udev/rules.d/99-ascend-acl.rules
SUBSYSTEM=="davinci", RUN+="/usr/bin/setfacl -m u:radxa:rw /dev/%k"
权威判据:别只看 id
检查方式反映的内容是否可信
id radxa / getent initgroups radxaNSS 数据库当前值仅代表新登录会怎样
grep ^Groups /proc/<pid>/status进程实际持有的组✅ 这才是真正生效的

排查这类"改组后依然没权限"的问题,一定要以后者为准。


十一、已知情况与遗留项

11.1 首次安装后模块不会自动加载

安装日志显示:

[INFO]install ko, hot reset status: ko_abort, first_time:y
[INFO]remove ko-put-path success

对应 driver_ko_install_manually() 的条件:

if [ "$hotreset_status" != "ko_abort" ] && [ "$first_time" = "y" ] || [ "$hotreset_status" = "scan_success" ]; then
    # 执行 modprobe_host_kos

本机 hotreset_status=ko_abortfirst_time=y,条件不成立,
因此跳过了自动 modprobe。这与本机不是华为整机、
日志中 The chip can not set the hot reset flag(芯片不支持设置热复位标志)有关,
属于预期的分支行为,不是安装失败

本次通过手动 sudo modprobe drv_pcie_host 完成了加载,设备节点与 npu-smi 均正常。

待确认:重启后是否能自动加载。modules.alias 中已注册 17 条 drv_pcie_host 的 PCI modalias:

$ grep -c drv_pcie_host /lib/modules/6.6.89-1-sky1/modules.alias
17
alias pci:v0000203Fd0000D500sv*sd*bc*sc*i* drv_pcie_host

理论上 udev 会按 PCI 设备自动匹配加载 drv_pcie_host,其余模块随依赖拉起。
但本机尚未重启验证,建议重启后确认:

npu-smi info          # 应仍能正常输出
ls -l /dev/davinci0

若重启后未自动加载,可把模块加入 /etc/modules,或依赖 host_sys_init.service

11.2 uio 模块缺失(无害)

$ sudo modprobe uio
modprobe: FATAL: Module uio not found in directory /lib/modules/6.6.89-1-sky1

内核配置为 # CONFIG_UIO is not set,而 host_sys_init.sh 会尝试 modprobe uio

实测无影响:驱动、设备节点、npu-smi 全部正常。
310P 走的是标准 PCIe 驱动路径,并不依赖 UIO 设备节点,该报错只是脚本硬编码 modprobe uio 产生的噪音。

11.3 drv_vascend 加载失败(仅影响 vNPU 虚拟化)

$ sudo modprobe drv_vascend
drv_vascend: Unknown symbol mdev_register_parent (err -2)
drv_vascend: Unknown symbol mdev_unregister_parent (err -2)

drv_vascend 提供 vNPU(虚拟化切分) 能力,需要内核的 mdev 支持。
本机内核未启用该功能,所以加载失败。

对当前使用无影响:以物理整卡方式使用 NPU 不需要这个模块。
只有需要把 310P 切分给多个虚拟机时才需要,届时得换用支持 mdev 的内核。

11.4 固件未安装

/usr/local/Ascend/firmware 不存在,即本次只装了驱动,没有装固件

可选固件包:

Ascend-hdk-310p-npu-firmware_7.8.0.5.216.run
Ascend-hdk-310p-npu-firmware_7.8.0.6.201.run
Ascend-hdk-310p-npu-firmware_7.8.0.7.220.run

驱动 version.info 声明的兼容固件范围是 compatible_version_fw=[6.4.0,6.4.99],[7.0.0,8.9.9]

当前驱动工作正常(Health=OK、温度 42–54°C),固件非必需;
若需使用依赖特定固件版本的功能,再按「先驱动、后固件」的顺序升级。


十二、操作速查

# ---- 硬件:BIOS 里把 PCIe link up timeout 从 300ms 改成 500ms,再确认认卡 ----
lspci -nn | grep -i 19e5

# ---- 环境检查 ----
uname -r                                    # 内核版本
dpkg -l | grep -E "dkms|linux-headers"      # dkms / 头文件是否齐全
ls -d /lib/modules/$(uname -r)/build        # 构建目录软链接

# ---- 观察补丁要探测的枚举 ----
grep -n "enum profile_type" /lib/modules/$(uname -r)/build/include/linux/profile.h

# ---- 定位编译错误 ----
sudo grep -nE "error:" /var/lib/dkms/davinci_ascend/1.0/build/make.log

# ---- 解包 / 改补丁 / 重打包 ----
./xxx.run --noexec --extract=$PWD/extract
bash extract/driver/script/makeself.sh --header extract/driver/script/makeself-header.sh \
     --help-header extract/driver/script/help.info --pigz --complevel 4 --nomd5 --sha256 \
     "$PWD/extract" "$PWD/xxx-fixed.run" "ASCEND DRIVER RUN PACKAGE" driver/script/install.sh

# ---- 安装 ----
sudo ./xxx-fixed.run --full --quiet
grep Driver_Install_Status /etc/ascend_install.info       # complete = 成功
dkms status | grep davinci                                # built = 编译成功

# ---- 加载与验证 ----
sudo modprobe drv_pcie_host
ls -l /dev/davinci0 /dev/devmm_svm /dev/hisi_hdc
sudo npu-smi info

# ---- 非 root 使用 ----
sudo usermod -aG HwHiAiUser $USER && newgrp HwHiAiUser    # 或重新登录
npu-smi info

# ---- 卸载(如需回退)----
sudo ./xxx-fixed.run --uninstall

十三、总结

项目结果
问题一插卡后系统完全认不到卡(lspci19e5:d500
问题一原因主板 BIOS 的 PCIe link up timeout 默认 300ms,短于 310P 完成链路训练所需时间,BIOS 判定插槽无设备
问题一解决BIOS 中把 PCIe link up timeout 由 300ms 改为 500ms(不涉及驱动/内核改动)
问题二DKMS 编译失败,redeclaration of 'enum profile_type'
失败根因CIX 6.6 定制内核在 include/linux/profile.h 中保留了主线 v5.17 已删除的 enum profile_type,与驱动按版本号自行定义的同名枚举冲突,叠加 -Werror 导致构建失败
修复方式构建时探测内核头文件,命中则定义宏,驱动改为 #include <linux/profile.h> 复用内核定义;对两类内核都正确
修改文件driver/kernel/dev_inc/inc/dbl/kernel_adapt.hdriver/kernel/Makefile_mini1910p
关键约束补丁必须打进 .run 包,安装时 /usr/src 会被包内源码覆盖
产物Ascend-hdk-310p-npu-driver_25.5.2_linux-aarch64-fixed.run
安装状态Driver_Install_Status=completedkms: built
运行验证npu-smi info 识别到 310P3Health=OK、显存 44213MB
非 root 使用用户加入 HwHiAiUser 组并重新登录(注意 tmux 会继承旧组)
遗留项重启后自动加载未实测;固件未安装;uio / drv_vascend 缺失不影响当前物理整卡使用

回头看,这两个坑其实分属两个层面:
认不到卡是主板固件层的时序问题,一颗参数就能解决;
编译失败是驱动对内核版本的假设过于乐观,遇到"版本号新、接口旧"的厂商定制内核就翻车。
希望这篇记录能让同样在这块板子上折腾 310P 的朋友少走点弯路。

如果这篇对你有帮助,欢迎点赞收藏;有不同情况的也欢迎在评论区交流。


免责说明:本文记录的是特定硬件与软件组合(香橙派 6 / Orange Pi 6,CIX P1 / CD8180 + 刷 Radxa CIX 镜像的 Debian 12 + 内核 6.6.89-1-sky1 + 昇腾驱动 25.5.2)上的实测过程。
由于香橙派 6 与 Radxa Orion O6 同为 CIX P1(CD8180)方案、共用同一套 CIX 内核与固件,
文中涉及的 CIX 内核头文件路径、6.6.89-1-sky1 等主机名/路径信息均来自 Radxa 镜像,在 Orion O6 上大概率同样适用,但请以自己环境的实际情况为准。
BIOS 选项名称与位置、驱动源码路径可能随 BIOS 版本和驱动版本变化,请自行核实。
修改厂商驱动源码属于非官方做法,仅供排障参考;生产环境建议向厂商反馈并等待官方适配。

本文内容由 AtomCode + deepseek-flash 辅助生成;文中命令、日志与输出均来自实机实测,整理过程中的人工判断与结论仅供参考,请结合自身环境核实后再操作。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐