]> rtime.felk.cvut.cz Git - l4.git/blob - l4/pkg/vmm/server/virtio_network_switch/main.cc
update
[l4.git] / l4 / pkg / vmm / server / virtio_network_switch / main.cc
1 #include <l4/re/env>
2 #include <l4/re/error_helper>
3 #include <l4/re/util/cap_alloc>
4 #include <l4/re/util/object_registry>
5 #include <l4/re/debug>
6
7 #include <l4/sys/thread>
8 #include <l4/sys/factory>
9 #include <l4/sys/compiler.h>
10
11 #include <l4/cxx/bitfield>
12 #include <l4/cxx/ipc_stream>
13 #include <l4/cxx/ipc_server>
14
15 #include <l4/re/dataspace>
16 #include <l4/re/util/meta>
17
18 #include <cstring>
19 #include <unistd.h>
20 #include <algorithm>
21
22 #include "virtio.h"
23
24 #include "pthread.h"
25
26 //#define CONFIG_STATS 1
27 //#define CONFIG_BENCHMARK 1
28
29 static L4::Cap<L4::Kobject> rcv_cap[2];
30
31 namespace Virtio {
32 class Mmio_remote : public L4::Kobject_t<Mmio_remote, L4::Kobject>
33 {
34   L4_KOBJECT(Mmio_remote)
35 };
36 }
37
38 template< typename T >
39 class Irq_object : public L4::Server_object
40 {
41 private:
42   int dispatch(l4_umword_t, L4::Ipc::Iostream &)
43   {
44     static_cast<T*>(this)->kick();
45     return 0;
46   }
47
48 public:
49   L4::Cap<L4::Irq> irq_obj_cap() const
50   { return L4::cap_cast<L4::Irq>(obj_cap()); }
51 };
52
53 template<typename T>
54 class Mmio_device_t : public L4::Server_object
55 {
56
57   int dispatch(l4_umword_t, L4::Ipc::Iostream &ios)
58   {
59     l4_msgtag_t tag;
60     ios >> tag;
61
62     switch (tag.label())
63       {
64       case L4::Meta::Protocol:
65         return L4Re::Util::handle_meta_request<Virtio::Mmio_remote>(ios);
66
67       case 0:
68         break;
69
70       default:
71         return -L4_EBADPROTO;
72       }
73
74     L4::Opcode op;
75     ios >> op;
76     switch (op)
77       {
78       case 0:
79           {
80             unsigned reg;
81             char size;
82             ios >> reg >> size;
83             l4_uint32_t val = static_cast<T*>(this)->read(reg, size);
84             ios << val;
85             return 0;
86           }
87
88       case 1:
89           {
90             unsigned reg;
91             char size;
92             l4_uint32_t val;
93             ios >> reg >> size >> val;
94             static_cast<T*>(this)->write(reg, size, val);
95             return 0;
96           }
97
98       case 2:
99           {
100             L4::Ipc::Snd_fpage irq_cap_fp, ds_cap_fp;
101             l4_addr_t ds_base;
102
103             if (!tag.items())
104               return -L4_EINVAL;
105
106             ios >> ds_base >> irq_cap_fp >> ds_cap_fp;
107
108             if (!irq_cap_fp.cap_received())
109               return -L4_EINVAL;
110
111             kick_guest_irq.get().move(L4::cap_cast<L4::Irq>(rcv_cap[0]));
112             static_cast<T*>(this)->check_n_init_shm(ds_cap_fp, rcv_cap[1],
113                                                     ds_base);
114
115             ios << static_cast<T*>(this)->irq_obj_cap()
116                 << static_cast<T*>(this)->device_config_ds.get();
117             return 0;
118           }
119       }
120
121     return 0;
122   }
123
124 protected:
125   L4Re::Util::Auto_cap<L4::Irq>::Cap kick_guest_irq;
126 };
127
128 namespace Virtio {
129
130
131 class Dev : public Mmio_device_t<Dev>, public Irq_object<Dev>
132 {
133 public:
134   typedef Irq_object<Dev> Irq;
135   typedef Mmio_device_t<Dev> Mmio;
136
137   template<typename REG>
138   void register_obj(REG *registry, char const *service = 0)
139   {
140     L4Re::chkcap(registry->register_irq_obj(static_cast<Irq*>(this)));
141     if (service)
142       L4Re::chkcap(registry->register_obj(static_cast<Mmio*>(this), service));
143     else
144       L4Re::chkcap(registry->register_obj(static_cast<Mmio*>(this)));
145
146     kick_guest_irq = L4Re::Util::cap_alloc.alloc<L4::Irq>();
147     guest_shm = L4Re::Util::cap_alloc.alloc<L4Re::Dataspace>();
148   }
149
150 protected:
151   Ring *_current_q;
152   Ring *_queues;
153   unsigned _num_queues;
154
155   L4Re::Rm::Auto_region<l4_addr_t> shm;
156   l4_addr_t shm_base;
157   l4_mword_t _shm_offset;
158   L4Re::Util::Auto_cap<L4Re::Dataspace>::Cap guest_shm;
159   L4Re::Rm::Auto_region<Virtio::Dev_config*> device_config;
160 public:
161   L4Re::Util::Auto_cap<L4Re::Dataspace>::Cap device_config_ds;
162
163 public:
164   Dev(l4_uint32_t vendor, l4_uint32_t device)
165   : _current_q(0), _queues(0), _num_queues(0)
166   {
167     L4Re::Util::Auto_cap<L4Re::Dataspace>::Cap cfg
168       = L4Re::chkcap(L4Re::Util::cap_alloc.alloc<L4Re::Dataspace>());
169     L4Re::chksys(L4Re::Env::env()->mem_alloc()->alloc(L4_PAGESIZE, cfg.get()));
170     L4Re::chksys(L4Re::Env::env()->rm()->attach(&device_config, L4_PAGESIZE,
171                                                 L4Re::Rm::Search_addr,
172                                                 cfg.get()));
173     device_config_ds = cfg;
174
175     device_config->vendor = vendor;
176     device_config->device = device;
177     device_config->irq_status = 0;
178     device_config->status = Dev_config::Status(0);
179     device_config->host_features = Dev_config::Features(0);
180     device_config->page_size = L4_PAGESIZE;
181   }
182
183   template<typename T>
184   T *access(Virtio::Ptr<T> p)
185   { return (T*)(p.get() + _shm_offset); }
186
187   void check_n_init_shm(L4::Ipc::Snd_fpage shm_cap_fp, L4::Cap<void> rcv_cap,
188                         l4_addr_t base)
189   {
190     if (!shm_cap_fp.cap_received())
191       L4Re::chksys(-L4_EINVAL);
192
193     guest_shm.get().move(L4::cap_cast<L4Re::Dataspace>(rcv_cap));
194     L4Re::chksys(L4Re::Env::env()->rm()->attach(&shm, guest_shm->size(),
195                  L4Re::Rm::Search_addr, guest_shm.get()));
196
197     printf("PORT[%p]: shm @ %lx\n", this, shm.get());
198
199     shm_base = base;
200     _shm_offset = shm.get() - base;
201   }
202
203   virtual l4_uint32_t read_config(unsigned /*reg*/) { return 0; }
204   virtual void write_config(unsigned /*reg*/, l4_uint32_t /*value*/) {}
205   virtual void kick() = 0;
206
207   Ring *queue(unsigned idx)
208   {
209     if (idx < _num_queues)
210       return &_queues[idx];
211     return 0;
212   }
213
214   virtual void reset() {}
215
216   l4_uint32_t read(unsigned reg, char /*size*/)
217   {
218     if (reg >= 0x100)
219       return read_config(reg - 0x100);
220
221     switch (reg >> 2)
222       {
223       case 0: return *reinterpret_cast<l4_uint32_t const *>("virt");
224       case 1: return 1;
225       case 2: return device_config->device;
226       case 3: return device_config->vendor;
227       case 4: return device_config->host_features.raw;
228       case 13:
229         if (_current_q)
230           return _current_q->max_num;
231         else
232           return 0;
233
234       case 16:
235         if (_current_q)
236           return (unsigned long)_current_q->desc / device_config->page_size;
237         else
238           return 0;
239
240       case 24:
241         if (0)
242           {
243             l4_uint32_t tmp = device_config->irq_status;
244             device_config->irq_status = 0;
245             return tmp;
246           }
247         return 1;
248
249       case 28: return device_config->status.raw;
250       }
251     return ~0;
252   }
253
254   void write(unsigned reg, char /*size*/, l4_uint32_t value)
255   {
256     if (reg >= 0x100)
257       {
258         write_config(reg - 0x100, value);
259         return;
260       }
261
262     switch (reg >> 2)
263       {
264       case 4:
265         device_config->guest_features.raw = value;
266         break;
267
268       case 10:
269         device_config->page_size = value;
270         break;
271
272       case 12:
273         _current_q = queue(value);
274         break;
275
276       case 14:
277         if (_current_q)
278           _current_q->num = _current_q->max_num >= value
279                             ? value
280                             : _current_q->max_num;
281         break;
282
283       case 15:
284         if (_current_q)
285           _current_q->align = value;
286         break;
287
288       case 16:
289         if (_current_q)
290           _current_q->setup((void *)(shm.get()
291                                      + value * device_config->page_size
292                                      - shm_base));
293         break;
294
295       case 20:
296         // TODO: Kick must be implemented in VMM
297         break;
298
299       case 28:
300         device_config->status.raw = value;
301         if (value == 0)
302           reset();
303         break;
304       }
305   }
306 };
307
308 }
309
310 #ifdef CONFIG_STATS
311 static void *stats_thread_loop(void *);
312 #endif
313
314 class Virtio_net;
315
316 class Switch
317 {
318 public:
319   struct Hdr_flags
320   {
321     l4_uint8_t raw;
322     CXX_BITFIELD_MEMBER( 0, 0, need_csum, raw);
323     CXX_BITFIELD_MEMBER( 1, 1, data_valid, raw);
324   };
325
326   struct Hdr
327   {
328     Hdr_flags flags;
329     l4_uint8_t gso_type;
330     l4_uint16_t hdr_len;
331     l4_uint16_t gso_size;
332     l4_uint16_t csum_start;
333     l4_uint16_t csum_offset;
334     l4_uint16_t num_buffers;
335   };
336
337   virtual void tx(Hdr const *, Virtio::Ring::Desc *descs, unsigned pkt,
338                   Virtio_net *p) = 0;
339 };
340
341
342 class Virtio_net : public Virtio::Dev
343 {
344 public:
345   struct Features : Virtio::Dev_config::Features
346   {
347     CXX_BITFIELD_MEMBER( 0,  0, csum, raw);       // host handles partial csum
348     CXX_BITFIELD_MEMBER( 1,  1, guest_csum, raw); // guest handles partial csum
349     CXX_BITFIELD_MEMBER( 5,  5, mac, raw);        // host has given mac
350     CXX_BITFIELD_MEMBER( 6,  6, gso, raw);        // host handles packets /w any GSO
351     CXX_BITFIELD_MEMBER( 7,  7, guest_tso4, raw); // guest handles TSOv4 in
352     CXX_BITFIELD_MEMBER( 8,  8, guest_tso6, raw); // guest handles TSOv6 in
353     CXX_BITFIELD_MEMBER( 9,  9, guest_ecn, raw);  // guest handles TSO[6] with ECN in
354     CXX_BITFIELD_MEMBER(10, 10, guest_ufo, raw);  // guest handles UFO in
355     CXX_BITFIELD_MEMBER(11, 11, host_tso4, raw);  // host handles TSOv4 in
356     CXX_BITFIELD_MEMBER(12, 12, host_tso6, raw);  // host handles TSOv6 in
357     CXX_BITFIELD_MEMBER(13, 13, host_ecn, raw);   // host handles TSO[6] with ECN in
358     CXX_BITFIELD_MEMBER(14, 14, host_ufo, raw);   // host handles UFO
359     CXX_BITFIELD_MEMBER(15, 15, mrg_rxbuf, raw);  // host can merge receive buffers
360     CXX_BITFIELD_MEMBER(16, 16, status, raw);     // virtio_net_config.status available
361     CXX_BITFIELD_MEMBER(17, 17, ctrl_vq, raw);    // Control channel available
362     CXX_BITFIELD_MEMBER(18, 18, ctrl_rx, raw);    // Control channel RX mode support
363     CXX_BITFIELD_MEMBER(19, 19, ctrl_vlan, raw);  // Control channel VLAN filtering
364     CXX_BITFIELD_MEMBER(20, 20, ctrl_rx_extra, raw); // Extra RX mode control support
365     CXX_BITFIELD_MEMBER(21, 21, guest_announce, raw); // Guest can announce device on the network
366     CXX_BITFIELD_MEMBER(22, 22, mq, raw);         // Device supports Receive Flow Steering
367     CXX_BITFIELD_MEMBER(23, 23, ctrl_mac_addr, raw); // Set MAC address
368   };
369
370   typedef Switch::Hdr Hdr;
371
372   Features &host_features()
373   { return static_cast<Features &>(device_config->host_features); }
374   Features host_features() const
375   { return static_cast<Features const &>(device_config->host_features); }
376
377   enum
378   {
379     Rx = 0,
380     Tx = 1
381   };
382
383 #ifdef CONFIG_STATS
384   unsigned long num_tx;
385   unsigned long num_rx;
386   unsigned long num_dropped;
387   unsigned long num_irqs;
388 #endif
389
390   Virtio_net()
391   : Virtio::Dev(0x44, 1)
392 #ifdef CONFIG_STATS
393     , num_tx(0), num_rx(0), num_dropped(0), num_irqs(0)
394 #endif
395   {
396     _queues = _q;
397     device_config->num_queues = _num_queues = 2;
398     _q[Rx].max_num = 0x1000;
399     _q[Tx].max_num = 0x1000;
400
401     host_features().csum()      = true;
402     host_features().host_tso4() = true;
403     host_features().host_tso6() = true;
404     host_features().host_ufo()  = true;
405     host_features().host_ecn()  = true;
406
407     host_features().guest_csum() = true;
408     host_features().guest_tso4() = true;
409     host_features().guest_tso6() = true;
410     host_features().guest_ufo()  = true;
411     host_features().guest_ecn()  = true;
412   }
413
414   void reset()
415   {
416     _q[Rx].desc = 0;
417     _q[Tx].desc = 0;
418   }
419
420   bool process_tx_q()
421   {
422     if (0)
423       printf("%s: process tx queue\n", name);
424     Virtio::Ring *q = &_q[Tx];
425
426     l4_uint32_t irqs = 0;
427
428     Virtio::Ring::Desc *d;
429     do
430       {
431         q->used->flags.no_notify() = 1;
432         if (0)
433           printf("q->avail.idx=%d\n", q->avail->idx);
434         while ((d = q->next_avail()))
435           {
436             if (0)
437               d->dump(1);
438
439 #ifndef CONFIG_BENCHMARK
440             if (L4_UNLIKELY(d->flags.write()))
441               {
442                 printf("PORT[%p]: input buffer in TX queue (skip)\n", this);
443                 q->consumed(d);
444                 return true;
445               }
446
447             if (L4_UNLIKELY(!d->flags.next()))
448               {
449                 printf("PORT[%p]: input w/o data\n", this);
450                 q->consumed(d);
451                 return true;
452               }
453 #endif
454
455             Hdr const *hdr = access(d->buf<Hdr const>());
456
457             Virtio::Ring::Desc *pkt = &q->desc[d->next];
458
459 #ifndef CONFIG_BENSCHMARK
460             if (L4_UNLIKELY(pkt->flags.write()))
461               {
462                 printf("PORT[%p]: input buffer in TX queue (skip)\n", this);
463                 q->consumed(d);
464                 return true;
465               }
466 #endif
467 #ifdef CONFIG_STATS
468             ++num_tx;
469 #endif
470             if (0)
471               printf("TX[%s]\n", name ? name : "<unk>");
472             net_switch->tx(hdr, q->desc, d->next, this);
473
474             q->consumed(d);
475             irqs = true;
476           }
477
478         q->used->flags.no_notify() = 0;
479       }
480     while (q->desc_avail());
481     if (0)
482       printf("%s: wait\n", name);
483     return irqs;
484   }
485
486   void notify_guest(Virtio::Ring *queue)
487   {
488     if (queue->avail->flags.no_irq())
489       return;
490
491     // we do not care about this anywhere, so skip
492     if (0)
493       device_config->irq_status |= 1;
494
495     kick_guest_irq->trigger();
496 #ifdef CONFIG_STATS
497     ++num_irqs;
498 #endif
499   }
500
501   virtual void kick()
502   {
503     if (process_tx_q())
504       notify_guest(&_q[Tx]);
505   }
506
507   bool rx(Hdr const *tx_hdr, Virtio::Ring::Desc *tx_descs, unsigned tx_idx,
508           Virtio_net *tx_port)
509   {
510     if (L4_UNLIKELY(!device_config->status.running()))
511       return false;
512
513     if (0)
514       printf("%s: copy packet\n", name);
515
516     Virtio::Ring *q = &_q[Rx];
517
518     if (0)
519       printf("q->avail.idx=%d\n", q->avail->idx);
520     Virtio::Ring::Desc *d = q->next_avail();
521
522     if (L4_UNLIKELY(!d))
523       return false;
524
525 #ifndef CONFIG_BENCHMARK
526     if (L4_UNLIKELY(!d->flags.write()))
527       {
528         printf("PORT[%p]: invalid buffer in RX queue\n", this);
529         q->consumed(d);
530         notify_guest(q);
531         return false;
532       }
533
534     if (L4_UNLIKELY(!d->flags.next()))
535       {
536         printf("PORT[%p]: invalid buffer in RX queue\n", this);
537         q->consumed(d);
538         notify_guest(q);
539         return false;
540       }
541 #endif
542
543     Virtio::Ring::Desc *rxb = q->desc + d->next;
544     Virtio::Ring::Desc *txb = tx_descs + tx_idx;
545     char *rx_addr = access(rxb->buf<char>());
546     char const *tx_addr = tx_port->access(txb->buf<char const>());
547
548     Hdr *rx_hdr = access(d->buf<Hdr>());
549
550     rx_hdr->flags.raw = 0;
551     rx_hdr->gso_type = 0;
552
553 #ifdef CONFIG_STATS
554     ++num_rx;
555 #endif
556
557     if (tx_hdr->flags.need_csum())
558       rx_hdr->flags.data_valid() = 1;
559
560     unsigned long tx_space = txb->len;
561     unsigned long rx_space = rxb->len;
562     unsigned long rx_bytes = d->len;
563
564     for (;;)
565       {
566         unsigned long cpy = std::min(tx_space, rx_space);
567
568         memcpy(rx_addr, tx_addr, cpy);
569
570         rx_addr += cpy;
571         tx_addr += cpy;
572
573         rx_space -= cpy;
574         tx_space -= cpy;
575
576         rx_bytes += cpy;
577
578         if (tx_space == 0)
579           {
580             if (!txb->flags.next())
581               break;
582
583             txb = tx_descs + txb->next;
584             tx_addr = tx_port->access(txb->buf<char const>());
585             tx_space = txb->len;
586           }
587
588         if (rx_space == 0)
589           {
590             if (!rxb->flags.next())
591               break;
592
593             rxb = q->desc + rxb->next;
594             rx_addr = access(rxb->buf<char>());
595             rx_space = rxb->len;
596           }
597
598         if (0)
599           printf("more to copy rx=%s\n", name);
600       }
601
602 #ifndef CONFIG_BENCHMARK
603     if (L4_UNLIKELY(tx_space))
604       printf("PORT[%p]: truncate packet: %lx bytes left\n", this, tx_space);
605 #endif
606
607     q->consumed(d, rx_bytes);
608     notify_guest(q);
609
610     return true;
611   }
612
613   Switch *net_switch;
614   char const *name;
615
616 private:
617   Virtio::Ring _q[2];
618   friend void *stats_thread_loop(void *);
619 };
620
621
622 template<unsigned PORTS>
623 class Switch_t : public Switch
624 {
625 public:
626   enum
627   {
628     Num_ports = PORTS
629   };
630
631   Virtio_net port[PORTS];
632
633   Switch_t()
634   {
635     for (unsigned i = 0; i < PORTS; ++i)
636       port[i].net_switch = this;
637   }
638
639   void tx(Hdr const *tx_hdr, Virtio::Ring::Desc *descs, unsigned pkt,
640           Virtio_net *p)
641   {
642     for (unsigned i = 0; i < PORTS; ++i)
643       if (&port[i] != p)
644         {
645           if (L4_UNLIKELY(!port[i].rx(tx_hdr, descs, pkt, p)))
646             {
647 #ifdef CONFIG_STATS
648               ++port[i].num_dropped;
649 #endif
650             }
651         }
652   }
653 };
654
655
656 struct Loop_hooks
657 : L4::Ipc_svr::Ignore_errors,
658   L4::Ipc_svr::Default_timeout,
659   L4::Ipc_svr::Compound_reply
660 {
661   void setup_wait(L4::Ipc::Istream &istr, L4::Ipc_svr::Reply_mode)
662   {
663     istr.reset();
664     istr << L4::Ipc::Small_buf(rcv_cap[0].cap(), L4_RCV_ITEM_LOCAL_ID);
665     istr << L4::Ipc::Small_buf(rcv_cap[1].cap(), L4_RCV_ITEM_LOCAL_ID);
666     l4_utcb_br_u(istr.utcb())->bdr = 0;
667   }
668 };
669
670 static L4Re::Util::Object_registry registry;
671 static L4::Server<Loop_hooks> server(l4_utcb());
672
673 static Switch_t<2> net;
674
675 #ifdef CONFIG_STATS
676 static void *stats_thread_loop(void *)
677 {
678   for (;;)
679     {
680       sleep(1);
681       for (unsigned i = 0; i < net.Num_ports; ++i)
682         {
683           Virtio_net *p = &net.port[i];
684           printf("%s: tx:%ld rx:%ld drp:%ld irqs:%ld ri:%d:%d  ",
685                  p->name, p->num_tx, p->num_rx, p->num_dropped, p->num_irqs,
686                  p->device_config->status.running()
687                  ? (unsigned)p->_q[0].avail->idx : -1,
688                  (unsigned)p->_q[0].current_avail);
689         }
690       printf("\n");
691     }
692   return NULL;
693 };
694 #endif
695
696 int main()
697 {
698   printf("Hello from virtio server\n");
699   rcv_cap[0] = L4Re::chkcap(L4Re::Util::cap_alloc.alloc<L4::Kobject>());
700   rcv_cap[1] = L4Re::chkcap(L4Re::Util::cap_alloc.alloc<L4::Kobject>());
701
702 #ifdef CONFIG_STATS
703   pthread_t stats_thread;
704   pthread_create(&stats_thread, NULL, stats_thread_loop, NULL);
705 #endif
706
707   net.port[0].register_obj(&registry, "net0");
708   net.port[0].name = "net0";
709   net.port[1].register_obj(&registry, "net1");
710   net.port[1].name = "net1";
711
712   server.loop(registry);
713   return 0;
714 }