Re: [PATCH] Input: evdev - drop partial events after emptying the buffer

From: Aniroop Mathur
Date: Mon Jan 04 2016 - 11:32:16 EST


On Mon, Jan 4, 2016 at 2:22 PM, Benjamin Tissoires
<benjamin.tissoires@xxxxxxxxx> wrote:
> On Mon, Jan 4, 2016 at 8:50 AM, Aniroop Mathur <a.mathur@xxxxxxxxxxx> wrote:
>> On Jan 4, 2016 5:08 AM, "Peter Hutterer" <peter.hutterer@xxxxxxxxx> wrote:
>>>
>>> On Sat, Jan 02, 2016 at 08:39:21PM -0800, Dmitry Torokhov wrote:
>>> > On Thu, Dec 31, 2015 at 03:36:47AM +0530, Aniroop Mathur wrote:
>>> > > This patch introduces concept to drop partial events in evdev handler
>>> > > itself after emptying the buffer which are dropped by all evdev
>>> > > clients in userspace after SYN_DROPPED occurs.
>>> > > This in turn saves space of partial events in evdev handler buffer
>>> > > and reduces evdev client reading requests.
>>> >
>>> > Let's add a few people who write consumer code to see if this is
>>> > something that they consider useful.
>>>
>>> yeah, it's useful though we already have the code in libevdev to work around
>>> this. Still, it reduces the number of events discarde by the client, so it'll be a net
>>> plus. but, afaict, there's a bug in this implementation.
>>> The doc states: "Client should ignore all events up to and including next
>>> SYN_REPORT event". If you drop partial events, you need to have an empty
>>> SYN_REPORT after the SYN_DROPPED before you start with full events again.
>>> This patch skips that, so after the SYN_DROPPED you have a valid full event
>>> that will be ignored by any client currently capable of handling
>>> SYN_DROPPED.
>>> Example: let's assume a device sending ABS_X/ABS_Y fast enough to cause a
>>> SYN_DROPPED, you may have this queue:
>>> ABS_X
>>> ABS_Y
>>> SYN_REPORT
>>> ...
>>> SYN_DROPPED
>>> ABS_Y <---- partial event
>>> SYN_REPORT <---- client discards up to here, sync state
>>> ABS_X
>>> ABS_Y
>>> SYN_REPORT <---- first full event after sync
>>>
>>> With this patch this sequence becomes:
>>> ABS_X
>>> ABS_Y
>>> SYN_REPORT
>>> ...
>>> SYN_DROPPED
>>> [kernel discards ABS_Y + SYN_REPORT as partial event]
>>> ABS_X
>>> ABS_Y
>>> SYN_REPORT <--- client discards up to here, sync state
>>> <--- there is no event after sync
>>>
>>> That's a change in kernel behaviour and will make all current clients
>>> potentially buggy, you'll really need the empty SYN_REPORT here.
>>>
>>
>> Thank you for your input, Mr. Peter.
>> Actually, there is a need to update the documentation as well after this patch
>> so that clients no more ignore the events after SYN_DROPPED occurs and
>> should read the events normally. I skipped updating the documentation in
>> this patch as I thought of getting a consent first.
>> * SYN_DROPPED:
>> - Used to indicate buffer overrun in the evdev client's event queue.
>> Client should ignore all events up to and including next SYN_REPORT
>> event and query the device (using EVIOCG* ioctls) to obtain its
>> current state
>> + From kernel version <4.4.x> onwards, clients do no need to ignore
>> + events anymore and should read normally as there will be no
>> + partial events after SYN_DROPPED occurs.
>
> Hi Aniroop,
>
> this just won't do. As Peter said, there are current implementation of
> SYN_DROPPED around, which ignore the events until the next SYN_REPORT.
> If you change the protocol by updating the doc, you will just break
> existing userspace which has not included a check on the kernel
> version (and honestly, checking the kernel version from the userspace
> point of view is just a nightmare when distributions start backporting
> changes here and there).
>
> The kernel rule is "do not break userspace", so we can not accept this.
> Peter suggested you just add an empty SYN_REPORT after SYN_DROPPED
> which would solve the whole problem: clients already handling
> SYN_DROPPED will receive the next valid event, and those who don't (or
> which will be updated) will not have to do anything more.
>
> The only cons I can think of is that multitouch protocol A will be a
> pain to handle with this empty SYN_REPORT if you do not handle the
> SYN_DROPPED as per the doc.
> But on the other hand, if you have a MT protocol A device, you are
> screwed anyway because you need mtdev and so let's use libevdev at
> this point.
>
>>
>> As far as I've worked on client codes, this client code change is easy and
>
> Nope, checking the kernel version is not "easy" as this is not reliable.
>
>> even if some clients miss to update the code then it seems not much of
>> a problem because 8 packets are already dropped so an additional packet
>> would not cause any trouble in case of buffer overrun.
>
> Nope again. In case of a SYN_DROPPED, the client syncs its internal
> state by using ioctls. So if you drop one valid event, you are not in
> sync again and the SYN_DROPPED is moot.
>

Thanks for the explanation Mr. Benjamin.
I understood your concern and I have sent the v2 for this patch.

Regards,
Aniroop Mathur

> Cheers,
> Benjamin
>
>>
>> Regards,
>> Aniroop Mathur
>>
>>> > >
>>> > > Signed-off-by: Aniroop Mathur <a.mathur@xxxxxxxxxxx>
>>> > > ---
>>> > > drivers/input/evdev.c | 49 +++++++++++++++++++++++++++++++++++++++++++++----
>>> > > 1 file changed, 45 insertions(+), 4 deletions(-)
>>> > >
>>> > > diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
>>> > > index e9ae3d5..e7b612e 100644
>>> > > --- a/drivers/input/evdev.c
>>> > > +++ b/drivers/input/evdev.c
>>> > > @@ -58,6 +58,7 @@ struct evdev_client {
>>> > > struct list_head node;
>>> > > unsigned int clk_type;
>>> > > bool revoked;
>>> > > + bool drop_pevent; /* specifies whether partial events need to be dropped */
>>> > > unsigned long *evmasks[EV_CNT]; > > unsigned int bufsize;
>>> > > struct input_event buffer[];
>>> > > @@ -192,6 +193,7 @@ static int evdev_set_clk_type(struct evdev_client *client, unsigned int clkid)
>>> > > {
>>> > > unsigned long flags;
>>> > > unsigned int clk_type;
>>> > > + struct input_event *ev;
>>> > >
>>> > > switch (clkid) {
>>> > >
>>> > > @@ -218,6 +220,17 @@ static int evdev_set_clk_type(struct evdev_client *client, unsigned int clkid)
>>> > > spin_lock_irqsave(&client->buffer_lock, flags);
>>> > >
>>> > > if (client->head != client->tail) {
>>> > > +
>>> > > + /*
>>> > > + * Set drop_pevent to true if last event packet is
>>> > > + * not stored completely in buffer.
>>> > > + */
>>> > > + client->head--;
>>> > > + client->head &= client->bufsize - 1;
>>> > > + ev = &client->buffer[client->head];
>>> > > + if (!(ev->type == EV_SYN && ev->code == SYN_REPORT))
>>> > > + client->drop_pevent = true;
>>> > > +
>>> > > client->packet_head = client->head = client->tail;
>>> > > __evdev_queue_syn_dropped(client);
>>> > > }
>>> > > @@ -228,31 +241,51 @@ static int evdev_set_clk_type(struct evdev_client *client, unsigned int clkid)
>>> > > return 0;
>>> > > }
>>> > >
>>> > > -static void __pass_event(struct evdev_client *client,
>>> > > +static bool __pass_event(struct evdev_client *client,
>>> > > const struct input_event *event)
>>> > > {
>>> > > + struct input_event *prev_ev;
>>> > > +
>>> > > client->buffer[client->head++] = *event;
>>> > > client->head &= client->bufsize - 1;
>>> > >
>>> > > if (unlikely(client->head == client->tail)) {
>>> > > /*
>>> > > - * This effectively "drops" all unconsumed events, leaving
>>> > > - * EV_SYN/SYN_DROPPED plus the newest event in the queue.
>>> > > + * This effectively "drops" all unconsumed events, storing
>>> > > + * EV_SYN/SYN_DROPPED and the newest event in the queue but
>>> > > + * only if it is not part of partial packet.
>>> > > + * Set drop_pevent to true if last event packet is not stored
>>> > > + * completely in buffer and set to false if SYN_REPORT occurs.
>>> > > */
>>> > > +
>>> > > client->tail = (client->head - 2) & (client->bufsize - 1);
>>> > >
>>> > > + prev_ev = &client->buffer[client->tail];
>>> > > + if (!(prev_ev->type == EV_SYN && prev_ev->code == SYN_REPORT)) {
>>>
>>> IMO a (prev_ev->type != EV_SYN || prev_ev->code != SYN_REPORT) would be
>>> easier to read than this (!(a && b)).
>>>
>>> Cheers,
>>> Peter
>>>
>>> > > + client->drop_pevent = true;
>>> > > + client->head--;
>>> > > + client->head &= client->bufsize - 1;
>>> > > + }
>>> > > +
>>> > > client->buffer[client->tail].time = event->time;
>>> > > client->buffer[client->tail].type = EV_SYN;
>>> > > client->buffer[client->tail].code = SYN_DROPPED;
>>> > > client->buffer[client->tail].value = 0;
>>> > >
>>> > > client->packet_head = client->tail;
>>> > > +
>>> > > + if (event->type == EV_SYN && event->code == SYN_REPORT) {
>>> > > + client->drop_pevent = false;
>>> > > + return true;
>>> > > + }
>>> > > }
>>> > >
>>> > > if (event->type == EV_SYN && event->code == SYN_REPORT) {
>>> > > client->packet_head = client->head;
>>> > > kill_fasync(&client->fasync, SIGIO, POLL_IN);
>>> > > }
>>> > > +
>>> > > + return false;
>>> > > }
>>> > >
>>> > > static void evdev_pass_values(struct evdev_client *client,
>>> > > @@ -284,10 +317,18 @@ static void evdev_pass_values(struct evdev_client *client,
>>> > > wakeup = true;
>>> > > }
>>> > >
>>> > > + /* drop partial events until SYN_REPORT occurs */
>>> > > + if (client->drop_pevent) {
>>> > > + if (v->type == EV_SYN && v->code == SYN_REPORT)
>>> > > + client->drop_pevent = false;
>>> > > + continue;
>>> > > + }
>>> > > +
>>> > > event.type = v->type;
>>> > > event.code = v->code;
>>> > > event.value = v->value;
>>> > > - __pass_event(client, &event);
>>> > > + if (__pass_event(client, &event))
>>> > > + wakeup = false;
>>> > > }
>>> > >
>>> > > spin_unlock(&client->buffer_lock);
>>> > > --
>>> > > 2.6.2
>>> > >
>>> >
>>> > --
>>> > Dmitry
>>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/