Linux 内核 E3C EC168 DVB-T USB 接收机支持:设备卡表(cardlist)与驱动源码解析
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
EC168 是 E3C(eMPIA? 不,E3C Technology)推出的 USB2.0 DVB-T 接收方案,Linux 内核通过dvb-usb-v2框架中的 EC168 驱动对其提供支持。本文以内核文档 dvb-usb-ec168-cardlist.rst 中的设备卡表(card list)为主体,结合 ec168.c、ec168.h 等源码,完整说明该驱动支持哪些硬件(按 USB ID 识别)、底层如何工作、如何编译启用并验证设备是否被内核正确驱动。
一、文档定位:media 子系统的设备卡表体系
在内核 Media Controller(drivers/media)文档体系中,"card list"(卡片清单)用于把每一款被驱动支持的实际硬件与其厂商 VID(Vendor ID)/ 产品 PID(Product ID)组合逐条登记,方便用户查询"我手上的这个 USB 棒能不能被内核识别"。卡表文档的规范索引见 cardlist.rst,其中 USB 类设备统一由 usb-cardlist.rst 汇总跳转,EC168 正是该体系下众多 DVB USB 卡表之一。
dvb-usb-ec168-cardlist.rst 是面向 EC168 驱动的专属卡表。从源码驱动 ec168.c 的MODULE_DESCRIPTION("E3C EC168 driver")、ec168.h 的文件头注释以及固件名dvb-usb-ec168.fw可以确认,驱动、文档与硬件方案一一对应。
二、支持硬件清单:E3C EC168 reference design
卡表文档通过 reStructuredText 的flat-table指令定义了表格,共两列:Card name(设备名)与USB IDs(支持的 USB 设备 ID 集合)。当前版本内核中该卡表登记的设备如下:
| Card name(设备名) | USB IDs(USB 设备标识) |
|---|---|
| E3C EC168 reference design | 18b4:1689、18b4:fffa、18b4:fffb、18b4:1001、18b4:1002 |
其中18b4是厂商 ID(VID),:后的四位十六进制数是产品 ID(PID)。也就是说,只要 USB 设备枚举后返回的 idVendor 为0x18b4,且 idProduct 落在上述五个取值之一,内核的 EC168 驱动就会认领该设备。
该表登记的是 E3C 官方 EC168 参考设计板卡;市面上基于同一参考设计由 E3C 原厂或第三方贴牌出货的设备,往往也复用这几组 ID,因此这张表同时也是用户判断"杂牌 DVB-T 棒是否 EC168 主控"的快速清单。
三、USB ID 在源码中的定义与匹配
卡表文档中的五组 USB ID,在源码中以宏形式集中定义在 USB ID 头文件 dvb-usb-ids.h:
- 第 44 行:
#define USB_VID_E3C 0x18b4(VID) - 第 211~215 行分别定义了五个 PID 宏:
USB_PID_E3C_EC168 = 0x1689USB_PID_E3C_EC168_2 = 0xfffaUSB_PID_E3C_EC168_3 = 0xfffbUSB_PID_E3C_EC168_4 = 0x1001USB_PID_E3C_EC168_5 = 0x1002
这正好与卡表文档完全一一对应(从源码结构看,文档中列出的就是驱动ec168_id[]匹配表里全部条目),印证了"文档卡表 == 驱动实际支持的硬件集合"这一约定。
匹配表的真实落点
在 ec168.c 中,上述宏被组装进内核usb_device_id表:
static const struct usb_device_id ec168_id[] = { { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168, &ec168_props, "E3C EC168 reference design", NULL)}, { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_2, &ec168_props, "E3C EC168 reference design", NULL)}, { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_3, &ec168_props, "E3C EC168 reference design", NULL)}, { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_4, &ec168_props, "E3C EC168 reference design", NULL)}, { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_5, &ec168_props, "E3C EC168 reference design", NULL)}, {} }; MODULE_DEVICE_TABLE(usb, ec168_id);注意所有五条匹配记录共享同一个ec168_props(设备属性描述)和同一设备名字符串"E3C EC168 reference design"——这正是卡表文档中那唯一一行 Card name 的由来:五种 USB ID 在驱动看来是同一款参考设计硬件的不同固件/批次变体,不做功能区分。MODULE_DEVICE_TABLE(usb, ...)会把该表导出到模块的 USB id_table,供modprobe等工具做基于 VID/PID 的自动装载。
接着它注册了标准的usb_driver回调:
static struct usb_driver ec168_driver = { .name = KBUILD_MODNAME, .id_table = ec168_id, .probe = dvb_usbv2_probe, .disconnect = dvb_usbv2_disconnect, .suspend = dvb_usbv2_suspend, .resume = dvb_usbv2_resume, .no_dynamic_id = 1, .soft_unbind = 1, };即:USB 核心探测到匹配的18b4:xxxx设备后,会调用dvb_usbv2_probe,把设备交给通用的 dvb-usb-v2 框架,再由ec168_props中注册的专属回调完成固件下载、前端(demod)与调谐器(tuner)的挂载。no_dynamic_id = 1表示不允许运行时动态注入新的 VID/PID——意味着只有卡表/匹配表中列出的 ID 会被支持,这正是该卡表文档具有权威性的技术前提。
四、配套驱动源码:EC168 硬件方案是怎么工作的
卡片清单只是"对号",真正干活的是同一目录下的驱动实现。下面把 ec168.c 中反映出的硬件方案拆解开,帮助你理解这块接收棒的内外结构。
1. 整体架构:片上解调 + 外挂调谐器
解调器(Demod):EC168 主控芯片内部集成了 EC100 DVB-T 解调器。驱动在 ec168.c 中配置:
static struct ec100_config ec168_ec100_config = { .demod_address = 0xff, /* not real address, demod is integrated */ };注释明确说明
0xff并非真实 I2C 地址——解调器是片内集成的,走的是 USB 厂商命令而非外部 I2C。frontend_attach回调通过dvb_attach(ec100_attach, ...)挂载 EC100 前端。调谐器(Tuner):片外使用MXL5005S调谐器,I2C 地址
0xc6,其详细配置见 ec168.c 的ec168_mxl5003s_config(中频 4.57 MHz、晶振 16 MHz、ZIF 零中频、数字模式等)。tuner_attach回调通过mxl5005s_attach把它与前端的 I2C 适配器绑定。USB 端点:接口描述见
ec168_props中.bInterfaceNumber = 1与流配置DVB_USB_STREAM_BULK(0x82, 6, 32 * 512)——即接口 0 是 HID,接口 1 才是 DVB-T,TS 流通过批量端点0x82传输(6 个 urb、每个 32×512 字节缓冲区)。
2. 厂商控制命令协议
设备与主机之间的寄存器读写全部通过 USB vendor control transfer 完成。命令集合定义在 ec168.h:
enum ec168_cmd { DOWNLOAD_FIRMWARE = 0x00, /* 下载固件 */ CONFIG = 0x01, DEMOD_RW = 0x03, /* 读写片上解调器寄存器 */ GPIO = 0x04, STREAMING_CTRL = 0x10, /* 开启/停止 TS 流 */ READ_I2C = 0x20, /* 经 USB 桥接读外部 I2C */ WRITE_I2C = 0x21, /* 经 USB 桥接写外部 I2C */ HID_DOWNLOAD = 0x30, GET_CONFIG, SET_CONFIG, READ_DEMOD, WRITE_DEMOD, };而每次传输的数据信封由struct ec168_req(ec168.h)承载:
struct ec168_req { u8 cmd; /* [1] */ u16 value; /* [2|3] */ u16 index; /* [4|5] */ u16 size; /* [6|7] */ u8 *data; };请求在 ec168.c 的ec168_ctrl_msg()中被翻译成一次usb_control_msg()调用,并根据命令方向设置USB_DIR_IN/USB_DIR_OUT。实现细节上可以看到一个典型的嵌入式问题规避:每次传输前强制msleep(1); /* avoid I2C errors */,以等待器件内部状态稳定,避免后续 I2C 访问出错。
3. 桥接 I2C:让 Linux I2C 子系统操作片外器件
由于解调器和调谐器处于器件内部/外部总线上,而主机只能访问 USB,驱动实现了虚拟 I2C 适配器。在ec168_i2c_xfer()(ec168.c)中可以看到地址分派逻辑:
- 目标地址为片上解调器(
0xff虚拟地址)→ 转为READ_DEMOD/WRITE_DEMOD,把寄存器号编码进index = 0xff00 + reg; - 其他 I2C 地址(如调谐器
0xc6)→ 转为READ_I2C/WRITE_I2C厂商命令,I2C 地址编码进index = 0x0100 + addr; - 单条消息长度超过 1 或涉及多段消息时按
num > 2直接返回-EINVAL拒绝。
最终通过ec168_i2c_algo注册为标准的i2c_algorithm,使dvb-usb-v2框架与 EC100/MXL5005S 驱动可以透明地走i2c_transfer()访问硬件。
4. 固件下载与状态机
EC168 与绝大多数 DVB USB 方案一样遵循COLD/WARM 两阶段启动:
- 上电后器件处于 COLD 态(无固件)。
ec168_identify_state()(ec168.c)通过GET_CONFIG命令读回状态字节,0x01判为 WARM,否则为 COLD。 - COLD 态下,
ec168_download_firmware()(ec168.c)把固件按每包最大 2048 字节分块下发(#define LEN_MAX 2048 /* max packet size */),随后发送SET_CONFIG("set warm?")、一次 GPIO 写(源码注释坦承 "really needed - no idea what does")、以及一次激活调谐器 I2C 的WRITE_I2C(0x00c6)。固件文件名由 ec168.h 定义:dvb-usb-ec168.fw,并经MODULE_FIRMWARE()声明,配合CONFIG_EXTRA_FIRMWARE或发行版固件包提供。
这一大段源码即是对卡表文档的最好注解:表中那些 USB ID 标识的"reference design",就是"内置 EC100 解调、外挂 MXL5005S、需要下载dvb-usb-ec168.fw固件"的一整套 dvb-usb-v2 设备。
五、内核配置与模块装载
要启用该设备支持,在内核配置中打开对应选项即可。Kconfig 条目见 drivers/media/usb/dvb-usb-v2/Kconfig:
config DVB_USB_EC168 tristate "E3C EC168 DVB-T USB2.0 support" depends on DVB_USB_V2 select DVB_EC100 select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT help Say Y here to support the E3C EC168 DVB-T USB2.0 receiver.它依赖DVB_USB_V2(dvb-usb-v2 核心框架),并且强制select DVB_EC100(片上解调器驱动),在开启MEDIA_SUBDRV_AUTOSELECT时自动选择 MXL5005S 调谐器驱动。Makefile将其编入模块后,可手动装载:
# 编译内核时配置 CONFIG_DVB_USB_EC168=m,然后: modprobe dvb-usb-ec168装载成功后,若再插入上表任一 VID/PID 的接收棒,内核日志会打印设备识别信息;modinfo dvb-usb-ec168可以查看模块声明支持的固件文件(firmware: dvb-usb-ec168.fw)与描述信息。
六、实践验证:如何确认你的 EC168 设备被驱动
由于卡表以 USB ID 为唯一判据,验证步骤非常直接(以下命令依赖系统已安装 usbutils 等常规工具,仅用于查看与确认,不修改内核源码):
# 1. 查看 USB 总线上是否存在匹配的 VID:PID lsusb | grep -i 18b4 # 期望看到形如 "Bus xxx Device xxx: ID 18b4:1689 ..." 的输出 # 2. 查看驱动是否已认领该接口(需以有权限的用户执行) lsusb -t # 查看 USB 设备树中该设备绑定的 driver # 3. 查看内核日志中的枚举与固件下载过程 dmesg | grep -iE 'ec168|dvb|18b4'如果dmesg中出现 "downloading firmware" 之后报错或反复 COLD/WARM 切换,最常见的根因是系统缺少/lib/firmware/dvb-usb-ec168.fw——该文件并非内核源码自带,需从发行版固件包(如linux-firmware)或设备厂商渠道获取并放置到标准固件目录。
七、小结
围绕 dvb-usb-ec168-cardlist.rst 这张"一行表格",我们可以串联起完整的硬件支持链路:
- 卡表文档登记 E3C EC168 reference design 及其 5 个 USB ID(
18b4:1689/fffa/fffb/1001/1002),这是硬件识别层面的官方清单; - dvb-usb-ids.h 以宏形式固化上述 ID,ec168.c 的
ec168_id[]把它们映射到同一套设备属性并注册 USB 驱动; - 驱动实现层面,EC168 方案 = 片内 EC100 DVB-T 解调 + 片外 MXL5005S 调谐器 + 虚拟 I2C 桥 + 厂商控制命令下载
dvb-usb-ec168.fw固件的 dvb-usb-v2 标准流程。
对于开发者而言,这张卡表既是"改硬件/换固件时如何新增一个 PID 条目"的参照入口,也是排查"USB 棒插上却没反应"问题的第一张排查对照表。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考