Re: [PATCH 2/4] Input: introduce ABS_MAX2/CNT2 and friends
flat view
From: Peter Hutterer <hidden>
Date: 2013-12-18 23:37:40
Also in:
lkml
On Tue, Dec 17, 2013 at 04:48:52PM +0100, David Herrmann wrote:
quoted hunk ↗ jump to hunk
As we painfully noticed during the 3.12 merge-window our EVIOCGABS/EVIOCSABS API is limited to ABS_MAX<=0x3f. We tried several hacks to work around it but if we ever decide to increase ABS_MAX, the EVIOCSABS ioctl ABI might overflow into the next byte causing horrible misinterpretations in the kernel that we cannot catch. Therefore, we decided to go with ABS_MAX2/CNT2 and introduce two new ioctls to get/set abs-params. They no longer encode the ABS code in the ioctl number and thus allow up to 4 billion ABS codes. The new API also allows to query multiple ABS values with one call. To allow EVIOCSABS2(code = 0, cnt = ABS_CNT2) we need to silently ignore writes to ABS_MT_SLOT. Furthermore, for better compatibility with newer user-space, we ignore writes to unknown codes. Hence, if we ever increase ABS_MAX2, new user-space will work with code=0,cnt=ABS_CNT2 just fine even on old kernels. Note that we also need to increase EV_VERSION so user-space can reliably know whether ABS2 is supported. Unfortunately, we return EINVAL instead of ENOSYS for unknown evdev ioctls so it's nearly impossible to catch reliably without EVIOCGVERSION. Signed-off-by: David Herrmann <redacted> --- drivers/hid/hid-debug.c | 2 +- drivers/hid/hid-input.c | 2 +- drivers/input/evdev.c | 95 +++++++++++++++++++++++++++++++- drivers/input/input.c | 14 ++--- drivers/input/keyboard/goldfish_events.c | 6 +- drivers/input/keyboard/hil_kbd.c | 2 +- drivers/input/misc/uinput.c | 6 +- include/linux/hid.h | 2 +- include/linux/input.h | 6 +- include/uapi/linux/input.h | 42 +++++++++++++- include/uapi/linux/uinput.h | 2 +- 11 files changed, 155 insertions(+), 24 deletions(-)diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 8453214..d32fa30 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c@@ -862,7 +862,7 @@ static const char *relatives[REL_MAX + 1] = { [REL_WHEEL] = "Wheel", [REL_MISC] = "Misc", }; -static const char *absolutes[ABS_CNT] = { +static const char *absolutes[ABS_CNT2] = { [ABS_X] = "X", [ABS_Y] = "Y", [ABS_Z] = "Z", [ABS_RX] = "Rx", [ABS_RY] = "Ry", [ABS_RZ] = "Rz",diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index d97f232..a02721c 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c@@ -1300,7 +1300,7 @@ static bool hidinput_has_been_populated(struct hid_input *hidinput) for (i = 0; i < BITS_TO_LONGS(REL_CNT); i++) r |= hidinput->input->relbit[i]; - for (i = 0; i < BITS_TO_LONGS(ABS_CNT); i++) + for (i = 0; i < BITS_TO_LONGS(ABS_CNT2); i++) r |= hidinput->input->absbit[i]; for (i = 0; i < BITS_TO_LONGS(MSC_CNT); i++)diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index a06e125..32b74e5 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c@@ -643,7 +643,7 @@ static int handle_eviocgbit(struct input_dev *dev, case 0: bits = dev->evbit; len = EV_MAX; break; case EV_KEY: bits = dev->keybit; len = KEY_MAX; break; case EV_REL: bits = dev->relbit; len = REL_MAX; break; - case EV_ABS: bits = dev->absbit; len = ABS_MAX; break; + case EV_ABS: bits = dev->absbit; len = ABS_MAX2; break; case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break; case EV_LED: bits = dev->ledbit; len = LED_MAX; break; case EV_SND: bits = dev->sndbit; len = SND_MAX; break;@@ -671,6 +671,93 @@ static int handle_eviocgbit(struct input_dev *dev, } #undef OLD_KEY_MAX +static int evdev_handle_get_abs2(struct input_dev *dev, void __user *p) +{ + u32 code, cnt, valid_cnt, i; + struct input_absinfo2 __user *pinfo = p; + struct input_absinfo abs; + + if (copy_from_user(&code, &pinfo->code, sizeof(code))) + return -EFAULT; + if (copy_from_user(&cnt, &pinfo->cnt, sizeof(cnt))) + return -EFAULT; + if (!cnt) + return 0; + + if (!dev->absinfo) + valid_cnt = 0; + else if (code > ABS_MAX2) + valid_cnt = 0; + else if (code + cnt <= code || code + cnt > ABS_MAX2) + valid_cnt = ABS_MAX2 - code + 1; + else + valid_cnt = cnt; + + for (i = 0; i < valid_cnt; ++i) { + /* + * Take event lock to ensure that we are not + * copying data while EVIOCSABS2 changes it. + * Might be inconsistent, otherwise. + */ + spin_lock_irq(&dev->event_lock); + abs = dev->absinfo[code + i]; + spin_unlock_irq(&dev->event_lock); + + if (copy_to_user(&pinfo->info[i], &abs, sizeof(abs))) + return -EFAULT; + } + + memset(&abs, 0, sizeof(abs)); + for (i = valid_cnt; i < cnt; ++i) + if (copy_to_user(&pinfo->info[i], &abs, sizeof(abs))) + return -EFAULT; + + return 0;
why don't you return the number of valid copied axes to the user? that seems better even than forcing the remainder to 0.
quoted hunk ↗ jump to hunk
+} + +static int evdev_handle_set_abs2(struct input_dev *dev, void __user *p) +{ + struct input_absinfo2 __user *pinfo = p; + struct input_absinfo *abs; + u32 code, cnt, i; + size_t size; + + if (!dev->absinfo) + return 0; + if (copy_from_user(&code, &pinfo->code, sizeof(code))) + return -EFAULT; + if (copy_from_user(&cnt, &pinfo->cnt, sizeof(cnt))) + return -EFAULT; + if (!cnt || code > ABS_MAX2) + return 0; + + if (code + cnt <= code || code + cnt > ABS_MAX2) + cnt = ABS_MAX2 - code + 1; + + size = cnt * sizeof(*abs); + abs = memdup_user(pinfo->info, size); + if (IS_ERR(abs)) + return PTR_ERR(abs); + + /* + * Take event lock to ensure that we are not + * changing device parameters in the middle + * of event. + */ + spin_lock_irq(&dev->event_lock); + for (i = 0; i < cnt; ++i) { + /* silently drop ABS_MT_SLOT */ + if (code + i == ABS_MT_SLOT) + continue; + + dev->absinfo[code + i] = abs[i]; + } + spin_unlock_irq(&dev->event_lock); + + kfree(abs); + return 0; +} + static int evdev_handle_get_keycode(struct input_dev *dev, void __user *p) { struct input_keymap_entry ke = {@@ -898,6 +985,12 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, client->clkid = i; return 0; + case EVIOCGABS2: + return evdev_handle_get_abs2(dev, p); + + case EVIOCSABS2: + return evdev_handle_set_abs2(dev, p); + case EVIOCGKEYCODE: return evdev_handle_get_keycode(dev, p);
[...]
quoted hunk ↗ jump to hunk
diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c index 927ad9a..4660ed1 100644 --- a/drivers/input/misc/uinput.c +++ b/drivers/input/misc/uinput.c@@ -311,7 +311,7 @@ static int uinput_validate_absbits(struct input_dev *dev) unsigned int cnt; int retval = 0; - for (cnt = 0; cnt < ABS_CNT; cnt++) { + for (cnt = 0; cnt < ABS_CNT2; cnt++) { int min, max; if (!test_bit(cnt, dev->absbit)) continue;@@ -474,7 +474,7 @@ static int uinput_setup_device2(struct uinput_device *udev, return -EINVAL; /* rough check to avoid huge kernel space allocations */ - max = ABS_CNT * sizeof(*user_dev2->abs) + sizeof(*user_dev2); + max = ABS_CNT2 * sizeof(*user_dev2->abs) + sizeof(*user_dev2);
hmm, if you ever up the value of ABS_CNT2 and you don't have it query-able, userspace won't be able to create a uinput device on a kernel with a smaller ABS_CNT2. worst of all, the only error you get is EINVAL, which is also used for invalid axis ranges, etc.
quoted hunk ↗ jump to hunk
if (count > max) return -EINVAL;@@ -780,7 +780,7 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, break; case UI_SET_ABSBIT: - retval = uinput_set_bit(arg, absbit, ABS_MAX); + retval = uinput_set_bit(arg, absbit, ABS_MAX2); break; case UI_SET_MSCBIT:diff --git a/include/linux/hid.h b/include/linux/hid.h index 31b9d29..c21d8bb 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h@@ -828,7 +828,7 @@ static inline void hid_map_usage(struct hid_input *hidinput, switch (type) { case EV_ABS: *bit = input->absbit; - *max = ABS_MAX; + *max = ABS_MAX2; break; case EV_REL: *bit = input->relbit;diff --git a/include/linux/input.h b/include/linux/input.h index 82ce323..c6add6f 100644 --- a/include/linux/input.h +++ b/include/linux/input.h@@ -129,7 +129,7 @@ struct input_dev { unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; - unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; + unsigned long absbit[BITS_TO_LONGS(ABS_CNT2)]; unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];@@ -210,8 +210,8 @@ struct input_dev { #error "REL_MAX and INPUT_DEVICE_ID_REL_MAX do not match" #endif -#if ABS_MAX != INPUT_DEVICE_ID_ABS_MAX -#error "ABS_MAX and INPUT_DEVICE_ID_ABS_MAX do not match" +#if ABS_MAX2 != INPUT_DEVICE_ID_ABS_MAX +#error "ABS_MAX2 and INPUT_DEVICE_ID_ABS_MAX do not match" #endif #if MSC_MAX != INPUT_DEVICE_ID_MSC_MAXdiff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index bd24470..1856461 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h@@ -32,7 +32,7 @@ struct input_event { * Protocol version. */ -#define EV_VERSION 0x010001 +#define EV_VERSION 0x010002
A history in the comments would be nice. something like /** * 0x010000: original version * 0x010001: support for long scancodes * 0x010002: added ABS_CNT2/ABS_MAX2, EVIOCSABS2, EVIOCGABS2 */
quoted hunk ↗ jump to hunk
/* * IOCTLs (0x00 - 0x7f)@@ -74,6 +74,30 @@ struct input_absinfo { }; /** + * struct input_absinfo2 - used by EVIOC[G/S]ABS2 ioctls
please spell the two out (at least once in this paragraph), it makes it grep-able.
quoted hunk ↗ jump to hunk
+ * @code: First ABS code to query + * @cnt: Number of ABS codes to query starting at @code + * @info: #@cnt absinfo structures to get/set abs parameters for all codes + * + * This structure is used by the new EVIOC[G/S]ABS2 ioctls which + * do the same as the old EVIOC[G/S]ABS ioctls but avoid encoding + * the ABS code in the ioctl number. This allows a much wider + * range of ABS codes. Furthermore, it allows to query multiple codes with a + * single call.
"new" and "old" have a tendency to become "old" and "before old" quickly, just skip both. A simple "use of EVIOCGABS/EVIOCSABS is discouraged except on kernels without EVIOCGABS2/EVIOCSABS2 support" is enough to signal which one is preferred.
quoted hunk ↗ jump to hunk
+ * + * Note that this silently drops any requests to set ABS_MT_SLOT. Hence, it is + * allowed to call this with code=0 cnt=ABS_CNT2. Furthermore, retrieving + * invalid codes returns all 0, setting them does nothing. So you must check + * with EVIOCGBIT first if you want reliable results. This behavior is needed + * to allow forward compatibility to new ABS codes.
I think this needs rewording, I was quite confused reading this. How about: "For axes not present on the device and for axes exceeding the kernel's built-in ABS_CNT2 maximum, EVIOCGABS2 sets all values in the struct absinfo to 0. EVIOCGSABS2 silently ignores write requests to these axes. ABS_MT_SlOT is an immutable axis and cannot be modified by EVIOCSABS2, the respective value is silently ignored." also, please document the return value of the ioctl. Cheers, Peter
quoted hunk ↗ jump to hunk
+ */ +struct input_absinfo2 { + __u32 code; + __u32 cnt; + struct input_absinfo info[1]; +}; + +/** * struct input_keymap_entry - used by EVIOCGKEYCODE/EVIOCSKEYCODE ioctls * @scancode: scancode represented in machine-endian form. * @len: length of the scancode that resides in @scancode buffer.@@ -153,6 +177,8 @@ struct input_keymap_entry { #define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */ #define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */ +#define EVIOCGABS2 _IOR('E', 0x92, struct input_absinfo2) /* get abs value/limits */ +#define EVIOCSABS2 _IOW('E', 0x93, struct input_absinfo2) /* set abs value/limits */ #define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */@@ -835,11 +861,23 @@ struct input_keymap_entry { #define ABS_MT_TOOL_X 0x3c /* Center X tool position */ #define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */ - +/* + * ABS_MAX/CNT is limited to a maximum of 0x3f due to the design of EVIOCGABS + * and EVIOCSABS ioctls. Other kernel APIs like uinput also hardcoded it. Do + * not modify this value and instead use the extended ABS_MAX2/CNT2 API. + */ #define ABS_MAX 0x3f #define ABS_CNT (ABS_MAX+1) /* + * Due to API restrictions the legacy evdev API only supports ABS values up to + * ABS_MAX/CNT. Use the extended *ABS2 ioctls to operate on any ABS values in + * between ABS_MAX and ABS_MAX2. + */ +#define ABS_MAX2 0x3f +#define ABS_CNT2 (ABS_MAX2+1) + +/* * Switch events */diff --git a/include/uapi/linux/uinput.h b/include/uapi/linux/uinput.h index c2e8710..27ee521 100644 --- a/include/uapi/linux/uinput.h +++ b/include/uapi/linux/uinput.h@@ -140,7 +140,7 @@ struct uinput_user_dev2 { char name[UINPUT_MAX_NAME_SIZE]; struct input_id id; __u32 ff_effects_max; - struct input_absinfo abs[ABS_CNT]; + struct input_absinfo abs[ABS_CNT2]; }; #endif /* _UAPI__UINPUT_H_ */-- 1.8.5.1