Windows驱动开发入门-串口过滤驱动

设备绑定

IoAttachDevice

把一个物理设备与一个设备对象绑定。

1
2
3
4
5
6
7
8
NTSTATUS IoAttachDevice(
IN PDEIVE_OBJECT SourceDevice,
//用来过滤的虚拟设备
IN PUNICODE_STRING TargetDevice,
//要绑定的目标设备 例如\Device\Serial0以此类推
OUT PDEVICE_OBJECT *AttachedDevice
//返回被绑定的设备指针
)

IoAttachDeviceToDeviceStackSafe

上面那个API没法绑定没名字的设备,这个根据设备对象指针绑定。

1
2
3
4
5
6
7
8
NTSTATUS IoAttachDeviceToDeviceStackSafe(
IN PDEVICE_OBJECT SourceDevice,
//过滤设备
IN PDEVICE_OBJECT TargetDevice,
//要被绑定的设备栈中设备
IN OUT PDEVICE_OBJECT* AttachedToDeviceObject
//返回被绑定的设备
)

IoAttachDeviceToDeviceStack

上面那个的不安全版本,但是就用这个了。

1
2
3
4
PDEVICE_OBJECT IoAttachDeviceToDeviceStackSafe(
IN PDEVICE_OBJECT SourceDevice,
IN PDEVICE_OBJECT TargetDevice
)

获取设备

IoCreateDevice

生成过滤设备。

1
2
3
4
5
6
7
8
9
10
11
12
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
//本驱动
IN ULONG DeviceExtensionSize,
//设备扩展 暂时0
IN OPTIONAL PUNICODE_STRING DeviceName,
//设备名 过滤设备一般不用名字
IN DEVICE_TYPE DeviceType,
//设备类型
IN ULONG DeviceCharacteristics
//设备特征 一般0
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
NTSTATUS ccpAttachDevice(PDRIVER_OBJECT driver, PDEVICE_OBJECT oldobj, PDEVICE_OBJECT* fltobj, PDEVICE_OBJECT* next) {
NTSTATUS status=STATUS_SUCCESS;
PDEVICE_OBJECT topdev = NULL;
// 生成设备,然后绑定之。
status = IoCreateDevice(driver, 0, NULL, oldobj->DeviceType, 0, FALSE, fltobj);
if (status != STATUS_SUCCESS)
return status;
// 拷贝重要标志位。
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;
// 绑定一个设备到另一个设备上
topdev = IoAttachDeviceToDeviceStack(*fltobj, oldobj);
if (topdev == NULL) {
// 如果绑定失败了,销毁设备,重新来过。
IoDeleteDevice(*fltobj);
*fltobj = NULL;
status = STATUS_UNSUCCESSFUL;
return status;
};
*next = topdev;
// 设置这个设备已经启动。
(*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
};

IoGetDeviceObjectPointer

从名字获得设备对象指针。

1
2
3
4
5
6
7
8
9
10
NTSTATUS IoGetDeviceObjectPointer(
IN PUNICODE_STRING ObjectName,
//设备名
IN ACCESS_MASK DesiredAccess,
//期望权限 一般FILE_ALL_ACCESS
OUT PFILE_OBJECT* FileObject,
//文件对象 没啥用
OUT PDEVICE_OBJECT* DeviceObject
//要的设备对象
)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 打开一个端口设备
PDEVICE_OBJECT ccpOpenCom(ULONG id, PNTSTATUS status) {
UNICODE_STRING name_str = { 0 };
static WCHAR name[32] = { 0 };
PFILE_OBJECT fileobj = NULL;
PDEVICE_OBJECT devobj = NULL;
// 输入字符串。
memset(name, 0, sizeof(WCHAR) * 32);
RtlStringCchPrintfW(name, 32, TEXT("\\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;
};

绑定所有串口

很简单,假设计算机串口最多32个,然后挨个试,能成功绑定的就绑定,绑定不了的就算了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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 };
// 这个函数绑定所有的串口。
VOID ccpAttachAllComs(PDRIVER_OBJECT driver) {
ULONG i = 0;
PDEVICE_OBJECT com_ob = { 0 };
NTSTATUS status = STATUS_SUCCESS;
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]);
// 取消object引用。
};
return;
};

请求

通过请求的主功能号进行分类,读请求主功能号为IRP_MJ_READ,写请求主功能号为IRP_MJ_WRITE。

当过滤驱动不做任何事儿,直接放行请求到底层设备时,使用IoSkipCurrentIrpStackLocation跳过当前栈空间,并用IoCallDriver把这个请求发送给底层设备。

关于如何获取串口写请求所发送来的数据,IRP结构中有三处缓冲区结构,反正这仨都读一遍就行,哪个能读到就是哪个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp) {
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status=STATUS_SUCCESS;
ULONG i=0, j=0;
// 首先得知道发送给了哪个设备。设备一共最多CCP_MAX_COM_ID个,是前面的代码保存好的,都在s_fltobj中。
for (i = 0; i < CCP_MAX_COM_ID; i++)
if (s_fltobj[i] == device) {
// 所有电源操作,全部直接放过。
if (irpsp->MajorFunction == IRP_MJ_POWER) {
// 直接发送,然后返回说已经被处理了。
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp);
return PoCallDriver(s_nextobj[i], irp);
};
// 此外我们只过滤写请求。写请求的话,获得缓冲区以及其长度。然后打印一下。
if (irpsp->MajorFunction == IRP_MJ_WRITE) {
// 如果是写,先获得长度
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;
// 打印内容
for (j = 0; j < len; ++j)
DbgPrint("comcap: Send Data: %2x\r\n", buf[j]);
};
// 这些请求直接下发执行即可。我们并不禁止或者改变它。
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i], irp);
};
// 如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误。
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
};

动态卸载

一定要解除绑定,要不一卸载就蓝屏。IoDetachDevice将绑定的设备解绑,IoDeleteDevice删除设备以回收内存,KeDelayExecutionThread纯粹负责延时。卸载时可能还有些IRP正在被处理,取消非常麻烦还不一定成功,最好的方法就解绑设备后是等五秒,处理后再删除设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define  DELAY_ONE_MICROSECOND  (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
VOID ccpUnload(PDRIVER_OBJECT drv) {
ULONG i=0;
LARGE_INTEGER interval = { 0 };
// 首先解除绑定
for (i = 0; i < CCP_MAX_COM_ID; i++)
if (s_nextobj[i] != NULL)
IoDetachDevice(s_nextobj[i]);
// 睡眠5秒。等待所有irp处理结束
interval.QuadPart = (5 * 1000 * DELAY_ONE_MILLISECOND);
KeDelayExecutionThread(KernelMode, FALSE, &interval);
// 删除这些设备
for (i = 0; i < CCP_MAX_COM_ID; i++)
if (s_fltobj[i] != NULL)
IoDeleteDevice(s_fltobj[i]);
return;
};

驱动设备入口

1
2
3
4
5
6
7
8
9
10
11
12
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) {
size_t i=0;
// 所有的分发函数都设置成一样的。
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
driver->MajorFunction[i] = ccpDispatch;
// 支持动态卸载。
driver->DriverUnload = ccpUnload;
// 绑定所有的串口。
ccpAttachAllComs(driver);
// 直接返回成功即可。
return STATUS_SUCCESS;
};