| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 | /* * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */#include <shell/shell_history.h>#include <string.h>/* * 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;	uint16_t len;	uint16_t padding;	char data[0];};void z_shell_history_mode_exit(struct shell_history *history){	history->current = NULL;}bool z_shell_history_get(struct shell_history *history, bool up,			 uint8_t *dst, uint16_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,			uint8_t *src, size_t len, uint16_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;	uint32_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 z_shell_history_purge(struct shell_history *history){	while (remove_from_tail(history)) {	}}void z_shell_history_put(struct shell_history *history, uint8_t *line,			 size_t len){	sys_dnode_t *l_item; /* list item */	struct shell_history_item *h_item;	uint32_t total_len = len + offsetof(struct shell_history_item, data);	uint32_t claim_len;	uint32_t claim2_len;	uint16_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;	}	z_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,						(uint8_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,						   (uint8_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 z_shell_history_init(struct shell_history *history){	sys_dlist_init(&history->list);	history->current = NULL;}
 |