diff options
author | Tai Kuo <taikuo@google.com> | 2021-02-11 16:22:44 +0800 |
---|---|---|
committer | Tai Kuo <taikuo@google.com> | 2021-03-03 23:40:10 +0800 |
commit | f81f4a17bd2c9d9cbc115a7344124833b102b67e (patch) | |
tree | 706ab70bd43b1469ca590da8102af341c80cfd58 | |
parent | 95aab3ee1559f0b986394bf29a41f5a5594019bc (diff) | |
download | common-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-- | Kconfig | 10 | ||||
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | include/uapi/input/touch_offload.h | 280 | ||||
-rw-r--r-- | touch_offload.c | 677 | ||||
-rw-r--r-- | touch_offload.h | 113 |
5 files changed, 1096 insertions, 1 deletions
@@ -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. @@ -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 |