1113 lines
35 KiB
C
1113 lines
35 KiB
C
/*++
|
|
|
|
Copyright (c) 1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
comport.c
|
|
|
|
Abstract: ESC/POS (serial) interface for USB Point-of-Sale devices
|
|
|
|
Author:
|
|
|
|
ervinp
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
#include <WDM.H>
|
|
|
|
#include <usbdi.h>
|
|
#include <usbdlib.h>
|
|
#include <usbioctl.h>
|
|
|
|
#include "escpos.h"
|
|
#include "debug.h"
|
|
|
|
|
|
NTSTATUS CreateSymbolicLink(POSPDOEXT *pdoExt)
|
|
{
|
|
NTSTATUS status;
|
|
PWCHAR comPortName;
|
|
static WCHAR comNamePrefix[] = L"\\DosDevices\\COM";
|
|
WCHAR buf[sizeof(comNamePrefix)/sizeof(WCHAR) + 4];
|
|
LONG numLen = MyLog(10, pdoExt->comPortNumber)+1;
|
|
|
|
ASSERT((numLen > 0) && (numLen <= 4));
|
|
|
|
RtlCopyMemory(buf, comNamePrefix, sizeof(comNamePrefix));
|
|
|
|
NumToDecString( buf+sizeof(comNamePrefix)/sizeof(WCHAR)-1,
|
|
(USHORT)pdoExt->comPortNumber,
|
|
(USHORT)numLen);
|
|
buf[sizeof(comNamePrefix)/sizeof(WCHAR) - 1 + numLen] = UNICODE_NULL;
|
|
|
|
comPortName = MemDup(buf, sizeof(buf));
|
|
if (comPortName){
|
|
RtlInitUnicodeString(&pdoExt->symbolicLinkName, comPortName);
|
|
|
|
status = IoCreateSymbolicLink(&pdoExt->symbolicLinkName, &pdoExt->pdoName);
|
|
if (NT_SUCCESS(status)){
|
|
UNICODE_STRING comPortSuffix;
|
|
|
|
/*
|
|
* Create the '\Device\POS_x = COMx' entry under HKLM\DEVICEMAP\SERIALCOMM
|
|
*/
|
|
RtlInitUnicodeString(&comPortSuffix, pdoExt->symbolicLinkName.Buffer+(sizeof(L"\\DosDevices\\")-sizeof(WCHAR))/sizeof(WCHAR));
|
|
status = RtlWriteRegistryValue( RTL_REGISTRY_DEVICEMAP,
|
|
L"SERIALCOMM",
|
|
pdoExt->pdoName.Buffer,
|
|
REG_SZ,
|
|
comPortSuffix.Buffer,
|
|
comPortSuffix.Length + sizeof(WCHAR));
|
|
if (NT_SUCCESS(status)){
|
|
if (isWin9x){
|
|
NTSTATUS tmpStatus;
|
|
|
|
/*
|
|
* Delete the temporary 'COMx=COMx' holder value we created earlier.
|
|
*/
|
|
tmpStatus = RtlDeleteRegistryValue( RTL_REGISTRY_DEVICEMAP,
|
|
L"SERIALCOMM",
|
|
comPortSuffix.Buffer);
|
|
}
|
|
}
|
|
else {
|
|
DBGERR(("CreateSymbolicLink: RtlWriteRegistryValue failed with status %xh.", status));
|
|
}
|
|
|
|
}
|
|
else {
|
|
DBGERR(("IoCreateSymbolicLink failed with status %xh.", status));
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(comPortName);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS DestroySymbolicLink(POSPDOEXT *pdoExt)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
/*
|
|
* Delete the symbolic link.
|
|
* pdoExt->pdoName.Buffer, which was allocated at pdo creation time,
|
|
* will get deleted when we delete the pdo.
|
|
*/
|
|
ASSERT(pdoExt->symbolicLinkName.Buffer);
|
|
IoDeleteSymbolicLink(&pdoExt->symbolicLinkName);
|
|
|
|
status = RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP,
|
|
L"SERIALCOMM",
|
|
pdoExt->pdoName.Buffer);
|
|
|
|
FREEPOOL(pdoExt->symbolicLinkName.Buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS OpenComPort(POSPDOEXT *pdoExt, PIRP irp)
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
|
|
|
|
// BUGBUG FINISH - security, permissions, etc.
|
|
|
|
DBGVERBOSE(("OpenComPort: shareAccess=%xh, desiredAccess=%xh, options=%xh.",
|
|
(ULONG)irpSp->Parameters.Create.ShareAccess,
|
|
(ULONG)irpSp->Parameters.Create.SecurityContext->DesiredAccess,
|
|
(ULONG)irpSp->Parameters.Create.Options));
|
|
|
|
if (irpSp->Parameters.Create.Options & FILE_DIRECTORY_FILE){
|
|
/*
|
|
* Attempt to open this device as a directory
|
|
*/
|
|
DBGWARN(("OpenComPort: STATUS_NOT_A_DIRECTORY"));
|
|
status = STATUS_NOT_A_DIRECTORY;
|
|
}
|
|
else {
|
|
FILEEXT *fileExt = ALLOCPOOL(NonPagedPool, sizeof(FILEEXT));
|
|
if (fileExt){
|
|
KIRQL oldIrql;
|
|
|
|
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
|
|
|
|
if (IsListEmpty(&pdoExt->fileExtensionList)){
|
|
|
|
ASSERT(irpSp->FileObject->FsContext == NULL);
|
|
irpSp->FileObject->FsContext = fileExt; // BUGBUG ?
|
|
|
|
fileExt->signature = ESCPOS_TAG;
|
|
fileExt->fileObject = irpSp->FileObject;
|
|
InsertTailList(&pdoExt->fileExtensionList, &fileExt->listEntry);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
/*
|
|
* Only allow one open for now
|
|
*/
|
|
DBGWARN(("OpenComPort: failing repeat open"));
|
|
FREEPOOL(fileExt);
|
|
status = STATUS_SHARING_VIOLATION;
|
|
}
|
|
|
|
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
irp->IoStatus.Information = 0;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS CloseComPort(POSPDOEXT *pdoExt, PIRP irp)
|
|
{
|
|
NTSTATUS status;
|
|
KIRQL oldIrql;
|
|
FILEEXT *callerFileExt, *myFileExt;
|
|
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
|
|
|
|
ASSERT(irpSp->FileObject);
|
|
ASSERT(irpSp->FileObject->FsContext);
|
|
callerFileExt = (FILEEXT *)irpSp->FileObject->FsContext;
|
|
ASSERT(callerFileExt->signature == ESCPOS_TAG);
|
|
|
|
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
|
|
|
|
if (IsListEmpty(&pdoExt->fileExtensionList)){
|
|
status = STATUS_DEVICE_DATA_ERROR;
|
|
}
|
|
else {
|
|
PLIST_ENTRY listEntry;
|
|
|
|
// BUGBUG - assume only one open for now
|
|
listEntry = RemoveHeadList(&pdoExt->fileExtensionList);
|
|
myFileExt = CONTAINING_RECORD(listEntry, FILEEXT, listEntry);
|
|
ASSERT(myFileExt->signature == ESCPOS_TAG);
|
|
if (myFileExt == callerFileExt){
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
/*
|
|
* We only allow one open, so this should have been the one
|
|
*/
|
|
ASSERT(myFileExt == callerFileExt);
|
|
InsertHeadList(&pdoExt->fileExtensionList, &myFileExt->listEntry);
|
|
status = STATUS_DEVICE_DATA_ERROR;
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
|
|
|
|
/*
|
|
* Wait until we've dropped the spinlock to call outside the driver to free memory.
|
|
*/
|
|
if (NT_SUCCESS(status)){
|
|
irpSp->FileObject->FsContext = NULL;
|
|
FREEPOOL(myFileExt);
|
|
}
|
|
else {
|
|
DBGERR(("CloseComPort: failing with status %xh.", status));
|
|
}
|
|
|
|
irp->IoStatus.Information = 0;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
PWCHAR CreateChildPdoName(PARENTFDOEXT *parentFdoExt, ULONG portNumber)
|
|
{
|
|
static WCHAR pdoNamePrefix[] = L"\\Device\\POS_";
|
|
WCHAR buf[sizeof(pdoNamePrefix)/sizeof(WCHAR)+4] = { 0 };
|
|
PWCHAR result;
|
|
ULONG numLen = MyLog(10, portNumber)+1;
|
|
|
|
ASSERT((numLen > 0) && (numLen <= 4));
|
|
|
|
RtlCopyMemory(buf, pdoNamePrefix, sizeof(pdoNamePrefix));
|
|
NumToDecString(buf+sizeof(pdoNamePrefix)/sizeof(WCHAR)-1, (USHORT)portNumber, (USHORT)numLen);
|
|
|
|
result = MemDup(buf, sizeof(buf));
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
NTSTATUS CreateCOMPdo( PARENTFDOEXT *parentFdoExt,
|
|
ULONG comInterfaceIndex,
|
|
ENDPOINTINFO *inputEndpointInfo,
|
|
ENDPOINTINFO *outputEndpointInfo,
|
|
ENDPOINTINFO *statusEndpointInfo)
|
|
{
|
|
NTSTATUS status;
|
|
LONG comPortNumber;
|
|
|
|
comPortNumber = GetComPort(parentFdoExt, comInterfaceIndex);
|
|
if (comPortNumber == -1){
|
|
status = STATUS_DEVICE_DATA_ERROR;
|
|
}
|
|
else {
|
|
PWCHAR deviceName = CreateChildPdoName(parentFdoExt, comPortNumber);
|
|
if (deviceName){
|
|
PDEVICE_OBJECT pdo;
|
|
UNICODE_STRING uPdoName;
|
|
|
|
RtlInitUnicodeString(&uPdoName, deviceName);
|
|
|
|
status = IoCreateDevice(parentFdoExt->driverObj,
|
|
sizeof(DEVEXT),
|
|
&uPdoName, // name for this device
|
|
FILE_DEVICE_SERIAL_PORT,
|
|
0, // device characteristics
|
|
FALSE, // exclusive - BUGBUG ?
|
|
&pdo); // our device object
|
|
|
|
if (NT_SUCCESS(status)){
|
|
DEVEXT *devExt = (DEVEXT *)pdo->DeviceExtension;
|
|
POSPDOEXT *pdoExt;
|
|
KIRQL oldIrql;
|
|
|
|
RtlZeroMemory(devExt, sizeof(DEVEXT));
|
|
|
|
devExt->signature = DEVICE_EXTENSION_SIGNATURE;
|
|
devExt->isPdo = TRUE;
|
|
|
|
pdoExt = &devExt->pdoExt;
|
|
|
|
pdoExt->state = STATE_INITIALIZED;
|
|
pdoExt->pdo = pdo;
|
|
pdoExt->parentFdoExt = parentFdoExt;
|
|
pdoExt->comPortNumber = comPortNumber;
|
|
|
|
/*
|
|
* Initializing variables and endpointinfo for Serial Emulation
|
|
*/
|
|
if (parentFdoExt->posFlag & SERIAL_EMULATION) {
|
|
InitializeSerEmulVariables(pdoExt);
|
|
pdoExt->statusEndpointInfo = *statusEndpointInfo;
|
|
DBGVERBOSE(("CreateCOMPdo: EndpointInfo & Variables for Serial Emulation initialized"));
|
|
}
|
|
|
|
RtlInitUnicodeString(&pdoExt->pdoName, deviceName);
|
|
|
|
InitializeListHead(&pdoExt->fileExtensionList);
|
|
InitializeListHead(&pdoExt->pendingReadIrpsList);
|
|
InitializeListHead(&pdoExt->pendingWriteIrpsList);
|
|
InitializeListHead(&pdoExt->completedReadPacketsList);
|
|
|
|
KeInitializeSpinLock(&pdoExt->devExtSpinLock);
|
|
|
|
ExInitializeWorkItem(&pdoExt->writeWorkItem, WorkItemCallback_Write, pdoExt);
|
|
ExInitializeWorkItem(&pdoExt->readWorkItem, WorkItemCallback_Read, pdoExt);
|
|
|
|
pdoExt->totalQueuedReadDataLength = 0;
|
|
|
|
/*
|
|
* Copy contents of endpointInfo structures. To incorporate
|
|
* the ODD_ENDPOINT feature we have to make the checks below.
|
|
*/
|
|
if (inputEndpointInfo){
|
|
pdoExt->inputEndpointInfo = *inputEndpointInfo;
|
|
}
|
|
|
|
if (outputEndpointInfo){
|
|
pdoExt->outputEndpointInfo = *outputEndpointInfo;
|
|
}
|
|
|
|
pdo->StackSize = parentFdoExt->functionDevObj->StackSize+1;
|
|
|
|
/*
|
|
* Insert child PDO in parent's device relations array.
|
|
*/
|
|
KeAcquireSpinLock(&parentFdoExt->devExtSpinLock, &oldIrql);
|
|
parentFdoExt->deviceRelations->Objects[parentFdoExt->deviceRelations->Count++] = pdo;
|
|
KeReleaseSpinLock(&parentFdoExt->devExtSpinLock, oldIrql);
|
|
|
|
ASSERT(!(pdo->Flags & DO_POWER_PAGABLE));
|
|
pdo->Flags |= DO_POWER_PAGABLE;
|
|
|
|
DBGVERBOSE(("CreateCOMPdo: created pdo %ph, pdoExt = %ph.", pdo, pdoExt));
|
|
}
|
|
else {
|
|
DBGERR(("CreateCOMPdo: IoCreateDevice failed with status %xh.", status));
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)){
|
|
ReleaseCOMPort(comPortNumber);
|
|
}
|
|
}
|
|
|
|
ASSERT(NT_SUCCESS(status));
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS CleanupIO(POSPDOEXT *pdoExt, PIRP irp)
|
|
{
|
|
// BUGBUG FINISH
|
|
irp->IoStatus.Information = 0;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS QueryInfo(POSPDOEXT *pdoExt, PIRP irp)
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpSp;
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(irp);
|
|
|
|
switch (irpSp->Parameters.QueryFile.FileInformationClass){
|
|
|
|
case FileBasicInformation:
|
|
DBGVERBOSE((" QueryInfo: FileBasicInformation"));
|
|
{
|
|
PFILE_BASIC_INFORMATION basicInfoBuf = irp->AssociatedIrp.SystemBuffer;
|
|
ASSERT(basicInfoBuf);
|
|
RtlCopyMemory(basicInfoBuf, &pdoExt->fileBasicInfo, sizeof(FILE_BASIC_INFORMATION));
|
|
irp->IoStatus.Information = sizeof(FILE_BASIC_INFORMATION);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
break;
|
|
|
|
case FileStandardInformation:
|
|
DBGVERBOSE((" QueryInfo: FileStandardInformation"));
|
|
{
|
|
PFILE_STANDARD_INFORMATION stdInfoBuf = irp->AssociatedIrp.SystemBuffer;
|
|
ASSERT(stdInfoBuf);
|
|
stdInfoBuf->AllocationSize.QuadPart = 0;
|
|
stdInfoBuf->EndOfFile = stdInfoBuf->AllocationSize;
|
|
stdInfoBuf->NumberOfLinks = 0;
|
|
stdInfoBuf->DeletePending = FALSE;
|
|
stdInfoBuf->Directory = FALSE;
|
|
irp->IoStatus.Information = sizeof(FILE_STANDARD_INFORMATION);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
DBGVERBOSE((" QueryInfo: FilePositionInformation"));
|
|
/*
|
|
* Always return position 0
|
|
*/
|
|
((PFILE_POSITION_INFORMATION)irp->AssociatedIrp.SystemBuffer)->CurrentByteOffset.QuadPart = 0;
|
|
irp->IoStatus.Information = sizeof(FILE_POSITION_INFORMATION);
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case FileEndOfFileInformation:
|
|
DBGWARN((" QueryInfo: FileEndOfFileInformation"));
|
|
irp->IoStatus.Information = 0;
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
default:
|
|
DBGWARN((" QueryInfo: ??? (%xh)", (ULONG)irpSp->Parameters.QueryFile.FileInformationClass));
|
|
irp->IoStatus.Information = 0;
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS SetInfo(POSPDOEXT *pdoExt, PIRP irp)
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpSp;
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(irp);
|
|
|
|
switch (irpSp->Parameters.SetFile.FileInformationClass){
|
|
|
|
case FileBasicInformation:
|
|
DBGVERBOSE((" SetInfo: FileBasicInformation"));
|
|
{
|
|
PFILE_BASIC_INFORMATION basicInfoBuf = irp->AssociatedIrp.SystemBuffer;
|
|
ASSERT(basicInfoBuf);
|
|
RtlCopyMemory(&pdoExt->fileBasicInfo, basicInfoBuf, sizeof(FILE_BASIC_INFORMATION));
|
|
irp->IoStatus.Information = sizeof(FILE_BASIC_INFORMATION);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
break;
|
|
|
|
case FileEndOfFileInformation:
|
|
DBGVERBOSE((" SetInfo: FileEndOfFileInformation"));
|
|
irp->IoStatus.Information = 0;
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
#define FileAllocationInformation 19 // BUGBUG - defined in ntioapi.h
|
|
case FileAllocationInformation:
|
|
DBGVERBOSE((" SetInfo: FileAllocationInformation"));
|
|
irp->IoStatus.Information = 0;
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
DBGWARN((" SetInfo: ??? (%xh)", (ULONG)irpSp->Parameters.SetFile.FileInformationClass));
|
|
irp->IoStatus.Information = 0;
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS FlushBuffers(POSPDOEXT *pdoExt)
|
|
{
|
|
LIST_ENTRY irpsToCompleteList;
|
|
PLIST_ENTRY listEntry;
|
|
PIRP irp;
|
|
KIRQL oldIrql;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
DBGVERBOSE(("FlushBuffers"));
|
|
|
|
/*
|
|
* This is so we don't loop forever if they get re-queued on the same thread.
|
|
*/
|
|
InitializeListHead(&irpsToCompleteList);
|
|
|
|
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
|
|
|
|
/*
|
|
* Flush all pending Read and Wait Irps (if serial emulation feature is ON)
|
|
*/
|
|
while (irp = DequeueReadIrp(pdoExt, TRUE)){
|
|
InsertTailList(&irpsToCompleteList, &irp->Tail.Overlay.ListEntry);
|
|
}
|
|
|
|
if (pdoExt->parentFdoExt->posFlag & SERIAL_EMULATION) {
|
|
while (irp = DequeueWaitIrp(pdoExt)){
|
|
InsertTailList(&irpsToCompleteList, &irp->Tail.Overlay.ListEntry);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Empty out the queued readPackets.
|
|
* It's ok to free locked memory with the spinlock held.
|
|
*/
|
|
while (!IsListEmpty(&pdoExt->completedReadPacketsList)){
|
|
READPACKET *readPacket;
|
|
|
|
listEntry = RemoveHeadList(&pdoExt->completedReadPacketsList);
|
|
ASSERT(listEntry);
|
|
readPacket = CONTAINING_RECORD(listEntry, READPACKET, listEntry);
|
|
FreeReadPacket(readPacket);
|
|
}
|
|
|
|
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
|
|
|
|
while (!IsListEmpty(&irpsToCompleteList)) {
|
|
listEntry = RemoveHeadList(&irpsToCompleteList);
|
|
ASSERT(listEntry);
|
|
irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
|
|
irp->IoStatus.Information = 0;
|
|
irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
|
|
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS InternalIoctl(POSPDOEXT *pdoExt, PIRP irp)
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpSp;
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(irp);
|
|
|
|
switch (irpSp->Parameters.DeviceIoControl.IoControlCode){
|
|
|
|
// BUGBUG FINISH
|
|
|
|
default:
|
|
DBGVERBOSE(("InternalIoctl: ??? (%xh)", (ULONG)irpSp->Parameters.DeviceIoControl.IoControlCode));
|
|
status = irp->IoStatus.Status;
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
LONG GetComPort(PARENTFDOEXT *parentFdoExt, ULONG comInterfaceIndex)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the serial COM port index for a serial interface we're about to create.
|
|
If this is not the first plug-in, it should be sitting in the registry.
|
|
If this is the first plug-in, call GetFreeComPortNumber to reserve a new
|
|
static COM port for this device and store it in our software key.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
Return COM port number or -1 if unsuccessful.
|
|
|
|
--*/
|
|
{
|
|
LONG comNumber = -1;
|
|
NTSTATUS status;
|
|
HANDLE hRegDevice;
|
|
|
|
status = IoOpenDeviceRegistryKey( parentFdoExt->physicalDevObj,
|
|
PLUGPLAY_REGKEY_DEVICE,
|
|
KEY_READ,
|
|
&hRegDevice);
|
|
if (NT_SUCCESS(status)){
|
|
UNICODE_STRING keyName;
|
|
PKEY_VALUE_FULL_INFORMATION keyValueInfo;
|
|
ULONG keyValueTotalSize, actualLength;
|
|
WCHAR interfaceKeyName[] = L"COMPortForInterfaceXXXX";
|
|
|
|
NumToHexString( interfaceKeyName+sizeof(interfaceKeyName)/sizeof(WCHAR)-1-4,
|
|
(USHORT)comInterfaceIndex,
|
|
4);
|
|
|
|
RtlInitUnicodeString(&keyName, interfaceKeyName);
|
|
keyValueTotalSize = sizeof(KEY_VALUE_FULL_INFORMATION) +
|
|
keyName.Length*sizeof(WCHAR) +
|
|
sizeof(ULONG);
|
|
keyValueInfo = ALLOCPOOL(PagedPool, keyValueTotalSize);
|
|
if (keyValueInfo){
|
|
status = ZwQueryValueKey( hRegDevice,
|
|
&keyName,
|
|
KeyValueFullInformation,
|
|
keyValueInfo,
|
|
keyValueTotalSize,
|
|
&actualLength);
|
|
if (NT_SUCCESS(status)){
|
|
|
|
ASSERT(keyValueInfo->Type == REG_DWORD);
|
|
ASSERT(keyValueInfo->DataLength == sizeof(ULONG));
|
|
|
|
comNumber = (LONG)*((PULONG)(((PCHAR)keyValueInfo)+keyValueInfo->DataOffset));
|
|
DBGVERBOSE(("GetComPort: read comport #%xh for interface %xh from registry.", (ULONG)comNumber, comInterfaceIndex));
|
|
}
|
|
else {
|
|
|
|
/*
|
|
* No COM port number recorded in registry.
|
|
* Allocate a new static COM port from the COM name arbiter
|
|
* and record it in our software key for the next PnP.
|
|
*/
|
|
comNumber = GetFreeComPortNumber();
|
|
if (comNumber == -1){
|
|
DBGERR(("GetComPort: GetFreeComPortNumber failed"));
|
|
}
|
|
else {
|
|
status = ZwSetValueKey( hRegDevice,
|
|
&keyName,
|
|
0,
|
|
REG_DWORD,
|
|
&comNumber,
|
|
sizeof(ULONG));
|
|
if (!NT_SUCCESS(status)){
|
|
DBGERR(("GetComPort: ZwSetValueKey failed with status %xh.", status));
|
|
}
|
|
}
|
|
}
|
|
|
|
FREEPOOL(keyValueInfo);
|
|
}
|
|
else {
|
|
ASSERT(keyValueInfo);
|
|
}
|
|
|
|
ZwClose(hRegDevice);
|
|
}
|
|
else {
|
|
DBGERR(("GetComPort: IoOpenDeviceRegistryKey failed with %xh.", status));
|
|
}
|
|
|
|
|
|
return comNumber;
|
|
}
|
|
|
|
|
|
|
|
|
|
LONG GetFreeComPortNumber()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Find the index of the next unused serial COM port name in the system
|
|
(e.g. COM3, COM4, etc).
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
Return COM port number or -1 if unsuccessful.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG comNumber = -1;
|
|
|
|
|
|
if (isWin9x){
|
|
/*
|
|
* Windows 98
|
|
* Find the first unused name under Hardware\DeviceMap\SerialComm.
|
|
*
|
|
*
|
|
* BUGBUG:
|
|
* This algorithm does not find all the COM ports reserved
|
|
* by modems. May want to port tomgreen's AllocateCommPort
|
|
* function from \faulty\Wdm10\usb\driver\ccport\utils.c
|
|
*/
|
|
HANDLE hKey;
|
|
UNICODE_STRING keyName;
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
|
|
RtlInitUnicodeString(&keyName, L"\\Registry\\Machine\\Hardware\\DeviceMap\\SerialComm");
|
|
InitializeObjectAttributes( &objectAttributes,
|
|
&keyName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
(PSECURITY_DESCRIPTOR)NULL);
|
|
|
|
status = ZwOpenKey(&hKey, KEY_QUERY_VALUE | KEY_SET_VALUE, &objectAttributes);
|
|
if (NT_SUCCESS(status)){
|
|
#define MAX_COMPORT_NAME_LEN (sizeof("COMxxxx")-1)
|
|
UCHAR keyValueBytes[sizeof(KEY_VALUE_FULL_INFORMATION)+(MAX_COMPORT_NAME_LEN+1)*sizeof(WCHAR)+sizeof(ULONG)];
|
|
PKEY_VALUE_FULL_INFORMATION keyValueInfo = (PKEY_VALUE_FULL_INFORMATION)keyValueBytes;
|
|
ULONG i, actualLen;
|
|
ULONG keyIndex = 0;
|
|
|
|
/*
|
|
* This bitmask represents the used COM ports.
|
|
* Bit i set indicates com port i+1 is reserved.
|
|
* Initialize with COM1 and COM2 reserved.
|
|
*
|
|
* BUGBUG - only works for up to 32 ports.
|
|
*/
|
|
ULONG comNameMask = 3;
|
|
|
|
do {
|
|
status = ZwEnumerateValueKey(
|
|
hKey,
|
|
keyIndex++,
|
|
KeyValueFullInformation,
|
|
keyValueInfo,
|
|
sizeof(keyValueBytes),
|
|
&actualLen);
|
|
if (NT_SUCCESS(status)){
|
|
if (keyValueInfo->Type == REG_SZ){
|
|
PWCHAR valuePtr = (PWCHAR)(((PCHAR)keyValueInfo)+keyValueInfo->DataOffset);
|
|
if (!WStrNCmpI(valuePtr, L"COM", 3)){
|
|
/*
|
|
* valuePtr+3 points the index portion of the COMx string,
|
|
* but we can't call LAtoD on it because it is
|
|
* NOT NULL-TERMINATED.
|
|
* So copy the index into our own buffer,
|
|
* null-terminate that,
|
|
* and call LAtoD to get the numerical index.
|
|
*/
|
|
WCHAR comPortIndexString[4+1];
|
|
ULONG thisComNumber;
|
|
for (i = 0; (i < 4) && (i < keyValueInfo->DataLength/sizeof(WCHAR)); i++){
|
|
comPortIndexString[i] = valuePtr[3+i];
|
|
}
|
|
comPortIndexString[i] = UNICODE_NULL;
|
|
|
|
thisComNumber = LAtoD(comPortIndexString);
|
|
if (thisComNumber == 0){
|
|
ASSERT(thisComNumber != 0);
|
|
}
|
|
else if (thisComNumber <= sizeof(ULONG)*8){
|
|
comNameMask |= 1 << (thisComNumber-1);
|
|
}
|
|
else {
|
|
ASSERT(thisComNumber <= sizeof(ULONG)*8);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (NT_SUCCESS(status));
|
|
|
|
/*
|
|
* First clear bit in comNameMask represents the first available COM name.
|
|
*/
|
|
for (i = 0; i < sizeof(ULONG)*8; i++){
|
|
if (!(comNameMask & (1 << i))){
|
|
WCHAR comName[] = L"COMxxxx";
|
|
ULONG comNumLen;
|
|
|
|
/*
|
|
* Save the COM port number that we're returning.
|
|
*/
|
|
comNumber = i+1;
|
|
DBGVERBOSE(("GetFreeComPortNumber: got free COM port #%xh.", comNumber));
|
|
|
|
/*
|
|
* Write a temporary COMx=COMx holder value to the SERIALCOMM key
|
|
* so that no other PDOs get this COM port number.
|
|
* This value will get overwritten by <symbolicLinkName=COMx> when the pdo is started.
|
|
*/
|
|
comNumLen = MyLog(10, comNumber)+1;
|
|
ASSERT(comNumLen <= 4);
|
|
NumToDecString(comName+3, (USHORT)comNumber, (USHORT)comNumLen);
|
|
comName[3+comNumLen] = UNICODE_NULL;
|
|
status = RtlWriteRegistryValue( RTL_REGISTRY_DEVICEMAP,
|
|
L"SERIALCOMM",
|
|
comName,
|
|
REG_SZ,
|
|
comName,
|
|
(3 + comNumLen + 1) * sizeof(WCHAR));
|
|
ASSERT(NT_SUCCESS(status));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else {
|
|
DBGERR(("GetFreeComPortNumber: ZwOpenKey failed with status %xh", status));
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
/*
|
|
* Windows NT.
|
|
* Use the COM Name Arbiter bitmap.
|
|
*/
|
|
|
|
HANDLE hKey;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
UNICODE_STRING keyName;
|
|
NTSTATUS status;
|
|
|
|
|
|
RtlInitUnicodeString(&keyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\COM Name Arbiter");
|
|
InitializeObjectAttributes( &objectAttributes,
|
|
&keyName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
(PSECURITY_DESCRIPTOR)NULL);
|
|
|
|
status = ZwOpenKey( &hKey,
|
|
KEY_QUERY_VALUE | KEY_SET_VALUE,
|
|
&objectAttributes);
|
|
if (NT_SUCCESS(status)){
|
|
UNICODE_STRING valueName;
|
|
PVOID rawData;
|
|
ULONG dataSize;
|
|
|
|
RtlInitUnicodeString(&valueName, L"ComDB");
|
|
|
|
ASSERT(hKey);
|
|
|
|
dataSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION);
|
|
|
|
/*
|
|
* Allocate one extra byte in case we have to add a byte to ComDB
|
|
*/
|
|
rawData = ALLOCPOOL(NonPagedPool, dataSize+1);
|
|
|
|
if (rawData){
|
|
status = ZwQueryValueKey( hKey,
|
|
&valueName,
|
|
KeyValuePartialInformation,
|
|
rawData,
|
|
dataSize,
|
|
&dataSize);
|
|
if (status == STATUS_BUFFER_OVERFLOW){
|
|
FREEPOOL(rawData);
|
|
ASSERT(dataSize > FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
|
|
/*
|
|
* Allocate one extra byte in case we have to add a byte to ComDB
|
|
*/
|
|
rawData = ALLOCPOOL(NonPagedPool, dataSize+1);
|
|
if (rawData){
|
|
status = ZwQueryValueKey( hKey,
|
|
&valueName,
|
|
KeyValuePartialInformation,
|
|
rawData,
|
|
dataSize,
|
|
&dataSize);
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status)){
|
|
PKEY_VALUE_PARTIAL_INFORMATION keyPartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION)rawData;
|
|
ULONG b, i;
|
|
BOOLEAN done = FALSE;
|
|
|
|
ASSERT(dataSize >= FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
|
|
ASSERT(keyPartialInfo->Type == REG_BINARY);
|
|
|
|
/*
|
|
* The ComDB value is just a bit mask where bit n set indicates
|
|
* that COM port # n+1 is taken.
|
|
* Get the index of the first unset bit; starting with bit 2 (COM3).
|
|
*/
|
|
for (b = 0; (b < dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data)) && !done; b++){
|
|
|
|
for (i = (b == 0) ? 2 : 0; (i < 8) && !done; i++){
|
|
if (keyPartialInfo->Data[b] & (1 << i)){
|
|
/*
|
|
* This COM port (#8*b+i+1) is taken, go to the next one.
|
|
*/
|
|
}
|
|
else {
|
|
/*
|
|
* Found a free COM port.
|
|
* Write the value back with the new bit set.
|
|
* Only write back the number of bytes we read earlier.
|
|
* Only use this COM port if the write succeeds.
|
|
*
|
|
* Note: careful with the size of the KEY_VALUE_PARTIAL_INFORMATION
|
|
* struct. Its real size is 0x0D bytes,
|
|
* but the compiler aligns it to 0x10 bytes.
|
|
* So use FIELD_OFFSET, not sizeof, to determine
|
|
* how many bytes to write.
|
|
*/
|
|
keyPartialInfo->Data[b] |= (1 << i);
|
|
status = ZwSetValueKey( hKey,
|
|
&valueName,
|
|
0,
|
|
REG_BINARY,
|
|
(PVOID)keyPartialInfo->Data,
|
|
dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
if (NT_SUCCESS(status)){
|
|
comNumber = 8*b + i + 1;
|
|
DBGVERBOSE(("GetFreeComPortNumber: got free COM port #%xh.", comNumber));
|
|
}
|
|
else {
|
|
DBGERR(("GetFreeComPortNumber: ZwSetValueKey failed with %xh.", status));
|
|
}
|
|
|
|
done = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((b == dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data)) && !done){
|
|
/*
|
|
* No more available bits in ComDB, so add a byte.
|
|
*/
|
|
ASSERT(comNumber == -1);
|
|
ASSERT(b > 0);
|
|
DBGWARN(("ComDB overflow -- adding new byte"));
|
|
|
|
keyPartialInfo->Data[b] = 1;
|
|
dataSize++;
|
|
|
|
status = ZwSetValueKey( hKey,
|
|
&valueName,
|
|
0,
|
|
REG_BINARY,
|
|
(PVOID)keyPartialInfo->Data,
|
|
dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
if (NT_SUCCESS(status)){
|
|
comNumber = 8*b + 1;
|
|
DBGVERBOSE(("GetFreeComPortNumber: got free COM port #%xh.", comNumber));
|
|
}
|
|
else {
|
|
DBGERR(("GetFreeComPortNumber: ZwSetValueKey #2 failed with %xh.", status));
|
|
}
|
|
}
|
|
|
|
ASSERT(comNumber != -1);
|
|
}
|
|
else {
|
|
DBGERR(("GetFreeComPortNumber: ZwQueryValueKey failed with %xh.", status));
|
|
}
|
|
|
|
/*
|
|
* Check that we didn't fail the second allocation before freeing this buffer.
|
|
*/
|
|
if (rawData){
|
|
FREEPOOL(rawData);
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
status = ZwClose(hKey);
|
|
ASSERT(NT_SUCCESS(status));
|
|
}
|
|
else {
|
|
DBGERR(("GetFreeComPortNumber: ZwOpenKey failed with %xh.", status));
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT(comNumber != -1);
|
|
return comNumber;
|
|
}
|
|
|
|
|
|
|
|
VOID ReleaseCOMPort(LONG comPortNumber)
|
|
{
|
|
|
|
if (isWin9x){
|
|
/*
|
|
* We punt on this for Win9x.
|
|
* That's ok since the SERIALCOMM keys are dynamically-generated at each boot,
|
|
* so if start fails a COM port number will just be unavailable until the next boot.
|
|
*/
|
|
DBGWARN(("ReleaseCOMPort: not implemented for Win9x")); // BUGBUG
|
|
}
|
|
else {
|
|
|
|
HANDLE hKey = NULL;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
UNICODE_STRING keyName;
|
|
NTSTATUS status;
|
|
|
|
ASSERT(comPortNumber > 0);
|
|
|
|
RtlInitUnicodeString(&keyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\COM Name Arbiter");
|
|
InitializeObjectAttributes( &objectAttributes,
|
|
&keyName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
(PSECURITY_DESCRIPTOR)NULL);
|
|
|
|
status = ZwOpenKey(&hKey, KEY_QUERY_VALUE | KEY_SET_VALUE, &objectAttributes);
|
|
if (NT_SUCCESS(status)){
|
|
UNICODE_STRING valueName;
|
|
PVOID rawData;
|
|
ULONG dataSize;
|
|
|
|
RtlInitUnicodeString(&valueName, L"ComDB");
|
|
|
|
ASSERT(hKey);
|
|
|
|
dataSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION);
|
|
rawData = ALLOCPOOL(NonPagedPool, dataSize);
|
|
|
|
if (rawData){
|
|
status = ZwQueryValueKey( hKey,
|
|
&valueName,
|
|
KeyValuePartialInformation,
|
|
rawData,
|
|
dataSize,
|
|
&dataSize);
|
|
if (status == STATUS_BUFFER_OVERFLOW){
|
|
FREEPOOL(rawData);
|
|
ASSERT(dataSize > FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
|
|
rawData = ALLOCPOOL(NonPagedPool, dataSize);
|
|
if (rawData){
|
|
status = ZwQueryValueKey( hKey,
|
|
&valueName,
|
|
KeyValuePartialInformation,
|
|
rawData,
|
|
dataSize,
|
|
&dataSize);
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status)){
|
|
PKEY_VALUE_PARTIAL_INFORMATION keyPartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION)rawData;
|
|
|
|
ASSERT(dataSize > FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
|
|
ASSERT(keyPartialInfo->Type == REG_BINARY);
|
|
|
|
/*
|
|
* The ComDB value is just a bit mask where bit n set indicates
|
|
* that COM port # n+1 is taken.
|
|
* Get the index of the first unset bit; starting with bit 2 (COM3).
|
|
*
|
|
* Note: careful with the size of the KEY_VALUE_PARTIAL_INFORMATION
|
|
* struct. Its real size is 0x0D bytes,
|
|
* but the compiler aligns it to 0x10 bytes.
|
|
* So use FIELD_OFFSET, not sizeof, to determine
|
|
* how many bytes to write.
|
|
*/
|
|
ASSERT(comPortNumber >= 3);
|
|
if ((comPortNumber > 0) && (comPortNumber <= (LONG)(dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data))*8)){
|
|
ASSERT(keyPartialInfo->Data[(comPortNumber-1)/8] & (1 << ((comPortNumber-1) & 7)));
|
|
keyPartialInfo->Data[(comPortNumber-1)/8] &= ~(1 << ((comPortNumber-1) & 7));
|
|
status = ZwSetValueKey( hKey,
|
|
&valueName,
|
|
0,
|
|
REG_BINARY,
|
|
(PVOID)keyPartialInfo->Data,
|
|
dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
|
|
if (NT_SUCCESS(status)){
|
|
DBGVERBOSE(("ReleaseCOMPort: released COM port # %xh.", comPortNumber));
|
|
}
|
|
else {
|
|
DBGERR(("ReleaseCOMPort: ZwSetValueKey failed with %xh.", status));
|
|
}
|
|
}
|
|
else {
|
|
ASSERT((comPortNumber > 0) && (comPortNumber <= (LONG)(dataSize-FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data))*8));
|
|
}
|
|
}
|
|
else {
|
|
DBGERR(("ReleaseCOMPort: ZwQueryValueKey failed with %xh.", status));
|
|
}
|
|
|
|
/*
|
|
* Check that we didn't fail the second allocation before freeing this buffer.
|
|
*/
|
|
if (rawData){
|
|
FREEPOOL(rawData);
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
status = ZwClose(hKey);
|
|
ASSERT(NT_SUCCESS(status));
|
|
}
|
|
else {
|
|
DBGERR(("ReleaseCOMPort: ZwOpenKey failed with %xh.", status));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|