適用於 S/390 和 zSeries 的 Linux¶
通用裝置支援 (CDS) 裝置驅動程式 I/O 支援例程
- 作者
Ingo Adlung
Cornelia Huck
版權所有,IBM Corp. 1999-2002
簡介¶
本文件介紹了 Linux/390 的通用裝置支援例程。與其他硬體架構不同,ESA/390 定義了一種統一的 I/O 訪問方法。這減輕了裝置驅動程式的負擔,因為他們不必處理不同的匯流排型別、輪詢與中斷處理、共享與非共享中斷處理、DMA 與埠 I/O (PIO) 以及其他更多硬體功能。但是,這意味著要麼每個裝置驅動程式都需要自己實現硬體 I/O 連線功能,要麼作業系統提供一種統一的方法來訪問硬體,從而提供每個裝置驅動程式都需要提供的所有功能。
本文件並不打算詳細解釋 ESA/390 硬體架構的每個細節。這些資訊可以從 ESA/390 操作原理手冊(IBM 表單編號 SA22-7201)中獲得。
為了為 ESA/390 I/O 介面構建通用裝置支援,引入了一個功能層,該層為硬體提供通用 I/O 訪問方法。
通用裝置支援層包括下面定義的 I/O 支援例程。 其中一些實現了通用的 Linux 裝置驅動程式介面,而另一些是 ESA/390 平臺特有的。
- 注意
為了為 S/390 編寫驅動程式,您還需要檢視 S/390 驅動程式模型介面 中描述的介面。
從 2.4 移植驅動程式的注意事項
主要變化是
這些函式使用 ccw_device 而不是 irq(子通道)。
所有驅動程式都必須定義一個 ccw_driver(參見 S/390 驅動程式模型介面)和關聯的函式。
request_irq()和free_irq()不再由驅動程式完成。oper_handler 被 ccw_driver 的 probe() 和 set_online() 函式(某種程度上)取代。
not_oper_handler 被 ccw_driver 的 remove() 和 set_offline() 函式(某種程度上)取代。
通道裝置層已消失。
必須調整中斷處理程式以使用 ccw_device 作為引數。 此外,它們不返回 devstat,而是返回 irb。
在啟動 io 之前,必須透過
ccw_device_set_options()設定選項。驅動程式發出通道程式並處理中斷本身,而不是呼叫 read_dev_chars()/read_conf_data()。
- ccw_device_get_ciw()
從擴充套件感知資料獲取命令。
- ccw_device_start()、ccw_device_start_timeout()、ccw_device_start_key()、ccw_device_start_key_timeout()
啟動 I/O 請求。
- ccw_device_resume()
恢復通道程式執行。
- ccw_device_halt()
終止裝置上處理的當前 I/O 請求。
- do_IRQ()
通用中斷例程。 每當向系統呈現 I/O 中斷時,中斷入口例程都會呼叫此函式。 do_IRQ() 例程確定中斷狀態,並根據使用 do_IO() 啟動 I/O 請求期間定義的規則(標誌)呼叫特定於裝置的中斷處理程式。
接下來的章節將更詳細地描述 do_IRQ() 以外的函式。 由於 do_IRQ() 僅從 Linux/390 第一級中斷處理程式呼叫,並且不包括裝置驅動程式可呼叫介面,因此未對其進行描述。 相反,do_IO() 的功能描述也描述了裝置特定中斷處理程式的輸入。
- 注意
所有解釋也適用於 64 位架構 s390x。
Linux/390 裝置驅動程式的通用裝置支援 (CDS)¶
常規資訊¶
以下章節描述了 Linux/390 通用裝置支援 (CDS) 提供的 I/O 相關介面例程,以便在 IBM ESA/390 硬體平臺上實現特定於裝置的驅動程式實現。 這些介面旨在提供每個裝置驅動程式實現所需的功能,以允許在 ESA/390 平臺上驅動特定的硬體裝置。 一些介面例程是 Linux/390 特有的,而另一些介面例程也可以在其他 Linux 平臺實現中找到。 各種函式原型、資料宣告和宏定義可以在架構特定的 C 標頭檔案 linux/arch/s390/include/asm/irq.h 中找到。
CDS 介面概念概述¶
與其他硬體平臺不同,ESA/390 架構沒有定義由特定中斷控制器管理的中斷線路和可能允許或不允許共享中斷、DMA 處理等的匯流排系統。相反,ESA/390 架構實現了一個所謂的通道子系統,該子系統提供了物理連線到系統的裝置的統一檢視。儘管 ESA/390 硬體平臺知道各種不同的外圍裝置連線,例如磁碟裝置(又名 DASD)、磁帶、通訊控制器等,但所有這些裝置都可以透過定義明確的訪問方法訪問,並且它們以統一的方式呈現 I/O 完成:I/O 中斷。每個裝置都透過所謂的子通道向系統唯一標識,其中 ESA/390 架構允許連線 64k 個裝置。
然而,Linux 最初是在 Intel PC 架構上構建的,它具有兩個級聯的 8259 可程式設計中斷控制器 (PIC),最多允許 15 條不同的中斷線路。 連線到此類系統的所有裝置共享這 15 箇中斷級別。 連線到 ISA 匯流排系統的裝置不得共享中斷級別(又名 IRQ),因為 ISA 匯流排基於邊沿觸發中斷。 MCA、EISA、PCI 和其他匯流排系統基於電平觸發中斷,因此允許共享 IRQ。 但是,如果多個裝置透過相同的(共享)IRQ 呈現其硬體狀態,則作業系統必須呼叫在此 IRQ 上註冊的每個裝置驅動程式,以確定擁有引發中斷的裝置的裝置驅動程式。
在核心 2.4 之前,Linux/390 過去透過 IRQ(子通道)提供介面。 對於通用 I/O 層的內部使用,這些仍然存在。 但是,裝置驅動程式應該僅透過 ccw_device 使用新的呼叫介面。
在其啟動期間,Linux/390 系統會檢查外圍裝置。 每個裝置都由 ESA/390 通道子系統透過所謂的子通道唯一定義。 雖然子通道號是系統生成的,但每個子通道也採用使用者定義的屬性,即所謂的裝置號。 子通道號和裝置號都不能超過 65535。在 sysfs 初始化期間,會收集有關控制單元型別和裝置型別的資訊,這些型別意味著特定的 I/O 命令(通道命令字 - CCW)以操作裝置。 裝置驅動程式可以在其初始化步驟中檢索此組硬體資訊,以使用儲存在 struct ccw_device 中的資訊識別它們支援的裝置。 此方法意味著 Linux/390 不需要探測空閒(未啟用)的中斷請求線 (IRQ) 來驅動其裝置。 在適用的情況下,裝置驅動程式可以使用發出 READ DEVICE CHARACTERISTICS ccw 來檢索其線上例程中的裝置特性。
為了便於 I/O 啟動,CDS 層提供了一個 ccw_device_start() 介面,該介面採用裝置特定的通道程式(一個或多個 CCW)作為輸入,設定所需的特定於架構的控制塊,並代表裝置驅動程式啟動 I/O 請求。 ccw_device_start() 例程允許指定它是否期望 CDS 層在每次觀察到中斷時通知裝置驅動程式,或者僅使用最終狀態通知。 有關更多詳細資訊,請參見 ccw_device_start()。 裝置驅動程式絕不能自己發出 ESA/390 I/O 命令,而必須使用 Linux/390 CDS 介面。
為了取消長時間執行的 I/O 請求,CDS 層提供了 ccw_device_halt() 函式。 有些裝置需要最初發出 HALT SUBCHANNEL (HSCH) 命令,而沒有掛起的 I/O 請求。 此函式也由 ccw_device_halt() 涵蓋。
get_ciw() - 獲取命令資訊字
此呼叫使裝置驅動程式能夠從擴充套件的 SenseID 資料中獲取有關支援的命令的資訊。
struct ciw *
ccw_device_get_ciw(struct ccw_device *cdev, __u32 cmd);
cdev |
要檢索命令的 ccw_device。 |
cmd |
要檢索的命令型別。 |
NULL |
沒有可用的擴充套件資料、無效裝置或未找到命令。 |
!NULL |
請求的命令。 |
ccw_device_start() - Initiate I/O Request
ccw_device_start() 例程是 I/O 請求前端處理器。 所有裝置驅動程式 I/O 請求都必須使用此例程發出。 裝置驅動程式絕不能自己發出 ESA/390 I/O 命令。 相反,ccw_device_start() 例程提供了驅動任意裝置所需的所有介面。
此描述還涵蓋了傳遞給裝置驅動程式中斷處理程式的狀態資訊,因為它與呼叫 ccw_device_start() 時與關聯的 I/O 請求一起定義的規則(標誌)相關。
int ccw_device_start(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
unsigned long flags);
int ccw_device_start_timeout(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
unsigned long flags,
int expires);
int ccw_device_start_key(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
__u8 key,
unsigned long flags);
int ccw_device_start_key_timeout(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
__u8 key,
unsigned long flags,
int expires);
cdev |
ccw_device I/O 的目標 |
cpa |
通道程式的邏輯起始地址 |
user_intparm |
使用者特定的中斷資訊; 將返回到裝置驅動程式的中斷處理程式。 允許裝置驅動程式將中斷與特定的 I/O 請求關聯。 |
lpm |
定義用於特定 I/O 請求的通道路徑。 值為 0 將使 cio 使用 opm。 |
key |
用於 I/O 的儲存金鑰(對於使用儲存金鑰 != 預設金鑰的儲存進行操作非常有用) |
flag |
定義要對 I/O 處理執行的操作 |
expires |
jiffies 中的超時值。 在此之後,通用 I/O 層將終止正在執行的程式,並以 ERR_PTR(-ETIMEDOUT) 作為 irb 呼叫中斷處理程式。 |
可能的標誌值是
DOIO_ALLOW_SUSPEND |
通道程式可能會被掛起 |
DOIO_DENY_PREFETCH |
不允許 CCW 預取; 通常,這意味著通道程式可能會被修改 |
DOIO_SUPPRESS_INTER |
不要在中間狀態下呼叫處理程式 |
cpa 引數指向通道程式的第一個格式 1 CCW
struct ccw1 {
__u8 cmd_code;/* command code */
__u8 flags; /* flags, like IDA addressing, etc. */
__u16 count; /* byte count */
__u32 cda; /* data address */
} __attribute__ ((packed,aligned(8)));
定義了以下 CCW 標誌值
CCW_FLAG_DC |
資料鏈接 |
CCW_FLAG_CC |
命令連結 |
CCW_FLAG_SLI |
禁止不正確的長度 |
CCW_FLAG_SKIP |
跳過 |
CCW_FLAG_PCI |
PCI |
CCW_FLAG_IDA |
間接定址 |
CCW_FLAG_SUSPEND |
掛起 |
透過 ccw_device_set_options(),裝置驅動程式可以為裝置指定以下選項
DOIO_EARLY_NOTIFICATION |
允許提前中斷通知 |
DOIO_REPORT_ALL |
報告所有中斷條件 |
ccw_device_start() 函式返回
0 |
成功完成或請求成功啟動 |
-EBUSY |
該裝置當前正在處理之前的 I/O 請求,或者該裝置上存在掛起的狀態。 |
-ENODEV |
cdev 無效,該裝置未執行或 ccw_device 未聯機。 |
當 I/O 請求完成時,CDS 第一級中斷處理程式會將狀態累積到 struct irb 中,然後呼叫裝置中斷處理程式。 intparm 欄位將包含裝置驅動程式已與特定 I/O 請求關聯的值。 如果識別出掛起的裝置狀態,則 intparm 將設定為 0(零)。 這可能在 I/O 啟動期間發生,或者由於警報狀態通知而延遲發生。 在任何情況下,此狀態都與當前(最後一個)I/O 請求無關。 在延遲狀態通知的情況下,不會顯示特殊中斷來指示 I/O 完成,因為 I/O 請求從未啟動,即使 ccw_device_start() 成功完成並返回。
irb 可能包含一個錯誤值,裝置驅動程式應該首先檢查此錯誤值
-ETIMEDOUT |
通用 I/O 層在指定的超時值後終止了請求 |
-EIO |
通用 I/O 層由於錯誤狀態而終止了請求 |
如果 irb 中擴充套件狀態字 (esw) 中的併發感知標誌已設定,則 esw 中 erw.scnt 欄位描述了擴充套件控制字 irb->scsw.ecw[] 中可用的特定於裝置的感知位元組數。 裝置驅動程式本身不需要裝置感知。
裝置中斷處理程式可以使用以下定義來調查感知位元組 0 中編碼的主要單元檢查源
SNS0_CMD_REJECT |
0x80 |
SNS0_INTERVENTION_REQ |
0x40 |
SNS0_BUS_OUT_CHECK |
0x20 |
SNS0_EQUIPMENT_CHECK |
0x10 |
SNS0_DATA_CHECK |
0x08 |
SNS0_OVERRUN |
0x04 |
SNS0_INCOMPL_DOMAIN |
0x01 |
根據裝置狀態,可以將這些值中的多個一起設定。 有關詳細資訊,請參閱裝置特定的文件。
irb->scsw.cstat 欄位提供(累積的)子通道狀態
SCHN_STAT_PCI |
程式控制的中斷 |
SCHN_STAT_INCORR_LEN |
長度不正確 |
SCHN_STAT_PROG_CHECK |
程式檢查 |
SCHN_STAT_PROT_CHECK |
保護檢查 |
SCHN_STAT_CHN_DATA_CHK |
通道資料檢查 |
SCHN_STAT_CHN_CTRL_CHK |
通道控制檢查 |
SCHN_STAT_INTF_CTRL_CHK |
介面控制檢查 |
SCHN_STAT_CHAIN_CHECK |
連結檢查 |
irb->scsw.dstat 欄位提供(累積的)裝置狀態
DEV_STAT_ATTENTION |
注意 |
DEV_STAT_STAT_MOD |
狀態修改器 |
DEV_STAT_CU_END |
控制單元結束 |
DEV_STAT_BUSY |
忙 |
DEV_STAT_CHN_END |
通道結束 |
DEV_STAT_DEV_END |
裝置結束 |
DEV_STAT_UNIT_CHECK |
單元檢查 |
DEV_STAT_UNIT_EXCEP |
單元異常 |
有關單個標誌含義的詳細資訊,請參閱 ESA/390 操作原理手冊。
使用說明
ccw_device_start() 必須在停用狀態下呼叫,並保持 ccw 裝置鎖。
裝置驅動程式可以從其中斷處理程式中發出下一個 ccw_device_start() 呼叫。 不需要安排一個 bottom-half,除非需要安排一個非確定性地長時間執行的錯誤恢復過程或類似過程。 在 I/O 處理期間,Linux/390 通用 I/O 裝置驅動程式已經獲得了 IRQ 鎖,即處理程式在呼叫 ccw_device_start() 時不得嘗試再次獲取它,否則我們將陷入死鎖狀態!
如果裝置驅動程式依賴於在啟動下一個 I/O 請求之前完成的 I/O 請求,則可以透過將 NoOp I/O 命令 CCW_CMD_NOOP 連結到提交的 CCW 鏈的末尾來減少 I/O 處理開銷。 這將強制通道結束和裝置結束狀態一起呈現,並伴隨單箇中斷。 但是,應該謹慎使用此方法,因為它意味著通道將保持忙碌,無法處理同一通道上其他裝置的 I/O 請求。 因此,例如,讀取命令絕不應使用此技術,因為結果無論如何都會透過單箇中斷呈現。
為了最大限度地減少 I/O 開銷,裝置驅動程式僅應在裝置可以報告在裝置結束之前裝置驅動程式迫切依賴的中間中斷資訊的情況下使用 DOIO_REPORT_ALL。 在這種情況下,所有 I/O 中斷都會呈現給裝置驅動程式,直到識別出最終狀態。
如果裝置能夠從非同步呈現的 I/O 錯誤中恢復,則可以使用 DOIO_EARLY_NOTIFICATION 標誌執行重疊 I/O。 雖然某些裝置始終將通道結束和裝置結束一起報告,並伴隨單箇中斷,但其他裝置在通道準備好進行下一個 I/O 請求時呈現主要狀態(通道結束),並在裝置上完成資料傳輸時呈現次要狀態(裝置結束)。
上面的標誌允許利用此功能,例如,對於可以處理網路上丟失的資料的通訊裝置,以允許增強的 I/O 處理。
除非通道子系統隨時呈現輔助狀態中斷,否則利用此功能將導致僅將主要狀態中斷呈現給裝置驅動程式,同時執行重疊 I/O。 當呈現沒有錯誤的輔助狀態(警報狀態)時,這表示自上次輔助(最終)狀態以來發出的所有重疊 ccw_device_start() 請求已成功完成。
打算在通道命令字 (CCW) 上設定掛起標誌的通道程式必須使用 DOIO_ALLOW_SUSPEND 選項啟動 I/O 操作,否則掛起標誌將導致通道程式檢查。 在通道程式被掛起時,通道子系統將生成一箇中間中斷。
ccw_device_resume() - 恢復通道程式執行
如果裝置驅動程式選擇透過在特定的 CCW 上設定 CCW 掛起標誌來掛起當前通道程式的執行,則通道程式執行將被掛起。為了恢復通道程式執行,CIO 層提供了 ccw_device_resume() 例程。
int ccw_device_resume(struct ccw_device *cdev);
cdev |
請求恢復操作的 ccw_device |
ccw_device_resume() 函式返回
0 |
掛起的通道程式已恢復 |
-EBUSY |
狀態掛起 |
-ENODEV |
cdev 無效或非操作子通道 |
-EINVAL |
恢復函式不適用 |
-ENOTCONN |
沒有 I/O 請求等待完成 |
使用說明
請檢視 ccw_device_start() 用法說明,瞭解有關掛起通道程式的更多詳細資訊。
ccw_device_halt() - 停止 I/O 請求處理
有時,裝置驅動程式可能需要停止長時間執行的通道程式處理,或者裝置可能需要最初發出停止子通道 (HSCH) I/O 命令。為此,提供了 ccw_device_halt() 命令。
必須在停用狀態下呼叫 ccw_device_halt(),並且持有 ccw 裝置鎖。
int ccw_device_halt(struct ccw_device *cdev,
unsigned long intparm);
cdev |
請求停止操作的 ccw_device |
intparm |
中斷引數;該值僅在沒有未完成的 I/O 時使用,否則返回與 I/O 請求關聯的 intparm |
ccw_device_halt() 函式返回
0 |
請求已成功啟動 |
-EBUSY |
裝置當前正忙,或狀態掛起。 |
-ENODEV |
cdev 無效。 |
-EINVAL |
裝置未執行或 ccw 裝置未聯機。 |
使用說明
裝置驅動程式可以透過編寫在其末尾透過通道內轉移 (TIC) 命令 (CCW_CMD_TIC) 迴圈回到其開頭的通道程式來編寫永無止境的通道程式。通常,這是由網路裝置驅動程式透過設定 PCI CCW 標誌 (CCW_FLAG_PCI) 來執行的。一旦執行此 CCW,就會生成程式控制中斷 (PCI)。然後,裝置驅動程式可以執行適當的操作。在中斷對網路裝置的未完成讀取(無論是否帶有 PCI 標誌)之前,需要 ccw_device_halt() 結束未完成的操作。
ccw_device_clear() - Terminage I/O Request Processing
為了終止子通道上的所有 I/O 處理,使用了清除子通道 (CSCH) 命令。可以透過 ccw_device_clear() 發出。
必須在停用狀態下呼叫 ccw_device_clear(),並且持有 ccw 裝置鎖。
int ccw_device_clear(struct ccw_device *cdev, unsigned long intparm);
cdev |
請求清除操作的 ccw_device |
intparm |
中斷引數(請參閱 |
ccw_device_clear() 函式返回
0 |
請求已成功啟動 |
-ENODEV |
cdev 無效 |
-EINVAL |
裝置未執行或 ccw 裝置未聯機。 |
其他支援例程¶
本章介紹 Linux/390 裝置驅動程式程式設計環境中要使用的各種例程。
get_ccwdev_lock()
獲取裝置特定鎖的地址。然後將其用於 spin_lock() / spin_unlock() 呼叫中。
__u8 ccw_device_get_path_mask(struct ccw_device *cdev);
獲取當前可用於 cdev 的路徑的掩碼。