Aros/開發者/Docs/Devices1.3
AMIGA 裝置驅動程式指南
版權所有 (c) 1990 Markus Wandel
版本 0.12,1990 年 5 月 21 日。
分發:以未修改形式免費。
免責宣告:我不知道我在說什麼;不保證本文件中任何內容的正確性。並非所有這些都符合康懋達認可的程式設計和文件實踐。Amiga OS 的版本 1.4/2.0 可能使本文件的部分內容過時,尤其是我提到 exec 函式的內部機制的地方。
更正:如果你發現錯誤並希望更正,請與我聯絡,以便我可以擁有最新的主副本。目前,您可以透過 (613) 591-7698 聯絡到我。如果此號碼失效,請致電我的父母 (705) 785-3383 或 (705) 736-2285 (夏季) 並獲取當前號碼。
目錄
0. 簡介 1. 裝置結構
1.1. DEVICE NODES 1.2. CONSTRUCTING A DEVICE NODE 1.3. STANDARD DEVICE FORMAT 1.4. DEVICES IN ROM 1.5. DEVICES ON DISK 1.6. JUMP VECTOR CHECKSUMS
2. 裝置 I/O 協議
2.1. THE I/O REQUEST STRUCTURE
2.2. OPENING AND CLOSING A DEVICE
2.3. EXPUNGING A DEVICE
2.4. UNIT STRUCTURES
2.5. THE BEGINIO FUNCTION
2.6. THE ABORTIO FUNCTION
2.7. EXEC I/O FUNCTIONS
2.8. CALLING BEGINIO DIRECTLY
2.8. SYNCHRONOUS I/O
2.9. ASYNCHRONOUS I/O
2.9.1. WAITING FOR A SPECIFIC I/O REQUEST
2.9.2. WAITING ON A SPECIFIC REPLY PORT
2.9.3. GENERAL CASE
3. 通用命令和錯誤號
3.1. COMMANDS
3.1.1. CMD_RESET
3.1.2. CMD_READ
3.1.3. CMD_WRITE
3.1.4. CMD_UPDATE
3.1.5. CMD_CLEAR
3.1.6. CMD_STOP
3.1.7. CMD_START
3.1.8. CMD_FLUSH
3.2. ERROR NUMBERS
4. 磁碟裝置驅動程式
4.1. COMMANDS
4.1.1. CMD_READ AND CMD_WRITE
4.1.2. TD_MOTOR
4.1.3. TD_SEEK
4.1.4. TD_FORMAT
4.1.5. TD_PROTSTATUS
4.1.6. TD_RAWREAD AND TD_RAWWRITE
4.1.7. TD_GETDRIVETYPE
4.1.8. TD_GETNUMTRACKS
4.1.9. TD_CHANGESTATE
4.1.10. OTHER COMMANDS
4.2. ERROR NUMBERS
4.3. SCSIDIRECT PROTOCOL
4.4. A MINIMAL DISK COMMAND SUBSET
5. 參考資料 6. 修訂歷史
0. 簡介
很多人要求我向他們解釋 Amiga 裝置驅動程式。有很多內容需要解釋,因此我決定寫下我對這個主題的所有了解。這是結果。
這不是一個獨立的文件。它假設您熟悉多工、訊息傳遞級別的 Amiga 程式設計。它假設您擁有來自康懋達的自動文件、包含檔案和示例裝置驅動程式。這些文件中有很多資訊是重複的,但並非全部。
我希望我對這個主題比現在瞭解得更多,我希望我是一個更好的作家。唉,我沒有,我也不是。本文件包含編寫磁碟駐留、自動載入和可刪除裝置驅動程式所需的一切,但您可能需要閱讀不止一次。要編寫一個好的、可移動介質磁碟驅動程式,您將不得不做一些額外的研究。
1. 裝置結構
本節描述與已載入裝置關聯的資料結構,以及如何將裝置放入系統中。就本節討論的內容而言,庫和裝置是相同的。
1.1. 裝置節點
裝置透過其裝置節點為系統所知。裝置節點由三部分組成
(a) a jump table to the device functions (b) a library structure (c) any private data that the device has
"裝置地址"是庫節點的基地址;因此跳轉表位於該地址的負偏移量處,其他所有內容都位於正偏移量處。
跳轉表中的每個條目都是指向 32 位地址的“jmp”。因此第一個跳轉位於裝置地址的偏移量 -6 處,第二個位於偏移量 -12 處,依此類推。以下四個對所有裝置和庫都是標準的
-6: Open -12: Close -18: Expunge -24: ExtFunc
這些分別是開啟、關閉和刪除(解除安裝)裝置的入口點。最後一個似乎是為將來擴充套件而準備的。
裝置驅動程式還有兩個標準函式
-30: BeginIO -36: AbortIO
這些分別是提交 I/O 請求和取消掛起請求的入口點。
庫結構如下所示,採用“展開”形式。
struct Library {
struct Node {
struct Node *ln_Succ;
struct Node *ln_Pred;
UBYTE ln_Type;
/* NT_DEVICE = 3 */
BYTE ln_Pri;
char *ln_Name;
} lib_Node;
UBYTE lib_Flags;
/* LIBF_SUMMING = 1
LIBF_CHANGED = 2
LIBF_SUMUSED = 4
LIBF_DELEXP = 8 */
UBYTE lib_pad;
UWORD lib_NegSize;
UWORD lib_PosSize;
UWORD lib_Version;
UWORD lib_Revision;
APTR lib_IdString;
ULONG lib_Sum;
UWORD lib_OpenCnt;
};
該裝置透過頂部的節點結構連結在系統裝置列表上。裝置列表可以在 ExecBase->DeviceList 中找到。有趣的是“ln_Type”欄位,它必須是 NT_DEVICE,以及“ln_Name”欄位,它必須是標準形式的裝置名稱,例如“serial.device”。
"lib_PosSize" 和 "lib_NegSize" 欄位指示裝置基地址以上和以下使用的位元組數。因此,“lib_NegSize”是跳轉向量的尺寸,“lib_PosSize”是庫結構的尺寸加上使用者的私有資料(如果有)。
"lib_Version"、"lib_Revision" 和 "lib_IdString" 欄位儲存有關裝置的更多資訊。對於“serial.device”,版本 34,修訂版 12,該字串將如下所示
"serial 34.12 (27 Mar 1989)"
這似乎是公認的標準格式。
"lib_Sum" 欄位以及標誌位 "LIBF_SUMMING"、"LIBF_CHANGED" 和 "LIBF_SUMUSED" 用於跳轉向量校驗和機制,稍後將對此進行討論。
"lib_OpenCnt" 欄位計算裝置當前開啟的次數。如果它不為零並且請求刪除,則裝置可以使用 "LIBF_DELEXP" 標誌來記住它應該儘快消失。
1.2. 構造裝置節點
理論上,您可以只為裝置節點分配所有需要的記憶體,手動初始化跳轉向量和庫節點中的必需欄位,獲取 ExecBase->DeviceList,執行 Forbid(),並使用 Enqueue()、AddHead() 或 AddTail() 將該節點新增到列表中。但有一個更簡單的方法。它是以下函式
AddDevice(device)
A1
它接受一個已初始化的裝置節點,執行 Forbid()/Permit(),並將該節點新增到 ExecBase->DeviceList 中。它還會呼叫 Sumkick()(稍後討論)。節點本身可以用以下函式構造
library = MakeLibrary(vectors, structure, init, dataSize, segList) D0 A0 A1 A2 D0 D1
第一個引數指向跳轉向量的函式地址表。它可以具有以下格式之一
(a) Relative
vectors: dc.w -1
dc.w func1-vectors
dc.w func2-vectors
...
dc.w -1
(b) Absolute
vectors: dc.l func1
dc.l func2
...
dc.l -1
"dataSize" 引數確定裝置節點的“正尺寸”。它必須至少是庫結構的尺寸。從提供的跳轉向量條目數量可以隱式得出“負尺寸”。
"MakeLibrary" 函式將分配適當數量的記憶體,填充跳轉向量,然後使用 InitStruct() 清除和初始化正偏移量區域。“structure”引數指向 InitStruct() 函式的資料表。此函式是一個複雜的東西,最好與提供的宏一起使用。以下是康懋達示例裝置驅動程式中的表
dataTable:
INITBYTE LN_TYPE,NT_DEVICE
INITLONG LN_NAME,myName
INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
INITWORD LIB_VERSION,VERSION
INITWORD LIB_REVISION,REVISION
INITLONG LIB_IDSTRING,idString
DC.L 0
這意味著
- store a byte NT_DEVICE at offset LN_TYPE
- store the device name as a longword (pointer) at LN_NAME
- store LIBF_SUMUSED!LIBF_CHANGED in the "lib_Flags" field
- store the version, revision, and IdString in the appropriate fields.
如果您想了解有關 Initstruct() 函式的更多資訊(它非常靈活,可以執行比上面建議的更多操作),您應該閱讀它的自動文件,可能還需要閱讀反彙編程式碼。
最後,我們有 "init" 和 "segList" 引數。“init”是裝置自身初始化程式碼的地址。在節點構造完成後呼叫它。“segList”描述裝置程式碼的載入位置。初始化程式碼使用 D0 中的裝置節點地址和 A0 中的 SegList 引數進行呼叫。它返回的值是 MakeLibrary 返回的值。因此初始化程式碼通常會按接收到的方式返回裝置節點地址。
唯一需要注意的是,“structure”和“init”欄位可以設定為零,以分別不初始化正偏移量區域和不呼叫任何初始化程式碼。
因此,為了總結到目前為止我們所瞭解的內容,獲得裝置進入系統的第一原理方法如下
- LoadSeg() the code into memory, if necessary - MakeLibrary() to build the library node - AddDevice() with the result to add the node to the device list.
1.3. 標準裝置格式
實際裝置採用標準格式,允許它們在沒有了解其內部機制的情況下載入和初始化。標準格式基於“駐留結構”或“RomTag”。這些結構標記系統 ROM 中以及磁碟上的 LIBS: 和 DEVS: 目錄中的各種庫和裝置。該結構如下所示
struct Resident {
UWORD rt_MatchWord;
/* RTC_MATCHWORD = 0x4AFC */
struct Resident *rt_MatchTag;
APTR rt_EndSkip;
UBYTE rt_Flags;
/* RTF_COLDSTART = 1
RTF_AUTOINIT = 128 */
UBYTE rt_Version;
UBYTE rt_Type;
/* NT_DEVICE = 3 */
BYTE rt_Pri;
char *rt_Name;
char *rt_IdString;
APTR rt_Init;
};
前兩個欄位是特殊的,允許在 ROM 掃描期間找到該結構。它們是官方的 68000 “非法指令”,操作碼 0x4AFC,後面跟著指向該指令的指標。下一個欄位透過指向掃描應該繼續的地址來加快 ROM 掃描速度,通常在由此 RomTag 標記的區域的末尾。
"rt_Version" 欄位具有熟悉的版本號,"rt_Type" 欄位對於我們的應用程式而言是 NT_DEVICE。“rt_Pri”欄位與“rt_Version”欄位一起,決定了在出現這種情況時,將接受哪個具有相同名稱的多個模組。 "rt_Name" 和 "rt_IdString" 欄位標識模組;它們應該設定為前面討論過的裝置名稱和 IdString,用於裝置節點。
由 RomTag 標記的東西被稱為“駐留模組”,它們的初始化方式如下
InitResident(resident, segList)
A1 D1
這裡,“resident”指向 RomTag,“segList”標識與其關聯的程式碼(這是可選的)。
InitResident() 函式首先檢查 RomTag 中是否設定了 RTF_AUTOINIT 標誌。如果沒有,它只是用 A0 作為“segList”傳遞的值呼叫 "rt_Init" 指向的例程,然後返回。
如果設定了 RTF_AUTOINIT 標誌,則該函式將為您完成所有裝置初始化工作。在這種情況下,"rt_Init" 指向一個包含四個長字的資料表,其中包含用於 "MakeLibrary" 函式的這些引數
- dataSize - vectors - structure - init
InitResident 使用這些值加上強制性的 "segList" 引數呼叫 MakeLibrary 函式。除非 MakeLibrary() 返回空值,否則新的庫節點現在將根據節點中的 "ln_Type" 欄位新增到適當的系統列表中。如果它是 NT_DEVICE,則該節點將新增到裝置列表中,這就是我們想要的。
您現在應該有足夠的知識來建立一個標準裝置標頭檔案和初始化函式,並瞭解康懋達示例裝置驅動程式中的那個函式。
1.4. ROM 中的裝置
ROM 中的裝置是在啟動時對整個 ROM 進行 RomTag 結構掃描時找到的。找到的 RomTag 將新增到 ExecBase 中的駐留模組列表中。所有設定了 RTF_COLDSTART 標誌的模組都將使用 InitResident() 自動啟動。
模組可以透過一種我在這裡不描述的機制進行“ramkick”。這會導致它們在復位後存活下來,在 RAM 中被找到,並在冷啟動時與 ROM 模組一起初始化。這就是 RAD:可引導 RAM 磁碟的工作原理。
1.5. 磁碟上的裝置
磁碟上的裝置駐留在 DEVS:目錄中。它們是標準的物件模組,並使用 LoadSeg() 載入。載入後,會掃描 seglist 的第一個塊以查詢 RomTag,並使用 InitResident() 初始化裝置。在構建磁碟駐留裝置時,請注意連結器不要在程式碼的前面新增一個虛擬塊(舊版本的 BLINK 會這樣做),因為這會導致無法找到您的 RomTag。
1.6. 跳轉向量校驗和
庫結構中的“lib_Sum”欄位可以用來儲存跳轉向量的校驗和,以防止意外或未經授權的修改。“LIBF_SUMUSED”標誌位表示應執行此操作。“LIBF_CHANGED”標誌位表示跳轉向量已修改,校驗和無效。以下函式實現了這種機制
SumLibrary(library)
A1
它執行以下操作
IF the LIBF_SUMUSED flag is set THEN
Forbid()
IF the LIBF_CHANGED flag is set THEN
Compute checksum and store in lib_Sum
Clear the LIBF_CHANGED flag
ELSE
IF the lib_Sum field is zero THEN
Compute checksum and store in lib_Sum
ELSE
Compute checksum
IF checksum does not match lib_Sum THEN
Put up recoverable alert #81000003
Store new checksum in lib_Sum
ENDIF
ENDIF
ENDIF
Permit()
ENDIF
因此,在建立新的裝置節點時,最好設定 LIBF_SUMUSED 和 LIBF_CHANGED 位,以便第一次呼叫 SumLibrary() 更新校驗和,而以後的呼叫會檢測對跳轉向量的修改。
系統在 SetFunction() 呼叫中設定 LIBF_CHANGED 位並呼叫 SumLibrary(),以保持校驗和有效。
2. 裝置 I/O 協議
本節介紹裝置載入和初始化後的介面。
2.1. I/O 請求結構
系統透過使用“I/O 請求”結構與裝置驅動程式通訊。以下是這種結構的常用版本,但“io_Error”欄位之後的欄位都是裝置驅動程式特定的,可以省略或重新定義。
struct IOStdReq {
struct Message {
struct Node {
struct Node *ln_Succ;
struct Node *ln_Pred;
UBYTE ln_Type;
/* NT_MESSAGE = 5
NT_REPLYMSG = 7 */
BYTE ln_Pri;
char *ln_Name;
} mn_Node;
struct MsgPort *mn_ReplyPort;
UWORD mn_Length;
} io_Message;
struct Device *io_Device;
struct Unit *io_Unit;
UWORD io_Command;
UBYTE io_Flags;
/* IOF_QUICK = 1 */
BYTE io_Error;
ULONG io_Actual;
ULONG io_Length;
APTR io_Data;
ULONG io_Offset;
};
I/O 請求中的第一個欄位是一個標準的訊息結構,允許它在訊息埠上排隊並進行回覆。“io_Device”欄位指向裝置結構,“io_Unit”欄位指向裝置驅動程式為每個功能單元(例如磁碟驅動器)維護的“單元”結構。因此,這兩個欄位標識了 I/O 請求的目標。
2.2. 開啟和關閉裝置
要與裝置通訊,至少需要一個 I/O 請求,以及一個訊息埠,裝置可以將已完成處理的 I/O 請求返回到該埠。可以使用以下兩個 C 庫函式輕鬆建立這些內容
MyPort = CreatePort( name, pri ) struct MsgPort *MyPort; char *name; BYTE pri;
ioStdReq = CreateStdIO( ioReplyPort ) struct IOStdReq *ioStdReq; struct MsgPort *ioReplyPort;
第一個函式建立一個訊息埠和關聯的訊號位,並返回埠的地址。第二個函式建立一個 IOStdReq 結構,填寫回復埠的地址,並返回結構的地址。然後可以使用此函式開啟裝置驅動程式
error = OpenDevice(devName, unitNumber, iORequest, flags) D0 A0 D0 A1 D1
引數是裝置的名稱(例如“trackdisk.device”),單元號(例如內部驅動器的 0),已完成的 I/O 請求結構的地址,以及包含裝置驅動程式特殊資訊的“標誌”字。以下 C 程式碼將為內部驅動器開啟 trackdisk 驅動程式,然後再次關閉它
/*
* Do not supply a name for the message port, since supplying a name
* will put it on the public message port list.
*/
td_mp = CreatePort(0L,0L);
if(!td_mp) exit(99);
td_iob = CreateStdIO(td_mp);
if(!td_iob) {
DeletePort(td_mp);
exit(99);
}
if(OpenDevice("trackdisk.device",0L,td_iob,0L)) {
printf("Unable to open floppy device driver.\n");
DeleteStdIO(td_iob);
DeletePort(td_mp);
exit(99);
}
/*
* I/O request is ready for operations here.
*/
CloseDevice(td_iob); DeleteStdIO(td_iob); DeletePort(td_mp);
OpenDevice() 和 CloseDevice() 是 exec 函式,但它們會被名為“ramlib.library”的東西攔截,該東西處理磁碟駐留裝置和庫的載入和解除安裝。因此,OpenDevice() 呼叫可能會導致載入和初始化裝置,而 CloseDevice() 可能會導致解除安裝裝置。
OpenDevice() 最終會導致呼叫已初始化的裝置驅動程式的 Open() 函式(在跳轉向量中偏移 -6)。這在呼叫程式的上下文中(任務)發生。裝置驅動程式傳遞以下資訊
D0: Unit number D1: Flags A1: I/O request pointer A6: Device node pointer
exec 將已經清除 I/O 請求中的“io_Error”欄位,並將裝置節點指標儲存在“io_Device”欄位中。
如果 Open() 函式成功,它必須初始化“io_Unit”欄位,以便以後使用 I/O 請求。如果失敗,它必須將“io_Error”欄位設定為適當的數字。退出時“io_Error”欄位的值由 OpenDevice() 函式返回。
裝置驅動程式應使用其裝置節點中的“lib_OpenCnt”欄位跟蹤未完成開啟的數量。一些裝置驅動程式可以支援任意數量的併發開啟(例如磁碟驅動程式),而其他驅動程式可以在“獨佔訪問”模式下開啟(例如序列埠)。
單元號和標誌是兩個 32 位字,其格式由裝置驅動程式編寫者決定。有關單元編號方案的示例,請參閱包含檔案“devices/scsidisk.h”,有關標誌位的示例用法,請參閱“devices/serial.h”。
CloseDevice() 呼叫最終會導致呼叫裝置驅動程式的 Close() 函式,該函式在跳轉向量中偏移 -12。I/O 請求指標在 A1 中傳遞,裝置節點指標在 A6 中傳遞。返回值決定了對已關閉裝置的操作。如果為零,則保留該裝置。如果為非零,則表示裝置已執行延遲清除並希望解除安裝。在這種情況下,返回值是初始化時傳遞給裝置的“segList”引數。下一節將對此進行詳細介紹。
2.3. 清除裝置
可以使用此函式從系統中刪除裝置
error = RemDevice(device) D0 A1
如果系統記憶體不足並嘗試回收空間,則系統可能會自行發出此函式。無論哪種方式,都會透過裝置的 Expunge() 入口點(在跳轉向量中偏移 -18)呼叫裝置。暫存器 A6 設定為指向裝置節點。
裝置現在應該關閉其活動,即刪除中斷伺服器,釋放緩衝區等等。然後,它應該從裝置列表中取消連結其裝置節點,並釋放該節點,從而將事物恢復到在使用 InitResident() 啟動之前時的狀態。最後,它應該返回初始化時傳遞給它的“segList”引數。如果裝置來自磁碟,系統將使用此引數來解除安裝其程式碼。
如果裝置在收到 Expunge() 呼叫時不處於空閒狀態,它可以延遲操作。為此,它在庫結構中設定 LIBF_DELEXP 標誌,並返回零。這表示它將在最早的機會刪除自身。當最後一個 Close() 呼叫到達時,它將像上面描述的那樣關閉自身,並返回 segList 值以指示它已完成此操作,並且應該解除安裝。
2.4. 單元結構
許多裝置驅動程式管理多個功能單元。例如,trackdisk 驅動程式可以處理多達四個軟盤驅動器。首選方法是為每個功能單元(例如驅動器)使用單獨的“單元”結構。通常,單元結構至少包含以下內容
struct Unit {
struct MsgPort unit_MsgPort;
UBYTE unit_flags;
/* UNITF_ACTIVE = 1
UNITF_INTASK = 2 */
UBYTE unit_pad;
UWORD unit_OpenCnt;
};
當開啟裝置驅動程式時,它使用單元號選擇適當的單元結構,並將指向該結構的指標儲存在 I/O 請求中。稍後,它可以將待處理的 I/O 請求排隊到訊息埠上,以便由單元任務進行處理。
2.5. BeginIO 函式
所有 I/O 請求都透過其跳轉向量中的 BeginIO() 函式進入裝置驅動程式。裝置驅動程式在請求任務的上下文中進入,A6 指向裝置節點,A1 指向 I/O 請求結構。
通常,裝置驅動程式現在將使用 PutMsg() 將 I/O 請求排隊到訊息埠上(在單元結構中),以便由內部任務進行處理。然後它可以從 BeginIO() 函式返回。當 exec 檢查 I/O 請求是否已完成時,它會檢查其型別欄位,如果它是 NT_MESSAGE(如 PutMsg() 呼叫所致),它就知道它仍在進行中。最終,內部任務會收到 I/O 請求,對其進行操作,並執行 ReplyMsg()。這將 I/O 請求返回到呼叫者的回覆埠,並將它的型別設定為 NT_REPLYMSG,表明它已完成。
很明顯,裝置驅動程式不必完全遵循此過程。簡短的命令(例如檢查磁碟是否準備就緒)可以直接在呼叫者的上下文中完成。I/O 請求只需在最後使用 ReplyMsg() 返回即可,如果 BeginIO() 函式在 I/O 請求尚未完成的情況下返回,則它的型別欄位必須是除 NT_REPLYMSG 之外的其他東西。
IOF_QUICK 標誌表示了一種特殊的 I/O 處理情況。當它被設定時,表示請求者使用了 DoIO() 函式,因此在 I/O 完成之前不會執行任何操作。在這種情況下,裝置驅動程式可以在呼叫者的上下文中執行整個 I/O 操作,並立即返回。訊息傳遞和任務切換開銷被消除。當 BeginIO() 函式在 IOF_QUICK 位仍被設定的情況下返回時,表示 I/O 操作已完成。
如果裝置驅動程式看到 IOF_QUICK 標誌被設定,但無法內聯執行 I/O 處理,它可以簡單地清除該標誌,並像往常一樣使用 ReplyMsg() 返回 I/O 請求。
BeginIO() 函式對 I/O 請求中的命令和引數進行操作,並設定“io_Error”欄位以指示結果。exec I/O 函式將此欄位的值返回給呼叫者;BeginIO() 本身不返回值。“io_Error”設定為零表示沒有發生錯誤。
2.6. AbortIO 函式
一些裝置驅動程式操作,例如等待超時或序列埠上的輸入,可能需要在完成之前中止。AbortIO() 函式為此提供支援。裝置驅動程式透過其 AbortIO() 入口點進入,將要中止的 I/O 請求的地址放在 A1 中,裝置節點指標放在 A6 中。如果裝置驅動程式確定 I/O 請求確實正在進行,並且可以成功中止它,它將返回零,否則它將返回非零錯誤程式碼。
成功中止的 I/O 請求透過正常方法返回,即 ReplyMsg()。“io_Error”欄位應該指示它沒有正常完成。
2.7. exec I/O 函式
以下原語用於與裝置驅動程式通訊。假設驅動程式已使用 OpenDevice() 開啟,並且存在已初始化的 I/O 請求。
SendIO(iORequest)
A1
此函式呼叫裝置驅動程式中的 BeginIO() 入口點,IOF_QUICK 清除。這意味著裝置驅動程式應該使用 ReplyMsg() 返回 I/O 請求。
error = DoIO(iORequest) D0 A1
此函式呼叫裝置驅動程式中的 BeginIO() 入口點,IOF_QUICK 設定。如果裝置驅動程式保留 IOF_QUICK 設定,它將立即返回給呼叫者。返回值是 I/O 請求中“io_Error”欄位的擴充套件值。如果 IOF_QUICK 位被清除,它將繼續執行 WaitIO()。
error = WaitIO(iORequest) D0 A1
此函式等待 I/O 請求完成。如果 I/O 請求設定了 IOF_QUICK 標誌,它不可能正在進行,因此它會立即返回。否則,I/O 請求將使用 ReplyMsg() 返回,函式將按如下方式進行
Get the signal number from the I/O request's reply port
Disable()
WHILE iORequest->io_Message.mn_Node.ln_Type != NT_REPLYMSG DO
Wait() for the reply port signal
ENDWHILE
Unlink the I/O request from the reply port's message queue
Enable()
最後,它返回 I/O 請求中的“io_Error”,擴充套件到長字,與往常一樣。
result = CheckIO(iORequest) D0 A1
此函式檢查指定的 I/O 請求是否已完成。如果它的 IOF_QUICK 位被設定,或者它的型別是 NT_REPLYMSG,則認為它已完成。在這種情況下,函式將返回 I/O 請求的地址。如果 I/O 請求未完成,它將返回零。此函式不會從回覆埠中刪除 I/O 請求。
error = AbortIO(iORequest) D0 A1
此函式呼叫裝置驅動程式中的 AbortIO() 入口點,如前所述。
2.8. 直接呼叫 BeginIO
有一個操作是 DoIO() 和 SendIO() 無法處理的,那就是傳送一個設定了 IOF_QUICK 標誌但沒有等待它完成的 I/O 請求。這就是“儘快執行,但如果要花費一段時間,不要等待”。對於此操作,使用者必須手動設定 IOF_QUICK,然後透過其 BeginIO() 入口點直接呼叫裝置驅動程式。以下 C 庫函式將執行後者
void BeginIO( ioRequest ) struct IOStdReq *ioRequest;
由於 WaitIO() 和 CheckIO() 知道 IOF_QUICK 標誌,因此以這種方式提交的 I/O 請求可以照常由 WaitIO() 和 CheckIO() 處理。
2.8. 同步 I/O
當呼叫者不希望在 I/O 操作完成之前繼續執行時,就會進行同步 I/O。在這種情況下,呼叫者只需設定 I/O 請求並對其執行 DoIO()。當 DoIO() 返回時,I/O 就完成了。
2.9. 非同步 I/O
當呼叫者希望提交 I/O 請求並在其完成時執行其他操作時,就會進行非同步 I/O。如前所述,此類 I/O 透過 SendIO() 或 BeginIO() 提交。有多種方法可以等待非同步 I/O 請求完成,下面將討論其中幾種。
2.9.1. 等待特定 I/O 請求
使用 WaitIO() 函式等待單個特定 I/O 請求。該函式將在該特定 I/O 請求完成之前不會返回。
2.9.2. 等待特定回覆埠
如果有多個 I/O 請求處於掛起狀態並且所有請求都將到達同一個回覆埠,則可以使用以下方法
WHILE I/O requests are outstanding DO
WaitPort() on the port
GetMsg() on the port
ENDWHILE
2.9.3. 一般情況
通常,程式會等待多個事件中的一個發生,而這些事件的唯一共同點是它們將設定訊號。然後,程式必須從所有 I/O 回覆埠獲取訊號位,將它們與要等待的其他訊號位合併,並對結果進行 Wait()。當 Wait() 返回並設定對應於 I/O 回覆埠的訊號位時,可以嘗試在該埠上執行 GetMsg() 以檢視是否已完成 I/O 操作。透過這種方式,可以以任何順序一起處理 I/O 完成和其他事件。
3. 通用命令和錯誤號
本節列出了 Amiga 系統為所有型別的裝置驅動程式預定義的命令和錯誤編號。
3.1. 命令
命令編號儲存在 I/O 請求的 "io_Command" 欄位中。包含檔案 "exec/io.h" 中的通用命令編號如下
#define CMD_INVALID 0 #define CMD_RESET 1 #define CMD_READ 2 #define CMD_WRITE 3 #define CMD_UPDATE 4 #define CMD_CLEAR 5 #define CMD_STOP 6 #define CMD_START 7 #define CMD_FLUSH 8
#define CMD_NONSTD 9
可以看出,命令編號零無效,大於 9 的命令編號是自定義定義的。其餘命令如下
3.1.1. CMD_RESET
這會將裝置重置到已知的初始狀態。在此命令發出時未處理的掛起 I/O 請求應返回錯誤。
3.1.2. CMD_READ
這請求從單元上的位置 "io_Offset" 讀取 "io_Length" 個數據項,並存儲在呼叫者的記憶體中的 "io_Data" 中。實際傳輸的資料量在 "io_Actual" 中返回。具體細節取決於裝置型別。
3.1.3. CMD_WRITE
這請求將資料從呼叫者的記憶體傳輸到 I/O 單元。引數與 CMD_READ 相同。
3.1.4. CMD_UPDATE
這請求將所有已緩衝但未寫入的資料強制輸出到 I/O 單元。例如,它可能會將磁碟裝置中的軌道緩衝區寫入。
3.1.5. CMD_CLEAR
這請求使裝置為給定單元緩衝的所有資料無效。因此,例如,它將丟棄等待在序列輸入緩衝區中的資料。
3.1.6. CMD_STOP
這請求單元停止處理命令。在 CMD_STOP 發出時未處理的 I/O 請求將等待直到收到 CMD_START 或 CMD_RESET 或它們被中止。
3.1.7. CMD_START
這請求單元清除 CMD_STOP 條件並恢復處理命令。無論收到多少個 CMD_STOP,只需要一個 CMD_START。
3.1.8. CMD_FLUSH
這請求單元重新整理所有掛起的命令。所有已排隊但尚未處理的 I/O 請求應返回錯誤。
3.2. 錯誤編號
包含檔案 "exec/errors.h" 列出了以下標準錯誤編號。
#define IOERR_OPENFAIL -1 /* device/unit failed to open */ #define IOERR_ABORTED -2 /* request aborted */ #define IOERR_NOCMD -3 /* command not supported */ #define IOERR_BADLENGTH -4 /* not a valid length */
4. 磁碟裝置驅動程式
真實的裝置驅動程式通常支援比第 3 節中列出的更多命令。本節描述了磁碟裝置驅動程式使用的命令集。此資訊是編寫與現有檔案系統和磁碟修復程式相容的磁碟驅動程式所必需的。
所有 "正常" 磁碟命令使用前面給出的結構的 I/O 請求。不討論使用更大 I/O 請求的 "擴充套件" trackdisk 命令。
4.1. 命令
包含檔案 "devices/trackdisk.h" 列出了以下命令編號
#define TD_MOTOR (CMD_NONSTD+0) /* 9 */ #define TD_SEEK (CMD_NONSTD+1) /* 10 */ #define TD_FORMAT (CMD_NONSTD+2) /* 11 */ #define TD_REMOVE (CMD_NONSTD+3) /* 12 */ #define TD_CHANGENUM (CMD_NONSTD+4) /* 13 */ #define TD_CHANGESTATE (CMD_NONSTD+5) /* 14 */ #define TD_PROTSTATUS (CMD_NONSTD+6) /* 15 */ #define TD_RAWREAD (CMD_NONSTD+7) /* 16 */ #define TD_RAWWRITE (CMD_NONSTD+8) /* 17 */ #define TD_GETDRIVETYPE (CMD_NONSTD+9) /* 18 */ #define TD_GETNUMTRACKS (CMD_NONSTD+10) /* 19 */ #define TD_ADDCHANGEINT (CMD_NONSTD+11) /* 20 */ #define TD_REMCHANGEINT (CMD_NONSTD+12) /* 21 */
其中一些命令特定於可移動介質和/或 trackdisk.device,硬磁碟驅動程式不需要支援它們。後面會對此進行更多介紹。以下各節描述了各個命令。
4.1.1. CMD_READ 和 CMD_WRITE
這些是通用命令,但詳細資訊特定於裝置型別。對於磁碟裝置,"io_Length" 和 "io_Offset" 欄位必須是裝置支援的扇區大小的精確倍數。目前,這是 512 位元組。如果錯誤在操作中途停止,則 "io_Actual" 欄位可能會返回與 "io_Length" 不同的值。
4.1.2. TD_MOTOR
此命令開啟和關閉軟盤電機。"io_Length" 應設定為零以關閉,或設定為一以開啟。"io_Actual" 將返回電機的先前狀態。電機將在需要時自動開啟,但永遠不會再次關閉;因此,使用者必須發出 TD_MOTOR 命令將其關閉。
4.1.3. TD_SEEK
此命令將讀寫磁頭移動到 "io_Offset" 中指示的位置,該位置必須是扇區大小的精確倍數。其他命令中隱含了尋道,但可以使用此命令在讀寫操作之前提前 "預尋道" 裝置,如果提前知道所需位置。
4.1.4. TD_FORMAT
此命令採用與 CMD_WRITE 相同的引數並執行相同的操作,但有以下兩個例外
(a) The area to be written must be a formattable unit, i.e. in the
trackdisk.device it must be one or more complete tracks.
(b) The area is written with the data regardless of its previous
contents; it need not already be formatted.
這描述了 trackdisk.device 的觀察行為。"TD_FORMAT" 的自動文件與此資訊不一致。
硬磁碟驅動程式通常將 TD_FORMAT 實現為對 CMD_WRITE 的簡單呼叫。這允許使用 AmigaDOS "format" 命令對硬磁碟進行 "高階格式化"。
一些古老的 SASI/SCSI 磁碟控制器板有 "格式化軌道" 命令,因此專門針對它們的驅動程式可以像 trackdisk.device 一樣實現 TD_FORMAT。
4.1.5. TD_PROTSTATUS
如果驅動器中存在磁碟,則此命令返回 "io_Actual" 設定為零(如果磁碟可寫),非零(如果磁碟受保護)。如果不存在磁碟,則 I/O 請求將返回 "io_Error" 設定為 "TDERR_DiskChanged"。
4.1.6. TD_RAWREAD 和 TD_RAWWRITE
這些命令在 trackdisk.device 上讀取和寫入整個軌道的原始 MFM 資料。有關詳細資訊,請參閱自動文件。
4.1.7. TD_GETDRIVETYPE
此命令特定於 trackdisk.device,並在 "io_Actual" 中返回磁碟驅動器型別。這是以下之一
1: 3.5", 80 track drive 2: 5.25", 40 track drive
4.1.8. TD_GETNUMTRACKS
此命令特定於 trackdisk.device,並在 "io_Actual" 中返回磁碟單元上的軌道數量。
4.1.9. TD_CHANGESTATE
此命令檢查可移動介質驅動器中是否存在磁碟。它返回 "io_Actual" 設定為零(如果存在),非零(如果不存在)。
4.1.10. 其他命令
由於目前對此瞭解不足,因此不再介紹剩餘命令。讀者應參閱相應的自動文件。在某些情況下,需要使用 trackdisk.device 進行實驗才能獲得所有詳細資訊。
4.2. 錯誤編號
包含檔案 "devices/trackdis.h" 中列出了以下錯誤編號。
#define TDERR_NotSpecified 20 /* general catchall */ #define TDERR_NoSecHdr 21 /* couldn't even find a sector */ #define TDERR_BadSecPreamble 22 /* sector looked wrong */ #define TDERR_BadSecID 23 /* ditto */ #define TDERR_BadHdrSum 24 /* header had incorrect checksum */ #define TDERR_BadSecSum 25 /* data had incorrect checksum */ #define TDERR_TooFewSecs 26 /* couldn't find enough sectors */ #define TDERR_BadSecHdr 27 /* another "sector looked wrong" */ #define TDERR_WriteProt 28 /* can't write to a protected disk */ #define TDERR_DiskChanged 29 /* no disk in the drive */ #define TDERR_SeekError 30 /* couldn't find track 0 */ #define TDERR_NoMem 31 /* ran out of memory */ #define TDERR_BadUnitNum 32 /* asked for a unit > NUMUNITS */ #define TDERR_BadDriveType 33 /* not a drive that trackdisk groks */ #define TDERR_DriveInUse 34 /* someone else allocated the drive */ #define TDERR_PostReset 35 /* user hit reset; awaiting doom */
4.3. SCSIDIRECT 協議
大多數 SCSI 裝置驅動程式支援發出通用 SCSI 命令的附加命令。包含檔案 "devices/scsidisk.h" 中詳細介紹了這一點。
4.4. 最小磁碟命令子集
很明顯,要實現一個支援可移動介質的完整 Amiga 磁碟驅動程式是一項艱鉅的任務,我甚至不敢說自己瞭解所有必要的要求。但如果你想要的只是一個簡單的硬磁碟驅動程式,那麼只需要一個非常小的命令子集。首先,可以將 Expunge() 存根為始終返回零,表示延遲清除,永遠不會完成。可以將 AbortIO() 存根為始終返回非零結果,表示它失敗了。可以刪除單元結構;你可以在 I/O 請求的 "io_Unit" 欄位中儲存任何內容。在我的裝置驅動程式中,我只儲存了 SCSI 裝置編號和一些標誌。你可以將任何錯誤都返回 I/O 錯誤 #20(通用萬能錯誤),除了可能未實現的命令。無需進行快速 I/O;只需始終清除 IOF_QUICK 並忘記它。將所有 I/O 請求傳送到單個內部任務以進行處理。最後,可以按如下方式實現命令
CMD_READ, CMD_WRITE: implement fully
TD_FORMAT: same as CMD_WRITE
TD_GETDRIVETYPE: return 3.5" drive
CMD_RESET, CMD_UPDATE, CMD_CLEAR, CMD_STOP, CMD_START, CMD_FLUSH, TD_MOTOR, TD_SEEK, TD_REMOVE, TD_CHANGENUM, TD_CHANGESTATE, TD_PROTSTATUS, TD_ADDCHANGEINT, TD_REMCHANGEINT: clear "io_Actual" and return
Others: reject with IOERR_NOCMD
由此產生的驅動程式可以與快速和慢速檔案系統以及我嘗試過的所有磁碟編輯/修復實用程式完美配合。如果你從頭開始設定硬磁碟,你可以始終讓一個 hack 驅動程式執行,然後在硬磁碟執行的情況下編寫一個 "正確的" 驅動程式。
5. 參考資料
- 1.3 包含檔案
- 1.2 自動文件
- Commodore 的示例磁碟裝置驅動程式,來自 DevCon 88 磁碟。
Be sure you get the one which launches a task, not the older ones which launch a process. That doesn't work in an autoboot context.
- Exec 1.2 反彙編,由我自己完成。從 Fred Fish 磁碟 188 中獲取。
6. 修訂歷史
0.10 (90/05/20) 初始版本 0.11 (90/05/21) 校對,小幅更新,更美觀的格式 0.12 (90/05/21) 修正了一些拼寫錯誤