Virtual Smart Card Reader error in monitoring card status.

Dear experts,

I am developing a virtual smart card reader but I am sticking on make it visible to Smart Card Resource Manager.
My source code is based on this guide **Smart Card Reader Devices Design Guide - Windows drivers | Microsoft Learn ** and Windows-driver-samples\smartcrd.
However, I just make it very simple as I can understand.

SmartcardDeviceControl return STATUS_DEVICE_BUSY(0x80000011) for IoControlCode IOCTL_SMARTCARD_IS_ABSENT(0x31002C or 3211308 in decimal) in callback routine EvtIoDeviceControl.

I guess this is a problem prevent my reader visible to Smart Card Resource Manager but I don’t know how to fix it.
I am new in this field. Any guidance would be really helpful.

Please check my source code in the first comment.

And this is how I install the driver.

devcon install VPJSCReader.inf root\VPJSCReader
Device node created. Install is complete when drivers are installed...
Updating drivers for root\VPJSCReader from c:\Users\minh\Downloads\VPJSCReader\VPJSCReader.inf.
Drivers installed successfully.

Here is debugger’s log.

VPJSCReader!DriverEntry: Enter - KMDF Version Built Oct 14 2019 08:50:44
VPJSCReader!DriverEntry: Exit 0
VPJSCReader!VPJSCReaderEvtDeviceAdd: Enter
VPJSCReader!VPJSCReaderEvtDeviceAdd: Exit
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 4 InputBufferLength 4 IoControlCode 3211272
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 36 InputBufferLength 4 IoControlCode 3211272
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 36 InputBufferLength 4 IoControlCode 3211272
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 4 InputBufferLength 4 IoControlCode 3211272
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 4 InputBufferLength 4 IoControlCode 3211272
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 0 InputBufferLength 0 IoControlCode 3211304
VPJSCReader!CBCardTracking: Entry
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 4 InputBufferLength 0 IoControlCode 3211320
VPJSCReaderLibComplete called: status 0, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 0 InputBufferLength 0 IoControlCode 3211308
VPJSCReaderLibComplete called: status 80000011, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 0 InputBufferLength 0 IoControlCode 3211308
VPJSCReaderLibComplete called: status 80000011, Context 00001B784F5051D8
VPJSCReaderEvtIoDeviceControl Queue 0x00001B78487674E8, Request 0x00001B784F5051D8 OutputBufferLength 0 InputBufferLength 0 IoControlCode 3211308
VPJSCReaderLibComplete called: status 80000011, Context 00001B784F5051D8

In the last three IO request, it return status 80000011.

Here is my device manager.

And here is my event viewer.

My driver source code.

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;
    	SmartcardSetDebugLevel(DEBUG_ALL);
    	SmartcardDebug(
		DEBUG_TRACE,
		("VPJSCReader!DriverEntry: Enter - KMDF Version Built %s %s\n",
			__DATE__, __TIME__)
	);
        WDF_DRIVER_CONFIG_INIT(&config,
                           VPJSCReaderEvtDeviceAdd
                           );
        status = WdfDriverCreate(DriverObject,
                             RegistryPath,
							 WDF_NO_OBJECT_ATTRIBUTES,
                             &config,
                             WDF_NO_HANDLE
                             );
        if (!NT_SUCCESS(status)) {
		KdPrint(("WdfDriverCreate failed with status 0x%x\n", status));
        return status;
    }
    	SmartcardDebug(
		DEBUG_TRACE,
		("VPJSCReader!DriverEntry: Exit %x\n",
			status)
	);
        return status;
}
NTSTATUS
VPJSCReaderEvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;
        UNREFERENCED_PARAMETER(Driver);
        PAGED_CODE();
    	SmartcardDebug(
		DEBUG_TRACE,
		("VPJSCReader!VPJSCReaderEvtDeviceAdd: Enter\n")
	);
        status = VPJSCReaderCreateDevice(DeviceInit);
    	SmartcardDebug(
		DEBUG_TRACE,
		("VPJSCReader!VPJSCReaderEvtDeviceAdd: Exit\n")
	);
        return status;
}
NTSTATUS
VPJSCReaderCreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_OBJECT_ATTRIBUTES deviceAttributes;
    PDEVICE_CONTEXT deviceContext = NULL;
    WDFDEVICE device;
    NTSTATUS status;
        PAGED_CODE();
    	WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_SMARTCARD);
	WdfDeviceInitSetExclusive(DeviceInit, TRUE);
	WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoBuffered);
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
	deviceAttributes.EvtCleanupCallback = VPJSCReaderEvtDriverContextCleanup;
        status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
        if (NT_SUCCESS(status)) {       
        deviceContext = DeviceGetContext(device);
    		deviceContext->Device = device;
		InterlockedIncrement((PLONG)& deviceContext->DeviceInstanceNo);
            status = WdfDeviceCreateDeviceInterface(
            device,
            &SmartCardReaderGuid,
            NULL // ReferenceString
            );
            if (NT_SUCCESS(status)) {
            status = VPJSCReaderQueueInitialize(device);
        }
    }
    	if (!NT_SUCCESS(status)) {
		return status;
	}
            return VPJSCReaderRegisterWithSmcLib(deviceContext);
}
NTSTATUS
VPJSCReaderRegisterWithSmcLib(
	PDEVICE_CONTEXT DeviceContext
) {
	PREADER_EXTENSION ReaderExtension;
	PSMARTCARD_EXTENSION SmartcardExtension;
	NTSTATUS status;
    	PAGED_CODE();
    	//   set up the device extension.
	SmartcardExtension = &DeviceContext->SmartcardExtension;
    	//   allocate the reader extension
	ReaderExtension = ExAllocatePoolWithTag(
		NonPagedPoolNx,
		sizeof(READER_EXTENSION),
		SMARTCARD_POOL_TAG
	);
    	if (ReaderExtension == NULL) {
		SmartcardLogError(
			WdfDriverWdmGetDriverObject(WdfGetDriver()),
			STATUS_INSUFFICIENT_RESOURCES,
			NULL,
			0
		);
		status = STATUS_INSUFFICIENT_RESOURCES;
		return status;
	}
    	RtlZeroMemory(ReaderExtension, sizeof(READER_EXTENSION));
	SmartcardExtension->ReaderExtension = ReaderExtension;
    	//   setup smartcard extension - callback's
	SmartcardExtension->ReaderFunction[RDF_CARD_POWER] = CBCardPower;
	SmartcardExtension->ReaderFunction[RDF_TRANSMIT] = CBTransmit;
	SmartcardExtension->ReaderFunction[RDF_CARD_TRACKING] = CBCardTracking;
	SmartcardExtension->ReaderFunction[RDF_SET_PROTOCOL] = CBSetProtocol;
    	RtlCopyMemory( SmartcardExtension->VendorAttr.VendorName.Buffer, VPJSC_VENDOR_NAME, sizeof(VPJSC_VENDOR_NAME));
	SmartcardExtension->VendorAttr.VendorName.Length = sizeof(VPJSC_VENDOR_NAME);
    	RtlCopyMemory(SmartcardExtension->VendorAttr.IfdType.Buffer, VPJSC_IFD_TYPE, sizeof(VPJSC_IFD_TYPE));
	SmartcardExtension->VendorAttr.IfdType.Length =	sizeof(VPJSC_IFD_TYPE);
    	SmartcardExtension->VendorAttr.UnitNo = DeviceContext->DeviceInstanceNo;
    	SmartcardExtension->VendorAttr.IfdVersion.BuildNumber = 0;
    	//   store firmware revision in ifd version
	SmartcardExtension->VendorAttr.IfdVersion.VersionMajor = 0;
	SmartcardExtension->VendorAttr.IfdVersion.VersionMinor = 0;
	SmartcardExtension->VendorAttr.IfdSerialNo.Length = 0;
    	//   setup smartcard extension - reader capabilities
	SmartcardExtension->ReaderCapabilities.SupportedProtocols = SCARD_PROTOCOL_T0;
    	SmartcardExtension->ReaderCapabilities.ReaderType = SCARD_READER_TYPE_VENDOR;
    	SmartcardExtension->ReaderCapabilities.MechProperties = 0;
	SmartcardExtension->ReaderCapabilities.Channel = 0;
    	SmartcardExtension->ReaderCapabilities.CLKFrequency.Default = 4000;
	SmartcardExtension->ReaderCapabilities.CLKFrequency.Max = 4000;
    	// reader could support higher data rates
	SmartcardExtension->ReaderCapabilities.DataRatesSupported.List = 0;
	SmartcardExtension->ReaderCapabilities.DataRatesSupported.Entries = 0;
	SmartcardExtension->ReaderCapabilities.DataRate.Default = 9600;
	SmartcardExtension->ReaderCapabilities.DataRate.Max = 9600;
    	SmartcardExtension->Version = SMCLIB_VERSION;
	SmartcardExtension->SmartcardRequest.BufferSize = MIN_BUFFER_SIZE;
	SmartcardExtension->SmartcardReply.BufferSize = MIN_BUFFER_SIZE;
    	SmartcardExtension->ReaderCapabilities.CurrentState = SCARD_ABSENT;
    	status = SmartcardInitialize(SmartcardExtension);
	if (status != STATUS_SUCCESS) {
    		SmartcardLogError(
			WdfDriverWdmGetDriverObject(WdfGetDriver()),
			STATUS_INSUFFICIENT_RESOURCES,
			NULL,
			0
		);
		return status;
	}
    	SmartcardExtension->OsData->DeviceObject = WdfDeviceWdmGetDeviceObject(DeviceContext->Device);
    	return status;
}

NTSTATUS
CBCardPower(
	PSMARTCARD_EXTENSION SmartcardExtension
) {
	UNREFERENCED_PARAMETER(SmartcardExtension);
	SmartcardDebug(DEBUG_TRACE,("VPJSCReader!CBCardPower: Entry\n"));	
	return STATUS_NO_MEDIA;
}
NTSTATUS
CBSetProtocol(
	PSMARTCARD_EXTENSION SmartcardExtension
) {
	UNREFERENCED_PARAMETER(SmartcardExtension);
	SmartcardDebug(DEBUG_TRACE, ("VPJSCReader!CBSetProtocol: Entry\n"));
	return STATUS_NO_MEDIA;
}
NTSTATUS
CBTransmit(
	PSMARTCARD_EXTENSION SmartcardExtension
) {
	UNREFERENCED_PARAMETER(SmartcardExtension);
	SmartcardDebug(DEBUG_TRACE, ("VPJSCReader!CBTransmit: Entry\n"));
	return STATUS_NO_MEDIA;
}
NTSTATUS
CBCardTracking(
	PSMARTCARD_EXTENSION SmartcardExtension
) {
	UNREFERENCED_PARAMETER(SmartcardExtension);
	SmartcardDebug(DEBUG_TRACE, ("VPJSCReader!CBCardTracking: Entry\n"));
	return STATUS_SUCCESS;
}

    
_Function_class_(IO_COMPLETION_ROUTINE)
NTSTATUS
VPJSCReaderLibComplete(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp,
	IN PVOID Context
)
{
	UNREFERENCED_PARAMETER(DeviceObject);
    
	KdPrint(("VPJSCReaderLibComplete called: status %x, Context %p\n", Irp->IoStatus.Status, Context));
    
	WdfRequestComplete((WDFREQUEST)Context, Irp->IoStatus.Status);
    
	return STATUS_MORE_PROCESSING_REQUIRED;
}
        
VOID
VPJSCReaderEvtIoDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
    )
{    
	PDEVICE_CONTEXT deviceExtension;
	PIRP irp;
    
	KdPrint(("VPJSCReaderEvtIoDeviceControl Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d\n",
		Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode));
    
	deviceExtension = DeviceGetContext(WdfIoQueueGetDevice(Queue));
    
	irp = WdfRequestWdmGetIrp(Request);
        
	SET_REQUEST_IN_IRP(irp, Request);
        
	IoCopyCurrentIrpStackLocationToNext(irp);
    
	IoSetCompletionRoutine(irp,
		VPJSCReaderLibComplete,
		Request,
		TRUE,
		TRUE,
		TRUE
	);
    
	IoSetNextIrpStackLocation(irp);
    
	(VOID)SmartcardDeviceControl(&(deviceExtension->SmartcardExtension), irp);
    
    return;
}

There are a few problems here. Have you done any reading at all about the functions you are calling?

I don’t understand why you would use IoSetCompletionRoutine instead of WdfRequestSetCompletionRoutine. When an I/O completion routine is finished with an IRP, as yours is, you need to return the status from the IRP, and not STATUS_MORE_PROCESSING_REQUIRED. WdfRequestSetCompletionRoutine would have handled that for you.

However, at a higher level, the whole completion routine processing is totally wrong. SmartcardDeviceControl is NOT passing the request into another driver. Go look at the documentation page. You do not need to worry about a stack location and a completion routine. That whole routine can be condensed to
deviceExtension = DeviceGetContext…
irp = WdfRequestWdmGetIrp(Request);
return SmartcardDeviceControl(&(deviceExtension->SmartcardExtension), irp);

Are you getting calls to your callbacks? SmartcardDeviceControl handles requests that don’t need hardware, but anything that does need hardware has to call into your callbacks.

Why did you SetExclusive on the device object? Did you read that somewhere?

Your InterlockedIncrement of DeviceInstanceNo is silly. That routine will be called exactly once for each device, and each one will get a fresh device context. Thus, that number will never be anything other than 1.

Hi @Tim_Roberts,
Thank you very much for your comments.

My source code is based on some resources such as https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard and Windows-driver-samples\smartcrd. I know this is not the whole picture and I’ve just focused on some functions related to smart card, not all of them.
For me, there is a huge amount of knowledge in this filed and I’ve not understand it clearly.

Are you getting calls to your callbacks? SmartcardDeviceControl handles requests that don’t need hardware, but anything that does need hardware has to call into your callbacks.
Yes, as you see in debugger’s log, routine CBCardTracking is called. If I change

SmartcardExtension->ReaderCapabilities.CurrentState = SCARD_ABSENT;

to

SmartcardExtension->ReaderCapabilities.CurrentState = SCARD_POWERED;

routine CBCardPower is also getting called.

Now, I remove redundant code as your comments and the driver run same as it before changed.

The problem may be in this callback routine CBCardTracking:

NTSTATUS
    CBCardTracking(PSMARTCARD_EXTENSION SmartcardExtension) {
	UNREFERENCED_PARAMETER(SmartcardExtension);
	SmartcardDebug(DEBUG_TRACE, ("VPJSCReader!CBCardTracking: Entry\n"));
	return STATUS_SUCCESS;
}

Because, I do nothing here, just return success.
I think that I need to do something with SmartcardExtension->OsData->NotificationIrp but I don’t know how.

OK, so you’ve established that the framework is fundamentally correct. Now you have to write the guts of your driver. That’s where the real work is.

Is there any one here who implement success a virtual smart card reader?
Please help me, I am still sticking with the problem of my reader is not listed by SCardListReaders.

My Event IO device control routine VPJSCReaderEvtIoDeviceControl is kept the same in above source code.
And here what I receive after SmartcardDeviceControl is called(I am using WinDbg to get these information).

For IoControlCode = IOCTL_SMARTCARD_IS_PRESENT, it returns Irp->IoStatus.Status = STATUS_SUCCESS => same as my expectation.
For IoControlCode = IOCTL_SMARTCARD_GET_STATE,  it returns Irp->IoStatus.Status = STATUS_SUCCESS, state via Output Buffer = SCARD_ABSENT => same as my expectation.
For IoControlCode = IOCTL_SMARTCARD_IS_ABSENT, it returns Irp->IoStatus.Status= STATUS_DEVICE_BUSY, not same as my expectation. It should be STATUS_SUCCESS.

I think this problem here is a cause that prevent my reader listed by SCardListReaders. Any idea is welcome!

I just come back here to update that the problem has been resolved.
Sorry for the late update.

Hi @minhpta ,
Would you mind sharing what the solution was in case someone has the same issue in the future.
Thanks,
Eric

@Eric_Wittmayer

Yes, absolutely. I will share It in a completed topic but not now because eventhought my solution works but not as my expectation.
I have another problem here , It makes my solution is not completed.

Hi @minhpta
How did you solve this problem?

Thanks

Certainly, designing a solid framework is crucial, but the true essence of any driver lies in the implementation details.