/* * Copyright (c) Microsoft Corporation, 1993. All Rights Reserved. */ /* * vcinit.c * * * 32-bit Video Capture driver * kernel-mode support library - initialisation routines * * Geraint Davies, Feb 93 */ #include #include "vckpriv.h" /* --- internal functions -------------------------------------------- */ /* * create a possible device name by appending a given number to * the standard basename, and appending that to the string \device. * Puts the result in the counted string pName, which must be initialised * to point to sufficient storage. */ NTSTATUS VC_CreateNumberedName( int DeviceNumber, // number to append LPWSTR pHeader, // header for device (eg \Device) LPWSTR pBase, // base name - eg VidCap, WaveOut PUNICODE_STRING pUniqueName // result goes here ) { UNICODE_STRING UnicodeNumber; WCHAR Number[12]; // big enough for any 32-bit number /* * start the string off with \device * -assumes the string is correctly inited to 0 length. */ if (pHeader != NULL) { RtlAppendUnicodeToString(pUniqueName, pHeader); } /* * add the base name (eg VidCap) */ RtlAppendUnicodeToString(pUniqueName, pBase); /* * convert the number to a unicode string and append that */ UnicodeNumber.Buffer = Number; UnicodeNumber.MaximumLength = sizeof(Number); RtlIntegerToUnicodeString(DeviceNumber, 10, &UnicodeNumber); RtlAppendUnicodeStringToString(pUniqueName, &UnicodeNumber); /* done!: we now have eg \Device\VidCap0 - its up to the caller * to establish if this is unique or not */ return(STATUS_SUCCESS); } /* * given a name in \device, create a symbolic link into \dosdevices * for this name so that the device can be opened by Win32 applications. */ NTSTATUS VC_CreateLink(PUNICODE_STRING pusName) { UNICODE_STRING LinkObject; WCHAR LinkName[80]; LinkName[0] = UNICODE_NULL; RtlInitUnicodeString(&LinkObject, LinkName); LinkObject.MaximumLength = sizeof(LinkName); RtlAppendUnicodeToString(&LinkObject, L"\\DosDevices"); // DeviceSize is a known compile time constant - use a define // rather than a variable as this reduces code size. #define DeviceSize (sizeof(L"\\Device") - sizeof(UNICODE_NULL)) pusName->Buffer += DeviceSize / sizeof(WCHAR); pusName->Length -= DeviceSize; RtlAppendUnicodeStringToString(&LinkObject, pusName); pusName->Buffer -= DeviceSize / sizeof(WCHAR); pusName->Length += DeviceSize; return (IoCreateSymbolicLink(&LinkObject, pusName)); } /* * translate the bus-relative port address using the HAL, * and map the port address into memory if necessary */ VOID VC_MapPort(PDEVICE_INFO pDevInfo, PUCHAR PortBase, ULONG NrOfPorts) { PHYSICAL_ADDRESS PortAddress; PHYSICAL_ADDRESS MappedAddress; pDevInfo->PortMemType = 1; // i/o space PortAddress.LowPart = (ULONG) PortBase; PortAddress.HighPart = 0; HalTranslateBusAddress( pDevInfo->BusType, pDevInfo->BusNumber, PortAddress, &pDevInfo->PortMemType, &MappedAddress); if (pDevInfo->PortMemType == 0) { /* memory mapped i/o - map the physical address into system space*/ pDevInfo->PortBase = MmMapIoSpace(MappedAddress, NrOfPorts, FALSE); dprintf2(("ports at 0x%x", pDevInfo->PortBase)); } else { pDevInfo->PortBase = (PUCHAR) MappedAddress.LowPart; dprintf2(("ports at 0x%x (i/o space)", pDevInfo->PortBase)); } pDevInfo->NrOfPorts = NrOfPorts; } /* * translate the bus-relative frame buffer address using the HAL, * and map the whole buffer into system memory. */ VOID VC_MapMemory(PDEVICE_INFO pDevInfo, PUCHAR Base, ULONG Length) { PHYSICAL_ADDRESS BaseAddress; PHYSICAL_ADDRESS MappedAddress; pDevInfo->FrameMemType = 0; // memory space BaseAddress.LowPart = (ULONG) Base; BaseAddress.HighPart = 0; HalTranslateBusAddress( pDevInfo->BusType, pDevInfo->BusNumber, BaseAddress, &pDevInfo->FrameMemType, &MappedAddress); if (pDevInfo->FrameMemType == 0) { /* memory mapped i/o - map the physical address into system space*/ pDevInfo->FrameBase = MmMapIoSpace(MappedAddress, Length, FALSE); dprintf2(("frame buffer at 0x%x", pDevInfo->FrameBase)); } else { pDevInfo->FrameBase = (PUCHAR) MappedAddress.LowPart; dprintf(("using raw address 0x%x", pDevInfo->FrameBase)); } pDevInfo->FrameLength = Length; } /* * Report Port, Interrupt and Frame Buffer Memory resources and detect any * conflict. return TRUE if conflict reported, false if everything ok. */ BOOLEAN VC_ReportResources( PDRIVER_OBJECT pDriverObject, PDEVICE_INFO pDevInfo, PUCHAR PortBase, ULONG NrOfPorts, ULONG Interrupt, BOOLEAN bLatched, PUCHAR pFrameBuffer, ULONG FrameLength) { UCHAR ResBuffer[3 * sizeof(CM_RESOURCE_LIST) ]; PCM_RESOURCE_LIST ResourceList; PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor; BOOLEAN ResourceConflict; NTSTATUS Status; ResourceList = (PCM_RESOURCE_LIST) ResBuffer; Descriptor = ResourceList->List[0].PartialResourceList.PartialDescriptors; ResourceConflict = FALSE; /* init the buffer to 0 */ RtlZeroMemory(ResBuffer, sizeof(ResBuffer)); /* we have only one list of resources (there's only one bus!?) */ ResourceList->Count = 1; ResourceList->List[0].InterfaceType = pDevInfo->BusType; ResourceList->List[0].BusNumber = pDevInfo->BusNumber; /* add the i/o ports in use to the list */ ResourceList->List[0].PartialResourceList.Count++; Descriptor->Type = CmResourceTypePort; Descriptor->ShareDisposition = CmResourceShareDeviceExclusive; Descriptor->u.Port.Start.LowPart = (ULONG) PortBase; Descriptor->u.Port.Length = NrOfPorts; Descriptor->Flags = (USHORT)((pDevInfo->PortMemType == 0 ? CM_RESOURCE_PORT_MEMORY : CM_RESOURCE_PORT_IO)); Descriptor++; /* add the interrupt to the list */ ResourceList->List[0].PartialResourceList.Count++; Descriptor->Type = CmResourceTypeInterrupt; Descriptor->ShareDisposition = CmResourceShareDeviceExclusive; Descriptor->u.Interrupt.Level = Interrupt; Descriptor->u.Interrupt.Vector = Interrupt; Descriptor->Flags = (USHORT)((bLatched ? CM_RESOURCE_INTERRUPT_LATCHED : CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE)); Descriptor++; /* add the frame buffer to the list */ ResourceList->List[0].PartialResourceList.Count++; Descriptor->Type = CmResourceTypeMemory; Descriptor->ShareDisposition = CmResourceShareDeviceExclusive; Descriptor->u.Memory.Start.LowPart = (ULONG) pFrameBuffer; Descriptor->u.Memory.Length = FrameLength; Descriptor->Flags = 0; Descriptor++; /* now report the resources */ Status = IoReportResourceUsage(NULL, pDriverObject, ResourceList, (PUCHAR)Descriptor - (PUCHAR)ResourceList, NULL, NULL, 0, FALSE, &ResourceConflict); return(ResourceConflict); } NTSTATUS VC_GetBusCallout( IN PVOID Context, IN PUNICODE_STRING PathName, IN INTERFACE_TYPE BusType, IN ULONG BusNumber, IN PKEY_VALUE_FULL_INFORMATION *BusInformation, IN CONFIGURATION_TYPE ControllerType, IN ULONG ControllerNumber, IN PKEY_VALUE_FULL_INFORMATION *ControllerInformation, IN CONFIGURATION_TYPE PeripheralType, IN ULONG PeripheralNumber, IN PKEY_VALUE_FULL_INFORMATION *PeripheralInformation ) /*++ Routine Description Called back from IoQueryDeviceDescription in VC_GetBus to verify that the bus type asked for is found. Notify VC_GetBus via the context info (ptr to a BOOL). Arguments via Return Value STATUS_SUCCESS if ok, otherwise an error status --*/ { // the fact that we are called is sufficient to indicate that // the bus has been found. * (PBOOLEAN)Context = TRUE; return(STATUS_SUCCESS); } NTSTATUS VC_GetBus( IN INTERFACE_TYPE BusType, OUT PULONG pBusNumber ) /*++ Routine Description Find if there is a bus of type BusType. Arguments BusType - bus type: isa or eisa.. pBusNumber - return location for bus number if found Return Value STATUS_SUCCESS if found, or STATUS_DEVICE_DOES_NOT_EXIST if no such bus. --*/ { BOOLEAN fSuccess = FALSE; *pBusNumber = 0; IoQueryDeviceDescription( &BusType, pBusNumber, NULL, NULL, NULL, NULL, VC_GetBusCallout, (PVOID) &fSuccess); if (fSuccess) { return(STATUS_SUCCESS); } else { return(STATUS_DEVICE_DOES_NOT_EXIST); } } /* * save the path to our registry entry (including parameters subkey) * in paged-pool memory. */ PWCHAR VC_SaveRegistryPathName( PUNICODE_STRING RegistryPathName ) { int Length; PWCHAR SavedString; Length = RegistryPathName->Length + sizeof(PARMS_SUBKEY) + sizeof(UNICODE_NULL); // Include backslash SavedString = ExAllocatePool(PagedPool, Length); // Only access on caller thread if (SavedString == NULL) { return NULL; } // // Copy the character data // RtlCopyMemory(SavedString, RegistryPathName->Buffer, RegistryPathName->Length); SavedString[RegistryPathName->Length / sizeof(WCHAR)] = L'\\'; SavedString[RegistryPathName->Length / sizeof(WCHAR) + 1] = UNICODE_NULL; // // Append the parameters suffix prepended by a backslash // wcscat(SavedString, PARMS_SUBKEY); return SavedString; } /* --- external functions -------------------------------------------- */ /* * VC_Init * * Create the device object, and any necessary related setup, and * allocate device extension data. The device extension data is * a DEVICE_INFO struct plus however much data the caller wants for * hardware-specific data. * * parameters: * pDriverObject - pointer to driver object (arg to DriverEntry) * RegistryPathName - entry for this driver in registry (arg to DriverEntry) * HWInfoSize - amount of data to allocate at end of DeviceExtension * * returns pointer to device extension data as DEVICE_INFO struct. */ PDEVICE_INFO VC_Init( PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPathName, ULONG HWInfoSize) { NTSTATUS Status; UNICODE_STRING usName; // name of device to create WCHAR usNameBuffer[MAX_VIDCAP_NAME_LENGTH]; PDEVICE_INFO pDevInfo; // h-w specific device extension data PDEVICE_OBJECT pDevObj; int DeviceNumber; #if DBG dprintf1(("VC Init called")); if (VCDebugLevel > 4) { DbgBreakPoint(); } #endif /* * create the device object * - we need to create a unique name for this device, by trying * to create names with 0, then 1 etc appended until the name is unique. */ for(DeviceNumber = 0; DeviceNumber < MAX_VIDCAP_DEVICES; DeviceNumber++) { usName.Buffer = usNameBuffer; usName.MaximumLength = sizeof(usNameBuffer); usName.Length = 0; VC_CreateNumberedName( DeviceNumber, L"\\Device\\", DD_VIDCAP_DEVICE_NAME_U, &usName ); Status = IoCreateDevice(pDriverObject, sizeof(DEVICE_INFO) + HWInfoSize, &usName, 0, FILE_DEVICE_SOUND, // device type:multimedia == sound? FALSE, // not exclusive - more than one thread access &pDevObj); if (NT_SUCCESS(Status)) { break; } } if (!NT_SUCCESS(Status)) { dprintf(("Cannot create device object")); VC_Cleanup(pDriverObject); return(NULL); } /* get pointer to device extension and init data*/ pDevInfo = (PDEVICE_INFO) pDevObj->DeviceExtension; pDevInfo->DeviceNumber = DeviceNumber; pDevInfo->State = State_Idle; pDevInfo->DeviceInUse = 0; pDevInfo->pSystemBuffer = NULL; /* ptr back to device object */ pDevInfo->pDeviceObject = pDevObj; /* initialise the device mutex */ KeInitializeMutex(&pDevInfo->Mutex, 1); /* init dpc data and register with Io system */ pDevInfo->DpcRequested = FALSE; IoInitializeDpcRequest(pDevObj, VC_Deferred); /* initialise queue of add-buffer irps */ InitializeListHead(&pDevInfo->BufferHead); /* initialise queue of wait-error irps */ InitializeListHead(&pDevInfo->WaitErrorHead); pDevInfo->nSkipped = 0; /* initialise the spinlock - this is used to protect the * DPC routine for callers to VC_SynchronizeDPC */ KeInitializeSpinLock(&pDevInfo->DeviceSpinLock); /* * create a link into dosdevices for this same name so that win32 * apps can open the device (they need to open eg \\.\VidCap0 ) */ if (!NT_SUCCESS(VC_CreateLink(&usName))) { dprintf(("cannot create link")); VC_Cleanup(pDriverObject); return(NULL); } /* * save the registry pathname in pDevInfo, appending the * Parameters subkey */ pDevInfo->ParametersKey = VC_SaveRegistryPathName(RegistryPathName); if (pDevInfo->ParametersKey == NULL) { VC_Cleanup(pDriverObject); return(NULL); } /* * now store the device name in the registry. We need to store the * base name + number, not the leading \Device or \DosDevices, since * these are not seen by the win32 app (they prepend \\.\ to this * base name). */ usName.Buffer = usNameBuffer; usName.MaximumLength = sizeof(usNameBuffer); usName.Length = 0; VC_CreateNumberedName( DeviceNumber, NULL, DD_VIDCAP_DEVICE_NAME_U, &usName ); /* * convert the counted string to a null-terminated string * before passing to the registry. Note that the Length field of * counted strings is a byte length. */ usName.Buffer[usName.Length / sizeof(WCHAR)] = UNICODE_NULL; Status = RtlWriteRegistryValue( RTL_REGISTRY_ABSOLUTE, pDevInfo->ParametersKey, REG_DEVNAME, REG_SZ, usName.Buffer, usName.Length ); /* set up dispatch table */ pDriverObject->DriverUnload = VC_Cleanup; pDriverObject->MajorFunction[IRP_MJ_CREATE] = VC_Dispatch; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = VC_Dispatch; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VC_Dispatch; return (pDevInfo); } /* * VC_GetResources * * map port and frame buffer into system address space or i/o space, and * report resource usage of the ports, interrupt and physical memory * address used. * * Note: We do not connect the interrupt: this is not done until * a subsequent call to VC_ConnectInterrupt. We do, however, report * usage of the interrupt. * * Don't call VC_Cleanup to clean up if failure: the h/w driver will * call VC_Cleanup AFTER writing an errorcode to the registry * * we return TRUE if success, or FALSE if we couldn't get the resources. */ BOOLEAN VC_GetResources( PDEVICE_INFO pDevInfo, PDRIVER_OBJECT pDriverObject, PUCHAR PortBase, ULONG NrOfPorts, ULONG Interrupt, BOOLEAN bLatched, PUCHAR pFrameBuffer, ULONG FrameLength) { NTSTATUS Status; /* find which bus we're on - check it is isa or (if no isa) eisa */ pDevInfo->BusType = Isa; Status = VC_GetBus(pDevInfo->BusType, &pDevInfo->BusNumber); if (!NT_SUCCESS(Status)) { pDevInfo->BusType = Eisa; Status = VC_GetBus(pDevInfo->BusType, &pDevInfo->BusNumber); if (!NT_SUCCESS(Status)) { return(FALSE); } } /* * map the port address - we need to do this before reporting resources * since we need to work out whether it is memory-mapped or i/o-mapped */ VC_MapPort(pDevInfo, PortBase, NrOfPorts); /* report the resources (port, interrupt, framebuffer) that we use * and check that there is no conflict */ if (VC_ReportResources(pDriverObject, pDevInfo, PortBase, NrOfPorts, Interrupt, bLatched, pFrameBuffer, FrameLength)) { dprintf1(("resource conflict detected")); return(FALSE); } /* map the frame buffer into system memory */ VC_MapMemory(pDevInfo, pFrameBuffer, FrameLength); return (TRUE); } /* * VC_ConnectInterrupt * * This assumes that VC_GetResources has already been called to report the * resource usage, and that the VC_CALLBACK table has been set up * to handle interrupts. * * returns TRUE if success. */ BOOLEAN VC_ConnectInterrupt( PDEVICE_INFO pDevInfo, ULONG Interrupt, BOOLEAN bLatched) { KAFFINITY Affinity; KIRQL InterruptRQL; ULONG Vector; NTSTATUS Status; Vector = HalGetInterruptVector(pDevInfo->BusType, pDevInfo->BusNumber, Interrupt, Interrupt, &InterruptRQL, &Affinity); Status = IoConnectInterrupt(&pDevInfo->InterruptObject, VC_InterruptService, pDevInfo, NULL, Vector, InterruptRQL, InterruptRQL, bLatched ? Latched : LevelSensitive, FALSE, Affinity, FALSE); return ((BOOLEAN) NT_SUCCESS(Status)); } /* * clean up any allocations etc that can be freed on last close. * * called at device unload and at last close */ VOID VC_Close(PDEVICE_INFO pDevInfo) { PIRP pIrp; /* * cancel any outstanding irps - for this we need to hold the * cancel spinlock */ /* cancel any in the queue of wait-error requests */ for (;;) { pIrp = VC_ExtractNextIrp(&pDevInfo->WaitErrorHead, FALSE); if (!pIrp) { break; } pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(pIrp, IO_NO_INCREMENT); } /* cancel any in the queue of add-buffer requests */ for (;;) { pIrp = VC_ExtractNextIrp(&pDevInfo->BufferHead, FALSE); if (!pIrp) { break; } pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_CANCELLED; /* free the pages and mdl we allocated */ //MmUnlockPages(pIrp->MdlAddress); //IoFreeMdl(pIrp->MdlAddress); IoCompleteRequest(pIrp, IO_NO_INCREMENT); } /* free system buffer if we have one */ if (pDevInfo->pSystemBuffer) { ExFreePool(pDevInfo->pSystemBuffer); } } VOID VC_Cleanup( PDRIVER_OBJECT pDriverObject ) /*++ Routine Description Cleanup on driver unload or aborted load. Arguments pDriverObject - pointer to a driver object Return Value None --*/ { PDEVICE_INFO pDevInfo; UNICODE_STRING usLink; WCHAR LinkName[MAX_VIDCAP_NAME_LENGTH]; NTSTATUS Status; if (pDriverObject->DeviceObject == NULL) { /* * if there is no device created yet, then there can * be nothing to do */ return; } pDevInfo = (PDEVICE_INFO) pDriverObject->DeviceObject->DeviceExtension; // clean up irp q and sysbuffer VC_Close(pDevInfo); /* call the device-specific cleanup if there is one */ if (pDevInfo->Callback.CleanupFunc != NULL) { pDevInfo->Callback.CleanupFunc(pDevInfo); } dprintf2(("completed h/w cleanup")); /* free memory mapping if memory-mapped i/o */ if (pDevInfo->PortMemType == 0) { MmUnmapIoSpace(pDevInfo->PortBase, pDevInfo->NrOfPorts); dprintf2(("unmapped ports")); } if (pDevInfo->FrameBase != NULL) { if (pDevInfo->FrameMemType == 0) { MmUnmapIoSpace(pDevInfo->FrameBase, pDevInfo->FrameLength); dprintf2(("unmapped frame buffer")); } } if (pDevInfo->InterruptObject) { IoDisconnectInterrupt(pDevInfo->InterruptObject); } dprintf2(("disconnected interrupt")); /* un-report any resources used by the driver and the device */ { CM_RESOURCE_LIST EmptyList; BOOLEAN ResourceConflict; EmptyList.Count = 0; IoReportResourceUsage(NULL, pDriverObject, &EmptyList, sizeof(ULONG), pDriverObject->DeviceObject, &EmptyList, sizeof(ULONG), FALSE, &ResourceConflict); } /* free paged-pool registry pathname */ if (pDevInfo->ParametersKey != NULL) { ExFreePool(pDevInfo->ParametersKey); pDevInfo->ParametersKey = NULL; } /* free symbolic link */ usLink.Buffer = LinkName; usLink.MaximumLength = sizeof(LinkName); usLink.Length = 0; VC_CreateNumberedName( pDevInfo->DeviceNumber, L"\\DosDevices\\", DD_VIDCAP_DEVICE_NAME_U, &usLink ); Status = IoDeleteSymbolicLink(&usLink); /* free device object */ IoDeleteDevice(pDriverObject->DeviceObject); dprintf1(("driver unloaded")); }