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