1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "GraphicsDetectorVkExternalMemoryHost.h"
18
19 #include "Vulkan.h"
20
21 #if defined(__linux__)
22 #include <sys/mman.h>
23 #include <syscall.h>
24 #include <unistd.h>
25 #endif
26
27 namespace gfxstream {
28 namespace {
29
30 #if defined(__linux__)
31 class ScopedFd {
32 public:
ScopedFd(int fd)33 explicit ScopedFd(int fd) { Reset(fd); }
~ScopedFd()34 ~ScopedFd() { Reset(); }
35
36 ScopedFd(const ScopedFd&) = delete;
37 ScopedFd& operator=(const ScopedFd&) = delete;
38
ScopedFd(ScopedFd && rhs)39 ScopedFd(ScopedFd&& rhs) {
40 Reset(rhs.Release());
41 rhs.mFd = -1;
42 }
43
operator =(ScopedFd && rhs)44 ScopedFd& operator=(ScopedFd&& rhs) {
45 Reset(rhs.Release());
46 return *this;
47 }
48
Get() const49 int Get() const {
50 return mFd;
51 }
52
Release()53 int Release() {
54 int localFd = mFd;
55 mFd = -1;
56 return localFd;
57 }
58
Map(size_t size)59 void* Map(size_t size) {
60 if (mFd == -1) {
61 return nullptr;
62 }
63
64 mMappedAddr = mmap(nullptr, size, PROT_WRITE | PROT_READ, MAP_SHARED, mFd, 0);
65 if (mMappedAddr == nullptr) {
66 return nullptr;
67 }
68
69 mMappedSize = size;
70 return mMappedAddr;
71 }
72
Unmap()73 void Unmap() {
74 if (mMappedAddr != nullptr) {
75 munmap(mMappedAddr, mMappedSize);
76 mMappedAddr = nullptr;
77 mMappedSize = 0;
78 }
79 }
80
81 private:
Reset(int newFd=-1)82 void Reset(int newFd = -1) {
83 if (mFd != -1) {
84 //Unmap();
85 close(mFd);
86 }
87 mFd = newFd;
88 }
89
90 int mFd = -1;
91 void* mMappedAddr = nullptr;
92 size_t mMappedSize = 0;
93 };
94
CreateSharedMemory(vkhpp::DeviceSize size)95 gfxstream::expected<ScopedFd, std::string> CreateSharedMemory(vkhpp::DeviceSize size) {
96 int fd = -1;
97
98 #if !defined(ANDROID)
99 fd = memfd_create("unused-name", MFD_CLOEXEC);
100 #elif defined(__NR_memfd_create)
101 // Android host toolchain using a really old glibc ?_?
102 fd = syscall(__NR_memfd_create, "unused-name", MFD_CLOEXEC);
103 #else
104 return gfxstream::unexpected("Failed to create shared memory: memfd_create unavailable.");
105 #endif
106
107 if (fd == -1) {
108 return gfxstream::unexpected("Failed to create shared memory: " +
109 std::string(strerror(errno)));
110 }
111 if (ftruncate(fd, static_cast<off_t>(size))) {
112 const std::string error = std::string(strerror(errno));
113 close(fd);
114 return gfxstream::unexpected("Failed to resize shared memory: " + error);
115 }
116 return ScopedFd(fd);
117 }
118
CheckImportingSharedMemory(vkhpp::PhysicalDevice & physicalDevice)119 gfxstream::expected<Ok, std::string> CheckImportingSharedMemory(
120 vkhpp::PhysicalDevice& physicalDevice) {
121 const vkhpp::PhysicalDeviceMemoryProperties physicalDeviceMemoryProperties =
122 physicalDevice.getMemoryProperties();
123
124 const auto properties2 =
125 physicalDevice
126 .getProperties2<vkhpp::PhysicalDeviceProperties2, //
127 vkhpp::PhysicalDeviceExternalMemoryHostPropertiesEXT>();
128
129 const auto& physicalDeviceExternalMemoryHostProperties =
130 properties2.get<vkhpp::PhysicalDeviceExternalMemoryHostPropertiesEXT>();
131
132 const float queuePriority = 1.0f;
133 const vkhpp::DeviceQueueCreateInfo deviceQueueCreateInfo = {
134 .queueFamilyIndex = 0,
135 .queueCount = 1,
136 .pQueuePriorities = &queuePriority,
137 };
138 const std::vector<const char*> requestedDeviceExtensions = {
139 VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME,
140 };
141 const vkhpp::DeviceCreateInfo deviceCreateInfo = {
142 .queueCreateInfoCount = 1,
143 .pQueueCreateInfos = &deviceQueueCreateInfo,
144 .enabledExtensionCount = static_cast<uint32_t>(requestedDeviceExtensions.size()),
145 .ppEnabledExtensionNames = requestedDeviceExtensions.data(),
146 };
147 auto device = VK_EXPECT_RV_OR_STRING(physicalDevice.createDeviceUnique(deviceCreateInfo));
148
149 constexpr const vkhpp::DeviceSize kSize = 16384;
150
151 auto shm = GFXSTREAM_EXPECT(CreateSharedMemory(kSize));
152
153 void* mappedShm = shm.Map(kSize);
154 if (mappedShm == MAP_FAILED) {
155 return gfxstream::unexpected("Failed to mmap shared memory: " +
156 std::string(strerror(errno)));
157 }
158
159 const vkhpp::MemoryHostPointerPropertiesEXT memoryHostPointerProps =
160 VK_EXPECT_RV_OR_STRING(device->getMemoryHostPointerPropertiesEXT(
161 vkhpp::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, mappedShm));
162
163 uint32_t memoryTypeIndex = -1;
164 for (uint32_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++) {
165 if (memoryHostPointerProps.memoryTypeBits & (1 << i)) {
166 memoryTypeIndex = i;
167 break;
168 }
169 }
170 if (memoryTypeIndex == -1) {
171 return gfxstream::unexpected("Failed to find memory type compatible with shm.");
172 }
173
174 const vkhpp::ImportMemoryHostPointerInfoEXT memoryHostPointerInto = {
175 .handleType = vkhpp::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
176 .pHostPointer = mappedShm,
177 };
178
179 const vkhpp::MemoryAllocateInfo memoryAllocateInfo = {
180 .pNext = &memoryHostPointerInto,
181 .allocationSize = kSize,
182 .memoryTypeIndex = memoryTypeIndex,
183 };
184
185 auto memory = VK_EXPECT_RV_OR_STRING(device->allocateMemoryUnique(memoryAllocateInfo));
186 return Ok{};
187 }
188 #endif // defined(__linux__)
189
PopulateVulkanExternalMemoryHostQuirkImpl(::gfxstream::proto::GraphicsAvailability * availability)190 gfxstream::expected<Ok, vkhpp::Result> PopulateVulkanExternalMemoryHostQuirkImpl(
191 ::gfxstream::proto::GraphicsAvailability* availability) {
192 auto vk = GFXSTREAM_EXPECT(Vk::Load());
193
194 ::gfxstream::proto::VulkanAvailability* vulkanAvailability = availability->mutable_vulkan();
195
196 auto physicalDevices = VK_EXPECT_RV(vk.instance().enumeratePhysicalDevices());
197 for (uint32_t i = 0; i < physicalDevices.size(); i++) {
198 auto& physicalDevice = physicalDevices[i];
199 auto* outPhysicalDevice = vulkanAvailability->mutable_physical_devices(i);
200
201 const auto exts = VK_EXPECT_RV(physicalDevice.enumerateDeviceExtensionProperties());
202
203 std::unordered_set<std::string> extensions;
204 for (const auto& ext : exts) {
205 extensions.insert(std::string(ext.extensionName));
206 }
207 if (extensions.find(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) == extensions.end()) {
208 continue;
209 }
210
211 auto* quirks = outPhysicalDevice->mutable_quirks()
212 ->mutable_external_memory_host_quirks();
213
214 #if defined(__linux__)
215 const auto shmResult = CheckImportingSharedMemory(physicalDevice);
216 if (shmResult.ok()) {
217 quirks->set_can_import_shm(true);
218 } else {
219 quirks->add_errors("can_import_shm error: " + shmResult.error());
220 quirks->set_can_import_shm(false);
221 }
222 #endif // defined(__linux__)
223 }
224
225 return Ok{};
226 }
227
228 } // namespace
229
PopulateVulkanExternalMemoryHostQuirk(::gfxstream::proto::GraphicsAvailability * availability)230 gfxstream::expected<Ok, std::string> PopulateVulkanExternalMemoryHostQuirk(
231 ::gfxstream::proto::GraphicsAvailability* availability) {
232 return PopulateVulkanExternalMemoryHostQuirkImpl(availability)
233 .transform_error([](vkhpp::Result r) { return vkhpp::to_string(r); });
234 }
235
236 } // namespace gfxstream
237