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.
This commit is contained in:
parent
9ae9a506c6
commit
849fd7a975
@ -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…
x
Reference in New Issue
Block a user