-
-
Notifications
You must be signed in to change notification settings - Fork 63
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
pbio/os: Prototype simpler and unified async OS. #298
Open
laurensvalk
wants to merge
3
commits into
master
Choose a base branch
from
os
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The goal is to: - Add error return value to all protothreads. - Reduce complexity and code size. - Avoid risk of overruning event queue. - Use single poll flag and no broadcasting between threads. - Cross-platform handling of events during sleep. - Make stm32, ev3, nxt all work the same way. For now the new OS also drives the contiki event loop so we can migrate processes one by one instead of breaking everything at once.
Idling with WFI was previously implemented in the MicroPython HAL. Now that we moved it to pbio/os in a platform agnostic way, we can fix this longstanding open REVISIT note.
This is a simple example to show that we can convert the processes one at a time.
Example usage: pbio_error_t pbio_os_wait_example(pbio_os_state_t *state, uint32_t duration) {
static pbio_os_timer_t timer;
ASYNC_BEGIN(state);
printf("Going to wait for %d ms\n", duration);
// We can return errors, parse args etc.
if (duration > 2000) {
return PBIO_ERROR_INVALID_ARG;
}
// This is a nice convenience wrapper I always missed.
AWAIT_MS(state, &timer, duration);
ASYNC_END(PBIO_SUCCESS);
}
pbio_error_t pbio_os_sub_example(pbio_os_state_t *state) {
static pbio_os_state_t child;
static uint32_t counter;
pbio_error_t err;
ASYNC_BEGIN(state);
for (counter = 0; counter < 100; counter++) {
AWAIT(state, &child, err = pbio_os_wait_example(&child, 100 * counter));
if (err != PBIO_SUCCESS) {
printf("Something bad happened!\n");
return err;
}
}
// Return arbitrary error code. Macro is needed to close up the } bracket just like in Contiki.
ASYNC_END(PBIO_SUCCESS);
}
// Outermost "process thread" looks the same but must only have the state argument
pbio_error_t pbio_os_example_thread(pbio_os_state_t *state) {
static pbio_os_state_t child;
pbio_error_t err;
ASYNC_BEGIN(state);
// Await and get/use return value of awaited thread.
AWAIT(state, &child, err = pbio_os_sub_example(&child));
if (err != PBIO_SUCCESS) {
return err;
}
ASYNC_END(PBIO_SUCCESS);
} |
Download the artifacts for this pull request: |
Similarly, the following: PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) {
PT_BEGIN(pt);
if (!msg || !length) {
*err = PBIO_ERROR_INVALID_ARG;
PT_EXIT(pt);
}
if (uart->tx_buf) {
*err = PBIO_ERROR_BUSY;
PT_EXIT(pt);
}
uart->tx_buf = msg;
uart->tx_buf_size = length;
uart->tx_buf_index = 0;
if (timeout) {
etimer_set(&uart->tx_timer, timeout);
}
uart->USART->CR1 |= USART_CR1_TXEIE;
// Await completion or timeout.
PT_WAIT_UNTIL(pt, uart->tx_buf_index == uart->tx_buf_size || (timeout && etimer_expired(&uart->tx_timer)));
if (timeout && etimer_expired(&uart->tx_timer)) {
uart->USART->CR1 &= ~(USART_CR1_TXEIE | USART_CR1_TCIE);
*err = PBIO_ERROR_TIMEDOUT;
} else {
etimer_stop(&uart->tx_timer);
*err = PBIO_SUCCESS;
}
uart->tx_buf = NULL;
PT_END(pt);
} Becomes: pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) {
ASYNC_BEGIN(state);
if (!msg || !length) {
return PBIO_ERROR_INVALID_ARG;
}
if (uart->tx_buf) {
return PBIO_ERROR_BUSY;
}
uart->tx_buf = msg;
uart->tx_buf_size = length;
uart->tx_buf_index = 0;
if (timeout) {
pbio_os_timer_set(&uart->tx_timer, timeout);
}
uart->USART->CR1 |= USART_CR1_TXEIE;
// Await completion or timeout.
AWAIT_UNTIL(state, uart->tx_buf_index == uart->tx_buf_size || (timeout && pbio_os_timer_is_expired(&uart->tx_timer)));
uart->tx_buf = NULL;
if (timeout && pbio_os_timer_is_expired(&uart->tx_timer)) {
uart->USART->CR1 &= ~(USART_CR1_TXEIE | USART_CR1_TCIE);
return PBIO_ERROR_TIMEDOUT;
}
ASYNC_END(PBIO_SUCCESS);
} Error handling at the start and towards the end looks quite a bit more elegant. Awaiting on the condition remains basically the same. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overall, the goals are to reduce complexity, reduce code size, and make it more cross platform.
In this PR, the new approach still drives the original
pbio
event loop as well, so we can convert one process at a time without breaking things. The charger process is done here as a simple test to illustrate what it looks like. Once all processes have been converted and everything is working, we could drop the Contiki dependency.Not everything we need is implemented yet, but it is already easy to see that
pbio/os
is much easier to get into than the full contiki library. Mainly because we only implement what we need, but also due to the simplifications introduced below.Returning errors from protothreads is currently cumbersome. This modification allows protothreads to return error codes.
PBIO_ERROR_AGAIN
essentially takes the role ofPT_YIELDED
/PT_WAITING
. Everything else indicates completion with the respective error or success.Contiki partially distinguishes (but not really) between waiting and yielding. In practice, both yield and the latter yields at least once. And then both macros exist separately for processes and protothreads so you end up with four variants that are almost the same but subtly different. I think we can do everything with a single set of
AWAIT
macros.The contiki event timers are more complicated than we need for our use case. Currently, a timer tick first polls the timer process and then posts N events depending on the number of active timers. This is not be safe if we have more timer events than fit in the event queue. Also, etimers use the global
PROCESS_CURRENT
which requires hacks such asPROCESS_CONTEXT_BEGIN
when accessing them from elsewhere.Instead of using various events and polling individual processes throughout, I think we may be able to work with just a single
pbio_os_request_poll()
function. Then all processes will check their current wait condition. This should not be that much more computationally expensive than checking theneeds_poll
flag on each process (and it may be less due to overall simplicity).We currently use broadcasting between processes in a few places, which can be hard to follow and risks overflowing the event queue as already stated in a few existing
REVISIT
s around the code. We have recently eliminated this for the uart process. We should be able to do this for the status change too.We can move some of the logic for handling pending events and entering sleep mode to
pbio
instead of having it inmphalport
. Aside from hooks such asenable_irq
it becomes platform agnostic. This also brings the virtual hub a bit closer to the embedded hubs. It also unifies the EV3 and NXT with Powered Up.