#include "genport.h" #include "stdlib.h" #define ResListSize sizeof(pResourceList) NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) // *********** Called when driver starts *************** { ULONG PortBase[10]; // Port location, in NT's address form. ULONG PortCount[10]; // Count of contiguous I/O ports PHYSICAL_ADDRESS PortAddress[10]; PLOCAL_DEVICE_INFO pLocalInfo; // Device extension: // local information for each device. NTSTATUS Status; PDEVICE_OBJECT DeviceObject; CM_RESOURCE_LIST pResourceList[10]; // Resource usage list to report to system BOOLEAN ResourceConflict; // This is set true if our I/O ports // conflict with another driver USHORT NumberRanges, i; PHYSICAL_ADDRESS MappedAddress[10]; ULONG MemType[10]; // We're now trying to get 10 base ports and 10 port counts. // If there's no corresponding registry entry we break and // save the number of successfully requested ones. // If we get no parameters then exit the routine. for(i = 0; i < 10; i++) { static WCHAR SubKeyString[] = L"\\Parameters"; UNICODE_STRING paramPath; RTL_QUERY_REGISTRY_TABLE paramTable[3]; ULONG DefaultBase = 0; //BASE_PORT; ULONG DefaultCount = 0; //NUMBER_PORTS; paramPath.MaximumLength = RegistryPath->Length + sizeof(SubKeyString); paramPath.Buffer = ExAllocatePool(PagedPool, paramPath.MaximumLength); if (paramPath.Buffer != NULL) { RtlMoveMemory( paramPath.Buffer, RegistryPath->Buffer, RegistryPath->Length); RtlMoveMemory( ¶mPath.Buffer[RegistryPath->Length / 2], SubKeyString, sizeof(SubKeyString)); paramPath.Length = paramPath.MaximumLength - 2; RtlZeroMemory(¶mTable[0], sizeof(paramTable)); paramTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; paramTable[0].Name = L"IoPortAddress0"; // Entry looks like "IoPortAddress_" where _ is // index of current port base. // So here we change "0" at the end of registry entry // name to current loop variable value. paramTable[0].Name[13] = i + 48; paramTable[0].EntryContext = &PortBase[i]; paramTable[0].DefaultType = REG_DWORD; paramTable[0].DefaultData = &DefaultBase; paramTable[0].DefaultLength = sizeof(ULONG); paramTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT; paramTable[1].Name = L"IoPortCount0"; paramTable[1].Name[11] = i + 48; // Here's the same trick as above. paramTable[1].EntryContext = &PortCount[i]; paramTable[1].DefaultType = REG_DWORD; paramTable[1].DefaultData = &DefaultCount; paramTable[1].DefaultLength = sizeof(ULONG); Status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE, paramPath.Buffer, ¶mTable[0], NULL, NULL); ExFreePool(paramPath.Buffer); if (!NT_SUCCESS(Status) || PortCount[i] == 0 || PortBase[i] == 0) break; // We can't get registry entry - exit loop. } } // Remeber how many port ranges did we get from registry. // If none - exit routine with error code. NumberRanges = i; if(NumberRanges == 0) return STATUS_DEVICE_CONFIGURATION_ERROR; // Now we're filling the resource usage list with port numbers and counts. // This is used to tell the system what we're going to use. RtlZeroMemory((PVOID)pResourceList, ResListSize); pResourceList->Count = NumberRanges; for(i = 0; i < NumberRanges; i++) { pResourceList->List[i].InterfaceType = Isa; // pResourceList->List[i].Busnumber = 0; Already 0 pResourceList->List[i].PartialResourceList.Count = 1; PortAddress[i].LowPart = PortBase[i]; PortAddress[i].HighPart = 0; // Register resource usage (ports) // // This ensures that there isn't a conflict between this driver and // a previously loaded one or a future loaded one. pResourceList->List[i].PartialResourceList.PartialDescriptors[0].Type = CmResourceTypePort; pResourceList->List[i].PartialResourceList.PartialDescriptors[0].ShareDisposition = CmResourceShareDriverExclusive; pResourceList->List[i].PartialResourceList.PartialDescriptors[0].Flags = CM_RESOURCE_PORT_IO; pResourceList->List[i].PartialResourceList.PartialDescriptors[0].u.Port.Start = PortAddress[i]; pResourceList->List[i].PartialResourceList.PartialDescriptors[0].u.Port.Length = PortCount[i]; } // Report our resource usage and detect conflicts Status = IoReportResourceUsage( NULL, DriverObject, pResourceList, ResListSize, NULL, NULL, 0, FALSE, &ResourceConflict); if (ResourceConflict) Status = STATUS_DEVICE_CONFIGURATION_ERROR; if (!NT_SUCCESS(Status)) return Status; // Fill driver object's method pointers with our functions. DriverObject->MajorFunction[IRP_MJ_CREATE] = GpdDispatch; DriverObject->MajorFunction[IRP_MJ_CLOSE] = GpdDispatch; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = GpdDispatch; DriverObject->DriverUnload = GpdUnload; // Create our device. Status = GpdCreateDevice( GPD_DEVICE_NAME, GPD_TYPE, DriverObject, &DeviceObject ); if ( !NT_SUCCESS(Status) ) { // // Error creating device - release resources // RtlZeroMemory((PVOID)pResourceList, ResListSize); // Unreport our resource usage Status = IoReportResourceUsage( NULL, DriverObject, pResourceList, ResListSize, NULL, NULL, 0, FALSE, &ResourceConflict); return Status; } for(i = 0; i < NumberRanges; i++) { // Convert the IO port address into a form NT likes. MemType[i] = 1; // located in IO space HalTranslateBusAddress( Isa, 0, PortAddress[i], &MemType[i], &MappedAddress[i] ); // Initialize the local driver info for each device object. pLocalInfo = (PLOCAL_DEVICE_INFO)DeviceObject->DeviceExtension; if (MemType[i] == 0) { // Port is accessed through memory space - so get a virtual address pLocalInfo->PortWasMapped[i] = TRUE; // BUGBUG // MmMapIoSpace can fail if we run out of PTEs, we should be // checking the return value here pLocalInfo->PortBase[i] = MmMapIoSpace(MappedAddress[i], PortCount[i], FALSE); } else { pLocalInfo->PortWasMapped[i] = FALSE; pLocalInfo->PortBase[i] = (PVOID)MappedAddress[i].LowPart; } pLocalInfo->DeviceObject = DeviceObject; pLocalInfo->DeviceType = GPD_TYPE; pLocalInfo->PortCount[i] = PortCount[i]; pLocalInfo->PortMemoryType[i] = MemType[i]; pLocalInfo->NumberRanges = NumberRanges; } return Status; } NTSTATUS GpdCreateDevice( IN PWSTR PrototypeName, IN DEVICE_TYPE DeviceType, IN PDRIVER_OBJECT DriverObject, OUT PDEVICE_OBJECT *ppDevObj ) /*++ Routine Description: This routine creates the device object and the symbolic link in \DosDevices. Ideally a name derived from a "Prototype", with a number appended at the end should be used. For simplicity, just use the fixed name defined in the include file. This means that only one device can be created. A symbolic link must be created between the device name and an entry in \DosDevices in order to allow Win32 applications to open the device. Arguments: PrototypeName - Name base, # WOULD be appended to this. DeviceType - Type of device to create DriverObject - Pointer to driver object created by the system. ppDevObj - Pointer to place to store pointer to created device object Return Value: STATUS_SUCCESS if the device and link are created correctly, otherwise an error indicating the reason for failure. --*/ { NTSTATUS Status; // Status of utility calls UNICODE_STRING NtDeviceName; UNICODE_STRING Win32DeviceName; // Get UNICODE name for device. RtlInitUnicodeString(&NtDeviceName, PrototypeName); Status = IoCreateDevice( // Create it. DriverObject, sizeof(LOCAL_DEVICE_INFO), &NtDeviceName, DeviceType, 0, FALSE, // Not Exclusive ppDevObj ); if (!NT_SUCCESS(Status)) return Status; // Give up if create failed. // Clear local device info memory RtlZeroMemory((*ppDevObj)->DeviceExtension, sizeof(LOCAL_DEVICE_INFO)); // // Set up the rest of the device info // These are used for IRP_MJ_READ and IRP_MJ_WRITE which we don't use // // (*ppDevObj)->Flags |= DO_BUFFERED_IO; // (*ppDevObj)->AlignmentRequirement = FILE_BYTE_ALIGNMENT; // RtlInitUnicodeString(&Win32DeviceName, DOS_DEVICE_NAME); Status = IoCreateSymbolicLink( &Win32DeviceName, &NtDeviceName ); if (!NT_SUCCESS(Status)) // If we we couldn't create the link then { // abort installation. IoDeleteDevice(*ppDevObj); } return Status; } NTSTATUS GpdDispatch( IN PDEVICE_OBJECT pDO, IN PIRP pIrp ) /*++ Routine Description: This routine is the dispatch handler for the driver. It is responsible for processing the IRPs. Arguments: pDO - Pointer to device object. pIrp - Pointer to the current IRP. Return Value: STATUS_SUCCESS if the IRP was processed successfully, otherwise an error indicating the reason for failure. --*/ { PLOCAL_DEVICE_INFO pLDI; PIO_STACK_LOCATION pIrpStack; NTSTATUS Status; // Initialize the irp info field. // This is used to return the number of bytes transfered. pIrp->IoStatus.Information = 0; pLDI = (PLOCAL_DEVICE_INFO)pDO->DeviceExtension; // Get local info struct pIrpStack = IoGetCurrentIrpStackLocation(pIrp); // Set default return status Status = STATUS_NOT_IMPLEMENTED; // Dispatch based on major fcn code. switch (pIrpStack->MajorFunction) { case IRP_MJ_CREATE: case IRP_MJ_CLOSE: // We don't need any special processing on open/close so we'll // just return success. Status = STATUS_SUCCESS; break; case IRP_MJ_DEVICE_CONTROL: // Dispatch on IOCTL switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_GPD_READ_PORT_UCHAR: case IOCTL_GPD_READ_PORT_USHORT: case IOCTL_GPD_READ_PORT_ULONG: Status = GpdIoctlReadPort( pLDI, pIrp, pIrpStack, pIrpStack->Parameters.DeviceIoControl.IoControlCode ); break; case IOCTL_GPD_WRITE_PORT_UCHAR: case IOCTL_GPD_WRITE_PORT_USHORT: case IOCTL_GPD_WRITE_PORT_ULONG: Status = GpdIoctlWritePort( pLDI, pIrp, pIrpStack, pIrpStack->Parameters.DeviceIoControl.IoControlCode ); break; } break; } // We're done with I/O request. Record the status of the I/O action. pIrp->IoStatus.Status = Status; // Don't boost priority when returning since this took little time. IoCompleteRequest(pIrp, IO_NO_INCREMENT ); return Status; } NTSTATUS GpdIoctlReadPort( IN PLOCAL_DEVICE_INFO pLDI, IN PIRP pIrp, IN PIO_STACK_LOCATION IrpStack, IN ULONG IoctlCode ) /*++ Routine Description: This routine processes the IOCTLs which read from the ports. Arguments: pLDI - our local device data pIrp - IO request packet IrpStack - The current stack location IoctlCode - The ioctl code from the IRP Return Value: STATUS_SUCCESS -- OK STATUS_INVALID_PARAMETER -- The buffer sent to the driver was too small to contain the port, or the buffer which would be sent back to the driver was not a multiple of the data size. STATUS_ACCESS_VIOLATION -- An illegal port number was given. --*/ { // NOTE: Use METHOD_BUFFERED ioctls. PULONG pIOBuffer; // Pointer to transfer buffer // (treated as an array of longs). ULONG InBufferSize; // Amount of data avail. from caller. ULONG OutBufferSize; // Max data that caller can accept. ULONG nPort; // Port number to read ULONG DataBufferSize; USHORT i, RangeNumber; NTSTATUS Status; // Size of buffer containing data from application InBufferSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength; // Size of buffer for data to be sent to application OutBufferSize = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; // NT copies inbuf here before entry and copies this to outbuf after // return, for METHOD_BUFFERED IOCTL's. pIOBuffer = (PULONG)pIrp->AssociatedIrp.SystemBuffer; // Check to ensure input buffer is big enough to hold a port number and // the output buffer is at least as big as the port data width. // switch (IoctlCode) { default: // There isn't really any default but /* FALL THRU */ // this will quiet the compiler. case IOCTL_GPD_READ_PORT_UCHAR: DataBufferSize = sizeof(UCHAR); break; case IOCTL_GPD_READ_PORT_USHORT: DataBufferSize = sizeof(USHORT); break; case IOCTL_GPD_READ_PORT_ULONG: DataBufferSize = sizeof(ULONG); break; } if ( InBufferSize != sizeof(ULONG) || OutBufferSize < DataBufferSize ) { return STATUS_INVALID_PARAMETER; } // Buffers are big enough. nPort = *pIOBuffer; // Get the I/O port number from the buffer. // Now we test port number passed here from IOCTL. // It must be in one of the ranges specified. Status = -1; for(i = 0; i < pLDI->NumberRanges; i++) { // If port is really in our range exit loop with success code. if ( (nPort >= (ULONG)pLDI->PortBase[i]) && (nPort < (ULONG)pLDI->PortBase[i] + pLDI->PortCount[i]) || (nPort + DataBufferSize <= (ULONG)pLDI->PortBase[i] + pLDI->PortCount[i]) || ((nPort - (ULONG)pLDI->PortBase[i]) & (DataBufferSize - 1)) == 0 ) { Status = 0; break; } } if(!NT_SUCCESS(Status)) { return STATUS_ACCESS_VIOLATION; // It was not legal. } // Remember the range number port is in. RangeNumber = i; if (pLDI->PortMemoryType[RangeNumber] == 1) { // Address is in I/O space switch (IoctlCode) { case IOCTL_GPD_READ_PORT_UCHAR: *(PUCHAR)pIOBuffer = READ_PORT_UCHAR( (PUCHAR)nPort ); break; case IOCTL_GPD_READ_PORT_USHORT: *(PUSHORT)pIOBuffer = READ_PORT_USHORT( (PUSHORT)nPort ); break; case IOCTL_GPD_READ_PORT_ULONG: *(PULONG)pIOBuffer = READ_PORT_ULONG( (PULONG)nPort ); break; } } else { // Address is in Memory space switch (IoctlCode) { case IOCTL_GPD_READ_PORT_UCHAR: *(PUCHAR)pIOBuffer = READ_REGISTER_UCHAR( (PUCHAR)nPort ); break; case IOCTL_GPD_READ_PORT_USHORT: *(PUSHORT)pIOBuffer = READ_REGISTER_USHORT( (PUSHORT)nPort ); break; case IOCTL_GPD_READ_PORT_ULONG: *(PULONG)pIOBuffer = READ_REGISTER_ULONG( (PULONG)nPort ); break; } } // Indicate # of bytes read // pIrp->IoStatus.Information = DataBufferSize; return STATUS_SUCCESS; } NTSTATUS GpdIoctlWritePort( IN PLOCAL_DEVICE_INFO pLDI, IN PIRP pIrp, IN PIO_STACK_LOCATION IrpStack, IN ULONG IoctlCode ) /*++ Routine Description: This routine processes the IOCTLs which write to the ports. Arguments: pLDI - our local device data pIrp - IO request packet IrpStack - The current stack location IoctlCode - The ioctl code from the IRP Return Value: STATUS_SUCCESS -- OK STATUS_INVALID_PARAMETER -- The buffer sent to the driver was too small to contain the port, or the buffer which would be sent back to the driver was not a multiple of the data size. STATUS_ACCESS_VIOLATION -- An illegal port number was given. --*/ { // NOTE: Use METHOD_BUFFERED ioctls. PULONG pIOBuffer; // Pointer to transfer buffer // (treated as array of longs). ULONG InBufferSize ; // Amount of data avail. from caller. ULONG OutBufferSize ; // Max data that caller can accept. ULONG nPort; // Port number to read or write. ULONG DataBufferSize; USHORT i, RangeNumber; NTSTATUS Status; // Size of buffer containing data from application InBufferSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength; // Size of buffer for data to be sent to application OutBufferSize = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; // NT copies inbuf here before entry and copies this to outbuf after return, // for METHOD_BUFFERED IOCTL's. pIOBuffer = (PULONG) pIrp->AssociatedIrp.SystemBuffer; // We don't return any data on a write port. pIrp->IoStatus.Information = 0; // Check to ensure input buffer is big enough to hold a port number as well // as the data to write. // // The relative port # is a ULONG, and the data is the type appropriate to // the IOCTL. // switch (IoctlCode) { default: // There isn't really any default but /* FALL THRU */ // this will quiet the compiler. case IOCTL_GPD_WRITE_PORT_UCHAR: DataBufferSize = sizeof(UCHAR); break; case IOCTL_GPD_WRITE_PORT_USHORT: DataBufferSize = sizeof(USHORT); break; case IOCTL_GPD_WRITE_PORT_ULONG: DataBufferSize = sizeof(ULONG); break; } if ( InBufferSize < (sizeof(ULONG) + DataBufferSize) ) { return STATUS_INVALID_PARAMETER; } nPort = *pIOBuffer++; // Now we test port number passed here from IOCTL. // It must be in one of the ranges specified. Status = -1; for(i = 0; i < pLDI->NumberRanges; i++) { // If port is really in our range exit loop with success code. if ( (nPort >= (ULONG)pLDI->PortBase[i]) && (nPort < (ULONG)pLDI->PortBase[i] + pLDI->PortCount[i]) || (nPort + DataBufferSize <= (ULONG)pLDI->PortBase[i] + pLDI->PortCount[i]) || ((nPort - (ULONG)pLDI->PortBase[i]) & (DataBufferSize - 1)) == 0 ) { Status = 0; break; } } if(!NT_SUCCESS(Status)) { return STATUS_ACCESS_VIOLATION; // It was not legal. } // Remember the range number port is in. RangeNumber = i; if (pLDI->PortMemoryType[RangeNumber] == 1) { // Address is in I/O space switch (IoctlCode) { case IOCTL_GPD_WRITE_PORT_UCHAR: WRITE_PORT_UCHAR( (PUCHAR)nPort, *(PUCHAR)pIOBuffer ); break; case IOCTL_GPD_WRITE_PORT_USHORT: WRITE_PORT_USHORT( (PUSHORT)nPort, *(PUSHORT)pIOBuffer ); break; case IOCTL_GPD_WRITE_PORT_ULONG: WRITE_PORT_ULONG( (PULONG)nPort, *(PULONG)pIOBuffer ); break; } } else { // Address is in Memory space switch (IoctlCode) { case IOCTL_GPD_WRITE_PORT_UCHAR: WRITE_REGISTER_UCHAR( (PUCHAR)nPort, *(PUCHAR)pIOBuffer ); break; case IOCTL_GPD_WRITE_PORT_USHORT: WRITE_REGISTER_USHORT( (PUSHORT)nPort, *(PUSHORT)pIOBuffer ); break; case IOCTL_GPD_WRITE_PORT_ULONG: WRITE_REGISTER_ULONG( (PULONG)nPort, *(PULONG)pIOBuffer ); break; } } return STATUS_SUCCESS; } VOID GpdUnload( PDRIVER_OBJECT DriverObject ) /*++ Routine Description: This routine prepares our driver to be unloaded. It is responsible for freeing all resources allocated by DriverEntry as well as any allocated while the driver was running. The symbolic link must be deleted as well. Arguments: DriverObject - Pointer to driver object created by the system. Return Value: None --*/ { PLOCAL_DEVICE_INFO pLDI; CM_RESOURCE_LIST NullResourceList; BOOLEAN ResourceConflict; UNICODE_STRING Win32DeviceName; USHORT i; // Find our global data pLDI = (PLOCAL_DEVICE_INFO)DriverObject->DeviceObject->DeviceExtension; // Unmap the ports for(i = 0; i < pLDI->NumberRanges; i++) { if (pLDI->PortWasMapped[i]) { MmUnmapIoSpace(pLDI->PortBase[i], pLDI->PortCount[i]); } } // Report we're not using any hardware. If we don't do this // then we'll conflict with ourselves (!) on the next load RtlZeroMemory((PVOID)&NullResourceList, sizeof(NullResourceList)); IoReportResourceUsage( NULL, DriverObject, &NullResourceList, sizeof(ULONG), NULL, NULL, 0, FALSE, &ResourceConflict ); // Assume all handles are closed down. // Delete the things we allocated - devices, symbolic links RtlInitUnicodeString(&Win32DeviceName, DOS_DEVICE_NAME); IoDeleteSymbolicLink(&Win32DeviceName); IoDeleteDevice(pLDI->DeviceObject); }