Technical Document #41

Technical Document #41
How do I share a memory buffer between Kernel PlugIn and user-mode projects for DMA or other purposes?

Beginning with v9.2.1 of WinDriver, you can use the WDC_SharedBufferAlloc() function to allocated a shared memory buffer, which can be used both from a user-mode application and a Kernel PlugIn driver — refer to the documentation of this function in the WinDriver PCI User's Manual.

Following is an explanation of how to share memory between the user mode and the Kernel PlugIn in earlier versions of WinDriver, using a common buffer that is allocated in the kernel mode and mapped to the user mode.

The following refers to low-level WinDriver APIs. When using a newer version of WinDriver, which supports the WDC API, use the relevant high-level WDC APIs instead. For versions 9.2.1 and above of WinDriver, simply use the WDC_SharedBufferAlloc() function, as indicated above.

Call WD_DMALock() from the user mode to allocate a buffer in the kernel. To allocate a contiguous kernel buffer, set the DMA_KERNEL_BUFFER_ALLOC flag in the dwOptions field of the WD_DMA structure. WD_DMALock() returns a mapping of the physical address of the allocated buffer to both the kernel and user-mode address space.

Note: Beginning with version 6.0.0 of WinDriver, for contiguous-buffer DMA allocation WD_DMALock() also returns the kernel mapping of the allocated buffer — within dma.pKernelAddr.
In previous versions of WinDriver, the kernel address was not obtainable via WD_DMALock() and there was need in an additional step, calling WD_CardRegister().

You can access the allocated memory from your user mode application using the returned user mode mapped address (dma.pUserAddr) as if it was the result of a malloc() call.

To access the shared memory buffer from the Kernel PlugIn application, you need to pass the kernel mapping of the allocated buffer to the Kernel PlugIn. This address is returned by WD_DMALock() in dma.pKernelAddr.

(Do not try to access the kernel mapped address directly in the user mode. Since this is a kernel mode address, you will generate a protection violation exception if you try to access it from the user mode. For direct user mode access, use the user mode mapping — dma.pUserAddr.)

Now you should pass the kernel virtual address to the Kernel PlugIn.
In order to do this, create a WD_KERNEL_PLUGIN_CALL structure and set the pData field to hold the virtual kernel address — dma.pKernelAddr.

You can then pass this structure to the Kernel PlugIn in one of the following ways:

  1. A call to WD_KernelPlugInCall(). In this case you will be able to access the kernel virtual address address from within the KP_Call callback function in the Kernel PlugIn.

  2. A call to WD_IntEnable() / InterruptEnable() (or InterruptThreadEnable() — in version 5.2.2 and below). In this case, in the user mode you will store the WD_KERNEL_PLUGIN_CALL structure in the kpCall field of the WD_INTERRUPT structure that is passed to the function. You will then be able to access the kernel virtual address from within the KP_IntEnable callback function in the Kernel PlugIn.

After retrieving the kernel virtual buffer address in the Kernel PlugIn, store it in the Kernel PlugIn module (in a global variable or an allocated memory location). Now, you can access the same memory buffer in both kernel mode and user mode.

Note: The access to the common buffer is not synchronized by WinDriver. You may add access synchronization.


  /* Sample user mode code: */

  WD_DMA dma;
  WD_CARD_REGISTER cardReg; /* For version 5.2.2 or below */
  hWD = WD_Open();

  dma.pUserAddr = NULL;
  dma.dwBytes = 0x10000; /* allocate 16K */
  dma.dwOptions = DMA_KERNEL_BUFFER_ALLOC; /* kernel contiguous buffer */
  WD_DMALock(hWD, &dma);
  if (!dma.hDma)
     printf("failed allocating dma buffer\n");
     /* exit*/
  /* At this point, dma.pUserAddr holds the 
      user mode mapping of the allocated memory, 
      dma.pKernelAddr holds the kernel mapping of  
      the memory. */
  /* ////////////////////////////////////////////////////////// */

  /* Pass the kernel address to the Kernel PlugIn: */

  BZERO (kerPlug);
  kerPlug.pcDriverName = KP_DRIVER_NAME;
  WD_KernelPlugInOpen(hWD, &kerPlug);
  if (!kerPlug.hKernelPlugIn)
      printf("failed opening a handle to the Kernel PlugIn\n");
      /* exit */

  BZERO (kpCall);
  kpCall.hKernelPlugIn = kerPlug.hKernelPlugIn;
  kpCall.dwMessage = YOUR_MESSAGE;
  kpCall.pData = dma.pKernelAddr;  
  WD_KernelPlugInCall(hWD, &kpCall);

For a detailed description of the WinDriver APIs used above, refer to the Function Reference section in the WinDriver PCI Low-Level API Reference (or in the main WinDriver User's Manual, in earlier versions of WinDriver).
For further clarifications regarding DMA allocation with WinDriver, refer to the relevant section in the WinDriver PCI User's Manual, under "Advanced Issues".