UDI Device Enumeration
Key structures
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;
Key Service Calls
void udi_enumerate_req(udi_enumerate_cb_t *cb, udi_ubit8_t enumeration_level);
/* Values for enumeration_level */
#define UDI_ENUMERATE_START 1
#define UDI_ENUMERATE_START_RESCAN 2
#define UDI_ENUMERATE_NEXT 3
#define UDI_ENUMERATE_NEW 4
#define UDI_ENUMERATE_DIRECTED 5
#define UDI_ENUMERATE_RELEASE 6
void udi_enumerate_ack (udi_enumerate_cb_t *cb, udi_ubit8_t enumeration_result, udi_index_t ops_idx);
/* Values for enumeration_result */
#define UDI_ENUMERATE_OK 0
#define UDI_ENUMERATE_LEAF 1
#define UDI_ENUMERATE_DONE 2
#define UDI_ENUMERATE_RESCAN 3
#define UDI_ENUMERATE_REMOVED 4
#define UDI_ENUMERATE_REMOVED_SELF 5
#define UDI_ENUMERATE_RELEASED 6
#define UDI_ENUMERATE_FAILED 255
Overview
After a UDI driver is loaded by the Management Agent (the operating system kernel), the MA proceeds to establish a specific IPC channel to the driver's primary region which will be used for driver control, called the "Management Metalanguage Channel". This particular UDI channel is unique, in that it is not strictly a two-way dialogue channel. Almost every other UDI channel specified by the core specification or by any metalanguage enables the driver to send messages over the channel, and enables the kernel to send messages to the driver. The Management Metalanguage only allows the MA to call into the driver, and forbids the driver to send messages to the MA unless first asked to do so.
The MA calls udi_enumerate_req() on a newly initiated device driver instance with an 'enumeration_level' argument of UDI_ENUMERATE_START to begin obtaining information about child devices of that device. The MA will loop, calling into the driver instance repeatedly with an 'enumeration_level' argument of UDI_ENUMERATE_NEXT, and the driver will respond to each call with a udi_enumerate_ack(), giving information on a new child device. This will continue until the driver responds with a udi_enumerate_ack() that has an 'enumeration_result' argument of UDI_ENUMERATE_DONE.
For devices with no children, the standard thing is to either always respond to a call to udi_enumerate_req() with a udi_enumerate_ack() that gives an 'enumeration_result' of UDI_ENUMERATE_LEAF (i.e.: "I am a leaf device, and I have no children"), or to make use of the MA-provided stub function that will do exactly the same thing on behalf of the driver.
Generally, bus drivers (PCI, ISA, ACPI, etc) and root enumerators (PCI IDE Controller, SATA, etc) will respond to udi_enumerate_req() with child devices, and most other types of drivers will simply return no children, because they generally have none. Each distinct child device identified by a parent driver should have a unique child_ID in the udi_enumerate_cb_t IPC control block.
The purpose of the udi_enumerate_req() and udi_enumerate_ack() pair is to enable the host kernel to construct its internal tree of devices. As a result, the information returned about each child device is information that is relevant to the kernel's device tree's construction.
Returning child device attributes
Key structures
udi_enumerate_cb_t (See above).
typedef struct {
char attr_name[32];
udi_ubit8_t attr_value[64];
udi_ubit8_t attr_length;
udi_instance_attr_type_t attr_type;
} udi_instance_attr_list_t;
Synopsis
The 'attr_name' and 'attr_value' pairs are, as you have probably guessed, key-value pairs for child-device attribute identification. On every call to udi_enumerate_ack() the driver returns the udi_enumerate_cb_t message block to the MA, with an array of these key-value pairs.
The attribute keys are specified by each UDI Bus Metalanguage. For example, a child-device returned by a PCI bus driver would have the following attribute keys:
- 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).
Note well: the information returned by a parent device is agnostic of the type of device that the child is -- the device can be a modem, a network card, a graphics card, or anything else. The parent bus driver is not required to know what type of device the child is. It must only return information about the child device that allows the MA (kernel) to search the disk for a driver to match that device. For the PCI bus, this information is the PCI "vendor ID" and PCI "device ID".
The kernel will take this information and create a new node in its device tree. Then, for each child device, it will search the disk for drivers that match the instance attributes of the child device in question. The following are examples to aid understanding:
- PCI bus driver enumerates a device with attributes "pci_vendor_id" and "pci_device_id".
- pci_vendor_id = 0x8086, pci_device_id = 0x2411:
- Kernel searches disk for device drivers matching this combination of vendor and device.
- If the correct driver is installed, it will find that the combination matches the driver for "IDE Controller (UltraATA/66)", an Intel IDE controller.
- The kernel loads the driver for "IDE Controller (UltraATA/66)".
- pci_vendor_id = 0x8086, pci_device_id = 0x2411:
- PCI bus driver enumerates a device with attributes "pci_vendor_id" and "pci_device_id".
- pci_vendor_id = 0x1002, pci_device_id = 0x5159:
- Kernel searches disk for device drivers matching this combination of vendor and device.
- If the correct driver is installed, it will find that the combination matches the driver for "Radeon 7000 series AGP", an ATI/AMD graphics chip.
- The kernel loads the driver for "Radeon 7000 series AGP".
- pci_vendor_id = 0x1002, pci_device_id = 0x5159:
Hot-Plug enumeration
Normal enumeration sequence
The normal enumeration sequence goes as follows:
- The UDI MA (kernel) first calls into the driver (udi_enumerate_req()) with an 'enumeration_level' argument of UDI_ENUMERATE_START.
- The driver responds to each call until it has enumerated all child devices, at which point it returns UDI_ENUMERATE_DONE.
- The MA keeps calling udi_enumerate_req() with 'UDI_ENUMERATE_NEXT' until it gets a UDI_ENUMERATE_DONE from the driver.
Hot-plug enumeration
When the MA is done doing the "first" enumeration loop, it may want to know about any child devices that get added (or removed) at runtime. To this end, the MA can make a call to udi_enumerate_req() with an 'enumeration_level' argument of UDI_ENUMERATE_NEW. UDI_ENUMERATE_NEW will cause the driver to hold on to the IPC message, and not respond until a child device change has been detected, such as a hot-add or hot-removal of a child device. At that point, the driver will respond to the kernel with information about the event.
- If the event is the hot-addition of a new device, the driver will respond with UDI_ENUMERATE_OK
- The kernel will post a new UDI_ENUMERATE_NEW request, and the driver will hold on to it again until a new hot-plug event occurs.
- If the event is the hot-removal of a child device, the driver will respond with UDI_ENUMERATE_REMOVED, and set the child_ID member of the message block to the ID of the child that has been removed.
- The kernel will post a new UDI_ENUMERATE_NEW request, and the driver will hold on to it again until a new hot-plug event occurs.
- If the event is the hot-removal of the parent node itself, such as a bus being removed without warning, etc, the running driver will respond with UDI_ENUMERATE_REMOVED_SELF.
- The kernel will most likely terminate the parent driver at this point, as well as all of its children.