| /* | 
 |  * Copyright (c) 2018 Nordic Semiconductor ASA | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #include <zephyr/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 |                |                          | | 
 |  * +-----------------+------------+----------------+--------------------------+ | 
 |  * | 
 |  * As an optimization, the added padding is attributed to the preceding item | 
 |  * instead of the current item. This way the padding will be freed one item | 
 |  * sooner. | 
 |  */ | 
 | 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(history->ring_buf, NULL, 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, *h_prev_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_prev_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); | 
 |  | 
 | 	if (l_item && | 
 | 	   (h_prev_item->len == len) && | 
 | 	   (memcmp(h_prev_item->data, line, len) == 0)) { | 
 | 		/* Same command as before, do not store */ | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	do { | 
 | 		if (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 resetting ring buffer we | 
 | 			 * ensure that it is capable to provide continues memory | 
 | 			 * of ring buffer capacity length. | 
 | 			 */ | 
 | 			ring_buf_reset(history->ring_buf); | 
 | 		} | 
 |  | 
 | 		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) { | 
 | 				/* | 
 | 				 * We may get here only if a previous entry | 
 | 				 * exists. Stick the excess padding to it. | 
 | 				 */ | 
 | 				h_prev_item->padding += claim_len; | 
 | 				total_len += 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); | 
 | 		remove_from_tail(history); | 
 | 	} while (1); | 
 | } | 
 |  | 
 | void z_shell_history_init(struct shell_history *history) | 
 | { | 
 | 	sys_dlist_init(&history->list); | 
 | 	history->current = NULL; | 
 | } |