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