/* * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include /* * History must store strings (commands) and allow traversing them and adding * new string. When new item is added then first it is compared if it is not * the same as the last one (then it is not stored). If there is no room in the * buffer to store the new item, oldest one is removed until there is a room. * * Items are allocated and stored in the ring buffer. Items then a linked in * the list. * * Because stored strings must be copied and compared, it is more convenient to * store them in the ring buffer in a way that they are not split into two * chunks (when ring buffer wraps). To ensure that item is in a single chunk, * item includes padding. If continues area for new item cannot be allocated * then allocated space is increased by the padding. * * If item does not fit at the end of the ring buffer padding is added: * * +-----------+----------------+-----------------------------------+---------+ * | header | "history item" | | padding | * | padding | | | | * +-----------+----------------+-----------------------------------+---------+ * * If item fits in the ring buffer available space then there is no padding: * +-----------------+------------+----------------+--------------------------+ * | | header | "history item" | | * | | no padding | | | * +-----------------+------------+----------------+--------------------------+ */ struct shell_history_item { sys_dnode_t dnode; u16_t len; u16_t padding; char data[0]; }; void shell_history_mode_exit(struct shell_history *history) { history->current = NULL; } bool shell_history_get(struct shell_history *history, bool up, u8_t *dst, u16_t *len) { struct shell_history_item *h_item; /* history item */ sys_dnode_t *l_item; /* list item */ if (sys_dlist_is_empty(&history->list)) { *len = 0U; return false; } if (!up) { /* button down */ if (history->current == NULL) { /* Not in history mode. It is started by up button. */ *len = 0U; return false; } l_item = sys_dlist_peek_prev_no_check(&history->list, history->current); } else { /* button up */ l_item = (history->current == NULL) ? sys_dlist_peek_head_not_empty(&history->list) : sys_dlist_peek_next_no_check(&history->list, history->current); } history->current = l_item; h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); if (l_item) { memcpy(dst, h_item->data, h_item->len); *len = h_item->len; dst[*len] = '\0'; return true; } *len = 0U; return false; } static void add_to_head(struct shell_history *history, struct shell_history_item *item, u8_t *src, size_t len, u16_t padding) { item->len = len; item->padding = padding; memcpy(item->data, src, len); sys_dlist_prepend(&history->list, &item->dnode); } /* Returns true if element was removed. */ static bool remove_from_tail(struct shell_history *history) { sys_dnode_t *l_item; /* list item */ struct shell_history_item *h_item; u32_t total_len; if (sys_dlist_is_empty(&history->list)) { return false; } l_item = sys_dlist_peek_tail(&history->list); sys_dlist_remove(l_item); h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); total_len = offsetof(struct shell_history_item, data) + h_item->len + h_item->padding; ring_buf_get_finish(history->ring_buf, total_len); return true; } void shell_history_purge(struct shell_history *history) { while (remove_from_tail(history)) { } } void shell_history_put(struct shell_history *history, u8_t *line, size_t len) { sys_dnode_t *l_item; /* list item */ struct shell_history_item *h_item; u32_t total_len = len + offsetof(struct shell_history_item, data); u32_t claim_len; u32_t claim2_len; u16_t padding = (~total_len + 1) & (sizeof(void *) - 1); /* align to word. */ total_len += padding; if (total_len > ring_buf_capacity_get(history->ring_buf)) { return; } shell_history_mode_exit(history); if (len == 0) { return; } l_item = sys_dlist_peek_head(&history->list); h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); if (l_item && (h_item->len == len) && (memcmp(h_item->data, line, len) == 0)) { /* Same command as before, do not store */ return; } do { claim_len = ring_buf_put_claim(history->ring_buf, (u8_t **)&h_item, total_len); /* second allocation may succeed if we were at the end of the * buffer. */ if (claim_len < total_len) { claim2_len = ring_buf_put_claim(history->ring_buf, (u8_t **)&h_item, total_len); if (claim2_len == total_len) { ring_buf_put_finish(history->ring_buf, claim_len); padding += claim_len; claim_len = total_len; } } if (claim_len == total_len) { add_to_head(history, h_item, line, len, padding); ring_buf_put_finish(history->ring_buf, claim_len); break; } ring_buf_put_finish(history->ring_buf, 0); if (remove_from_tail(history) == false) { __ASSERT_NO_MSG(ring_buf_is_empty(history->ring_buf)); /* if history is empty reset ring buffer. Even when * ring buffer is empty, it is possible that available * continues memory in worst case equals half of the * ring buffer capacity. By reseting ring buffer we * ensure that it is capable to provide continues memory * of ring buffer capacity length. */ ring_buf_reset(history->ring_buf); } } while (1); } void shell_history_init(struct shell_history *history) { sys_dlist_init(&history->list); history->current = NULL; }