/// <summary>
                /// Produce the number of bytes read in the buffer.
                /// </summary>
                /// <remarks>
                /// If the number of bytes read is zero, this function should also be called, as it indicates
                /// that there are no more bytes pending. The recommendation from MS documentation for reading
                /// from the serial port indicates to read the buffer data, until a result of zero is given,
                /// which indicates to wait for the next receiving character.
                /// </remarks>
                /// <param name="bytes">Number of bytes read.</param>
                private void ProcessReadEvent(uint bytes)
                {
                    m_Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: ProcessReadEvent: {1} bytes", Name, bytes);
                    if (bytes == 0) {
                        m_ReadByteAvailable = false;
                    } else {
                        lock (m_ReadLock) {
                            if (m_Trace.Switch.ShouldTrace(System.Diagnostics.TraceEventType.Verbose)) {
                                // Converting everything to strings before the call is expensive.
                                m_Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                                    "{0}: SerialThread: ProcessReadEvent: End={1}; Bytes={2}", Name,
                                    m_Buffers.ReadBuffer.End, bytes);
                            }
                            m_Buffers.ReadBuffer.Produce((int)bytes);
                            if (m_Buffers.ReadBuffer.Free == 0) {
                                m_ReadBufferNotFullEvent.Reset();
                            }
                            m_ReadBufferNotEmptyEvent.Set();
                            m_ReadBufferEvent.Set();
                        }

                        bool eof = (m_ReadByteEof & EofByte.InBuffer) != 0;
                        OnCommEvent(eof ? NativeMethods.SerialEventMask.EV_RXFLAG : NativeMethods.SerialEventMask.EV_RXCHAR);

                        // The EofByte is so designed, that we can use a bit shift to get to the next state.
                        // We don't lock this, as only the Serial I/O thread reads/writes this flag.
                        m_ReadByteEof = (EofByte)(((int)m_ReadByteEof << 1) & 0x03);
                    }
                }
                /// <summary>
                /// Do work based on the mask event that has occurred.
                /// </summary>
                /// <param name="mask">The mask that was provided.</param>
                private void ProcessWaitCommEvent(NativeMethods.SerialEventMask mask)
                {
                    if (mask != (int)0) {
                        m_Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: ProcessWaitCommEvent: {1}", Name, mask);
                    }

                    // Reading a character
                    if ((mask & NativeMethods.SerialEventMask.EV_RXCHAR) != 0) {
                        m_ReadByteAvailable = true;
                    }
                    if ((mask & NativeMethods.SerialEventMask.EV_RXFLAG) != 0) {
                        m_ReadByteAvailable = true;
                        // An EOF character may already be in our buffer. We don't lock this, as only
                        // the Serial I/O thread reads/writes this flag.
                        m_ReadByteEof |= EofByte.InDriver;
                    }

                    // We don't raise an event for characters immediately, but only after the read operation
                    // is complete.
                    OnCommEvent(mask & ~(NativeMethods.SerialEventMask.EV_RXCHAR | NativeMethods.SerialEventMask.EV_RXFLAG));

                    if ((mask & NativeMethods.SerialEventMask.EV_TXEMPTY) != 0) {
                        lock (m_WriteLock) {
                            if (m_Buffers.WriteBuffer.Length == 0) {
                                m_Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "{0}: SerialThread: ProcessWaitCommEvent: TX-BUFFER empty", Name);
                                m_TxBufferEmpty.Set();
                                m_TxEmptyEvent = false;
                            } else {
                                // Because the main event loop handles CommEvents before WriteEvents, it could be
                                // that a write event occurs immediately after, actually emptying the buffer.
                                m_TxEmptyEvent = true;
                            }
                        }
                    }

                    if ((mask & (NativeMethods.SerialEventMask.EV_RXCHAR | NativeMethods.SerialEventMask.EV_ERR)) != 0) {
                        NativeMethods.ComStatErrors comErr;
                        bool result = UnsafeNativeMethods.ClearCommError(m_ComPortHandle, out comErr, IntPtr.Zero);
                        int e = Marshal.GetLastWin32Error();
                        if (result) {
                            comErr = (NativeMethods.ComStatErrors)((int)comErr & 0x10F);
                            if (comErr != 0) {
                                OnCommErrorEvent(comErr);
                            }
                        } else {
                            m_Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                                "{0}: SerialThread: ClearCommError: WINERROR {1}", Name, e);
                        }
                    }
                }