/*++ Copyright (C) Microsoft Corporation, 2000 Module Name: pnp.c Abstract: This file contains plug and play code for the NT iSCSI port driver. Environment: kernel mode only Revision History: --*/ #include "port.h" #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, iScsiPortAddDevice) #pragma alloc_text(PAGE, iScsiPortUnload) #endif // ALLOC_PRAGMA #define NUM_DEVICE_TYPE_INFO_ENTRIES 18 extern ISCSI_DEVICE_TYPE DeviceTypeInfo[]; VOID iSpInitFdoExtension( IN OUT PISCSI_FDO_EXTENSION ); ISCSI_DEVICE_TYPE DeviceTypeInfo[NUM_DEVICE_TYPE_INFO_ENTRIES] = { {"Disk", "GenDisk", L"DiskPeripheral", TRUE}, {"Sequential", "", L"TapePeripheral", TRUE}, {"Printer", "GenPrinter", L"PrinterPeripheral", FALSE}, {"Processor", "", L"OtherPeripheral", FALSE}, {"Worm", "GenWorm", L"WormPeripheral", TRUE}, {"CdRom", "GenCdRom", L"CdRomPeripheral", TRUE}, {"Scanner", "GenScanner", L"ScannerPeripheral", FALSE}, {"Optical", "GenOptical", L"OpticalDiskPeripheral", TRUE}, {"Changer", "ScsiChanger", L"MediumChangerPeripheral", TRUE}, {"Net", "ScsiNet", L"CommunicationsPeripheral", FALSE}, {"ASCIT8", "ScsiASCIT8", L"ASCPrePressGraphicsPeripheral", FALSE}, {"ASCIT8", "ScsiASCIT8", L"ASCPrePressGraphicsPeripheral", FALSE}, {"Array", "ScsiArray", L"ArrayPeripheral", FALSE}, {"Enclosure", "ScsiEnclosure", L"EnclosurePeripheral", FALSE}, {"RBC", "ScsiRBC", L"RBCPeripheral", TRUE}, {"CardReader", "ScsiCardReader", L"CardReaderPeripheral", FALSE}, {"Bridge", "ScsiBridge", L"BridgePeripheral", FALSE}, {"Other", "ScsiOther", L"OtherPeripheral", FALSE} }; PISCSI_FDO_EXTENSION iSpGlobalFdoExtension = NULL; HANDLE iSpTdiNotificationHandle = NULL; NTSTATUS iScsiPortAddDevice( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject ) /*+++ Routine Description: This routine handles add-device requests for the iSCSI port driver Arguments: DriverObject - a pointer to the driver object for this device PhysicalDeviceObject - a pointer to the PDO we are being added to Return Value: STATUS_SUCCESS if successful Appropriate NTStatus code on error --*/ { PDEVICE_OBJECT deviceObject; PDEVICE_OBJECT newDeviceObject; PISCSI_FDO_EXTENSION fdoExtension; PCOMMON_EXTENSION commonExtension; NTSTATUS status; UNICODE_STRING deviceName; RtlInitUnicodeString(&deviceName, ISCSI_FDO_DEVICE_NAME); status = IoCreateDevice(DriverObject, sizeof(ISCSI_FDO_EXTENSION), &deviceName, FILE_DEVICE_NETWORK, 0, FALSE, &deviceObject); if (!NT_SUCCESS(status)) { DebugPrint((1, "iScsiPortAddDevice failed. Status %lx\n", status)); return status; } newDeviceObject = IoAttachDeviceToDeviceStack(deviceObject, PhysicalDeviceObject); if (newDeviceObject == NULL) { DebugPrint((0, "IoAttachDeviceToDeviceStack failed in iScsiAddDevice\n")); IoDeleteDevice(deviceObject); return STATUS_UNSUCCESSFUL; } deviceObject->Flags |= DO_DIRECT_IO; fdoExtension = deviceObject->DeviceExtension; commonExtension = &(fdoExtension->CommonExtension); iSpGlobalFdoExtension = fdoExtension; RtlZeroMemory(fdoExtension, sizeof(ISCSI_FDO_EXTENSION)); fdoExtension->LowerPdo = PhysicalDeviceObject; commonExtension->LowerDeviceObject = newDeviceObject; commonExtension->DeviceObject = deviceObject; commonExtension->IsPdo = FALSE; commonExtension->CurrentPnpState = 0xff; commonExtension->PreviousPnpState = 0xff; commonExtension->IsNetworkReady = FALSE; commonExtension->IsRemoved = NO_REMOVE; commonExtension->RemoveLock = 0; KeInitializeEvent(&(commonExtension->RemoveEvent), SynchronizationEvent, FALSE); KeInitializeSpinLock(&(fdoExtension->EnumerationSpinLock)); IoInitializeTimer(deviceObject, iSpTickHandler, NULL); IoStartTimer(fdoExtension->DeviceObject); // // Initialize the entry points for this device // iScsiPortInitializeDispatchTables(); commonExtension->MajorFunction = FdoMajorFunctionTable; deviceObject->Flags &= ~DO_DEVICE_INITIALIZING; DebugPrint((3, "Add Device was successful\n")); return STATUS_SUCCESS; } VOID iScsiPortUnload( IN PDRIVER_OBJECT DriverObject ) { PISCSIPORT_DRIVER_EXTENSION driverExtension; driverExtension = IoGetDriverObjectExtension( DriverObject, (PVOID)ISCSI_TAG_DRIVER_EXTENSION); if (driverExtension != NULL) { ExFreePool(driverExtension->RegistryPath.Buffer); driverExtension->RegistryPath.Buffer = NULL; driverExtension->RegistryPath.Length = 0; driverExtension->RegistryPath.MaximumLength = 0; } iSpGlobalFdoExtension = NULL; iSpTdiNotificationHandle = NULL; return; } NTSTATUS iScsiPortFdoPnp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PCOMMON_EXTENSION commonExtension = DeviceObject->DeviceExtension; PISCSI_FDO_EXTENSION fdoExtension = DeviceObject->DeviceExtension; PIO_STACK_LOCATION irpStack; NTSTATUS status = STATUS_SUCCESS; ULONG isRemoved; BOOLEAN forwardIrp = FALSE; PAGED_CODE(); irpStack = IoGetCurrentIrpStackLocation(Irp); DebugPrint((1, "FdoPnp for DeviceObject %x, Irp %x, MN %x\n", DeviceObject, Irp, (irpStack->MinorFunction))); isRemoved = iSpAcquireRemoveLock(DeviceObject, Irp); if (isRemoved) { iSpReleaseRemoveLock(DeviceObject, Irp); Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_DEVICE_DOES_NOT_EXIST; } switch (irpStack->MinorFunction) { case IRP_MN_START_DEVICE: { status = iSpSendIrpSynchronous(commonExtension->LowerDeviceObject, Irp); if (NT_SUCCESS(status)) { // // Register for notification from network interface // to be notified of network ready state // if ((commonExtension->IsNetworkReady) == FALSE) { iSpRegisterForNetworkNotification(); } // // Initialize various fields in the // FDO Extension. // iSpInitFdoExtension(fdoExtension); Irp->IoStatus.Status = status; } break; } case IRP_MN_QUERY_STOP_DEVICE: { status = STATUS_SUCCESS; Irp->IoStatus.Status = status; forwardIrp = TRUE; break; } case IRP_MN_CANCEL_STOP_DEVICE: { status = STATUS_SUCCESS; Irp->IoStatus.Status = status; forwardIrp = TRUE; break; } case IRP_MN_STOP_DEVICE: { if (iSpTdiNotificationHandle != NULL) { TdiDeregisterPnPHandlers(iSpTdiNotificationHandle); iSpTdiNotificationHandle = NULL; } // // If network node hasn't been released yet, // release it now // if ((fdoExtension->LocalNodesInitialized) == TRUE) { ULONG inx; for (inx = 0; inx < (fdoExtension->NumberOfTargets); inx++) { iSpStopNetwork(fdoExtension->PDOList[inx]); fdoExtension->PDOList[inx] = NULL; } fdoExtension->LocalNodesInitialized = FALSE; fdoExtension->NumberOfTargets = 0; } fdoExtension->LocalNodesInitialized = FALSE; Irp->IoStatus.Status = status; Irp->IoStatus.Information = 0L; if (NT_SUCCESS(status)) { forwardIrp = TRUE; } break; } case IRP_MN_QUERY_REMOVE_DEVICE: { DebugPrint((0, "Query remove received.\n")); status = STATUS_SUCCESS; Irp->IoStatus.Status = status; forwardIrp = TRUE; break; } case IRP_MN_CANCEL_REMOVE_DEVICE: { status = STATUS_SUCCESS; Irp->IoStatus.Status = status; forwardIrp = TRUE; break; } case IRP_MN_REMOVE_DEVICE: { // // Deregister network notification if we // already haven't // if (iSpTdiNotificationHandle != NULL) { TdiDeregisterPnPHandlers(iSpTdiNotificationHandle); iSpTdiNotificationHandle = NULL; } // // If network node hasn't been released yet, // release it now // if ((fdoExtension->LocalNodesInitialized) == TRUE) { ULONG inx; for (inx = 0; inx < (fdoExtension->NumberOfTargets); inx++) { iSpStopNetwork(fdoExtension->PDOList[inx]); IoDeleteDevice(fdoExtension->PDOList[inx]); fdoExtension->PDOList[inx] = NULL; } fdoExtension->LocalNodesInitialized = FALSE; fdoExtension->NumberOfTargets = 0; } fdoExtension->LocalNodesInitialized = FALSE; iSpReleaseRemoveLock(DeviceObject, Irp); commonExtension->IsRemoved = REMOVE_PENDING; DebugPrint((1, "Waiting for remove event.\n")); KeWaitForSingleObject(&(commonExtension->RemoveEvent), Executive, KernelMode, FALSE, NULL); DebugPrint((1, "Will process remove now.\n")); status = iSpSendIrpSynchronous(commonExtension->LowerDeviceObject, Irp); if (NT_SUCCESS(status)) { IoDetachDevice(commonExtension->LowerDeviceObject); IoDeleteDevice(commonExtension->DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; } else { commonExtension->IsRemoved = NO_REMOVE; Irp->IoStatus.Status = status; } IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; break; } case IRP_MN_QUERY_DEVICE_RELATIONS: { PIO_WORKITEM workItem = NULL; PDEVICE_RELATIONS deviceRelations; PDEVICE_OBJECT lowerDevice; ULONG relationSize; DEVICE_RELATION_TYPE relationType; relationType = irpStack->Parameters.QueryDeviceRelations.Type; if (relationType != BusRelations) { forwardIrp = TRUE; break; } if ((fdoExtension->EnumerationComplete) == TRUE) { ASSERT((fdoExtension->NumberOfTargets) > 0); relationSize = sizeof(DEVICE_RELATIONS) + (sizeof(PDEVICE_OBJECT) * (fdoExtension->NumberOfTargets)); deviceRelations = iSpAllocatePool(PagedPool, relationSize, ISCSI_TAG_DEVICE_RELATIONS); if (deviceRelations != NULL) { PDEVICE_OBJECT pdo; ULONG inx; deviceRelations->Count = fdoExtension->NumberOfTargets; for (inx = 0; (inx < (fdoExtension->NumberOfTargets)); inx++) { pdo = fdoExtension->PDOList[inx]; ASSERT(pdo != NULL); ObReferenceObject(pdo); deviceRelations->Objects[inx] = pdo; } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = (ULONG_PTR) deviceRelations; IoCopyCurrentIrpStackLocationToNext(Irp); lowerDevice = fdoExtension->CommonExtension.LowerDeviceObject; iSpReleaseRemoveLock(DeviceObject, Irp); return IoCallDriver(lowerDevice, Irp); } else { Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; Irp->IoStatus.Information = 0L; iSpReleaseRemoveLock(DeviceObject, Irp); IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_INSUFFICIENT_RESOURCES; } } status = STATUS_SUCCESS; // // Setup the local network nodes if not already done // if ((fdoExtension->LocalNodesInitialized) == FALSE) { status = iSpInitializeLocalNodes(DeviceObject); } if (NT_SUCCESS(status)) { // // Allocate a workitem to perform device // enumeration // fdoExtension->EnumerationThreadLaunched = FALSE; workItem = IoAllocateWorkItem(DeviceObject); if (workItem != NULL) { IoMarkIrpPending(Irp); fdoExtension->EnumerationIrp = Irp; fdoExtension->EnumerationComplete = FALSE; fdoExtension->EnumerationWorkItem = workItem; IoQueueWorkItem(workItem, iSpEnumerateDevicesAsynchronous, DelayedWorkQueue, fdoExtension); iSpReleaseRemoveLock(DeviceObject, Irp); return STATUS_PENDING; } else { Irp->IoStatus.Information = 0L; status = STATUS_INSUFFICIENT_RESOURCES; } break; } else { DebugPrint((1, "Could not setup the node. Status : %x\n", status)); Irp->IoStatus.Status = status; Irp->IoStatus.Information = 0L; } break; } default: { forwardIrp = TRUE; break; } } // switch (irpStack->MinorFunction) iSpReleaseRemoveLock(DeviceObject, Irp); if (forwardIrp == TRUE) { IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(commonExtension->LowerDeviceObject, Irp); } else { status = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); } return status; } NTSTATUS iSpSendIrpSynchronous( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS status; KEVENT event; PAGED_CODE(); if (DeviceObject == NULL) { DebugPrint((1, "DeviceObject NULL. Irp %x\n", Irp)); return Irp->IoStatus.Status; } KeInitializeEvent(&event, NotificationEvent, FALSE); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, iSpSetEvent, &event, TRUE, TRUE, TRUE); status = IoCallDriver(DeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = Irp->IoStatus.Status; } return status; } NTSTATUS iScsiPortGetDeviceId( IN PDEVICE_OBJECT Pdo, OUT PUNICODE_STRING UnicodeString ) /*++ Routine Description: This routine will allocate space for and fill in a device id string for the specified Pdo. This string is generated from the bus type (scsi) and the type of the device. Arguments: Pdo - a pointer to the physical device object being queried UnicodeString - a pointer to an already allocated unicode string structure. This routine will allocate and fill in the buffer of this structure Returns: status --*/ { PISCSI_PDO_EXTENSION pdoExtension = Pdo->DeviceExtension; PINQUIRYDATA inquiryData = &(pdoExtension->InquiryData); UCHAR buffer[256]; PUCHAR rawIdString = buffer; ANSI_STRING ansiIdString; ULONG whichString; PAGED_CODE(); ASSERT(UnicodeString != NULL); RtlZeroMemory(buffer, sizeof(buffer)); sprintf(rawIdString, "SCSI\\%s", iSpGetDeviceTypeInfo(inquiryData->DeviceType)->DeviceTypeString); rawIdString += strlen(rawIdString); ASSERT(*rawIdString == '\0'); for(whichString = 0; whichString < 3; whichString++) { PUCHAR headerString; PUCHAR sourceString; ULONG sourceStringLength; ULONG i; switch(whichString) { // // Vendor Id // case 0: { sourceString = inquiryData->VendorId; sourceStringLength = sizeof(inquiryData->VendorId); headerString = "Ven"; break; } // // Product Id // case 1: { sourceString = inquiryData->ProductId; sourceStringLength = sizeof(inquiryData->ProductId); headerString = "Prod"; break; } // // Product Revision Level // case 2: { sourceString = inquiryData->ProductRevisionLevel; sourceStringLength = sizeof(inquiryData->ProductRevisionLevel); headerString = "Rev"; break; } } // // Start at the end of the source string and back up until we find a // non-space, non-null character. // for(; sourceStringLength > 0; sourceStringLength--) { if((sourceString[sourceStringLength - 1] != ' ') && (sourceString[sourceStringLength - 1] != '\0')) { break; } } // // Throw the header string into the block // sprintf(rawIdString, "&%s_", headerString); rawIdString += strlen(headerString) + 2; // // Spew the string into the device id // for(i = 0; i < sourceStringLength; i++) { *rawIdString = (sourceString[i] != ' ') ? (sourceString[i]) : ('_'); rawIdString++; } ASSERT(*rawIdString == '\0'); } RtlInitAnsiString(&ansiIdString, buffer); DebugPrint((1, "DeviceId for logical unit %#p is %Z\n", Pdo, &ansiIdString)); return RtlAnsiStringToUnicodeString(UnicodeString, &ansiIdString, TRUE); } NTSTATUS iScsiPortGetInstanceId( IN PDEVICE_OBJECT Pdo, OUT PUNICODE_STRING UnicodeString ) /*++ Routine Description: This routine will allocate space for and fill in an instance id string for the specified Pdo. This string will be generated either from the device type + serial number of the device (if it has a serial number) or from the address of the device. Arguments: Pdo - a pointer to the physical device object being queried UnicodeString - a pointer to an already allocated unicode string structure. This routine will allocate and fill in the buffer of this structure Returns: status --*/ { PISCSI_PDO_EXTENSION pdoExtension = Pdo->DeviceExtension; PDRIVER_OBJECT driverObject = Pdo->DriverObject; PISCSI_DEVICE_TYPE deviceTypeInfo; UCHAR idStringBuffer[64]; ANSI_STRING ansiIdString; PAGED_CODE(); ASSERT(UnicodeString != NULL); // // can't use serial number even if it exists since a device which is // multiply connected to the same bus (dual-ported device) will have // the same serial number at each connection and would confuse the PNP. // sprintf(idStringBuffer, "%x%x%x", pdoExtension->PathId, pdoExtension->TargetId, pdoExtension->Lun ); RtlInitAnsiString(&ansiIdString, idStringBuffer); return RtlAnsiStringToUnicodeString(UnicodeString, &ansiIdString, TRUE); } NTSTATUS iScsiPortGetCompatibleIds( IN PDRIVER_OBJECT DriverObject, IN PINQUIRYDATA InquiryData, OUT PUNICODE_STRING UnicodeString ) /*++ Routine Description: This routine will allocate space for and fill in a compatible id multi string for the specified Pdo. This string is generated using the bus and device types for the device Arguments: InquiryData - the inquiry data to generate compatible ids from. UnicodeString - a pointer to an already allocated unicode string structure. This routine will allocate and fill in the buffer of this structure Returns: status --*/ { UCHAR s[sizeof("SCSI\\DEVICE_TYPE_GOES_HERE")]; PSTR stringBuffer[] = { s, "SCSI\\RAW", NULL}; // // Fill in the scsi specific string // sprintf(stringBuffer[0], "SCSI\\%s", iSpGetDeviceTypeInfo(InquiryData->DeviceType)->DeviceTypeString); // // Set up the first id string // return iScsiPortStringArrayToMultiString( DriverObject, UnicodeString, stringBuffer); } NTSTATUS iScsiPortGetHardwareIds( IN PDRIVER_OBJECT DriverObject, IN PINQUIRYDATA InquiryData, OUT PUNICODE_STRING UnicodeString ) /*++ Routine Description: This routine will allocate space for and fill in a hardware id multi string for the specified Pdo. This string is generated using the device type and the inquiry data. Arguments: InquiryData - the inquiry data to be converted into id strings. UnicodeString - a pointer to an already allocated unicode string structure. This routine will allocate and fill in the buffer of this structure Returns: status --*/ #define NUMBER_HARDWARE_STRINGS 6 { PISCSI_DEVICE_TYPE devTypeInfo = iSpGetDeviceTypeInfo(InquiryData->DeviceType); ULONG i; PSTR strings[NUMBER_HARDWARE_STRINGS + 1]; UCHAR scratch[64]; NTSTATUS status; PAGED_CODE(); // // Zero out the string buffer // RtlZeroMemory(strings, sizeof(strings)); try { for(i = 0; i < NUMBER_HARDWARE_STRINGS; i++) { RtlZeroMemory(scratch, sizeof(scratch)); // // Build each of the hardware id's // switch(i) { // // Bus + Dev Type + Vendor + Product + Revision // case 0: { sprintf(scratch, "SCSI\\%s", devTypeInfo->DeviceTypeString); CopyField(scratch + strlen(scratch), InquiryData->VendorId, 8, '_'); CopyField(scratch + strlen(scratch), InquiryData->ProductId, 16, '_'); CopyField(scratch + strlen(scratch), InquiryData->ProductRevisionLevel, 4, '_'); break; } // // bus + device + vendor + product // case 1: { sprintf(scratch, "SCSI\\%s", devTypeInfo->DeviceTypeString); CopyField(scratch + strlen(scratch), InquiryData->VendorId, 8, '_'); CopyField(scratch + strlen(scratch), InquiryData->ProductId, 16, '_'); break; } // // bus + device + vendor // case 2: { sprintf(scratch, "SCSI\\%s", devTypeInfo->DeviceTypeString); CopyField(scratch + strlen(scratch), InquiryData->VendorId, 8, '_'); break; } // // bus \ vendor + product + revision[0] // case 3: { sprintf(scratch, "SCSI\\"); // // Fall through to the next set. // } // // vendor + product + revision[0] (win9x) // case 4: { CopyField(scratch + strlen(scratch), InquiryData->VendorId, 8, '_'); CopyField(scratch + strlen(scratch), InquiryData->ProductId, 16, '_'); CopyField(scratch + strlen(scratch), InquiryData->ProductRevisionLevel, 1, '_'); break; } case 5: { sprintf(scratch, "%s", devTypeInfo->GenericTypeString); break; } default: { ASSERT(FALSE); break; } } if(strlen(scratch) != 0) { strings[i] = iSpAllocatePool(PagedPool, strlen(scratch) + sizeof(UCHAR), ISCSI_TAG_PNP_ID); if(strings[i] == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; leave; } strcpy(strings[i], scratch); } else { break; } } status = iScsiPortStringArrayToMultiString(DriverObject, UnicodeString, strings); leave; } finally { for(i = 0; i < NUMBER_HARDWARE_STRINGS; i++) { if(strings[i]) { ExFreePool(strings[i]); } } } return status; } #undef NUMBER_HARDWARE_STRINGS VOID CopyField( IN PUCHAR Destination, IN PUCHAR Source, IN ULONG Count, IN UCHAR Change ) /*++ Routine Description: This routine will copy Count string bytes from Source to Destination. If it finds a nul byte in the Source it will translate that and any subsequent bytes into Change. It will also replace spaces with the specified character. Arguments: Destination - the location to copy bytes Source - the location to copy bytes from Count - the number of bytes to be copied Return Value: none --*/ { ULONG i = 0; BOOLEAN pastEnd = FALSE; PAGED_CODE(); for(i = 0; i < Count; i++) { if(!pastEnd) { if(Source[i] == 0) { pastEnd = TRUE; Destination[i] = Change; } else if(Source[i] == ' ') { Destination[i] = Change; } else { Destination[i] = Source[i]; } } else { Destination[i] = Change; } } return; } PISCSI_DEVICE_TYPE iSpGetDeviceTypeInfo( IN UCHAR DeviceType ) { PAGED_CODE(); if(DeviceType >= NUM_DEVICE_TYPE_INFO_ENTRIES) { return &(DeviceTypeInfo[NUM_DEVICE_TYPE_INFO_ENTRIES - 1]); } else { return &(DeviceTypeInfo[DeviceType]); } }; VOID iSpInitFdoExtension( IN OUT PISCSI_FDO_EXTENSION FdoExtension ) { PIO_SCSI_CAPABILITIES capabilities; capabilities = &(FdoExtension->IoScsiCapabilities); // // For now, we are going to hard code most of the values // capabilities->Length = sizeof(IO_SCSI_CAPABILITIES); capabilities->MaximumTransferLength = 0x8000; capabilities->MaximumPhysicalPages = 9; capabilities->SupportedAsynchronousEvents = 0; capabilities->AlignmentMask = 0; capabilities->TaggedQueuing = FALSE; capabilities->AdapterScansDown = FALSE; capabilities->AdapterUsesPio = FALSE; }