Since i’m on vacation next week, i thought i’d tackle something light this week.
Last time i talked about GetScatterGatherList and PutScatterGatherList and how much better they are than the older method of doing DMA. But as much as I like these two functions, they have one major problem that hit us while we were working on the storage stack – they allocate memory.
During the development of Windows XP one of our goals in the storage team was to ensure we could successfully issue disk I/O operations even if the pool was exhasted or the system address space was full. In Windows even kernel memory is pageable, and when you can’t page in a kernel thread’s stack the system can’t really continue. The kernel can crash if a page-in fails due to a bad block on disk, or due to some driver returning STATUS_INSUFFICIENT_RESOURCES.
AllocateAdapterChannel & MapTransfer would work for us, but the performance isn’t good on modern systems since you can’t issue more than one request at a time through the channel. We needed something new.
The trick to making forward progress even when you can’t freely allocate memory is to preallocate all the resources you need for at least one I/O operation for use in emergencies. When you get an I/O you try to allocate what you need, and if you can’t get it you try to use the “emergency” resources. If those aren’t available you queue the request for later processing when the emergency resources are free.
In order to do this around DMA, we needed the ability to pre-allocate a scatter-gather list, then hand that to the DMA engine to fill in. This is exactly what BuildScatterGatherList does – it constructs the SG list within the supplied buffer but otherwise acts just like GetScatterGatherList.
There’s only one problem. GetScatterGatherList doesn’t just allocate space for your scatter gather list. It also allocates private memory so that it can track the DMA mapping operation – list entries for enqueing it, the map register base, the number of map registers – all of those things you would normally have to keep track of yourself. Obviously BuildScatterGatherList can’t allocate memory, and your driver shouldn’t have to guess how much extra space it might need. So how do you know how big to make the buffer you hand in?
You find that out by calling CalculateScatterGatherList(). It takes a CurrentVa and a Length along with an optional MDL. This function determines the size of buffer that BuildScatterGatherList requires. If you provide an MDL the function will compute the required size for any chained MDLs as well. If you provide NULL for the MDL then it uses CurrentVa and Length to determine how many pages you’re transferring & determines the rest from there.
With these two functions you can ensure that you’ll always have enough memory to handle one DMA mapping for some reasonable sized I/O operation (where that reasonable size is whatever you passed in for Length).