<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>A Hole In My Head</title><link>http://blogs.msdn.com/doronh/default.aspx</link><description>Doron Holan's musings on drivers and other nibbles and bits</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>WDFREQUESTs are for sharing in KMDF v1.9</title><link>http://blogs.msdn.com/doronh/archive/2009/03/05/wdfrequests-are-for-sharing-in-kmdf-v1-9.aspx</link><pubDate>Fri, 06 Mar 2009 00:48:14 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9460771</guid><dc:creator>doronh</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/doronh/comments/9460771.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=9460771</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=9460771</wfw:comment><description>&lt;p&gt;In my last &lt;a href="http://blogs.msdn.com/doronh/archive/2009/03/04/wdfrequests-are-not-for-sharing.aspx" target="_blank"&gt;post&lt;/a&gt; I described why a WDFREQUEST is unique to a particular WDFDEVICE.&amp;#160; There is one particular programming pattern where this is not the behavior you want.&amp;#160; This pattern is when you have each PDO accepting IO requests which it then forwards on to the parent WDFDEVICE for processing. One great in box example of this is usbhub.sys.&amp;#160; Each usbhub PDO receives URBs which are then forwarded to the parent FDO and the FDO is where all IO processing occurs.&amp;#160; &lt;/p&gt;  &lt;p&gt;If you want to apply this pattern to a KMDF driver written to a v1.7 or earlier and take advantage of WDFQUEUEs you had to send the requests from the PDO to the FDO with WdfRequestSend so that they were represented to the FDO. The easiest way to do this is to create a WDFIOTARGET for the FDO itself and then have each PDO send IO to that WDFIOTARGET as shown in the following 3 code snippets. &lt;/p&gt;  &lt;h3&gt;EvtDriverDeviceAdd for the FDO&lt;/h3&gt;  &lt;pre&gt;NTSTATUS EvtDriverDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) 
{
   WDFDEVICE device;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&amp;amp;woa, FDO_EXTENSION);

   // ...initialize the DeviceInit...
   status = WdfDeviceCreate(&amp;amp;DeviceInit, &amp;amp;woa, &amp;amp;device);

   if (!NT_SUCCESS(status)) {
      return status;
   }

   PFDO_EXTENSION pFdoExt = GetFdoExt(device);
   status = WdfIoTargetCreate(device, WDF_NO_OBJECT_ATTRIBUTES, &amp;amp;pFdoExt-&amp;gt;SelfTarget);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   // open the WDFIOTARGET to point our own PDEVICE_OBJECT
   WDF_IO_TARGET_OPEN_PARAMS openParams;
   WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(openParams, WdfDeviceWdmGetDeviceObject(device));

   status = WdfIoTargetOpen(pFdoExt-&amp;gt;SelfTarget, &amp;amp;openParams);
   if (!NT_SUCCESS(status)) {
      return status;
   }
   ...
   return status;
}&lt;/pre&gt;

&lt;h3&gt;EvtChildListCreateDevice for the PDO&lt;/h3&gt;

&lt;pre&gt;NTSTATUS EvtChildListCreateDevice(
    WDFCHILDLIST ChildList, 
    PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription,
    PWDFDEVICE_INIT ChildInit
    )
{
   WDFDEVICE pdo;
   NTSTATUS status;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&amp;amp;woa, PDO_EXTENSION);

   status = WdfDeviceCreate(&amp;amp;ChildInit, &amp;amp;woa, &amp;amp;device);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   PPDO_EXTENSION pPdoExt = GetPdoExt(device);
   PFDO_EXTENSION pFdoExt = GetFdoExt(WdfPdoGetParent(device));

   pPdoExt-&amp;gt;ParentTarget = pFdoExt-&amp;gt;SelfTarget;
   ...
   return status;
}&lt;/pre&gt;

&lt;h3&gt;EvtIoDefault for the PDO&lt;/h3&gt;

&lt;pre&gt;typedef
VOID EvtIoDefault(WDFQUEUE Queue, WDFREQUEST Request)&lt;/pre&gt;

&lt;pre&gt;{
    // ...  extract Request type ...
    if (&lt;i&gt;RequestShouldBeSentToParent&lt;/i&gt;()) {
       WdfRequestFormatRequestUsingCurrentType(Request);
       WdfRequestSend(Request, GetPdoExt(WdfIoQueueGetDevice(Queue))-&amp;gt;ParentTarget);
    }
}&lt;/pre&gt;

&lt;p&gt;As you can see, this is a bit cumbersome!&amp;#160; While it works, it is not ideal.&amp;#160; The KMDF team addressed this issue in v1.9 by adding 2 new DDIs that must be used together&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/dd434715.aspx" target="_blank"&gt;WdfPdoInitAllowForwardingRequestToParent&lt;/a&gt; which tells KMDF that you will be forwarding IO from a PDO to the parent FDO.&amp;#160; Internally this sets up some bookkeeping and, more importantly, sets the PDO’s PDEVICE_OBJECT’s StackSize to the FDO’s PDEVICE_OBJECT StackSize+1 so that there will be enough stack locations in the underlying PIRP for both the parent and child stacks &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/dd434849.aspx" target="_blank"&gt;WdfRequestForwardToParentDeviceIoQueue&lt;/a&gt; which removes the need for the custom WDFIOTARGET and WdfRequestSend and directly presents the PDO’s WDFREQUEST to the FDO’s WDFQUEUE &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s now rewrite the 3 code snippets to make use of these new DDIs, new code in &lt;font color="#ff0000"&gt;red&lt;/font&gt;&lt;/p&gt;

&lt;h3&gt;NEW EvtDriverDeviceAdd for the FDO&lt;/h3&gt;

&lt;pre&gt;NTSTATUS EvtDriverDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) 
{
   WDFDEVICE device;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&amp;amp;woa, FDO_EXTENSION);

   // ...initialize the DeviceInit...
   status = WdfDeviceCreate(&amp;amp;DeviceInit, &amp;amp;woa, &amp;amp;device);

   if (!NT_SUCCESS(status)) {
      return status;
   }

   PFDO_EXTENSION pFdoExt = GetFdoExt(device);
&lt;strike&gt;
   status = WdfIoTargetCreate(device, WDF_NO_OBJECT_ATTRIBUTES, &amp;amp;pFdoExt-&amp;gt;SelfTarget);
   if (!NT_SUCCESS(status)) {
      return status;
   }

   // open the WDFIOTARGET to point our own PDEVICE_OBJECT
   WDF_IO_TARGET_OPEN_PARAMS openParams;
   WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(openParams, WdfDeviceWdmGetDeviceObject(device));

   status = WdfIoTargetOpen(pFdoExt-&amp;gt;SelfTarget, &amp;amp;openParams);
   if (!NT_SUCCESS(status)) {
      return status;
   }
&lt;/strike&gt;

&lt;font color="#ff0000"&gt;
   // initialize a WDF_IO_QUEUE_CONFIG to your needs
   status = WdfIoQueueCreate(device, ..., &amp;amp;pFdoExt-&amp;gt;&amp;gt;ChildProcessingQueue);
   if (!NT_SUCCESS(status)) {
      return status;
   }
&lt;/font&gt;
   ...
   return status;
}&lt;/pre&gt;

&lt;h3&gt;NEW EvtChildListCreateDevice for the PDO&lt;/h3&gt;

&lt;pre&gt;NTSTATUS EvtChildListCreateDevice(
    WDFCHILDLIST ChildList, 
    PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription,
    PWDFDEVICE_INIT ChildInit
    )
{
   WDFDEVICE pdo;
   NTSTATUS status;

   WDF_OBJECT_ATTRIBUTES woa;
   WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&amp;amp;woa, PDO_EXTENSION);

&lt;font color="#ff0000"&gt;
   WdfPdoInitAllowForwardingRequestToParent(ChildInit);
&lt;/font&gt;   

   status = WdfDeviceCreate(&amp;amp;ChildInit, &amp;amp;woa, &amp;amp;device);
   if (!NT_SUCCESS(status)) {
      return status;
   }

&lt;strike&gt;   PPDO_EXTENSION pPdoExt = GetPdoExt(device);
   PFDO_EXTENSION pFdoExt = GetFdoExt(WdfPdoGetParent(device));

   pPdoExt-&amp;gt;ParentTarget = pFdoExt-&amp;gt;SelfTarget;&lt;/strike&gt;
   ...
   return status;
}&lt;/pre&gt;

&lt;h3&gt;NEW EvtIoDefault for the PDO&lt;/h3&gt;

&lt;pre&gt;VOID EvtIoDefault(WDFQUEUE Queue, WDFREQUEST Request)&lt;/pre&gt;

&lt;pre&gt;{
    // ...  extract Request type ...
    if (&lt;i&gt;RequestShouldBeSentToParent&lt;/i&gt;()) {
&lt;strike&gt;
       WdfRequestFormatRequestUsingCurrentType(Request);
       WdfRequestSend(Request, GetPdoExt(WdfIoQueueGetDevice(Queue))-&amp;gt;ParentTarget);
&lt;/strike&gt;
&lt;font color="#ff0000"&gt;
       WDF_REQUEST_FORWARD_OPTIONS options;
       WDF_REQUEST_FORWARD_OPTIONS_INIT(&amp;amp;options);
       options.Flags = WDF_REQUEST_FORWARD_OPTIONS_FLAGS;&lt;/font&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;font color="#ff0000"&gt;
       status = WdfRequestForwardToParentDeviceIoQueue(
           Request, 
           GetFdoExt(WdfPdoGetParent(WdfIoQueueGetDevice(Queue)))-&amp;gt;ChildProcessingQueue,
           &amp;amp;options);
&lt;/font&gt;
    }
}&lt;/pre&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9460771" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/KMDF/default.aspx">KMDF</category><category domain="http://blogs.msdn.com/doronh/archive/tags/Design+Patterns/default.aspx">Design Patterns</category></item><item><title>WDFREQUESTs are not for sharing</title><link>http://blogs.msdn.com/doronh/archive/2009/03/04/wdfrequests-are-not-for-sharing.aspx</link><pubDate>Thu, 05 Mar 2009 03:58:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9459155</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/9459155.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=9459155</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=9459155</wfw:comment><description>&lt;P&gt;&lt;EM&gt;FYI: this is a bit of a long post, but I wanted to be thorough and illustrative and give some insight into how the framework works and potential design that could have been made, but were not for the sake of simplicity and performance&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;A common misconception a WDFREQUEST handle is the assumption that the WDFREQUEST handle value “follows” (e.g. stays the same) the PIRP around &lt;EM&gt;everywhere &lt;/EM&gt;that the PIRP goes to.&amp;nbsp; Basically, the idea is that everywhere that the PIRP is sent or presented, the same WDFREQUST handle will be used.&amp;nbsp; The reality is that the same WDFREQUEST handle value will only be used while the PIRP is owned by the WDFDEVICE for which it was created.&amp;nbsp; This means that if you send the WDFREQUEST to another driver with a call to &lt;A href="http://msdn.microsoft.com/en-us/library/aa492032.aspx" target=_blank mce_href="http://msdn.microsoft.com/en-us/library/aa492032.aspx"&gt;WdfRequestSend&lt;/A&gt; (e.g. transfer ownership to the driver you are sending it to), the driver which receives the PIRP will have a different handle for the WFDREQUEST.&lt;/P&gt;
&lt;P&gt;This means that the WDFREQUEST cannot be used to send extra data or buffers to the driver which will receive the sent request.&amp;nbsp; For instance, this pattern does not work.&amp;nbsp; First, in the sending driver (let's say request's handle value is 0xA) you format a request context, format the request and send it to the WDFIOTARGET&lt;/P&gt;&lt;PRE&gt;typedef struct _EXTRA_BUFFER {
   PVOID Data;
   ULONG DataLength;
   ...
} EXTRA_BUFFER, *PEXTRA_BUFFER;

PEXTRA_BUFFER pExtra = NULL;
WDF_OBEJCT_ATTRIBUTES woa;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&amp;amp;woa, EXTRA_BUFFER);
NTSTATUS status;

status = WdfObjectAllocateContext(request, &amp;amp;woa, (PVOID*) &amp;amp;pExtra);

// .. initialize pExtra ...
// ... format request as an internal IOCTL ...

if (WdfRequestSend(request, ...) == FALSE) {
   WdfRequestComplete(request, WdfRequestGetStatus(request));
}&lt;/PRE&gt;
&lt;P&gt;And then in the receiving driver's WDFQUEUE dispatch routine would have a new WDFREQUEST handle value (let’s say 0xAA) and not the sender’s WDFREQUEST (0xA).&amp;nbsp; &lt;/P&gt;&lt;PRE&gt;VOID EvtInternalDeviceIoControl(
    __in WDFQUEUE Queue,
    __in WDFREQUEST Request,
    __in size_t OutputBufferLength,
    __in size_t InputBufferLength,
    __in ULONG IoControlCode
    ) { ... }&lt;/PRE&gt;
&lt;P&gt;Think about how KMDf would have been implemented if the same WDFREQUEST was presented to the lower driver.&amp;nbsp; One of two things would have had to occur:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The first option would have been for the WDFREQUEST handle value to be smuggled somewhere in the PIRP (with the most likely candidate being DriverContext[]), but that would have been unreliable and prone to compatibility issues with non KMDF drivers in the stack. &lt;/LI&gt;
&lt;LI&gt;The second option would have been to maintain a global mapping in KMDF that mapped from PIRP pointer value to WDFREQUEST handle.&amp;nbsp; This would have been very expensive to maintain because we would have had to look up the mapping every time a WDFQUEUE handled PIRP arrived in the framework.&amp;nbsp; It would have grown more even more expensive because this proposed map would have been guarded by a global lock which meant that all KMDF drivers would have been acquiring and releasing this one lock, making it very contentious and a huge performance problem.&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Let’s take a step back and look at why there is a WDFREQUEST handle value when you send a request to another device.&amp;nbsp; Here is what happens when a PIRP arrives in a KMDF driver&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;Call the potentially registered WDM preprocess routine&lt;/LI&gt;
&lt;LI&gt;If it is a PIRP that will be processed by a WDFQUEUE (a read/write/IOCTL/internal IOCTL), allocate a WDFREQUEST for the PIRP. If it is not one of these types, pass it to the appropriate pipeline in the framework&lt;/LI&gt;
&lt;LI&gt;Call the potentially registered in process callback&lt;/LI&gt;
&lt;LI&gt;Pass the WDFREQUEST to the WDFQUEUE handler so that it will be presented on the appropriate WDFQUEUE&lt;/LI&gt;
&lt;LI&gt;The WDFQUEUE calls the right IO event callback that you registered when the WDFQUEUE was created&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;Step #2 is the key here.&amp;nbsp; The WDFREQUEST is always allocated (from a lookaside if that matters).&amp;nbsp; It means that there is no 1:1 correspondence between a PIRP and WDFREQUEST value. Let’s say we wanted to have a 1:1 correspondence for a singular WDFDEVICE though(and not across multiple WDFDEVICE as in the first example). We would need a WDFDEVICE based mapping of active PIRPs to WDFREQUEST handles to see if the PIRP is an active PIRP in the WDFDEVICE.&amp;nbsp; As in the case with the previously proposed global mapping, such a WDFDEVICE wide mapping would also be prohibitively expensive.&amp;nbsp; While the lock contention would move from a global scope to a WDFDEVICE scope and thus reducing some contention, such a lock would still be a performance hot spot since all PIRPs arriving into the driver would be contending on this lock. &lt;/P&gt;
&lt;P&gt;When you look at how KMDF allocates a WDFREQUEST it becomes clear that the WDFRQUEST loosely maps back to the current stack location in the PIRP, not the PIRP itself!&amp;nbsp; Think about the sending the WDFREQUEST to another driver case.&amp;nbsp; The sending driver has its own stack location and its own WDFREQUEST. The receiving driver has its own stack location and WDFREQUEST as well.&amp;nbsp; Just to reinforce this idea, let’s consider a final example.&amp;nbsp; Let’s say &lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Your driver received a WDFREQUEST formatted for IOCTL ‘A’ in your dispatch routine&lt;/LI&gt;
&lt;LI&gt;In processing the request your driver formatted (the next stack location) it for IOCTL ‘B’ &lt;/LI&gt;
&lt;LI&gt;The request is sent to the top of your stack (which means your driver will see the PIRP in its dispatch routine eventually as IOCTL ‘B’).&amp;nbsp; &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;When PIRP enters your driver as IOCTL 'B', it will have a new WDFREQUEST handle!&amp;nbsp; This is completely by design.&amp;nbsp; The first stack location (IOCTL ‘A’) has one WDFREQUEST, the subsequent stack location (IOCTL ‘B’) has another WDFREQUEST.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;In conclusion, WDFREQUEST handles are local to a specific WDFDEVICE.&amp;nbsp; In fact, they are local to a specific stack location.&amp;nbsp; The context off of a WDFREQUEST can only be used by your driver for that particular WDFDEVICE and is not meant to be shared across WDFDEVICEs.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9459155" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/KMDF/default.aspx">KMDF</category></item><item><title>Great WinHEC presentation on device interfaces compared to device clases</title><link>http://blogs.msdn.com/doronh/archive/2009/02/03/great-winhec-presentation-on-device-interfaces-compared-to-device-clases.aspx</link><pubDate>Wed, 04 Feb 2009 04:03:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9394119</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/9394119.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=9394119</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=9394119</wfw:comment><description>&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;&lt;EM&gt;[This is a repeat of a post I made to NTDEV, but I wanted to make sure I reached as many people as possible.]&lt;/EM&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;FONT size=3 face=Calibri&gt;I just read this deck, &lt;/FONT&gt;&lt;A href="http://download.microsoft.com/download/5/E/6/5E66B27B-988B-4F50-AF3A-C2FF1E62180F/CON-T615_WH08.pptx"&gt;&lt;FONT size=3 face=Calibri&gt;http://download.microsoft.com/download/5/E/6/5E66B27B-988B-4F50-AF3A-C2FF1E62180F/CON-T615_WH08.pptx&lt;/FONT&gt;&lt;/A&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;, which was presented at WinHEC this past year.&amp;nbsp; It is by far the best explanation of device interfaces and device classes that I have seen in 10 years.&amp;nbsp; It worthwhile reading for both the new and experienced driver developer.&amp;nbsp; &lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;FONT size=3&gt;&lt;FONT face=Calibri&gt;I was asked to enter a name and password to down the file, I hit cancel and it downloaded just fine.&amp;nbsp; I don’t know if this was an IE8/Win7 quirk or not, so YMMV.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT size=3 face=Calibri&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9394119" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/WDK+Docs/default.aspx">WDK Docs</category></item><item><title>MSDN link on how to set up a user or kernel debugger</title><link>http://blogs.msdn.com/doronh/archive/2009/01/29/msdn-link-on-how-to-set-up-a-user-or-kernel-debugger.aspx</link><pubDate>Thu, 29 Jan 2009 22:15:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9383543</guid><dc:creator>doronh</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/doronh/comments/9383543.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=9383543</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=9383543</wfw:comment><description>&lt;P&gt;This has got to be one of the top FAQs out there: &lt;FONT color=#0000ff&gt;&lt;STRONG&gt;&lt;EM&gt;&lt;FONT color=#990000&gt;how do I set up a kernel debugger?&lt;/FONT&gt;&lt;/EM&gt;&lt;/STRONG&gt;&amp;nbsp; &lt;/FONT&gt;I just stumbled across a link on MSDN which gives instructions not only on how to set up a kernel debugger on all transports (serial, 1394, usb2), but also how to set up a user mode debugger or how to attach to a virtual machine. Pretty cool.&amp;nbsp; This used to be an internal web page at Microsoft (and I think a topic in debugger.chm), it is great that it is now public&lt;/P&gt;
&lt;P&gt;The MSDN topic is &lt;A href="http://msdn.microsoft.com/en-us/library/cc266370.aspx" target=_blank mce_href="http://msdn.microsoft.com/en-us/library/cc266370.aspx"&gt;Starting the Debugger&lt;/A&gt;. Add it to your bookmarks and maybe we can create room in the FAQ for another question ;)&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9383543" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/WinDBG_2F00_KD+Fun/default.aspx">WinDBG/KD Fun</category></item><item><title>Dude, what happened to Doron?</title><link>http://blogs.msdn.com/doronh/archive/2009/01/27/dude-what-happened-to-doron.aspx</link><pubDate>Wed, 28 Jan 2009 01:09:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9378732</guid><dc:creator>doronh</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/doronh/comments/9378732.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=9378732</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=9378732</wfw:comment><description>&lt;P&gt;So, I have not written anything in over 6 months and yet I have posted on NTDEV and public newsgroups. What gives?&amp;nbsp; Well, the short answer is that I have been short on time these past few months.&amp;nbsp; I have had only enough extra curricular time to read NTDEV and occassionally the newsgroups. Why? Well, for starters we had a second baby this past June and that took the majority of my time ;).&amp;nbsp; Combine that with parental leave and a project at work taking all of my time for nearly all of last year led to a very poor showing on the blog for 2008.&amp;nbsp; I promise to change that this year. &lt;/P&gt;
&lt;P&gt;Have a good new year and hopefully better quality drivers!&lt;/P&gt;
&lt;P&gt;d&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9378732" width="1" height="1"&gt;</description></item><item><title>How do I cancel an IRP that another thread may be completing at the same time?</title><link>http://blogs.msdn.com/doronh/archive/2008/06/29/how-do-i-cancel-an-irp-that-another-thread-may-be-completing-at-the-same-time.aspx</link><pubDate>Mon, 30 Jun 2008 09:16:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8670424</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8670424.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8670424</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8670424</wfw:comment><description>&lt;P&gt;Let's say that you allocated a PIRP and sent it down your device stack.&amp;nbsp; You free the PIRP in the completion routine and then return STATUS_MORE_PROCESSING_REQUIRED.&amp;nbsp; To make life more fun, you decide that you want to be able to cancel the sent IRP after you have sent it so you try to do it simple like this&lt;/P&gt;&lt;PRE&gt;typedef struct _DEVICE_EXTENSION {
    KSPIN_LOCK SentIrpLock;
    PIRP SentIrp;
} DEVICE_EXTENSION;&lt;/PRE&gt;
&lt;P&gt;Sending thread:&lt;/P&gt;&lt;PRE&gt;KeAcquireSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);
devext-&amp;gt;SentIrp = Irp;
KeReleaseSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);&lt;/PRE&gt;
&lt;P&gt;Canceling thread:&lt;/P&gt;&lt;PRE&gt;KeAcquireSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);
if (devext-&amp;gt;AllocatedIrp != NULL) {
   IoCancelIrp(devext-&amp;gt;SentIrp);
}
KeReleaseSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);&lt;/PRE&gt;
&lt;P&gt;Completion routine:&lt;/P&gt;&lt;PRE&gt;PIRP irp;

KeAcquireSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);
irp = devext-&amp;gt;SentIrp;&lt;/PRE&gt;&lt;PRE&gt;devext-&amp;gt;SentIrp = NULL;
KeReleaseSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);

IoFreeIrp(irp);

return STATUS_MORE_PROCESSING_REQUIRED;&lt;/PRE&gt;
&lt;P&gt;And it then deadlocks ;). If the call to IoCancelIrp causes the IRP to be completed in the calling context (e.g. the one which has acquired the lock), the completion routine will run and try to acquire the lock (SentIrpLock) on the same thread which holds it.&lt;/P&gt;
&lt;P&gt;So, life is not that simple and you have to do something more. The basic solution is that you need extra state to track who is touching the PIRP and who can free it. Walter Oney's book has a solution (IIRC, it is in the self initiated I/O section, but I do not have the book handy), but IMHO it is a bit complicated. KMDF has a solution to this problem which I like much more (imagine that ;)).&lt;/P&gt;
&lt;P&gt;You need an extra LONG, calling it CompletionCount per PIRP that you want to be able to cancel.&lt;/P&gt;&lt;PRE&gt;&amp;nbsp;&lt;/PRE&gt;
&lt;OL&gt;
&lt;LI&gt;You initialize CompletionCount to 1 before sending it down the stack and storing it in devext. &lt;/LI&gt;
&lt;LI&gt;Whenever there is a thread that wants to cancel the PIRP, it tries to interlocked increment CompletionCount only if and only if the current CompletionCount value is &amp;gt; 0. For this you need to roll your own InterlockedIncrementWithFloor which is fortunately not that hard and I have already shown you how to do that, &lt;A href="http://blogs.msdn.com/doronh/archive/2006/12/06/creating-your-own-interlockedxxx-operation.aspx" mce_href="http://blogs.msdn.com/doronh/archive/2006/12/06/creating-your-own-interlockedxxx-operation.aspx"&gt;http://blogs.msdn.com/doronh/archive/2006/12/06/creating-your-own-interlockedxxx-operation.aspx&lt;/A&gt;. &lt;/LI&gt;
&lt;LI&gt;After the canceling thread has called InterlockedIncrementWithFloor and IoCancelIrp, it calls InterlockedDecrement. &lt;/LI&gt;
&lt;LI&gt;Whomever wants to complete the PIRP, like the completion routine, interlock decrements CompletionCount. &lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;If the returned value from InterlockedDecrement is zero, the caller can complete the PIRP. If not, somebody else is trying to touch the PIRP and you must leave the PIRP alone.&amp;nbsp; So here is the revised code:&lt;/P&gt;&lt;PRE&gt;typedef struct _DEVICE_EXTENSION {
    KSPIN_LOCK SentIrpLock;
    PIRP SentIrp;
    ULONG CompletionCount;
} DEVICE_EXTENSION;&lt;/PRE&gt;
&lt;P&gt;Sending thread:&lt;/P&gt;&lt;PRE&gt;KeAcquireSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);
&lt;FONT color=#ff0000&gt;devext-&amp;gt;CompletionCount = 1;
&lt;/FONT&gt;devext-&amp;gt;SentIrp = Irp;&lt;/PRE&gt;&lt;PRE&gt;KeReleaseSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);&lt;/PRE&gt;
&lt;P&gt;Canceling thread:&lt;/P&gt;&lt;PRE&gt;PIRP irp = NULL;
KeAcquireSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);
if (devext-&amp;gt;AllocatedIrp != NULL &lt;FONT color=#ff0000&gt;&amp;amp;&amp;amp; MyInterlockedIcrementeWithFloor(&amp;amp;devext-&amp;gt;CompletionCount, 0) &amp;gt; 0&lt;/FONT&gt;) {
&lt;FONT color=#ff0000&gt;   irp = devext-&amp;gt;SentIrp;
&lt;/FONT&gt;}
KeReleaseSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);

&lt;FONT color=#ff0000&gt;if (irp != NULL) {
    IoCancelIrp(irp);
    if (InterlockedDecrement(&amp;amp;devext-&amp;gt;CompletionCount) == 0) {
        IoFreeIrp(irp);
    }
}
&lt;/FONT&gt;&lt;/PRE&gt;
&lt;P&gt;Completion routine:&lt;/P&gt;&lt;PRE&gt;PIRP irp = NULL;

KeAcquireSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);
irp = devext-&amp;gt;SentIrp;&lt;PRE&gt;devext-&amp;gt;SentIrp = NULL;
KeReleaseSpinLock(&amp;amp;devext-&amp;gt;SentIrpLock, ...);

&lt;FONT color=#ff0000&gt;if (InterlockedDecrement(&amp;amp;devext-&amp;gt;CompletionCount) == 0) {&lt;/FONT&gt;
    IoFreeIrp(irp);
&lt;FONT color=#ff0000&gt;}&lt;/FONT&gt;

return STATUS_MORE_PROCESSING_REQUIRED;&lt;/PRE&gt;&lt;/PRE&gt;
&lt;P&gt;The beauty of this solution is that if you add more actors (let's say a timer for an async timeout, all you have to do is bump the CompletionCount to account for them to asynchronously rundown if you cannot cancel them.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8670424" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/KMDF/default.aspx">KMDF</category><category domain="http://blogs.msdn.com/doronh/archive/tags/WDM/default.aspx">WDM</category></item><item><title>What should you change in a sample before you ship it?</title><link>http://blogs.msdn.com/doronh/archive/2008/04/23/what-should-you-change-in-a-sample-before-you-ship-it.aspx</link><pubDate>Wed, 23 Apr 2008 21:41:19 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8419605</guid><dc:creator>doronh</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8419605.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8419605</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8419605</wfw:comment><description>&lt;p&gt;I was going to write about how to do this, but the awesome folks at WHDC got to it before I did.&amp;#160; I did get to review it before it was published, so I did have some influence in what is in the tip ;). So on this one my job is easy, just go read the &lt;a href="http://www.microsoft.com/whdc/driver/tips/SampleChange.mspx" target="_blank"&gt;tip&lt;/a&gt;!&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8419605" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/Coding+Thoughts/default.aspx">Coding Thoughts</category></item><item><title>The WDF 1.7 cointstallers are now available</title><link>http://blogs.msdn.com/doronh/archive/2008/04/17/the-wdf-1-7-cointstallers-are-now-available.aspx</link><pubDate>Fri, 18 Apr 2008 02:20:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8405110</guid><dc:creator>doronh</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8405110.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8405110</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8405110</wfw:comment><description>&lt;P&gt;After a long wait (thank you for your patience!), the WDF 1.7 coinstallers are now up on the connect site.&amp;nbsp; To get the bits&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;go to &lt;A class="" href="http://connect.microsoft.com/" target=_blank mce_href="http://connect.microsoft.com"&gt;http://connect.microsoft.com&lt;/A&gt; &lt;/LI&gt;
&lt;LI&gt;Log in using your passport account&lt;/LI&gt;
&lt;LI&gt;Navigate to the WDF &lt;A class="" href="https://connect.microsoft.com/site/sitehome.aspx?SiteID=148" target=_blank mce_href="https://connect.microsoft.com/site/sitehome.aspx?SiteID=148"&gt;page&lt;/A&gt; (I don't know where it lives in the connection directory, &lt;EM&gt;sigh&lt;/EM&gt;)&lt;/LI&gt;
&lt;LI&gt;Choose Downloads on the left&lt;/LI&gt;
&lt;LI&gt;The package is dated 4/17/2008, you may be able to get it directly from &lt;A class="" href="https://connect.microsoft.com/Downloads/DownloadDetails.aspx?SiteID=148&amp;amp;DownloadID=11680" target=_blank mce_href="https://connect.microsoft.com/Downloads/DownloadDetails.aspx?SiteID=148&amp;amp;DownloadID=11680"&gt;here&lt;/A&gt; once you have logged on&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;Enjoy and let the signing and shipping of v1.7 WDF drivers begin!&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8405110" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/WDF/default.aspx">WDF</category></item><item><title>Debugger commands (.step_filter) that make my life easier</title><link>http://blogs.msdn.com/doronh/archive/2008/04/16/debugger-commands-step-filter-that-make-my-life-easier.aspx</link><pubDate>Thu, 17 Apr 2008 02:02:19 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8399699</guid><dc:creator>doronh</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8399699.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8399699</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8399699</wfw:comment><description>&lt;p&gt;This is a pretty cool and somewhat obscure debugger command.&amp;#160; It allows you to tell the debugger what functions to skip if you are using the &lt;a href="http://msdn2.microsoft.com/en-us/library/cc266759.aspx" target="_blank"&gt;trace command&lt;/a&gt; ('t').&amp;#160; I think of the trace command as the 'step into' command though, but that is just me.&amp;#160; Let's say we have the following simple application:&lt;/p&gt;  &lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;&lt;stdio.h&gt;

struct Foo {
    Foo() : m_value(0) { }

    int Increment() { return ++m_value; }
    static void Print(int i) { printf(&amp;quot;%d\n&amp;quot;, i); }

    int m_value;
};

int _cdecl main(int argc, char *argv[])
{
    Foo f;
    Foo::Print(f.Increment());
    return 0;
}&lt;/pre&gt;

&lt;p&gt;If I were to run the program under the debugger and use the 't' command for each line, it would step into every function. I typically use 't' instead of 'p' because I usually want to step into a function at some point in time and I tend to press 'p' one too many times ;). Here is an example of the debugger session:&lt;/p&gt;

&lt;pre&gt;0:000&amp;gt; g test!main
&amp;gt;   13: {
0:000&amp;gt; t
&amp;gt;   14:     Foo f;
0:000&amp;gt; t
&amp;gt;    4:     Foo() : m_value(0) { }
0:000&amp;gt; t
&amp;gt;   15:     Foo::Print(&lt;font color="blue"&gt;f.Increment()&lt;/font&gt;);
0:000&amp;gt; t
&amp;gt;    6:     int Increment() { return ++m_value; } [1]
0:000&amp;gt; t
&amp;gt;   15:     Foo::Print(f.Increment());
0:000&amp;gt; t
&amp;gt;    7:     static void Print(int i) { printf(&amp;quot;%d\n&amp;quot;, i); } [2]
0:000&amp;gt; gu
&amp;gt;   16:     return 0;
0:000&amp;gt; t
&amp;gt;   17: }
0:000&amp;gt; t
test!__mainCRTStartup+0x102:&lt;/pre&gt;

&lt;p&gt;Let's look at the statement Foo::Print(f.Increment()); When using the trace command, it will first step into Foo::Increment ([1]) before stepping into Foo::Print() ([2]). But let's say that I never want to step into Foo::Increment because I know that it is a simple function that I do not want to debug. I can tell the debugger to ignore trace commands into this function with the &lt;a href="http://msdn2.microsoft.com/en-us/library/cc266868.aspx" target="_blank"&gt;.step_filter&lt;/a&gt; command. The command takes a semi-colon delineated list of &lt;em&gt;fully qualified &lt;/em&gt;symbol names (which can include wildcards so you can filter out entire modules) to ignore. Let's see the debugger session again with this command:&lt;/p&gt;

&lt;pre&gt;0:000&amp;gt; g test!main
&amp;gt;   13: {
0:000&amp;gt; .step_filter &amp;quot;test!Foo::Increment&amp;quot;
Filter out code symbols matching:
  test!Foo::Increment
0:000&amp;gt; t
&amp;gt;   14:     Foo f;
0:000&amp;gt; t
&amp;gt;    4:     Foo() : m_value(0) { }
0:000&amp;gt; t
&lt;font color="red"&gt;&amp;gt;   15:     Foo::Print(f.Increment());&lt;/font&gt;
0:000&amp;gt; t
&amp;gt;    7:     static void Print(int i) { printf(&amp;quot;%d\n&amp;quot;, i); }
0:000&amp;gt; gu
&amp;gt;   16:     return 0;
0:000&amp;gt; t
&amp;gt;   17: }
0:000&amp;gt; t
test!__mainCRTStartup+0x102:&lt;/pre&gt;

&lt;p&gt;You will see now that when I trace into Foo::Print(f.Increment()); that the f.Increment() call is executed but not trace into (ignored is not the right word because it has run, I just didn't see it line by line) and I step directly into Foo::Print(). I think this is a pretty powerful debugger command, it can save you a lot of time if you are always accidentally stepping into the wrong function like I always do ;).&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8399699" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/WinDBG_2F00_KD+Fun/default.aspx">WinDBG/KD Fun</category></item><item><title>EvtDevicePreprocessWdmIrp is not entirely free</title><link>http://blogs.msdn.com/doronh/archive/2008/04/02/evtdevicepreprocesswdmirp-is-not-entirely-free.aspx</link><pubDate>Thu, 03 Apr 2008 00:14:09 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8352404</guid><dc:creator>doronh</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8352404.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8352404</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8352404</wfw:comment><description>&lt;p&gt;One of the WDM escapes in KMDF is &lt;a href="http://msdn2.microsoft.com/en-us/library/aa490986.aspx" target="_blank"&gt;EvtDeviceWdmIrpPreprocess&lt;/a&gt; (or EvtDevicePreprocessWdmIrp in the API in which you register it) which you can register for by calling &lt;a href="http://msdn2.microsoft.com/en-us/library/aa491157.aspx" target="_blank"&gt;WdfDeviceInitAssignWdmIrpPreprocessCallback&lt;/a&gt;.&amp;#160; This function allows you to process a WDM PIRP before KMDF sees it and potentially processes it.&amp;#160; From a KMDF adoption point of view, this functionaltiy was a very strong requirement.&amp;#160; Without, there would be not be a defined way for a KMDF to support IRPs that KMDF did not natively support.&amp;#160; For instance, the KMDF serial example registers a preprocess routine for &lt;a href="http://msdn2.microsoft.com/en-us/library/ms795838.aspx" target="_blank"&gt;IRP_MJ_FLUSH_BUFFERS&lt;/a&gt;, from [ddkroot]\src\kmdf\serial\pnp.c: &lt;/p&gt;  &lt;pre&gt;//
// Since framework queues doesn't handle IRP_MJ_FLUSH_BUFFERS,
// IRP_MJ_QUERY_INFORMATION and IRP_MJ_SET_INFORMATION requests,
// we will register a preprocess callback to handle them.
//
status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
    DeviceInit,
    SerialFlush
    IRP_MJ_FLUSH_BUFFERS,
    NULL, // pointer minor function table
    0); // number of entries in the table&lt;/pre&gt;

&lt;p&gt;You should use this functionality in your driver only if this is the last resort you have; it does not come for free.&amp;#160; When you register for any preprocess routine, KMDF will increase the &lt;a href="http://msdn2.microsoft.com/en-us/library/aa491677.aspx" target="_blank"&gt;StackSize&lt;/a&gt; in the underlying WDM PDEVICE_OBJECT.&amp;#160; This is called out in the documention for &amp;quot;&lt;a href="http://msdn2.microsoft.com/en-us/library/aa490286.aspx" target="_blank"&gt;Preprocessing and Postprocessing IRPs&lt;/a&gt;&amp;quot; in KMDF. By increasing the stack size, &lt;em&gt;all &lt;/em&gt;PIRPs that are sent to your device will have an extra &lt;a href="http://msdn2.microsoft.com/en-us/library/aa491675.aspx" target="_blank"&gt;IO stack location&lt;/a&gt;.&amp;#160; This means that the PIRP is a bit larger than it would otherwise be; larger not only for the IRP_MJ code that you registered for, but for each and every PIRP that is sent to your device regardless of IRP_MJ code.&amp;#160; Additionally, if your StackSize goes over an internal threshhold in the IO manager, the way the PIRP is allocated could be different (for instance, devices with a StackSize of &amp;lt;= N might have a PIRP allocated out of a lookaside list while a StackSize &amp;gt; N might always be allocated by calling ExAllocatePool each time).&amp;#160; Mind you, the internal threshhold is a bit high so if you have a root enumerated device or your initial StackSize is low, this is not something that you will see.&lt;/p&gt;

&lt;p&gt;So while registering a preprocess routine is a very useful thing to be able to do, you should consider your options carefully and only register a preprocess routine if you have no other option.&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8352404" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/KMDF/default.aspx">KMDF</category></item><item><title>Using KeAcquireSpinLockAtDpcLevel is only a perf gain if you know you are DISPATCH_LEVEL</title><link>http://blogs.msdn.com/doronh/archive/2008/03/28/using-keacquirespinlockatdpclevel-is-only-a-perf-gain-if-you-know-you-are-dispatch-level.aspx</link><pubDate>Sat, 29 Mar 2008 01:42:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8342502</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8342502.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8342502</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8342502</wfw:comment><description>&lt;P&gt;Well, that is certainly a long title ;). &lt;/P&gt;
&lt;P&gt;First, let us look at an approximate implementation of &lt;A href="http://msdn2.microsoft.com/en-us/library/ms801656.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms801656.aspx"&gt;KeAcquireSpinLock&lt;/A&gt; and &lt;A href="http://msdn2.microsoft.com/en-us/library/ms801898.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms801898.aspx"&gt;KeRaiseIrql&lt;/A&gt; (and yes I know that KeRaiseIrql is really a #define to KfRaiseIrql, but it is the same thing that happens in the end…) &lt;/P&gt;&lt;PRE&gt;KIRQL KeAcquireSpinLock(PKSPIN_LOCK SpinLock, PKIRQL PreviousIrql)
{
    KeRaiseIrql(DISPATCH_LEVEL, PreviousIrql);
    &lt;EM&gt;[spin on the lock until it has been acquired]&lt;/EM&gt;
}

VOID KeRaiseIrql(KIRQL NewIrql, PKIRQL, PKIRQL OldIrql)
{
    OldIrql = KeGetCurrentIrql();
&lt;EM&gt;    [raise IRQL to NewIrql]&lt;/EM&gt;
}
&lt;/PRE&gt;
&lt;P&gt;What I want to emphasize is that KeAcquireSpinLock will retrieve the current IRQL (to know what to restore the IRQL to when the lock is released) as a part of acquiring the spin lock. Retrieving the current irql is a relatively expensive operation. Enter &lt;A href="http://msdn2.microsoft.com/en-us/library/ms801669.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms801669.aspx"&gt;KeAcquireSpinLockAtDpcLevel&lt;/A&gt;. KeAcquireSpinLockAtDpcLevel does away with the IRQL change and just implements &lt;EM&gt;[spin on the lock until it has been acquired]&lt;/EM&gt;, but it does this with a large caveat…you must be running at DISPATCH_LEVEL (in reality it requires IRQL &amp;gt;= DISPATCH_LEVEL, but that is another discussion for another day) . It requires DISPATCH_LEVEL or higher so that you do not deadlock. Another caveat to effectively use KeAcquireSpinLockAtLevel you must know 100% that you are at DISPATCH_LEVEL. Naively, one could think that the following code optimizes for both cases &lt;/P&gt;&lt;PRE&gt;if (KeGetCurrentIrql() &amp;lt; DISPATCH_LEVEL) {
    KeAcquireSpinLock(&amp;amp;lock, &amp;amp;oldIrql);
}
else {
    KeAcquireSpinLockAtDpcLevel(&amp;amp;lock);
}
&lt;/PRE&gt;
&lt;P&gt;But the problem here is that in the case of the current IRQL &amp;lt; DISPATCH_LEVEL, the current IRQL is being retrieved twice (once in your code, once in KeAcquireSpinLock), so this relatively expensive operation is being performed twice when it should be performed only once. What all of this boils down to is that you must know with 100% certainty that the current IRQL is DISPATCH_LEVEL before you can use KeAcquireSpinLockAtDpcLevel effectively. Here are a couple of contexts here you can know for certain that the IRQL is DISPATCH_LEVEL. &lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;In a DPC (for ISR, for a timer, your own) &lt;/LI&gt;
&lt;LI&gt;After you have acquired a spin lock. In the case where you have nested locked being acquired (first A, then B), after you have called KeAcquireSpinLock(&amp;amp;A) you can then call KeAcquireSpinLockAtDpcLevel(&amp;amp;B) &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Notice that a completion routine is not guaranteed to be called at IRQL == DISPATCH_LEVEL! While you may see that it is called at dispatch, it is not something that you can rely on 100% of the time. For instance, the lower driver could complete the IRP at passive level in an error condition.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8342502" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/Design+Patterns/default.aspx">Design Patterns</category></item><item><title>Inconceivableable</title><link>http://blogs.msdn.com/doronh/archive/2008/03/18/inconceivableable.aspx</link><pubDate>Wed, 19 Mar 2008 02:55:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8324240</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8324240.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8324240</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8324240</wfw:comment><description>&lt;P&gt;I have no idea who created the name for PNP_DEVICE_NOT_DISABLEABLE, but I probably have the same reaction as you ... "seriously?&amp;nbsp; that is what they named?"&amp;nbsp; I mean come on, I think it could have at least been named PNP_DEVICE_CANNOT_BE_DISABLED.&amp;nbsp; I am sure you can think of some better names too. If so, please leave a comment with your suggestions!&amp;nbsp; While we had a chance to rectify this in KMDF in the &lt;FONT color=#000000&gt;&lt;A class="" href="http://msdn2.microsoft.com/en-us/library/aa491094.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/aa491094.aspx"&gt;WDF_DEVICE_STATE&lt;/A&gt;&lt;/FONT&gt;&amp;nbsp;structure, we chose to keep the field name (NotDisableable)&amp;nbsp;similar to the WDM name to avoid confusion.&lt;/P&gt;
&lt;P&gt;For any readers who have not encountered this bit, it is a part of &lt;A href="http://msdn2.microsoft.com/en-us/library/ms805981.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms805981.aspx"&gt;PNP_DEVICE_STATE&lt;/A&gt;.&amp;nbsp; You set this bit in the IRP_MJ_PNP/&lt;A href="http://msdn2.microsoft.com/en-us/library/ms806467.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms806467.aspx"&gt;IRP_MN_QUERY_PNP_DEVICE_STATE&lt;/A&gt; IRP after calling &lt;A href="http://msdn2.microsoft.com/en-us/library/aa490455.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/aa490455.aspx"&gt;IoInvalidateDeviceState&lt;/A&gt;.&amp;nbsp; &lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8324240" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/WDM/default.aspx">WDM</category></item><item><title>Once not disableable, forever not disableable</title><link>http://blogs.msdn.com/doronh/archive/2008/03/18/once-not-disableable-forever-not-disableable.aspx</link><pubDate>Tue, 18 Mar 2008 18:21:12 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8324256</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8324256.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8324256</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8324256</wfw:comment><description>&lt;p&gt;One interesting quirk about the &lt;a href="http://msdn2.microsoft.com/en-us/library/ms805981.aspx" target="_blank"&gt;PNP_DEVICE_NOT_DISABLEABLE&lt;/a&gt; state is that once it has been set and the PnP manager has processed it, the state is sticky.&amp;#160; By sticky I mean that even if you attempt to clear this bit on a subsequent IRP_MN_QUERY_PNP_DEVICE_STATE IRP, the PnP manager ignores your changes to this state.&amp;#160; This state remains stuck until any of the following occur&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;The machine is rebooted and the device is reenumerated&lt;/li&gt;    &lt;li&gt;The device (or any device in its ancestry) is surprise removed&lt;/li&gt;    &lt;li&gt;The device (or any device in its ancestry) is ejected&lt;/li&gt; &lt;/ol&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8324256" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/WDM/default.aspx">WDM</category></item><item><title>Returning failure from DriverEntry</title><link>http://blogs.msdn.com/doronh/archive/2008/03/17/returning-failure-from-driverentry.aspx</link><pubDate>Tue, 18 Mar 2008 02:56:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8298729</guid><dc:creator>doronh</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8298729.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8298729</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8298729</wfw:comment><description>&lt;P&gt;One thing that is easily overlooked about &lt;A href="http://msdn2.microsoft.com/en-us/library/ms794742.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms794742.aspx"&gt;implementing&lt;/A&gt; &lt;A href="http://msdn2.microsoft.com/en-us/library/ms795702.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms795702.aspx"&gt;DriverEntry&lt;/A&gt; is that upon return !NT_SUCCESS, DriverUnload is not called.&amp;nbsp; I mentioned this anecdotally in a previous &lt;A href="http://blogs.msdn.com/doronh/archive/2006/10/23/hindsight-is-20-20-evtdriverunload-should-have-not-been-in-kmdf.aspx" target=_blank mce_href="http://blogs.msdn.com/doronh/archive/2006/10/23/hindsight-is-20-20-evtdriverunload-should-have-not-been-in-kmdf.aspx"&gt;post&lt;/A&gt;, but it is worth expanding on.&amp;nbsp; I was bit by this oversight when I was working on the Bluetooth stack.&amp;nbsp; Driver verifier&amp;nbsp; correctly identified that my driver had leaked pool.&amp;nbsp; The code looked something like this&lt;/P&gt;&lt;PRE&gt;// Globals
UNICODE_STRING gRegistryPath = { 0 };

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NTSTATUS status;

    DriverObject-&amp;gt;DriverUnload = DriverUnload;

    gRegistryPath.Length = RegistryPath-&amp;gt;Length;
    gRegistryPath.MaximumLength = RegistryPath-&amp;gt;MaximumLength;
    gRegistryPath.Buffer = (PWCHAR) ExAllocatePoolWithTag(PagedPool, gRegistryPath.MaximumLength, [tag]);
    if (gRegistryPath.Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
    RtlCopyMemory(gRegistryPath.Buffer, RegistryPath-&amp;gt;Buffer, gRegistryPath.Length);

    status = RegisterWithPortDriver(DriverObject, ...);
    if (!NT_SUCCESS(status)) { return status; } &amp;lt;== leak right here!

    // ... other init ...

    return status;
}

void DriverUnload(PDRIVER_OBJECT DriverObject)
{
    ExFreePool(gRegistryPath.Buffer);
    RtlZeroMemory(&amp;amp;gRegistryPath, sizeof(gRegistryPath));
}&lt;/PRE&gt;
&lt;P&gt;While many WDM drivers do very little outside of initializing the dispatch table and other fields in their DriverObject, a miniport driver or a KMDF driver must register with their port driver (like &lt;A href="http://msdn2.microsoft.com/en-us/library/ms805428.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms805428.aspx"&gt;ScsiPortInitialize&lt;/A&gt;) or framework (&lt;A href="http://msdn2.microsoft.com/en-us/library/aa491318.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/aa491318.aspx"&gt;WdfDriverCreate&lt;/A&gt;) and this registration can introduce failure in DriverEntry (just like in my code sample above).&amp;nbsp; What to do?&amp;nbsp; &lt;/P&gt;
&lt;P&gt;In a WDM driver you have to be very careful and manage this manually.&amp;nbsp; Either you have a common error exit path out of DriverEntry which performs the cleanup (or manually calls your DriverUnload routine) or cleanup on each possible point of error.&amp;nbsp; This pattern is very easy to get wrong and is not very maintainable, it is quite easy to add a new allocation and forget to cleanup it up later.&lt;/P&gt;
&lt;P&gt;In a KMDF driver things are a bit easier to manage if you follow a particular pattern.&amp;nbsp; While &lt;A href="http://msdn2.microsoft.com/en-us/library/aa491324.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/aa491324.aspx"&gt;EvtDriverUnload&lt;/A&gt; has the same problems as the WDM DriverUnload, the EvtObjectCleanup routine registered on the WDFDRIVER is called in both scenarios.&amp;nbsp; To re-emphasize, the EvtObjectCleanup registered on WDFDRIVER will be called when either DriverEntry returns !NT_SUCCESS or if your driver is gracefully unloaded later.&amp;nbsp; This means that if you put all of your cleanup in the cleanup routine your DriverEntry implemention becomes much simpler.&amp;nbsp; The one caveat is that the call to WdfDriverCreate must come before any allocations in your driver or state chaning APIs.&amp;nbsp; &lt;A href="http://msdn2.microsoft.com/en-us/library/ms793144.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms793144.aspx"&gt;WPP_INIT_TRACING&lt;/A&gt;&amp;nbsp;is one such state changing API where you must undo its effects by calling &lt;A class="" href="http://msdn2.microsoft.com/en-us/library/ms793159.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/ms793159.aspx"&gt;WPP_CLEANUP&lt;/A&gt;.&amp;nbsp; Quite a few WDK samples show this pattern (although suprisingly to me, not all!), let us look at the nonpnp sample (%wdk%\src\kmdf\nonpnp\sys\nonpnp.c) &lt;/P&gt;&lt;PRE&gt;NTSTATUS
DriverEntry(
    IN OUT PDRIVER_OBJECT   DriverObject,
    IN PUNICODE_STRING      RegistryPath
    )
{
    NTSTATUS                       status;
    WDF_DRIVER_CONFIG              config;
    WDFDRIVER                      hDriver;
    PWDFDEVICE_INIT                pInit = NULL;
    WDF_OBJECT_ATTRIBUTES          attributes;

    WDF_DRIVER_CONFIG_INIT(&amp;amp;config, WDF_NO_EVENT_CALLBACK);

    // Tell the framework that this is non-pnp driver so that it doesn't set the default AddDevice routine.
    config.DriverInitFlags |= WdfDriverInitNonPnpDriver;

    // NonPnp driver must explicitly register an unload routine for the driver to be unloaded.
    config.EvtDriverUnload = NonPnpEvtDriverUnload;

&lt;FONT color=#ff0000&gt;    &lt;/FONT&gt;&lt;B&gt;&lt;FONT color=#ff0000&gt;// Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.&lt;/FONT&gt;&lt;/B&gt;
    WDF_OBJECT_ATTRIBUTES_INIT(&amp;amp;attributes);
    attributes.EvtCleanupCallback = NonPnpEvtDriverContextCleanup;

    status = WdfDriverCreate(DriverObject,
                            RegistryPath,
                            &amp;amp;attributes,
                            &amp;amp;config,
                            &amp;amp;hDriver);
    if (!NT_SUCCESS(status)) {
        KdPrint (("NonPnp: WdfDriverCreate failed with status 0x%x\n", status));
        return status;
    }

&lt;FONT color=#ff0000&gt;    &lt;/FONT&gt;&lt;B&gt;&lt;FONT color=#ff0000&gt;// Since we are calling WPP_CLEANUP in the DriverContextCleanup
    // callback we should initialize WPP Tracing after WDFDRIVER
    // object is created to ensure that we cleanup WPP properly
    // if we return failure status from DriverEntry. This
    // eliminates the need to call WPP_CLEANUP in every path
    // of DriverEntry.&lt;/FONT&gt;&lt;/B&gt;
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    ...

    return status;
}

VOID NonPnpEvtDriverContextCleanup(WDFDRIVER Driver)
{
    WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver));
}&lt;/PRE&gt;
&lt;P&gt;The red comments show what is going on.&amp;nbsp; Hopefully the code is self explanatory.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;Incidentally, another pattern you can use for global memory allocations is to allocate the memory with &lt;A href="http://msdn2.microsoft.com/en-us/library/aa491566.aspx" target=_blank mce_href="http://msdn2.microsoft.com/en-us/library/aa491566.aspx"&gt;WdfMemoryCreate&lt;/A&gt; without specifying a parent object.&amp;nbsp; The WDFDRIVER will be the parent object by default and since all child objects are destroyed when the parent is destroyed, all of your allocations will be destroyed after EvtDriverUnload has been called when the WDFDRIVER is destroyed in the unload path.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8298729" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/Design+Patterns/default.aspx">Design Patterns</category></item><item><title>one of the books that started it all...</title><link>http://blogs.msdn.com/doronh/archive/2008/03/12/one-of-the-books-that-started-it-all.aspx</link><pubDate>Wed, 12 Mar 2008 18:38:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8163222</guid><dc:creator>doronh</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/doronh/comments/8163222.aspx</comments><wfw:commentRss>http://blogs.msdn.com/doronh/commentrss.aspx?PostID=8163222</wfw:commentRss><wfw:comment>http://blogs.msdn.com/doronh/rsscomments.aspx?PostID=8163222</wfw:comment><description>&lt;P&gt;During my sophomore year at &lt;A href="http://www.calpoly.edu/" target=_blank mce_href="http://www.calpoly.edu/"&gt;Cal Poly&lt;/A&gt;, I decided that I wanted to learn about threads, synchronization techniques and other topics associated modern operating systems.&amp;nbsp; Windows 95 had made its debut (yes, it is not a modern OS, but I didn't know that at the time!) and I had heard about Windows NT, but had never seen or used it.&amp;nbsp; Since I had Win95 on my machine at home, I decided that I would go to the &lt;A href="http://www.elcorralbookstore.com/" target=_blank mce_href="http://www.elcorralbookstore.com/"&gt;bookstore&lt;/A&gt; and buy a book on threading.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;Well, needless to say there was not a book that was dedicated to threading, but I found a couple of books on Windows programming that included threading as topics.&amp;nbsp; It came down to 2 books, Jeffrey Richter's "Advanced Windows for Win95 and NT" (with &lt;A href="http://www.amazon.com/Advanced-Windows-Developers-Guide-Win32/dp/1556156774/ref=sr_1_11?ie=UTF8&amp;amp;s=books&amp;amp;qid=1205277849&amp;amp;sr=1-11" target=_blank mce_href="http://www.amazon.com/Advanced-Windows-Developers-Guide-Win32/dp/1556156774/ref=sr_1_11?ie=UTF8&amp;amp;s=books&amp;amp;qid=1205277849&amp;amp;sr=1-11"&gt;Napoleon&lt;/A&gt; on the cover) and some other book.&amp;nbsp; I obviously picked Richter's book ;), although now it is called &lt;A href="http://www.amazon.com/Windows-via-C%2B%2B-Pro-Developer/dp/0735624240/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1205278444&amp;amp;sr=1-1" target=_blank mce_href="http://www.amazon.com/Windows-via-C%2B%2B-Pro-Developer/dp/0735624240/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1205278444&amp;amp;sr=1-1"&gt;"Windows via C/C++&lt;/A&gt;."&lt;/P&gt;
&lt;P&gt;This book was, and still is, awesome.&amp;nbsp; I have both the original and new editions.&amp;nbsp; Great detail, very easy to read and met it my needs perfectly (unlike "&lt;A class="" href="http://www.amazon.com/Inside-Microsoft-Programming-Kraig-Brockschmidt/dp/1556158432/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1205278587&amp;amp;sr=1-1" target=_blank mce_href="http://www.amazon.com/Inside-Microsoft-Programming-Kraig-Brockschmidt/dp/1556158432/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1205278587&amp;amp;sr=1-1"&gt;Inside OLE&lt;/A&gt;" by Kraig Brockschmidt which was full of information, I just could not stay awake reading it :) ).&amp;nbsp; IMHO, it successfully started me down the road to becoming a well rounded developer and I think it helped in getting my foot in the door at Microsoft.&amp;nbsp; I have even had the pleasure of letting Jeff know personally what a great book it is and how it shaped me for years to com.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8163222" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/doronh/archive/tags/Coding+Thoughts/default.aspx">Coding Thoughts</category></item></channel></rss>