Добрового всем времени суток!
Решил поднять эту тему, так как тоже "хочу попытаться выяснить, что же всё-таки делает функция GetScatterGatherList()"(с), но немного с другой стороны
В других, к сожалению, не было близких обсуждений. По рекомендации модераторов может создадим новую ветку.
Небольшое вступление.
В описании работы DMA очень часто упомянают, что для устройств не поддерживающих 64-битные трансферы при использовании 64-битных Windows или 32-битных Windows с PAE
scatter-gather список дробится на куски, при чём размер каждого куска списка ограничен количеством MAP-регистров системы, выделенных для DMA-адаптера функцией IoGetDmaAdapter.
Т.е. если функция IoGetDmaAdapter вернёт 16 буферов (у меня Windows 2008 Server так делает, а 64битная 7ка выдаёт 256 буферов), то вероятнее всего максимальный кусок scatter-gather списка
обрабатываемый зараз должен быть 64Кб (меньше для 1го и последнго трансфера и больше если вдруг Windows замапит подряд 2 страницы памяти, чего я ещё не встречал). При больших объёмах данных
(у меня может быть 12Мб каждые 16мс для одной железки) надо обрабатывать очень много прерываний (в моём случае получается >10000 в секунду), что не очень красиво.
К тому же ограничение на 64-битную фдресацию памячти может быть не только у устройств, но и чипсет может не поддерживать полноценные 64-битные трансферы с шины PCI.
Если я не прав - поправьте.
Возращаясь к GetScatterGatherList:
У меня есть рабочий пример использования GetScatterGatherList, который в любых условиях генерит полный scatter-gather список:
1. Всё инициализируется без отхождений от рекомендаций использования функций в WDDK
static NTSTATUS StartDevice(
PDEVICE_OBJECT DeviceObject,
PCM_FULL_RESOURCE_DESCRIPTOR FullResourceDescriptor,
PCM_FULL_RESOURCE_DESCRIPTOR FullResourceDescriptorTranslated
)
{
......................
......................
RtlZeroMemory(&DeviceDescription, sizeof(DeviceDescription));
DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION;
DeviceDescription.Master = TRUE;
DeviceDescription.ScatterGather = TRUE;
DeviceDescription.DemandMode = TRUE;
DeviceDescription.AutoInitialize = FALSE;
DeviceDescription.Dma32BitAddresses = TRUE;
DeviceDescription.IgnoreCount = FALSE;
DeviceDescription.DmaChannel = 0;
DeviceDescription.InterfaceType = PCIBus;
DeviceDescription.DmaWidth = Width32Bits;
DeviceDescription.DmaSpeed = Compatible;
DeviceDescription.MaximumLength = (ULONG)-1; // 0x100000; // number of bytes
DeviceExtension->DmaAdapter = IoGetDmaAdapter(
DeviceExtension->PhysicalDeviceObject,
&DeviceDescription,
&NumberOfMapRegisters
) ;
......................
......................
}
......................
static VOID StartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
......................
switch ( IrpStack->MajorFunction )
{
................
case IRP_MJ_WRITE: ntStatus = ReadWrite(DeviceObject, Irp, TRUE); break;
case IRP_MJ_READ: ntStatus = ReadWrite(DeviceObject, Irp, FALSE); break;
................
}
......................
}
......................
static NTSTATUS ReadWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, BOOLEAN Write)
{
......................
......................
ntStatus = DeviceExtension->DmaAdapter->DmaOperations->GetScatterGatherList(
DeviceExtension->DmaAdapter,
DeviceObject,
Mdl,
VirtualAddress,
Length,
(PDRIVER_CONTROL)GetScatterGatherListControl,
MappedBuffer,
Write
);
2. В фунции
static VOID GetScatterGatherListControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PSCATTER_GATHER_LIST ScatterGather,
IN PVOID Context)
Собираем список scatter-gather, добавляя кусочки трансферов из полученных замапленных областей, но если вызов функции для конкретного трасфера не первый, то список не перезатираем, а добавлям в конец новые элементы. В конце функции не посылаем, получившийся список нашему устройсву, а просто выходим из функции.
3. Если не возникли проблемы, то в функции ReadWrite ставим стасут занято и
.............
if ( ntStatus == STATUS_PENDING )
{
KIRQL OldIrql;
IoAcquireCancelSpinLock(&OldIrql);
IoSetCancelRoutine(Irp, CancelRoutine);
IoReleaseCancelSpinLock(OldIrql);
IoMarkIrpPending(Irp);
return ntStatus;
}
.................
}
А теперь самое интересное и непонятное: функция ReadWrite с одним и тем же IRP будет вызываться столько раз, насколько кусочков у нас разбит трансфер. Каждый кусочек в GetScatterGatherListControl будем маппироваться в новую область, т.е. не перезатирая старые map-регистры!
4. Когда в список соберётся весь трансфер - запускаем DMA.
5. После окончания DMA освобождаем память через PutScatterGatherList.
Кто-нибудь разбирался с таким поведением Windows?
Если это не баг, то зачем было мучить людей с map-регистрами?
PS Может долго и запутано, но на мой взгляд очень интересный случай, требующий дискуссии.
Добавлено через 1 час, 31 минуту и 32 секунды:а теперь самое интересное и непонятное: функция ReadWrite с одним и тем же IRP будет вызываться столько раз, насколько кусочков у нас разбит трансфер. Каждый кусочек в GetScatterGatherListControl будем маппироваться в новую область, т.е. не перезатирая старые map-регистры!
4. Когда в список соберётся весь трансфер - запускаем DMA.
5. После окончания DMA освобождаем память через PutScatterGatherList.
Извиняюсь за ошибку. Недосмотрел весь код. Схема работы немного другая:
1а. Делаем команду ReadFile (WriteFile) для куска данных не превышающего размер максимальный размер трансфера, т.е. 60КБ для 16 буфферов.
3а. Пока трансфер не окончен делаем переход на 1а.
4. Когда в список соберётся весь трансфер - запускаем DMA.
5. После окончания DMA освобождаем память через PutScatterGatherList.
Путь своеобразный, но! получается map-буфера выделяются для IRP трансфера, а не для канала.