Custom echo function malfunctioning when using mutex

Hello all,
I am new at FreeRTOS
Want to ask some advice about this situation:
I am building a small application to scan QR codes. This consist of 4 simple tasks, each of which blink a LED at a different frequency, and actually are accessory tasks to test the scheduling is being done right.
The main function is waiting to receive data coming from the scanner to save it before sending it to the terminal using a custom echo function.
This receiving function is not actually running in a freeRTOS task, because it runs in the background based on DMA to send and receive the data. When data arrives, it is received and sent by DMA, no CPU resources are used here. No FreeRTOS scheduling either for this activity.
Sending data is direct, for receiving I use a callback function.
Now, the custom echo function, uses a function that sends data to terminal, which is as mentioned based on DMA. Below is the function I use to send the data:

static void dataSend(char* msg){
	uint8_t newSize = strlen(msg);
	memset(txDma, 0, BUFFERSIZE);
	memcpy(txDma, msg, newSize);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_4, newSize);
        LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_4);
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4);
	LL_mDelay(10);
}

The ISR for transmission is here:

void DMA1_Channel4_5_6_7_IRQHandler(void)
{
  if(LL_DMA_IsActiveFlag_TC4(DMA1))
  {
    LL_DMA_ClearFlag_TC4(DMA1);
		LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4);
		LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_4);
  }
}

Here is the echo function using dataSend:

void echo(char* fmt, ...){
  static char    echoString[BUFFERSIZE];
  static SemaphoreHandle_t echoMutex = NULL;
  va_list     a;
  if(NULL == echoMutex)
  	echoMutex = xSemaphoreCreateMutex();
  if(NULL == echoMutex)
  	return;
  xSemaphoreTake( echoMutex, portMAX_DELAY );
  va_start(a, fmt);
  vsnprintf(echoString, BUFFERSIZE, fmt, a);
  va_end(a);
  dataSend((char *)echoString);
  xSemaphoreGive( echoMutex );
}

Here is the ISR for reception:

if(LL_USART_IsEnabledIT_RTO(USART2) && LL_USART_IsActiveFlag_RTO(USART2)){
	LL_USART_ClearFlag_RTO(USART2);
	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);		
	rxCount = rxSize - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_5);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, rxSize);
	USART2_RTOCallback();		
  }

and here the callback function USART2_RTOCallback():

void USART2_RTOCallback(void){
	memset(rxData, 0, BUFFERSIZE);
	memcpy(rxData, rxDma, rxCount);
	rxData[rxCount] = '\0';
	echo("%s\r\n", rxData);
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);	
}

I have tested this echo function either without using a mutex and without using FreeRTOS at all and it works fine. However, when include all FreeRTOS mutex related lines of code, it prints only the first time and the next echo calls does not print anything. I cannot figure out where the problems might be. Should I create the mutex as a global or inside the echo function is Ok ?
Another question is, considering it works apparently normal without a mutex. Is it right to use it in that way or it might cause problems when scaling the code to more functionality or adding more tasks ?
Another problem is: I use LL_mDelay() inside the dataSend function because when I use vtaskDelay(…) function it also stops receiving data
Please give me some advices about how to go with this
Thank you in advance

The callback is invoked by the ISR. Basically in ISRs you have to use the FromISR tagged FreeRTOS API. Also in ISRs blocking functions like xSemaphoreTake are not possible and it’d be also problematic for the application behavior wasting too much time in an ISR.
Question is do you need mutexing at all ? Isn’t the code path in the ISR calling echo invoked exclusively for a specific interrupt ?

Thank you for your reply,
Yes, the last question is one of my doubts. I call echo function from the reception callback. It might happen that while receiving, another transmission start, but the transmission does not call echo function. Receptions are already mutually exclusive, i mean, I cannot be having two different receptions at the same time because when receiving, the interrupt is disabled and only enabled back when the reception process has finished. Does it means that using mutex here is unnecessary ?
But what if another task try to use echo function while reception is using it ?
Also, I found this in documentation: “Mutex type semaphores (those created using a call to xSemaphoreCreateMutex()) must not be used with this macro”(xSemaphoreGiveFromISR)

I’d agree that you don’t need to protect anything if you know that echo can’t be invoked concurrently.
xSemaphoreCreateMutex dynamically allocates memory from heap. This is usually not possible in ISRs and hence there is no FromISR version. And it’s also usually not necessary. A resource is better pre-allocated and just used later on also for performance reasons. In addition the mutex allocation itself isn’t mutex protected and would be affected by race condition problems. So this approach would be broken anyway.
BTW In ISRs you could use (non-blocking) critical sections using taskENTER/EXIT_CRITICAL_FROM_ISR to protect shared data.

Another, minor hint: In dataSend it could be more efficient and faster to check/wait for the DMA channel being idle/free to use again instead of blindly post-wait a fixed, empiric amount of time.
In other words I’d poll-wait for the DMA being completed at the beginning of dataSend, fire-up the DMA transfer and continue. Very likely next time when dataSend is invoked the previous transfer is completed and you don’t have to wait/delay at all.

Yes, you are right, waiting until the channel is free is the right way to do it. I have temporarily reduced it to 2ms, which is actually a protection time against data being sent continuously, and for my application, performance will not be seriously affected