一、背景
实验室之前的一个小车项目,想用CAN总线作小车各个部分的通讯,而且是想用实时Linux系统—xenomai。但是针对这种不是很常用的实时系统,一般厂商不会提供带这种系统的驱动的设备。于是找到了PEAK-System的产品,因为主板是用的研华PC104标准的工控机PCM3362,所以选择的CAN卡是PC/104接口—PCAN-PC/104,CAN卡和PC104通过PC104插槽连接(驱动程序对PC104和ISA接口没作区分)如下图示
二、本篇文章的目的
在我用这个产品的Linux驱动,并测试CAN发送接收功能的过程中,遇到了一些问题,最后也解决了。在此将这个过程记录下来。供学习参考。
同时,我觉得PEAK-System的驱动程序源码写的非常好看,打算学习一下它们的程序设计风格,在我接下来的项目中模仿它们的风格写自己的驱动程序。另外我非常想吐嘈一下研华的CAN卡产品,驱动做的很烂,而且只支持很老的Linux内核版本,更不要说支持实时Linux了,而且驱动程序源码也写的不好。
三、测试过程和问题以及解决方法
要使用产品提供的驱动程序,并自己的计算机上编译并装载,当然得主要看产品给的驱动程序使用说明文档。因为全是厂家写的,怎么给编译参数,怎么给装载参数,都得仔细读文档。
比如编译时的make参数选择,如果不带任务参数也行,就会用makefile文件里提供的默认参数。一般厂家的同一功能的产品会包含许多同类的产品,比如Peak的CAN卡有各种接口的CAN卡,他们把驱动都写在一个驱动文件里了。make的时候要仔细读怎么给参数,如果文档给的不清楚,就去看makefile文件中,各个参数的意思!
1、sudo make PAR=NO USB=NO PCI=NO PCIEC=NO DNG=NO ISA=ISA_SUPPORT NET=NO PCC=NO RT=NO_RT DBG=DEBUG
大概意思是只编译ISA接口的驱动,其它接口就不编译了,如果编译也行。 DEG=DEBUG打开debug模式,这样会把一些调试程序也编译进驱动,在运行过程中,驱动程序会输出一些调试信息,供调试找问题用。这项功能很有用,值得学习!!
2、sudo make install
像make install,make clean等命令,都是makefile里面用脚本实现的,通常make install是把一些文件拷贝到根文件 / 目录下去。我了解的是Peak的make install会把用modprobe 命令装载该驱动的规则拷贝过去。 modprobe命令需要一个规则文件??这个原理我还没了解过。
3、装载驱动程序sudo modprobe pcan 或者 sudo insmod pcan.ko
我只会用insmod,对modprobe不是很了解。但是若1步骤中把所以接口驱动都编译进去,则用insmod加载会失败,说是找不到很多符号。而modprobe则能成功,是因为modprobe正是解决加载过程中,驱动程序之间的依赖问题。 但是如果只编译ISA驱动,用insmod命令就行了。
用dmesg命令查看加载驱动程序的输出信息,内核打印函数printk(KERN_ALERT "messages")输出的信息。
用tail /var/log/messages查看输出信息。。。。尚未发现这二个的区别。。。
4、驱动程序装载后,不一定成功,虽然用lsmod也能显视出装载模块里有pcan了,但是可能没有申请相应的资源成功,比如用cat /proc/pcan 或者 cat /proc/ioports等命令查看相应的中断,地址端口有没有被分配给pcan设备。
不得不说PEAK-System的一个坑爹的地方就在这里,我一直照着说明书装。最后对设备打开操作时,却始终出现找不到设备的错误,就是在此。因为在3中装载也必须给参数,否则驱动程序则不为任何设备申请资源(没有默认值)。所以我用cat /proc/pcan一直是空。
正确的命令应该是: sudo insmod pcan.ko type=isa,isa (type必须显视给定,而其它ioports, 中断号等用默认的就行)
我大部分时间都是卡在4步骤,最后是去读驱动程序源码,并且调试时一步一步用printk()输出调试信息才发现必须加type=isa,isa参数。见源文件pcan_main.c中的make_legacy_devices函数!
另外一个大坑是,就算以上过程无误,驱动程序也不一定能加载正确。必须是先保证CAN卡和PC104已经电路硬件上连接完好。我也在这里卡了很久,最后发现是插槽没有插牢固,而驱动程序会去检测CAN卡,如没有检测到,仍然不分配资源。见pcan_isa.c中的函数pcan_create_isa_devices(),它调用同文件中的函数,而这个函数需要调pcan.sja1000.c中的硬件探测函数:sja1000_probe(dev);如果没有探测到硬件,则失败。
5、经过以上步骤, 一般能保证驱动程序正常加载成功了。然后还需要在Linux文件系统中/dev 下创建设备节点,设备节点通过主次设备号和驱动程序关联起来。可以用sudo mknod c手动创建。
也可以通过产品提供的脚本程序创建,用脚本创建,需要读文档了解参数,和创建出来的节点,哪些是和我的产品对应的。比如用sudo ./pcan_make_devices 2创建的脚本中,/dev/pcan8和/dev/pcan9 是对应PCAN-PC/104的文件节点。
6、可以用cat /dev/pcan8 读设备或者 echo "" > /dev/pcan9 写设备 cat,和echo均是shell命令,最终应该都是调用OS的read()和write()系统调用。 这样测试设备是最简单的,也可以写个C程序,或者用产品自己提供的测试程序来测试。
四、补充总结一:用DEBUG输出调试信息
感觉这是定位驱动程序运行出错地方的较好的方法,在驱动程序源程序的可能运行失败的函数中或者其它地方加上DEBUG输出信息,这样在调试驱动程序中根据dmesg输出信息就能快速定位到问题出现在哪。 但是又希望程序正式发行的时候,不需要这些调试信息,因为它们可能会影响程序的运行性能。
方法是:在驱动程序正文中需要输出调试信息的地方,都像下面的例子那样写,这样一般在Makefile文件里定义DEBUG宏,或者通过make命令传递参数控制是否开启DEBUG宏。同理可能用#ifdef 这样的预编译命令来控制类似的由一些条件,决定生成的代码。比如设备类型,或者系统版本决定不同的代码或者函数。
写法一:在每一个地方都这样写。
#ifdef DEBUG
printk(KERN_INFO "%s: DEBUG is s witched on\n", DEVICE_NAME);
#endif
写法二:在源程序中也用宏来控制,生成条件代码。然后所有需要输出调试信息的地方直接用DPRINTK()函数! 这样写更简单,不每次都加#ifdef #endif
#ifdef DEBUG
#define DPRINTK printk
#else
#define DPRINTK(x...) do { ; } while (0)或者#define DPRINTK(stuff...)
#endif
输出信息举例:开启DEBUG宏编译的PEAK-System的驱动程序,在insmod时的输出信息如下(用dmesg查看):很容易找到pcan: sja1000_probe() failed失败的,原因即找到CAN卡硬件接触不好,或者没有CAN卡硬件。
771.672985] pcan: Release_20110912_n (le)
[ 771.672998] pcan: driver config [dbg] [mod] [isa]
[ 771.673005] pcan: DEBUG is switched on
[ 771.673068] pcan: make_legacy_devices()
[ 771.673076] chenbotest: being in the make_legacy_devices() in for() i=0
[ 771.673087] chenbotest: being in the pcan_create_isa_devices()
[ 771.673097] pcan: pcan_create_isa_devices(0x0, 0)
[ 771.673110] chenbotest: being in the pcan_isa_init()
[ 771.673118] pcan: pcan_create_filter_chain()
[ 771.673130] pcan: init_same_irq_list(f17c8000)
[ 771.673139] pcan: isa device minor 8 expected (io=0x0300,irq=10)
[ 771.673146] chenbotest: pcan_isa_init() ok!
[ 771.673153] pcan: sja1000_probe()
[ 771.673162] pcan: CLKDIVIDER traced (0x00)
[ 771.673180] pcan: Hopefully switched to PeliCAN mode
[ 771.673189] pcan: CHIPSTATUS traced (0x00)
[ 771.673196] pcan: sja1000_probe() failed
[ 771.673202] chenbotest: sja1000_probe() fail
[ 771.673209] pcan: pcan_isa_cleanup()
[ 771.673218] pcan: pcan_delete_filter_chain(0xf23bd6c0)
[ 771.673226] pcan: pcan_delete_filter_all(0xf23bd6c0)
[ 771.673236] pcan: pcan_create_isa_devices() failed!
[ 771.673243] chenbotest:pcan_creat_isa_device() failed
[ 771.673250] pcan: pcan_create_isa_shared_irq_lists()
[ 771.673258] pcan: cleanup_module()
[ 771.673296] pcan: removed.
四、待完善地方: makefile文件怎么传递DEBUG宏? modprobe 和 insmod区别?