| File: | /shared/playproj/i2c/src/ddc/ddc_displays.c |
| Warning: | line 207, column 16 Value stored to 'psc' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /** @file ddc_displays.c |
| 2 | * Access displays, whether DDC or USB |
| 3 | * |
| 4 | * This file and ddc_display_ref_reports.c cross-reference each other. |
| 5 | */ |
| 6 | |
| 7 | // Copyright (C) 2014-2022 Sanford Rockowitz <rockowitz@minsoft.com> |
| 8 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 9 | |
| 10 | #include "config.h" |
| 11 | |
| 12 | /** \cond */ |
| 13 | #include <assert.h> |
| 14 | #include <errno(*__errno_location ()).h> |
| 15 | #include <glib-2.0/glib.h> |
| 16 | #include <string.h> |
| 17 | #include <sys/stat.h> |
| 18 | #include <time.h> |
| 19 | |
| 20 | #include "util/debug_util.h" |
| 21 | #include "util/edid.h" |
| 22 | #include "util/error_info.h" |
| 23 | #include "util/failsim.h" |
| 24 | #include "util/report_util.h" |
| 25 | #include "util/string_util.h" |
| 26 | #include "util/sysfs_util.h" |
| 27 | #ifdef ENABLE_UDEV1 |
| 28 | #include "util/udev_usb_util.h" |
| 29 | #include "util/udev_util.h" |
| 30 | #endif |
| 31 | /** \endcond */ |
| 32 | |
| 33 | #include "public/ddcutil_types.h" |
| 34 | #include "private/ddcutil_types_private.h" |
| 35 | |
| 36 | #include "base/core.h" |
| 37 | #include "base/ddc_packets.h" |
| 38 | #include "base/feature_metadata.h" |
| 39 | #include "base/linux_errno.h" |
| 40 | #include "base/monitor_model_key.h" |
| 41 | #include "base/parms.h" |
| 42 | #include "base/rtti.h" |
| 43 | |
| 44 | #include "vcp/vcp_feature_codes.h" |
| 45 | |
| 46 | #include "i2c/i2c_bus_core.h" |
| 47 | #include "i2c/i2c_strategy_dispatcher.h" |
| 48 | #include "i2c/i2c_sysfs.h" |
| 49 | |
| 50 | #ifdef USE_USB1 |
| 51 | #include "usb/usb_displays.h" |
| 52 | #endif |
| 53 | |
| 54 | #include "dynvcp/dyn_feature_files.h" |
| 55 | |
| 56 | #include "ddc/ddc_packet_io.h" |
| 57 | #include "ddc/ddc_vcp.h" |
| 58 | #include "ddc/ddc_vcp_version.h" |
| 59 | |
| 60 | #include "ddc/ddc_display_ref_reports.h" |
| 61 | #include "ddc/ddc_displays.h" |
| 62 | |
| 63 | // Default trace class for this file |
| 64 | static DDCA_Trace_Group TRACE_GROUP = DDCA_TRC_DDC; |
| 65 | |
| 66 | static GPtrArray * all_displays = NULL((void*)0); // all detected displays |
| 67 | static GPtrArray * display_open_errors = NULL((void*)0); // array of Bus_Open_Error |
| 68 | static int dispno_max = 0; // highest assigned display number |
| 69 | static int async_threshold = DISPLAY_CHECK_ASYNC_THRESHOLD_DEFAULT0xff; |
| 70 | #ifdef USE_USB1 |
| 71 | static bool_Bool detect_usb_displays = true1; |
| 72 | #else |
| 73 | static bool_Bool detect_usb_displays = false0; |
| 74 | #endif |
| 75 | |
| 76 | // |
| 77 | // Functions to perform initial checks |
| 78 | // |
| 79 | |
| 80 | /** Sets the threshold for async display examination. |
| 81 | * If the number of /dev/i2c devices for which DDC communication is to be |
| 82 | * checked is greater than or equal to the threshold value, examine each |
| 83 | * device in a separate thread. |
| 84 | * |
| 85 | * @param threshold threshold value |
| 86 | */ |
| 87 | void |
| 88 | ddc_set_async_threshold(int threshold) { |
| 89 | // DBGMSG("threshold = %d", threshold); |
| 90 | async_threshold = threshold; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | static inline bool_Bool |
| 95 | value_bytes_zero_for_any_value(DDCA_Any_Vcp_Value * pvalrec) { |
| 96 | bool_Bool result = pvalrec && pvalrec->value_type == DDCA_NON_TABLE_VCP_VALUE && |
| 97 | pvalrec->val.c_nc.mh == 0 && |
| 98 | pvalrec->val.c_nc.ml == 0 && |
| 99 | pvalrec->val.c_nc.sh == 0 && |
| 100 | pvalrec->val.c_nc.sl == 0; |
| 101 | return result; |
| 102 | } |
| 103 | |
| 104 | |
| 105 | /** Collects initial monitor checks to perform them on a single open of the |
| 106 | * monitor device, and to avoid repeating them. |
| 107 | * |
| 108 | * Performs the following tests: |
| 109 | * - Checks that DDC communication is working. |
| 110 | * - Checks if the monitor uses DDC Null Response to indicate invalid VCP code |
| 111 | * - Checks if the monitor uses mh=ml=sh=sl=0 to indicate invalid VCP code |
| 112 | * |
| 113 | * @param dh pointer to #Display_Handle for open monitor device |
| 114 | * @return **true** if DDC communication with the display succeeded, **false** otherwise. |
| 115 | * |
| 116 | * @remark |
| 117 | * Sets bits in dh->dref->flags |
| 118 | * * @remark |
| 119 | * It has been observed that DDC communication can fail even if slave address x37 |
| 120 | * is valid on the I2C bus. |
| 121 | * @remark |
| 122 | * ADL does not notice that a reported display, e.g. Dell 1905FP, does not support |
| 123 | * DDC. |
| 124 | * @remark |
| 125 | * Monitors are supposed to set the unsupported feFFature bit in a valid DDC |
| 126 | * response, but a few monitors (mis)use the Null Response instead to indicate |
| 127 | * an unsupported feature. Others return with the unsupported feature bit not |
| 128 | * set, but all bytes (mh, ml, sh, sl) zero. |
| 129 | * @remark |
| 130 | * Note that the test here is not perfect, as a Null Response might |
| 131 | * in fact indicate a transient error, but that is rare. |
| 132 | * @remark |
| 133 | * Output level should have been set <= DDCA_OL_NORMAL prior to this call since |
| 134 | * verbose output is distracting. |
| 135 | */ |
| 136 | // static // non-static for backtrace |
| 137 | bool_Bool |
| 138 | ddc_initial_checks_by_dh(Display_Handle * dh) { |
| 139 | bool_Bool debug = false0; |
| 140 | TRACED_ASSERT(dh && dh->dref)do { if (dh && dh->dref) { ; } else { dbgtrc(DDCA_TRC_ALL , 0, __func__, 140, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "dh && dh->dref", "ddc_displays.c", 140); syslog (3, "Assertion failed: \"%s\" in file %s at line %d", "dh && dh->dref" , "ddc_displays.c", 140); exit(1); } } while (0); |
| 141 | DBGTRC_STARTING(debug, TRACE_GROUP, "dh=%s", dh_repr(dh))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 141 , "ddc_displays.c", "Starting ""dh=%s", dh_repr(dh)); |
| 142 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "communication flags: %s", interpret_dref_flags_t(dh->dref->flags))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 142 , "ddc_displays.c", " ""communication flags: %s", interpret_dref_flags_t (dh->dref->flags)); |
| 143 | |
| 144 | DDCA_Any_Vcp_Value * pvalrec; |
| 145 | |
| 146 | if (!(dh->dref->flags & DREF_DDC_COMMUNICATION_CHECKED0x0080)) { |
| 147 | Public_Status_Code psc = 0; |
| 148 | Error_Info * ddc_excp = ddc_get_vcp_value(dh, 0x00, DDCA_NON_TABLE_VCP_VALUE, &pvalrec); |
| 149 | psc = (ddc_excp) ? ddc_excp->status_code : 0; |
| 150 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "ddc_get_vcp_value() for feature 0x00 returned: %s, pvalrec=%p",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 151 , "ddc_displays.c", " ""ddc_get_vcp_value() for feature 0x00 returned: %s, pvalrec=%p" , errinfo_summary(ddc_excp), pvalrec) |
| 151 | errinfo_summary(ddc_excp), pvalrec)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 151 , "ddc_displays.c", " ""ddc_get_vcp_value() for feature 0x00 returned: %s, pvalrec=%p" , errinfo_summary(ddc_excp), pvalrec); |
| 152 | TRACED_ASSERT( (psc == 0 && pvalrec) || (psc != 0 && !pvalrec) )do { if ((psc == 0 && pvalrec) || (psc != 0 && !pvalrec)) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 152 , "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "(psc == 0 && pvalrec) || (psc != 0 && !pvalrec)" , "ddc_displays.c", 152); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "(psc == 0 && pvalrec) || (psc != 0 && !pvalrec)" , "ddc_displays.c", 152); exit(1); } } while (0); |
| 153 | |
| 154 | if (psc == DDCRC_RETRIES(-(3000 +7 ) ) && debug) |
| 155 | DBGMSG(" Try errors: %s", errinfo_causes_string(ddc_excp))dbgtrc(DDCA_TRC_ALL, 0, __func__, 155, "ddc_displays.c", " Try errors: %s" , errinfo_causes_string(ddc_excp)); |
| 156 | if (ddc_excp) |
| 157 | errinfo_free(ddc_excp); |
| 158 | |
| 159 | DDCA_IO_Mode io_mode = dh->dref->io_path.io_mode; |
| 160 | if (io_mode == DDCA_IO_USB) { |
| 161 | if (psc == 0 || psc == DDCRC_DETERMINED_UNSUPPORTED(-(3000 +12) )) { |
| 162 | dh->dref->flags |= DREF_DDC_COMMUNICATION_WORKING0x0040; |
| 163 | } |
| 164 | } |
| 165 | else { |
| 166 | TRACED_ASSERT(psc != DDCRC_DETERMINED_UNSUPPORTED)do { if (psc != (-(3000 +12) )) { ; } else { dbgtrc(DDCA_TRC_ALL , 0, __func__, 166, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "psc != DDCRC_DETERMINED_UNSUPPORTED", "ddc_displays.c", 166 ); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "psc != DDCRC_DETERMINED_UNSUPPORTED", "ddc_displays.c", 166 ); exit(1); } } while (0); // only set at higher levels, unless USB |
| 167 | |
| 168 | // What if returns -EIO? Dell AW3418D returns -EIO for unsupported features |
| 169 | // EXCEPT that it returns mh=ml=sh=sl=0 for feature 0x00 (2/2019) |
| 170 | |
| 171 | if ( psc == DDCRC_NULL_RESPONSE(-(3000 +2 ) ) || |
| 172 | psc == DDCRC_ALL_RESPONSES_NULL(-(3000 +11) ) || |
| 173 | psc == 0 || |
| 174 | psc == DDCRC_REPORTED_UNSUPPORTED(-(3000 +5 ) ) ) |
| 175 | { |
| 176 | dh->dref->flags |= DREF_DDC_COMMUNICATION_WORKING0x0040; |
| 177 | |
| 178 | if (psc == DDCRC_REPORTED_UNSUPPORTED(-(3000 +5 ) )) |
| 179 | dh->dref->flags |= DREF_DDC_USES_DDC_FLAG_FOR_UNSUPPORTED0x0200; |
| 180 | |
| 181 | else if (psc == DDCRC_NULL_RESPONSE(-(3000 +2 ) ) || psc == DDCRC_ALL_RESPONSES_NULL(-(3000 +11) )) |
| 182 | dh->dref->flags |= DREF_DDC_USES_NULL_RESPONSE_FOR_UNSUPPORTED0x0800; |
| 183 | |
| 184 | else { |
| 185 | TRACED_ASSERT( psc == 0)do { if (psc == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__ , 185, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "psc == 0", "ddc_displays.c", 185); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "psc == 0", "ddc_displays.c", 185); exit(1); } } while (0); |
| 186 | TRACED_ASSERT(pvalrec && pvalrec->value_type == DDCA_NON_TABLE_VCP_VALUE )do { if (pvalrec && pvalrec->value_type == DDCA_NON_TABLE_VCP_VALUE ) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 186, "ddc_displays.c" , "Assertion failed: \"%s\" in file %s at line %d", "pvalrec && pvalrec->value_type == DDCA_NON_TABLE_VCP_VALUE" , "ddc_displays.c", 186); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "pvalrec && pvalrec->value_type == DDCA_NON_TABLE_VCP_VALUE" , "ddc_displays.c", 186); exit(1); } } while (0); |
| 187 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "pvalrec: value_type=%d, mh=%d, ml=%d, sh=%d, sl=%d",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 189 , "ddc_displays.c", " ""pvalrec: value_type=%d, mh=%d, ml=%d, sh=%d, sl=%d" , pvalrec->value_type, pvalrec->val.c_nc.mh, pvalrec-> val.c_nc.ml, pvalrec->val.c_nc.sh, pvalrec->val.c_nc.sl ) |
| 188 | pvalrec->value_type, pvalrec->val.c_nc.mh, pvalrec->val.c_nc.ml,dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 189 , "ddc_displays.c", " ""pvalrec: value_type=%d, mh=%d, ml=%d, sh=%d, sl=%d" , pvalrec->value_type, pvalrec->val.c_nc.mh, pvalrec-> val.c_nc.ml, pvalrec->val.c_nc.sh, pvalrec->val.c_nc.sl ) |
| 189 | pvalrec->val.c_nc.sh, pvalrec->val.c_nc.sl)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 189 , "ddc_displays.c", " ""pvalrec: value_type=%d, mh=%d, ml=%d, sh=%d, sl=%d" , pvalrec->value_type, pvalrec->val.c_nc.mh, pvalrec-> val.c_nc.ml, pvalrec->val.c_nc.sh, pvalrec->val.c_nc.sl ); |
| 190 | |
| 191 | if (value_bytes_zero_for_any_value(pvalrec)) |
| 192 | { |
| 193 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "Setting DREF_DDC_USES_MH_ML_SH_SL_ZERO_FOR_UNSUPPORTED")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 193 , "ddc_displays.c", " ""Setting DREF_DDC_USES_MH_ML_SH_SL_ZERO_FOR_UNSUPPORTED" ); |
| 194 | dh->dref->flags |= DREF_DDC_USES_MH_ML_SH_SL_ZERO_FOR_UNSUPPORTED0x0400; |
| 195 | } |
| 196 | else { |
| 197 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "Setting DREF_DDC_DOES_NOT_INDICATE_UNSUPPORTED")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 197 , "ddc_displays.c", " ""Setting DREF_DDC_DOES_NOT_INDICATE_UNSUPPORTED" ); |
| 198 | dh->dref->flags |= DREF_DDC_DOES_NOT_INDICATE_UNSUPPORTED0x0100; |
| 199 | } |
| 200 | } |
| 201 | } // end, communication working |
| 202 | |
| 203 | else { // communication failed |
| 204 | if ( i2c_force_bus /* && psc == DDCRC_RETRIES */) { |
| 205 | DBGTRC_NOPREFIX(debug || true , TRACE_GROUP, "dh=%s, Forcing DDC communication success.",dbgtrc( (debug || 1) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__ , 206, "ddc_displays.c", " ""dh=%s, Forcing DDC communication success." , dh_repr(dh)) |
| 206 | dh_repr(dh) )dbgtrc( (debug || 1) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__ , 206, "ddc_displays.c", " ""dh=%s, Forcing DDC communication success." , dh_repr(dh)); |
| 207 | psc = 0; |
Value stored to 'psc' is never read | |
| 208 | dh->dref->flags |= DREF_DDC_COMMUNICATION_WORKING0x0040; |
| 209 | // dh->dref->flags |= DREF_DDC_DOES_NOT_INDICATE_UNSUPPORTED; |
| 210 | dh->dref->flags |= DREF_DDC_USES_DDC_FLAG_FOR_UNSUPPORTED0x0200; // good_enuf_for_test |
| 211 | if ( vcp_version_eq(dh->dref->vcp_version_xdf, DDCA_VSPEC_UNQUERIED)) // may have been forced by option --mccs |
| 212 | dh->dref->vcp_version_xdf = DDCA_VSPEC_V22; // good enuf for test |
| 213 | } |
| 214 | } |
| 215 | } // end, io_mode == DDC_IO_I2C |
| 216 | dh->dref->flags |= DREF_DDC_COMMUNICATION_CHECKED0x0080; |
| 217 | |
| 218 | if ( dh->dref->flags & DREF_DDC_COMMUNICATION_WORKING0x0040 ) { |
| 219 | // Would prefer to defer checking version until actually needed to avoid additional DDC io |
| 220 | // during monitor detection. Unfortunately, this would introduce ddc_open_display(), with |
| 221 | // its possible error states, into other functions, e.g. ddca_get_feature_list_by_dref() |
| 222 | if ( vcp_version_eq(dh->dref->vcp_version_xdf, DDCA_VSPEC_UNQUERIED)) { // may have been forced by option --mccs |
| 223 | set_vcp_version_xdf_by_dh(dh); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | } // end, !DREF_DDC_COMMUNICATION_CHECKED |
| 228 | |
| 229 | DBGTRC_RET_BOOL(debug, TRACE_GROUP, dh->dref->flags & DREF_DDC_COMMUNICATION_WORKING, "dh=%s", dh_repr(dh))dbgtrc_returning_expression( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP ), 0, __func__, 229, "ddc_displays.c", ( (dh->dref->flags & 0x0040) ? "true" : "false" ), "dh=%s", dh_repr(dh)); |
| 230 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "communication flags: %s", interpret_dref_flags_t(dh->dref->flags))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 230 , "ddc_displays.c", " ""communication flags: %s", interpret_dref_flags_t (dh->dref->flags)); |
| 231 | return dh->dref->flags & DREF_DDC_COMMUNICATION_WORKING0x0040; |
| 232 | } |
| 233 | |
| 234 | |
| 235 | /** Given a #Display_Ref, opens the monitor device and calls #initial_checks_by_dh() |
| 236 | * to perform initial monitor checks. |
| 237 | * |
| 238 | * @param dref pointer to #Display_Ref for monitor |
| 239 | * @return **true** if DDC communication with the display succeeded, **false** otherwise. |
| 240 | */ |
| 241 | bool_Bool |
| 242 | ddc_initial_checks_by_dref(Display_Ref * dref) { |
| 243 | bool_Bool debug = false0; |
| 244 | DBGTRC_STARTING(debug, TRACE_GROUP, "dref=%s", dref_repr_t(dref))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 244 , "ddc_displays.c", "Starting ""dref=%s", dref_repr_t(dref)); |
| 245 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "dref->flags: %s", interpret_dref_flags_t(dref->flags))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 245 , "ddc_displays.c", " ""dref->flags: %s", interpret_dref_flags_t (dref->flags)); |
| 246 | |
| 247 | bool_Bool result = false0; |
| 248 | Display_Handle * dh = NULL((void*)0); |
| 249 | Public_Status_Code psc = 0; |
| 250 | |
| 251 | psc = ddc_open_display(dref, CALLOPT_ERR_MSG0x80, &dh); |
| 252 | if (psc == 0) { |
| 253 | result = ddc_initial_checks_by_dh(dh); |
| 254 | ddc_close_display(dh); |
| 255 | } |
| 256 | // else { // why else? |
| 257 | dref->flags |= DREF_DDC_COMMUNICATION_CHECKED0x0080; |
| 258 | // } |
| 259 | if (psc == -EBUSY16) |
| 260 | dref->flags |= DREF_DDC_BUSY0x8000; |
| 261 | |
| 262 | DBGTRC_DONE(debug, TRACE_GROUP, "Returning %s. dref = %s", sbool(result), dref_repr_t(dref) )dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 262 , "ddc_displays.c", "Done ""Returning %s. dref = %s", sbool (result), dref_repr_t(dref)); |
| 263 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "communication flags: %s", interpret_dref_flags_t(dref->flags))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 263 , "ddc_displays.c", " ""communication flags: %s", interpret_dref_flags_t (dref->flags)); |
| 264 | return result; |
| 265 | } |
| 266 | |
| 267 | |
| 268 | /** Performs initial checks in a thread |
| 269 | * |
| 270 | * @param data display reference |
| 271 | */ |
| 272 | void * |
| 273 | threaded_initial_checks_by_dref(gpointer data) { |
| 274 | bool_Bool debug = false0; |
| 275 | |
| 276 | Display_Ref * dref = data; |
| 277 | TRACED_ASSERT(memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0 )do { if (memcmp(dref->marker, "DREF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 277, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 277); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 277); exit(1); } } while (0); |
| 278 | DBGTRC_STARTING(debug, TRACE_GROUP, "dref = %s", dref_repr_t(dref) )dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 278 , "ddc_displays.c", "Starting ""dref = %s", dref_repr_t(dref )); |
| 279 | |
| 280 | ddc_initial_checks_by_dref(dref); |
| 281 | // g_thread_exit(NULL); |
| 282 | DBGTRC_DONE(debug, TRACE_GROUP, "Returning NULL. dref = %s,", dref_repr_t(dref) )dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 282 , "ddc_displays.c", "Done ""Returning NULL. dref = %s,", dref_repr_t(dref)); |
| 283 | return NULL((void*)0); |
| 284 | } |
| 285 | |
| 286 | |
| 287 | /** Spawns threads to perform initial checks and waits for them all to complete. |
| 288 | * |
| 289 | * @param all_displays #GPtrArray of pointers to #Display_Ref |
| 290 | */ |
| 291 | void ddc_async_scan(GPtrArray * all_displays) { |
| 292 | bool_Bool debug = false0; |
| 293 | DBGTRC_STARTING(debug, TRACE_GROUP, "all_displays=%p, display_count=%d", all_displays, all_displays->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 293 , "ddc_displays.c", "Starting ""all_displays=%p, display_count=%d" , all_displays, all_displays->len); |
| 294 | |
| 295 | GPtrArray * threads = g_ptr_array_new(); |
| 296 | for (int ndx = 0; ndx < all_displays->len; ndx++) { |
| 297 | Display_Ref * dref = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 298 | TRACED_ASSERT( memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0 )do { if (memcmp(dref->marker, "DREF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 298, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 298); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 298); exit(1); } } while (0); |
| 299 | |
| 300 | GThread * th = |
| 301 | g_thread_new( |
| 302 | dref_repr_t(dref), // thread name |
| 303 | threaded_initial_checks_by_dref, |
| 304 | dref); // pass pointer to display ref as data |
| 305 | g_ptr_array_add(threads, th); |
| 306 | } |
| 307 | DBGMSF(debug, "Started %d threads", threads->len)do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 307, "ddc_displays.c" , "Started %d threads", threads->len); } while(0); |
| 308 | for (int ndx = 0; ndx < threads->len; ndx++) { |
| 309 | GThread * thread = g_ptr_array_index(threads, ndx)((threads)->pdata)[ndx]; |
| 310 | g_thread_join(thread); // implicitly unrefs the GThread |
| 311 | } |
| 312 | DBGMSF(debug, "Threads joined")do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 312, "ddc_displays.c" , "Threads joined"); } while(0); |
| 313 | g_ptr_array_free(threads, true1); |
| 314 | |
| 315 | DBGTRC_DONE(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 315 , "ddc_displays.c", "Done """); |
| 316 | } |
| 317 | |
| 318 | |
| 319 | /** Loops through a list of display refs, performing initial checks on each. |
| 320 | * |
| 321 | * @param all_displays #GPtrArray of pointers to #Display_Ref |
| 322 | */ |
| 323 | void |
| 324 | ddc_non_async_scan(GPtrArray * all_displays) { |
| 325 | bool_Bool debug = false0; |
| 326 | DBGTRC_STARTING(debug, TRACE_GROUP, "checking %d displays", all_displays->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 326 , "ddc_displays.c", "Starting ""checking %d displays", all_displays ->len); |
| 327 | |
| 328 | for (int ndx = 0; ndx < all_displays->len; ndx++) { |
| 329 | Display_Ref * dref = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 330 | TRACED_ASSERT( memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0 )do { if (memcmp(dref->marker, "DREF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 330, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 330); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 330); exit(1); } } while (0); |
| 331 | ddc_initial_checks_by_dref(dref); |
| 332 | } |
| 333 | DBGTRC_DONE(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 333 , "ddc_displays.c", "Done """); |
| 334 | } |
| 335 | |
| 336 | |
| 337 | // |
| 338 | // Functions to get display information |
| 339 | // |
| 340 | |
| 341 | /** Gets a list of all detected displays, whether they support DDC or not. |
| 342 | * |
| 343 | * Detection must already have occurred. |
| 344 | * |
| 345 | * @return **GPtrArray of #Display_Ref instances |
| 346 | */ |
| 347 | GPtrArray * |
| 348 | ddc_get_all_displays() { |
| 349 | // ddc_ensure_displays_detected(); |
| 350 | TRACED_ASSERT(all_displays)do { if (all_displays) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__ , 350, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "all_displays", "ddc_displays.c", 350); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "all_displays", "ddc_displays.c", 350); exit(1); } } while ( 0); |
| 351 | return all_displays; |
| 352 | } |
| 353 | |
| 354 | |
| 355 | /** Gets a list of all detected displays, optionally excluding those |
| 356 | * that are invalid. |
| 357 | * |
| 358 | * @return **GPtrArray of #Display_Ref instances |
| 359 | */ |
| 360 | GPtrArray * |
| 361 | ddc_get_filtered_displays(bool_Bool include_invalid_displays) { |
| 362 | bool_Bool debug = false0; |
| 363 | DBGTRC_STARTING(debug, TRACE_GROUP, "include_invalid_displays=%s", sbool(include_invalid_displays))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 363 , "ddc_displays.c", "Starting ""include_invalid_displays=%s" , sbool(include_invalid_displays)); |
| 364 | TRACED_ASSERT(all_displays)do { if (all_displays) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__ , 364, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "all_displays", "ddc_displays.c", 364); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "all_displays", "ddc_displays.c", 364); exit(1); } } while ( 0); |
| 365 | GPtrArray * result = g_ptr_array_sized_new(all_displays->len); |
| 366 | for (int ndx = 0; ndx < all_displays->len; ndx++) { |
| 367 | Display_Ref * cur = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 368 | if (include_invalid_displays || cur->dispno > 0) { |
| 369 | g_ptr_array_add(result, cur); |
| 370 | } |
| 371 | } |
| 372 | DBGTRC_DONE(debug, TRACE_GROUP, "Returning array of size %d", result->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 372 , "ddc_displays.c", "Done ""Returning array of size %d", result->len); |
| 373 | if (debug || IS_TRACING()is_tracing(TRACE_GROUP, "ddc_displays.c", __func__)) { |
| 374 | ddc_dbgrpt_drefs("Display_Refs:", result, 2); |
| 375 | } |
| 376 | return result; |
| 377 | } |
| 378 | |
| 379 | |
| 380 | /** Returns the number of detected displays. |
| 381 | * |
| 382 | * @param include_invalid_displays |
| 383 | * @return number of displays, 0 if display detection has not yet occurred. |
| 384 | */ |
| 385 | int |
| 386 | ddc_get_display_count(bool_Bool include_invalid_displays) { |
| 387 | int display_ct = -1; |
| 388 | if (all_displays) { |
| 389 | display_ct = 0; |
| 390 | for (int ndx=0; ndx<all_displays->len; ndx++) { |
| 391 | Display_Ref * dref = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 392 | TRACED_ASSERT(memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0)do { if (memcmp(dref->marker, "DREF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 392, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 392); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 392); exit(1); } } while (0); |
| 393 | if (dref->dispno > 0 || include_invalid_displays) { |
| 394 | display_ct++; |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | return display_ct; |
| 399 | } |
| 400 | |
| 401 | |
| 402 | /** Returns list of all open() errors encountered during display detection. |
| 403 | * |
| 404 | * @return **GPtrArray of #Bus_Open_Error. |
| 405 | */ |
| 406 | GPtrArray * |
| 407 | ddc_get_bus_open_errors() { |
| 408 | return display_open_errors; |
| 409 | } |
| 410 | |
| 411 | |
| 412 | |
| 413 | |
| 414 | // |
| 415 | // Phantom displays |
| 416 | // |
| 417 | |
| 418 | static bool_Bool |
| 419 | edid_ids_match(Parsed_Edid * edid1, Parsed_Edid * edid2) { |
| 420 | bool_Bool result = false0; |
| 421 | result = streq(edid1->mfg_id, edid2->mfg_id) && |
| 422 | streq(edid1->model_name, edid2->model_name) && |
| 423 | edid1->product_code == edid2->product_code && |
| 424 | streq(edid1->serial_ascii, edid2->serial_ascii) && |
| 425 | edid1->serial_binary == edid2->serial_binary; |
| 426 | return result; |
| 427 | } |
| 428 | |
| 429 | |
| 430 | /** Check if an invalid #Display_Reference can be regarded as a phantom |
| 431 | * of a given valid #Display_Reference. |
| 432 | * |
| 433 | * @param invalid_dref |
| 434 | * @param valid_dref |
| 435 | * @return true/false |
| 436 | * |
| 437 | * - Both are /dev/i2c devices |
| 438 | * - The EDID id fields must match |
| 439 | * - For the invalid #Display_Reference: |
| 440 | * - attribute status must exist and equal "disconnected" |
| 441 | * - attribute enabled must exist and equal "disabled" |
| 442 | * - attribute edid must not exist |
| 443 | */ |
| 444 | bool_Bool |
| 445 | is_phantom_display(Display_Ref* invalid_dref, Display_Ref * valid_dref) { |
| 446 | bool_Bool debug = false0; |
| 447 | char * invalid_repr = strdup(dref_repr_t(invalid_dref)); |
| 448 | char * valid_repr = strdup(dref_repr_t(valid_dref)); |
| 449 | DBGTRC_STARTING(debug, TRACE_GROUP, "invalid_dref=%s, valid_dref=%s",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 450 , "ddc_displays.c", "Starting ""invalid_dref=%s, valid_dref=%s" , invalid_repr, valid_repr) |
| 450 | invalid_repr, valid_repr)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 450 , "ddc_displays.c", "Starting ""invalid_dref=%s, valid_dref=%s" , invalid_repr, valid_repr); |
| 451 | free(invalid_repr); |
| 452 | free(valid_repr); |
| 453 | |
| 454 | bool_Bool result = false0; |
| 455 | // User report has shown that 128 byte EDIDs can differ for the valid and |
| 456 | // invalid display. Specifically, byte 24 was seen to differ, with one |
| 457 | // having RGB 4:4:4 and the other RGB 4:4:4 + YCrCb 4:2:2!. So instead of |
| 458 | // simply byte comparing the 2 EDIDs, check the identifiers. |
| 459 | if (edid_ids_match(invalid_dref->pedid, valid_dref->pedid)) { |
| 460 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "EDIDs match")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 460 , "ddc_displays.c", " ""EDIDs match"); |
| 461 | if (invalid_dref->io_path.io_mode == DDCA_IO_I2C && |
| 462 | valid_dref->io_path.io_mode == DDCA_IO_I2C) |
| 463 | { |
| 464 | int invalid_busno = invalid_dref->io_path.path.i2c_busno; |
| 465 | // int valid_busno = valid_dref->io_path.path.i2c_busno; |
| 466 | char buf0[40]; |
| 467 | snprintf(buf0, 40, "/sys/bus/i2c/devices/i2c-%d", invalid_busno); |
| 468 | bool_Bool old_silent = set_rpt_sysfs_attr_silent(!(debug|| IS_TRACING()is_tracing(TRACE_GROUP, "ddc_displays.c", __func__))); |
| 469 | char * invalid_rpath = NULL((void*)0); |
| 470 | bool_Bool ok = RPT_ATTR_REALPATH(0, &invalid_rpath, buf0, "device")rpt_attr_realpath(0, &invalid_rpath, buf0, "device", ((void *)0)); |
| 471 | if (ok) { |
| 472 | result = true1; |
| 473 | char * attr_value = NULL((void*)0); |
| 474 | ok = RPT_ATTR_TEXT(0, &attr_value, invalid_rpath, "status")rpt_attr_text(0, &attr_value, invalid_rpath, "status", (( void*)0)); |
| 475 | if (!ok || !streq(attr_value, "disconnected")) |
| 476 | result = false0; |
| 477 | ok = RPT_ATTR_TEXT(0, &attr_value, invalid_rpath, "enabled")rpt_attr_text(0, &attr_value, invalid_rpath, "enabled", ( (void*)0)); |
| 478 | if (!ok || !streq(attr_value, "disabled")) |
| 479 | result = false0; |
| 480 | GByteArray * edid; |
| 481 | ok = RPT_ATTR_EDID(0, &edid, invalid_rpath, "edid")rpt_attr_edid(0, &edid, invalid_rpath, "edid", ((void*)0) ); // is "edid" needed |
| 482 | if (ok) { |
| 483 | result = false0; |
| 484 | g_byte_array_free(edid, true1); |
| 485 | } |
| 486 | } |
| 487 | set_rpt_sysfs_attr_silent(old_silent); |
| 488 | } |
| 489 | } |
| 490 | DBGTRC_DONE(debug, TRACE_GROUP, "Returning: %s", sbool(result) )dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 490 , "ddc_displays.c", "Done ""Returning: %s", sbool(result )); |
| 491 | return result; |
| 492 | } |
| 493 | |
| 494 | |
| 495 | /** Mark phantom displays. |
| 496 | * |
| 497 | * Solit the #Display_Ref's in a GPtrArray into those that have |
| 498 | * already been determined to be valid (dispno > 0) and those |
| 499 | * that are invalid (dispno < 0). |
| 500 | * |
| 501 | * For each invalid array, check to see if it is a phantom display |
| 502 | * corresponding to one of the valid displays. If so, set its dispno |
| 503 | * to DISPNO_INVALID and save a pointer to the valid display ref. |
| 504 | * |
| 505 | * @param all_displays array of pointers to #Display_Ref |
| 506 | * |
| 507 | * @remark |
| 508 | * This handles the case where DDC communication works for one |
| 509 | * /dev/i2c bus but not another. It does not handle the case where |
| 510 | * communication succeeds on both /dev/i2c devices. |
| 511 | */ |
| 512 | void |
| 513 | filter_phantom_displays(GPtrArray * all_displays) { |
| 514 | bool_Bool debug = false0; |
| 515 | DBGTRC_STARTING(debug, TRACE_GROUP, "all_displays->len = %d", all_displays->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 515 , "ddc_displays.c", "Starting ""all_displays->len = %d", all_displays ->len); |
| 516 | GPtrArray* valid_displays = g_ptr_array_sized_new(all_displays->len); |
| 517 | GPtrArray* invalid_displays = g_ptr_array_sized_new(all_displays->len); |
| 518 | for (int ndx = 0; ndx < all_displays->len; ndx++) { |
| 519 | Display_Ref * dref = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 520 | TRACED_ASSERT( memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0 )do { if (memcmp(dref->marker, "DREF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 520, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 520); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 520); exit(1); } } while (0); |
| 521 | if (dref->dispno < 0) // DISPNO_INVALID, DISPNO_PHANTOM, DISPNO_REMOVED |
| 522 | g_ptr_array_add(invalid_displays, dref); |
| 523 | else |
| 524 | g_ptr_array_add(valid_displays, dref); |
| 525 | } |
| 526 | DBGTRC_NOPREFIX(debug, TRACE_GROUP, "%d valid displays, %d invalid displays",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 527 , "ddc_displays.c", " ""%d valid displays, %d invalid displays" , valid_displays->len, invalid_displays->len) |
| 527 | valid_displays->len, invalid_displays->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 527 , "ddc_displays.c", " ""%d valid displays, %d invalid displays" , valid_displays->len, invalid_displays->len); |
| 528 | if (invalid_displays->len > 0 || valid_displays->len == 0 ) { |
| 529 | for (int invalid_ndx = 0; invalid_ndx < invalid_displays->len; invalid_ndx++) { |
| 530 | Display_Ref * invalid_ref = g_ptr_array_index(invalid_displays, invalid_ndx)((invalid_displays)->pdata)[invalid_ndx]; |
| 531 | for (int valid_ndx = 0; valid_ndx < valid_displays->len; valid_ndx++) { |
| 532 | Display_Ref * valid_ref = g_ptr_array_index(valid_displays, valid_ndx)((valid_displays)->pdata)[valid_ndx]; |
| 533 | if (is_phantom_display(invalid_ref, valid_ref)) { |
| 534 | invalid_ref->dispno = DISPNO_PHANTOM-2; // -2 |
| 535 | invalid_ref->actual_display = valid_ref; |
| 536 | } |
| 537 | } |
| 538 | } |
| 539 | } |
| 540 | g_ptr_array_free(invalid_displays, true1); |
| 541 | g_ptr_array_free(valid_displays, true1); |
| 542 | DBGTRC_DONE(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 542 , "ddc_displays.c", "Done """); |
| 543 | } |
| 544 | |
| 545 | |
| 546 | // |
| 547 | // Display Detection |
| 548 | // |
| 549 | |
| 550 | /** Emits a debug report of a list of #Bus_Open_Error. |
| 551 | * |
| 552 | * @param open_errors array of #Bus_Open_Error |
| 553 | * @param depth logical indentation depth |
| 554 | */ |
| 555 | void dbgrpt_bus_open_errors(GPtrArray * open_errors, int depth) { |
| 556 | int d1 = depth+1; |
| 557 | if (!open_errors || open_errors->len == 0) { |
| 558 | rpt_vstring(depth, "Bus open errors: None"); |
| 559 | } |
| 560 | else { |
| 561 | rpt_vstring(depth, "Bus open errors:"); |
| 562 | for (int ndx = 0; ndx < open_errors->len; ndx++) { |
| 563 | Bus_Open_Error * cur = g_ptr_array_index(open_errors, ndx)((open_errors)->pdata)[ndx]; |
| 564 | assert(cur->io_mode != DDCA_IO_ADL)((void) sizeof ((cur->io_mode != DDCA_IO_ADL) ? 1 : 0), __extension__ ({ if (cur->io_mode != DDCA_IO_ADL) ; else __assert_fail ( "cur->io_mode != DDCA_IO_ADL", "ddc_displays.c", 564, __extension__ __PRETTY_FUNCTION__); })); |
| 565 | rpt_vstring(d1, "%s bus: %-2d, error: %d", |
| 566 | (cur->io_mode == DDCA_IO_I2C) ? "I2C" : "hiddev", |
| 567 | cur->devno, cur->error); |
| 568 | } |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | |
| 573 | /** Detects all connected displays by querying the I2C and USB subsystems. |
| 574 | * |
| 575 | * @param open_errors_loc where to return address of #GPtrArray of #Bus_Open_Error |
| 576 | * @return array of #Display_Ref |
| 577 | */ |
| 578 | // static |
| 579 | GPtrArray * |
| 580 | ddc_detect_all_displays(GPtrArray ** i2c_open_errors_loc) { |
| 581 | bool_Bool debug = false0; |
| 582 | DBGTRC_STARTING(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 582 , "ddc_displays.c", "Starting """); |
| 583 | dispno_max = 0; |
| 584 | GPtrArray * bus_open_errors = g_ptr_array_new(); |
| 585 | GPtrArray * display_list = g_ptr_array_new(); |
| 586 | |
| 587 | int busct = i2c_detect_buses(); |
| 588 | DBGMSF(debug, "i2c_detect_buses() returned: %d", busct)do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 588, "ddc_displays.c" , "i2c_detect_buses() returned: %d", busct); } while(0); |
| 589 | uint busndx = 0; |
| 590 | for (busndx=0; busndx < busct; busndx++) { |
| 591 | I2C_Bus_Info * businfo = i2c_get_bus_info_by_index(busndx); |
| 592 | if ( (businfo->flags & I2C_BUS_ADDR_0X500x20) && businfo->edid ) { |
| 593 | Display_Ref * dref = create_bus_display_ref(businfo->busno); |
| 594 | dref->dispno = DISPNO_INVALID-1; // -1, guilty until proven innocent |
| 595 | dref->pedid = businfo->edid; // needed? |
| 596 | dref->mmid = monitor_model_key_new( |
| 597 | dref->pedid->mfg_id, |
| 598 | dref->pedid->model_name, |
| 599 | dref->pedid->product_code); |
| 600 | |
| 601 | // drec->detail.bus_detail = businfo; |
| 602 | dref->detail = businfo; |
| 603 | dref->flags |= DREF_DDC_IS_MONITOR_CHECKED0x0010; |
| 604 | dref->flags |= DREF_DDC_IS_MONITOR0x0008; |
| 605 | g_ptr_array_add(display_list, dref); |
| 606 | } |
| 607 | else if ( !(businfo->flags & I2C_BUS_ACCESSIBLE0x40) ) { |
| 608 | Bus_Open_Error * boe = calloc(1, sizeof(Bus_Open_Error)); |
| 609 | boe->io_mode = DDCA_IO_I2C; |
| 610 | boe->devno = businfo->busno; |
| 611 | boe->error = businfo->open_errno; |
| 612 | g_ptr_array_add(bus_open_errors, boe); |
| 613 | } |
| 614 | } |
| 615 | |
| 616 | #ifdef USE_USB1 |
| 617 | if (detect_usb_displays) { |
| 618 | GPtrArray * usb_monitors = get_usb_monitor_list(); // array of USB_Monitor_Info |
| 619 | // DBGMSF(debug, "Found %d USB displays", usb_monitors->len); |
| 620 | for (int ndx=0; ndx<usb_monitors->len; ndx++) { |
| 621 | Usb_Monitor_Info * curmon = g_ptr_array_index(usb_monitors,ndx)((usb_monitors)->pdata)[ndx]; |
| 622 | TRACED_ASSERT(memcmp(curmon->marker, USB_MONITOR_INFO_MARKER, 4) == 0)do { if (memcmp(curmon->marker, "UMNF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 622, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(curmon->marker, USB_MONITOR_INFO_MARKER, 4) == 0" , "ddc_displays.c", 622); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(curmon->marker, USB_MONITOR_INFO_MARKER, 4) == 0" , "ddc_displays.c", 622); exit(1); } } while (0); |
| 623 | Display_Ref * dref = create_usb_display_ref( |
| 624 | curmon->hiddev_devinfo->busnum, |
| 625 | curmon->hiddev_devinfo->devnum, |
| 626 | curmon->hiddev_device_name); |
| 627 | dref->dispno = DISPNO_INVALID-1; // -1 |
| 628 | dref->pedid = curmon->edid; |
| 629 | if (dref->pedid) |
| 630 | dref->mmid = monitor_model_key_new( |
| 631 | dref->pedid->mfg_id, |
| 632 | dref->pedid->model_name, |
| 633 | dref->pedid->product_code); |
| 634 | else |
| 635 | dref->mmid = monitor_model_key_new("UNK", "UNK", 0); |
| 636 | // drec->detail.usb_detail = curmon; |
| 637 | dref->detail = curmon; |
| 638 | dref->flags |= DREF_DDC_IS_MONITOR_CHECKED0x0010; |
| 639 | dref->flags |= DREF_DDC_IS_MONITOR0x0008; |
| 640 | g_ptr_array_add(display_list, dref); |
| 641 | } |
| 642 | |
| 643 | GPtrArray * usb_open_errors = get_usb_open_errors(); |
| 644 | if (usb_open_errors && usb_open_errors->len > 0) { |
| 645 | for (int ndx = 0; ndx < usb_open_errors->len; ndx++) { |
| 646 | Bus_Open_Error * usb_boe = (Bus_Open_Error *) g_ptr_array_index(usb_open_errors, ndx)((usb_open_errors)->pdata)[ndx]; |
| 647 | Bus_Open_Error * boe_copy = calloc(1, sizeof(Bus_Open_Error)); |
| 648 | boe_copy->io_mode = DDCA_IO_USB; |
| 649 | boe_copy->devno = usb_boe->devno; |
| 650 | boe_copy->error = usb_boe->error; |
| 651 | g_ptr_array_add(bus_open_errors, boe_copy); |
| 652 | } |
| 653 | } |
| 654 | } |
| 655 | #endif |
| 656 | |
| 657 | // verbose output is distracting within scans |
| 658 | // saved and reset here so that async threads are not adjusting output level |
| 659 | DDCA_Output_Level olev = get_output_level(); |
| 660 | if (olev == DDCA_OL_VERBOSE) |
| 661 | set_output_level(DDCA_OL_NORMAL); |
| 662 | |
| 663 | DBGMSF(debug, "display_list->len=%d, async_threshold=%d",do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 664, "ddc_displays.c" , "display_list->len=%d, async_threshold=%d", display_list ->len, async_threshold); } while(0) |
| 664 | display_list->len, async_threshold)do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 664, "ddc_displays.c" , "display_list->len=%d, async_threshold=%d", display_list ->len, async_threshold); } while(0); |
| 665 | if (display_list->len >= async_threshold) |
| 666 | ddc_async_scan(display_list); |
| 667 | else |
| 668 | ddc_non_async_scan(display_list); |
| 669 | |
| 670 | if (olev == DDCA_OL_VERBOSE) |
| 671 | set_output_level(olev); |
| 672 | |
| 673 | // assign display numbers |
| 674 | for (int ndx = 0; ndx < display_list->len; ndx++) { |
| 675 | Display_Ref * dref = g_ptr_array_index(display_list, ndx)((display_list)->pdata)[ndx]; |
| 676 | TRACED_ASSERT( memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0 )do { if (memcmp(dref->marker, "DREF", 4) == 0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__, 676, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 676); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "memcmp(dref->marker, DISPLAY_REF_MARKER, 4) == 0", "ddc_displays.c" , 676); exit(1); } } while (0); |
| 677 | if (dref->flags & DREF_DDC_COMMUNICATION_WORKING0x0040) |
| 678 | dref->dispno = ++dispno_max; |
| 679 | else if (dref->flags & DREF_DDC_BUSY0x8000) |
| 680 | dref->dispno = DISPNO_BUSY-4; |
| 681 | else { |
| 682 | dref->dispno = DISPNO_INVALID-1; // -1; |
| 683 | } |
| 684 | } |
| 685 | |
| 686 | filter_phantom_displays(display_list); |
| 687 | |
| 688 | if (bus_open_errors->len > 0) { |
| 689 | *i2c_open_errors_loc = bus_open_errors; |
| 690 | } |
| 691 | else { |
| 692 | g_ptr_array_free(bus_open_errors, false0); |
| 693 | *i2c_open_errors_loc = NULL((void*)0); |
| 694 | } |
| 695 | |
| 696 | if (debug) { |
| 697 | DBGMSG("Displays detected:")dbgtrc(DDCA_TRC_ALL, 0, __func__, 697, "ddc_displays.c", "Displays detected:" ); |
| 698 | ddc_dbgrpt_drefs("display_list:", display_list, 1); |
| 699 | dbgrpt_bus_open_errors(*i2c_open_errors_loc, 1); |
| 700 | } |
| 701 | DBGTRC_DONE(debug, TRACE_GROUP, "Returning %p, Detected %d valid displays",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 702 , "ddc_displays.c", "Done ""Returning %p, Detected %d valid displays" , display_list, dispno_max) |
| 702 | display_list, dispno_max)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 702 , "ddc_displays.c", "Done ""Returning %p, Detected %d valid displays" , display_list, dispno_max); |
| 703 | return display_list; |
| 704 | } |
| 705 | |
| 706 | |
| 707 | /** Initializes the master display list in global variable #all_displays and |
| 708 | * records open errors in global variable #display_open_errors. |
| 709 | * |
| 710 | * Does nothing if the list has already been initialized. |
| 711 | */ |
| 712 | void |
| 713 | ddc_ensure_displays_detected() { |
| 714 | bool_Bool debug = false0; |
| 715 | DBGTRC_STARTING(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 715 , "ddc_displays.c", "Starting """); |
| 716 | if (!all_displays) { |
| 717 | // i2c_detect_buses(); // called in ddc_detect_all_displays() |
| 718 | all_displays = ddc_detect_all_displays(&display_open_errors); |
| 719 | } |
| 720 | DBGTRC_DONE(debug, TRACE_GROUP,dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 722 , "ddc_displays.c", "Done ""all_displays=%p, all_displays has %d displays" , all_displays, all_displays->len) |
| 721 | "all_displays=%p, all_displays has %d displays",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 722 , "ddc_displays.c", "Done ""all_displays=%p, all_displays has %d displays" , all_displays, all_displays->len) |
| 722 | all_displays, all_displays->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 722 , "ddc_displays.c", "Done ""all_displays=%p, all_displays has %d displays" , all_displays, all_displays->len); |
| 723 | } |
| 724 | |
| 725 | |
| 726 | /** Discards all detected displays. |
| 727 | * |
| 728 | * - All open displays are closed |
| 729 | * - The list of open displays in #all_displays is discarded |
| 730 | * - The list of errors in #display_open_errors is discarded |
| 731 | * - The list of detected I2C buses is discarded |
| 732 | * - The USB monitor list is discarded |
| 733 | */ |
| 734 | void |
| 735 | ddc_discard_detected_displays() { |
| 736 | bool_Bool debug = false0; |
| 737 | DBGTRC_STARTING(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 737 , "ddc_displays.c", "Starting """); |
| 738 | // grab locks to prevent any opens? |
| 739 | ddc_close_all_displays(); |
| 740 | #ifdef USE_USB1 |
| 741 | discard_usb_monitor_list(); |
| 742 | #endif |
| 743 | if (all_displays) { |
| 744 | for (int ndx = 0; ndx < all_displays->len; ndx++) { |
| 745 | Display_Ref * dref = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 746 | dref->flags |= DREF_TRANSIENT0x0004; // hack to allow all Display References to be freed |
| 747 | #ifndef NDEBUG |
| 748 | DDCA_Status ddcrc = free_display_ref(dref); |
| 749 | TRACED_ASSERT(ddcrc==0)do { if (ddcrc==0) { ; } else { dbgtrc(DDCA_TRC_ALL, 0, __func__ , 749, "ddc_displays.c", "Assertion failed: \"%s\" in file %s at line %d" , "ddcrc==0", "ddc_displays.c", 749); syslog(3, "Assertion failed: \"%s\" in file %s at line %d" , "ddcrc==0", "ddc_displays.c", 749); exit(1); } } while (0); |
| 750 | #endif |
| 751 | } |
| 752 | g_ptr_array_free(all_displays, true1); |
| 753 | all_displays = NULL((void*)0); |
| 754 | if (display_open_errors) { |
| 755 | g_ptr_array_free(display_open_errors, true1); |
| 756 | display_open_errors = NULL((void*)0); |
| 757 | } |
| 758 | } |
| 759 | free_sys_drm_connectors(); |
| 760 | i2c_discard_buses(); |
| 761 | DBGTRC_DONE(debug, TRACE_GROUP, "")dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 761 , "ddc_displays.c", "Done """); |
| 762 | } |
| 763 | |
| 764 | |
| 765 | void |
| 766 | ddc_redetect_displays() { |
| 767 | bool_Bool debug = false0; |
| 768 | DBGTRC_STARTING(debug, TRACE_GROUP, "all_displays=%p", all_displays)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 768 , "ddc_displays.c", "Starting ""all_displays=%p", all_displays ); |
| 769 | ddc_discard_detected_displays(); |
| 770 | // i2c_detect_buses(); // called in ddc_detect_all_displays() |
| 771 | all_displays = ddc_detect_all_displays(&display_open_errors); |
| 772 | if (debug) { |
| 773 | ddc_dbgrpt_drefs("all_displays:", all_displays, 1); |
| 774 | // dbgrpt_valid_display_refs(1); |
| 775 | } |
| 776 | DBGTRC_DONE(debug, TRACE_GROUP, "all_displays=%p, all_displays->len = %d",dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 777 , "ddc_displays.c", "Done ""all_displays=%p, all_displays->len = %d" , all_displays, all_displays->len) |
| 777 | all_displays, all_displays->len)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 777 , "ddc_displays.c", "Done ""all_displays=%p, all_displays->len = %d" , all_displays, all_displays->len); |
| 778 | } |
| 779 | |
| 780 | |
| 781 | /** Checks that a #Display_Ref is in array **all_displays** |
| 782 | * of all valid #Display_Ref values. |
| 783 | * |
| 784 | * @param dref #Display_Ref |
| 785 | * @return true/false |
| 786 | */ |
| 787 | bool_Bool |
| 788 | ddc_is_valid_display_ref(Display_Ref * dref) { |
| 789 | bool_Bool debug = false0; |
| 790 | DBGTRC_STARTING(debug, TRACE_GROUP, "dref=%p -> %s", dref, dref_repr_t(dref))dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 790 , "ddc_displays.c", "Starting ""dref=%p -> %s", dref, dref_repr_t (dref)); |
| 791 | bool_Bool result = false0; |
| 792 | if (all_displays) { |
| 793 | for (int ndx = 0; ndx < all_displays->len; ndx++) { |
| 794 | Display_Ref* cur = g_ptr_array_index(all_displays, ndx)((all_displays)->pdata)[ndx]; |
| 795 | DBGMSF(debug, "Checking vs valid dref %p", cur)do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 795, "ddc_displays.c" , "Checking vs valid dref %p", cur); } while(0); |
| 796 | |
| 797 | if (cur == dref) { |
| 798 | // if (cur->dispno > 0) // why? |
| 799 | result = true1; |
| 800 | break; |
| 801 | } |
| 802 | } |
| 803 | } |
| 804 | DBGTRC_DONE(debug, TRACE_GROUP, "Returning %s. dref=%p, dispno=%d", sbool(result), dref, dref->dispno)dbgtrc( (debug) ? DDCA_TRC_ALL : (TRACE_GROUP), 0, __func__, 804 , "ddc_displays.c", "Done ""Returning %s. dref=%p, dispno=%d" , sbool(result), dref, dref->dispno); |
| 805 | return result; |
| 806 | } |
| 807 | |
| 808 | |
| 809 | /** Indicates whether displays have already been detected |
| 810 | * |
| 811 | * @return true/false |
| 812 | */ |
| 813 | bool_Bool |
| 814 | ddc_displays_already_detected() |
| 815 | { |
| 816 | return all_displays; |
| 817 | } |
| 818 | |
| 819 | |
| 820 | /** Controls whether USB displays are to be detected. |
| 821 | * |
| 822 | * Must be called before any function that triggers display detection. |
| 823 | * |
| 824 | * @param onoff true if USB displays are to be detected, false if not |
| 825 | * @retval DDCRC_OK normal |
| 826 | * @retval DDCRC_INVALID_OPERATION function called after displays have been detected |
| 827 | * @retval DDCRC_UNIMPLEMENTED ddcutil was not built with USB support |
| 828 | * |
| 829 | * @remark |
| 830 | * If this function is not called, the default (if built with USB support) is on |
| 831 | */ |
| 832 | DDCA_Status |
| 833 | ddc_enable_usb_display_detection(bool_Bool onoff) { |
| 834 | bool_Bool debug = false0; |
| 835 | DBGMSF(debug, "Starting. onoff=%s", sbool(onoff))do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 835, "ddc_displays.c" , "Starting. onoff=%s", sbool(onoff)); } while(0); |
| 836 | |
| 837 | DDCA_Status rc = DDCRC_UNIMPLEMENTED(-(3000 +15) ); |
| 838 | #ifdef USE_USB1 |
| 839 | if (ddc_displays_already_detected()) { |
| 840 | rc = DDCRC_INVALID_OPERATION(-(3000 +14) ); |
| 841 | } |
| 842 | else { |
| 843 | detect_usb_displays = onoff; |
| 844 | rc = DDCRC_OK0; |
| 845 | } |
| 846 | #endif |
| 847 | DBGMSF(debug, "Done. Returning %s", psc_name_code(rc))do { if (debug) dbgtrc(DDCA_TRC_ALL, 0, __func__, 847, "ddc_displays.c" , "Done. Returning %s", psc_name_code(rc)); } while(0); |
| 848 | return rc; |
| 849 | } |
| 850 | |
| 851 | |
| 852 | /** Indicates whether USB displays are to be detected |
| 853 | * |
| 854 | * @return true/false |
| 855 | */ |
| 856 | bool_Bool |
| 857 | ddc_is_usb_display_detection_enabled() { |
| 858 | return detect_usb_displays; |
| 859 | } |
| 860 | |
| 861 | |
| 862 | void |
| 863 | init_ddc_displays() { |
| 864 | RTTI_ADD_FUNC(ddc_async_scan)rtti_func_name_table_add(ddc_async_scan, "ddc_async_scan");; |
| 865 | RTTI_ADD_FUNC(ddc_detect_all_displays)rtti_func_name_table_add(ddc_detect_all_displays, "ddc_detect_all_displays" );; |
| 866 | RTTI_ADD_FUNC(ddc_initial_checks_by_dh)rtti_func_name_table_add(ddc_initial_checks_by_dh, "ddc_initial_checks_by_dh" );; |
| 867 | RTTI_ADD_FUNC(ddc_initial_checks_by_dref)rtti_func_name_table_add(ddc_initial_checks_by_dref, "ddc_initial_checks_by_dref" );; |
| 868 | RTTI_ADD_FUNC(ddc_is_valid_display_ref)rtti_func_name_table_add(ddc_is_valid_display_ref, "ddc_is_valid_display_ref" );; |
| 869 | RTTI_ADD_FUNC(ddc_non_async_scan)rtti_func_name_table_add(ddc_non_async_scan, "ddc_non_async_scan" );; |
| 870 | RTTI_ADD_FUNC(ddc_redetect_displays)rtti_func_name_table_add(ddc_redetect_displays, "ddc_redetect_displays" );; |
| 871 | RTTI_ADD_FUNC(filter_phantom_displays)rtti_func_name_table_add(filter_phantom_displays, "filter_phantom_displays" );; |
| 872 | RTTI_ADD_FUNC(is_phantom_display)rtti_func_name_table_add(is_phantom_display, "is_phantom_display" );; |
| 873 | RTTI_ADD_FUNC(threaded_initial_checks_by_dref)rtti_func_name_table_add(threaded_initial_checks_by_dref, "threaded_initial_checks_by_dref" );; |
| 874 | } |
| 875 |