6 Porting Basic Driver–Use Parallel Device
6.3 Map Program Section
本章節以 Null Modem 撰寫簡單的對應程式,並探討每個部分轉換對應的方式。
6.3.1 Map Include File
Windows 所有 Device Driver 的函式庫都包含在 wdm.h 及 ntddk.h8這兩個標頭檔,
但 Linux 則將這些函數依類別分散在不同的標頭檔,所以轉換時會把要分析的 Device Driver 原始程式檔先解譯(Parser)一次後,得知所有 Windows 對應的 Linux 函數及該函數 所引用的標頭檔,再將這些標頭檔與程式設計師所定義的標頭檔放在 Include File 的區 段中,如 Table 45 所示。
Windows Linux
#include <wdm.h>
#include <ntddk.h>
#include < linux/kernel.h > /* for module support */
#include < linux/module.h > /* for module support */
#include < linux/fs.h > /* for struct file_operations */
#include < linux/wrapper.h > /* for module_(un)register_chrdev() */
#include < linux/types.h > /* for ssize_t */
#include <linux/errno.h> /* for return status */
#include <linux/mm.h> /* for memory management */
#include <linux/interrupt.h> /* for interrupt */
#include <asm/uaccess.h> /* for memory copy */
#include <asm/io.h> /* for hardware */
#include <ntstatus.h> #include <linux/errno-base.h>
Table 45: Linux Mapped Header Files
如 Table 46 所示,轉換時對應到 Linux 有用到 Set Memory 的相關函數,如 kmalloc、
kfree、memset 等函數,表示在轉換到 Linux 在 Include File Area 要加入#include
<linux/slab.h>;轉換時對應到 Linux 有用到 Double Linked List 的相關函數,如 list_add、
list_add_tail、list_move_tail 等函數,則表示在轉換到 Linux 在 Include File Area 必須要 加入#include <linux/list.h>,依此類推。使用到回傳值的部分,則 Windows 的 ntstatus.h 對應到 Linux 的 linux/errno-base.h。
Type Include files Functions
Set Memory linux/slab.h kmalloc、kfree、memset
8 在 Vista 版本之前,wdm.h 為 ntddk.h 的子集合
Double Linked List
linux/list.h list_add、list_add_tail、list_move_tail、list_empty、
list_move
Atomic asm/atomic.h atomic_inc、atomic_dec Memory Copy asm/uaccess.h copy_from_user、copy_to_user Interrupt linux/interrupt.h request_irq、free_irq
Timer linux/time.h init_timer、del_timer、add_timer、mod_timer
Spinlock asm/spinlock.h spin_lock_init、spin_lock、spin_unlock
Table 46: Linux Mapped Header Files
6.3.2 Map Function Declaration Area
Windows 在每個 Entry Function 所傳入的參數都有 DEVICEOBJECT 及 Irp 這兩個變 數,Linux 沒有像 Irp 這樣的傳遞機制,從 Table 47 可看出 Linux 函數宣告區每個 Entry Function 所傳入的參數不盡相同,因此轉換時會將這部分的傳遞機制做適當的替換。
Windows Linux NTSTATUS DriverEntry(IN PDRIVER_OBJECT
DriverObject,IN PUNICODE_STRING RegistryPath);
static struct file_operations XXX_fop
NTSTATUS XXX_AddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject);
static int __init XXX_init(void);
module_init(XXX_init);
DriverObject->DriverUnload(); static void __exit XXX_exit(void);
module _exit(XXX_exit);
NTSTATUS XXX_DispatchCreate(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp);
int XXX_open (struct inode *, struct file *);
NTSTATUS XXX_DispatchClose(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp);
int XXX_release (struct inode *, struct file *);
NTSTATUS XXX_DispatchRead(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp);
size_t XXX_aio_read (struct kiocb *, char __user
*, size_t, loff_t);
NTSTATUS XXX_DispatchWrite(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp);
size_t XXX_aio_write (struct kiocb *, const char __user *, size_t, loff_t);
NTSTATUS
XXX_DispatchDeviceControl( PDEVICE_OBJECT fdo, PIRP Irp);
int XXX_ioctl (struct inode *, struct file *, unsigned int, unsigned long);
Table 47: The Comparison of Program Section
6.3.3 Map Global Data Area
因兩個平台均使用 C 語言開發,全域變數的部分是屬於 Driver 邏輯處理的部分,不 受平台及 Driver Model 所影響,如下面 Parallel Port 的範例,由於兩邊的硬體均相同因 此 1~5 行宣告的部分可直接對應,不需做任何轉換上的修改;而 6~9 行為程式邏輯的控 制變數同樣可以直接取用,不需修改。
1: #define BASEPORT 0x378 2: #define DATAPORT BASEPORT 3: #define STATUSPORT (DATAPORT+1) 4: #define CTRLPORT (DATAPORT+2)
5: #define PARALLEL_PORT_INTERRUPT 7 6: #define MAXIMUM_BUFFER_SIZE 128 7: static unsigned int counter = 0;
8: static char string [MAXIMUM_BUFFER_SIZE];
9: static int data;
I/O Control 的對應之前已有詳細的介紹,在 Table 48 的範例中設有兩個 IOCTL 分 別為 IOCTL_READ_DATA 及 IOCTL_WRITE_DATA(紅色字體);在 Windows 新增的 Device Type 對應到 Linux 值為 t (Documentation/ioctl-number.txt);查詢/proc/devices 沒有 使用的號碼為 253;Device Name 也必須轉換為 Linux 可用的字串。
Windows Linux
#define IOCTL_READ_DATA CTL_CODE (FILE_DEVICE_UNKNOWN, 0x8001,
METHOD_BUFFERED, FILE_READ_ACCESS)
#define IOCTL_READ_DATA _IOR('t', 1, unsigned int) I/O Control
#define IOCTL_WRITE_DATA CTL_CODE (FILE_DEVICE_UNKNOWN, 0x8002,
METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_WRITE_DATA _IOW('t', 2, unsigned int)
Device Type #define DeviceType FILE_DEVICE_UNKNOWN #define PARALLEL_MAJOR 253 Device Name #define DeviceName L"\\Device\\TestDevice" #define DeviceName "TestDevice"
Table 48: The Comparison of I/O Control and Device Name
6.3.4 Map Driver Internal Data
Windows 及 Linux 的 Driver 都有可重入(Reentrant)的特性,因此一段 Driver Code 要 能被安全的同時執行,則不能使用全域變數來儲存狀態,Driver 必須使用內部資料來記 錄每個 Device 在 Driver Code 所新增的行程資訊,如 Table 49,此部分可放在 Windows 的 DeviceObjectÆDEVICE_EXTENSION 對應到 Linux 則為 fileÆprivate_data(綠色字體)。
Windows Linux typedef struct _DEVICE_EXTENSION { typedef struct private_data {
Odds
OS
PUCHAR buffer;
ULONG buf_size;
ULONG chars_in_buffer;
KSPIN_LOCK lock;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
unsigned char *buffer;
unsigned long buf_size;
unsigned long *chars_in_buffer;
spinlock_t lock;
} private_data , *p_private_data ; NTSTATUS XXX_DriverOpen(IN PDEVICE_OBJECT
DeviceObject,IN PIRP Irp) {
PDEVICE_EXTENSION pdx=
(PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
…..
}
static int XXX_open(struct inode *inode, struct file *file)
{
p_private_data pdx =
(p_private_data)file-> private_data;
…..
}
Table 49: The Comparison of Driver Internal Data
6.3.5 Map Driver Entry Function
Driver 在 Entry Function 定義所有的 Function Pointer,如 Table 50 的藍色字體,
Windows 是放置在 DriverEntry()函數,這個函數是 Windows Driver 最先呼叫的函數,相 當於 C 語言的 main(),而 Linux 則被放 file_operations 的 structure 中。在 Windows 中 DriverEntry 函數還包含了一些初始化的動作,這部分無法在 file_operations 的 structure 處理,Linux 放在 init_module 所定義的函數來處理, Windows 及 Linux 都有對應的 Entry Function,如 Create、Close、Read、Write、Ioctl、Flush(紫色字體)。比較特殊的一點是 Linux 會加上此 Driver Module 的相關資訊(深藍色字體)。
Windows Linux
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath){
DriverObject->DriverExtension->AddDevice = XXX_AddDevice;
DriverObject->MajorFunction[IRP_MJ_CREATE] = XXX_ DispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = XXX_DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = XXX_DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = XXX_DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]=XXX_DispatchFlush;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=XXX_DispatchDev iceControl;
DriverObject->DriverUnload = XXX_DriverUnload;
//Other initial function return STATUS_SUCCESS;
}
static struct file_operations XXX_fops
={
owner = THIS_MODULE, .open = XXX_open, .release = XXX_release, .read= XXX_aio_read,
Static void XXX_init_module(void) { //Other initial function
NTSTATUS XXX_AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
{}
VOID XXX_DriverUnload(PDRIVER_OBJECT DriverObject) {}
return 0;
}
static void XX_cleanup_module(void) {}
Table 50: The Comparison of Entry Function
6.3.6 Map Create Function Block
如 Table 51 所示,在新增裝置(Device)時,要先轉換兩邊的裝置名(咖啡色字體),
接下來初始化裝置的內部變數(藍色字體),新增裝置(紅色字體),設定中斷(紫色字體),
產生中斷(橘色字體),最後回傳是否成功(粉紅色字體)。因為 Linux 沒有 UniCode 及 IRP 所以不移轉(灰色字體)。
Windows Linux
#define DeviceName L"\\Device\\TestDevice";
NTSTATUS skeleton_DriverOpen(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) {
PDEVICE_EXTENSION pdopdx =
(PDEVICE_EXTENSION)DEVICEOBJECT->DeviceExtension
;
NTSTATUS ntStatus;
UNICODE_STRING uniDeviceName;
RtlInitUnicodeString(&uniDeviceName, DeviceName);
ntStatus = IoCreateDevice(DriverObject,sizeof
(DEVICE_EXTENSION),&uniDeviceName,FILE_DEVICE_UN KNOWN,0,FALSE, &fdo);
//set parallel port to interrupt mode; pins are output WRITE_PORT_UCHAR(CONTROL_PORT, 0x10);
KdPrint("Generate interrupt (intr/ACK = pin 10)\n");
//generate interrupt
WRITE_PORT_UCHAR(BASEPORT,0);
#define DeviceName "TestDevice“
static int skeleton_open(struct inode *inode, struct file *file) {
p_private_data private_data = (p_private_data)file->private_data;
int ret;
ret = register_chrdev(SKELETON_MAJOR, DeviceName, &XXX_fops);
//set parallel port to interrupt mode; pins are output outb(0x10, CONTROL_PORT);
printk("Generate interrupt (intr/ACK = pin 10)\n");
//generate interrupt outb(0, BASEPORT);
outb(255, BASEPORT);
Functions used for module description
WRITE_PORT_UCHAR(BASEPORT,255);
WRITE_PORT_UCHAR(BASEPORT,0);
KdPrint("Interrupt generated.\n");
fdo->Flags |= DO_BUFFERED_IO;
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus ; }
outb(0, BASEPORT);
printk("Interrupt generated.\n");
return ret;
}
Table 51: The Comparison of Create Function
6.3.7 Map Close Function Block
當裝置要關閉時,如 Table 52,裝置結束前關閉 Parallel Port 的 IRQ(咖啡色字體),
刪除該裝置(紅色字體)並中止中斷(紫色字體)。因為 Linux 沒有 UniCode 及 IRP 所以不 移轉(灰色字體)。
Windows Linux
NTSTATUS skeleton_DriverClose(IN
PDEVICE_OBJECT DeviceObject,IN PIRP Irp) { UNICODE_STRING uniDOSDeviceName;
RtlInitUnicodeString(&uniDOSDeviceName, DOS_SYMBOLIC_NAME);
IoDeleteSymbolicLink (&uniDOSDeviceName);
KdPrint("skeleton_release\n");
/* Disable Parallel Port IRQ's */
WRITE_PORT_UCHAR( CONTROL_PORT , 0xEF);
IoDeleteDevice(DriverObject->DeviceObject);
/* Disconnect Interrupt */
IoDisconnectInterrupt(PARALLEL_PORT_INTERRUPT);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
static int skeleton_release(struct inode *inode, struct file
*file) {
printk("skeleton_release\n ");
/* Disable Parallel Port IRQ's */
outb( 0xEF, CONTROL_PORT);
unregister_chrdev(SKELETON_MAJOR, DeviceName);
/* Disconnect Interrupt */
free_irq(PARALLEL_PORT_INTERRUPT, NULL);
return 0;
}
Table 52: The Comparison of Close Function
6.3.8 Map Write Function Block
當/dev/TestDevice 被寫入資料時,會呼叫此函數來處理,如 Table 53 紅色箭頭所示,
左邊的 Windows 函數將 buffer 的位址指標(buf = Irp->AssociatedIrp.SystemBuffer)、欲讀 取之資料長度(count = stack->Parameters.Write.Length;)及回傳讀取資料長度均放在 IRP 做傳遞(Irp->IoStatus.Information = count;),因此必須使用函數
IoGetCurrentIrpStackLocation(Irp)來取得 IRP 指標;而 Linux 則將 buffer 的位址指標(const char *buf)、欲讀取之資料長度(size_t count)及回傳讀取資料長度(static ssize_t)均放在函 數的傳入及傳出參數裏。
Windows Linux
NTSTATUS skeleton_DriverWrite(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) {
NTSTATUS status;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
PUCHAR buf = NULL;
ULONG count;
count = stack->Parameters.Write.Length;
if (DEVICEOBJECT->Flags & DO_BUFFERED_IO) buf = Irp->AssociatedIrp.SystemBuffer;
else if (DEVICEOBJECT->Flags&DO_DIRECT_IO) buf = Irp->MdlAddress;
status = RtlCopyMemory(string,buf,count);
if (status != STATUS_SUCCESS)
status = STATUS_UNSUCCESSFUL;
counter += count;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = count;
IoCompleteRequest( Irp, IO_NO_INCREMENT);
return status;
}
static ssize_t skeleton_write(struct file *file, const char *buf,size_t count, loff_t *ppos) { int status;
status = copy_from_user(string,buf,count);
if (status!= 0)
status = -EFAULT;
counter += count;
return count;
}
Table 53: The Comparison of Write Function
6.3.9 Map Ioctl Function Block
程式設計師想另行擴充自訂的命令可在 I/O Control 定義 Control Code(紅色字體),
一旦有應用程式呼叫到此 Driver 的 Control Code 來執行,系統便會呼叫此函數執行,從 Table 54 右半部可看出,因 Linux 沒有 UniCode 及 IRP 所以不移轉(灰色字體)。
Windows Linux
NTSTATUS skeleton_DriverDeviceControl(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) {
static int skeleton_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg) {
NTSTATUS ntStatus =STATUS_SUCCESS;
irpStack = IoGetCurrentIrpStackLocation(Irp);
if( DEVICEOBJECT->Flags & DO_BUFFERED_IO ) ioBuffer = Irp->AssociatedIrp.SystemBuffer;
ioControlCode=irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioControlCode){
case IOCTL_READ_DATA:
Irp->IoStatus.Information = sizeof(int);
if (RtlCopyMemory(&data, (int *)arg, sizeof(int))) ntStatus = STATUS_UNSUCCESSFUL;
break;
case IOCTL_WRITE_DATA:
Irp->IoStatus.Information = sizeof(int);
if (RtlCopyMemory((int *)arg, &data, sizeof(int))) ntStatus = STATUS_UNSUCCESSFUL;
break;
default:
ntStatus = STATUS_INVALID_PARAMETER;
}
Irp->IoStatus.Status = ntStatus;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus;
}
int ret = 0;
switch ( cmd ) {
case IOCTL_READ_DATA:
if (copy_from_user(&data, (int *)arg, sizeof(int))) ret = -EFAULT;
break;
case IOCTL_WRITE_DATA:
if (copy_to_user(&data,(int *)arg, sizeof(int))) ret = -EFAULT;
Table 54: The Comparison of Ioctl Function