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:

The marker between the trace vdev's startup stage and its running stage is 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, and the vdev trace may start calling functions, such as vdev_thread_create(), that may not be called during the vdev startup stage.

Overview of the startup stage

The first thing every vdev must do is register with the hypervisor framework; that is, it must tell the qvm process instance that is assembling the VM in which the vdev will reside:

Defining the device

The vdev defines itself as a data structure. This structure is initialized by the startup, and updated as the state of the vdev changes:

/// 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
	int emit_logger;	///< 1 = write to logger (as defined by qvm config file). 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 that are specific to itself; that is, options that aren't common to all vdevs. Common options, 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).

The code snippets below show how the trace vdev defines its options:

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,
};
Note:

Don't repeat an option name in a context (for information about options in contexts 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 that that 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 context: another VM component or vdev. This means that 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 pre-defined callbacks (see vdtrace_control() in “Running: vdtrace_*() functions” below).

The trace vdev's vdtrace_control() 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 VM configuration (including your vdev). If the configuration isn't valid, the qvm process instance parsing the configuration exits with exit code 65. If the qvm process exits while parsing your vdev's options, the message accompanying the exit code can help you understand the error. These messages include:

For more information about qvm exit codes, see qvm 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 pre-defined 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 in the Virtual Device Developer's API Reference).

When option parsing is complete, the trace vdev checks to make sure that all the required options were provided, and exits with an error if this is not the case:

if(vdp->v_block.location == QSL_NO_LOCATION) {
	qvm_outf(QVM_OUTI_QVM, "%s: OPTIONS_END no location specified", __func__);
} else {
	qvm_outf(QVM_OUTI_QVM, "%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, in this order:

  1. Defines the vdev's options (see Defining the options above).
  2. Populates the vdev_factory data structure with the information that defines vdev trace.
  3. Calls the vdev_register_factory() function to register the vdev as defined by the information in the vdev_factory structure.

After defining its vdev-specific options, the vdtrace_register() function populates a vdev_factory structure with the information needed to define the vdev, and calls this populated structure vdtrace_factory.

The vdtrace_factory structure is populated as follows:

next
Always set this to NULL; reserved for the qvm process.
control
The function that controls the vdev: vdtrace_control() (see vdtrace_control()).
vread
The vdev's vread() function: vdtrace_vread(); when the guest writes, the vdev must call this function (see vdtrace_vread() and vdtrace_vwrite()).
vwrite
The vdev's vwrite() function: vdtrace_vwrite(); when the guest reads, the vdev must call this function (see vdtrace_vread() and vdtrace_vwrite()).
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.
For example, for a vdev in the qvm/vdev/foo/ directory, the build will create the vdev-foo.so shared object. This is the name you use when you add the vdev to the hypervisor host build (see the Building a Hypervisor System chapter in the QHS 2.0 User's Guide).
The qvm process expects the vdev to be specified in the VM configuration without these additions: vdev foo [options] (see the Configuration chapter in the QNX Hypervisor for Safety 2.0 User's Guide).
factory_flags
These flags control the vdev's capabilities; they must be set when the vdev is created (see factory_flags in the Virtual Device Developer's API Reference).

The flags set for this vdev are:

  • VFF_INTR_NO — the intr option isn't allowed
  • VFF_MUTEX — the device needs a mutex (not actually needed in vdev trace, but the source code for this vdev uses mutexes just to show how it is done)
  • VFF_VDEV_MEM — the loc option can be mem (i.e., the vdev is allowed to be a mem)
acc_size
Accepts only 32-bit access (four bytes) at a time: 1u << 4.
extra_space
Set aside local data space for this vdev; the space required is defined by the trace_state data structure (see Defining the device in this chapter).
safety
Mark this vdev as a safety variant; this designation is fictious, and only included here as an example (see vdev-core_anonymous_enum in the Virtual Device Developer's API Reference).

For reference, here is the bit of code in the vdtrace_register() function that populates the vdtrace_factory structure to define vdev trace:

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 that local data space is available for the internal structure.
	.extra_space = sizeof(struct trace_state),
	// For the safety version of vdev-trace, require "safety"; otherwise,
	// don't require "safety".
	.safety = VDEV_SAFETY_SELECTED,
};
Note:

Note that, by convention, the factory page:

  • data structure includes the suffix _factory
  • 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

After it populates the vdtrace_factory structure with the vdev trace definition, the vdtrace_register() function calls vdev_register_factory(), handing it the vdev definition and the vdev's application binary interface (ABI) version:

vdev_register_factory(&vdtrace_factory, QVM_VDEV_ABI);

See vdev_register_factory(), and Definitions in abi.h in the API Reference.