1 // Copyright (C) 2023 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <assert.h>
16 #include <fcntl.h>
17 #include <lib/magma/magma_common_defs.h>
18 #include <log/log.h>
19 #include <stdarg.h>
20 #include <stdint.h>
21 #include <string.h>
22 #include <sys/mman.h>
23 #include <unistd.h>
24 #include <xf86drm.h>
25 
26 #include <limits>
27 #include <mutex>
28 #include <thread>
29 #include <unordered_map>
30 
31 #include "EncoderDebug.h"
32 #include "VirtioGpuAddressSpaceStream.h"
33 #include "magma_enc.h"
34 
get_ns_monotonic(bool raw)35 static uint64_t get_ns_monotonic(bool raw) {
36     struct timespec time;
37     int ret = clock_gettime(raw ? CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC, &time);
38     if (ret < 0) return 0;
39     return static_cast<uint64_t>(time.tv_sec) * 1000000000ULL + time.tv_nsec;
40 }
41 
42 class MagmaClientContext : public magma_encoder_context_t {
43    public:
44     MagmaClientContext(AddressSpaceStream* stream);
45 
stream()46     AddressSpaceStream* stream() {
47         return reinterpret_cast<AddressSpaceStream*>(magma_encoder_context_t::m_stream);
48     }
49 
50     magma_status_t get_fd_for_buffer(magma_buffer_t buffer, int* fd_out);
51 
mutex()52     std::mutex& mutex() { return m_mutex_; }
53 
54     static magma_status_t magma_device_import(void* self, magma_handle_t device_channel,
55                                               magma_device_t* device_out);
56     static magma_status_t magma_device_query(void* self, magma_device_t device, uint64_t id,
57                                              magma_handle_t* handle_out, uint64_t* value_out);
58     static magma_status_t magma_buffer_get_handle(void* self, magma_buffer_t buffer,
59                                                   magma_handle_t* handle_out);
60     static magma_status_t magma_buffer_export(void* self, magma_buffer_t buffer,
61                                               magma_handle_t* handle_out);
62     static magma_status_t magma_poll(void* self, magma_poll_item_t* items, uint32_t count,
63                                      uint64_t timeout_ns);
64     static magma_status_t magma_connection_create_buffer(void* self, magma_connection_t connection,
65                                                          uint64_t size, uint64_t* size_out,
66                                                          magma_buffer_t* buffer_out,
67                                                          magma_buffer_id_t* id_out);
68     static void magma_connection_release_buffer(void* self, magma_connection_t connection,
69                                                 magma_buffer_t buffer);
70 
set_thread_local_context_lock(std::unique_lock<std::mutex> * lock)71     static void set_thread_local_context_lock(std::unique_lock<std::mutex>* lock) { t_lock = lock; }
72 
get_thread_local_context_lock()73     static std::unique_lock<std::mutex>* get_thread_local_context_lock() { return t_lock; }
74 
75     magma_device_import_client_proc_t magma_device_import_enc_;
76     magma_buffer_get_handle_client_proc_t magma_buffer_get_handle_enc_;
77     magma_poll_client_proc_t magma_poll_enc_;
78     magma_connection_create_buffer_client_proc_t magma_connection_create_buffer_enc_;
79     magma_connection_release_buffer_client_proc_t magma_connection_release_buffer_enc_;
80 
81     int render_node_fd_;
82 
83     // Stores buffer info upon creation.
84     struct BufferInfo {
85         magma_connection_t connection;  // Owning connection.
86         uint64_t size;                  // Actual size.
87         magma_buffer_id_t id;           // Id.
88     };
89     std::unordered_map<magma_buffer_t, BufferInfo> buffer_info_;
90 
91     std::mutex m_mutex_;
92     static thread_local std::unique_lock<std::mutex>* t_lock;
93 };
94 
95 // This makes the mutex lock available to decoding methods that can take time
96 // (eg magma_poll), to prevent one thread from locking out others.
97 class ContextLock {
98    public:
ContextLock(MagmaClientContext * context)99     ContextLock(MagmaClientContext* context) : m_context_(context), m_lock_(context->mutex()) {
100         m_context_->set_thread_local_context_lock(&m_lock_);
101     }
102 
~ContextLock()103     ~ContextLock() { m_context_->set_thread_local_context_lock(nullptr); }
104 
105    private:
106     MagmaClientContext* m_context_;
107     std::unique_lock<std::mutex> m_lock_;
108 };
109 
110 // static
111 thread_local std::unique_lock<std::mutex>* MagmaClientContext::t_lock;
112 
MagmaClientContext(AddressSpaceStream * stream)113 MagmaClientContext::MagmaClientContext(AddressSpaceStream* stream)
114     : magma_encoder_context_t(stream, new gfxstream::guest::ChecksumCalculator) {
115     magma_device_import_enc_ = magma_client_context_t::magma_device_import;
116     magma_buffer_get_handle_enc_ = magma_client_context_t::magma_buffer_get_handle;
117     magma_poll_enc_ = magma_client_context_t::magma_poll;
118     magma_connection_create_buffer_enc_ = magma_client_context_t::magma_connection_create_buffer;
119 
120     magma_client_context_t::magma_device_import = &MagmaClientContext::magma_device_import;
121     magma_client_context_t::magma_device_query = &MagmaClientContext::magma_device_query;
122     magma_client_context_t::magma_buffer_get_handle = &MagmaClientContext::magma_buffer_get_handle;
123     magma_client_context_t::magma_buffer_export = &MagmaClientContext::magma_buffer_export;
124     magma_client_context_t::magma_poll = &MagmaClientContext::magma_poll;
125     magma_client_context_t::magma_connection_create_buffer =
126         &MagmaClientContext::magma_connection_create_buffer;
127     magma_client_context_t::magma_connection_release_buffer =
128         &MagmaClientContext::magma_connection_release_buffer;
129 }
130 
131 // static
magma_device_import(void * self,magma_handle_t device_channel,magma_device_t * device_out)132 magma_status_t MagmaClientContext::magma_device_import(void* self, magma_handle_t device_channel,
133                                                        magma_device_t* device_out) {
134     auto context = reinterpret_cast<MagmaClientContext*>(self);
135 
136     magma_handle_t placeholder = 0xacbd1234;  // not used
137 
138     magma_status_t status = context->magma_device_import_enc_(self, placeholder, device_out);
139 
140     // The local fd isn't needed, just close it.
141     int fd = device_channel;
142     close(fd);
143 
144     return status;
145 }
146 
get_fd_for_buffer(magma_buffer_t buffer,int * fd_out)147 magma_status_t MagmaClientContext::get_fd_for_buffer(magma_buffer_t buffer, int* fd_out) {
148     *fd_out = -1;
149 
150     auto it = buffer_info_.find(buffer);
151     if (it == buffer_info_.end()) {
152         ALOGE("%s: buffer (%llu) not found in map", __func__, (unsigned long long)buffer);
153         return MAGMA_STATUS_INVALID_ARGS;
154     }
155     auto& info = it->second;
156 
157     // TODO(fxbug.dev/42073573): Evaluate deferred guest resource creation.
158     auto blob = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma)
159                     ->createBlob({.size = info.size,
160                                   .flags = kBlobFlagMappable | kBlobFlagShareable,
161                                   .blobMem = kBlobMemHost3d,
162                                   .blobId = info.id});
163     if (!blob) {
164         return MAGMA_STATUS_INTERNAL_ERROR;
165     }
166 
167     VirtGpuExternalHandle handle{};
168     int result = blob->exportBlob(handle);
169     if (result != 0 || handle.osHandle < 0) {
170         return MAGMA_STATUS_INTERNAL_ERROR;
171     }
172 
173     *fd_out = handle.osHandle;
174 
175     return MAGMA_STATUS_OK;
176 }
177 
magma_device_query(void * self,magma_device_t device,uint64_t id,magma_handle_t * handle_out,uint64_t * value_out)178 magma_status_t MagmaClientContext::magma_device_query(void* self, magma_device_t device,
179                                                       uint64_t id, magma_handle_t* handle_out,
180                                                       uint64_t* value_out) {
181     auto context = reinterpret_cast<MagmaClientContext*>(self);
182 
183     // TODO(b/277219980): Support guest-allocated buffers.
184     constexpr magma_bool_t kHostAllocate = 1;
185 
186     uint64_t value = 0;
187     uint64_t result_buffer_mapping_id = 0;
188     uint64_t result_buffer_size = 0;
189     magma_status_t status = context->magma_device_query_fudge(
190         self, device, id, kHostAllocate, &result_buffer_mapping_id, &result_buffer_size, &value);
191     if (status != MAGMA_STATUS_OK) {
192         ALOGE("magma_device_query_fudge failed: %d\n", status);
193         return status;
194     }
195 
196     // For non-buffer queries, just return the value.
197     if (result_buffer_size == 0) {
198         if (!value_out) {
199             ALOGE("MAGMA_STATUS_INVALID_ARGS\n");
200             return MAGMA_STATUS_INVALID_ARGS;
201         }
202         *value_out = value;
203         ALOGE("MAGMA_STATUS_OK (value = %llu)\n", (unsigned long long)value);
204         return MAGMA_STATUS_OK;
205     }
206 
207     // Otherwise, create and return a fd for the host-allocated buffer.
208     if (!handle_out) {
209         ALOGE("MAGMA_STATUS_INVALID_ARGS\n");
210         return MAGMA_STATUS_INVALID_ARGS;
211     }
212 
213     ALOGI("opening blob id %llu size %llu\n", (unsigned long long)result_buffer_mapping_id,
214           (unsigned long long)result_buffer_size);
215     auto blob = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma)
216                     ->createBlob({.size = result_buffer_size,
217                                   .flags = kBlobFlagMappable | kBlobFlagShareable,
218                                   .blobMem = kBlobMemHost3d,
219                                   .blobId = result_buffer_mapping_id});
220     if (!blob) {
221         ALOGE("VirtGpuDevice::createBlob failed\n");
222         return MAGMA_STATUS_INTERNAL_ERROR;
223     }
224 
225     VirtGpuExternalHandle handle{};
226     int result = blob->exportBlob(handle);
227     if (result != 0 || handle.osHandle < 0) {
228         ALOGE("VirtGpuResource::exportBlob failed\n");
229         return MAGMA_STATUS_INTERNAL_ERROR;
230     }
231 
232     *handle_out = handle.osHandle;
233     return MAGMA_STATUS_OK;
234 }
235 
magma_buffer_get_handle(void * self,magma_buffer_t buffer,magma_handle_t * handle_out)236 magma_status_t MagmaClientContext::magma_buffer_get_handle(void* self, magma_buffer_t buffer,
237                                                            magma_handle_t* handle_out) {
238     auto context = reinterpret_cast<MagmaClientContext*>(self);
239     magma_buffer_info_t info{};
240     magma_status_t status = context->magma_buffer_get_info(self, buffer, &info);
241     if (status != MAGMA_STATUS_OK) return status;
242     magma_handle_t mapping_id = 0;
243     status = context->magma_buffer_get_handle_enc_(self, buffer, &mapping_id);
244     if (status != MAGMA_STATUS_OK) return status;
245     auto blob = VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma)
246                     ->createBlob({.size = info.size,
247                                   .flags = kBlobFlagMappable | kBlobFlagShareable,
248                                   .blobMem = kBlobMemHost3d,
249                                   .blobId = mapping_id});
250     if (!blob) {
251         return MAGMA_STATUS_INTERNAL_ERROR;
252     }
253 
254     VirtGpuExternalHandle handle{};
255     int result = blob->exportBlob(handle);
256     if (result != 0 || handle.osHandle < 0) {
257         return MAGMA_STATUS_INTERNAL_ERROR;
258     }
259     *handle_out = handle.osHandle;
260     return MAGMA_STATUS_OK;
261 }
262 
magma_buffer_export(void * self,magma_buffer_t buffer,magma_handle_t * handle_out)263 magma_status_t MagmaClientContext::magma_buffer_export(void* self, magma_buffer_t buffer,
264                                                        magma_handle_t* handle_out) {
265     auto context = reinterpret_cast<MagmaClientContext*>(self);
266 
267     int fd;
268     magma_status_t status = context->get_fd_for_buffer(buffer, &fd);
269     if (status != MAGMA_STATUS_OK) return status;
270 
271     *handle_out = fd;
272 
273     return MAGMA_STATUS_OK;
274 }
275 
276 // We can't pass a non-zero timeout to the server, as that would block the server from handling
277 // requests from other threads. So we busy wait here, which isn't ideal; however if the server did
278 // block, gfxstream would busy wait for the response anyway.
magma_poll(void * self,magma_poll_item_t * items,uint32_t count,uint64_t timeout_ns)279 magma_status_t MagmaClientContext::magma_poll(void* self, magma_poll_item_t* items, uint32_t count,
280                                               uint64_t timeout_ns) {
281     auto context = reinterpret_cast<MagmaClientContext*>(self);
282 
283     int64_t time_start = static_cast<int64_t>(get_ns_monotonic(false));
284 
285     int64_t abs_timeout_ns = time_start + timeout_ns;
286 
287     if (abs_timeout_ns < time_start) {
288         abs_timeout_ns = std::numeric_limits<int64_t>::max();
289     }
290 
291     bool warned_for_long_poll = false;
292 
293     while (true) {
294         magma_status_t status = context->magma_poll_enc_(self, items, count, 0);
295 
296         if (status != MAGMA_STATUS_TIMED_OUT) return status;
297 
298         // Not ready, allow other threads to work in with us
299         get_thread_local_context_lock()->unlock();
300 
301         std::this_thread::yield();
302 
303         int64_t time_now = static_cast<int64_t>(get_ns_monotonic(false));
304 
305         // TODO(fxb/122604): Add back-off to the busy loop, ideally based on recent sleep
306         // patterns (e.g. start polling shortly before next expected burst).
307         if (!warned_for_long_poll && time_now - time_start > 5000000000) {
308             ALOGE("magma_poll: long poll detected (%llu us)",
309                   (unsigned long long)((time_now - time_start) / 1000));
310             warned_for_long_poll = true;
311         }
312 
313         if (time_now >= abs_timeout_ns) break;
314 
315         get_thread_local_context_lock()->lock();
316     }
317 
318     return MAGMA_STATUS_TIMED_OUT;
319 }
320 
321 // Magma 1.0 no longer tracks buffer size and id on behalf of the client, so we mirror it here.
magma_connection_create_buffer(void * self,magma_connection_t connection,uint64_t size,uint64_t * size_out,magma_buffer_t * buffer_out,magma_buffer_id_t * id_out)322 magma_status_t MagmaClientContext::magma_connection_create_buffer(void* self,
323                                                                   magma_connection_t connection,
324                                                                   uint64_t size, uint64_t* size_out,
325                                                                   magma_buffer_t* buffer_out,
326                                                                   magma_buffer_id_t* id_out) {
327     auto context = reinterpret_cast<MagmaClientContext*>(self);
328 
329     // TODO(b/277219980): support guest-allocated buffers
330     magma_status_t status = context->magma_connection_create_buffer_enc_(
331         self, connection, size, size_out, buffer_out, id_out);
332     if (status != MAGMA_STATUS_OK) return status;
333 
334     auto [_, inserted] = context->buffer_info_.emplace(
335         *buffer_out, BufferInfo{.connection = connection, .size = *size_out, .id = *id_out});
336     if (!inserted) {
337         ALOGE("magma_connection_create_buffer: duplicate entry in buffer info map");
338         return MAGMA_STATUS_INTERNAL_ERROR;
339     }
340 
341     return MAGMA_STATUS_OK;
342 }
343 
magma_connection_release_buffer(void * self,magma_connection_t connection,magma_buffer_t buffer)344 void MagmaClientContext::magma_connection_release_buffer(void* self, magma_connection_t connection,
345                                                          magma_buffer_t buffer) {
346     auto context = reinterpret_cast<MagmaClientContext*>(self);
347 
348     context->magma_connection_release_buffer_enc_(self, connection, buffer);
349 
350     // Invalid buffer or connection is treated as no-op by magma, so only log as verbose.
351     auto it = context->buffer_info_.find(buffer);
352     if (it == context->buffer_info_.end()) {
353         ALOGV("magma_connection_release_buffer: buffer (%llu) not found in map",
354               (unsigned long long)buffer);
355         return;
356     }
357     if (it->second.connection != connection) {
358         ALOGV(
359             "magma_connection_release_buffer: buffer (%llu) attempted release using wrong "
360             "connection (expected %llu, received %llu)",
361             (unsigned long long)buffer, (unsigned long long)it->second.connection,
362             (unsigned long long)connection);
363         return;
364     }
365     context->buffer_info_.erase(it);
366 }
367 
368 template <typename T, typename U>
SafeCast(const U & value)369 static T SafeCast(const U& value) {
370     if (value > std::numeric_limits<T>::max() || value < std::numeric_limits<T>::min()) {
371         abort();
372     }
373     return static_cast<T>(value);
374 }
375 
376 // We have a singleton client context for all threads.  We want all client
377 // threads served by a single server RenderThread.
GetMagmaContext()378 MagmaClientContext* GetMagmaContext() {
379     static MagmaClientContext* s_context;
380     static std::once_flag once_flag;
381 
382     std::call_once(once_flag, []() {
383         auto stream = createVirtioGpuAddressSpaceStream(kCapsetGfxStreamMagma);
384         assert(stream);
385 
386         // RenderThread expects flags: send zero 'clientFlags' to the host.
387         {
388             auto pClientFlags =
389                 reinterpret_cast<unsigned int*>(stream->allocBuffer(sizeof(unsigned int)));
390             *pClientFlags = 0;
391             stream->commitBuffer(sizeof(unsigned int));
392         }
393 
394         s_context = new MagmaClientContext(stream);
395         auto render_node_fd =
396             VirtGpuDevice::getInstance(VirtGpuCapset::kCapsetGfxStreamMagma)->getDeviceHandle();
397         s_context->render_node_fd_ = SafeCast<int>(render_node_fd);
398 
399         ALOGE("Created new context\n");
400         fflush(stdout);
401     });
402 
403     return s_context;
404 }
405 
406 // Used in magma_entry.cpp
407 // Always lock around the encoding methods because we have a singleton context.
408 #define GET_CONTEXT                              \
409     MagmaClientContext* ctx = GetMagmaContext(); \
410     ContextLock lock(ctx)
411 
412 #include "magma_entry.cpp"
413