Aros/開發者/文件/裝置
來自 http://wandel.ca/homepage/execdis/devices_doc.txt
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 在多工、訊息傳遞級別的程式設計。它假設您擁有來自康懋達的 autodocs、包含檔案和示例裝置驅動程式。這些文件中的許多資訊都被複制了,但並非全部。
我希望我對這個主題的瞭解比現在更多,並且我是一個更好的作家。唉,我沒有,我也不是。本文件包含編寫磁碟駐留、自動載入和可清除裝置驅動程式所需的所有內容,但您可能需要閱讀多次。要編寫一個好的可移動介質磁碟驅動程式,您需要進行一些額外的研究。
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() 函式的更多資訊(它非常靈活,並且可以執行超出上述建議的功能),您應該閱讀其 autodoc,並可能還需要閱讀反彙編程式碼。
最後,我們有“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,後跟指向該指令的指標。下一個欄位透過指向掃描應繼續的地址(通常在由此 RomTag 標記的區域的末尾)來加快 ROM 掃描速度。
“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 標誌。如果沒有,它只需呼叫“rt_Init”指向的例程,A0 包含作為“segList”傳遞的值,然後返回。
如果設定了 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() 更新校驗和,並且後續呼叫檢測跳轉向量修改。
系統設定 LIBF_CHANGED 位並呼叫 SumLibrary() 作為 SetFunction() 呼叫的部分,以保持校驗和有效。
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 請求結構的地址,以及包含裝置驅動程式特殊資訊的“flags”字。以下 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 請求
等待單個特定 I/O 請求可以使用 WaitIO() 函式完成。在該特定 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 請求。“擴充套件”trackdisk 命令(使用更大的 I/O 請求)不在此處討論。
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”應設定為零以將其關閉,或設定為 1 以將其開啟。“io_Actual”將返回電機先前的狀態。電機將在需要時自動開啟,但永遠不會再次關閉;因此,使用者必須發出 TD_MOTOR 命令將其關閉。
4.1.3. TD_SEEK
此命令將讀/寫磁頭移動到“io_Offset”中指示的位置,“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
生成的驅動程式與快速和慢速檔案系統以及我嘗試過的所有磁碟編輯/修復實用程式完美配合。如果您要從頭開始啟動硬磁碟,則始終可以使一個臨時驅動程式工作,然後在硬磁碟執行時編寫一個“正確的”驅動程式。
5. 參考
- 1.3 包含檔案
- 1.2 自動文件
- 來自 DevCon 88 磁碟的 Commodore 示例磁碟裝置驅動程式。
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) 修正了一些錯別字