]> rtime.felk.cvut.cz Git - l4.git/blob - l4/pkg/vmm/server/src/virtio_proxy.h
Update
[l4.git] / l4 / pkg / vmm / server / src / virtio_proxy.h
1 /*
2  * (c) 2013-2014 Alexander Warg <warg@os.inf.tu-dresden.de>
3  *     economic rights: Technische Universität Dresden (Germany)
4  *
5  * This file is part of TUD:OS and distributed under the terms of the
6  * GNU General Public License 2.
7  * Please see the COPYING-GPL-2 file for details.
8  */
9 #pragma once
10
11 #include <l4/sys/capability>
12 #include <l4/sys/meta>
13
14 #include <l4/re/dataspace>
15 #include <l4/re/error_helper>
16 #include <l4/re/util/cap_alloc>
17
18 #include <l4/re/env>
19 #include <l4/re/rm>
20
21 #include <l4/cxx/ipc_stream>
22 #include <l4/cxx/ipc_server>
23
24 #include <l4/l4virtio/l4virtio>
25 #include <l4/l4virtio/virtqueue>
26 #include <l4/l4virtio/virtio_block.h>
27
28 #include <cstring>
29
30 #include "irq_emitter.h"
31 #include "vm_ram.h"
32
33 namespace L4virtio { namespace Driver {
34
35 /**
36  * \brief Client-side implementation for a general virtio device.
37  */
38 class Device
39 {
40 public:
41   /**
42    * Contacts the device and sets up the config page.
43    *
44    * \param srvcap    Capability for device communication.
45    * \param guest_irq Irq capability to send to device.
46    *
47    * \throws L4::Runtime_error if the initialisation fails
48    *
49    * This function contacts the server, sets up the notification
50    * channels and the configuration dataspace. After this is done,
51    * the caller can set up any dataspaces it needs.
52    */
53   void driver_connect(L4::Cap<L4virtio::Device> srvcap,
54                       L4::Cap<L4::Irq> guest_irq)
55   {
56     _device = srvcap;
57
58     _host_irq = L4Re::chkcap(L4Re::Util::cap_alloc.alloc<L4::Irq>(),
59                              "Cannot allocate host irq");
60
61     _config_cap = L4Re::chkcap(L4Re::Util::cap_alloc.alloc<L4Re::Dataspace>(),
62                                "Cannot allocate cap for config dataspace");
63
64     auto *e = L4Re::Env::env();
65     L4Re::chksys(e->rm()->attach(&_config, L4_PAGESIZE, L4Re::Rm::Search_addr,
66                                  L4::Ipc::make_cap_rw(_config_cap.get()), 0,
67                                  L4_PAGESHIFT),
68                  "Cannot attach config dataspace");
69
70     L4Re::chksys(_device->register_iface(guest_irq, _host_irq.get(),
71                                          _config_cap.get()),
72                  "Error registering interface with device");
73
74     if (memcmp(&_config->magic, "virt", 4) != 0)
75       L4Re::chksys(-L4_ENODEV, "Device config has wrong magic value");
76
77     if (_config->version != 1)
78       L4Re::chksys(-L4_ENODEV, "Invalid virtio version, must be 1");
79   }
80
81
82   /**
83    * Share a dataspace with the device.
84    *
85    * \param ds      Dataspace to share with the device.
86    * \param offset  Offset in dataspace where the shared part starts.
87    * \param size    Total size in bytes of the shared space.
88    * \param devaddr Start of shared space in the device address space.
89    *
90    * Although this function allows to share only a part of the given dataspace
91    * for convenience, the granularity of sharing is always the dataspace level.
92    * Thus, the remainder of the dataspace is not protected from the device.
93    */
94   int register_ds(L4::Cap<L4Re::Dataspace> ds, l4_umword_t offset,
95                   l4_umword_t size, l4_uint64_t devaddr)
96   {
97     return _device->register_ds(L4::Ipc::make_cap_rw(ds), devaddr, offset, size);
98   }
99
100   int config_queue(int num)
101   {
102     return _device->config_queue(num);
103   }
104
105   L4virtio::Device::Config_hdr *device_config() const
106   { return _config.get(); }
107
108   unsigned selected_queue() const
109   { return _queue_sel; }
110
111   l4_uint32_t page_size() const
112   { return _config->guest_page_size; }
113
114   L4virtio::Device::Config_queue *queue_config(int num) const
115   {
116     return &_config->queues()[num];
117   }
118
119   l4_uint32_t read(unsigned reg)
120   {
121     switch (reg >> 2)
122       {
123       case 0: return *reinterpret_cast<l4_uint32_t const *>("virt");
124       case 1: return 1;
125       case 2: return _config->device;
126       case 3: return _config->vendor;
127       case 4: return (_host_feat_sel < 8) ?
128                        _config->host_features[_host_feat_sel] : 0;
129       case 13: return (_queue_sel < _config->num_queues) ?
130                        _config->queues()[_queue_sel].num_max : 0;
131       case 16: return (_queue_sel < _config->num_queues) ?
132                        _config->queues()[_queue_sel].pfn : 0;
133       case 24: return 1; // currently unused: _config->irq_status;
134       case 28: return _config->status;
135       default: return 0;
136       }
137   }
138
139   void write(unsigned reg, l4_uint32_t value)
140   {
141     // XXX make sure we don't write outside config page because the
142     // driver side screwed up
143     switch (reg >> 2)
144       {
145       case 20:
146         _host_irq->trigger();
147         break;
148       case 5:
149         _host_feat_sel = value;
150         break;
151       case 8:
152         if (_guest_feat_sel < 8)
153           _config->guest_features[_guest_feat_sel] = value;
154         break;
155       case 9:
156         _guest_feat_sel = value;
157         break;
158       case 10:
159         _config->guest_page_size = value;
160         break;
161       case 12:
162         _queue_sel = value;
163         break;
164       case 14:
165         if (_queue_sel < _config->num_queues)
166            _config->queues()[_queue_sel].num = value;
167         break;
168       case 15:
169         if (_queue_sel < _config->num_queues)
170            _config->queues()[_queue_sel].align = value;
171         break;
172       case 16:
173         if (_queue_sel < _config->num_queues)
174           {
175            _config->queues()[_queue_sel].pfn = value;
176            _device->config_queue(_queue_sel);
177           }
178         break;
179       case 28:
180         _device->set_status(value);
181         break;
182       }
183   }
184
185
186 protected:
187   L4::Cap<L4virtio::Device> _device;
188   L4Re::Rm::Auto_region<L4virtio::Device::Config_hdr *> _config;
189   L4Re::Util::Auto_cap<L4::Irq>::Cap _guest_irq;
190
191 private:
192   L4Re::Util::Auto_cap<L4::Irq>::Cap _host_irq;
193   L4Re::Util::Auto_cap<L4Re::Dataspace>::Cap _config_cap;
194
195   unsigned _queue_sel = 0;
196   unsigned _host_feat_sel = 0;
197   unsigned _guest_feat_sel = 0;
198
199 };
200
201 } } // namespace
202
203 namespace Virtio {
204
205 class Proxy_dev : public L4::Irqep_t<Proxy_dev>
206 {
207 private:
208   // -- network specific
209   L4virtio::Driver::Virtqueue _txq;
210   Vmm::Vm_ram *_iommu;
211   // -------------------
212 public:
213   Proxy_dev(Vmm::Vm_ram *iommu, Vmm::Irq_emitter const &irq)
214   : _iommu(iommu), _irq(irq) {}
215
216   l4_uint32_t read(unsigned reg, char, unsigned)
217   { return _dev.read(reg); }
218
219   void write(unsigned reg, char, l4_uint32_t value, unsigned)
220   {
221     // -- network specific
222     if (reg >> 2 == 20)
223         _txq.no_notify_host(true);
224
225     _dev.write(reg, value);
226
227     if (reg >> 2 == 16 && _dev.selected_queue() == 1)
228       {
229         auto *q = _dev.queue_config(1);
230         _txq.setup(q->num, q->align,
231                    _iommu->access(L4virtio::Ptr<void>(value * _dev.page_size())));
232       }
233   }
234
235   template<typename REG>
236   void register_obj(REG *registry, L4::Cap<L4virtio::Device> host,
237                     L4::Cap<L4Re::Dataspace> ram, l4_addr_t ram_base)
238   {
239     L4::Cap<L4::Irq> guest_irq = L4Re::chkcap(registry->register_irq_obj(this));
240
241     _dev.driver_connect(host, guest_irq);
242     L4Re::chksys(_dev.register_ds(ram, 0, ram->size(), ram_base));
243   }
244
245   void handle_irq()
246   {  _irq.inject(vmm_current_cpu_id); }
247
248 private:
249   Vmm::Irq_emitter _irq;
250
251   L4virtio::Driver::Device _dev;
252 };
253
254
255 }