1 /*
2 * Copyright (C) 2024 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 #define LOG_TAG "libpixelusb-usbdp"
18
19 #include "include/pixelusb/UsbDpUtils.h"
20
21 #include <android-base/file.h>
22 #include <android-base/parseint.h>
23 #include <android-base/strings.h>
24 #include <dirent.h>
25 #include <pthread.h>
26 #include <stdio.h>
27 #include <sys/epoll.h>
28 #include <sys/eventfd.h>
29 #include <sys/timerfd.h>
30 #include <utils/Log.h>
31
32 #include <cstring>
33
34 using aidl::android::hardware::usb::DisplayPortAltModeStatus;
35 using android::base::ParseUint;
36 using android::base::ReadFileToString;
37 using android::base::Trim;
38 using android::base::WriteStringToFile;
39
40 #define LINK_TRAINING_STATUS_UNKNOWN "0"
41 #define LINK_TRAINING_STATUS_SUCCESS "1"
42 #define LINK_TRAINING_STATUS_FAILURE "2"
43 #define LINK_TRAINING_STATUS_FAILURE_SINK "3"
44
45 #define DISPLAYPORT_CAPABILITIES_RECEPTACLE_BIT 6
46
47 #define DISPLAYPORT_STATUS_DEBOUNCE_MS 2000
48 /*
49 * Type-C HAL should wait 2 seconds to reattempt DisplayPort Alt Mode entry to
50 * allow the port and port partner to settle Role Swaps.
51 */
52 #define DISPLAYPORT_ACTIVATE_DEBOUNCE_MS 2000
53 // Number of times the HAL should reattempt to enter DisplayPort Alt Mode
54 #define DISPLAYPORT_ACTIVATE_MAX_RETRIES 2
55
56 namespace android {
57 namespace hardware {
58 namespace google {
59 namespace pixel {
60 namespace usb {
61 // Set by signal handler to destroy thread
62 volatile bool destroyDisplayPortThread;
63
64 constexpr char kPortPartnerPath[] = "/sys/class/typec/port0-partner/";
65
UsbDp(const char * const drmPath)66 UsbDp::UsbDp(const char *const drmPath)
67 : mDrmPath(drmPath),
68 mPollRunning(false),
69 mPollStarting(false),
70 mFirstSetupDone(false),
71 mIrqCountCache(),
72 mCallback(NULL),
73 mPayload(NULL),
74 mPartnerSupportsDisplayPort(false),
75 mLock(PTHREAD_MUTEX_INITIALIZER) {
76 pthread_condattr_t attr;
77 if (pthread_condattr_init(&attr)) {
78 ALOGE("pthread_condattr_init failed: %s", strerror(errno));
79 abort();
80 }
81 if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) {
82 ALOGE("pthread_condattr_setclock failed: %s", strerror(errno));
83 abort();
84 }
85 if (pthread_cond_init(&mCV, &attr)) {
86 ALOGE("usbdp: pthread_cond_init failed: %s", strerror(errno));
87 abort();
88 }
89 if (pthread_condattr_destroy(&attr)) {
90 ALOGE("pthread_condattr_destroy failed: %s", strerror(errno));
91 abort();
92 }
93 mDisplayPortEventPipe = eventfd(0, EFD_NONBLOCK);
94 if (mDisplayPortEventPipe == -1) {
95 ALOGE("mDisplayPortEventPipe eventfd failed: %s", strerror(errno));
96 abort();
97 }
98 mDisplayPortDebounceTimer = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
99 if (mDisplayPortDebounceTimer == -1) {
100 ALOGE("mDisplayPortDebounceTimer timerfd failed: %s", strerror(errno));
101 abort();
102 }
103 mActivateTimer = timerfd_create(CLOCK_MONOTONIC, 0);
104 if (mActivateTimer == -1) {
105 ALOGE("mActivateTimer timerfd failed: %s", strerror(errno));
106 abort();
107 }
108 }
109
110 /* Class Get/Set Helper Functions */
111 /**
112 * isFirstSetupDone()
113 *
114 * Return:
115 * boolean indicating if first poll thread had been initialized.
116 */
isFirstSetupDone()117 bool UsbDp::isFirstSetupDone() {
118 return mFirstSetupDone;
119 }
120
121 /**
122 * setClientPath()
123 *
124 * Sets UsbDp.mClientPath.
125 *
126 * Input:
127 * @path: path to I2c/SPMI Client
128 */
setClientPath(string path)129 void UsbDp::setClientPath(string path) {
130 mClientPath = path;
131 }
132
133 /**
134 * getPollRunning()
135 *
136 * Return:
137 * boolean indicating if mPoll is currently running
138 */
getPollRunning()139 bool UsbDp::getPollRunning() {
140 return mPollRunning;
141 }
142
143 /**
144 * setPartnerSupportsDisplayport()
145 *
146 * Input:
147 * @supportsDp: boolean indicating DP support
148 */
setPartnerSupportsDisplayPort(bool supportsDp)149 void UsbDp::setPartnerSupportsDisplayPort(bool supportsDp) {
150 mPartnerSupportsDisplayPort = supportsDp;
151 }
152
153 /**
154 * getPartnerSupportsDisplayport()
155 *
156 * Return:
157 * boolean indicating if port partner supports DisplayPort
158 */
getPartnerSupportsDisplayPort()159 bool UsbDp::getPartnerSupportsDisplayPort() {
160 return mPartnerSupportsDisplayPort;
161 }
162
163 /**
164 * updateDisplayPortEventPipe()
165 *
166 * Writes to mDisplayPortEventPipe
167 *
168 * Input:
169 * @flag: value to write to mDisplayPortEventPipe
170 */
updateDisplayPortEventPipe(uint64_t flag)171 void UsbDp::updateDisplayPortEventPipe(uint64_t flag) {
172 write(mDisplayPortEventPipe, &flag, sizeof(flag));
173 }
174
175 /**
176 * registerCallback()
177 *
178 * Registers callback to be run when mDisplayPortDebounceTimer triggers
179 *
180 * Input:
181 * @callback: callback function to be performed
182 * @payload: pointer to Usb data structure
183 */
registerCallback(void (* callback)(void * (payload)),void * payload)184 void UsbDp::registerCallback(void (*callback)(void *(payload)), void *payload) {
185 mCallback = callback;
186 mPayload = payload;
187 }
188
189 /* Internal fd, epoll, timer Helper Functions */
190 // Opens file with given flags
displayPortPollOpenFileHelper(const char * file,int flags)191 static int displayPortPollOpenFileHelper(const char *file, int flags) {
192 int fd = open(file, flags);
193 if (fd == -1) {
194 ALOGE("usbdp: worker: open at %s failed; errno=%d", file, errno);
195 }
196 return fd;
197 }
198
199 // Sets timerfd (fd) to trigger after (ms) milliseconds.
200 // Setting ms to 0 disarms the timer.
armTimerFdHelper(int fd,int ms)201 static int armTimerFdHelper(int fd, int ms) {
202 struct itimerspec ts;
203 int ret;
204
205 ts.it_interval.tv_sec = 0;
206 ts.it_interval.tv_nsec = 0;
207 ts.it_value.tv_sec = ms / 1000;
208 ts.it_value.tv_nsec = (ms % 1000) * 1000000;
209
210 ret = timerfd_settime(fd, 0, &ts, NULL);
211 if (ret < 0) {
212 ALOGE("usbdp: %s failed to arm timer", __func__);
213 }
214
215 return ret;
216 }
217
218 // Returns timespec that expires in debounceMs ms
setTimespecTimer(int debounceMs)219 static struct timespec setTimespecTimer(int debounceMs) {
220 struct timespec to;
221 struct timespec now;
222
223 clock_gettime(CLOCK_MONOTONIC, &now);
224 to.tv_nsec = now.tv_nsec + ((debounceMs % 1000) * 1000000);
225 to.tv_sec = now.tv_sec + (debounceMs / 1000);
226 // Overflow handling
227 to.tv_sec += to.tv_sec / 1000000000L;
228 to.tv_nsec = to.tv_nsec % 1000000000L;
229
230 return to;
231 }
232
233 /* Setup/Shutdown Helper Functions */
displayPortPollWork(void * param)234 void *displayPortPollWork(void *param) {
235 UsbDp *usbDp = reinterpret_cast<UsbDp *>(param);
236
237 usbDp->displayPortPollWorkHelper();
238
239 return NULL;
240 }
241
242 /**
243 * setupDisplayPortPoll()
244 *
245 * Used by USB HAL to setup DisplayPort work thread. Consecutive calls to
246 * setupDisplayPortPoll will exit if the currently starting thread has not
247 * established sysfs links, otherwise assume that the file descriptors have
248 * become stale and setup needs to be performed again.
249 *
250 */
setupDisplayPortPoll()251 void UsbDp::setupDisplayPortPoll() {
252 uint64_t flag = DISPLAYPORT_SHUTDOWN_CLEAR;
253 mFirstSetupDone = true;
254 int ret;
255
256 ALOGI("usbdp: setup: beginning setup for displayport poll thread");
257 mPartnerSupportsDisplayPort = true;
258
259 /*
260 * If thread is currently starting, then it hasn't setup DisplayPort fd's, and we can abandon
261 * this process.
262 */
263 if (mPollStarting) {
264 ALOGI("usbdp: setup: abandoning poll thread because another startup is in progress");
265 return;
266 }
267
268 /*
269 * Check to see if thread is currently running. If it is, then we assume that it must have
270 * invalid DisplayPort fd's and the new thread takes over.
271 */
272 if (mPollRunning) {
273 shutdownDisplayPortPoll(true);
274 pthread_mutex_lock(&mCVLock);
275 struct timespec to = setTimespecTimer(DISPLAYPORT_POLL_WAIT_MS);
276 ret = pthread_cond_timedwait(&mCV, &mCVLock, &to);
277 if (ret == ETIMEDOUT) {
278 ALOGI("usbdp: setup: Wait for poll to shutdown timed out, starting new poll anyways.");
279 }
280 pthread_mutex_unlock(&mCVLock);
281 }
282
283 // Indicate that startup procedure is initiated (mutex protects two threads running setup at
284 // once)
285 mPollStarting = true;
286
287 // Reset shutdown signals because shutdown() does not perform self clean-up
288 write(mDisplayPortEventPipe, &flag, sizeof(flag));
289 destroyDisplayPortThread = false;
290
291 /*
292 * Create a background thread to poll DisplayPort system files
293 */
294 if (pthread_create(&mPoll, NULL, displayPortPollWork, this)) {
295 ALOGE("usbdp: setup: failed to create displayport poll thread %d", errno);
296 goto error;
297 }
298 ALOGI("usbdp: setup: successfully started displayport poll thread");
299 return;
300
301 error:
302 mPollStarting = false;
303 return;
304 }
305
306 /**
307 * shutdownDisplayPortPollHelper()
308 *
309 * Discover the DisplayPort driver sysfs attribute directory. Iterates through
310 * all port partner alt mode directories and queries for displayport sysfs group
311 * to do so.
312 *
313 */
shutdownDisplayPortPollHelper()314 void UsbDp::shutdownDisplayPortPollHelper() {
315 uint64_t flag = DISPLAYPORT_SHUTDOWN_SET;
316 int ret;
317
318 // Write shutdown signal to child thread.
319 ret = write(mDisplayPortEventPipe, &flag, sizeof(flag));
320 if (ret < 0) {
321 ALOGE("usbdp: shutdownDisplayPortPollHelper write failure, %d", ret);
322 }
323 pthread_join(mPoll, NULL);
324 writeHpdOverride(string(mDrmPath), "0");
325 pthread_mutex_lock(&mCVLock);
326 pthread_cond_signal(&mCV);
327 pthread_mutex_unlock(&mCVLock);
328 }
329
shutdownDisplayPortPollWork(void * param)330 void *shutdownDisplayPortPollWork(void *param) {
331 UsbDp *usbDp = reinterpret_cast<UsbDp *>(param);
332
333 usbDp->shutdownDisplayPortPollHelper();
334 ALOGI("usbdp: shutdown: displayport thread shutdown complete.");
335 return NULL;
336 }
337
338 /**
339 * shutdownDisplayPortPoll()
340 *
341 * Discover the DisplayPort driver sysfs attribute directory. Iterates through
342 * all port partner alt mode directories and queries for displayport sysfs group
343 * to do so.
344 *
345 * Input
346 * @force: boolean to indicate if thread should be shutdown irrespective of
347 * whether or not the thread is running.
348 *
349 */
shutdownDisplayPortPoll(bool force)350 void UsbDp::shutdownDisplayPortPoll(bool force) {
351 string displayPortUsbPath;
352
353 ALOGI("usbdp: shutdown: beginning shutdown for displayport poll thread");
354
355 /*
356 * Determine if should shutdown thread
357 *
358 * getDisplayPortUsbPathHelper locates a DisplayPort directory, no need to double check
359 * directory.
360 *
361 * Force is put in place to shutdown even when displayPortUsbPath is still present.
362 * Happens when back to back BIND events are sent and fds are no longer current.
363 */
364 if (!mPollRunning ||
365 (!force && getDisplayPortUsbPathHelper(&displayPortUsbPath) == Status::SUCCESS)) {
366 return;
367 }
368 // Shutdown is nonblocking to let other usb operations continue
369 if (pthread_create(&mDisplayPortShutdownHelper, NULL, shutdownDisplayPortPollWork, this)) {
370 ALOGE("usbdp: shutdown: shutdown worker pthread creation failed %d", errno);
371 }
372 ALOGI("usbdp: shutdown: shutdown thread initialized, force:%d", force);
373 }
374
375 /* Sysfs Helper Functions */
376
377 /**
378 * getDisplayPortUsbPathHelper()
379 *
380 * Discover the DisplayPort driver sysfs attribute directory. Iterates through
381 * all port partner alt mode directories and queries for displayport sysfs group
382 * to do so.
383 *
384 * Input
385 * @path: outparameter to save DisplayPort path
386 *
387 * Return:
388 * SUCCESS if sysfs group exists, ERROR otherwise.
389 */
getDisplayPortUsbPathHelper(string * path)390 Status getDisplayPortUsbPathHelper(string *path) {
391 DIR *dp;
392 Status result = Status::ERROR;
393
394 dp = opendir(kPortPartnerPath);
395 if (dp) {
396 struct dirent *ep;
397 /* Iterate through all alt modes to find displayport driver */
398 while ((ep = readdir(dp))) {
399 if (ep->d_type == DT_DIR) {
400 DIR *displayPortDp;
401 string portPartnerPath =
402 string(kPortPartnerPath) + string(ep->d_name) + "/displayport/";
403 displayPortDp = opendir(portPartnerPath.c_str());
404 if (displayPortDp) {
405 *path = portPartnerPath;
406 closedir(displayPortDp);
407 result = Status::SUCCESS;
408 break;
409 }
410 }
411 }
412 closedir(dp);
413 }
414
415 return result;
416 }
417
418 /**
419 * readDisplayPortAttribute()
420 *
421 * Reads value of given sysfs node
422 *
423 * Input
424 * @attribute: sysfs attribute to read. Function supports
425 * "hpd", "pin_assignment", "link_status", and "vdo"
426 * @usb_path: path to the port partner's displayport sysfs group
427 * @value: outparamenter to write sysfs value to
428 *
429 * Return:
430 * SUCCESS on successful read, ERROR otherwise
431 */
readDisplayPortAttribute(string attribute,string usb_path,string * value)432 Status UsbDp::readDisplayPortAttribute(string attribute, string usb_path, string *value) {
433 string attrPath;
434
435 if (!strncmp(attribute.c_str(), "hpd", strlen("hpd")) ||
436 !strncmp(attribute.c_str(), "pin_assignment", strlen("pin_assignment"))) {
437 attrPath = usb_path + attribute;
438 } else if (!strncmp(attribute.c_str(), "link_status", strlen("link_status"))) {
439 attrPath = mDrmPath + "link_status";
440 } else if (!strncmp(attribute.c_str(), "vdo", strlen("vdo"))) {
441 attrPath = usb_path + "/../vdo";
442 } else {
443 goto error;
444 }
445
446 // Read Attribute
447 if (ReadFileToString(attrPath.c_str(), value)) {
448 return Status::SUCCESS;
449 }
450
451 error:
452 ALOGE("usbdp: Failed to read Type-C attribute %s", attribute.c_str());
453 return Status::ERROR;
454 }
455
456 /**
457 * writeDisplayPortAttribute()
458 *
459 * Copy value of usb sysfs attribute to corresponding drm attribute.
460 *
461 * Input
462 * @attribute: sysfs attribute to read. Function supports
463 * "hpd", "irq_hpd_count", "link_status", and "orientation"
464 * @usb_path: path to the port partner's displayport sysfs group
465 * @drm_path: path to the drm for link_status monitoring
466 *
467 * Return:
468 * SUCCESS on successful write, ERROR otherwise
469 */
writeDisplayPortAttribute(string attribute,string usb_path)470 Status UsbDp::writeDisplayPortAttribute(string attribute, string usb_path) {
471 string attrUsb, attrDrm, attrDrmPath;
472
473 // Get Drm Path
474 attrDrmPath = mDrmPath + attribute;
475
476 // Read Attribute
477 if (!ReadFileToString(usb_path, &attrUsb)) {
478 ALOGE("usbdp: Failed to open or read Type-C attribute %s", attribute.c_str());
479 return Status::ERROR;
480 }
481 attrUsb = Trim(attrUsb);
482
483 // Separate Logic for hpd and pin_assignment
484 if (!strncmp(attribute.c_str(), "hpd", strlen("hpd"))) {
485 if (!strncmp(attrUsb.c_str(), "0", strlen("0"))) {
486 // Read DRM attribute to compare
487 if (!ReadFileToString(attrDrmPath, &attrDrm)) {
488 ALOGE("usbdp: Failed to open or read hpd from drm");
489 return Status::ERROR;
490 }
491 if (!strncmp(attrDrm.c_str(), "0", strlen("0"))) {
492 ALOGI("usbdp: Skipping hpd write when drm and usb both equal 0");
493 return Status::SUCCESS;
494 }
495 }
496 } else if (!strncmp(attribute.c_str(), "irq_hpd_count", strlen("irq_hpd_count"))) {
497 uint32_t temp;
498 if (!ParseUint(attrUsb, &temp)) {
499 ALOGE("usbdp: failed parsing irq_hpd_count:%s", attrUsb.c_str());
500 return Status::ERROR;
501 }
502 // Used to cache the values read from tcpci's irq_hpd_count.
503 // Update drm driver when cached value is not the same as the read value.
504 ALOGI("usbdp: mIrqCountCache:%u irq_hpd_count:%u", mIrqCountCache, temp);
505 if (mIrqCountCache == temp) {
506 return Status::SUCCESS;
507 } else {
508 mIrqCountCache = temp;
509 }
510 attrDrmPath = mDrmPath + "irq_hpd";
511 } else if (!strncmp(attribute.c_str(), "pin_assignment", strlen("pin_assignment"))) {
512 size_t pos = attrUsb.find("[");
513 if (pos != string::npos) {
514 ALOGI("usbdp: Modifying Pin Config from %s", attrUsb.c_str());
515 attrUsb = attrUsb.substr(pos + 1, 1);
516 } else {
517 // Don't write anything
518 ALOGI("usbdp: Pin config not yet chosen, nothing written.");
519 return Status::ERROR;
520 }
521 }
522
523 // Write to drm
524 if (!WriteStringToFile(attrUsb, attrDrmPath)) {
525 ALOGE("usbdp: Failed to write attribute %s to drm: %s", attribute.c_str(), attrUsb.c_str());
526 return Status::ERROR;
527 }
528 ALOGI("usbdp: Successfully wrote attribute %s: %s to drm.", attribute.c_str(), attrUsb.c_str());
529 return Status::SUCCESS;
530 }
531
532 /**
533 * writeHpdOverride()
534 *
535 * Manually writes value to drm hpd stsfs node
536 *
537 * Input
538 * @drm_path: path to the drm
539 * @value: hpd value
540 *
541 * Return:
542 * SUCCESS on successful read, ERROR otherwise
543 */
writeHpdOverride(string drm_path,string value)544 Status UsbDp::writeHpdOverride(string drm_path, string value) {
545 string attrDrmPath;
546
547 // Get Drm Path
548 attrDrmPath = drm_path + "hpd";
549
550 // Write to drm
551 if (!WriteStringToFile(value, attrDrmPath)) {
552 ALOGE("usbdp: hpd override failed: %s", value.c_str());
553 return Status::ERROR;
554 }
555 ALOGI("usbdp: hpd override success: %s", value.c_str());
556 return Status::SUCCESS;
557 }
558
559 /**
560 * queryPartnerSvids()
561 *
562 * query port partner's supported alt mode svids
563 *
564 * Input
565 * @svids: outparameter to return queried svids
566 *
567 * Return:
568 * SUCCESS on successful operation, ERROR otherwise.
569 */
queryPartnerSvids(std::vector<string> * svids)570 Status queryPartnerSvids(std::vector<string> *svids) {
571 DIR *dp;
572
573 dp = opendir(kPortPartnerPath);
574 if (dp != NULL) {
575 struct dirent *ep;
576 // Iterate through directories for Alt Mode SVIDs
577 while ((ep = readdir(dp))) {
578 if (ep->d_type == DT_DIR) {
579 string svid;
580 string portPartnerPath = string(kPortPartnerPath) + string(ep->d_name) + "/svid";
581 if (ReadFileToString(portPartnerPath, &svid)) {
582 (*svids).push_back(Trim(svid));
583 }
584 }
585 }
586 closedir(dp);
587 } else {
588 return Status::ERROR;
589 }
590 return Status::SUCCESS;
591 }
592
593 /* AIDL Helper Functions */
parsePinAssignmentHelper(string pinAssignments)594 DisplayPortAltModePinAssignment parsePinAssignmentHelper(string pinAssignments) {
595 size_t pos = pinAssignments.find("[");
596 if (pos != string::npos) {
597 pinAssignments = pinAssignments.substr(pos + 1, 1);
598 if (pinAssignments == "C") {
599 return DisplayPortAltModePinAssignment::C;
600 } else if (pinAssignments == "D") {
601 return DisplayPortAltModePinAssignment::D;
602 } else if (pinAssignments == "E") {
603 return DisplayPortAltModePinAssignment::E;
604 }
605 }
606 return DisplayPortAltModePinAssignment::NONE;
607 }
608
parseLinkTrainingStatusHelper(string linkTrainingStatus)609 LinkTrainingStatus parseLinkTrainingStatusHelper(string linkTrainingStatus) {
610 linkTrainingStatus = Trim(linkTrainingStatus);
611 if (linkTrainingStatus == LINK_TRAINING_STATUS_SUCCESS) {
612 return LinkTrainingStatus::SUCCESS;
613 } else if (linkTrainingStatus == LINK_TRAINING_STATUS_FAILURE ||
614 linkTrainingStatus == LINK_TRAINING_STATUS_FAILURE_SINK) {
615 return LinkTrainingStatus::FAILURE;
616 }
617 return LinkTrainingStatus::UNKNOWN;
618 }
619
isDisplayPortPlug(string vdoString)620 bool isDisplayPortPlug(string vdoString) {
621 unsigned long vdo;
622 unsigned long receptacleFlag = 1 << DISPLAYPORT_CAPABILITIES_RECEPTACLE_BIT;
623
624 vdoString = Trim(vdoString);
625 if (ParseUint(vdoString.c_str(), &vdo)) {
626 /* We check to see if receptacleFlag is 0, meaning that the DP interface is presented on a
627 * USB-C plug.
628 */
629 return !(vdo & receptacleFlag);
630 } else {
631 ALOGE("usbdp: isDisplayPortPlug: errno:%d", errno);
632 }
633
634 return false;
635 }
636
637 /**
638 * constructAltModeData()
639 *
640 * Constructs DisplayPortAltModeData for framework layer propagation.
641 *
642 * Input
643 * @hpd: hpd state
644 * @pin_assignment: selected pin assignment
645 * @link_status: link training status
646 * @vdo: port partner displayport alt mode vdo
647 *
648 * Return:
649 * DisplayPortAltModeData structure
650 */
constructAltModeData(string hpd,string pin_assignment,string link_status,string vdo)651 AltModeData::DisplayPortAltModeData constructAltModeData(string hpd, string pin_assignment,
652 string link_status, string vdo) {
653 AltModeData::DisplayPortAltModeData dpData;
654
655 // vdo
656 if (isDisplayPortPlug(vdo)) {
657 dpData.cableStatus = DisplayPortAltModeStatus::CAPABLE;
658 } else {
659 dpData.partnerSinkStatus = DisplayPortAltModeStatus::CAPABLE;
660 }
661
662 // hpd, status
663 if (!strncmp(hpd.c_str(), "1", strlen("1"))) {
664 dpData.hpd = true;
665 }
666
667 // pin
668 dpData.pinAssignment = parsePinAssignmentHelper(pin_assignment);
669
670 // link training
671 link_status = Trim(link_status);
672 dpData.linkTrainingStatus = parseLinkTrainingStatusHelper(link_status);
673 if (dpData.linkTrainingStatus == LinkTrainingStatus::SUCCESS) {
674 dpData.partnerSinkStatus = dpData.partnerSinkStatus == DisplayPortAltModeStatus::CAPABLE
675 ? DisplayPortAltModeStatus::ENABLED
676 : DisplayPortAltModeStatus::UNKNOWN;
677 dpData.cableStatus = dpData.cableStatus == DisplayPortAltModeStatus::CAPABLE
678 ? DisplayPortAltModeStatus::ENABLED
679 : DisplayPortAltModeStatus::UNKNOWN;
680 if (dpData.partnerSinkStatus == DisplayPortAltModeStatus::ENABLED) {
681 dpData.cableStatus = DisplayPortAltModeStatus::ENABLED;
682 }
683 } else if (dpData.linkTrainingStatus == LinkTrainingStatus::FAILURE &&
684 dpData.partnerSinkStatus == DisplayPortAltModeStatus::CAPABLE) {
685 // 2.0 cable that fails EDID reports not capable, other link training failures assume
686 // 3.0 cable that fails in all other cases.
687 dpData.cableStatus = (link_status == LINK_TRAINING_STATUS_FAILURE_SINK)
688 ? DisplayPortAltModeStatus::NOT_CAPABLE
689 : DisplayPortAltModeStatus::CAPABLE;
690 }
691
692 return dpData;
693 }
694
695 /* Primary Poll Work */
displayPortPollWorkHelper()696 void UsbDp::displayPortPollWorkHelper() {
697 /* epoll fields */
698 int epoll_fd;
699 struct epoll_event ev_hpd, ev_pin, ev_orientation, ev_eventfd, ev_link;
700 struct epoll_event ev_debounce, ev_activate;
701 int epoll_flags = EPOLLIN | EPOLLET;
702 int epoll_nevents = 0;
703 /* fd fields */
704 int hpd_fd, pin_fd, orientation_fd, link_training_status_fd;
705 int fd_flags = O_RDONLY;
706 /* DisplayPort Link Setup statuses */
707 bool orientationSet = false, pinSet = false;
708 int activateRetryCount = 0;
709 /* File paths */
710 string displayPortUsbPath, irqHpdCountPath, hpdPath, pinAssignmentPath, orientationPath;
711 string tcpcBus, linkPath, partnerActivePath, portActivePath;
712 /* Other */
713 unsigned long res;
714 int ret = 0;
715
716 mPollRunning = true;
717 mPollStarting = false;
718
719 /*---------- Setup ----------*/
720
721 if (getDisplayPortUsbPathHelper(&displayPortUsbPath) == Status::ERROR) {
722 ALOGE("usbdp: worker: could not locate usb displayport directory");
723 goto usb_path_error;
724 }
725
726 ALOGI("usbdp: worker: displayport usb path located at %s", displayPortUsbPath.c_str());
727 hpdPath = displayPortUsbPath + "hpd";
728 pinAssignmentPath = displayPortUsbPath + "pin_assignment";
729 orientationPath = "/sys/class/typec/port0/orientation";
730 linkPath = string(mDrmPath) + "link_status";
731
732 partnerActivePath = displayPortUsbPath + "../mode1/active";
733 portActivePath = "/sys/class/typec/port0/port0.0/mode1/active";
734
735 if (mClientPath.empty()) {
736 ALOGE("usbdp: worker: mClientPath not defined");
737 goto bus_client_error;
738 }
739
740 irqHpdCountPath = mClientPath + "irq_hpd_count";
741 ALOGI("usbdp: worker: irqHpdCountPath:%s", irqHpdCountPath.c_str());
742
743 epoll_fd = epoll_create(64);
744 if (epoll_fd == -1) {
745 ALOGE("usbdp: worker: epoll_create failed; errno=%d", errno);
746 goto epoll_fd_error;
747 }
748
749 if ((hpd_fd = displayPortPollOpenFileHelper(hpdPath.c_str(), fd_flags)) == -1) {
750 goto hpd_fd_error;
751 }
752 if ((pin_fd = displayPortPollOpenFileHelper(pinAssignmentPath.c_str(), fd_flags)) == -1) {
753 goto pin_fd_error;
754 }
755 if ((orientation_fd = displayPortPollOpenFileHelper(orientationPath.c_str(), fd_flags)) == -1) {
756 goto orientation_fd_error;
757 }
758 if ((link_training_status_fd = displayPortPollOpenFileHelper(linkPath.c_str(), fd_flags)) ==
759 -1) {
760 goto link_training_status_fd_error;
761 }
762
763 // Set epoll_event events and flags
764 epoll_flags = EPOLLIN | EPOLLET;
765 ev_hpd.events = epoll_flags;
766 ev_pin.events = epoll_flags;
767 ev_orientation.events = epoll_flags;
768 ev_eventfd.events = epoll_flags;
769 ev_link.events = epoll_flags;
770 ev_debounce.events = epoll_flags;
771 ev_activate.events = epoll_flags;
772
773 ev_hpd.data.fd = hpd_fd;
774 ev_pin.data.fd = pin_fd;
775 ev_orientation.data.fd = orientation_fd;
776 ev_eventfd.data.fd = mDisplayPortEventPipe;
777 ev_link.data.fd = link_training_status_fd;
778 ev_debounce.data.fd = mDisplayPortDebounceTimer;
779 ev_activate.data.fd = mActivateTimer;
780
781 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, hpd_fd, &ev_hpd) == -1) {
782 ALOGE("usbdp: worker: epoll_ctl failed to add hpd; errno=%d", errno);
783 goto error;
784 }
785 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pin_fd, &ev_pin) == -1) {
786 ALOGE("usbdp: worker: epoll_ctl failed to add pin; errno=%d", errno);
787 goto error;
788 }
789 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, orientation_fd, &ev_orientation) == -1) {
790 ALOGE("usbdp: worker: epoll_ctl failed to add orientation; errno=%d", errno);
791 goto error;
792 }
793 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, link_training_status_fd, &ev_link) == -1) {
794 ALOGE("usbdp: worker: epoll_ctl failed to add link status; errno=%d", errno);
795 goto error;
796 }
797 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mDisplayPortDebounceTimer, &ev_debounce) == -1) {
798 ALOGE("usbdp: worker: epoll_ctl failed to add framework update debounce; errno=%d", errno);
799 goto error;
800 }
801 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mActivateTimer, &ev_activate) == -1) {
802 ALOGE("usbdp: worker: epoll_ctl failed to add activate debounce; errno=%d", errno);
803 goto error;
804 }
805 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mDisplayPortEventPipe, &ev_eventfd) == -1) {
806 ALOGE("usbdp: worker: epoll_ctl failed to add event pipe; errno=%d", errno);
807 goto error;
808 }
809
810 /* Arm timer to see if DisplayPort Alt Mode Activates */
811 armTimerFdHelper(mActivateTimer, DISPLAYPORT_ACTIVATE_DEBOUNCE_MS);
812
813 while (!destroyDisplayPortThread) {
814 struct epoll_event events[64];
815
816 epoll_nevents = epoll_wait(epoll_fd, events, 64, -1);
817 if (epoll_nevents == -1) {
818 if (errno == EINTR)
819 continue;
820 ALOGE("usbdp: worker: epoll_wait failed; errno=%d", errno);
821 break;
822 }
823
824 for (int n = 0; n < epoll_nevents; n++) {
825 if (events[n].data.fd == hpd_fd) {
826 if (!pinSet || !orientationSet) {
827 ALOGW("usbdp: worker: HPD may be set before pin_assignment and orientation");
828 if (!pinSet && writeDisplayPortAttribute("pin_assignment", pinAssignmentPath) ==
829 Status::SUCCESS) {
830 pinSet = true;
831 }
832 if (!orientationSet &&
833 writeDisplayPortAttribute("orientation", orientationPath) ==
834 Status::SUCCESS) {
835 orientationSet = true;
836 }
837 }
838 writeDisplayPortAttribute("hpd", hpdPath);
839 armTimerFdHelper(mDisplayPortDebounceTimer, DISPLAYPORT_STATUS_DEBOUNCE_MS);
840 } else if (events[n].data.fd == pin_fd) {
841 if (writeDisplayPortAttribute("pin_assignment", pinAssignmentPath) ==
842 Status::SUCCESS) {
843 pinSet = true;
844 armTimerFdHelper(mDisplayPortDebounceTimer, DISPLAYPORT_STATUS_DEBOUNCE_MS);
845 }
846 } else if (events[n].data.fd == orientation_fd) {
847 if (writeDisplayPortAttribute("orientation", orientationPath) == Status::SUCCESS) {
848 orientationSet = true;
849 armTimerFdHelper(mDisplayPortDebounceTimer, DISPLAYPORT_STATUS_DEBOUNCE_MS);
850 }
851 } else if (events[n].data.fd == link_training_status_fd) {
852 armTimerFdHelper(mDisplayPortDebounceTimer, DISPLAYPORT_STATUS_DEBOUNCE_MS);
853 } else if (events[n].data.fd == mDisplayPortDebounceTimer) {
854 ret = read(mDisplayPortDebounceTimer, &res, sizeof(res));
855 ALOGI("usbdp: dp debounce triggered, val:%lu ret:%d", res, ret);
856 if (ret < 0) {
857 ALOGW("usbdp: debounce read errno:%d", errno);
858 continue;
859 }
860 if (mCallback) {
861 mCallback(mPayload);
862 }
863 } else if (events[n].data.fd == mActivateTimer) {
864 string activePartner, activePort;
865
866 if (ReadFileToString(partnerActivePath.c_str(), &activePartner) &&
867 ReadFileToString(portActivePath.c_str(), &activePort)) {
868 // Retry activate signal when DisplayPort Alt Mode is active on port but not
869 // partner.
870 if (!strncmp(activePartner.c_str(), "no", strlen("no")) &&
871 !strncmp(activePort.c_str(), "yes", strlen("yes")) &&
872 activateRetryCount < DISPLAYPORT_ACTIVATE_MAX_RETRIES) {
873 if (!WriteStringToFile("1", partnerActivePath)) {
874 ALOGE("usbdp: Failed to activate port partner Alt Mode");
875 } else {
876 ALOGI("usbdp: Attempting to activate port partner Alt Mode");
877 }
878 activateRetryCount++;
879 armTimerFdHelper(mActivateTimer, DISPLAYPORT_ACTIVATE_DEBOUNCE_MS);
880 } else {
881 ALOGI("usbdp: DisplayPort Alt Mode is active, or disabled on port");
882 }
883 } else {
884 activateRetryCount++;
885 armTimerFdHelper(mActivateTimer, DISPLAYPORT_ACTIVATE_DEBOUNCE_MS);
886 ALOGE("usbdp: Failed to read active state from port or partner");
887 }
888 } else if (events[n].data.fd == mDisplayPortEventPipe) {
889 uint64_t flag = 0;
890 if (!read(mDisplayPortEventPipe, &flag, sizeof(flag))) {
891 if (errno == EAGAIN)
892 continue;
893 ALOGI("usbdp: worker: Shutdown eventfd read error");
894 goto error;
895 }
896 if (flag == DISPLAYPORT_SHUTDOWN_SET) {
897 ALOGI("usbdp: worker: Shutdown eventfd triggered");
898 destroyDisplayPortThread = true;
899 break;
900 } else if (flag == DISPLAYPORT_IRQ_HPD_COUNT_CHECK) {
901 ALOGI("usbdp: worker: IRQ_HPD event through DISPLAYPORT_IRQ_HPD_COUNT_CHECK");
902 writeDisplayPortAttribute("irq_hpd_count", irqHpdCountPath);
903 }
904 }
905 }
906 }
907
908 error:
909 /* Need to disarm so new threads don't get old event */
910 armTimerFdHelper(mDisplayPortDebounceTimer, 0);
911 armTimerFdHelper(mActivateTimer, 0);
912 close(link_training_status_fd);
913 link_training_status_fd_error:
914 close(orientation_fd);
915 orientation_fd_error:
916 close(pin_fd);
917 pin_fd_error:
918 close(hpd_fd);
919 hpd_fd_error:
920 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, mDisplayPortDebounceTimer, &ev_debounce);
921 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, mActivateTimer, &ev_activate);
922 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, mDisplayPortEventPipe, &ev_eventfd);
923 close(epoll_fd);
924 epoll_fd_error:
925 bus_client_error:
926 usb_path_error:
927 mPollRunning = false;
928 ALOGI("usbdp: worker: exiting worker thread");
929 }
930
931 } // namespace usb
932 } // namespace pixel
933 } // namespace google
934 } // namespace hardware
935 } // namespace android
936