接下来,将修改刚刚写的memory驱动,来在一个真实设备上进行真实的操作。使用简单并且常见的计算机并口作为例子,新驱动的名称叫做:parlelport。
并口实际上是一个允许输入输出数字信息的设备。它有一个母的D-25连接头 ,有25针。从内部看,从CPU视图看,并口有3字节的存储,在PC上,基地址(设备的起始地址)通常是0x378。在本例中,我们仅使用包含完整数字输出的第一个字节。
上面提到的字节和外部接头针脚之间的连接情况不下图:
图2: 并口的第一个字节和它在D-25连接头上对应的针脚
“并口” 驱动:模块初始化
刚才的memory_init函数需要进行修改--指定RAM地址为保留的并口的内
存地址(0x378)。check_region函数用于检查一个内存区域是否可用,并
且,用request_region函数保留指定的内存区域给当前设备。这两个函数都
有两个参数,内存区域的基地址以及长度。另外request_region函数还需要
一个指定模块名称的字符串。
<parlelport modified init module> =
/* Registering port */
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 port free! */
if (!port) {
release_region(0x378,1);
}
“并口” 驱动:读取设备
本例中,需要增加一个真实设备的读取动作,以允许向用户空间传送信息。inb函数就是干这活的。它的参数是并口的内存地址,它的返回值是端口的内容。
<parlelport inport> =
/* Reading port */
parlelport_buffer = inb(0x378);
“并口” 驱动:向设备写数据
同样的,需要增加一个向设备写数据的函数,以使过后,向用户空间传送信息成为可能。函数outb可以完成此功能;它的参数是要向端口写的数据以及端口的内存地址。
<parlelport outport> =
/* Writing to the port */
outb(parlelport_buffer,0x378);
完整的“并口” 驱动
接下来,列除完整的并口模块代码。你需要把memory驱动里的单词memory 用parlelport替代。替代的结果如下:
<parlelport.c> =
<parlelport initial>
<parlelport init module>
<parlelport exit module>
<parlelport open>
<parlelport release>
<parlelport read>
<parlelport write>
初始化
在驱动初始化部分,并口使用了另外一个主设备号61。并且全局变量memory_buffer变成了port,还有,多了两个#include语句:
ioport.h 和io.h。
<parlelport initial> =
/* Necessary includes 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("Dual BSD/GPL");
/* Function declaration of parlelport.c */
int parlelport_open(struct inode *inode, struct file
*filp);
int parlelport_release(struct inode *inode, struct file
*filp);
ssize_t parlelport_read(struct file *filp, char *buf,
size_t count, loff_t *f_pos);
ssize_t parlelport_write(struct file *filp, char *buf,
size_t count, loff_t *f_pos);
void parlelport_exit(void);
int parlelport_init(void);
/* Structure that declares the common */
/* file access fcuntions */
struct file_operations parlelport_fops = {
read: parlelport_read,
write: parlelport_write,
open: parlelport_open,
release: parlelport_release
};
/* Driver global variables */
/* Major number */
int parlelport_major = 61;
/* Control variable for memory */
/* reservation of the parallel port*/
int port;
module_init(parlelport_init);
module_exit(parlelport_exit);
模块初始化
模块初始化,涉及了之前讲的并口数据存储方式。
<parlelport init module> =
int parlelport_init(void) {
int result;
/* Registering device */
result = register_chrdev(parlelport_major,
"parlelport",
&parlelport_fops);
if (result < 0) {
printk(
"<1>parlelport: cannot obtain major number %d/n",
parlelport_major);
return result;
}
<parlelport modified init module>
printk("<1>Inserting parlelport module/n");
return 0;
fail:
parlelport_exit();
return result;
}
卸载模块
卸载方式和之前的memory驱动类似。
<parlelport exit module> =
void parlelport_exit(void) {
/* Make major number free! */
unregister_chrdev(parlelport_major, "parlelport");
<parlelport modified exit module>
printk("<1>Removing parlelport module/n");
}
打开设备
这部分和memory驱动的一样
<parlelport open> =
int parlelport_open(struct inode *inode, struct file
*filp) {
/* Success */
return 0;
}
关闭设备
同样的,和上面的匹配,对应。
<parlelport release> =
int parlelport_release(struct inode *inode, struct file
*filp) {
/* Success */
return 0;
}
读取设备
读取函数和memory的相似,但被修改成读取设备的一个端口。
<parlelport read> =
ssize_t parlelport_read(struct file *filp, char *buf,
size_t count, loff_t *f_pos) {
/* Buffer to read the device */
char parlelport_buffer;
<parlelport inport>
/* We transfer data to user space */
copy_to_user(buf,&parlelport_buffer,1);
/* We change the reading position as best suits */
if (*f_pos == 0) {
*f_pos+=1;
return 1;
} else {
return 0;
}
}
向设备写数据
和memory例子类似,向设备写数据。
<parlelport write> =
ssize_t parlelport_write( struct file *filp, char *buf,
size_t count, loff_t *f_pos) {
char *tmp;
/* Buffer writing to the device */
char parlelport_buffer;
tmp=buf+count-1;
copy_from_user(&parlelport_buffer,tmp,1);
<parlelport outport>
return 1;
}
使用LEDs测试“并口” 驱动
在这一节里,将介绍如何用几个简单的LED灯以直观的显示并口的状态。
警告:连接设备到并口可能伤害你的计算机。请确认电路接地,并且在设备连接电脑时,电脑是关闭的。你要为该实验可能引起的任何问题承担责任。
按图3所示电路建立实验设备。
需要首先确认所有硬件连接正确。接下来,关闭PC,把所建的设备连接到并口。然后打开PC,并且,所有卸载所有并口相关的模块(如,lp, parport, parport_pc等)。Debian Sarge的hotplug模块可能引起麻烦,所以也需要卸载。如果文件/dev/parlelport不存在,请首先用以下命令建立该文件:
# mknod /dev/parlelport c 61 0
改变并口的权限,是任何人都可以读或写:
# chmod 666 /dev/parlelport
模块parlelport现在可以安装了。可以通过以下命令检查它分配到的输入输
出端口地址是否是0x378:
$ cat /proc/ioports
执行以下命令,可打开LED并且检查系统是否工作正常:
$ echo -n A >/dev/parlelport
0号和6号LED应该亮了,其它的则该是熄的。
可以通过以下命令检查并口状态:
$ cat /dev/parlelport
图 3: 监控并口的LED的电路图
最终的应用程序:闪光灯
最后,我开发了一个有趣的应用程序:它可以让LED不停的闪烁。要实现这个功能需要用户空间应用程序,一次只向/dev/parlelport设备写一个字位(bit)。
<lights.c> =
#include <stdio.h>
#include <unistd.h></p>
int main() {
unsigned char byte,dummy;
FILE * PARLELPORT;
/* Opening the device parlelport */
PARLELPORT=fopen("/dev/parlelport","w");
/* We remove the buffer from the file i/o */
setvbuf(PARLELPORT,&dummy,_IONBF,1);
/* Initializing the variable to one */
byte=1;
/* We make an infinite loop */
while (1) {
/* Writing to the parallel port */
/* to turn on a LED */
printf("Byte value 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 lights lights.c
执行:
$ lights
LED灯将一个接一个的闪烁。图4是闪烁的LED灯和运行该程序的Linux系统。