首页
社区
课程
招聘
[旧帖] [原创]初级串口驱动开发学习笔记(求邀请码) 0.00雪花
发表于: 2010-12-13 22:34 1900

[旧帖] [原创]初级串口驱动开发学习笔记(求邀请码) 0.00雪花

2010-12-13 22:34
1900
一.        从驱动文件走进开发第一步:
一般,编译一个驱动需要三个主要文件:
.c文件(.cpp亦可)   sources文件    makefile文件
其中.c文件为驱动实现文件,sources文件和makefile文件可以理解为辅助文件,用于编译之用。
二.        根据框架图来逐步细化:
1.        串口过滤驱动入口:
所有Windows驱动均是以DriverEntry函数作为入口,好比C语言中的main函数或MFC中的WinMain函数;据驱动程序的框架,我们可以在这个函数里面定义处理IRP的派遣函数,卸载函数,及绑定串口函数。
另外,我们设定过滤的串口个数设为:CCP_MAX_COM_ID;
现在我们来用代码来见证这种构思:
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
    size_t i;
    // 所有的分发函数都设置成一样的。
    for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
    {
        driver->MajorFunction[i] = ccpDispatch;
    }
    // 支持动态卸载。
    driver->DriverUnload = ccpUnload;
    // 绑定所有的串口。
    ccpAttachAllComs(driver);
    // 直接返回成功即可。
    return STATUS_SUCCESS;
}
2.派遣函数:
    2.1电源操作:
                  对于每个驱动都会对应一些设备的开关,比如现在面对的电源,所以我们在驱动加载时打开即可:
// 所有电源操作,全部直接放过。
   if(irpsp->MajorFunction == IRP_MJ_POWER)
   {
     // 直接发送,然后返回说已经被处理了。
     PoStartNextPowerIrp(irp);
     IoSkipCurrentIrpStackLocation(irp);
     return PoCallDriver(s_nextobj[i],irp);
    }
2.2过滤写请求:
a.写请求的数据:
  首先我们来看一下IRP数据结构:
typedef struct _IRP {
  PMDL  MdlAddress;
  ULONG  Flags;
  union {
    struct _IRP  *MasterIrp;
    PVOID  SystemBuffer;
  } AssociatedIrp;
  IO_STATUS_BLOCK  IoStatus;
  KPROCESSOR_MODE  RequestorMode;
  BOOLEAN PendingReturned;
  BOOLEAN  Cancel;
  KIRQL  CancelIrql;
  PDRIVER_CANCEL  CancelRoutine;
  PVOID UserBuffer;
  union {
    struct {
    union {
      KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
      struct {
        PVOID  DriverContext[4];
      };
    };
    PETHREAD  Thread;
    LIST_ENTRY  ListEntry;
    } Overlay;
  } Tail;
} IRP, *PIRP;
其中有3个地方可以描述缓冲区:
IRP->MdlAddress :把应用层的地址空间映射到内核空间(以虚拟地址实现);
IRP->UserBuffer :应用层的缓冲区地址直接放在UserBuffer里,在内核空间中访问;
IRP-> SystemBuffer :把应用层中内存空间中的缓冲数据拷贝到内核空间。
不同的IO类别,IRP的缓冲区不同。因为我们要获取的是所有的数据,所以3个地方我们都去就是了。另外,在此之前,我们得申请一块缓冲区供存放读取的书据,这又涉及到此块缓冲区应该申请多大的问题,幸好WDK文档中,对于IRP_MJ_WRITE有如下说明:
The buffer supplies data for the device or driver. The buffer's length is specified by Parameters.Write.Length in the driver's IO_STACK_LOCATION structure.
所以对于写操作而言,缓冲区长度可以如下获得:
ULONG len = irpsp->Parameters.Write.Length;
下面就是获取数据写入缓冲区了:
PUCHAR buf = NULL;
if(irp->MdlAddress != NULL)
   buf =(PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
else
   buf = (PUCHAR)irp->UserBuffer;
if(buf == NULL)
buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
b.打印数据:
// 打印内容
for(j=0;j<len;++j)
{
DbgPrint("comcap: Send Data: %2x\r\n",
buf[j]);
}
C.请求数据后的善后处理:
        对于请求数据后的处理,我们不得不谈谈过滤的概念:
        过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真实驱动程序,就加入了新的功能。简单点说,就是从中间层”偷看信息”,而不让别人知道。所以作为”偷窥者”,我们最好让这个信息回到它该去的地方,以下代码可以帮我们顺利”逃过凶险”:
// 这些请求直接下发执行即可。我们并不禁止或者改变它。
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i],irp);
3.动态卸载函数:
作为一名合格的程序员,我们应考虑到加载后的驱动程序如何卸载,这里的动态卸载函数有点像Windows应用程序里的回调函数,在入口函数中声明,等待事件发生就触发此函数。在没有介绍绑定的前提下,我们先做一个约定:
在卸载函数中先要完成解除过滤驱动绑定的功能,否则会出现蓝屏。
同时我们要补充的一点就是,在卸载过滤驱动时,必须要考虑到一个时间差的问题,即一些IRP的请求是否已经处理完。作为保险我们最好在删除设备前能在线程中睡眠一小段时间,以确保发送过来的请求处理完成,也避免因此而产生蓝屏现象。
        好啦,停下来把我们的思路再整理整理,首先记得我们的约定,解除过滤驱动绑定的功能;然后因为请求处理时间差的问题,我们得”睡眠”一小段时间;然后呢,就是删除设备了。下面用代码来实现:
// 1.首先解除绑定
for(i=0;i<CCP_MAX_COM_ID;i++)
{
        if(s_nextobj[i] != NULL)
                IoDetachDevice(s_nextobj[i]);
}
// 2.睡眠 5 秒。等待所有irp处理结束
interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);               
KeDelayExecutionThread(KernelMode,FALSE,&interval);
//3. 删除这些设备
for(i=0;i<CCP_MAX_COM_ID;i++)
{
        if(s_fltobj[i] != NULL)
                IoDeleteDevice(s_fltobj[i]);
}
4.绑定串口
4.1打开一个端口设备
        在入口函数DriverEntry中已经传进一个设备的名字,通过这个名字我们就可以调用IoGetDeviceObjectPointer来获得这个设备对象的指针。以下是这个函数的原型:
NTSTATUS
IoGetDeviceObjectPointer(
                IN PUNICODE_STRING  ObjectName,   //设备名字
                IN ACCESS_MASK  DesiredAccess,    //期望访问的权限
                OUT PFILE_OBJECT  *FileObject,    //输出一个文件对象
                OUT PDEVICE_OBJECT  *DeviceObject //输出这个设备对象的指针
);
        这里有一点需要注意,这个函数返回的参数中有一个FileObject文件对象,在使用这个函数之后必须把它”解除应用”,否则会引起内存泄漏。
        另外,在驱动开发中字符串一般都由一个叫做 UNICODE_STRING 的结构体来存储,下面是它的定义:
typedef struct _UNICODE_STRING {
        USHORT  Length;
        USHORT  MaximumLength;
        PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
结构体内的参数根据其名称就可以断定,就不多说了。
以下是实现打开一个端口的代码:
// 打开一个端口设备
PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status)
{
        UNICODE_STRING name_str;
        static WCHAR name[32] = { 0 };
        PFILE_OBJECT fileobj = NULL;
        PDEVICE_OBJECT devobj = NULL;
        // 输入字符串。
        memset(name,0,sizeof(WCHAR)*32);
        RtlStringCchPrintfW(
                name,32,
                L"\\Device\\Serial%d",id);
        RtlInitUnicodeString(&name_str,name);
        // 打开设备对象
        *status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);
        if (*status == STATUS_SUCCESS)
                ObDereferenceObject(fileobj);
        return devobj;
}
4.2 绑定设备
        在绑定一个设备之前,我们最好先生成一个过滤设备,用它来绑定现有设备。如此,我们先用 IoCreateDevice 函数生成一个设备,其原型如下:
NTSTATUS
IoCreateDevice(
        IN PDRIVER_OBJECT  DriverObject,   //本驱动的驱动对象
        IN ULONG  DeviceExtensionSize,     //设备扩展
        IN PUNICODE_STRING  DeviceName  OPTIONAL,  //设备名称
        IN DEVICE_TYPE  DeviceType,                //设备类型
        IN ULONG  DeviceCharacteristics,           //设备特征
        IN BOOLEAN  Exclusive,
        OUT PDEVICE_OBJECT  *DeviceObject   //产生一个设备对象指针
);
        值得注意的是,在绑定一个设备之前,应该把这个设备对象的多个子域设置成要和绑定的目标对象一致,包括标志和特征。下面给个小示例:
// 拷贝重要标志位。
if(oldobj->Flags & DO_BUFFERED_IO)
        (*fltobj)->Flags |= DO_BUFFERED_IO;
if(oldobj->Flags & DO_DIRECT_IO)
        (*fltobj)->Flags |= DO_DIRECT_IO;
if(oldobj->Flags & DO_BUFFERED_IO)
        (*fltobj)->Flags |= DO_BUFFERED_IO;
if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
        (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
(*fltobj)->Flags |=  DO_POWER_PAGABLE;
然后就是把这个过滤设备绑定到现有设备上了,在此,我们必须要有一个根据设备对象指针(而不是名字)来进行绑定,在WDK中 IoAttachDeviceToDeviceStack 这个函数课用来实现此功能,其原型如下:
PDEVICE_OBJECT
IoAttachDeviceToDeviceStack(
        IN PDEVICE_OBJECT  SourceDevice,
        IN PDEVICE_OBJECT  TargetDevice
);
绑定好设备之后,最好能将这个设备设置成已经启动:
// 设置这个设备已经启动。
(*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
4.3 绑定所有串口:
        如果要实现所有的串口,就必须能够实现串口过滤设备的一一对应,因此用数组实现最好,如果笔者没记错的话,串口个数在计算机上好像最大只能设置成256,在此我们设定为32作为实验之用吧。以下用代码来巩固我们的设想:
//假定要检测的串口个数为32个
#define CCP_MAX_COM_ID 32
//保存所有的过滤设备指针
static PDEVICE_OBJECT s_fltobj[CCP_MAX_COM_ID] = { 0 };
//保存所有真实设备指针
static PDEVICE_OBJECT s_nextobj[CCP_MAX_COM_ID] = { 0 };
ULONG i;
PDEVICE_OBJECT com_ob;
NTSTATUS status;
for(i = 0;i<CCP_MAX_COM_ID;i++)
{
        // 获得object引用,打开一个端口设备
        com_ob = ccpOpenCom(i,&status);
        if(com_ob == NULL)
                continue;
        // 在这里绑定。并不管绑定是否成功
        // 生成一个设备,然后绑定在领了一个设备上
        ccpAttachDevice(driver,com_ob,&s_fltobj[i],&s_nextobj[i]);
}
三.        生成驱动并测试
用Windows Driver Kits中的x86 Checked Build Environment生成驱动;
1.        用DriverMonitor控制驱动的加载和卸载,软件图标如下:
用DriverMonitor加载和卸载串口驱动comcap.sys会有相应信息显示如下:

2.用Dbgview来观察加载驱动后串口的输出消息:

四.        对comcap.c进行修改,使之对所有的串口输出都禁止
对于禁止串口的输出,我们可以回忆一下当我们的过滤设备接收消息后是如何处理的,根据当时的需求,我们只是做了一个监视的功能,现在要做一个控制的功能,所以回到处理时的代码看看:
// 这些请求直接下发执行即可。我们并不禁止或者改变它。
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i],irp);
对于禁止输出无非就是我们拿到消息之后,不要往下传,”就当什么事情也没发生”。想想,我们要不往下传,可以取消调用上面的函数即可。(即将源文件中的这两行代码注释即可)

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (10)
雪    币: 21
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
lai学习看看
2010-12-13 22:41
0
雪    币: 49
活跃值: (29)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
非常感谢,文章不错,收藏了。
2010-12-13 22:52
0
雪    币: 10
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
新人多多学习啊
2010-12-14 12:24
0
雪    币: 111
活跃值: (184)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
这个是我的学习笔记,能对大家有作用,深感荣幸!
2010-12-14 12:33
0
雪    币: 137
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
学习下 ,支持
2010-12-14 22:15
0
雪    币: 8
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
学习
2010-12-14 22:39
0
雪    币: 201
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
不太看得懂,先学习了
2010-12-15 00:12
0
雪    币: 9
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
正在看Linux设备驱动程序(第三版),同时复习下c语言基础
2010-12-15 15:34
0
雪    币: 111
活跃值: (184)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
顶自己一个,大家一起学习,互相学习!
2010-12-16 22:24
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
这个要支持一下,可以学习!!
2010-12-17 09:24
0
游客
登录 | 注册 方可回帖
返回
//