xref: /openbmc/google-misc/subprojects/ncsid/src/ncsi_state_machine.cpp (revision dab96f131fb3a46d93f1093feccc9095d8589ece)
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*