TCP Connection in Interrupt
d-marson opened this issue · comments
Hello,
We have found your project to be a great tool for experimenting and learning, so first of all, thank you for making this available!
We are trying an exercise to trigger a TCP connection with a button press (connected via GPIO pins) and would like to use an interrupt to do so. We have been able to successfully set up an interrupt handler and use it to blink an LED; after the LED blinks, control is handed back to the main loop as expected. However, when we swap out the LED code for code that tries to establish a TCP connection, the device seems to hang when it tries to execute the Connect method of CSocket. No connection is detected and no messages are written to console.
We tested our TCP connection code within the main loop and our server was able to receive connections, so we're not sure what the issue may be when we're running it within the interrupt handler. Below is our kernel.cpp
code; we appreciate any suggestions you may have!
#include "kernel.h"
#include <circle/string.h>
#include <circle/sched/task.h>
#include <circle/net/in.h>
#include <circle/net/netsubsystem.h>
#include <circle/net/socket.h>
#include <circle/net/ipaddress.h>
// Network configuration
#define USE_DHCP
static const char FromKernel[] = "kernel";
CActLED CKernel::ActLED; // initialize LED
CKernel::CKernel (void)
: m_Screen (m_Options.GetWidth (), m_Options.GetHeight ()),
m_Timer (&m_Interrupt),
m_Logger (m_Options.GetLogLevel (), &m_Timer),
m_GPIOManager (&m_Interrupt), // try to set up GPIO manager
m_GPIO18 (18, GPIOModeOutput), // access to GPIO pin 18 for LED
m_GPIO17 (17, GPIOModeInput), // access to GPIO pin 17 for button
m_USBHCI (&m_Interrupt, &m_Timer)
{
ActLED.Blink (5); // show we are alive
m_GPIO18.Write (LOW); // initialize as off
}
CKernel::~CKernel (void)
{
}
boolean CKernel::Initialize (void)
{
boolean bOK = TRUE;
if (bOK)
{
bOK = m_Screen.Initialize ();
}
if (bOK)
{
bOK = m_Serial.Initialize (115200);
}
if (bOK)
{
CDevice *pTarget = m_DeviceNameService.GetDevice (m_Options.GetLogDevice (), FALSE);
if (pTarget == 0)
{
pTarget = &m_Screen;
}
bOK = m_Logger.Initialize (pTarget);
}
if (bOK)
{
bOK = m_Interrupt.Initialize ();
}
if (bOK)
{
bOK = m_Timer.Initialize ();
}
if (bOK)
{
bOK = m_GPIOManager.Initialize ();
}
if (bOK)
{
bOK = m_USBHCI.Initialize ();
}
if (bOK)
{
bOK = m_Net.Initialize ();
}
return bOK;
}
TShutdownMode CKernel::Run (void)
{
m_Logger.Write (FromKernel, LogNotice, "Compile time: " __DATE__ " " __TIME__);
// Test LED
m_GPIO18.Write (HIGH);
m_Timer.MsDelay (1000);
m_GPIO18.Write (LOW);
// Get IP
CString IPString;
m_Net.GetConfig ()->GetIPAddress ()->Format (&IPString);
m_Logger.Write (FromKernel, LogNotice, "Starting up server object.");
ActLED.Blink (1);
// Set up GPIO pin interrupt
unsigned int myPin = 17;
CGPIOPin myInputPin (myPin, GPIOModeInput, &m_GPIOManager);
// Connect pin interrupt
myInputPin.ConnectInterrupt (foo, this);
myInputPin.EnableInterrupt (GPIOInterruptOnFallingEdge);
while (1)
{
// // Write to console
// m_Logger.Write (FromKernel, LogNotice, "Attempting repeating connection.");
// // Socket
// CSocket *pSocket = new CSocket (&m_Net, IPPROTO_TCP);
// // IP
// u8 TargetIPArray[] = {192, 168, 1, 100};
// CIPAddress ForeignIP(TargetIPArray);
// u16 nForeignPort = 5001;
// // Connect
// pSocket->Connect (ForeignIP, nForeignPort);
// // Send
// CString message(" test message \r\n\r\n");
// unsigned messageLength = message.GetLength();
// pSocket->Send (message, messageLength, MSG_DONTWAIT);
// // close connection
// delete pSocket;
// m_Logger.Write (FromKernel, LogNotice, "Attempt completed.");
}
return ShutdownHalt;
}
void foo ( void *pParam )
{
CKernel *pThis = (CKernel *) pParam;
pThis->myInterruptHandler();
}
void CKernel::myInterruptHandler ()
{
// Write status
m_Logger.Write (FromKernel, LogNotice, "Entered Interrupt.");
// Socket
CSocket *pSocket2 = new CSocket (&m_Net, IPPROTO_TCP);
// IP
u8 TargetIPArray2[] = {192, 168, 1, 100};
CIPAddress ForeignIP2(TargetIPArray2);
u16 nForeignPort2 = 5000;
// Connect
pSocket2->Connect (ForeignIP2, nForeignPort2);
// Send
CString message2(" interrupt message \r\n\r\n");
unsigned messageLength2 = message2.GetLength();
pSocket2->Send (message2, messageLength2, MSG_DONTWAIT);
// close connection
delete pSocket2;
// write status
m_Logger.Write (FromKernel, LogNotice, "Exit Interrupt.");
}
This behavior is intended. The networking functions have to run in a task context at TASK_LEVEL
, because they may block, while waiting for some event from the network. In an IRQ handler at IRQ_LEVEL
the execution cannot block, because an IRQ handler has to be finished as soon as possible, so that the system remains responsive on other arriving IRQs.
As a solution you can set a volatile flag in the IRQ handler, when the button is pressed, which is read in the main loop. When in the main loop the flag is read as set, the network connection is established there. The flag has to be reset then.
Thanks for appreciating Circle!
Thank you for the rapid and detailed response, this is very helpful for our understanding!