요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===========
VGA Arbiter
===========
Graphic devices are accessed through ranges in I/O or memory space. While most
modern devices allow relocation of such ranges, some "Legacy" VGA devices
implemented on PCI will typically have the same "hard-decoded" addresses as
they did on ISA. For more details see "PCI Bus Binding to IEEE Std 1275-1994
Standard for Boot (Initialization Configuration) Firmware Revision 2.1"
Section 7, Legacy Devices.
The Resource Access Control (RAC) module inside the X server [0] existed for
the legacy VGA arbitration task (besides other bus management tasks) when more
than one legacy device co-exist on the same machine. But the problem happens
when these devices are trying to be accessed by different userspace clients
(e.g. two servers in parallel). Their address assignments conflict. Moreover,
ideally, being a userspace application, it is not the role of the X server to
control bus resources. Therefore an arbitration scheme outside of the X server
is needed to control the sharing of these resources. This document introduces
the operation of the VGA arbiter implemented for the Linux kernel.
vgaarb kernel/userspace ABI
---------------------------
The vgaarb is a module of the Linux Kernel. When it is initially loaded, it
scans all PCI devices and adds the VGA ones inside the arbitration. The
arbiter then enables/disables the decoding on different devices of the VGA
legacy instructions. Devices which do not want/need to use the arbiter may
explicitly tell it by calling vga_set_legacy_decoding().
The kernel exports a char device interface (/dev/vga_arbiter) to the clients,
which has the following semantics:
open
Opens a user instance of the arbiter. By default, it's attached to the
default VGA device of the system.
close
Close a user instance. Release locks made by the user
read
Return a string indicating the status of the target like:
"<card_ID>,decodes=<io_state>,owns=<io_state>,locks=<io_state> (ic,mc)"
An IO state string is of the form {io,mem,io+mem,none}, mc and
ic are respectively mem and io lock counts (for debugging/
diagnostic only). "decodes" indicate what the card currently
decodes, "owns" indicates what is currently enabled on it, and
"locks" indicates what is locked by this card. If the card is
unplugged, we get "invalid" then for card_ID and an -ENODEV
error is returned for any command until a new card is targeted.
write
Write a command to the arbiter. List of commands:
target <card_ID>
switch target to card <card_ID> (see below)
lock <io_state>
acquires locks on target ("none" is an invalid io_state)
trylock <io_state>
non-blocking acquire locks on target (returns EBUSY if
unsuccessful)
unlock <io_state>
release locks on target
unlock all
release all locks on target held by this user (not implemented
yet)
decodes <io_state>
set the legacy decoding attributes for the card
poll
event if something changes on any card (not just the target)
card_ID is of the form "PCI:domain:bus:dev.fn". It can be set to "default"
to go back to the system default card (TODO: not implemented yet). Currently,
only PCI is supported as a prefix, but the userland API may support other bus
types in the future, even if the current kernel implementation doesn't.
Note about locks:
The driver keeps track of which user has which locks on which card. It
supports stacking, like the kernel one. This complexifies the implementation
a bit, but makes the arbiter more tolerant to user space problems and able
to properly cleanup in all cases when a process dies.
Currently, a max of 16 cards can have locks simultaneously issued from
user space for a given user (file descriptor instance) of the arbiter.
In the case of devices hot-{un,}plugged, there is a hook - pci_notify() - to
notify them being added/removed in the system and automatically added/removed
in the arbiter.
There is also an in-kernel API of the arbiter in case DRM, vgacon, or other
drivers want to use it.
In-kernel interface
-------------------
.. kernel-doc:: include/linux/vgaarb.h
:internal:
.. kernel-doc:: drivers/pci/vgaarb.c
:export:
libpciaccess
------------
To use the vga arbiter char device, an API was implemented inside the
libpciaccess library. One field was added to struct pci_device (each device
on the system)::
/* the type of resource decoded by the device */
int vgaarb_rsrc;
Besides it, in pci_system were added::
int vgaarb_fd;
int vga_count;
struct pci_device *vga_target;
struct pci_device *vga_default_dev;
The vga_count is used to track how many cards are being arbitrated, so for
instance, if there is only one card, then it can completely escape arbitration.
These functions below acquire VGA resources for the given card and mark those
resources as locked. If the resources requested are "normal" (and not legacy)
resources, the arbiter will first check whether the card is doing legacy
decoding for that type of resource. If yes, the lock is "converted" into a
legacy resource lock. The arbiter will first look for all VGA cards that
might conflict and disable their IOs and/or Memory access, including VGA
forwarding on P2P bridges if necessary, so that the requested resources can
be used. Then, the card is marked as locking these resources and the IO and/or
Memory access is enabled on the card (including VGA forwarding on parent
P2P bridges if any). In the case of vga_arb_lock(), the function will block
if some conflicting card is already locking one of the required resources (or
any resource on a different bus segment, since P2P bridges don't differentiate
VGA memory and IO afaik). If the card already owns the resources, the function
succeeds. vga_arb_trylock() will return (-EBUSY) instead of blocking. Nested
calls are supported (a per-resource counter is maintained).
Set the target device of this client. ::
int pci_device_vgaarb_set_target (struct pci_device *dev);
For instance, in x86 if two devices on the same bus want to lock different
resources, both will succeed (lock). If devices are in different buses and
trying to lock different resources, only the first who tried succeeds. ::
int pci_device_vgaarb_lock (void);
int pci_device_vgaarb_trylock (void);
Unlock resources of device. ::
int pci_device_vgaarb_unlock (void);
Indicates to the arbiter if the card decodes legacy VGA IOs, legacy VGA
Memory, both, or none. All cards default to both, the card driver (fbdev for
example) should tell the arbiter if it has disabled legacy decoding, so the
card can be left out of the arbitration process (and can be safe to take
interrupts at any time. ::
int pci_device_vgaarb_decodes (int new_vgaarb_rsrc);
Connects to the arbiter device, allocates the struct ::
int pci_device_vgaarb_init (void);
Close the connection ::
void pci_device_vgaarb_fini (void);
xf86VGAArbiter (X server implementation)
----------------------------------------
X server basically wraps all the functions that touch VGA registers somehow.
References
----------
Benjamin Herrenschmidt (IBM?) started this work when he discussed such design
with the Xorg community in 2005 [1, 2]. In the end of 2007, Paulo Zanoni and
Tiago Vignatti (both of C3SL/Federal University of Paraná) proceeded his work
enhancing the kernel code to adapt as a kernel module and also did the
implementation of the user space side [3]. Now (2009) Tiago Vignatti and Dave
Airlie finally put this work in shape and queued to Jesse Barnes' PCI tree.
0) https://cgit.freedesktop.org/xorg/xserver/commit/?id=4b42448a2388d40f257774fbffdccaea87bd0347
1) https://lists.freedesktop.org/archives/xorg/2005-March/006663.html
2) https://lists.freedesktop.org/archives/xorg/2005-March/006745.html
3) https://lists.freedesktop.org/archives/xorg/2007-October/029507.html
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Legacy VGA 자원 충돌과 kernel arbiter
1-21Graphics device는 I/O space 또는 memory space의 range를 통해 접근합니다. 현대 장치는 대부분 range를 relocation할 수 있지만 PCI의 legacy VGA device는 ISA 시절과 같은 hard-decoded address를 쓰는 경우가 많습니다. 자세한 배경은 `PCI Bus Binding to IEEE Std 1275-1994 Standard for Boot (Initialization Configuration) Firmware Revision 2.1`의 Section 7 `Legacy Devices`를 참고합니다.
X server의 Resource Access Control(RAC) module은 한 시스템에 legacy VGA device가 여러 개 있을 때 arbitration을 포함한 bus management를 수행했습니다. 하지만 서로 다른 userspace client, 예를 들어 병렬로 실행되는 두 server가 각 device에 접근하면 address assignment가 충돌합니다.
Userspace application인 X server가 bus resource를 통제하는 것도 이상적이지 않습니다. 따라서 이 자원 공유를 X server 밖에서 제어할 arbitration scheme이 필요하며, 이 문서는 Linux kernel에 구현된 VGA arbiter의 동작을 설명합니다.
Arbitration이 필요한 이유를 정리합니다.
충돌하는 legacy decode를 kernel이 직렬화합니다.
===========
VGA Arbiter
===========
Graphic devices are accessed through ranges in I/O or memory space. While most
modern devices allow relocation of such ranges, some "Legacy" VGA devices
implemented on PCI will typically have the same "hard-decoded" addresses as
they did on ISA. For more details see "PCI Bus Binding to IEEE Std 1275-1994
Standard for Boot (Initialization Configuration) Firmware Revision 2.1"
Section 7, Legacy Devices.
The Resource Access Control (RAC) module inside the X server [0] existed for
the legacy VGA arbitration task (besides other bus management tasks) when more
than one legacy device co-exist on the same machine. But the problem happens
when these devices are trying to be accessed by different userspace clients
(e.g. two servers in parallel). Their address assignments conflict. Moreover,
ideally, being a userspace application, it is not the role of the X server to
control bus resources. Therefore an arbitration scheme outside of the X server
is needed to control the sharing of these resources. This document introduces
the operation of the VGA arbiter implemented for the Linux kernel.
/dev/vga_arbiter userspace ABI
22-96`vgaarb`는 Linux kernel module입니다. 처음 load되면 모든 PCI device를 scan하고 VGA device를 arbitration 대상에 추가합니다. Arbiter는 device별 legacy VGA instruction decode를 enable 또는 disable합니다. Arbiter가 필요 없는 device는 `vga_set_legacy_decoding()`을 호출해 명시적으로 알릴 수 있습니다.
Kernel은 client에 character device `/dev/vga_arbiter`를 노출합니다. `open`은 user instance를 만들고 기본적으로 시스템 default VGA device에 연결합니다. `close`는 instance를 닫고 해당 user가 획득한 lock을 해제합니다.
`read`는 target 상태를 `"<card_ID>,decodes=<io_state>,owns=<io_state>,locks=<io_state> (ic,mc)"` 형식의 문자열로 반환합니다. `io_state`는 `io`, `mem`, `io+mem`, `none` 중 하나입니다. `mc`와 `ic`는 각각 memory와 I/O lock count이며 debugging·diagnostic 용도입니다.
`decodes`는 card가 현재 decode하는 자원, `owns`는 현재 enable된 자원, `locks`는 card가 lock한 자원을 나타냅니다. Card가 unplug되면 `card_ID`에 `invalid`가 표시되고 새 target을 지정할 때까지 모든 command가 `-ENODEV`를 반환합니다.
`write` command 중 `target <card_ID>`는 target card를 바꿉니다. `lock <io_state>`는 target에 blocking lock을 획득하고, `trylock <io_state>`는 nonblocking으로 시도해 실패하면 `EBUSY`를 반환합니다. `unlock <io_state>`는 해당 lock을 해제하며 `unlock all`은 이 user가 target에 보유한 lock을 모두 해제하는 명령이지만 아직 구현되지 않았습니다. `decodes <io_state>`는 card의 legacy decoding attribute를 설정합니다.
`poll`은 target뿐 아니라 어느 card에서든 상태가 변하면 event를 제공합니다. `card_ID` 형식은 `PCI:domain:bus:dev.fn`입니다. `default`를 지정해 시스템 default card로 돌아가는 기능은 TODO이며 아직 구현되지 않았습니다. 현재 prefix는 PCI만 지원하지만 userspace API는 향후 다른 bus type을 지원할 수 있습니다.
Driver는 어느 user가 어느 card의 어떤 lock을 보유하는지 추적합니다. Kernel lock처럼 stacking을 지원하므로 구현은 복잡해지지만 userspace 오류에 강하고 process가 종료될 때 모든 경우를 정리할 수 있습니다. 한 user, 즉 file descriptor instance는 동시에 최대 16개 card에 lock을 발급할 수 있습니다.
Device hotplug와 hot-unplug 때는 `pci_notify()` hook이 추가·제거를 알려 arbiter 대상도 자동으로 갱신합니다. DRM, vgacon 또는 다른 kernel driver가 사용할 in-kernel API도 제공됩니다.
Character device operation과 상태 표현입니다.
Target을 선택하고 legacy 자원 ownership을 획득합니다.
vgaarb kernel/userspace ABI
---------------------------
The vgaarb is a module of the Linux Kernel. When it is initially loaded, it
scans all PCI devices and adds the VGA ones inside the arbitration. The
arbiter then enables/disables the decoding on different devices of the VGA
legacy instructions. Devices which do not want/need to use the arbiter may
explicitly tell it by calling vga_set_legacy_decoding().
The kernel exports a char device interface (/dev/vga_arbiter) to the clients,
which has the following semantics:
open
Opens a user instance of the arbiter. By default, it's attached to the
default VGA device of the system.
close
Close a user instance. Release locks made by the user
read
Return a string indicating the status of the target like:
"<card_ID>,decodes=<io_state>,owns=<io_state>,locks=<io_state> (ic,mc)"
An IO state string is of the form {io,mem,io+mem,none}, mc and
ic are respectively mem and io lock counts (for debugging/
diagnostic only). "decodes" indicate what the card currently
decodes, "owns" indicates what is currently enabled on it, and
"locks" indicates what is locked by this card. If the card is
unplugged, we get "invalid" then for card_ID and an -ENODEV
error is returned for any command until a new card is targeted.
write
Write a command to the arbiter. List of commands:
target <card_ID>
switch target to card <card_ID> (see below)
lock <io_state>
acquires locks on target ("none" is an invalid io_state)
trylock <io_state>
non-blocking acquire locks on target (returns EBUSY if
unsuccessful)
unlock <io_state>
release locks on target
unlock all
release all locks on target held by this user (not implemented
yet)
decodes <io_state>
set the legacy decoding attributes for the card
poll
event if something changes on any card (not just the target)
card_ID is of the form "PCI:domain:bus:dev.fn". It can be set to "default"
to go back to the system default card (TODO: not implemented yet). Currently,
only PCI is supported as a prefix, but the userland API may support other bus
types in the future, even if the current kernel implementation doesn't.
Note about locks:
The driver keeps track of which user has which locks on which card. It
supports stacking, like the kernel one. This complexifies the implementation
a bit, but makes the arbiter more tolerant to user space problems and able
to properly cleanup in all cases when a process dies.
Currently, a max of 16 cards can have locks simultaneously issued from
user space for a given user (file descriptor instance) of the arbiter.
In the case of devices hot-{un,}plugged, there is a hook - pci_notify() - to
notify them being added/removed in the system and automatically added/removed
in the arbiter.
There is also an in-kernel API of the arbiter in case DRM, vgacon, or other
drivers want to use it.
In-kernel API와 libpciaccess locking
97-172In-kernel interface는 `include/linux/vgaarb.h`의 internal kernel-doc와 `drivers/pci/vgaarb.c`의 exported 함수 문서로 제공됩니다.
VGA arbiter character device를 사용하도록 libpciaccess에도 API가 구현됐습니다. 시스템의 각 device를 나타내는 `struct pci_device`에는 device가 decode하는 resource type을 담는 `int vgaarb_rsrc` field가 추가됐습니다.
`pci_system`에는 `int vgaarb_fd`, `int vga_count`, `struct pci_device *vga_target`, `struct pci_device *vga_default_dev`가 추가됐습니다. `vga_count`는 arbitration 중인 card 수를 추적하므로 card가 하나뿐이면 arbitration을 완전히 피할 수 있습니다.
Lock 함수는 주어진 card의 VGA resource를 획득하고 locked 상태로 표시합니다. 요청이 legacy가 아닌 normal resource라면 arbiter는 먼저 card가 해당 type을 legacy decode하는지 확인합니다. 그렇다면 lock을 legacy resource lock으로 변환합니다.
Arbiter는 충돌할 수 있는 모든 VGA card를 찾고 I/O 또는 memory access를 disable합니다. 필요하면 P2P bridge의 VGA forwarding도 disable하여 요청 자원을 안전하게 사용할 수 있게 합니다. 그다음 요청 card가 자원을 lock한 것으로 표시하고 card와 parent P2P bridge의 I/O 또는 memory access를 enable합니다.
`vga_arb_lock()`은 충돌 card가 필요한 resource를 이미 lock했으면 block합니다. P2P bridge는 VGA memory와 I/O를 구분하지 않는 것으로 알려져 있으므로 다른 bus segment의 어떤 resource lock과도 충돌할 수 있습니다. Card가 이미 resource를 own하면 성공하고, `vga_arb_trylock()`은 block 대신 `-EBUSY`를 반환합니다. Resource별 counter로 nested call도 지원합니다.
Client target 지정 함수는 `int pci_device_vgaarb_set_target(struct pci_device *dev);`입니다. x86에서 같은 bus의 두 device가 서로 다른 resource를 lock하면 둘 다 성공하지만, 다른 bus에 있는 device가 서로 다른 resource를 lock하려 하면 먼저 시도한 쪽만 성공합니다.
Resource 획득 함수는 `pci_device_vgaarb_lock()`과 `pci_device_vgaarb_trylock()`, 해제 함수는 `pci_device_vgaarb_unlock()`입니다. `pci_device_vgaarb_decodes(int new_vgaarb_rsrc)`는 card가 legacy VGA I/O, memory, 둘 다 또는 어느 것도 decode하지 않는다고 arbiter에 알립니다.
모든 card의 기본값은 I/O와 memory 둘 다입니다. Fbdev 같은 card driver가 legacy decoding을 disable했다면 arbiter에 알려 arbitration에서 제외할 수 있게 해야 하며, 그러면 언제든 안전하게 interrupt를 받을 수 있습니다. `pci_device_vgaarb_init()`은 arbiter device에 연결하고 구조를 할당하며 `pci_device_vgaarb_fini()`는 연결을 닫습니다.
Target·lock·decode·connection 함수입니다.
충돌 card를 차단한 뒤 target access를 허용합니다.
In-kernel interface
-------------------
.. kernel-doc:: include/linux/vgaarb.h
:internal:
.. kernel-doc:: drivers/pci/vgaarb.c
:export:
libpciaccess
------------
To use the vga arbiter char device, an API was implemented inside the
libpciaccess library. One field was added to struct pci_device (each device
on the system)::
/* the type of resource decoded by the device */
int vgaarb_rsrc;
Besides it, in pci_system were added::
int vgaarb_fd;
int vga_count;
struct pci_device *vga_target;
struct pci_device *vga_default_dev;
The vga_count is used to track how many cards are being arbitrated, so for
instance, if there is only one card, then it can completely escape arbitration.
These functions below acquire VGA resources for the given card and mark those
resources as locked. If the resources requested are "normal" (and not legacy)
resources, the arbiter will first check whether the card is doing legacy
decoding for that type of resource. If yes, the lock is "converted" into a
legacy resource lock. The arbiter will first look for all VGA cards that
might conflict and disable their IOs and/or Memory access, including VGA
forwarding on P2P bridges if necessary, so that the requested resources can
be used. Then, the card is marked as locking these resources and the IO and/or
Memory access is enabled on the card (including VGA forwarding on parent
P2P bridges if any). In the case of vga_arb_lock(), the function will block
if some conflicting card is already locking one of the required resources (or
any resource on a different bus segment, since P2P bridges don't differentiate
VGA memory and IO afaik). If the card already owns the resources, the function
succeeds. vga_arb_trylock() will return (-EBUSY) instead of blocking. Nested
calls are supported (a per-resource counter is maintained).
Set the target device of this client. ::
int pci_device_vgaarb_set_target (struct pci_device *dev);
For instance, in x86 if two devices on the same bus want to lock different
resources, both will succeed (lock). If devices are in different buses and
trying to lock different resources, only the first who tried succeeds. ::
int pci_device_vgaarb_lock (void);
int pci_device_vgaarb_trylock (void);
Unlock resources of device. ::
int pci_device_vgaarb_unlock (void);
Indicates to the arbiter if the card decodes legacy VGA IOs, legacy VGA
Memory, both, or none. All cards default to both, the card driver (fbdev for
example) should tell the arbiter if it has disabled legacy decoding, so the
card can be left out of the arbitration process (and can be safe to take
interrupts at any time. ::
int pci_device_vgaarb_decodes (int new_vgaarb_rsrc);
Connects to the arbiter device, allocates the struct ::
int pci_device_vgaarb_init (void);
Close the connection ::
void pci_device_vgaarb_fini (void);
X server wrapper와 구현 역사
173-191X server의 `xf86VGAArbiter` 구현은 VGA register를 건드리는 모든 함수를 기본적으로 wrapping합니다.
Benjamin Herrenschmidt는 2005년 Xorg community와 설계를 논의하며 이 작업을 시작했습니다. 관련 기록은 `https://lists.freedesktop.org/archives/xorg/2005-March/006663.html`과 `https://lists.freedesktop.org/archives/xorg/2005-March/006745.html`에 있습니다.
2007년 말 C3SL/Federal University of Paraná의 Paulo Zanoni와 Tiago Vignatti가 kernel code를 module에 맞게 개선하고 userspace 구현도 만들었습니다. 기록은 `https://lists.freedesktop.org/archives/xorg/2007-October/029507.html`에 있습니다.
2009년 Tiago Vignatti와 Dave Airlie가 코드를 정리해 Jesse Barnes의 PCI tree에 올렸습니다. RAC 관련 기준 commit은 `https://cgit.freedesktop.org/xorg/xserver/commit/?id=4b42448a2388d40f257774fbffdccaea87bd0347`입니다.
Kernel과 userspace 구현의 주요 시점입니다.
X server RAC에서 kernel arbitration으로 역할이 이동했습니다.
xf86VGAArbiter (X server implementation)
----------------------------------------
X server basically wraps all the functions that touch VGA registers somehow.
References
----------
Benjamin Herrenschmidt (IBM?) started this work when he discussed such design
with the Xorg community in 2005 [1, 2]. In the end of 2007, Paulo Zanoni and
Tiago Vignatti (both of C3SL/Federal University of Paraná) proceeded his work
enhancing the kernel code to adapt as a kernel module and also did the
implementation of the user space side [3]. Now (2009) Tiago Vignatti and Dave
Airlie finally put this work in shape and queued to Jesse Barnes' PCI tree.
0) https://cgit.freedesktop.org/xorg/xserver/commit/?id=4b42448a2388d40f257774fbffdccaea87bd0347
1) https://lists.freedesktop.org/archives/xorg/2005-March/006663.html
2) https://lists.freedesktop.org/archives/xorg/2005-March/006745.html
3) https://lists.freedesktop.org/archives/xorg/2007-October/029507.html
요약·해설
vgaarbiter.rst:1-191Legacy VGA I/O·memory decode 충돌을 조정하는 kernel·userspace ABI를 설명합니다.
Source와 관련 구현입니다.