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