• 沒有找到結果。

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

相關文件