/*++ Copyright (c) 1995 Microsoft Corporation Module Name: channel.c Abstract: This module implements the executive channel object. Channel obects provide a very high speed local interprocess communication mechanism. Author: David N. Cutler (davec) 26-Mar-1995 Environment: Kernel mode only. Revision History: --*/ #include "ki.h" // // Define local function prototypes. // VOID KiAllocateReceiveBufferChannel ( VOID ); VOID KiCloseChannel ( IN PEPROCESS Process, IN PVOID Object, IN ACCESS_MASK GrantedAccess, IN ULONG ProcessHandleCount, IN ULONG SystemHandleCount ); VOID KiDeleteChannel ( IN PVOID Object ); NTSTATUS KiListenChannel ( IN PRECHANNEL ServerChannel, IN KPROCESSOR_MODE WaitMode, OUT PCHANNEL_MESSAGE *Message ); PKTHREAD KiRendezvousWithThread ( IN PRECHANNEL WaitChannel, IN ULONG WaitMode ); // // Address of event object type descriptor. // POBJECT_TYPE KeChannelType; // // Structure that describes the mapping of generic access rights to object // specific access rights for event objects. // #ifdef ALLOC_DATA_PRAGMA #pragma const_seg("INITCONST") #endif const GENERIC_MAPPING KiChannelMapping = { STANDARD_RIGHTS_READ | CHANNEL_READ_MESSAGE, STANDARD_RIGHTS_WRITE | CHANNEL_WRITE_MESSAGE, STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE, CHANNEL_ALL_ACCESS }; #ifdef ALLOC_DATA_PRAGMA #pragma const_seg() #endif // // Define function sections. // #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, KiAllocateReceiveBufferChannel) #pragma alloc_text(INIT, KiChannelInitialization) #pragma alloc_text(PAGE, KiDeleteChannel) #pragma alloc_text(PAGE, KiRundownChannel) #pragma alloc_text(PAGE, NtCreateChannel) #pragma alloc_text(PAGE, NtListenChannel) #pragma alloc_text(PAGE, NtOpenChannel) #pragma alloc_text(PAGE, NtSetContextChannel) #endif NTSTATUS NtCreateChannel ( OUT PHANDLE ChannelHandle, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL ) /*++ Routine Description: This function creates a server listen channel object and opens a handle to the object with the specified desired access. Arguments: ChannelHandle - Supplies a pointer to a variable that will receive the channel object handle. ObjectAttributes - Supplies a pointer to an object attributes structure. Return Value: If the channel object is created, then a success status is returned. Otherwise, a failure status is returned. --*/ { #if 0 PVOID ChannelObject; KPROCESSOR_MODE PreviousMode; PRECHANNEL ServerChannel; HANDLE ServerHandle; NTSTATUS Status; // // Establish an exception handler, probe and zero the output handle // address, and attempt to create a channel object. If the probe fails // or access to the object attributes fails, then return the exception // code as the service status. // PreviousMode = KeGetPreviousMode(); try { // // Get previous processor mode and probe output handle address if // necessary. // if (PreviousMode != KernelMode) { ProbeAndZeroHandle(ChannelHandle); } // // Allocate channel object. // Status = ObCreateObject(PreviousMode, KeChannelType, ObjectAttributes, PreviousMode, NULL, sizeof(ECHANNEL), 0, 0, &ChannelObject); } except(ExSystemExceptionFilter()) { return GetExceptionCode(); } // // If the channel object was successfully created, then initialize the // channel object and insert the channel object in the process handle // table. // if (NT_SUCCESS(Status)) { ServerChannel = (PRECHANNEL)ChannelObject; ServerChannel->Type = LISTEN_CHANNEL; ServerChannel->State = ServerIdle; ServerChannel->OwnerProcess = &PsGetCurrentProcess()->Pcb; ServerChannel->ClientThread = NULL; ServerChannel->ServerThread = NULL; ServerChannel->ServerContext = NULL; ServerChannel->ServerChannel = NULL; KeInitializeEvent(&ServerChannel->ReceiveEvent, SynchronizationEvent, FALSE); KeInitializeEvent(&ServerChannel->ClearToSendEvent, SynchronizationEvent, FALSE); Status = ObInsertObject(ServerChannel, NULL, CHANNEL_ALL_ACCESS, 0, NULL, &ServerHandle); // // If the channel object was successfully inserted in the process // handle table, then attempt to write the channel object handle // value. If the write attempt fails, then do not report an error. // When the caller attempts to access the handle value, an access // violation will occur. // if (NT_SUCCESS(Status)) { try { *ChannelHandle = ServerHandle; } except(ExSystemExceptionFilter()) { } } } // // Return service status. // return Status; #else return STATUS_NOT_IMPLEMENTED; #endif } NTSTATUS NtListenChannel ( IN HANDLE ChannelHandle, OUT PCHANNEL_MESSAGE *Message ) /*++ Routine Description: This function listens for a client message. N.B. This function can only be executed from a server thread. Arguments: ChannelHandle - Supplies a handle to a listen channel on which the server thread listens. Message - Supplies a pointer to a variable that receives a pointer to the client message header. Return Value: If the function is successfully completed, then a success status is returned. Otherwise, a failure status is returned. --*/ { #if 0 KPROCESSOR_MODE PreviousMode; PRECHANNEL ServerChannel; PRKTHREAD ServerThread; NTSTATUS Status; // // Establish an exception handler, probe the output message address, // and allocate a receive buffer if necessary. If the probe fails or // the receive buffer allocation is not successful, then return the // exception code as the service status. // ServerThread = KeGetCurrentThread(); try { // // Get previous processor mode and probe output message address. // PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { ProbeAndNullPointer(Message); } // // If the current thread does not have an associated receive buffer, // then attempt to allocate one now. If the allocation fails, then // an exception is raised. // if (ServerThread->Section == NULL) { KiAllocateReceiveBufferChannel(); } } except(ExSystemExceptionFilter()) { return GetExceptionCode(); } // // Reference channel object by handle. // Status = ObReferenceObjectByHandle(ChannelHandle, CHANNEL_ALL_ACCESS, KeChannelType, PreviousMode, &ServerChannel, NULL); // // If the reference was successful and the channel is a listen channel, // then wait for a client message to arrive. // if (NT_SUCCESS(Status)) { if (ServerChannel->ServerChannel != NULL) { Status = STATUS_INVALID_PARAMETER; // **** fix **** } else { Status = KiListenChannel(ServerChannel, PreviousMode, Message); } ObDereferenceObject(ServerChannel); } // // Return service status. // return Status; #else return STATUS_NOT_IMPLEMENTED; #endif } NTSTATUS NtOpenChannel ( OUT PHANDLE ChannelHandle, IN POBJECT_ATTRIBUTES ObjectAttributes ) /*++ Routine Description: This function opens a handle to a server channel by creating a message channel that is connected to the specified server channel. Arguments: ChannelHandle - Supplies a pointer to a variable that will receive the channel object handle. ObjectAttributes - Supplies a pointer to an object attributes structure. Return Value: If the channel object is opened, then a success status is returned. Otherwise, a failure status is returned. --*/ { #if 0 PRECHANNEL ClientChannel; HANDLE ClientHandle; PKTHREAD ClientThread; KPROCESSOR_MODE PreviousMode; PRECHANNEL ServerChannel; PVOID ServerObject; NTSTATUS Status; // // Establish an exception handler, probe and zero the output handle // address, and attempt to open the server channel object. If the // probe fails, then return the exception code as the service status. // try { // // Get previous processor mode and probe output handle address // if necessary. // PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { ProbeAndZeroHandle(ChannelHandle); } // // Reference the server channel object with the specified desired // access. // Status = ObReferenceObjectByName(ObjectAttributes->ObjectName, ObjectAttributes->Attributes, NULL, CHANNEL_ALL_ACCESS, KeChannelType, PreviousMode, NULL, (PVOID *)&ServerObject); } except(ExSystemExceptionFilter()) { return GetExceptionCode(); } // // If the reference was successful, then attempt to create a client // channel object. // if (NT_SUCCESS(Status)) { // // If the owner process of the server channel is the same as // the current process, then a server thread is attempting to // open a client handle. This is not allowed since it would // not be possible to distinguish the server from the cient. // ClientThread = KeGetCurrentThread(); ServerChannel = (PECHANNEL)ServerObject; if (ServerChannel->OwnerProcess == ClientThread->ApcState.Process) { Status = STATUS_INVALID_PARAMETER; // **** fix *** } else { Status = ObCreateObject(PreviousMode, KeChannelType, NULL, PreviousMode, NULL, sizeof(ECHANNEL), 0, 0, (PVOID *)&ClientChannel); // // If the channel object was successfully created, then // initialize the channel object and attempt to insert the // channel object in the server process channel table. // if (NT_SUCCESS(Status)) { ClientChannel->Type = MESSAGE_CHANNEL; ClientChannel->State = ClientIdle; ClientChannel->OwnerProcess = &PsGetCurrentProcess()->Pcb; ClientChannel->ClientThread = NULL; ClientChannel->ServerThread = NULL; ClientChannel->ServerContext = NULL; ClientChannel->ServerChannel = ServerChannel; KeInitializeEvent(&ClientChannel->ReceiveEvent, SynchronizationEvent, FALSE); KeInitializeEvent(&ClientChannel->ClearToSendEvent, SynchronizationEvent, FALSE); // // Create a handle to the message channel object. // Status = ObInsertObject(ClientChannel, NULL, CHANNEL_ALL_ACCESS, 0, NULL, &ClientHandle); // // If the channel object was successfully inserted in the // client process handle table, then attempt to write the // client channel object handle value. If the write attempt // fails, then do not report an error. When the caller // attempts to access the handle value, an access violation // will occur. // if (NT_SUCCESS(Status)) { try { *ChannelHandle = ClientHandle; } except(ExSystemExceptionFilter()) { } } return Status; } } ObDereferenceObject(ServerChannel); } // // Return service status. // return Status; #else return STATUS_NOT_IMPLEMENTED; #endif } NTSTATUS NtReplyWaitSendChannel ( IN PVOID Text, IN ULONG Length, OUT PCHANNEL_MESSAGE *Message ) /*++ Routine Description: This function sends a reply message to a client and waits for a send. N.B. This function can only be executed from a server thread that has an assoicated message channel. Arguments: Text - Supplies a pointer to the message text. Length - Supplies the length of the message text. Message - Supplies a pointer to a variable that receives the send message header. Return Value: If the function is successfully completed, then a succes status is returned. Otherwise, a failure status is returned. --*/ { #if 0 PKTHREAD ClientThread; PCHANNEL_MESSAGE ClientView; PRECHANNEL MessageChannel; KPROCESSOR_MODE PreviousMode; PECHANNEL ServerChannel; PKTHREAD ServerThread; NTSTATUS Status; // // Establish an exception handler, probe the output message address, // probe the message text, and allocate a receive buffer if necessary. // If either of the probes fail or the receive buffer allocation is // not successful, then return the exception code as the service // status. // ServerThread = KeGetCurrentThread(); try { // // Get previous processor mode and probe output message address and // the message text if necessary. // PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { ProbeForRead(Text, Length, sizeof(CHAR)); ProbeAndNullPointer(Message); } // // If the current thread does not have an associated receive buffer, // then attempt to allocate one now. If the allocation fails, then // an exception is raised. // if (ServerThread->Section == NULL) { KiAllocateReceiveBufferChannel(); } } except(ExSystemExceptionFilter()) { return GetExceptionCode(); } // // If the message length is greater than the host page size minus // the message header length, then return an error. // if (Length >= (PAGE_SIZE - sizeof(CHANNEL_MESSAGE))) { return STATUS_BUFFER_OVERFLOW; } // // If the server thread has an associated message channel, the channel // is in server receive message state, and the channel server thread // matches the current thread. // // This implies that: // // 1. The channel is a message channel. // // 2. The channel is being accessed by the server thread. // // 3. The channel is associated with a listen channel. // // 4. There is currently a server channel owner. // // 5. There is currently a client channel owner. // KiLockDispatcherDatabase(&ServerThread->WaitIrql); MessageChannel = ServerThread->Channel; if ((MessageChannel == NULL) || (MessageChannel->State != ServerReceiveMessage) || (MessageChannel->ServerThread != ServerThread)) { // // A message is not associated with the current thread, // the message channel is in the wrong state, or the // current thread is not the owner of the channel. // KiUnlockDispatcherDatabase(ServerThread->WaitIrql); Status = STATUS_INVALID_PARAMETER; // **** fix **** } else { // // Rendezvous with the client thread so a transfer of the // reply text to the client thread can occur. // ClientThread = KiRendezvousWithThread(MessageChannel, PreviousMode); // // Control is returned when: // // 1. The server thread is being terminated (USER_APC). // // 2. A rendezvous with a client thread has occured. // // N.B. If the status value is less than zero, then it // is the address of the client thread. // if ((LONG)ClientThread < 0) { // // The client thread is returned as the rendezvous status // with the thread in the transition state. Get the address // of the client thread system view, establish an exception // handler, and transfer the message text from the server's // buffer to the client's receive buffer. If an exception // occurs during the copy, then return the exception code // as the service status. // ClientView = ClientThread->SystemView; Status = STATUS_SUCCESS; if (Length != 0) { try { RtlCopyMemory(ClientView + 1, Text, Length); } except (ExSystemExceptionFilter()) { Status = GetExceptionCode(); } } // // Set the channel message parameters. // ClientView->Text = (PVOID)(ClientThread->ThreadView + 1); ClientView->Length = Length; ClientView->Context = NULL; ClientView->Base = Text; ClientView->Close = FALSE; // // Raise IRQL to dispatch level, lock the dispatcher // database, and check if the message was successfully // transfered to the client's receive buffer. If the // message was successfully transfered to the client's // receive buffer. then reset the channel state, fill // in the message parameters, ready the client thread, // and listen for the next message. Otherwise, set the // client wait status and ready the client thread for // execution. // KiLockDispatcherDatabase(&ServerThread->WaitIrql); if (NT_SUCCESS(Status)) { MessageChannel->State = ClientIdle; MessageChannel->ClientThread = NULL; MessageChannel->ServerThread = NULL; ClientThread->WaitStatus = STATUS_SUCCESS; // // Reference the server channel and dereference the // message channel. // ServerChannel = MessageChannel->ServerChannel; ObReferenceObject(ServerChannel); ObDereferenceObject(MessageChannel); // // If there are no clients waiting to send to the server, // then switch directly to the client thread. Otherwise, // ready the client thread, then listen for the next // message. // if (IsListEmpty(&ServerChannel->ClearToSendEvent.Header.WaitListHead) == FALSE) { KiReadyThread(ClientThread); KiUnlockDispatcherDatabase(ServerThread->WaitIrql); Status = KiListenChannel(ServerChannel, PreviousMode, Message); } else { Status = KiSwitchToThread(ClientThread, WrRendezvous, PreviousMode, &ServerChannel->ReceiveEvent); // // If a client message was successfully received, then // attempt to write the address of the send message // address. If the write attempt fails, then do not // report an error. When the caller attempts to access // the message address, an access violation will occur. // if (NT_SUCCESS(Status)) { try { *Message = ServerThread->ThreadView; } except(ExSystemExceptionFilter()) { } } } ObDereferenceObject(ServerChannel); } else { // // The reply message was not successfully transfered // to the client receive buffer because of an access // violation encountered durring the access to the // server buffer. // ClientThread->WaitStatus = STATUS_KERNEL_APC; KiReadyThread(ClientThread); KiUnlockDispatcherDatabase(ServerThread->WaitIrql); } } else { // // The server thread is terminating and the channel // structures will be cleaned up by the termiantion // code. // Status = (NTSTATUS)ClientThread; } } // // Return service status. // return Status; #else return STATUS_NOT_IMPLEMENTED; #endif } NTSTATUS NtSendWaitReplyChannel ( IN HANDLE ChannelHandle, IN PVOID Text, IN ULONG Length, OUT PCHANNEL_MESSAGE *Message ) /*++ Routine Description: This function sends a message to a server and waits for a reply. N.B. This function can only be executed from a client thread. Arguments: ChannelHandle - Supplies a handle to a message channel over which the specified message text is sent. Text - Supplies a pointer to the message text. Length - Supplies the length of the message text. Message - Supplies a pointer to a variable that receives a pointer to the reply message header. Return Value: If the function is successfully completed, then a success status is returned. Otherwise, a failure status is returned. --*/ { #if 0 PKTHREAD ClientThread; PRECHANNEL MessageChannel; KPROCESSOR_MODE PreviousMode; PRECHANNEL ServerChannel; PKTHREAD ServerThread; PCHANNEL_MESSAGE ServerView; NTSTATUS Status; // // Establish an exception handler, probe the output message address, // probe the message text, and allocate a receive buffer if necessary. // If either of the probes fail or the receive buffer allocation is // not successful, then return the exception code as the service // status. // ClientThread = KeGetCurrentThread(); try { // // Get previous processor mode and probe output message address // and the message text. // PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { ProbeForRead(Text, Length, sizeof(UCHAR)); ProbeAndNullPointer(Message); } // // If the current thread does not have an associated receive buffer, // then attempt to allocate one now. If the allocation fails, then // an exception is raised. // if (ClientThread->Section == NULL) { KiAllocateReceiveBufferChannel(); } } except(ExSystemExceptionFilter()) { return GetExceptionCode(); } // // If the message length is greater than the host page size minus // the message header length, then return an error. // if (Length >= (PAGE_SIZE - sizeof(CHANNEL_MESSAGE))) { return STATUS_BUFFER_OVERFLOW; } // // Reference channel object by handle. // Status = ObReferenceObjectByHandle(ChannelHandle, CHANNEL_ALL_ACCESS, KeChannelType, PreviousMode, (PVOID *)&MessageChannel, NULL); // // If the reference was successful, then check if the channel is in // the client idle state. // // This implies that: // // 1. The channel is a message channel. // // 2. The channel is being accessed by a client thread. // // 3. The channel is connected to a listen channel. // // 4. There is currently no client thread channel owner. // // 5. There is currently no server thread channel owner. // if (NT_SUCCESS(Status)) { KiLockDispatcherDatabase(&ClientThread->WaitIrql); if (MessageChannel->State != ClientIdle) { // // The message channel is in the wrong state. // KiUnlockDispatcherDatabase(ClientThread->WaitIrql); Status = STATUS_INVALID_PARAMETER; // **** fix **** } else { // // Set the channel state, set the client owner thread, and // rendezvous with a server thread. // MessageChannel->State = ClientSendWaitReply; MessageChannel->ClientThread = ClientThread; ClientThread->Channel = MessageChannel; ServerChannel = MessageChannel->ServerChannel; ServerThread = KiRendezvousWithThread(ServerChannel, PreviousMode); // // Control is returned when: // // 1. The client thread is being terminated (USER_APC). // // 2. A rendezvous with a server thread has occured. // // N.B. If the status value is less than zero, then it // is the address of the server thread. // if ((LONG)ServerThread < 0) { // // The server thread is returned as the rendezvous status // with the thread in the transition state. Get the address // of the server thread system view, establish an exception // handler, and transfer the message text from the client's // buffer to the server's receive buffer. If an exception // occurs during the copy, then return the exception code // as the service status. // ServerView = ServerThread->SystemView; if (Length != 0) { try { RtlCopyMemory(ServerView + 1, Text, Length); } except (ExSystemExceptionFilter()) { Status = GetExceptionCode(); } } // // Set the channel message parameters. // ServerView->Text = (PVOID)(ServerThread->ThreadView + 1); ServerView->Length = Length; ServerView->Context = MessageChannel->ServerContext; ServerView->Base = Text; ServerView->Close = FALSE; // // Raise IRQL to dispatch level, lock the dispatcher // database and check if the message was successfully // transfered to the server's receive buffer. If the // message was successfully transfered, then set the // channel state, set the server thread address, set // the address of the message channel in the server // thread, increment the message channel reference // count, fill in the message parameters, and switch // directly to the server thread. Otherwise, set the // channel state, and reready the server thread for // execution. // KiLockDispatcherDatabase(&ClientThread->WaitIrql); if (NT_SUCCESS(Status)) { MessageChannel->State = ServerReceiveMessage; MessageChannel->ServerThread = ServerThread; ObReferenceObject(MessageChannel); ServerThread->Channel = MessageChannel; Status = KiSwitchToThread(ServerThread, WrRendezvous, PreviousMode, &MessageChannel->ReceiveEvent); // // If the send and subsequent reply from the server // thread is successful, then attempt to write the // address of the reply message address. If the write // attempt fails, then do not report an error. When // the caller attempts to access the message address, // an access violation will occur. // if (NT_SUCCESS(Status)) { try { *Message = ClientThread->ThreadView; } except(ExSystemExceptionFilter()) { } } } else { // // The send message was not successfully transfered // to the server receive buffer because of an access // violation encountered durring the access to the // client buffer. // MessageChannel->State = ClientIdle; MessageChannel->ClientThread = NULL; ClientThread->Channel = NULL; ServerThread->WaitStatus = STATUS_KERNEL_APC; KiReadyThread(ServerThread); KiUnlockDispatcherDatabase(ClientThread->WaitIrql); } } else { // // The client thread is terminating and the channel // structures will be cleaned up by the termination // code. // Status = (NTSTATUS)ServerThread; } } ObDereferenceObject(MessageChannel); } // // Return service status. // return Status; #else return STATUS_NOT_IMPLEMENTED; #endif } NTSTATUS NtSetContextChannel ( IN PVOID Context ) /*++ Routine Description: This function stores a context value for the current associated message channel. N.B. This function can only be executed from a server thread that has an associated message channel. Arguments: Context - Supplies a context value that is to be stored in the associated message channel. Return Value: If the channel information is set, then a success status is returned. Otherwise, a failure status is returned. --*/ { #if 0 PRECHANNEL MessageChannel; PKTHREAD CurrentThread; NTSTATUS Status; // // If the thread has an assoicated channel and the server thread for // the channel is the current thread, then store the channel context. // CurrentThread = KeGetCurrentThread(); MessageChannel = CurrentThread->Channel; if ((MessageChannel == NULL) || (CurrentThread != MessageChannel->ServerThread)) { Status = STATUS_INVALID_PARAMETER; // ****** FIX ***** } else { MessageChannel->ServerContext = Context; Status = STATUS_SUCCESS; } // // Return service status. // return Status; #else return STATUS_NOT_IMPLEMENTED; #endif } #if 0 VOID KiAllocateReceiveBufferChannel ( VOID ) /*++ Routine Description: This function creates an unnamed section with a single page, maps a view of the section into the current process and into the system address space, and associates the view with the current thread. Arguments: None. Return Value: If a channel receive buffer is not allocated, then raise an exception. --*/ { LARGE_INTEGER MaximumSize; PEPROCESS Process; NTSTATUS Status; PKTHREAD Thread; LARGE_INTEGER ViewOffset; ULONG ViewSize; // // Create an unnamed section object. // Thread = KeGetCurrentThread(); ASSERT(Thread->Section == NULL); MaximumSize.QuadPart = PAGE_SIZE; Status = MmCreateSection(&Thread->Section, 0, NULL, &MaximumSize, PAGE_READWRITE, SEC_COMMIT, NULL, NULL); if (NT_SUCCESS(Status)) { // // Map a view of the section into the current process. // Process = PsGetCurrentProcess(); ViewOffset.QuadPart = 0; ViewSize = PAGE_SIZE; Status = MmMapViewOfSection(Thread->Section, Process, &Thread->ThreadView, 0, ViewSize, &ViewOffset, &ViewSize, ViewUnmap, 0, PAGE_READWRITE); if (NT_SUCCESS(Status)) { // // Map a view of the section into the system address // space. // Status = MmMapViewInSystemSpace(Thread->Section, &Thread->SystemView, &ViewSize); if (NT_SUCCESS(Status)) { return; } MmUnmapViewOfSection(Process, Thread->ThreadView); } ObDereferenceObject(Thread->Section); } // // The allocation of a receive buffer was not successful. Raise an // exception that will be caught by the caller. // ExRaiseStatus(Status); return; } BOOLEAN KiChannelInitialization ( VOID ) /*++ Routine Description: This function creates the channel object type descriptor at system initialization and stores the address of the object type descriptor in global storage. Arguments: None. Return Value: A value of TRUE is returned if the channel object type descriptor is successfully initialized. Otherwise a value of FALSE is returned. --*/ { OBJECT_TYPE_INITIALIZER ObjectTypeInitializer; NTSTATUS Status; UNICODE_STRING TypeName; // // Initialize string descriptor. // RtlInitUnicodeString(&TypeName, L"Channel"); // // Create channel object type descriptor. // RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer)); ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer); ObjectTypeInitializer.GenericMapping = KiChannelMapping; ObjectTypeInitializer.PoolType = NonPagedPool; ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(ECHANNEL); ObjectTypeInitializer.ValidAccessMask = CHANNEL_ALL_ACCESS; ObjectTypeInitializer.InvalidAttributes = OBJ_EXCLUSIVE | OBJ_INHERIT | OBJ_PERMANENT; ObjectTypeInitializer.CloseProcedure = KiCloseChannel; ObjectTypeInitializer.DeleteProcedure = KiDeleteChannel; Status = ObCreateObjectType(&TypeName, &ObjectTypeInitializer, NULL, &KeChannelType); // // If the channel object type descriptor was successfully created, then // return a value of TRUE. Otherwise return a value of FALSE. // return (BOOLEAN)(NT_SUCCESS(Status)); } VOID KiCloseChannel ( IN PEPROCESS Process, IN PVOID Object, IN ACCESS_MASK GrantedAccess, IN ULONG ProcessHandleCount, IN ULONG SystemHandleCount ) /*++ Routine Description: This function is called when a handle to a channel is closed. If the hanlde is the last handle in the process to the channel object and the channel object is a message channel, then send a message to the server indicating that the client handle is being closed. Arguments: Object - Supplies a pointer to an executive channel. Return Value: None. --*/ { PECHANNEL MessageChannel = (PECHANNEL)Object; // // If the object is a message channel and hte last handle is being // closed, then send a message to the server indicating that the // channel is being closed. // if ((MessageChannel->ServerChannel != NULL) && (ProcessHandleCount == 1)) { } return; } VOID KiDeleteChannel ( IN PVOID Object ) /*++ Routine Description: This function is the delete routine for channel objects. Its function is to ... Arguments: Object - Supplies a pointer to an executive channel. Return Value: None. --*/ { PRECHANNEL ChannelObject = (PECHANNEL)Object; // // If the channel is a message channel, then dereference the connnected // server channel. // if (ChannelObject->ServerChannel != NULL) { ObDereferenceObject(ChannelObject->ServerChannel); } return; } VOID KiRundownChannel ( VOID ) /*++ Routine Description: This function runs down associated channel object and receive buffers when the a thread is terminated. Arguments: None. Return Value: None. --*/ { PKTHREAD Thread; // // If the current thread has an associated receive buffer, then unmap // the receive buffer and dereference the underlying section. // Thread = KeGetCurrentThread(); if (Thread->Section != NULL) { MmUnmapViewOfSection(PsGetCurrentProcess(), Thread->ThreadView); MmUnmapViewInSystemSpace(Thread->SystemView); ObDereferenceObject(Thread->Section); Thread->Section = NULL; } // // If the current thread has an associated channel, then ... // return; } NTSTATUS KiListenChannel ( IN PRECHANNEL ServerChannel, IN KPROCESSOR_MODE WaitMode, OUT PCHANNEL_MESSAGE *Message ) /*++ Routine Description: This function listens for a client message to arrive. N.B. This function can only be executed from a server thread. Arguments: ServerChannel - Supplies a pointer to a litent channel on which the server thread listens. WaitMode - Supplies the processor wait mode. Message - Supplies a pointer to a variable that receives a pointer to the client message header. Return Value: If the function is successfully completed, then a success status is returned. Otherwise, a failure status is returned. --*/ { PKEVENT ClearToSendEvent; PKTHREAD ClientThread; PKQUEUE Queue; PKTHREAD ServerThread; PKWAIT_BLOCK WaitBlock; PLIST_ENTRY WaitEntry; NTSTATUS WaitStatus; // // Raise IRQL to dispatch level and lock the dispatcher database. // ServerThread = KeGetCurrentThread(); KiLockDispatcherDatabase(&ServerThread->WaitIrql); // // Start of wait loop. // // Note this loop is repeated if a kernel APC is delivered in the // middle of the wait or a kernel APC is pending on the first attempt // through the loop. // do { // // Check if there is a thread waiting on the clear to send event. // ClearToSendEvent = &ServerChannel->ClearToSendEvent; WaitEntry = ClearToSendEvent->Header.WaitListHead.Flink; if (WaitEntry != &ClearToSendEvent->Header.WaitListHead) { WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry); ClientThread = WaitBlock->Thread; // // Remove the wait block from the wait list of the receive event, // and remove the client thread from the wait list. // RemoveEntryList(&WaitBlock->WaitListEntry); RemoveEntryList(&ClientThread->WaitListEntry); // // If the client thread is processing a queue entry, then increment the // count of currently active threads. // Queue = ClientThread->Queue; if (Queue != NULL) { Queue->CurrentCount += 1; } // // Set the wait completion status to kernel APC so the client // will attempt another rendezvous and ready the client thread // for execution. // ClientThread->WaitStatus = STATUS_KERNEL_APC; KiReadyThread(ClientThread); } // // Test to determine if a kernel APC is pending. // // If a kernel APC is pending and the previous IRQL was less than // APC_LEVEL, then a kernel APC was queued by another processor // just after IRQL was raised to DISPATCH_LEVEL, but before the // dispatcher database was locked. // // N.B. that this can only happen in a multiprocessor system. // if (ServerThread->ApcState.KernelApcPending && (ServerThread->WaitIrql < APC_LEVEL)) { // // Unlock the dispatcher database and lower IRQL to its // previous value. An APC interrupt will immediately occur // which will result in the delivery of the kernel APC if // possible. // KiUnlockDispatcherDatabase(ServerThread->WaitIrql); } else { // // Test if a user APC is pending. // if ((WaitMode != KernelMode) && (ServerThread->ApcState.UserApcPending)) { WaitStatus = STATUS_USER_APC; break; } // // Construct a wait block for the clear to send event object. // WaitBlock = &ServerThread->WaitBlock[0]; ServerThread->WaitBlockList = WaitBlock; ServerThread->WaitStatus = (NTSTATUS)0; WaitBlock->Object = (PVOID)&ServerChannel->ReceiveEvent; WaitBlock->NextWaitBlock = WaitBlock; WaitBlock->WaitKey = (CSHORT)STATUS_SUCCESS; WaitBlock->WaitType = WaitAny; InsertTailList(&ServerChannel->ReceiveEvent.Header.WaitListHead, &WaitBlock->WaitListEntry); // // If the current thread is processing a queue entry, then // attempt to activate another thread that is blocked on the // queue object. // Queue = ServerThread->Queue; if (Queue != NULL) { KiActivateWaiterQueue(Queue); } // // Set the thread wait parameters, set the thread dispatcher // state to Waiting, and insert the thread in the wait list. // ServerThread->Alertable = FALSE; ServerThread->WaitMode = WaitMode; ServerThread->WaitReason = WrRendezvous; ServerThread->WaitTime = KiQueryLowTickCount(); ServerThread->State = Waiting; KiInsertWaitList(WaitMode, ServerThread); // // Switch context to selected thread. // // Control is returned at the original IRQL. // ASSERT(KeIsExecutingDpc() == FALSE); ASSERT(ServerThread->WaitIrql <= DISPATCH_LEVEL); WaitStatus = KiSwapThread(); // // If the thread was not awakened to deliver a kernel mode APC, // then return wait status. // if (WaitStatus != STATUS_KERNEL_APC) { // // If a client message was successfully received, then // attempt to write the address of the send message // address. If the write attempt fails, then do not // report an error. When the caller attempts to access // the message address, an access violation will occur. // if (NT_SUCCESS(WaitStatus)) { try { *Message = ServerThread->ThreadView; } except(ExSystemExceptionFilter()) { } } return WaitStatus; } } // // Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database. // KiLockDispatcherDatabase(&ServerThread->WaitIrql); } while (TRUE); // // Unlock the dispatcher database and return the target thread. // KiUnlockDispatcherDatabase(ServerThread->WaitIrql); return WaitStatus; } PKTHREAD KiRendezvousWithThread ( IN PRECHANNEL WaitChannel, IN ULONG WaitMode ) /*++ Routine Description: This function performs a rendezvous with a thread waiting on the channel receive event. N.B. This routine is called with the dispatcher database locked. N.B. The wait IRQL is assumed to be set for the current thread. N.B. Control is returned from this function with the dispatcher database unlocked. Arguments: WaitChannel - Supplies a pointer to a channel whose receive event is the target of the rendezvous operation. WaitMode - Supplies the processor wait mode. Return Value: If a thread rendezvous is successfully performed, then the address of the thread object is returned as the completion status. Otherwise, if the wait completes because of a timeout or because the thread is being terminated, then the appropriate status is returned. --*/ { PKTHREAD CurrentThread; PKQUEUE Queue; PKTHREAD TargetThread; PKWAIT_BLOCK WaitBlock; PLIST_ENTRY WaitEntry; NTSTATUS WaitStatus; // // Start of wait loop. // // Note this loop is repeated if a kernel APC is delivered in the // middle of the wait or a kernel APC is pending on the first attempt // through the loop. // // If the rendezvous event wait list is not empty, then remove the first // entry from the list, compute the address of the respective thread, // cancel the thread timer if appropraite, and return the thread address. // Otherwise, wait for a thread to rendezvous with. // CurrentThread = KeGetCurrentThread(); do { // // Check if there is a thread waiting on the rendezvous event. // WaitEntry = WaitChannel->ReceiveEvent.Header.WaitListHead.Flink; if (WaitEntry != &WaitChannel->ReceiveEvent.Header.WaitListHead) { WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry); TargetThread = WaitBlock->Thread; // // Remove the wait block from the wait list of the receive event, // and remove the target thread from the wait list. // RemoveEntryList(&WaitBlock->WaitListEntry); RemoveEntryList(&TargetThread->WaitListEntry); // // If the target thread is processing a queue entry, then increment the // count of currently active threads. // Queue = TargetThread->Queue; if (Queue != NULL) { Queue->CurrentCount += 1; } // // Set the thread state to transistion. // TargetThread->State = Transition; break; } else { // // Test to determine if a kernel APC is pending. // // If a kernel APC is pending and the previous IRQL was less than // APC_LEVEL, then a kernel APC was queued by another processor // just after IRQL was raised to DISPATCH_LEVEL, but before the // dispatcher database was locked. // // N.B. that this can only happen in a multiprocessor system. // if (CurrentThread->ApcState.KernelApcPending && (CurrentThread->WaitIrql < APC_LEVEL)) { // // Unlock the dispatcher database and lower IRQL to its // previous value. An APC interrupt will immediately occur // which will result in the delivery of the kernel APC if // possible. // KiUnlockDispatcherDatabase(CurrentThread->WaitIrql); } else { // // Test if a user APC is pending. // if ((WaitMode != KernelMode) && (CurrentThread->ApcState.UserApcPending)) { TargetThread = (PKTHREAD)STATUS_USER_APC; break; } // // Construct a wait block for the clear to send event object. // WaitBlock = &CurrentThread->WaitBlock[0]; CurrentThread->WaitBlockList = WaitBlock; CurrentThread->WaitStatus = (NTSTATUS)0; WaitBlock->Object = (PVOID)&WaitChannel->ClearToSendEvent; WaitBlock->NextWaitBlock = WaitBlock; WaitBlock->WaitKey = (CSHORT)STATUS_SUCCESS; WaitBlock->WaitType = WaitAny; InsertTailList(&WaitChannel->ClearToSendEvent.Header.WaitListHead, &WaitBlock->WaitListEntry); // // If the current thread is processing a queue entry, then // attempt to activate another thread that is blocked on the // queue object. // Queue = CurrentThread->Queue; if (Queue != NULL) { KiActivateWaiterQueue(Queue); } // // Set the thread wait parameters, set the thread dispatcher // state to Waiting, and insert the thread in the wait list. // CurrentThread->Alertable = FALSE; CurrentThread->WaitMode = (KPROCESSOR_MODE)WaitMode; CurrentThread->WaitReason = WrRendezvous; CurrentThread->WaitTime = KiQueryLowTickCount(); CurrentThread->State = Waiting; KiInsertWaitList(WaitMode, CurrentThread); // // Switch context to selected thread. // // Control is returned at the original IRQL. // ASSERT(KeIsExecutingDpc() == FALSE); ASSERT(CurrentThread->WaitIrql <= DISPATCH_LEVEL); WaitStatus = KiSwapThread(); // // If the thread was not awakened to deliver a kernel mode APC, // then return wait status. // if (WaitStatus != STATUS_KERNEL_APC) { return (PKTHREAD)WaitStatus; } } // // Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database. // KiLockDispatcherDatabase(&CurrentThread->WaitIrql); } } while (TRUE); // // Unlock the dispatcher database and return the target thread. // KiUnlockDispatcherDatabase(CurrentThread->WaitIrql); return TargetThread; } #endif