qubes.devices – Devices

The main concept is that a domain (backend) can expose (potentially multiple) devices, each through a port, where only one device can be in one port at any given time. Such devices can be connected to other domains (frontends). Devices can be of different buses (like ‘pci’, ‘usb’, etc.). Each device bus is implemented by an extension (see qubes.ext).

Devices are identified by a pair (port, device_id), where port is a pair (backend domain, port_id). Both port_id and device_id are str, and in addition, port_id is unique per backend. More about the requirements for port_id and device_id can be found in the sections below.

Classes

qubes.device_protocol.Port: a pair <backend_domain>:<port_id> with devclass (e.g., pci, usb). In the previous version (before QubesOS 4.3), this was referred to as Device, and port_id was named ident.

qubes.device_protocol.AnyPort: A class used to handle cases where any port is accepted.

qubes.device_protocol.VirtualDevice: A pair <port>:<device_id>. This class links a device identified by device_id to a specific port. If both values are specified, the instance represents a device connected to that particular port. If the port is of type AnyPort, it represents a device identified by device_id that can be connected to any port. This is used by qubes.device_protocol.DeviceInfo, which describes what to do with a device identified by device_id when connected anywhere. Similarly, when device_id is *, the instance represents any potential device connected to the given port. As a result, the device is considered “virtual” meaning it may or may not represent an actual device in the system. A device with *:* (any port and any device) is not permitted.

qubes.device_protocol.DeviceInfo: Derived from VirtualDevice. Extensions should assume that Port is provided, and based on that, device_id should return the same string for the same device, regardless of which port it is connected to. The device_id acts as a device hash and should be “human-readable”. It must contain only digits, ASCII letters, spaces, and the following characters: !#$%&()*+,-./:;<>?@[]^_{|}~. It cannot be empty or equal to *.

qubes.device_protocol.DeviceAssignment: Represents the relationship between a VirtualDevice and a frontend_domain. There are four modes: #. manual (attachment): The device is manually attached to frontend_domain. This type of assignment does not persist across domain restarts. #. auto-attach: Any device that matches a VirtualDevice will be automatically attached to the frontend_domain when discovered or during domain startup. #. ask-to-attach: Functions like auto-attach, but prompts the user for confirmation before attaching. If no GUI is available, the prompt is ignored. #. required: The device must be available during frontend_domain startup and will be attached before the domain is started.

qubes.device_protocol.DeviceInterface: Represents device interfaces as a 7-character code in the format BCCSSII, where B indicates the devclass (e.g., p for PCI, u for USB, ? for unknown), CC is the class code, SS is the subclass code, and II represents the interface code.

qubes.device_protocol.DeviceCategory: Provides an easy-to-use, arbitrary subset of interfaces with names assigned to categories considered as most relevant to users. When needed, the class should be extended with new categories. This structure allows for quick identification of the device type and can be useful when displaying devices to the end user.

Device Assignment vs Attachment

For clarity let’s us introduce two types of assignments: potential and real (attachment). Attachment indicates that the device has been attached by the Qubes backend to its frontend VM and is visible from its perspective. Potential assignment, on the other hand, has tree modes: auto-attach, ask-to-attach and required. For detailed descriptions, take a look at qubes.device_protocol.DeviceAssignment documentation. In general we refer to potential assignment as assignment and real assignment as attachment. To check whether the device is currently attached, we check qubes.device_protocol.DeviceAssignment.attached(), while to check whether an (potential) assignment exists, we check qubes.device_protocol.DeviceAssignment.attach_automatically(). Potential and real connections may coexist at the same time, in which case both values will be true.

Understanding Device Identity

It is important to understand that qubes.device_protocol.Port does not correspond to the device itself, but rather to the port to which the device is connected. Therefore, when assigning a device to a VM, such as sys-usb:1-1.1, the port 1-1.1 is actually assigned, and thus every devices connected to it will be automatically attached. Similarly, when assigning vm:sda, every block device with the name sda will be automatically attached. We can limit this using qubes.device_protocol.DeviceInfo.device_id(), which returns a string containing information presented by the device, such as for example vendor_id, product_id, serial_number, and encoded interfaces. In the case of block devices, device_id consists of the parent’s device_id to which the device is connected (if any) and the interface/partition number. In practice, this means that, a partition on a USB drive will only be automatically attached to a frontend domain if the parent presents the correct serial number etc.

Actions

The assign action means that a device will be assigned to the frontend VM in a potential form (this does not change the current system state). This will result in an attempt to automatically attach the device upon the next VM startup. If mode=required, and the device cannot be attached, the VM startup will fail. Additionally, upon device detection (device-added), an attempt will be made to attach the device. However, at any time (unless mode=required), the user can manually modify this state by performing attach or detach on the device, changing the current system state. This will not alter the assignment, and automatic attachment attempts will still be made in the future. To remove the assignment the user need to perform unassign (see next section).

Assignment Management

Assignments can be edited at any time: regardless of whether the VM is running or the device is currently attached. Removing the assignment does not change the real system state, so if the device is currently attached and the user remove the assignment, it will not be detached, but it will not be automatically attached in the future. Similarly, it works the other way around with assign.

Proper Assignment States

In short, we can think of device assignment in terms of three flags: #. attached - indicating whether the device is currently assigned, #. attach_automatically - indicating whether the device will be automatically attached by the system daemon, #. required - determining whether the failure of automatic attachment should result in the domain startup being interrupted.

Then the possible states of assignment (attached, automatically_attached, required) are as follow: #. (True, False, False) -> domain is running, device is manually attached and could be manually detach any time. #. (True, True, False) -> domain is running, device is attached and could be manually detach any time (see 4.), but in the future will be auto-attached again. #. (True, True, True) -> domain is running, device is attached and couldn’t be detached. #. (False, True, False) -> device is assigned to domain, but not attached because either (i) domain is halted, device (ii) manually detached or (iii) attach to different domain. #. (False, True, True) -> domain is halted, device assigned to domain and required to start domain.

Note that if required=True then automatically_attached=True.

Conflicted Assignments

If a connected device has multiple assignments to different frontend_domain instances, the user will be asked to choose which domain connect the device to. If no GUI client is available, the device will not be connected to any domain. If multiple assignments exist for a connected device with different options but to the same frontend_domain, the most specific assignment will take precedence, according to the following order (from highest to lowest priority): #. Assignment specifies both port and device_id. #. Assignment specifies only the port. #. Assignment specifies only the device_id.

It is important to note that only one matching assignment can exist within each of the categories listed above.

Port Assignment

It is possible to not assign a specific device but rather a port, (e.g., we can use the –port flag in the client). In this case, the value * will appear in the identity field of the qubes.xml file. This indicates that the identity presented by the devices will be ignored, and all connected devices will be automatically attached.

PCI Devices

PCI devices cannot be manually attached to a VM at any time. We must first create an assignment (assign) as required (in client we can use –required flag). Then, it will be automatically attached upon each VM startup. However, if a PCI device is currently in use by another VM, the startup of the second VM will fail.

Microphone

The microphone cannot be assigned with the mode=required to any VM.

USB Devices

The USB devices cannot be assigned with the mode=required to any VM.

API for various types of devices.

Main concept is that some domain may expose (potentially multiple) devices, which can be attached to other domains. Devices can be of different buses (like ‘pci’, ‘usb’, etc.). Each device bus is implemented by an extension.

Devices are identified by pair of (backend domain, port_id), where port_id is str and can contain only characters from [a-zA-Z0-9._-] set.

Such extension should:
  • provide qubes.devices endpoint - a class descendant from qubes.device_protocol.DeviceInfo, designed to hold device description (including bus-specific properties)

  • handle device-attach:bus and device-detach:bus events for performing the attach/detach action; events are fired even when domain isn’t running and extension should be prepared for this; handlers for those events can be coroutines

  • handle device-list:bus event - list devices exposed by particular domain; it should return a list of appropriate DeviceInfo objects

  • handle device-get:bus event - get one device object exposed by this domain of given identifier

  • handle device-list-attached:bus event - list devices currently attached to this domain

  • fire device-list-change:bus and following device-added:bus or device-removed:bus events when a device list change is detected (new/removed device)

Note that device-listing event handlers cannot be asynchronous. This for example means you cannot call qrexec service there. This is intentional to keep device listing operation cheap. You need to design the extension to take this into account (for example by using QubesDB).

Extension may use QubesDB watch API (QubesVM.watch_qdb_path(path), then handle domain-qdb-change:path) to detect changes and fire device-list-change:class event.

exception qubes.devices.DeviceAlreadyAssigned[source]

Bases: QubesException, KeyError

Trying to assign already assigned device.

exception qubes.devices.DeviceAlreadyAttached[source]

Bases: QubesException, KeyError

Trying to attach already attached device.

exception qubes.devices.DeviceNotAssigned[source]

Bases: QubesException, KeyError

Trying to unassign not assigned device.

exception qubes.devices.UnrecognizedDevice[source]

Bases: QubesException, ValueError

Device identity is not as expected.

class qubes.devices.AssignedCollection[source]

Bases: object

Helper object managing assigned devices.

add(assignment: DeviceAssignment)[source]

Add assignment to collection

discard(assignment: DeviceAssignment)[source]

Discard assignment from a collection.

get(device: DeviceInfo) DeviceAssignment[source]

Returns the corresponding DeviceAssignment for the device.

class qubes.devices.DeviceCollection(vm, bus)[source]

Bases: object

Bag for devices.

Used as default value for DeviceManager.__missing__() factory.

Parameters:
  • vm – VM for which we manage devices

  • bus – device bus

This class emits following events on VM object:

device-added:<class>(device)

Fired when new device is discovered.

device-removed:<class>(device)

Fired when device is no longer exposed by a backend VM.

device-attach:<class>(device, options)

Fired when device is attached to a VM.

Handler for this event may be asynchronous.

Parameters:
  • deviceDeviceInfo object to be attached

  • optionsdict of attachment options

device-pre-attach:<class>(device)

Fired before device is attached to a VM

Handler for this event may be asynchronous.

Parameters:

deviceDeviceInfo object to be attached

device-detach:<class>(port)

Fired when device is detached from a VM.

Handler for this event can be asynchronous (a coroutine).

Parameters:

deviceDeviceInfo object to be attached

device-pre-detach:<class>(port)

Fired before device is detached from a VM

Handler for this event can be asynchronous (a coroutine).

Parameters:

portPort object from which device be detached

device-assign:<class>(device, options)

Fired when device is assigned to a VM.

Handler for this event may be asynchronous.

Parameters:
  • deviceDeviceInfo object to be assigned

  • optionsdict of assignment options

device-unassign:<class>(device)

Fired when device is unassigned from a VM.

Handler for this event can be asynchronous (a coroutine).

Parameters:

deviceDeviceInfo object to be unassigned

device-list:<class>

Fired to get list of devices exposed by a VM. Handlers of this event should return a list of py:class:DeviceInfo objects (or appropriate class specific descendant)

device-get:<class>(port_id)

Fired to get a single device, given by the port_id parameter. Handlers of this event should either return appropriate object of DeviceInfo, or None. Especially should not raise exceptions.KeyError.

device-list-attached:<class>

Fired to get list of currently attached devices to a VM. Handlers of this event should return list of devices actually attached to a domain, regardless of its settings.

async assign(assignment: DeviceAssignment)[source]

Assign device to domain.

async attach(assignment: DeviceAssignment)[source]

Attach device to domain.

async detach(port: Port)[source]

Detach device from domain.

get_assigned_devices(required_only: bool = False) Iterable[DeviceAssignment][source]

Devices assigned to this vm (included in qubes.xml).

Safe to access before libvirt bootstrap.

get_attached_devices() Iterable[DeviceAssignment][source]

List devices which are attached to this vm.

get_dedicated_devices() Iterable[DeviceAssignment][source]

List devices which are attached or assigned to this vm.

get_exposed_devices() Iterable[DeviceInfo][source]

List devices exposed by this vm.

load_assignment(device_assignment: DeviceAssignment)[source]

Load DeviceAssignment retrieved from qubes.xml

This can be used only for loading qubes.xml, when VM events are not enabled yet.

async unassign(assignment: DeviceAssignment)[source]

Unassign device from domain.

async update_assignment(device: VirtualDevice, mode: AssignmentMode)[source]

Update assignment mode of an already assigned device.

Parameters:
  • device (VirtualDevice) – device for which change required flag

  • mode (AssignmentMode) – new assignment mode

class qubes.devices.DeviceManager(vm)[source]

Bases: dict

Device manager that hold all devices by their classes.

Parameters:

vm – VM for which we manage devices