How PnP Works in ReactOS
Where do baby PDOs come from?
Something, probably a bus driver, creates a PDO using data gathered when enumerating devices. This PDO will initially just implement functionality provided by the bus driver, such as power and enumeration service.
Each PDO created this way has a DeviceID and likely a BaseConfigVector with it. The DeviceID is used by the PnP manager to look up the associated service (more on this later) and the BaseConfigVector is an image of a IO_RESOURCE_REQUIREMENTS_LIST. The IO_RESOURCE_REQUIREMENTS_LIST contains the actual physical location of the enumerated hardware from the CPU's perspective.
The PDO enumerated by the Root bus is created as in PnpRootCreateDevice (ntoskrnl/io/pnproot.c). This is a normal device object, with the extras tacked on in the DeviceExtension. Each bus does this. By creating root bus entries in the registry, you can make your own device attachments!
The root bus driver is built-in, but it does everything uniformly with the other bus drivers. In the course of starting up, the root bus driver handles an IRP_MN_QUERY_BUS_RELATIONS irp, which sets up any devices listed as root children in the registry.
Ok what happens next
Well we have the PDO which knows the power state of the device, but it isn't connected to anything useful yet.
To bring up the device, NtPlugPlayControl is called from umpnpmgr after the device is discovered (IoRegisterPlugPlayNotification knows how to register for notification), and it in turn eventually calls IopLoadServiceModule if the indicated driver (which is ferreted out by umpnpmgr and entered in the registry) is not running.
You can quickly add any device attachment you like ... here's how:
Make a Root bus entry like this:
; Serial port COM1 HKLM,"SYSTEM\CurrentControlSet\Enum\Root\*PNP0501\1","HardwareID",0x00010000,"*PNP0501" HKLM,"SYSTEM\CurrentControlSet\Enum\Root\*PNP0501\1","DeviceDesc",0x00000000,"Communication port" HKLM,"SYSTEM\CurrentControlSet\Enum\Root\*PNP0501\1\LogConf","BasicConfigVector",0x000A0001,\ 58,00,00,00,\ 01,00,00,00,\ 00,00,00,00, 00,00,00,00, 00,00,00,00, 00,00,00,00, 00,00,00,00,\ 01,00,00,00,\ 01,00,01,00,\ 02,00,00,00,\ 00,01,01,00, 11,00,00,00, 08,00,00,00, 00,00,00,00, \ f8,03,00,00, 00,00,00,00, ff,03,00,00, 00,00,00,00, \ 00,02,01,00, 11,00,00,00, 04,00,00,00, 04,00,00,00
Notice that in the above, there are two resource assignments:
- A HardwareID of PNP0501 ... That's how umpnpmgr will find the inf entries
- A port assignment 00,01,01,00 for 0x3f8 -> 0x3ff
- An IRQ assigment for 4
These are passed to the:
[StdMfg] %*PNP0500.DeviceDesc% = ComPort_Inst,*PNP0500,*PNP0501
Part in media\inf\ports.inf, which knows that:
[ComPort_Inst.NT.Services] AddService = serial, 0x00000002, serial_Service_Inst AddService = serenum, , serenum_Service_Inst
It attaches to the 'serial' and 'serenum' services in a stack. This info is put in the registry where the kernel can find it.
So umpnpmgr ...
- discovers that a new PDO exists by receiving a notification
- searches the .infs for a matching DeviceID and enters the indicated information into the registry
- then asks the kernel to initialize the device (possibly calling IopLoadServiceModule along the way)
- at this point, the kernel is starting the device and has the DRIVER_OBJECT associated with the service that will run it
- It sends the DRIVER_OBJECT (there is no DEVICE_OBJECT associated with the driver at this point) an IRP_MJ_PNP:IRP_MN_ADD_DEVICE irp with the indicated PDO as an argument
- The driver consumes the config data
- Does IoAttachDeviceToDeviceStack
- Does IoCreateDevice with a name, creating the device's actual personality and well known name
- Now, we have a new device!