/*++ Module Name: mca.c Abstract: Sample device driver to register itself with the HAL and log Machine Check Errors on a Intel Architecture Platform Author: Environment: Kernel mode Notes: Revision History: --*/ #include #include #include #include #include "imca.h" // // Device names for the MCA driver // #define MCA_DEVICE_NAME "\\Device\\imca" // ANSI Name #define MCA_DEVICE_NAME_U L"\\Device\\imca" // Unicode Name #define MCA_DEVICE_NAME_DOS "\\DosDevices\\imca" // Device Name for Win32 App NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ); NTSTATUS MCAOpen( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); NTSTATUS MCAClose( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); VOID MCAStartIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); #if defined(_AMD64_) ERROR_SEVERITY MCADriverExceptionCallback( IN PDEVICE_OBJECT DeviceObject, IN PKTRAP_FRAME TrapFrame, IN PKEXCEPTION_FRAME ExceptionFrame, IN PMCA_EXCEPTION InException ); #else ERROR_SEVERITY MCADriverExceptionCallback( IN PDEVICE_OBJECT DeviceObject, IN PMCA_EXCEPTION InException ); #endif VOID MCADriverDpcCallback( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemContext1, IN PVOID SystemContext2 ); NTSTATUS MCACleanup( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); VOID McaCancelIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); NTSTATUS MCADeviceControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); VOID MCAUnload( IN PDRIVER_OBJECT DriverObject ); NTSTATUS MCACreateSymbolicLinkObject( VOID ); VOID MCAProcessWorkItem( PVOID Context ); // // This temporary buffer holds the data between the Machine Check error // notification from HAL and the asynchronous IOCTL completion to the // application // typedef struct _MCA_DEVICE_EXTENSION { PDEVICE_OBJECT DeviceObject; PIRP SavedIrp; BOOLEAN WorkItemQueued; WORK_QUEUE_ITEM WorkItem; // Place to log the exceptions. Whenever the exception callback // routine is called by the HAL MCA component, record the exception here. // This can potentially be a link list. MCA_EXCEPTION McaException; } MCA_DEVICE_EXTENSION, *PMCA_DEVICE_EXTENSION; #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, DriverEntry) #pragma alloc_text(INIT, MCACreateSymbolicLinkObject) #endif // ALLOC_PRAGMA NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) /*++ Routine Description: This routine does the driver specific initialization at entry time Arguments: DriverObject: Pointer to the driver object RegistryPath: Path to driver's registry key Return Value: Success or failure --*/ { UNICODE_STRING UnicodeString; NTSTATUS Status = STATUS_SUCCESS; PMCA_DEVICE_EXTENSION Extension; PDEVICE_OBJECT McaDeviceObject; MCA_DRIVER_INFO McaDriverInfo; // // Create device object for MCA device. // RtlInitUnicodeString(&UnicodeString, MCA_DEVICE_NAME_U); // // Device is created as exclusive since only a single thread can send // I/O requests to this device // Status = IoCreateDevice( DriverObject, sizeof(MCA_DEVICE_EXTENSION), &UnicodeString, FILE_DEVICE_UNKNOWN, 0, TRUE, &McaDeviceObject ); if (!NT_SUCCESS( Status )) { DbgPrint("Mca DriverEntry: IoCreateDevice failed\n"); return Status; } McaDeviceObject->Flags |= DO_BUFFERED_IO; Extension = McaDeviceObject->DeviceExtension; RtlZeroMemory(Extension, sizeof(MCA_DEVICE_EXTENSION)); Extension->DeviceObject = McaDeviceObject; // // Make the device visible to Win32 subsystem // Status = MCACreateSymbolicLinkObject (); if (!NT_SUCCESS( Status )) { DbgPrint("Mca DriverEntry: McaCreateSymbolicLinkObject failed\n"); return Status; } // // Set up the device driver entry points. // DriverObject->MajorFunction[IRP_MJ_CREATE] = MCAOpen; DriverObject->MajorFunction[IRP_MJ_CLOSE] = MCAClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MCADeviceControl; DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MCACleanup; DriverObject->DriverUnload = MCAUnload; DriverObject->DriverStartIo = MCAStartIo; // // Register the driver with the HAL // McaDriverInfo.ExceptionCallback = MCADriverExceptionCallback; McaDriverInfo.DpcCallback = MCADriverDpcCallback; McaDriverInfo.DeviceContext = McaDeviceObject; Status = HalSetSystemInformation( HalMcaRegisterDriver, sizeof(MCA_DRIVER_INFO), (PVOID)&McaDriverInfo ); if (!NT_SUCCESS( Status )) { DbgPrint("Mca DriverEntry: HalMcaRegisterDriver failed\n"); // // Clean up whatever we have done so far // MCAUnload(DriverObject); return(STATUS_UNSUCCESSFUL); } // // This is the place where you would check non-volatile area (if any) for // any Machine Check errros logged and process them. // ... // return STATUS_SUCCESS; } NTSTATUS MCACreateSymbolicLinkObject( VOID ) /*++ Routine Description: Makes MCA device visible to Win32 subsystem Arguments: None Return Value: Success or failure --*/ { NTSTATUS Status; STRING DosString; STRING NtString; UNICODE_STRING DosUnicodeString; UNICODE_STRING NtUnicodeString; // // Create a symbolic link for sharing. // RtlInitAnsiString( &DosString, MCA_DEVICE_NAME_DOS ); Status = RtlAnsiStringToUnicodeString( &DosUnicodeString, &DosString, TRUE ); if ( !NT_SUCCESS( Status )) { return Status; } RtlInitAnsiString( &NtString, MCA_DEVICE_NAME ); Status = RtlAnsiStringToUnicodeString( &NtUnicodeString, &NtString, TRUE ); if ( !NT_SUCCESS( Status )) { return Status; } Status = IoCreateSymbolicLink( &DosUnicodeString, &NtUnicodeString ); RtlFreeUnicodeString( &DosUnicodeString ); RtlFreeUnicodeString( &NtUnicodeString ); return (Status); } // // Dispatch routine for close requests // NTSTATUS MCAClose( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: Close dispatch routine Arguments: DeviceObject: Pointer to the device object Irp: Incoming Irp Return Value: Success or failure --*/ { NTSTATUS Status = STATUS_SUCCESS; // // Complete the request and return status. // Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return (Status); } NTSTATUS MCAOpen( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is the dispatch routine for create/open requests. Arguments: DeviceObject: Pointer to the device object Irp: Incoming Irp Return Value: Success or failure --*/ { // // Complete the request and return status. // Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = FILE_OPENED; IoCompleteRequest(Irp, IO_NO_INCREMENT); return (STATUS_SUCCESS); } ERROR_SEVERITY MCADriverExceptionCallback( IN PDEVICE_OBJECT DeviceObject, #if defined(_AMD64_) IN PKTRAP_FRAME TrapFrame, IN PKEXCEPTION_FRAME ExceptionFrame, #endif IN PMCA_EXCEPTION InException ) /*++ Routine Description: This is the callback routine for MCA exception. It was registered by this driver at INIT time with the HAL as a callback when a non-restartable error occurs. This routine simply copies the information to a platform specific area NOTE: If the information needs to be saved in NVRAM, this is the place to do it. Once you return from this callback, the system is going to bugcheck. Arguments: DeviceObject: Pointer to the device object InException: Exception information record Return Value: None --*/ { PMCA_DEVICE_EXTENSION Extension = DeviceObject->DeviceExtension; PCHAR Destination, Source; UCHAR Bytes; // // An exception has occured on a processor. // Perform any vendor specific action here like saving stuff in NVRAM // NOTE : No system services of any kind can be used here. // // // Save the exception from HAL. May want to use link list for these // exceptions. // Destination = (PCHAR)&(Extension->McaException); // Put your platform // specific destination Source = (PCHAR)InException; // // Copy from source to destination here // #if defined(_IA64_) // // Return information to the generic HAL MCA handler. // // Update it accordingly here, the default value being the ERROR_SEVERITY value // in the MCA exception. // return( InException->ErrorSeverity ); #endif // defined(_IA64_) #if defined(_AMD64_) // // Return the error severity information to the HAL MCA handler. // // Chose from: // // ErrorRecoverable // ErrorFatal // ErrorCorrected // return ErrorRecoverable; #endif } // // DPC routine for IRP completion // VOID MCADriverDpcCallback( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemContext1, IN PVOID SystemContext2 ) /*++ Routine Description: This is the DPC - callback routine for MCA exception. It was registered by this driver at INIT time with the HAL as a DPC callback when a restartable error occurs (which causes Machine Check exception) Arguments: Dpc: The DPC Object itself DefferedContext: Pointer to the device object SystemContext1: Not used SystemContext2: Not used Return Value: None --*/ { PMCA_DEVICE_EXTENSION Extension; Extension = ((PDEVICE_OBJECT)DeferredContext)->DeviceExtension; if (Extension->SavedIrp == NULL) { // // We got an MCA exception but no app was asking for anything. // return; } // // If we have reached this point, it means that the exception was // restartable. Since we cannot read the log at Dispatch level, // queue a work item to read the Machine Check log at Passive level // if (Extension->WorkItemQueued == FALSE) { // // Set a boolean to indicate that we have already queued a work item // Extension->WorkItemQueued = TRUE; // // Initialize the work item // ExInitializeWorkItem(&Extension->WorkItem, (PWORKER_THREAD_ROUTINE)MCAProcessWorkItem, (PVOID)DeferredContext ); // // Queue the work item for processing at PASSIVE level // ExQueueWorkItem(&Extension->WorkItem, CriticalWorkQueue); } } VOID MCAProcessWorkItem( PVOID Context ) /*++ Routine Description: This routine gets invoked when a work item is queued from the DPC callback routine for a restartable machine check error. Its job is to read the machine check registers and copy the log to complete the asynchronous IRP Arguments: Context : Pointer to the device object Return Value: None --*/ { PMCA_DEVICE_EXTENSION Extension; KIRQL CancelIrql; ULONG ReturnedLength; NTSTATUS Status; Extension = ((PDEVICE_OBJECT)Context)->DeviceExtension; // // Mark this IRP as non-cancellable // IoAcquireCancelSpinLock(&CancelIrql); if (Extension->SavedIrp->Cancel == TRUE) { IoReleaseCancelSpinLock(CancelIrql); } else { IoSetCancelRoutine(Extension->SavedIrp, NULL); IoReleaseCancelSpinLock(CancelIrql); // // Call HalQuerySystemInformation() to obtain MCA log. // Status = HalQuerySystemInformation( HalMcaLogInformation, sizeof(MCA_EXCEPTION), Extension->SavedIrp->AssociatedIrp.SystemBuffer, &ReturnedLength ); ASSERT(Status != STATUS_NO_SUCH_DEVICE); ASSERT(Status != STATUS_NOT_FOUND); IoStartPacket(((PDEVICE_OBJECT)Context), Extension->SavedIrp, 0, NULL); Extension->SavedIrp = NULL; Extension->WorkItemQueued = FALSE; } } VOID MCAStartIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine completes the async call from the app Arguments: DeviceObject: Pointer to the device object Irp: Incoming Irp Return Value: None --*/ { // // The system Buffer has already been setup // Irp->IoStatus.Information = sizeof(MCA_EXCEPTION); Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest( Irp, IO_NO_INCREMENT ); IoStartNextPacket(DeviceObject, TRUE); } VOID McaCancelIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This function gets called when the IRP is cancelled. When this routine is called, we hold the cancel spin lock and we are at DISPATCH level Arguments: DeviceObject and the Irp to be cancelled. Return Value: None. --*/ { ((PMCA_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->SavedIrp = NULL; Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; IoReleaseCancelSpinLock(Irp->CancelIrql); IoCompleteRequest(Irp, IO_NO_INCREMENT); } NTSTATUS MCADeviceControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is the dispatch routine for the IOCTL requests to driver. It accepts an I/O Request Packet, performs the request, and then returns with the appropriate status. Arguments: DeviceObject: Pointer to the device object Irp: Incoming Irp Return Value: Success or failure --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PMCA_DEVICE_EXTENSION Extension = DeviceObject->DeviceExtension; KIRQL CancelIrql; ULONG ReturnedLength; ULONG PhysicalAddress; KIRQL OldIrql; // // Get a pointer to the current stack location in the IRP. This is // where the function codes and parameters are stored. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // The individual IOCTLs will return errors if HAL MCA is not installed // // // Switch on the IOCTL code that is being requested by the user. If the // operation is a valid one for this device do the needful. // switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_READ_BANKS: // // we need a user buffer for this call to complete // Our user buffer is in SystemBuffer // if (Irp->AssociatedIrp.SystemBuffer == NULL) { Status = STATUS_UNSUCCESSFUL; break; } if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength != sizeof(MCA_EXCEPTION)) { Status = STATUS_UNSUCCESSFUL; break; } // // Call HalQuerySystemInformation() to obtain MCA log. // This call can also fail if the processor does not support // Intel Machine Check Architecture // Status = HalQuerySystemInformation( HalMcaLogInformation, sizeof(MCA_EXCEPTION), Irp->AssociatedIrp.SystemBuffer, &ReturnedLength ); if (NT_SUCCESS(Status)) { Irp->IoStatus.Information = ReturnedLength; } else { if (Status == STATUS_NO_SUCH_DEVICE) { // // MCA support not available\n"); // NOTHING; } if (Status == STATUS_NOT_FOUND) { // // No machine check errors present\n"); // NOTHING; } Irp->IoStatus.Information = 0; } break; case IOCTL_READ_BANKS_ASYNC: if (Irp->AssociatedIrp.SystemBuffer == NULL) { Status = STATUS_UNSUCCESSFUL; break; } if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength != sizeof(MCA_EXCEPTION)) { Status = STATUS_UNSUCCESSFUL; break; } // // Implementation Note: // // Our Async model is such that the next DeviceIoControl // does not start from the app until the previous one // completes (asynchronously). Since there is no inherent // parallelism that is needed here, we do not have to worry // about protecting data integrity in the face of more than // one app level ioctls active at the same time. // // // asynchronous reads provide a mechanism for the // app to asynchronously get input from the HAL on an // exception. This request is marked as pending at this time // but it will be completed when an MCA exception occurs. // IoMarkIrpPending(Irp); // // Complete the processing in StartIo Dispatch routine // ASSERT: at any given time there is only 1 async call pending // So just save the pointer // if (Extension->SavedIrp == NULL) { Extension->SavedIrp = Irp; } else { // // We can have ONLY one outstanding ASYNC request // Status = STATUS_DEVICE_BUSY; break; } // // Set the IRP to cancellable state // IoAcquireCancelSpinLock(&CancelIrql); IoSetCancelRoutine(Irp, McaCancelIrp); IoReleaseCancelSpinLock(CancelIrql); return(STATUS_PENDING); break; default: // // This should not happen // DbgPrint("MCA driver: Bad ioctl\n"); Status = STATUS_NOT_IMPLEMENTED; break; } // // Copy the final status into the return status, complete the request and // get out of here. // if (Status != STATUS_PENDING) { // // Complete the Io Request // Irp->IoStatus.Status = Status; IoCompleteRequest( Irp, IO_NO_INCREMENT ); } return (Status); } NTSTATUS MCACleanup( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This is the dispatch routine for cleanup requests. All queued IRPs are completed with STATUS_CANCELLED. Arguments: DeviceObject: Pointer to the device object Irp: Incoming Irp Return Value: Success or failure --*/ { PIRP CurrentIrp; PMCA_DEVICE_EXTENSION Extension = DeviceObject->DeviceExtension; // // Complete all queued requests with STATUS_CANCELLED. // if (Extension->SavedIrp != NULL) { CurrentIrp = Extension->SavedIrp; // // Acquire the Cancel Spinlock // IoAcquireCancelSpinLock(&CurrentIrp->CancelIrql); Extension->SavedIrp = NULL; if (CurrentIrp->Cancel == TRUE) { // // Cancel routine got called for this one. // No need to do anything else // IoReleaseCancelSpinLock(CurrentIrp->CancelIrql); } else { if (CurrentIrp->CancelRoutine == NULL) { // // Release the Cancel Spinlock // IoReleaseCancelSpinLock(CurrentIrp->CancelIrql); } else { (CurrentIrp->CancelRoutine)(DeviceObject, CurrentIrp ); } } } // // Complete the Cleanup Dispatch with STATUS_SUCCESS // Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return(STATUS_SUCCESS); } VOID MCAUnload( IN PDRIVER_OBJECT DriverObject ) /*++ Routine Description: Dispatch routine for unloads Arguments: DeviceObject: Pointer to the device object Return Value: None --*/ { NTSTATUS Status; STRING DosString; UNICODE_STRING DosUnicodeString; // // Delete the user visible device name. // RtlInitAnsiString( &DosString, MCA_DEVICE_NAME_DOS ); Status = RtlAnsiStringToUnicodeString( &DosUnicodeString, &DosString, TRUE ); if ( !NT_SUCCESS( Status )) { DbgPrint("MCAUnload: Error in RtlAnsiStringToUnicodeString\n"); return; } Status = IoDeleteSymbolicLink( &DosUnicodeString ); RtlFreeUnicodeString( &DosUnicodeString ); // // Delete the device object // IoDeleteDevice(DriverObject->DeviceObject); return; }