Defining and registering (vdev trace)
During its startup stage, the trace vdev constructs and configures itself, then registers itself in the VM framework.
As with other vdevs (and just like most physical devices), the life of vdev trace can be divided into two stages:
- startup — the vdev constructs itself
- running — the vdev responds to requests from the pseudo-driver in the guest
The marker between the vdev's startup stage and its running stage is the receipt of the VDEV_CTRL_GUEST_CONFIGURED callback. This callback marks the point at which the vdev and the qvm process have finished constructing and configuring the vdev and the VM. At this point, the vdev trace may start calling functions, such as vdev_thread_create(), that may not be called during the startup stage.
Overview of the startup stage
The first thing every vdev must do is register with the hypervisor framework. This means it must announce the following to the qvm process instance that is assembling the VM in which the vdev will reside:
- that the vdev exists
- what the vdev's options are
- any other information that qvm needs to know about the vdev; for example, the trace vdev specifies the constructor argument to indicate that the vdev shared object should be loaded at runtime
Defining the device
/// One instance of the trace device.
struct trace_state {
pthread_t thread; ///< The thread ID for the vdtrace_thread
uint32_t counter; ///< A counter that increments for each trace_device read
uint32_t period_us; ///< The counter period in microseconds
int emit_logger; ///< 1 = write to logger (as defined in the VM configuration).
///< Default = 0.
int emit_trace; ///< 1 = emit a trace event to the hypervisor host.
///< Default = 0.
int trace_event; ///< The event to emit (only if emit_trace is specified)
};The trace vdev's vdtrace_*() functions update this
structure when the vdev is running (see The running vdev (vdev trace)
in this
chapter).
Defining the options
Like other vdevs in QNX hypervisor VMs, the trace vdev doesn't need to
define all its options. It needs to define only those options specific to itself.
Options that are common to all vdevs, such as the
loc option, are defined by the qvm process,
which looks after parsing them (see Common vdev options
in the User's Guide).
enum {
OPT_LOGGER_IDX,
OPT_TRACE_EVENT_IDX,
OPT_NUM_OPTS
};
...
static const char *const trace_options[OPT_NUM_OPTS + 1] = {
[OPT_LOGGER_IDX] = QVM_OPTION_NO_ARG_PREFIX "emit-logger",
[OPT_TRACE_EVENT_IDX] = "emit-trace",
[OPT_NUM_OPTS] = NULL,
};Don't repeat an option in a context (for more information, see
Contexts
in the User's Guide Configuration
chapter).
That is, when you define your vdev's options, don't repeat an option name unless
you know this is what your vdev needs (see vdev hpet in the User's Guide for
an example of a vdev that repeats an option).
When a VDEV_CTRL_OPTIONS_END callback is issued, the qvm process instance returns to a higher context; that is, parsing moves on to the next VM component or vdev. This means you may use the same option name for different options in different vdevs—though this may confuse your users.
Parsing the vdev options
In the trace vdev, parsing the options and assembling the vdev is
handled by the vdtrace_control() function, using predefined callbacks.
This function calls qvm_parse_num(); the qvm/utils.h
header file defines this and other qvm_parse_*() functions, which your
vdev should call to parse its options (see the utils.h
chapter in the Virtual Device
Developer's API Reference).
When you call the qvm_parse_*() functions, the qvm process looks after checking the validity of the VM configuration (including your vdev). If the configuration isn't valid, the qvm process instance parsing the configuration exits with exit code 65. If qvm exits while parsing your vdev's options, the message accompanying the exit code can help you understand the error. The message can be one of:
- Unknown option — the qvm process encountered an option in the
configuration that isn't defined in your vdev, or you have marked your vdev
QVM_OPTION_NO_ARG_PREFIX but the configuration includes an
option (see
Definitions in vdev-core.h
in the Virtual Device Developer's API Reference). - Expecting a number near XXX — the vdev expects an option, but none is provided in the configuration.
For more information about the exit code, see qvm process exit codes
in the
User's Guide Monitoring and Troubleshooting
chapter.
Notice that vdtrace_control() includes the
VDEV_CTRL_GEN_FDT and VDEV_CTRL_GEN_ACPI predefined
callbacks, which can be called to generate a node for the new device in the VM's
FDT, or add the device to the VM's ACPI tables (see
Definitions in vdev-core.h
).
if (vdp->v_block.location == QSL_NO_LOCATION) {
qvm_fatal(QLS_QVM, "%s: OPTIONS_END no location specified", __func__);
} else {
vdev_logf(QL_QVM_INFO, "%s: OPTIONS_END Location 0x%lx", __func__,
vdp->v_block.location);
}
break;Note that the qvm process can't handle this check; it has no way of knowing what options your vdev requires in addition to the common options.
Defining the vdev in the factory structure
In vdev trace, registering the function with the VM framework is handled by the vdev's vdtrace_register() function, which does the following:
- Defines the vdev's options (see above).
- Populates the vdev_factory data structure with the information that defines vdev trace.
- Calls the vdev_register_factory() function to register the vdev as it's defined in the vdev_factory structure, which it calls vdtrace_factory.
The vdtrace_factory structure is populated as follows:
- next
- Always set to NULL; reserved for the qvm process.
- control
- The function that performs operations on the vdev: vdtrace_control().
- vread
- The vdev's vread() function: vdtrace_vread(); when the guest writes, the VM must call this function.
- vwrite
- The vdev's vwrite() function: vdtrace_vwrite(); when the guest reads, the VM must call this function.
- option_list
- The vdev's option definitions: trace_options (see
Defining the options
above). - name
- The vdev's name. Typically, set to NULL, in which case the build process adds the vdev- prefix and the .so suffix to the vdev's directory name when it creates the vdev shared object.
- factory_flags
- These flags control the vdev's capabilities; they must be set when the vdev is created (see vdev_factory_flags_t in the Virtual Device Developer's API Reference).
- acc_sizes
- A bitset of left-shifted values that define a list of allowed access sizes.
For example, if
1u << 4is set, the vdev accepts accessing 32 bits (four bytes) at a time. - extra_space
- Set aside local data space for this vdev's state; the space required is defined by
the trace_state data structure (see
Defining the device
above).
static struct vdev_factory vdtrace_factory = {
.next = NULL, // Patched
.control = vdtrace_control,
.vread = vdtrace_vread,
.vwrite = vdtrace_vwrite,
.option_list = trace_options,
.name = NULL, // Patched
.factory_flags = VFF_INTR_NO | VFF_MUTEX | VFF_VDEV_MEM,
// Accept only 32-bit access
.acc_sizes = 1u << 4,
// Make sure we have local data space for our internal structure
.extra_space = sizeof(struct trace_state),
};By convention, the factory page data structure includes the suffix _factory, and the members and the functions they reference follow a similar naming pattern (e.g., vdtrace_control, vdtrace_control).
You don't need to follow this convention, but it may make reading vdev code easier when you come back to it in the future.
For more information about vdev_factory and
vdev_factory_flags, see the vdev-core.h
chapter in the
Virtual Device Developer's API Reference.
Registering the vdev
vdev_register_factory(&vdtrace_factory, QVM_VDEV_ABI);See vdev_register_factory(), and Definitions in abi.h
in the API
Reference.
