From 4d93187d31f46957d5137d3d0442ed8cb18d8035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20S=C3=B6ntgen?= Date: Tue, 10 Aug 2021 18:18:16 +0200 Subject: [PATCH] libdrm: introduce Gpu session for etnaviv * The Gpu session back end translates all DRM API requests of the client into matching Gpu session operations. * Enable ioctl for etnaviv Fixes #4329. --- repos/libports/lib/mk/spec/arm_v8/libdrm.mk | 5 +- .../libports/src/lib/libdrm/ioctl_etnaviv.cc | 553 +++++++++++++----- 2 files changed, 407 insertions(+), 151 deletions(-) diff --git a/repos/libports/lib/mk/spec/arm_v8/libdrm.mk b/repos/libports/lib/mk/spec/arm_v8/libdrm.mk index 894e4b627d..0278876f51 100644 --- a/repos/libports/lib/mk/spec/arm_v8/libdrm.mk +++ b/repos/libports/lib/mk/spec/arm_v8/libdrm.mk @@ -2,7 +2,4 @@ include $(REP_DIR)/lib/mk/libdrm.inc include $(call select_from_repositories,lib/import/import-libdrm.mk) -# -# Enable when GPU multiplexer is available for Vivante -# -#SRC_CC := ioctl_etnaviv.cc +SRC_CC := ioctl_etnaviv.cc diff --git a/repos/libports/src/lib/libdrm/ioctl_etnaviv.cc b/repos/libports/src/lib/libdrm/ioctl_etnaviv.cc index b437a83e9b..8663f29536 100644 --- a/repos/libports/src/lib/libdrm/ioctl_etnaviv.cc +++ b/repos/libports/src/lib/libdrm/ioctl_etnaviv.cc @@ -13,14 +13,18 @@ */ /* Genode includes */ +#include +#include #include #include -#include -#include +#include +#include #include extern "C" { +#include #include +#include #include #include @@ -100,40 +104,6 @@ const char *command_name(unsigned long request) } -/** - * Check if request is OUT - */ -static bool constexpr req_out(unsigned long request) -{ - return (request & IOC_OUT); -} - - -/** - * Check if request is IN - */ -static bool constexpr req_in(unsigned long request) -{ - return (request & IOC_IN); -} - - -/** - * Convert FreeBSD (libc) I/O control to Linux (DRM driver) - */ -static unsigned long to_linux(unsigned long request) -{ - /* - * FreeBSD and Linux have swapped IN/OUT values. - */ - unsigned long lx = request & 0x0fffffffu; - if (req_out(request)) { lx |= IOC_IN; } - if (req_in (request)) { lx |= IOC_OUT; } - - return lx; -} - - namespace Drm { size_t get_payload_size(drm_etnaviv_gem_submit const &submit); @@ -163,6 +133,7 @@ size_t Drm::get_payload_size(drm_etnaviv_gem_submit const &submit) size += sizeof (drm_etnaviv_gem_submit_reloc) * submit.nr_relocs; size += sizeof (drm_etnaviv_gem_submit_bo) * submit.nr_bos; size += sizeof (drm_etnaviv_gem_submit_pmr) * submit.nr_pmrs; + size += submit.stream_size; return size; } @@ -285,131 +256,425 @@ void Drm::deserialize(drm_version *version, char *content) } -class Drm_call +namespace Gpu { + using namespace Genode; + + struct Call; +} /* namespace Gpu */ + + +struct Gpu::Buffer +{ + Gpu::Connection &_gpu; + + Id_space::Element const _elem; + + Dataspace_capability const cap; + size_t const size; + + Constructible _attached_buffer { }; + + Buffer(Gpu::Connection &gpu, + size_t size, + Id_space &space) + : + _gpu { gpu }, + _elem { *this, space }, + cap { _gpu.alloc_buffer(_elem.id(), size) }, + size { size } + { } + + virtual ~Buffer() + { + _gpu.free_buffer(_elem.id()); + } + + bool mmap(Genode::Env &env) + { + if (!_attached_buffer.constructed()) { + _attached_buffer.construct(env.rm(), cap); + } + + return _attached_buffer.constructed(); + } + + Genode::addr_t mmap_addr() + { + return reinterpret_cast(_attached_buffer->local_addr()); + } + + Gpu::Buffer_id id() const + { + return _elem.id(); + } +}; + + +class Gpu::Call { private: - Genode::Env &_env; - Genode::Heap _heap { _env.ram(), _env.rm() }; - Genode::Allocator_avl _drm_alloc { &_heap }; - Drm::Connection _drm_session { _env, &_drm_alloc, 1024*1024 }; + /* + * Noncopyable + */ + Call(Call const &) = delete; + Call &operator=(Call const &) = delete; + + Genode::Env &_env; + Genode::Heap _heap { _env.ram(), _env.rm() }; + + /***************** + ** Gpu session ** + *****************/ + + Gpu::Connection _gpu_session; + Gpu::Info_etnaviv const &_gpu_info { + *_gpu_session.attached_info() }; + + Id_space _buffer_space { }; + + /* + * Play it safe, glmark2 apparently submits araound 110 KiB at + * some point. + */ + enum { EXEC_BUFFER_SIZE = 256u << 10 }; + Constructible _exec_buffer { }; + + void _wait_for_completion(uint32_t fence) + { + Sequence_number const seqno { .value = fence }; + do { + if (_gpu_session.complete(seqno)) + break; + + _env.ep().wait_and_dispatch_one_io_signal(); + } while (true); + } + + template + bool _apply_handle(uint32_t handle, FN const &fn) + { + Buffer_id const id { .value = handle }; + + bool found = false; + _buffer_space.apply(id, [&] (Buffer &b) { + fn(b); + found = true; + }); + + return found; + } + + Dataspace_capability _lookup_cap_from_handle(uint32_t handle) + { + Dataspace_capability cap { }; + auto lookup_cap = [&] (Buffer const &b) { + cap = b.cap; + }; + (void)_apply_handle(handle, lookup_cap); + return cap; + } + + /****************************** + ** Device DRM I/O controls ** + ******************************/ + + int _drm_etnaviv_gem_cpu_fini(drm_etnaviv_gem_cpu_fini &arg) + { + return _apply_handle(arg.handle, [&] (Buffer const &b) { + _gpu_session.unmap_buffer(b.id()); + }) ? 0 : -1; + } + + int _drm_etnaviv_gem_cpu_prep(drm_etnaviv_gem_cpu_prep &arg) + { + int res = -1; + return _apply_handle(arg.handle, [&] (Buffer const &b) { + + Gpu::Mapping_attributes attrs { false, false }; + + if (arg.op == ETNA_PREP_READ) + attrs.readable = true; + else + + if (arg.op == ETNA_PREP_WRITE) + attrs.writeable = true; + + /* + * For now we ignore NOSYNC + */ + + bool const to = arg.timeout.tv_sec != 0; + if (to) { + for (int i = 0; i < 100; i++) { + Dataspace_capability const map_cap = + _gpu_session.map_buffer(b.id(), false, attrs); + if (map_cap.valid()) { + res = 0; + break; + } + } + } + else { + Dataspace_capability const map_cap = + _gpu_session.map_buffer(b.id(), false, attrs); + if (map_cap.valid()) + res = 0; + } + }) ? res : -1; + } + + int _drm_etnaviv_gem_info(drm_etnaviv_gem_info &arg) + { + return _apply_handle(arg.handle, + [&] (Buffer &b) { + if (!b.mmap(_env)) + return; + arg.offset = reinterpret_cast<::uint64_t>(b.mmap_addr()); + }) ? 0 : -1; + } + + template + void _alloc_buffer(::uint64_t const size, FUNC const &fn) + { + size_t donate = size; + Buffer *buffer = nullptr; + retry( + [&] () { + retry( + [&] () { + buffer = + new (&_heap) Buffer(_gpu_session, size, + _buffer_space); + }, + [&] () { + _gpu_session.upgrade_caps(2); + }); + }, + [&] () { + _gpu_session.upgrade_ram(donate); + }); + + if (buffer) + fn(*buffer); + } + + int _drm_etnaviv_gem_new(drm_etnaviv_gem_new &arg) + { + ::uint64_t const size = arg.size; + + try { + _alloc_buffer(size, [&](Buffer const &b) { + arg.handle = b.id().value; + }); + return 0; + } catch (...) { + return -1; + } + } + + int _drm_etnaviv_gem_submit(drm_etnaviv_gem_submit &arg) + { + size_t const payload_size = Drm::get_payload_size(arg); + if (payload_size > EXEC_BUFFER_SIZE) { + Genode::error(__func__, ": exec buffer too small (", + (unsigned)EXEC_BUFFER_SIZE, ") needed ", payload_size); + return -1; + } + + /* + * Copy each array flat to the exec buffer and adjust the + * addresses in the submit object. + */ + char *local_exec_buffer = (char*)_exec_buffer->mmap_addr(); + Genode::memset(local_exec_buffer, 0, EXEC_BUFFER_SIZE); + Drm::serialize(&arg, local_exec_buffer); + + try { + Genode::uint64_t const pending_exec_buffer = + _gpu_session.exec_buffer(_exec_buffer->id(), + EXEC_BUFFER_SIZE).value; + arg.fence = pending_exec_buffer & 0xffffffffu; + return 0; + } catch (Gpu::Session::Invalid_state) { } + + return -1; + } + + int _drm_etnaviv_gem_wait(drm_etnaviv_gem_wait &) + { + warning(__func__, ": not implemented"); + return -1; + } + + int _drm_etnaviv_gem_userptr(drm_etnaviv_gem_userptr &) + { + warning(__func__, ": not implemented"); + return -1; + } + + int _drm_etnaviv_get_param(drm_etnaviv_param &arg) + { + if (arg.param > Gpu::Info_etnaviv::MAX_ETNAVIV_PARAMS) { + errno = EINVAL; + return -1; + } + + arg.value = _gpu_info.param[arg.param]; + return 0; + } + + int _drm_etnaviv_pm_query_dom(drm_etnaviv_pm_domain &) + { + warning(__func__, ": not implemented"); + return -1; + } + + int _drm_etnaviv_pm_query_sig(drm_etnaviv_pm_signal &) + { + warning(__func__, ": not implemented"); + return -1; + } + + int _drm_etnaviv_wait_fence(drm_etnaviv_wait_fence &arg) + { + _wait_for_completion(arg.fence); + return 0; + } + + int _device_ioctl(unsigned cmd, void *arg) + { + if (!arg) { + errno = EINVAL; + return -1; + } + + switch (cmd) { + case DRM_ETNAVIV_GEM_CPU_FINI: + return _drm_etnaviv_gem_cpu_fini(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GEM_CPU_PREP: + return _drm_etnaviv_gem_cpu_prep(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GEM_INFO: + return _drm_etnaviv_gem_info(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GEM_NEW: + return _drm_etnaviv_gem_new(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GEM_SUBMIT: + return _drm_etnaviv_gem_submit(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GEM_USERPTR: + return _drm_etnaviv_gem_userptr(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GEM_WAIT: + return _drm_etnaviv_gem_wait(*reinterpret_cast(arg)); + case DRM_ETNAVIV_GET_PARAM: + return _drm_etnaviv_get_param(*reinterpret_cast(arg)); + case DRM_ETNAVIV_PM_QUERY_DOM: + return _drm_etnaviv_pm_query_dom(*reinterpret_cast(arg)); + case DRM_ETNAVIV_PM_QUERY_SIG: + return _drm_etnaviv_pm_query_sig(*reinterpret_cast(arg)); + case DRM_ETNAVIV_WAIT_FENCE: + return _drm_etnaviv_wait_fence(*reinterpret_cast(arg)); + default: break; + } + + return 0; + } + + /******************************* + ** Generic DRM I/O controls ** + *******************************/ + + int _drm_gem_close(drm_gem_close const &gem_close) + { + return _apply_handle(gem_close.handle, + [&] (Gpu::Buffer &b) { + destroy(_heap, &b); + }) ? 0 : -1; + } + + int _drm_version(drm_version &version) + { + static char buffer[1] = { '\0' }; + + version.version_major = 1; + version.version_minor = 3; + version.version_patchlevel = 0; + version.name_len = 0; + version.name = buffer; + version.date_len = 0; + version.date = buffer; + version.desc_len = 0; + version.desc = buffer; + + return 0; + } + + int _generic_ioctl(unsigned cmd, void *arg) + { + if (!arg) { + errno = EINVAL; + return -1; + } + + switch (cmd) { + case command_number(DRM_IOCTL_GEM_CLOSE): + return _drm_gem_close(*reinterpret_cast(arg)); + case command_number(DRM_IOCTL_VERSION): + return _drm_version(*reinterpret_cast(arg)); + default: + error("unhandled generic DRM ioctl: ", Genode::Hex(cmd)); + break; + } + + return -1; + } public: - Drm_call(Genode::Env &env) : _env(env) { } + Call(Env &env) + : + _env { env }, + _gpu_session { _env } + { + try { + _exec_buffer.construct(_gpu_session, + (size_t)EXEC_BUFFER_SIZE, + _buffer_space); + } catch (...) { + throw Gpu::Session::Invalid_state(); + } + if (!_exec_buffer->mmap(_env)) + throw Gpu::Session::Invalid_state(); + } + + ~Call() { } int ioctl(unsigned long request, void *arg) { - size_t size = IOCPARM_LEN(request); - - bool const in = req_in(request); - bool const out = req_out(request); - - unsigned long const lx_request = to_linux(request); - - /* - * Adjust packet size for flatten arrays. - */ - if (command_number(request) == DRM_ETNAVIV_GEM_SUBMIT) { - /* account for the arrays */ - drm_etnaviv_gem_submit *submit = - reinterpret_cast(arg); - size_t const payload_size = Drm::get_payload_size(*submit); - size += payload_size; - } else - - /* - * Adjust packet size for user pointer storage. - */ - if (command_number(request) == command_number(DRM_IOCTL_VERSION)) { - drm_version *version = - reinterpret_cast(arg); - size_t const payload_size = Drm::get_payload_size(*version); - size += payload_size; - } - - Drm::Session::Tx::Source &src = *_drm_session.tx(); - Drm::Packet_descriptor p { src.alloc_packet(size), lx_request }; - - /* - * Copy each array flat to the packet buffer and adjust the - * addresses in the submit object. - */ - if (device_number(request) == DRM_ETNAVIV_GEM_SUBMIT) { - drm_etnaviv_gem_submit *submit = - reinterpret_cast(arg); - char *content = src.packet_content(p); - Drm::serialize(submit, content); - } else - - /* - * Copy and adjust user pointer in DRM version object. - */ - if (command_number(request) == command_number(DRM_IOCTL_VERSION)) { - drm_version *version = - reinterpret_cast(arg); - char *content = src.packet_content(p); - Drm::serialize(version, content); - } else - - /* - * The remaining ioctls get the memcpy treament. Hopefully there - * are no user pointers left... - */ - if (in) { - Genode::memcpy(src.packet_content(p), arg, size); - } - - /* - * For the moment we perform a "blocking" packetstream operation - * which could be time-consuming but is easier to debug. Eventually - * it should be replace by a asynchronous operation. - */ - src.submit_packet(p); - p = src.get_acked_packet(); - - if (out && arg) { - /* - * Adjust user pointers back to make the client happy. - */ - if (command_number(request) == command_number(DRM_IOCTL_VERSION)) { - drm_version *version = - reinterpret_cast(arg); - char *content = src.packet_content(p); - Drm::deserialize(version, content); - - } else { - // XXX handle unserializaton in a better way - Genode::memcpy(arg, src.packet_content(p), size); - } - } - - src.release_packet(p); - return p.error(); + bool const device_request = device_ioctl(request); + return device_request ? _device_ioctl(device_number(request), arg) + : _generic_ioctl(command_number(request), arg); } - void *mmap(unsigned long offset, unsigned long size) + void *mmap(unsigned long offset, unsigned long /* size */) { - Genode::Ram_dataspace_capability cap = _drm_session.object_dataspace(offset, size); - if (!cap.valid()) { - return (void *)-1; - } - - try { - return _env.rm().attach(cap); - } catch (...) { } - - return (void *)-1; + /* + * Buffer should have been mapped during GEM INFO call. + */ + return (void*)offset; } void munmap(void *addr) { - _env.rm().detach(addr); + /* + * We rely on GEM CLOSE to destroy the buffer and thereby + * to remove the local mapping. AFAICT the 'munmap' is indeed + * (always) followed by the CLOSE I/O control. + */ + (void)addr; } }; -static Genode::Constructible _drm; +static Genode::Constructible _drm; void drm_init(Genode::Env &env) @@ -418,12 +683,6 @@ void drm_init(Genode::Env &env) } -void drm_complete() -{ - Genode::error(__func__, ": called, not implemented yet"); -} - - /** * Dump I/O control request to LOG */