VGA Arbiter

Graphic devices are accessed through ranges in I/O or memory space. While mostmodern devices allow relocation of such ranges, some “Legacy” VGA devicesimplemented on PCI will typically have the same “hard-decoded” addresses asthey did on ISA. For more details see “PCI Bus Binding to IEEE Std 1275-1994Standard for Boot (Initialization Configuration) Firmware Revision 2.1”Section 7, Legacy Devices.

The Resource Access Control (RAC) module inside the X server [0] existed forthe legacy VGA arbitration task (besides other bus management tasks) when morethan one legacy device co-exist on the same machine. But the problem happenswhen 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 tocontrol bus resources. Therefore an arbitration scheme outside of the X serveris needed to control the sharing of these resources. This document introducesthe 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, itscans all PCI devices and adds the VGA ones inside the arbitration. Thearbiter then enables/disables the decoding on different devices of the VGAlegacy instructions. Devices which do not want/need to use the arbiter mayexplicitly tell it by callingvga_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 thedefault 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 andic are respectively mem and io lock counts (for debugging/diagnostic only). “decodes” indicate what the card currentlydecodes, “owns” indicates what is currently enabled on it, and“locks” indicates what is locked by this card. If the card isunplugged, we get “invalid” then for card_ID and an -ENODEVerror 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 ifunsuccessful)

unlock <io_state>

release locks on target

unlock all

release all locks on target held by this user (not implementedyet)

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 bustypes 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. Itsupports stacking, like the kernel one. This complexifies the implementationa bit, but makes the arbiter more tolerant to user space problems and ableto properly cleanup in all cases when a process dies.Currently, a max of 16 cards can have locks simultaneously issued fromuser 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() - tonotify them being added/removed in the system and automatically added/removedin the arbiter.

There is also an in-kernel API of the arbiter in case DRM, vgacon, or otherdrivers want to use it.

In-kernel interface

intvga_get_interruptible(structpci_dev*pdev,unsignedintrsrc)

Parameters

structpci_dev*pdev

pci device of the VGA card or NULL for the system default

unsignedintrsrc

bit mask of resources to acquire and lock

Description

Shortcut to vga_get with interruptible set to true.

On success, release the VGA resource again withvga_put().

intvga_get_uninterruptible(structpci_dev*pdev,unsignedintrsrc)

shortcut tovga_get()

Parameters

structpci_dev*pdev

pci device of the VGA card or NULL for the system default

unsignedintrsrc

bit mask of resources to acquire and lock

Description

Shortcut to vga_get with interruptible set to false.

On success, release the VGA resource again withvga_put().

structpci_dev*vga_default_device(void)

return the default VGA device, for vgacon

Parameters

void

no arguments

Description

This can be defined by the platform. The default implementation israther dumb and will probably only work properly on single VGA cardsetups and/or x86 platforms.

If your VGA default device is not PCI, you’ll have to return NULL here.In this case, I assume it will not conflict with any PCI card. If thisis not true, I’ll have to define two arch hooks for enabling/disablingthe VGA default device if that is possible. This may be a problem withreal _ISA_ VGA cards, in addition to a PCI one. I don’t know at thispoint how to deal with that card. Can their IOs be disabled at all? Ifnot, then I suppose it’s a matter of having the proper arch hook tellingus about it, so we basically never allow anybody to succeed avga_get().

intvga_remove_vgacon(structpci_dev*pdev)

deactivate VGA console

Parameters

structpci_dev*pdev

PCI device.

Description

Unbind and unregister vgacon in case pdev is the default VGA device.Can be called by GPU drivers on initialization to make sure VGA registeraccess done by vgacon will not disturb the device.

intvga_get(structpci_dev*pdev,unsignedintrsrc,intinterruptible)

acquire & lock VGA resources

Parameters

structpci_dev*pdev

PCI device of the VGA card or NULL for the system default

unsignedintrsrc

bit mask of resources to acquire and lock

intinterruptible

blocking should be interruptible by signals ?

Description

Acquire VGA resources for the given card and mark those resourceslocked. If the resources requested are “normal” (and not legacy)resources, the arbiter will first check whether the card is doing legacydecoding for that type of resource. If yes, the lock is “converted” intoa legacy resource lock.

The arbiter will first look for all VGA cards that might conflict and disabletheir IOs and/or Memory access, including VGA forwarding on P2P bridges ifnecessary, so that the requested resources can be used. Then, the card ismarked as locking these resources and the IO and/or Memory accesses areenabled on the card (including VGA forwarding on parent P2P bridges if any).

This function will block if some conflicting card is already locking one ofthe required resources (or any resource on a different bus segment, since P2Pbridges don’t differentiate VGA memory and IO afaik). You can indicatewhether this blocking should be interruptible by a signal (for userlandinterface) or not.

Must not be called at interrupt time or in atomic context. If the cardalready owns the resources, the function succeeds. Nested calls aresupported (a per-resource counter is maintained)

On success, release the VGA resource again withvga_put().

0 on success, negative error code on failure.

voidvga_put(structpci_dev*pdev,unsignedintrsrc)

release lock on legacy VGA resources

Parameters

structpci_dev*pdev

PCI device of VGA card or NULL for system default

unsignedintrsrc

bit mask of resource to release

Description

Release resources previously locked byvga_get() orvga_tryget(). Theresources aren’t disabled right away, so that a subsequentvga_get() onthe same card will succeed immediately. Resources have a counter, solocks are only released if the counter reaches 0.

voidvga_set_legacy_decoding(structpci_dev*pdev,unsignedintdecodes)

Parameters

structpci_dev*pdev

PCI device of the VGA card

unsignedintdecodes

bit mask of what legacy regions the card decodes

Description

Indicate to the arbiter if the card decodes legacy VGA IOs, legacy VGAMemory, both, or none. All cards default to both, the card driver (fbdev forexample) should tell the arbiter if it has disabled legacy decoding, so thecard can be left out of the arbitration process (and can be safe to takeinterrupts at any time.

intvga_client_register(structpci_dev*pdev,unsignedint(*set_decode)(structpci_dev*pdev,booldecode))

register or unregister a VGA arbitration client

Parameters

structpci_dev*pdev

PCI device of the VGA client

unsignedint(*set_decode)(structpci_dev*pdev,booldecode)

VGA decode change callback

Description

Clients have two callback mechanisms they can use.

set_decode callback: If a client can disable its GPU VGA resource, itwill get a callback from this to set the encode/decode state.

Rationale: we cannot disable VGA decode resources unconditionallybecause some single GPU laptops seem to require ACPI or BIOS access tothe VGA registers to control things like backlights etc. Hopefully newermulti-GPU laptops do something saner, and desktops won’t have anyspecial ACPI for this. The driver will get a callback when VGAarbitration is first used by userspace since some older X servers haveissues.

Does not check whether a client forpdev has been registered already.

To unregister, callvga_client_unregister().

Return

0 on success, -ENODEV on failure

libpciaccess

To use the vga arbiter char device, an API was implemented inside thelibpciaccess library. One field was added tostructpci_device (each deviceon 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 forinstance, if there is only one card, then it can completely escape arbitration.

These functions below acquire VGA resources for the given card and mark thoseresources as locked. If the resources requested are “normal” (and not legacy)resources, the arbiter will first check whether the card is doing legacydecoding for that type of resource. If yes, the lock is “converted” into alegacy resource lock. The arbiter will first look for all VGA cards thatmight conflict and disable their IOs and/or Memory access, including VGAforwarding on P2P bridges if necessary, so that the requested resources canbe used. Then, the card is marked as locking these resources and the IO and/orMemory access is enabled on the card (including VGA forwarding on parentP2P bridges if any). In the case ofvga_arb_lock(), the function will blockif some conflicting card is already locking one of the required resources (orany resource on a different bus segment, since P2P bridges don’t differentiateVGA memory and IO afaik). If the card already owns the resources, the functionsucceeds.vga_arb_trylock() will return (-EBUSY) instead of blocking. Nestedcalls 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 differentresources, both will succeed (lock). If devices are in different buses andtrying 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 VGAMemory, both, or none. All cards default to both, the card driver (fbdev forexample) should tell the arbiter if it has disabled legacy decoding, so thecard can be left out of the arbitration process (and can be safe to takeinterrupts 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 designwith the Xorg community in 2005 [1, 2]. In the end of 2007, Paulo Zanoni andTiago Vignatti (both of C3SL/Federal University of Paraná) proceeded his workenhancing the kernel code to adapt as a kernel module and also did theimplementation of the user space side [3]. Now (2009) Tiago Vignatti and DaveAirlie finally put this work in shape and queued to Jesse Barnes’ PCI tree.

  1. https://cgit.freedesktop.org/xorg/xserver/commit/?id=4b42448a2388d40f257774fbffdccaea87bd0347

  2. https://lists.freedesktop.org/archives/xorg/2005-March/006663.html

  3. https://lists.freedesktop.org/archives/xorg/2005-March/006745.html

  4. https://lists.freedesktop.org/archives/xorg/2007-October/029507.html