Skip to content

Commit 71eaf78

Browse files
authored
Merge branch 'master' into cps-hid-timings
2 parents 9419879 + 38b7051 commit 71eaf78

File tree

5 files changed

+284
-45
lines changed

5 files changed

+284
-45
lines changed

NEWS.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ https://github.com/networkupstools/nut/milestone/11
7777
use of `struct timeval={-1,-1}` as a trigger to `select(..., NULL)`,
7878
as logged in one part of code and not handled in the other, for the
7979
indefinite wait [#1922, #2392, #2686, #2670]
80+
* The `disable_fix_report_desc` option introduced for `usbhid-ups` driver
81+
since NUT v2.8.1 was not applied for early dialog with the device while
82+
its report descriptors were being discovered. Now this flag, as well as
83+
`interruptsize` and `interruptonly`, are considered before we first try
84+
to open the USB device handle. [#1575, #1512]
85+
* In `cps_fix_report_desc()` we intended to fix-up input and output voltages
86+
in certain cases against high voltage transfer, we only fixed-up one of
87+
them. [#1245]
8088
8189
- development iterations of NUT should now identify with not only the semantic
8290
version of a preceding release, but with git-derived information about the
@@ -200,6 +208,9 @@ https://github.com/networkupstools/nut/milestone/11
200208
Also adjust default `offdelay` and `ondelay` to reasonable values,
201209
and warn the users with CPS devices if their configured values are
202210
not multiples of 60. [#432, #1394]
211+
* in `cps-hid` subdriver, `cps_fix_report_desc()` method should now handle
212+
mismatched `LogMax` ranges for input and output voltages, whose USB Report
213+
Descriptors are wrongly encoded by some firmware versions. [#1512]
203214
* USB parameters (per `usb_communication_subdriver_t`) are now set back to
204215
their default values during enumeration after probing each subdriver.
205216
Having an unrelated device connected with a VID:PID matching the
@@ -234,6 +245,8 @@ https://github.com/networkupstools/nut/milestone/11
234245
* ...should now not log "insufficient permissions on everything" alone when
235246
some devices were accessible but just did not match -- clarify that case
236247
in the next line, when applicable. [PR #2699]
248+
* ...should now track the fact of `assumed_LogMax` (typically when firmware
249+
encoding logic is wrong, and `-1` is resolved by parser). [#1512, #1040]
237250
238251
- Introduced a new driver concept for interaction with OS-reported hardware
239252
monitoring readings. Currently instantiated as `hwmon_ina219` specifically

drivers/cps-hid.c

Lines changed: 152 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -317,20 +317,26 @@ static int cps_claim(HIDDevice_t *hd) {
317317
}
318318
}
319319

320-
/* CPS Models like CP900EPFCLCD/CP1500PFCLCDa return a syntactically legal but incorrect
321-
* Report Descriptor whereby the Input High Transfer Max/Min values
322-
* are used for the Output Voltage Usage Item limits.
320+
/* CPS Models like CP900EPFCLCD/CP1500PFCLCDa return a syntactically
321+
* legal but incorrect Report Descriptor whereby the Input High Transfer
322+
* Max/Min values are used for the Output Voltage Usage Item limits.
323323
* Additionally the Input Voltage LogMax is set incorrectly for EU models.
324324
* This corrects them by finding and applying fixed
325325
* voltage limits as being more appropriate.
326326
*/
327327

328328
static int cps_fix_report_desc(HIDDevice_t *pDev, HIDDesc_t *pDesc_arg) {
329329
HIDData_t *pData;
330+
int retval = 0;
330331

331332
int vendorID = pDev->VendorID;
332333
int productID = pDev->ProductID;
333334
if (vendorID != CPS_VENDORID || (productID != 0x0501 && productID != 0x0601)) {
335+
upsdebugx(3,
336+
"NOT Attempting Report Descriptor fix for UPS: "
337+
"Vendor: %04x, Product: %04x "
338+
"(vendor/product not matched)",
339+
vendorID, productID);
334340
return 0;
335341
}
336342

@@ -343,43 +349,168 @@ static int cps_fix_report_desc(HIDDevice_t *pDev, HIDDesc_t *pDesc_arg) {
343349
return 0;
344350
}
345351

346-
upsdebugx(3, "Attempting Report Descriptor fix for UPS: Vendor: %04x, Product: %04x", vendorID, productID);
352+
upsdebugx(3, "Attempting Report Descriptor fix for UPS: "
353+
"Vendor: %04x, Product: %04x", vendorID, productID);
347354

348-
/* Apply the fix cautiously by looking for input voltage, high voltage transfer and output voltage report usages.
349-
* If the output voltage log min/max equals high voltage transfer log min/max then the bug is present.
350-
* To fix it Set both the input and output voltages to pre-defined settings.
355+
/* Apply the fix cautiously by looking for input voltage,
356+
* high voltage transfer and output voltage report usages.
357+
* If the output voltage log min/max equals high voltage
358+
* transfer log min/max, then the bug is present.
359+
*
360+
* To fix it set both the input and output voltages to our
361+
* pre-defined settings CPS_VOLTAGE_LOGMIN/CPS_VOLTAGE_LOGMAX.
351362
*/
352363

353-
if ((pData=FindObject_with_ID_Node(pDesc_arg, 16, USAGE_POW_HIGH_VOLTAGE_TRANSFER))) {
364+
if ((pData=FindObject_with_ID_Node(pDesc_arg, 16 /* 0x10 */, USAGE_POW_HIGH_VOLTAGE_TRANSFER))) {
354365
long hvt_logmin = pData->LogMin;
355366
long hvt_logmax = pData->LogMax;
356-
upsdebugx(4, "Report Descriptor: hvt input LogMin: %ld LogMax: %ld", hvt_logmin, hvt_logmax);
367+
upsdebugx(4, "Original Report Descriptor: hvt input "
368+
"LogMin: %ld LogMax: %ld", hvt_logmin, hvt_logmax);
357369

358-
if ((pData=FindObject_with_ID_Node(pDesc_arg, 18, USAGE_POW_VOLTAGE))) {
370+
if ((pData=FindObject_with_ID_Node(pDesc_arg, 18 /* 0x12 */, USAGE_POW_VOLTAGE))) {
359371
long output_logmin = pData->LogMin;
360372
long output_logmax = pData->LogMax;
361-
upsdebugx(4, "Report Descriptor: output LogMin: %ld LogMax: %ld",
362-
output_logmin, output_logmax);
373+
upsdebugx(4, "Original Report Descriptor: output "
374+
"LogMin: %ld LogMax: %ld",
375+
output_logmin, output_logmax);
363376

364377
if (hvt_logmin == output_logmin && hvt_logmax == output_logmax) {
365378
pData->LogMin = CPS_VOLTAGE_LOGMIN;
366379
pData->LogMax = CPS_VOLTAGE_LOGMAX;
367-
upsdebugx(3, "Fixing Report Descriptor. Set Output Voltage LogMin = %d, LogMax = %d",
368-
CPS_VOLTAGE_LOGMIN , CPS_VOLTAGE_LOGMAX);
369-
if ((pData=FindObject_with_ID_Node(pDesc_arg, 15, USAGE_POW_VOLTAGE))) {
380+
upsdebugx(3, "Fixing Report Descriptor: "
381+
"set Output Voltage LogMin = %d, LogMax = %d",
382+
CPS_VOLTAGE_LOGMIN, CPS_VOLTAGE_LOGMAX);
383+
384+
if ((pData=FindObject_with_ID_Node(pDesc_arg, 15 /* 0x0F */, USAGE_POW_VOLTAGE))) {
370385
long input_logmin = pData->LogMin;
371386
long input_logmax = pData->LogMax;
372-
upsdebugx(4, "Report Descriptor: input LogMin: %ld LogMax: %ld",
373-
input_logmin, input_logmax);
374-
upsdebugx(3, "Fixing Report Descriptor. Set Input Voltage LogMin = %d, LogMax = %d",
375-
CPS_VOLTAGE_LOGMIN , CPS_VOLTAGE_LOGMAX);
387+
upsdebugx(4, "Original Report Descriptor: input "
388+
"LogMin: %ld LogMax: %ld",
389+
input_logmin, input_logmax);
390+
391+
pData->LogMin = CPS_VOLTAGE_LOGMIN;
392+
pData->LogMax = CPS_VOLTAGE_LOGMAX;
393+
upsdebugx(3, "Fixing Report Descriptor: "
394+
"set Input Voltage LogMin = %d, LogMax = %d",
395+
CPS_VOLTAGE_LOGMIN, CPS_VOLTAGE_LOGMAX);
376396
}
377397

378-
return 1;
398+
retval = 1;
379399
}
380400
}
381401
}
382-
return 0;
402+
403+
if ((pData=FindObject_with_ID_Node(pDesc_arg, 18 /* 0x12 */, USAGE_POW_VOLTAGE))) {
404+
HIDData_t *output_pData = pData;
405+
long output_logmin = output_pData->LogMin;
406+
long output_logmax = output_pData->LogMax;
407+
bool output_logmax_assumed = output_pData->assumed_LogMax;
408+
409+
if ((pData=FindObject_with_ID_Node(pDesc_arg, 15 /* 0x0F */, USAGE_POW_VOLTAGE))) {
410+
HIDData_t *input_pData = pData;
411+
long input_logmin = input_pData->LogMin;
412+
long input_logmax = input_pData->LogMax;
413+
bool input_logmax_assumed = input_pData->assumed_LogMax;
414+
415+
if ( (output_logmax_assumed || input_logmax_assumed)
416+
/* && output_logmax != input_logmax */
417+
) {
418+
/* We often get 0x0F ReportdId LogMax=65535
419+
* and 0x12 ReportdId LogMax=255 because of
420+
* wrong encoding. See e.g. analysis at
421+
* https://github.com/networkupstools/nut/issues/1512#issuecomment-1224652911
422+
*/
423+
upsdebugx(4, "Original Report Descriptor: output 0x12 "
424+
"LogMin: %ld LogMax: %ld (assumed: %s) Size: %" PRIu8,
425+
output_logmin, output_logmax,
426+
output_logmax_assumed ? "yes" : "no",
427+
output_pData->Size);
428+
upsdebugx(4, "Original Report Descriptor: input 0x0f "
429+
"LogMin: %ld LogMax: %ld (assumed: %s) Size: %" PRIu8,
430+
input_logmin, input_logmax,
431+
input_logmax_assumed ? "yes" : "no",
432+
input_pData->Size);
433+
434+
/* First pass: try our hard-coded limits */
435+
if (output_logmax_assumed && output_logmax < CPS_VOLTAGE_LOGMAX) {
436+
output_logmax = CPS_VOLTAGE_LOGMAX;
437+
}
438+
439+
if (input_logmax_assumed && input_logmax < CPS_VOLTAGE_LOGMAX) {
440+
input_logmax = CPS_VOLTAGE_LOGMAX;
441+
}
442+
443+
/* Second pass: align the two */
444+
if (output_logmax_assumed && output_logmax < input_logmax) {
445+
output_logmax = input_logmax;
446+
} else if (input_logmax_assumed && input_logmax < output_logmax) {
447+
input_logmax = output_logmax;
448+
}
449+
450+
/* Second pass: cut off according to bit-size
451+
* of each value */
452+
if (input_logmax_assumed
453+
&& input_pData->Size > 1
454+
&& input_pData->Size <= sizeof(long)*8
455+
) {
456+
/* Note: usually values are signed, but
457+
* here we are about compensating for
458+
* poorly encoded maximums, so limit by
459+
* 2^(size)-1, e.g. for "size==16" the
460+
* limit should be "2^16 - 1 = 65535";
461+
* note that in HIDParse() we likely
462+
* set 65535 here in that case. See
463+
* also comments there (hidparser.c)
464+
* discussing signed/unsigned nuances.
465+
*/
466+
/* long sizeMax = (1L << (input_pData->Size - 1)) - 1; */
467+
long sizeMax = (1L << (input_pData->Size)) - 1;
468+
if (input_logmax > sizeMax) {
469+
input_logmax = sizeMax;
470+
}
471+
}
472+
473+
if (output_logmax_assumed
474+
&& output_pData->Size > 1
475+
&& output_pData->Size <= sizeof(long)*8
476+
) {
477+
/* See comment above */
478+
/* long sizeMax = (1L << (output_pData->Size - 1)) - 1; */
479+
long sizeMax = (1L << (output_pData->Size)) - 1;
480+
if (output_logmax > sizeMax) {
481+
output_logmax = sizeMax;
482+
}
483+
}
484+
485+
if (input_logmax != input_pData->LogMax) {
486+
upsdebugx(3, "Fixing Report Descriptor: "
487+
"set Input Voltage LogMax = %ld",
488+
input_logmax);
489+
input_pData->LogMax = input_logmax;
490+
retval = 1;
491+
}
492+
493+
if (output_logmax != output_pData->LogMax) {
494+
upsdebugx(3, "Fixing Report Descriptor: "
495+
"set Output Voltage LogMax = %ld",
496+
output_logmax);
497+
output_pData->LogMax = output_logmax;
498+
retval = 1;
499+
}
500+
}
501+
}
502+
}
503+
504+
if (!retval) {
505+
/* We did not `return 1` above, so... */
506+
upsdebugx(3,
507+
"SKIPPED Report Descriptor fix for UPS: "
508+
"Vendor: %04x, Product: %04x "
509+
"(problematic conditions not matched)",
510+
vendorID, productID);
511+
}
512+
513+
return retval;
383514
}
384515

385516
subdriver_t cps_subdriver = {

drivers/hidparser.c

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,99 @@ static int HIDParse(HIDParser_t *pParser, HIDData_t *pData)
316316

317317
case ITEM_LOG_MAX :
318318
pParser->Data.LogMax = FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
319-
/* HACK: If treating the value as signed (FormatValue(...)) results in a LogMax that is
320-
* less than the LogMin value then it is likely that the LogMax value has been
321-
* incorrectly encoded by the UPS firmware (field too short and overflowed into sign
322-
* bit). In that case, reinterpret it as an unsigned number and log the problem.
323-
* This hack is not correct in the sense that it only looks at the LogMin value for
324-
* this item, whereas the HID spec says that Logical values persist in global state.
319+
/* HACK: If treating the value as signed (FormatValue(...))
320+
* results in a LogMax that is less than the LogMin
321+
* value then it is likely that the LogMax value has
322+
* been incorrectly encoded by the UPS firmware
323+
* (field was too short and overflowed into sign bit).
324+
* In that case, reinterpret it as an unsigned number
325+
* and log the problem. See also *_fix_report_desc()
326+
* methods that follow up in some *-hid.c subdrivers.
327+
* This hack is not correct in the sense that it only
328+
* looks at the LogMin value for this item, whereas
329+
* the HID spec says that Logical values persist in
330+
* global state.
331+
* Note the values MAY be signed or unsigned, according
332+
* to rules and circumstances explored below.
333+
*
334+
* RATIONALE: per discussions such as:
335+
* * https://github.com/networkupstools/nut/issues/1512#issuecomment-1238310056
336+
* The encoding of small integers in the logical/physical
337+
* min/max fields (in fact, I think in anywhere they
338+
* encode integers) is independent of the size of
339+
* the (feature) field they end up referring to.
340+
* One should use the smallest size encoding
341+
* (0, 1, 2, or 4 bytes are the options) that can
342+
* represent, as a signed quantity, the value you
343+
* need to encode. See HID spec 1.11, sec 6.2.2.2
344+
* "Short Items". Given a 16 bit report field, with
345+
* logical values 0..65535, it should use a 0 byte
346+
* encoding for the logical minimum (0x14, rather
347+
* than 0x15 0x00) and a 4-byte encoding for the
348+
* logical maximum (0x27 0xFF 0xFF 0x00 0x00).
349+
* Their encoding choice does suggest you cannot
350+
* have an unsigned 32-bit report item with logical
351+
* maximum >2147483647 (unless you assume, as I did,
352+
* that "if max < min" then it's just a bad encoding
353+
* of a positive number that ran into the sign bit).
354+
* * https://github.com/networkupstools/nut/pull/2718#issuecomment-2547021458
355+
* This is what the spec says (page labelled 19 of
356+
* hid1_11.pdf, physical page 29 of 97) --
357+
* 5.8 Format of Multibyte Numeric Values
358+
* Multibyte numeric values in reports are
359+
* represented in little-endian format, with the
360+
* least significant byte at the lowest address.
361+
* The Logical Minimum and Logical Maximum values
362+
* identify the range of values that will be found
363+
* in a report.
364+
* If Logical Minimum and Logical Maximum are
365+
* both positive values then a sign bit is
366+
* unnecessary in the report field and the
367+
* contents of a field can be assumed to
368+
* be an unsigned value.
369+
* Otherwise, all integer values are signed
370+
* values represented in 2's complement format.
371+
* Floating point values are not allowed.
372+
* * https://github.com/networkupstools/nut/pull/2718#issuecomment-2547065141
373+
* The number of bytes in the encoding of the
374+
* LogMin and LogMax fields is only loosely tied
375+
* to the "Size" of the field that they are
376+
* describing -- but the implementers on the
377+
* UPS side don't seem to quite get that.
378+
* It's all starting to come back to me...
379+
* If you're trying to describe a report field
380+
* that is 16-bits and has (unsigned) values
381+
* from 0..65535 range, then you SHOULD have
382+
* a LogMin field containing value 0, and
383+
* a LogMax field that contains value 65535.
384+
* Since all numeric fields are interpreted as
385+
* signed "two's-complement" values (except for
386+
* that note above about the *report values*,
387+
* NOT the values in the report description), to
388+
* encode such a LogMax field you would have to
389+
* express the *LogMax field* in a 4-byte encoding
390+
* in the *report description*.
391+
* That's independent of the ultimate 2-byte
392+
* *report value* that these LogMin and LogMax
393+
* are describing.
394+
* We suppose that some coder at the UPS company
395+
* took a shortcut, and set not only "LogMin = 0",
396+
* but also effectively "LogMax = -1" (because they
397+
* used a 2-byte encoding with all bits set, not a
398+
* 4-byte encoding), and then NUT is left to decide
399+
* what they actually intended.
400+
* My interpretation of that is that they're trying
401+
* to say e.g. 0..65535, because if they had meant
402+
* 0..32767 they would have just written that (as
403+
* 0..7FFF which fits in the signed 2-byte repr.),
404+
* but unless they're actually trying to represent
405+
* e.g. voltages over 327 V, deciding that the
406+
* limit is a signed 32767 should also be fine.
407+
*
408+
* ...and maybe some in other tickets
409+
*
410+
* TL;DR: there is likely a mis-understanding
411+
* of the USB spec by firmware developers.
325412
*/
326413
if (pParser->Data.LogMax < pParser->Data.LogMin) {
327414
upslogx(LOG_WARNING,
@@ -332,6 +419,7 @@ static int HIDParse(HIDParser_t *pParser, HIDData_t *pData)
332419
pParser->Data.LogMax,
333420
pParser->Value,
334421
pParser->Data.ReportID);
422+
pParser->Data.assumed_LogMax = true;
335423
pParser->Data.LogMax = (long) pParser->Value;
336424
}
337425
break;

drivers/hidtypes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ extern "C" {
3535
#include <sys/types.h>
3636

3737
#include "nut_stdint.h"
38+
#include "nut_bool.h"
3839

3940
/*
4041
* Constants
@@ -308,6 +309,8 @@ typedef struct {
308309

309310
long LogMin; /* Logical Min */
310311
long LogMax; /* Logical Max */
312+
bool assumed_LogMax; /* Logical Max assumed (e.g. "-1" initially)? */
313+
311314
long PhyMin; /* Physical Min */
312315
long PhyMax; /* Physical Max */
313316
int8_t have_PhyMin; /* Physical Min defined? */

0 commit comments

Comments
 (0)