NFC Core 的 HCI 後端¶
作者:Eric Lapuyade, Samuel Ortiz
概述¶
HCI 層實現了 ETSI TS 102 622 V10.2.0 規範的大部分內容。 它使編寫基於 HCI 的 NFC 驅動程式變得容易。 HCI 層作為 NFC Core 後端執行,實現抽象的 nfc 裝置並將 NFC Core API 轉換為 HCI 命令和事件。
HCI¶
HCI 向 NFC Core 註冊為 nfc 裝置。 來自使用者空間的請求透過 netlink 套接字路由到 NFC Core,然後路由到 HCI。 從這一點開始,它們被轉換為一系列 HCI 命令,傳送到主機控制器(晶片)中的 HCI 層。 命令可以同步執行(傳送上下文阻塞等待響應)或非同步執行(響應從 HCI Rx 上下文返回)。 也可以從主機控制器接收 HCI 事件。 將對其進行處理,並根據需要將翻譯轉發到 NFC Core。 有一些鉤子可以讓 HCI 驅動程式處理專有事件或覆蓋標準行為。 HCI 使用 2 個執行上下文
一個用於執行命令:nfc_hci_msg_tx_work()。 在任何給定時刻只能執行一個命令。
一個用於分派接收到的事件和命令:nfc_hci_msg_rx_work()。
HCI 會話初始化¶
會話初始化是一個 HCI 標準,不幸的是,它必須支援專有閘道器。 這就是驅動程式將傳遞一個專有閘道器列表的原因,該列表必須是會話的一部分。 當設定 hci 裝置時,HCI 將確保所有這些閘道器都連線了管道。 如果晶片支援預先開啟的閘道器和偽靜態管道,則驅動程式可以將該資訊傳遞給 HCI core。
HCI 閘道器和管道¶
閘道器定義了可以找到某些服務的“埠”。 為了訪問服務,必須建立一個到該閘道器的管道並將其開啟。 在此實現中,管道完全隱藏。 公共 API 僅知道閘道器。 這與驅動程式需要將命令傳送到專有閘道器而不知道連線到它的管道是一致的。
驅動程式介面¶
驅動程式通常分為兩部分編寫:物理鏈路管理和 HCI 管理。 這使得維護一個可以透過各種 phy (i2c, spi, ...) 連線的晶片的驅動程式變得更容易。
HCI 管理¶
驅動程式通常會向 HCI 註冊自身並提供以下入口點
struct nfc_hci_ops {
int (*open)(struct nfc_hci_dev *hdev);
void (*close)(struct nfc_hci_dev *hdev);
int (*hci_ready) (struct nfc_hci_dev *hdev);
int (*xmit) (struct nfc_hci_dev *hdev, struct sk_buff *skb);
int (*start_poll) (struct nfc_hci_dev *hdev,
u32 im_protocols, u32 tm_protocols);
int (*dep_link_up)(struct nfc_hci_dev *hdev, struct nfc_target *target,
u8 comm_mode, u8 *gb, size_t gb_len);
int (*dep_link_down)(struct nfc_hci_dev *hdev);
int (*target_from_gate) (struct nfc_hci_dev *hdev, u8 gate,
struct nfc_target *target);
int (*complete_target_discovered) (struct nfc_hci_dev *hdev, u8 gate,
struct nfc_target *target);
int (*im_transceive) (struct nfc_hci_dev *hdev,
struct nfc_target *target, struct sk_buff *skb,
data_exchange_cb_t cb, void *cb_context);
int (*tm_send)(struct nfc_hci_dev *hdev, struct sk_buff *skb);
int (*check_presence)(struct nfc_hci_dev *hdev,
struct nfc_target *target);
int (*event_received)(struct nfc_hci_dev *hdev, u8 gate, u8 event,
struct sk_buff *skb);
};
open() 和 close() 應該開啟和關閉硬體。
hci_ready() 是一個可選的入口點,在 hci 會話設定完成後立即呼叫。 驅動程式可以使用它來執行必須使用 HCI 命令執行的其他初始化。
xmit() 應該簡單地將幀寫入物理鏈路。
start_poll() 是一個可選的入口點,應該將硬體設定為輪詢模式。 只有當硬體使用專有閘道器或與 HCI 標準略有不同的機制時,才必須實現此功能。
dep_link_up() 在檢測到 p2p 目標後呼叫,以完成 p2p 連線設定,並將需要傳遞迴 nfc core 的硬體引數傳遞給它。
dep_link_down() 被呼叫以關閉 p2p 鏈路。
target_from_gate() 是一個可選的入口點,用於返回與專有閘道器相對應的 nfc 協議。
complete_target_discovered() 是一個可選的入口點,允許驅動程式執行自動啟用已發現目標所需的其他專有處理。
如果需要專有 HCI 命令才能將資料傳送到標籤,則驅動程式必須實現 im_transceive()。 某些標籤型別將需要自定義命令,其他標籤型別可以使用標準 HCI 命令寫入。 驅動程式可以檢查標籤型別,然後執行專有處理,或返回 1 以請求標準處理。 資料交換命令本身必須非同步傳送。
tm_send() 在 p2p 連線的情況下被呼叫以傳送資料
check_presence() 是一個可選的入口點,核心將定期呼叫它來檢查已啟用的標籤是否仍在範圍內。 如果未實現此功能,核心將無法將 tag_lost 事件推送到使用者空間
event_received() 被呼叫來處理來自晶片的事件。 驅動程式可以處理該事件或返回 1 以讓 HCI 嘗試標準處理。
在 rx 路徑上,驅動程式負責使用 nfc_hci_recv_frame() 將傳入的 HCP 幀推送到 HCI。 HCI 將負責重新聚合和處理。 這必須從可以休眠的上下文中完成。
PHY 管理¶
物理鏈路 (i2c, ...) 管理由以下結構定義
struct nfc_phy_ops {
int (*write)(void *dev_id, struct sk_buff *skb);
int (*enable)(void *dev_id);
void (*disable)(void *dev_id);
};
- enable()
開啟 phy(電源),使其準備好傳輸資料
- disable()
關閉 phy
- write()
將資料幀傳送到晶片。 請注意,為了使更高的層(例如 llc)能夠儲存幀以進行重新發送,此函式不得更改 skb。 它也不得返回肯定的結果(成功返回 0,失敗返回負數)。
來自晶片的資料應直接傳送到 nfc_hci_recv_frame()。
LLC¶
CPU 和晶片之間的通訊通常需要一些鏈路層協議。 這些協議被隔離為由 HCI 層管理的模組。 目前有兩個模組:nop(原始傳輸)和 shdlc。 新的 llc 必須實現以下功能
struct nfc_llc_ops {
void *(*init) (struct nfc_hci_dev *hdev, xmit_to_drv_t xmit_to_drv,
rcv_to_hci_t rcv_to_hci, int tx_headroom,
int tx_tailroom, int *rx_headroom, int *rx_tailroom,
llc_failure_t llc_failure);
void (*deinit) (struct nfc_llc *llc);
int (*start) (struct nfc_llc *llc);
int (*stop) (struct nfc_llc *llc);
void (*rcv_from_drv) (struct nfc_llc *llc, struct sk_buff *skb);
int (*xmit_from_hci) (struct nfc_llc *llc, struct sk_buff *skb);
};
- init()
分配和初始化您的私有儲存
- deinit()
清理
- start()
建立邏輯連線
- stop ()
終止邏輯連線
- rcv_from_drv()
處理來自晶片併發送到 HCI 的資料
- xmit_from_hci()
處理由 HCI 傳送併發送到晶片的資料
llc 必須在使用前向 nfc 註冊。 透過呼叫以下命令來執行此操作
nfc_llc_register(const char *name, const struct nfc_llc_ops *ops);
同樣,請注意,llc 不處理物理鏈路。 因此,對於給定的晶片驅動程式,將任何物理鏈路與任何 llc 混合在一起非常容易。
包含的驅動程式¶
包含一個基於 HCI 的 NXP PN544 驅動程式,透過 I2C 匯流排連線,並使用 shdlc。
執行上下文¶
執行上下文如下: - IRQ 處理程式 (IRQH):快速,無法休眠。 將傳入的幀傳送到 HCI,在 HCI 中,它們被傳遞到當前的 llc。 對於 shdlc,幀在 shdlc rx 佇列中排隊。
SHDLC 狀態機工作程式 (SMW)
僅當使用 llc_shdlc 時:處理 shdlc rx & tx 佇列。
分派 HCI cmd 響應。
HCI Tx Cmd 工作程式 (MSGTXWQ)
序列化 HCI 命令的執行。
在響應超時的情況下完成執行。
HCI Rx 工作程式 (MSGRXWQ)
分派傳入的 HCI 命令或事件。
來自使用者空間呼叫的 Syscall 上下文 (SYSCALL)
從 NFC Core 呼叫的 HCI 中的任何入口點
執行 HCI 命令的工作流程(使用 shdlc)¶
可以使用以下 API 輕鬆地同步執行 HCI 命令
int nfc_hci_send_cmd (struct nfc_hci_dev *hdev, u8 gate, u8 cmd,
const u8 *param, size_t param_len, struct sk_buff **skb)
API 必須從可以休眠的上下文中呼叫。 大多數情況下,這將是 syscall 上下文。 skb 將返回在響應中收到的結果。
在內部,執行是非同步的。 因此,此 API 所做的只是將 HCI 命令排隊,在堆疊上設定一個本地等待佇列,並wait_event()等待完成。 該等待不可中斷,因為可以保證命令在一些短超時後無論如何都會完成。
然後將排程 MSGTXWQ 上下文並呼叫 nfc_hci_msg_tx_work()。 此函式將取消排隊下一個掛起的命令並將其 HCP 片段傳送到較低層,較低層恰好是 shdlc。 然後,它將啟動一個計時器,以便能夠在沒有收到響應的情況下完成命令並出現超時錯誤。
SMW 上下文被排程並呼叫 nfc_shdlc_sm_work()。 此函式處理 shdlc 的輸入和輸出成幀。 它使用驅動程式 xmit 傳送幀,並在從驅動程式 IRQ 處理程式填充的 skb 佇列中接收傳入的幀。 SHDLC I(nformation) 幀有效負載是 HCP 片段。 它們被聚合以形成完整的 HCI 幀,這些幀可以是響應、命令或事件。
HCI 響應會立即從該上下文中分派以解除阻塞等待命令的執行。 響應處理涉及呼叫 nfc_hci_msg_tx_work() 在傳送命令時提供的完成回撥。 然後,完成回撥將喚醒 syscall 上下文。
也可以使用此 API 非同步執行該命令
static int nfc_hci_execute_cmd_async(struct nfc_hci_dev *hdev, u8 pipe, u8 cmd,
const u8 *param, size_t param_len,
data_exchange_cb_t cb, void *cb_context)
工作流程是相同的,只不過 API 呼叫會立即返回,並且將使用來自 SMW 上下文的結果呼叫回撥。
接收 HCI 事件或命令的工作流程¶
HCI 命令或事件不是從 SMW 上下文分派的。 相反,它們被排隊到 HCI rx_queue,並將從 HCI rx 工作程式上下文 (MSGRXWQ) 分派。 這樣做是為了允許 cmd 或事件處理程式也執行其他命令(例如,處理來自 PN544 的 NFC_HCI_EVT_TARGET_DISCOVERED 事件需要向讀取器 A 閘道器發出 ANY_GET_PARAMETER 以獲取有關已發現目標的資訊)。
通常,此類事件將從 MSGRXWQ 上下文傳播到 NFC Core。
錯誤管理¶
與 NFC Core 請求的執行同步發生的錯誤只是作為請求的執行結果返回。 這些很容易。
非同步發生的錯誤(例如,在後臺協議處理執行緒中)必須報告,以便上層不會不知道下面出了問題,並且知道預期的事件可能永遠不會發生。 這些錯誤的處理方式如下
驅動程式 (pn544) 無法傳遞傳入的幀:它儲存該錯誤,以便隨後對驅動程式的任何呼叫都將導致該錯誤。 然後,它使用 NULL 引數呼叫標準 nfc_shdlc_recv_frame() 以向上報告該問題。 shdlc 儲存 EREMOTEIO 粘滯狀態,這將觸發 SMW 依次向上報告。
SMW 基本上是一個後臺執行緒,用於處理傳入和傳出的 shdlc 幀。 該執行緒還將檢查 shdlc 粘滯狀態,並在發現由於 shdlc 或下面的原因發生的不可恢復錯誤而無法再執行時報告給 HCI。 如果問題發生在 shdlc 連線期間,則透過連線完成來報告該錯誤。
HCI:如果發生內部 HCI 錯誤(幀丟失),或者 HCI 從較低層報告錯誤,則 HCI 將使用該錯誤完成當前正在執行的命令,或者如果沒有執行命令,則直接通知 NFC Core。
NFC Core:當 NFC Core 從下面收到錯誤通知並且輪詢處於活動狀態時,它將向用戶空間傳送一個帶有空標籤列表的標籤發現事件,以使其知道輪詢操作將永遠無法檢測到標籤。 如果輪詢未啟用並且該錯誤是粘滯的,則較低級別將在下次呼叫時返回它。