1. 细碎知识点
- Windows中串口设备具有固定的名字,第一个串口叫"\Device\Serial0",第二个叫"\Device\Serial1"
- 快速构造UNICODE_STRING:UNICODE_STRING com_name = RTL_CONSTANT_STRING(L"\Device\Serial0");
- 过滤设备一般不需要名称,IoCreateDevice传入NULL即可
- 一个驱动中生成从属于其他驱动的设备对象,理论上是可以的
- IRP是上层设备之间传递请求的常见数据结构,但并非唯一的数据结构
- 串口设备接受到的请求都是IRP
- 设备卸载的相关API:IoDetachDevice、IoDeleteDevice、KeDelayExecutionThread
2. 设备绑定的API
IoAttachDevice绑定有名设备
NTSTATUS IoAttachDevice (
IN PDEVICE_OBJECT SourceDevice,
IN PUNICODE_STRING TargetDevice,
OUT PDEVICE_OBJECT *AttachDevice
);
SourceDevice
:调用者生成的用来过滤的虚拟设备
TargetDevice
:字符串,要被绑定的设备的名字
AttachDevice
:返回被绑定的设备对象的指针
IoAttachDevice只能用来绑定具有名称的设备,且总是会绑定设备栈上最顶层的那个设备
IoAttachDeviceToDeviceStack、IoAttachDeviceToDeviceStackSafe绑定无名设备
NTSTATUS IoAttachDeviceToDeviceStackSafe(
IN PDEVICE_OBJECT SourceDevice, // 过滤设备
IN PDEVICE_OBJECT TargetDevice, // 要被绑定的设备栈中的设备
IN OUT PDEVICE_OBJECT *AttachedToDeviceObject// 返回最终被绑定的设备
);
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
IN PDEVICE_OBJECT SourceDevice,
IN PDEVICE_OBJECT TargetDevice
);
TargetDevice
:指针,要被绑定的设备的指针
返回值
:返回最终被绑定的设备
IoAttachDeviceToDeviceStack只能再windows2000下使用,推荐使用IoAttachDeviceToDeviceStackSafe
3. 生成过滤设备并绑定
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, //本驱动的驱动对象,DriverEntry传入
IN ULONG DeviceExtensionSize, //设备扩展
IN PUNICODE_STRING DeviceName OPTIONAL, //设备名,过滤驱动一般传入NULL即可
IN DEVICE_TYPE DeviceType, //设备类型,与要过滤的设备保持一致
IN ULONG DeviceCharacteristics, //凭经验直接填0
IN BOOLEAN Exclusive, //是否独占,一般FALSE
OUT PDEVICE_OBJECT *DeviceObject //输出创建好的设备对象
);
绑定示例代码:
NTSTATUS ccpAttachDevice(
PDRIVER_OBJECT driver,
PDEVICE_OBJECT oldobj,
PDEVICE_OBJECT *fltobj,
PDEVICE_OBJECT *next)
{
NTSTATUS status;
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->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;
}
从名字获得设备对象
NTSTATUS
IoGetDeviceObjectPointer(
IN PUNICODE_STRING ObjectName, //要获取设备对象的设备名
IN ACCESS_MASK DesiredAccess, //期望获得的权限
OUT PFILE_OBJECT *FileObject, //获得设备对象同时获得的文件对象
OUT PDEVICE_OBJECT *DeviceObject //获得的设备对象
);
FileObject
在获得后不用时,必须通过ObDereferenceObject解除引用,否则内存泄漏
IRP请求的区分
IRP都具有一个主功能号和一个次功能号
// 这里的 irpsp 称为 IRP 的栈空间,IoGetCurrentIrpStackLocation 获得当前栈空间
// 栈空间是非常重要的数据结构
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
if(irpsp->MajorFunction == IRP_MJ_WRITE)
{
// 如果是写…
}
else if(irpsp->MajorFunction == IRP_MJ_READ)
{
// 如果是读…
}
IRP的过滤行为
- 请求被允许通过,过滤不做任何事情
// 跳过当前栈空间 IoSkipCurrentIrpStackLocation(irp); // 将请求发送到对应的真实设备。记得我们前面把真实设备都保存在 s_nextobj // 数组中。那么这里 i 应该是多少?这取决于现在的 IRP 发到了哪个 // 过滤设备上。后面讲解分发函数时读者将了解到这一点 status = IoCallDriver(s_nextobj[i],irp);
- 请求直接被否决,过滤禁止这个请求通过
- 过滤完成了请求,但对请求进行了修改
从串口中获取IRP传递的数据
//缓冲区的获取
PBYTE buffer = NULL;
if(irp->MdlAddress != NULL)
buffer = (PBYTE)MmGetSystemAddressForMdlSafe(irp->MdlAddress);
else
buffer = (PBYTE)irp->UserBuffer;
if(buffer == NULL)
buffer = (PBYTE)irp->AssociatedIrp.SystemBuffer;
//缓冲区大小的获取
ULONG length = irpsp->Parameters.Write.Length;
串口过滤的完整分发函数
NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
{
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status;
ULONG i,j;
// 首先要知道发送给了哪个设备。设备最多一共有 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;
}
动态卸载串口过滤
#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;
LARGE_INTEGER interval;
// 首先解除绑定
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]);
}
}