Mutex to guard every hardware resource?

qrthur wrote on Wednesday, April 19, 2017:


I am working on a relatively big embedded system (about 7/8 external peripherals connected via i2c/spi/sdio/uart/usb/…), on a single core mcu (cortex-m4)

It seems that when a context switch occurs, if I am performing something with a hardware bus for example, then it will fail. It is not surprising. But I am not sure what would be the best way to solve this problem. Should I have 1 single mutex, that guards every hardware resource? Or should I just use the functions vTaskSuspendAll and xTaskResumeAll instead? Or even worst, taskENTER_CRITICAL? (i don’t think hardware interrupts break my hardware depend code, because they are so quick and the processing happens in another task)

I am a bit lost and some guidance would be very much appreciated. I kind of feel that with what ever solution I have above, there is always the same problem, is that 95% of my code will be guarded. And then, I don’t know where to put the calls to take/give, in my hardware drivers them selves? Or in my application code before calling hardware functions?


rtel wrote on Wednesday, April 19, 2017:

The correct mutual exclusion method to use depends on a number of
factors, such as how long (in time) the region of code being protected
will take to execute. I would recommend starting by reading through the
relevant chapters in the book:

richard_damon wrote on Thursday, April 20, 2017:

I tend to put a seperate mutex on each muulti-deviice channel like an I2C bus so that when one task is doing the I/O, other tasks that want to talk to a device get blocked until the channel is free. You definately don’t want to use something like vTaskSuspendAll, as while you are waiting for the I/O to complete you WANT other tasks to use the CPU (you are using interrrupt driven drivds for the I/O I presume).

I put the calls to the mutex within the driver (very near the beginning/end) so with the protected area it doesn’t need to worry about concurent access.

If a device is fast enough that you want have time to switch to another task while it is running, then it might be reasonable to use Suspend/Resume all, but a mutex might still be advisable if a higher priority task may need the CPU in the middle of a transfer.

hs2sf wrote on Thursday, April 20, 2017:

Another approach is to have a ISR + I/O task pair making up a device driver for each specific HW interface. Higher level application logic runs in it’s own task(s) and communicate with I/O tasks e.g. using (I/O command) queues.
This would drastically reduce the need of (low level) HW interface related locking when doing the actual I/O. Also this scheme allows prioritizing HW interfaces if needed by assigning desired priorities to interrupt and I/O task.

qrthur wrote on Thursday, April 20, 2017:

Some helpful info here, cheers

@HS2: I thought of doing kind of what you propose, but it seems to add a lot of complexity/overhead, having tasks going back and forth too often in my case. On the other hand, it also look like a very clean design and a fine way of splitting logic code and hardware work. I don’t think I will apply this method for my current project, as I would be running out of time, but I keep this in mind :slight_smile:

@Richard Damon: Alright, so for interrupt driven calls, it is indeed a no brainer, I will use mutexes. However, using 1 mutex per bus will work?
If we take this simple example: code1 start using busA, gets preempted, code2 uses busB, then code1 resumes, and finishes work with busA
My assumption would be that code1 could fail if too much time happened? Which is why I would only use 1 single guard for every bus. Tell me what you think! (I am aware it depends on many things, type of bus, delays, etc.)

richard_damon wrote on Thursday, April 20, 2017:

If the I/O transfer is being done with interrupts, then the transfer typically doesn’t get stopped by another task using another bus. (For one thing, sending the data doesn’t use that much time, so once code2 starts bus B, then it pends waiting for something to do, giving code1 time to get in for any little snippets of task level work to do the transfer.

Also, busses like I2c/spi tend not to have tight timeout requriements, and what you have tend to be solvable by just requiring that the code sending needs too have all the data ready before making the request, at which point the ISR/DMA handles all the transfers. If you have a bus that does require all the data to go out in one packet without gaps, this is especially important, it is much simpler to just require all the data that goes out together to be available before starting. If you do have devices that you can’t do this for some reason, then you have given yourself a much tougher job, and those tasks that do this are probably top priority so code2 CAN’T interrupt them because of priority rules.

qrthur wrote on Thursday, April 20, 2017:

I see. I think I know where I’m headed now, so thank you very much!