// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #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 if (frame->channel_type[i] == CONTEXT_CHANNEL_TYPE_DRIVER_STATUS) channel_size = TOUCH_OFFLOAD_FRAME_SIZE_DRIVER_STATUS; else if (frame->channel_type[i] == CONTEXT_CHANNEL_TYPE_STYLUS_STATUS) channel_size = TOUCH_OFFLOAD_FRAME_SIZE_STYLUS_STATUS; 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) + hweight_long(context->config.context_channel_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++; } } for (mask = CONTEXT_CHANNEL_BIT_START; mask <= CONTEXT_CHANNEL_BIT_END; mask <<= 1) { struct TouchOffloadChannelHeader *chan_header; if (!(context->config.context_channel_types & mask)) continue; frame->channel_type[chan] = (__u32)mask; size = 0; switch (mask) { case CONTEXT_CHANNEL_TYPE_DRIVER_STATUS: size = TOUCH_OFFLOAD_FRAME_SIZE_DRIVER_STATUS; break; case CONTEXT_CHANNEL_TYPE_STYLUS_STATUS: size = TOUCH_OFFLOAD_FRAME_SIZE_STYLUS_STATUS; break; default: pr_err("%s: Invalid channel_type = 0x%08X\n", __func__, mask); goto invalid_context_channel; } frame->channel_data[chan] = kzalloc(size, GFP_KERNEL); if (frame->channel_data[chan] == NULL) goto kzalloc_channel_fail; chan_header = (struct TouchOffloadChannelHeader *) frame->channel_data[chan]; chan_header->channel_type = (__u32)mask; chan_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; invalid_context_channel: 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) || (configure.config.auto_reporting && !context->caps.auto_reporting) || (configure.config.coord_filter && !context->caps.coord_filter)) { 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; } else if ((configure.config.context_channel_types & ~context->caps.context_channel_types) != 0) { pr_err("%s: Invalid configuration enables unsupported context 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) + hweight_long( configure.config.context_channel_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; /* 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); if (!strnlen(context->device_name, sizeof(context->device_name))) scnprintf(context->device_name, sizeof(context->device_name), "%s", DEVICE_NAME); pr_info("%s: %s.\n", __func__, context->device_name); /* Initialize char device */ cdev_init(&context->dev, &touch_offload_fops); ret = alloc_chrdev_region(&context->dev_num, 0, 1, context->device_name); if (ret < 0) { pr_err("%s: register_chrdev failed with error = %u\n", __func__, ret); return ret; } ret = cdev_add(&context->dev, context->dev_num, 1); if (ret < 0) { pr_err("%s: cdev_add failed with error = %u\n", __func__, ret); goto err_cdev_add; } context->cls = class_create(context->device_name); if (IS_ERR(context->cls)) { pr_err("%s: class_create failed with error = %ld.\n", __func__, PTR_ERR(context->cls)); ret = PTR_ERR(context->cls); goto err_class_create; } context->device = device_create(context->cls, NULL, context->dev_num, NULL, context->device_name); if (IS_ERR(context->device)) { pr_err("%s: device_create failed with error = %ld.\n", __func__, PTR_ERR(context->device)); ret = PTR_ERR(context->device); goto err_device_create; } return ret; err_device_create: class_destroy(context->cls); err_class_create: cdev_del(&context->dev); err_cdev_add: unregister_chrdev_region(context->dev_num, 1); return ret; } EXPORT_SYMBOL(touch_offload_init); int touch_offload_cleanup(struct touch_offload_context *context) { pr_debug("%s\n", __func__); device_destroy(context->cls, context->dev_num); class_destroy(context->cls); cdev_del(&context->dev); unregister_chrdev_region(context->dev_num, 1); complete_all(&context->reserve_returned); touch_offload_free_buffers(context); return 0; } EXPORT_SYMBOL(touch_offload_cleanup); MODULE_DESCRIPTION("Touch Offload to AP"); MODULE_AUTHOR("Steve Pfetsch "); MODULE_LICENSE("GPL v2");