xref: /openbmc/google-misc/ncsid/test/nic_mock.cpp (revision 03eba281)
1 #include "nic_mock.h"
2 
3 #include "platforms/nemora/portable/ncsi.h"
4 
5 #include <algorithm>
6 #include <cstddef>
7 #include <stdexcept>
8 
9 namespace mock
10 {
11 
12 bool NCSIFrame::parse_ethernet_frame(const ncsi_buf_t& ncsi_buf)
13 {
14     std::memcpy(&dst_mac_, ncsi_buf.data, sizeof(dst_mac_));
15     std::memcpy(&src_mac_, ncsi_buf.data + sizeof(dst_mac_), sizeof(src_mac_));
16     // The constant defined in a way that assumes big-endian platform, so we are
17     // just going to calculate it here properly.
18     const uint8_t et_hi = *(ncsi_buf.data + 2 * sizeof(mac_addr_t));
19     const uint8_t et_lo = *(ncsi_buf.data + 2 * sizeof(mac_addr_t) + 1);
20     ethertype_ = (et_hi << 8) + et_lo;
21 
22     if (ethertype_ != NCSI_ETHERTYPE)
23     {
24         return false;
25     }
26 
27     // This code parses the NC-SI command, according to spec and
28     // as defined in platforms/nemora/portable/ncsi.h
29     // It takes some shortcuts to only retrieve the data we are interested in,
30     // such as using offsetof ot get to a particular field.
31     control_packet_type_ =
32         *(ncsi_buf.data + offsetof(ncsi_header_t, control_packet_type));
33     channel_id_ = *(ncsi_buf.data + offsetof(ncsi_header_t, channel_id));
34 
35     size_t payload_offset = sizeof(ncsi_header_t);
36     if (control_packet_type_ & NCSI_RESPONSE)
37     {
38         is_response_ = true;
39         control_packet_type_ &= ~NCSI_RESPONSE;
40         std::memcpy(&response_code_, ncsi_buf.data + payload_offset,
41                     sizeof(response_code_));
42         response_code_ = ntohs(response_code_);
43         std::memcpy(&reason_code_,
44                     ncsi_buf.data + payload_offset + sizeof(reason_code_),
45                     sizeof(reason_code_));
46         reason_code_ = ntohs(reason_code_);
47         payload_offset += sizeof(reason_code_) + sizeof(response_code_);
48     }
49 
50     if (control_packet_type_ == NCSI_OEM_COMMAND)
51     {
52         std::memcpy(&manufacturer_id_, ncsi_buf.data + payload_offset,
53                     sizeof(manufacturer_id_));
54         manufacturer_id_ = ntohl(manufacturer_id_);
55         // Number of reserved bytes after manufacturer_id_ = 3
56         oem_command_ =
57             *(ncsi_buf.data + payload_offset + sizeof(manufacturer_id_) + 3);
58         payload_offset += sizeof(ncsi_oem_extension_header_t);
59     }
60 
61     packet_raw_ =
62         std::vector<uint8_t>(ncsi_buf.data, ncsi_buf.data + ncsi_buf.len);
63     // TODO: Verify payload length.
64 
65     return true;
66 }
67 
68 uint32_t NIC::handle_request(const ncsi_buf_t& request_buf,
69                              ncsi_buf_t* response_buf)
70 {
71     const ncsi_header_t* ncsi_header =
72         reinterpret_cast<const ncsi_header_t*>(request_buf.data);
73 
74     NCSIFrame request_frame;
75     request_frame.parse_ethernet_frame(request_buf);
76     save_frame_to_log(request_frame);
77 
78     uint32_t response_size;
79     if (is_loopback_)
80     {
81         std::memcpy(response_buf, &request_buf, sizeof(request_buf));
82         response_size = request_buf.len;
83     }
84     else if (std::find(simple_commands_.begin(), simple_commands_.end(),
85                        ncsi_header->control_packet_type) !=
86              simple_commands_.end())
87     {
88         // Simple Response
89         response_size =
90             ncsi_build_simple_ack(request_buf.data, response_buf->data);
91     }
92     else
93     {
94         // Not-so-Simple Response
95         switch (ncsi_header->control_packet_type)
96         {
97             case NCSI_GET_VERSION_ID:
98                 response_size = ncsi_build_version_id_ack(
99                     request_buf.data, response_buf->data, &version_);
100                 break;
101             case NCSI_GET_CAPABILITIES:
102                 response_size = sizeof(ncsi_capabilities_response_t);
103                 {
104                     ncsi_capabilities_response_t response;
105                     ncsi_build_response_header(
106                         request_buf.data, reinterpret_cast<uint8_t*>(&response),
107                         0, 0, response_size - sizeof(ncsi_header_t));
108                     response.channel_count = channel_count_;
109                     std::memcpy(response_buf->data, &response,
110                                 sizeof(response));
111                 }
112                 break;
113             case NCSI_GET_PASSTHROUGH_STATISTICS:
114                 if (is_legacy_)
115                 {
116                     response_size = ncsi_build_pt_stats_legacy_ack(
117                         request_buf.data, response_buf->data, &stats_legacy_);
118                 }
119                 else
120                 {
121                     response_size = ncsi_build_pt_stats_ack(
122                         request_buf.data, response_buf->data, &stats_);
123                 }
124                 break;
125             case NCSI_GET_LINK_STATUS:
126                 response_size = ncsi_build_link_status_ack(
127                     request_buf.data, response_buf->data, &link_status_);
128                 break;
129             case NCSI_OEM_COMMAND:
130                 response_size = handle_oem_request(request_buf, response_buf);
131                 break;
132             default:
133                 response_size = ncsi_build_simple_nack(
134                     request_buf.data, response_buf->data, 1, 1);
135                 break;
136         }
137     }
138 
139     response_buf->len = response_size;
140 
141     return response_size;
142 }
143 
144 uint32_t NIC::handle_oem_request(const ncsi_buf_t& request_buf,
145                                  ncsi_buf_t* response_buf)
146 {
147     const ncsi_oem_simple_cmd_t* oem_cmd =
148         reinterpret_cast<const ncsi_oem_simple_cmd_t*>(request_buf.data);
149     uint32_t response_size;
150     switch (oem_cmd->oem_header.oem_cmd)
151     {
152         case NCSI_OEM_COMMAND_GET_HOST_MAC:
153             response_size = ncsi_build_oem_get_mac_ack(
154                 request_buf.data, response_buf->data, &mac_);
155             break;
156         case NCSI_OEM_COMMAND_SET_FILTER:
157         {
158             const ncsi_oem_set_filter_cmd_t* cmd =
159                 reinterpret_cast<const ncsi_oem_set_filter_cmd_t*>(
160                     request_buf.data);
161             if (set_filter(cmd->hdr.channel_id, cmd->filter))
162             {
163                 response_size = ncsi_build_oem_simple_ack(request_buf.data,
164                                                           response_buf->data);
165             }
166             else
167             {
168                 response_size = ncsi_build_simple_nack(
169                     request_buf.data, response_buf->data, 3, 4);
170             }
171         }
172         break;
173         case NCSI_OEM_COMMAND_ECHO:
174             response_size =
175                 ncsi_build_oem_echo_ack(request_buf.data, response_buf->data);
176             break;
177         case NCSI_OEM_COMMAND_GET_FILTER:
178         {
179             const ncsi_simple_command_t* cmd =
180                 reinterpret_cast<const ncsi_simple_command_t*>(
181                     request_buf.data);
182             if (cmd->hdr.channel_id == 0)
183             {
184                 response_size = ncsi_build_oem_get_filter_ack(
185                     request_buf.data, response_buf->data, &ch0_filter_);
186             }
187             else if (cmd->hdr.channel_id == 1)
188             {
189                 response_size = ncsi_build_oem_get_filter_ack(
190                     request_buf.data, response_buf->data, &ch1_filter_);
191             }
192             else
193             {
194                 response_size = ncsi_build_simple_nack(
195                     request_buf.data, response_buf->data, 3, 4);
196             }
197         }
198         break;
199         default:
200             response_size = ncsi_build_simple_nack(request_buf.data,
201                                                    response_buf->data, 1, 2);
202             break;
203     }
204 
205     return response_size;
206 }
207 
208 bool NIC::is_filter_configured(uint8_t channel) const
209 {
210     if (channel == 0)
211     {
212         return is_ch0_filter_configured_;
213     }
214     else if (channel == 1)
215     {
216         return is_ch1_filter_configured_;
217     }
218 
219     throw std::invalid_argument("Unsupported channel");
220 }
221 
222 bool NIC::set_filter(uint8_t channel, const ncsi_oem_filter_t& filter)
223 {
224     ncsi_oem_filter_t* nic_filter;
225     if (channel == 0)
226     {
227         nic_filter = &ch0_filter_;
228         is_ch0_filter_configured_ = true;
229     }
230     else if (channel == 1)
231     {
232         nic_filter = &ch1_filter_;
233         is_ch1_filter_configured_ = true;
234     }
235     else
236     {
237         throw std::invalid_argument("Unsupported channel");
238     }
239 
240     std::memcpy(nic_filter->mac, filter.mac, MAC_ADDR_SIZE);
241     nic_filter->ip = 0;
242     nic_filter->port = filter.port;
243     return true;
244 }
245 
246 const ncsi_oem_filter_t& NIC::get_filter(uint8_t channel) const
247 {
248     if (channel == 0)
249     {
250         return ch0_filter_;
251     }
252     else if (channel == 1)
253     {
254         return ch1_filter_;
255     }
256 
257     throw std::invalid_argument("Unsupported channel");
258 }
259 
260 void NIC::set_hostless(bool is_hostless)
261 {
262     auto set_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto
263     {
264         return lhs | rhs;
265     };
266 
267     auto clear_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto
268     {
269         return lhs & ~rhs;
270     };
271 
272     auto flag_op = is_hostless ? set_flag_op : clear_flag_op;
273 
274     if (channel_count_ > 0)
275     {
276         ch0_filter_.flags =
277             flag_op(ch0_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
278     }
279 
280     if (channel_count_ > 1)
281     {
282         ch1_filter_.flags =
283             flag_op(ch1_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
284     }
285 }
286 
287 void NIC::toggle_hostless()
288 {
289     if (channel_count_ > 0)
290     {
291         ch0_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
292     }
293 
294     if (channel_count_ > 1)
295     {
296         ch1_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
297     }
298 }
299 
300 bool NIC::is_hostless()
301 {
302     return ch0_filter_.flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS;
303 }
304 
305 void NIC::save_frame_to_log(const NCSIFrame& frame)
306 {
307     if (cmd_log_.size() >= max_log_size_)
308     {
309         cmd_log_.erase(cmd_log_.begin());
310     }
311 
312     cmd_log_.push_back(frame);
313 }
314 
315 const std::vector<uint8_t> NIC::simple_commands_ = {
316     NCSI_CLEAR_INITIAL_STATE,
317     NCSI_SELECT_PACKAGE,
318     NCSI_DESELECT_PACKAGE,
319     NCSI_ENABLE_CHANNEL,
320     NCSI_DISABLE_CHANNEL,
321     NCSI_RESET_CHANNEL,
322     NCSI_ENABLE_CHANNEL_NETWORK_TX,
323     NCSI_DISABLE_CHANNEL_NETWORK_TX,
324     NCSI_AEN_ENABLE,
325     NCSI_SET_LINK,
326     NCSI_SET_VLAN_FILTER,
327     NCSI_ENABLE_VLAN,
328     NCSI_DISABLE_VLAN,
329     NCSI_SET_MAC_ADDRESS,
330     NCSI_ENABLE_BROADCAST_FILTER,
331     NCSI_DISABLE_BROADCAST_FILTER,
332     NCSI_ENABLE_GLOBAL_MULTICAST_FILTER,
333     NCSI_DISABLE_GLOBAL_MULTICAST_FILTER,
334     NCSI_SET_NCSI_FLOW_CONTROL,
335 };
336 
337 } // namespace mock
338