UDI Driver Loading

From OSDev Wiki
Jump to navigation Jump to search


This page seeks to give a minimalistic overview of the general sequence for loading a UDI driver from disk, and using it to instantiate a detected device. It touches on what information is needed to load a driver, and how the various parts of the 'udi_init_info_t' work together with the .udiprops/udiprops.txt configuration files and metalanguages to enable the kernel to make decisions on which drivers to load when attempting to instantiate drivers for the devices in its device tree.

This is a non-trivial article, and is probably only useful to persons with a moderately advanced kernel of their own. Recommended prior reading includes, but is not limited to UDI Device Enumeration, and the UDI v1.01 specifications.

The UDI device tree model

UDI employes a very simple device tree model: devices enumerate children for the kernel, and the kernel instantiates the devices by loading drivers for them, and then if those devices have children, their drivers enumerate them in turn, and then the kernel instantiates those child devices by loading drivers for them, and so on ad infinitum.

The most obvious approach that would occur to the reader then, is most likely to have a tree-like structure, or some other such structure to hold device nodes in the kernel's device tree, and build the device tree from the information returned by UDI drivers.

The relationship between metalanguages and device enumeration

Key structures

/* The structure used to represent a single named attribute and its value. */
typedef struct {
	char attr_name[UDI_MAX_ATTR_NAMELEN];
	udi_ubit8_t attr_value[UDI_MAX_ATTR_SIZE];
	udi_ubit8_t attr_length;
	udi_instance_attr_type_t attr_type;
} udi_instance_attr_list_t;

/* The structure used to represent a child device when being enumerated by a parent driver.
 * Notice the "attr_list" struct member, which is essentially an array of named
 * attributes.
 **/
typedef struct {
	udi_cb_t gcb;
	udi_ubit32_t child_ID;
	void *child_data;
	udi_instance_attr_list_t *attr_list;
	udi_ubit8_t attr_valid_length;
	const udi_filter_element_t *filter_list;
	udi_ubit8_t filter_list_length;
	udi_ubit8_t parent_ID;
} udi_enumerate_cb_t;

Device Instance Attributes

When a UDI driver enumerates its child devices to the kernel, it does so using a series of named "instance attributes". The particular attributes returned by the driver for the child device depend on the type of device that the parent is: for example, if the parent is a PCI bus, then it would return attributes that describe a PCI device. If the parent device is an ISA root bus device, it would return attributes that best describe an ISA device.

Who decides what attributes "best" describe a PCI child device? Or what guarantee is there that different drivers for the same bus won't return different named attributes, or employ custom naming schemes for attributes? The answer is quite simple: the attributes returned by enumerator drivers for a particular bus, are determined by the UDI metalanguage for that bus. For example, the UDI PCI Metalanguage specifies the bindings for PCI drivers, relative to the core specification. In more detail, we can examine the PCI Metalanguage's actual attributes, as it defines them:

  • bus_type (attr_type: STRING).
  • pci_vendor_id (attr_type: UBIT32).
  • pci_device_id (attr_type: UBIT32).
  • pci_revision_id (attr_type: UBIT32).
  • pci_baseclass (attr_type: UBIT32).
  • pci_sub_class (attr_type: UBIT32).
  • pci_prog_if (attr_type: UBIT32).
  • pci_subsystem_vendor_id (attr_type: UBIT32).
  • pci_subsystem_id (attr_type: UBIT32).
  • pci_unit_address (attr_type: UBIT32).
  • pci_slot (attr_type: UBIT32).

These attributes "best" describe a PCI child device to the kernel. This information is complete, and more than sufficient for enabling the kernel to add this new child device to its device tree. The 'pci_vendor_id' and 'pci_device_id' can then be used to search the disk for a driver that matches those attributes (PCI device/vendor ID).

Naturally, this is the recommended, and officially standardized Metalanguage for the PCI bus, but the UDI specification is generic in nature, and new metalanguages can be developed with ease; it is perfectly possible for a retarded rogue party to choose to define a custom PCI Metalanguage binding. Thankfully, even this would not cause any problems with interoperability: the rogue party's custom PCI metalanguage would actually seamlessly fit into existing UDI environments without any modification. The reasons for this are beyond the scope of this article -- suffice it to say that competing standards are not an issue for, or a threat to UDI compliant kernels. Drivers will "just work".

Synopsis so far

Parent drivers enumerate their child devices to the kernel, and they tell the kernel about the attributes of their child devices using attributes specified by a standardized UDI committee sanctioned metalanguage, or a custom metalanguage. The kernel can then use these attributes as criteria when searching the disk for the correct or preferred driver for a device.

The relationship between Device Instance Attributes and udiprops.txt/.udiprops

Introduction to Udiprops

Much like Microsoft Windows' .INI configuration information files that come with drivers, UDI uses a text based configuration approach to distributing drivers. This configuration shall always be distributed with drivers in one of the following forms:

  • For binary-only drivers, the configuration information shall be embedded into the driver's executable binary object in an executable section called ".udiprops".
  • For source distributions of drivers, the configuration information shall be found in a file called "udiprops.txt" within the source distribution.

What is udiprops used for?

Udiprops gives information about how to compile the driver (for source distributions), the metalanguages used in the driver binaries, and it also gives information about how to match the driver against instance attributes when a kernel is searching the disk for the correct driver to use to instantiate a given device.

Naturally, it would occur to any fast-thinking reader that this means that the kernel would need to search the udiprops of each driver when trying to find the correct driver for a device. Naturally, it would also subsequently occur to a faster-thinking reader that a more mature implementation could have a database or registry of sorts with the instance attribute information stripped out to enable fast index lookups for driver attributes. The actual method used to search the drivers available on disk is not specified by the UDI Core Specification. A kernel can choose to slowly read each driver's udiprops, or compile the information in a database, or even not to do either of these, and just load a fixed set of drivers that it knows it needs. All of this is up to the implementor.

Matching device instance attributes against udiprops

message 1001 Fake driver written by DeceptionCorp Inc.
message 1002 DeceptionCorp Inc.
message 1003 PO-BOX #123 Fake Valley, Not Real City, USA; Tel 765-4321;

name     1002
supplier 1002
contact  1003

requires udi_pci 0x101
requires udi_sysbus 0x101

meta 1 udi_pci
meta 2 udi_sysbus

message 200 Example VGA graphics device, model 1
message 201 Example VGA graphics device, model 2

device 200 1 \
   bus_type      string pci \
   pci_vendor_id ubit32 0x8086 \
   pci_device_id ubit32 0x1234

device 201 1 \
   bus_type      string pci \
   pci_vendor_id   ubit32 0x8086 \
   pci_device_id   ubit32 0x2345 \
   pci_revision_id ubit32 0x02

device 201 2 \
   bus_type           string system \
   sysbus_mem_addr_lo 0xA0000 \
   sysbus_mem_addr_hi 0x0 \
   sysbus_mem_size    0x20000 \
   sysbus_io_addr_lo  0x3C0 \
   sysbus_io_addr_hi  0x0 \
   sysbus_io_size     0x8 \
   sysbus_intr0       16

The above is an example udiprops which has been constructed on the fly to meet the needs of this article. It is simplified to the extent of being non-compliant, so that the bare minimum information needed to enable a kernel to match a driver against instance attributes can be explained. That said, a few extra lines have been added (name, supplier, contact) to make it look more wholesome. It demonstrates declaration of a single driver that supports multiple different devices (model 1 and model 2), and shows how to declare a single device that can be found on multiple buses (Model 2 can be found on the PCI bus or the ISA bus, as is very common for VGA devices).

"Device" lines in udiprops

Each "device" line in a udiprops configuration describes one set of attribute match values which are meant to be compared against a given device in the kernel's device tree. In most cases, these will describe separate devices, but it is also possible for a device to be configurable on multiple different buses. For example, a VGA graphics controller may be enumerated as part of the ISA bus, or it may be enumerated from the PCI bus. The I/O ports and memory locations will usually remain the same regardless of which bus it is exposed on, but naturally, each device is different, and this may not hold true for every device.

device <device_name_string_index> <bus_metalanguage_index> \
   <attribute_name> <attribute_type> <attribute_value>

"Device" declaration lines with the same '<device_name_string_index>' number refer to the same device. "Device" declaration lines with differing '<device_name_string_index>' numbers refer to different devices. As can be seen in the example udiprops above, there are two distinct devices which our example driver claims to support:

  • Example VGA graphics device, model 1; message-string-index 200.
  • Example VGA graphics device, model 2; message-string-index 201.


The '<bus_metalanguage_index>' enables the kernel to know what Metalanguage libraries need to be loaded to properly start the driver process when the driver is loaded for a match against this "device" line. For example, the first two devices would require the 'udi_pci' bus metalanguage libraries to be loaded, while the third device declaration would instead require the 'udi_sysbus' library. Our fake driver's udiprops claims to enumerate devices on two buses:

  • udi_pci (metalanguage index 1).
  • udi_sysbus (metalanguage index 2).


The named device instance attributes which follow the '<bus_metalanguage_index>' all follow the attributes mandated by the particular UDI bus Metalanguage that the device declaration uses. For example, the UDI PCI Metalanguage uses 'bus_type', 'pci_vendor_id' and 'pci_device_id' among other attributes for device attributes. A driver need not specify every named attribute in a "device" declaration, but supplying a large number of attributes will make locating the correct driver easier. For the PCI bus though, drivers really need only specify 'pci_vendor_id' and 'pci_device_id', because as the user most likely already knows, these two are for the most part all that are needed to match a PCI device against its driver.

A sharp reader would also have noticed that the named instance attributes listed for the ISA sysbus "device" declaration of "Model 2" do not have the same attributes as a PCI device. This is because the attributes of a PCI device differ from the attributes of an ISA device, and the UDI Sysbus Metalanguage defines different attributes for sysbus devices. Different bus metalanguages use different attributes to describe child devices because different buses work differently. The same would apply to say, IDE devices enumerated by an IDE driver. IDE/PATA disks obviously cannot rationally be expected to be identified by 'pci_vendor_id' and 'pci_device_id'.

Conclusion

So in conclusion, our fake driver above declares two distinct devices. The first can only be found on the PCI bus, and the second can be found on both the PCI and the ISA buses. The kernel would match the device tree instance attributes against the udiprops of each driver on disk, or against its database, or whatever its search method may be, and find the best driver to use, and load that driver to service the device.