1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <string.h>
18 
19 #include <netinet/in.h>
20 
21 #include "platforms/nemora/portable/ncsi.h"
22 #include "platforms/nemora/portable/ncsi_client.h"
23 #include "platforms/nemora/portable/ncsi_fsm.h"
24 
25 #define GO_TO_STATE(variable, state) do { *variable = state; } while (0)
26 #define GO_TO_NEXT_STATE(variable) do { (*variable)++; } while (0)
27 
28 // TODO - This state machine needs to be rewritten, now that we have a
29 // better idea of the states and transitions involved.
30 // The NC-SI related states of the state machine are currently organized in
31 // request/response pairs. However when I added support for the second channel
32 // this resulted in more hard-coded pairs which worked okay for X
33 // (despite some ugliness, see ch_under_test below) but broke down for X
34 // since it only supports 1 channel. For now just add a little more ugliness
35 // by stepping by 1 or 3 when going from a pair to the next depending on whether
36 // the second channel is supported (1) or not (3 - skip over the second channel
37 // pair).
38 #define GO_TO_NEXT_CHANNEL(variable, ncsi_state)\
39   do { *variable += (ncsi_state->channel_count == 1)\
40       ? 3 : 1; } while (0)
41 
ncsi_fsm_clear_state(ncsi_state_t * ncsi_state)42 static void ncsi_fsm_clear_state(ncsi_state_t* ncsi_state) {
43   // This implicitly resets:
44   //   l2_config_state   to NCSI_STATE_L2_CONFIG_BEGIN
45   //   l3l4_config_state to NCSI_STATE_L3L4_CONFIG_BEGIN
46   //   test_state        to NCSI_STATE_TEST_BEGIN
47   memset(ncsi_state, 0, sizeof(ncsi_state_t));
48 }
49 
ncsi_fsm_fail(ncsi_state_t * ncsi_state,network_debug_t * network_debug)50 static void ncsi_fsm_fail(ncsi_state_t* ncsi_state,
51                           network_debug_t* network_debug) {
52   network_debug->ncsi.fail_count++;
53   memcpy(&network_debug->ncsi.state_that_failed, ncsi_state,
54          sizeof(network_debug->ncsi.state_that_failed));
55   ncsi_fsm_clear_state(ncsi_state);
56 }
57 
ncsi_fsm_connection_state(const ncsi_state_t * ncsi_state,const network_debug_t * network_debug)58 ncsi_connection_state_t ncsi_fsm_connection_state(
59     const ncsi_state_t* ncsi_state, const network_debug_t* network_debug) {
60   if (!network_debug->ncsi.enabled) {
61     return NCSI_CONNECTION_DISABLED;
62   }
63   if (ncsi_state->l2_config_state != NCSI_STATE_L2_CONFIG_END) {
64     if (network_debug->ncsi.loopback) {
65       return NCSI_CONNECTION_LOOPBACK;
66     } else {
67       return NCSI_CONNECTION_DOWN;
68     }
69   }
70   if (ncsi_state->l3l4_config_state != NCSI_STATE_L3L4_CONFIG_END) {
71     return NCSI_CONNECTION_UP;
72   }
73   return NCSI_CONNECTION_UP_AND_CONFIGURED;
74 }
75 
ncsi_fsm_poll_l2_config(ncsi_state_t * ncsi_state,network_debug_t * network_debug,ncsi_buf_t * ncsi_buf,mac_addr_t * mac)76 ncsi_response_type_t ncsi_fsm_poll_l2_config(ncsi_state_t* ncsi_state,
77                                              network_debug_t* network_debug,
78                                              ncsi_buf_t* ncsi_buf,
79                                              mac_addr_t* mac) {
80   ncsi_l2_config_state_t* const state_variable = &ncsi_state->l2_config_state;
81   ncsi_response_type_t ncsi_response_type = NCSI_RESPONSE_NONE;
82   uint32_t len = 0;
83 
84   switch(*state_variable) {
85   case NCSI_STATE_RESTART:
86     if (++ncsi_state->restart_delay_count >= NCSI_FSM_RESTART_DELAY_COUNT) {
87       network_debug->ncsi.pending_restart = false;
88       GO_TO_NEXT_STATE(state_variable);
89       ncsi_state->restart_delay_count = 0;
90     }
91     break;
92   case NCSI_STATE_CLEAR_0: // necessary to get mac
93     len = ncsi_cmd_clear_initial_state(ncsi_buf->data, CHANNEL_0_ID);
94     GO_TO_NEXT_STATE(state_variable);
95     break;
96   case NCSI_STATE_CLEAR_0_RESPONSE:
97     {
98       bool loopback = false;
99       ncsi_response_type = ncsi_validate_std_response(
100           ncsi_buf->data, ncsi_buf->len, NCSI_CLEAR_INITIAL_STATE);
101       if (NCSI_RESPONSE_ACK == ncsi_response_type) {
102         GO_TO_NEXT_STATE(state_variable);
103       } else {
104         // If we did not receive a response but we did receive something,
105         // then maybe there is a physical loopback, so check that we received
106         // exactly what we sent
107         if (ncsi_buf->len >= sizeof(ncsi_simple_command_t)) {
108           ncsi_simple_command_t expected_loopback_data;
109           (void)ncsi_cmd_clear_initial_state((uint8_t*)&expected_loopback_data,
110                                              CHANNEL_0_ID);
111           if (0 == memcmp((uint8_t*)&expected_loopback_data,
112                           ncsi_buf->data, sizeof(expected_loopback_data))) {
113             loopback = true;
114           }
115         }
116         ncsi_fsm_fail(ncsi_state, network_debug);
117       }
118       network_debug->ncsi.loopback = loopback;
119     }
120     break;
121   case NCSI_STATE_GET_VERSION:
122     len = ncsi_cmd_get_version(ncsi_buf->data, CHANNEL_0_ID);
123     GO_TO_NEXT_STATE(state_variable);
124     break;
125   case NCSI_STATE_GET_VERSION_RESPONSE:
126     ncsi_response_type = ncsi_validate_std_response(
127         ncsi_buf->data, ncsi_buf->len, NCSI_GET_VERSION_ID);
128     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
129       ncsi_version_id_response_t* get_version_response =
130           (ncsi_version_id_response_t*)ncsi_buf->data;
131       // TODO - Add check for this being actually X
132       network_debug->ncsi.mlx_legacy =
133           ((ntohl(get_version_response->version.firmware_version) >> 24) ==
134            0x08);
135       GO_TO_NEXT_CHANNEL(state_variable, ncsi_state);
136     } else {
137       ncsi_fsm_fail(ncsi_state, network_debug);
138     }
139     break;
140   case NCSI_STATE_GET_CAPABILITIES:
141     len = ncsi_cmd_get_capabilities(ncsi_buf->data, CHANNEL_0_ID);
142     GO_TO_NEXT_STATE(state_variable);
143     break;
144   case NCSI_STATE_GET_CAPABILITIES_RESPONSE:
145     ncsi_response_type = ncsi_validate_std_response(
146         ncsi_buf->data, ncsi_buf->len, NCSI_GET_CAPABILITIES);
147     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
148       const ncsi_capabilities_response_t* get_capabilities_response =
149         (ncsi_capabilities_response_t*) ncsi_buf->data;
150       if (1 != get_capabilities_response->channel_count &&
151           2 != get_capabilities_response->channel_count) {
152         /* TODO: Return Error
153         CPRINT("[NCSI Unsupported channel count {}]\n",
154                 get_capabilities_response->channel_count);
155           */
156         ncsi_fsm_fail(ncsi_state, network_debug);
157       } else {
158         ncsi_state->channel_count =
159           get_capabilities_response->channel_count;
160         GO_TO_NEXT_CHANNEL(state_variable, ncsi_state);
161       }
162     } else{
163       ncsi_fsm_fail(ncsi_state, network_debug);
164     }
165     break;
166   case NCSI_STATE_CLEAR_1:
167     len = ncsi_cmd_clear_initial_state(ncsi_buf->data, CHANNEL_1_ID);
168     GO_TO_NEXT_STATE(state_variable);
169     break;
170   case NCSI_STATE_CLEAR_1_RESPONSE:
171     ncsi_response_type = ncsi_validate_std_response(
172         ncsi_buf->data, ncsi_buf->len, NCSI_CLEAR_INITIAL_STATE);
173     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
174       GO_TO_NEXT_STATE(state_variable);
175     } else {
176       ncsi_fsm_fail(ncsi_state, network_debug);
177     }
178     break;
179   case NCSI_STATE_RESET_CHANNEL_0:
180     if (network_debug->ncsi.pending_stop) {
181       len = ncsi_cmd_reset_channel(ncsi_buf->data, CHANNEL_0_ID);
182       GO_TO_NEXT_STATE(state_variable);
183     } else {
184       // skip resetting channels
185       GO_TO_STATE(state_variable, NCSI_STATE_GET_MAC);
186     }
187     break;
188   case NCSI_STATE_RESET_CHANNEL_0_RESPONSE:
189     ncsi_response_type = ncsi_validate_std_response(
190         ncsi_buf->data, ncsi_buf->len, NCSI_RESET_CHANNEL);
191     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
192       GO_TO_NEXT_CHANNEL(state_variable, ncsi_state);
193     } else {
194       ncsi_fsm_fail(ncsi_state, network_debug);
195     }
196     break;
197   case NCSI_STATE_RESET_CHANNEL_1:
198     len = ncsi_cmd_reset_channel(ncsi_buf->data, CHANNEL_1_ID);
199     GO_TO_NEXT_STATE(state_variable);
200     break;
201   case NCSI_STATE_RESET_CHANNEL_1_RESPONSE:
202     ncsi_response_type = ncsi_validate_std_response(
203         ncsi_buf->data, ncsi_buf->len, NCSI_RESET_CHANNEL);
204     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
205       GO_TO_NEXT_STATE(state_variable);
206     } else {
207       ncsi_fsm_fail(ncsi_state, network_debug);
208     }
209     break;
210   case NCSI_STATE_STOPPED:
211     network_debug->ncsi.pending_stop = false;
212     // Reset the L2 config state machine through fail(). This state machine
213     // will not be executed again so long as 'enabled' is false.
214     network_debug->ncsi.enabled = false;
215     ncsi_fsm_fail(ncsi_state, network_debug);
216     break;
217     // TODO: Add check for MFG ID and firmware version before trying
218     // any OEM commands.
219   case NCSI_STATE_GET_MAC:
220     // Only get MAC from channel 0, because that's the one that identifies the
221     // host machine (for both MDB and DHCP).
222     len = ncsi_oem_cmd_get_host_mac(ncsi_buf->data, CHANNEL_0_ID);
223     GO_TO_NEXT_STATE(state_variable);
224     break;
225   case NCSI_STATE_GET_MAC_RESPONSE:
226     ncsi_response_type = ncsi_validate_oem_response(
227         ncsi_buf->data, ncsi_buf->len, NCSI_OEM_COMMAND_GET_HOST_MAC);
228     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
229       ncsi_host_mac_response_t* get_mac_response =
230         (ncsi_host_mac_response_t*) ncsi_buf->data;
231       memcpy(mac->octet, get_mac_response->mac, sizeof(mac_addr_t));
232       GO_TO_NEXT_STATE(state_variable);
233     } else {
234       ncsi_fsm_fail(ncsi_state, network_debug);
235     }
236     break;
237   case NCSI_STATE_SET_MAC_FILTER_0:
238     len = ncsi_cmd_set_mac(ncsi_buf->data, CHANNEL_0_ID, mac);
239     GO_TO_NEXT_STATE(state_variable);
240     break;
241   case NCSI_STATE_SET_MAC_FILTER_0_RESPONSE:
242     ncsi_response_type = ncsi_validate_std_response(
243         ncsi_buf->data, ncsi_buf->len, NCSI_SET_MAC_ADDRESS);
244     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
245       GO_TO_NEXT_CHANNEL(state_variable, ncsi_state);
246     } else{
247       ncsi_fsm_fail(ncsi_state, network_debug);
248     }
249     break;
250   case NCSI_STATE_SET_MAC_FILTER_1:
251     len = ncsi_cmd_set_mac(ncsi_buf->data, CHANNEL_1_ID, mac);
252     GO_TO_NEXT_STATE(state_variable);
253     break;
254   case NCSI_STATE_SET_MAC_FILTER_1_RESPONSE:
255     ncsi_response_type = ncsi_validate_std_response(
256         ncsi_buf->data, ncsi_buf->len, NCSI_SET_MAC_ADDRESS);
257     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
258       GO_TO_NEXT_STATE(state_variable);
259     } else{
260       ncsi_fsm_fail(ncsi_state, network_debug);
261     }
262     break;
263   case NCSI_STATE_ENABLE_CHANNEL_0:
264     len = ncsi_cmd_enable_channel(ncsi_buf->data, CHANNEL_0_ID);
265     GO_TO_NEXT_STATE(state_variable);
266     break;
267   case NCSI_STATE_ENABLE_CHANNEL_0_RESPONSE:
268     ncsi_response_type = ncsi_validate_std_response(
269         ncsi_buf->data, ncsi_buf->len, NCSI_ENABLE_CHANNEL);
270     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
271       GO_TO_NEXT_CHANNEL(state_variable, ncsi_state);
272     } else{
273       ncsi_fsm_fail(ncsi_state, network_debug);
274     }
275     break;
276   case NCSI_STATE_ENABLE_CHANNEL_1:
277     len = ncsi_cmd_enable_channel(ncsi_buf->data, CHANNEL_1_ID);
278     GO_TO_NEXT_STATE(state_variable);
279     break;
280   case NCSI_STATE_ENABLE_CHANNEL_1_RESPONSE:
281     ncsi_response_type = ncsi_validate_std_response(
282         ncsi_buf->data, ncsi_buf->len, NCSI_ENABLE_CHANNEL);
283     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
284       GO_TO_NEXT_STATE(state_variable);
285     } else{
286       ncsi_fsm_fail(ncsi_state, network_debug);
287     }
288     break;
289   // TODO: Enable broadcast filter to block ARP.
290   case NCSI_STATE_ENABLE_TX:
291     // The NIC FW transmits all passthrough TX on the lowest enabled channel,
292     // so there is no point in enabling TX on the second channel.
293     // TODO: - In the future we may add a check for link status,
294     //         in which case we may want to intelligently disable ch.0
295     //         (if down) and enable ch.1
296     len = ncsi_cmd_enable_tx(ncsi_buf->data, CHANNEL_0_ID);
297     GO_TO_NEXT_STATE(state_variable);
298     break;
299   case NCSI_STATE_ENABLE_TX_RESPONSE:
300     ncsi_response_type = ncsi_validate_std_response(
301         ncsi_buf->data, ncsi_buf->len, NCSI_ENABLE_CHANNEL_NETWORK_TX);
302     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
303       GO_TO_NEXT_STATE(state_variable);
304     } else{
305       ncsi_fsm_fail(ncsi_state, network_debug);
306     }
307     break;
308   case NCSI_STATE_L2_CONFIG_END:
309     // Done
310     break;
311   default:
312     ncsi_fsm_fail(ncsi_state, network_debug);
313     break;
314   }
315 
316   ncsi_buf->len = len;
317   return ncsi_response_type;
318 }
319 
write_ncsi_oem_config_filter(uint8_t * buffer,uint8_t channel,network_debug_t * network_debug,mac_addr_t * mac,uint32_t ipv4_addr,uint16_t rx_port)320 static uint32_t write_ncsi_oem_config_filter(uint8_t* buffer, uint8_t channel,
321                                              network_debug_t* network_debug,
322                                              mac_addr_t* mac,
323                                              uint32_t ipv4_addr,
324                                              uint16_t rx_port) {
325   uint32_t len;
326   (void)ipv4_addr;
327   if (network_debug->ncsi.oem_filter_disable) {
328     mac_addr_t zero_mac = {.octet = {0,}};
329     len = ncsi_oem_cmd_set_filter(buffer, channel, &zero_mac, 0, 0, 0);
330 
331   } else {
332     len = ncsi_oem_cmd_set_filter(buffer, channel, mac, 0, rx_port, 1);
333   }
334   return len;
335 }
336 
ncsi_fsm_poll_l3l4_config(ncsi_state_t * ncsi_state,network_debug_t * network_debug,ncsi_buf_t * ncsi_buf,mac_addr_t * mac,uint32_t ipv4_addr,uint16_t rx_port)337 ncsi_response_type_t ncsi_fsm_poll_l3l4_config(ncsi_state_t* ncsi_state,
338                                                network_debug_t* network_debug,
339                                                ncsi_buf_t* ncsi_buf,
340                                                mac_addr_t* mac,
341                                                uint32_t ipv4_addr,
342                                                uint16_t rx_port) {
343   uint32_t len = 0;
344   ncsi_response_type_t ncsi_response_type = NCSI_RESPONSE_NONE;
345 
346   if (ncsi_state->l3l4_config_state == NCSI_STATE_L3L4_CONFIG_BEGIN) {
347     ncsi_state->l3l4_channel = 0;
348     ncsi_state->l3l4_waiting_response = false;
349     ncsi_state->l3l4_config_state = NCSI_STATE_CONFIG_FILTERS;
350   }
351 
352   /* Go through every state with every channel. */
353   if (ncsi_state->l3l4_waiting_response) {
354     ncsi_response_type = ncsi_validate_oem_response(
355         ncsi_buf->data, ncsi_buf->len, ncsi_state->l3l4_command);
356     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
357       /* Current channel ACK'ed, go to the next one. */
358       ncsi_state->l3l4_channel++;
359       if (ncsi_state->l3l4_channel >= ncsi_state->channel_count) {
360         /* All channels done, reset channel number and go to the next state.
361          * NOTE: This assumes that state numbers are sequential.*/
362         ncsi_state->l3l4_config_state += 1;
363         ncsi_state->l3l4_channel = 0;
364       }
365     } else {
366       ncsi_fsm_fail(ncsi_state, network_debug);
367     }
368 
369     ncsi_state->l3l4_waiting_response = false;
370   } else {
371     // Send appropriate command.
372     switch(ncsi_state->l3l4_config_state) {
373       case NCSI_STATE_CONFIG_FILTERS:
374         len =
375             write_ncsi_oem_config_filter(ncsi_buf->data, ncsi_state->l3l4_channel,
376                                          network_debug, mac, ipv4_addr, rx_port);
377         ncsi_state->l3l4_command = NCSI_OEM_COMMAND_SET_FILTER;
378         ncsi_state->l3l4_waiting_response = true;
379         break;
380       default:
381         ncsi_fsm_fail(ncsi_state, network_debug);
382         break;
383     }
384   }
385 
386   ncsi_buf->len = len;
387   return ncsi_response_type;
388 }
389 
390 /*
391  * Start a sub-section of the state machine that runs health checks.
392  * This is dependent on the NC-SI configuration being completed
393  * (e.g. ncsi_channel_count must be known).
394  */
ncsi_fsm_start_test(network_debug_t * network_debug,uint8_t channel_count)395 static bool ncsi_fsm_start_test(network_debug_t* network_debug,
396                                 uint8_t channel_count) {
397   if (network_debug->ncsi.test.max_tries > 0) {
398     network_debug->ncsi.test.runs++;
399     if (2 == channel_count) {
400       network_debug->ncsi.test.ch_under_test ^= 1;
401     } else {
402       network_debug->ncsi.test.ch_under_test = 0;
403     }
404     return true;
405   }
406   return false;
407 }
408 
409 /*
410  * Allow for a limited number of retries for the NC-SI test because
411  * it can fail under heavy TCP/IP load (since NC-SI responses share
412  * the RX buffers in chip/$(CHIP)/net.c with TCP/IP incoming traffic).
413  */
ncsi_fsm_retry_test(network_debug_t * network_debug)414 static bool ncsi_fsm_retry_test(network_debug_t* network_debug) {
415   const uint8_t max_tries = network_debug->ncsi.test.max_tries;
416   if (max_tries) {
417     uint8_t remaining_tries = max_tries - 1 - network_debug->ncsi.test.tries;
418     if (remaining_tries > 0) {
419       network_debug->ncsi.test.tries++;
420       return true;
421     }
422   }
423   network_debug->ncsi.test.tries = 0;
424   return false;
425 }
426 
427 /*
428  * Returns true if we have executed an NC-SI Get OEM Filter command for all
429  * channels and the flags indicate that it is running in hostless mode.
430  * This means that we can DHCP/ARP if needed.
431  * Otherwise returns false.
432  *
433  * NOTE: We default to false, if we cannot complete the L2 config state
434  *   machine or the test sequence.
435  */
ncsi_fsm_is_nic_hostless(const ncsi_state_t * ncsi_state)436 bool ncsi_fsm_is_nic_hostless(const ncsi_state_t* ncsi_state) {
437   uint8_t flags = ncsi_state->flowsteering[0].flags;
438   if (ncsi_state->channel_count > 1) {
439     flags &= ncsi_state->flowsteering[1].flags;
440   }
441   return flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS;
442 }
443 
ncsi_fsm_update_passthrough_stats(const ncsi_passthrough_stats_t * increment,network_debug_t * network_debug)444 static void ncsi_fsm_update_passthrough_stats(
445     const ncsi_passthrough_stats_t* increment, network_debug_t* network_debug) {
446   ncsi_passthrough_stats_t* accumulated =
447       &network_debug->ncsi.pt_stats_be[network_debug->ncsi.test.ch_under_test];
448 #define ACCUMULATE_PT_STATS(stat) accumulated->stat += increment->stat;
449   ACCUMULATE_PT_STATS(tx_packets_received_hi);
450   ACCUMULATE_PT_STATS(tx_packets_received_lo);
451   ACCUMULATE_PT_STATS(tx_packets_dropped);
452   ACCUMULATE_PT_STATS(tx_channel_errors);
453   ACCUMULATE_PT_STATS(tx_undersized_errors);
454   ACCUMULATE_PT_STATS(tx_oversized_errors);
455   ACCUMULATE_PT_STATS(rx_packets_received);
456   ACCUMULATE_PT_STATS(rx_packets_dropped);
457   ACCUMULATE_PT_STATS(rx_channel_errors);
458   ACCUMULATE_PT_STATS(rx_undersized_errors);
459   ACCUMULATE_PT_STATS(rx_oversized_errors);
460 #undef ACCUMULATE_PT_STATS
461 }
462 
ncsi_fsm_update_passthrough_stats_legacy(const ncsi_passthrough_stats_legacy_t * read,network_debug_t * network_debug)463 static void ncsi_fsm_update_passthrough_stats_legacy(
464     const ncsi_passthrough_stats_legacy_t* read,
465     network_debug_t* network_debug) {
466   // Legacy MLX response does not include tx_packets_received_hi and also MLX
467   // counters
468   // are not reset on read (i.e. we cannot accumulate them).
469   ncsi_passthrough_stats_t* accumulated =
470       &network_debug->ncsi.pt_stats_be[network_debug->ncsi.test.ch_under_test];
471   accumulated->tx_packets_received_hi = 0;
472   accumulated->tx_packets_received_lo = read->tx_packets_received;
473 #define COPY_PT_STATS(stat) accumulated->stat = read->stat;
474   COPY_PT_STATS(tx_packets_dropped);
475   COPY_PT_STATS(tx_channel_errors);
476   COPY_PT_STATS(tx_undersized_errors);
477   COPY_PT_STATS(tx_oversized_errors);
478   COPY_PT_STATS(rx_packets_received);
479   COPY_PT_STATS(rx_packets_dropped);
480   COPY_PT_STATS(rx_channel_errors);
481   COPY_PT_STATS(rx_undersized_errors);
482   COPY_PT_STATS(rx_oversized_errors);
483 #undef COPY_PT_STATS
484 }
485 
ncsi_fsm_poll_test(ncsi_state_t * ncsi_state,network_debug_t * network_debug,ncsi_buf_t * ncsi_buf,mac_addr_t * mac,uint32_t ipv4_addr,uint16_t rx_port)486 ncsi_response_type_t ncsi_fsm_poll_test(ncsi_state_t* ncsi_state,
487                                         network_debug_t* network_debug,
488                                         ncsi_buf_t* ncsi_buf, mac_addr_t* mac,
489                                         uint32_t ipv4_addr, uint16_t rx_port) {
490   ncsi_test_state_t* const state_variable =
491       &ncsi_state->test_state;
492   ncsi_response_type_t ncsi_response_type = NCSI_RESPONSE_NONE;
493   uint32_t len = 0;
494 
495   switch(*state_variable) {
496   case NCSI_STATE_TEST_PARAMS:
497     if (ncsi_fsm_start_test(network_debug, ncsi_state->channel_count)) {
498       GO_TO_NEXT_STATE(state_variable);
499     } else {
500       // debugging only - skip test by setting max_tries to 0
501       GO_TO_STATE(state_variable, NCSI_STATE_TEST_END);
502     }
503     break;
504   case NCSI_STATE_ECHO:
505     len = ncsi_oem_cmd_echo(ncsi_buf->data,
506                             network_debug->ncsi.test.ch_under_test,
507                             network_debug->ncsi.test.ping.tx);
508     network_debug->ncsi.test.ping.tx_count++;
509     GO_TO_NEXT_STATE(state_variable);
510     break;
511   case NCSI_STATE_ECHO_RESPONSE:
512     ncsi_response_type = ncsi_validate_oem_response(
513         ncsi_buf->data, ncsi_buf->len, NCSI_OEM_COMMAND_ECHO);
514     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
515       network_debug->ncsi.test.ping.rx_count++;
516       ncsi_oem_echo_response_t* echo_response =
517         (ncsi_oem_echo_response_t*) ncsi_buf->data;
518       if (0 == memcmp(echo_response->pattern,
519                       network_debug->ncsi.test.ping.tx,
520                       sizeof(echo_response->pattern))) {
521         GO_TO_NEXT_STATE(state_variable);
522         break;
523       } else {
524         network_debug->ncsi.test.ping.bad_rx_count++;
525         memcpy(network_debug->ncsi.test.ping.last_bad_rx,
526                echo_response->pattern,
527                sizeof(network_debug->ncsi.test.ping.last_bad_rx));
528       }
529     }
530     if (ncsi_fsm_retry_test(network_debug)) {
531       GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN);
532     } else {
533       ncsi_fsm_fail(ncsi_state, network_debug);
534     }
535     break;
536   case NCSI_STATE_CHECK_FILTERS:
537     len = ncsi_oem_cmd_get_filter(ncsi_buf->data,
538                                   network_debug->ncsi.test.ch_under_test);
539     GO_TO_NEXT_STATE(state_variable);
540     break;
541   case NCSI_STATE_CHECK_FILTERS_RESPONSE:
542     ncsi_response_type = ncsi_validate_oem_response(
543         ncsi_buf->data, ncsi_buf->len, NCSI_OEM_COMMAND_GET_FILTER);
544     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
545       ncsi_oem_get_filter_response_t* get_filter_response =
546         (ncsi_oem_get_filter_response_t*) ncsi_buf->data;
547       // Stash away response because it contains information about NIC mode
548       memcpy((void*)ncsi_state->flowsteering[
549           network_debug->ncsi.test.ch_under_test].regid,
550              (void*)get_filter_response->filter.regid,
551              sizeof(ncsi_state->flowsteering[0].regid));
552       ncsi_state->flowsteering[
553           network_debug->ncsi.test.ch_under_test].flags =
554           get_filter_response->filter.flags;
555       // Test filter parameters only if we know that we configured the NIC,
556       // and if the NIC is in host-based mode (it appears to return all zeros's
557       // in hostless mode!).
558       if (NCSI_STATE_L3L4_CONFIG_END != ncsi_state->l3l4_config_state ||
559           ncsi_fsm_is_nic_hostless(ncsi_state)) {
560         GO_TO_NEXT_STATE(state_variable);
561         break;
562       }
563       ncsi_oem_set_filter_cmd_t expected;
564       (void)write_ncsi_oem_config_filter(
565           (uint8_t*)&expected, network_debug->ncsi.test.ch_under_test,
566           network_debug, mac, ipv4_addr, rx_port);
567       /* TODO: handle these responses in error reporting routine */
568       if (0 != memcmp((void*)&get_filter_response->filter.mac,
569                        (void*)&expected.filter.mac,
570                        sizeof(expected.filter.mac))) {
571         ncsi_response_type = NCSI_RESPONSE_UNEXPECTED_PARAMS;
572       } else if (get_filter_response->filter.ip != expected.filter.ip ||
573             get_filter_response->filter.port != expected.filter.port) {
574         ncsi_response_type = NCSI_RESPONSE_UNEXPECTED_PARAMS;
575       } else {
576         GO_TO_NEXT_STATE(state_variable);
577         break;
578       }
579     }
580     if (ncsi_fsm_retry_test(network_debug)) {
581       GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN);
582     } else {
583       ncsi_fsm_fail(ncsi_state, network_debug);
584     }
585     break;
586   case NCSI_STATE_GET_PT_STATS:
587     len = ncsi_cmd_get_passthrough_stats(
588         ncsi_buf->data, network_debug->ncsi.test.ch_under_test);
589     GO_TO_NEXT_STATE(state_variable);
590     break;
591   case NCSI_STATE_GET_PT_STATS_RESPONSE:
592     if (!network_debug->ncsi.mlx_legacy) {
593       ncsi_response_type = ncsi_validate_std_response(
594           ncsi_buf->data, ncsi_buf->len, NCSI_GET_PASSTHROUGH_STATISTICS);
595       if (ncsi_response_type == NCSI_RESPONSE_ACK) {
596         const ncsi_passthrough_stats_response_t* response =
597             (const ncsi_passthrough_stats_response_t*)ncsi_buf->data;
598         ncsi_fsm_update_passthrough_stats(&response->stats, network_debug);
599         GO_TO_NEXT_STATE(state_variable);
600         break;
601       }
602     } else {
603       uint32_t response_size =
604           ncsi_get_response_size(NCSI_GET_PASSTHROUGH_STATISTICS) -
605           sizeof(uint32_t);
606       ncsi_response_type = ncsi_validate_response(
607           ncsi_buf->data, ncsi_buf->len, NCSI_GET_PASSTHROUGH_STATISTICS, false,
608           response_size);
609       if (NCSI_RESPONSE_ACK == ncsi_response_type) {
610         const ncsi_passthrough_stats_legacy_response_t* legacy_response =
611             (const ncsi_passthrough_stats_legacy_response_t*)ncsi_buf->data;
612         ncsi_fsm_update_passthrough_stats_legacy(&legacy_response->stats,
613                                                  network_debug);
614         GO_TO_NEXT_STATE(state_variable);
615         break;
616       }
617     }
618     if (ncsi_fsm_retry_test(network_debug)) {
619       GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN);
620     } else {
621       ncsi_fsm_fail(ncsi_state, network_debug);
622     }
623     break;
624   case NCSI_STATE_GET_LINK_STATUS:
625     // We only care about ch.0 link status because that's the only one we use
626     // to transmit.
627     len = ncsi_cmd_get_link_status(ncsi_buf->data, 0);
628     GO_TO_NEXT_STATE(state_variable);
629     break;
630   case NCSI_STATE_GET_LINK_STATUS_RESPONSE:
631     ncsi_response_type = ncsi_validate_std_response(
632         ncsi_buf->data, ncsi_buf->len, NCSI_GET_LINK_STATUS);
633     if (NCSI_RESPONSE_ACK == ncsi_response_type) {
634       const ncsi_link_status_response_t* response =
635           (const ncsi_link_status_response_t*)ncsi_buf->data;
636       const uint32_t link_status = ntohl(response->link_status.link_status);
637       if (link_status & NCSI_LINK_STATUS_UP) {
638         GO_TO_NEXT_STATE(state_variable);
639         break;
640       }
641       // TODO: report this error.
642       // CPRINT("[NCSI Link Status down {:#08x}]\n", link_status);
643     }
644     if (ncsi_fsm_retry_test(network_debug)) {
645       GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN);
646     } else {
647       ncsi_fsm_fail(ncsi_state, network_debug);
648     }
649     break;
650   case NCSI_STATE_TEST_END:
651     network_debug->ncsi.test.tries = 0;
652     if (network_debug->ncsi.pending_restart) {
653       ncsi_fsm_fail(ncsi_state, network_debug); // (Ab)use fail to restart.
654     }
655     if (++ncsi_state->retest_delay_count >= NCSI_FSM_RETEST_DELAY_COUNT) {
656       GO_TO_STATE(state_variable, NCSI_STATE_TEST_BEGIN);
657       ncsi_state->retest_delay_count = 0;
658     }
659     break;
660   default:
661     ncsi_fsm_fail(ncsi_state, network_debug);
662     break;
663   }
664 
665   ncsi_buf->len = len;
666   return ncsi_response_type;
667 }
668