收起左侧

建议虚拟机USB直通改变单一的VID和PID识别设备方法,增加bus号

1
回复
41
查看
[ 复制链接 ]

2

主题

19

回帖

0

牛值

江湖小虾

目前飞牛USB直通简单地用usb设备号作为设备区分标志,插两个同型号的usb设备100%就会出现这个网友所遇到的问题(硬件直通添加usb无法启动虚拟机

https://club.fnnas.com/forum.php?mod=viewthread&tid=40334

(出处: 飞牛私有云论坛 fnOS)),原因是虚拟机没发区分哪个是哪个usb设备,直接就报错。

所以建议飞牛开发大佬能够将虚拟机USB直通改变单一的VID和PID识别设备方法,增加bus号,解决同型号多设备的usb直通问题。

我让AI临时帮我解决了,但是普通网友的动手能力肯定不够的。希望官方出手解决。

全新飞牛 + iStoreOS 多 4G 模块 USB 直通 · 从零跑通手册

目标:在一台全新飞牛上,把多个同型号 4G 模块(如 EC20/EC25,VID:PID 相同)直通进 iStoreOS 虚拟机,实现开机自动挂载、拔插自由、GUI 不报错。照本手册阶段 0→8 一遍跑通。
适用终端:MobaXterm(多行粘贴正常)。脚本创建首选 MobaXterm 左侧 SFTP 上传

工具与粘贴约定(先读,省后面所有坑)

  • 本教程命令按 MobaXterm 编写,多行命令可整段复制粘贴,一次回车执行。
  • 创建脚本文件有两条路,强烈推荐路线甲
    • 路线甲(首选,零风险):在 Windows 用任意编辑器(记事本/VSCode/Notepad++)把脚本内容存成文件 → 用 MobaXterm 左侧 SFTP 面板拖到目标目录 → 回终端 chmod +x。MobaXterm 连上 SSH 后左侧自动就是 SFTP,在左侧上方路径框输入 /usr/local/bin/etc/libvirt/hooks 回车即可定位,直接把文件拖进去。完全不经过终端粘贴,不可能出错。
    • 路线乙(备选):终端 nano 路径 → 粘贴内容 → Ctrl+O 回车 → Ctrl+X粘完必须 wc -l 路径 看行数,跟教程给的行数对得上才算成功;对不上说明你的终端/配置吞了换行,退回路线甲。
  • 本文不再使用 base64(base64 只适合“原样复制某台现成机器”,不适合“通用全新教程”,因为 VM 名/VID/PID 都要改)。

原理 30 秒(重建时心里有底)

  • 同型号冲突的根因:libvirt 按 vendor/product 在 XML 里声明直通时,分不清两个一模一样的设备,报 Multiple USB devices ... use <address> to specify one
  • 本方案的解法:开机时不在 XML 里写任何 USB 直通,虚拟机先空着启动(永远能起,不会报 Multiple);启动完成后由 libvirt hook 触发脚本,脚本实时读 sysfs 找出当前在线的、VID/PID 匹配的模块,拿到每个设备真实的 bus/device 号,逐个 attach-device 热挂。插几个挂几个、拔了的不挂也不报错。
  • 关键简化:因为不在 XML 里硬绑定,你不需要bus/port/device只需要模块的 VID 和 PID 两个值
  • USB 直通不需要 IOMMU / VT-d(那是 PCIe 直通才要的)。USB 走 managed hostdev + usbfs,只要 CPU 虚拟化(VT-x/AMD-V)开了即可。别去折腾 IOMMU,白费功夫。
  • managed='yes' 的边界(血的教训):它假定设备占用/释放全程由 libvirt 控制。虚拟机运行时任何让 USB“断开→重连”的事件(物理拔模块 / 接触不良 / 客户机里发 AT+CFUN=1,1 软重启)都会污染 libvirtd 内存,导致后续挂不上、甚至连累另一个模块。所以:动 USB 必须在关机窗口;要复位模块用“重启虚拟机”代替 AT 软重启。
  • GUI 红字 invalid argument 的来源:只有两种——① 绕过 GUI 用命令行改过配置导致 GUI 账本对不上;② libvirtd 被重启(系统更新/自救命令)后飞牛后端连接断了。全新环境按本教程规矩来,①不会发生;②万一遇到,第 8 节一条命令修好,不影响虚拟机。

阶段 0:硬件与 BIOS 准备

  1. BIOS 开 CPU 虚拟化:Intel 开 Intel VT-x(Virtualization Technology),AMD 开 SVM Mode。不开则 kvm 起不来,虚拟机建不了。
  2. 规划 USB 插口:把要直通的 4G 模块插到固定的物理 USB 口上,插好后尽量别换口、别换集线器。两个模块插哪两个口都行(hook 方案不依赖具体口,但固定插口能让 device 号更稳定、减少意外)。
  3. 模块先插好、上电,再开始后面的操作。

阶段 1:飞牛初始化 + 安装虚拟机功能

飞牛 GUI 路径随版本略有差异,下面给“GUI 操作 + 命令行验证”双保险,命令行能确认组件在位即可,不必纠结 GUI 菜单名。

  1. GUI 安装虚拟机功能:打开飞牛 → 应用中心/套件中心 → 安装并启用「虚拟机」应用 → 首次打开让它完成初始化(会部署 libvirt/qemu)。

  2. 命令行验证组件在位(MobaXterm 连上飞牛 SSH,逐条执行):

    which qemu-system-x86_64          # 应输出 /usr/bin/qemu-system-x86_64
    systemctl is-active libvirtd      # 应输出 active
    lsmod | grep -E 'kvm_intel|kvm_amd'   # 应有一行 kvm_intel 或 kvm_amd
    
    • lsmod 为空:说明虚拟化模块没加载,先 modprobe kvm_intel(AMD 用 modprobe kvm_amd);若报错,回 BIOS 确认 VT-x/AMD-V 已开。
    • qemu-system-x86_64 不存在:说明虚拟机套件没装好,回 GUI 重装/启用「虚拟机」。
  3. (可选确认)virsh version 能正常输出版本号,说明 libvirt 客户端可用。


阶段 2:准备 iStoreOS 镜像 + 飞牛 GUI 建机 + 装系统

  1. 下载 iStoreOS 镜像:到 iStoreOS 官方发布页下载 x86-64 版本(combinedgeneric,格式可能是 .img.gz / .img / .qcow2)。

  2. 格式转换(按需):飞牛建机一般要 qcow2。若你下的是 .img.gz,先解压再转换:

    gunzip -k xxx.img.gz                          # 得到 xxx.img
    qemu-img convert -f raw -O qcow2 xxx.img /vol1/xxx.qcow2   # 转成 qcow2,放到数据盘
    

    若下载的就是 .qcow2,跳过转换,直接上传到飞牛数据盘某目录备用。

  3. 飞牛 GUI 建机(⚠️ 关键纪律):

    • 新建虚拟机,磁盘指向第 2 步的 qcow2(不要新建空盘,除非你要从 ISO 装 iStoreOS)。
    • 规格参考:CPU 1 核、内存 1024 MB、磁盘够用即可。
    • 网卡选「桥接」,桥接到你飞牛 LAN 口对应的桥(飞牛里常是 br0 或某个 ovs 桥,按你环境选;选桥接 iStoreOS 才能拿到局域网 IP,方便后面 SSH 进去装驱动)。
    • ⚠️ 建机过程中以及之后,绝不要在 GUI 里添加任何 USB 直通设备。 这是本方案 GUI 不红字、不冲突的前提。
    • 记下建出来的虚拟机名(飞牛常给随机名,下文用 VMNAME 代指,你自己替换;本文示例沿用 t08lhu72)。
  4. 启动虚拟机、装/进入 iStoreOS:GUI 点启动,打开控制台(或命令行 virsh console VMNAME,退出按 Ctrl 再按 ])。若是 combined 镜像,启动即进系统;若需安装,按 iStoreOS 提示装到虚拟磁盘。

⚠️ iStoreOS 联网 IP 坑(OpenWrt 系经典,全新必遇):iStoreOS 默认 LAN IP 常是 192.168.1.110.0.0.1,可能跟你家局域网不在同网段、或与飞牛/路由器冲突,导致浏览器打不开 LuCI、也 ping 不通。
解决:用 virsh console VMNAME 进 iStoreOS 命令行,编辑 /etc/config/networkconfig interface 'lan'option ipaddr,改成你家局域网里一个空闲且同网段的 IP(如 192.168.x.200),保存后 /etc/init.d/network restart(或直接重启虚拟机)。改完用浏览器访问新 IP 进 LuCI,并确认 iStoreOS 能上外网ping -c 3 8.8.8.8 通),下一步装包要用外网。


阶段 3:iStoreOS 内装 4G 驱动包

此时 4G 模块还归宿主机、没直通,iStoreOS 里看不到设备是正常的;但驱动包可以提前装好,等阶段 5 直通后设备一进来就自动识别。

  1. 浏览器进 iStoreOS 的 LuCI → 系统 → 软件包 → 更新列表opkg update,需上一步外网已通)。
  2. 搜索并安装以下包(包名以 iStoreOS 软件源实际为准,OpenWrt 通用名如下;LuCI 搜 modem/qmi/option 也能找到):
    • kmod-usb-serial-option(串口驱动,产生 ttyUSB*)
    • kmod-usb-serial-wwan
    • kmod-usb-net-qmi-wwan(QMI 网卡驱动,产生 wwan* / cdc-wdm*)
    • libqmi-utils(提供 qmicli 拨号工具)
    • luci-proto-qmi(LuCI 图形化 QMI 拨号,推荐)
    • 可选:luci-app-mwan3 + mwan3(后面做双线负载均衡用)
  3. 装完先不用重启,进入阶段 4、5;直通后设备进来驱动会自动绑定。

阶段 4:查模块 VID/PID + 在宿主机建 hook 两件套

4.1 查你自己模块的 VID/PID(填进脚本用)

飞牛终端执行:

lsusb | grep -i -E 'quectel|2c7c|lte|modem'

看输出形如 ID 2c7c:0125 Quectel ... EC25 ...冒号前是 VID、冒号后是 PID。把这两个值记下(EC20/EC25 通常就是 2c7c / 0125,但以你机器 lsusb 实际为准)。

若一行都 grep 不到,说明模块没被宿主机识别:检查插口/供电,或 lsusb 看全部设备找你的模块型号。

4.2 创建核心脚本 attach-ec25.sh

把下面整段存成文件 attach-ec25.sh(Windows 编辑器存,注意把开头三行 VM/VID/PID 改成你自己的值):

#!/bin/sh
# attach-ec25.sh —— 把当前在线的 4G 模块热挂进虚拟机
# 无参数:挂所有在线模块(开机 hook 用)
# 两个参数 <bus> <dev>:只挂这一个(udev 用,主线可不用)
#
# ★★★ 改成你自己的值(VID/PID 见 4.1;VM 见阶段 2 记下的虚拟机名)★★★
VM="t08lhu72"
VID="2c7c"
PID="0125"
# ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

LOG=/var/log/attach-ec25.log
DONE=/var/run/attach-ec25.done
MAX_RETRY=5

# 日志超 512K 自动保留尾部 200 行
if [ -f "$LOG" ] && [ "$(stat -c%s "$LOG" 2>/dev/null || echo 0)" -gt 524288 ]; then
  tail -n 200 "$LOG" > "$LOG.tmp" && mv -f "$LOG.tmp" "$LOG"
fi
log(){ echo "$(date '+%F %T') $*" >> "$LOG"; }
norm(){ printf '%d' "$1" 2>/dev/null; }
is_attached(){ grep -qxF "$1:$2" "$DONE" 2>/dev/null; }
mark_attached(){ echo "$1:$2" >> "$DONE"; }

attach_one(){
  case "$1$2" in ''|*[!0-9]*) log "skip bad bus/dev: $1 $2"; return 1;; esac
  b=$(norm "$1"); d=$(norm "$2")
  if is_attached "$b" "$d"; then log "skip already-done bus=$b device=$d"; return 0; fi
  f="/tmp/ec25_${b}_${d}.xml"
  cat > "$f" <<EOF
<hostdev mode='subsystem' type='usb' managed='yes'>
  <source><address bus='$b' device='$d'/></source>
</hostdev>
EOF
  i=0
  while [ "$i" -lt "$MAX_RETRY" ]; do
    if virsh attach-device "$VM" "$f" >>"$LOG" 2>&1; then
      log "OK   attached bus=$b device=$d"; mark_attached "$b" "$d"; return 0
    fi
    i=$((i+1)); sleep 2
  done
  log "FAIL attach bus=$b device=$d after $MAX_RETRY tries"; return 1
}

log "=== run start args=[$*] ==="

if ! virsh domstate "$VM" 2>/dev/null | grep -q running; then
  log "VM not running, exit"; exit 0
fi

if [ "$#" -ge 2 ]; then
  attach_one "$1" "$2"
else
  : > "$DONE"
  found=0
  if [ -d /sys/bus/usb/devices ]; then
    for devdir in /sys/bus/usb/devices/*/; do
      devname="${devdir%/}"; devname="${devname##*/}"
      case "$devname" in *:*) continue;; esac
      [ -r "${devdir}idVendor" ] || continue
      [ "$(cat "${devdir}idVendor" 2>/dev/null)" = "$VID" ] || continue
      [ "$(cat "${devdir}idProduct" 2>/dev/null)" = "$PID" ] || continue
      [ -r "${devdir}busnum" ] && [ -r "${devdir}devnum" ] || continue
      attach_one "$(cat "${devdir}busnum")" "$(cat "${devdir}devnum")"; found=1
    done
  fi
  if [ "$found" = 0 ] && command -v lsusb >/dev/null 2>&1; then
    log "sysfs found nothing, fallback lsusb"
    lsusb -d "${VID}:${PID}" 2>/dev/null | awk '{gsub(/:/,"",$4); print $2, $4}' \
      | while read b d; do attach_one "$b" "$d"; done
  fi
fi
log "=== run end ==="

4.3 创建 hook 脚本 qemu

把下面整段存成文件 qemu文件名就叫 qemu,不要加 .sh;把里面 t08lhu72 改成你的虚拟机名,必须和 4.2 的 VM 一致):

#!/bin/sh
# libvirt qemu hook:记录每次调用;虚拟机 started 后后台热挂 4G 模块
GUEST="$1"; ACTION="$2"; SUB="$3"
echo "$(date '+%F %T') hook guest=$GUEST action=$ACTION sub=$SUB" >> /var/log/libvirt-hook-qemu.log

if [ "$GUEST" = "t08lhu72" ] && [ "$ACTION" = "started" ]; then
  nohup /usr/local/bin/attach-ec25.sh >/dev/null 2>&1 &
fi
exit 0

4.4 上传 + 赋权 + 自检

路线甲(SFTP 上传):MobaXterm 左侧路径框分别输入 /usr/local/bin/etc/libvirt/hooks(后者若不存在,先在终端 mkdir -p /etc/libvirt/hooks),把 attach-ec25.sh 拖进前者、qemu 拖进后者。然后回终端:

chmod +x /usr/local/bin/attach-ec25.sh /etc/libvirt/hooks/qemu

路线乙(nano 粘贴)nano /usr/local/bin/attach-ec25.sh 粘 4.2 内容保存;mkdir -p /etc/libvirt/hooks && nano /etc/libvirt/hooks/qemu 粘 4.3 内容保存;再执行上面 chmod

自检(应回显 syntax OK,行数约 67 / 8):

sh -n /usr/local/bin/attach-ec25.sh && sh -n /etc/libvirt/hooks/qemu && echo "syntax OK"
wc -l /usr/local/bin/attach-ec25.sh /etc/libvirt/hooks/qemu

sh -n 报语法错,几乎一定是文件被吞了换行/带了 Windows 换行符,执行 sed -i 's/\r$//' /usr/local/bin/attach-ec25.sh /etc/libvirt/hooks/qemu 后重测;仍不行就改用路线甲 SFTP 重传。


阶段 5:确认无硬绑定 + 开机自启 + 触发挂载 + 验证

  1. 确认虚拟机定义里没有 USB 直通(全新建机本来就没有,确认一下,防止手滑在 GUI 加过):

    virsh dumpxml VMNAME | grep -c hostdev     # 应输出 0
    

    若不为 0,说明你在 GUI 加过直通,回 GUI 把 USB 设备删掉,或用 virsh edit VMNAME 删掉 <hostdev>...</hostdev> 段。

  2. 开启开机自启

    virsh autostart VMNAME
    
  3. 重启虚拟机触发 hook(让刚建的 hook 生效,挂上模块):

    virsh destroy VMNAME; sleep 2; virsh start VMNAME; sleep 12
    
  4. 看日志

    echo "==hook=="; tail -n 6 /var/log/libvirt-hook-qemu.log
    echo "==attach=="; tail -n 12 /var/log/attach-ec25.log
    

    预期:hook 日志有本次时间戳的 action=started;attach 日志有 === run start === + 每个在线模块一条 OK attached bus=3 device=... + === run end ===


阶段 6:iStoreOS 内验证设备 + 拨号上线

SSH 或 LuCI 进 iStoreOS:

lsusb | grep -i -E 'quectel|2c7c'     # 行数应等于模块数
ls /dev/ttyUSB*                        # 每模块约 4 个串口
ls /dev/cdc-wdm*                       # 个数应等于模块数
ip link | grep wwan                    # wwan0 / wwan1 ...

数字对得上 = 直通成功且驱动加载完毕。

拨号(每个模块一条线),二选一:

  • LuCI 图形化(推荐):网络 → 接口 → 添加,协议选 QMI,设备填 /dev/cdc-wdm0(第二条线填 cdc-wdm1),填 APN(移动 cmnet / 联通 3gnet / 电信 ctnet),保存应用。第二条线同理再建一个接口。

  • 命令行

    echo Y > /sys/class/net/wwan0/qmi/raw_ip     # QMI 必开 raw_ip,否则拿不到 IP
    ip link set wwan0 up
    qmicli -d /dev/cdc-wdm0 --wds-start-network="apn=cmnet,ip-type=4" --client-no-release-cid
    qmicli -d /dev/cdc-wdm0 --wds-follow-network &
    udhcpc -i wwan0
    

    第二条线把命令里的 0 全换成 1

两线都 UP 后,若要双线负载/故障转移,在 mwan3 里把两个接口加入策略即可。


阶段 7:整机 reboot 验证开机自挂(最终关)

之前阶段 5 只验证了“重启虚拟机”。整机重启时 libvirtd/USB/驱动/hook 的启动顺序最乱,是最容易翻车的一关,必须验一次:

reboot

等飞牛完全起来、虚拟机自启后,SSH 回去:

virsh list --all                       # VMNAME 应为 running
tail -n 12 /var/log/attach-ec25.log    # 应有本次开机的 run start + 每条 OK + run end

再进 iStoreOS 看 lsusb | grep 2c7c 行数对得上。这一关过了,整套方案在任何启动方式下都稳了。


阶段 8:日常纪律 + 急救 + GUI 红字处理

8.1 日常操作纪律

想做的事 正确动作 禁止
拔/插整个 4G 模块 virsh destroy VMNAME,动手,再 virsh start VMNAME 运行中拔模块本体
换 SIM 卡(只抽卡、不拔模块) 开机直接换没事;换完多数自动认,需重拨就 ifdown/ifup 该线;换运营商改 APN 运行中拔模块本体
模块抽风/换卡不认想复位 virsh destroy + start(=干净重启模块) 客户机内发 AT+CFUN=1,1
改 APN/查信号/qmicli 看卡状态 直接做,不动 USB 连接,安全

8.2 急救:设备挂不上 / 误运行中拔过模块

单行整粘(清 libvirtd 内存脏状态 + 重置模块 + 重挂;把 2c7c 换成你的 VID):

virsh destroy VMNAME 2>/dev/null; systemctl restart libvirtd; for d in /sys/bus/usb/devices/*/; do [ "$(cat ${d}idVendor 2>/dev/null)" = "2c7c" ] || continue; echo 0 > ${d}authorized 2>/dev/null; sleep 1; echo 1 > ${d}authorized 2>/dev/null; done; sleep 3; virsh start VMNAME; sleep 12; tail -n 8 /var/log/attach-ec25.log

8.3 GUI 红字 Error: invalid argument(不影响 VM/SSH)

先精准:

systemctl restart trim_sac.service trim_http_cgi.service; sleep 3; systemctl is-active trim_sac.service trim_http_cgi.service

不行则 systemctl restart trim_main.service trim_nginx.service;再不行 reboot处理后用浏览器无痕窗口测 GUI(排除缓存)。

8.4 命令行控制面板(GUI 不可用时也能完全控制)

virsh list --all
virsh shutdown VMNAME      # 优雅关机
virsh destroy  VMNAME      # 强制断 电
virsh start    VMNAME      # 开机
virsh console  VMNAME      # 进 iStoreOS 终端,退出按 Ctrl 再按 ]
virsh vncdisplay VMNAME    # 返回 :N 则用 VNC 客户端连 飞牛IP:590N 看画面

8.5 改配置前先备份

cp /etc/libvirt/qemu/VMNAME.xml /root/VMNAME_$(date +%F_%H%M).xml

系统更新与重建影响速查

事件 影响 处理
飞牛小更新/补丁 hook 不受影响;可能因 libvirtd 重启致 GUI 短暂红字 红字→8.3
飞牛升级 libvirt/qemu 包 hook 文件不受影响;可能红字 红字→8.3
飞牛大版本/重装系统盘(/vol1 数据盘在) hook 两件套丢失;qcow2 在,虚拟机不用重装 重做阶段 4、5(建机定义若丢则先 GUI 指向旧 qcow2 重建)
飞牛换机/数据盘丢 全丢 阶段 0→8 全跑
iStoreOS 升级(保留配置) 无影响 通常无需操作
iStoreOS 升级(不保留配置)/重置/重装 驱动包与拨号丢 重做阶段 3、6

红线清单(置顶 memorize)

  1. 绝不在飞牛 GUI 给这台机添加 USB 直通——它的「硬件/直通」栏显示为空是正确的,真实直通靠 hook 热挂。一旦在 GUI 加,立刻回到同型号冲突老路。
  2. 别删 /usr/local/bin/attach-ec25.sh/etc/libvirt/hooks/qemu,它们是开机自挂本体。
  3. 别在 GUI 点删除/移除该虚拟机,尤其命令行改过配置后;要改先按 8.5 备份。
  4. 运行中别拔模块本体、别发 AT+CFUN=1,1;动 USB 必在关机窗口,复位用重启虚拟机。
  5. 脚本里 VM/VID/PID 三处(attach 脚本三处 + hook 脚本一处 VM)必须与你环境一致,否则 hook 过滤不到、不挂设备。
  6. 创建脚本首选 MobaXterm SFTP 上传;用 nano 粘则粘完 wc -l 看行数,并对 sh -n 语法检查。

照本手册,全新飞牛从阶段 0 走到阶段 7 即可稳定服役;日后无论飞牛还是 iStoreOS 出问题,按“影响速查表”定位到对应阶段重做即可一遍跑通。👍

收藏
送赞
分享

7

主题

1万

回帖

0

牛值

管理员

社区上线纪念勋章社区共建团荣誉勋章飞牛百度网盘玩家fnOS1.0上线纪念勋章

感谢您提供临时解决思路,我们会将该问题反馈给相关开发团队评估,并持续跟进。

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则