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