title: Enabling write-combining IO access in userspace
body: <p>I have a PCIe device with a userspace driver.  I'm writing commands to the device through a BAR, the commands are latency sensitive and amount of data is small (~64-bytes) so I don't want to use DMA.</p>
  
  <p>If I remap the physical address of the BAR in the kernel using <code>ioremap_wc</code> and then write 64-bytes to the BAR <em>inside the kernel</em>, I can see that the 64-bytes are written as a single TLP over PCIe.  If I allow my userspace program to <code>mmap</code> the region with the <code>MAP_SHARED</code> flag and then write 64-bytes I see multiple TPLs on the PCIe bus, rather than a single transaction.</p>
  
  <p>According to the kernel <a href="https://www.kernel.org/doc/Documentation/x86/pat.txt">PAT documentation</a> I should be able to export write-combined pages through to userspace:</p>
  
  <blockquote>
    <p>Drivers wanting to export some pages to userspace do it by using mmap
    interface and a combination of</p>
    
    <p>1) <code>pgprot_noncached()</code></p>
    
    <p>2) <code>io_remap_pfn_range()</code> or <code>remap_pfn_range()</code> or <code>vm_insert_pfn()</code></p>
    
    <p>With PAT support, a new API <code>pgprot_writecombine</code> is being added. So,
    drivers can continue to use the above sequence, with either
    <code>pgprot_noncached()</code> or <code>pgprot_writecombine()</code> in step 1, followed by
    step 2.</p>
  </blockquote>
  
  <p>Based on this documentation, the relevant kernel code from my mmap handler looks like this:</p>
  
  <pre><code> vma-&gt;vm_page_prot = pgprot_writecombine(vma-&gt;vm_page_prot);
  
   return io_remap_pfn_range(vma,
                             vma-&gt;vm_start,
                             info-&gt;mem[vma-&gt;vm_pgoff].addr &gt;&gt; PAGE_SHIFT,
                             vma-&gt;vm_end - vma-&gt;vm_start,
                             vma-&gt;vm_page_prot);
  </code></pre>
  
  <p>My PCIe device shows up in lspci with the BARs marked as prefetchable as expected:</p>
  
  <blockquote>
  <pre><code>    Latency: 0, Cache Line Size: 64 bytes
      Interrupt: pin A routed to IRQ 11
      Region 0: Memory at d8000000 (64-bit, prefetchable) [size=32M]
      Region 2: Memory at d4000000 (64-bit, prefetchable) [size=64M]
  </code></pre>
  </blockquote>
  
  <p>When I call <code>mmap</code> from userspace I see a log message (having set debugpat kernel boot parameter):</p>
  
  <blockquote>
    <p>reserve_memtype added [mem 0xd4000000-0xd7ffffff], track write-combining, req write-combining, ret write-combining</p>
  </blockquote>
  
  <p>I can also see in <code>/sys/kernel/debug/x86/pat_memtype_list</code> that a PAT entry looks correct and there are no overlapping regions:</p>
  
  <pre><code>write-combining @ 0xd4000000-0xd8000000
  uncached-minus  @ 0xd8000000-0xda000000
  </code></pre>
  
  <p>I have also checked that there are no MTRR entries that would conflict with the PAT configuration.  As far as I can see, everything is set up correctly for write-combining to occur in userspace, however using a PCIe analyser to observe the transactions on the PCIe bus there the userspace access pattern is completely different to the same write performed from the kernel after an <code>ioremap_wc</code> call.</p>
  
  <p>Why is write-combining not working as expected from userspace?</p>
  
  <p>What can I do to debug further?</p>
  
  <p>I'm currently running on a single socket 6-core i7-3930K.</p>