一个并口驱动程序:并行接口的描述
我将继续修改我之前创建的那个驱动程序从而使它可以在真正的设备上执行真正的任务。我将使用常见计算机的并口,并将这个驱动命名为“parleport”
并口是允许数字信息输入和输出的高效设备。更具体地说,它是D-25针母座。在内部,在CPU看来,它使用了三个字节的存储空间。在PC上,并口的基址(设备的第一个字节)通常都是0x378。在我们这个基本的例子中,我将使用它的第一个字节,这个字节由所有的数字输出组成。
以上提到的那个字节(第一个字节)和外部接口针脚的对应关系如下图所示:
图 并口的第一个字节和与它相对应的D-25母座外部接口
一个并口驱动程序:初始化模块
之前的memory_init函数需要做修改——改变RAM存储的分配以为并口预留存储地址(0x378)。为了达到这个目的,我们需要使用到检查内存区域是否可以获取的函数(check_region)和为设备预留空间的函数(request_region)。两个函数都使用内存区域的基址以及它的长度作为参数。request_region函数同时也接收一个描述模块的字符串。
<parlelport modified init module> =
/* Registeringport */
port =check_region(0x378, 1);
if(port) {
printk("<1>parlelport: cannot reserve 0x378\n");
result = port;
goto fail;
}
request_region(0x378,1, "parlelport");
一个并口驱动程序:卸载模块
卸载模块的处理和之前“memory”模块的卸载处理差不多,只是这里是释放之前为并口预留的内存,而不是释放内存。这个工作由release_region函数来完成,这个函数需要和check_region相同的参数。
<parlelport modified exit module> =
/* Make portfree! */
if(!port) {
release_region(0x378,1);
}
一个并口驱动程序:读设备
在本例中,我们需要添加一个真正的设备读取动作来完成将信息传递到用户空间。inb函数可以达到以上目的;它的参数是并口地址,它的返回值是端口的内容。
<parlelport inport> =
/* Reading port*/
parlelport_buffer= inb(0x378);
下表展示了这些新的函数
Events | Kernel functions |
Read data | inb |
Write data |
|
表 设备驱动程序事件和与之相关联的介于内核空间和硬件设备之间的函数
一个并口驱动程序:写设备
同样,你需要添加“写设备”的函数来完成将之后的数据传输到用户空间(译者:是否是应该表述为将数据从用户空间传输到设备?)。函数outb完成了这个功能;它有两个参数,一个是要写的内容,另一个是要写的地址。
<parlelport outport> =
/* Writing tothe port */
outb(parlelport_buffer,0x378);
下表总结了这个新函数
Events | Kernel functions |
Read data | inb |
Write data | outb |
表 设备驱动程序事件和与之相关联的介于内核空间和硬件设备之间的函数
一个完整的并口驱动
我将继续展示这个并口模块的所有代码。你需要将之前memory模块里面的单词“memory”全部替换成“parlelport”。最终结果如下显示:
<parlelport.c> =
<parlelportinitial>
<parlelportinit module>
<parlelportexit module>
<parlelportopen>
<parlelportrelease>
<parlelportread>
<parlelportwrite>
初始化部分
在这个驱动的初始化部分将要使用一个不同的major number,即61。同样地,全局变量memory_buffer也变成了port的地址,同时需要额外添加两个#include表达式以添加两个头文件:ioport.h 和io.h
<parlelport initial> =
/* Necessaryincludes for drivers */
#include<linux/init.h>
#include<linux/config.h>
#include<linux/module.h>
#include<linux/kernel.h> /* printk() */
#include<linux/slab.h> /* kmalloc() */
#include<linux/fs.h> /* everything... */
#include <linux/errno.h>/* error codes */
#include<linux/types.h> /* size_t */
#include<linux/proc_fs.h>
#include<linux/fcntl.h> /* O_ACCMODE */
#include<linux/ioport.h>
#include<asm/system.h> /* cli(), *_flags */
#include<asm/uaccess.h> /* copy_from/to_user */
#include<asm/io.h> /* inb, outb */
MODULE_LICENSE("DualBSD/GPL");
/* Functiondeclaration of parlelport.c */
intparlelport_open(struct inode *inode, struct file *filp);
intparlelport_release(struct inode *inode, struct file *filp);
ssize_t parlelport_read(structfile *filp, char *buf, size_t count, loff_t *f_pos);
ssize_tparlelport_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);
voidparlelport_exit(void);
intparlelport_init(void);
/* Structure thatdeclares the common */
/* file accessfcuntions */
struct file_operationsparlelport_fops = {
read: parlelport_read,
write: parlelport_write,
open: parlelport_open,
release: parlelport_release
};
/* Driver globalvariables */
/* Major number */
int parlelport_major =61;
/* Control variablefor memory */
/* reservation of theparallel port*/
int port;
module_init(parlelport_init);
module_exit(parlelport_exit);
模块初始化
在这个模块初始化例程中,我将引入之前描述的为并口预留内存的函数。
<parlelport init module> =
int parlelport_init(void) {
int result;
/* Registeringdevice */
result =register_chrdev(parlelport_major, "parlelport",&parlelport_fops);
if (result <0) {
printk("<1>parlelport: cannot obtain major number %d\n",parlelport_major);
returnresult;
}
<parlelportmodified init module>
printk("<1>Insertingparlelport module\n");
return 0;
fail:
parlelport_exit();
return result;
}
卸载模块
这个例程将要包括之前提到过的修改。
<parlelport exit module> =
void parlelport_exit(void) {
/* Make majornumber free! */
unregister_chrdev(parlelport_major,"parlelport");
<parlelportmodified exit module>
printk("<1>Removingparlelport module\n");
}
使用打开文件的方式打开设备
这个例程和memory驱动是一样的。
<parlelport open> =
int parlelport_open(struct inode *inode, structfile *filp) {
/* Success */
return 0;
}
使用关闭文件的方式关闭设备
同样,这个例程也和之前的memory驱动一样
<parlelport release> =
int parlelport_release(struct inode *inode, structfile *filp) {
/* Success */
return 0;
}
读设备
将读取的地址相应地改为设备的端口后,这个读函数就和之前的memeory模块中的读函数很相似了。
<parlelport read> =
ssize_t parlelport_read(struct file *filp, char*buf, size_t count, loff_t *f_pos) {
/* Buffer toread the device */
charparlelport_buffer;
<parlelportinport>
/* We transferdata to user space */
copy_to_user(buf,&parlelport_buffer,1);
/* We changethe reading position as best suits */
if (*f_pos ==0) {
*f_pos+=1;
return1;
} else {
return0;
}
}
写设备
除了这里是将内容写到设备,其他的和memory模块里面的这个函数时类似的。
<parlelport write> =
ssize_t parlelport_write( struct file *filp, char*buf, size_t count, loff_t *f_pos) {
char *tmp;
/* Bufferwriting to the device */
charparlelport_buffer;
tmp=buf+count-1;
copy_from_user(&parlelport_buffer,tmp,1);
<parlelportoutport>
return 1;
}
使用LED来测试并口的使用
在这个部分,我将详细讲述构建一个硬件平台的过程,这个硬件平台使用一些简单的LED来可视化并口的状态。
警告:将设备连接到你的并口可能会对你的计算机造成损害。请确保你的设备正确接地,同时在连接设备到你的计算机的时候务必使你的计算机保持关机状态。由于做这个实验出现的任何问题由你个人承担。
构建的电路将会在下面的图中展示。你也可以阅读由Zoller编写的《PC & Electronics: Connecting Your PC to theOutside World》作为参考手册。
图 使用LED点阵来监控并口的电子线路图
为了使用它,你必须首先确定所有的硬件都正确连接。然后,关闭你的PC并将你的设备连接到你的并口。然后,你可以打开你的计算机并卸载所有和你的并口相关的驱动程序(例如:lp、parport、parport_pc等)。Debian Sarge中的分配模块(distribution module)特别令人厌烦,因此你需要也将它卸载。如果文件“/dev/parleport”不存在,你需要键入以下命令创建它:
# mknod/dev/parlelport c 61 0
然后,你需要键入以下命令使它可以被任何人读写:
# chmod 666/dev/parlelport
然后,现在我们就可以安装模块parleport了,你可以键入以下命令来查看这个模块有效地位输入输出端口预约了地址0x378:
$ cat/proc/ioports
为了点亮LED并检查系统正在工作,需要执行以下命令:
$ echo -n A>/dev/parlelport
这个命令会点亮第0个和第6个LED,其他的则保持关闭状态。
你可以使用如下命令来检查并口的状态:
$ cat/dev/parlelport
最终应用:闪光灯
最后,我将开发一个很有趣的应用来使LED来依次闪烁。为了达到这个目的,我们需要在用户空间编写一个程序来每次只写一位到“/dev/parleport”设备。
<lights.c> =
#include <stdio.h>
#include <unistd.h></p>
int main() {
unsigned charbyte,dummy;
FILE *PARLELPORT;
/* Opening thedevice parlelport */
PARLELPORT=fopen("/dev/parlelport","w");
/* We removethe buffer from the file i/o */
setvbuf(PARLELPORT,&dummy,_IONBF,1);
/* Initializingthe variable to one */
byte=1;
/* We make aninfinite loop */
while (1) {
/*Writing to the parallel port */
/*to turn on a LED */
printf("Bytevalue is %d\n",byte);
fwrite(&byte,1,1,PARLELPORT);
sleep(1);
/*Updating the byte value */
byte<<=1;
if(byte == 0) byte = 1;
}
fclose(PARLELPORT);
}
我们可以使用常规的方法来编译它:
$ gcc -o lightslights.c
然后我们可以使用以下命令来执行它:
$ lights
LED灯此后就会一个接着一个地闪烁!下图展示了闪烁的LED等和执行程序的Linux计算机。
图 接在电路板上的LED和运行Linux系统的计算机。
有两个终端在显示:一个是显示parleport模块被加载,另一个显示light程序正在运行。