UDI Metalanguages

From OSDev Wiki
Jump to navigation Jump to search

UDI Metalanguages can be described succinctly as doing two things:

  • Defining APIs for IPC between drivers (and the host kernel's userspace).
  • Defining extensions to the Core UDI Specification to enable it to meet the needs of special device driver types.

Metalanguages are used in every UDI driver, since they are essentially APIs. Any API call that you use to call into a UDI driver is defined by a Metalanguage -- Metalanguages define UDI APIs. For example, if you were to call udi_devmgmt_req() to ask a driver to "Prepare to Suspend", you would be calling a function that is part of the UDI Management Metalanguage API. This Management Metalanguage API has other functions grouped under it as well, and together these functions make up the Management Metalanguage API:

typedef const struct {
  udi_usage_ind_op_t *usage_ind_op;
  udi_enumerate_req_op_t *enumerate_req_op;
  udi_devmgmt_req_op_t *devmgmt_req_op;
  udi_final_cleanup_req_op_t *final_cleanup_req_op;
} udi_mgmt_ops_t;

Metalanguage APIs form the basis of device-class APIs in UDI.

Metalanguage Operations Vectors

In the above example for the UDI Management Metalanguage, all of the Management Metalanguage API functions were grouped into one function-pointer struct. This is because the Management Metalanguage is a very simple API, so it can't really be separated into groups of related functions. By all means, every API can be exposed this way -- as a single congested block of functions with no separation or organization.

But it is beneficial to allow for the APIs to be broken into sub-groups of functions. Consider for example, yet again, a network card's driver. Most network card drivers can be split up into several groups of functions:

  • Functions related to device control and status management.
  • Functions related to sending network frames.
  • Functions related to receiving network frames from the network.

A naive implementation of a Network Metalanguage API could kludge all of its function together, regardless of their logical sub-grouping, similarly to this example below, where a naive Network Metalanguage groups all of its control, send and receive operations together indiscriminately:

typedef const struct {
/* Control and status related functions */
	udi_nd_bind_req_op_t *nd_bind_req_op;
	udi_nd_unbind_req_op_t *nd_unbind_req_op;
	udi_nd_enable_req_op_t *nd_enable_req_op;
	udi_nd_disable_req_op_t *nd_disable_req_op;
	udi_nd_ctrl_req_op_t *nd_ctrl_req_op;
	udi_nd_info_req_op_t *nd_info_req_op;
/* Send functions */
	udi_nd_tx_req_op_t *nd_tx_req_op;
	udi_nd_exp_tx_req_op_t *nd_exp_tx_req_op;
/* Frame receipt notification functions. */
	udi_nd_rx_rdy_op_t *nd_rx_rdy_op;
} udi_nd_ctrl_ops_t;

And it would be fine, and functional -- there is nothing technically wrong with this: the host kernel can call into the driver just fine, and the driver can respond to the requests just fine as well. However, if instead the Metalanguage API was to be redesigned, and all of these functions broken into groups of related logical sub-functions:

typedef const struct {
	udi_channel_event_ind_op_t *channel_event_ind_op;
	udi_nd_rx_rdy_op_t *nd_rx_rdy_op;
} udi_nd_rx_ops_t;

typedef const struct {
	udi_channel_event_ind_op_t *channel_event_ind_op;
	udi_nd_tx_req_op_t *nd_tx_req_op;
	udi_nd_exp_tx_req_op_t *nd_exp_tx_req_op;
} udi_nd_tx_ops_t;

typedef const struct {
	udi_channel_event_ind_op_t *channel_event_ind_op;
	udi_nd_bind_req_op_t *nd_bind_req_op;
	udi_nd_unbind_req_op_t *nd_unbind_req_op;
	udi_nd_enable_req_op_t *nd_enable_req_op;
	udi_nd_disable_req_op_t *nd_disable_req_op;
	udi_nd_ctrl_req_op_t *nd_ctrl_req_op;
	udi_nd_info_req_op_t *nd_info_req_op;
} udi_nd_ctrl_ops_t;

Then the kernel would be calling into a different struct for each of the groups of functions (control, send and receive). This would also enable the kernel to make assumptions about the grouping of these functions. Whereas before, the kernel could not assume that any function within the "big kludge of functions" was functionally distinct from the others, now it can assume that each individual structure containing function pointers implies a logically separate flow of signaling into the driver.

This enables the kernel to create separate IPC request queues (a.k.a., UDI Channels) for each group of functions (if it so desires); in the case of our example Network Metalanguage API, separate channels could be created for control IPC messages, send IPC messages and receive IPC notifications. Of course, this only applies to microkernels and hybrid kernels, because monolithic kernels call directly into their drivers without using any messages at all. A monolithic kernel would just ignore all of this.

Any single Metalanguage API can specify that its API be broken up into multiple function-groups. That is, any Metalanguage can specify <N> different functions, grouped into <M> different groupings, as seen fit by that Metalanguage. Drivers just provide their entry points for the functions, grouped as recommended by the Metalanguage specification.

Conclusion

Metalanguages extend the UDI Core Specification by introducing new APIs for adoption and standardization. Driver vendors can also choose to provide new metalanguages if they see fit. And drivers can provide any number of APIs as needed.

For example, a graphics driver can expose an OpenGL API via an OpenGL Metalanguage, and also expose a Direct 3D API via a D3D Metalanguage.