요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
========================
HCI backend for NFC Core
========================
- Author: Eric Lapuyade, Samuel Ortiz
- Contact: [email protected], [email protected]
General
-------
The HCI layer implements much of the ETSI TS 102 622 V10.2.0 specification. It
enables easy writing of HCI-based NFC drivers. The HCI layer runs as an NFC Core
backend, implementing an abstract nfc device and translating NFC Core API
to HCI commands and events.
HCI
---
HCI registers as an nfc device with NFC Core. Requests coming from userspace are
routed through netlink sockets to NFC Core and then to HCI. From this point,
they are translated in a sequence of HCI commands sent to the HCI layer in the
host controller (the chip). Commands can be executed synchronously (the sending
context blocks waiting for response) or asynchronously (the response is returned
from HCI Rx context).
HCI events can also be received from the host controller. They will be handled
and a translation will be forwarded to NFC Core as needed. There are hooks to
let the HCI driver handle proprietary events or override standard behavior.
HCI uses 2 execution contexts:
- one for executing commands : nfc_hci_msg_tx_work(). Only one command
can be executing at any given moment.
- one for dispatching received events and commands : nfc_hci_msg_rx_work().
HCI Session initialization
--------------------------
The Session initialization is an HCI standard which must unfortunately
support proprietary gates. This is the reason why the driver will pass a list
of proprietary gates that must be part of the session. HCI will ensure all
those gates have pipes connected when the hci device is set up.
In case the chip supports pre-opened gates and pseudo-static pipes, the driver
can pass that information to HCI core.
HCI Gates and Pipes
-------------------
A gate defines the 'port' where some service can be found. In order to access
a service, one must create a pipe to that gate and open it. In this
implementation, pipes are totally hidden. The public API only knows gates.
This is consistent with the driver need to send commands to proprietary gates
without knowing the pipe connected to it.
Driver interface
----------------
A driver is generally written in two parts : the physical link management and
the HCI management. This makes it easier to maintain a driver for a chip that
can be connected using various phy (i2c, spi, ...)
HCI Management
--------------
A driver would normally register itself with HCI and provide the following
entry points::
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() and close() shall turn the hardware on and off.
- hci_ready() is an optional entry point that is called right after the hci
session has been set up. The driver can use it to do additional initialization
that must be performed using HCI commands.
- xmit() shall simply write a frame to the physical link.
- start_poll() is an optional entrypoint that shall set the hardware in polling
mode. This must be implemented only if the hardware uses proprietary gates or a
mechanism slightly different from the HCI standard.
- dep_link_up() is called after a p2p target has been detected, to finish
the p2p connection setup with hardware parameters that need to be passed back
to nfc core.
- dep_link_down() is called to bring the p2p link down.
- target_from_gate() is an optional entrypoint to return the nfc protocols
corresponding to a proprietary gate.
- complete_target_discovered() is an optional entry point to let the driver
perform additional proprietary processing necessary to auto activate the
discovered target.
- im_transceive() must be implemented by the driver if proprietary HCI commands
are required to send data to the tag. Some tag types will require custom
commands, others can be written to using the standard HCI commands. The driver
can check the tag type and either do proprietary processing, or return 1 to ask
for standard processing. The data exchange command itself must be sent
asynchronously.
- tm_send() is called to send data in the case of a p2p connection
- check_presence() is an optional entry point that will be called regularly
by the core to check that an activated tag is still in the field. If this is
not implemented, the core will not be able to push tag_lost events to the user
space
- event_received() is called to handle an event coming from the chip. Driver
can handle the event or return 1 to let HCI attempt standard processing.
On the rx path, the driver is responsible to push incoming HCP frames to HCI
using nfc_hci_recv_frame(). HCI will take care of re-aggregation and handling
This must be done from a context that can sleep.
PHY Management
--------------
The physical link (i2c, ...) management is defined by the following structure::
struct nfc_phy_ops {
int (*write)(void *dev_id, struct sk_buff *skb);
int (*enable)(void *dev_id);
void (*disable)(void *dev_id);
};
enable():
turn the phy on (power on), make it ready to transfer data
disable():
turn the phy off
write():
Send a data frame to the chip. Note that to enable higher
layers such as an llc to store the frame for re-emission, this
function must not alter the skb. It must also not return a positive
result (return 0 for success, negative for failure).
Data coming from the chip shall be sent directly to nfc_hci_recv_frame().
LLC
---
Communication between the CPU and the chip often requires some link layer
protocol. Those are isolated as modules managed by the HCI layer. There are
currently two modules : nop (raw transfer) and shdlc.
A new llc must implement the following functions::
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():
allocate and init your private storage
deinit():
cleanup
start():
establish the logical connection
stop ():
terminate the logical connection
rcv_from_drv():
handle data coming from the chip, going to HCI
xmit_from_hci():
handle data sent by HCI, going to the chip
The llc must be registered with nfc before it can be used. Do that by
calling::
nfc_llc_register(const char *name, const struct nfc_llc_ops *ops);
Again, note that the llc does not handle the physical link. It is thus very
easy to mix any physical link with any llc for a given chip driver.
Included Drivers
----------------
An HCI based driver for an NXP PN544, connected through I2C bus, and using
shdlc is included.
Execution Contexts
------------------
The execution contexts are the following:
- IRQ handler (IRQH):
fast, cannot sleep. sends incoming frames to HCI where they are passed to
the current llc. In case of shdlc, the frame is queued in shdlc rx queue.
- SHDLC State Machine worker (SMW)
Only when llc_shdlc is used: handles shdlc rx & tx queues.
Dispatches HCI cmd responses.
- HCI Tx Cmd worker (MSGTXWQ)
Serializes execution of HCI commands.
Completes execution in case of response timeout.
- HCI Rx worker (MSGRXWQ)
Dispatches incoming HCI commands or events.
- Syscall context from a userspace call (SYSCALL)
Any entrypoint in HCI called from NFC Core
Workflow executing an HCI command (using shdlc)
-----------------------------------------------
Executing an HCI command can easily be performed synchronously using the
following API::
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)
The API must be invoked from a context that can sleep. Most of the time, this
will be the syscall context. skb will return the result that was received in
the response.
Internally, execution is asynchronous. So all this API does is to enqueue the
HCI command, setup a local wait queue on stack, and wait_event() for completion.
The wait is not interruptible because it is guaranteed that the command will
complete after some short timeout anyway.
MSGTXWQ context will then be scheduled and invoke nfc_hci_msg_tx_work().
This function will dequeue the next pending command and send its HCP fragments
to the lower layer which happens to be shdlc. It will then start a timer to be
able to complete the command with a timeout error if no response arrive.
SMW context gets scheduled and invokes nfc_shdlc_sm_work(). This function
handles shdlc framing in and out. It uses the driver xmit to send frames and
receives incoming frames in an skb queue filled from the driver IRQ handler.
SHDLC I(nformation) frames payload are HCP fragments. They are aggregated to
form complete HCI frames, which can be a response, command, or event.
HCI Responses are dispatched immediately from this context to unblock
waiting command execution. Response processing involves invoking the completion
callback that was provided by nfc_hci_msg_tx_work() when it sent the command.
The completion callback will then wake the syscall context.
It is also possible to execute the command asynchronously using this 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)
The workflow is the same, except that the API call returns immediately, and
the callback will be called with the result from the SMW context.
Workflow receiving an HCI event or command
------------------------------------------
HCI commands or events are not dispatched from SMW context. Instead, they are
queued to HCI rx_queue and will be dispatched from HCI rx worker
context (MSGRXWQ). This is done this way to allow a cmd or event handler
to also execute other commands (for example, handling the
NFC_HCI_EVT_TARGET_DISCOVERED event from PN544 requires to issue an
ANY_GET_PARAMETER to the reader A gate to get information on the target
that was discovered).
Typically, such an event will be propagated to NFC Core from MSGRXWQ context.
Error management
----------------
Errors that occur synchronously with the execution of an NFC Core request are
simply returned as the execution result of the request. These are easy.
Errors that occur asynchronously (e.g. in a background protocol handling thread)
must be reported such that upper layers don't stay ignorant that something
went wrong below and know that expected events will probably never happen.
Handling of these errors is done as follows:
- driver (pn544) fails to deliver an incoming frame: it stores the error such
that any subsequent call to the driver will result in this error. Then it
calls the standard nfc_shdlc_recv_frame() with a NULL argument to report the
problem above. shdlc stores a EREMOTEIO sticky status, which will trigger
SMW to report above in turn.
- SMW is basically a background thread to handle incoming and outgoing shdlc
frames. This thread will also check the shdlc sticky status and report to HCI
when it discovers it is not able to run anymore because of an unrecoverable
error that happened within shdlc or below. If the problem occurs during shdlc
connection, the error is reported through the connect completion.
- HCI: if an internal HCI error happens (frame is lost), or HCI is reported an
error from a lower layer, HCI will either complete the currently executing
command with that error, or notify NFC Core directly if no command is
executing.
- NFC Core: when NFC Core is notified of an error from below and polling is
active, it will send a tag discovered event with an empty tag list to the user
space to let it know that the poll operation will never be able to detect a
tag. If polling is not active and the error was sticky, lower levels will
return it at next invocation.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
NFC Core용 HCI backend
1-15Eric Lapuyade와 Samuel Ortiz가 작성한 HCI layer는 ETSI TS 102 622 V10.2.0의 많은 부분을 구현해 HCI 기반 NFC driver 작성을 단순화합니다.
NFC Core backend로서 abstract NFC device를 구현하고 NFC Core API를 HCI command와 event로 변환합니다.
Userspace API와 controller protocol 사이를 변환합니다.
========================
HCI backend for NFC Core
========================
- Author: Eric Lapuyade, Samuel Ortiz
- Contact: [email protected], [email protected]
General
-------
The HCI layer implements much of the ETSI TS 102 622 V10.2.0 specification. It
enables easy writing of HCI-based NFC drivers. The HCI layer runs as an NFC Core
backend, implementing an abstract nfc device and translating NFC Core API
to HCI commands and events.
Command·event와 실행 context
16-33HCI는 NFC Core에 NFC device로 등록됩니다. Userspace request는 netlink socket, NFC Core, HCI를 거쳐 host controller에 보낼 HCI command sequence로 변환됩니다.
Command는 sending context가 response를 기다리는 synchronous 방식 또는 HCI Rx context에서 response를 돌려주는 asynchronous 방식으로 실행할 수 있습니다.
Controller에서 받은 HCI event는 처리 후 필요에 따라 NFC Core로 변환해 전달합니다. Driver가 proprietary event를 처리하거나 standard behavior를 override할 hook도 있습니다.
Command 실행은 `nfc_hci_msg_tx_work()` context에서 serialize되어 한 번에 하나만 실행됩니다. 수신 command와 event dispatch는 `nfc_hci_msg_rx_work()` context가 담당합니다.
HCI
---
HCI registers as an nfc device with NFC Core. Requests coming from userspace are
routed through netlink sockets to NFC Core and then to HCI. From this point,
they are translated in a sequence of HCI commands sent to the HCI layer in the
host controller (the chip). Commands can be executed synchronously (the sending
context blocks waiting for response) or asynchronously (the response is returned
from HCI Rx context).
HCI events can also be received from the host controller. They will be handled
and a translation will be forwarded to NFC Core as needed. There are hooks to
let the HCI driver handle proprietary events or override standard behavior.
HCI uses 2 execution contexts:
- one for executing commands : nfc_hci_msg_tx_work(). Only one command
can be executing at any given moment.
- one for dispatching received events and commands : nfc_hci_msg_rx_work().
Session, gate, pipe
34-52HCI session initialization은 standard이지만 proprietary gate도 지원해야 합니다. Driver는 session에 포함할 proprietary gate 목록을 전달하고 HCI는 device setup 때 모든 gate에 pipe가 연결되도록 보장합니다.
Chip이 pre-opened gate와 pseudo-static pipe를 지원하면 driver가 이 정보도 HCI core에 전달합니다.
Gate는 service가 있는 port를 정의합니다. Service 접근에는 gate로 pipe를 만들고 열어야 하지만 이 구현은 pipe를 완전히 숨기고 public API에는 gate만 노출합니다. 따라서 driver는 연결된 pipe 번호를 몰라도 proprietary gate에 command를 보낼 수 있습니다.
Public API는 service gate만 다루고 core가 실제 pipe를 관리합니다.
HCI Session initialization
--------------------------
The Session initialization is an HCI standard which must unfortunately
support proprietary gates. This is the reason why the driver will pass a list
of proprietary gates that must be part of the session. HCI will ensure all
those gates have pipes connected when the hci device is set up.
In case the chip supports pre-opened gates and pseudo-static pipes, the driver
can pass that information to HCI core.
HCI Gates and Pipes
-------------------
A gate defines the 'port' where some service can be found. In order to access
a service, one must create a pipe to that gate and open it. In this
implementation, pipes are totally hidden. The public API only knows gates.
This is consistent with the driver need to send commands to proprietary gates
without knowing the pipe connected to it.
HCI driver management callback
53-124Driver는 physical link management와 HCI management 두 부분으로 나누는 것이 일반적입니다. 그러면 같은 chip을 I2C, SPI 등 여러 PHY에 연결하는 구현을 쉽게 유지할 수 있습니다.
`nfc_hci_ops.open/close`는 hardware power를 켜고 끕니다. Optional `hci_ready`는 session setup 직후 HCI command를 이용한 추가 초기화를 수행합니다. `xmit`은 physical link에 frame을 씁니다.
Optional `start_poll`은 proprietary gate나 standard와 다른 mechanism을 쓰는 hardware의 polling mode를 설정합니다. `dep_link_up/down`은 P2P target 연결을 완성하거나 내립니다.
`target_from_gate`는 proprietary gate의 NFC protocol을 반환하고 `complete_target_discovered`는 target auto-activation에 필요한 추가 proprietary 처리를 수행합니다.
`im_transceive`는 tag data에 proprietary HCI command가 필요할 때 구현합니다. Tag type을 확인해 직접 처리하거나 1을 반환해 standard 처리로 넘깁니다. Data exchange command는 asynchronous여야 합니다.
`tm_send`는 P2P data 전송, `check_presence`는 활성 tag가 field에 남아 있는지 주기적으로 확인합니다. 후자가 없으면 core가 userspace에 `tag_lost`를 보낼 수 없습니다.
`event_received`는 chip event를 처리하거나 1을 반환해 standard HCI 처리로 넘깁니다. Rx path의 driver는 incoming HCP frame을 sleep 가능한 context에서 `nfc_hci_recv_frame()`에 전달하고 HCI가 re-aggregation과 처리를 담당합니다.
Driver interface
----------------
A driver is generally written in two parts : the physical link management and
the HCI management. This makes it easier to maintain a driver for a chip that
can be connected using various phy (i2c, spi, ...)
HCI Management
--------------
A driver would normally register itself with HCI and provide the following
entry points::
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() and close() shall turn the hardware on and off.
- hci_ready() is an optional entry point that is called right after the hci
session has been set up. The driver can use it to do additional initialization
that must be performed using HCI commands.
- xmit() shall simply write a frame to the physical link.
- start_poll() is an optional entrypoint that shall set the hardware in polling
mode. This must be implemented only if the hardware uses proprietary gates or a
mechanism slightly different from the HCI standard.
- dep_link_up() is called after a p2p target has been detected, to finish
the p2p connection setup with hardware parameters that need to be passed back
to nfc core.
- dep_link_down() is called to bring the p2p link down.
- target_from_gate() is an optional entrypoint to return the nfc protocols
corresponding to a proprietary gate.
- complete_target_discovered() is an optional entry point to let the driver
perform additional proprietary processing necessary to auto activate the
discovered target.
- im_transceive() must be implemented by the driver if proprietary HCI commands
are required to send data to the tag. Some tag types will require custom
commands, others can be written to using the standard HCI commands. The driver
can check the tag type and either do proprietary processing, or return 1 to ask
for standard processing. The data exchange command itself must be sent
asynchronously.
- tm_send() is called to send data in the case of a p2p connection
- check_presence() is an optional entry point that will be called regularly
by the core to check that an activated tag is still in the field. If this is
not implemented, the core will not be able to push tag_lost events to the user
space
- event_received() is called to handle an event coming from the chip. Driver
can handle the event or return 1 to let HCI attempt standard processing.
On the rx path, the driver is responsible to push incoming HCP frames to HCI
using nfc_hci_recv_frame(). HCI will take care of re-aggregation and handling
This must be done from a context that can sleep.
PHY management
125-147I2C 같은 physical link는 `nfc_phy_ops`의 `write`, `enable`, `disable`로 정의합니다.
`enable`은 PHY power를 켜고 data transfer 준비를 하며 `disable`은 끕니다. `write`는 chip에 data frame을 보냅니다.
LLC 등 상위 layer가 frame을 retransmission용으로 보관할 수 있도록 `write`는 `skb`를 변경하면 안 됩니다. 성공은 0, 실패는 음수를 반환하며 양수는 반환하지 않습니다.
Chip에서 온 data는 `nfc_hci_recv_frame()`으로 직접 보냅니다.
PHY Management
--------------
The physical link (i2c, ...) management is defined by the following structure::
struct nfc_phy_ops {
int (*write)(void *dev_id, struct sk_buff *skb);
int (*enable)(void *dev_id);
void (*disable)(void *dev_id);
};
enable():
turn the phy on (power on), make it ready to transfer data
disable():
turn the phy off
write():
Send a data frame to the chip. Note that to enable higher
layers such as an llc to store the frame for re-emission, this
function must not alter the skb. It must also not return a positive
result (return 0 for success, negative for failure).
Data coming from the chip shall be sent directly to nfc_hci_recv_frame().
LLC module과 포함 driver
148-194CPU와 chip 통신에는 link-layer protocol이 필요한 경우가 많으며 HCI layer가 module로 분리해 관리합니다. 현재 `nop` raw transfer와 `shdlc` 두 module이 있습니다.
새 LLC는 private storage를 준비하는 `init`, 정리하는 `deinit`, logical connection을 여닫는 `start/stop`, driver에서 HCI 방향의 `rcv_from_drv`, HCI에서 chip 방향의 `xmit_from_hci`를 구현합니다.
사용 전에 `nfc_llc_register(name, ops)`로 NFC에 등록해야 합니다. LLC는 physical link를 처리하지 않으므로 같은 chip driver에서 어떤 PHY와도 조합하기 쉽습니다.
포함된 예시는 I2C에 연결되고 SHDLC를 사용하는 NXP PN544 HCI driver입니다.
Protocol framing과 physical transport를 독립 조합합니다.
LLC
---
Communication between the CPU and the chip often requires some link layer
protocol. Those are isolated as modules managed by the HCI layer. There are
currently two modules : nop (raw transfer) and shdlc.
A new llc must implement the following functions::
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():
allocate and init your private storage
deinit():
cleanup
start():
establish the logical connection
stop ():
terminate the logical connection
rcv_from_drv():
handle data coming from the chip, going to HCI
xmit_from_hci():
handle data sent by HCI, going to the chip
The llc must be registered with nfc before it can be used. Do that by
calling::
nfc_llc_register(const char *name, const struct nfc_llc_ops *ops);
Again, note that the llc does not handle the physical link. It is thus very
easy to mix any physical link with any llc for a given chip driver.
Included Drivers
----------------
An HCI based driver for an NXP PN544, connected through I2C bus, and using
shdlc is included.
IRQ·SHDLC·HCI worker context
195-222IRQ handler, IRQH는 빠르고 sleep할 수 없습니다. Incoming frame을 HCI의 현재 LLC로 보내며 SHDLC에서는 Rx queue에 넣습니다.
SHDLC State Machine worker, SMW는 SHDLC 사용 시 Rx/Tx queue와 HCI command response dispatch를 처리합니다.
HCI Tx worker, MSGTXWQ는 command 실행을 serialize하고 response timeout이면 완료 처리합니다. HCI Rx worker, MSGRXWQ는 incoming HCI command와 event를 dispatch합니다.
SYSCALL은 userspace call에서 NFC Core를 거쳐 HCI entry point를 호출하는 context입니다.
Execution Contexts
------------------
The execution contexts are the following:
- IRQ handler (IRQH):
fast, cannot sleep. sends incoming frames to HCI where they are passed to
the current llc. In case of shdlc, the frame is queued in shdlc rx queue.
- SHDLC State Machine worker (SMW)
Only when llc_shdlc is used: handles shdlc rx & tx queues.
Dispatches HCI cmd responses.
- HCI Tx Cmd worker (MSGTXWQ)
Serializes execution of HCI commands.
Completes execution in case of response timeout.
- HCI Rx worker (MSGRXWQ)
Dispatches incoming HCI commands or events.
- Syscall context from a userspace call (SYSCALL)
Any entrypoint in HCI called from NFC Core
HCI command 실행 흐름
223-265`nfc_hci_send_cmd()`는 sleep 가능한 context, 대개 syscall에서 synchronous API로 호출하며 response `skb`를 돌려줍니다.
내부 실행은 asynchronous입니다. API는 command를 enqueue하고 stack의 local wait queue를 설정한 뒤 `wait_event()`로 완료를 기다립니다. 짧은 timeout 안에 반드시 끝나므로 wait는 interruptible이 아닙니다.
MSGTXWQ의 `nfc_hci_msg_tx_work()`가 command를 dequeue하고 HCP fragment를 SHDLC에 보내며 response timeout timer를 시작합니다.
SMW의 `nfc_shdlc_sm_work()`가 framing과 Rx/Tx queue를 처리합니다. SHDLC I-frame payload인 HCP fragment를 response·command·event의 완전한 HCI frame으로 aggregate합니다.
Response는 SMW에서 즉시 completion callback으로 dispatch되어 대기 중인 syscall을 깨웁니다.
`nfc_hci_execute_cmd_async()`는 같은 흐름을 사용하지만 즉시 반환하고 SMW context가 결과 callback을 호출합니다.
Synchronous API 뒤에서 worker와 SHDLC가 비동기로 실행됩니다.
Workflow executing an HCI command (using shdlc)
-----------------------------------------------
Executing an HCI command can easily be performed synchronously using the
following API::
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)
The API must be invoked from a context that can sleep. Most of the time, this
will be the syscall context. skb will return the result that was received in
the response.
Internally, execution is asynchronous. So all this API does is to enqueue the
HCI command, setup a local wait queue on stack, and wait_event() for completion.
The wait is not interruptible because it is guaranteed that the command will
complete after some short timeout anyway.
MSGTXWQ context will then be scheduled and invoke nfc_hci_msg_tx_work().
This function will dequeue the next pending command and send its HCP fragments
to the lower layer which happens to be shdlc. It will then start a timer to be
able to complete the command with a timeout error if no response arrive.
SMW context gets scheduled and invokes nfc_shdlc_sm_work(). This function
handles shdlc framing in and out. It uses the driver xmit to send frames and
receives incoming frames in an skb queue filled from the driver IRQ handler.
SHDLC I(nformation) frames payload are HCP fragments. They are aggregated to
form complete HCI frames, which can be a response, command, or event.
HCI Responses are dispatched immediately from this context to unblock
waiting command execution. Response processing involves invoking the completion
callback that was provided by nfc_hci_msg_tx_work() when it sent the command.
The completion callback will then wake the syscall context.
It is also possible to execute the command asynchronously using this 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)
The workflow is the same, except that the API call returns immediately, and
the callback will be called with the result from the SMW context.
수신 event·command workflow
266-278Incoming HCI command나 event는 SMW에서 직접 dispatch하지 않고 HCI `rx_queue`에 넣어 MSGRXWQ가 처리합니다.
이 분리는 event handler가 다른 HCI command도 실행할 수 있게 합니다. 예를 들어 PN544의 `NFC_HCI_EVT_TARGET_DISCOVERED` 처리는 reader A gate에 `ANY_GET_PARAMETER`를 보내 target 정보를 가져와야 합니다.
일반적으로 event는 MSGRXWQ context에서 NFC Core로 전파됩니다.
SHDLC worker와 event handler context를 분리해 handler의 command 실행을 허용합니다.
Workflow receiving an HCI event or command
------------------------------------------
HCI commands or events are not dispatched from SMW context. Instead, they are
queued to HCI rx_queue and will be dispatched from HCI rx worker
context (MSGRXWQ). This is done this way to allow a cmd or event handler
to also execute other commands (for example, handling the
NFC_HCI_EVT_TARGET_DISCOVERED event from PN544 requires to issue an
ANY_GET_PARAMETER to the reader A gate to get information on the target
that was discovered).
Typically, such an event will be propagated to NFC Core from MSGRXWQ context.
동기·비동기 error 전파
279-311NFC Core request 실행과 동시에 발생한 error는 request result로 반환합니다. Background protocol thread의 asynchronous error는 upper layer가 예정된 event가 오지 않을 사실을 알 수 있도록 명시적으로 전파해야 합니다.
PN544 driver가 incoming frame 전달에 실패하면 error를 저장해 이후 call이 같은 error를 반환하게 하고 `nfc_shdlc_recv_frame(NULL)`로 위에 알립니다. SHDLC는 `EREMOTEIO` sticky status를 저장합니다.
SMW는 sticky status를 확인해 unrecoverable error로 더 실행할 수 없으면 HCI에 보고합니다. SHDLC connection 중이면 connect completion으로 전달합니다.
HCI internal frame loss나 lower-layer error는 현재 command를 해당 error로 완료하거나, 실행 중 command가 없으면 NFC Core에 직접 통지합니다.
Polling 중 NFC Core가 error를 받으면 빈 tag list의 discovered event를 userspace에 보내 더 이상 tag를 찾을 수 없음을 알립니다. Polling이 아니고 sticky error라면 다음 lower-layer call에서 반환됩니다.
Driver에서 sticky 상태를 거쳐 HCI와 NFC Core, userspace까지 손실 없이 전파합니다.
Error management
----------------
Errors that occur synchronously with the execution of an NFC Core request are
simply returned as the execution result of the request. These are easy.
Errors that occur asynchronously (e.g. in a background protocol handling thread)
must be reported such that upper layers don't stay ignorant that something
went wrong below and know that expected events will probably never happen.
Handling of these errors is done as follows:
- driver (pn544) fails to deliver an incoming frame: it stores the error such
that any subsequent call to the driver will result in this error. Then it
calls the standard nfc_shdlc_recv_frame() with a NULL argument to report the
problem above. shdlc stores a EREMOTEIO sticky status, which will trigger
SMW to report above in turn.
- SMW is basically a background thread to handle incoming and outgoing shdlc
frames. This thread will also check the shdlc sticky status and report to HCI
when it discovers it is not able to run anymore because of an unrecoverable
error that happened within shdlc or below. If the problem occurs during shdlc
connection, the error is reported through the connect completion.
- HCI: if an internal HCI error happens (frame is lost), or HCI is reported an
error from a lower layer, HCI will either complete the currently executing
command with that error, or notify NFC Core directly if no command is
executing.
- NFC Core: when NFC Core is notified of an error from below and polling is
active, it will send a tag discovered event with an empty tag list to the user
space to let it know that the poll operation will never be able to detect a
tag. If polling is not active and the error was sticky, lower levels will
return it at next invocation.
요약과 해설
nfc-hci.rst:1-311HCI backend는 NFC Core request를 gate 기반 command로 변환하고 PHY·LLC와 worker context를 분리해 synchronous·asynchronous 실행과 error 전파를 처리합니다.