diff mbox

[1/1] HID: Fix unit exponent parsing again

Message ID 1381666192-25309-1-git-send-email-spbnick@gmail.com (mailing list archive)
State New, archived
Delegated to: Jiri Kosina
Headers show

Commit Message

Nikolai Kondrashov Oct. 13, 2013, 12:09 p.m. UTC
Revert some changes done in 774638386826621c984ab6994439f474709cac5e.

Revert all changes done in hidinput_calc_abs_res as it mistakingly used
"Unit" item exponent nibbles to affect resolution value. This wasn't
breaking resolution calculation of relevant axes of any existing
devices, though, as they have only one dimension to their units and thus
1 in the corresponding nible.

Revert to reading "Unit Exponent" item value as a signed integer in
hid_parser_global to fix reading specification-complying values. This
fixes resolution calculation of devices complying to the HID standard,
including Huion, KYE, Waltop and UC-Logic graphics tablets which have
their report descriptors fixed by the drivers.

Explanations follow.

There are two "unit exponents" in HID specification and it is important
not to mix them. One is the global "Unit Exponent" item and another is
nibble values in the global "Unit" item. See 6.2.2.7 Global Items.

The "Unit Exponent" value is just a signed integer and is used to scale
the integer resolution unit values, so fractions can be expressed.

The nibbles of "Unit" value are used to select the unit system (nibble
0), and presence of a particular basic unit type in the unit formula and
its *exponent* (or power, nibbles 1-6). And yes, the latter is in two
complement and zero means absence of the unit type.

Taking the representation example of (integer) joules from the
specification:

[mass(grams)][length(centimeters)^2][time(seconds)^-2] * 10^-7

the "Unit Exponent" would be -7 (or 0xF9, if stored as a byte) and the
"Unit" value would be 0xE121, signifying:

Nibble  Part        Value   Meaning
-----   ----        -----   -------
0       System      1       SI Linear
1       Length      2       Centimeters^2
2       Mass        1       Grams
3       Time        -2      Seconds^-2

To give the resolution in e.g. hundredth of joules the "Unit Exponent"
item value should have been -9.

See also the examples of "Unit" values for some common units in the same
chapter.

However, there is a common misunderstanding about the "Unit Exponent"
value encoding, where it is assumed to be stored the same as nibbles in
"Unit" item. This is most likely due to the specification being a bit
vague and overloading the term "unit exponent". This also was and still
is proliferated by the official "HID Descriptor Tool", which makes this
mistake and stores "Unit Exponent" as such. This format is also
mentioned in books such as "USB Complete" and in Microsoft's hardware
design guides.

As a result many devices currently on the market use this encoding and
so the driver should support them.
---
 drivers/hid/hid-core.c  | 11 ++++++-----
 drivers/hid/hid-input.c | 13 ++++---------
 2 files changed, 10 insertions(+), 14 deletions(-)

Comments

Benjamin Tissoires Oct. 17, 2013, 6:56 p.m. UTC | #1
Hi Nikolai,

On Sun, Oct 13, 2013 at 8:09 AM, Nikolai Kondrashov <spbnick@gmail.com> wrote:
> Revert some changes done in 774638386826621c984ab6994439f474709cac5e.
>
> Revert all changes done in hidinput_calc_abs_res as it mistakingly used
> "Unit" item exponent nibbles to affect resolution value. This wasn't
> breaking resolution calculation of relevant axes of any existing
> devices, though, as they have only one dimension to their units and thus
> 1 in the corresponding nible.
>
> Revert to reading "Unit Exponent" item value as a signed integer in
> hid_parser_global to fix reading specification-complying values. This
> fixes resolution calculation of devices complying to the HID standard,
> including Huion, KYE, Waltop and UC-Logic graphics tablets which have
> their report descriptors fixed by the drivers.
>
> Explanations follow.
>
> There are two "unit exponents" in HID specification and it is important
> not to mix them. One is the global "Unit Exponent" item and another is
> nibble values in the global "Unit" item. See 6.2.2.7 Global Items.
>
> The "Unit Exponent" value is just a signed integer and is used to scale
> the integer resolution unit values, so fractions can be expressed.
>
> The nibbles of "Unit" value are used to select the unit system (nibble
> 0), and presence of a particular basic unit type in the unit formula and
> its *exponent* (or power, nibbles 1-6). And yes, the latter is in two
> complement and zero means absence of the unit type.
>
> Taking the representation example of (integer) joules from the
> specification:
>
> [mass(grams)][length(centimeters)^2][time(seconds)^-2] * 10^-7
>
> the "Unit Exponent" would be -7 (or 0xF9, if stored as a byte) and the
> "Unit" value would be 0xE121, signifying:
>
> Nibble  Part        Value   Meaning
> -----   ----        -----   -------
> 0       System      1       SI Linear
> 1       Length      2       Centimeters^2
> 2       Mass        1       Grams
> 3       Time        -2      Seconds^-2
>
> To give the resolution in e.g. hundredth of joules the "Unit Exponent"
> item value should have been -9.
>
> See also the examples of "Unit" values for some common units in the same
> chapter.
>
> However, there is a common misunderstanding about the "Unit Exponent"
> value encoding, where it is assumed to be stored the same as nibbles in
> "Unit" item. This is most likely due to the specification being a bit
> vague and overloading the term "unit exponent". This also was and still
> is proliferated by the official "HID Descriptor Tool", which makes this
> mistake and stores "Unit Exponent" as such. This format is also
> mentioned in books such as "USB Complete" and in Microsoft's hardware
> design guides.
>
> As a result many devices currently on the market use this encoding and
> so the driver should support them.
> ---
>  drivers/hid/hid-core.c  | 11 ++++++-----
>  drivers/hid/hid-input.c | 13 ++++---------
>  2 files changed, 10 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index b8470b1..013cad0 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -319,7 +319,7 @@ static s32 item_sdata(struct hid_item *item)
>
>  static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
>  {
> -       __u32 raw_value;
> +       __s32 raw_value;
>         switch (item->tag) {
>         case HID_GLOBAL_ITEM_TAG_PUSH:
>
> @@ -370,10 +370,11 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
>                 return 0;
>
>         case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:
> -               /* Units exponent negative numbers are given through a
> -                * two's complement.
> -                * See "6.2.2.7 Global Items" for more information. */
> -               raw_value = item_udata(item);
> +               /* Many devices provide unit exponent as a two's complement
> +                * nibble due to the common misunderstanding of HID
> +                * specification 1.11, 6.2.2.7 Global Items. Attempt to handle
> +                * both this and the standard encoding. */
> +               raw_value = item_sdata(item);
>                 if (!(raw_value & 0xfffffff0))
>                         parser->global.unit_exponent = hid_snto32(raw_value, 4);
>                 else
> diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
> index 8741d95..d97f232 100644
> --- a/drivers/hid/hid-input.c
> +++ b/drivers/hid/hid-input.c
> @@ -192,6 +192,7 @@ static int hidinput_setkeycode(struct input_dev *dev,
>         return -EINVAL;
>  }
>
> +
>  /**
>   * hidinput_calc_abs_res - calculate an absolute axis resolution
>   * @field: the HID report field to calculate resolution for
> @@ -234,23 +235,17 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
>         case ABS_MT_TOOL_Y:
>         case ABS_MT_TOUCH_MAJOR:
>         case ABS_MT_TOUCH_MINOR:
> -               if (field->unit & 0xffffff00)           /* Not a length */
> -                       return 0;
> -               unit_exponent += hid_snto32(field->unit >> 4, 4) - 1;
> -               switch (field->unit & 0xf) {
> -               case 0x1:                               /* If centimeters */
> +               if (field->unit == 0x11) {              /* If centimeters */
>                         /* Convert to millimeters */
>                         unit_exponent += 1;
> -                       break;
> -               case 0x3:                               /* If inches */
> +               } else if (field->unit == 0x13) {       /* If inches */
>                         /* Convert to millimeters */
>                         prev = physical_extents;
>                         physical_extents *= 254;
>                         if (physical_extents < prev)
>                                 return 0;
>                         unit_exponent -= 1;
> -                       break;
> -               default:
> +               } else {

I finally understood what you mean here, and why I introduced such a change.
I have weird report descriptors available in the field which are
making a funny use of units:

3M multitouch devices declare:
0x05, 0x01,                    //     Usage Page (Generic Desktop)    148
0x26, 0xff, 0x7f,              //     Logical Maximum (32767)         150
0x75, 0x10,                    //     Report Size (16)                153
0x55, 0x0e,                    //     Unit Exponent (-2)              155
0x65, 0x33,                    //     Unit (Inch^3,EngLinear)         157
0x09, 0x30,                    //     Usage (X)                       159
0x35, 0x00,                    //     Physical Minimum (0)            161
0x46, 0x3a, 0x06,              //     Physical Maximum (1594)         163
0x81, 0x02,                    //     Input (Data,Var,Abs)            166

which became fine with the non reverted code -> the unit exponent
became 1 ( = -2 + 3). However this is wrong according to the spec as
you mentioned because X and Y are not volumes (Inch^3), but Length
(Inch^1).

Even stranger, I have some eGalax devices which declares:
0x05, 0x01,                    //     Usage Page (Generic Desktop)    58
0x09, 0x30,                    //     Usage (X)                       60
0x75, 0x10,                    //     Report Size (16)                62
0x95, 0x01,                    //     Report Count (1)                64
0x55, 0x0d,                    //     Unit Exponent (-3)              66
0x65, 0x33,                    //     Unit (Inch^3,EngLinear)         68
0x35, 0x00,                    //     Physical Minimum (0)            70
0x46, 0xc1, 0x20,              //     Physical Maximum (8385)         72
0x26, 0xff, 0x7f,              //     Logical Maximum (32767)         75
0x81, 0x02,                    //     Input (Data,Var,Abs)            78

X is a volume, but if you compute in the way it is currently
implemented, it leads to a non unit value :)

Fortunately, latest Win 8 specification for multitouch screens,
requires the unit and unit exponent fields to be correctly
implemented, which I can see on the reports I have.

So I was trying to fix a wrong and fancy interpretation of the spec,
based on some devices which did this bad interpretation.

To sum up:
acked-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>

Cheers,
Benjamin


>                         return 0;
>                 }
>                 break;
> --
> 1.8.4.rc3
>
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jiri Kosina Oct. 18, 2013, 12:58 p.m. UTC | #2
On Thu, 17 Oct 2013, Benjamin Tissoires wrote:

> > Revert some changes done in 774638386826621c984ab6994439f474709cac5e.
> >
> > Revert all changes done in hidinput_calc_abs_res as it mistakingly used
> > "Unit" item exponent nibbles to affect resolution value. This wasn't
> > breaking resolution calculation of relevant axes of any existing
> > devices, though, as they have only one dimension to their units and thus
> > 1 in the corresponding nible.
> >
> > Revert to reading "Unit Exponent" item value as a signed integer in
> > hid_parser_global to fix reading specification-complying values. This
> > fixes resolution calculation of devices complying to the HID standard,
> > including Huion, KYE, Waltop and UC-Logic graphics tablets which have
> > their report descriptors fixed by the drivers.
> >
> > Explanations follow.
[ ... snip ... ]
> I finally understood what you mean here, and why I introduced such a change.
> I have weird report descriptors available in the field which are
> making a funny use of units:

Ok, it took me quite some time to figure this all out. Good work Nikolai, 
thanks.

I am about to apply it, just a minor thing -- you seem to have forgotten 
your Signed-off-by: line in the patch. Could you please send it to me, so 
that I can ammend it to the patch and apply it?

Thanks.
Nikolai Kondrashov Oct. 18, 2013, 1:08 p.m. UTC | #3
Hi Jiri,

On 10/18/2013 03:58 PM, Jiri Kosina wrote:
> I am about to apply it, just a minor thing -- you seem to have forgotten
> your Signed-off-by: line in the patch. Could you please send it to me, so
> that I can ammend it to the patch and apply it?

Sure, sorry, haven't sent any kernel patches in a while.

Signed-off-by: Nikolai Kondrashov <spbnick@gmail.com>

Sincerely,
Nick
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jiri Kosina Oct. 18, 2013, 1:14 p.m. UTC | #4
On Fri, 18 Oct 2013, Nikolai Kondrashov wrote:

> > I am about to apply it, just a minor thing -- you seem to have forgotten
> > your Signed-off-by: line in the patch. Could you please send it to me, so
> > that I can ammend it to the patch and apply it?
> 
> Sure, sorry, haven't sent any kernel patches in a while.
> 
> Signed-off-by: Nikolai Kondrashov <spbnick@gmail.com>

Thanks, applied.
diff mbox

Patch

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index b8470b1..013cad0 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -319,7 +319,7 @@  static s32 item_sdata(struct hid_item *item)
 
 static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
 {
-	__u32 raw_value;
+	__s32 raw_value;
 	switch (item->tag) {
 	case HID_GLOBAL_ITEM_TAG_PUSH:
 
@@ -370,10 +370,11 @@  static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
 		return 0;
 
 	case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:
-		/* Units exponent negative numbers are given through a
-		 * two's complement.
-		 * See "6.2.2.7 Global Items" for more information. */
-		raw_value = item_udata(item);
+		/* Many devices provide unit exponent as a two's complement
+		 * nibble due to the common misunderstanding of HID
+		 * specification 1.11, 6.2.2.7 Global Items. Attempt to handle
+		 * both this and the standard encoding. */
+		raw_value = item_sdata(item);
 		if (!(raw_value & 0xfffffff0))
 			parser->global.unit_exponent = hid_snto32(raw_value, 4);
 		else
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 8741d95..d97f232 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -192,6 +192,7 @@  static int hidinput_setkeycode(struct input_dev *dev,
 	return -EINVAL;
 }
 
+
 /**
  * hidinput_calc_abs_res - calculate an absolute axis resolution
  * @field: the HID report field to calculate resolution for
@@ -234,23 +235,17 @@  __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
 	case ABS_MT_TOOL_Y:
 	case ABS_MT_TOUCH_MAJOR:
 	case ABS_MT_TOUCH_MINOR:
-		if (field->unit & 0xffffff00)		/* Not a length */
-			return 0;
-		unit_exponent += hid_snto32(field->unit >> 4, 4) - 1;
-		switch (field->unit & 0xf) {
-		case 0x1:				/* If centimeters */
+		if (field->unit == 0x11) {		/* If centimeters */
 			/* Convert to millimeters */
 			unit_exponent += 1;
-			break;
-		case 0x3:				/* If inches */
+		} else if (field->unit == 0x13) {	/* If inches */
 			/* Convert to millimeters */
 			prev = physical_extents;
 			physical_extents *= 254;
 			if (physical_extents < prev)
 				return 0;
 			unit_exponent -= 1;
-			break;
-		default:
+		} else {
 			return 0;
 		}
 		break;