diff --git a/repos/os/recipes/src/vmm/used_apis b/repos/os/recipes/src/vmm/used_apis index 2e642939a5..3d08083134 100644 --- a/repos/os/recipes/src/vmm/used_apis +++ b/repos/os/recipes/src/vmm/used_apis @@ -1,7 +1,11 @@ base-hw base block_session +gui_session +framebuffer_session +input_session nic_session os terminal_session timer_session +blit diff --git a/repos/os/src/server/vmm/README b/repos/os/src/server/vmm/README index 32a65a7a77..49f36b5e2f 100644 --- a/repos/os/src/server/vmm/README +++ b/repos/os/src/server/vmm/README @@ -70,9 +70,10 @@ For each virtio_device node the following attributes need to be set: A unique name denoting the device. :type: - The Virtio type of device. One can decide in between "console", "net", + The Virtio type of device. One can decide in between "console", "net", "gpu", and "block". The "console" type gets mapped to a Genode Terminal session, - "net" is mapped to a Nic session, and "block" to a Block session. + "net" is mapped to a Nic session, "gpu" is mapped to a Gui session, + and "block" to a Block session. Additional devices diff --git a/repos/os/src/server/vmm/config.h b/repos/os/src/server/vmm/config.h index cb188d6299..f9bd1075f4 100644 --- a/repos/os/src/server/vmm/config.h +++ b/repos/os/src/server/vmm/config.h @@ -38,7 +38,7 @@ class Vmm::Config struct Virtio_device : List_model::Element { - enum Type { INVALID, CONSOLE, NET, BLOCK }; + enum Type { INVALID, CONSOLE, NET, BLOCK, GPU }; enum { MMIO_SIZE = 0x200 }; @@ -94,6 +94,7 @@ class Vmm::Config if (type == "console") t = Virtio_device::CONSOLE; if (type == "net") t = Virtio_device::NET; if (type == "block") t = Virtio_device::BLOCK; + if (type == "gpu") t = Virtio_device::GPU; return t; } diff --git a/repos/os/src/server/vmm/target.inc b/repos/os/src/server/vmm/target.inc index 8145ba550e..78bd276483 100644 --- a/repos/os/src/server/vmm/target.inc +++ b/repos/os/src/server/vmm/target.inc @@ -1,6 +1,6 @@ TARGET = vmm REQUIRES = hw -LIBS = base +LIBS = base blit SRC_CC += address_space.cc SRC_CC += cpu_base.cc SRC_CC += config.cc @@ -11,6 +11,7 @@ SRC_CC += main.cc SRC_CC += mmio.cc SRC_CC += pl011.cc SRC_CC += vm.cc +SRC_CC += virtio_gpu.cc INC_DIR += $(PRG_DIR)/../.. $(PRG_DIR) vpath %.cc $(PRG_DIR)/../.. diff --git a/repos/os/src/server/vmm/virtio_device.h b/repos/os/src/server/vmm/virtio_device.h index 40c4276ad0..533c678271 100644 --- a/repos/os/src/server/vmm/virtio_device.h +++ b/repos/os/src/server/vmm/virtio_device.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include diff --git a/repos/os/src/server/vmm/virtio_gpu.cc b/repos/os/src/server/vmm/virtio_gpu.cc new file mode 100644 index 0000000000..430b7d1971 --- /dev/null +++ b/repos/os/src/server/vmm/virtio_gpu.cc @@ -0,0 +1,265 @@ +/* + * \brief Virtio GPU implementation + * \author Stefan Kalkowski + * \date 2021-02-19 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#include +#include + + +void Vmm::Virtio_gpu_queue::notify(Virtio_gpu_device & dev) +{ + memory_barrier(); + bool inform = false; + for (Ring_index avail_idx = _avail.current(); + _cur_idx != avail_idx; _cur_idx.inc()) { + try { + Index idx = _avail.get(_cur_idx); + Virtio_gpu_control_request + request(idx, _descriptors, _ram, dev); + _used.add(_cur_idx.idx(), idx, request.size()); + } catch (Exception & e) { + error(e); + } + inform = true; + } + + if (!inform) + return; + + _used.write(_cur_idx.idx()); + memory_barrier(); + if (_avail.inject_irq()) dev.assert_irq(); +} + + +void Vmm::Virtio_gpu_control_request::_get_display_info() +{ + Display_info_response dir { _desc_addr(1) }; + memset((void*)dir.base(), 0, Display_info_response::SIZE); + dir.write(Control_header::Type::OK_DISPLAY_INFO); + + dir.write(0); + dir.write(0); + dir.write(_device._fb_mode.area.w()); + dir.write(_device._fb_mode.area.h()); + dir.write(1); + dir.write(0); +} + + +void Vmm::Virtio_gpu_control_request::_resource_create_2d() +{ + Resource_create_2d c2d { _desc_addr(0) }; + Control_header response { _desc_addr(1) }; + + if (c2d.read() != + Resource_create_2d::Format::B8G8R8X8) { + warning("Unsupported pixel fomat (id=", + c2d.read(), ")!"); + response.write(Control_header::Type::ERR_INVALID_PARAMETER); + return; + } + + using Resource = Virtio_gpu_device::Resource; + + try { + new (_device._heap) + Resource(_device, + c2d.read(), + c2d.read(), + c2d.read()); + response.write(Control_header::Type::OK_NO_DATA); + } catch(...) { + response.write(Control_header::Type::ERR_OUT_OF_MEMORY); + } +} + + +void Vmm::Virtio_gpu_control_request::_resource_delete() +{ + using Resource = Virtio_gpu_device::Resource; + using Scanout = Resource::Scanout; + + Resource_unref rur { _desc_addr(0) }; + Control_header response { _desc_addr(1) }; + + response.write(Control_header::Type::ERR_INVALID_RESOURCE_ID); + uint32_t id = rur.read(); + + _device._resources.for_each([&] (Resource & res) { + if (res.id != id) + return; + + res.scanouts.for_each([&] (Scanout & sc) { + destroy(_device._heap, &sc); }); + destroy(_device._heap, &res); + response.write(Control_header::Type::OK_NO_DATA); + }); +} + + +void Vmm::Virtio_gpu_control_request::_resource_attach_backing() +{ + using Resource = Virtio_gpu_device::Resource; + using Entry = Resource_attach_backing::Memory_entry; + + Resource_attach_backing rab { _desc_addr(0) }; + addr_t entry_base { _desc_addr(1) }; + Control_header response { _desc_addr(2) }; + + response.write(Control_header::Type::ERR_INVALID_RESOURCE_ID); + uint32_t id = rab.read(); + unsigned nr = rab.read(); + + _device._resources.for_each([&] (Resource & res) { + if (res.id != id) + return; + + try { + for (unsigned i = 0; i < nr; i++) { + Entry entry(entry_base+i*Entry::SIZE); + size_t sz = entry.read(); + addr_t off = _device._ram.local_address(entry.read(), sz) + - _device._ram.local(); + res.attach(off, sz); + } + response.write(Control_header::Type::OK_NO_DATA); + } catch (Exception &) { + response.write(Control_header::Type::ERR_INVALID_PARAMETER); + } + }); +} + + +void Vmm::Virtio_gpu_control_request::_set_scanout() +{ + Set_scanout scr { _desc_addr(0) }; + Control_header response { _desc_addr(1) }; + + uint32_t id = scr.read(); + uint32_t sid = scr.read(); + response.write(id ? Control_header::Type::ERR_INVALID_RESOURCE_ID + : Control_header::Type::OK_NO_DATA); + + using Resource = Virtio_gpu_device::Resource; + using Scanout = Resource::Scanout; + _device._resources.for_each([&] (Resource & res) { + if (!id || id == res.id) + res.scanouts.for_each([&] (Scanout & sc) { + if (sc.id == sid) destroy(_device._heap, &sc); }); + + if (res.id != id) + return; + + try { + new (_device._heap) Scanout(res.scanouts, sid, + scr.read(), + scr.read(), + scr.read(), + scr.read()); + response.write(Control_header::Type::OK_NO_DATA); + } catch(...) { + response.write(Control_header::Type::ERR_OUT_OF_MEMORY); + } + }); +} + + +void Vmm::Virtio_gpu_control_request::_resource_flush() +{ + Resource_flush rf { _desc_addr(0) }; + Control_header response { _desc_addr(1) }; + + uint32_t id = rf.read(); + response.write(Control_header::Type::ERR_INVALID_RESOURCE_ID); + + using Resource = Virtio_gpu_device::Resource; + _device._resources.for_each([&] (Resource & res) { + if (res.id != id) + return; + + uint32_t x = rf.read(); + uint32_t y = rf.read(); + uint32_t w = rf.read(); + uint32_t h = rf.read(); + + if (x > res.area.w() || + y > res.area.h() || + w > res.area.w() || + h > res.area.h() || + x + w > res.area.w() || + y + h > res.area.h()) { + response.write(Control_header::Type::ERR_INVALID_PARAMETER); + return; + } + + enum { BYTES_PER_PIXEL = Virtio_gpu_device::BYTES_PER_PIXEL }; + response.write(Control_header::Type::OK_NO_DATA); + + void * src = + (void*)((addr_t)res.dst_ds.local_addr() + + (res.area.w() * y + x) * BYTES_PER_PIXEL); + void * dst = + (void*)((addr_t)_device._fb_ds->local_addr() + + (_device._fb_mode.area.w() * y + x) * BYTES_PER_PIXEL); + size_t line = res.area.w() * BYTES_PER_PIXEL; + + blit(src, line, dst, line, w*BYTES_PER_PIXEL, h); + + _device._gui.framebuffer()->refresh(x, y, w, h); + }); +} + + +void Vmm::Virtio_gpu_control_request::_transfer_to_host_2d() +{ + Transfer_to_host_2d tth { _desc_addr(0) }; + Control_header response { _desc_addr(1) }; + + uint32_t id = tth.read(); + response.write(Control_header::Type::ERR_INVALID_RESOURCE_ID); + + enum { BYTES_PER_PIXEL = Virtio_gpu_device::BYTES_PER_PIXEL }; + + using Resource = Virtio_gpu_device::Resource; + _device._resources.for_each([&] (Resource & res) + { + if (res.id != id) + return; + + uint32_t x = tth.read(); + uint32_t y = tth.read(); + uint32_t w = tth.read(); + uint32_t h = tth.read(); + addr_t off = tth.read(); + + if (x + w > res.area.w() || y + h > res.area.h()) { + response.write(Control_header::Type::ERR_INVALID_PARAMETER); + return; + } + + void * src = (void*)((addr_t)res.src_ds.local_addr() + off); + void * dst = (void*)((addr_t)res.dst_ds.local_addr() + + (y * res.area.w() + x) * BYTES_PER_PIXEL); + size_t line = res.area.w() * BYTES_PER_PIXEL; + + blit(src, line, dst, line, w*BYTES_PER_PIXEL, h); + + response.write(Control_header::Type::OK_NO_DATA); + }); +} + + +void Vmm::Virtio_gpu_control_request::_update_cursor() { } + + +void Vmm::Virtio_gpu_control_request::_move_cursor() { } diff --git a/repos/os/src/server/vmm/virtio_gpu.h b/repos/os/src/server/vmm/virtio_gpu.h new file mode 100644 index 0000000000..444b6e95ab --- /dev/null +++ b/repos/os/src/server/vmm/virtio_gpu.h @@ -0,0 +1,469 @@ +/* + * \brief Virtio GPU implementation + * \author Stefan Kalkowski + * \date 2021-02-19 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _VIRTIO_GPU_H_ +#define _VIRTIO_GPU_H_ + +#include +#include +#include +#include + +#include +#include + +namespace Vmm { + class Virtio_gpu_queue; + class Virtio_gpu_control_request; + class Virtio_gpu_device; + using namespace Genode; +} + + +class Vmm::Virtio_gpu_queue : public Virtio_split_queue +{ + private: + + Ring_index _used_idx; + + friend class Virtio_gpu_control_request; + + public: + + enum { CONTROL, CURSOR, QUEUE_COUNT }; + + using Virtio_split_queue::Virtio_split_queue; + + void notify(Virtio_gpu_device &); +}; + + +class Vmm::Virtio_gpu_control_request +{ + private: + + using Index = Virtio_gpu_queue::Descriptor_index; + using Descriptor = Virtio_gpu_queue::Descriptor; + using Descriptor_array = Virtio_gpu_queue::Descriptor_array; + + struct Control_header : Mmio + { + enum { SIZE = 24 }; + + struct Type : Register<0, 32> + { + enum Commands { + /* 2D commands */ + GET_DISPLAY_INFO = 0x0100, + RESOURCE_CREATE_2D, + RESOURCE_UNREF, + SET_SCANOUT, + RESOURCE_FLUSH, + TRANSFER_TO_HOST_2D, + RESOURCE_ATTACH_BACKING, + RESOURCE_DETACH_BACKING, + GET_CAPSET_INFO, + GET_CAPSET, + GET_EDID, + + /* cursor commands */ + UPDATE_CURSOR = 0x0300, + MOVE_CURSOR, + }; + + enum Responses { + OK_NO_DATA = 0x1100, + OK_DISPLAY_INFO, + OK_CAPSET_INFO, + OK_CAPSET, + OK_EDID, + ERR_UNSPEC = 0x1200, + ERR_OUT_OF_MEMORY, + ERR_INVALID_SCANOUT_ID, + ERR_INVALID_RESOURCE_ID, + ERR_INVALID_CONTEXT_ID, + ERR_INVALID_PARAMETER, + }; + }; + struct Flags : Register<0x4, 32> {}; + struct Fence_id : Register<0x8, 64> {}; + struct Ctx_id : Register<0x10, 32> {}; + + using Mmio::Mmio; + }; + + struct Display_info_response : Control_header + { + enum { SIZE = Control_header::SIZE + 24*16 }; + + struct X : Register<0x18, 32> {}; + struct Y : Register<0x1c, 32> {}; + struct Width : Register<0x20, 32> {}; + struct Height : Register<0x24, 32> {}; + struct Enabled : Register<0x28, 32> {}; + struct Flags : Register<0x2c, 32> {}; + + using Control_header::Control_header; + }; + + struct Resource_create_2d : Control_header + { + enum { SIZE = Control_header::SIZE + 16 }; + + struct Resource_id : Register<0x18, 32> {}; + + struct Format : Register<0x1c, 32> + { + enum { + B8G8R8A8 = 1, + B8G8R8X8 = 2, + A8R8G8B8 = 3, + X8R8G8B8 = 4, + R8G8B8A8 = 67, + X8B8G8R8 = 68, + A8B8G8R8 = 121, + R8G8B8X8 = 134, + }; + }; + + struct Width : Register<0x20, 32> {}; + struct Height : Register<0x24, 32> {}; + + using Control_header::Control_header; + }; + + struct Resource_unref : Control_header + { + enum { SIZE = Control_header::SIZE + 8 }; + + struct Resource_id : Register<0x18, 32> {}; + + using Control_header::Control_header; + }; + + struct Resource_attach_backing : Control_header + { + enum { SIZE = Control_header::SIZE + 8 }; + + struct Resource_id : Register<0x18, 32> {}; + struct Nr_entries : Register<0x1c, 32> {}; + + struct Memory_entry : Mmio + { + enum { SIZE = 16 }; + + struct Address : Register<0x0, 64> {}; + struct Length : Register<0x8, 32> {}; + + using Mmio::Mmio; + }; + + using Control_header::Control_header; + }; + + struct Set_scanout : Control_header + { + enum { SIZE = Control_header::SIZE + 24 }; + + struct X : Register<0x18, 32> {}; + struct Y : Register<0x1c, 32> {}; + struct Width : Register<0x20, 32> {}; + struct Height : Register<0x24, 32> {}; + struct Scanout_id : Register<0x28, 32> {}; + struct Resource_id : Register<0x2c, 32> {}; + + using Control_header::Control_header; + }; + + struct Resource_flush : Control_header + { + enum { SIZE = Control_header::SIZE + 24 }; + + struct X : Register<0x18, 32> {}; + struct Y : Register<0x1c, 32> {}; + struct Width : Register<0x20, 32> {}; + struct Height : Register<0x24, 32> {}; + struct Resource_id : Register<0x28, 32> {}; + + using Control_header::Control_header; + }; + + struct Transfer_to_host_2d :Control_header + { + enum { SIZE = Control_header::SIZE + 32 }; + + struct X : Register<0x18, 32> {}; + struct Y : Register<0x1c, 32> {}; + struct Width : Register<0x20, 32> {}; + struct Height : Register<0x24, 32> {}; + struct Offset : Register<0x28, 64> {}; + struct Resource_id : Register<0x30, 32> {}; + + using Control_header::Control_header; + }; + + Descriptor_array & _array; + Ram & _ram; + Virtio_gpu_device & _device; + Index _idx; + + Index _next(Descriptor desc) + { + if (!Descriptor::Flags::Next::get(desc.flags())) + throw Exception("Invalid request, no next descriptor"); + return desc.next(); + } + + Descriptor _desc(unsigned i) + { + Index idx = _idx; + for (; i; i--) + idx = _next(_array.get(idx)); + return _array.get(idx); + } + + addr_t _desc_addr(unsigned i) + { + Descriptor d = _desc(i); + return _ram.local_address(d.address(), d.length()); + } + + Control_header _ctrl_hdr { _desc_addr(0) }; + + void _get_display_info(); + void _resource_create_2d(); + void _resource_delete(); + void _resource_attach_backing(); + void _set_scanout(); + void _resource_flush(); + void _transfer_to_host_2d(); + void _update_cursor(); + void _move_cursor(); + + public: + + Virtio_gpu_control_request(Index id, + Descriptor_array & array, + Ram & ram, + Virtio_gpu_device & device) + : _array(array), _ram(ram), _device(device), _idx(id) + { + switch (_ctrl_hdr.read()) { + case Control_header::Type::GET_DISPLAY_INFO: + _get_display_info(); + break; + case Control_header::Type::RESOURCE_CREATE_2D: + _resource_create_2d(); + break; + case Control_header::Type::RESOURCE_UNREF: + _resource_delete(); + break; + case Control_header::Type::RESOURCE_ATTACH_BACKING: + _resource_attach_backing(); + break; + case Control_header::Type::SET_SCANOUT: + _set_scanout(); + break; + case Control_header::Type::RESOURCE_FLUSH: + _resource_flush(); + break; + case Control_header::Type::TRANSFER_TO_HOST_2D: + _transfer_to_host_2d(); + break; + case Control_header::Type::UPDATE_CURSOR: + _update_cursor(); + break; + case Control_header::Type::MOVE_CURSOR: + _move_cursor(); + break; + default: + error("Unknown control request ", + _ctrl_hdr.read()); + }; + } + + size_t size() { return Control_header::SIZE; } +}; + + +class Vmm::Virtio_gpu_device : public Virtio_device +{ + private: + + friend class Virtio_gpu_control_request; + + Env & _env; + Heap & _heap; + Attached_ram_dataspace & _ram_ds; + Gui::Connection _gui { _env }; + Cpu::Signal_handler _handler; + Constructible _fb_ds { }; + Framebuffer::Mode _fb_mode { _gui.mode() }; + Gui::Session::View_handle _view = _gui.create_view(); + bool _mode_changed { true }; + + using Area = Genode::Area<>; + using Rect = Genode::Rect<>; + + enum { BYTES_PER_PIXEL = 4 }; + + struct Resource : Registry::Element + { + struct Scanout : Registry::Element, Rect + { + uint32_t id; + + Scanout(Registry & registry, + uint32_t id, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) + : + Registry::Element(registry, *this), + Rect(Point((int)x,(int)y), Area((int)w,(int)h)) { } + + using Rect::Rect; + }; + + Virtio_gpu_device & device; + uint32_t id; + Area area; + + size_t _size() const { + return align_addr(area.w() * area.h() * BYTES_PER_PIXEL, 12); } + + addr_t attach_off { 0UL }; + Rm_connection rm { device._env }; + Region_map_client region_map { rm.create(_size()) }; + Attached_dataspace src_ds { device._env.rm(), + region_map.dataspace() }; + Attached_ram_dataspace dst_ds { device._env.ram(), + device._env.rm(), _size() }; + Registry scanouts; + + Resource(Virtio_gpu_device & dev, + uint32_t id, + uint32_t w, + uint32_t h) + : + Registry::Element(dev._resources, *this), + device(dev), + id(id), + area((int)w, (int)h) {} + + void attach(addr_t off, size_t sz) + { + if (attach_off + sz > _size()) + return; + + retry( + [&] () { + retry( + [&] { + region_map.attach(device._ram_ds.cap(), + sz, off, true, attach_off); + attach_off += sz; + }, + [&] { rm.upgrade_caps(2); }); + }, + [&] () { rm.upgrade_ram(8*1024); }); + } + }; + + Registry _resources {}; + + struct Configuration_area : Mmio_register + { + Virtio_gpu_device & dev; + + enum { + EVENTS_READ = 0, + EVENTS_CLEAR = 4, + SCANOUTS = 8 }; + + Register read(Address_range & range, Cpu&) override + { + if (range.start == EVENTS_READ && range.size == 4) + return dev._mode_changed ? 1 : 0; + + /* we support no multi-head, just return 1 */ + if (range.start == SCANOUTS && range.size == 4) + return 1; + + return 0; + } + + void write(Address_range & range, Cpu&, Register v) override + { + if (range.start == EVENTS_CLEAR && range.size == 4 && v == 1) + dev._mode_changed = false; + } + + Configuration_area(Virtio_gpu_device & device) + : Mmio_register("GPU config area", Mmio_register::RO, 0x100, 16), + dev(device) { device.add(*this); } + } _config_area{ *this }; + + void _mode_change() + { + Genode::Mutex::Guard guard(_mutex); + + _fb_mode = _gui.mode(); + + _gui.buffer(_fb_mode, false); + + if (_fb_mode.area.count() > 0) + _fb_ds.construct(_env.rm(), + _gui.framebuffer()->dataspace()); + + using Command = Gui::Session::Command; + _gui.enqueue(_view, Rect(Point(0, 0), _fb_mode.area)); + _gui.enqueue(_view, Gui::Session::View_handle()); + _gui.execute(); + + _mode_changed = true; + } + + void _notify(unsigned idx) override + { + if (idx < Virtio_gpu_queue::QUEUE_COUNT) + _queue[idx]->notify(*this); + } + + enum Device_id { GPU = 16 }; + + public: + + Virtio_gpu_device(const char * const name, + const uint64_t addr, + const uint64_t size, + unsigned irq, + Cpu & cpu, + Mmio_bus & bus, + Ram & ram, + Env & env, + Heap & heap, + Attached_ram_dataspace & ram_ds) + : + Virtio_device(name, addr, size, + irq, cpu, bus, ram, GPU), + _env(env), _heap(heap), _ram_ds(ram_ds), + _handler(cpu, env.ep(), *this, &Virtio_gpu_device::_mode_change) + { + _gui.mode_sigh(_handler); + _mode_change(); + } + + void assert_irq() { _assert_irq(); } +}; + +#endif /* _VIRTIO_GPU_H_ */ diff --git a/repos/os/src/server/vmm/vm.cc b/repos/os/src/server/vmm/vm.cc index 7d40779c33..5b8ebe9650 100644 --- a/repos/os/src/server/vmm/vm.cc +++ b/repos/os/src/server/vmm/vm.cc @@ -13,6 +13,10 @@ #include #include +#include +#include +#include +#include using Vmm::Vm; @@ -112,12 +116,18 @@ Vm::Vm(Genode::Env & env, Heap & heap, Config & config) Virtio_net(dev.name.string(), (uint64_t)dev.mmio_start, dev.mmio_size, dev.irq, boot_cpu(), _bus, _ram, env)); - return; + return; case Config::Virtio_device::BLOCK: _device_list.insert(new (_heap) Virtio_block_device(dev.name.string(), (uint64_t)dev.mmio_start, dev.mmio_size, dev.irq, boot_cpu(), _bus, _ram, env, heap)); + return; + case Config::Virtio_device::GPU: + _device_list.insert(new (_heap) + Virtio_gpu_device(dev.name.string(), (uint64_t)dev.mmio_start, + dev.mmio_size, dev.irq, boot_cpu(), + _bus, _ram, env, heap, _vm_ram)); default: return; }; diff --git a/repos/os/src/server/vmm/vm.h b/repos/os/src/server/vmm/vm.h index 715debda75..28b4a4368e 100644 --- a/repos/os/src/server/vmm/vm.h +++ b/repos/os/src/server/vmm/vm.h @@ -21,9 +21,7 @@ #include #include #include -#include -#include -#include +#include #include #include