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 |