/**************************************************************************** * * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved * ****************************************************************************/ #include #include #include #include #include /* Required for copy_to_user. */ #include #include #include #include "mxman.h" #include "mxmgmt_transport_format.h" /* Required for MXMGR_MESSAGE_PAYLOAD_SIZE. */ #define DEVICE_NAME "lerna" #define DEVICE_CLASS "scsc_config" #define DEVICE_COUNT (1) static const void *scsc_lerna_pending; #define SCSC_LERNA_WAIT_TIMEOUT (2000) static DECLARE_COMPLETION(scsc_lerna_wait); /** * MSMGR_MESSAGE_PAYLOAD_SIZE is not a nice power of 2, so use sizeof(msmgr_message) * just for something more aesthetically pleasing. */ #define SCSC_LERNA_BUFFER_SIZE (sizeof(struct mxmgr_message)) static uint8_t scsc_lerna_request_buffer[SCSC_LERNA_BUFFER_SIZE]; static uint8_t scsc_lerna_response_buffer[SCSC_LERNA_BUFFER_SIZE]; static dev_t scsc_lerna_device_id; static struct class *scsc_lerna_class_p; static struct device *scsc_lerna_device_p; static struct cdev scsc_lerna_cdev; static int scsc_lerna_chardev_open(struct inode *inodep, struct file *filep); static ssize_t scsc_lerna_chardev_read(struct file *filep, char *buffer, size_t len, loff_t *offset); static ssize_t scsc_lerna_chardev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset); static int scsc_lerna_chardev_release(struct inode *inodep, struct file *filep); static struct file_operations scsc_lerna_fops = { .open = scsc_lerna_chardev_open, .read = scsc_lerna_chardev_read, .write = scsc_lerna_chardev_write, .release = scsc_lerna_chardev_release, }; static atomic_t scsc_lerna_atomic; struct scsc_lerna_cmd_header { uint8_t magic_number; /* Set to 0x08. */ uint8_t cid; /* Action command identifier. */ uint16_t payload_length; /* Payload length. 0 for value query. */ uint16_t psid; /* PSID to query. */ uint8_t row_index; /* Row index, or 0 for non-table querying. */ uint8_t group_index; /* Group index, or 0 for default (group not assigned). */ }; static int scsc_lerna_chardev_open(struct inode *inodep, struct file *filep) { (void)inodep; (void)filep; if (atomic_inc_return(&scsc_lerna_atomic) > 1) { atomic_dec(&scsc_lerna_atomic); /* Someone already has this open. Denied. */ SCSC_TAG_DEBUG(LERNA, "character device busy, try again later.\n"); return -EBUSY; } SCSC_TAG_DEBUG(LERNA, "opening lerna character device.\n"); return 0; } static ssize_t scsc_lerna_chardev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) { const struct scsc_lerna_cmd_header *header; unsigned long wait_result; ssize_t read_count; int error_count; (void)filep; (void)offset; wait_result = wait_for_completion_timeout(&scsc_lerna_wait, msecs_to_jiffies(SCSC_LERNA_WAIT_TIMEOUT)); if (wait_result == 0) { SCSC_TAG_ERR(LERNA, "read timeout; firmware not responding, or read without write.\n"); return -ETIMEDOUT; } if (!scsc_lerna_pending) { /* Pointer is NULL, indicating that a reply hasn't been sent from firmware. */ SCSC_TAG_DEBUG(LERNA, "pending reply is null.\n"); return -ENOMSG; } header = (const struct scsc_lerna_cmd_header *)(scsc_lerna_pending); read_count = sizeof(struct scsc_lerna_cmd_header) + header->payload_length; /* Make sure there's enough space to read out the buffer. */ if (len < read_count) { SCSC_TAG_ERR(LERNA, "insufficient buffer space supplied for read.\n"); return -ENOBUFS; } error_count = copy_to_user(buffer, scsc_lerna_pending, read_count); if (error_count) { SCSC_TAG_ERR(LERNA, "could not read from lerna character device.\n"); return -EFAULT; } SCSC_TAG_DEBUG(LERNA, "read buffer of size: %lu\n", read_count); /* Value was read out, and is no longer considered valid. Need to write before another read. */ scsc_lerna_pending = NULL; return read_count; } static ssize_t scsc_lerna_chardev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { SCSC_TAG_DEBUG(LERNA, "writing buffer of size: %lu\n", len); /* At a minimum, any request (read or write) must include a command header. */ if (len >= sizeof(struct scsc_lerna_cmd_header)) { /* Header at least fits, but maybe a write value wants more... */ if (len <= SCSC_LERNA_BUFFER_SIZE) { if (copy_from_user(scsc_lerna_request_buffer, buffer, len)) { SCSC_TAG_ERR(LERNA, "copy_from_user failed.\n"); return -EFAULT; } mxman_lerna_send(NULL, scsc_lerna_request_buffer, len); } else { /* Message size too long, don't write anything. */ return -EMSGSIZE; } } else { return -EBADR; } return len; } static int scsc_lerna_chardev_release(struct inode *inodep, struct file *filep) { (void)inodep; (void)filep; if (atomic_read(&scsc_lerna_atomic) == 0) { SCSC_TAG_ALERT(LERNA, "character device release without open.\n"); } else { /* Done with the character device, release the lock on it. */ atomic_dec(&scsc_lerna_atomic); } SCSC_TAG_DEBUG(LERNA, "lerna character device closed.\n"); return 0; } int scsc_lerna_init(void) { int result; /** * Reset important globals to some kind of sane value. This should be done * whenever the module is loaded explicitly to be sure global values haven't * been previously trashed. */ scsc_lerna_device_id = 0; scsc_lerna_class_p = NULL; scsc_lerna_device_p = NULL; /* Make sure to initialise the atomic used to lock char device access. */ atomic_set(&scsc_lerna_atomic, 0); /** * Allocate device id(s) for the character device. Use alloc_register_chrdev * because this is the new way of doing things, and it will dynamically allocate * a major number. Returns non-zero on failure. */ result = alloc_chrdev_region(&scsc_lerna_device_id, 0, DEVICE_COUNT, DEVICE_NAME); if (result) { /* Failure to register char dev, auto fail to initialise module. */ SCSC_TAG_ALERT(LERNA, "lerna failed to register character device.\n"); return result; } scsc_lerna_class_p = class_create(THIS_MODULE, DEVICE_CLASS); if (IS_ERR(scsc_lerna_class_p)) { /* Could not create class, failure, remember to unregister device id(s). */ unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); SCSC_TAG_ALERT(LERNA, "lerna failed to create character class.\n"); return PTR_ERR(scsc_lerna_class_p); } scsc_lerna_device_p = device_create(scsc_lerna_class_p, NULL, scsc_lerna_device_id, NULL, DEVICE_NAME); if (IS_ERR(scsc_lerna_device_p)) { class_destroy(scsc_lerna_class_p); unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); SCSC_TAG_ALERT(LERNA, "lerna failed to create character device.\n"); return PTR_ERR(scsc_lerna_device_p); } /** * At this point, the device is registered, along with class definition. The character device * itself can now be initialised to provide the kernel with callback information for various * actions taken on the device. */ cdev_init(&scsc_lerna_cdev, &scsc_lerna_fops); scsc_lerna_cdev.owner = THIS_MODULE; result = cdev_add(&scsc_lerna_cdev, scsc_lerna_device_id, DEVICE_COUNT); if (result) { /* Failure to add character device to file system. */ cdev_del(&scsc_lerna_cdev); class_destroy(scsc_lerna_class_p); unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); SCSC_TAG_ALERT(LERNA, "lerna failed to add character device.\n"); return result; } /* At this point, the cdev is live and can be used. */ SCSC_TAG_INFO(LERNA, "lerna intialisation complete.\n"); return 0; /* 0 for module loaded, non-zero for module load failure. */ } void scsc_lerna_deinit(void) { /* Character device needs deleting. */ cdev_del(&scsc_lerna_cdev); /* Destroy device. */ device_destroy(scsc_lerna_class_p, scsc_lerna_device_id); /* Unregister the device class. Not sure if this means that a register earlier is required. */ class_unregister(scsc_lerna_class_p); /* Destroy created class. Be careful of the order this is called in. */ class_destroy(scsc_lerna_class_p); /** * Don't forget to unregister device id(s). Major number is dynamically allocated, * so the base id is remembered and passed along to the unregister here. */ unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT); SCSC_TAG_INFO(LERNA, "lerna shutdown complete.\n"); } void scsc_lerna_response(const void *message) { /** * Buffer the response from the firmware so that future messages from firmware * don't overwrite this accidentally. This means async messages are allowed while * waiting for the character device read from userspace, without impacting lerna's * request/response communications. */ const struct scsc_lerna_cmd_header *header; ssize_t read_count; if (message != NULL) { header = (const struct scsc_lerna_cmd_header *)(message); read_count = sizeof(struct scsc_lerna_cmd_header) + header->payload_length; if (read_count <= SCSC_LERNA_BUFFER_SIZE) { memcpy(scsc_lerna_response_buffer, message, read_count); scsc_lerna_pending = scsc_lerna_response_buffer; } else { SCSC_TAG_DEBUG(LERNA, "readout too large for response buffering.\n"); /* No response possible, let the userspace application deal with it. */ scsc_lerna_pending = NULL; } } complete(&scsc_lerna_wait); }