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