國立交通大學

Save this PDF as:
 WORD  PNG  TXT  JPG

Size: px
Start display at page:

Download "國立交通大學"

Transcription

1 國立交通大學 電機學院與資訊學院資訊學程 碩士論文 移植 Windows 裝置驅動程式至 Linux 之可行性研究 Feasibility Study for Porting Windows Device Drivers to Linux 研究生 : 謝清茹 指導教授 : 張瑞川教授 中華民國九十五年六月

2 移植 Windows 裝置驅動程式至 Linux 之可行性研究 Feasibility Study for Porting Windows Device Drivers to Linux 研究生 : 謝清茹 指導教授 : 張瑞川 Student:Ching-Ju Hsieh Advisor:Ruei-Chuan Chang 國立交通大學電機學院與資訊學院專班資訊學程碩士論文 A Thesis Submitted to Degree Program of Electrical Engineering and Computer Science College of Computer Science National Chiao Tung University in Partial Fulfillment of the Requirements for the Degree of Master of Science in Computer Science June 2006 Hsinchu, Taiwan, Republic of China 中華民國九十五年六月

3 移植 Windows 裝置驅動程式至 Linux 之可行性研究 學生 : 謝清茹 指導教授 : 張瑞川 國立交通大學電機學院與資訊學院資訊學程(研究所)碩士班 摘要目前市面上最受歡迎的作業系統有二 : 一為 Windows 作業系統, 另一則為 Linux 作業系統 雖然後者支援的應用程式眾多且為免費的作業系統, 但礙於 Linux 平台能支援的驅動程式較少, 使用者裝置無法在該平台得到和 Windows 平台相同的支援, 也因此大大的降低了一般使用者在 Linux 平台的普及率 此外, 近年來嵌入式裝置大量的被開發應用, 生活中常見的裝置包括數位相機 印表機 手機等各式各樣的產品不斷推出, 這些裝置都需要硬體廠商開發驅動程式才能與電腦的作業系統互通, 因此能快速開發穩定且支援多平台的驅動程式成為各家廠商相互競爭的關鍵一環 開發 Windows 驅動程式, 除了要了解裝置的硬體規格以及整個裝置的功能需求外也要了解整個 Windows 驅動程式模型, 這些就已經花掉一個程式設計師相當多的時間來學習, 如果再加上開發 Linux 平台的驅動程式可能要耗費更多的人力及時間, 也影響產品上市的時間 其實要開發同一裝置在兩個平台的驅動程式, 除了平台上驅動程式模型不同外, 其它的硬體規格及功能需求應是一樣的 因此本研究以 Windows 2000 及 Linux 2.6 作業系統為主, 來探討兩種平台移植的可行性, 並使用 Lex/Yacc 來建置一個可擴充的模型讓不同的裝置及作業系統可以在此建立對應及轉換, 以期能為兩個平台的驅動程式開發者節省開發時間 關鍵字 : 移植 Windows 2000 Linux 2.6 裝置驅動程式模式 驅動程式產生器 Lex/Yacc i

4 Feasibility Study for Porting Windows Device Drivers to Linux Student: Chin-Ju Hsieh Advisor: Ruei-Chuan Chang Degree Program of Electrical Engineering and Computer Science National Chiao Tung University ABSTRACT Currently, the most popular operating system has two: one is Windows operating system, the other is Linux operating system. Although the latter is free and supported a lot of applications. The shortage of Linux driver to obstructs the user to get equally support like Windows. It also slashes Linux popularity in general user. Since a lot of embedded devices has been developed and applied in our life. We can see a various of device includes: digital camera printer mobile phone be produced. These devices need the hardware provider develop device drivers to communicate with operating system. Therefore, it s a critical point for all providers who can rapidly develop stable and multiplatform supported device driver to compete with others. Besides, if you want to develop Windows device driver, you must highly understand the hardware of the device all function require of the device and Windows Driver Model. The above things will take programmer a lot of times to learn and not to mention develop Linux device driver. All the factors will also influence the time to market. Actually, to develop the driver between Windows and Linux, the hardware and functionality will be the same except the device driver model. Therefore, we discuses the feasibility of porting driver from Windows 2000 to Linux 2.6 and using Lex/Yacc to build an extendable mapping and transfer mechanism in different device and platform. Hope it can save the time for driver developer s to build drivers between two platforms. Keywords: Porting Windows 2000 Linux 2.6 Device Driver Model Driver Generator Lex/Yacc ii

5 誌 謝 感謝張瑞川教授讓我加入計算機系統實驗室, 近而有機會接觸作業系統核心知識 此外要感謝張大緯教授不辭辛勞的指導, 在選題 構思 寫作及完稿過程中提醒我許多考慮欠週的地方並提供可能的解決方法 在實驗室的期間要感謝學長學弟的幫忙, 尤其要感謝實驗室老大張明絜的協助 ; 感謝彭美僑及陳彥百學弟給我 Lex/Yacc 方面的指導 ; 感謝戴函昱學弟給我 Linux 2.6 驅動程式模型的指導 ; 感謝邱國政學弟給我口試前的指導 ; 感謝我的朋友謝博文 許伯鈞給予我許多論文撰寫的經驗及壓力調適的方法 研究所兩年, 還要感謝惠我良多的鐘崇斌教授要求我們在學習上保有嚴謹的態度 認真的精神 自我要求及獨立思考的能力 ; 感謝一路上一起努力學習的同學們 ; 最後僅將此成果獻給最關心我的母親 iii

6 摘 INDEX 要... i ABSTRACT... ii 誌 謝... iii INDEX... iv LIST OF FIGURES... vii LIST OF TABLES... vii 1 Introduction Motivation and Objective Background and Relateive Research Limitation of Research Architecture and Content of Research The Comparison of Device Driver Model The Architecture of Device Driver and HAL Introduction the Device Driver Model Enumerate Device Store Driver Relative Information Supported Hardware List and Access Type The Different Between two Platforms Driver The Comparison of Development Environment Check System Device Driver Compiler Require Files Building Environment Manual Install Located and Set Loading Sequence Debug Kernel Mode Driver The Classify of Driver Function The Skeleton of Program Function Classify the Map with Driver Function The Comparison of Driver Function Entry Function Point in Driver Hardware Reply Status iv

7 5.4 Data Type Create Device Object and Set Permission I/O Control Memory Management Interrupt Synchronization Double Linked List Timer Work Thread Module Description and Function Export IRP Relative Function Others Porting Basic Driver Use Parallel Device Analysis Each Function Block Introduce Parallel Port Device Map Program Section Map Include File Map Function Declaration Area Map Global Data Area Map Driver Internal Data Map Driver Entry Function Map Create Function Block Map Close Function Block Map Write Function Block Map Ioctl Function Block Test Using Lex/Yacc to Build Driver Generator Transfer Model Full Map Function Use Lex to Recognize String and Build Token Use Yacc to Build the Grammar Rule Relocate All Token v

8 8 Conclusion and Future Work References Appendix A. Windows Driver Skeleton Appendix B. Linux Driver Skeleton vi

9 LIST OF FIGURES Figure 1: Use HAL Routines to Access Hardware Figure 2: Use HAL Routines to Access Hareware in Windows Figure 3: Use HAL Routines to Access Hareware in Linux Figure 4: Physical Device Object vs Function Device Object in Windows Figure 5: Kset vs Kobject in Linux Figure 6: Windows Device Tree Figure 7: Linux Device Tree Figure 8: The Class Key between Windows and Linux Figure 9: Check All the Devices Driver in System Figure 10: Windows DDK has Checked and Free Build Figure 11: Loading Sequence in Windows Driver Figure 12: OS Crash in Both Platforms Figure 13: Store the Function Map in Database Figure 14: The Comparison of Program Section Figure 15: 25 pins Null Modem Figure 16: Parallel Port Introduction Figure 17: Convert all Full Map Function to Lex Definition LIST OF TABLES Table 1: Tool for Driver Generator Table 2: The Comparison of Driver Information in Two Platforms Table 3: The Example with Windows Hardware ID Table 4: The Example with Linux Hardware ID Table 5: Windows and Linux's Access Type Table 6: The Different between Windows and Linux Driver Table 7: Compiler Required Files Table 8: The Example with Makefile Table 9: Compile your Device Driver Table 10: Management and Located of Device Driver Table 11: Driver Loading Sequence Table 12: Debug Relative Function and Tool vii

10 Table 13: Construct a Program Function Table 14: The Category of Single Identifier Table 15: The Statistic of Function Map Table 16: Full Map with Data Copy Function Table 17: Partial Map with I/O Map Function Table 18: Partial Map with Memory Set Function Table 19: Linux Only Function Table 20: Windows Only Function Table 21: Driver Relative Mapping Data Table 22: The Entry Function between Windows and Linux Table 23: The Hardware Function between Windows and Linux Table 24: The Map of Port I/O Function Table 25: The Reply Status between Windows and Linux Table 26: The Data Type between Windows and Linux Table 27: Create Device Object and Set Driver Permission Table 28: Determine I/O Type between Windows and Linux Table 29: Create Device Function between Windows and Linux Table 30: The I/O Control between Windows and Linux Table 31: The Memory Management Function between Windows and Linux Table 32: The Interrupt Function between Windows and Linux Table 33: The Interrupt Function between Windows and Linux Table 34: The Synchronization Function between Windows and Linux Table 35: The Double Linked List Function between Windows and Linux Table 36: The Timer Function between Windows and Linux Table 37: The Work Thread Function between Windows and Linux Table 38: The Module Description is Linux Only Function Table 39: The EXPORT_SYMBOL is Linux Only Function Table 40: The IRP Relative Function between Windows and Linux Table 41: The Symbolic Link Function between Windows and Linux Table 42: The Break When False and Memory Barrier Function between Windows and Linux Table 43: Set Thread Priority between Windows and Linux viii

11 Table 44: Parallel Port Pins Table 45: Linux Mapped Header Files Table 46: Linux Mapped Header Files Table 47: The Comparison of Program Section Table 48: The Comparison of I/O Control and Device Name Table 49: The Comparison of Driver Internal Data Table 50: The Comparison of Entry Function Table 51: The Comparison of Create Function Table 52: The Comparison of Close Function Table 53: The Comparison of Write Function Table 54: The Comparison of Ioctl Function Table 55: Use W2L.l to Build Token and Compile by Lex Table 56: Use W2L.y to Relocate and Compile by Yacc Table 57: Generate the y.tab.h to Define Token ix

12 1 Introduction 1.1 Motivation and Objective 把 Windows 驅動程式移植 (Porting) 到 Linux 平台並不是為了反微軟或者特意支持自 由軟體, 而是因為工作經常在撰寫這兩個平台的驅動程式, 越接觸就越發覺兩者有不少 共同點, 也就越想了解這兩個平台的驅動程式 作業系統及裝置之間互動機制的異同, 最後便興起了互轉兩個平台驅動程式的念頭, 希望能幫助 Windows 平台的驅動程式設 計者能更快速的開發出 Linux 平台的驅動程式 最初題目訂為 : 不同平台驅動程式之互換, 打算著手兩邊驅動程式互換, 但後來發 現僅僅單一平台的驅動程式就已經非常的龐大, 如果要做兩邊互轉範圍實在太大, 短期 間非我一己之力能夠完成, 與教授討論後, 教授認為市面上驅動程式普遍都有撰寫支援 Windows 平台的驅動程式, 少有支援 Linux 的驅動程式, 多數的裝置無法在 Linux 平台 找到對應的驅動程式, 也因此造成 Linux 的普及率比較低, 因此建議我做移植 Windows 裝置驅動程式至 Linux 的移植, 希望能研究出一套模型將 Windows 驅動程式快速地移植 到 Linux 平台 1.2 Background and Relateive Research 各種類別的裝置在 Windows 及 Linux 均有其特有的裝置驅動程式模型 (Device Driver Model), 而市面上開發驅動程式產生器 (Driver Generator) 相關產品的公司有 Jungo 及 Compuware 這兩家公司, 但也只針對較常見的 USB PCI 及 Network 的裝置做單一 平台驅動程式產生器, 並沒有兩個平台驅動程式互轉的工具, 如 Table 1 所示 Provider OS Windows Linux VxWorks Jungo - WinDriver USB/PCI USB/PCI USB/PCI Compuware - SoftICE USB/PCI Table 1: Tool for Driver Generator 論文研究方面在 2004 年南非大學曾在 acm 提出這兩種平台的一般用途驅動程式 (General Purpose Driver) 做簡略的比較 [2], 但其它像記憶體管理 計時器 (Timer) 同步 (Synchronous) 中斷(Interrupt) 執行緒(Thread) 硬體及工作佇列等部分並沒有做任何的探討 而對岸的論文則有探討 Windows 或 Linux 單一平台下的驅動程式做程式自動產生器的研究, 國內則有長庚大學馮立琪教授及暨南大學姜美玲教授提出裝置驅動程式模型 (Device Driver Model) 相關論文之探討 在研究的期間也發現將 Windows 平台的應用程式移植 (Porting) 到 Linux 平台, 將 MFC 的 GUI 程式轉換成 gtk+ 的程式的工具 -wxwindows[1][3], 雖然轉換的程式類型不同, 但也提供一個可參考的轉換模型 -10-

13 1.3 Limitation of Research 由於兩個平台關於驅動程式相關函數眾多, 本研究參考 DDK 網站統計常用的 Windows 函數來對映 (Mapping) 到 Linux 平台 且兩個平台驅動程式大多使用 C 語言開 發, 因此本研究僅限於 C 語言開發的 Driver Code 做移植,C++ 則不予以討論 此外,Windows 及 Linux 均將 Driver 區分為 Kernel Mode Driver 及 User Mode Driver[17][18], 本研究僅就 Kernel Mode Driver 做探討 並以 Windows 2000[10][11][14][15][22] 移轉到 Linux 2.6[4][6][9][12][13][16] 做主要探討之平台, 在轉換 程式只限制處理已編譯正確的 Windows Driver Code 主要研究以兩個平台裝置驅動程 式模型 (Device Driver Model) 及一般用途的字元驅動程式 (General Purpose Character Driver) 作轉換, 並以 Parallel Port Driver[7] 為例來轉換及測試 ; 至於兩個平台編譯連結的 makefile 及 Windows INF 檔並不做轉換程式 1.4 Architecture and Content of Research 對照的方法將由小至大比對 : 從單一識別字 (Single Identifier) 的對應 到整句函式 原型 (Function Prototype) 的對應 再到整個函式區塊 (Function Block) 的對應 最後再到 整個程式區段 (Program Section) 的對應 轉換的方法則是使用 MS-SQL Server 為資料庫, 建立 DDK 常用函數的對應及分 類, 再用 Lex/Yacc 建立轉換所需的權杖及文法規則 (Token/Grammar Rule) 檔, 最後再讀 取 Windows Driver 的 Token 重新定位 (Relocate) 到 Linux Driver 的對應格式, 如此便可完 成整個函式區塊對應 (Function Block) 論文內容則分為九章, 主要敍述如下 : 一 緒論 : 簡要概述整個論文的研究目的 研究範圍及研究架構 二 裝置驅動程式模型 (Device Driver Model) 比較 三 開發及編譯環境之比較 四 兩個平台上裝置驅動程式架構的比較 五 兩個平台上裝置驅動程式函數比較 : 包括記憶體管理 計時器 同步 中斷 硬體 資料型式及工作佇列之比較 六 驅動程式的轉換 - 以 Parallel Device 為例 七 以 Lex 及 Yacc 來建立驅動程式之程式產生器 八 結論及未來方向 九 參考文獻 -11-

14 2 The Comparison of Device Driver Model 2.1 The Architecture of Device Driver and HAL Windows 實作了一個 hal.dll(/winnt/systen32/hal.dll) 來提供與硬體無關的介面給上 層, 讓上層透過硬體抽象層 (HAL,Hardware Abstract Layer) 與硬體溝通, 而無須考慮硬 體的差異, 與硬體平台有關的部分均透過 hal.dll 來負責處理 Linux 的硬體抽象層在其核心原始程式碼 (/lib/modules/version/kernel/) 的 /arch 目錄 [9], 此目錄包含所有支援的處理器, 如下面指令所示 : /hal/linux/arch]# ls alpha/ cris/ ia64/ mips/ parisc/ s390/ sh/ sparc64/ arm/ i386/ m68k/ mips64/ ppc/ s390x/ sparc/ 透過這些與硬體相關的基本函式配合上層運作的其它模組, 便可以在最小修改的情 況下完成與硬體抽離的架構 如 Figure 1 所示 [5]: Windows Linux Figure 1: Use HAL Routines to Access Hardware 2.2 Introduction the Device Driver Model Windows 將 Driver 分成三類, 分別為 Bus Driver Filter Driver 及 Function Driver, 而 Driver 所產生的 Device Object 分別有 Physical Device Object(PDO) Function Device Object(FDO) 及 Filter Device Object(FiDO) Windows 其 Bus Driver 與 Function Driver 的關係 為 Parent/Child Relationship, 當開始放入硬體時, 扮演 Parant 的 Bus Driver 用來列舉 -12-

15 (Enumerate) 出匯流排上裝置的 Hardware IDs 並產生 PDO, 隨後系統載入對應的 Child Function Driver 來管理該 Device, 而 Filter Driver 則有過濾 觀察及修改封包的功能, 可被放置在 Function Driver 的上下層 Microsoft 在 Windows 2000/98/ME 之後版本的 Driver 開始遵從 Windows Driver Model(WDM),WDM 的 Driver 支援隨插即用 (Plug and Play) 電源管理(Power Management) 及 Windows Management Instrumentation (WMI) 由 OS 的隨插即用管理員 (Pnp Manager) I/O 管理員及電源管理員 (Power Manager) 來控管 Device 的生命週期, 此外 Windows Driver 使用 IRP(I/O Request Packet) 來傳遞資訊 1, 即使負責的 Driver 不必使用到 IRP 的資料, 也要將 IRPs 傳遞給其它層的 Driver, 如 Figure 2[2] 所示 Figure 2: Use HAL Routines to Access Hareware in Windows Linux 在 Kernel 2.6 版也加入隨插即用 (Plug and Play) 電源管理(Power Management) 及熱插拔 (hotplug) 的機制 Linux 為了實作電源管理, 因此在 2.6 版加入了 KSet 及 Kobject 來定義 Bus Driver 與 Child Driver 之間上下層的關係, 以便讓各個 Device 在電源關閉時有正確先後的順序, 如 Figure 3[2] 所示 1 除了一些特殊的 Driver 不處理 IRP, 比如 NDIS miniport driver 是呼叫 NDIS 所的函數來註冊 -13-

16 Figure 3: Use HAL Routines to Access Hareware in Linux 2.3 Enumerate Device Windows 的 Bus Driver 會產生出一個個的 Device Object 並串接起來, 如 Figure 4 所 示 首先 Bus Driver 產生本身的 FDO, 接下來再產生 Child PDO, 利用每個新產生的 Device Object 其 NextDevice 欄位來串接各個 Child Device Object, 所有的 Device Object 均指向同 一個 Driver Object, 並用 ReferenceCount 來計數這個物件被參照的次數 [22] typedef struct _DEVICE_OBJECT {. CSHORT Type; USHORT Size; LONG ReferenceCount; struct _DRIVER_OBJECT *DriverObject; struct _DEVICE_OBJECT *NextDevice; union { Queue; LIST_ENTRY ListEntry; WAIT_CONTEXT_BLOCK Wcb; Figure 4: Physical Device Object vs Function Device Object in Windows -14-

17 在 Linux 也有雷同的機制, 每個 kobject 均指向同一個 kset, 每個 kobject 再彼此串接成 kset child list, 並同時有 ReferenceCount 來得知物件被引用的次數, 如 Figure 5[4] struct kobject { char * k_name; char name[kobj_name_len]; struct kref kref; struct list_head entry; struct kset * kset; struct kobject * parent; struct kobj_type * ktype; struct dentry * dentry; ; Figure 5: Kset vs Kobject in Linux 接下來再用 Bus Driver 列舉 (Enumerate) 下去就會形成一個 Device Tree, 如 Figure 6 及 Figure 7 分別為 Windows Device Tree 及 Linux Device Tree Windows 藉由 PnP Manager 來維持整個 Device Tree 的架構, 而 Linux 則使用 probe/remove/match/hotplug 等函數來維持其 Device Tree Figure 6: Windows Device Tree -15-

18 Figure 7: Linux Device Tree 2.4 Store Driver Relative Information Windows 平台將 Device Driver 與 Bus 的關聯及一些設定的資訊儲存在 Registry( 可以 在 Start Run, 輸入 regedit 來查看內容 ), 而 Linux 在核心 2.6 版也開始有相對應的資訊, 這 些資訊被放在 sysfs 2 裏,sysfs 是一個存在記憶體的 Virtual File System, 可用來建構完整的 Device Tree, 在命令列使用 tree command 可查看其內容 (#tree /sys) 由 Table 2 可看到 Windows 的 Registry 與 Linux 的 sysfs 所對應的 Hardware Key Service Key 及 Class Key Key OS Windows Registry Linux sysfs Hardware Key (represents all of the devices) Service Key (represents the device Drivers) Class Key (represent the class) HKLM\System\CurrentControlSet\Enum HKLM\System\CurrentControlSet\Service HKLM\System\CurrentControlSet\Control\Class /sys/devices /sys/bus/drivers /sys/class Table 2: The Comparison of Driver Information in Two Platforms Figure 8 可看兩種 OS 均有 Class Key, 用來存放 Device 抽象化後的類別, 如網路卡可 以是 PCI USB PCMCIA 的介面, 但都屬於 Network Class 2 Linux 2.6 建議程式設計師將控管 Driver 的資訊由以往的 /proc 目錄移至 /sys 目錄 -16-

19 Windows Linux # tree /sys/class -- bluetooth -- firmware `-- timeout -- graphics -- i2c-adapter -- i2c-dev -- input -- event0 `-- dev -- event1 `-- dev -- mice `-- dev `-- mouse0 `-- dev -- net -- eth0 -- addr_len -- address -- broadcast -- device ->../../../devices/pci0000:00/0000:00:1e.0/0000:02: driver ->../../../bus/pci/drivers/r8169 Figure 8: The Class Key between Windows and Linux 2.5 Supported Hardware List and Access Type Device 通常會有一塊 EPROM 來儲存 Hardware ID, 當系統偵測到硬體時, 該類別的 Bus Driver 會與該類別的 Driver 所設的 ID 值比對後, 進而載入對應的 Driver Windows Driver 將這些資訊放在 *.inf 檔, 如 Table 3, 而 Linux 則是放在各個 *_ids.h 的檔案裏, 像是 pci_ids.h, 如 Table 4 [Manufacturer] %MfgName%=Avance [Avance] %ALCAUD.Desc%=AC97AUD,PCI\VEN_8086&DEV_24C5&SUBSYS_000817C0 %ALCAUD.Desc%=AC97AUD,PCI\VEN_8086&DEV_24C5&SUBSYS_0241A0A0 %ALCAUD.Desc%=AC97AUD,PCI\VEN_8086&DEV_24C5&SUBSYS_0242A0A0 %ALCAUD.Desc%=AC97AUD,PCI\VEN_1039&DEV_7012&SUBSYS_0247A0A0 %ALCAUD.Desc%=AC97AUD,PCI\VEN_1039&DEV_7012&SUBSYS_0248A0A0 Table 3: The Example with Windows Hardware ID -17-

20 static struct pci_device_id i810_ids[] devinitdata = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1), { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3), { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG), { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC), { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG), { 0,, ; Table 4: The Example with Linux Hardware ID Windows Driver 存取資料的方式有兩種, 分別為 Buffer I/O 與 Direct I/O;Buffer I/O 為預設值, 用在資料量小 傳輸速度較慢的 Device, 如滑鼠及鍵盤 ; 而 Direct I/O 則用在 資料量大 需要快速傳輸的 Device, 像 PIO 或 DMA 的傳輸方式, 常見的 Device 有 SCSI Disk 及 VGA Card 在 Linux 也依存取方式將 Driver 分成兩種, 一為處理少量資料的 Character Device Driver, 另一種則是以 Block 的單位來存取資料的 Block Device Driver, 如 Table 5[22] Windows Linux Characteristic Device Buffer I/O Character Device Slow/small size,but it s Mouse Keyboard Driver default and easy to use/nonpage pool Direct I/O Block Device Driver Fast/large size,but hard to use. (PIO or DMA) SCSI Disk VGA Card Table 5: Windows and Linux's Access Type 2.6 The Different Between two Platforms Driver 兩個平台其 Device Driver 各有其特有做法及部分的差異, 如 Table 6, 本節就其差異 做討論 Windows Only IRP Filter driver WMI Unicode IRQL Function can be pagable/non-pagable Symbolic link to access by application Similar Issue Linux Only GPL/non GPL likely function to optimistic compiler Use set bit & test bit Symbolic link to link with sysfs -18-

21 Major/Minor function Pass Parameter Using INF File Put a lot information in INF file Major/Minor device Pass Parameter Using insmod Command Put in driver code Table 6: The Different between Windows and Linux Driver Windows Only, 也就是 Windows 存在但 Linux 不存在的部分 : - Windows 把某些資訊集合起來, 放在一個特定的 IRP(I/O request packet)structure 裏, 因此各個 Entry Function 都使用 IRP 變數來傳遞,Linux 沒有這樣統一的封包格式, 因此每個 Entry Function 所傳入的變數都不一樣 - 因 Windows Driver 不像 Linux 為開放原始碼, 因此無法看到 Source Code, 一般使用者無法自行修改 / 重新編譯 Driver, 所以微軟創造了 Upper 及 Lower Filter Driver, 能有彈性的掛在 Function Driver 上下層去觀察及修改 Packet 的資料, 而 Linux 就可以直接改 Driver Code, 因此少有這層動作 - Windows 有 Windows Management Instrumentation(WMI) 的設定的機制, 可向系統管理員報告管理訊息及允許用戶設定控制設備的操作,Linux 則沒有 - Windows 所有字串都要轉成 Unicode 的資料型別才能與使用者應用程式溝通, Linux 則沒有這樣的資料型別要轉換 - Windows Driver 的各個函數均有指定其 IRQ 層級 (Interrupt Request Level), 限制各個函數要在某個層級才能執行, 以整增加整個 Driver 的執行速度,Linux 則沒有 - Windows 有限制某些 Function 一定要使用不可分頁的分頁區 (Nonpagable Page), 強制該行程在整個 Driver 執行期間不可被置換空間 (Swap Out) 到記憶體外, 在 Linux 之下沒有規定, 如下所示, 函數 IsoUsb_DispatchCreate 被指定要放在可分頁區 : #pragma alloc_text(page, IsoUsb_DispatchCreate) Linux Only, 也就是 Linux 存在但 Windows 不存在的部分 : - Linux 有 unlikely 函數來加強編譯時最佳化的處理,Windows 則沒有 - 因 Linux 為 Open Souce 的平台, 因此其 Linux Module 有區分 GPL 及 non GPL, 如果您將 Driver 設為 non-gpl 則會因此無法使用 GPL only 的 Symbols - Linux 有 set bit 及 test_bit 的位元操作 (Bit Operations),Windows 則沒有 Similar Issue, 也就是兩個平台相近的部分 : - Windows 及 Linux 都有提到 Symbolic Link, 但兩邊意義不同 Windows 的 Symbolic Link 可以開啟一個讓 Application 存取的介面, 而 Linux 則是用來建立 sysfs 內 Device 與 Driver 的關聯 - Windows 及 Linux 都有提到 major 及 minor number 但兩邊意義不同 Window 的 major 及 minor 是指 Driver Entry Function;Linux 則是 12 bits 的 major 加上 20 bits 的 minor 組成一個 32 bits 的 Unique ID, 這部分類似 Windows 的 16 bytes GUID, 用來辨識一個唯一 (Unique) 的 Device -19-

22 - Linux 在插入模組 (insmod) 時, 可帶入參數, 在 Windows 則是在 INF 檔帶入參數 - Windows 有許多 Driver 資訊被放在 INF 檔, 而 Linux 則是放在程式內, 如 Class Hardware ID Input Parameter Device Description Device Display Name, 因此這部分也需撰寫轉換到 Linux 對應的程式 -20-

23 3 The Comparison of Development Environment 在開始比對兩邊 Device Driver 的程式碼前, 想先就整個作業系統在驅動程式的安裝 及編譯及開發環境做粗略的對應及比較, 進而了解 Device Driver 在兩個平台開發環境的 異同及 Driver 運作機制 3.1 Check System Device Driver 在 Windows 要查看所有已載入的 Device Driver 要開啟 Device Manager (Start Run, 輸入 devmgmt.msc), 在 Linux 則使用 lsmod 指令來查看核心目前載入那些 Driver Modules, 如 Figure 9 所示 Windows s Device Manager Linux s lsmod command Figure 9: Check All the Devices Driver in System 3.2 Compiler Require Files 一般來說, 要能在 Windows 平台成功編譯一個基本的驅動程式需要四類檔 : 第一個是 makefile 檔 第二個是 Driver Source Code 第三個是 sources 文件 第四個檔是 RC 檔 -21-

24 RC 檔及 sources 檔是根據需求修改 Linux 平台只需 makefile 及 Driver Code 即可, 如 Table 7 所示 Windows makefile *.c *.h RC sources Linux makefile *.c *.h Table 7: Compiler Required Files 兩邊的編譯檔都有使用 Makefile,Makefile 是用來指定要編譯的檔及要連結的函式 庫 (Library) 不過 Windows 不用修改 Makefile, 而是用 sources 檔來取代 Makefile 的功能 Table 1 是兩邊編譯程式的範例, 作法十分類似, 但本論文將不會探討兩個平台 Makefile 的轉換 source in Windows TARGETNAME = TestDriver TARGETPATH = obj TARGETTYPE = DRIVER MSC_WARNING_LEVEL =/W3 C_DEFINES = $(C_DEFINES) /DMAXDEBUG /DDEBUG /DDBG=1 SOURCES = TestDriver.c PRECOMPILED_ INCLUDE=precomp. h PRECOMPILED_PCH =precomp.pch PRECOMPILED_OBJ=precomp.obj makefile in Linux obj-m := TestDriver.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) EXTRA_ CFLAGS := -I/usr/test/include -I/usr/include/ default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules Table 8: The Example with Makefile 3.3 Building Environment 在 Windows 平台編譯 Driver 必須安裝 Windows DDK(Device Driver Kit), 用其編譯器 來編譯 Device Driver, 而 Linux 則是使用一般的 gcc(gnu Compiler Collection) 來編譯程 式 其編譯指令如 Table 9 Windows use DDK Building tools C:\WINDDK\3790\src\network\ndis\netvm ini>build -cz Table 9: Compile your Device Driver Linux use gcc #gcc -D KERNEL -DMODULE -I/usr/src/linux /include -c -O -Wall demodriver.c -o demodriver.ko #./makefile 如 Figure 10,Windows Device Driver 的編譯與連結是由 DDK Builder 來控制, 其編譯器有 Checked 與 Free 兩種版本 [22] Checked Build 建立含有除錯資訊的驅動程式, 您可 -22-

25 在開發時期使用 編譯程式最佳化在此狀況下是停用的狀態且程式中將會加入用於 Debugging 的條件程式碼 Free Build 則是用於要發行的驅動程式, 此時程式碼已經過最佳化且 Debug 程式碼已被停用 在此環境下編譯完成的驅動程式您可用於效能測試或寄送給客戶, 對應到 Linux 相當於在 gcc 編譯時使用參數 -O 來做最佳化, 如 Table 9 右方 Linux 的部分所示 Figure 10: Windows DDK has Checked and Free Build 3.4 Manual Install Located and Set Loading Sequence 在 Windows 平台手動安裝 Driver 須使用 Device Manager 來幫忙安裝, 而在 Linux 則是 用 insmod 及 remod 指令安裝及移除 ;Driver 放置的位置及副檔名分別整理在 Table 10 Windows Linux OS Odds The way to manual Create an INF file and use # insmod./fdo.ko install Device Manager to install The way to manual Remove the device driver by # rmmod./fdo.ko remove Device Manager Driver configuration %win%/inf /etc/modules.conf(#modprobe) file Driver location %win%/system32/drivers /lib/modules /proc/modules /sys/modules Driver file extension *.sys *.ko Table 10: Management and Located of Device Driver 若要指定 Device Driver 開機後的啟動順序, 分別可在 Windows 的 Registry 及 Linux Kernel 的 drivers 目錄下, 修改 makefile, 如 Table 11 及 Figure 11 Windows Set the LoadOrderGroup in INF File to set the boot sequence in HKLM\CurrentControlSet\Control\ServiceGroupOrderKey Linux usr\src\linux-2.6.9\drivers\makefile Table 11: Driver Loading Sequence -23-

26 Figure 11: Loading Sequence in Windows Driver 3.5 Debug Kernel Mode Driver 兩邊的平台也各提供了一些類似的方法讓我們在 Kernel Mode 有和善的除錯工具, Table 12 為兩個平台相關的函數及工具 Windows OS Odds Linux Print message function KdPrint(); printk(); False then break expression ASSERT(); BUG_ON(); WARN_ON(); Show the message DbgViewer Tool #dmesg Kernel mode WinDbg/KD/SoftICE/Dump message can appear at console and /var/log/messages gdb/oops debugging tools Kernel Memory Document DDK usr/src/linux/documentation/ Table 12: Debug Relative Function and Tool 在撰寫驅動程式的過程中有時會導致整個系統當機, 在 Windows 平台會顯示藍色死 -24-

27 亡畫面 ("Blue Screen of Death" - BSOD), 並秀出錯誤的位址及錯誤代碼 (Bug Code) 3, 而 Linux 也有類似的訊息來告知使用者系統已當機, 如 Figure 12 Windows example blue screen Linux will show panic or halt Figure 12: OS Crash in Both Platforms 3 相關資料請查看 DDK 的 inc\w2k\bugcodes.h -25-

28 從上面的種種對應及比較可以看出兩個平台在編輯及處理程式提供了許多功能相 近的工具, 也因此我們可用這些工具觀察出兩個平台驅動程式與作業系統 硬體互動的 更多對應之處 -26-

29 4 The Classify of Driver Function 4.1 The Skeleton of Program Function 要分析及轉換一個程式前, 我們必須先了解一個程式的結構, 以下面一個 C 語言所 寫的範例為例 : void main() { printf( = ", add(5,5)); int add(x,y) { return sum(5,5); 程式由小到大的分別由三個步驟完成, 如 Table 13 紅色箭頭所示 : Step 1: 單一識別子 ( Single Identifier) 組成一行函數原型 (Function Prototype) Step 2: 每個單行的函數原型再組成一個函數區塊 (Function Block) Step 3: 所有函數區塊的集合就形成了最後的程式區段 (Program Section) Element Single Identifier Example void main int printf sum return printf( = ", add(5,5)); Function Prototype sum(5,5); Function Block Program Section int add(x,y) { return sum(5,5); void main() { printf( = ", add(5,5)); int add(x,y) { return sum(5,5); Table 13: Construct a Program Function -27-

30 把要比對的識別子 (Identifier) 分成五種型態, 存在資料庫並給予一個代號 (flag) 來區 別, 如 Table 14 所示, 藍色字體為 Windows Identifier 紅色字體為 Linux Identifier, 分別 介紹如下 : No Flag Type Example 1 F Function ExFreePool kfree KeSetPriorityThread set_user_nice 2 S Status STATUS_UNSUCCESSFUL -EFAULT STATUS_DEVICE_BUSY -EBUSY 3 P Function Parameter PagedPool GFP_KERNEL NonPagedPool GFP_ATOMIC 4 T Data Type PUINT unsigned int * KSPIN_LOCK spinlock_t LIST_ENTRY list_head 5 D Driver Only DriverEntry file_operations DEVICE_EXTENSION private_data Table 14: The Category of Single Identifier 第一種 F 為函數, 釋放記憶體的函數原型在 Windows 和 Linux 分別為 : VOID ExFreePool(IN PVOID P); void kfree (void *ptr); 以及調整執行緒優先權的函數原型在 Windows 和 Linux 分別為 : KPRIORITY KeSetPriorityThread(IN PKTHREAD Thread, IN KPRIORITY Priority); void set_user_nice ( task_t *p, long nice); 以及插入項目到串列尾部的函數原型在 Windows 和 Linux 分別為 : VOID InsertTailList(IN PLIST_ENTRY ListHead,IN PLIST_ENTRY Entry); void list_add_tail(struct list_head *new, struct list_head *head); 第二種 S 為回傳的狀態值, 失敗的回傳值在 Windows 和 Linux 分別為 : STATUS_UNSUCCESSFUL -EFAULT 以及裝置忙碌的回傳值在 Windows 和 Linux 分別為 : STATUS_DEVICE_BUSY -EBUSY -28-

31 第三種 P 為函數的參數值, 可移出分頁的參數在 Windows 和 Linux 分別為 : PagedPool GFP_KERNEL 以及不可移出分頁的參數在 Windows 和 Linux 分別為 : NonPagedPool GFP_ATOMIC 第四種 T 為資料型態, 指向無正負符號的整數型態變數, 在 Windows 和 Linux 分別為 : PUINT unsigned int * 以及自旋鎖的資料型別在 Windows 和 Linux 分別為 : KSPIN_LOCK spinlock_t 以及雙向鏈結串列型態在 Windows 和 Linux 分別為 : LIST_ENTRY list_head 第五種 D 為驅動程式特有的識別子, 驅動程式指定 Entry Function 在 Windows 和 Linux 分別為 : DriverEntry file_operations 以及驅動程式內部資料在 Windows 和 Linux 分別為 : DEVICE_EXTENSION private_data 4.2 Classify the Map with Driver Function 收集常用的 Function 比對後, 可以完全對應 (Full Map) 的 Identifier 有 77 個, 部分對應 (Partial Map) 的有 33 個, 只有 Windows 才有的 Function 有 73 個, 只有 Linux 才有的 Function 有 28 個, 如 Table 15 Symbol Map 0 Full Map 77 1 Partial Map 33 2 Only for Windows, will mark it 73 3 Only for Linux, no influence or added Count

32 Table 15: The Statistic of Function Map Total 211 兩邊的程式都是使用 C 語言來撰寫, 所以平時用到的函數, 如 sizeof() sum() 函數 在兩個平台的寫法是一樣的, 因此不須修改及轉換 Full Map: 是指函數功能相同及參數相同者 以資料複製的函數為例, 如 Table 16, 在 Windows 的函數名為 RtlCopyMemory, 在 Linux 函數名為 copy_from_user, 均有指定來源 目的及長度參數, 則可直接將函數名做置換即可 Function Prototype Token without parameter Windows VOID RtlCopyMemory( IN VOID UNALIGNED *Destination, IN CONST VOID UNALIGNED *Source, IN SIZE_T Length ); RtlCopyMemory Linux Unsigned long copy_from_user( void *to, const void *from, unsigned long n ); copy_from_user Table 16: Full Map with Data Copy Function Partial Map: 函數功能完全相同但參數部分相同, 以 I/O 位址對映到記憶體的函數為例, 如 Table 17, 在 Windows 的函數名為 MmMapIoSpace, 在 Linux 函數名為 ioremap, 均有指定記憶體位置及大小的參數, 但 Windows 平台多了一個指定 CacheType 的參數 Function Prototype Token with parameter Windows PVOID MmMapIoSpace(IN PHYSICAL_ADDRESS PhysicalAddress, IN ULONG NumberOfBytes, IN MEMORY_CACHING_TYPE CacheType ); MmMapIoSpace Linux void *ioremap( unsigned long phys_addr, unsigned long size ); ioremap Table 17: Partial Map with I/O Map Function 另一個類似的例子是將某塊記憶體清空為零的函數, 如 Table 18, 在 Windows 的函 數名為 RtlZeroMemory 對應到 Linux 的函數名為 memset, 均有指定記憶體位置及長度的參 數, 但 Linux 平台的函數較有彈性, 在第二個參數可設定將記憶體清成偏好的值 此部 -30-

33 分要轉換必須將各個 Identifier 轉成 Lex/Yacc 可辨識的 Token, 再重新排放 Function Prototype Windows VOID RtlZeroMemory( IN VOID UNALIGNED *Destination, IN SIZE_T Length ); Linux void *memset( void *s, int c, // can set to prefer value size_t n ); Table 18: Partial Map with Memory Set Function 特殊情況一 :Windows 沒有但 Linux 才有的函數, 像 module_init 及 module_exit 這兩個函數是 Linux Driver 特有的函數, 如 Table 19 此部分轉換後由 Driver Generator 另行加入程式碼即可 Map Type Include Windows Linux 2 F linux/init.h Windows not Exist but Linux Exist module_init(xxx_init); 2 F linux/init.h Windows not Exist but Linux Exist module_exit(xxx_exit); Table 19: Linux Only Function 特殊情況二 :Windows 有但 Linux 沒有的函數, 像 IoCreateSymbolicLink 及 IoDeleteSymbolicLink 這兩個函數是 Windows Driver 特有的函數, 如 Table 20 此部分轉換到 Linux 後必須將 Windows 程式註解掉 Map Type Include Windows Linux 3 F IoCreateSymbolicLink Windows Exist but Linux not Exist 3 F IoDeleteSymbolicLink Windows Exist but Linux not Exist Table 20: Windows Only Function 有了上面的分類基礎及處理方式才能著手建立轉換模式, 在後面的章節我們會使用 Lex/Yacc[19][20] 來做轉換, 並將這些函數的對應建立在資料庫裏以便做前置處理 -31-

34 5 The Comparison of Driver Function 有了函數分類的方法, 便可著手收集 Device Driver 的相關函數, 分類並記錄對應函數的各種屬性 (Map Type), 以便轉換時能有足夠的訊息判斷及處理 在本章我們把 Windows Driver 一般用途 (General Purpose) 的函數分成十五類並找出 Linux 對應的函數來探討 最後的結果會存放在資料庫, 並以欄位記錄對應函數的類別 是否完全對應 Linux 函數的標頭檔以及對應的函數名稱, 如 Figure 13 Figure 13: Store the Function Map in Database 5.1 Entry Function Point in Driver Windows 將 Driver 所需的 Entry Function 利用 DriverObject 的變數來傳遞而 Linux 則 是用 file 物件, 如 Table 21 所示,Windows 的 Entry Function 被註冊在 DriverObject->MajorFunction, 而 Linux 則是註冊在 file->file_operations 此外 Windows 將 Driver Internal Data 記錄在 DeviceObject->DeviceExtension 而 Linux 則是記錄在 file->private_data Windows Linux Entry Function DriverObject->MajorFunction file->file_operations Driver Internal Data DeviceObject->DeviceExtension; file->private_data; Parameter will passed in Entry Function DeviceObject IRP file inode, etc. Table 21: Driver Relative Mapping Data -32-

35 Windows 及 Linux 在 Kernel Mode Driver 都定義一組的基本的函數, 當 Driver 初始化時 就會註冊並呼叫這些函數, 如 Figure 22, 一般來說整個 Driver 在載入及卸載的過程中會 與 OS 有 Load Unload Read Write I/O 電源 熱插拔的問題要處理 Windows Linux Entry DriverEntry() file_operations Function Init DriverObject->DriverExtension->AddDevice XXX_init module_init(xxx_init); Unload DriverObject->DriverUnload XXX_exit module_init(xxx_exit); Function IRP_MJ_CREATE open pointer IRP_MJ_CLOSE release IRP_MJ_READ read 4 IRP_MJ_WRITE write IRP_MJ_DEVICE_CONTROL ioctl IRP_MJ_FLUSH_BUFFERS flush IRP_MJ_SHUTDONW module_exit(xxx_exit); IRP_MJ_PNP Minor Functions: IRP_MN_START_DEVICE IRP_MN_QUERY_STOP_DEVICE IRP_MN_STOP_DEVICE IRP_MN_CANCEL_STOP_DEVICE IRP_MN_QUERY_REMOVE_DEVICE IRP_MN_REMOVE_DEVICE IRP_MN_CANCEL_REMOVE_DEVICE IRP_MN_SURPRISE_REMOVAL IRP_MN_QUERY_CAPABILITIES IRP_MN_QUERY_PNP_DEVICE_STATE IRP_MN_DEVICE_USAGE_NOTIFICATION Bus Driver:.match.hotplug Child Driver:.probe.remove 4 因 Windows Driver 的 I/O 為非同步 I/O(asynchronous I/O) 所以在 linux 的 file_operations 應選用 aio_write/aio_read 來對應, 並在標頭檔加入 <linux/aio.h> -33-

36 IRP_MJ_POWER Minor Functions: IRP_MN_SET_POWER IRP_MN_QUERY_POWER IRP_MN_WAIT_WAKE IRP_MN_POWER_SEQUENCE IRP_MJ_INTERNAL_DEVICE_CONTROL IRP_MJ_CLEANUP IRP_MJ_QUERY_INFORMATION IRP_MJ_SET_INFORMATION IRP_MJ_SYSTEM_CONTROL Windows not Exist but Linux Exist Windows not Exist but Linux Exist Windows not Exist but Linux Exist Windows not Exist but Linux Exist Child Driver:.shutdown //power off.suspend //low power.resume //full power in <linux/device.h>. enum { SUSPEND_NOTIFY, SUSPEND_SAVE_STATE, SUSPEND_DISABLE, SUSPEND_POWER_DOWN, ; enum { RESUME_POWER_ON, RESUME_RESTORE_STATE, RESUME_ENABLE, ; Windows Exist but Linux not Exist Windows Exist but Linux not Exist Windows Exist but Linux not Exist Windows Exist but Linux not Exist Windows Exist but Linux not Exist mmap check_flags llseek send_file Table 22: The Entry Function between Windows and Linux 5.2 Hardware Windows 與 Linux 硬體操作的函數, 如 Table 23 所示, 大部分的函數參數相同且完全 對應 (Map=0) Map Type Include Windows Linux 0 F asm/io.h WRITE_PORT_BUFFER_UCHAR outsb 0 F asm/io.h WRITE_PORT_BUFFER_ULONG outsl 0 F asm/io.h WRITE_PORT_BUFFER_USHORT outsw 1 F asm/io.h WRITE_PORT_UCHAR outb 1 F asm/io.h WRITE_PORT_ULONG outl 1 F asm/io.h WRITE_PORT_USHORT outw -34-

37 0 F asm/io.h WRITE_REGISTER_BUFFER_UCHAR iowrite8_rep 0 F asm/io.h WRITE_REGISTER_BUFFER_ULONG iowrite32_rep 0 F asm/io.h WRITE_REGISTER_BUFFER_USHORT iowrite16_rep 0 F asm/io.h WRITE_REGISTER_UCHAR iowrite8 0 F asm/io.h WRITE_REGISTER_ULONG iowrite32 0 F asm/io.h WRITE_REGISTER_USHORT iowrite16 0 F asm/io.h READ_PORT_BUFFER_UCHAR insb 0 F asm/io.h READ_PORT_BUFFER_ULONG insl 0 F asm/io.h READ_PORT_BUFFER_USHORT insw 0 F asm/io.h READ_PORT_UCHAR inb 0 F asm/io.h READ_PORT_ULONG inl 0 F asm/io.h READ_PORT_USHORT inw 0 F asm/io.h READ_REGISTER_BUFFER_UCHAR ioread8_rep 0 F asm/io.h READ_REGISTER_BUFFER_ULONG ioread32_rep 0 F asm/io.h READ_REGISTER_BUFFER_USHORT ioread16_rep 0 F asm/io.h READ_REGISTER_UCHAR ioread8 0 F asm/io.h READ_REGISTER_ULONG ioread32 0 F asm/io.h READ_REGISTER_USHORT ioread16 Table 23: The Hardware Function between Windows and Linux 以 Table 24 所對應的函數為例,WRITE_PORT_BUFFER_UCHAR outsb 傳入的參 數都是一樣 Function Prototype Token without parameter Windows VOID WRITE_PORT_BUFFER_UCHAR( IN PUCHAR Port, IN PUCHAR Buffer, IN ULONG Count); WRITE_PORT_BUFFER_UCHAR Linux void outsb( int port, void *addr, unsigned long count); outsb Table 24: The Map of Port I/O Function 5.3 Reply Status Windows 與 Linux 的回傳值狀態的對應, 如 Table 25 所示 Windows 的狀態值被定義 在 DDK 的 ntstatus.h, 此檔定義了 500 多個值 ; 而 Linux 則被定義在 errno-base.h, 只有定義 34 個值 因此無法對應的部分會先行轉成 -EFALUT 值, 再由程式設計師自行判斷調整 Map Type Include Windows Linux -35-

38 0 S linux/errno-base.h STATUS_INSUFFICIENT_RESOURCES -ENOMEM 0 S linux/errno-base.h STATUS_INVALID_PARAMETER -EINVAL 0 S linux/errno-base.h STATUS_UNSUCCESSFUL -EFAULT 0 S linux/errno-base.h STATUS_DEVICE_BUSY -EBUSY 0 S linux/errno-base.h STATUS_UNEXPECTED_IO_ERROR -EIO 0 S STATUS_SUCCESS 0 Table 25: The Reply Status between Windows and Linux 5.4 Data Type Windows 與 Linux 一般資料型態的函數, 兩邊函數原型 (Function Prototype) 幾乎完全 對應 (Map=0), 如 Table 26 所示 Map Type Include Windows Linux 0 T linux/types.h PUINT u8 * 0 T linux/types.h PUSHORT u16 * 0 T linux/types.h PULONG u32 * 0 T linux/types.h PULONGLONG u64 * 0 T linux/types.h PVOID void * 0 T linux/types.h PUCHAR unsigned char * 0 T linux/types.h PUINT unsigned int * 0 T linux/types.h PUSHORT unsigned short * 0 T linux/types.h PULONG unsigned long * 0 T linux/types.h UCHAR unsigned char 0 T linux/types.h UCHAR_PTR unsigned char * 0 T linux/types.h UINT unsigned int 0 T linux/types.h UINT_PTR unsigned int * 0 T linux/types.h USHORT unsigned short 0 T linux/types.h USHORT_PTR unsigned short * 0 T linux/types.h ULONG unsigned long 0 T linux/types.h ULONG_PTR unsigned long * Table 26: The Data Type between Windows and Linux 5.5 Create Device Object and Set Permission 如 Table 27 所示,Windows 使用 IoCreateDevice 函數來建立 DeviceObject( 裝置物件 ), 並以 DeviceCharacteristics 參數設定存取權限 ; 而 Linux 則是使用 mknod 來建立一個 Device File, 再使用 chmod 指令來設定權限, 兩者的差別在於 Windows 將權限寫在程式裏, 而 Linux 是利用指令去變更存取 Device File 的權限 Windows -36- Linux

39 Format NTSTATUS IoCreateDevice( IN PDRIVER_OBJECT DriverObject, IN ULONG DeviceExtensionSize, IN PUNICODE_STRING DeviceName OPTIONAL, IN DEVICE_TYPE DeviceType, IN ULONG DeviceCharacteristics, IN BOOLEAN Exclusive, OUT PDEVICE_OBJECT *DeviceObject ); Driver checks any ACLs for the file name DeviceObject ->Flags = DO_DIRECT_IO; Windows DeviceCharacteristics parameters Administrator/Root FILE_DEVICE_SECURE_OPEN 700 Read only FILE_READ_ONLY_DEVICE 444 Create device file: mknod device_file_name type major minor Change file permission: chmod permissions device_file_name int register_chrdev( unsigned int major, const char * name, struct file_operations *fops ); Linux chmod permissions parameters Table 27: Create Device Object and Set Driver Permission Windows 與 Linux 註冊及解註冊 Device 函數, 如 Table 28 所示 Windows 的 IoCreateDevice 函數會根據產生的 DeviceObject 其 Flags 值 (DeviceObject ->Flags) 來決定對應到 Linux 的是 register_chrdev/unregister_chrdev 或 register_blkdev/unregister_blkdev 函數, 也決定 Linux 用 mknod 命令來建立 device file 時要使用參數 c(character Device) 或 b(block Device) 的 type 值 ( 咖啡色字體 ) Map Type Include Windows DeviceObject->Flags Linux 1 F linux/fs.h DO_DIRECT_IO register_chrdev IoCreateDevice 1 F linux/fs.h DO_BUFFER_IO register_blkdev 1 F linux/fs.h DO_DIRECT_IO unregister_chrdev IoDeleteDevice 1 F linux/fs.h DO_BUFFER_IO unregister_blkdev Table 28: Determine I/O Type between Windows and Linux 以 Table 29 的例子為例, 產生的裝置物件 (DeviceObject) 是 Windows 的 DriverObject 對應到 Linux 的 fops 參數 ( 紫色字體 );Windows 產生裝置之相關函數為 IoCreateDevice 及 DeviceObject ->Flags = DO_DIRECT_IO; 對應到 Linux 則是 register_chrdev 及 mknod 的參數 ( 紅色字體 ); 裝置名稱是 Windows 的 DeviceName 對應到 Linux 的 name 參數 ( 深藍色字體 ); 裝置類別在 Windows 指定 DeviceType 為 FILE_DEVICE_UNKNOWN 對應到 Linux 為 Major 5 參數 ( 咖啡色字體 ); 權限設定是 Windows 的 FILE_DEVICE_SECURE_OPEN 對應到 5 在 Linux 使用 cat /proc/devices 來查看可用的 Major/Minor 值 -37-

40 Linux 的 chmod 700 的 permisson 參數 ( 綠色字體 ) Windows Example char devicename= TestDevice status = IoCreateDevice( DriverObject, sizeof(device_extension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, 0, &fdo); DeviceObject ->Flags = DO_DIRECT_IO; Linux # cat /proc/devices //check major # mknod /dev/testdevice c # chmod 700 /dev/testdevice char name="testdevice"; int major=253; int register_chrdev( unsigned int major, const char * name, struct file_operations *fops ); Table 29: Create Device Function between Windows and Linux 5.6 I/O Control 6 在 32 位元的平台上,Windows 與 Linux 對於 I/O Control 函數的對應也有類似的手 法, 如 Table 30 Windows 的 DeviceType 對應到 Linux 的 type 參數 ( 紫色字體 ), 用來表示裝 置類別,Windows 的 DeviceType 共有 16 bits, 而 Linux 的 type 共有 8 bits, 如 : Windows: #define FILE_DEVICE_PARALLEL_PORT 0x Linux: #define 'p' 80-9F Windows 的 Function Code 參數對應到 Linux 的 nr 參數 ( 綠色字體 ), 用來識別函數是由 此裝置發出的,Windows 的 FunctionCode 共有 12 bits, 而 Linux 的 nr 共有 8 bits; 因 Windows 規定使用者自訂的 I/O Control Code 必須在 0x8000 以後, 所以移轉到 Linux 要再減掉此數 字即可 Windows 的 RequiredAccess 參數 (FILE_READ_ACCESS FILE_WRITE_ACCESS FILE_ANY_ACCESS) 對應到 Linux 的 direction 參數 (_IOR _IOW _IOWR), 均為 2 bits( 紅色字體 ) 比較不同的是 Windows 在 Method 欄位要指明存取方式是小量資料或大量資料, Linux 沒有這樣的做法, 因此可以忽略 ; 但 Linux 尚需手動修改 size 參數, 指定 I/O Control 的資料型別 Map Type Include Windows Linux 1 F asm/ioctl.h Windows not Exist but Linux Exist #define _IO(type,nr) 1 F asm/ioctl.h 1 F asm/ioctl.h #define IOCTL_Device_Function CTL_CODE(DeviceType, FunctionCode, Method, FILE_READ_ACCESS) #define IOCTL_Device_Function CTL_CODE(DeviceType, #define IOCTL_Device_Function _IOR(type,nr,size) #define IOCTL_Device_Function _IOW(type,nr,size) 6 請參照 Windows DDK 及 Linux Kernel 的 Documentation/ioctl-number.txt -38-

41 1 F asm/ioctl.h FunctionCode, Method, FILE_WRITE_ACCESS) #define IOCTL_Device_Function CTL_CODE(DeviceType, FunctionCode, Method, FILE_ANY_ACCESS) #define IOCTL_Device_Function _IOWR(type,nr,size) Table 30: The I/O Control between Windows and Linux 5.7 Memory Management Windows 與 Linux 記憶體管理的函數, 如 Table 31, 兩邊函數原型 (Function Prototype) 幾乎完全對應 Map Type Include Windows Linux 1 F linux/slab.h ExAllocatePool kmalloc 0 F linux/slab.h ExFreePool kfree 1 F linux/slab.h RtlZeroMemory memset 0 F asm/uaccess.h RtlCopyMemory copy_from_user 0 F asm/uaccess.h RtlMoveMemory copy_from_user 0 F linux/slab.h MmFreePagesFromMdl free_page 1 F linux/vmalloc.h MmMapIoSpace ioremap 1 F linux/vmalloc.h MmUnmapIoSpace iounmap 2 F linux/mm.h Windows not Exist but Linux Exist do_mmap 0 P linux/gfp.h NonPagedPool GFP_ATOMIC 0 P linux/gfp.h PagedPool GFP_KERNEL Table 31: The Memory Management Function between Windows and Linux 5.8 Interrupt Windows 與 Linux 註冊中斷的函數, 如 Table 32,Linux 比 Windows 多出許多中斷函數 來處理中斷, 這部分需要程式設計師自己參酌是否需要加到程式中 Map Type Include Windows Linux 1 F linux/interrupt.h IoConnectInterrupt request_irq 1 F linux/interrupt.h IoDisconnectInterrupt free_irq 0 P asm/signal.h 0 P asm/signal.h In Function IoConnectInterrupt() ShareVector=FALSE In Function IoConnectInterrupt() ShareVector=TRUE In Function request_irq() flags=sa_interrupt In Function request_irq() flags=sa_shirq 2 F linux/interrupt.h Windows not Exist but Linux Exist void local_irq_save(unsigned long flags); -39-

42 2 F linux/interrupt.h Windows not Exist but Linux Exist void local_irq_disable(void); 2 F linux/interrupt.h Windows not Exist but Linux Exist void local_irq_restore(unsigned long flags); 2 F linux/interrupt.h Windows not Exist but Linux Exist void local_irq_enable(void); 2 F linux/interrupt.h Windows not Exist but Linux Exist unsigned long probe_irq_on(void); 2 F linux/interrupt.h Windows not Exist but Linux Exist int probe_irq_off(unsigned long); 2 F linux/interrupt.h Windows not Exist but Linux Exist void enable_irq(int irq); 2 F linux/interrupt.h Windows not Exist but Linux Exist void disable_irq(int irq); Table 32: The Interrupt Function between Windows and Linux 如 Table 33 所示,Windows 的 IoConnectInterrupt/IoDisconnectInterrupt 函數對應到 Linux 的 request_irq/free_irq 函數 ; 在 Windows 的 ServiceRoutine 對應到 Linux 的 handler 函數, 用來指定中斷時的處理常式 ( 綠色字體 ); 在 Windows 利用 ShareVector 參數來判斷是否為 Share Interrupt, 對應到 Linux 函數則是利用 flags 參數來指定是否中斷共享 ( 紫色字體 );Windows 的 Vector/Irql 對應到 Linux 的 irq( 紅色字體 ), 指明其中斷向量 ;Windows 的 ServiceContext 對應到 Linux 的 dev_name, 指明其中斷服務常數所需要的參數 ( 咖啡色字體 ) Windows NTSTATUS IoConnectInterrupt( OUT PKINTERRUPT *InterruptObject, IN PKSERVICE_ROUTINE ServiceRoutine, IN PVOID ServiceContext, IN PKSPIN_LOCK SpinLock OPTIONAL, IN ULONG Vector, IN KIRQL Irql, IN KIRQL SynchronizeIrql, IN KINTERRUPT_MODE InterruptMode, IN BOOLEAN ShareVector, IN KAFFINITY ProcessorEnableMask, IN BOOLEAN FloatingSave); Linux int request_irq( unsigned int irq, irqreturn_t (*handler)(), unsigned long flags, const char *dev_name, void *dev_id); Table 33: The Interrupt Function between Windows and Linux 5.9 Synchronization Windows 與 Linux 的同步函數, 如 Table 34 Linux 比 Windows 多出許多同步函數來處 理同步, 這部分需要程式設計師自己參酌是否需要加到程式中 以取得自旋鎖 (SpinLock) 為例,Windows 多了一個 IRQ Level 的參數要輸出, 而 Linux 沒有這樣的機制, 因此轉換 時可將這部分忽略不處理 Windows: VOID KeAcquireSpinLock(IN PKSPIN_LOCK -40- SpinLock, OUT PKIRQL

43 OldIrql); Linux: void spin_lock(spinlock_t *lock); Map Type Include Windows Linux 0 F asm/atomic.h InterlockedIncrement atomic_inc 0 F asm/atomic.h InterlockedDecrement atomic_dec 2 F asm/atomic.h Windows not Exist but Linux Exist void atomic_set(atomic_t *v, int i); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_read(atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_add(int i, atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_sub(int i, atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_inc(atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_dec(atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_inc_and_test(atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_dec_and_test(atomic_t *v); 2 F asm/atomic.h Windows not Exist but Linux Exist int atomic_sub_and_test(int i, atomic_t *v); 2 F asm/bitops.h Windows not Exist but Linux Exist int set_bit(int nr, void *addr); 2 F asm/bitops.h Windows not Exist but Linux Exist int test_bit(int nr, void *addr); 2 F asm/bitops.h Windows not Exist but Linux Exist void clear_bit(int nr,void *addr); 2 F asm/bitops.h Windows not Exist but Linux Exist void change_bit(int bit, void *addr); 2 F asm/bitops.h Windows not Exist but Linux Exist int test_and_set_bit(int nr, void *addr); 2 F asm/bitops.h Windows not Exist but Linux Exist int test_and_clear_bit(nr,void *addr); 2 F asm/bitops.h Windows not Exist but Linux Exist int test_and_change_bit(int nr, void *addr); 0 T linux/types.h KSPIN_LOCK spinlock_t 0 T linux/types.h PKSPIN_LOCK spinlock_t * 3 F KeReadStateSemaphore Windows Exist but Linux not Exist 1 F KeReleaseSemaphore up 1 F KeInitializeSemaphore sema_init 1 F KeWaitForMultipleObjects down_interruptible 1 F KeWaitForSingleObject down_interruptible 0 F asm/spinlock.h KeInitializeSpinLock spin_lock_init 1 F asm/spinlock.h KeAcquireSpinLock spin_lock 1 F asm/spinlock.h KeReleaseSpinLock spin_unlock 2 F Windows not Exist but Linux Exist void spin_lock_irq(spinlock_t *lock); 2 F Windows not Exist but Linux Exist void spin_lock_bh(spinlock_t *lock); 2 F Windows not Exist but Linux Exist void spin_unlock_bh(spinlock_t *lock); 0 F KeAcquireSpinLockAtDpcLevel spin_lock 0 F KeReleaseSpinLockFromDpcLevel spin_unlock 2 F Windows not Exist but Linux Exist void init_rwsem(struct rw_semaphore *sem); -41-

44 2 F Windows not Exist but Linux Exist void down_read(struct rw_semaphore *sem); 2 F Windows not Exist but Linux Exist void up_read(struct rw_semaphore *sem); 2 F Windows not Exist but Linux Exist void down_write(struct rw_semaphore *sem); 2 F Windows not Exist but Linux Exist void up_write(struct rw_semaphore *sem); 2 F Windows not Exist but Linux Exist 2 F Windows not Exist but Linux Exist void spin_lock_irqsave(spinlock_t *lock, unsigned long flags); void spin_ulock_irqrestore(spinlock_t *lock, unsigned long flags); Table 34: The Synchronization Function between Windows and Linux 5.10 Double Linked List Windows 與 Linux 的雙向鏈結函數, 如 Table 35 所示, 兩邊函數原型 (Function Prototype) 幾乎完全對應 Map Type Include Windows Linux 0 F linux/list.h InitializeListHead INIT_LIST_HEAD 0 F linux/list.h InsertHeadList list_add 0 F linux/list.h InsertTailList list_add_tail 0 F linux/list.h IsListEmpty list_empty 1 F linux/list.h RemoveHeadList list_move 1 F linux/list.h RemoveTailList list_move_tail 0 T linux/list.h PLIST_ENTRY list_head * 0 T linux/list.h LIST_ENTRY list_head Table 35: The Double Linked List Function between Windows and Linux 5.11 Timer Windows 與 Linux 的計時器函數, 如 Table 36 所示 Windows 平台使用 KeSetTimer 函 數來指定時間及回呼函數 (Call Back Function) 而 Linux 則使用 timer_list 的結構來存放這些 對應參數 Map Type Include Windows Linux 0 F linux/time.h KeInitializeTimer init_timer 0 F linux/time.h KeCancelTimer del_timer 2 F linux/time.h Windows not Exist but Linux Exist int mod_timer(struct timer_list *timer, unsigned long expires); -42-

45 1 F linux/time.h BOOLEAN KeSetTimer( IN PKTIMER Timer, IN LARGE_INTEGER DueTime, IN PKDPC Dpc OPTIONAL ); struct timer_list TIMER_INITIALIZER( _function, _expires, _data); _timer.expires _timer.function Table 36: The Timer Function between Windows and Linux 5.12 Work Thread Windows 與 Linux 的工作項函數, 如 Table 37 所示 Map Type Include Windows Linux 1 F linux/interrupt.h IoAllocateWorkItem create_workqueue 0 F linux/workqueue.h IoFreeWorkItem destroy_workqueue 1 F linux/interrupt.h IoQueueWorkItem int queue_work 3 F linux/workqueue.h IoSizeOfWorkItem Windows Exist but Linux not Exist Table 37: The Work Thread Function between Windows and Linux 5.13 Module Description and Function Export Module 是 Linux 獨有但 Windows 沒有的函數, 如 Table 38 所示, 此部分會在轉換時加 入, 程式設計師可自行調整內容 Map Type Include Windows Linux 2 F linux/init.h Windows not Exist but Linux Exist module_init(xxx_init); 2 F linux/init.h Windows not Exist but Linux Exist module_exit(xxx_exit); 2 P Windows not Exist but Linux Exist MODULE_AUTHOR("Undefined"); 2 P Windows not Exist but Linux Exist MODULE_LICENSE("GPL"); 2 P Windows not Exist but Linux Exist MODULE_DESCRIPTION("Undefined"); Table 38: The Module Description is Linux Only Function Function Export 也是 Linux 才有的函數, 如 Table 39, 此部分視程式設計師需要增加 Map Type Include Windows Linux 2 F Windows not Exist but Linux Exist EXPORT_SYMBOL(name); 2 F Windows not Exist but Linux Exist EXPORT_SYMBOL_GPL(name); Table 39: The EXPORT_SYMBOL is Linux Only Function 5.14 IRP Relative Function 在 Linux 沒有 Irp 的封包也沒有 IRQ Level, 如 Table 40, 因此這部分可忽略不必移轉 -43-

46 到 Linux Map Type Include Windows Linux 3 F IoFreeIrp Windows Exist but Linux not Exist 3 F IoGetCurrentIrpStackLocation Windows Exist but Linux not Exist 3 F IoGetNextIrpStackLocation Windows Exist but Linux not Exist 3 F IoInitializeIrp Windows Exist but Linux not Exist 3 F IoMakeAssociatedIrp Windows Exist but Linux not Exist 3 F IoMapTransfer Windows Exist but Linux not Exist 3 F IoMarkIrpPending Windows Exist but Linux not Exist 3 F IoSetNextIrpStackLocation Windows Exist but Linux not Exist 3 F IoSizeOfIrp Windows Exist but Linux not Exist 3 F IoSkipCurrentIrpStackLocation Windows Exist but Linux not Exist 3 F IRP Windows Exist but Linux not Exist 3 F KeLowerIrql Windows Exist but Linux not Exist 3 F KeRaiseIrql Windows Exist but Linux not Exist 3 F IoCallDriver Windows Exist but Linux not Exist 3 F IoCancelIrp Windows Exist but Linux not Exist 3 F IoAllocateIrp Windows Exist but Linux not Exist 3 F RtlInitUnicodeString Windows Exist but Linux not Exist 3 F RtlUnicodeStringToAnsiString Windows Exist but Linux not Exist Table 40: The IRP Relative Function between Windows and Linux 5.15 Others Windows 的 SymbolicLink 函數及 UniCode 的資料型別,Linux 沒有故不處理 Windows 的字串會以 L"\\Device\\DeviceName" 的格式表示, 因此對應到 Linux 要轉換為 "DeviceName", 如 Table 41 Map Type Include Windows Linux 3 F IoCreateSymbolicLink Windows Exist but Linux not Exist 3 F IoDeleteSymbolicLink Windows Exist but Linux not Exist 3 T UNICODE_STRING Windows Exist but Linux not Exist 1 S L"\\Device\\DeviceName" "DeviceName" 7 Table 41: The Symbolic Link Function between Windows and Linux 遇誤即停的偵錯模式 (Break when False) 及 (Memory Barrier), 如 Table 42 7 Linux 會將此裝置放在 /dev/devicename -44-

47 Map Type Include Windows Linux 0 F assert bugon 0 F asm/system.h KeMemoryBarrier mb 2 F asm/system.h Windows not Exist but Linux Exist void rmb(void); 2 F asm/system.h Windows not Exist but Linux Exist void read_barrier_depends(void); 2 F asm/system.h Windows not Exist but Linux Exist void wmb(void); 2 F asm/system.h Windows not Exist but Linux Exist void smp_rmb(void); 2 F asm/system.h Windows not Exist but Linux Exist void smp_read_barrier_depends(void); 2 F asm/system.h Windows not Exist but Linux Exist void smp_wmb(void); 2 F asm/system.h Windows not Exist but Linux Exist void smp_mb(void); Table 42: The Break When False and Memory Barrier Function between Windows and Linux 如 Table 43 為設定執行緒 (Thread) 的優先權函數, 雖然這兩個函數的原型一樣, 只需傳入一個參數, 但因 Windows 的 kpriority 參數值有 32 個 Range,0 最低 (LOW_PRIORITY)~31 最高 (HIGH_PRIORITY), 而 Linux 的 nice value: 19 最低 ~ -20 最高, 兩邊的定義相反, 因此轉換時要做調整 Windows: KPRIORITY KeSetPriorityThread(IN PKTHREAD Thread,IN KPRIORITY Priority); Linux: void set_user_nice(task_t *p, long nice); Map Type Include Windows Linux 3 F KeSetPriorityThread set_user_nice kpriority: 0(LOW_PRIORITY)~31(HIGH_PRIORITY) nice value: 19(low)~ -20(high) Table 43: Set Thread Priority between Windows and Linux -45-

48 6 Porting Basic Driver Use Parallel Device 6.1 Analysis Each Function Block 上一章討論了兩個平台 Driver 的個別函數如何對應, 本章則開始將所有函數區塊 (Function Block) 做個別討論, 並將所有函數區段組合成完整的 Device Driver 所應具備的 程式區段 (Program Section), 如 Figure 14 可看出兩個平台 Device Driver 程式區段的完整 對應, 下面就各個函數區段做簡單的介紹, 之後會在各小節做對應的範例介紹 [8][21] Include File Area Include File Area Function Declaration Area Global Data Area, Driver Internal Data Function Declaration Area Global Data area, Driver Internal Data DriverEntry Function file_operations structure Functions in DriverEntry Functions in file_operations Functions used in driver Functions used in driver Functions used in module Windows Linux Figure 14: The Comparison of Program Section Include File Area 就是 Device Driver 所有引用到的標頭檔 (Header File), 在前一章討論到對應的函數均有在資料庫的 Include 欄位記錄該函數所屬之標頭檔, 以供轉換到 Linux 用來建置系統標頭檔 Function Declaration Area 是將所有函數的原型 (Function Prototype) 在宣告區做宣告 ;Global Data Area 是將所有全域變數做宣告 ;Driver Internal Data 是指 Driver 內所引用的變數, 也就是 Windows 的 DeviceObject->DeviceExtension 以及 Linux 所對應的 file->private_data -46-

49 Windows Driver 的 DriverEntry Function 對應到 Linux 為 file_operations structure 兩邊都是指明 OS 與 Device 互動時所會呼叫的 Driver Function Pointer, 如 Create Close Read Write Ioctl Windows Driver 的 Functions in DriverEntry 以及 Linux 所對應的 Functions in file_operations 則是上面 Function Pointer 的實作 Functions used in Driver 則是上面 Function Pointer 撰寫時另外再延伸的相關函數 最後一塊 Functions used in modules 是 Linux Device Driver 才有的, 用來註明此 Driver Module 的一些說明及更換 init/exit 函數的名稱 6.2 Introduce Parallel Port Device 本章以簡單的 Parallel Port Device 為例, 探討下列各個 Function Block 的對應方式 : Register Unregistered device Function Entry Pointer Open Release Read Write Ioctl Interrupt Hardware 本實驗是使用 25 pins 的 null modem 做為測試裝置, 如 Figure14, 因為 Parallel Port 容易取得且能實作出簡單的中斷與 I/O Figure 15: 25 pins Null Modem 由 Figure 16 得知 LPT1 的 IRQ 為 7, 其 I/O 的起始位置 (base_addr) 為 0x387, 依序 其 Data port 為 base_addr Status port 為 base_addr+1 Control port 為 base_addr+2[7] -47-

50 PORT Name Interrupt Starting I/O Ending I/O LPT1 IRQ7 0x387 0x37f Figure 16: Parallel Port Introduction 每個 Port 其所屬的 bit 各控制不同的地方, 每個 bit 詳細的意義可參照 Table 44 ADDRESS: BASE + 0 NAME: DATA PORT write / read/write ADDRESS: BASE + 1 NAME: STATUS PORT read only ADDRESS: BASE + 2 NAME: CONTROL PORT read/write bit # name 0 D0 1 D1 2 D2 3 D3 4 D4 5 D5 6 D6 7 D7 bit # name 0 reserved 1 reserved 2 /IRQ 3 ERROR 4 SELECT-IN 5 PAPER-OUT 6 ACK 7 /BUSY bit # Name 0 STROBE 1 LINEFEED 2 INIT 3 SELECT 4 enable IRQ (ACK) 5 enable bi-dir mode 6 Unused 7 Unused Table 44: Parallel Port Pins -48-

51 為了能夠看到中斷發生, 我們將 2~9 任一輸出腳位 (output pin) 與中斷腳位 10(IRQ pin) 相連, 可看到 outb 在 init_module() 會產生一個波形訊號在 output 腳位上 6.3 Map Program Section 本章節以 Null Modem 撰寫簡單的對應程式, 並探討每個部分轉換對應的方式 Map Include File Windows 所有 Device Driver 的函式庫都包含在 wdm.h 及 ntddk.h 8 這兩個標頭檔, 但 Linux 則將這些函數依類別分散在不同的標頭檔, 所以轉換時會把要分析的 Device Driver 原始程式檔先解譯 (Parser) 一次後, 得知所有 Windows 對應的 Linux 函數及該函數 所引用的標頭檔, 再將這些標頭檔與程式設計師所定義的標頭檔放在 Include File 的區 段中, 如 Table 45 所示 Windows Linux #include <wdm.h> #include <ntddk.h> #include <ntstatus.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 <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 的子集合 -49-

52 Double Linked linux/list.h list_add list_add_tail list_move_tail list_empty List 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 Map Function Declaration Area Windows 在每個 Entry Function 所傳入的參數都有 DEVICEOBJECT 及 Irp 這兩個變 數,Linux 沒有像 Irp 這樣的傳遞機制, 從 Table 47 可看出 Linux 函數宣告區每個 Entry Function 所傳入的參數不盡相同, 因此轉換時會將這部分的傳遞機制做適當的替換 Windows NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath); NTSTATUS XXX_AddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject); DriverObject->DriverUnload(); NTSTATUS XXX_DispatchCreate(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp); NTSTATUS XXX_DispatchClose(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp); NTSTATUS XXX_DispatchRead(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp); NTSTATUS XXX_DispatchWrite(PDEVICE_OBJECT DEVICEOBJECT, PIRP Irp); NTSTATUS XXX_DispatchDeviceControl( PDEVICE_OBJECT fdo, PIRP Irp); Linux static struct file_operations XXX_fop static int init XXX_init(void); module_init(xxx_init); static void exit XXX_exit(void); module _exit(xxx_exit); int XXX_open (struct inode *, struct file *); int XXX_release (struct inode *, struct file *); size_t XXX_aio_read (struct kiocb *, char user *, size_t, loff_t); size_t XXX_aio_write (struct kiocb *, const char user *, size_t, loff_t); int XXX_ioctl (struct inode *, struct file *, unsigned int, unsigned long); Table 47: The Comparison of Program Section Map Global Data Area -50-

53 因兩個平台均使用 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 可用的字串 Odds OS Windows Linux I/O Control #define IOCTL_READ_DATA CTL_CODE #define IOCTL_READ_DATA (FILE_DEVICE_UNKNOWN, 0x8001, _IOR('t', 1, unsigned int) METHOD_BUFFERED, FILE_READ_ACCESS) #define IOCTL_WRITE_DATA CTL_CODE #define IOCTL_WRITE_DATA (FILE_DEVICE_UNKNOWN, 0x8002, _IOW('t', 2, unsigned int) METHOD_BUFFERED, FILE_WRITE_ACCESS) 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 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 { -51-

54 PUCHAR buffer; ULONG buf_size; ULONG chars_in_buffer; KSPIN_LOCK lock; DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS XXX_DriverOpen(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) { PDEVICE_EXTENSION pdx= (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;.. unsigned char *buffer; unsigned long buf_size; unsigned long *chars_in_buffer; spinlock_t lock; private_data, *p_private_data ; 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 Windows 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 的相關資訊 ( 深藍色字體 ) 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; static struct file_operations XXX_fops ={ owner = THIS_MODULE,.open = XXX_open,.release = XXX_release,.read= XXX_aio_read,.write= XXX_aio_write,.flush=XXX_flush,.ioctl= XXX_ioctl, ; //Other initial function return STATUS_SUCCESS; Static void XXX_init_module(void) { //Other initial function -52-

55 NTSTATUS XXX_AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo) { VOID XXX_DriverUnload(PDRIVER_OBJECT DriverObject) { return 0; static void XX_cleanup_module(void) { Functions used for module description module_init(xxx_init_module); module_exit(xxx_cleanup_module); MODULE_AUTHOR( Nobody"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Skeleton" ); Table 50: The Comparison of Entry Function Windows Map Create Function Block 如 Table 51 所示, 在新增裝置 (Device) 時, 要先轉換兩邊的裝置名 ( 咖啡色字體 ), 接下來初始化裝置的內部變數 ( 藍色字體 ), 新增裝置 ( 紅色字體 ), 設定中斷 ( 紫色字體 ), 產生中斷 ( 橘色字體 ), 最後回傳是否成功 ( 粉紅色字體 ) 因為 Linux 沒有 UniCode 及 IRP 所以不移轉 ( 灰色字體 ) 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); -53-

56 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 Windows Map Close Function Block 當裝置要關閉時, 如 Table 52, 裝置結束前關閉 Parallel Port 的 IRQ( 咖啡色字體 ), 刪除該裝置 ( 紅色字體 ) 並中止中斷 ( 紫色字體 ) 因為 Linux 沒有 UniCode 及 IRP 所以不 移轉 ( 灰色字體 ) 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); 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 */ IoDisconnectInterrupt(PARALLEL_PORT_INTERRUPT); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; /* Disconnect Interrupt */ free_irq(parallel_port_interrupt, NULL); return 0; Table 52: The Comparison of Close Function Map Write Function Block -54-

57 當 /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 static ssize_t skeleton_write(struct file *file, DeviceObject,IN PIRP Irp) { const char *buf,size_t count, loff_t *ppos) { NTSTATUS status; int 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); status = copy_from_user(string,buf,count); if (status!= STATUS_SUCCESS) if (status!= 0) status = STATUS_UNSUCCESSFUL; status = -EFAULT; counter += count; counter += count; Irp->IoStatus.Status = status; Irp->IoStatus.Information = count; IoCompleteRequest( Irp, IO_NO_INCREMENT); return status; return count; Table 53: The Comparison of Write Function Windows Map Ioctl Function Block 程式設計師想另行擴充自訂的命令可在 I/O Control 定義 Control Code( 紅色字體 ), 一旦有應用程式呼叫到此 Driver 的 Control Code 來執行, 系統便會呼叫此函數執行, 從 Table 54 右半部可看出, 因 Linux 沒有 UniCode 及 IRP 所以不移轉 ( 灰色字體 ) 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) { -55-

58 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; break; default: ret = -EINVAL; return ret; Table 54: The Comparison of Ioctl Function 6.4 Test 將轉換成 Linux 平台的 Driver Module 安裝到系統中 [8]: Kernel 2.6: # insmod./testdevice.ko 在 /dev 目錄下建立一個 device entry: # mknod -m 666 /dev/testdevice c 32 0 當 module 被載入後, 可以看到產生的中斷 : initializing module Generating interrupt now on all output pins (intr/ack = pin 10) >>> PARALLEL PORT INT HANDLED: interruptcount=1 Interrupt generated. You should see the handler-message 再利用./user 來對 /dev/testdevice 做 read/write/ioctl 的控制 : #./user String 'Skeleton Kernel Module Test' written to /dev/testdevice String 'Skeleton Kernel Module Test' read from /dev/testdevice -56-

59 IOCTL test: written: ' ' - received: ' 寫入資料到 Linux module 之中 : this is a pen. [press CTRL-D] 再將資料讀回 : # cat < /dev/testdevice -57-

60 7 Using Lex/Yacc to Build Driver Generator 本論文其實可自行開發驅動程式產生器 (Driver Generator), 但市面上已有 Lex/Yacc[19][20] 這套免費且功能強大的解譯器 (Parser), 常被用來做 Portable C Compiler, 因此決定採用這個 Tool 來辨識語法並轉換程式碼, 實做出驅動程式產生器 (Driver Generator) 7.1 Transfer Model 轉換的順序將由小至大轉換 : 從 Full Map 的函數 Partial Map 的函數 最後再到整 個函式區塊 (Function Block) 的重置 (Relocate) 在轉換前先使用 MS-SQL 為資料庫, 建立所有單一識別子的對應, 並將 Full Map 的 部分建立在 Lex 的語彙對應 ; 接下來再處理 Partial Map 及 Windows Only 的函數, 此部分 使用 Lex 來辨識語句建立語彙規則 (Lexical Rules), 並用 Yacc 來辦識語法建立對應的文法 規則 (Mapping Grammar Rule); 最後是整個 Function Block 之間重置 (Relocate) 的處理, 再 利用另一個 Lex/Yacc 寫好的程式再解譯 (Parse) 一次, 將所有 Windows Driver Token 對應到 Linux Driver Model 應有的對應格式, 如此便可完成整個 Device Driver Code 的轉換 7.2 Full Map Function 先前我們已將要分析的函數分類並存在 MainMap 的資料庫中, 如 Figure 17, 利用 Select 語法將資料庫中完全對應 (FullMap) 的函數 (Maped Field=0) 直接轉換 : select WName, 'printf("'+rtrim(lname)+'");' from MainMap where Mapped = 0-58-