-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
kernel: Allow to use k_sem_take() without MULTITHREADING #8368
kernel: Allow to use k_sem_take() without MULTITHREADING #8368
Conversation
There is no thread queue if CONFIG_MULTITHREADING=n is set. With the current implementation, _impl_k_sem_take() calls _pend_current_thread() that moves the current thread in the waiting queue. But when there is no MULTITHREADING support, there is only one thread. This change implements _impl_k_sem_take() as a busy loop when no MULTITHREADING support. It means the semaphore would have to be released in the ISR to exit the loop. This issue has been triggered while porting MCUBoot (no multithreading support) to my platform. Some drivers needed during the boot process were using semaphore. Signed-off-by: Olivier Martin <olivier.martin@proglove.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, you want to use semaphores so you can wait for an interrupt delivery? Why can't you just spin in the app?
This seems like a lot of code to do just that. CONFIG_MULTITHREADING is maybe poorly defined, but it's not really intended that IPC primitives work there. The idea was to have an absolutely minimal core of bootstrap code to permit writing things like bootloaders. Semaphores don't really fit that model.
@Olivier-ProGlove which driver is mcuboot using that causes the issue? |
Codecov Report
@@ Coverage Diff @@
## master #8368 +/- ##
=======================================
Coverage 64.61% 64.61%
=======================================
Files 422 422
Lines 40284 40284
Branches 6800 6800
=======================================
Hits 26030 26030
Misses 11121 11121
Partials 3133 3133
Continue to review full report at Codecov.
|
@andyross As you might know MCUBoot bootloader has been ported to Zephyr. And Zephyr drivers can be used by the bootloader without any changes which is really great! But MCUBoot does not support multi-threading. When I ported MCUBoot to our platform, the bootloader was failing to start. After investigating the issue I discovered it was due to a semaphore used by one of our drivers. At that time, I have not thought if the semaphore was relevant or not in our driver. But I was thinking semaphores could be used at other places in the code path and I did not want to redesign all our drivers. That the reason of my patch. If we do not want to enable semaphore in a multithreaded environment then we should protect the semaphore API with:
@mike-scott sorry I do not remember which drivers was it but I can check if you want even if I think it is one of our internal drivers. |
@mike-scott Most of the flash drivers use semaphores. In our case it was the winbound driver (w25qxxdv) but if you do a grep for k_sem you can see that qmsi, sam0, nrf, stm32, mcux, nios2_qspi also use semaphores for syncing. @carlescufi This is the patch we talked about on IRC @andyross flash drivers are essential for bootloaders, so I think we either have to live with semaphores in the minimal core or the drivers have to be changed accordingly. |
@JoeHut if there are no ram/rom constraints, you should be able to build mcuboot with multithreading, single thread was added just to minimise footprint. my concern is that this might not end with semaphores and other object will be pulled in to support other drivers, so single thread and the need for it becomes useless. |
But semaphores don't actually work with CONFIG_MULTHREADING=n! They work only if the count never goes to zero, or if the count is only ever incremented from an ISR. And that's not a semaphore as understood by code written to a semaphore API. Nor is it very useful, compared to simple spin waiting. I mean, the only value that MULTITHREADING=n has is size. And this is adding functionality to the image just to get something that you can't really get reliably just seems like the wrong tactic. Stated alternatively: if your flash drivers were written to assume OS synchronization primitives and threading, you probably need to be running them under an OS kernel. The docs specifically tell you "Kernel objects will most probably not behave as expected, especially with regards to pending". I think this is a great example for why. Can you dig more carefully through your drivers, I think that's where you want to do the port to CONFIG_MULTITHREADING=n. They need to know they can't use a real semaphore. (Finally: dollars to donuts says that all those semaphore calls in your drivers are doing is locking anyway, which you can just remove, right?) |
IMO it also has security advantages: less code == smaller attack surface. |
Wouldn't that require different versions of the same drivers for use in MCUboot and within a multithreaded Zephyr application? |
|
@andyross I get your point. The patch was a fix for us but I understand that it is not the proper way to go. Fine for me. If I get it right the alternatives are:
|
Is there a solution where semaphores degrade gracefully in the absence of multithreading, so MCUboot users don't have to audit all of their Zephyr drivers now and forever for this sort of thing? |
Would this work? Maybe we really should make sempahores work in a single-threaded environment since they also have use-case to synchronize with ISRs. |
@andrewboie @mbolivar Yeah, it sort of depends on what the driver is doing with those semaphores. We can't have a general "semaphore" object, because that inherently implies threading. Two possibilities that have been discussed so far:
All I'm saying is let's not try to fix this by fooling the driver into thinking semaphore actually work, because they don't. |
Also @mbolivar: you absolutely have to audit any driver you use from a MULTITHREADING=n environment. It's not "Zephyr" at that point. The APIs don't work, or work partially, or were never tested. The docs specifically warn about that. No free lunch. :) |
|
What would you recommend as a way for driver ISRs to unblock waiting code (be it one thread out of many or the hard loop if multithreading=n)? Polling "works", but wouldn't power management be a concern? |
The code submitted above is fundamentally just a busy loop too. If you need to wait for an ISR from user code, spinning is your only option (beyond writing some arch-specific code in the loop, of course). We have PM code as part of the idle thread, but of course there is no idle thread. FWIW, I did a super-quick audit of the semaphore usage of the files under drivers/flash:
So it seems to me that this problem should just go away without need for polling nor semaphore API abuse. [1] -ELINENOISEINFILENAME, couldn't read |
@andyross I get your point, that is a fair statement. But in this case, let's disable all kernel API that will not be supported in Or we enable multithreading in MCUBoot (https://github.com/runtimeco/mcuboot/blob/master/boot/zephyr/prj.conf#L32) to prevent this kind of patch in the future in Zephyr's project: |
I suppose my question was really about the availability of a single kernel API for waiting on an ISR that could be used in both multithreaded and non-multithreaded mode (since the flash drivers must work in both situations) that wouldn't imply adding a bunch of ifdefs to stitch things together.
For your reference: IOW, there was nothing there before; this is a band-aid to limp along until someone from NXP has time to fully implement the flash API.
I think you mean
There's no radio in use to sync with when running this driver in MCUboot. There is later on when the chain-loaded zephyr image boots up and wants to use the same driver at the same time as communicating via Bluetooth. Hence, the ...NRF_RADIO_SYNC knob is n when building the driver for MCUboot, and y in applications that need it for BT. So I'm not sure how that applies.
Sorry If I'm being dense, but it seems like removing the locks outright and replacing them with busy loops means that flash drivers will spin unnecessarily when multithreading is enabled. What am I missing here? |
@mbolivar : I think you might be conflating the two cases: If you have some C code that needs to "wait" for an IRQ to be delivered before returning: obviously you can't return out of the function, so you either have to context switch away to do something else, or you have to spin. No other options (except maybe spinning + using some arch-specific mechanism to gate power like asm("hlt") or whatever). But none of the flash drivers need to do that. Instead, all they need to so is use the semaphore as a mutex to prevent other threads on the system from coming along and stomping on the flash operation while it's in progress (I'm guessing this is done with a semaphore and not irq_lock() because flash operations are comparatively slow. But in !MULTITHREADING, there are no other threads. You don't replace those with busy loops, you replace them with no code at all, because there is no one for you to lock against. |
There is no thread queue if CONFIG_MULTITHREADING=nis set. Using Event Queue Polling and Status Triggering (State Machines) May Be Easier to Implement Not knowing whether this mechanism will be provided? |
Yes so that is what is done in some of our drivers. The point is whether we want to provide a "spinning + flag" mechanism for managing this:
|
@andyross will Context: CC @nvlsianpu |
@carlescufi I have come to the same suspicions as mcuboot serial-recovery mode stops responding since #8370 was merged. |
@carlescufi I'd strongly expect the k_queue/fifo/lifo behavior would be the same as semaphores (which are just queues without data, semantically). You can't have a working k_fifo() without threads. You could (in theory) have one that was never empty, or one that was only ever inserted to from an ISR. Whether the code works or not is unknown. As far as the need for a "power-friendly wait for ISR" API, I'm not opposed. I just don't like abusing the semaphore API to provide it. Note again that the flash drivers don't need that, though. |
@andyross understood. We will replace the |
Thinking about this on the "new API" level (which would require modifying drivers of course): The "locking" needed by the flash drivers can be solved with a variant semaphore API. Consider:
These implement a semaphore with restricted semantics (Of course in
That API can be supported in both MULTITHREADING and !MULTITHREADING modes, because we're guaranteed it's a noop when there are no other threads to contend on the lock. A "wait for ISR" API would still not be mappable directly to a sempahore, you'd have to write new code and select it via #ifdef. But the calls could be simple, e.g. (the naming here is deliberately designed to look like an x86 MONITOR/MWAIT, but lots of archs have similar tricks):
|
can this be closed now? |
From our side, yes. I think it still should be documented in MCUboot that the selected drivers (e.g. flash, uart for serial recovery) have to be reviewed that they work without multithreading. |
This sounds like dependency on MULTITHREADING should be added to any drivers which requiress multithreding. |
I am not sure we found a solution for this issue. As far as I can see the status of non-threaded firmware is undefined with Zehpyr. And the documentation says MULTITHREADING=n is risky: http://docs.zephyrproject.org/reference/kconfig/CONFIG_MULTITHREADING.html. We know there are issues when non-threaded firmwares use some/most Zephyr kernel API. MCUBoot seems to be the bootloader solution chosen by Zephyr project at the moment. MCUBoot uses What should be the message for MCUBoot? "We do not guarantee MCUBoot Zephyr's backend being fonctional. Do not use MCUBoot Zephyr's backend on production device without being aware of See @carlescufi recent issue "Right now this is completely preventing proper use of MCUboot.": #8393 (comment) I see three possible solutions:
|
Some of this is semantics. There will never be a generic solution to this issue where you get to have MULTITHREADING=n and still use whatever APIs you want. If you want APIs, you use Zephyr. But for all the APIs that have actually been discussed, there are IMHO very acceptable solutions available (see above). I'd be happy to work on something like that, or someone else could. Neither the locking nor monitor features are large or complicated (though implementing monitor in a non-toy way requires platform-specific assembly). But that analaysis needs to be repeated for every new driver or feature you want to suck into MULTITHREADING=n. No free lunch. |
I agree with @oliviermartin, that we should disable all API which cannot be used without threads when |
Be aware SPI uses semaphore: https://github.com/zephyrproject-rtos/zephyr/blob/master/drivers/spi/spi_context.h#L27 |
I think we will go for this option and remove the single thread support which was experimental to start with, see #9808 |
Thanks a lot @nashif for following on this pull-request 👍 |
There is no thread queue if
CONFIG_MULTITHREADING=n
is set.With the current implementation,
_impl_k_sem_take()
calls_pend_current_thread()
that moves the current thread in the waiting queue.But when there is no MULTITHREADING support, there is only one thread.
This change implements
_impl_k_sem_take()
as a busy loop when no MULTITHREADING support.It means the semaphore would have to be released in the ISR to exit the loop.
This issue has been triggered while porting MCUBoot (no multithreading support) to my platform. Some drivers needed during the boot process were using semaphore.
Signed-off-by: Olivier Martin olivier.martin@proglove.com