Peripheral: handle dict requests on a dedicated worker thread
Inflating a 16KB string block inside the BT RX thread overflowed its stack and froze the peripheral on the first steno stroke. The GATT write callback now only copies the request (<= 7 bytes) and submits to a 4KB-stack work queue; the worker resolves and notifies. Single request in flight, busy requests dropped (central times out and retries). GATT service + handlers now compile on the peripheral only. Inflate Huffman tables made static (single-context) to cut per-call stack use ~2KB. Host round-trip re-verified: 42,000 vectors, 0 mismatches.
This commit is contained in:
parent
728cdaf432
commit
c0756e6428
2 changed files with 95 additions and 19 deletions
|
|
@ -191,7 +191,8 @@ static int dynamic_tables(struct bitstream *s,
|
|||
static const uint8_t order[19] = {
|
||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
||||
};
|
||||
uint8_t lengths[MAX_CODES];
|
||||
/* Static to keep embedded stack use minimal (single-context). */
|
||||
static uint8_t lengths[MAX_CODES];
|
||||
uint32_t hlit, hdist, hclen;
|
||||
|
||||
if (bits(s, 5, &hlit) < 0 || bits(s, 5, &hdist) < 0 ||
|
||||
|
|
@ -214,7 +215,7 @@ static int dynamic_tables(struct bitstream *s,
|
|||
lengths[order[i]] = (uint8_t)v;
|
||||
}
|
||||
|
||||
struct huffman clcode;
|
||||
static struct huffman clcode;
|
||||
if (construct(&clcode, lengths, 19) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -297,7 +298,11 @@ int steno_inflate(const uint8_t *src, size_t src_len,
|
|||
dst_len += len;
|
||||
s.pos += len;
|
||||
} else if (type == 1 || type == 2) {
|
||||
struct huffman lencode, distcode;
|
||||
/* Static: ~1.2 KB combined — too large for the caller's
|
||||
* stack on embedded threads. Decoder is single-context
|
||||
* (one worker on the peripheral; host tests are
|
||||
* single-threaded). */
|
||||
static struct huffman lencode, distcode;
|
||||
int ret = (type == 1) ? fixed_tables(&lencode, &distcode)
|
||||
: dynamic_tables(&s, &lencode, &distcode);
|
||||
if (ret < 0) {
|
||||
|
|
|
|||
103
src/split_dict.c
103
src/split_dict.c
|
|
@ -38,8 +38,7 @@ static uint8_t rx_buf[sizeof(struct steno_response_pkt) + SPLIT_DICT_MAX_TEXT];
|
|||
static uint16_t rx_len;
|
||||
static uint8_t seq_counter;
|
||||
|
||||
/* Notify buffer (peripheral side) */
|
||||
static uint8_t notify_buf[sizeof(struct steno_response_pkt) + SPLIT_DICT_MAX_TEXT];
|
||||
|
||||
|
||||
/* Cache instance */
|
||||
static struct split_cache dict_cache;
|
||||
|
|
@ -66,7 +65,36 @@ static void make_resolve_key(uint32_t slot, uint8_t dict, uint32_t key[2])
|
|||
key[1] = slot;
|
||||
}
|
||||
|
||||
/* --- GATT Write Callbacks (peripheral side handlers) --- */
|
||||
/* --- Peripheral side: request worker ---
|
||||
*
|
||||
* Requests arrive in the BT RX thread, whose stack is far too small to
|
||||
* inflate 16 KB string blocks (and blocking it starves the link). The
|
||||
* write callback only copies the packet and hands it to a dedicated
|
||||
* work queue; the worker resolves the request and notifies the reply.
|
||||
*/
|
||||
|
||||
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) && \
|
||||
!defined(CONFIG_STENO_DEBUG_NO_GATT)
|
||||
#define STENO_DICT_PERIPHERAL 1
|
||||
#endif
|
||||
|
||||
#ifdef STENO_DICT_PERIPHERAL
|
||||
|
||||
#define STENO_REQ_MAX 16
|
||||
|
||||
struct steno_req_slot {
|
||||
struct bt_conn *conn; /* reference held until reply sent */
|
||||
const struct bt_gatt_attr *attr; /* static service attribute */
|
||||
uint8_t buf[STENO_REQ_MAX];
|
||||
uint16_t len;
|
||||
};
|
||||
|
||||
static uint8_t notify_buf[sizeof(struct steno_response_pkt) + SPLIT_DICT_MAX_TEXT];
|
||||
static struct steno_req_slot req_slot;
|
||||
static atomic_t req_busy;
|
||||
|
||||
K_THREAD_STACK_DEFINE(steno_workq_stack, 4096);
|
||||
static struct k_work_q steno_workq;
|
||||
|
||||
static void send_response(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
uint8_t seq, int ret, const char *text)
|
||||
|
|
@ -134,28 +162,64 @@ static ssize_t handle_resolve(struct bt_conn *conn,
|
|||
return len;
|
||||
}
|
||||
|
||||
static void req_work_handler(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
|
||||
struct bt_conn *conn = req_slot.conn;
|
||||
const struct bt_gatt_attr *attr = req_slot.attr;
|
||||
const uint8_t *bytes = req_slot.buf;
|
||||
uint16_t len = req_slot.len;
|
||||
|
||||
switch (bytes[0]) {
|
||||
case STENO_MSG_GET_STRING:
|
||||
handle_get_string(conn, attr, bytes, len);
|
||||
break;
|
||||
case STENO_MSG_RESOLVE:
|
||||
handle_resolve(conn, attr, bytes, len);
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("Unknown msg type: 0x%02x", bytes[0]);
|
||||
send_response(conn, attr, len >= 2 ? bytes[1] : 0, -EIO, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
bt_conn_unref(conn);
|
||||
req_slot.conn = NULL;
|
||||
atomic_clear(&req_busy);
|
||||
}
|
||||
|
||||
static K_WORK_DEFINE(req_work, req_work_handler);
|
||||
|
||||
static ssize_t dict_req_write_cb(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
const void *buf, uint16_t len,
|
||||
uint16_t offset, uint8_t flags)
|
||||
{
|
||||
const uint8_t *bytes = buf;
|
||||
|
||||
if (len < 1) {
|
||||
if (len < 1 || len > STENO_REQ_MAX) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
}
|
||||
|
||||
switch (bytes[0]) {
|
||||
case STENO_MSG_GET_STRING:
|
||||
return handle_get_string(conn, attr, buf, len);
|
||||
case STENO_MSG_RESOLVE:
|
||||
return handle_resolve(conn, attr, buf, len);
|
||||
default:
|
||||
LOG_WRN("Unknown msg type: 0x%02x", bytes[0]);
|
||||
return BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED);
|
||||
/* One request in flight: the central blocks per request, so a
|
||||
* second arrival means it already timed out — drop this one and
|
||||
* let it retry. */
|
||||
if (atomic_set(&req_busy, 1) != 0) {
|
||||
LOG_WRN("Request dropped: worker busy");
|
||||
return len;
|
||||
}
|
||||
|
||||
req_slot.conn = bt_conn_ref(conn);
|
||||
req_slot.attr = attr;
|
||||
memcpy(req_slot.buf, buf, len);
|
||||
req_slot.len = len;
|
||||
|
||||
k_work_submit_to_queue(&steno_workq, &req_work);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#endif /* STENO_DICT_PERIPHERAL */
|
||||
|
||||
/* --- Notification callback (central side) --- */
|
||||
|
||||
static uint8_t notify_cb(struct bt_conn *conn,
|
||||
|
|
@ -188,7 +252,7 @@ static uint8_t notify_cb(struct bt_conn *conn,
|
|||
|
||||
/* --- GATT Service Definition --- */
|
||||
|
||||
#ifndef CONFIG_STENO_DEBUG_NO_GATT
|
||||
#ifdef STENO_DICT_PERIPHERAL
|
||||
BT_GATT_SERVICE_DEFINE(steno_dict_svc,
|
||||
BT_GATT_PRIMARY_SERVICE(STENO_UUID_SERVICE),
|
||||
|
||||
|
|
@ -201,7 +265,7 @@ BT_GATT_SERVICE_DEFINE(steno_dict_svc,
|
|||
NULL, dict_req_write_cb, NULL),
|
||||
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
|
||||
);
|
||||
#endif /* !CONFIG_STENO_DEBUG_NO_GATT */
|
||||
#endif /* STENO_DICT_PERIPHERAL */
|
||||
|
||||
/* --- Central-side client: connection tracking + GATT discovery --- */
|
||||
|
||||
|
|
@ -500,6 +564,13 @@ int split_dict_init(void)
|
|||
split_cache_init(&dict_cache);
|
||||
seq_counter = 0;
|
||||
|
||||
#ifdef STENO_DICT_PERIPHERAL
|
||||
k_work_queue_start(&steno_workq, steno_workq_stack,
|
||||
K_THREAD_STACK_SIZEOF(steno_workq_stack),
|
||||
K_PRIO_PREEMPT(10), NULL);
|
||||
k_thread_name_set(&steno_workq.thread, "steno_dict");
|
||||
#endif
|
||||
|
||||
LOG_INF("Split dict initialized (protocol v4)");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue