这个是win驱动课的作业,题目是设计一个通用的io端口读写驱动,因为我的电脑配置太低无法运行虚拟机,就用Linux完成了作业。
read和write的处理并发读写的部分来自ldd3。
1.驱动程序
/*通用IO端口读写驱动*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/moduleparam.h>
#include <linux/proc_fs.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <asm/current.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
//ioctl的命令字定义
#define TEST_MAGIC_NUM 'k'
#define PORT_SET _IOW(TEST_MAGIC_NUM,1,int)
#define PORT_GET _IO(TEST_MAGIC_NUM,2)
#define PORT_LOCK _IO(TEST_MAGIC_NUM,3)
#define PORT_UNLOCK _IO(TEST_MAGIC_NUM,4)
#define MAXBUF 512 //自定义数据区大小为512
#define DEVICE_NAME "/dev/test_device"
#define DEVICE_SCCUESS 0
#define PORT_LOCKED -2
//全局变量尽量采用static变量,避免“污染”内核的变量的命名空间
static int major_number = 1000;
static int minor_number = 0;
static int port_number = 0x70;
module_param(major_number , int , S_IRUGO);
module_param(minor_number , int , S_IRUGO);
//module_param(port_number , long , S_IRUGO);
static dev_t device_number; //设备号
static struct cdev *my_cdev=NULL;
static void *pdev=NULL; //自定义区域,装载模块的时候分配一片区域
//内核信号量,等待队列,用于实现读写的同步
static struct semaphore g_sem;
//static unsigned buf_flag=0;
static unsigned int port_flag = 0;
static DECLARE_WAIT_QUEUE_HEAD(g_queue);
//打开和读写操作,读写操作将实现阻塞I/O,为防止读写冲突,设备只允许一个进程的一个操作
int test_open(struct inode * inode ,struct file *filp)
{
printk("device open OK,process name %s,process ID %i\n",current->comm,current->pid);
return DEVICE_SCCUESS;
}
int test_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
int i=0;
printk("operation: read device");
if(down_interruptible(&g_sem))//获得锁
{
printk(KERN_WARNING "Get semaphore error\n");
return -1;
}
while(port_flag)//端口被占用,等待
{
up(&g_sem);
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if(wait_event_interruptible(g_queue,0 == port_flag))
return -ERESTARTSYS;//可能是信号使得进程被唤醒
if(down_interruptible(&g_sem))
return -ERESTARTSYS;
}
//此时已经获得锁,且操作可以执行
//做标记,不允许修改端口
port_flag=1;
for(i=0;i<count;i++)
((char*)pdev)[i]=inb(port_number);
port_flag=0;//不再占用端口
up(&g_sem);//释放信号量
wake_up_interruptible(&g_queue);//唤醒等待队列中的进程
i=copy_to_user((void*)buf,pdev,count);//读出的数据拷贝给应用程序
if(i<0)
{
printk( KERN_WARNING "copy to user error\n");
return -EFAULT;
}
return count-i;
}
long test_ioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{
int retval=0;
printk("in test_ioctl\n");
switch(cmd)
{
case PORT_SET:
if(port_flag )
{
retval = PORT_LOCKED;
break;
}
else //没有其他进程在IO
{
port_number=arg;
retval = DEVICE_SCCUESS;
printk("port number changed\n");
break;
}
case PORT_GET:
retval = port_number;
printk("port number returned\n");
break;
default: break;
}
return retval;
}
int test_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
int i=0,j=0;
printk("operation: write device\n");
i=_copy_from_user(pdev,(void*)buf,count);//数据拷贝过来并,并将指定端口
if(i<0)
{
printk( KERN_WARNING "copy to user error\n");
return -EFAULT;
}
if(down_interruptible(&g_sem))//获得锁
{
printk(KERN_WARNING "Get semaphore error\n");
return -1;
}
while(port_flag)//操作无法执行,等待
{
up(&g_sem);
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if(wait_event_interruptible(g_queue,0 == port_flag))
return -ERESTARTSYS;//可能是信号使得进程被唤醒
if(down_interruptible(&g_sem))
return -ERESTARTSYS;
}
//此时已经获得锁,且操作可以执行
port_flag=1;
for(j=0;j<count-i;j++)
outb(((char*)pdev)[j],port_number);
port_flag=0;
up(&g_sem);
wake_up_interruptible(&g_queue);
return (count-i);
}
//用户空间进程设备关闭时执行,也就是对应的close操作
int test_release(struct inode *inode, struct file* filp)
{
return 0;
}
static struct file_operations test_ops = {
.owner = THIS_MODULE,
.read = test_read,
.write = test_write,
.release = test_release,
.open = test_open,
.unlocked_ioctl=test_ioctl,
};
int test_init(void) //内核装载时执行,注册设备和分配自由存储区
{
int err=0;
//分配一块缓冲区
pdev = kmalloc(MAXBUF,GFP_KERNEL);
if(NULL == pdev)
{
printk(KERN_WARNING "kernel cannot mallocate memory\n");
return -1;
}
//注册设备
device_number = MKDEV(major_number,minor_number);
err = register_chrdev_region(device_number,1,DEVICE_NAME);
if( 0 > err)
{
printk( KERN_WARNING "cannot register device ,major %d,minor %d\n",major_number,minor_number);
kfree(pdev);//先撤销之前的动作
return err;
}
//分配一个cdev
my_cdev = cdev_alloc();
if(NULL == my_cdev)
{
printk(KERN_WARNING "cannot allocate cdev structure\n");
kfree(pdev);
unregister_chrdev_region(device_number,1);
return -1;
}
//添加设备
my_cdev->ops= &test_ops;
my_cdev->owner = THIS_MODULE;
err=cdev_add(my_cdev,device_number,1);//添加一个设备
if(0 > err)
{
printk(KERN_WARNING"cannot add the device\n");
kfree(pdev);
unregister_chrdev_region(device_number,1);
cdev_del(my_cdev);
return err;
}
//初始化信号量,等待队列已经初始化
sema_init(&g_sem,1);
//设置标志
//buf_flag=0;
port_flag=0;
printk("init io driver OK\n");
return DEVICE_SCCUESS;
}
void test_exit(void) //内核卸载时候执行,注销设备和释放自由存储区
{
printk("remove the test module\n");
unregister_chrdev_region(device_number,1);
cdev_del(my_cdev);
kfree(pdev);
printk(" common io driver unload\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
2. 应用测试
首先用make工具编译然后用insmod装载内核模块
然后执行下面的命令添加一个设备文件
mknod /dev/test_device c 1000 0
然后把一个发光二极管的两个引脚和串口的4、7针接触,再运行这个程序,可以
看到二极管的闪烁现象#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <linux/ioctl.h>//使用下面的宏
#define DEVICE_NAME "/dev/test_device"#define TEST_MAGIC_NUM 'k'#define PORT_SET _IOW(TEST_MAGIC_NUM,1,int)#define PORT_GET _IO(TEST_MAGIC_NUM,2)#define PORT_LOCK _IO(TEST_MAGIC_NUM,3)int main(void){ char buf[24]; int fd=open(DEVICE_NAME,O_RDWR); if(fd < 0) { perror("open device"); exit(fd); }
int i=0;
for(i=0;i<30;i++)
{ ioctl(fd, PORT_SET,0x3fc); buf[0]=1;//DTR=0;RTS=1; write(fd,(void *)buf,1);sleep(1);
buf[0]=2;//DTR=1;RTS=0; write(fd,(void *)buf,1);
sleep(1);
}
close(retcode);
return 0;}
原文地址:http://hi.baidu.com/handsoul/item/4701bdd5c9031b9d270ae71f