요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
============================
PCI Peer-to-Peer DMA Support
============================
The PCI bus has pretty decent support for performing DMA transfers
between two devices on the bus. This type of transaction is henceforth
called Peer-to-Peer (or P2P). However, there are a number of issues that
make P2P transactions tricky to do in a perfectly safe way.
One of the biggest issues is that PCI doesn't require forwarding
transactions between hierarchy domains, and in PCIe, each Root Port
defines a separate hierarchy domain. To make things worse, there is no
simple way to determine if a given Root Complex supports this or not.
(See PCIe r4.0, sec 1.3.1). Therefore, as of this writing, the kernel
only supports doing P2P when the endpoints involved are all behind the
same PCI bridge, as such devices are all in the same PCI hierarchy
domain, and the spec guarantees that all transactions within the
hierarchy will be routable, but it does not require routing
between hierarchies.
The second issue is that to make use of existing interfaces in Linux,
memory that is used for P2P transactions needs to be backed by struct
pages. However, PCI BARs are not typically cache coherent so there are
a few corner case gotchas with these pages so developers need to
be careful about what they do with them.
Driver Writer's Guide
=====================
In a given P2P implementation there may be three or more different
types of kernel drivers in play:
* Provider - A driver which provides or publishes P2P resources like
memory or doorbell registers to other drivers.
* Client - A driver which makes use of a resource by setting up a
DMA transaction to or from it.
* Orchestrator - A driver which orchestrates the flow of data between
clients and providers.
In many cases there could be overlap between these three types (i.e.,
it may be typical for a driver to be both a provider and a client).
For example, in the NVMe Target Copy Offload implementation:
* The NVMe PCI driver is both a client, provider and orchestrator
in that it exposes any CMB (Controller Memory Buffer) as a P2P memory
resource (provider), it accepts P2P memory pages as buffers in requests
to be used directly (client) and it can also make use of the CMB as
submission queue entries (orchestrator).
* The RDMA driver is a client in this arrangement so that an RNIC
can DMA directly to the memory exposed by the NVMe device.
* The NVMe Target driver (nvmet) can orchestrate the data from the RNIC
to the P2P memory (CMB) and then to the NVMe device (and vice versa).
This is currently the only arrangement supported by the kernel but
one could imagine slight tweaks to this that would allow for the same
functionality. For example, if a specific RNIC added a BAR with some
memory behind it, its driver could add support as a P2P provider and
then the NVMe Target could use the RNIC's memory instead of the CMB
in cases where the NVMe cards in use do not have CMB support.
Provider Drivers
----------------
A provider simply needs to register a BAR (or a portion of a BAR)
as a P2P DMA resource using :c:func:`pci_p2pdma_add_resource()`.
This will register struct pages for all the specified memory.
After that it may optionally publish all of its resources as
P2P memory using :c:func:`pci_p2pmem_publish()`. This will allow
any orchestrator drivers to find and use the memory. When marked in
this way, the resource must be regular memory with no side effects.
For the time being this is fairly rudimentary in that all resources
are typically going to be P2P memory. Future work will likely expand
this to include other types of resources like doorbells.
Client Drivers
--------------
A client driver only has to use the mapping API :c:func:`dma_map_sg()`
and :c:func:`dma_unmap_sg()` functions as usual, and the implementation
will do the right thing for the P2P capable memory.
Orchestrator Drivers
--------------------
The first task an orchestrator driver must do is compile a list of
all client devices that will be involved in a given transaction. For
example, the NVMe Target driver creates a list including the namespace
block device and the RNIC in use. If the orchestrator has access to
a specific P2P provider to use it may check compatibility using
:c:func:`pci_p2pdma_distance()` otherwise it may find a memory provider
that's compatible with all clients using :c:func:`pci_p2pmem_find()`.
If more than one provider is supported, the one nearest to all the clients will
be chosen first. If more than one provider is an equal distance away, the
one returned will be chosen at random (it is not an arbitrary but
truly random). This function returns the PCI device to use for the provider
with a reference taken and therefore when it's no longer needed it should be
returned with pci_dev_put().
Once a provider is selected, the orchestrator can then use
:c:func:`pci_alloc_p2pmem()` and :c:func:`pci_free_p2pmem()` to
allocate P2P memory from the provider. :c:func:`pci_p2pmem_alloc_sgl()`
and :c:func:`pci_p2pmem_free_sgl()` are convenience functions for
allocating scatter-gather lists with P2P memory.
Struct Page Caveats
-------------------
Driver writers should be very careful about not passing these special
struct pages to code that isn't prepared for it. At this time, the kernel
interfaces do not have any checks for ensuring this. This obviously
precludes passing these pages to userspace.
P2P memory is also technically IO memory but should never have any side
effects behind it. Thus, the order of loads and stores should not be important
and ioreadX(), iowriteX() and friends should not be necessary.
P2P DMA Support Library
=======================
.. kernel-doc:: drivers/pci/p2pdma.c
:export:
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
PCI P2P DMA의 범위와 안전 제약
1-27PCI 버스는 버스에 연결된 두 장치 사이의 DMA 전송을 상당히 잘 지원하며, 이 문서는 이를 Peer-to-Peer 또는 P2P transaction이라고 부릅니다. 다만 모든 토폴로지에서 안전하게 사용할 수 있는 것은 아닙니다.
가장 큰 문제는 PCI가 서로 다른 hierarchy domain 사이의 transaction forwarding을 요구하지 않는다는 점입니다. PCIe에서는 각 Root Port가 별도 hierarchy domain을 정의하고, 특정 Root Complex가 domain 간 forwarding을 지원하는지 간단히 판별할 방법도 없습니다. 이 제약은 PCIe r4.0 section 1.3.1과 관련됩니다.
따라서 이 문서 시점의 커널은 모든 endpoint가 동일 PCI bridge 뒤에 있는 경우만 P2P로 지원합니다. 같은 hierarchy 안의 transaction은 specification이 routable임을 보장하지만, 서로 다른 hierarchy 사이의 routing은 보장하지 않습니다.
두 번째 문제는 기존 Linux 인터페이스를 사용하려면 P2P memory가 `struct page`로 뒷받침돼야 한다는 점입니다. PCI BAR는 일반적으로 cache coherent하지 않으므로 이 특수 page를 일반 RAM page처럼 전달하거나 접근할 때 corner case가 생깁니다.
동일 bridge 아래의 endpoint는 같은 hierarchy domain에 있으므로 routing이 보장됩니다.
.. SPDX-License-Identifier: GPL-2.0
============================
PCI Peer-to-Peer DMA Support
============================
The PCI bus has pretty decent support for performing DMA transfers
between two devices on the bus. This type of transaction is henceforth
called Peer-to-Peer (or P2P). However, there are a number of issues that
make P2P transactions tricky to do in a perfectly safe way.
One of the biggest issues is that PCI doesn't require forwarding
transactions between hierarchy domains, and in PCIe, each Root Port
defines a separate hierarchy domain. To make things worse, there is no
simple way to determine if a given Root Complex supports this or not.
(See PCIe r4.0, sec 1.3.1). Therefore, as of this writing, the kernel
only supports doing P2P when the endpoints involved are all behind the
same PCI bridge, as such devices are all in the same PCI hierarchy
domain, and the spec guarantees that all transactions within the
hierarchy will be routable, but it does not require routing
between hierarchies.
The second issue is that to make use of existing interfaces in Linux,
memory that is used for P2P transactions needs to be backed by struct
pages. However, PCI BARs are not typically cache coherent so there are
a few corner case gotchas with these pages so developers need to
be careful about what they do with them.
Provider, client와 orchestrator
28-64P2P 구현에는 세 종류 이상의 커널 드라이버 역할이 참여할 수 있습니다. provider는 memory나 doorbell register 같은 P2P resource를 제공하거나 publish합니다. client는 해당 resource를 출발지 또는 목적지로 삼아 DMA transaction을 설정합니다. orchestrator는 client와 provider 사이의 data flow를 조정합니다.
역할은 상호 배타적이지 않습니다. 하나의 드라이버가 provider이면서 client일 수 있고, 전체 전송을 조정하는 orchestrator까지 맡을 수 있습니다.
NVMe Target Copy Offload에서 NVMe PCI driver는 CMB, 즉 Controller Memory Buffer를 P2P memory resource로 공개하므로 provider입니다. 요청 buffer로 P2P memory page를 직접 받아들이므로 client이며, CMB를 submission queue entry에 사용할 수 있으므로 orchestrator 역할도 수행합니다.
같은 구성에서 RDMA driver는 RNIC가 NVMe device의 공개 memory로 직접 DMA하도록 하는 client입니다. NVMe Target driver `nvmet`은 RNIC에서 CMB로, 이어서 NVMe device로 흐르는 data와 반대 방향의 data를 orchestrate합니다.
현재 커널이 지원하는 구성은 이 배열뿐이지만, RNIC가 자체 BAR 뒤에 memory를 제공한다면 RNIC driver를 provider로 확장할 수 있습니다. 그러면 CMB가 없는 NVMe card에서도 NVMe Target이 RNIC memory를 대신 사용할 가능성이 있습니다.
Driver Writer's Guide
=====================
In a given P2P implementation there may be three or more different
types of kernel drivers in play:
* Provider - A driver which provides or publishes P2P resources like
memory or doorbell registers to other drivers.
* Client - A driver which makes use of a resource by setting up a
DMA transaction to or from it.
* Orchestrator - A driver which orchestrates the flow of data between
clients and providers.
In many cases there could be overlap between these three types (i.e.,
it may be typical for a driver to be both a provider and a client).
For example, in the NVMe Target Copy Offload implementation:
* The NVMe PCI driver is both a client, provider and orchestrator
in that it exposes any CMB (Controller Memory Buffer) as a P2P memory
resource (provider), it accepts P2P memory pages as buffers in requests
to be used directly (client) and it can also make use of the CMB as
submission queue entries (orchestrator).
* The RDMA driver is a client in this arrangement so that an RNIC
can DMA directly to the memory exposed by the NVMe device.
* The NVMe Target driver (nvmet) can orchestrate the data from the RNIC
to the P2P memory (CMB) and then to the NVMe device (and vice versa).
This is currently the only arrangement supported by the kernel but
one could imagine slight tweaks to this that would allow for the same
functionality. For example, if a specific RNIC added a BAR with some
memory behind it, its driver could add support as a P2P provider and
then the NVMe Target could use the RNIC's memory instead of the CMB
in cases where the NVMe cards in use do not have CMB support.
Provider driver의 resource 등록과 publish
65-82Provider driver는 `pci_p2pdma_add_resource()`로 BAR 전체 또는 일부를 P2P DMA resource로 등록합니다. 이 호출은 지정한 memory 전체에 대응하는 `struct page`들을 등록합니다.
그 뒤 `pci_p2pmem_publish()`로 모든 resource를 P2P memory로 선택적으로 publish할 수 있습니다. Publish된 resource는 orchestrator driver가 탐색하고 사용할 수 있습니다. 이 방식으로 표시하는 resource는 side effect가 없는 regular memory여야 합니다.
현재 resource 분류는 비교적 단순하여 보통 모든 resource가 P2P memory입니다. 향후 doorbell 같은 다른 resource type으로 확장될 가능성이 있습니다.
BAR 범위를 page-backed P2P resource로 등록한 뒤 필요하면 전역 탐색 대상으로 공개합니다.
Provider Drivers
----------------
A provider simply needs to register a BAR (or a portion of a BAR)
as a P2P DMA resource using :c:func:`pci_p2pdma_add_resource()`.
This will register struct pages for all the specified memory.
After that it may optionally publish all of its resources as
P2P memory using :c:func:`pci_p2pmem_publish()`. This will allow
any orchestrator drivers to find and use the memory. When marked in
this way, the resource must be regular memory with no side effects.
For the time being this is fairly rudimentary in that all resources
are typically going to be P2P memory. Future work will likely expand
this to include other types of resources like doorbells.
Client driver의 DMA mapping
83-90Client driver는 일반 scatter-gather DMA와 마찬가지로 `dma_map_sg()`와 `dma_unmap_sg()`를 사용하면 됩니다. DMA mapping 구현이 P2P 가능 memory를 인식하여 알맞게 처리합니다.
Client Drivers
--------------
A client driver only has to use the mapping API :c:func:`dma_map_sg()`
and :c:func:`dma_unmap_sg()` functions as usual, and the implementation
will do the right thing for the P2P capable memory.
Orchestrator의 호환성 검사와 memory 할당
91-113Orchestrator가 먼저 할 일은 한 transaction에 참여할 모든 client device의 목록을 만드는 것입니다. NVMe Target은 namespace block device와 사용 중인 RNIC를 이 목록에 넣습니다.
사용할 특정 P2P provider를 알고 있으면 `pci_p2pdma_distance()`로 client들과의 호환성을 검사합니다. Provider가 정해지지 않았다면 `pci_p2pmem_find()`로 모든 client와 호환되는 memory provider를 찾습니다.
지원 가능한 provider가 여러 개면 모든 client에 가장 가까운 provider가 우선 선택됩니다. 같은 거리의 provider가 여럿이면 임의적 선택이 아니라 실제 random 선택을 수행합니다.
`pci_p2pmem_find()`는 reference를 획득한 provider의 PCI device를 반환합니다. 더 이상 필요하지 않을 때 반드시 `pci_dev_put()`으로 reference를 돌려줘야 합니다.
Provider를 선택한 뒤 `pci_alloc_p2pmem()`과 `pci_free_p2pmem()`으로 P2P memory를 할당·해제합니다. `pci_p2pmem_alloc_sgl()`과 `pci_p2pmem_free_sgl()`은 P2P memory로 scatter-gather list를 만들고 해제하는 편의 함수입니다.
모든 client와 호환되는 가까운 provider를 고르고 reference와 P2P allocation의 수명을 각각 정리합니다.
Orchestrator Drivers
--------------------
The first task an orchestrator driver must do is compile a list of
all client devices that will be involved in a given transaction. For
example, the NVMe Target driver creates a list including the namespace
block device and the RNIC in use. If the orchestrator has access to
a specific P2P provider to use it may check compatibility using
:c:func:`pci_p2pdma_distance()` otherwise it may find a memory provider
that's compatible with all clients using :c:func:`pci_p2pmem_find()`.
If more than one provider is supported, the one nearest to all the clients will
be chosen first. If more than one provider is an equal distance away, the
one returned will be chosen at random (it is not an arbitrary but
truly random). This function returns the PCI device to use for the provider
with a reference taken and therefore when it's no longer needed it should be
returned with pci_dev_put().
Once a provider is selected, the orchestrator can then use
:c:func:`pci_alloc_p2pmem()` and :c:func:`pci_free_p2pmem()` to
allocate P2P memory from the provider. :c:func:`pci_p2pmem_alloc_sgl()`
and :c:func:`pci_p2pmem_free_sgl()` are convenience functions for
allocating scatter-gather lists with P2P memory.
특수 `struct page`의 사용 제한
114-126P2P memory를 뒷받침하는 특수 `struct page`는 이를 처리할 준비가 되지 않은 코드에 전달하면 안 됩니다. 현재 커널 인터페이스는 이 오용을 자동 검사하지 않으므로 책임은 driver 작성자에게 있습니다. 특히 이 page를 userspace로 전달하는 것은 금지됩니다.
P2P memory는 기술적으로 IO memory이지만 그 뒤에 side effect가 있어서는 안 됩니다. 따라서 load와 store의 순서는 중요하지 않아야 하고 `ioreadX()`, `iowriteX()` 계열 접근자를 사용할 필요가 없어야 합니다.
Struct Page Caveats
-------------------
Driver writers should be very careful about not passing these special
struct pages to code that isn't prepared for it. At this time, the kernel
interfaces do not have any checks for ensuring this. This obviously
precludes passing these pages to userspace.
P2P memory is also technically IO memory but should never have any side
effects behind it. Thus, the order of loads and stores should not be important
and ioreadX(), iowriteX() and friends should not be necessary.
P2P DMA 지원 라이브러리 kernel-doc
127-131이 절은 `drivers/pci/p2pdma.c`에 있는 export된 kernel-doc 항목을 P2P DMA Support Library API로 포함합니다. 앞에서 설명한 resource 등록, 호환성 탐색, allocation과 수명 관리 함수의 기준 구현 문서가 이 source path에서 생성됩니다.
P2P DMA Support Library
=======================
.. kernel-doc:: drivers/pci/p2pdma.c
:export:
요약과 해설
p2pdma.rst:1-131PCI P2P DMA는 현재 동일 bridge 뒤의 endpoint만 안전하게 지원합니다. Provider가 side-effect 없는 BAR memory를 page-backed resource로 공개하고, client는 표준 DMA mapping을 사용하며, orchestrator가 모든 client와 호환되는 가까운 provider를 선택해 reference와 allocation 수명을 관리합니다.