• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 "vkms_tester.h"
18 
19 #include <android-base/file.h>
20 #include <cassert>
21 #include <cstdio>
22 #include <cstdlib>
23 #include <cutils/properties.h>
24 #include <dirent.h>
25 #include <errno.h>
26 #include <log/log.h>
27 #include <string>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 #include <unistd.h>
31 #include <unordered_map>
32 #include <vector>
33 #include <inttypes.h>
34 
35 namespace hcct {
36 
37 namespace {
38 // `/config/vkms` is the base directory for VKMS in ConfigFS. `my-vkms` is the
39 // chosen name of the VKMS instance which can be anything.
40 constexpr const char *kVkmsBaseDir = "/config/vkms/my-vkms";
41 constexpr int kPlaneTypePrimary = 1;
42 
43 // https://cs.android.com/android/platform/superproject/main/+/main:external/libdrm/xf86drmMode.h;l=190
44 enum class ConnectorStatus {
45   kConnected = 1,
46   kDisconnected = 2,
47   kUnknown = 3,
48 };
49 } // namespace
50 
51 // static
52 std::unique_ptr<VkmsTester>
CreateWithGenericConnectors(int displaysCount)53 VkmsTester::CreateWithGenericConnectors(int displaysCount) {
54   if (displaysCount < 0) {
55     ALOGE("Invalid number of displays: %i. At least one connector must be "
56           "specified.",
57           displaysCount);
58     return nullptr;
59   }
60 
61   auto tester = std::unique_ptr<VkmsTester>(new VkmsTester(displaysCount));
62 
63   if (!tester->mInitialized) {
64     ALOGE("Failed to initialize VkmsTester with Generic Connectors");
65     return nullptr;
66   }
67 
68   return tester;
69 }
70 
71 // static
CreateWithBuilders(const std::vector<VkmsConnectorBuilder> & builders)72 std::unique_ptr<VkmsTester> VkmsTester::CreateWithBuilders(
73     const std::vector<VkmsConnectorBuilder> &builders) {
74   if (builders.empty()) {
75     ALOGE("Empty configuration provided. At least one connector must be "
76           "specified.");
77     return nullptr;
78   }
79 
80   auto tester =
81       std::unique_ptr<VkmsTester>(new VkmsTester(builders.size(), builders));
82 
83   if (!tester->mInitialized) {
84     ALOGE("Failed to initialize VkmsTester with Builder Config");
85     return nullptr;
86   }
87 
88   return tester;
89 }
90 
91 // static
ForceDeleteVkmsDir()92 void VkmsTester::ForceDeleteVkmsDir() { ShutdownAndCleanUpVkms(); }
93 
VkmsTester(size_t displaysCount,const std::vector<VkmsConnectorBuilder> & builders)94 VkmsTester::VkmsTester(size_t displaysCount,
95                        const std::vector<VkmsConnectorBuilder> &builders) {
96   mInitialized = ToggleHwc3(false) && SetVkmsAsDisplayDriver() &&
97                  SetupDisplays(displaysCount, builders) && ToggleVkms(true) &&
98                  ToggleHwc3(true);
99   if (!mInitialized) {
100     ALOGE("Failed to set up VKMS");
101     ShutdownAndCleanUpVkms();
102     return;
103   }
104 
105   mActiveConnectorsCount = displaysCount;
106 }
107 
~VkmsTester()108 VkmsTester::~VkmsTester() {
109   if (mDisableCleanupOnDestruction) {
110     ALOGI("Skipping cleanup on destruction. mDisableCleanupOnDestruction is "
111           "set to true.");
112     return;
113   }
114 
115   ShutdownAndCleanUpVkms();
116 }
117 
ToggleConnector(int connectorIndex,bool enable)118 bool VkmsTester::ToggleConnector(int connectorIndex, bool enable) {
119   return SetConnectorStatus(connectorIndex, enable);
120 }
121 
DisableCleanupOnDestruction()122 void VkmsTester::DisableCleanupOnDestruction() {
123   mDisableCleanupOnDestruction = true;
124 }
125 
SetVkmsAsDisplayDriver()126 bool VkmsTester::SetVkmsAsDisplayDriver() {
127   // TODO(b/398831713): Setup an official doc for reference.
128 
129   // Set HWC to use VKMS as the display driver.
130   if (property_set("vendor.hwc.drm.device", "/dev/dri/card1") != 0) {
131     ALOGE("Failed to set vendor.hwc.drm.device property");
132     return false;
133   } else {
134     ALOGI("Successfully set vendor.hwc.drm.device property");
135   }
136 
137   // Create VKMS directory
138   if (mkdir(kVkmsBaseDir, 0777) == 0) {
139     ALOGI("Successfully created directory %s", kVkmsBaseDir);
140     return true;
141   }
142 
143   return false;
144 }
145 
SetupDisplays(int displaysCount,const std::vector<VkmsConnectorBuilder> & builders)146 bool VkmsTester::SetupDisplays(
147     int displaysCount, const std::vector<VkmsConnectorBuilder> &builders) {
148   bool isExplicitConfig = !builders.empty();
149   if (isExplicitConfig && displaysCount != builders.size()) {
150     ALOGE("Mismatch between requested displays count and builder config size");
151     return false;
152   }
153 
154   for (int i = 0; i < displaysCount; ++i) {
155     CreateResource(DrmResource::kCrtc, i);
156     CreateResource(DrmResource::kEncoder, i);
157     LinkToCrtc(DrmResource::kEncoder, i, i);
158 
159     CreateResource(DrmResource::kConnector, i);
160     // Unless explicitly configured, set all connectors to disconnected.
161     SetConnectorStatus(i, isExplicitConfig && builders[i].mEnabledAtStart);
162     if (isExplicitConfig) {
163       SetConnectorType(i, builders[i].mType);
164       if (builders[i].mMonitorName.type != edid::MonitorName::Type::UNSET) {
165         SetConnectorEdid(i, builders[i].mMonitorName);
166       }
167     } else {
168       // Set connector type, eDP for first one, DP for the rest
169       SetConnectorType(i, i == 0 ? ConnectorType::keDP
170                                  : ConnectorType::kDisplayPort);
171     }
172     LinkConnectorToEncoder(i, i);
173 
174     int additionalOverlays =
175         isExplicitConfig ? builders[i].mAdditionalOverlayPlanes : 0;
176     for (int j = 0; j < 2 + additionalOverlays; ++j) {
177       CreateResource(DrmResource::kPlane, mLatestPlaneId);
178       // For each connector, create at least 2 planes, a primary and a cursor
179       // PLUS any additional overlay planes
180       PlaneType type;
181       switch (j) {
182       case 0:
183         type = PlaneType::kCursor;
184         break;
185       case 1:
186         type = PlaneType::kPrimary;
187         break;
188       default:
189         type = PlaneType::kOverlay;
190         break;
191       }
192       SetPlaneType(mLatestPlaneId, type);
193       SetPlaneFormat(mLatestPlaneId);
194       LinkToCrtc(DrmResource::kPlane, mLatestPlaneId, i);
195 
196       mLatestPlaneId++;
197     }
198 
199     ALOGI("Successfully set up display %i", i);
200   }
201 
202   return true;
203 }
204 
205 // static
ToggleVkms(bool enable)206 bool VkmsTester::ToggleVkms(bool enable) {
207   std::string path = std::string(kVkmsBaseDir) + "/enabled";
208   std::string value = enable ? "1" : "0";
209   if (!android::base::WriteStringToFile(value, path)) {
210     ALOGE("Failed to toggle VKMS: %s", strerror(errno));
211     return false;
212   }
213 
214   ALOGI("Successfully toggled VKMS at %s", path.c_str());
215   return true;
216 }
217 
218 // static
ToggleHwc3(bool enable)219 bool VkmsTester::ToggleHwc3(bool enable) {
220   const char *serviceName = "vendor.hwcomposer-3";
221   const char *propertyName = "ctl.start";
222   const char *propertyStopName = "ctl.stop";
223 
224   if (property_set(enable ? propertyName : propertyStopName, serviceName) !=
225       0) {
226     ALOGE("Failed to set property %s to %s",
227           enable ? propertyName : propertyStopName, serviceName);
228     return false;
229   }
230 
231   ALOGI("Successfully set property %s to %s",
232         enable ? propertyName : propertyStopName, serviceName);
233   return true;
234 }
235 
CreateResource(DrmResource resource,int index)236 bool VkmsTester::CreateResource(DrmResource resource, int index) {
237   std::string resourceBase = kDrmResourceBase.at(resource);
238   std::string resourceDir =
239       std::string(kVkmsBaseDir) + "/" + resourceBase + std::to_string(index);
240   if (mkdir(resourceDir.c_str(), 0777) != 0) {
241     ALOGE("Failed to create directory %s: %s", resourceDir.c_str(),
242           strerror(errno));
243     return false;
244   }
245 
246   return true;
247 }
248 
SetConnectorStatus(int index,bool enable)249 bool VkmsTester::SetConnectorStatus(int index, bool enable) {
250   std::string connectorDir = std::string(kVkmsBaseDir) + "/" +
251                              kDrmResourceBase.at(DrmResource::kConnector) +
252                              std::to_string(index);
253   std::string connectedPath = connectorDir + "/status";
254   ConnectorStatus status =
255       enable ? ConnectorStatus::kConnected : ConnectorStatus::kDisconnected;
256   std::string connectedValue = std::to_string(static_cast<int>(status));
257 
258   if (!android::base::WriteStringToFile(connectedValue, connectedPath)) {
259     ALOGE("Failed to toggle connector %i: %s", index, strerror(errno));
260     return false;
261   }
262 
263   ALOGI("Successfully toggled connector %i: %s", index,
264         enable ? "connected" : "disconnected");
265   return true;
266 }
267 
SetConnectorType(int index,ConnectorType type)268 bool VkmsTester::SetConnectorType(int index, ConnectorType type) {
269   std::string connectorDir = std::string(kVkmsBaseDir) + "/" +
270                              kDrmResourceBase.at(DrmResource::kConnector) +
271                              std::to_string(index);
272   std::string typePath = connectorDir + "/type";
273   std::string typeValue = std::to_string(static_cast<int>(type));
274   if (!android::base::WriteStringToFile(typeValue, typePath)) {
275     ALOGE("Failed to write connector type: %s", strerror(errno));
276     return false;
277   }
278 
279   ALOGI("Successfully set connector %i type to %i", index,
280         static_cast<int>(type));
281   return true;
282 }
283 
SetConnectorEdid(int index,edid::MonitorName monitorName)284 bool VkmsTester::SetConnectorEdid(int index, edid::MonitorName monitorName) {
285   std::vector<uint8_t> edidData = edid::getBinaryEdidForMonitor(monitorName);
286   if (edidData.empty()) {
287     ALOGE("Failed to get EDID data for monitor");
288     return false;
289   }
290 
291   std::string connectorDir = std::string(kVkmsBaseDir) + "/" +
292                              kDrmResourceBase.at(DrmResource::kConnector) +
293                              std::to_string(index);
294   std::string edidPath = connectorDir + "/edid";
295 
296   int fd = open(edidPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
297   if (fd == -1) {
298     ALOGE("Failed to open EDID file for writing: %s", strerror(errno));
299     return false;
300   }
301 
302   bool success =
303       android::base::WriteFully(fd, edidData.data(), edidData.size());
304   close(fd);
305 
306   if (success) {
307     ALOGI("Successfully wrote EDID data with size %" PRIu64 " to connector %i",
308         edidData.size(), index);
309   } else {
310     ALOGE("Failed to write complete EDID data: %s", strerror(errno));
311   }
312 
313   return success;
314 }
315 
SetPlaneType(int index,PlaneType type)316 bool VkmsTester::SetPlaneType(int index, PlaneType type) {
317   std::string planeDir = std::string(kVkmsBaseDir) + "/" +
318                          kDrmResourceBase.at(DrmResource::kPlane) +
319                          std::to_string(index);
320   std::string typePath = planeDir + "/type";
321   std::string typeValue = std::to_string(static_cast<int>(type));
322   if (!android::base::WriteStringToFile(typeValue, typePath)) {
323     ALOGE("Failed to write plane type: %s", strerror(errno));
324     return false;
325   }
326 
327   ALOGI("Successfully set plane %i type to %i", index, static_cast<int>(type));
328   return true;
329 }
330 
SetPlaneFormat(int index)331 bool VkmsTester::SetPlaneFormat(int index) {
332   std::string planeDir = std::string(kVkmsBaseDir) + "/" +
333                          kDrmResourceBase.at(DrmResource::kPlane) +
334                          std::to_string(index);
335   std::string formatPath = planeDir + "/supported_formats";
336   // TODO(markyacoub): This is now hardcoded to all formats. Extend this later.
337   std::string formatValue = "+*";
338   if (!android::base::WriteStringToFile(formatValue, formatPath)) {
339     ALOGE("Failed to write plane format: %s", strerror(errno));
340     return false;
341   }
342 
343   ALOGI("Successfully set plane %i format", index);
344   return true;
345 }
346 
LinkToCrtc(DrmResource resource,int resourceIdx,int crtcIdx)347 bool VkmsTester::LinkToCrtc(DrmResource resource, int resourceIdx,
348                             int crtcIdx) {
349   std::string crtcName =
350       kDrmResourceBase.at(DrmResource::kCrtc) + std::to_string(crtcIdx);
351   std::string resourceDir = std::string(kVkmsBaseDir) + "/" +
352                             kDrmResourceBase.at(resource) +
353                             std::to_string(resourceIdx);
354   std::string possibleCrtcPath = resourceDir + "/possible_" + crtcName;
355   std::string crtcDir = std::string(kVkmsBaseDir) + "/" + crtcName;
356 
357   // Now create the symlink
358   if (symlink(crtcDir.c_str(), possibleCrtcPath.c_str()) != 0) {
359     ALOGE("Failed to create symlink at %s pointing to %s: %s",
360           possibleCrtcPath.c_str(), crtcDir.c_str(), strerror(errno));
361     return false;
362   }
363 
364   ALOGI("Successfully linked %s to %s", possibleCrtcPath.c_str(),
365         crtcDir.c_str());
366   return true;
367 }
368 
LinkConnectorToEncoder(int connectorIdx,int encoderIdx)369 bool VkmsTester::LinkConnectorToEncoder(int connectorIdx, int encoderIdx) {
370   std::string encoderName =
371       kDrmResourceBase.at(DrmResource::kEncoder) + std::to_string(encoderIdx);
372   std::string connectorDir = std::string(kVkmsBaseDir) + "/" +
373                              kDrmResourceBase.at(DrmResource::kConnector) +
374                              std::to_string(connectorIdx);
375   std::string possibleEncoderPath = connectorDir + "/possible_" + encoderName;
376   std::string encoderDir = std::string(kVkmsBaseDir) + "/" + encoderName;
377 
378   // Now create the symlink
379   if (symlink(encoderDir.c_str(), possibleEncoderPath.c_str()) != 0) {
380     ALOGE("Failed to create symlink at %s pointing to %s: %s",
381           possibleEncoderPath.c_str(), encoderDir.c_str(), strerror(errno));
382     return false;
383   }
384 
385   ALOGI("Successfully linked %s to %s", possibleEncoderPath.c_str(),
386         encoderDir.c_str());
387   return true;
388 }
389 
390 // static
391 // ConfigFS has special rules about deletion, so we need to clean up manually
392 // every layer.
ShutdownAndCleanUpVkms()393 void VkmsTester::ShutdownAndCleanUpVkms() {
394   ToggleHwc3(false);
395   ToggleVkms(false);
396   // Give the kernel a longer time to release resources
397   usleep(500000);
398 
399   // Clean up manually created relationships first under
400   // possible_(crtcs/encoders). This is required before we started cleaning up
401   // the directories.
402   FindAndCleanupPossibleLinks(kVkmsBaseDir);
403   CleanUpDirAndChildren(kVkmsBaseDir);
404 }
405 
406 // static
FindAndCleanupPossibleLinks(const std::string & dirPath)407 void VkmsTester::FindAndCleanupPossibleLinks(const std::string &dirPath) {
408   DIR *dir = opendir(dirPath.c_str());
409   if (!dir) {
410     return;
411   }
412 
413   struct dirent *entry;
414   while ((entry = readdir(dir)) != nullptr) {
415     if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
416       continue;
417     }
418 
419     std::string path = dirPath + "/" + entry->d_name;
420     struct stat statBuf;
421 
422     if (lstat(path.c_str(), &statBuf) != 0) {
423       continue;
424     }
425 
426     if (S_ISDIR(statBuf.st_mode)) {
427       // If this is a "possible_*" directory, process it specially
428       if (strstr(entry->d_name, "possible_") == entry->d_name) {
429         DIR *dir = opendir(path.c_str());
430         if (!dir) {
431           return;
432         }
433 
434         // First try to remove any contents
435         struct dirent *entry;
436         while ((entry = readdir(dir)) != nullptr) {
437           if (strcmp(entry->d_name, ".") == 0 ||
438               strcmp(entry->d_name, "..") == 0) {
439             continue;
440           }
441 
442           std::string fullPath = path + "/" + entry->d_name;
443           unlink(
444               fullPath
445                   .c_str()); // Try to remove anything inside, ignoring errors
446         }
447 
448         closedir(dir);
449 
450         // Then try to remove the directory itself
451         rmdir(path.c_str());
452       } else {
453         // Otherwise recursively look for more possible_* directories
454         FindAndCleanupPossibleLinks(path);
455       }
456     }
457   }
458 
459   closedir(dir);
460 }
461 
462 // static
CleanUpDirAndChildren(const std::string & dirPath)463 void VkmsTester::CleanUpDirAndChildren(const std::string &dirPath) {
464   DIR *dir = opendir(dirPath.c_str());
465   if (!dir) {
466     ALOGW("Failed to open directory %s: %s - skipping", dirPath.c_str(),
467           strerror(errno));
468     return;
469   }
470 
471   struct dirent *entry;
472   while ((entry = readdir(dir)) != nullptr) {
473     // Skip "." and ".."
474     if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
475       continue;
476     }
477 
478     std::string path = dirPath + "/" + entry->d_name;
479     struct stat statBuf;
480 
481     if (lstat(path.c_str(), &statBuf) != 0) {
482       ALOGW("Failed to stat %s: %s - skipping", path.c_str(), strerror(errno));
483       continue;
484     }
485 
486     if (S_ISDIR(statBuf.st_mode)) {
487       CleanUpDirAndChildren(path);
488     } else {
489     }
490   }
491 
492   closedir(dir);
493 
494   // Remove the directory itself. Do not check for errors as directories that
495   // are auto-created can't be manually deleted. It's a no-op otherwise so we
496   // can ignore the return value.
497   rmdir(dirPath.c_str());
498 }
499 
500 } // namespace hcct
501