Simplify overlapped I/O handling, and add a command timeout.

If we don't get a response from a command in a while, resend it.  This doesn't
normally happen (it only happened during firmware development), but it makes
command sending more robust, so let's keep it.  This also uses a single OVERLAPPED
for a whole command, which is simpler.
master
Glenn Maynard 6 years ago
parent 9ae9a506c6
commit 849fd7a975
  1. 59
      sdk/Windows/SMXDeviceConnection.cpp
  2. 11
      sdk/Windows/SMXDeviceConnection.h

@ -11,7 +11,11 @@ using namespace SMX;
SMX::SMXDeviceConnection::PendingCommandPacket::PendingCommandPacket() SMX::SMXDeviceConnection::PendingCommandPacket::PendingCommandPacket()
{ {
memset(&m_OverlappedWrite, 0, sizeof(m_OverlappedWrite)); }
SMXDeviceConnection::PendingCommand::PendingCommand()
{
memset(&m_Overlapped, 0, sizeof(m_Overlapped));
} }
shared_ptr<SMX::SMXDeviceConnection> SMXDeviceConnection::Create() shared_ptr<SMX::SMXDeviceConnection> SMXDeviceConnection::Create()
@ -114,6 +118,32 @@ bool SMX::SMXDeviceConnection::ReadPacket(string &out)
void SMX::SMXDeviceConnection::CheckReads(wstring &error) void SMX::SMXDeviceConnection::CheckReads(wstring &error)
{ {
if(m_pCurrentCommand)
{
// See if this command timed out. This doesn't happen often, so this is
// mostly just a failsafe. The controller takes a moment to initialize on
// startup, so we use a large enough timeout that this doesn't trigger on
// every connection.
double fSecondsAgo = SMX::GetMonotonicTime() - m_pCurrentCommand->m_fSentAt;
if(fSecondsAgo > 2.0f)
{
// If we didn't get a response in this long, we're not going to. Retry the
// command by cancelling its I/O and moving it back to the command queue.
//
// if we were delayed and the response is in the queue, we'll get out of sync
Log("Command timed out. Retrying...");
CancelIoEx(m_hDevice->value(), &m_pCurrentCommand->m_Overlapped);
// Block until the cancellation completes. This should happen quickly.
DWORD unused;
GetOverlappedResultEx(m_hDevice->value(), &m_pCurrentCommand->m_Overlapped, &unused, INFINITE, false);
m_aPendingCommands.push_front(m_pCurrentCommand);
m_pCurrentCommand = nullptr;
Log("Command requeued");
}
}
DWORD bytes; DWORD bytes;
int result = GetOverlappedResult(m_hDevice->value(), &overlapped_read, &bytes, FALSE); int result = GetOverlappedResult(m_hDevice->value(), &overlapped_read, &bytes, FALSE);
if(result == 0) if(result == 0)
@ -280,15 +310,13 @@ void SMX::SMXDeviceConnection::BeginAsyncRead(wstring &error)
void SMX::SMXDeviceConnection::CheckWrites(wstring &error) void SMX::SMXDeviceConnection::CheckWrites(wstring &error)
{ {
if(m_pCurrentCommand && !m_pCurrentCommand->m_Packets.empty()) if(m_pCurrentCommand)
{ {
// A command is in progress. See if any writes have completed. // A command is in progress. See if its writes have completed.
while(!m_pCurrentCommand->m_Packets.empty()) if(m_pCurrentCommand->m_bWriting)
{ {
shared_ptr<PendingCommandPacket> pFirstPacket = m_pCurrentCommand->m_Packets.front();
DWORD bytes; DWORD bytes;
int iResult = GetOverlappedResult(m_hDevice->value(), &pFirstPacket->m_OverlappedWrite, &bytes, FALSE); int iResult = GetOverlappedResult(m_hDevice->value(), &m_pCurrentCommand->m_Overlapped, &bytes, FALSE);
if(iResult == 0) if(iResult == 0)
{ {
int windows_error = GetLastError(); int windows_error = GetLastError();
@ -297,17 +325,15 @@ void SMX::SMXDeviceConnection::CheckWrites(wstring &error)
return; return;
} }
m_pCurrentCommand->m_Packets.pop_front(); m_pCurrentCommand->m_bWriting = false;
} }
// Don't clear m_pCurrentCommand here. It'll stay set until we get a PACKET_FLAG_HOST_CMD_FINISHED // Don't clear m_pCurrentCommand here. It'll stay set until we get a PACKET_FLAG_HOST_CMD_FINISHED
// packet from the device, which tells us it's ready to receive another command. // packet from the device, which tells us it's ready to receive another command.
} //
// Don't send packets if there's a command in progress.
// Don't send packets if there's a command in progress.
if(m_pCurrentCommand)
return; return;
}
// Stop if we have nothing to do. // Stop if we have nothing to do.
if(m_aPendingCommands.empty()) if(m_aPendingCommands.empty())
@ -316,6 +342,9 @@ void SMX::SMXDeviceConnection::CheckWrites(wstring &error)
// Send the next command. // Send the next command.
shared_ptr<PendingCommand> pPendingCommand = m_aPendingCommands.front(); shared_ptr<PendingCommand> pPendingCommand = m_aPendingCommands.front();
// Record the time. We can use this for timeouts.
pPendingCommand->m_fSentAt = SMX::GetMonotonicTime();
for(shared_ptr<PendingCommandPacket> &pPacket: pPendingCommand->m_Packets) for(shared_ptr<PendingCommandPacket> &pPacket: pPendingCommand->m_Packets)
{ {
// In theory the API allows this to return success if the write completed successfully without needing to // In theory the API allows this to return success if the write completed successfully without needing to
@ -324,7 +353,7 @@ void SMX::SMXDeviceConnection::CheckWrites(wstring &error)
// so this assumes all writes are async. // so this assumes all writes are async.
DWORD unused; DWORD unused;
// Log(ssprintf("Write: %s", BinaryToHex(pPacket->sData).c_str())); // Log(ssprintf("Write: %s", BinaryToHex(pPacket->sData).c_str()));
if(!WriteFile(m_hDevice->value(), pPacket->sData.data(), pPacket->sData.size(), &unused, &pPacket->m_OverlappedWrite)) if(!WriteFile(m_hDevice->value(), pPacket->sData.data(), pPacket->sData.size(), &unused, &pPendingCommand->m_Overlapped))
{ {
int windows_error = GetLastError(); int windows_error = GetLastError();
if(windows_error != ERROR_IO_PENDING && windows_error != ERROR_IO_INCOMPLETE) if(windows_error != ERROR_IO_PENDING && windows_error != ERROR_IO_INCOMPLETE)
@ -335,6 +364,8 @@ void SMX::SMXDeviceConnection::CheckWrites(wstring &error)
} }
} }
pPendingCommand->m_bWriting = true;
// Remove this command and store it in m_pCurrentCommand, and we'll stop sending data until the command finishes. // Remove this command and store it in m_pCurrentCommand, and we'll stop sending data until the command finishes.
m_pCurrentCommand = pPendingCommand; m_pCurrentCommand = pPendingCommand;
m_aPendingCommands.pop_front(); m_aPendingCommands.pop_front();

@ -87,13 +87,19 @@ private:
PendingCommandPacket(); PendingCommandPacket();
string sData; string sData;
OVERLAPPED m_OverlappedWrite;
}; };
// Commands that are waiting to be sent: // Commands that are waiting to be sent:
struct PendingCommand { struct PendingCommand {
PendingCommand();
list<shared_ptr<PendingCommandPacket>> m_Packets; list<shared_ptr<PendingCommandPacket>> m_Packets;
// The overlapped struct for writing this command's packets. m_bWriting is true
// if we're waiting for the write to complete.
OVERLAPPED m_Overlapped;
bool m_bWriting = false;
// This is only called if m_bWaitForResponse if true. Otherwise, we send the command // This is only called if m_bWaitForResponse if true. Otherwise, we send the command
// and forget about it. If the command has a response, it'll be in buf. // and forget about it. If the command has a response, it'll be in buf.
function<void(string response)> m_pComplete; function<void(string response)> m_pComplete;
@ -101,6 +107,9 @@ private:
// If true, once we send this command we won't send any other commands until we get // If true, once we send this command we won't send any other commands until we get
// a response. // a response.
bool m_bIsDeviceInfoCommand = false; bool m_bIsDeviceInfoCommand = false;
// The SMX::GetMonotonicTime when we started sending this command.
double m_fSentAt = 0;
}; };
list<shared_ptr<PendingCommand>> m_aPendingCommands; list<shared_ptr<PendingCommand>> m_aPendingCommands;

Loading…
Cancel
Save