
简介PCI转串口卡驱动程序合集面向需要为Y-7503、Y-7504、Y-7505、Y-7509、Y-7507等型号串口卡恢复或部署串行通信的硬件运维与工控调试人员重点解决操作系统无法识别PCI串口卡、I/O端口与中断资源分配异常、多串口并发通信不稳定等问题。压缩包共828个文件、约32.11MB包含inf安装信息、sys驱动核心、exe安装程序、dll运行库、cat数字签名、pdf/html说明文档及bat静默安装/卸载脚本等覆盖设备识别、资源分配、参数配置与错误处理等环节。已有895人学习下载。读者可从中获得对应型号的驱动文件、安装与静默部署脚本、串口参数配置工具及排错参考便于在工控机、老设备维护和多串口通信场景中完成驱动匹配与稳定联调。1. 先别急着装驱动PCI 串口卡不认卡的三个真实原因某产线工控机换主板后原来的多串口卡插回 PCI 槽系统设备列表里多了一个未知设备lspci能看到通信控制器/dev/ttyS*却没有增加。现场第一反应是卡坏了实际多数是 PCI串口驱动没有把设备绑定到串口子系统。这份 PCI 转串口卡驱动程序资源要解决的就是这段链路从 PCI 配置空间读厂商 ID、设备 ID、BAR 和 IRQ再把 UART 寄存器、tty/COM 口注册、RS485 方向控制接起来。适合维护老旧工控设备、做多串口采集、手里有白牌卡需要改 ID 表的人只想插上即用、不打算看日志的人先别碰源码。2. 从 PCI 配置空间读起厂商 ID、BAR 与 IRQ 的落地核对拿到驱动源码包先别急着敲构建命令。PCI 转串口卡能不能被驱动认领第一步不是串口代码而是 PCI 层匹配设备有没有被枚举出来、厂商 ID 和设备 ID 是否在驱动 ID 表里、BAR 有没有分配到地址、IRQ 有没有挂上。很多“驱动装了但不出 COM 口”的现场问题卡在这一层跟 UART 寄存器一点关系都没有。PCI 配置空间里最关键的是 Vendor ID、Device ID、Class Code、六个 BAR、Interrupt Line/Pin以及 Subsystem Vendor/Device传统 PCI 配置空间 256 字节PCIe 扩展配置空间可到 4KB但驱动绑定通常只看前一小段。2.1 用 lspci 和 sysfs 锁定厂商 ID、设备 ID、BAR先抓数字 ID不要只看设备名。lspci显示的名字来自设备数据库白牌卡、贴牌卡经常显示成未知通信控制器名字不可靠-nn后面的十六进制数字才是驱动 ID 表要用的值。下面这条命令把设备树、数字 ID、BAR 和中断信息一起拉出来先确认卡在哪个总线号上。# -nn 显示数字 ID-vvv 展开 BAR/IRQ/能力-t 先看 PCI 拓扑 lspci -t lspci -nnvvv | grep -i -B 2 -A 16 communication controller # 假设已经确认 BDF 为 0000:03:00.0再单独看这块卡 lspci -nnvvv -s 0000:03:00.0逻辑上lspci -t用来确认卡挂在哪个根端口或桥后面避免把桥的设备 ID 当成串口卡 ID-nnvvv里的[xxxx:yyyy]分别是厂商 ID 和设备 IDRegion 0到Region 5是 BARInterrupt line是系统分配的 IRQ。参数上-s后面跟 BDFBDF 格式是域:总线:设备.功能现场换槽后总线号会变脚本里不要写死。若lspci能看到设备但Region全是not assigned先查固件或桥窗口分配不要改串口驱动。sysfs 是更直接的核对入口驱动 probe 前可以先把资源读出来存档后续对比“驱动改完后资源有没有变化”。下面这段命令把 vendor、device、class、resource 和 irq 全部落到文本里方便贴到工单里。# 把 BDF 换成实际值resource 每行对应一个 BAR BDF0000:03:00.0 cat /sys/bus/pci/devices/$BDF/vendor cat /sys/bus/pci/devices/$BDF/device cat /sys/bus/pci/devices/$BDF/class cat /sys/bus/pci/devices/$BDF/resource cat /sys/bus/pci/devices/$BDF/irq # 看当前绑定到哪个驱动空目录表示还没绑定 ls -l /sys/bus/pci/devices/$BDF/driver逻辑上vendor和device是十六进制文本class用来确认是不是0x07开头的通信控制器resource每一行是start end flags行号对应 BAR0 到 BAR5irq是当前分配的中断号。参数上BDF必须按现场替换resource里start end 0表示该 BAR 没分配或不存在。看到driver目录为空说明 PCI 层还没绑定驱动这时候去查串口注册没有意义。2.2 读 BAR 与 IRQ判断是 IO 映射还是 MMIOBAR 不是“内存地址”四个字就能带过。BAR 的低位标志决定访问方式bit0 为 1 表示 IO 端口空间为 0 表示 MMIOMMIO 还要看是 32 位还是 64 位 BAR64 位 BAR 会占两个连续 BAR 槽。驱动里用pci_iomap还是ioremap、用inb/outb还是readb/writeb都取决于这个标志。IRQ 则要确认是传统 INTx 还是 MSI/MSI-X共享中断线上返回IRQ_NONE的规则不一样。下面这段 Python 只做一件事把resource文件解析成 BAR 类型和长度避免人工看十六进制看花眼。它不依赖第三方库现场机器上直接跑。#!/usr/bin/env python3 # 解析 /sys/bus/pci/devices/BDF/resource标出 IO/MMIO 与 BAR 长度 import sys path sys.argv[1] if len(sys.argv) 1 else /sys/bus/pci/devices/0000:03:00.0/resource with open(path) as f: for idx, line in enumerate(f): start_s, end_s, flags_s line.split() start, end, flags int(start_s, 16), int(end_s, 16), int(flags_s, 16) if start 0 and end 0: continue kind IO if flags 0x1 else MMIO length end - start 1 print(fBAR{idx}: {kind} start0x{start:x} end0x{end:x} len{length})逻辑上resource每行三个十六进制字段第三个字段的 bit0 是 IO 空间标志end - start 1才是 BAR 实际长度。参数上脚本第一个参数传resource路径不传就用示例 BDF现场一定要换成实际 BDF。若输出长度是 256、512 这类小值可能是 I/O 映射的串口寄存器窗口若长度很大常见是 MMIO 映射驱动里要用readb/readl而不是端口 IO 指令。注意/sys/bus/pci/devices/BDF/resource里能看到地址不代表内核驱动已经pci_request_regions成功。两个驱动抢同一段 BAR 时地址照样显示probe 里却会返回-EBUSY。2.3 建立设备 ID 表驱动绑定前的第一道门槛PCI 驱动绑定靠 ID 表匹配不靠文件名。源码包里如果 ID 表只有一两个厂商 ID白牌卡换个丝印就绑不上这时要么补 ID要么用new_id做临时验证。临时验证可以写 sysfs 的new_id但重启后失效长期维护还是要把 ID 写进表里。子系统 ID 也要留意同一厂商/设备 ID 下不同子型号可能对应不同 UART 时钟或 RS485 方向逻辑必要时用PCI_DEVICE_SUB做细分。/* 设备 ID 表0x1234/0x5678 只是占位必须换成 lspci 读到的数字 */ static const struct pci_device_id xxx_pci_tbl[] { { PCI_DEVICE(0x1234, 0x5678) }, /* 只匹配厂商 ID/设备 ID */ { PCI_DEVICE_SUB(0x1234, 0x5678, 0x9abc, 0xdef0) }, /* 细分到子系统 */ { 0, } }; MODULE_DEVICE_TABLE(pci, xxx_pci_tbl);逻辑上PCI_DEVICE展开成厂商 ID 和设备 ID 两个匹配字段PCI_DEVICE_SUB再加子系统厂商 ID 和子系统设备 ID数组末尾必须留一个全零项否则匹配会越界。参数上0x1234、0x5678是占位值照抄一定绑不上MODULE_DEVICE_TABLE负责生成热插拔别名缺了它冷插拔可能正常热插拔就不一定。临时验证可以在/sys/bus/pci/drivers/驱动名/new_id写入厂商ID 设备ID但只适合现场定位不要当成发布方案。3. 接进串口子系统8250 兼容路径和非标 UART 的分叉口PCI 层绑定成功只说明驱动拿到了 BAR 和 IRQ离/dev/ttyS*或 COM 口还差一层串口框架。这里先做选型寄存器布局如果和 8250 兼容优先走serial8250注册路径少写一大坨uart_ops如果寄存器偏移、FIFO 触发、中断状态位都对不上就别硬套老老实实实现最小uart_ops。选错路径的代价很高8250 兼容路径改几行 ID 表和时钟就能跑非标路径要把收发、中断、termios、RS485 全部自己接一遍。3.1 先判断能不能复用 8250 兼容时钟、寄存器偏移、FIFO 三个硬指标判断能不能走 8250 兼容不看卡上印了什么名字看三个硬指标寄存器偏移是否按THR/RBR/DLL、IER/DLM、IIR/FCR、LCR、MCR、LSR、MSR、SCR排列输入时钟能不能推出 16 倍过采样分频FIFO 深度和触发级别是否能用标准位描述。三项里有一项对不上强行复用会出现“能打开、发出去乱码、收几个字节就卡死”这类玄学问题。判断项走 8250 兼容路径走自写 uart_ops寄存器布局偏移与标准 8250 一致偏移或位定义不同输入时钟有明确uartclk按 16 倍分频过采样可配或需要小数分频FIFO标准触发级别可描述FIFO 深度/触发自定义中断状态IIR 语义接近标准状态位需要自己解码适配成本低改 ID 表、BAR、时钟高收发中断全要自己写这张表在现场很有用先按表打勾再决定改哪条代码路径。8250 兼容路径的核心是填struct uart_8250_port把port.mapbase、port.membase、port.iotype、port.uartclk、port.regshift、port.flags填对再调serial8250_register_8250_port。非标路径则要自己维护struct uart_port和struct uart_ops从startup到set_termios都要有落点。3.2 非标芯片的 uart_ops 最小实现startup、set_termios、中断处理非标路径不用一上来写全先保三条主路径发送、接收中断、波特率设置。发送路径从 tty 环形缓冲区取字节写 THR发送空中断里继续推接收中断里读 RBR用uart_insert_char塞进 tty 缓冲再tty_flip_buffer_push推给上层。中断处理要先判断“这个中断是不是我的”共享中断线上不是自己的中断必须返回IRQ_NONE否则会把整条中断线拖死。/* 最小发送路径从 tty 环形缓冲取字节写 THR空则关发送中断 */ static void xxx_start_tx(struct uart_port *port) { struct xxx_port *p container_of(port, struct xxx_port, port); unsigned long flags; spin_lock_irqsave(port-lock, flags); while (!uart_circ_empty(port-state-xmit) !xxx_tx_full(p)) { u8 ch port-state-xmit.buf[port-state-xmit.tail]; writeb(ch, p-membase XXX_THR); port-state-xmit.tail (port-state-xmit.tail 1) (UART_XMIT_SIZE - 1); port-icount.tx; } if (uart_circ_empty(port-state-xmit)) xxx_stop_tx(port); spin_unlock_irqrestore(port-lock, flags); } /* 接收中断先看 IIR 是否有未决中断再按 FIFO 逐字节取 */ static irqreturn_t xxx_irq(int irq, void *dev_id) { struct xxx_port *p dev_id; u8 iir readb(p-membase XXX_IIR); if ((iir 0x01) 0) /* bit01 表示无未决中断 */ return IRQ_NONE; if (iir 0x04) { /* 接收数据可用 */ while (readb(p-membase XXX_LSR) XXX_LSR_DR) { u8 ch readb(p-membase XXX_RBR); uart_insert_char(p-port, 0, 0, ch, TTY_NORMAL); } } tty_flip_buffer_push(p-port.state-port); return IRQ_HANDLED; }逻辑上发送路径在port-lock保护下操作 tty 环形缓冲tail按UART_XMIT_SIZE回绕接收路径先读 IIR 判断中断来源再循环读 LSR 的 DR 位确保 FIFO 里的字节被取空。参数上XXX_THR、XXX_IIR、XXX_LSR、XXX_RBR要换成芯片手册里的寄存器偏移xxx_tx_full按 FIFO 深度实现。共享中断线上IRQ_NONE不是可选项返回错了系统日志里会出现“nobody cared”一类中断风暴记录。3.3 probe 与 removeioremap、request_irq、tty_port 注册顺序probe 的顺序决定失败时好不好回滚。常规顺序是pci_enable_device唤醒设备pci_request_regions占住 BARpci_iomap映射寄存器填uart_port申请 IRQ最后uart_add_one_port注册到串口框架。任何一步失败都要按相反顺序释放不能只return了事。remove 则先摘串口端口再释放 IRQ、取消映射、释放 region、关闭设备顺序反了容易出现“端口已注销但中断还在进”的崩溃。static int xxx_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct xxx_port *p; int ret; p devm_kzalloc(pdev-dev, sizeof(*p), GFP_KERNEL); if (!p) return -ENOMEM; ret pci_enable_device(pdev); if (ret) return ret; ret pci_request_regions(pdev, xxx_serial); if (ret) goto err_disable; p-membase pci_iomap(pdev, 0, 0); /* 假设寄存器在 BAR0 */ if (!p-membase) { ret -ENOMEM; goto err_regions; } p-port.iotype UPIO_MEM; p-port.mapbase pci_resource_start(pdev, 0); p-port.membase p-membase; p-port.irq pdev-irq; p-port.uartclk 1843200; /* 必须按原理图/手册改 */ p-port.fifosize 16; /* 按实际 FIFO 深度改 */ p-port.ops xxx_ops; p-port.flags UPF_BOOT_AUTOCONF; ret uart_add_one_port(xxx_uart_driver, p-port); if (ret) goto err_unmap; pci_set_drvdata(pdev, p); return 0; err_unmap: pci_iounmap(pdev, p-membase); err_regions: pci_release_regions(pdev); err_disable: pci_disable_device(pdev); return ret; }逻辑上pci_enable_device让设备进入可访问状态pci_request_regions防止别的驱动抢同一段 BARpci_iomap把物理 BAR 映射成内核虚拟地址uart_add_one_port成功后上层才会看到 tty 设备。参数上uartclk填错是最常见的“能打开但乱码”来源fifosize填大了会误判发送空填小了吞吐上不去。remove 里对应写uart_remove_one_port、pci_iounmap、pci_release_regions、pci_disable_device顺序与 probe 相反。4. 波特率、流控与 RS485把 termios 参数落到寄存器应用层看到的115200 8N1只是 termios 参数驱动要把它翻译成分频值、LCR 位、FIFO 触发和方向控制。这里最容易翻车的是时钟原理图上晶振频率、芯片内部倍频、外部时钟输入三选一填错一个数低波特率看着正常高波特率误码率飙升。另一个高频问题是流控应用开了crtscts驱动没接 RTS/CTS 引脚发送端一直等 CTS现场表现就是“能发几个字节后卡住”。4.1 波特率从输入时钟到分频寄存器反推标准 16 倍过采样下分频值约等于uartclk / (16 * baud)整数部分写 DLL/DLM余数部分有小数分频寄存器就写没有就只能选接近的整数分频。高波特率下分频值变小舍入误差被放大921600 这类速率尤其明显。先用手算脚本把误差打出来再决定要不要换时钟源或改用更低的过采样倍率比在示波器上盲调快得多。#!/usr/bin/env python3 # 反推 16 倍过采样的整数分频并打印相对误差百分比 def divisor(uartclk, baud, oversample16): div uartclk / (oversample * baud) nearest round(div) err abs(div - nearest) / div * 100 return nearest, err # 14_745_600 只是示例时钟必须换成板上的实际 uartclk for baud in (9600, 115200, 921600): d, err divisor(14_745_600, baud) print(f{baud}: divisor{d}, err{err:.3f}%)逻辑上uartclk是串口控制器实际拿到的输入时钟oversample常见为 16round得到最接近的整数分频误差百分比用来判断高波特率能不能接受。参数上14_745_600不能照抄必须按原理图或芯片手册改若误差超过 2% 到 3%高波特率下丢字节、帧错误就会明显增多。有些芯片支持 8 倍或 4 倍过采样分频值会变脚本里的 oversample本文还有配套的精品资源点击获取