编写智能串口卡驱动程序

作者:Randolph Bentson

事情起初很简单。我一直在为我的家用系统寻找升级方案,那是一台破旧的 Unix 工作站,只有 8MB 内存和 40MB 磁盘。我一直在关注 386BSD,但有点被 AT&T 的诉讼等事情吓退了。突然在去年九月,我注意到另一个系统,Linux,并看到了指向发行版的文章。我将一个发行版复制到了一些软盘上(在一台 Sun 系统上,哎),并在一个通常运行 MS-DOS 的客户系统上借用了一个分区。

当我发现 Linux 加载如此容易,功能如此强大时,我知道我找到了答案。我花了很多客户的月度账单,通过他们的采购代理订购了一台强大的 486 电脑。我有点担心组装这些部件,但制造领域的家伙们很想看看它是如何工作的。即使我想阻止他们组装,我也无法阻止。

这一切都非常及时。如果我想毕业,我必须回到我的研究中,完成我的论文。虽然我可以访问互联网上的大量系统,但从远处分析数据会很麻烦。有了现在我的新系统上所有可用的 X 工具,我在家里的工作效率大大提高了。对于这一切,我欠 Linux 社区很多,感谢他们提供的我如此依赖的工具。

然后,我报答这份恩情(并从中受益)的机会来了。Phil Hughes 在本地 Linux 邮件列表中发布了以下内容

... anyone out there want to write a driver for the Cyclades serial
board? This is an ISA-bus card with a Cirrus Logic RISC processor
on it....
Why do you want to do this?
1. Linux needs it :-)
2. You will get a free 8-port board for your effort
What you have to do:
1. Write a driver that really works and make it
   available on the net.
2. Write an article on this for Linux Journal

这听起来很划算!我立刻发了一封便条,说我愿意承担这项任务。我在 70 年代做了很多操作系统内核内部工作,在 80 年代破解了 BSD Unix 内核,并在过去几年做了一些 PC 驱动程序。这似乎说服了 Phil 我能胜任这份工作。他写道,他“最大的担心是那些不知道设备驱动程序是什么的人会决定尝试编写驱动程序,以便他们可以获得免费的板卡”。

然后事情就搁置了,直到我完成了最后一份论文答辩笔记。(实际上我希望我的导师不会问我在做什么。)幸运的是,我们花了一段时间才理清细节并等到板卡到货,所以我的延迟看起来还不算太糟。最终到货的开发者套件包含大量信息:关于 Cirrus 芯片的 130 页数据手册,以及大量代码片段,展示了如何访问该板卡。有了这些东西,我终于开始在内核内部工作了。

字符 tty 类接口跨多个文件实现。一些文件着眼于数据流的高级部分;实现规范字符处理、换行到回车符转换、命令行回显、转义字符处理等等。另一些文件则涉及控制设备;控制台、键盘和串行线路。我通过复制 serial.c 并将所有 rs_something 形式的名称更改为 cy_something 来启动我的驱动程序。然后我必须将这个新的低级驱动程序连接到系统。有两个例程被调用来完成这项工作:cy_init 和 cy_open。一旦这些例程被添加到 tty_io.c 中,Cyclades 驱动程序的开发就仅限于新文件 cyclades.c。

最开始的部分是识别板卡并初始化数据结构。Cyclades 提供了合适的代码来完成前者,而 serial.c 代码负责后者。一个主要的区别出现了。serial.c 代码基于每个端口都有自己的 IRQ 的设计,当端口关闭时,IRQ 将被禁用。我必须将 IRQ 设置代码移动到板卡初始化中。最初的成功是启动系统并获得报告板卡及其端口存在的消息。

下一个阶段是修复 cy_open 例程。一旦我剥离了 IRQ 相关的东西,剩下的主要是在低级驱动程序和高级驱动程序之间建立链接。这根本不需要太多改动。我只是添加了一个子程序调用来初始化板卡上的端口,设置字符大小、波特率、调制解调器控制信号等。这似乎奏效了,所以我继续进行下一部分。

然后它变得可怕起来;实际上是发送字符。再次,使用来自 serial.c 的代码作为大纲(强烈暗示必须对硬件做些什么),我更改了 cy_write 和 cy_interrupt。

每当高级驱动程序将字符排队等待发送时,就会调用 cy_write。serial.c 版本实际上将一些初始字符塞进了一个硬件寄存器,该寄存器开始发送字符。我更改了这一点,以便我只启用中断;中断服务例程将是唯一将字符放在线路上的代码。

中断服务甚至更可怕。这是第一次,代码将在调用程序的上下文之外执行。如果事情出错,将很难弄清楚问题出在哪里。幸运的是,来自 Cyclades 和 serial.c 的代码片段合并时没有太多困难。每当发生中断时,它都表明板卡上的某些东西需要处理。编程很简单(:-)),轮询每个芯片以查看它们的四个端口中是否有任何端口需要注意,如果是,则它是用于发送、接收还是调制解调器条件。带着一些忐忑不安,我编译了这些更改,并重新构建并重新启动了内核。一个简单的测试:date > /dev/ttyC0 成功了!

我很高兴取得了如此大的进展,因为大约在这个时候,我收到了来自 Cyclades 的询问。他们想知道事情进展如何。我如释重负地回复说,我正在积极测试“功能越来越全面的驱动程序版本”。

这受到了欢迎。他们发布了一个关于 Linux 驱动程序的小广告,并且开始收到回复。对我来说,这也是好消息。将会有很多人来测试我的代码。

我继续努力,鼓起勇气尝试接收端,并解决一个神秘的(对我而言)内核崩溃问题。作为测试的一部分,我实际上在测试传输之前发出了“sync”命令,因此内核崩溃尚未造成任何损害。

我将崩溃追溯到一个过早清除的指针。通过将 serial.c 代码与我的代码进行比较,我发现我移动了太多东西。cy_close 中的一些小检查修复了这个问题。(我还探索了如何从控制台中获得一些显示,以显示发生了什么。我四处查看,发现虽然 printk() 并不总是适用于来自中断服务例程的消息(如果曾经适用),但如果中断被关闭,则可以随时调用 console_print()。稍微努力一下,我就可以撒上单字符消息,显示例程的进度。更多对 console_print() 的调用使我能够集中找出问题所在。)最后,在充分利用阵亡将士纪念日周末之后,我报告了以下内容

功能
  • 自动检测板卡并为给定地址使用分配的 IRQ

  • 将 DTR 表示为打开/关闭状态的函数

  • 可以在所有八个端口上发送/接收数据

  • 适用于终端问题的登录会话

  • 在发送第一个字符之前接收字符被某些东西视为“挂断”;一旦发送第一个字符,接收工作正常 缺点

  • 速度固定为 9600 波特

  • 模式固定为 8 位,无奇偶校验,1 个停止位

  • 调制解调器状态被忽略

  • wait-on-open 不等待

  • break 被忽略

  • 为 release 0.99p12 测试编写

  • 尚未测试同时发送/接收

  • 尚未测试同时多端口操作

几天后,我又补充说

看起来我报告的 Cyclodes 驱动程序的问题实际上更深层次地存在于内核中,并且与其他异步驱动程序一起出现,也就是说,它一开始就存在。因此,我暂时忽略这个问题,因为其他端口适用于我知道的所有应用程序,并将重点放在使剩余功能正确运行上。首先是速度和线路模式的东西,然后是调制解调器控制。

然后

我已经添加了速度设置代码,并在高达 19200 的速度下进行了测试。一旦我安装了一些环回电缆,我将检查更高的速度。

它现在可以识别奇偶校验错误和 break,wait-on-open 功能可以工作,并且已经测试了多个同时发送和接收。内核升级到 1.1.8 已经完成,我正在与其他人一起更严格地测试它。

查看我的日志,可以看出我是如何断断续续地工作的。我总共花了一个多星期在这上面,大部分时间都是一天一天地花。这之后是很多小时的阅读文档。

那么我得到了什么,我会再做一次吗?

我得到一个机会来报答社区。我有机会在内核内部玩耍。我更加确信我可以为这个系统编写驱动程序并使它们工作。我还得到了一块多路复用板。

我不确定我会再做一次。不是说它有多苛刻,但它确实花时间。我不认为第二次的收获会像第一次那么大。尽管如此,如果有人向我提供一个足够有趣的设备,我会很动心的。

Randolph Bentson 可以通过以下方式联系到他:(bentson@grieg.seaslug.org)

加载 Disqus 评论