驱动编写与调试(2)

I/O请求包数据结构

MdlAddress(PMDL)域指向一个内存描述符表(MDL),该表描述了一个与该请求关联的用户模式缓冲区。如果顶级设备对象的Flags域为DO_DIRECT_IO,则I/O管理器为IRP_MJ_READ或IRP_MJ_WRITE请求创建这个MDL。如果一个IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式,则I/O管理器为该请求使用的输出缓冲区创建一个MDL。MDL本身用于描述用户模式虚拟缓冲区,但它同时也含有该缓冲区锁定内存页的物理地址。为了访问用户模式缓冲区,驱动程序必须做一点额外工作。

Flags(ULONG)域包含一些对驱动程序只读的标志。但这些标志与WDM驱动程序无关。

AssociatedIrp(union)域是一个三指针联合。其中,与WDM驱动程序相关的指针是AssociatedIrp.SystemBuffer。 SystemBuffer指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中。对于IRP_MJ_READ和IRP_MJ_WRITE操作,如果顶级设备指定DO_BUFFERED_IO标志,则I/O管理器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作,如果I/O控制功能代码指出需要缓冲区,则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲区,这也是创建IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据,或者是DeviceIoControl调用中所谓的输入数据。对于读请求,设备驱动程序把读出的数据填到这个缓冲区,然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了METHOD_BUFFERED的I/O控制操作,驱动程序把所谓的输出数据放到这个缓冲区,然后I/O管理器再把数据复制到用户模式的输出缓冲区。
oStatus(IO_STATUS_BLOCK)是一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构。IoStatus.Status域将收到一个NTSTATUS代码,而IoStatus.Information的类型为ULONG_PTR,它将收到一个信息值,该信息值的确切含义要取决于具体的IRP类型和请求完成的状态。Information域的一个公认用法是用于保存数据传输操作,如IRP_MJ_READ,的流量总计。某些PnP请求把这个域作为指向另外一个结构的指针,这个结构通常包含查询请求的结果。

RequestorMode将等于一个枚举常量UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。

PendingReturned(BOOLEAN)如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。完成例程通过参考该域来避免自己与派遣例程间的潜在竞争。

Cancel(BOOLEAN)如果为TRUE,则表明IoCancelIrp已被调用,该函数用于取消这个请求。如果为FALSE,则表明没有调用IoCancelIrp函数。CancelIrql(KIRQL)是一个IQL值,表明那个专用的取消自旋锁是在这个IRQL上获取的。当你在取消例程中释放自旋锁时应参考这个域。

CancelRoutine(PDRIVER_CANCEL)是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接修改该域。

UserBuffer(PVOID) 对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求,该域包含输出缓冲区的用户模式虚拟地址。该域还用于保存读写请求缓冲区的用户模式虚拟地址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驱动程序,其读写例程通常不需要访问这个域。当处理一个METHOD_NEITHER控制操作时,驱动程序能用这个地址创建自己的MDL。

I/O堆栈

任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的IO_STACK_LOCATION结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,另外还有一个堆栈单元供IRP的创建者使用(见图)。堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。图中显示了堆栈单元的结构。

 

MajorFunction(UCHAR)是该IRP的主功能码。这个代码应该为类似IRP_MJ_READ一样的值,并与驱动程序对象中MajorFunction表的某个派遣函数指针相对应。如果该代码存在于某个特殊驱动程序的I/O堆栈单元中,它有可能一开始是,例如IRP_MJ_READ,而后被驱动程序转换成其它代码,并沿着驱动程序堆栈发送到低层驱动程序。

MinorFunction(UCHAR)是该IRP的副功能码。它进一步指出该IRP属于哪个主功能类。例如,IRP_MJ_PNP请求就有约一打的副功能码,如IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE,等等。

Parameters(union)是几个子结构的联合,每个请求类型都有自己专用的参数,而每个子结构就是一种参数。这些子结构包括Create(IRP_MJ_CREATE请求)、Read(IRP_MJ_READ请求)、StartDevice(IRP_MJ_PNP的IRP_MN_START_DEVICE子类型),等等。

DeviceObject(PDEVICE_OBJECT)是与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责填写。

FileObject(PFILE_OBJECT)是内核文件对象的地址,IRP的目标就是这个文件对象。驱动程序通常在处理清除请求(IRP_MJ_CLEANUP)时使用FileObject指针,以区分队列中与该文件对象无关的IRP。

CompletionRoutine(PIO_COMPLETION_ROUTINE)是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程序设置的。你绝对不要直接设置这个域,应该调用IoSetCompletionRoutine函数,该函数知道如何参考下一层驱动程序的堆栈单元。设备堆栈的最低一级驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。

Context(PVOID)是一个任意的与上下文相关的值,将作为参数传递给完成例程。你绝对不要直接设置该域;它由IoSetCompletionRoutine函数自动设置,其值来自该函数的某个参数。
(如果你想知道更详细的介绍请参看相关书籍)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux的8250串口驱动是一个非常常见的驱动程序,它允许Linux内核与串口设备进行通信。本文将介绍如何编写调试8250串口驱动程序。 1. 编写驱动程序 步骤一:定义串口设备结构体 首先,你需要定义一个串口设备结构体,它将存储有关串口设备的信息,例如波特率、数据位数、停止位数和校验位等。以下是一个示例结构体: ``` struct uart_port { unsigned int baud_rate; unsigned int data_bits; unsigned int stop_bits; unsigned int parity; unsigned int irq; unsigned int line; unsigned int type; void *private_data; }; ``` 步骤二:初始化串口设备驱动程序初始化期间,你需要为每个串口设备分配内存并初始化其结构体。以下是一个示例函数: ``` static int uart_init_port(struct uart_port *port) { // Allocate memory for the private data port->private_data = kmalloc(sizeof(struct my_private_data), GFP_KERNEL); if (!port->private_data) { return -ENOMEM; } // Initialize the private data ((struct my_private_data *)port->private_data)->tx_busy = false; // Initialize the UART hardware uart_hw_init(port); return 0; } ``` 步骤三:注册串口设备 一旦你初始化了每个串口设备,你需要将它们注册到Linux系统中。以下是一个示例函数: ``` static int uart_register_port(struct uart_port *port) { // Register the UART device if (uart_register_device(&my_uart_driver, port) != 0) { return -ENODEV; } return 0; } ``` 步骤四:实现串口数据读写 最后,你需要实现串口数据的读写函数。这些函数将允许用户空间的应用程序与串口设备进行通信。以下是一个示例函数: ``` static ssize_t uart_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct uart_port *port = my_uart_driver.state->port; // Read data from the UART hardware unsigned char *data = kmalloc(count, GFP_KERNEL); int len = uart_hw_read(port, data, count); // Copy the data to the user buffer if (copy_to_user(buf, data, len) != 0) { kfree(data); return -EFAULT; } kfree(data); return len; } static ssize_t uart_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct uart_port *port = my_uart_driver.state->port; // Copy the data from the user buffer unsigned char *data = kmalloc(count, GFP_KERNEL); if (copy_from_user(data, buf, count) != 0) { kfree(data); return -EFAULT; } // Write data to the UART hardware int len = uart_hw_write(port, data, count); kfree(data); return len; } ``` 2. 调试驱动程序 在调试8250串口驱动程序时,最好的方法是使用一个UART调试器。这个调试器将允许你将串口设备连接到另一台计算机,并通过串口进行通信。这样,你就可以实时查看串口设备发送和接收的数据。 如果你无法使用UART调试器,你可以使用Linux内核的调试器。以下是一些常用的调试技巧: - 使用 printk() 函数记录调试信息 - 使用 dmesg 命令查看内核日志 - 使用 gdb 调试内核模块 总结 编写调试8250串口驱动程序需要一定的经验和技巧。在编写驱动程序时,你需要定义一个串口设备结构体、初始化串口设备、注册串口设备和实现串口数据读写函数。在调试驱动程序时,最好使用一个UART调试器,或使用Linux内核的调试器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值