1 /*
2  * Copyright (c) 2018 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/shell/shell_history.h>
8 #include <string.h>
9 
10 /*
11  * History must store strings (commands) and allow traversing them and adding
12  * new string. When new item is added then first it is compared if it is not
13  * the same as the last one (then it is not stored). If there is no room in the
14  * buffer to store the new item, oldest one is removed until there is a room.
15  *
16  * Items are allocated and stored in the ring buffer. Items then a linked in
17  * the list.
18  *
19  * Because stored strings must be copied and compared, it is more convenient to
20  * store them in the ring buffer in a way that they are not split into two
21  * chunks (when ring buffer wraps). To ensure that item is in a single chunk,
22  * item includes padding. If continues area for new item cannot be allocated
23  * then allocated space is increased by the padding.
24  *
25  * If item does not fit at the end of the ring buffer padding is added: *
26  * +-----------+----------------+-----------------------------------+---------+
27  * | header    | "history item" |                                   | padding |
28  * | padding   |                |                                   |         |
29  * +-----------+----------------+-----------------------------------+---------+
30  *
31  * If item fits in the ring buffer available space then there is no padding:
32  * +-----------------+------------+----------------+--------------------------+
33  * |                 | header     | "history item" |                          |
34  * |                 | no padding |                |                          |
35  * +-----------------+------------+----------------+--------------------------+
36  *
37  * As an optimization, the added padding is attributed to the preceding item
38  * instead of the current item. This way the padding will be freed one item
39  * sooner.
40  */
41 struct shell_history_item {
42 	sys_dnode_t dnode;
43 	uint16_t len;
44 	uint16_t padding;
45 	char data[0];
46 };
47 
z_shell_history_mode_exit(struct shell_history * history)48 void z_shell_history_mode_exit(struct shell_history *history)
49 {
50 	history->current = NULL;
51 }
52 
z_shell_history_get(struct shell_history * history,bool up,uint8_t * dst,uint16_t * len)53 bool z_shell_history_get(struct shell_history *history, bool up,
54 			 uint8_t *dst, uint16_t *len)
55 {
56 	struct shell_history_item *h_item; /* history item */
57 	sys_dnode_t *l_item; /* list item */
58 
59 	if (sys_dlist_is_empty(&history->list)) {
60 		*len = 0U;
61 		return false;
62 	}
63 
64 	if (!up) { /* button down */
65 		if (history->current == NULL) {
66 			/* Not in history mode. It is started by up button. */
67 			*len = 0U;
68 
69 			return false;
70 		}
71 
72 		l_item = sys_dlist_peek_prev_no_check(&history->list,
73 						      history->current);
74 	} else { /* button up */
75 		l_item = (history->current == NULL) ?
76 		sys_dlist_peek_head_not_empty(&history->list) :
77 		sys_dlist_peek_next_no_check(&history->list, history->current);
78 
79 	}
80 
81 	history->current = l_item;
82 	h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
83 
84 	if (l_item) {
85 		memcpy(dst, h_item->data, h_item->len);
86 		*len = h_item->len;
87 		dst[*len] = '\0';
88 		return true;
89 	}
90 
91 	*len = 0U;
92 	return false;
93 }
94 
add_to_head(struct shell_history * history,struct shell_history_item * item,uint8_t * src,size_t len,uint16_t padding)95 static void add_to_head(struct shell_history *history,
96 			struct shell_history_item *item,
97 			uint8_t *src, size_t len, uint16_t padding)
98 {
99 	item->len = len;
100 	item->padding = padding;
101 	memcpy(item->data, src, len);
102 	sys_dlist_prepend(&history->list, &item->dnode);
103 }
104 
105 /* Returns true if element was removed. */
remove_from_tail(struct shell_history * history)106 static bool remove_from_tail(struct shell_history *history)
107 {
108 	sys_dnode_t *l_item; /* list item */
109 	struct shell_history_item *h_item;
110 	uint32_t total_len;
111 
112 	if (sys_dlist_is_empty(&history->list)) {
113 		return false;
114 	}
115 
116 	l_item = sys_dlist_peek_tail(&history->list);
117 	sys_dlist_remove(l_item);
118 
119 	h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
120 
121 	total_len = offsetof(struct shell_history_item, data) +
122 			h_item->len + h_item->padding;
123 	ring_buf_get(history->ring_buf, NULL, total_len);
124 
125 	return true;
126 }
127 
z_shell_history_purge(struct shell_history * history)128 void z_shell_history_purge(struct shell_history *history)
129 {
130 	while (remove_from_tail(history)) {
131 	}
132 }
133 
z_shell_history_put(struct shell_history * history,uint8_t * line,size_t len)134 void z_shell_history_put(struct shell_history *history, uint8_t *line,
135 			 size_t len)
136 {
137 	sys_dnode_t *l_item; /* list item */
138 	struct shell_history_item *h_item, *h_prev_item;
139 	uint32_t total_len = len + offsetof(struct shell_history_item, data);
140 	uint32_t claim_len;
141 	uint32_t claim2_len;
142 	uint16_t padding = (~total_len + 1) & (sizeof(void *) - 1);
143 
144 	/* align to word. */
145 	total_len += padding;
146 
147 	if (total_len > ring_buf_capacity_get(history->ring_buf)) {
148 		return;
149 	}
150 
151 	z_shell_history_mode_exit(history);
152 
153 	if (len == 0) {
154 		return;
155 	}
156 
157 	l_item = sys_dlist_peek_head(&history->list);
158 	h_prev_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
159 
160 	if (l_item &&
161 	   (h_prev_item->len == len) &&
162 	   (memcmp(h_prev_item->data, line, len) == 0)) {
163 		/* Same command as before, do not store */
164 		return;
165 	}
166 
167 	do {
168 		if (ring_buf_is_empty(history->ring_buf)) {
169 			/* if history is empty reset ring buffer. Even when
170 			 * ring buffer is empty, it is possible that available
171 			 * continues memory in worst case equals half of the
172 			 * ring buffer capacity. By resetting ring buffer we
173 			 * ensure that it is capable to provide continues memory
174 			 * of ring buffer capacity length.
175 			 */
176 			ring_buf_reset(history->ring_buf);
177 		}
178 
179 		claim_len = ring_buf_put_claim(history->ring_buf,
180 						(uint8_t **)&h_item, total_len);
181 		/* second allocation may succeed if we were at the end of the
182 		 * buffer.
183 		 */
184 		if (claim_len < total_len) {
185 			claim2_len =
186 				ring_buf_put_claim(history->ring_buf,
187 						   (uint8_t **)&h_item, total_len);
188 			if (claim2_len == total_len) {
189 				/*
190 				 * We may get here only if a previous entry
191 				 * exists. Stick the excess padding to it.
192 				 */
193 				h_prev_item->padding += claim_len;
194 				total_len += claim_len;
195 				claim_len = total_len;
196 			}
197 		}
198 
199 		if (claim_len == total_len) {
200 			add_to_head(history, h_item, line, len, padding);
201 			ring_buf_put_finish(history->ring_buf, claim_len);
202 			break;
203 		}
204 
205 		ring_buf_put_finish(history->ring_buf, 0);
206 		remove_from_tail(history);
207 	} while (1);
208 }
209 
z_shell_history_init(struct shell_history * history)210 void z_shell_history_init(struct shell_history *history)
211 {
212 	sys_dlist_init(&history->list);
213 	history->current = NULL;
214 }
215