none
MmMapLockedPagesSpecifyCache sometimes triggers BSOD RRS feed

  • Question

  • To map a DMA buffer directly to userspace (when transferring data at over 800MB/s, avoiding memcpy pays off), I'm using the following sequence of calls:

    {
    	ULONG allocSize = (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); /* Round up to PAGE_SIZE */
    	PVOID virtual_addr;
    	NTSTATUS status;
    
    	status = WdfCommonBufferCreate(
    		deviceContext->DmaEnabler,
    		allocSize,
    		WDF_NO_OBJECT_ATTRIBUTES,
    		&block->buffer);
    	if (!NT_SUCCESS(status)) {
    		DbgPrint("%s: WdfCommonBufferCreate failed: %#x\n", __FUNCTION__, status);
    		return status;
    	}
    	block->size = allocSize;
    	block->phys_addr = WdfCommonBufferGetAlignedLogicalAddress(block->buffer);
    	virtual_addr = WdfCommonBufferGetAlignedVirtualAddress(block->buffer);
    	RtlZeroMemory(virtual_addr, allocSize);
    	block->mdl = IoAllocateMdl(virtual_addr, allocSize, FALSE, FALSE, NULL);
    	if (block->mdl == NULL) {
    		DbgPrint("%s: IoAllocateMdl failed\n", __FUNCTION__);
    		return STATUS_INSUFFICIENT_RESOURCES;
    	}
    	MmBuildMdlForNonPagedPool(block->mdl); /* doesn't return any status code */
    	try
    	{
    		block->user_data = MmMapLockedPagesSpecifyCache(block->mdl, UserMode, MmNonCached /*Probably ignored*/, NULL, FALSE, NormalPagePriority);
    		if (!block->user_data) {
    			DbgPrint("%s: MmMapLockedPagesSpecifyCache failed\n", __FUNCTION__);
    			return STATUS_INSUFFICIENT_RESOURCES;
    		}
    		return STATUS_SUCCESS;
    	}
    	except(EXCEPTION_EXECUTE_HANDLER)
    	{
    		DbgPrint("%s: MmMapLockedPagesSpecifyCache raised exception\n", __FUNCTION__);
    		return STATUS_INSUFFICIENT_RESOURCES;
    	}

    This usually works fine. However, if I create a loop in userspace to keep mapping and unmapping the buffer, I get a BSOD. According to WinDbg, the cause of the BSOD is the "MmMapLockedPagesSpecifyCache" call. I explicitly requested the method not to bugcheck, which is what the FALSE is supposed to do. I also added the try..except block since this method might throw exceptions according to the documentation. Unfortunately, the only information I could find on how to do that was this:

    https://msdn.microsoft.com/en-us/library/ff546823(v=vs.85).aspx

    Which doesn't explain anything really. Apparently some C exception handling was invented here, but the reader is left clueless as to how to properly use that feature.

    The method is called at PASSIVE level.

    This is the extra information I could extract from WinDbg:

    STACK_TEXT:  
    ffffd001`cde846e8 fffff800`c9f863e5 : 00000000`0000007a 00000000`00000001 ffffffff`d0000006 ffffe000`6ff4e8c0 : nt!KeBugCheckEx
    ffffd001`cde846f0 fffff800`c9e94032 : 00000000`00000000 00000000`00000000 ffffe000`00000000 ffffffff`ffffffff : nt! ?? ::FNODOBFM::`string'+0x1cb25
    ffffd001`cde84780 fffff800`ca2b1db1 : 00000089`4fe60000 ffffe000`720ba620 00000000`00000000 00000089`00000000 : nt!MiMapLockedPagesInUserSpaceHelper+0x172
    ffffd001`cde84820 fffff800`7b7ebde9 : ffffe000`720ba5f0 ffffe000`00000001 00001fff`00000000 00000089`4fe60000 : nt!MiMapLockedPagesInUserSpace+0x241

    KERNEL_DATA_INPAGE_ERROR (7a)
    The requested page of kernel data could not be read in.  Typically caused by
    a bad block in the paging file or disk controller error. Also see
    KERNEL_STACK_INPAGE_ERROR.
    If the error status is 0xC000000E, 0xC000009C, 0xC000009D or 0xC0000185,
    it means the disk subsystem has experienced a failure.
    If the error status is 0xC000009A, then it means the request failed because
    a filesystem failed to make forward progress.
    Arguments:
    Arg1: 0000000000000001, lock type that was held (value 1,2,3, or PTE address)
    Arg2: ffffffffd0000006, error status (normally i/o status code)
    Arg3: ffffe0006ff4e8c0, current process (virtual address for lock type 3, or PTE)
    Arg4: fffff68044a7f000, virtual address that could not be in-paged (or PTE contents if arg1 is a PTE address)
    



    Monday, July 11, 2016 6:12 AM

Answers

  • My conclusion for now is that there's some undocumented behaviour here and mapping DMA buffers to userspace doesn't actually work in Windows. I'll just emulate the feature by allocating 'shadow' buffers in userspace and copy the data to/from them.
    Thursday, July 14, 2016 5:46 AM

All replies

  • Extra information of interest:

    Test system runs Windows 8.1

    User process starts again each loop (no resource leak like address space on that side).

    The crash occurs somewhat random, sometimes the first iteration, sometime after a hundred.

    I modified the driver to allocate the DMA buffers only once at startup and only re-do the mapping to userspace (starting at "RtlZeroMemory..."). Did not make a difference, still the same crashes.

    The DmaEnabler is configured for a 32-bit DMA controller, using single-packet transfers (the hardware can queue several request, but not enough to call it scatter-gather capable):

    WDF_DMA_ENABLER_CONFIG dmaConfig;
    WDF_DMA_ENABLER_CONFIG_INIT(&dmaConfig,
      WdfDmaProfilePacket, /* 32-bit, single packet */
      0x80000000u); /* 2GB should be enough */
    status = WdfDmaEnablerCreate(
      device,
      &dmaConfig,
      WDF_NO_OBJECT_ATTRIBUTES,
      &deviceContext->DmaEnabler);
    

    Monday, July 11, 2016 11:06 AM
  • Lots of debugging sessions later, I found the internal method that triggered the bugcheck by disassembling the "FNODOBFM" part of the call stack:

    nt!MiMakeSystemAddressValid

    I have no clue what it does though.


    Tuesday, July 12, 2016 8:18 AM
  • My conclusion for now is that there's some undocumented behaviour here and mapping DMA buffers to userspace doesn't actually work in Windows. I'll just emulate the feature by allocating 'shadow' buffers in userspace and copy the data to/from them.
    Thursday, July 14, 2016 5:46 AM