1 // Copyright 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "ncsi_state_machine.h"
16
17 #include "common_defs.h"
18 #include "platforms/nemora/portable/default_addresses.h"
19 #include "platforms/nemora/portable/ncsi_fsm.h"
20
21 #include <arpa/inet.h>
22 #include <netinet/ether.h>
23 #include <unistd.h>
24
25 #include <stdplus/print.hpp>
26
27 #include <chrono>
28 #include <cstdint>
29 #include <cstdlib>
30 #include <cstring>
31 #include <string>
32 #include <thread>
33 #include <utility>
34
35 #define ETHER_NCSI 0x88f8
36
37 // Only log messages a single time and drop all duplicates to prevent log spam.
38 // Having duplicate messages printed has historically not been helpful in
39 // debugging issues with this program.
do_log(std::string && line)40 static void do_log(std::string&& line)
41 {
42 constexpr auto line_dup_time = std::chrono::hours(1);
43 static std::chrono::steady_clock::time_point last_line_time;
44 static size_t line_rep_count = 0;
45 static std::string last_line;
46
47 auto now = std::chrono::steady_clock::now();
48 if (line != last_line || line_dup_time + last_line_time < now)
49 {
50 if (line_rep_count > 0)
51 {
52 stdplus::println(stderr, "... Repeated {} times ...",
53 line_rep_count);
54 }
55 stdplus::print(stderr, "{}", line);
56 last_line = std::move(line);
57 last_line_time = now;
58 line_rep_count = 0;
59 }
60 else
61 {
62 line_rep_count++;
63 }
64 }
65
66 #define CPRINT(...) do_log(std::format(__VA_ARGS__))
67
68 #ifdef NCSID_VERBOSE_LOGGING
69 #define DEBUG_PRINTF printf
70 #else
71 #define DEBUG_PRINTF(...)
72 #endif
73
74 namespace ncsi
75 {
76
77 namespace
78 {
79
80 const char kStateFormat[] = "l2_config=%d/%d l3l4_config=%d/%d test=%d/%d";
81 // This assumes that the number of states is < 100, so each number
82 // in the format above does not take more than two characters to represent,
83 // thus %d (two characters) is substituted for the number which is also
84 // two characters max.
85 constexpr auto kStateFormatLen = sizeof(kStateFormat);
86
snprintf_state(char * buffer,int size,const ncsi_state_t * state)87 void snprintf_state(char* buffer, int size, const ncsi_state_t* state)
88 {
89 (void)snprintf(buffer, size, kStateFormat, state->l2_config_state,
90 NCSI_STATE_L2_CONFIG_END, state->l3l4_config_state,
91 NCSI_STATE_L3L4_CONFIG_END, state->test_state,
92 NCSI_STATE_TEST_END);
93 }
94
print_state(const ncsi_state_t & state)95 void print_state(const ncsi_state_t& state)
96 {
97 (void)state;
98 DEBUG_PRINTF(kStateFormat, state.l2_config_state, NCSI_STATE_L2_CONFIG_END,
99 state.l3l4_config_state, NCSI_STATE_L3L4_CONFIG_END,
100 state.test_state, NCSI_STATE_TEST_END);
101 DEBUG_PRINTF(" restart_delay_count=%d\n", state.restart_delay_count);
102 }
103
104 const uint8_t echo_pattern[NCSI_OEM_ECHO_PATTERN_SIZE] = {
105 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
106 0xFF, 0xFF, 0xFF, 0xFF, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A,
107 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0x12, 0x34, 0x56, 0x78,
108 0x9A, 0xBC, 0xDE, 0xF0, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10};
109
110 } // namespace
111
reset()112 void StateMachine::reset()
113 {
114 std::memset(&ncsi_state_, 0, sizeof(ncsi_state_));
115 ncsi_state_.restart_delay_count = NCSI_FSM_RESTART_DELAY_COUNT - 1;
116 network_debug_.ncsi.test.max_tries = MAX_TRIES;
117 // This needs to be initialized in the firmware.
118 network_debug_.ncsi.test.ch_under_test = 0;
119 network_debug_.ncsi.oem_filter_disable = false;
120
121 network_debug_.ncsi.pending_stop = false;
122 network_debug_.ncsi.enabled = true;
123 network_debug_.ncsi.loopback = false;
124 }
125
StateMachine()126 StateMachine::StateMachine()
127 {
128 reset();
129 network_debug_.ncsi.pending_restart = true;
130 std::memcpy(network_debug_.ncsi.test.ping.tx, echo_pattern,
131 sizeof(echo_pattern));
132 }
133
~StateMachine()134 StateMachine::~StateMachine()
135 {
136 CPRINT("[NCSI stopping]\n");
137 }
138
poll_l2_config()139 size_t StateMachine::poll_l2_config()
140 {
141 size_t len = 0;
142 mac_addr_t mac;
143 net_config_->get_mac_addr(&mac);
144 ncsi_response_type_t response_type = ncsi_fsm_poll_l2_config(
145 &ncsi_state_, &network_debug_, &ncsi_buf_, &mac);
146
147 auto* response = reinterpret_cast<ncsi_simple_response_t*>(ncsi_buf_.data);
148
149 if (response_type == NCSI_RESPONSE_ACK)
150 {
151 /* If the response is MAC response, some extra handling needed. */
152 if ((NCSI_RESPONSE | NCSI_OEM_COMMAND) ==
153 response->hdr.control_packet_type)
154 {
155 auto* oem_response =
156 reinterpret_cast<ncsi_oem_simple_response_t*>(ncsi_buf_.data);
157 if (oem_response->oem_header.oem_cmd ==
158 NCSI_OEM_COMMAND_GET_HOST_MAC)
159 {
160 net_config_->set_mac_addr(mac);
161 }
162 }
163 }
164 else if (NCSI_RESPONSE_NONE == response_type)
165 {
166 /* Buffer is ready to be sent. */
167 len = ncsi_buf_.len;
168 ncsi_buf_.len = 0;
169 }
170 else
171 {
172 report_ncsi_error(response_type);
173 }
174
175 return len;
176 }
177
poll_simple(ncsi_simple_poll_f poll_func)178 size_t StateMachine::poll_simple(ncsi_simple_poll_f poll_func)
179 {
180 mac_addr_t mac;
181 net_config_->get_mac_addr(&mac);
182 const uint16_t rx_port = DEFAULT_ADDRESSES_RX_PORT;
183
184 ncsi_response_type_t response_type =
185 poll_func(&ncsi_state_, &network_debug_, &ncsi_buf_, &mac, 0, rx_port);
186
187 auto* response = reinterpret_cast<ncsi_simple_response_t*>(ncsi_buf_.data);
188
189 size_t len = 0;
190 if (response_type == NCSI_RESPONSE_NONE)
191 {
192 /* Buffer is ready to be sent, or we are done. */
193 len = ncsi_buf_.len;
194 ncsi_buf_.len = 0;
195 }
196 else if (response->hdr.control_packet_type ==
197 (NCSI_RESPONSE | NCSI_GET_LINK_STATUS))
198 {
199 auto status_response =
200 reinterpret_cast<ncsi_link_status_response_t*>(response);
201 bool new_link_up = ntohl(status_response->link_status.link_status) &
202 NCSI_LINK_STATUS_UP;
203 if (!link_up_ || new_link_up != *link_up_)
204 {
205 CPRINT("[NCSI link {}]\n", new_link_up ? "up" : "down");
206 link_up_ = new_link_up;
207 }
208 }
209 else if (response->hdr.control_packet_type ==
210 (NCSI_RESPONSE | NCSI_OEM_COMMAND))
211 {
212 auto* oem_response =
213 reinterpret_cast<ncsi_oem_simple_response_t*>(ncsi_buf_.data);
214 if (oem_response->oem_header.oem_cmd == NCSI_OEM_COMMAND_GET_FILTER)
215 {
216 bool new_hostless = ncsi_fsm_is_nic_hostless(&ncsi_state_);
217 if (!hostless_ || new_hostless != *hostless_)
218 {
219 CPRINT("[NCSI nic {}]\n",
220 new_hostless ? "hostless" : "hostfull");
221 net_config_->set_nic_hostless(new_hostless);
222 hostless_ = new_hostless;
223 }
224 }
225 }
226 else if (response_type != NCSI_RESPONSE_ACK)
227 {
228 report_ncsi_error(response_type);
229 }
230
231 return len;
232 }
233
report_ncsi_error(ncsi_response_type_t response_type)234 void StateMachine::report_ncsi_error(ncsi_response_type_t response_type)
235 {
236 char state_string[kStateFormatLen];
237 snprintf_state(state_string, sizeof(state_string), &ncsi_state_);
238 auto* response =
239 reinterpret_cast<const ncsi_simple_response_t*>(ncsi_buf_.data);
240 switch (response_type)
241 {
242 case NCSI_RESPONSE_UNDERSIZED:
243 if (!ncsi_buf_.len)
244 {
245 network_debug_.ncsi.rx_error.timeout_count++;
246 CPRINT("[NCSI timeout in state {}]\n", state_string);
247 }
248 else
249 {
250 network_debug_.ncsi.rx_error.undersized_count++;
251 CPRINT("[NCSI undersized response in state {}]\n",
252 state_string);
253 }
254 break;
255 case NCSI_RESPONSE_NACK:
256 network_debug_.ncsi.rx_error.nack_count++;
257 CPRINT(
258 "[NCSI nack in state {}. Response: {:#04x} Reason: {:#04x}]\n",
259 state_string, ntohs(response->response_code),
260 ntohs(response->reason_code));
261 break;
262 case NCSI_RESPONSE_UNEXPECTED_TYPE:
263 network_debug_.ncsi.rx_error.unexpected_type_count++;
264 CPRINT(
265 "[NCSI unexpected response in state {}. Response type: {:#02x}]\n",
266 state_string, response->hdr.control_packet_type);
267 break;
268 case NCSI_RESPONSE_UNEXPECTED_SIZE:
269 {
270 int expected_size;
271 if ((NCSI_RESPONSE | NCSI_OEM_COMMAND) ==
272 response->hdr.control_packet_type)
273 {
274 auto* oem_response =
275 reinterpret_cast<ncsi_oem_simple_response_t*>(
276 ncsi_buf_.data);
277 expected_size = ncsi_oem_get_response_size(
278 oem_response->oem_header.oem_cmd);
279 }
280 else
281 {
282 expected_size = ncsi_get_response_size(
283 response->hdr.control_packet_type & (~NCSI_RESPONSE));
284 }
285 network_debug_.ncsi.rx_error.unexpected_size_count++;
286 CPRINT("[NCSI unexpected response size in state {}."
287 " Expected {}]\n",
288 state_string, expected_size);
289 }
290 break;
291 case NCSI_RESPONSE_OEM_FORMAT_ERROR:
292 network_debug_.ncsi.rx_error.unexpected_type_count++;
293 CPRINT("[NCSI OEM format error]\n");
294 break;
295 case NCSI_RESPONSE_UNEXPECTED_PARAMS:
296 CPRINT("[NCSI OEM Filter MAC or TCP/IP Config Mismatch]\n");
297 break;
298 default:
299 /* NCSI_RESPONSE_ACK and NCSI_RESPONSE_NONE are not errors and need
300 * not be handled here, so this branch is just to complete the
301 * switch.
302 */
303 CPRINT("[NCSI OK]\n");
304 break;
305 }
306 }
307
receive_ncsi()308 int StateMachine::receive_ncsi()
309 {
310 int len;
311 do
312 {
313 // Return value of zero means timeout
314 len = sock_io_->recv(ncsi_buf_.data, sizeof(ncsi_buf_.data));
315 if (len > 0)
316 {
317 auto* hdr = reinterpret_cast<struct ether_header*>(ncsi_buf_.data);
318 if (ETHER_NCSI == ntohs(hdr->ether_type))
319 {
320 ncsi_buf_.len = len;
321 break;
322 }
323 }
324
325 ncsi_buf_.len = 0;
326 } while (len > 0);
327
328 return ncsi_buf_.len;
329 }
330
run_test_fsm(size_t * tx_len)331 void StateMachine::run_test_fsm(size_t* tx_len)
332 {
333 // Sleep and restart when test FSM finishes.
334 if (is_test_done())
335 {
336 std::this_thread::sleep_for(std::chrono::seconds(retest_delay_s_));
337 // Skip over busy wait in state machine - already waited.
338 ncsi_state_.retest_delay_count = NCSI_FSM_RESTART_DELAY_COUNT;
339 }
340 // until NCSI_STATE_TEST_END
341 *tx_len = poll_simple(ncsi_fsm_poll_test);
342 }
343
run(int max_rounds)344 void StateMachine::run(int max_rounds)
345 {
346 if (!net_config_ || !sock_io_)
347 {
348 CPRINT("StateMachine configuration incomplete: "
349 "net_config_: <{}>, sock_io_: <{}>",
350 reinterpret_cast<void*>(net_config_),
351 reinterpret_cast<void*>(sock_io_));
352 return;
353 }
354
355 bool infinite_loop = (max_rounds <= 0);
356 while (infinite_loop || --max_rounds >= 0)
357 {
358 receive_ncsi();
359
360 size_t tx_len = 0;
361 switch (ncsi_fsm_connection_state(&ncsi_state_, &network_debug_))
362 {
363 case NCSI_CONNECTION_DOWN:
364 case NCSI_CONNECTION_LOOPBACK:
365 tx_len = poll_l2_config();
366 break;
367 case NCSI_CONNECTION_UP:
368 if (!is_test_done() || ncsi_fsm_is_nic_hostless(&ncsi_state_))
369 {
370 run_test_fsm(&tx_len);
371 }
372 else
373 {
374 // - Only start L3/L4 config when test is finished
375 // (it will last until success
376 // (i.e. NCSI_CONNECTION_UP_AND_CONFIGURED) or fail.
377 tx_len = poll_simple(ncsi_fsm_poll_l3l4_config);
378 }
379 break;
380 case NCSI_CONNECTION_UP_AND_CONFIGURED:
381 run_test_fsm(&tx_len);
382 break;
383 case NCSI_CONNECTION_DISABLED:
384 if (network_debug_.ncsi.pending_restart)
385 {
386 network_debug_.ncsi.enabled = true;
387 }
388 break;
389 default:
390 fail();
391 }
392
393 if (tx_len)
394 {
395 print_state(ncsi_state_);
396
397 sock_io_->write(ncsi_buf_.data, tx_len);
398 }
399 }
400 }
401
clear_state()402 void StateMachine::clear_state()
403 {
404 // This implicitly resets:
405 // l2_config_state to NCSI_STATE_L2_CONFIG_BEGIN
406 // l3l4_config_state to NCSI_STATE_L3L4_CONFIG_BEGIN
407 // test_state to NCSI_STATE_TEST_BEGIN
408 std::memset(&ncsi_state_, 0, sizeof(ncsi_state_));
409 }
410
fail()411 void StateMachine::fail()
412 {
413 network_debug_.ncsi.fail_count++;
414 clear_state();
415 }
416
set_sockio(net::SockIO * sock_io)417 void StateMachine::set_sockio(net::SockIO* sock_io)
418 {
419 sock_io_ = sock_io;
420 }
421
set_net_config(net::ConfigBase * net_config)422 void StateMachine::set_net_config(net::ConfigBase* net_config)
423 {
424 net_config_ = net_config;
425 }
426
set_retest_delay(unsigned delay)427 void StateMachine::set_retest_delay(unsigned delay)
428 {
429 retest_delay_s_ = delay;
430 }
431
432 } // namespace ncsi
433