Skip to main content
Pilous Droip
Senior
December 18, 2018
Question

Ethernet close connection and restart MCU

  • December 18, 2018
  • 10 replies
  • 2626 views

Hi,

I have STM32F767 - Nucleo kit. I implement FreeRTOS, lwIP. And I create this connect.

struct tcp_pcb *server_pcb;
 
struct server_struct
{
	uint8_t state; 			/* current connection state */
	uint8_t retries;
	struct tcp_pcb *pcb; 	/* pointer on the current tcp_pcb */
	struct pbuf *p; 		/* pointer on the received/to be transmitted pbuf */
};
 
struct server_struct *ss;
 
enum server_states
{
	ES_NONE = 0,
	ES_ACCEPTED,
	ES_RECEIVED,
	ES_CLOSING
};
 
static void tcp_server_send(struct tcp_pcb *tpcb, struct server_struct *es)
{
	struct pbuf *ptr;
	err_t wr_err = ERR_OK;
	while ((wr_err == ERR_OK) && (es->p != NULL) && (es->p->len <= tcp_sndbuf(tpcb)))
	{
		ptr = es->p;
		wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);
		if (wr_err == ERR_OK)
		{
			uint16_t plen;
			uint8_t freed;
			plen = ptr->len;
			es->p = ptr->next;
			if(es->p != NULL)
			{
				pbuf_ref(es->p);
			}
			do
			{
				freed = pbuf_free(ptr);
			}
			while(freed == 0);
			tcp_recved(tpcb, plen);
		}
		else if(wr_err == ERR_MEM)
		{
			es->p = ptr;
		}
		else
		{
			//other problem
		}
	}
}
 
static void tcp_server_connection_close(struct tcp_pcb *tpcb, struct server_struct *es)
{
	// remove all callbacks
	tcp_arg(tpcb, NULL);
	tcp_sent(tpcb, NULL);
	tcp_recv(tpcb, NULL);
	tcp_err(tpcb, NULL);
	tcp_poll(tpcb, NULL, 1);
	if (es != NULL)
	{
		mem_free(es);
	}
	tcp_close(tpcb);
}
 
static void tcp_server_error(void *arg, err_t err)
{
	//-TODO-
}
 
static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb)
{
	err_t ret_err;
	struct server_struct *es;
	es = (struct server_struct *)arg;
 
	if (es != NULL)
	{
		if (es->p != NULL)
		{
		}
		else
		{
			if(es->state == ES_CLOSING)
		 {
				tcp_server_connection_close(tpcb, es);
		 }
		}
		ret_err = ERR_OK;
	}
	else
	{
		tcp_abort(tpcb);
		ret_err = ERR_ABRT;
	}
	return ret_err;
}
 
static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len)
{
	struct server_struct *es;
	LWIP_UNUSED_ARG(len);
	es = (struct server_struct *)arg;
	es->retries = 0;
	if(es->p != NULL)
	{
		tcp_server_send(tpcb, es);
	}
	else
	{
		if(es->state == ES_CLOSING)
		{
			tcp_server_connection_close(tpcb, es);
		}
	}
	return ERR_OK;
}
 
static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
	char str1[100];
	struct server_struct *es;
	err_t ret_err;
	LWIP_ASSERT("arg != NULL",arg != NULL);
	es = (struct server_struct *)arg;
 
	if (p == NULL)
	{
		es->state = ES_CLOSING;
		if(es->p == NULL)
		{
			tcp_recved(tpcb, p->tot_len);
		}
		else
		{
			//acknowledge received packet
			tcp_sent(tpcb, tcp_server_sent);
			//send remaining data
			tcp_server_send(tpcb, es);
		}
		ret_err = ERR_OK;
	}
	else if(err != ERR_OK)
	{
		if (p != NULL)
		{
			es->p = NULL;
			pbuf_free(p);
		}
		ret_err = err;
	}
	else if(es->state == ES_ACCEPTED)
	{
		tcp_recved(tpcb, p->tot_len);
		strncpy(str1,p->payload,p->len);
		str1[p->len] = '\0';
/* -- Send data to Serial PORT */
		PrintSerial(str1);
		ret_err = ERR_OK;
	}
	else if (es->state == ES_RECEIVED)
	{
		if(es->p == NULL)
		{
			ret_err = ERR_OK;
		}
		else
		{
			struct pbuf *ptr;
			ptr = es->p;
			pbuf_chain(ptr,p);
		}
		ret_err = ERR_OK;
	}
	else if(es->state == ES_CLOSING)
	{
		tcp_recved(tpcb, p->tot_len);
		es->p = NULL;
		pbuf_free(p);
		ret_err = ERR_OK;
	}
	else
	{
		tcp_recved(tpcb, p->tot_len);
		es->p = NULL;
		pbuf_free(p);
		ret_err = ERR_OK;
	}
	return ret_err;
}
 
static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
	err_t ret_err;
	struct server_struct *es;
	LWIP_UNUSED_ARG(arg);
	LWIP_UNUSED_ARG(err);
	tcp_setprio(newpcb, TCP_PRIO_MIN);
	es = (struct server_struct *)mem_malloc(sizeof(struct server_struct));
	ss = (struct server_struct *)mem_malloc(sizeof(struct server_struct));
	if (es != NULL)
	{
		es->state = ES_ACCEPTED;
		es->pcb = newpcb;
	 ss->pcb = newpcb;
	 es->retries = 0;
	 es->p = NULL;
	 tcp_arg(newpcb, es);
	 tcp_recv(newpcb, tcp_server_recv);
	 tcp_err(newpcb, tcp_server_error);
	 tcp_sent(newpcb, tcp_server_sent);
	 tcp_poll(newpcb, tcp_server_poll, 1);
	 ret_err = ERR_OK;
	}
	else
	{
		tcp_server_connection_close(newpcb, es);
		ret_err = ERR_MEM;
	}
	return ret_err;
}
 
 
void Hello(void *pvParameters)
{
	ip_addr_t ipaddr;
	IP4_ADDR(&ipaddr, 192, 168, 11, 208);
 
	server_pcb = tcp_new();
	if (server_pcb != NULL)
	{
		err_t err;
		err = tcp_bind(server_pcb, IP_ADDR_ANY, 502);
		if (err == ERR_OK)
		{
			server_pcb = tcp_listen(server_pcb);
			tcp_accept(server_pcb, tcp_server_accept);
		}
		else
		{
			memp_free(MEMP_TCP_PCB, server_pcb);
		}
	}
 
 	while(1)
 	{
 		vTaskDelay(100);
 	}
}
 

In main function I create a task:

xTaskCreate( Hello, "Hello", 4096, ( void * ) NULL, 2, NULL );

And program works (when I send anything over TCP i see it in serial output). Problem is when I try disconnect. MCU is restart.

Any idea, what is wrong?

I always get this massage:

HardFault Exception 

Here is output:

  • >>>> HardFault Exception <<<<

  • Fault registers:
  • SCB->HFSR = 0x40000000
  •  DEBUGEVT = 0
  •  FORCED  = 1
  •  VECTTBL  = 0
  • SCB->CFSR = 0x00010000
  •  IACCVIOL = 0
  •  DACCVIOL = 0
  •  MUNSTKERR = 0
  •  MSTKERR  = 0
  •  MLSPERR  = 0
  •  MMARVALID = 0
  •  IBUSERR  = 0
  •  PRECISE E = 0
  •  IMPRECISE = 0
  •  UNSTKERR = 0
  •  STKERR  = 0
  •  LSPERR  = 0
  •  BFARVALID = 0
  •  UNDEFINSTR= 1
  •  INVSTATE = 0
  •  INVPC   = 0
  •  NOCP   = 0
  •  UNALIGNED = 0
  •  DIVBYZERO = 0
  • SCB->DFSR = 0x00000000
  • SCB->MMFAR = 0x00000000
  • SCB->BFAR = 0x00000000
  • SCB->AFSR = 0x00000000
  • SCB->ABFSR = 0x00000000

  • Exception stack frame:
  • R0  0x20045c98
  • R1  0x20068d8c
  • R2  0x00000003
  • R3  0x00009a90
  • R12  0x20068a54
  • LR  0x08007e87
  • PC  0x0800fd5c
  • xPSR 0x61000000

  •  >>> forcing SYSTEM RESET
This topic has been closed for replies.

10 replies

AvaTar
Senior III
December 18, 2018

Tried to decode the SCB register contents ?

Here is a document for reference: www.keil.com/appnotes/files/apnt209.pdf

Pilous Droip
Senior
December 18, 2018

I think this is the answer to your question?

  •  UNDEFINSTR= 1
  •  FORCED  = 1

Or what specifically should I look for?

Pilous Droip
Senior
December 18, 2018

I looked into disassembly. But I don't see a problem.

xTaskCreate( BlinkLED, "BlinkLED", 4096, ( void * ) NULL, 2, NULL );
0800f8ce: mov r3, r4
0800f8d0: mov.w r2, #4096 ; 0x1000
0800f8d4: ldr r1, [pc, #20] ; (0x800f8ec <NUCLEO_CB_SetupMyTasks+56>)
0800f8d6: str r4, [sp, #4]
0800f8d8: str r5, [sp, #0]
0800f8da: ldr r0, [pc, #20] ; (0x800f8f0 <NUCLEO_CB_SetupMyTasks+60>)
0800f8dc: bl 0x800236c <xTaskCreate>
146 }
0800f8e0: add sp, #12
0800f8e2: pop {r4, r5, pc}
0800f8e4: strb r4, [r5, r4]
0800f8e6: lsrs r1, r0, #32
0800f8e8: ldrb.w r0, [r5, <undefined>]
0800f8ec: strb r4, [r6, r4]
0800f8ee: lsrs r1, r0, #32
0800f8f0: ; <UNDEFINED> instruction: 0xf7990800
 NMI_Handler:
0800f8f4: bkpt 0x0000
0800f8f6: b.n 0x800f8f6 <NMI_Handler+2>
 88 {
 HardFault_InfoDump:
0800f8f8: stmdb sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}
0800f8fc: mov r1, r0
102 	uint32_t hfsr = SCB->HFSR;
0800f8fe: ldr r5, [pc, #452] ; (0x800fac4 <HardFault_InfoDump+460>)
0800f900: sub sp, #36 ; 0x24
110 	hfprintf("\n\n\n>>>> HardFault Exception <<<<\n");

Tesla DeLorean
Guru
December 18, 2018

Ok, and what is the reported hard fault status related to this code?

Can't relate the prior reported PC to anything here.

Need to show the 5 instructions either side of the faulting instruction to get some minimal context.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
AvaTar
Senior III
December 18, 2018

Did you check the call stack in the toolchain's debugger ?

Or could you identify the C/Assembler intruction causing the fault ?

Reproduction should be easy if I understood correctly.

Perhaps an insufficient stack (task stack) size, or the mem_free() gets haywire ?

Piranha
Principal III
December 18, 2018

Maybe this is not the main problem, but You have made probably the most popular lwIP+RTOS related mistake in the world - "raw" APIs are not thread-safe!

https://www.nongnu.org/lwip/2_1_x/multithreading.html

http://lwip.100.n7.nabble.com/lwip-2-0-2-freertos-ARM7-td29599.html

From the second link: "If you use threads, you may want to use netconn or socket API if that's easier for you. You still can use the callback API, but you need to use tcpip_callback() or LOCK_TCPIP_CORE (if you configured lwIP to use core locking) to sync with lwIP thread."

This ir true also for netif_***(), dhcp_***() and other callback style APIs. The only exceptions are pbuf and memory alloc/free functions when SYS_LIGHTWEIGHT_PROT is enabled (enabled by default) - these are thread-safe. Also NETIF, DHCP and AUTOIP functions have thread-safe netifapi_***() alternatives.

Connection callbacks by default are called from lwIP core "tcpip_thread" and, if You enable LWIP_TCPIP_CORE_LOCKING_INPUT (disabled by default), then from the thread in which You call netif->input(). In both cases You must not use tcpip_callback() or LOCK_TCPIP_CORE() in callbacks.

If You don't do this right, then the firmware will always seem to work but fail occasionally. My suggested solution is to put LOCK_TCPIP_CORE() in line 234 and UNLOCK_TCPIP_CORE() in line 250. Test it and report if anything changes.

Pilous Droip
Senior
December 19, 2018

I enable core locking input in lwipopts.h

#define LWIP_TCPIP_CORE_LOCKING_INPUT (1)

And modificate my hello function:

void Hello(void *pvParameters)
{
	ip_addr_t ipaddr;
	IP4_ADDR(&ipaddr, 192, 168, 11, 208);
	LOCK_TCPIP_CORE();
	server_pcb = tcp_new();
	if (server_pcb != NULL)
	{
		err_t err;
		err = tcp_bind(server_pcb, IP_ADDR_ANY, 502);
		if (err == ERR_OK)
		{
			server_pcb = tcp_listen(server_pcb);
			tcp_accept(server_pcb, tcp_server_accept);
		}
		else
		{
			memp_free(MEMP_TCP_PCB, server_pcb);
		}
	}
	UNLOCK_TCPIP_CORE();
	while(1)
	{
  		vTaskDelay(100);
	}
}

And problem is the same.

>>>> HardFault Exception <<<<
 
Fault registers:
SCB->HFSR = 0x40000000
 DEBUGEVT = 0
 FORCED = 1
 VECTTBL = 0
SCB->CFSR = 0x00010000
 IACCVIOL = 0
 DACCVIOL = 0
 MUNSTKERR = 0
 MSTKERR = 0
 MLSPERR = 0
 MMARVALID = 0
 IBUSERR = 0
 PRECISE E = 0
 IMPRECISE = 0
 UNSTKERR = 0
 STKERR = 0
 LSPERR = 0
 BFARVALID = 0
 UNDEFINSTR= 1
 INVSTATE = 0
 INVPC = 0
 NOCP = 0
 UNALIGNED = 0
 DIVBYZERO = 0
SCB->DFSR = 0x00000000
SCB->MMFAR = 0x00000000
SCB->BFAR = 0x00000000
SCB->AFSR = 0x00000000
SCB->ABFSR = 0x00000000
 
Exception stack frame:
R0 0x20045c7c
R1 0x2006896c
R2 0x00000003
R3 0x00009a90
R12 0x20068634
LR 0x08007e87
PC 0x0800fd48
xPSR 0x61000000
 
 
 >>> forcing SYSTEM RESET

Pilous Droip
Senior
December 19, 2018

I try debug.

When I send close connection, I go here:

if (p == NULL)
	{
		es->state = ES_CLOSING;
		if(es->p == NULL)
		{
			tcp_recved(tpcb, p->tot_len);
		}
		else
		{
			//acknowledge received packet
			tcp_sent(tpcb, tcp_server_sent);
			//send remaining data
			tcp_server_send(tpcb, es);
		}
		ret_err = ERR_OK;
	}

And when I get here (line 6):

tcp_recved(tpcb, p->tot_len);

Program crash

__attribute((naked))
void HardFault_Handler(void)
{
	/* Determine MSP/PSP usage and pass the value as first argument
	 * to get the stack frame dump */
 
	__asm volatile (
		"tst lr, #4 \n"
		"ite eq \n"
		"mrseq r0, msp \n"
		"mrsne r0, psp \n"
		"bl HardFault_InfoDump");
}

And program is restart.

Piranha
Principal III
December 20, 2018

Don't do locking in callback functions! Also LWIP_TCPIP_CORE_LOCKING_INPUT is not required - it is optional and should be enabled when You know that You need it. LWIP_TCPIP_CORE_LOCKING is the one required with RTOS and SYS_LIGHTWEIGHT_PROT very desirable. Defaults in file opt.h are good in this regard.

Now I looked on Your code with more attention. By the way, tcp_recved() is the data acknowledge function, not tcp_sent() - wrong comment on line 142. And tcp_sent() only sets up callback function pointer so it's enough to do it once in tcp_server_accept() - line 143 is needless. Lines 181-187 are also needless, because the "else" section at line 188 does exactly the same. But the main problem is related to connection closing code...

https://www.nongnu.org/lwip/2_1_x/group__tcp__raw.html#ga8afd0b316a87a5eeff4726dc95006ed0

"The callback function will be passed a NULL pbuf to indicate that the remote host has closed the connection."

https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a780cfac08b02c66948ab94ea974202e8

"p The received data (or NULL when the connection has been closed!)"

So, when tcp_server_recv() is called with p == NULL, You should close the connection on Your side with tcp_close() and return ERR_OK. At that point You can't acknowledge or send data on it anymore. And it doesn't make sense anyway, because the actual network connection has already been closed. Actually everything related to ES_CLOSING seems to be broken and tcp_server_poll() is needless altogether. You should rework that connection closing logic, but start with lines 136-146.

Additionally I suggest setting up LWIP_PLATFORM_ASSERT() in such a way that it prints out (for example to ST-LINK's virtual COM port) the message, disables interrupts and goes into infinite loop, from which You can break out by changing a loop variable with a debugger. After that loop interrupts must be enabled again for debugging purposes. Actually such a function is useful for all code in a firmware, not only lwIP... =)