-
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
extmod/btstack: Schedule ops for background completion. #6159
Conversation
93fb814
to
bbcb4bf
Compare
ports/unix/mpthreadport.c
Outdated
@@ -65,7 +65,8 @@ STATIC pthread_key_t tls_key; | |||
// The mutex is used for any code in this port that needs to be thread safe. | |||
// Specifically for thread management, access to the linked list is one example. | |||
// But also, e.g. scheduler state. | |||
STATIC pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; | |||
STATIC pthread_mutex_t thread_mutex; | |||
STATIC pthread_mutexattr_t thread_mutex_attr; |
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.
Does this attr struct need to be in global data? Can't it be temporary, on the stack in mp_thread_init()
? Is there any reference that says either way?
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.
Looking at the implementation, yes, seems fine to put it on the stack. Also, there's an example of doing exactly that in my copy of APUE.
extmod/modbluetooth.c
Outdated
} | ||
if (entry) { | ||
if (value_len > entry->data_alloc) { | ||
entry->data = m_new(uint8_t, value_len); |
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.
This may raise an exception which would leave the function without releasing the bluetooth lock (yes this would be much clearer without NLR...). I think it can be fixed pretty easily by using m_new_maybe()
and then explicitly raising an OOM error after releasing the lock. Or just returning MP_ENOMEM
. But the latter requires all callers to check the return value if they can't guarantee the write won't need to allocate.
Also, related, do we need to check that all calls of mp_bluetooth_gatts_db_write()
do not have the bluetooth lock?
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.
Also, related, do we need to check that all calls of
mp_bluetooth_gatts_db_write()
do not have the bluetooth lock?
I checked this. It looks ok, the bluetooth lock is only held in sections of code that are "low level" or call into BTstack. Ie there are no calls into the uPy runtime (that could raise).
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.
There are very few callers of these methods, and most already checked the return value. I've made all uses do so.
} | ||
assert(btstack_linked_list_remove(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t*)pending_op)); | ||
MICROPY_PY_BLUETOOTH_EXIT | ||
} |
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.
Is it possible to explicitly m_del()
the pending_op->buf
here, because we were responsible for allocating it and know it'll never be used again?
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.
The buf is inline in the pending_op struct. But we can del the whole op.
I've split "remove (and optionally del)" from "find a pending op". Anywhere that attempts to unref a pending op, that isn't in interrupt context, now also frees it.
DEBUG_EVENT_printf("btstack_finish_pending_operation: found value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len); | ||
assert(btstack_linked_list_remove(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t*)pending_op)); | ||
MICROPY_PY_BLUETOOTH_EXIT | ||
return pending_op; |
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.
as above, is it possible to explicitly free the buf
before returning?
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.
The caller of this function needs it (e.g. event_type == GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE
in the packet handler). But all the callers can free it.
|
||
att_server_request_to_send_notification(&pending_op->context_registration, conn_handle); | ||
// We don't know how many bytes will be sent. | ||
*value_len = 0; |
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.
hmm, returning 0 here means the caller always needs to test for this value to know if the notify was sent immediately or is pending... but there's really no other way to do it. But the docs don't actually specify any return value for gatts_notify()
so maybe it should just return None
in all cases?
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.
Yes, this was never reliably used anywhere. I've made gatts_notify
always return None
as suggested.
return 0; | ||
} else { | ||
return btstack_error_to_errno(err); | ||
} | ||
} | ||
|
||
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { |
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.
I just noticed: it doesn't look like this function is ever used??
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.
I've added gatts_indicate(conn_handle, value_handle)
to modbluetooth. (And added to the ble_characteristic.py multitest)
Does this mean you can queue an arbitrary number of notify/indicate data packets? |
This mutex is used to make the unix port behave more like bare metal (i.e. it allows "IRQ handlers" to run exclusively). In order to match the bare-metal behavior of MICROPY_BEGIN_ATOMIC_SECTION, this mutex needs to be recursive.
gatts_notify/indicate will now run in the background if the ACL buffer is currently full. gattc_write(mode=0) (no response) will now allow for one outstanding write. gattc_write(mode=1) (with response) will now copy the buffer so that it can't be modified by the caller. All four paths also now track the buffer while the op is in progress.
bbcb4bf
to
7e3dd8c
Compare
Added fixes as a new commit so they can be reviewed independently. Will rebase and squash into the other commits after review. |
7e3dd8c
to
0ab90c8
Compare
Thanks for updating, it all looks very good. Only minor thing I noticed is that the new |
Also clarify behavior of `gatts_notify` and add some TODOs about adding an event for indication acknowledgement.
The goal of this PR is to allow using
ble.gatts_notify()
at any time, even if the stack is not ready to send the notification right now.This also addresses the same issue for
ble.gatts_indicate
andble.gattc_write
(without response). The result is that you can now:Unfortunately the three cases notify/indicate, write-with-response and write-without-response all require slightly different implementations.
This PR also addresses two other issues: