e84490ba575f86e7948274311d883d5eaad36cfe
[vpp.git] / src / vnet / devices / virtio / virtio.c
1 /*
2  *------------------------------------------------------------------
3  * Copyright (c) 2017 Cisco and/or its affiliates.
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
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fcntl.h>
21 #include <net/if.h>
22 #include <linux/if_tun.h>
23 #include <sys/ioctl.h>
24 #include <sys/eventfd.h>
25
26 #include <vlib/vlib.h>
27 #include <vlib/pci/pci.h>
28 #include <vlib/unix/unix.h>
29 #include <vnet/ethernet/ethernet.h>
30 #include <vnet/ip/ip4_packet.h>
31 #include <vnet/ip/ip6_packet.h>
32 #include <vnet/devices/virtio/virtio.h>
33 #include <vnet/devices/virtio/pci.h>
34 #include <vnet/interface/rx_queue_funcs.h>
35
36 virtio_main_t virtio_main;
37
38 #define _IOCTL(fd,a,...) \
39   if (ioctl (fd, a, __VA_ARGS__) < 0) \
40     { \
41       err = clib_error_return_unix (0, "ioctl(" #a ")"); \
42       goto error; \
43     }
44
45 static clib_error_t *
46 call_read_ready (clib_file_t * uf)
47 {
48   vnet_main_t *vnm = vnet_get_main ();
49   u64 b;
50
51   CLIB_UNUSED (ssize_t size) = read (uf->file_descriptor, &b, sizeof (b));
52   vnet_hw_if_rx_queue_set_int_pending (vnm, uf->private_data);
53
54   return 0;
55 }
56
57
58 clib_error_t *
59 virtio_vring_init (vlib_main_t * vm, virtio_if_t * vif, u16 idx, u16 sz)
60 {
61   virtio_vring_t *vring;
62   int i;
63
64   if (!is_pow2 (sz))
65     return clib_error_return (0, "ring size must be power of 2");
66
67   if (sz > 32768)
68     return clib_error_return (0, "ring size must be 32768 or lower");
69
70   if (sz == 0)
71     sz = 256;
72
73   if (idx % 2)
74     {
75       vlib_thread_main_t *thm = vlib_get_thread_main ();
76       vec_validate_aligned (vif->txq_vrings, TX_QUEUE_ACCESS (idx),
77                             CLIB_CACHE_LINE_BYTES);
78       vring = vec_elt_at_index (vif->txq_vrings, TX_QUEUE_ACCESS (idx));
79       if (thm->n_vlib_mains > vif->num_txqs)
80         clib_spinlock_init (&vring->lockp);
81     }
82   else
83     {
84       vec_validate_aligned (vif->rxq_vrings, RX_QUEUE_ACCESS (idx),
85                             CLIB_CACHE_LINE_BYTES);
86       vring = vec_elt_at_index (vif->rxq_vrings, RX_QUEUE_ACCESS (idx));
87     }
88   i = sizeof (vring_desc_t) * sz;
89   i = round_pow2 (i, CLIB_CACHE_LINE_BYTES);
90   vring->desc = clib_mem_alloc_aligned (i, CLIB_CACHE_LINE_BYTES);
91   clib_memset (vring->desc, 0, i);
92
93   i = sizeof (vring_avail_t) + sz * sizeof (vring->avail->ring[0]);
94   i = round_pow2 (i, CLIB_CACHE_LINE_BYTES);
95   vring->avail = clib_mem_alloc_aligned (i, CLIB_CACHE_LINE_BYTES);
96   clib_memset (vring->avail, 0, i);
97   // tell kernel that we don't need interrupt
98   vring->avail->flags = VRING_AVAIL_F_NO_INTERRUPT;
99
100   i = sizeof (vring_used_t) + sz * sizeof (vring_used_elem_t);
101   i = round_pow2 (i, CLIB_CACHE_LINE_BYTES);
102   vring->used = clib_mem_alloc_aligned (i, CLIB_CACHE_LINE_BYTES);
103   clib_memset (vring->used, 0, i);
104
105   vring->queue_id = idx;
106   ASSERT (vring->buffers == 0);
107   vec_validate_aligned (vring->buffers, sz, CLIB_CACHE_LINE_BYTES);
108
109   if (idx & 1)
110     {
111       clib_memset_u32 (vring->buffers, ~0, sz);
112     }
113
114   vring->size = sz;
115   vring->call_fd = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);
116   vring->kick_fd = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);
117   virtio_log_debug (vif, "vring %u size %u call_fd %d kick_fd %d", idx,
118                     vring->size, vring->call_fd, vring->kick_fd);
119
120   return 0;
121 }
122
123 inline void
124 virtio_free_buffers (vlib_main_t * vm, virtio_vring_t * vring)
125 {
126   u16 used = vring->desc_in_use;
127   u16 last = vring->last_used_idx;
128   u16 mask = vring->size - 1;
129
130   while (used)
131     {
132       vlib_buffer_free (vm, &vring->buffers[last & mask], 1);
133       last++;
134       used--;
135     }
136 }
137
138 clib_error_t *
139 virtio_vring_free_rx (vlib_main_t * vm, virtio_if_t * vif, u32 idx)
140 {
141   virtio_vring_t *vring =
142     vec_elt_at_index (vif->rxq_vrings, RX_QUEUE_ACCESS (idx));
143
144   clib_file_del_by_index (&file_main, vring->call_file_index);
145   close (vring->kick_fd);
146   close (vring->call_fd);
147   if (vring->used)
148     {
149       virtio_free_buffers (vm, vring);
150       clib_mem_free (vring->used);
151     }
152   if (vring->desc)
153     clib_mem_free (vring->desc);
154   if (vring->avail)
155     clib_mem_free (vring->avail);
156   vec_free (vring->buffers);
157   return 0;
158 }
159
160 clib_error_t *
161 virtio_vring_free_tx (vlib_main_t * vm, virtio_if_t * vif, u32 idx)
162 {
163   virtio_vring_t *vring =
164     vec_elt_at_index (vif->txq_vrings, TX_QUEUE_ACCESS (idx));
165
166   clib_file_del_by_index (&file_main, vring->call_file_index);
167   close (vring->kick_fd);
168   close (vring->call_fd);
169   if (vring->used)
170     {
171       virtio_free_buffers (vm, vring);
172       clib_mem_free (vring->used);
173     }
174   if (vring->desc)
175     clib_mem_free (vring->desc);
176   if (vring->avail)
177     clib_mem_free (vring->avail);
178   vec_free (vring->buffers);
179   gro_flow_table_free (vring->flow_table);
180   virtio_vring_buffering_free (vm, vring->buffering);
181   clib_spinlock_free (&vring->lockp);
182   return 0;
183 }
184
185 void
186 virtio_set_packet_coalesce (virtio_if_t * vif)
187 {
188   vnet_main_t *vnm = vnet_get_main ();
189   vnet_hw_interface_t *hw = vnet_get_hw_interface (vnm, vif->hw_if_index);
190   virtio_vring_t *vring;
191   vif->packet_coalesce = 1;
192   vec_foreach (vring, vif->txq_vrings)
193   {
194     gro_flow_table_init (&vring->flow_table,
195                          vif->type & (VIRTIO_IF_TYPE_TAP |
196                                       VIRTIO_IF_TYPE_PCI), hw->tx_node_index);
197   }
198 }
199
200 clib_error_t *
201 virtio_set_packet_buffering (virtio_if_t * vif, u16 buffering_size)
202 {
203   vnet_main_t *vnm = vnet_get_main ();
204   vnet_hw_interface_t *hw = vnet_get_hw_interface (vnm, vif->hw_if_index);
205   virtio_vring_t *vring;
206   clib_error_t *error = 0;
207   vif->packet_buffering = 1;
208
209   vec_foreach (vring, vif->txq_vrings)
210   {
211     if ((error =
212          virtio_vring_buffering_init (&vring->buffering, hw->tx_node_index,
213                                       buffering_size)))
214       {
215         break;
216       }
217   }
218
219   return error;
220 }
221
222 void
223 virtio_vring_set_rx_queues (vlib_main_t *vm, virtio_if_t *vif)
224 {
225   vnet_main_t *vnm = vnet_get_main ();
226   virtio_vring_t *vring;
227
228   vnet_hw_if_set_input_node (vnm, vif->hw_if_index, virtio_input_node.index);
229
230   vec_foreach (vring, vif->rxq_vrings)
231     {
232       vring->queue_index = vnet_hw_if_register_rx_queue (
233         vnm, vif->hw_if_index, RX_QUEUE_ACCESS (vring->queue_id),
234         VNET_HW_IF_RXQ_THREAD_ANY);
235       vring->buffer_pool_index = vlib_buffer_pool_get_default_for_numa (
236         vm, vnet_hw_if_get_rx_queue_numa_node (vnm, vring->queue_index));
237       if (vif->type == VIRTIO_IF_TYPE_TAP || vif->type == VIRTIO_IF_TYPE_TUN)
238         {
239
240           clib_file_t f = {
241             .read_function = call_read_ready,
242             .flags = UNIX_FILE_EVENT_EDGE_TRIGGERED,
243             .file_descriptor = vring->call_fd,
244             .private_data = vring->queue_index,
245             .description = format (0, "%U vring %u", format_virtio_device_name,
246                                    vif->dev_instance, vring->queue_id),
247           };
248
249           vring->call_file_index = clib_file_add (&file_main, &f);
250           vnet_hw_if_set_rx_queue_file_index (vnm, vring->queue_index,
251                                               vring->call_file_index);
252         }
253     }
254   vnet_hw_if_update_runtime_data (vnm, vif->hw_if_index);
255 }
256
257 inline void
258 virtio_set_net_hdr_size (virtio_if_t * vif)
259 {
260   if (vif->features & VIRTIO_FEATURE (VIRTIO_NET_F_MRG_RXBUF) ||
261       vif->features & VIRTIO_FEATURE (VIRTIO_F_VERSION_1))
262     vif->virtio_net_hdr_sz = sizeof (virtio_net_hdr_v1_t);
263   else
264     vif->virtio_net_hdr_sz = sizeof (virtio_net_hdr_t);
265 }
266
267 inline void
268 virtio_show (vlib_main_t * vm, u32 * hw_if_indices, u8 show_descr, u32 type)
269 {
270   u32 i, j, hw_if_index;
271   virtio_if_t *vif;
272   vnet_main_t *vnm = &vnet_main;
273   virtio_main_t *mm = &virtio_main;
274   virtio_vring_t *vring;
275   struct feat_struct
276   {
277     u8 bit;
278     char *str;
279   };
280   struct feat_struct *feat_entry;
281
282   static struct feat_struct feat_array[] = {
283 #define _(s,b) { .str = #s, .bit = b, },
284     foreach_virtio_net_features
285 #undef _
286     {.str = NULL}
287   };
288
289   struct feat_struct *flag_entry;
290   static struct feat_struct flags_array[] = {
291 #define _(b,e,s) { .bit = b, .str = s, },
292     foreach_virtio_if_flag
293 #undef _
294     {.str = NULL}
295   };
296
297   if (!hw_if_indices)
298     return;
299
300   for (hw_if_index = 0; hw_if_index < vec_len (hw_if_indices); hw_if_index++)
301     {
302       vnet_hw_interface_t *hi =
303         vnet_get_hw_interface (vnm, hw_if_indices[hw_if_index]);
304       vif = pool_elt_at_index (mm->interfaces, hi->dev_instance);
305       if (vif->type != type)
306         continue;
307       vlib_cli_output (vm, "Interface: %U (ifindex %d)",
308                        format_vnet_hw_if_index_name, vnm,
309                        hw_if_indices[hw_if_index], vif->hw_if_index);
310       if (type == VIRTIO_IF_TYPE_PCI)
311         {
312           vlib_cli_output (vm, "  PCI Address: %U", format_vlib_pci_addr,
313                            &vif->pci_addr);
314         }
315       if (type & (VIRTIO_IF_TYPE_TAP | VIRTIO_IF_TYPE_TUN))
316         {
317           u8 *str = 0;
318           if (vif->host_if_name)
319             vlib_cli_output (vm, "  name \"%s\"", vif->host_if_name);
320           if (vif->net_ns)
321             vlib_cli_output (vm, "  host-ns \"%s\"", vif->net_ns);
322           if (vif->host_mtu_size)
323             vlib_cli_output (vm, "  host-mtu-size \"%d\"",
324                              vif->host_mtu_size);
325           if (type == VIRTIO_IF_TYPE_TAP)
326             vlib_cli_output (vm, "  host-mac-addr: %U",
327                              format_ethernet_address, vif->host_mac_addr);
328           vlib_cli_output (vm, "  host-carrier-up: %u", vif->host_carrier_up);
329
330           vec_foreach_index (i, vif->vhost_fds)
331             str = format (str, " %d", vif->vhost_fds[i]);
332           vlib_cli_output (vm, "  vhost-fds%v", str);
333           vec_free (str);
334           vec_foreach_index (i, vif->tap_fds)
335             str = format (str, " %d", vif->tap_fds[i]);
336           vlib_cli_output (vm, "  tap-fds%v", str);
337           vec_free (str);
338         }
339       vlib_cli_output (vm, "  gso-enabled %d", vif->gso_enabled);
340       vlib_cli_output (vm, "  csum-enabled %d", vif->csum_offload_enabled);
341       vlib_cli_output (vm, "  packet-coalesce %d", vif->packet_coalesce);
342       vlib_cli_output (vm, "  packet-buffering %d", vif->packet_buffering);
343       if (type & (VIRTIO_IF_TYPE_TAP | VIRTIO_IF_TYPE_PCI))
344         vlib_cli_output (vm, "  Mac Address: %U", format_ethernet_address,
345                          vif->mac_addr);
346       vlib_cli_output (vm, "  Device instance: %u", vif->dev_instance);
347       vlib_cli_output (vm, "  flags 0x%x", vif->flags);
348       flag_entry = (struct feat_struct *) &flags_array;
349       while (flag_entry->str)
350         {
351           if (vif->flags & (1ULL << flag_entry->bit))
352             vlib_cli_output (vm, "    %s (%d)", flag_entry->str,
353                              flag_entry->bit);
354           flag_entry++;
355         }
356       if (type == VIRTIO_IF_TYPE_PCI)
357         {
358           device_status (vm, vif);
359         }
360       vlib_cli_output (vm, "  features 0x%lx", vif->features);
361       feat_entry = (struct feat_struct *) &feat_array;
362       while (feat_entry->str)
363         {
364           if (vif->features & (1ULL << feat_entry->bit))
365             vlib_cli_output (vm, "    %s (%d)", feat_entry->str,
366                              feat_entry->bit);
367           feat_entry++;
368         }
369       vlib_cli_output (vm, "  remote-features 0x%lx", vif->remote_features);
370       feat_entry = (struct feat_struct *) &feat_array;
371       while (feat_entry->str)
372         {
373           if (vif->remote_features & (1ULL << feat_entry->bit))
374             vlib_cli_output (vm, "    %s (%d)", feat_entry->str,
375                              feat_entry->bit);
376           feat_entry++;
377         }
378       vlib_cli_output (vm, "  Number of RX Virtqueue  %u", vif->num_rxqs);
379       vlib_cli_output (vm, "  Number of TX Virtqueue  %u", vif->num_txqs);
380       if (vif->cxq_vring != NULL
381           && vif->features & VIRTIO_FEATURE (VIRTIO_NET_F_CTRL_VQ))
382         vlib_cli_output (vm, "  Number of CTRL Virtqueue 1");
383       vec_foreach_index (i, vif->rxq_vrings)
384       {
385         vring = vec_elt_at_index (vif->rxq_vrings, i);
386         vlib_cli_output (vm, "  Virtqueue (RX) %d", vring->queue_id);
387         vlib_cli_output (vm,
388                          "    qsz %d, last_used_idx %d, desc_next %d, desc_in_use %d",
389                          vring->size, vring->last_used_idx, vring->desc_next,
390                          vring->desc_in_use);
391         if (vif->is_packed)
392           {
393             vlib_cli_output (vm,
394                              "    driver_event.flags 0x%x driver_event.off_wrap %d device_event.flags 0x%x device_event.off_wrap %d",
395                              vring->driver_event->flags,
396                              vring->driver_event->off_wrap,
397                              vring->device_event->flags,
398                              vring->device_event->off_wrap);
399             vlib_cli_output (vm,
400                              "    avail wrap counter %d, used wrap counter %d",
401                              vring->avail_wrap_counter,
402                              vring->used_wrap_counter);
403           }
404         else
405           vlib_cli_output (vm,
406                            "    avail.flags 0x%x avail.idx %d used.flags 0x%x used.idx %d",
407                            vring->avail->flags, vring->avail->idx,
408                            vring->used->flags, vring->used->idx);
409         if (type & (VIRTIO_IF_TYPE_TAP | VIRTIO_IF_TYPE_TUN))
410           {
411             vlib_cli_output (vm, "    kickfd %d, callfd %d", vring->kick_fd,
412                              vring->call_fd);
413           }
414         if (show_descr)
415           {
416             vlib_cli_output (vm, "\n  descriptor table:\n");
417             vlib_cli_output (vm,
418                              "   id          addr         len  flags  next/id      user_addr\n");
419             vlib_cli_output (vm,
420                              "  ===== ================== ===== ====== ======= ==================\n");
421             for (j = 0; j < vring->size; j++)
422               {
423                 if (vif->is_packed)
424                   {
425                     vring_packed_desc_t *desc = &vring->packed_desc[j];
426                     vlib_cli_output (vm,
427                                      "  %-5d 0x%016lx %-5d 0x%04x %-8d 0x%016lx\n",
428                                      j, desc->addr,
429                                      desc->len,
430                                      desc->flags, desc->id, desc->addr);
431                   }
432                 else
433                   {
434                     vring_desc_t *desc = &vring->desc[j];
435                     vlib_cli_output (vm,
436                                      "  %-5d 0x%016lx %-5d 0x%04x %-8d 0x%016lx\n",
437                                      j, desc->addr,
438                                      desc->len,
439                                      desc->flags, desc->next, desc->addr);
440                   }
441               }
442           }
443       }
444       vec_foreach_index (i, vif->txq_vrings)
445       {
446         vring = vec_elt_at_index (vif->txq_vrings, i);
447         vlib_cli_output (vm, "  Virtqueue (TX) %d", vring->queue_id);
448         vlib_cli_output (vm,
449                          "    qsz %d, last_used_idx %d, desc_next %d, desc_in_use %d",
450                          vring->size, vring->last_used_idx, vring->desc_next,
451                          vring->desc_in_use);
452         if (vif->is_packed)
453           {
454             vlib_cli_output (vm,
455                              "    driver_event.flags 0x%x driver_event.off_wrap %d device_event.flags 0x%x device_event.off_wrap %d",
456                              vring->driver_event->flags,
457                              vring->driver_event->off_wrap,
458                              vring->device_event->flags,
459                              vring->device_event->off_wrap);
460             vlib_cli_output (vm,
461                              "    avail wrap counter %d, used wrap counter %d",
462                              vring->avail_wrap_counter,
463                              vring->used_wrap_counter);
464           }
465         else
466           vlib_cli_output (vm,
467                            "    avail.flags 0x%x avail.idx %d used.flags 0x%x used.idx %d",
468                            vring->avail->flags, vring->avail->idx,
469                            vring->used->flags, vring->used->idx);
470         if (type & (VIRTIO_IF_TYPE_TAP | VIRTIO_IF_TYPE_TUN))
471           {
472             vlib_cli_output (vm, "    kickfd %d, callfd %d", vring->kick_fd,
473                              vring->call_fd);
474           }
475         if (vring->flow_table)
476           {
477             vlib_cli_output (vm, "    %U", gro_flow_table_format,
478                              vring->flow_table);
479           }
480         if (vif->packet_buffering)
481           {
482             vlib_cli_output (vm, "    %U", virtio_vring_buffering_format,
483                              vring->buffering);
484           }
485         if (show_descr)
486           {
487             vlib_cli_output (vm, "\n  descriptor table:\n");
488             vlib_cli_output (vm,
489                              "   id          addr         len  flags  next/id      user_addr\n");
490             vlib_cli_output (vm,
491                              "  ===== ================== ===== ====== ======== ==================\n");
492             for (j = 0; j < vring->size; j++)
493               {
494                 if (vif->is_packed)
495                   {
496                     vring_packed_desc_t *desc = &vring->packed_desc[j];
497                     vlib_cli_output (vm,
498                                      "  %-5d 0x%016lx %-5d 0x%04x %-8d 0x%016lx\n",
499                                      j, desc->addr,
500                                      desc->len,
501                                      desc->flags, desc->id, desc->addr);
502                   }
503                 else
504                   {
505                     vring_desc_t *desc = &vring->desc[j];
506                     vlib_cli_output (vm,
507                                      "  %-5d 0x%016lx %-5d 0x%04x %-8d 0x%016lx\n",
508                                      j, desc->addr,
509                                      desc->len,
510                                      desc->flags, desc->next, desc->addr);
511                   }
512               }
513           }
514       }
515       if (vif->cxq_vring != NULL
516           && vif->features & VIRTIO_FEATURE (VIRTIO_NET_F_CTRL_VQ))
517         {
518           vring = vif->cxq_vring;
519           vlib_cli_output (vm, "  Virtqueue (CTRL) %d", vring->queue_id);
520           vlib_cli_output (vm,
521                            "    qsz %d, last_used_idx %d, desc_next %d, desc_in_use %d",
522                            vring->size, vring->last_used_idx,
523                            vring->desc_next, vring->desc_in_use);
524           if (vif->is_packed)
525             {
526               vlib_cli_output (vm,
527                                "    driver_event.flags 0x%x driver_event.off_wrap %d device_event.flags 0x%x device_event.off_wrap %d",
528                                vring->driver_event->flags,
529                                vring->driver_event->off_wrap,
530                                vring->device_event->flags,
531                                vring->device_event->off_wrap);
532               vlib_cli_output (vm,
533                                "    avail wrap counter %d, used wrap counter %d",
534                                vring->avail_wrap_counter,
535                                vring->used_wrap_counter);
536             }
537           else
538             {
539               vlib_cli_output (vm,
540                                "    avail.flags 0x%x avail.idx %d used.flags 0x%x used.idx %d",
541                                vring->avail->flags, vring->avail->idx,
542                                vring->used->flags, vring->used->idx);
543             }
544           if (type & (VIRTIO_IF_TYPE_TAP | VIRTIO_IF_TYPE_TUN))
545             {
546               vlib_cli_output (vm, "    kickfd %d, callfd %d", vring->kick_fd,
547                                vring->call_fd);
548             }
549           if (show_descr)
550             {
551               vlib_cli_output (vm, "\n  descriptor table:\n");
552               vlib_cli_output (vm,
553                                "   id          addr         len  flags  next/id      user_addr\n");
554               vlib_cli_output (vm,
555                                "  ===== ================== ===== ====== ======== ==================\n");
556               for (j = 0; j < vring->size; j++)
557                 {
558                   if (vif->is_packed)
559                     {
560                       vring_packed_desc_t *desc = &vring->packed_desc[j];
561                       vlib_cli_output (vm,
562                                        "  %-5d 0x%016lx %-5d 0x%04x %-8d 0x%016lx\n",
563                                        j, desc->addr,
564                                        desc->len,
565                                        desc->flags, desc->id, desc->addr);
566                     }
567                   else
568                     {
569                       vring_desc_t *desc = &vring->desc[j];
570                       vlib_cli_output (vm,
571                                        "  %-5d 0x%016lx %-5d 0x%04x %-8d 0x%016lx\n",
572                                        j, desc->addr,
573                                        desc->len,
574                                        desc->flags, desc->next, desc->addr);
575                     }
576                 }
577             }
578         }
579
580     }
581
582 }
583
584 static clib_error_t *
585 virtio_init (vlib_main_t * vm)
586 {
587   virtio_main_t *vim = &virtio_main;
588   clib_error_t *error = 0;
589
590   vim->log_default = vlib_log_register_class ("virtio", 0);
591   vlib_log_debug (vim->log_default, "initialized");
592
593   return error;
594 }
595
596 VLIB_INIT_FUNCTION (virtio_init);
597
598 /*
599  * fd.io coding-style-patch-verification: ON
600  *
601  * Local Variables:
602  * eval: (c-set-style "gnu")
603  * End:
604  */