Fwd: [PATCH v3 6/9] platform/chrome: Add event handling

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



---------- Forwarded message ---------
From: Enric Balletbo Serra <eballetbo@xxxxxxxxx>
Date: Tue, Jan 22, 2019 at 8:02 AM
Subject: Re: [PATCH v3 6/9] platform/chrome: Add event handling
To: Nick Crews <ncrews@xxxxxxxxxxxx>
Cc: linux-kernel <linux-kernel@xxxxxxxxxxxxxxx>, Guenter Roeck
<groeck@xxxxxxxxxxxx>, Simon Glass <sjg@xxxxxxxxxxxx>, Daniel Kurtz
<djkurtz@xxxxxxxxxx>, <dlaurie@xxxxxxxxxxxx>, Duncan Laurie
<dlaurie@xxxxxxxxxx>, Nick Crews <ncrews@xxxxxxxxxx>, Enric Balletbo i
Serra <enric.balletbo@xxxxxxxxxxxxx>, Benson Leung
<bleung@xxxxxxxxxxxx>, Dmitry Torokhov <dmitry.torokhov@xxxxxxxxx>


Hi Nick,

I'd like to have some feedback from input subsystem if it's possible,
so adding linux-input@xxxxxxxxxxxxxxx and Dmitry. Don't forget to add
them for the next versions.

Missatge de Nick Crews <ncrews@xxxxxxxxxxxx> del dia ds., 19 de gen.
2019 a les 1:16:
>
> From: Duncan Laurie <dlaurie@xxxxxxxxxx>
>
> The Wilco Embedded Controller can return extended events that
> are not handled by standard ACPI objects.  These events can
> include hotkeys which map to standard functions like brightness
> controls, or information about EC controlled features like the
> charger or battery.
>
> These events are triggered with an ACPI Notify(0x90) and the
> event data buffer is read through an ACPI method provided by
> the BIOS which reads the event buffer from EC RAM.
>
> These events are then processed, with hotkey events being sent
> to the input subsystem and other events put into a queue which
> can be read by a userspace daemon via a sysfs attribute.
>
> > evtest /dev/input/event6
> Input driver version is 1.0.1
> Input device ID: bus 0x19 vendor 0x0 product 0x0 version 0x0
> Input device name: "Wilco EC hotkeys"
> Supported events:
>   Event type 0 (EV_SYN)
>   Event type 1 (EV_KEY)
>     Event code 224 (KEY_BRIGHTNESSDOWN)
>     Event code 225 (KEY_BRIGHTNESSUP)
>     Event code 240 (KEY_UNKNOWN)
>   Event type 4 (EV_MSC)
>     Event code 4 (MSC_SCAN)
> Properties:
> Testing ... (interrupt to exit)
> Event: type 4 (EV_MSC), code 4 (MSC_SCAN), value 57
> Event: type 1 (EV_KEY), code 224 (KEY_BRIGHTNESSDOWN), value 1
> Event: -------------- SYN_REPORT ------------
> Event: type 1 (EV_KEY), code 224 (KEY_BRIGHTNESSDOWN), value 0
> Event: -------------- SYN_REPORT ------------
> Event: type 4 (EV_MSC), code 4 (MSC_SCAN), value 58
> Event: type 1 (EV_KEY), code 225 (KEY_BRIGHTNESSUP), value 1
> Event: -------------- SYN_REPORT ------------
> Event: type 1 (EV_KEY), code 225 (KEY_BRIGHTNESSUP), value 0
> Event: -------------- SYN_REPORT ------------
>
> Signed-off-by: Duncan Laurie <dlaurie@xxxxxxxxxx>
> Signed-off-by: Nick Crews <ncrews@xxxxxxxxxxxx>
> ---
>
> Changes in v3:
> - change err check from "if (ret < 0)" to "if (ret)"
>
> Changes in v2:
> - rm "wilco_ec_event -" prefix from docstring
> - rm license boiler plate
> - Add sysfs directory documentation
> - Fix cosmetics
> - events are init()ed before subdrivers now
>
>  drivers/platform/chrome/wilco_ec/Makefile |   2 +-
>  drivers/platform/chrome/wilco_ec/core.c   |  14 +-
>  drivers/platform/chrome/wilco_ec/event.c  | 347 ++++++++++++++++++++++
>  include/linux/platform_data/wilco-ec.h    |  34 +++
>  4 files changed, 395 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/platform/chrome/wilco_ec/event.c
>
> diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile
> index 84ad2772247c..18070d0f8b5e 100644
> --- a/drivers/platform/chrome/wilco_ec/Makefile
> +++ b/drivers/platform/chrome/wilco_ec/Makefile
> @@ -1,5 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0
>
>  wilco_ec-objs                          := core.o mailbox.o debugfs.o sysfs.o \
> -                                          legacy.o
> +                                          legacy.o event.o
>  obj-$(CONFIG_WILCO_EC)                 += wilco_ec.o
> diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
> index dd8b896b3833..65588cfcc7a2 100644
> --- a/drivers/platform/chrome/wilco_ec/core.c
> +++ b/drivers/platform/chrome/wilco_ec/core.c
> @@ -77,6 +77,13 @@ static int wilco_ec_probe(struct platform_device *pdev)
>                 goto rm_debugfs;
>         }
>
> +       /* Prepare to handle events */
> +       ret = wilco_ec_event_init(ec);
> +       if (ret) {
> +               dev_err(dev, "Failed to setup event handling\n");
> +               goto remove_sysfs;
> +       }
> +
>         /*
>          * Register a RTC platform device that will get picked up by the RTC
>          * subsystem. This platform device will get freed when its parent device
> @@ -88,11 +95,13 @@ static int wilco_ec_probe(struct platform_device *pdev)
>         if (IS_ERR(child_pdev)) {
>                 dev_err(dev, "Failed to create RTC platform device\n");
>                 ret = PTR_ERR(child_pdev);
> -               goto remove_sysfs;
> +               goto remove_events;
>         }
>
>         return 0;
>
> +remove_events:
> +       wilco_ec_event_remove(ec);
>  remove_sysfs:
>         wilco_ec_sysfs_remove(ec);
>  rm_debugfs:
> @@ -106,6 +115,9 @@ static int wilco_ec_remove(struct platform_device *pdev)
>  {
>         struct wilco_ec_device *ec = platform_get_drvdata(pdev);
>
> +       /* Stop handling EC events */
> +       wilco_ec_event_remove(ec);
> +
>         /* Remove sysfs attributes */
>         wilco_ec_sysfs_remove(ec);
>
> diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c
> new file mode 100644
> index 000000000000..7601c02dffe8
> --- /dev/null
> +++ b/drivers/platform/chrome/wilco_ec/event.c
> @@ -0,0 +1,347 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Event handling for Wilco Embedded Controller
> + *
> + * Copyright 2018 Google LLC
> + *
> + * The Wilco Embedded Controller can return extended events that
> + * are not handled by standard ACPI objects.  These events can
> + * include hotkeys which map to standard functions like brightness
> + * controls, or information about EC controlled features like the
> + * charger or battery.
> + *
> + * These events are triggered with an ACPI Notify(0x90) and the
> + * event data buffer is read through an ACPI method provided by
> + * the BIOS which reads the event buffer from EC RAM.
> + *
> + * These events are then processed, with hotkey events being sent
> + * to the input subsystem and other events put into a queue which
> + * can be read by a userspace daemon via a sysfs attribute.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.h>
> +#include <linux/list.h>
> +#include <linux/platform_data/wilco-ec.h>
> +#include <linux/platform_device.h>
> +
> +/* ACPI Notify event code indicating event data is available */
> +#define EC_ACPI_NOTIFY_EVENT           0x90
> +
> +/* ACPI Method to execute to retrieve event data buffer from the EC */
> +#define EC_ACPI_GET_EVENT              "^QSET"
> +
> +/* Maximum number of words in event data returned by the EC */
> +#define EC_ACPI_MAX_EVENT_DATA         6
> +
> +/* Keep at most 100 events in the queue */
> +#define EC_EVENT_QUEUE_MAX             100
> +
> +/**
> + * enum ec_event_type - EC event categories.
> + * @EC_EVENT_TYPE_HOTKEY: Hotkey event for handling special keys.
> + * @EC_EVENT_TYPE_NOTIFY: EC feature state changes.
> + * @EC_EVENT_TYPE_SYSTEM: EC system messages.
> + */
> +enum ec_event_type {
> +       EC_EVENT_TYPE_HOTKEY = 0x10,
> +       EC_EVENT_TYPE_NOTIFY = 0x11,
> +       EC_EVENT_TYPE_SYSTEM = 0x12,
> +};
> +
> +/**
> + * struct ec_event - Extended event returned by the EC.
> + * @size: Number of words in structure after the size word.
> + * @type: Extended event type from &enum ec_event_type.
> + * @event: Event data words.  Max count is %EC_ACPI_MAX_EVENT_DATA.
> + */
> +struct ec_event {
> +       u16 size;
> +       u16 type;
> +       u16 event[0];
> +} __packed;
> +
> +/**
> + * struct ec_event_entry - Event queue entry.
> + * @list: List node.
> + * @size: Number of bytes in event structure.
> + * @event: Extended event returned by the EC.  This should be the last
> + *         element because &struct ec_event includes a zero length array.
> + */
> +struct ec_event_entry {
> +       struct list_head list;
> +       size_t size;
> +       struct ec_event event;
> +};
> +
> +static const struct key_entry wilco_ec_keymap[] = {
> +       { KE_KEY, 0x0057, { KEY_BRIGHTNESSDOWN } },
> +       { KE_KEY, 0x0058, { KEY_BRIGHTNESSUP } },
> +       { KE_END, 0 }
> +};
> +
> +/**
> + * wilco_ec_handle_events() - Handle Embedded Controller events.
> + * @ec: EC device.
> + * @buf: Buffer of event data.
> + * @length: Length of event data buffer.
> + *
> + * Return: Number of events in queue or negative error code on failure.
> + *
> + * This function will handle EC extended events, sending hotkey events
> + * to the input subsystem and queueing others to be read by userspace.
> + */
> +static int wilco_ec_handle_events(struct wilco_ec_device *ec,
> +                                 u8 *buf, u32 length)
> +{
> +       struct wilco_ec_event *queue = &ec->event;
> +       struct ec_event *event;
> +       struct ec_event_entry *entry;
> +       size_t entry_size, num_words;
> +       u32 offset = 0;
> +
> +       while (offset < length) {
> +               event = (struct ec_event *)(buf + offset);
> +               if (!event)
> +                       return -EINVAL;
> +
> +               dev_dbg(ec->dev, "EC event type 0x%02x size %d\n", event->type,
> +                       event->size);
> +
> +               /* Number of 16bit event data words is size - 1 */
> +               num_words = event->size - 1;
> +               entry_size = sizeof(*event) + (num_words * sizeof(u16));
> +
> +               if (num_words > EC_ACPI_MAX_EVENT_DATA) {
> +                       dev_err(ec->dev, "Invalid event word count: %d > %d\n",
> +                               num_words, EC_ACPI_MAX_EVENT_DATA);
> +                       return -EOVERFLOW;
> +               };
> +
> +               /* Ensure event does not overflow the available buffer */
> +               if ((offset + entry_size) > length) {
> +                       dev_err(ec->dev, "Event exceeds buffer: %d > %d\n",
> +                               offset + entry_size, length);
> +                       return -EOVERFLOW;
> +               }
> +
> +               /* Point to the next event in the buffer */
> +               offset += entry_size;
> +
> +               /* Hotkeys are sent to the input subsystem */
> +               if (event->type == EC_EVENT_TYPE_HOTKEY) {
> +                       if (sparse_keymap_report_event(queue->input,
> +                                                      event->event[0],
> +                                                      1, true))
> +                               continue;
> +
> +                       /* Unknown hotkeys are put into the event queue */
> +                       dev_dbg(ec->dev, "Unknown hotkey 0x%04x\n",
> +                               event->event[0]);
> +               }
> +
> +               /* Prepare event for the queue */
> +               entry = kzalloc(entry_size, GFP_KERNEL);
> +               if (!entry)
> +                       return -ENOMEM;
> +
> +               /* Copy event data */
> +               entry->size = entry_size;
> +               memcpy(&entry->event, event, entry->size);
> +
> +               /* Add this event to the queue */
> +               mutex_lock(&queue->lock);
> +               list_add_tail(&entry->list, &queue->list);
> +               queue->count++;
> +               mutex_unlock(&queue->lock);
> +       }
> +
> +       return queue->count;
> +}
> +
> +/**
> + * wilco_ec_acpi_notify() - Handler called by ACPI subsystem for Notify.
> + * @device: EC ACPI device.
> + * @value: Value passed to Notify() in ACPI.
> + * @data: Private data, pointer to EC device.
> + */
> +static void wilco_ec_acpi_notify(acpi_handle device, u32 value, void *data)
> +{
> +       struct wilco_ec_device *ec = data;
> +       struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> +       union acpi_object *obj;
> +       acpi_status status;
> +       int count;
> +
> +       /* Currently only handle event notifications */
> +       if (value != EC_ACPI_NOTIFY_EVENT) {
> +               dev_err(ec->dev, "Invalid event: 0x%08x\n", value);
> +               return;
> +       }
> +
> +       /* Execute ACPI method to get event data buffer */
> +       status = acpi_evaluate_object(device, EC_ACPI_GET_EVENT,
> +                                     NULL, &event_buffer);
> +       if (ACPI_FAILURE(status)) {
> +               dev_err(ec->dev, "Error executing ACPI method %s()\n",
> +                        EC_ACPI_GET_EVENT);
> +               return;
> +       }
> +
> +       obj = (union acpi_object *)event_buffer.pointer;
> +       if (!obj) {
> +               dev_err(ec->dev, "Nothing returned from %s()\n",
> +                       EC_ACPI_GET_EVENT);
> +               return;
> +       }
> +       if (obj->type != ACPI_TYPE_BUFFER) {
> +               dev_err(ec->dev, "Invalid object returned from %s()\n",
> +                       EC_ACPI_GET_EVENT);
> +               kfree(obj);
> +               return;
> +       }
> +       if (obj->buffer.length < sizeof(struct ec_event)) {
> +               dev_err(ec->dev, "Invalid buffer length %d from %s()\n",
> +                       obj->buffer.length, EC_ACPI_GET_EVENT);
> +               kfree(obj);
> +               return;
> +       }
> +
> +       /* Handle events and notify sysfs if any queued for userspace */
> +       count = wilco_ec_handle_events(ec, obj->buffer.pointer,
> +                                      obj->buffer.length);
> +
> +       if (count > 0) {
> +               dev_dbg(ec->dev, "EC event queue has %d entries\n", count);
> +               sysfs_notify(&ec->dev->kobj, NULL, "event");
> +       }
> +
> +       kfree(obj);
> +}
> +
> +static ssize_t wilco_ec_event_read(struct file *filp, struct kobject *kobj,
> +                                  struct bin_attribute *attr,
> +                                  char *buf, loff_t off, size_t count)
> +{
> +       struct wilco_ec_device *ec = attr->private;
> +       struct ec_event_entry *entry;
> +
> +       /* Only supports reading full events */
> +       if (off != 0)
> +               return -EINVAL;
> +
> +       /* Remove the first event and provide it to userspace */
> +       mutex_lock(&ec->event.lock);
> +       entry = list_first_entry_or_null(&ec->event.list,
> +                                        struct ec_event_entry, list);
> +       if (entry) {
> +               if (entry->size < count)
> +                       count = entry->size;
> +               memcpy(buf, &entry->event, count);
> +               list_del(&entry->list);
> +               kfree(entry);
> +               ec->event.count--;
> +       } else {
> +               count = 0;
> +       }
> +       mutex_unlock(&ec->event.lock);
> +
> +       return count;
> +}
> +
> +static void wilco_ec_event_clear(struct wilco_ec_device *ec)
> +{
> +       struct ec_event_entry *entry, *next;
> +
> +       mutex_lock(&ec->event.lock);
> +
> +       /* Clear the event queue */
> +       list_for_each_entry_safe(entry, next, &ec->event.list, list) {
> +               list_del(&entry->list);
> +               kfree(entry);
> +               ec->event.count--;
> +       }
> +
> +       mutex_unlock(&ec->event.lock);
> +}
> +
> +int wilco_ec_event_init(struct wilco_ec_device *ec)
> +{
> +       struct wilco_ec_event *event = &ec->event;
> +       struct device *dev = ec->dev;
> +       struct acpi_device *adev = ACPI_COMPANION(dev);
> +       acpi_status status;
> +       int ret;
> +
> +       if (!adev) {
> +               dev_err(dev, "Unable to find Wilco ACPI Device\n");
> +               return -ENODEV;
> +       }
> +
> +       INIT_LIST_HEAD(&event->list);
> +       mutex_init(&event->lock);
> +
> +       /* Allocate input device for hotkeys */
> +       event->input = input_allocate_device();
> +       if (!event->input)
> +               return -ENOMEM;
> +       event->input->name = "Wilco EC hotkeys";
> +       event->input->phys = "ec/input0";
> +       event->input->id.bustype = BUS_HOST;
> +       ret = sparse_keymap_setup(event->input, wilco_ec_keymap, NULL);
> +       if (ret) {
> +               dev_err(dev, "Unable to setup input device keymap\n");
> +               input_free_device(event->input);
> +               return ret;
> +       }
> +       ret = input_register_device(event->input);
> +       if (ret) {
> +               dev_err(dev, "Unable to register input device\n");
> +               input_free_device(event->input);
> +               return ret;
> +       }
> +
> +       /* Create sysfs attribute for userspace event handling */
> +       sysfs_bin_attr_init(&event->attr);
> +       event->attr.attr.name = "event";
> +       event->attr.attr.mode = 0400;
> +       event->attr.read = wilco_ec_event_read;
> +       event->attr.private = ec;
> +       ret = device_create_bin_file(dev, &event->attr);
> +       if (ret) {
> +               dev_err(dev, "Failed to create event attribute in sysfs\n");
> +               input_unregister_device(event->input);
> +               return ret;
> +       }
> +
> +       /* Install ACPI handler for Notify events */
> +       status = acpi_install_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
> +                                            wilco_ec_acpi_notify, ec);
> +       if (ACPI_FAILURE(status)) {
> +               dev_err(dev, "Failed to register notifier %08x\n", status);
> +               device_remove_bin_file(dev, &event->attr);
> +               input_unregister_device(event->input);
> +               return -ENODEV;
> +       }
> +
> +       return 0;
> +}
> +
> +void wilco_ec_event_remove(struct wilco_ec_device *ec)
> +{
> +       struct acpi_device *adev = ACPI_COMPANION(ec->dev);
> +
> +       /* Stop new events */
> +       if (adev)
> +               acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
> +                                          wilco_ec_acpi_notify);
> +
> +       /* Remove event interfaces */
> +       device_remove_bin_file(ec->dev, &ec->event.attr);
> +       input_unregister_device(ec->event.input);
> +
> +       /* Clear the event queue */
> +       wilco_ec_event_clear(ec);
> +}
> diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h
> index b1487a259a12..2728fad90a3a 100644
> --- a/include/linux/platform_data/wilco-ec.h
> +++ b/include/linux/platform_data/wilco-ec.h
> @@ -9,7 +9,9 @@
>  #define WILCO_EC_H
>
>  #include <linux/device.h>
> +#include <linux/input.h>
>  #include <linux/kernel.h>
> +#include <linux/list.h>
>
>  #define WILCO_EC_FLAG_NO_RESPONSE      BIT(0) /* EC does not respond */
>  #define WILCO_EC_FLAG_EXTENDED_DATA    BIT(1) /* EC returns 256 data bytes */
> @@ -24,6 +26,22 @@
>  /* Extended commands have 256 bytes of response data */
>  #define EC_MAILBOX_DATA_SIZE_EXTENDED  256
>
> +/**
> + * struct wilco_ec_event - EC extended events.
> + * @lock: Mutex to guard the list of events.
> + * @list: Queue of EC events to be provided to userspace.
> + * @attr: Sysfs attribute for userspace to read events.
> + * @count: Count of events in the queue.
> + * @input: Input device for hotkey events.
> + */
> +struct wilco_ec_event {
> +       struct mutex lock;
> +       struct list_head list;
> +       struct bin_attribute attr;
> +       size_t count;
> +       struct input_dev *input;
> +};
> +
>  /**
>   * struct wilco_ec_device - Wilco Embedded Controller handle.
>   * @dev: Device handle.
> @@ -34,6 +52,7 @@
>   * @data_buffer: Buffer used for EC communication.  The same buffer
>   *               is used to hold the request and the response.
>   * @data_size: Size of the data buffer used for EC communication.
> + * @event: EC extended event handler.
>   */
>  struct wilco_ec_device {
>         struct device *dev;
> @@ -43,6 +62,7 @@ struct wilco_ec_device {
>         struct resource *io_packet;
>         void *data_buffer;
>         size_t data_size;
> +       struct wilco_ec_event event;
>  };
>
>  /**
> @@ -152,4 +172,18 @@ int wilco_ec_sysfs_init(struct wilco_ec_device *ec);
>   */
>  void wilco_ec_sysfs_remove(struct wilco_ec_device *ec);
>
> +/**
> + * wilco_ec_event_init() - Prepare to handle EC events.
> + * @ec: EC device.
> + *
> + * Return: 0 for success or negative error code on failure.
> + */
> +int wilco_ec_event_init(struct wilco_ec_device *ec);
> +
> +/**
> + * wilco_ec_event_remove() - Remove EC event handler.
> + * @ec: EC device.
> + */
> +void wilco_ec_event_remove(struct wilco_ec_device *ec);
> +
>  #endif /* WILCO_EC_H */
> --
> 2.20.1.321.g9e740568ce-goog
>



[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux