summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2021-02-11 16:22:44 +0800
committerTai Kuo <taikuo@google.com>2021-03-03 23:40:10 +0800
commitf81f4a17bd2c9d9cbc115a7344124833b102b67e (patch)
tree706ab70bd43b1469ca590da8102af341c80cfd58
parent95aab3ee1559f0b986394bf29a41f5a5594019bc (diff)
downloadcommon-f81f4a17bd2c9d9cbc115a7344124833b102b67e.tar.gz
touch: common: Add touch_offload module
Bug: 173331163 Test: touch_offload module is built from the external module. Signed-off-by: Tai Kuo <taikuo@google.com> Change-Id: I0de609638e0b30de2673d7599e1529081763b0cf
-rw-r--r--Kconfig10
-rw-r--r--Makefile17
-rw-r--r--include/uapi/input/touch_offload.h280
-rw-r--r--touch_offload.c677
-rw-r--r--touch_offload.h113
5 files changed, 1096 insertions, 1 deletions
diff --git a/Kconfig b/Kconfig
index 18b5701..ffc7b76 100644
--- a/Kconfig
+++ b/Kconfig
@@ -19,3 +19,13 @@ config TOUCHSCREEN_TBN
To compile this driver as a module, choose M here: the module will be
called touch_bus_negotiator.
+
+config TOUCHSCREEN_OFFLOAD
+ tristate "Touchscreen algorithm offload"
+ depends on (TOUCHSCREEN_FTS || TOUCHSCREEN_SEC_TS)
+ help
+ Say Y here to enable touchscreen algorithm offload. This module allows
+ the touchscreen driver to feed heatmap data into a char device to be
+ consumed by a client outside the kernel.
+
+ If unsure, say N.
diff --git a/Makefile b/Makefile
index ec3469e..2eb28da 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,30 @@
obj-$(CONFIG_TOUCHSCREEN_TBN) += touch_bus_negotiator.o
obj-$(CONFIG_TOUCHSCREEN_HEATMAP) += heatmap.o
+obj-$(CONFIG_TOUCHSCREEN_OFFLOAD) += touch_offload.o
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
M ?= $(shell pwd)
+KERNEL_UAPI_HEADERS_DIR ?= $(shell readlink -m ${COMMON_OUT_DIR}/kernel_uapi_headers)
KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_TBN=m
KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_HEATMAP=m
+KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_OFFLOAD=m
EXTRA_CFLAGS += -DDYNAMIC_DEBUG_MODULE
+EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/common/include
-modules modules_install clean:
+modules clean:
$(MAKE) -C $(KERNEL_SRC) M=$(M) \
$(KBUILD_OPTIONS) \
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
$(@)
+
+modules_install: headers_install
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) \
+ $(KBUILD_OPTIONS) \
+ EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
+ $(@)
+
+headers_install:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) \
+ INSTALL_HDR_PATH="${KERNEL_UAPI_HEADERS_DIR}/usr" \
+ $(@)
diff --git a/include/uapi/input/touch_offload.h b/include/uapi/input/touch_offload.h
new file mode 100644
index 0000000..81b7384
--- /dev/null
+++ b/include/uapi/input/touch_offload.h
@@ -0,0 +1,280 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _UAPI_TOUCH_OFFLOAD_H
+#define _UAPI_TOUCH_OFFLOAD_H
+
+#define TOUCH_OFFLOAD_MAGIC '7'
+
+/* Bus interface type */
+#define BUS_TYPE_I2C 0
+#define BUS_TYPE_SPI 1
+#define BUS_TYPE_I3C 2
+
+/* Indicates full heatmap frame vs. partial */
+#define HEATMAP_SIZE_PARTIAL 0
+#define HEATMAP_SIZE_FULL 1
+
+/* Touch channel data types */
+#define TOUCH_DATA_TYPE_COORD 0x01
+#define TOUCH_DATA_TYPE_RAW 0x02
+#define TOUCH_DATA_TYPE_FILTERED 0x04
+#define TOUCH_DATA_TYPE_BASELINE 0x08
+#define TOUCH_DATA_TYPE_STRENGTH 0x10
+
+/* Touch channel scan types */
+#define TOUCH_SCAN_TYPE_MUTUAL 0x40
+#define TOUCH_SCAN_TYPE_SELF 0x80
+
+
+//////////////////////////////////////////////////////////////
+
+/* TouchOffloadCaps
+ *
+ * touch_offload_major_version - Major version for breaking changes
+ * touch_offload_minor_version - Minor version for small, compatible changes
+ * device_id - device-specific identifier
+ * display_width - width of device display in pixels
+ * display_height - height of device display in pixels
+ * tx_size - number of TX channels
+ * rx_size - number of RX channels
+ * bus_type - bus interface type
+ * bus_speed_hz - bus frequency
+ * heatmap_size - partial or full heatmap
+ * touch_data_scan_types - channel data types available
+ * touch_scan_types - channel scan types available
+ * continuous_reporting - driver supports continuous touch reports
+ * noise_reporting - driver supports noise status messages
+ * cancel_reporting - driver supports sending cancel events
+ * size_reporting - driver supports size information
+ * filter_grip - driver supports disabling underlying grip suppression
+ * filter_palm - driver supports disabling underlying palm rejection
+ * num_sensitivity_settings - number of sensitivity options provided
+ */
+struct TouchOffloadCaps {
+ /* Version info */
+ __u32 touch_offload_major_version;
+ __u32 touch_offload_minor_version;
+ __u8 reserved1[8];
+
+ /* Device info */
+ __u32 device_id;
+ __u16 display_width;
+ __u16 display_height;
+ __u16 tx_size;
+ __u16 rx_size;
+ __u8 bus_type;
+ __u32 bus_speed_hz;
+ __u8 reserved2[16];
+
+ /* Algorithm info */
+ __u8 heatmap_size;
+ __u16 touch_data_types;
+ __u16 touch_scan_types;
+ __u8 reserved3[16];
+
+ /* Feature flags */
+ __u8 continuous_reporting;
+ __u8 noise_reporting;
+ __u8 cancel_reporting;
+ __u8 size_reporting;
+ __u8 filter_grip;
+ __u8 filter_palm;
+ __u8 num_sensitivity_settings;
+ __u8 reserved4[32];
+};
+
+/* TouchOffloadConfig
+ *
+ * continuous_reporting - enable continuous touch reports
+ * noise_reporting - enable noise status messages
+ * cancel_reporting - enable cancel events
+ * filter_grip - enable underlying grip suppression
+ * filter_palm - enable underlying palm rejection
+ * num_sensitivity_settings - number of sensitivity options provided
+ * read_coords - allocate a channel to coordinate data
+ * mutual_data_types - bitfield of mutual data types to collect
+ * self_data_types - bitfield of self data types to collect
+ */
+struct TouchOffloadConfig {
+ /* Feature flags */
+ __u8 continuous_reporting;
+ __u8 noise_reporting;
+ __u8 cancel_reporting;
+ __u8 filter_grip;
+ __u8 filter_palm;
+ __u8 sensitivity_setting;
+ __u8 reserved1[16];
+
+ /* Data to read */
+ __u8 read_coords;
+ __u16 mutual_data_types;
+ __u16 self_data_types;
+ __u8 reserved2[16];
+};
+
+/* TouchOffloadFrameHeader
+ *
+ * frame_size - number of bytes in the frame
+ * index - unique, sequential frame index
+ * timestamp - frame timestamp in nanoseconds
+ * num_channels - number of channels included in the frame
+ */
+struct TouchOffloadFrameHeader {
+ __u32 frame_size;
+ __u64 index;
+ __u64 timestamp;
+ __u8 num_channels;
+} __attribute__((packed));
+
+/* TouchOffloadChannelHeader
+ *
+ * channel_type - touch type stored in the channel
+ * channel_size - size in bytes of the channel sample
+ */
+struct TouchOffloadChannelHeader {
+ __u8 channel_type;
+ __u32 channel_size;
+} __attribute__((packed));
+
+/* CoordStatus
+ *
+ * COORD_STATUS_INACTIVE - slot is unused
+ * COORD_STATUS_FINGER - normal finger touch
+ * COORD_STATUS_EDGE - edge touch
+ * COORD_STATUS_PALM - palm touch
+ * COORD_STATUS_CANCEL - canceled touch
+ */
+enum CoordStatus {
+ COORD_STATUS_INACTIVE = 0x00,
+ COORD_STATUS_FINGER = 0x01,
+ COORD_STATUS_EDGE = 0x02,
+ COORD_STATUS_PALM = 0x03,
+ COORD_STATUS_CANCEL = 0x04
+};
+
+/* Maximum number of touches that are tracked simultaneously */
+#define MAX_COORDS 10
+
+/* TouchOffloadCoord
+ *
+ * x - x component of touch location
+ * y - y component of touch location
+ * status - type of touch
+ * major - size of the larger axis of the touch blob
+ * minor - size of the smaller axis of the touch blob
+ * pressure - z-axis or force exerted on touch touch point
+ */
+struct TouchOffloadCoord {
+ __u16 x;
+ __u16 y;
+ enum CoordStatus status;
+ __u32 major;
+ __u32 minor;
+ __u32 pressure;
+ __u8 reserved1[16];
+} __attribute__((packed));
+
+/* TouchOffloadDataCoord
+ *
+ * header - header shared by all channels in a frame
+ * coords - array of MAX_COORD coordinates
+ */
+struct TouchOffloadDataCoord {
+ struct TouchOffloadChannelHeader header;
+ struct TouchOffloadCoord coords[MAX_COORDS];
+ __u8 reserved1[16];
+} __attribute__((packed));
+#define TOUCH_OFFLOAD_FRAME_SIZE_COORD (sizeof(struct TouchOffloadDataCoord))
+
+/* TouchOffloadData2d
+ *
+ * header - header shared by all channels in a frame
+ * tx_size - number of tx channels
+ * rx_size - number of rx channels
+ * data - pointer to raw touch data buffer
+ */
+struct TouchOffloadData2d {
+ struct TouchOffloadChannelHeader header;
+ __u16 tx_size;
+ __u16 rx_size;
+ __u8 reserved1[16];
+ __u8 data[1];
+} __attribute__((packed));
+#define TOUCH_OFFLOAD_DATA_SIZE_2D(rx, tx) (sizeof(__u16)*(rx)*(tx))
+#define TOUCH_OFFLOAD_FRAME_SIZE_2D(rx, tx) \
+ (sizeof(struct TouchOffloadData2d) - 1 + \
+ TOUCH_OFFLOAD_DATA_SIZE_2D((rx), (tx)))
+
+/* TouchOffloadData1d
+ *
+ * header - header shared by all channels in a frame
+ * tx_size - number of tx channels
+ * rx_size - number of rx channels
+ * data - pointer to raw touch data buffer
+ */
+struct TouchOffloadData1d {
+ struct TouchOffloadChannelHeader header;
+ __u16 tx_size;
+ __u16 rx_size;
+ __u8 reserved1[16];
+ __u8 data[1];
+} __attribute__((packed));
+#define TOUCH_OFFLOAD_DATA_SIZE_1D(rx, tx) (sizeof(__u16)*((rx)+(tx)))
+#define TOUCH_OFFLOAD_FRAME_SIZE_1D(rx, tx) \
+ (sizeof(struct TouchOffloadData1d) - 1 + \
+ TOUCH_OFFLOAD_DATA_SIZE_1D((rx), (tx)))
+
+////////////////////////////////////////////////////////////
+
+/* TouchOffloadIocGetCaps
+ *
+ * caps - capabilities provided by the touch driver
+ */
+struct TouchOffloadIocGetCaps {
+ struct TouchOffloadCaps caps;
+ __u8 reserved1[16];
+};
+
+/* TouchOffloadIocConfigure
+ *
+ * config - features to be used by the touch_offload client
+ */
+struct TouchOffloadIocConfigure {
+ struct TouchOffloadConfig config;
+ __u8 reserved1[16];
+};
+
+/* TouchOffloadIocReport
+ *
+ * index - unique, sequential frame index
+ * timestamp - frame timestamp in nanoseconds
+ * num_coords - number of coordinates contained in "coords"
+ * coords - array of coordinates to be reported to the driver
+ */
+struct TouchOffloadIocReport {
+ __u64 index;
+ __u64 timestamp;
+ __u8 num_coords;
+ __u8 reserved1[16];
+ struct TouchOffloadCoord coords[MAX_COORDS];
+};
+
+/* Ioctl to retrieve the capabilities of the touch driver */
+#define TOUCH_OFFLOAD_IOC_RD_GETCAPS \
+ _IOR(TOUCH_OFFLOAD_MAGIC, 0, struct TouchOffloadIocGetCaps)
+
+/* Ioctl to set the configuration of the touch driver */
+#define TOUCH_OFFLOAD_IOC_WR_CONFIGURE \
+ _IOW(TOUCH_OFFLOAD_MAGIC, 1, struct TouchOffloadIocConfigure)
+
+/* Ioctl to start the touch_offload pipeline */
+#define TOUCH_OFFLOAD_IOC_START _IOC(TOUCH_OFFLOAD_MAGIC, 2)
+
+/* Ioctl to report coordinates to the driver */
+#define TOUCH_OFFLOAD_IOC_WR_REPORT \
+ _IOW(TOUCH_OFFLOAD_MAGIC, 3, struct TouchOffloadIocReport)
+
+/* Ioctl to stop the touch_offload pipeline */
+#define TOUCH_OFFLOAD_IOC_STOP _IOC(TOUCH_OFFLOAD_MAGIC, 4)
+
+#endif /* _UAPI_TOUCH_OFFLOAD_H */
diff --git a/touch_offload.c b/touch_offload.c
new file mode 100644
index 0000000..2a214e3
--- /dev/null
+++ b/touch_offload.c
@@ -0,0 +1,677 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+
+#include "touch_offload.h"
+
+static int touch_offload_open(struct inode *inode, struct file *file)
+{
+ struct touch_offload_context *context =
+ container_of(inode->i_cdev, struct touch_offload_context, dev);
+
+ file->private_data = context;
+ pr_debug("%s\n", __func__);
+
+ mutex_lock(&context->file_lock);
+ if (context->file_in_use) {
+ mutex_unlock(&context->file_lock);
+ return -EBUSY;
+ }
+ context->file_in_use = true;
+ mutex_unlock(&context->file_lock);
+
+ /* Prepare context for consumer. E.g., event queue */
+ return 0;
+}
+
+static int touch_offload_release(struct inode *inode, struct file *file)
+{
+ struct touch_offload_context *context = file->private_data;
+
+ mutex_lock(&context->file_lock);
+ context->file_in_use = false;
+ mutex_unlock(&context->file_lock);
+
+ pr_debug("%s\n", __func__);
+ return 0;
+}
+
+static int pack_frame(struct touch_offload_context *context,
+ struct touch_offload_frame *frame, char **packed)
+{
+ /* TODO: compute precise size of a packed frame */
+ int max_packed_frame_size =
+ sizeof(struct touch_offload_frame) +
+ TOUCH_OFFLOAD_DATA_SIZE_2D(context->caps.rx_size,
+ context->caps.tx_size) *
+ MAX_CHANNELS;
+ /* TODO: preallocate memory for a single packed frame */
+ char *packed_mem = NULL;
+ char *ptr = NULL;
+ int channel_size;
+ int i = 0;
+
+ if (!frame || frame->num_channels > MAX_CHANNELS)
+ return -EINVAL;
+
+ packed_mem = kzalloc(max_packed_frame_size, GFP_KERNEL);
+ if (packed_mem == NULL)
+ return -ENOMEM;
+ ptr = packed_mem;
+
+ /* Copy the header */
+
+ memcpy(ptr, &frame->header, sizeof(frame->header));
+ ptr += sizeof(frame->header);
+
+ /* Copy frame data */
+
+ for (i = 0; i < frame->num_channels; i++) {
+ if (frame->channel_type[i] == TOUCH_DATA_TYPE_COORD)
+ channel_size = TOUCH_OFFLOAD_FRAME_SIZE_COORD;
+ else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_MUTUAL) != 0)
+ channel_size =
+ TOUCH_OFFLOAD_FRAME_SIZE_2D(context->caps.rx_size,
+ context->caps.tx_size);
+ else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_SELF) != 0)
+ channel_size =
+ TOUCH_OFFLOAD_FRAME_SIZE_1D(context->caps.rx_size,
+ context->caps.tx_size);
+ else {
+ pr_err("%s: Invalid channel_type = 0x%08X", __func__,
+ frame->channel_type[i]);
+ kfree(packed_mem);
+ return -EINVAL;
+ }
+ memcpy(ptr, frame->channel_data[i], channel_size);
+ ptr += channel_size;
+ }
+
+ *packed = packed_mem;
+ return (ptr - packed_mem);
+}
+
+static ssize_t touch_offload_read(struct file *file, char __user *user_buffer,
+ size_t size, loff_t *offset)
+{
+ struct touch_offload_context *context = file->private_data;
+ struct touch_offload_frame *frame;
+ size_t copy_size;
+ int result;
+ unsigned long remaining;
+
+ pr_debug("%s\n", __func__);
+
+ if (context->num_buffers == 0)
+ return -EINVAL;
+
+ /* Block until touch events occur */
+ /* Block (on completion?) until len >= 0. */
+ /* Lock the event buffer */
+ /* Copy contents of event buffer to service */
+ /* Reset completion since len=0 */
+ /* Unlock and return */
+
+ /* If the end of the data is reached, free the packed_frame and
+ * return 0
+ */
+
+ mutex_lock(&context->buffer_lock);
+
+ if (context->packed_frame != NULL &&
+ *offset == context->packed_frame_size) {
+ pr_err("%s: [Unexpected!] The buffer should have been recycled after the previous read.\n",
+ __func__);
+ kfree(context->packed_frame);
+ context->packed_frame = NULL;
+ *offset = 0;
+ mutex_unlock(&context->buffer_lock);
+ return 0;
+ } else if (context->packed_frame == NULL) {
+ /* Process the next queued buffer */
+
+ while (list_empty(&context->frame_queue)) {
+ /* Presumably more buffers on the way, so block */
+ mutex_unlock(&context->buffer_lock);
+
+ /* Non-blocking read? */
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ /* Block until data is available */
+ if (wait_event_interruptible(context->read_queue,
+ !list_empty(&context->frame_queue)))
+ return -ERESTARTSYS;
+
+ /* Check that the pipeline is still running */
+ mutex_lock(&context->buffer_lock);
+ }
+
+ frame = list_entry(context->frame_queue.next,
+ struct touch_offload_frame, entry);
+ list_del(&frame->entry);
+
+ result = pack_frame(context, frame, &context->packed_frame);
+ list_add_tail(&frame->entry, &context->free_pool);
+ if (result <= 0) {
+ pr_err("%s: Error packing frame! Result = %d.\n",
+ __func__, result);
+ mutex_unlock(&context->buffer_lock);
+ return -EINVAL;
+ }
+ if (result != context->packed_frame_size) {
+ pr_err("%s: Packed frame size (%d) does not match size allocated per frame(%d)!\n",
+ __func__, result, context->packed_frame_size);
+ }
+
+ }
+
+ /* Transfer the maximum amount of data */
+ copy_size = min((long long)size, context->packed_frame_size - *offset);
+ remaining = copy_to_user(user_buffer, context->packed_frame + *offset,
+ copy_size);
+ if (remaining != 0)
+ pr_err("%s: copy_to_user unexpectedly failed to copy %lu bytes.\n",
+ __func__, remaining);
+ *offset += copy_size - remaining;
+
+ /* Recycle the frame if transfer was complete */
+ if (*offset == context->packed_frame_size) {
+ kfree(context->packed_frame);
+ context->packed_frame = NULL;
+ *offset = 0;
+ }
+
+ mutex_unlock(&context->buffer_lock);
+
+ return copy_size - remaining;
+}
+
+static unsigned int touch_offload_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct touch_offload_context *context = file->private_data;
+ unsigned int flags = 0;
+
+ pr_debug("%s\n", __func__);
+
+ poll_wait(file, &context->read_queue, wait);
+
+ mutex_lock(&context->buffer_lock);
+
+ if (context->packed_frame || !list_empty(&context->frame_queue))
+ flags |= POLLIN | POLLRDNORM;
+
+ mutex_unlock(&context->buffer_lock);
+ return flags;
+}
+
+static int touch_offload_allocate_buffers(struct touch_offload_context *context,
+ int nb)
+{
+ struct touch_offload_frame *frame = NULL;
+ int i;
+ int num_channels;
+ __u32 mask;
+ __u32 size = 0;
+
+ pr_debug("%s\n", __func__);
+
+ num_channels = (context->config.read_coords ? 1 : 0) +
+ hweight_long(context->config.mutual_data_types) +
+ hweight_long(context->config.self_data_types);
+ if (num_channels == 0 || num_channels > MAX_CHANNELS) {
+ pr_err("%s: Configuration enables more (%d) than %d channels!\n",
+ __func__, num_channels, MAX_CHANNELS);
+ return -EINVAL;
+ }
+
+
+ mutex_lock(&context->buffer_lock);
+
+ /* Add new buffers to the free_pool */
+ for (i = 0; i < nb; i++) {
+ int chan = 0;
+ struct touch_offload_frame *frame =
+ kzalloc(sizeof(struct touch_offload_frame), GFP_KERNEL);
+
+ if (frame == NULL) {
+ mutex_unlock(&context->buffer_lock);
+ return -ENOMEM;
+ }
+
+ frame->header.frame_size = sizeof(frame->header);
+
+ /* Allocate component buffers */
+
+ if (context->config.read_coords) {
+ struct TouchOffloadDataCoord *coord;
+
+ frame->channel_type[chan] = TOUCH_DATA_TYPE_COORD;
+ size = TOUCH_OFFLOAD_FRAME_SIZE_COORD;
+ frame->channel_data[chan] = kzalloc(size,
+ GFP_KERNEL);
+ if (frame->channel_data[chan] == NULL)
+ goto kzalloc_channel_fail;
+ coord = (struct TouchOffloadDataCoord *)
+ frame->channel_data[chan];
+ coord->header.channel_type = TOUCH_DATA_TYPE_COORD;
+ coord->header.channel_size = size;
+ frame->channel_data_size[chan] = size;
+ frame->header.frame_size += size;
+ chan++;
+ }
+ for (mask = 0x01; mask < 0x100; mask <<= 1) {
+ if ((context->config.mutual_data_types & mask) != 0) {
+ struct TouchOffloadData2d *data;
+
+ frame->channel_type[chan] =
+ TOUCH_SCAN_TYPE_MUTUAL | mask;
+ size = TOUCH_OFFLOAD_FRAME_SIZE_2D(
+ context->caps.rx_size,
+ context->caps.tx_size);
+ frame->channel_data[chan] = kzalloc(size,
+ GFP_KERNEL);
+ if (frame->channel_data[chan] == NULL)
+ goto kzalloc_channel_fail;
+ data = (struct TouchOffloadData2d *)
+ frame->channel_data[chan];
+ data->header.channel_type =
+ TOUCH_SCAN_TYPE_MUTUAL | mask;
+ data->header.channel_size = size;
+ frame->channel_data_size[chan] = size;
+ frame->header.frame_size += size;
+ chan++;
+ }
+ }
+ for (mask = 0x01; mask < 0x100; mask <<= 1) {
+ if ((context->config.self_data_types & mask) != 0) {
+ struct TouchOffloadData1d *data;
+
+ frame->channel_type[chan] =
+ TOUCH_SCAN_TYPE_SELF | mask;
+ size = TOUCH_OFFLOAD_FRAME_SIZE_1D(
+ context->caps.rx_size,
+ context->caps.tx_size);
+ frame->channel_data[chan] = kzalloc(size,
+ GFP_KERNEL);
+ if (frame->channel_data[chan] == NULL)
+ goto kzalloc_channel_fail;
+ data = (struct TouchOffloadData1d *)
+ frame->channel_data[chan];
+ data->header.channel_type =
+ TOUCH_SCAN_TYPE_SELF | mask;
+ data->header.channel_size = size;
+ frame->channel_data_size[chan] = size;
+ frame->header.frame_size += size;
+ chan++;
+ }
+ }
+
+ frame->num_channels = chan;
+ frame->header.num_channels = chan;
+
+ if (context->packed_frame_size == 0)
+ context->packed_frame_size = frame->header.frame_size;
+ if (context->packed_frame_size != frame->header.frame_size)
+ pr_err("%s: Frame size mismatch! %d != %d.\n", __func__,
+ context->packed_frame_size,
+ frame->header.frame_size);
+
+ list_add_tail(&frame->entry, &context->free_pool);
+ context->num_buffers++;
+ }
+
+ mutex_unlock(&context->buffer_lock);
+ return 0;
+
+kzalloc_channel_fail:
+ /* Free all channels of "frame" before returning */
+ if (frame)
+ for (i = 0; i < MAX_CHANNELS; i++)
+ kfree(frame->channel_data[i]);
+ kfree(frame);
+
+ mutex_unlock(&context->buffer_lock);
+ return -ENOMEM;
+}
+
+static int touch_offload_free_buffers(struct touch_offload_context *context)
+{
+ int freed = 0;
+ int chan = 0;
+
+ pr_debug("%s\n", __func__);
+
+ mutex_lock(&context->buffer_lock);
+
+ /* Ensure there is no outstanding reserved_frame before continuing */
+ while (context->reserved_frame != NULL) {
+ mutex_unlock(&context->buffer_lock);
+ wait_for_completion(&context->reserve_returned);
+ mutex_lock(&context->buffer_lock);
+ }
+
+ if (context->num_buffers > 0) {
+ while (!list_empty(&context->free_pool)) {
+ struct list_head *next = context->free_pool.next;
+ struct touch_offload_frame *frame =
+ list_entry(next, struct touch_offload_frame, entry);
+
+ list_del(next);
+ for (chan = 0; chan < frame->num_channels; chan++)
+ kfree(frame->channel_data[chan]);
+ kfree(frame);
+ freed++;
+ }
+
+ while (!list_empty(&context->frame_queue)) {
+ struct list_head *next = context->frame_queue.next;
+ struct touch_offload_frame *frame =
+ list_entry(next, struct touch_offload_frame, entry);
+
+ list_del(next);
+ for (chan = 0; chan < frame->num_channels; chan++)
+ kfree(frame->channel_data[chan]);
+ kfree(frame);
+ freed++;
+ }
+ }
+ if (freed != context->num_buffers)
+ pr_err("%s: mismatch between the number of buffers allocated(%d) and freed(%d)!",
+ __func__, context->num_buffers, freed);
+
+ /* clean up */
+ context->num_buffers = 0;
+ context->reserved_frame = NULL;
+ INIT_LIST_HEAD(&context->free_pool);
+ INIT_LIST_HEAD(&context->frame_queue);
+ context->packed_frame_size = 0;
+
+ mutex_unlock(&context->buffer_lock);
+
+ return 0;
+}
+
+static long touch_offload_ioctl(struct file *file, unsigned int ioctl_num,
+ unsigned long ioctl_param)
+{
+ struct touch_offload_context *context = file->private_data;
+ unsigned long err = 0;
+
+ pr_debug("%s: ioctl_num=0x%08X, ioctl_param=0x%08lX\n", __func__,
+ ioctl_num, ioctl_param);
+
+ switch (ioctl_num) {
+ case TOUCH_OFFLOAD_IOC_RD_GETCAPS:
+ {
+ struct TouchOffloadIocGetCaps getCaps;
+
+ /* Copy previously-populated caps */
+ memcpy(&getCaps.caps, &context->caps,
+ sizeof(context->caps));
+
+ err = copy_to_user((void *)ioctl_param, &getCaps,
+ sizeof(getCaps));
+ if (err != 0) {
+ pr_err("%s: copy_to_failed with err=0x%08lX",
+ __func__, err);
+ return err;
+ }
+ break;
+ }
+
+ case TOUCH_OFFLOAD_IOC_WR_CONFIGURE:
+ {
+ struct TouchOffloadIocConfigure configure;
+ int NUM_BUFFERS = 4;
+ int num_channels;
+
+ err = copy_from_user(&configure, (void *)ioctl_param,
+ sizeof(configure));
+ if (err != 0) {
+ pr_err("%s: copy_from_user failed with err=0x%08lX",
+ __func__, err);
+ return err;
+ }
+
+ /* TODO: stop any active streaming */
+
+ /* Purge any previously-allocated buffers */
+ touch_offload_free_buffers(context);
+
+ memset(&context->config, 0, sizeof(context->config));
+ if ((configure.config.continuous_reporting &&
+ !context->caps.continuous_reporting) ||
+ (configure.config.noise_reporting &&
+ !context->caps.noise_reporting) ||
+ (configure.config.cancel_reporting &&
+ !context->caps.cancel_reporting) ||
+ (configure.config.filter_grip &&
+ !context->caps.filter_grip) ||
+ (configure.config.filter_palm &&
+ !context->caps.filter_palm)) {
+ pr_err("%s: Invalid configuration enables unsupported features!\n",
+ __func__);
+ err = -EINVAL;
+ return err;
+ } else if (configure.config.sensitivity_setting >=
+ context->caps.num_sensitivity_settings) {
+ pr_err("%s: Invalid configuration enables unsupported sensitivity setting!\n",
+ __func__);
+ err = -EINVAL;
+ return err;
+ } else if ((configure.config.mutual_data_types &
+ ~context->caps.touch_data_types) != 0 ||
+ (configure.config.self_data_types &
+ ~context->caps.touch_data_types) != 0) {
+ pr_err("%s: Invalid configuration enables unsupported data types!\n",
+ __func__);
+ err = -EINVAL;
+ return err;
+ }
+
+ num_channels = (configure.config.read_coords ? 1 : 0) +
+ hweight_long(
+ configure.config.mutual_data_types) +
+ hweight_long(
+ configure.config.self_data_types);
+ if (num_channels <= 0 || num_channels > MAX_CHANNELS) {
+ pr_err("%s: Invalid configuration enables more (%d) than %d channels!\n",
+ __func__, num_channels, MAX_CHANNELS);
+ err = -EINVAL;
+ return err;
+ }
+
+ /* Copy sanitized config */
+ memcpy(&context->config, &configure.config,
+ sizeof(context->config));
+
+ /* Allocate frames */
+ err = touch_offload_allocate_buffers(context, NUM_BUFFERS);
+ if (err != 0) {
+ pr_err("%s: failed to allocate buffers. err = 0x%08X.\n",
+ __func__, (unsigned int)err);
+ return err;
+ }
+
+ break;
+ }
+
+ case TOUCH_OFFLOAD_IOC_WR_REPORT:
+ {
+ struct TouchOffloadIocReport report;
+
+ err = copy_from_user(&report, (void *)ioctl_param,
+ sizeof(report));
+ if (err != 0) {
+ pr_err("%s: copy_from_user failed with err=0x%08lx.\n",
+ __func__, err);
+ return err;
+ }
+
+ context->report_cb(context->hcallback, &report);
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+ return err;
+}
+
+const struct file_operations touch_offload_fops = {
+ .owner = THIS_MODULE,
+ .open = touch_offload_open,
+ .release = touch_offload_release,
+ .read = touch_offload_read,
+ .poll = touch_offload_poll,
+ .unlocked_ioctl = touch_offload_ioctl
+};
+
+int touch_offload_reserve_frame(struct touch_offload_context *context,
+ struct touch_offload_frame **frame)
+{
+ int ret = 0;
+
+ pr_debug("%s\n", __func__);
+
+ if (!context || !frame)
+ return -EINVAL;
+ *frame = NULL;
+
+ mutex_lock(&context->buffer_lock);
+
+ if (context->num_buffers == 0 || list_empty(&context->free_pool) ||
+ context->reserved_frame != NULL) {
+ pr_debug("%s: buffer not available.\n", __func__);
+ ret = -EINVAL;
+ } else {
+ reinit_completion(&context->reserve_returned);
+ context->reserved_frame =
+ list_entry(context->free_pool.next,
+ struct touch_offload_frame, entry);
+ list_del(&context->reserved_frame->entry);
+ *frame = context->reserved_frame;
+ }
+
+ mutex_unlock(&context->buffer_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(touch_offload_reserve_frame);
+
+int touch_offload_queue_frame(struct touch_offload_context *context,
+ struct touch_offload_frame *frame)
+{
+ int ret = 0;
+
+ pr_debug("%s\n", __func__);
+
+ if (!context || !frame)
+ return -EINVAL;
+
+ mutex_lock(&context->buffer_lock);
+
+ if (context->num_buffers == 0 || frame != context->reserved_frame) {
+ pr_err("%s: incorrect or NULL buffer submitted.\n", __func__);
+ ret = -EINVAL;
+ } else {
+ /* TODO: Restore important constant fields */
+
+ list_add_tail(&frame->entry, &context->frame_queue);
+ context->reserved_frame = NULL;
+ complete_all(&context->reserve_returned);
+
+ wake_up(&context->read_queue);
+ }
+
+ mutex_unlock(&context->buffer_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(touch_offload_queue_frame);
+
+int touch_offload_init(struct touch_offload_context *context)
+{
+ int ret = 0;
+
+ pr_debug("%s\n", __func__);
+
+ /* Initialize ioctl interface */
+ context->file_in_use = false;
+ mutex_init(&context->file_lock);
+
+ /* Initialize the buffer pool */
+ context->num_buffers = 0;
+ context->reserved_frame = NULL;
+ INIT_LIST_HEAD(&context->free_pool);
+ INIT_LIST_HEAD(&context->frame_queue);
+ mutex_init(&context->buffer_lock);
+
+ init_waitqueue_head(&context->read_queue);
+
+ init_completion(&context->reserve_returned);
+ complete_all(&context->reserve_returned);
+
+ /* Initialize char device */
+ context->major_num = register_chrdev(0, DEVICE_NAME,
+ &touch_offload_fops);
+ if (context->major_num < 0) {
+ pr_err("%s: register_chrdev failed with error = %u\n",
+ __func__, context->major_num);
+ return context->major_num;
+ }
+
+ context->cls = class_create(THIS_MODULE, CLASS_NAME);
+ if (IS_ERR(context->cls)) {
+ pr_err("%s: class_create failed with error = %ld.\n",
+ __func__, PTR_ERR(context->cls));
+ unregister_chrdev(context->major_num, DEVICE_NAME);
+ return PTR_ERR(context->cls);
+ }
+
+ context->device = device_create(context->cls, NULL,
+ MKDEV(context->major_num, 0), NULL,
+ DEVICE_NAME);
+ if (IS_ERR(context->device)) {
+ pr_err("%s: device_create failed with error = %ld.\n",
+ __func__, PTR_ERR(context->device));
+ class_destroy(context->cls);
+ unregister_chrdev(context->major_num, DEVICE_NAME);
+ return PTR_ERR(context->device);
+ }
+
+ cdev_init(&context->dev, &touch_offload_fops);
+ cdev_add(&context->dev, MKDEV(context->major_num, 0), 1);
+
+ return ret;
+}
+EXPORT_SYMBOL(touch_offload_init);
+
+int touch_offload_cleanup(struct touch_offload_context *context)
+{
+ pr_debug("%s\n", __func__);
+
+ cdev_del(&context->dev);
+
+ device_destroy(context->cls, MKDEV(context->major_num, 0));
+
+ class_destroy(context->cls);
+
+ unregister_chrdev(context->major_num, DEVICE_NAME);
+
+ touch_offload_free_buffers(context);
+
+ return 0;
+}
+EXPORT_SYMBOL(touch_offload_cleanup);
+
+MODULE_DESCRIPTION("Touch Offload to AP");
+MODULE_AUTHOR("Steve Pfetsch <spfetsch@google.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/touch_offload.h b/touch_offload.h
new file mode 100644
index 0000000..a177317
--- /dev/null
+++ b/touch_offload.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef TOUCH_OFFLOAD_H
+#define TOUCH_OFFLOAD_H
+
+#include <linux/cdev.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+
+#include "uapi/input/touch_offload.h"
+
+/* Maximum number of channels of touch data */
+#define MAX_CHANNELS 5
+
+#define DEVICE_NAME "touch_offload"
+#define CLASS_NAME "touch_offload"
+
+/* Frame of touch data
+ *
+ * entry - list entry in either the free pool or the event queue
+ * header - frame header, including timestamp and index
+ * num_channels - number of channels in this frame
+ * channel_type - data type of each channel
+ * channel_data_size - size of the data in each channel
+ * channel_data - raw channel data
+ */
+struct touch_offload_frame {
+ struct list_head entry;
+ struct TouchOffloadFrameHeader header;
+ int num_channels;
+ int channel_type[MAX_CHANNELS];
+ __u32 channel_data_size[MAX_CHANNELS];
+ __u8 *channel_data[MAX_CHANNELS];
+};
+
+/* Touch Offload Context
+ *
+ * dev - char device
+ * major_num - device major number
+ * cls - pointer to class associated class
+ * device - pointer to associated device
+ * file - char device file for ioctl interface
+ * file_lock - mutex for the ioctl interface
+ * file_in_use - flag indicating the ioctl interface in use by one client
+ * caps - capabilities supported by the touch driver
+ * config - configuration in use by the touch driver
+ * coords - snapshot of the current coordinate state
+ * num_buffers - size of the pool of frame buffers
+ * free_pool - list of buffers available for use
+ * frame_queue - list of captured frames queued for the service
+ * reserved_frame - buffer ready to be filled with the next touch frame
+ * read_queue - waitqueue for blocked readers
+ * packed_frame - serialized frame being read by the char device client
+ * packed_frame_size - size of the array pointed to by packed_frame
+ * buffer_lock - mutex protecting buffer management
+ * reserve_returned - indicates that the reserved buffer was released
+ * hcallback - handle/pointer to driver's private callback context
+ * report_cb - driver callback used to report touch events
+ * offload_running - indicates whether the offload path is in use
+ */
+struct touch_offload_context {
+ /* ioctl interface */
+ struct cdev dev;
+ int major_num;
+ struct class *cls;
+ struct device *device;
+ struct file file;
+ struct mutex file_lock;
+ bool file_in_use;
+
+ /* touch capabilities */
+ struct TouchOffloadCaps caps;
+
+ /* touch capture configuration */
+ struct TouchOffloadConfig config;
+
+ /* coordinate state */
+ struct TouchOffloadCoord coords[MAX_COORDS];
+
+ /* buffer management */
+ int num_buffers;
+ struct list_head free_pool;
+ struct list_head frame_queue;
+ struct touch_offload_frame *reserved_frame;
+ wait_queue_head_t read_queue;
+ char *packed_frame;
+ __u32 packed_frame_size;
+ struct mutex buffer_lock;
+ struct completion reserve_returned;
+
+ /* callbacks */
+ void *hcallback;
+ void (*report_cb)(void *hcallback,
+ struct TouchOffloadIocReport *report);
+
+ int offload_running;
+};
+
+/* Initialize the touch_offload driver */
+int touch_offload_init(struct touch_offload_context *context);
+
+/* Clean-up the touch_offload driver */
+int touch_offload_cleanup(struct touch_offload_context *context);
+
+/* Allocate a new empty frame from the free pool */
+int touch_offload_reserve_frame(struct touch_offload_context *context,
+ struct touch_offload_frame **frame);
+
+/* Insert a populated frame into the queue */
+int touch_offload_queue_frame(struct touch_offload_context *context,
+ struct touch_offload_frame *frame);
+
+#endif // TOUCH_OFFLOAD_H