vmm: implement VirtIO GPU model

Ref genodelabs/genode#4698
This commit is contained in:
Stefan Kalkowski 2023-01-11 14:36:13 +01:00 committed by Christian Helmuth
parent f2188bd397
commit 85c8bd7d7e
9 changed files with 758 additions and 8 deletions

View File

@ -1,7 +1,11 @@
base-hw
base
block_session
gui_session
framebuffer_session
input_session
nic_session
os
terminal_session
timer_session
blit

View File

@ -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

View File

@ -38,7 +38,7 @@ class Vmm::Config
struct Virtio_device : List_model<Virtio_device>::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;
}

View File

@ -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)/../..

View File

@ -19,6 +19,7 @@
#include <util/mmio.h>
#include <util/reconstructible.h>
#include <cpu.h>
#include <gic.h>
#include <ram.h>
#include <mmio.h>

View File

@ -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 <blit/blit.h>
#include <virtio_gpu.h>
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<Used_queue::Idx>(_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>(Control_header::Type::OK_DISPLAY_INFO);
dir.write<Display_info_response::X>(0);
dir.write<Display_info_response::Y>(0);
dir.write<Display_info_response::Width>(_device._fb_mode.area.w());
dir.write<Display_info_response::Height>(_device._fb_mode.area.h());
dir.write<Display_info_response::Enabled>(1);
dir.write<Display_info_response::Flags>(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>() !=
Resource_create_2d::Format::B8G8R8X8) {
warning("Unsupported pixel fomat (id=",
c2d.read<Resource_create_2d::Format>(), ")!");
response.write<Control_header::Type>(Control_header::Type::ERR_INVALID_PARAMETER);
return;
}
using Resource = Virtio_gpu_device::Resource;
try {
new (_device._heap)
Resource(_device,
c2d.read<Resource_create_2d::Resource_id>(),
c2d.read<Resource_create_2d::Width>(),
c2d.read<Resource_create_2d::Height>());
response.write<Control_header::Type>(Control_header::Type::OK_NO_DATA);
} catch(...) {
response.write<Control_header::Type>(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>(Control_header::Type::ERR_INVALID_RESOURCE_ID);
uint32_t id = rur.read<Resource_unref::Resource_id>();
_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>(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>(Control_header::Type::ERR_INVALID_RESOURCE_ID);
uint32_t id = rab.read<Resource_attach_backing::Resource_id>();
unsigned nr = rab.read<Resource_attach_backing::Nr_entries>();
_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<Entry::Length>();
addr_t off = _device._ram.local_address(entry.read<Entry::Address>(), sz)
- _device._ram.local();
res.attach(off, sz);
}
response.write<Control_header::Type>(Control_header::Type::OK_NO_DATA);
} catch (Exception &) {
response.write<Control_header::Type>(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<Set_scanout::Resource_id>();
uint32_t sid = scr.read<Set_scanout::Scanout_id>();
response.write<Control_header::Type>(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<Set_scanout::X>(),
scr.read<Set_scanout::Y>(),
scr.read<Set_scanout::Width>(),
scr.read<Set_scanout::Height>());
response.write<Control_header::Type>(Control_header::Type::OK_NO_DATA);
} catch(...) {
response.write<Control_header::Type>(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<Resource_flush::Resource_id>();
response.write<Control_header::Type>(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<Resource_flush::X>();
uint32_t y = rf.read<Resource_flush::Y>();
uint32_t w = rf.read<Resource_flush::Width>();
uint32_t h = rf.read<Resource_flush::Height>();
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>(Control_header::Type::ERR_INVALID_PARAMETER);
return;
}
enum { BYTES_PER_PIXEL = Virtio_gpu_device::BYTES_PER_PIXEL };
response.write<Control_header::Type>(Control_header::Type::OK_NO_DATA);
void * src =
(void*)((addr_t)res.dst_ds.local_addr<void>() +
(res.area.w() * y + x) * BYTES_PER_PIXEL);
void * dst =
(void*)((addr_t)_device._fb_ds->local_addr<void>() +
(_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<Transfer_to_host_2d::Resource_id>();
response.write<Control_header::Type>(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<Transfer_to_host_2d::X>();
uint32_t y = tth.read<Transfer_to_host_2d::Y>();
uint32_t w = tth.read<Transfer_to_host_2d::Width>();
uint32_t h = tth.read<Transfer_to_host_2d::Height>();
addr_t off = tth.read<Transfer_to_host_2d::Offset>();
if (x + w > res.area.w() || y + h > res.area.h()) {
response.write<Control_header::Type>(Control_header::Type::ERR_INVALID_PARAMETER);
return;
}
void * src = (void*)((addr_t)res.src_ds.local_addr<void>() + off);
void * dst = (void*)((addr_t)res.dst_ds.local_addr<void>() +
(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>(Control_header::Type::OK_NO_DATA);
});
}
void Vmm::Virtio_gpu_control_request::_update_cursor() { }
void Vmm::Virtio_gpu_control_request::_move_cursor() { }

View File

@ -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 <base/attached_ram_dataspace.h>
#include <rm_session/connection.h>
#include <region_map/client.h>
#include <base/registry.h>
#include <gui_session/connection.h>
#include <virtio_device.h>
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<Control_header::Type>()) {
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<Control_header::Type>());
};
}
size_t size() { return Control_header::SIZE; }
};
class Vmm::Virtio_gpu_device : public Virtio_device<Virtio_gpu_queue, 2>
{
private:
friend class Virtio_gpu_control_request;
Env & _env;
Heap & _heap;
Attached_ram_dataspace & _ram_ds;
Gui::Connection _gui { _env };
Cpu::Signal_handler<Virtio_gpu_device> _handler;
Constructible<Attached_dataspace> _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<Resource>::Element
{
struct Scanout : Registry<Scanout>::Element, Rect
{
uint32_t id;
Scanout(Registry<Scanout> & registry,
uint32_t id,
uint32_t x, uint32_t y,
uint32_t w, uint32_t h)
:
Registry<Scanout>::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<Scanout> scanouts;
Resource(Virtio_gpu_device & dev,
uint32_t id,
uint32_t w,
uint32_t h)
:
Registry<Resource>::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<Out_of_ram>(
[&] () {
retry<Out_of_caps>(
[&] {
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<Resource> _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<Command::Geometry>(_view, Rect(Point(0, 0), _fb_mode.area));
_gui.enqueue<Command::To_front>(_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<Virtio_gpu_queue, 2>(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_ */

View File

@ -13,6 +13,10 @@
#include <fdt.h>
#include <vm.h>
#include <virtio_console.h>
#include <virtio_net.h>
#include <virtio_block.h>
#include <virtio_gpu.h>
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;
};

View File

@ -21,9 +21,7 @@
#include <cpu.h>
#include <gic.h>
#include <pl011.h>
#include <virtio_console.h>
#include <virtio_net.h>
#include <virtio_block.h>
#include <virtio_device.h>
#include <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>