zephyrproject-rtos / zephyr

Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
https://docs.zephyrproject.org
Apache License 2.0
10.84k stars 6.6k forks source link

Bluetooth: ATT: Processing and responding to ATT requests from another task #29270

Open e-rk opened 4 years ago

e-rk commented 4 years ago

Is your feature request related to a problem? Please describe. The current implementation of ATT/GATT layers forces the user to handle the ATT read/write requests in the read/write callback.

This is not ideal, because those callbacks run from the context or the "BT RX" task within Zephyr's BT stack. Some third party libraries I work with perform a set of complex operations during characteristic read/write request, are unfit for operation from multiple tasks, and require a rather large stack capacity on each tasks that runs it. There are other concurrent tasks that come into play and we try to consolidate entire processing to just a single one by means of queuing events.

The problem is that ATT read/write requests cannot be queued for later processing. Once we are outside the callback, it is too late to do anything.

Allowing the characteristic read/write to be processed from another task in some way would help with the above mentioned problems.

Describe the solution you'd like The solution would be to allow the user to put a request into a user-defined queue/buffer for later processing. A new API call would allow the user to explicitly respond to a request identified by a conn/attr pair. The fact of delayed processing could be communicated to Zephyr's stack by means of returning a special value from the read/write callbacks, which would hopefully help avoid changes in the existing public API. Zephyr would then place the request metadata that is necessary to form a response into some internal buffer.

Considerations The question arises about the sequentiality of requests. The advantage of current approach is that (I suppose) the ATT layer does not care, if the client is non-compliant with ATT specification and sends a new request before a response to the previous one was given, as the Bluetooth task processes the packets one at a time and responds to them immediately. Possible action would be to queue the new request and not do anything with it, until previous request is fully processed.

Another advantage of the current approach is that the Bluetooth stack gives little room for error to the user. I can imagine a situation, where the application does not send any response for whatever reason. One possible outcome would be that user notices that no new request callbacks arrive and fixes the bug in the application.

Additional context Simple sequence diagram showing how a request can be processed: Queuing

theob-pro commented 6 months ago

Bluetooth WG meeting (2024-04-18): Discussed as part of https://github.com/zephyrproject-rtos/zephyr/issues/66023.

Thalley commented 6 months ago

To expand on the above:

I would modify

typedef ssize_t (*bt_gatt_attr_write_func_t)(struct bt_conn *conn,
                         const struct bt_gatt_attr *attr,
                         const void *buf, uint16_t len,
                         uint16_t offset, uint8_t flags);

to

typedef void (*bt_gatt_attr_write_func_t)(struct bt_conn *conn,
                      const struct bt_gatt_attr *attr,
                      const void *buf, uint16_t len,
                      uint16_t offset, uint8_t flags);

and add a

int bt_gatt_write_rsp(struct bt_conn *conn, const struct bt_gatt_attr *attr, uint8_t att_err)

But since that would break the stable API, a stepping stone would be to keep bt_gatt_attr_write_func_t as is, and then add a temporary #define BT_GATT_NO_RESPONSE 0x1234 that the application can return in the callback to indicate it's intent on calling bt_gatt_write_rsp itself (either in the callback itself or later).

Once the type change can be finalized, the macro can be removed. The value of the BT_GATT_NO_RESPONSE effectively just need to be any value > 512 as, as any value > 512 would otherwise be an invalid value in ATT/GATT

jimmyzumthurm commented 6 months ago

Hi,

This feature would help us greatly as well. We have a BLE communication protocol that is using GATT Write Request - Write Response mechanism for Flow Control to transmit data between phone and a slower Zephyr based peripheral device. Most the data exchange is done using GATT Write Without Response, but each X packets, mobile sends GATT Write Request and it is expected that the device answers with Response only after the previous packets have been flushed.

With current Host implementation it is not possible to do it properly since we need to send the response from the write callback. Ideally it would be the thread handling the data that would send the Response when it's ready.

bjohnsonpt commented 3 months ago

Hello,

Wanted to throw our two cents in and say this feature would help us as well. The previous examples have been focusing on GATT writes, but this would be helpful in certain GATT read situations. We have a dual role device that sits in between a central device and a peripheral device. This dual role device acts as an arbiter between the central and peripheral. We want to be able to supply a GATT Read request from the central with the data returned from a GATT Read request to the peripheral, all through the arbiter.

Current implementation does not allow for this interaction as the GATT Read coming from the central needs to be blocked within the arbiter while it does its GATT read of the peripheral. The results of the peripheral read cannot make it back to the arbiter as it is handled in the same blocked thread. This necessitates two Read requests from the central; the first returns with no data as the arbiter sends a read request to the peripheral, the second can then return the data previously requested.

If we were able to delay the read response to the central without blocking the BT_RX/SYSWORKQ thread, we could wait on the callback from the peripheral read and send the proper data to the central as part of its original GATT Read request.