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 "edid_helper.h"
18 #include "vkms_tester.h"
19 #include <log/log.h>
20 #include <sstream>
21 #include <stdlib.h>
22 #include <string>
23 #include <unistd.h>
24 #include <vector>
25
26 // clang-format off
27 /*
28 * A binary that turns on vkms and creates connectors.
29 *
30 * Usage:
31 * Simple mode:
32 * ./setup_vkms_connectors_for_atest <number_of_connectors>
33 *
34 * Advanced mode:
35 * ./setup_vkms_connectors_for_atest --config TYPE,NUMBER_OF_OVERLAY_PLANES[,EDID_NAME]
36 * TYPE,NUMBER_OF_OVERLAY_PLANES[,EDID_NAME] ...
37 *
38 * Where:
39 * TYPE = connector type (DP, HDMIA, HDMIB, eDP, DSI, VGA, VIRTUAL, etc.)
40 * NUMBER_OF_OVERLAY_PLANES = number of additional overlay planes (integer)
41 * EDID_NAME = optional EDID profile name (e.g., ACI_9713_ASUS_VE258_DP)
42 *
43 * Examples:
44 * ./setup_vkms_connectors_for_atest 3 # Creates 3 virtual connectors
45 * ./setup_vkms_connectors_for_atest --config DP,2 HDMIA,1 # Creates 2 connectors with specific configs
46 * ./setup_vkms_connectors_for_atest --config DP,2,ACI_9713_ASUS_VE258_DP HDMIA,1,ACI_9155_ASUS_VH238_HDMI
47 * # Creates connectors with specific EDID profiles
48 *
49 * The binary will set up the vkms (virtual kernel mode setting) driver
50 * with the specified configuration. It disables cleanup on destruction
51 * so that the vkms setup persists after program termination.
52 */
53 // clang-format on
54
stringToMonitorName(const std::string & edidName,hcct::edid::MonitorName & monitorName)55 bool stringToMonitorName(const std::string &edidName,
56 hcct::edid::MonitorName &monitorName) {
57 if (edidName.empty()) {
58 return false;
59 }
60
61 // Check if it's an eDP
62 #define CHECK_EDP_MONITOR(monitor) \
63 if (edidName == #monitor) { \
64 monitorName = \
65 hcct::edid::MonitorName(hcct::edid::EdpMonitorName::monitor); \
66 return true; \
67 }
68 // Generate checks for eDPs
69 EDP_MONITOR_LIST(CHECK_EDP_MONITOR)
70
71 // Check if it's a DP monitor
72 #define CHECK_DP_MONITOR(monitor) \
73 if (edidName == #monitor) { \
74 monitorName = hcct::edid::MonitorName(hcct::edid::DpMonitorName::monitor); \
75 return true; \
76 }
77 // Generate checks for all DP monitors
78 DP_MONITOR_LIST(CHECK_DP_MONITOR)
79
80 // Check if it's an HDMI monitor
81 #define CHECK_HDMI_MONITOR(monitor) \
82 if (edidName == #monitor) { \
83 monitorName = \
84 hcct::edid::MonitorName(hcct::edid::HdmiMonitorName::monitor); \
85 return true; \
86 }
87 // Generate checks for all HDMI monitors
88 HDMI_MONITOR_LIST(CHECK_HDMI_MONITOR)
89
90 return false;
91 }
92
parseConnectorConfig(const std::string & configStr)93 hcct::VkmsTester::VkmsConnectorBuilder parseConnectorConfig(
94 const std::string &configStr) {
95 std::istringstream ss(configStr);
96 std::string typeStr, planesStr, edidStr;
97
98 auto builder = hcct::VkmsTester::VkmsConnectorBuilder::create();
99
100 // Parse type
101 if (std::getline(ss, typeStr, ',')) {
102 builder.withType(typeStr);
103 }
104
105 // Parse additional planes
106 if (std::getline(ss, planesStr, ',')) {
107 char *endptr;
108 int planes = strtol(planesStr.c_str(), &endptr, 10);
109 if (*endptr != '\0' || planes < 0) {
110 ALOGE("Invalid number of planes: %s", planesStr.c_str());
111 } else {
112 builder.withAdditionalOverlayPlanes(planes);
113 }
114 }
115
116 // Parse optional EDID name
117 if (std::getline(ss, edidStr, ',')) {
118 hcct::edid::MonitorName monitorName;
119 if (stringToMonitorName(edidStr, monitorName)) {
120 builder.withMonitor(monitorName);
121 } else {
122 ALOGE("Unknown EDID profile name: %s", edidStr.c_str());
123 }
124 }
125
126 return builder;
127 }
128
129 // Helper function to parse all connector configurations
parseConnectorConfigs(int argc,char * argv[],int startIndex)130 std::vector<hcct::VkmsTester::VkmsConnectorBuilder> parseConnectorConfigs(
131 int argc, char *argv[], int startIndex) {
132 std::vector<hcct::VkmsTester::VkmsConnectorBuilder> builders;
133
134 for (int i = startIndex; i < argc; i++) {
135 std::string configStr(argv[i]);
136 builders.push_back(parseConnectorConfig(configStr));
137 }
138
139 return builders;
140 }
141
printUsage(const char * programName)142 void printUsage(const char *programName) {
143 // clang-format off
144 ALOGI("Usage:");
145 ALOGI(" Simple mode: %s <number_of_connectors>", programName);
146 ALOGI(" Advanced mode: %s --config TYPE,NUMBER_OF_OVERLAY_PLANES[,EDID_NAME] TYPE,NUMBER_OF_OVERLAY_PLANES[,EDID_NAME] ...", programName);
147 ALOGI(" Where:");
148 ALOGI(" TYPE = connector type (DP, HDMIA, HDMIB, eDP, DSI, VGA, VIRTUAL, "
149 "WRITEBACK, DPI)");
150 ALOGI(" NUMBER_OF_OVERLAY_PLANES = number of additional overlay planes "
151 "(integer)");
152 ALOGI("Examples:");
153 ALOGI(" %s 3", programName);
154 ALOGI(" %s --config DP,2 HDMIA,1", programName);
155 ALOGI(" %s --config DP,2,ACI_9713_ASUS_VE258_DP HDMIA,1,ACI_9155_ASUS_VH238_HDMI", programName);
156 // clang-format on
157 }
158
main(int argc,char * argv[])159 int main(int argc, char *argv[]) {
160 if (argc <= 1) {
161 ALOGE("Error: No arguments provided");
162 printUsage(argv[0]);
163 return -1;
164 }
165
166 std::unique_ptr<hcct::VkmsTester> vkmsTester;
167
168 // Check for advanced mode with --config flag
169 if (strcmp(argv[1], "--config") == 0) {
170 if (argc <= 2) {
171 ALOGE("Error: No configuration parameters provided after --config");
172 printUsage(argv[0]);
173 return -1;
174 }
175
176 // Parse configs from multiple arguments starting from index 2
177 auto builders = parseConnectorConfigs(argc, argv, 2);
178 if (builders.empty()) {
179 ALOGE("Failed to parse connector configurations");
180 return -1;
181 }
182
183 ALOGI("Setting up vkms with %zu custom connectors", builders.size());
184 vkmsTester = hcct::VkmsTester::CreateWithBuilders(builders);
185 } else {
186 // Simple mode - just a number of connectors
187 char *endptr;
188 int numConnectors = strtol(argv[1], &endptr, 10);
189
190 if (*endptr != '\0' || numConnectors <= 0) {
191 ALOGE("Error: Invalid number of connectors: %s. Must be a positive "
192 "integer.",
193 argv[1]);
194 printUsage(argv[0]);
195 return -1;
196 }
197
198 ALOGI("Setting up vkms with %d generic connectors", numConnectors);
199 vkmsTester = hcct::VkmsTester::CreateWithGenericConnectors(numConnectors);
200 }
201
202 if (!vkmsTester) {
203 ALOGE("Failed to create VkmsTester");
204 return -1;
205 }
206
207 vkmsTester->DisableCleanupOnDestruction();
208
209 // Enable all connectors to run tests on them
210 for (int i = 0; i < vkmsTester->getActiveConnectorsCount(); i++) {
211 if (!vkmsTester->ToggleConnector(i, true)) {
212 ALOGE("Failed to enable connector %d", i);
213 return -1;
214 }
215 }
216
217 return 0;
218 }